一次模型调用里发生了什么:token、采样、成本
不懂 token 就调不好 agent —— 上下文爆掉、账单失控、复读机般的输出,根子都在这一节。
学完这节你能做到
- 算出一次请求的输入/输出 token 与费用,并解释缓存命中怎么改变这个数
- 解释 temperature、top-p、stop 对 agent 稳定性的影响
- 说清上下文窗口是硬约束,以及超限时的三种处理方式
这一节是底子。不懂 token 和缓存,后面「为什么第十轮突然崩」「为什么账单是别人的十倍」 这两个问题永远说不清。
一次请求由什么组成
{
"model": "claude-sonnet-5",
"max_tokens": 8000,
"system": "你是一个在本地工作目录里干活的助手……",
"tools": [ /* 每个工具的 name + description + schema */ ],
"messages": [
{ "role": "user", "content": "改一下这个函数" },
{ "role": "assistant", "content": [ { "type": "tool_use", ... } ] },
{ "role": "user", "content": [ { "type": "tool_result", ... } ] }
]
}
三件事值得记住:
- 模型没有记忆。上一轮说过的话,是因为你又发了一遍它才知道。 所谓「对话」是每次把全部历史重新寄过去。
system和tools每轮都要重发。它们不变,但每轮都在算钱、都在占窗口。max_tokens是给输出留的位置,它和输入一起挤同一个上下文窗口。
token:计价单位,也是配额单位
token 不是字符也不是词,是分词器切出来的片段。粗略口径(够用就行):
| 内容 | 大致换算 |
|---|---|
| 英文散文 | 1 token ≈ 4 字符 |
| 中文 | 1 汉字 ≈ 0.6–1 token |
| 代码 | 比同长度英文更贵(缩进、符号都吃 token) |
| JSON / 日志 | 最贵的一类,大量重复符号 |
不同分词器差别很大,同一段中文在两家模型上能差 30%。真要算准就用官方的 count_tokens 接口或分词库,不要拿字符数硬推。
上下文窗口是硬约束
窗口 = 输入 + 输出,一起算。200k 窗口留 16k 输出,可用输入就是 184k。 超了不是「截断」而是直接报错,整个请求失败。
超限的三种处理方式,代价各不相同:
| 做法 | 保住了什么 | 丢了什么 |
|---|---|---|
| 截断历史 | 简单、便宜 | 早期约束和决定,模型会重复犯已纠正过的错 |
| 摘要压缩(compaction) | 主线目标和进展 | 细节,比如某个报错的具体行号 |
| 外置 + 按需读取 | 什么都不丢 | 需要设计:写文件、建索引、加检索工具 |
Pi 默认走第二条:contextTokens > contextWindow − reserveTokens 时自动压缩,
reserveTokens 默认 16384、keepRecentTokens 默认 20000(近 20k 不压)。
L1 有一整节算这笔账。
prompt caching:agent 能用上的最大一笔折扣
同一段前缀重复发送时,提供方可以命中缓存,缓存读取价通常是输入价的一折左右。 agent 的请求恰好是「前缀高度重复」的形态 —— 系统提示、工具声明、早期历史每轮都一样。
命中的条件很朴素:前缀逐字节相同。所以:
[ system ][ 工具声明 ][ 早期历史 ][ 新增的一轮 ]
↑ 稳定 ─────────────────────↑ ↑ 只有这一段是新的
失效的条件也很朴素 —— 前面任何一个字节变了,从那里往后全部重算:
- 系统提示里注入当前时间(每轮都不同)→ 缓存命中率归零
- 工具列表顺序不稳定(比如从 Map 里遍历出来)→ 同上
- compaction 重写了历史 → 那一轮全价
按「越稳定的越靠前」排:系统提示 → 项目指令 → 工具声明 → 历史。 任何动态内容(时间、随机 id、当前分支)放到最后一条用户消息里,别塞进系统提示。
采样参数与 agent 的稳定性
- temperature —— agent 场景通常压低(0–0.3)。你要的是可复现的工具调用, 不是创意。
- stop / stop_reason ——
end_turn(说完了)、tool_use(要调工具)、max_tokens(输出被截断,Pi 记作length)、error、aborted。 循环的分支判断全靠它,写错就是死循环或半途而废。 - thinking / reasoning 档位 —— 更深的思考换更高的成本和延迟。Pi 把它做成
off / minimal / low / medium / high / xhigh / max,能按模型能力自动收敛。
输出被 max_tokens 截断时,最后那个工具调用的 JSON 可能是残缺的。 harness 必须识别这种情况并重试或续写,不能当成正常结果往下走。
延迟:用户能感觉到的两个数
- TTFT(首 token 时间)—— 决定「它是不是卡住了」的观感。所以要流式。
- TPS(每秒 token)—— 决定长回复的体感。
agent 的总耗时里,模型时间往往不是大头 —— 工具执行(跑测试、装依赖)经常更久。 优化前先量:哪一段最慢,往往和直觉相反。
一个 agent 每轮都在系统提示词开头注入「当前时间: 2026-08-25 14:03:12」。这会造成什么?
这一节的结论
- 模型无记忆,每轮重发全部历史;系统提示和工具声明每轮都在花钱。
- 窗口 = 输入 + 输出,超了是报错不是截断。
- 缓存靠稳定前缀,动态内容往后放。
stop_reason是循环的方向盘,length要特别处理。- 先量再优化 —— 慢的那一段常常是工具,不是模型。