拒绝上游宣告未完成的工具调用
流式的收尾门禁只防传输层截断,把模型侧截断当成了正常收尾:is_completion 只 判断 finish_reason 非空,length / content_filter / max_tokens 一律放行;三条 非流式路径同样原样接受 Chat finish_reason、Responses status 和 Anthropic stop_reason。下游没有任何一处对该字段做分支,因此参数恰好闭合成合法 JSON 的 截断调用会被真的执行。格式修复循环对这种形态无从察觉,它看到的 JSON 是合法的。 新增 reject_incomplete_tool_calls,流式与三条非流式路径统一拦截已知的截断、 过滤和失败终态:Chat 的 length / content_filter,Responses 的 incomplete / failed / cancelled,Anthropic 的 max_tokens / pause_turn / refusal。 刻意用黑名单而非白名单,未知值与缺失一律放行,否则会误杀不发或自定义该字段的 兼容网关。只在存在工具调用时生效——正文被 max_tokens 截断仍是可用的降级结果, 一并拒绝会打死所有触及输出上限的长文本回答。 与非流式畸形参数透传同时成立时本检查优先,原用例的 finish_reason 相应改为 tool_calls 以只验证透传本身。补 6 个用例覆盖三协议截断、纯文本截断放行和未知 finish_reason 放行。
This commit is contained in:
@@ -263,6 +263,8 @@ arguments 是否必须是完整 JSON **按流式与非流式区分,两者的
|
||||
|
||||
静默丢弃是明确禁止的实现方式:它会把“上游给了工具调用但我们没解出来”伪装成“上游只回了正文”——响应同时带解说文本时更会被当作普通回复成功返回,而带 `tool_choice=required` 的请求随后退化为格式修复循环,审计里只能看到“模型没按协议调用工具”,看不出真正成因在解析层。非流式 DTO 为兼容流式分片把字段改成可选后尤其要注意:可选字段解除了 serde 的强制校验,缺失必须在归一层重新拦截。
|
||||
|
||||
工具调用还必须来自**没有被上游宣告为未完成**的响应。上游给出明确的截断 / 过滤 / 失败终态时,即使参数恰好闭合成合法 JSON 也必须返回 `Deserialize`:字节完整不代表模型把本轮工具计划表达完了,而下游拿到 `tool_calls` 就会真的去执行,格式修复循环对"参数合法但内容被砍断"完全无从察觉。已知终态为——Chat 的 `length` / `content_filter`,Responses 的 `incomplete` / `failed` / `cancelled`,Anthropic 的 `max_tokens` / `pause_turn` / `refusal`。这里必须用黑名单而非白名单,未知值与缺失一律放行,否则会误杀不发或自定义该字段的兼容网关。该检查**只在存在工具调用时生效**:正文被 `max_tokens` 截断仍是可用的降级结果,一并拒绝会打死所有触及输出上限的长文本回答。与非流式畸形参数透传同时成立时,本检查优先。
|
||||
|
||||
流式工具调用必须来自已收尾的流:只要聚合出过工具 slot,收尾时就必须已观察到本协议的完成信号,否则按截断返回 `Deserialize`。完成信号按协议判定——Chat 为非空 `choices[].finish_reason` 或 `data: [DONE]`,Responses 为 `response.completed`,Anthropic 为带 `stop_reason` 的 `message_delta` 或 `message_stop`。不能用 `data: [DONE]` 作为统一判据:MiniMax 兼容层不发该标记,只发 `finish_reason`。也不能只用 “参数是合法 JSON” 当完成证明——顶层花括号闭合只说明单个参数对象字节完整,说明不了模型是否还要发下一个工具块,更说明不了上游随后会不会报 `max_tokens` 或 error;代理超时、网关自行掐断和 HTTP/2 提前 `END_STREAM` 都表现为干净 EOF,与正常收尾在字节层无法区分。该门禁当前只覆盖工具路径;纯文本响应缺完成信号仍按成功返回并打 warn,改动前必须先确认所有在用网关的文本收尾行为。流在任何工具分片到达前就断掉时槽位为空,门禁无从触发,这是已知残留缺口。
|
||||
|
||||
反过来,已经收尾的流遇到尾部传输 / 解析错误时必须保留结果,不能重跑 Provider。判断“有没有值得保留的东西”要看正文或工具调用任一非空,不能只看正文——纯工具调用响应的正文本来就是空的(MiniMax 的 Anthropic 工具流恒定如此),只看正文会让这类响应每次都被丢弃,白白多跑一轮往返。保留的安全性由“协议完成信号已到 + `finish_reason` 已到 + 工具参数完整 + 错误属可容忍尾部错误”共同保证,与正常路径判据一致。Anthropic 的 `message_stop` 与 Responses 的 `response.completed` 目前只标记完成、不标记流终止,因此收尾事件之后仍会读到 EOF,这条尾部路径是常态而非边缘情况。
|
||||
|
||||
Reference in New Issue
Block a user