Skip to content

fix(antigravity):思考签名在 Anthropic 入口缺失导致工具续接 400 —— 新增服务端签名缓存 - #298

Open
loney123456 wants to merge 1 commit into
aimod-cc:betafrom
loney123456:fix/antigravity-signature
Open

loney123456 wants to merge 1 commit into
aimod-cc:betafrom
loney123456:fix/antigravity-signature

Conversation

@loney123456

Copy link
Copy Markdown

问题

Claude Code 走 Anthropic Messages(/v1/messages)接入 antigravity 时,每轮工具续接都被上游 400 拒:

上游返回 400: Function call is missing a thought_signature in functionCall parts.
This is required for tools to work correctly, and missing thought_signature may lead to
degraded model performance. Additional data, function call `default_api:Bash` , position 262.

根因

thoughtSignature 的往返依赖客户端回带:

上游 parts[*].thoughtSignature
  → chat tool_call 的 extra_content.google.thought_signature   (antigravity_stream)
  → 客户端下一轮原样带回
  → call_signature 回填 functionCall.thoughtSignature           (antigravity_outbound)

这条链只在 OpenAI chat 协议上成立。 Anthropic Messages 的 tool_use 块没有 extra_content 字段,签名根本没有承载位置 —— 于是:

  • 响应侧挂在 extra_content 上的签名,Claude Code 看不见;
  • 请求侧 call_signature 只能在那三条 pointer 上找,全都取不到;
  • 每一轮把工具结果带回上游时,functionCall part 缺 thoughtSignature,上游 400。

抓真实报文可以确认:上游响应里确实带了签名(functionCall part 上的 thoughtSignature,
且该 part 自带 id),但出站请求里的 functionCall part 一个都没有。

方案

新增 thought_signature_cache:服务端自持的 call id → thoughtSignature 映射,
让签名不再依赖客户端回带。

  • 响应侧(antigravity_stream::tool_call_frames):拿到带签名的 functionCall 时,
    按它的 id 存一份;
  • 请求侧(antigravity_outbound::call_signature):客户端三条路径都没带时,
    按 id 取回。优先级 客户端显式回带 > 服务端缓存(前者是客户端自己的会话态,更准)。

选 call id 作键是因为实测两点:上游响应的 functionCall 自带 id 且与签名在同一个 part;
同一条历史在连续两轮请求里的 id 完全一致、无空值。所以不需要按出现顺序做位置匹配。

容量上限 4096 条、FIFO 淘汰;缓存未命中时行为与改动前完全一致(省略签名),
所以它是尽力而为的优化,不是正确性前提。

已知限制

缓存只在进程存活期内有效 —— 网关重启后历史会话的签名丢失,那一轮退化成改动前的行为。
要跨重启就得落库,这里刻意不做:签名是短时效的会话态数据,为它加表和迁移不划算。
(该限制已写在模块头。)

验证

单元测试:新增 4 个(往返、空输入忽略、空白裁剪、同 id 覆盖不涨 order)。

全量测试:cargo test --release → 585 passed / 0 failed。

端到端(同一二进制,唯一变量是缓存命中与否):

组 call id 缓存 出站 thoughtSignature 结果
实验组 第 1 轮真实返回的 id 命中 有 HTTP 200
对照组 编造的、网关从未见过的 id 未命中 无 HTTP 400(同一句 missing thought_signature)

对照组排除了「上游碰巧放行」的可能 —— 200 确实来自签名回填。

真实客户端:Claude Code Desktop(ua=claude-cli/2.1.295)经 cc-switch 接入后,
多轮工具调用(含连续两轮 Bash)全部正常,不再出现 400。

范围

改动 4 个文件:新增 thought_signature_cache.rs(142 行),
改 antigravity_stream.rs(+9)、antigravity_outbound.rs(+16)、mod.rs(+1)。

对既有行为无影响:客户端回带签名时优先用客户端的值,缓存只在取不到时才生效。


参考:#237(同一句上游报错,但那条是 OpenAI chat 路径的聚合器丢 extra_content)、#253。

🤖 本条回复由 AI Agent 生成
Generated by an AI agent.

问题:Claude Code 走 /v1/messages(Anthropic Messages),其 tool_use 块没有
extra_content 字段,响应侧挂在 extra_content.google.thought_signature 上的
签名回不来。抓真实报文可确认:上游响应里带了 thoughtSignature,而出站请求的
functionCall part 一个都没有,上游每轮工具续接返回 400。

方案:新增 thought_signature_cache(call id → signature)。上游响应里带签名的
functionCall 按它的 id 存一份,下一轮出站时按同一个 id 取回,不依赖客户端行为。
优先级:客户端显式回带 > 服务端缓存。

选 call id 作键:上游响应的 functionCall 自带 id 且与签名同 part;同一条历史在
连续两轮请求里 id 完全一致、无空值。容量上限 4096 条 FIFO 淘汰;未命中时行为
与改动前一致(省略签名),所以是尽力而为的优化,不是正确性前提。

已知限制:缓存只在进程存活期内有效,网关重启后那一轮退化为改动前行为(见模块头)。

验证:新增 4 个单测;cargo test --release 589 passed / 0 failed。
端到端同一二进制下,缓存命中 200 / 未命中 400(对照组排除了上游碰巧放行的可能)。
Claude Code Desktop 经 cc-switch 接入后多轮工具调用全部正常。

参考 aimod-cc#237(同一句报错,那条是 OpenAI chat 路径的聚合器丢 extra_content)、aimod-cc#253。
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.

1 participant