Appearance
🐣 小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看了一眼黑板,突然想到一个问题:"您说的是『字』,可我读代码时,看到的明明是 function、return 这样的整词啊?"
老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 token 4K ~ 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摊手:"不能根除,只能缓解。 常见的缓解办法:
- 让它查证再答(RAG,见第 13 章)——别让它凭记忆,让它翻资料
- 给它工具去验证(tool,见第 6 章)——让它实际去跑一下代码,而不是猜
- 降低温度(见下一节)——让它别那么"放飞"
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 章) |
课后实验
- 数 token:打开 tokenizer playground(或任意在线工具),输入一段你的代码,数数它有多少 token。感受"代码比你想的更费 token"。
- 感受幻觉:随便找个 LLM,问它一个你编造的不存在的 API(比如
Python 的dict.squash()方法怎么用),看它会不会一本正经地编。 - 调温度:用同一段 prompt("写一首关于秋天的诗"),分别用 temperature=0 和 temperature=1 跑 3 次,对比结果的稳定性。
- 玩采样参数:在支持 topP/topK 的工具里,固定其他参数,只把 topP 从 1 调到 0.3,观察回答是变得更"热门"还是更"保守"。
- 找"过时"证据:问一个模型"这个项目最新版本新增了什么",看它是否明显停在某个时间点。体会"训练截止日期"的存在——这正是 Agent 需要工具去查证的原因。
下一章:小a虽然会接龙,但遇到难题总是脱口而出答案——错的。老z说:"你先别急着答,把过程写出来。" 这就是思维链。→ 第 2 章 · 小a怎么"想"问题