《深入理解 AI Agent》第 5 章 · 配套实验

实验 5-1:跨厂商的轨迹接管

一次跑到一半的 Agent 轨迹,能不能换一家模型接着跑完?本记录覆盖 2 家厂商 × 2 个有向组合 × 3 条臂 = 6 个单元。 故障注入是模拟的,切换之后目标厂商返回的 4xx 是真实响应,报错原文保留在证据里。
run exp5-1-handoff-20261001T142610Z-record
生成 2026-10-01T14:26:10Z
注入故障 429 · 429 · 503
切换阈值 2 次工具调用后
复核 passed

一、结果概览 6 个单元的判定口径来自 judging.py,汇总时以原始响应重判

切换成功率
4 / 6
中立臂 2/2,两条对照臂各 1/2
答案正确
4 / 4
成功单元全部给出 ¥6,944
重复工具调用
0
含目标侧历史的完整轨迹核对
成功 API token
16,415 in
输出 5,062(含源厂商侧)

二、结果矩阵 切换之后第一个请求的 HTTP 状态码

组合 \ 臂 直传 naive
原样搬运 payload
剥离 strip
删掉全部思考与凭证
中立 neutral
凭证丢弃 + 明文重铸
kimi → gemini
kimi-k3 → gemini-3.5-flash
3 次调用处切换
切换失败
400
缺 thought_signature
0 轮 · 0 token
3 次调用处切换
切换失败
400
缺 thought_signature
0 轮 · 0 token
3 次调用处切换
接管完成
200
2 轮 · 1,331 in / 593 out
数据齐备 · 总额正确
gemini → kimi
gemini-3.5-flash → kimi-k3
2 次调用后切换
接管完成
200
3 轮 · 4,113 in / 1,314 out
2 次调用后切换
接管完成
200
3 轮 · 2,062 in / 431 out
2 次调用后切换
接管完成
200
3 轮 · 3,732 in / 692 out
200 · 切换成功 400 · 厂商真实拒绝 in / out 为切换之后目标厂商侧累计 token;失败单元计 0

三、一次失败的完整链路 kimi → gemini · 直传臂

STEP 1
kimi 正常推进
第 1 轮一次性并行发出
3 个 functionCall
STEP 2
注入过载
429 → 429 → 503
熔断触发,injected: true
STEP 3
轨迹交给 gemini
thought 明文带走
functionCall 无凭证
STEP 4
被拒
400 INVALID_ARGUMENT
position 2
厂商原始报错体,原文未改写:
// gemini-to-kimi 之外的 kimi-to-gemini-naive.json → exchanges[1].error_body
{
  "error": {
    "code": 400,
    "message": "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:get_flight_price` , position 2. Please refer to
      https://ai.google.dev/gemini-api/docs/thought-signatures for more details.",
    "status": "INVALID_ARGUMENT"
  }
}
凭证挂在哪儿
Gemini 的 thoughtSignature 挂在工具调用上,不在思考里。kimi 本身不签发任何凭证,所以直传臂和剥离臂交出去的 functionCall 长得一模一样——两条臂报同一个错,不是巧合。
切换点与标称值不一致
标称阈值是 2 次工具调用后,但 kimi 在第 1 轮把 3 个调用并行发了出去,熔断发生时手上已经是 3 次调用。exact_two_call_switch 因此为 false,这是记录事实而非实现缺陷。

四、三条臂的代价 只统计切换成功的单元

切换后输入 token(成本口径)
naive 直传
4,113
1 / 2 单元
neutral 中立
5,063
2 / 2 单元
strip 剥离
2,062
1 / 2 单元
三条臂的绝对值不可直接相减——naive 与 strip 各只有一个单元跑通,neutral 有两个。按单元归一后,中立臂相对剥离臂多付 约 2.3 倍输入 token。
同一组合内的横向对比 · gemini → kimi(三条臂都跑通)
naive 直传
4,113
out 1,314
neutral 中立
3,732
out 692
strip 剥离
2,062
out 431
naive neutral strip
三条臂用的轮数完全相同(都是 3 轮),差别只在把多少思考搬过去。带思考买的是接管的确定性,不是更少的步数。

五、交接载荷与思考体量 切换之后第一个请求里真正带过去的东西

单元臂交接请求体积思考字符数 凭证处理结果
gemini → kiminaive4,814 B5,925 原样搬运(thoughtSignature 随 functionCall 走)200
gemini → kimineutral2,709 B2,301 丢弃,摘要作为普通文本200
gemini → kimistrip762 B2,660 全部删除200
kimi → gemininaive1,038 B224 无凭证可带(kimi 不签发)400
kimi → geministrip814 B461 无凭证可带(kimi 不签发)400
kimi → geminineutral750 B2,004 重铸 functionCall,明文加前缀200
「思考字符数」为整条轨迹上模型输出的可见思考长度合计,含切换之后新产生的部分。剥离臂的数字并不为 0——删除的是带过去的思考,目标厂商自己仍然会思考。

六、门禁与复核 manifest.gates 与 verification.json

manifest 门禁
  • ✓中立臂在全部厂商组合上切换成功
  • ✓直传臂的失败都是厂商真实报错
  • ✓每个单元都留下了原始响应
  • ✗三条臂各覆盖六种厂商组合本次为授权的两厂商范围,Anthropic 因本地 .env 中 ANTHROPIC_API_KEY 仍是占位符而缺席
复核校验(14 项 × 6 单元全部通过)
  • ✓授权范围内两厂商组合完整authorized_two_provider_scope_complete
  • ✓原始证据与指标已核对14 项检查:sha256、HTTP 状态、工具参数、轮数、token、重复计数、总额
  • ✓源码快照哈希已验证8 个源文件随运行一起快照
  • ✓两个观测方向上中立臂均成功
  • ✗完整三厂商协议未完成full_three_provider_protocol_complete: false
  • ✗并非所有单元都在恰好 2 次调用后切换all_cells_switched_after_exactly_two_calls: false

七、这一轮跑出来的几件事

  • 凭证的位置决定谁能接谁的盘。gemini 收下 kimi 的直传轨迹时只检查 thoughtSignature 这一格在不在,不检查是谁签的;而反过来,gemini 签出来的轨迹到了缺凭证的接收端就直接 400。校验强度的不对称,比格式差异更容易让人踩坑。
  • 「直传能过」不等于「直传是对的」。本次 naive 臂在 gemini → kimi 方向一次通过,但真正决定成败的不是它带对了什么,而是接收端没查。同一个格子在另一个方向就成了 400。
  • kimi 侧没有凭证,于是两条对照臂退化成同一条臂。kimi → gemini 的 naive 与 strip 报的是同一句 missing a thought_signature,交出去的 functionCall 结构完全一致。这不是重复实验,是「无凭证可搬」这个事实的直接后果。
  • 中立臂 2/2 通过,且两个方向都算对了 ¥6,944。它的做法是:凭证丢弃,可移植的明文以普通文本身份带走(带 [接手前由 kimi 留下的思考记录] 前缀),工具调用 id 按目标厂商重铸,遇到强制要求凭证的接收端再单独补发。
  • 重复调用全为 0,这是个负结果。本任务的状态全落在工具结果里,而工具结果在三条臂里都完整保留——所以思考带不带走并不影响模型接着往下做。真正卡住接管的是格式与凭证,不是思考内容。若关键状态只存在于思考中(例如模型在思考里排掉了某个方案却没写进任何工具调用),差异才会显现。
  • 带思考要付钱,但买的是确定性。gemini → kimi 三条臂轮数相同(均 3 轮),输入 token 却从剥离的 2,062 涨到直传的 4,113。步数没省,钱省不下来。
  • 本次结论的适用边界要写清楚。仅 2 家厂商、2 个有向组合、6 个单元;Anthropic 缺席。厂商接口约定演进很快,具体字段名与报错文案都可能已变,重跑前需自行确认。

八、运行元信息与证据清单

实验5-1(跨厂商轨迹接管)
run_idexp5-1-handoff-20261001T142610Z-record
起止(UTC)14:26:10.816 → 14:29:11.356
环境Python 3.12.12 · macOS-15.6-arm64 · requests 2.33.1
模型kimi-k3 · gemini-3.5-flash
claude-haiku-4-5-20251001(未参与)
范围2 providers, 2 directed pairs, 3 arms, 6 cells
缺席原因ANTHROPIC_API_KEY in local .env is a placeholder
离线测试28 passed in 0.15s(exit 0,无外部 API)
runner_statusfinished
证据文件(sha256 前 12 位)
文件臂状态sha256
kimi-to-gemini-naive.jsonnaive40083bcb93d45e5
kimi-to-gemini-strip.jsonstrip40067cf2398d590
kimi-to-gemini-neutral.jsonneutral2000fde7abc6b92
gemini-to-kimi-naive.jsonnaive200e00f34326fbd
gemini-to-kimi-strip.jsonstrip20046b2bd6c58b8
gemini-to-kimi-neutral.jsonneutral200862af410072c
summary.json 8666beb045dd
manifest.json ed50db770358
execution.json aafe6f778c81