Agentpath
原理3 / 31 节 · 预计 30 分钟

一次模型调用里发生了什么: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", ... } ] }
  ]
}

三件事值得记住:

  1. 模型没有记忆。上一轮说过的话,是因为你又发了一遍它才知道。 所谓「对话」是每次把全部历史重新寄过去。
  2. systemtools 每轮都要重发。它们不变,但每轮都在算钱、都在占窗口。
  3. max_tokens 是给输出留的位置,它和输入一起挤同一个上下文窗口。

token:计价单位,也是配额单位

token 不是字符也不是词,是分词器切出来的片段。粗略口径(够用就行):

内容大致换算
英文散文1 token ≈ 4 字符
中文1 汉字 ≈ 0.6–1 token
代码比同长度英文更贵(缩进、符号都吃 token)
JSON / 日志最贵的一类,大量重复符号
×中文不是「一字一 token」

不同分词器差别很大,同一段中文在两家模型上能差 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)、erroraborted循环的分支判断全靠它,写错就是死循环或半途而废。
  • thinking / reasoning 档位 —— 更深的思考换更高的成本和延迟。Pi 把它做成 off / minimal / low / medium / high / xhigh / max,能按模型能力自动收敛。
!stop_reason = length 是个陷阱

输出被 max_tokens 截断时,最后那个工具调用的 JSON 可能是残缺的。 harness 必须识别这种情况并重试或续写,不能当成正常结果往下走。

延迟:用户能感觉到的两个数

  • TTFT(首 token 时间)—— 决定「它是不是卡住了」的观感。所以要流式。
  • TPS(每秒 token)—— 决定长回复的体感。

agent 的总耗时里,模型时间往往不是大头 —— 工具执行(跑测试、装依赖)经常更久。 优化前先量:哪一段最慢,往往和直觉相反。

检查点单选

一个 agent 每轮都在系统提示词开头注入「当前时间: 2026-08-25 14:03:12」。这会造成什么?

这一节的结论

  1. 模型无记忆,每轮重发全部历史;系统提示和工具声明每轮都在花钱。
  2. 窗口 = 输入 + 输出,超了是报错不是截断。
  3. 缓存靠稳定前缀,动态内容往后放。
  4. stop_reason 是循环的方向盘,length 要特别处理。
  5. 先量再优化 —— 慢的那一段常常是工具,不是模型。

延伸资料