流式工具参数类型非法改为失败关闭
Responses 与 Anthropic 的流式工具参数字段走裸 serde_json::Value 加 as_str,
类型不对时 as_str 返回 None,参数被当成字段缺失;归一层再把空参数补成 {},
于是一个身份完整、参数是合法 JSON 的调用直接交给下游执行。既有校验全都拦不住
——零参函数的 {} 和「参数类型错了所以变成 {}」在归一层无法区分。
实测复现(探针已删):Responses 的 delta / done / 终态载荷 arguments,以及
Anthropic 的 partial_json,无论传对象还是数字,一律返回
Ok(tool_calls=[{id, name, arguments:"{}"}])。同样输入下 Chat 返回
Deserialize("invalid type: map, expected a string")——它走强类型 DTO,本来就
失败关闭。同一平台层三协议对同一种畸形输入行为不一致。
归属:不是先前几次修复的后继问题。两个构成要件都来自 7c61e9a53——那批
as_str 用法是它一次性引入 Responses / Anthropic 流式工具解析时写的,
「空参数归一为 {}」当时也已在 finish_tool_calls 里;b1ef45fbd 把它挪进
normalize_tool_calls 时逐字搬运、语义未动。逐条查过本会话的槽位失败关闭、
身份冲突、截断门禁与终态正文恢复,都走别的分支,没有扩大它的可达面。
改为 tool_argument_str:字段不存在或为 null 返回 None(合法缺省),存在但
不是字符串返回 Deserialize。extract_responses_completed_tool_fragments 随之
改签名返回 Result。只覆盖参数字段——id 与函数名即使类型不对也只会退化成缺失,
随后被归一层按缺 id / 缺函数名拒绝,本来就是失败关闭。
隔离验证:把类型检查退回 as_str,五条拒绝用例全红。
platform-llm 101 passed(原 95)。新增 Responses delta / done / 终态载荷、
Anthropic 对象与数字 partial_json 五条拒绝,外加一条作用域守卫——字段缺失与
显式 null 仍归一为 {},零参函数不能被这条新规则误杀。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -277,6 +277,8 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复
|
||||
|
||||
槽位存在但被两个不同调用共用时同样必须失败关闭:同一槽位的 id 与函数名只允许**从缺失变为已知**或**重复同一个值**,出现互不相同的非空值即返回 `Deserialize`。“覆盖身份、追加参数”并不自洽——前一个调用参数为空时拼接结果就是后一个调用的合法 JSON,参数完整性检查兜不住,调用方只会拿到后一个工具,前一个静默消失;Responses 的权威完整参数还会整段覆盖,产出“前一个调用的身份配后一个调用的参数”。两者都会原样交给 Runtime 执行。已知触发路径有两条:兼容网关把 Chat 的 `index` 恒置 0,以及 Responses 的 `response.completed` 回退按 `output[]` 下标重建槽位时与流式 `output_index` 基准错位(例如 completed 载荷省略 reasoning item)。id 必须与函数名一同参与判定——并行调用同一个工具是最常见的并行场景,此时函数名相同,只有 id 能区分。空白身份按缺失跳过、不算冲突:部分兼容网关在续传分片里回发完整 `function` 对象且 `name` / `id` 为空串,按“不等即冲突”会把它们整批误杀,这也与归一层的空白即缺失约定一致。
|
||||
|
||||
工具参数字段必须区分“缺失”与“类型非法”:字段不存在或为 `null` 是合法缺省(零参函数),存在但不是字符串一律返回 `Deserialize`。把两者混同的写法(`as_str` 遇到非字符串返回 `None`)会让参数被当成缺省,归一层再补成 `{}`,于是一个身份完整、参数是合法 JSON 的调用直接交给下游执行,既有校验全都拦不住——零参函数的 `{}` 与“参数类型错了所以变成 `{}`”在归一层无法区分。涉及 Responses 的 `response.function_call_arguments.delta` 的 `delta`、`.done` 的 `arguments`、终态载荷 `output[].arguments`,以及 Anthropic `input_json_delta` 的 `partial_json`;Chat 走强类型 DTO,同样输入本就反序列化失败,本规则是把三协议口径拉齐。该约束只覆盖参数字段:id 与函数名即使类型不对也只会退化成缺失,随后被归一层按缺 id / 缺函数名拒绝,本来就是失败关闭。
|
||||
|
||||
反过来,已经收尾的流遇到尾部传输 / 解析错误时必须保留结果,不能重跑 Provider。判断“有没有值得保留的东西”要看正文或工具调用任一非空,不能只看正文——纯工具调用响应的正文本来就是空的(MiniMax 的 Anthropic 工具流恒定如此),只看正文会让这类响应每次都被丢弃,白白多跑一轮往返。保留的安全性由“协议完成信号已到 + `finish_reason` 已到 + 工具参数完整 + 错误属可容忍尾部错误”共同保证,与正常路径判据一致。Chat 的非空 `finish_reason` 与 Anthropic 带 `stop_reason` 的 `message_delta` 会标记完成但不直接终止读取,因此在后续终止事件缺失时仍可能进入这条尾部保留路径;Chat 的 `[DONE]`、Anthropic 的 `message_stop` 以及 Responses 的 `response.completed` / `response.incomplete` 已经直接终止读取。
|
||||
|
||||
错误边界固定如下:`StreamUnavailable` 只表示流式响应已给出 `tool_use` / `tool_calls` 完成原因但没有聚合出任何工具 slot,供调用方回退非流式,它不承担截断语义;`EmptyResponse` 表示最终文本和工具调用都为空,纯工具响应合法;`Deserialize` 覆盖 JSON / SSE / UTF-8 解析失败、缺少 `choices[0]`、流式工具身份缺失、流式工具槽位身份冲突、流式参数不完整,以及上述工具流未收尾截断。Anthropic 仍不支持 `web_search`、图片内容和纯 system 消息,必须至少有一条非 system 文本消息。
|
||||
|
||||
Reference in New Issue
Block a user