目标:让执行到中途的差旅 Agent 换一家模型继续完成任务,比较原样字段映射直传(naive)、剥离思考与凭证(strip)、保留可移植文字并转换调用结构(neutral)三种做法。
本次完成的是用户确认的双厂商范围,完整三家协议尚未通过。 本地 ANTHROPIC_API_KEY 为占位值,因此没有运行 Anthropic 相关方向。实际覆盖 Kimi → Gemini、Gemini → Kimi 两个方向 × 三条臂,共 6 个单元;第五章完整协议要求三家厂商的六个方向 × 三条臂,共 18 个单元。
命令
本次使用已有 .venv,通过 python-dotenv 在运行器导入前加载实验目录的 .env。运行器的实际参数与环境见 执行记录。等价复现命令如下,每次使用新目录保存结果:
cd chapter5/provider-failover
run_dir="validation/runs/exp5-1-handoff-$(date -u +%Y%m%dT%H%M%SZ)-replay"
../../.venv/bin/python - "$run_dir" <<'PY'
from dotenv import load_dotenv
import runpy, sys
load_dotenv(".env")
out = sys.argv[1]
sys.argv = ["run_handoff.py", "--pairs", "kimi:gemini", "gemini:kimi", "--out", out]
runpy.run_path("run_handoff.py", run_name="__main__")
PY
../../.venv/bin/python summarize.py "$run_dir"
本次运行目录为 exp5-1-handoff-20261001T142610Z-record,北京时间 2026-10-01 22:26:10 开始、22:29:11 结束,运行器耗时约 181 秒。模型为 kimi-k3 和 gemini-3.5-flash,本次所有 API 结果都是新调用,没有复用历史响应。
实验协议
任务为北京至东京 3 晚 4 天的出差预算,使用四个本地确定性工具查询机票、每晚住宿、每日餐费和日元汇率。模型调用使用真实厂商 API;价格与汇率是固定测试数据,正确总额为:
3,200 + 18,000 × 3 × 0.048 + 6,000 × 4 × 0.048 = 6,944 元
三条臂仅转换源厂商的历史步骤,目标厂商自己产生的思考与签名继续保留。中立臂向 Gemini 交接时,将源厂商的工具调用及结果转为普通文本,以避开无法提供 Gemini 签名的历史原生调用。
运行器在已执行工具调用数达到 2 后,于下一轮直接切换厂商。outage 中的 [429, 429, 503] 为人为设定的故障标记,本次没有真实注入这些 HTTP 响应,也没有执行独立的熔断状态机;实验验证的是切换后的格式兼容性与任务续跑。
运行结果
| 方向 | 做法 | 首个接管 HTTP | 数据齐备 / 总额正确 | 切换后成功轮数 | 输入 token | 输出 token | 重复调用 |
|---|---|---|---|---|---|---|---|
| Kimi → Gemini | 直传 | 400 | 否 / 否 | 0 | 0 | 0 | 0 |
| Kimi → Gemini | 剥离 | 400 | 否 / 否 | 0 | 0 | 0 | 0 |
| Kimi → Gemini | 中立 | 200 | 是 / 是 | 2 | 1,331 | 593 | 0 |
| Gemini → Kimi | 直传 | 200 | 是 / 是 | 3 | 4,113 | 1,314 | 0 |
| Gemini → Kimi | 剥离 | 200 | 是 / 是 | 3 | 2,062 | 431 | 0 |
| Gemini → Kimi | 中立 | 200 | 是 / 是 | 3 | 3,732 | 692 | 0 |
中立臂在两个已测方向上均接管成功并完成任务(2/2),直传与剥离各为 1/2。四个成功单元都给出准确的 6,944 元及正确费用明细。各臂的重复调用均为 0,因此本次没有观察到中立格式在减少重复调用上的优势。
Kimi → Gemini 的两个失败都是 Google 真实返回的 HTTP 400,原始错误为 Function call is missing a thought_signature in functionCall parts。剥离思考不能消除该错误,因为接收端要求签名的是历史工具调用结构;中立臂将这些调用转为文本后成功继续查询汇率并完成计算。原始请求、错误响应和最终回答均保存在六份单元 JSON 中。
表中 token 仅累计切换后成功请求的厂商用量,轮数也只计成功响应。两个 400 单元没有返回 usage,因此其 0 token 不表示已验证零费用。包括切换前请求,本次成功 API 响应报告了输入 16,415、输出 5,062 个 token;未按价格表换算货币费用。
在三条臂都成功的 Gemini → Kimi 方向上,中立臂输入比剥离多 1,670 个 token(约 81%),两者均需 3 个成功轮次。这是本次样本的观察,不能视为稳定的成本比例。
验证与证据
离线测试命令在仓库根目录执行:
pytest chapter5/provider-failover/tests -q
结果为 28 passed in 0.15s。测试使用本机 pytest 9.1.1,验证已有的轨迹转换、凭证保留、调用配对和错误处理等逻辑,不调用外部 API。
汇总后另执行本次留存的复核脚本:
cd chapter5/provider-failover
../../.venv/bin/python validation/runs/exp5-1-handoff-20261001T142610Z-record/verify_run.py
独立复核结果为 passed:双厂商的六个单元覆盖完整;六份记录的 SHA-256 与 manifest 一致;八份源文件快照哈希一致;原始响应中的工具调用、确定性工具结果、任务参数、首个接管状态、成功轮数、token 和重复调用计数均与记录一致。四个成功回答都包含精确总额。此处 passed 只适用于用户确认的双厂商范围。
限制
完整协议的“三条臂各覆盖六种厂商组合”门禁为 false;没有验证 Anthropic 的签名校验与相关四个切换方向。manifest 中“中立臂在全部厂商组合上切换成功”为 true,实际含义仅是两个已测方向通过,不能解释为六个方向均已验证。
本次 Kimi 在一轮中并行调用三个工具,切换实际发生在第三次调用之后;Gemini 的源轨迹则在第二次调用后切换。因此不同方向的剩余工作量不同,不能直接用 2 轮与 3 轮比较接管效率。
部分模型回答使用了“实时”“最新汇率”等措辞,但工具只返回固定测试数据,这些描述不成立。总额正确不表示回答中的所有陈述都准确。
每个单元只运行一次,三条臂各自重新生成源轨迹,没有固定相同前缀,也没有设置固定采样种子或估计置信区间。思考文本长度、后续回答风格和 token 差异都可能受随机生成影响。固定任务的工具结果已包含主要状态,不能据此推断思考中携带关键隐含状态时的表现;也不能据此证明真实宕机下的完整故障恢复能力。