fix(agent): surface FC/tool errors as observations to continue ReAct loop

- Streaming path: on tool_call execution error, emit an [Observation]
  chunk so the model sees the failure and can retry/adapt
- Non-streaming path: inject error as a user message so the loop
  continues with error context, not silently stop
This commit is contained in:
ZhenYi 2026-04-24 13:17:13 +08:00
parent 94825316dc
commit d89d02e81b

View File

@ -5,7 +5,9 @@ use async_openai::Client;
use async_openai::types::chat::{ use async_openai::types::chat::{
ChatCompletionMessageToolCalls, ChatCompletionRequestAssistantMessage, ChatCompletionMessageToolCalls, ChatCompletionRequestAssistantMessage,
ChatCompletionRequestAssistantMessageContent, ChatCompletionRequestMessage, ChatCompletionRequestAssistantMessageContent, ChatCompletionRequestMessage,
ChatCompletionRequestSystemMessage, ChatCompletionRequestUserMessage, ChatCompletionTool, ChatCompletionRequestSystemMessage, ChatCompletionRequestToolMessage,
ChatCompletionRequestToolMessageContent, ChatCompletionRequestUserMessage,
ChatCompletionRequestUserMessageContent, ChatCompletionTool,
ChatCompletionTools, CreateChatCompletionRequest, CreateChatCompletionResponse, ChatCompletionTools, CreateChatCompletionRequest, CreateChatCompletionResponse,
CreateChatCompletionStreamResponse, FinishReason, ReasoningEffort, ToolChoiceOptions, CreateChatCompletionStreamResponse, FinishReason, ReasoningEffort, ToolChoiceOptions,
}; };
@ -193,7 +195,24 @@ impl ChatService {
.collect(); .collect();
if !calls.is_empty() { if !calls.is_empty() {
let tool_messages = self.execute_tool_calls(calls, &request).await?; let tool_messages = match self.execute_tool_calls(calls, &request).await {
Ok(msgs) => msgs,
Err(e) => {
// Surface the error as a tool result so the model can continue
let err_text = format!("[Tool call failed: {}]", e);
messages.push(ChatCompletionRequestMessage::User(
ChatCompletionRequestUserMessage {
content: ChatCompletionRequestUserMessageContent::Text(err_text.clone()),
name: None,
},
));
tool_depth += 1;
if tool_depth >= max_tool_depth {
return Ok(err_text);
}
continue;
}
};
messages.extend(tool_messages); messages.extend(tool_messages);
tool_depth += 1; tool_depth += 1;
@ -387,7 +406,25 @@ impl ChatService {
}, },
)); ));
let tool_messages = self.execute_tool_calls(tool_calls, &request).await?; let tool_messages = match self.execute_tool_calls(tool_calls, &request).await {
Ok(msgs) => msgs,
Err(e) => {
// Stream the FC error as an observation so the user sees it
let err_text = format!("[Tool call failed: {}]", e);
on_chunk(AiStreamChunk {
content: err_text.clone(),
done: true,
})
.await;
// Return an empty tool result so the loop can continue
vec![ChatCompletionRequestMessage::Tool(
ChatCompletionRequestToolMessage {
tool_call_id: String::new(),
content: ChatCompletionRequestToolMessageContent::Text(err_text),
},
)]
}
};
messages.extend(tool_messages); messages.extend(tool_messages);
tool_depth += 1; tool_depth += 1;