Skip to content

fix(messages): always include signature on thinking blocks - #278

Merged
dwgx merged 1 commit into
dwgx:masterfrom
3219378872:fix/thinking-signature-key
Oct 5, 2026
Merged

dwgx merged 1 commit into
dwgx:masterfrom
3219378872:fix/thinking-signature-key

Conversation

@3219378872

Copy link
Copy Markdown
Contributor

改了什么 / What changed

/v1/messages 的 thinking 块现在始终带 signature 字段:

  • 非流式:之前 {"type":"thinking","thinking":"..."},现在 {"type":"thinking","thinking":"...","signature":""}(上游有真实签名时为真实值)
  • 流式:thinking 的 content_block_start 之前是 {"type":"thinking","thinking":""},现在是 {"type":"thinking","thinking":"","signature":""}。signature_delta 行为不变,仍只在上游给出真实 reasoning_signature 时发送。

为什么 / Why

Fixes #277

Grok Build 把 ContentBlock::Thinking.signature 反序列化为必填的 String,缺字段时整条响应 / SSE 事件都会失败(missing field signature);空字符串可以正常解析。7d72818 依据"Grok 拒绝 signature: \"\""删掉了这个字段,但实际情况正好相反。本 PR 恢复 Anthropic 的线上格式。

已验证:

  • 阅读 Grok Build 源码(xai-grok-sampling-types/src/messages.rs、xai-grok-sampler/src/client.rs),确认它对每个 SSE 事件做严格 serde 解析
  • 用最小 serde 程序确认:缺字段时报错,"" 可以正常解析
  • 在生产实例上复现了缺字段的问题;部署本补丁后,用真实的 Grok 请求验证流式和非流式均正常(结果见下方评论)

测试 / Testing

$ node --import ./test/setup-env.mjs --test test/messages.test.js test/messages-thinking-contract.test.js
ℹ pass 86
ℹ fail 0

$ npm run test:release   # Node v24.16.0
Suite: 4889 pass / 2 fail / 1 skip (391/391 files measured)
- test/restart.test.js: exit 1

restart.test.js 中失败的 2 条(detectSupervisor: "reports unsupervised when nothing matches"、"override value other than "1" is not honoured")在未打补丁的 origin/master 上同样失败,原因是本机环境被识别为 supervisor,与本改动无关。

Checklist

  • 代码风格和现有文件一致 / Code style matches existing files
  • 没有引入 npm 运行时依赖 / No new npm runtime dependencies (project is zero-dep)
  • 测试跑过了,贴了结果 / Tests run, output pasted above
  • 新行为有测试,且断言行为而不是 grep 源码文本 / New behaviour has tests that assert behaviour
  • 新开关默认关 / N/A(无新开关)
  • 加了开关就同步了文档 / N/A(无新开关)

🤖 Generated with Claude Code

7d72818 dropped the `signature` key from thinking blocks when upstream had no
real signature, on the assumption that Grok Build rejects `signature: ""`.
The opposite is true: Grok Build deserializes `ContentBlock::Thinking` with a
required `signature: String`, so an empty string parses fine while a missing
key fails the whole response / SSE event with "missing field `signature`".
The streamed content_block_start never carried the key at all.

Match Anthropic's wire shape instead:
- non-stream: thinking block always has `signature` (real value or "")
- stream: thinking content_block_start carries `signature: ""`;
  signature_delta is still only emitted for a real upstream signature

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@3219378872

3219378872 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

已部署到生产实例验证(5b52b87 + 本 PR,Devin Connect):

  • 直连 /v1/messages,模型 claude-opus-5-5-high、thinking 开启:
    • 流式:content_block_start → {"type":"thinking","thinking":"","signature":""}
    • 非流式:[("thinking", has signature=True, ""), ("text", False)]
  • Grok Build 1.0.46(api_backend = "messages",经 sub2api 透传):
    • 单轮推理:流中有 27 个 thinking_delta,result: success
    • 多轮工具调用(read_file → search_replace → run_terminal_command,3 轮):success,修复结果正确

修复前的失败依据:同一实例返回的 thinking 块缺少 signature,而 Grok 的 serde 定义对这种块报 missing field `signature` (已用最小程序验证)。修复前没有用 Grok 实际跑过一遍。

@dwgx
dwgx merged commit d3eaab0 into dwgx:master Oct 5, 2026
@dwgx

dwgx commented Oct 5, 2026

Copy link
Copy Markdown
Owner

谢谢 —— 这个修得对,已经合了(d3eaab0)。

我把两边的说法都查了一遍:原来那句「strict clients 会把 signature: "" 当无效值拒掉」(7d72818 / PR #228)没有根据 —— 没说版本、没给报错、没有复现。而 Grok Build 的公开源码里,ContentBlock::Thinking 的 signature 是必填、没有 #[serde(default)],流里每一帧都走 serde 解析,"" 能正常过;Anthropic 官方流式文档里 thinking 的 content_block_start 也是 "signature": ""。有证据的一边赢了没证据的一边,这条我会记进项目自己的质量条里,作为「注释写错理由也是缺陷」的实例。

我另外核了三件事:

  • 把 src/ 那一段单独回退,正好 4 条断言变红(88 个测试 / 84 过 / 4 挂)—— 守卫在真实路径上咬得住;
  • src/ 里会产 thinking 块的只有两处(非流与流的 start),你都改了,其余方言(gemini / responses)没有 signature 这个概念;
  • 入站的 thinking 块本来就会被丢掉(messages.js:693-725 从不读 signature),所以发 "" 在回环上是中性的。

一句实话:这个 PR 的 CI 这次没有跑(当时 GitHub Actions 故障、托管跑者的分配在延迟)。我的证据是我这边按你的 head b60ec3d 重建后跑的全量门:六步全过、4911 pass / 0 fail / 1 skip(393 文件),三个改动文件按 sha256 与你提交的 blob 逐个对过。

另外你开的 #277 我先留着,里面那条「signature 必填」的来源写得很清楚,后来的人会用到。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] /v1/messages 的 thinking 块缺少 signature 字段,导致 Grok Build 解析失败(missing field signature)

2 participants