修复: 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
+15 -6
View File
@@ -11,7 +11,7 @@
use serde::{Deserialize, Serialize};
use tracing::{debug, error};
use crate::provider::{StreamChunk, TokenUsage, ToolCallDelta};
use crate::provider::{tool_call_id_or_fallback, StreamChunk, TokenUsage, ToolCallDelta};
// ============================================================
// OpenAI API 请求/响应结构体
@@ -211,11 +211,20 @@ pub(crate) fn apply_openai_sse(data: &str, usage_accum: &mut Option<TokenUsage>)
let tool_calls = choice.delta.tool_calls.map(|tcs| {
tcs.into_iter()
.map(|tc| ToolCallDelta {
index: tc.index,
id: tc.id,
function_name: tc.function.as_ref().and_then(|f| f.name.clone()),
function_arguments: tc.function.and_then(|f| f.arguments),
.map(|tc| {
// CR-空 id:流式 chunk 的 id 可能为 Some("")SenseNova 兼容缺陷)。
// 仅对「provider 显式给了 id 字段」的 chunk 做兜底——NoneOpenAI
// 协议:仅首 chunk 携带 id,后续 chunk 无 id)保持 None,避免
// 覆盖首 chunk 的权威 id。Some("") → `gen_stream_{index}` fallback
// Some(非空) → 原样。下游 stream_recv 按 index 累积,draft.id 透传
// 至 ToolCall.idaccumulate_tool_calls 仅 Some 覆盖,None 不动)。
let id = tc.id.map(|raw| tool_call_id_or_fallback(&raw, tc.index as usize, "gen_stream"));
ToolCallDelta {
index: tc.index,
id,
function_name: tc.function.as_ref().and_then(|f| f.name.clone()),
function_arguments: tc.function.and_then(|f| f.arguments),
}
})
.collect()
});