实验课:真实帧逐帧回放,同一次运行换价目重算
本课要做的:用真实录制检验本章的三个结论——「三家线格式结构不同」「流式参数要按调用累积到结束信号」「账单的形状随负载变、缓存只省输入」;顺带确认页面上的拼装器、计价函数与原实现一致。
需要的时间:约 45 分钟。全部在浏览器里回放,不联网,不调用任何模型。
数据从哪来
- T5 真实录制(项目线,Apple M5 Max, 128 GB,Qwen/Qwen3-8B-MLX-4bit,mlx_lm.server on 127.0.0.1:8080 (OpenAI-compatible /v1/chat/completions)):agent 全程流式 9 轮;并行调用探针 6 帧;开思考的一轮 706 帧。每一帧都是原样的
data:行和到达时刻(客户端测;耗时受本机同时运行的其他负载影响,只能粗看;token 与轮数可比)。 - 按官方文档构造:Anthropic Messages 与 OpenAI Responses 的事件流,内容取自并行探针那条真实回复,切分点是构造的。不是录制:规则禁止接需要 API key 的云端 provider。
- 账单:7 次真实运行的逐轮 usage(服务端报告;本机服务不报缓存写入)× 两家官方价格页快照(2026-09-26)。
- 自检:拼装器是项目
miniagent/stream.py两个类的逐行移植,拼出的消息与录制逐字节比对;计价是 PicalculateCost的逐行移植,与 bun 跑的 Pi 原文、python 跑的项目cost.py与 CoreCoder 原文逐项比对。结果都显示在各组件底部。
实验台一 · 帧
任务 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 放时间戳」的虚线柱子,哪个是实测、哪个是推算?两者为什么是同一种失效?
实验台四 · 时间
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 的验收材料(见章首页)。
读数时注意
- token 不是云端的 token:账单里的数都是本机 Qwen3-8B 的 tokenizer 计数,思考长度也是这个 8B 模型的。Anthropic 价格页写明它从 Claude 4.7 起换了 tokenizer,同一段文字约多 30%。账单回答的是「如果这些 token 按这个价目」,不是任何云端的真实账单。
- 命中是本机服务的命中:mlx_lm.server 的前缀缓存跨请求共享(LRU)、没有最小长度、不报写入;云端的规则(最小长度、断点、TTL)在组件里用虚线「推算」叠加上去。
- 构造的事件流只证明拼装逻辑:它证明「按文档的事件模型,拼装器能拼对、朴素写法会错」,不证明云端真实的分段方式或时间节奏。
- 耗时只能粗看:录制时本机有其他任务同时占用。 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条项目实测(T5,2026-09-26,本机 mlx_lm.server,单次录制)。帧的顺序与内容是确定的记录,毫秒数受负载影响。 出处待补
- 起点 写死一家的线格式CoreCoder(OpenAI 兼容 + litellm 兜底)、本课项目 miniagent(标准库 urllib)解决了最短的路:CoreCoder 的
llm.py净 259 行,换 provider 只改 base URL 与 key。卡在同一段带工具调用的对话,换成 Anthropic Messages,9 个语义位置里 9 个换了路径或类型(参数从字符串变成对象,5 条消息变 3 条);Codex 干脆不认 Chat:配置wire_api = "chat"直接报错。 - 第一步 加一层抽象,或只留一种用满它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包打了补丁;而且抽象挡不住流式与缓存的细节。 - 第二步 流式:按调用分别累积,结束才解析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 倍。
- 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 讲怎么量「它到底修得好不好」。