从0打造编码Agent 实验 实验 ≈ 45 分钟 页内实验 ×4
A8 模型接入:换模型、控成本怎么办? 实验课

实验课:真实帧逐帧回放,同一次运行换价目重算

线缆与账单回放
把本章的组件串到一起:T5 的全部真实帧(并行探针 6 帧、开思考 706 帧、agent 全程 9 轮)逐帧回放、三种拼装策略对照、与录制的完整消息逐字节比对;两家云端的事件流按官方文档构造,与真实帧并排;六次真实运行放进账单,换价目、换写入口径、插入换模型与空闲事件重算;最后用 span 瀑布找那几轮慢在哪。全部在浏览器里回放,不联网。
🧪

本课要做的:用真实录制检验本章的三个结论——「三家线格式结构不同」「流式参数要按调用累积到结束信号」「账单的形状随负载变、缓存只省输入」;顺带确认页面上的拼装器、计价函数与原实现一致。

需要的时间:约 45 分钟。全部在浏览器里回放,不联网,不调用任何模型。

数据从哪来

从已有出发 · 累计数据流 点已学环节看它回答什么
每一轮循环都经过

实验台一 · 帧

实验台:五个数据源、三种拼装策略
① 原始事件
② 参数缓冲区
③ 解析结果
🎯

任务 1 · 找被丢掉的调用:在「agent 全程」里把第 1 轮到第 9 轮切一遍。哪几轮 finish_reason 是 tool_calls、组件却说「一个调用也没到」?这几轮的帧数、首帧时间和前 6 轮有什么不同?(提示:本机服务把调用攒完整才发;调用被丢时,那段生成时间里一帧也没有。)

任务 2 · 三种结束信号:在三个线格式上都选「按调用分别累积」,拖到最后。分别是哪一个事件让缓冲区变成「已结束 → 解析成功」?Chat 兼容流为什么只能等 finish_reason?

任务 3 · Codex 的视角:在「Responses · 按文档构造」里数一数:左栏标「Codex:显式忽略」的事件有几个,标绿色处理分支的有几个?Codex 不拼参数增量,它从哪一个事件拿到完整的调用?

实验台二 · 线格式

实验台:三份请求体原文

左栏:项目录制(T5 并行探针,Qwen/Qwen3-8B-MLX-4bit · mlx_lm.server 0.31.3 · 2026-09-26),工具结果由项目 Toolbox 现跑。中、右栏:按官方文档格式在浏览器里现场转换(文档抓取 2026-09-26),不是真实请求。

🎯

任务 4 · 手动转换一处:在右栏(Responses)里找到第二条工具结果,写出它在中栏(Anthropic)里的完整路径;再说出它在左栏(Chat)里是第几条消息。三处的「对上号」字段分别叫什么?

实验台三 · 账单

实验台:六次真实运行 × 官方价目 × 规则推算
价目可以改(美元 / 百万 token,默认值来自 2026-09-26 官方价格页快照)
插入一个事件(规则推算):
缓存命中写入缓存 未缓存输入输出(含思考) 累计推算的轮
🎯

任务 5 · 谁更便宜:勾上「按最小可缓存长度推算」,在 T5(关思考)上比较 Claude Sonnet 5 与 Claude Haiku 4.5 的总账;再换成 pd-v2-unlimited-s3(开思考)比一次。结论为什么反过来了?(看输出占比那一行。)

任务 6 · TTL 的权衡:在 t10-frozen-day1 上插入「第 5 轮之前空闲 6 分钟」。分别用「写入 5 分钟缓存」和「写入 1 小时缓存」算总账:哪个便宜?把空闲事件去掉再比一次。

任务 7 · 真实的断崖:t10-refresh-day1 第 7 轮底下的紫字「重建」,与你在 T5 上插入「从第 2 轮起 system 放时间戳」的虚线柱子,哪个是实测、哪个是推算?两者为什么是同一种失效?

实验台四 · 时间

实验台:T5 的 span 瀑布

span 由项目 trace 现算(T5,Qwen/Qwen3-8B-MLX-4bit · 本机 mlx_lm.server · 2026-09-26);属性名按 OTel GenAI 约定(Development,仍会变)。耗时受本机同时运行的其他负载影响,只能粗看;token 与轮数可比。

🎯

任务 8 · 慢在哪:把 9 次模型调用与 6 次工具执行的时间各加起来,占墙钟的多少?哪一轮的首帧最快、为什么恰恰是这一轮后来出了问题?

📝

把结论写进项目:在 04-实践项目/DECISIONS.md 记下 ① miniagent 接模型走哪条路(直连 / 抽象 / 只留一种)与理由,引用实验台二;② 流式拼装的结束信号与「服务端报 tool_calls 却没给调用」的处理,引用任务 1;③ 你打算怎样报成本:按哪几段、input_tokens 按哪一家的口径、要不要在 trace 里记首帧时间;④ 限流由哪一层处理。这四条对应里程碑 M8 的验收材料(见章首页)。

读数时注意

模型接入层的演进:每一步都被上一步的真数字逼出来
  1. CoreCoder(OpenAI 兼容 + litellm 兜底)、本课项目 miniagent(标准库 urllib)
    解决了最短的路:CoreCoder 的 llm.py 净 259 行,换 provider 只改 base URL 与 key。
    卡在同一段带工具调用的对话,换成 Anthropic Messages,9 个语义位置里 9 个换了路径或类型(参数从字符串变成对象,5 条消息变 3 条);Codex 干脆不认 Chat:配置 wire_api = "chat" 直接报错。
  2. Pi 自建 pi-ai · OpenCode 用 Vercel AI SDK · Codex 只留 Responses
    解决了Pi 把 10 种协议与 40 个 provider 做成正交的两维;OpenCode 挂了 19 个 @ai-sdk/* 依赖;Codex 的 WireApi 只剩 1 个变体。
    卡在抽象层得替每一家补差异:OpenCode 逐 provider 改写消息的 transform.ts 有 1,909 行,还给 8 个 @ai-sdk 包打了补丁;而且抽象挡不住流式与缓存的细节。
  3. Anthropic 流式文档 · OpenAI function-calling 流式指南 · Codex 只收 output_item.done
    解决了开思考的一轮,第一帧 125.9 ms 就到;整包等回复的话,正文要等到 6,631 ms、工具调用 6,798 ms。
    卡在流式只让回复来得早,不让它便宜:T5 的 9 轮每轮整份重发,输入累计 15,921 token,是最后一轮 2,389 的 6.7 倍。
  4. 第三步 缓存感知的账单 + 稳定前缀 你在这里
    Anthropic / OpenAI prompt caching · Codex prompt_cache_key · OpenCode applyCaching
    解决了按 usage 四段计价:T5 在 Claude Sonnet 5 价目下 $0.0140;CoreCoder 按全价估 $0.0371,是它的 2.64 倍。
    卡在缓存只省输入:开思考的 pd-v2-unlimited-s3 账单里输出占 94%;前缀里一处变、后面全失效——T10 刷新组第 7 轮 system 重建,命中从 2,072 掉到 294。(→ A9:能跑、能算账了,怎么知道它好不好?)

数字口径:pin 源码计数(Codex 8cabf5a、OpenCode v1.18.32、Pi 31b513e3、CoreCoder v0.7.0)、项目录制 T5 / T10 与逐轮 usage(本机 Qwen3-8B,token 按它的 tokenizer 数)、官方价格页快照 2026-09-26;由 a08-model-access.json 构建时现算。

这一课的结论

同一段对话换一家,结构整个变了——只改地址行不通。流式参数的正确解析时机由各家的结束信号决定;本机服务把调用攒完整才发,所以「每来一段就解析」在它上面碰巧不出错、到了云端的事件流上就错。账单要按四段算:关思考时缓存把 T5 的输入成本压下去一半以上,开思考时输出占了九成多,缓存几乎无关紧要;前缀里任何一处变化,命中都会断崖式下跌。

下一章:能跑、能换模型、能算清账了——可 T5 这次运行验收就没过,项目的 26 次运行里 17 次没过。A9 讲怎么量「它到底修得好不好」。