From e3a8a8cffb07f0466c6cd13f98a231309c41b432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Thu, 1 Oct 2026 08:28:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86:=20=E5=81=9C=E6=BB=9E?= =?UTF-8?q?=E5=88=A4=E5=AE=9A=E5=B7=A5=E5=85=B7=E5=90=8D=E6=98=A0=E5=B0=84?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E5=8E=BB=E9=87=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两处重复的 id 到 name 映射抽共用 helper --- .../src/commands/ai/agentic/stall_detector.rs | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src-tauri/src/commands/ai/agentic/stall_detector.rs b/src-tauri/src/commands/ai/agentic/stall_detector.rs index 7ef0c32..91d27e8 100644 --- a/src-tauri/src/commands/ai/agentic/stall_detector.rs +++ b/src-tauri/src/commands/ai/agentic/stall_detector.rs @@ -80,25 +80,14 @@ pub(crate) async fn count_recent_failures( if recent_tool_results.is_empty() { return (0u32, String::new()); } - // tool_call_id → tool_name 反查表:遍历 Assistant 消息的 tool_calls(开销与消息数线性)。 - // tool_result 消息只带 tool_call_id,工具名在其前驱 Assistant 头里。 - let mut id_to_name: std::collections::HashMap<&str, &str> = std::collections::HashMap::new(); - for m in &messages { - if let MessageRole::Assistant = m.role { - if let Some(calls) = m.tool_calls.as_ref() { - for c in calls { - id_to_name.insert(c.id.as_str(), c.function.name.as_str()); - } - } - } - } + let id_to_name = build_id_to_name(&messages); // 本轮失败的归一 key 追加进窗口(顺序:消息正序,即工具执行顺序)。 for m in recent_tool_results.into_iter().rev() { if is_failure_content(&m.content) { let tool_name = m .tool_call_id .as_deref() - .and_then(|id| id_to_name.get(id).copied()) + .and_then(|id| id_to_name.get(id).map(|s| s.as_str())) .unwrap_or("unknown_tool"); let key = failure_key(tool_name, &m.content); fail_window.push_back(key); @@ -151,6 +140,24 @@ fn failure_key(tool_name: &str, content: &str) -> String { format!("{}::{}:{:08x}", tool_name, bucket, h) } +/// 构建 tool_call_id → tool_name 反查表(断路器失败分组与 tool_result 摘要压缩共用)。 +/// +/// Tool 结果消息只带 tool_call_id,真实工具名在前驱 Assistant 消息的 +/// tool_calls[].function.name 里;查不到的 id 由调用方按各自语义兜底。 +fn build_id_to_name(messages: &[ChatMessage]) -> std::collections::HashMap { + let mut id_to_name = std::collections::HashMap::new(); + for m in messages { + if let MessageRole::Assistant = m.role { + if let Some(calls) = m.tool_calls.as_ref() { + for c in calls { + id_to_name.insert(c.id.clone(), c.function.name.clone()); + } + } + } + } + id_to_name +} + /// a 32-bit 哈希(零依赖,纯函数,断路器 key 归一专用;非密码学用途)。 fn fnv1a_32(bytes: &[u8]) -> u32 { let mut hash: u32 = 0x811c9dc5; @@ -227,21 +234,10 @@ pub(crate) fn summarize_tool_results( let mut original_bytes: usize = 0; let mut summarized_count: usize = 0; - // 构建 tool_call_id -> tool_name 映射:role=Tool 消息只带 tool_call_id(调用 ID), - // 真实工具名(read_file/run_command/...)在前序 role=Assistant 消息的 tool_calls[].function.name。 - // 旧实现误把 tool_call_id(如 call_f892)当 tool_name 传给 extract_key_info,导致: - // 1) 摘要头注释显示 call_f892 而非 read_file(显示 bug); - // 2) extract_key_info 的 read_file 豁免永远命中不了(传入的不是真实名)。 - // 用 owned String(非 &str 借用)避开与 messages.into_iter() 的所有权冲突; - // tool_call 数量极少(单轮几次),克隆开销可忽略。 - let mut id_to_name: std::collections::HashMap = std::collections::HashMap::new(); - for m in &messages { - if let Some(calls) = m.tool_calls.as_ref() { - for c in calls { - id_to_name.insert(c.id.clone(), c.function.name.clone()); - } - } - } + // tool_call_id → tool_name 反查与断路器共用 build_id_to_name;查不到(异常/老数据) + // 退化为 "tool"。旧实现曾误把 tool_call_id 当工具名,致摘要头显示 call_f892、 + // read_file 豁免永远命中不了。 + let id_to_name = build_id_to_name(&messages); let msgs: Vec = messages .into_iter()