From 99a57a816a9a9dfcb06d08be6ded7fdbb1defcde Mon Sep 17 00:00:00 2001 From: xiaoski Date: Wed, 13 May 2026 08:59:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0LLM=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/providers/anthropic.rs | 30 ++++++++++++++++++++++++++++-- src/providers/openai.rs | 30 ++++++++++++++++++++++++++++-- src/session/session.rs | 29 ++++++++++++++++++++++++----- 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/providers/anthropic.rs b/src/providers/anthropic.rs index fe33ada..b3ea30c 100644 --- a/src/providers/anthropic.rs +++ b/src/providers/anthropic.rs @@ -2,6 +2,7 @@ use async_trait::async_trait; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::time::Duration; use crate::bus::message::ContentBlock; use super::{ChatCompletionRequest, ChatCompletionResponse, LLMProvider, Tool, ToolCall}; @@ -9,6 +10,8 @@ use super::traits::Usage; use std::sync::Arc; use crate::storage::Storage; +const LLM_REQUEST_TIMEOUT_SECS: u64 = 300; + fn convert_content_blocks(blocks: &[ContentBlock]) -> Vec { blocks.iter().map(|b| match b { ContentBlock::Text { text } => { @@ -72,7 +75,10 @@ impl AnthropicProvider { model_extra: HashMap, ) -> Self { Self { - client: Client::new(), + client: Client::builder() + .timeout(Duration::from_secs(LLM_REQUEST_TIMEOUT_SECS)) + .build() + .unwrap_or_else(|_| Client::new()), name, api_key, base_url, @@ -238,7 +244,19 @@ impl LLMProvider for AnthropicProvider { let req_body_str = serde_json::to_string_pretty(&body).unwrap_or_default(); tracing::debug!(req_body = %req_body_str, "LLM request"); - let resp = req_builder.json(&body).send().await?; + let resp = req_builder.json(&body).send().await + .inspect_err(|e| { + let is_timeout = e.is_timeout(); + tracing::error!( + provider = %self.name, + model = %self.model_id, + url = %url, + timeout = is_timeout, + error = %e, + elapsed_ms = %start.elapsed().as_millis(), + "LLM API request failed" + ); + })?; let status = resp.status(); let body_text = resp.text().await?; @@ -254,6 +272,14 @@ impl LLMProvider for AnthropicProvider { .map(|s| s.to_string()) }) .unwrap_or_else(|| body_text.clone()); + tracing::error!( + provider = %self.name, + model = %self.model_id, + http_status = %status, + error = %error_msg, + elapsed_ms = %start.elapsed().as_millis(), + "LLM API returned error" + ); if let Some(ref storage) = self.storage { let _ = storage.append_llm_call( &self.name, &self.model_id, &req_body_str, diff --git a/src/providers/openai.rs b/src/providers/openai.rs index 4fde00c..6ff11a1 100644 --- a/src/providers/openai.rs +++ b/src/providers/openai.rs @@ -3,6 +3,7 @@ use reqwest::Client; use serde::Deserialize; use serde_json::{json, Value}; use std::collections::HashMap; +use std::time::Duration; use crate::bus::message::ContentBlock; use super::{ChatCompletionRequest, ChatCompletionResponse, LLMProvider, ToolCall}; @@ -10,6 +11,8 @@ use super::traits::Usage; use std::sync::Arc; use crate::storage::Storage; +const LLM_REQUEST_TIMEOUT_SECS: u64 = 300; + fn convert_content_blocks(blocks: &[ContentBlock]) -> Value { if blocks.len() == 1 && let ContentBlock::Text { text } = &blocks[0] { @@ -48,7 +51,10 @@ impl OpenAIProvider { model_extra: HashMap, ) -> Self { Self { - client: Client::new(), + client: Client::builder() + .timeout(Duration::from_secs(LLM_REQUEST_TIMEOUT_SECS)) + .build() + .unwrap_or_else(|_| Client::new()), name, api_key, base_url, @@ -206,7 +212,19 @@ impl LLMProvider for OpenAIProvider { let req_body_str = serde_json::to_string_pretty(&body).unwrap_or_default(); tracing::debug!(req_body = %req_body_str, "LLM request"); - let resp = req_builder.json(&body).send().await?; + let resp = req_builder.json(&body).send().await + .inspect_err(|e| { + let is_timeout = e.is_timeout(); + tracing::error!( + provider = %self.name, + model = %self.model_id, + url = %url, + timeout = is_timeout, + error = %e, + elapsed_ms = %start.elapsed().as_millis(), + "LLM API request failed" + ); + })?; let status = resp.status(); let text = resp.text().await?; @@ -214,6 +232,14 @@ impl LLMProvider for OpenAIProvider { if !status.is_success() { let error = format!("API error {}: {}", status, text); + tracing::error!( + provider = %self.name, + model = %self.model_id, + http_status = %status, + error = %error, + elapsed_ms = %start.elapsed().as_millis(), + "LLM API returned error" + ); if let Some(ref storage) = self.storage && let Err(e) = storage.append_llm_call( &self.name, &self.model_id, &req_body_str, diff --git a/src/session/session.rs b/src/session/session.rs index 5440618..a7f0c02 100644 --- a/src/session/session.rs +++ b/src/session/session.rs @@ -1360,7 +1360,10 @@ impl SessionManager { let result = session_guard.compressor .compress_if_needed(history) - .await?; + .await + .inspect_err(|e| { + tracing::warn!(error = %e, "Context compression failed in handle_message"); + })?; if result.created_timelines { session_guard.last_compressed_message_at = Some(chrono::Utc::now().timestamp_millis()); } @@ -1401,9 +1404,19 @@ impl SessionManager { } let mut retry = retry_result.history; retry.insert(0, ChatMessage::system(system_prompt)); - agent.process(retry).await? + agent.process(retry).await + .inspect_err(|e| { + tracing::error!(error = %e, "Agent retry after context compression failed"); + })? } - Err(e) => return Err(e), + Err(e) => { + tracing::error!( + error = %e, + elapsed = ?"LLM call in handle_message failed", + "Agent processing error — propagating to caller" + ); + return Err(e); + }, }; for msg in result.emitted_messages { @@ -1515,13 +1528,19 @@ impl SessionManager { // in context compression (system prompt is dynamic and should not be persisted). let mut history = session_guard.compressor .compress_if_needed(history) - .await? + .await + .inspect_err(|e| { + tracing::warn!(error = %e, "Context compression failed in handle_cron_message"); + })? .history; history.insert(0, ChatMessage::system(full_system_prompt)); let agent = session_guard.create_agent_with_notify(notify_tx)?; - let result = agent.process(history).await?; + let result = agent.process(history).await + .inspect_err(|e| { + tracing::error!(error = %e, "Agent processing error in handle_cron_message"); + })?; for msg in result.emitted_messages { session_guard.add_message(msg, true).await