Skip to content

做 Agent 开发,有些大模型本身的底层机制,你不得不了解

约 23 分钟

AI 私教专属 1v1 AI 私教,围绕本节内容深度教学

进入私教

要不要学大模型底层算法?

这是不少想学 Agent 的读者问过我的问题。我先跟大家讲个例子吧。之前有个读者跟我微信反馈,说作为一个传统的前端开发者,看到我的 AI 文章人都看麻了,很多概念都是第一次看到,似懂非懂的,开始焦虑了。

我一方面会觉得有些不好意思,觉得写的东西给他带来了一些困扰,但另一方面我也在思考,这种焦虑和迷茫感从何而来?后来我发现,文章里面大量的概念和名词,我默认大家已经比较熟悉了,但实际并非如此。

比如我在公众号里面讲上下文压缩的时候,提到"KV Cache 命中率是生产阶段最重要的单一指标"——这就有人问了:KV Cache 是啥?为什么改了 system prompt 开头就会让缓存全部失效?

再比如 Manus 用"约束解码"来控制 Agent 只能调用特定工具——这个约束解码到底是什么原理?为什么提前写几个 token 就能限制模型的行为?

这些概念和流程,其实是属于大模型底层 Transformer 架构的部分,但如果你要系统掌握 Agent 开发,有些核心的概念你是绕不过去的。

但究竟掌握到什么程度,哪些重要,哪些是可以跳过的,这非常考验人的认知和判断力,但幸运的是,我曾经把 LLM 的这些细节都研究清楚了,同时也对 Agent 有很多经验。我可以负责任地告诉你哪些部分是需要你认真了解的,并且给你讲清楚,而不是一上来就是公式和矩阵推导,让人看的云里雾里。

好,那我们现在就开始。

你的中文 prompt,到底被拆成了什么?

我们先从最基础的开始:Token。

你肯定听过"token"这个词,模型的上下文窗口是按 token 算的,API 计费也是按 token 算的。但 token 到底是什么?

简单说,模型不认识"文字",它只认识数字。所以你输入的任何内容——中文、英文、代码、标点符号——都会先被切成一小块一小块的"token",每个 token 对应一个数字编号。

Token 是个很伟大的发明,它最伟大的地方,就在于把智能的产生,转换成一个纯纯的数学问题。

因为每个 token 都对应了数字,你看到的现在大模型吐出的所有字,其实本质上是让大模型吐出一堆 token 对应的编号数字,只不过为了方便你阅读,最后把数字序列转回了文字序列而已。

英文的情况比较直观。比如 "Hello world" 会被拆成 ["Hello", " world"] 两个 token。常见的英文单词基本上一个词就是一个 token,有时候长一点的词会被拆成两三个。

但中文就不一样了。

中文的 token 拆分,说实话,挺"碎"的。比如"人工智能"这四个字,你可能觉得它应该是一个 token,毕竟是一个完整的词嘛。但实际上它会被拆成 2-3 个 token,具体取决于模型用的分词器(tokenizer)。

Token 化过程示意图

为什么会这样?因为大部分大模型用的分词算法叫 BPE(Byte Pair Encoding)。简单理解就是:模型在训练之前,先统计大量文本中哪些字符组合出现频率最高,把高频组合合并成一个 token。英文只有 26 个字母,高频组合容易合并,一个词基本一个 token;中文有几万个汉字,字符集大得多,BPE 合并效率天然低一些。

这直接导致了一个实际问题:同样的语义内容,中文比英文消耗更多的 token。

在早期的 GPT-2/GPT-3 时代,1 个中文汉字大约要 2-3 个 token,效率很低。现在的主流模型已经大幅优化了中文分词,但不同模型差异不小:Claude 和 GPT 系列大约 1 个汉字 ≈ 1~1.5 个 token,国产模型(Qwen、DeepSeek)做得更好,很多常见词可以整词编码。整体来看,同样语义的一句话,中文 token 数比英文多 30%-60%

为什么?根本原因在 UTF-8 编码:一个英文字母只占 1 字节,而一个中文汉字占 3 字节。BPE 是在字节层面做合并的,英文 1 字节起步,大量常见词(thefunctionlearning)都能被合并成单个 token,压缩率极高;中文 3 字节起步,BPE 光把 3 个字节拼回一个汉字就已经消耗合并预算了,留给词级别合并(比如把"机器"+"学习"合成一个 token)的空间天然更少。这是编码层面的结构性差距,不会因为模型升级而消失。

你可能觉得这个差异不大,但做 Agent 的时候会被放大很多倍。Agent 一个任务跑下来可能有几十次工具调用,每次的上下文都在累积。如果你用中文写 system prompt、中文输出,token 消耗比你预估的要多不少。

这也是为什么 OpenClaw 在做 token 估算的时候,会加一个 20% 的安全边距SAFETY_MARGIN = 1.2)。不是拍脑袋来的,就是因为中文这类多字节字符的 token 估算容易低估。

后面我们讲上下文压缩和 Prompt Caching 的时候,token 估算的准确性会直接影响压缩触发时机和成本计算。这里先有个概念就行。

模型是怎么"说话"的:一个字一个字蹦出来

好,现在你的 prompt 已经变成了一串 token 数字,喂给模型了。接下来模型要开始生成回复。

这里有一个非常重要的事实,很多人不知道:

大模型不是"想好了再说"的,它是"边说边想"的。

什么意思呢?模型生成文本的过程,是一个 token 接一个 token 往后蹦的。它先生成第 1 个 token,然后把第 1 个 token 加入输入,再生成第 2 个 token,然后把前 2 个 token 加入输入,再生成第 3 个……如此循环,直到生成一个"结束"标记。

这个过程叫自回归生成(Autoregressive Generation)

自回归生成过程示意图

打个比方:你跟一个人聊天,这个人不是先在脑子里想好一整段话再开口,而是说一个字、想一下、再说下一个字。每一个字都是基于前面所有已经说过的字来决定的。

听起来很低效对吧?但这恰恰解释了你在用 ChatGPT 或者 Claude 的时候看到的那个"打字机效果"——文字一个一个蹦出来。这不是为了好看做的动画效果,这就是模型真实的生成过程。

对 Agent 开发来说,这个机制有三个直接的影响:

第一,流式响应是天然的。

因为模型本来就是一个 token 一个 token 生成的,所以你不需要等它全部说完再展示给用户。生成一个就发一个,这就是 SSE(Server-Sent Events)流式响应。后面专门讲流式架构的时候会深入这个话题。

第二,工具调用的 JSON 要"攒完"才能解析。

模型在调用工具的时候,会输出一个 JSON 格式的参数。但因为是一个 token 一个 token 蹦出来的,你收到的不是一个完整的 JSON,而是一堆 JSON 碎片:

{"na        ← 第一批 token
me": "rea   ← 第二批
d_file",    ← 第三批
"path":     ← 第四批
"/src/in    ← 第五批
dex.ts"}    ← 最后一批,JSON 才完整

你得把这些碎片攒起来,等最后一个 } 到了,才能解析这个 JSON,才能真正去执行工具。Claude Code 的 StreamingToolExecutor 就是干这个事的——后面第 6 篇会拆它的源码。

第三,response prefill 能"操控"模型的行为。

这是做 Agent 最有用的技巧之一。既然模型是基于前面所有 token 来决定下一个 token 的,那如果我提前替模型把前几个 token 写好呢?

比如我想让模型只能调用浏览器相关的工具,我可以在 assistant 消息里预先填入:

json

复制

json
{"name": "browser_

模型看到这个前缀,就只能在 browser_ 开头的工具名里选了。它没法回头改已经"说过"的字,只能顺着往下说。

Manus 就是用这个方法来控制 Agent 在不同阶段只能用特定工具的——不需要删除任何工具定义,只需要引导模型的"开头"。

Attention 和 KV Cache:为什么上下文不是越长越好

接下来聊聊 Transformer 架构里最核心的东西:Attention(注意力机制)。

这就要聊到 Attention 里面最核心的三个角色了:Query、Key、Value

放心,这里我坚决不讲公式,因为没有必要。我用一个比喻来说。

Q、K、V:搜索引擎的比喻

别被这三个英文单词吓到,它们的逻辑跟你用搜索引擎一模一样。

你在 Google 搜索的时候,发生了什么?

  1. 你输入一个搜索词——"KV Cache 是什么"
  2. Google 拿你的搜索词去匹配所有网页的标签和关键词
  3. 匹配度高的网页,把它的内容返回给你

Attention 做的事情完全一样:

  • Query(查询):当前正在生成的 token 会问一个问题——"我需要什么信息来决定下一个词?"
  • Key(索引):前面每一个 token 都有一个标签——"我包含什么类型的信息?"
  • Value(内容):前面每一个 token 的实际内容——"这是我的具体信息。"

生成新 token 的时候,模型拿当前 token 的 Query,去和前面所有 token 的 Key 做匹配。匹配度高的,就把对应的 Value 拿过来,加权混合,作为生成下一个 token 的依据。

Q/K/V 注意力机制示意图

举个具体的例子。假设上下文是"小明昨天去了北京,今天他去了",现在要生成下一个 token:

  • 当前 token 的 Query 大概在问:"谁去了哪里?"
  • "小明"这个 token 的 Key 标签是"人名",匹配度高 → 它的 Value 被拉过来
  • "北京"的 Key 是"地名",匹配度也挺高 → Value 也被拉过来
  • "昨天"的 Key 是"时间词",跟当前问题关系不大 → Value 的权重就低

最终模型综合了高权重的 Value 信息,可能生成"上海"(另一个城市)。

这就是 Attention 的全部了。 没有什么神秘的,就是一个"查询-匹配-取值"的过程,只不过这个过程是可以被训练的——模型通过大量数据学会了怎么生成好的 Query、Key 和 Value。

现在你理解了为什么叫"注意力"了吧?Query 和 Key 的匹配度,就是"注意力权重"。模型对某个 token 的注意力越高,那个 token 的 Value 在最终结果里的占比就越大。

问题在于:每个新 token 都要拿自己的 Query 去和前面所有 token 的 Key 做匹配。n 个 token 就要做 n 次匹配,而上下文里每个 token 都可能成为"当前 token",所以总共是 n × n = 的计算量。10 个 token 要做 100 次,100 个 token 要做 10000 次,10000 个 token 要做 1 亿次。

更关键的是,随着上下文越来越长,每个 token 能分到的"注意力预算"就越少。上下文里塞的东西越多,模型对每一条信息的"记忆力"就越差。这不是我说的,Chroma 团队专门做过研究,叫 Context Rot——上下文腐烂。

这就是为什么我们后面要花非常大的篇幅来讲上下文工程。不是上下文窗口大就完事了,你得管理它。

KV Cache:已经算过的 Key 和 Value,别再算了

好,理解了 Q、K、V 之后,KV Cache 就很好理解了。

刚才说模型每生成一个新 token,都要拿 Query 去匹配前面所有 token 的 Key 和 Value。但你想想,如果模型已经生成了 100 个 token,现在要生成第 101 个,它需要用到前 100 个 token 的 Key 和 Value。等到要生成第 102 个的时候,前 101 个 token 的 Key 和 Value 又要用一遍——其中 100 个跟刚才是一模一样的。

重新计算一遍?太浪费了。

所以模型会把之前算过的每个 token 的 Key 和 Value 缓存起来,这个缓存就叫 KV Cache(Key-Value Cache)。现在你知道这个名字的由来了——缓存的就是 Attention 里面的 K 和 V。

KV Cache 工作原理示意图

打个比方:你在考试,每道题都需要翻课本找公式。KV Cache 就像你把常用公式抄在草稿纸上——后面的题直接看草稿纸就行,不用每次都翻书。

但 KV Cache 有一个关键限制:它是基于"前缀匹配"的。

什么意思?缓存只有在前缀完全一样的时候才能复用。

举个例子。你的 Agent 第一轮对话,发给模型的内容是:

[System Prompt] + [用户消息1] + [模型回复1]

第二轮对话,发的是:

[System Prompt] + [用户消息1] + [模型回复1] + [用户消息2]

因为前面的部分完全一样,只是末尾加了新内容,所以前面所有 token 的 KV Cache 都能复用。模型只需要计算新增的 [用户消息2] 的部分。

但如果你做了一件事——比如在 System Prompt 的开头加了个时间戳:

当前时间:2026-04-02 08:00:00    ← 这个每次都变!
[其余的 System Prompt]
[用户消息1]
[模型回复1]
[用户消息2]

完蛋。第一个 token 就不一样了,后面所有的 KV Cache 全部作废,需要从头重新计算。

这不是理论上的问题。Anthropic 的 API,缓存命中和缓存未命中的价格差 10 倍。 一次工具定义变动,一次调用就贵 10 倍。

Manus 的首席科学家 Peak 说:KV Cache 命中率是"生产阶段最重要的单一指标"。

这也是为什么我们后面会花一整篇(第 19 篇)来讲 Prompt Caching,还有一篇(第 20 篇)来辨析 KV Cache、Prompt Cache 和 Context Collapse 这三个容易混淆的概念。

现在你只需要记住一条原则:上下文的前面部分尽量不要变,新增内容往后面追加。 这一条就能帮你省很多钱。

模型是怎么"选词"的:从打分到概率

最后一个概念,也是理解"约束解码"的关键。

模型每生成一个 token,内部到底发生了什么?

简单来说,分三步:

第一步:给所有候选词打分(Logits)

模型的词汇表里有几万个 token(Claude 大概有 10 万多个)。每生成一个新 token,模型会给词汇表里的每一个 token 打一个原始分数,表示"根据前面的上下文,这个 token 接下来出现的可能性有多大"。

这些原始分数叫 logits

比如在"今天天气真"这个上下文后面,"好"这个 token 可能得分 5.2,"不"得分 3.1,"的"得分 1.8,而"跑步"可能只有 -2.3。

第二步:把分数变成概率(Softmax)

原始分数不好直接用——有正有负,也不知道总和是多少。所以需要用一个叫 softmax 的函数,把这些分数转换成概率分布:所有候选词的概率加起来等于 1。

softmax 做的事情很简单:分数越高的词,概率越大;分数越低的词,概率越小。而且这个差距会被放大——高分词的概率会比它的原始分数暗示的还要高。

经过 softmax 之后,"好"可能变成 65% 的概率,"不"15%,"的"8%,"跑步"0.01%。

Logits → Softmax → 采样 示意图

第三步:根据概率采样(Sampling)

拿到概率分布后,模型从里面"抽签"选一个 token。你可能会说了:直接拿概率最高的就可以了嘛!

但事实并非如此,概率高的只是更容易被选中,但不是确定的——这就是为什么你问模型同一个问题,有时候得到不同的回答。反过来思考,如果每次都拿概率最高了,那模型的输出基本就是那些 token 序列,就跟复读机一样,没啥智能可言了。

这里提一个你肯定用过的参数:temperature

说个冷知识,temperature 调的是 softmax 输出的概率分布的"尖锐程度":

  • temperature 接近 0:概率分布变得非常尖锐,高的特别高,低的也很低,最高分的那个 token 几乎 100% 被选中。模型输出变得确定、保守。
  • temperature = 1:正常的概率分布,有一定的随机性。
  • temperature > 1:概率分布变得更平坦(或者更平均),低分 token 也有机会被选中。模型输出变得更有创意,但也更容易胡说八道。

做 Agent 的时候,一般用比较低的 temperature(0 或者接近 0),因为你希望模型做可靠的决策,而不是来一段"创意写作"。

看到这里,你是不是对这个参数的原理恍然大悟?

为什么这些对 Agent 开发很重要?

因为理解了这个"打分 → 概率 → 采样"的过程,你就能理解两个在 Agent 里非常关键的技术:

Logit Mask(约束解码)

既然模型是从概率分布里"抽签"的,那如果我在抽签之前,直接把某些 token 的概率设为 0 呢?

比如我只希望模型输出 "yes" 或 "no",那我就把除了 "yes" 和 "no" 之外的所有 token 的 logit 设为负无穷(经过 softmax 后概率就是 0 了)。模型只能在 "yes" 和 "no" 之间选。

这就叫 logit mask,也叫约束解码。

Manus 在多 Agent 架构里用这个技术来确保子 Agent 的输出格式严格可控——不管模型"想"说什么,logit mask 保证它只能按规定的格式输出。

Structured Output(结构化输出)

同理,当你让模型输出一个 JSON 格式的工具调用时,模型怎么保证输出的 JSON 是合法的?

一种方式就是在每一步生成的时候,用 logit mask 只允许当前上下文下语法合法的 token。比如刚输出了 {"name": 之后,下一个 token 只能是引号 " 开头的字符串。

这就是为什么现在的模型能比较可靠地输出结构化的工具调用参数——不是它"理解"了 JSON 语法,而是在解码阶段被约束了。

后面第 9 篇讲 Function Calling 的时候,我们会回来聊这个话题。

串一下

我们来把这四个概念串起来,看看一次 Agent 的工具调用到底经历了什么:

  1. Tokenize:你的 prompt(system prompt + 对话历史 + 用户指令)被拆成一串 token 数字
  2. Attention 计算:模型回看所有 token,给每个 token 分配注意力权重,综合所有信息
  3. KV Cache:前面轮次已经算过的 token 直接复用缓存,只算新增的部分
  4. 生成 token:模型输出第一个 token 的 logits → softmax → 采样,得到第一个 token
  5. 自回归循环:把新 token 加入上下文,回到第 2 步,重复直到输出完整的工具调用 JSON
  6. 约束解码(如果用了的话):在第 4 步的采样阶段,通过 logit mask 限制模型只能输出合法的 token

整个过程就像一个打字员,一边打字一边翻阅桌上的资料,每打一个字都要参考前面所有已经打过的字和桌上的资料。而我们做 Agent 开发的工作,很大程度上就是在管理那张桌子——桌上放什么资料、放多少、怎么放,直接决定了这个打字员的工作质量。

这就是后面整个课程要讲的事,敬请期待吧。

一张表总结

我画了一张表,总结今天的内容,大家可以对照来复习;

一张表总结

这些概念不需要你能手推公式,但你需要知道它们是什么、为什么重要。后面的课程里,每次用到这些概念的时候,我都会提醒你回来翻翻这篇。

好了,装备点检完毕。下一篇我们正式进入 Agent 开发的世界——聊聊你的 Agent 架构为什么不应该停留在 LangChain 时代。

上一页从 ChatBot 到 Agent:一个 while 循环,凭什么让 AI 从"能聊天"变成"能干活"?下一页2026 年了,你的 Agent 架构还停留在 LangChain 时代吗?