Repository navigation
[Bug] V4 写侧 admission 不校验 tool-call 的 id/name:会话或永久打不开、或每次请求 400(0.1.7-rc.2 桌面端,含实测会话与帧级证据) #7944
shuaige1145
started this conversation in
General
Replies: 1 comment
|
补充一个 Anthropic Messages 协议下的复现,属于本帖的「B 类」(id 非空、name 为空 → 会话能打开,但此后每次请求都 400)。和本帖不同的是:这次 id 不是 provider 给的,而是 harness 自己补的,所以写入侧完全没报错,问题一直潜伏到下一次请求。 环境
发生了什么一个很长的会话(约 6.8 万条事件)里,模型在回合末尾输出总结文字,写到一半时流被截断。网关没有报错,而是正常收尾:
落盘的 {"type":"assistant/message","data":{"step":129,"message":{"role":"assistant","content":[
{"type":"reasoning","text":""},
{"type":"text","text":"……## 技术概要\n\n- **后端(Go):** 新增解析画面风格的模块。它把描述拆成两部分:文字颜色"},
{"type":"reasoning","text":""},
{"type":"tool-call","id":"call-3","name":"","arguments":""}
],"source":{"replayState":{"response":{"kind":"pi-ai","version":2,"api":"anthropic-messages","stopReason":"toolUse"}}}}}}随后的事件:
为什么 id 是
|
0 replies
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.
V4 写侧 admission 不校验 tool-call 的 id/name,可写出自己读不回来的会话日志
Summary
写入侧
assertV4RowAdmission(@deepseek-ai/dsh-session-format-v3-to-v4)在assistant/message行上不校验 content 里tool-callblock 的id/name,在tool/call行上也不校验
callId/name—— 只有tool/result行会走assertV4ToolResultMessage。而读取侧
Relationships.tool()用text(block["id"], "tool call id")硬性要求非空,重放侧(
@deepseek-ai/dsh-llm-pi-ai→ pi-ai 的 openai-completions 序列化)把block.name原样写进tool_calls[].function.name。于是 provider 只要返回一次
function.name为空(id也可能为空)的 tool call,DSH 就会写出自己读不回来的日志,落进两种都不可自愈的状态:
id为空 → 会话永久打不开:stored session "…" is corrupt: … SessionFormatError: tool call id requires a nonempty stringid非空但name为空 → 会话能打开,但之后每一次请求都被 provider 拒绝:400 {"code":"400","message":"Param Incorrect","param":"messages[N].tool_calls[0] is missing a function name"}Reproduction
路由:
provider=mimo、model=mimo-v2.6-flash、api=openai-completions(pi-ai 适配器)。function.name为空字符串,arguments是<parameter=command>…</parameter>形式的文本;该 arguments 的 JSON首个 key 恰好是它想调的工具名(例如
pwsh)。assistant/message:{"type":"tool-call","id":"","name":"","arguments":"…"}tool/call:{"callId":"","name":""}(name/arguments 与上面的 block 深相等)tool/result时被写入侧拒绝:format v4 tool/result at seq N requires toolCallId matching its tool source(UI 显示“本轮运行失败”),日志停在
tool/call之后,turn 保持打开。id补成同一非空call_…后可正常打开;再发一条消息 → 报 B 类错误(
tool_calls[0] is missing a function name,来自messages[298]);把
name补成pwsh后请求才发得出去。实测留证(DSH 0.1.7-rc.2 桌面端 nightly win-x64,Windows 11 10.0.26100 x64,format v4):
session-39e54170-88eb-4053-b340-915de9e710e4cb9121cd511c958fb5b1d63a457db1dd70daceb1202d94010ad5771d4aa8d92esession-3a4e7086-f780-47d8-8c0e-6b2f1e379ef665e059e10a47ab258f46f1f7f57d959a8793949e6d2989765b5d6e0b9e2213a1(上表 sha256 是“已补 id、未补 name”的状态;更早的纯损坏态分别是
c57a100c…、00a76788…。两次修复都只改 4 个字段 —— 两个
id/callId、两个name,arguments一字未动,容器层只重压含这些记录的那一帧,其余帧字节完全保留。)
Current behavior
历史加载失败,且没有官方修复入口。interruptedTurnClosers(@deepseek-ai/dsh-session)会按
block.id=""合成一个toolCallId=""的tool/result,这个合成结果同样过不了写入侧 admission,于是修复动作本身失败,会话永久不可读。
用户只看到“本轮运行失败”,日志里每次追加一个失败的
turn/end。Expected behavior
任一即可(建议 1 + 3):
assertV4RowAdmission(encodeEvent的唯一入口)补规则 ——assistant/message的每个tool-callblock 必须有非空字符串id与name;tool/call必须有非空callId/name,且name/arguments与已广告的 block 一致(即把
Relationships.tool()的判定提前到写入前)。宁可拒绝这一行,也不要写出一条读取侧必然拒绝的记录。
@deepseek-ai/dsh-llm-pi-ai)遇
function.name为空时不要落成 tool call(降级为 assistant text 或判为MALFORMED_RESPONSE),因为空名 tool call 在任何 OpenAI 兼容 provider 上都无法重放。interruptedTurnClosers不应为非法callId合成结果;读取侧遇空
id/name时可规范化(填占位 id/name)并把事件标记为已修复,而不是让整个会话不可读;至少要在 admission 失败时给出指向具体 seq 的可操作诊断。
Environment
0.1.7-rc.2(nightly 通道,win-x64;resources/app-update.yml→download.deepseek.com/dsh-desk/feeds/win-x64/)DSH_HOME:%USERPROFILE%\.dshsessions\<cwd-key>\<sessionId>\session.v4.jsonl.zstd,多帧 zstd 容器)mimo/mimo-v2.6-flash,api: openai-completions(
source.replayState.response.kind = "pi-ai",stopReason: "toolUse")附录:定位
@deepseek-ai/dsh-session-format-v3-to-v4的assertV4RowAdmission(对
tool/result调assertV4ToolResultMessage;对assistant/message只做assertV4DeveloperData/assertV4RetiredSyntax/assertV4SystemMessageFields)Relationships.tool():const id = text(block["id"], "tool call id")与const id = text(message === void 0 ? data["callId"] : message["toolCallId"], "tool call id")@deepseek-ai/dsh-session的openTurnClosers/interruptedTurnClosers@deepseek-ai/dsh-llm-pi-ai把 durable block 映射为 pi-aitoolCall(name: block.name),pi-ai 再序列化为
tool_calls[].function.name全量体检当前 21 个 v4 日志输出
invalid=0 replay-hazard=0,并与 DSH 自身的
import_chat(dsh4)读取路径交叉验证过修复结果All reactions