修复: AI 对话/工具可靠性(sanitize 三元组 + G2 签名重复 + handshake 不杀 loop + 空 tool_call id 兜底)

治 5 个对话停止/工具失败根因:sanitize 三元组按 id 配对治 400;G2 探索熔断从结果空
改签名重复判定(治误停正常探索);handshake 删越权强杀活 loop(generating 归 guard 单源);
空 tool_call id 兜底 gen_<index>(治 SenseNova 工具结果路由错位)。
This commit is contained in:
lxy
2026-08-02 02:21:30 +08:00
parent c76e77bd3c
commit 57d6a2d066
8 changed files with 991 additions and 185 deletions
+11 -10
View File
@@ -12,7 +12,7 @@
use serde::{Deserialize, Serialize};
use tracing::{error, warn};
use crate::provider::{StreamChunk, TokenUsage, ToolCallDelta};
use crate::provider::{tool_call_id_or_fallback, StreamChunk, TokenUsage, ToolCallDelta};
// ============================================================
// Anthropic API 请求/响应结构体
@@ -174,15 +174,16 @@ pub(crate) fn apply_anthropic_event(data: &str, usage_accum: &mut Option<TokenUs
if cb.get("type").and_then(|t| t.as_str()) == Some("tool_use") {
let idx = v.get("index").and_then(|i| i.as_u64()).unwrap_or(0) as u32;
let name = cb.get("name").and_then(|t| t.as_str()).map(|s| s.to_string());
// id 缺失时用占位 id 兜底流式后续 input_json_delta 按 index 累加,
// 中途无法整体跳过;占位 id 保证回传的 tool_use_id 非空,避免 GLM 500。
let id = match cb.get("id").and_then(|t| t.as_str()).map(|s| s.to_string()) {
Some(id) if !id.is_empty() => Some(id),
_ => {
let placeholder = format!("tool_missing_{}", idx);
warn!(%placeholder, name = ?name, "Anthropic 流式 tool_use 块缺少 id,已填占位 id(原样回传会触发 GLM 500)");
Some(placeholder)
}
// CR-空 idid 缺失/空时用 fallback 兜底流式后续 input_json_delta 按 index 累加,
// 中途无法整体跳过)。与同步路径 + OpenAI 路径共用 tool_call_id_or_fallbackDRY),
// prefix=`gen_anthropic_stream` 区分来源。非空原样。
let raw_id = cb.get("id").and_then(|t| t.as_str()).unwrap_or("");
let id = if raw_id.is_empty() {
let fallback = tool_call_id_or_fallback(raw_id, idx as usize, "gen_anthropic_stream");
warn!(%fallback, name = ?name, "Anthropic 流式 tool_use 块 id 为空,已生成 fallback id(原样回传会触发 GLM 500)");
Some(fallback)
} else {
Some(raw_id.to_string())
};
return StreamChunk {
delta: String::new(),