Repository navigation
[Bug] llm-pi-ai forwards empty tool id/name, crashing the session write (assertV4ToolResultMessage) #11151
leo-liushuhong
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
摘要
当 DeepSeek 模型通过第三方网关(
wdtrip-gateway)以 Anthropic Messages 协议(api: "anthropic-messages")对外提供、并由 harness 的 pi-ai 适配器(kind: "pi-ai",version: 2)消费时,网关偶尔会下发一个id与name均为空字符串的tool_use块(模型在 reasoning 里输出 DSML 标记、网关误判为工具调用所致)。llm-pi-ai适配器把这个空id/name原样透传成 harness 的tool-call块,于是:tool/call事件携带callId: "";unknown tool ""),ToolCallRecovery生成tool/result时toolCallId也为空字符串;assertV4ToolResultMessage拒绝toolCallId.length === 0,报错format v4 tool/result at seq N requires toolCallId matching its tool source,该tool/result事件无法写入会话,本轮强制终止。关键点:
llm-deepseek适配器早已对空工具身份做了防御(if (!content.id || !content.name) return malformed('empty tool identity')),但llm-pi-ai适配器缺失同一道防线——这是两个适配器之间的不一致,正是本次崩溃的根因。复现条件
wdtrip-gateway,api: "anthropic-messages"kind: "pi-ai",version: 2deepseek-v4-pro-huoshan[1m](响应模型deepseek-v4-pro-ga-260813)session-20fc753c-164e-48af-bf2a-b742355cec42表现症状(证据)
会话日志尾部展示了「占位名不崩、空 id 才崩」的精确对照:
seq 112assistant/message 含tool-call id="call_8uwxpajwomekefw1vnwkofrj" name="tool_name"(占位名tool_name、真实非空 id);seq 114tool/result正常写入(content 为Error: unknown tool "tool_name")——非空 id 走「未知工具」路径,不崩溃;seq 117assistant/message 含tool-call id="" name=""(空 id 与空 name);seq 118tool/callcallId="" name="";seq 119的tool/result缺失——写入被assertV4ToolResultMessage拒绝,没有step/end、没有turn/end,会话在此强制终止。错误信息在本会话中出现 41 次:
format v4 tool/result at seq … requires toolCallId matching its tool source。根因分析
崩溃链路(全部在官方
master上确认):packages/llm/llm-pi-ai/src/stream.ts的toolcall_end分支直接把event.toolCall.id/event.toolCall.name原样写入 harness 的tool-call块,id可以为空字符串。??拦不住空串:packages/llm/llm/src/assembler.ts里id: partial.toolCallId ?? brandString(\call-${index}`)用??,只拦undefined,不拦空串""`。packages/core/agent-loop/src/tool-calls.ts的callId = block.id、ToolCallRecovery的toolCallId = callId都直接继承空 id。packages/session/session-format-v3-to-v4/src/tool-role.ts的assertV4ToolResultMessage里toolCallId.length === 0抛SessionFormatError——报错文案「requires toolCallId matching its tool source」其实由空 id 分支触发,具有误导性。不一致点(关键):
packages/llm/llm-deepseek/src/translate.ts在tool_use分支已有if (!content.id || !content.name) return malformed('empty tool identity')(抛MALFORMED_RESPONSE,且该 code 不在默认重试清单内,不会被重试放大成循环)。llm-pi-ai缺失同一防线,导致同样类别的畸形响应一个被拒、一个崩溃。修复方案
在
packages/llm/llm-pi-ai/src/stream.ts的toolcall_end分支、yieldblock-end之前加入空工具身份校验,与llm-deepseek对齐:MALFORMED_RESPONSE不在默认重试清单(retryableCodes = [EMPTY_RESPONSE, RATE_LIMIT, SERVER, TIMEOUT, TRANSPORT]),不会把畸形响应放大成重试循环。修复代码见 fork 分支:leo-liushuhong/deepseek-harness@fix/llm-dsml-leak-and-empty-tool-identity(提交
9a2ced4d)修复后的验证
packages/llm/llm-pi-ai/tests/convert.spec.ts):新增it.each两例(空 id、空 name)断言抛MALFORMED_RESPONSE,vitest -t "empty tool identity"→ 2 例全部通过。tsx scripts/build.ts --scope typecheck全仓库 5 个 gate 全部通过(0 failed)。9a2ced4d,pre-commit 门(staged lint、whitespace、vendor manifest)全部通过。All reactions