A live-model e2e: any OpenAI-compatible brain must drive a shell turn (gated by env)

This commit is contained in:
AnyBot Dev 2026-11-09 20:57:32 +08:00
parent ef44db4645
commit 6f8568d16b
1 changed files with 51 additions and 0 deletions

View File

@ -2421,6 +2421,57 @@ mod tests {
/// a box: `ANYBOT_DATA_DIR=<scratch> cargo test --release -- --ignored
/// a_brain_without_chrome_browses_on_its_box --nocapture`.
#[test]
#[ignore]
fn real_model_drives_a_shell_turn() {
// A real brain, end to end: point it at any OpenAI-compatible endpoint with
// ANYBOT_LIVE_BASE / ANYBOT_LIVE_KEY / ANYBOT_LIVE_MODEL and run
// `cargo test real_model_drives -- --ignored --nocapture`. It asks the model to write
// a file and read it back; the turn must survive real tool calls, not a script.
let Ok(base) = std::env::var("ANYBOT_LIVE_BASE") else { eprintln!("no ANYBOT_LIVE_BASE; skipped"); return };
let key = std::env::var("ANYBOT_LIVE_KEY").unwrap_or_default();
let model = std::env::var("ANYBOT_LIVE_MODEL").unwrap_or_else(|_| "husky".into());
let own = crate::model::BOT_COMPUTER_NAME.to_string();
let _ = std::fs::remove_dir_all("/tmp/anybot-live");
let job = Job {
req: ChatRequest {
kind: ProviderKind::Custom,
base_url: base,
api_key: key,
model,
system: "你是 AnyBot。用工具完成使用者的要求,最後用一句中文回答。".into(),
history: vec![Message::user("在 /tmp/anybot-live 建立 hello.txt,內容寫 anybot-was-here,然後用 shell 讀回來,告訴我市面檔裡寫了什麼。")],
tools: tools::specs(),
session: String::new(),
},
computers: vec![tools::Target { name: own.clone(), place: tools::Place::Local { workdir: "/tmp".into() } }],
default_computer: own.clone(),
roles: tools::Roles { own, person: None, server: true },
local_execution: false,
direct: Arc::new(AtomicBool::new(true)),
..test_job()
};
let (tx, rx) = channel();
spawn(job, 1, tx, Arc::new(AtomicBool::new(false)), eframe::egui::Context::default());
let mut tools_seen = vec![];
let mut answer = String::new();
loop {
match rx.recv_timeout(Duration::from_secs(180)).expect("loop stalled") {
(_, StreamEvent::ToolCall(m)) => tools_seen.push(m.name.clone()),
(_, StreamEvent::ToolResult(m)) => {
println!("[tool] {}: {}", m.tool_name, m.text.chars().take(120).collect::<String>());
tools_seen.push(format!("done:{}", m.tool_name));
}
(_, StreamEvent::Text(t)) => answer.push_str(&t),
(_, StreamEvent::Done) => break,
(_, StreamEvent::Error(e)) => panic!("model error: {e}"),
_ => {}
}
}
println!("tools: {tools_seen:?}\nanswer: {answer}");
assert!(tools_seen.iter().any(|t| t.contains("shell")), "the model never ran a shell tool: {tools_seen:?}");
assert!(answer.contains("anybot-was-here") || std::fs::read_to_string("/tmp/anybot-live/hello.txt").unwrap_or_default().contains("anybot-was-here"), "the file never appeared; answer was: {answer}");
}
#[ignore]
fn a_brain_without_chrome_browses_on_its_box() {
let own = crate::model::BOT_COMPUTER_NAME;