use std::sync::Arc;
use futures_util::StreamExt;
use lc_agents::{AgentExecutor, AgentStreamEvent, FunctionCallingAgent};
use lc_core::language_models::LLMResult;
use lc_core::tools::ToolCall;
use lc_testkit::{RecordedExchange, ReplayProvider};
use lc_tools::Calculator;
#[tokio::test]
async fn agent_offline_replays_tool_call_loop() {
let tool_call_round = || RecordedExchange {
messages: vec![],
response: LLMResult {
content: String::new(),
model: "replay".to_string(),
tool_calls: Some(vec![ToolCall::builder("call_1")
.name("calculator")
.arguments("{\"expression\":\"2+2\"}".to_string())
.build()]),
..Default::default()
},
tools: None,
};
let exchanges = vec![
tool_call_round(),
tool_call_round(),
RecordedExchange {
messages: vec![],
response: LLMResult {
content: "4".to_string(),
model: "replay".to_string(),
..Default::default()
},
tools: None,
},
];
let replay = ReplayProvider::from_exchanges(exchanges);
let agent = FunctionCallingAgent::new(replay, vec![Arc::new(Calculator::new())], None);
let executor = AgentExecutor::new(Arc::new(agent), vec![Arc::new(Calculator::new())]);
let mut stream = executor.stream("2+2=?".to_string());
let mut events = Vec::new();
while let Some(event) = stream.next().await {
events.push(event.expect("回放 agent 执行不应失败"));
}
assert!(events.len() >= 4, "事件数不足: {events:?}");
assert!(
matches!(&events[0], AgentStreamEvent::ToolStart { name, .. } if name == "calculator"),
"第一个事件应为 calculator 的 ToolStart: {events:?}"
);
assert!(
events
.iter()
.any(|e| matches!(e, AgentStreamEvent::ToolEnd { .. })),
"应有 ToolEnd 事件: {events:?}"
);
match events.last().expect("有事件") {
AgentStreamEvent::FinalAnswer { content } => {
assert_eq!(content.trim(), "4", "最终答案应来自第二轮回放");
}
other => panic!("期望 FinalAnswer 终态,got {other:?}"),
}
}