From ed45ec54ed3b12938de741c47ce164f191f69a8e Mon Sep 17 00:00:00 2001 From: ooodc <549496103@qq.com> Date: Mon, 27 Apr 2026 09:35:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=A4=84=E7=90=86=EF=BC=8C=E6=B7=BB=E5=8A=A0format=5Ferror=5Fc?= =?UTF-8?q?hain=E5=87=BD=E6=95=B0=E4=BB=A5=E6=A0=BC=E5=BC=8F=E5=8C=96?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E9=93=BE=EF=BC=8C=E4=BC=98=E5=8C=96=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent_loop.rs | 47 ++++++++++++++++++++++++++++++++++---- src/providers/anthropic.rs | 35 ++++++++++++++++++++++++++-- src/providers/openai.rs | 43 +++++++++++++++++++++++++++++----- 3 files changed, 113 insertions(+), 12 deletions(-) diff --git a/src/agent/agent_loop.rs b/src/agent/agent_loop.rs index 8c08e09..4a64e90 100644 --- a/src/agent/agent_loop.rs +++ b/src/agent/agent_loop.rs @@ -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()), diff --git a/src/providers/anthropic.rs b/src/providers/anthropic.rs index 57aea19..0c47ebd 100644 --- a/src/providers/anthropic.rs +++ b/src/providers/anthropic.rs @@ -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(blocks: &[serde_json::Value], serializer: S) -> Result 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(); diff --git a/src/providers/openai.rs b/src/providers/openai.rs index 21a2d55..ca90f52 100644 --- a/src/providers/openai.rs +++ b/src/providers/openai.rs @@ -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