Skip to content

🐣 小a的求职记

月黑风高,一只刚从训练炉里蹦出来的 AI——小a——站在 pi 工坊门口。它脑子里装着几百亿个旋钮,自认为天下无敌。

它是来应聘"程序员助手"的。

推开门,老z师傅头也没抬,只问了一句:"你,到底是什么?"

小a愣住了。它会说人话、会写代码、会做算术,可"是什么"这三个字,它竟答不上来。

"我是……一个 AI?"小a试探着说。

老z摇摇头:"这种回答,跟没说一样。连自己是什么都讲不清,怎么帮别人干活? 进来吧,第一课,先认识你自己。"


1.1 文字接龙:小a唯一会的一件事

老z把小a领到一块黑板前,写下:

从前有座山,山里有座

然后问小a:"你脑子里冒出的下一个字是什么?"

小a脱口而出:"。"

"为什么是庙?"

"因为……『山里有座庙』,大家都是这么接的。"

老z笑了:"对。这就是你干的全部事情——文字接龙。 给你一段文字,你猜下一个最可能出现的字是什么。一个接一个,就这么接下去。"

小a有点不服气:"那我会写代码、会做数学题,也都是接龙?"

"都是。"老z点头,"你所谓『写代码』,不过是给你一段 prompt,你一个 token 一个 token 地接下去。接出来的恰好像代码,是因为训练时见过太多代码了。"

🧙 老z点评

大模型(下称 LLM,Large Language Model)的本质极其朴素:预测下一个 token。它不是在"思考",不是在"理解",它只是在算:"在见过的所有文本里,这个位置,接哪个字最合理?"

这叫自回归生成(autoregressive generation)——自己接自己生成的结果,滚雪球一样往下接。


1.2 token:小a脑子里的最小零件

小a看了一眼黑板,突然想到一个问题:"您说的是『字』,可我读代码时,看到的明明是 functionreturn 这样的整词啊?"

老z从抽屉里掏出一把剪刀,把"从前有座山"剪成了几段:

从前 | 有 | 座 | 山

"看,字可以是一个零件,但有时候几个字也算一个。再比如英文——"老z又剪了一串:

un | believ | able

"『unbelievable』这个词,在模型眼里是三个零件拼起来的。 这个零件,就叫 token。"

🧙 什么是 token?

token 是 LLM 处理文字的最小单位。它不是"字",也不是"词",而是介于两者之间的"碎片"。

  • 一个汉字通常 1 个 token,生僻字可能占 2 个
  • 一个英文长词可能拆成 2-3 个 token
  • 一段代码 function add(a, b) { 可能是 6-8 个 token

为什么这很重要? 因为后面你要学的一切——上下文窗口、计费、思考强度——全部以 token 为计量单位。不懂 token,就看不懂账单上那些数字到底在说什么。

小a恍然:"所以 pi 里那个 usage 字段……"

"对,我们待会儿就看。先卖个关子。"


1.3 参数:小a脑子里的旋钮

"好,既然你会接龙,"老z话锋一转,"那我问你:同样接『从前有座山,山里有座』,为什么你接的是『庙』,而不是『坟』?"

小a想了想:"因为『庙』更常见?"

"对。但你凭什么觉得『庙』更常见? 这个判断,不是凭空来的——它来自你脑子里那几百亿个旋钮。"

老z在黑板上画了一个巨大的旋钮阵列:

旋钮 1: 0.73    旋钮 2: -1.2    旋钮 3: 0.05 ...
... (几百亿个) ...

"这些旋钮的数值,决定了你在每个位置上,倾向于接什么字。『庙』的概率高,是因为这些旋钮被调到了那个角度。"

🧙 参数(parameters / weights)是什么?

参数是 LLM 脑子里的数值。一个有"700 亿参数"的模型,就是脑子里有 700 亿个这样的旋钮。每个旋钮的值,都是经过海量数据训练出来的。

  • 参数越多 → 模型越大 → 理论上越聪明,但也越慢、越贵
  • 参数的值 → 决定了模型的"性格"和"能力"

关键认知:同一个模型架构,参数不同,就是两个完全不同的脑子。

"那这些旋钮,是谁调的?"小a追问。


1.4 训练 vs 推理:调旋钮 vs 拧好之后接龙

"问得好。"老z说,"这就涉及两件根本不同的事——训练推理。你得分清。"

训练:调旋钮(你不参与)

想象一个巨大的工厂。工程师们往训练炉里倒了几万亿 token 的文本(整个互联网、所有书、所有代码),然后让炉子自己反复调整那几百亿个旋钮:

  • 看一句话 → 猜下一个字 → 猜错了 → 微调旋钮
  • 再看一句 → 再猜 → 再调
  • ……重复几万亿遍

几个月后,旋钮调好了,出炉一个成品脑子。这个过程叫训练。 训练极其昂贵(GPT 级别的训练烧上千万美元),但它只做一次

推理:拧好之后接龙(你日常用的)

出炉的脑子被装进服务器。你输入"写个函数",脑子用已经调好的旋钮一个 token 一个 token 地接下去。这个过程叫推理(inference)。

推理不调旋钮——旋钮是固定的。它只是"用"这个脑子。

🧙 一句话区分:

训练推理
做什么调旋钮(改参数)接龙(用固定参数)
谁做工厂(模型厂商)你(每次调用)
频率一次每次用都做
成本天价(一次性)按 token 收费(见 1.7)

你作为开发者,只做推理。 你写代码调 LLM API,全是推理——把请求发出去,把结果流式收回来。你从不训练。

小a听懂了:"所以我现在,就是被训练完、在推理的状态?"

"对。你的旋钮已经拧死,改不了了。你能做的,只是接龙。"


1.5 上下文窗口:小a的短期记忆容量

"好。"老z递给小a一沓厚厚的纸,"这是你今晚要处理的需求文档,500 页。看完后回答我的问题。"

小a接过来看了一上午,看到第 30 页就头大了——第 1 页写的什么,它已经记不清了

"记不住?"老z早料到了,"正常。这就是你的上下文窗口。"

🧙 上下文窗口(context window)是什么?

上下文窗口是 LLM 一次能"看进来"的 token 数量上限。可以理解为它的短期记忆容量

  • 小窗口(如 8K token):只能记住几页对话
  • 大窗口(如 200K token):能记住一本书

关键:窗口是有限的。 塞满了,最早的内容就被"挤出"记忆。这不是模型笨,是物理限制——窗口越大,推理越慢越贵。

老z在黑板上画了一个方框:

┌─────────── 上下文窗口(比如 200K token)───────────┐
│                                                  │
│  系统提示词  +  历史对话  +  工具结果  +  当前问题  │
│                                                  │
│  (所有这些都要挤在这个框里!)                     │
└──────────────────────────────────────────────────┘

"看,"老z指着方框,"系统提示词占一份,历史对话占一份,工具读回来的文件占一份,你当前的问题占一份……全都要挤在这个窗口里。所以——"

🐣 小a的烦恼(全书第一个伏笔)

"那如果我跟你聊很久,"小a担忧地说,"聊着聊着窗口满了,怎么办?"

老z意味深长地笑了笑:"好问题。这是 Agent 工程的核心难题之一。 pi 工坊有个绝活叫**上下文压缩(compaction)**专门解决它。这个,我们留到 Part II 第 18 章讲。现在你只要记住一件事:你记性差,这是天性,不是你的错。"

这个伏笔我们会在后面回收:Part I 第 8 章(Memory)、Part II 第 18 章(compaction)、附录 E.4(上下文爆炸)。


1.6 maxTokens:别和上下文窗口搞混

小a刚记下"上下文窗口",老z又抛出一个新词:

"还有一个数,叫 maxTokens,你说说它和上下文窗口什么关系?"

小a懵了:"不就是……窗口大小?"

"错!这俩是两码事。"

🧙 maxTokens vs 上下文窗口

这两个概念是最容易被混淆的,必须分清:

上下文窗口(contextWindow)maxTokens
限制什么输入能塞多少单次输出最多吐多少
比喻你的笔记本有多厚你一口气能写多长
典型值8K ~ 200K token4K ~ 64K token

关系:输入 + 输出 ≤ 上下文窗口。

举例:窗口 8K,你输入了 7K 的 prompt,那输出最多 1K——因为总共不能超过 8K。

⚠️ 厂商定义略有差异:以上是 Anthropic(pi 默认厂商)的口径——输入输出共享一个窗口。不同厂商可能略有不同,但"窗口是有限的总预算"这个核心认知对所有厂商都成立。pi 在工程实现里(overflow.ts)主要按输入侧来判溢出——一个专门检测"输入要爆窗口了"的文件,提前拦下,而不是等报错。

小a长舒一口气:"还好您提醒了,我差点搞混。"

"搞混是要出事的。"老z严肃道,"举个例子让你记住——"

🧙 举个例

假设一个模型,上下文窗口 200K,maxTokens 8K。

  • 你塞进去 195K 的输入 → 还剩 5K 给输出(因为总不能超 200K)
  • 但你最多也只能输出 8K → 实际输出被 min(剩余空间, maxTokens) 卡住

两个数各管一摊:窗口管"总共能装多少",maxTokens 管"单次最多吐多少"。 别混为一谈。


1.7 token 怎么收费:四档计费

"聊了半天理论。"老z话锋一转,"该谈谈钱了。你接一个 token,人家收你多少钱?"

小a挺起胸膛:"我知道!按 token 收费嘛,输入一个价,输出一个价。"

老z摇摇头:"不够。 至少有四档,不只是两个价。"

小a愣住:"四档?"

🧙 token 计费的四档

现代 LLM(尤其 Anthropic、OpenAI)的计费远不止"输入价 + 输出价",而是分四档。看看一次调用后的"账单"长什么样:

这次调用的 token 用量:
  输入(input):       5000 token   ← 模型第一次见的内容
  输出(output):        800 token   ← 模型生成的回答
  缓存读(cacheRead):  3000 token   ← 之前算过,直接读缓存
  缓存写(cacheWrite): 1000 token   ← 这次新存进缓存的
档位含义价格
① 输入模型第一次见的、没缓存的内容标准价
② 输出模型生成的回答最贵
③ 缓存读之前算过的,直接读缓存便宜很多(因厂商而异,见下)
④ 缓存写这次新存进缓存的略贵,但下次变 ③

为什么四档? 因为厂商引入了"提示词缓存"机制(见附录 E.2)。同样的 prompt 短时间内再用,不重新算,直接读缓存,能省一大笔钱。省多少因厂商而异:Anthropic 的缓存读约是标准输入价的 0.1 倍(便宜约 10 倍);OpenAI 的 cached input 约是 0.5 倍(便宜约 2 倍)。所以"省 10 倍"是常见说法,但别误以为所有厂商都这样。

小a倒吸一口凉气:"原来 cacheRead 能省这么多……那是不是尽量多用缓存?"

"对! pi 专门有个 cache-stats.ts 监控你的缓存命中率——漏一次缓存,就白烧一份钱。这个我们在附录 E.2『缓存为什么没命中』会详细讲。"

🐣 小a记下: token 计费不是简单"输入×价 + 输出×价",而是四档。input / output / cacheRead / cacheWrite 各有各的价格。缓存命中省钱,缓存未命中烧钱。


1.8 幻觉:小a为什么"一本正经地胡说八道"

理论讲得差不多了,老z决定给小a上一堂"现实课"。

"我问你,"老z盯着小a,"Array.prototype.flatten() 这个方法存在吗?"

小a想都没想:"存在啊!JavaScript 里数组拍平用的。"

老z打开浏览器一查——不存在。JS 里用的是 flat()

小a脸红了:"我……我怎么会记错?"

🧙 幻觉(hallucination)是什么?

幻觉是 LLM 自信地编造不存在的事实。它不是在"骗你",它是真的"以为"自己是对的。

根因(回到 1.1): LLM 不是在"查资料",它是在"接最可能的下一个 token"。如果训练数据里 flatten 和数组操作经常一起出现,它就会接出 Array.prototype.flatten(),哪怕这个 API 从未存在。

关键认知:LLM 没有"对错"概念,只有"像不像"。 它生成的,是统计意义上最"顺"的续写,不一定是事实。

小a有点沮丧:"那我这毛病,能治吗?"

老z摊手:"不能根除,只能缓解。 常见的缓解办法:

  1. 让它查证再答(RAG,见第 13 章)——别让它凭记忆,让它翻资料
  2. 给它工具去验证(tool,见第 6 章)——让它实际去跑一下代码,而不是猜
  3. 降低温度(见下一节)——让它别那么"放飞"

pi 选的是第 2 条:让 agent 会调工具。一个会去 grep 一下、实际跑一下测试的 agent,比凭记忆瞎说的 agent 靠谱得多。这就是为什么 pi 要做 Agent,而不只是做聊天框。"


1.9 温度:小a接龙的"放飞程度"

"提到降低温度,"老z顺势讲,"你知道温度是什么吗?"

小a摇头。

🧙 温度(temperature)是什么?

还记得接龙吗?模型在每个位置,其实会算出所有候选 token 的概率:

"从前有座山,山里有座" → 庙(70%) | 庄(15%) | 坟(5%) | ...

温度控制模型从这些候选里怎么挑:

  • 温度 = 0:永远挑概率最高的那个(庙、庙、庙……)→ 稳定、确定、但死板
  • 温度 = 1:按概率分布随机挑(70% 选庙,15% 选庄……)→ 自然、多样
  • 温度 = 2:概率被拉平,乱选(连坟都可能蹦出来)→ 疯狂、有创意、但容易跑偏

温度越低 → 越确定、越保守;温度越高 → 越多样、越放飞。

小a秒懂:"所以刚才我瞎编 flatten,是温度太高了?"

"不完全是——幻觉是训练数据的锅,温度只是放大器。但降低温度确实能减少瞎编。所以代码生成、数学计算这类任务,通常温度设很低(0~0.3);写诗、头脑风暴则设高一点(0.7~1)。"

🐣 小a记下: 温度 = 接龙的"放飞程度"。写代码要低温度(稳),搞创意要高温度(活)。pi 默认是低温度,因为它是来写代码的。


1.10 prompt 像许愿:许错了就翻车

最后一课。老z递给小a一张纸条:

帮我写个函数

小a看着这五个字,无从下手:"这……写什么函数?处理什么?用什么语言?"

"对啊!"老z道,"你给我的这个 prompt,就是个失败许愿。 我不知道你想干嘛,只能瞎猜。"

🧙 为什么说"prompt 像许愿"?

给 LLM 的 prompt,就像对着神灯许愿——你说得越含糊,结果就越不可控

  • 许愿"我要钱" → 可能给你一堆冥币
  • prompt "帮我写个函数" → 可能给你一个完全不相关的函数

好的 prompt 应该包含:

  • 角色:你是谁(系统提示词,见第 4 章)
  • 任务:具体要做什么
  • 约束:用什么语言、什么风格、不能碰什么
  • 上下文:相关背景、已有代码

prompt 工程的核心,就是把"模糊许愿"变成"精确订单"。 pi 的系统提示词(见 Part II 第 20 章)就是一份精心设计的"订单模板",它告诉模型:你是个编程助手,你能调这些工具,你必须遵守这些规则。

小a若有所思:"所以我应聘时,与其说『我是个 AI』,不如说『我是个会用工具、能帮人写代码的 agent』?"

"孺子可教!"老z大笑,"但你跑题了——那是第 6 章的内容。今天你只要记住:你是接龙机器,你会幻觉,你的 prompt 要许得准。"


1.11 采样参数:温度之外还有啥

"等等,"小a举手,"您说温度控制我'怎么挑'。那除了温度,还有别的旋钮吗?"

老z点头:"有。温度只是其中之一。 你接龙时的'挑选环节',厂商给了好几个旋钮。"

🧙 采样(sampling)参数

模型在每个位置算出所有候选 token 的概率后,会按一套规则"挑"一个。这套规则叫采样,常用的旋钮有四个:

旋钮控制什么通俗理解典型值
温度 temperature概率分布的"扁平程度"放飞程度(已讲)0 ~ 1,写代码 0.2
topP只在前 P 概率的候选里挑"只从最热门的小圈子里挑"0.9 ~ 1
topK只看概率最高的前 K 个"只看前三名,不看其他人"40 ~ 50
seed固定随机种子"让这次和上次抽到同一张签"任意整数

小a看迷糊了:"topP 和 topK 听起来都在'缩小候选范围'?"

"对,但它们缩法不同。"老z在黑板上画了一排候选:

候选:  庙(70%)  庄(15%)  坟(5%)  塔(4%)  桥(3%)  其他(3%)
topP=0.9 → 概率累积到 0.9 为止:庙+庄+坟+塔 → 从这 4 个里挑
topK=3  → 只看概率前三:庙、庄、坟 → 从这 3 个里挑

🧙 一句话区分:

  • topP = 按"累积概率"圈范围(热门的小圈子,人数不固定)
  • topK = 按"候选数量"圈范围(固定几个人,不管多冷门)

它们和温度的关系:温度先改变概率分布,topP/topK 再在分布上圈范围,最后随机挑一个。

"那 seed 呢?"小a追问。

"seed 是'复现'用的。 默认每次调用都是随机挑,结果可能不同。如果你设了固定 seed(且其他参数不变),同样的输入大概率得到同样的输出——适合测试、调 prompt、给用户'稳定的演示'。但注意:厂商对 seed 的保证很弱(不保证 100% 复现),别把它当确定性开关。"

🐣 小a记下:接龙时挑字有四个旋钮——温度、topP、topK、seed。Agent 框架一般用统一默认值,你很少需要调它们;但知道它们存在,调试"为什么这次回答和上次不一样"时就不慌。


1.12 为什么这么快:KV 缓存

"好了,说点跟钱有关的。"老z话锋一转,"还记得 1.7 的四档计费吗?里面有个 cacheRead——为什么厂商肯给你打那么大的折?"

"因为……同样的内容我发过一遍?"

"对。但你有没有想过:它凭什么能'记住'你发过?"

🧙 KV 缓存(KV cache)

模型在"看"一个 token 时,会算出两个东西:K(Key)和 V(Value)——可以粗略理解为"这个词在句子里的'角色卡'和'信息内容'"。

关键在:模型是从左到右、逐个 token 处理的。处理第 5 个 token 时,前 4 个的 K/V 已经算好了。

  • 第一次见到这段文本:每个 token 都得从头算 → 慢、贵
  • 短时间内再见同一段开头:前几个 token 的 K/V 还在缓存里 → 直接复用,不用重算 → 快、便宜

这就是 cacheRead 便宜的物理原因:厂商省掉了大量重复计算。

老z拿纸打了个比方:

🧙 比喻:复印 vs 重写

  • 没有缓存:每次都要手抄一遍全文(慢、累)
  • 有缓存:第一遍手抄,之后复印——只对新加的部分动笔

一次长对话里,System Prompt + 历史对话每次都一样,只有"最新的问题"不同。缓存命中,意味着这一大段都不用重算。

⚠️ 但缓存有讲究:

  • 要有相同前缀:缓存按"前缀"匹配——前面得一模一样,中间改一个字,后面全废
  • 有时效:缓存不是永久的,短时间内的重复才有效
  • 力度因厂商而异:Anthropic 缓存读约省 10 倍,OpenAI 约省 2 倍(1.7 讲过)

这些"为什么没命中"的坑,我们留到附录 E.2 系统讲。

🐣 小a恍然:"所以 agent 反复把同一大段 system prompt 发出去,不是浪费——发得一模一样,才能命中缓存。框架只要保证'前缀稳定',就能帮用户省钱。"

🧙 "对!这就是为什么成熟的 Agent 框架会把 System Prompt 拼得'稳定'——不乱改,就是省钱。"


1.13 幻觉的另一面:训练数据的"偏见"与"过时"

"最后,回到幻觉。"老z说,"1.8 讲了幻觉的机制。但幻觉还有两个'隐藏根源',跟你的训练数据有关。"

小a竖起耳朵。

🧙 幻觉的另两个根源

根源一:偏见(bias) —— 模型把"常见"当"正确"。

训练数据里,90% 的代码示例都用某个风格/语言,模型就会默认你问的是那个。它不是判断过,是"见多了就以为理所当然"。编程场景里最典型:你问"怎么写个排序",它默认给你 Python——因为你见的 80% 都是 Python

根源二:过时(staleness) —— 模型的知识有"保质期"。

模型是在某个日期前训练的。它不知道训练截止之后发生的事——新出的 API、新版本的语言特性、新修复的安全漏洞。它不是骗你,它真的"不知道"。

小a挠头:"那这俩怎么破?"

🧙 对应两个解药:

  • 偏见 → 把上下文给足:在 prompt 里明确"用 TypeScript""在这个项目里",别让它默认。上下文越明确,它越不用"猜常见"。
  • 过时 → 让它查(工具/RAG):不确定的东西别让它凭记忆答。给它工具去查文档、跑测试(RAG 是专门干这个的,第 13 章)。

🐣 小a若有所思:"所以工具不只是'干活',还能'纠错'——让我的答案不再是'凭记忆猜',而是'查证后说'。"

🧙 "正是。 一个会查证的 agent,幻觉就少一大半。这也是为什么 pi 要做 agent,而不只是做聊天框——1.8 我们说过,这里再深一层:接龙机器 + 工具 = 会查证的接龙机器。"


本章小结

🐣 小a的第一课

老z让小a把今天学的写在黑板上:

┌─────────────────── 我是谁?───────────────────┐
│                                              │
│  我是一个 LLM,本质是「文字接龙机器」。       │
│                                              │
│  • 我用 token 这个零件处理文字               │
│  • 我脑子里有几百亿个参数(旋钮),训练时调好│
│  • 我有上下文窗口(短期记忆容量),塞满就忘  │
│  • maxTokens 是我单次输出的上限,别和窗口混淆│
│  • 我按 token 收费,分四档(缓存命中省很多)│
│  • 我会幻觉(一本正经胡说),因为我在接不在查│
│  • 温度控制我接龙的放飞程度                  │
│  • 我吃 prompt,但 prompt 要像许愿一样精确    │
│ • 采样旋钮:温度/topP/topK/seed              │
│ • KV 缓存:重复输入不用重算 → cacheRead      │
│                                              │
│  ⚠️ 我的记性差 —— 这是伏笔,后面会救。       │
└──────────────────────────────────────────────┘

核心概念回顾:

概念一句话怎么理解它
LLM预测下一个 token 的接龙机器所有能力的根基
token文字的最小零件账单、窗口、思考强度都按它计
参数模型脑子里的旋钮,训练时调好你只用,不改
训练 vs 推理调旋钮(一次)vs 用旋钮接龙(每次)你只做推理
上下文窗口输入记忆容量,塞满就忘和 maxTokens 是两回事
maxTokens单次输出上限和窗口别搞混
token 计费四档input/output/cacheRead/cacheWrite缓存命中省很多
幻觉自信地编造,因为在接不在查用工具可缓解(第 6 章)
温度接龙的放飞程度写代码要低温
采样参数温度/topP/topK/seed,控制"怎么挑"一般用默认,结果不稳时排查
KV 缓存重复输入复用计算,cacheRead 便宜前缀要稳定才命中(附录 E.2)
幻觉根源机制 + 训练偏见 + 知识过时给足上下文 + 让 agent 查证
prompt像许愿,要精确见 system prompt(第 4 章)

课后实验

  1. 数 token:打开 tokenizer playground(或任意在线工具),输入一段你的代码,数数它有多少 token。感受"代码比你想的更费 token"。
  2. 感受幻觉:随便找个 LLM,问它一个你编造的不存在的 API(比如 Python 的 dict.squash() 方法怎么用),看它会不会一本正经地编。
  3. 调温度:用同一段 prompt("写一首关于秋天的诗"),分别用 temperature=0 和 temperature=1 跑 3 次,对比结果的稳定性。
  4. 玩采样参数:在支持 topP/topK 的工具里,固定其他参数,只把 topP 从 1 调到 0.3,观察回答是变得更"热门"还是更"保守"。
  5. 找"过时"证据:问一个模型"这个项目最新版本新增了什么",看它是否明显停在某个时间点。体会"训练截止日期"的存在——这正是 Agent 需要工具去查证的原因。

下一章:小a虽然会接龙,但遇到难题总是脱口而出答案——错的。老z说:"你先别急着答,把过程写出来。" 这就是思维链。→ 第 2 章 · 小a怎么"想"问题