feat: 增强错误处理,添加format_error_chain函数以格式化错误链,优化日志记录
This commit is contained in:
parent
fc8a0aa6ae
commit
ed45ec54ed
@ -134,6 +134,18 @@ fn normalize_tool_arguments(arguments: &serde_json::Value) -> serde_json::Value
|
||||
}
|
||||
}
|
||||
|
||||
fn format_error_chain(error: &(dyn std::error::Error + 'static)) -> String {
|
||||
let mut details = vec![error.to_string()];
|
||||
let mut current = error.source();
|
||||
|
||||
while let Some(source) = current {
|
||||
details.push(source.to_string());
|
||||
current = source.source();
|
||||
}
|
||||
|
||||
details.join("\ncaused by: ")
|
||||
}
|
||||
|
||||
fn is_recoverable_llm_error(error: &str) -> bool {
|
||||
let normalized = error.to_ascii_lowercase();
|
||||
normalized.contains("504")
|
||||
@ -442,7 +454,13 @@ impl AgentLoop {
|
||||
let response = match (*self.provider).chat(request).await {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
tracing::error!(error = %e, "LLM request failed");
|
||||
tracing::error!(
|
||||
provider = %self.provider.name(),
|
||||
model = %self.provider.model_id(),
|
||||
error = %e,
|
||||
error_details = %format_error_chain(e.as_ref()),
|
||||
"LLM request failed"
|
||||
);
|
||||
let assistant_message = ChatMessage::assistant(recoverable_llm_message(&e.to_string()));
|
||||
emitted_messages.push(assistant_message.clone());
|
||||
return Ok(AgentProcessResult {
|
||||
@ -618,7 +636,13 @@ impl AgentLoop {
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(error = %e, "Failed to get summary from LLM");
|
||||
tracing::error!(
|
||||
provider = %self.provider.name(),
|
||||
model = %self.provider.model_id(),
|
||||
error = %e,
|
||||
error_details = %format_error_chain(e.as_ref()),
|
||||
"Failed to get summary from LLM"
|
||||
);
|
||||
let final_message = ChatMessage::assistant(recoverable_llm_message(&e.to_string()));
|
||||
emitted_messages.push(final_message.clone());
|
||||
Ok(AgentProcessResult {
|
||||
@ -785,7 +809,7 @@ impl AgentLoop {
|
||||
}
|
||||
};
|
||||
|
||||
match tool.execute_with_context(&self.tool_context, normalized_arguments).await {
|
||||
match tool.execute_with_context(&self.tool_context, normalized_arguments.clone()).await {
|
||||
Ok(result) => {
|
||||
if result.success {
|
||||
if let Some(pending_output) = parse_pending_tool_output(&result.output) {
|
||||
@ -795,6 +819,14 @@ impl AgentLoop {
|
||||
}
|
||||
} else {
|
||||
let error = result.error.unwrap_or_default();
|
||||
tracing::error!(
|
||||
tool = %tool_call.name,
|
||||
args = %truncate_args(&tool_call.arguments, 2_000),
|
||||
normalized_args = %truncate_args(&normalized_arguments, 2_000),
|
||||
error = %error,
|
||||
output = %result.output,
|
||||
"Tool returned an error result"
|
||||
);
|
||||
ToolExecutionOutcome::failure(
|
||||
format!("Error: {}", error),
|
||||
Some(error),
|
||||
@ -802,7 +834,14 @@ impl AgentLoop {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(tool = %tool_call.name, error = %e, "Tool execution failed");
|
||||
tracing::error!(
|
||||
tool = %tool_call.name,
|
||||
args = %truncate_args(&tool_call.arguments, 2_000),
|
||||
normalized_args = %truncate_args(&normalized_arguments, 2_000),
|
||||
error = %e,
|
||||
error_details = %format!("{:#}", e),
|
||||
"Tool execution failed"
|
||||
);
|
||||
ToolExecutionOutcome::failure(
|
||||
format!("Error: {}", e),
|
||||
Some(e.to_string()),
|
||||
|
||||
@ -8,6 +8,18 @@ use crate::bus::message::ContentBlock;
|
||||
use super::{ChatCompletionRequest, ChatCompletionResponse, LLMProvider, Tool, ToolCall};
|
||||
use super::traits::Usage;
|
||||
|
||||
fn format_error_chain(error: &(dyn std::error::Error + 'static)) -> String {
|
||||
let mut details = vec![error.to_string()];
|
||||
let mut current = error.source();
|
||||
|
||||
while let Some(source) = current {
|
||||
details.push(source.to_string());
|
||||
current = source.source();
|
||||
}
|
||||
|
||||
details.join("\ncaused by: ")
|
||||
}
|
||||
|
||||
fn serialize_content_blocks<S>(blocks: &[serde_json::Value], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
@ -204,6 +216,15 @@ impl LLMProvider for AnthropicProvider {
|
||||
let text = resp.text().await?;
|
||||
|
||||
if !status.is_success() {
|
||||
tracing::error!(
|
||||
provider = %self.name,
|
||||
model = %self.model_id,
|
||||
url = %url,
|
||||
status = %status,
|
||||
response_len = text.len(),
|
||||
response_body = %text,
|
||||
"Anthropic API request failed"
|
||||
);
|
||||
return Err(format!("API error {}: {}", status, text).into());
|
||||
}
|
||||
|
||||
@ -213,8 +234,18 @@ impl LLMProvider for AnthropicProvider {
|
||||
tracing::debug!(status = %status, response_preview = %resp_preview, response_len = %text.len(), timeout_secs = self.llm_timeout_secs, "Anthropic response (first 100 chars shown)");
|
||||
}
|
||||
|
||||
let anthropic_resp: AnthropicResponse = serde_json::from_str(&text)
|
||||
.map_err(|e| format!("decode error: {} | body: {}", e, &text))?;
|
||||
let anthropic_resp: AnthropicResponse = serde_json::from_str(&text).map_err(|e| {
|
||||
tracing::error!(
|
||||
provider = %self.name,
|
||||
model = %self.model_id,
|
||||
url = %url,
|
||||
error = %format_error_chain(&e),
|
||||
response_len = text.len(),
|
||||
response_body = %text,
|
||||
"Failed to decode Anthropic response"
|
||||
);
|
||||
format!("decode error: {} | body: {}", e, &text)
|
||||
})?;
|
||||
|
||||
let mut content = String::new();
|
||||
let mut tool_calls = Vec::new();
|
||||
|
||||
@ -14,6 +14,18 @@ const INTERNAL_MODEL_EXTRA_KEYS: &[&str] = &[
|
||||
"mock_response_content",
|
||||
];
|
||||
|
||||
fn format_error_chain(error: &(dyn std::error::Error + 'static)) -> String {
|
||||
let mut details = vec![error.to_string()];
|
||||
let mut current = error.source();
|
||||
|
||||
while let Some(source) = current {
|
||||
details.push(source.to_string());
|
||||
current = source.source();
|
||||
}
|
||||
|
||||
details.join("\ncaused by: ")
|
||||
}
|
||||
|
||||
fn convert_content_blocks(blocks: &[ContentBlock]) -> Value {
|
||||
if blocks.len() == 1 {
|
||||
if let ContentBlock::Text { text } = &blocks[0] {
|
||||
@ -280,18 +292,37 @@ impl LLMProvider for OpenAIProvider {
|
||||
let text = resp.text().await?;
|
||||
|
||||
// Debug: Log LLM response (only in debug builds)
|
||||
if !status.is_success() {
|
||||
tracing::error!(
|
||||
provider = %self.name,
|
||||
model = %self.model_id,
|
||||
url = %url,
|
||||
status = %status,
|
||||
response_len = text.len(),
|
||||
response_body = %text,
|
||||
"OpenAI-compatible API request failed"
|
||||
);
|
||||
return Err(format!("API error {}: {}", status, text).into());
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let resp_preview: String = text.chars().take(100).collect();
|
||||
tracing::debug!(status = %status, response_preview = %resp_preview, response_len = %text.len(), timeout_secs = self.llm_timeout_secs, "LLM response (first 100 chars shown)");
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
return Err(format!("API error {}: {}", status, text).into());
|
||||
}
|
||||
|
||||
let openai_resp: OpenAIResponse = serde_json::from_str(&text)
|
||||
.map_err(|e| format!("decode error: {} | body: {}", e, &text))?;
|
||||
let openai_resp: OpenAIResponse = serde_json::from_str(&text).map_err(|e| {
|
||||
tracing::error!(
|
||||
provider = %self.name,
|
||||
model = %self.model_id,
|
||||
url = %url,
|
||||
error = %format_error_chain(&e),
|
||||
response_len = text.len(),
|
||||
response_body = %text,
|
||||
"Failed to decode OpenAI-compatible API response"
|
||||
);
|
||||
format!("decode error: {} | body: {}", e, &text)
|
||||
})?;
|
||||
|
||||
let content = openai_resp.choices[0]
|
||||
.message
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user