目标读者:不需要自己训练模型,但需要深刻理解 LLM 能做什么、不能做什么、为什么会出错的工程师和产品人。
一、LLM 是什么
1.1 一句话定义
LLM(Large Language Model,大语言模型)本质上是一个概率预测机器:
给定前面所有的文字,预测下一个词(Token)最可能是什么。
就这么简单。但正是这个看似简单的任务,在超大规模数据和参数下,涌现出了推理、写作、编程、翻译等复杂能力。
1.2 Token 是什么
LLM 处理的基本单位不是"字",而是 Token。
英文:
"Hello, world!" → ["Hello", ",", " world", "!"] → 4 个 Token
中文:
"你好世界" → ["你好", "世界"] → 2 个 Token
(中文通常 1~2 个汉字对应 1 个 Token)
代码:
"print('hello')" → ["print", "('", "hello", "')"] → 4 个 Token
关键数字:
- GPT-4 的上下文窗口:128K Token ≈ 约 10 万个汉字
- Claude 3.5:200K Token ≈ 约 15 万个汉字
- 1000 Token ≈ 750 个英文单词 ≈ 500 个汉字
二、Transformer:LLM 的核心架构
2.1 架构全景
输入文本(字符串)
↓
[Tokenizer] 文本 → Token ID 序列
"今天天气" → [1234, 5678, 9012]
↓
[Embedding 层] Token ID → 向量
每个 ID 映射为一个高维浮点数向量
[1234] → [0.12, -0.34, 0.87, ...]
↓
[N × Transformer Block]
├── [Self-Attention] 每个 Token 关注其他所有 Token,更新向量表示
└── [FFN] 前馈网络,进一步提取特征
↓
[输出层(Linear + Softmax)]
最后一个位置的向量 → 词表上每个 Token ID 的概率分布
{1234: 0.01, 5678: 0.60, 9012: 0.02, ...}
↓
[采样] 根据概率分布选出下一个 Token ID
→ 选出 ID: 5678
↓
[Detokenizer] Token ID → 文本
5678 → "很"
↓
输出文本(字符串)"很"
↓
[将新 Token 追加到输入,循环执行,直到生成结束符 <EOS>]
2.2 Self-Attention:最关键的机制
用一个完全不涉及数学的例子来理解。
大白话例子:开会投票决定谁说了算
想象你在处理这句话:
"小明把球给了小红,因为他很累了。"
现在的问题是:"他" 指的是谁?小明还是小红?
第一步:每个词都有一张"名片"和一张"问卷"
句子里每个词,系统给它准备两样东西:
- 名片(Key):写着"我是谁,我有什么特征"
- 问卷(Query):写着"我想找什么样的词"
小明 的名片:男性、人名、主语、做了动作
小红 的名片:女性、人名、宾语、接受了球
他 的问卷:我想找 → 男性、人名、主语
这张"名片"是人工打的吗?
不是。没有任何人手动标注"小明是男性、人名、主语"。
名片(Key)、问卷(Query)、内容(Value)全部是训练出来的:
每个词先变成一串数字(Embedding 向量),然后乘以三个矩阵(W_Q、W_K、W_V),
分别得到 Q、K、V。这三个矩阵是模型在训练过程中自动学习出来的参数。模型在训练时看了海量文本,发现"他/她/它"后面接的内容,往往和前面某个名词有关——
不断调整这三个矩阵,让"他的问卷"和"小明的名片"自然而然地匹配度更高。所以"男性、人名、主语"只是我们用人话描述的直觉,
真实的名片是一串几百维的浮点数,没有人能直接读懂,
但它的效果等价于"描述了这个词的语言特征"。
第二步:拿着问卷去匹配所有名片,打分
"他"拿着自己的问卷,挨个去和其他词的名片对照:
"他"的问卷 vs 小明的名片 → 男性✓ 人名✓ 主语✓ → 匹配度:90分
"他"的问卷 vs 小红的名片 → 男性✗ 人名✓ 主语✗ → 匹配度:20分
"他"的问卷 vs 球的名片 → 男性✗ 人名✗ 主语✗ → 匹配度:2分
"他"的问卷 vs 因为的名片 → ... → 匹配度:1分
第三步:按分数决定"借多少信息"
分数高的词,"他"就多借一点它的信息;分数低的词,少借或者不借:
"他"最终的理解 =
90% × 小明的含义
+ 20% × 小红的含义(归一化后)
+ 2% × 球的含义
+ ...
归一化之后大概是:
72% × 小明 + 16% × 小红 + 2% × 球 + ...
结果:"他"这个词现在已经带上了大量"小明"的信息。
后续模型继续生成文字时,就"知道"了——他 = 小明。
第四步:每个词都同时做这件事
不只是"他"在做,句子里每一个词都同时在做同样的事:
小明 → 看了所有词,更新自己(知道自己是主语,给了球)
球 → 看了所有词,更新自己(知道自己被传递了)
小红 → 看了所有词,更新自己(知道自己是接球的人)
他 → 看了所有词,更新自己(知道自己指的是小明)
累了 → 看了所有词,更新自己(知道是小明累了)
所有词同时互相"看"一遍,每个词都吸收了整个句子的上下文信息。
这就是"Self"(自身序列内部互相关注)的含义。
第五步:不止看一个角度(Multi-Head)
上面的例子只从"指代关系"这一个角度看。现实中模型同时开 32~128 个"视角",每个视角关注不同的语言关系:
视角1(指代关系):他 → 小明
视角2(动作关系):累了 → 小明(谁累了?)
视角3(位置关系):因为 → 连接前后两个事件
视角4(语气关系):...
...
视角N:...
最终把所有视角的结果拼在一起 → 全面理解这个句子
用一句话总结 Self-Attention
句子里的每个词,都去"问"其他所有词:"你跟我有多相关?",
然后按相关程度,从每个词那里借一点信息,
把这些信息混合在一起,更新自己的理解。
所有词同时做这件事,一轮结束后,每个词都"看懂"了整个句子的上下文。
Q、K、V 对应关系
现在再看这三个术语,就很好理解了:
| 术语 | 大白话 | 例子中对应 |
|---|---|---|
| Q(Query,查询) | 我的问卷,我在找什么 | "他"想找:男性、人名、主语 |
| K(Key,键) | 我的名片,我有什么特征 | 小明的名片:男性、人名、主语 |
| V(Value,值) | 我的实际内容 | 小明这个词完整的语义信息 |
公式 Attention(Q, K, V) = softmax(QK^T / √d) × V 做的就是:
用 Q 和 K 算匹配分数 → softmax 变成百分比 → 按百分比加权取 V。
Causal Mask:生成时不能"偷看未来"
还有一个重要限制:模型生成文字时,是一个词一个词往后写的,后面的词还没生成,所以不能用。
正在生成第5个词时:
能看到:词1 词2 词3 词4 [正在生成词5]
不能看:词6 词7 词8 ...(还不存在)
就像考试时:
你只能参考已经写下的答案,不能看还没写的部分
系统通过把"未来位置"的匹配分数强制设为负无穷,让它们经过 softmax 后权重变成 0,等效于视而不见。
训练期原理:W_Q/W_K/W_V 是怎么一步步生成的
用一个极小的例子,数字都是手算得出的,方便理解完整过程。
场景设定
词表只有 3 个词:猫、喜欢、鱼
训练样本:给模型看 "猫 喜欢" → 让它预测下一个词是 "鱼"
向量维度设为 2(现实中是几千维,这里为了手算用 2)。
第一步:把词变成向量(Embedding)
先给每个词一个初始向量(随机数,训练会调整):
猫 → [1.0, 0.5]
喜欢 → [0.3, 0.8]
鱼 → [0.9, 0.1]
输入 "猫 喜欢" 时,得到两个向量:
x_猫 = [1.0, 0.5]
x_喜欢 = [0.3, 0.8]
第二步:随机初始化 W_Q、W_K、W_V
训练开始时,这三个矩阵都是随机数:
W_Q = [[0.1, 0.2], W_K = [[0.4, 0.1], W_V = [[0.3, 0.5],
[0.3, 0.4]] [0.2, 0.3]] [0.1, 0.2]]
(现实中这些矩阵是几千×几千维,这里用 2×2 示意)
第三步:计算 Q、K、V(前向传播)
用每个词的 Embedding 乘以三个矩阵,得到 Q/K/V:
Q_猫 = x_猫 × W_Q = [1.0, 0.5] × W_Q = [0.25, 0.40]
K_猫 = x_猫 × W_K = [1.0, 0.5] × W_K = [0.50, 0.25]
V_猫 = x_猫 × W_V = [1.0, 0.5] × W_V = [0.35, 0.60]
Q_喜欢 = x_喜欢 × W_Q = [0.3, 0.8] × W_Q = [0.27, 0.38]
K_喜欢 = x_喜欢 × W_K = [0.3, 0.8] × W_K = [0.28, 0.27]
V_喜欢 = x_喜欢 × W_V = [0.3, 0.8] × W_V = [0.17, 0.31]
第四步:计算 Attention 权重("喜欢"该关注谁?)
用 Q_喜欢 分别和 K_猫、K_喜欢 做点积,算匹配分数:
score(喜欢→猫) = Q_喜欢 · K_猫
= 0.27×0.50 + 0.38×0.25 = 0.135 + 0.095 = 0.230
score(喜欢→喜欢) = Q_喜欢 · K_喜欢
= 0.27×0.28 + 0.38×0.27 = 0.076 + 0.103 = 0.179
经过 Softmax 变成权重(两个加起来=1):
weight(喜欢→猫) = 0.513
weight(喜欢→喜欢) = 0.487
"喜欢" 对 "猫" 的关注度略高(51% vs 49%)。
第五步:加权求和 V,得到"喜欢"的新向量
output_喜欢 = 0.513 × V_猫 + 0.487 × V_喜欢
= 0.513 × [0.35, 0.60] + 0.487 × [0.17, 0.31]
= [0.180, 0.308] + [0.083, 0.151]
= [0.263, 0.459]
这个 [0.263, 0.459] 就是"喜欢"经过 Attention 后的新向量,
它已经融合了"猫"的信息。
第六步:用新向量预测下一个词
把 output_喜欢 经过最后一层,得到词表上每个词的概率:
预测结果:
猫 → 15%
喜欢 → 10%
鱼 → 75% ← 模型预测"鱼"的概率最高
第七步:和正确答案对比,算误差(Loss)
正确答案是 "鱼",对应的目标是:
目标:猫=0%, 喜欢=0%, 鱼=100%
预测:猫=15%, 喜欢=10%, 鱼=75%
Loss(误差)= 预测和目标的差距
= 用交叉熵公式算出一个数,比如 0.288
Loss 越大,说明预测越不准,需要调整。
第八步:反向传播,调整 W_Q、W_K、W_V
这是训练的核心。根据 Loss,计算每个参数"调多少能让 Loss 变小":
调整方向:让 Loss 减小的方向(梯度下降)
W_Q 的某个数原来是 0.1
梯度告诉我们:这个数应该增大一点,Loss 才会减小
→ 调整后变成 0.1 + 学习率 × 梯度 = 0.1 + 0.01 × 0.5 = 0.105
W_K、W_V 同理,每个数字都被微调一点点
这一次调整完,模型预测"鱼"的概率可能从 75% 变成 76%。
第九步:重复几千亿次
换一条新的训练数据,重复第一步到第八步:
样本1:"猫 喜欢" → 预测"鱼" → 调整参数
样本2:"我 吃" → 预测"饭" → 调整参数
样本3:"天气 很" → 预测"好" → 调整参数
...
重复几千亿次
每次调整都是微小的,但经过几千亿次后:
W_Q 从随机数 → 变成"擅长提取查询特征"的矩阵
W_K 从随机数 → 变成"擅长描述词的特征"的矩阵
W_V 从随机数 → 变成"擅长携带语义内容"的矩阵
训练结束,推理期怎么用
训练完 W_Q/W_K/W_V 就固定了。用户输入新句子时:
用户输入:"小明把球给了小红,因为他"
1. 每个词 → Embedding 向量
2. × 固定的 W_Q → Q(每个词的"问卷")
3. × 固定的 W_K → K(每个词的"名片")
4. × 固定的 W_V → V(每个词的"内容")
5. 计算 Attention 权重
→ "他"的 Q 和"小明"的 K 匹配度高
→ "他"的新向量融合大量"小明"信息
6. 预测下一个词:"很"
7. 继续生成,直到结束
W_Q/W_K/W_V 没有变化,只是被"用"了一次。
一句话总结训练过程
训练 = 不断喂数据 → 预测 → 算误差 → 微调参数 → 重复
= 让 W_Q/W_K/W_V 从一堆废话随机数
慢慢变成"懂语言"的参数矩阵
大模型的"参数"在例子里指什么
人们常说 GPT-3 有 1750 亿参数、Claude 有几百亿参数,这些参数就是上面例子里那些矩阵里的每一个数字。
回到我们的小例子,把所有矩阵展开数一数:
Embedding 表(每个词一个向量):
猫 → [1.0, 0.5] ← 2 个数
喜欢 → [0.3, 0.8] ← 2 个数
鱼 → [0.9, 0.1] ← 2 个数
小计:3 个词 × 2 维 = 6 个参数
W_Q 矩阵:
[[0.1, 0.2],
[0.3, 0.4]] ← 4 个数 = 4 个参数
W_K 矩阵:
[[0.4, 0.1],
[0.2, 0.3]] ← 4 个参数
W_V 矩阵:
[[0.3, 0.5],
[0.1, 0.2]] ← 4 个参数
最后一层(输出层,把向量变成词的概率):
也是一个矩阵 ← 假设 6 个参数
────────────────────────────────
这个玩具模型总参数量 ≈ 6+4+4+4+6 = 24 个参数
这 24 个数字,就是这个玩具模型的全部"知识"。
训练过程就是反复调整这 24 个数字,让它们从随机值变成能预测"猫 喜欢 → 鱼"的值。
放大到真实大模型
真实模型和玩具模型的区别,只是数字的数量不同:
玩具模型(上面例子):
词表 3 个词,向量维度 2
→ 参数量:约 24 个
GPT-2(2019年):
词表 50257 个词,向量维度 768
Transformer 层数 12,每层有多个矩阵
→ 参数量:15 亿个数字
GPT-3(2020年):
向量维度 12288,层数 96
→ 参数量:1750 亿个数字
Claude Sonnet 4.6(现在):
→ 参数量:未公开,估计数百亿
结构完全一样:Embedding 表 + N 层(W_Q/W_K/W_V + 其他矩阵)+ 输出层。
只是维度更大、层数更多,参数量从 24 个变成了几百亿个。
参数里存的是什么
参数不是人写进去的规则,是训练出来的数字,但这些数字里隐含了语言知识:
W_Q/W_K/W_V 里的数字
→ 隐含了"什么词应该关注什么词"的规律
→ 比如代词倾向于关注前面的名词
Embedding 表里的数字
→ 隐含了词与词之间的语义关系
→ "国王"和"王后"的向量距离,接近"男人"和"女人"的向量距离
输出层矩阵里的数字
→ 隐含了"什么上下文后面跟什么词"的概率规律
→ 看到"我饿了,想吃",下一个词大概率是食物
所有这些"知识",都压缩在这几百亿个浮点数里。
模型文件之所以几十 GB,就是因为要存下这几百亿个数字。
这些数字如何和文字对应起来?
分两张表,串联起来完成"文字 ↔ 数字"的转换。
第一张表:词表(Vocabulary)— 文字 → ID
这是一张固定的查找表,训练前就确定好了,训练期间不变:
词表(Tokenizer 内置):
ID 词
───────────────
0 → [PAD] (填充符)
1 → [UNK] (未知词)
2 → [BOS] (句子开始)
3 → [EOS] (句子结束)
4 → "猫"
5 → "喜欢"
6 → "鱼"
7 → "我"
8 → "你"
...
50256 → "hello"
用户输入 "猫 喜欢 鱼" 时:
Tokenizer 查这张表:
"猫" → ID 4
"喜欢" → ID 5
"鱼" → ID 6
得到 ID 序列:[4, 5, 6]
这张表是人工构建的(统计语料中的高频词/子词),GPT 系列约有 50257 个词,Claude 更多。
第二张表:Embedding 表 — ID → 向量
这是一张可训练的矩阵,每一行对应一个 ID 的向量:
Embedding 表(训练过程中不断调整):
ID 向量(2维示意,实际几千维)
──────────────────────────────
0 → [0.00, 0.00] (PAD)
1 → [0.01, 0.02] (UNK)
2 → [0.11, 0.33] (BOS)
3 → [0.22, 0.44] (EOS)
4 → [1.00, 0.50] (猫)
5 → [0.30, 0.80] (喜欢)
6 → [0.90, 0.10] (鱼)
...
ID 序列 [4, 5, 6] 查这张表:
ID 4 → [1.00, 0.50] (猫的向量)
ID 5 → [0.30, 0.80] (喜欢的向量)
ID 6 → [0.90, 0.10] (鱼的向量)
这张表里的数字,就是参数的一部分,训练时会不断调整,让语义相近的词向量距离更近。
输出时反向查表:向量 → 文字
模型生成下一个词时,输出的是词表上每个 ID 的概率,再查回词表:
输出层计算结果(每个 ID 的概率):
ID 4(猫) → 5%
ID 5(喜欢) → 8%
ID 6(鱼) → 80%
ID 7(我) → 3%
...
选概率最高的 → ID 6
查词表 → ID 6 = "鱼"
输出文字 → "鱼"
完整的文字 ↔ 数字流程
输入侧:
文字 "猫 喜欢"
↓ 词表(固定查找表)
ID 序列 [4, 5]
↓ Embedding 表(可训练矩阵)
向量序列 [[1.0,0.5], [0.3,0.8]]
↓ Transformer(W_Q/W_K/W_V 等参数)
新向量(融合了上下文)
输出侧:
新向量
↓ 输出层矩阵(可训练参数)
每个 ID 的概率 [5%, 8%, 80%, 3%, ...]
↓ 采样/取最大值
ID 6
↓ 反查词表(固定查找表)
文字 "鱼"
两张表的区别
| 词表(Vocabulary) | Embedding 表 | |
|---|---|---|
| 内容 | 文字 ↔ ID 的映射 | ID ↔ 向量的映射 |
| 大小 | 约 5~10 万行 | 约 5~10 万行 × 向量维度 |
| 是否可训练 | 不变,训练前固定 | 会变,训练中不断调整 |
| 存储位置 | Tokenizer 文件里 | 模型参数文件里 |
| 作用 | 文字和数字的桥梁 | 让数字携带语义信息 |
词表是死的查找表,Embedding 表是活的参数矩阵。两者配合,完成"文字→语义向量→文字"的完整转换。
2.3 为什么叫"大"语言模型
参数量决定了模型的记忆和推理能力:
| 模型 | 参数量 | 能力水平 |
|---|---|---|
| GPT-2(2019) | 15 亿 | 能写连贯文章,推理较弱 |
| GPT-3(2020) | 1750 亿 | 涌现出 few-shot 学习能力 |
| GPT-4(2023) | 未公开(估计万亿级) | 接近人类专家水平 |
| Claude 3.5 Sonnet | 未公开 | 顶级推理和代码能力 |
涌现(Emergence):参数量超过某个阈值后,模型突然获得之前没有的能力。这不是线性增长,而是质变。
三、训练过程(知道即可)
3.1 预训练
数据来源:互联网文本、书籍、代码、论文...
↓
任务:给定前 N 个 Token,预测第 N+1 个 Token
↓
损失函数:预测错了就调整参数(梯度下降)
↓
重复数万亿次
↓
模型学会了语言规律、世界知识、推理模式
关键理解:预训练完的模型是一个"知识压缩器",把互联网上的信息压缩进了参数里。但它只会续写文本,不会"对话"。
3.2 RLHF:让模型变得有用
Reinforcement Learning from Human Feedback(人类反馈强化学习):
Step 1:监督微调(SFT)
人类写出"好的"对话示例
→ 模型学习如何回答问题
Step 2:奖励模型训练
人类对多个回答排序(A > B > C)
→ 训练一个"评分模型"
Step 3:PPO 强化学习
用评分模型给 LLM 的回答打分
→ 强化好回答,惩罚坏回答
→ 模型越来越符合人类期望
这一步产生了"对齐":模型从"续写机器"变成"乐于助人的助手"。
3.3 知识截止日期
模型的知识来自训练数据,训练数据有截止时间:
Claude Sonnet 4.6 的知识截止:2025 年 8 月
→ 2025 年 8 月之后发生的事,模型不知道
→ 模型可能会"自信地"给出错误的新信息(幻觉)
工程含义:涉及实时信息的场景,必须通过 RAG 或工具调用补充最新知识。
四、推理过程:模型如何生成文字
4.1 自回归生成
LLM 每次只生成一个 Token,然后把这个 Token 加入输入,再生成下一个:
输入:"今天天气"
→ 模型预测下一个 Token:{"很": 0.6, "不": 0.2, "真": 0.1, ...}
→ 采样选出"很"
→ 输入变成"今天天气很"
→ 模型预测:{"好": 0.5, "差": 0.3, "热": 0.15, ...}
→ 采样选出"好"
→ 输入变成"今天天气很好"
→ ...直到生成结束符
工程含义:
- 生成速度与输出长度成正比,输出越长越慢
- 流式输出(Streaming)就是每生成一个 Token 就立刻返回
4.2 Temperature:控制随机性
Temperature = 0 → 每次都选概率最高的 Token(确定性,适合代码/数学)
Temperature = 0.7 → 适度随机(平衡创意和准确,适合对话)
Temperature = 1.5 → 高度随机(创意写作,可能胡言乱语)
直觉:Temperature 越高,模型越"天马行空";越低,越"保守准确"。
4.3 Context Window:模型的"工作记忆"
┌──────────────────────────────────────────────┐
│ Context Window │
│ │
│ System Prompt │ 历史对话 │ 当前输入 │ 输出 │
│ (系统设定) │ │ │ │
└──────────────────────────────────────────────┘
↑
模型只能看到这个窗口内的内容
关键限制:
- 超出 Context Window 的内容,模型完全看不到(不是变模糊,是彻底消失)
- Context Window 越大,计算成本越高,速度越慢
- 长文本处理时,模型对"中间"部分的注意力往往弱于"开头"和"结尾"(Lost in the Middle 问题)
五、LLM 的能力边界
5.1 LLM 擅长的事
| 能力 | 原因 | 典型场景 |
|---|---|---|
| 文本生成与改写 | 训练目标本身就是预测文本 | 写作、翻译、摘要 |
| 代码生成 | 训练数据包含大量代码 | 写函数、修 Bug、解释代码 |
| 模式识别与分类 | 见过大量样本 | 情感分析、意图识别 |
| 知识问答 | 压缩了大量世界知识 | 百科问答、概念解释 |
| 格式转换 | 见过各种格式互转的例子 | JSON↔表格、Markdown↔HTML |
| 少样本学习(Few-shot) | 涌现能力 | 给几个例子,模型举一反三 |
| 推理(有限范围) | CoT 能力 | 数学题、逻辑推理 |
5.2 LLM 不擅长的事(能力边界)
① 精确计算
问:1234567 × 9876543 = ?
LLM 的做法:根据训练数据中的"数字模式"猜一个看起来合理的数
→ 可能是错的,而且模型说得很自信
正确做法:通过工具调用真正的计算器
原理:LLM 处理数字和处理文字一样,都是 Token 预测,没有真正的"计算"能力。
② 实时信息
问:今天比特币价格是多少?
LLM 的做法:给出训练截止日前的价格,或者编造一个
→ 必然是错的
正确做法:工具调用实时 API
③ 精确记忆长文本
给模型一份 10 万字的文档,然后问文档第 5000 行的第 3 个数字
→ 模型大概率回答错误
原理:"Lost in the Middle" —— 模型对上下文中间部分的注意力显著弱于头尾
④ 稳定的逻辑推理链
简单推理:模型能做
→ "如果 A > B,B > C,那么 A > C?" → 正确
复杂推理链:容易出错
→ 多步骤、多条件的推理,错误会累积
→ 特别是反事实推理("如果历史上没有发生X,Y会怎样")
⑤ 自我认知
问:你确定这个答案是对的吗?
LLM 的问题:模型不知道自己"知道"什么,"不知道"什么
→ 可能对错误的答案表现出高度自信
→ 也可能对正确的答案表示怀疑
这就是"幻觉"的根源之一
⑥ 持久记忆
对话结束后,下次对话模型完全不记得你
→ 每次对话都从零开始
→ 需要工程手段(数据库 + RAG)实现跨会话记忆
六、幻觉(Hallucination):最重要的工程挑战
6.1 什么是幻觉
模型自信地给出错误信息,叫做幻觉。
典型例子:
问:爱因斯坦在哪所大学获得博士学位?
错误回答(幻觉):"爱因斯坦在柏林洪堡大学获得博士学位"(编造的)
正确答案:苏黎世大学
更危险的幻觉:
问:引用几篇关于 XXX 的论文
→ 模型可能给出看起来真实的论文标题、作者、期刊,但完全是编造的
6.2 幻觉的根本原因
LLM 的本质:预测下一个 Token 的概率分布
≠ 检索真实事实
当模型"不知道"答案时:
→ 它不会说"我不知道"(这不是训练的主要目标)
→ 它会生成一个"看起来合理"的答案(因为这是训练的目标)
→ 这个"合理"但错误的答案 = 幻觉
6.3 减少幻觉的工程手段
| 手段 | 原理 | 适用场景 |
|---|---|---|
| RAG(检索增强生成) | 先检索真实文档,再让模型基于文档回答 | 知识库问答、文档分析 |
| 工具调用(Tool Use) | 让模型调用外部 API 获取真实数据 | 实时信息、精确计算 |
| 降低 Temperature | 减少随机性,避免"发挥" | 需要准确性的场景 |
| 要求引用来源 | Prompt 中要求模型给出依据 | 研究类、事实类问题 |
| 输出验证 | 用另一个模型或规则校验输出 | 高风险场景 |
| 思维链(CoT) | 让模型"逐步思考",减少跳跃 | 推理类问题 |
七、Prompt 工程:如何"驾驶"LLM
7.1 模型如何理解 Prompt
完整的输入结构:
┌─────────────────────────────────┐
│ System Prompt(系统提示) │ 定义模型角色、规则、约束
├─────────────────────────────────┤
│ Few-shot 示例(可选) │ 示范期望的输入输出格式
├─────────────────────────────────┤
│ User Message(用户输入) │ 当前任务描述
├─────────────────────────────────┤
│ Assistant Prefix(可选) │ 引导模型以特定方式开始回答
└─────────────────────────────────┘
7.2 高质量 Prompt 的要素
① 角色设定
差:帮我写代码
好:你是一个有 10 年经验的 Python 后端工程师,
擅长写可维护、有完善错误处理的生产级代码。
② 上下文完整
差:这个函数有 Bug,帮我修
好:以下是一个 Python 函数,输入是用户 ID(整数),
预期输出是用户名(字符串)。
当用户不存在时应该抛出 UserNotFoundError。
现在当输入 -1 时会崩溃,帮我修复:
[代码]
③ 输出格式明确
差:分析一下这个需求
好:分析以下需求,用 JSON 格式输出,包含字段:
- risks: 风险列表(数组)
- complexity: 复杂度(low/medium/high)
- estimatedDays: 估算工作日(整数)
④ 思维链(Chain of Thought)
在 Prompt 末尾加:
"请一步步思考,展示你的推理过程"
→ 显著提升复杂推理的准确性
→ 原理:迫使模型在 Token 层面"写出"中间步骤,减少跳跃
7.3 Prompt 的常见陷阱
| 陷阱 | 现象 | 解决方法 |
|---|---|---|
| 指令冲突 | System Prompt 和 User 要求矛盾 | 明确优先级,减少歧义 |
| 上下文污染 | 历史对话影响当前回答 | 关键任务用新会话 |
| 负面指令无效 | "不要做X"往往不如"做Y" | 用正面指令替代负面指令 |
| 过长 Prompt 失效 | 指令太多,模型"忘记"部分指令 | 精简核心指令,辅以示例 |
| 格式不稳定 | 同样 Prompt 输出格式不一致 | 用 Few-shot 示例固定格式 |
八、RAG:给 LLM 接上外部知识库
8.1 为什么需要 RAG
LLM 的知识:
✓ 截止训练数据的时间点
✗ 你的公司内部文档
✗ 实时信息
✗ 超过 Context Window 的长文档
RAG 解决:让模型基于"当下检索到的真实内容"回答
8.2 RAG 工作流程
离线阶段(建立知识库):
文档
↓ 切片(Chunking)
文档片段(每段 200~500 Token)
↓ Embedding 模型
向量(每段文字变成一串数字,语义相近的向量距离近)
↓ 存入向量数据库(Pinecone/Weaviate/pgvector)
在线阶段(用户查询):
用户问题
↓ Embedding 模型
问题向量
↓ 向量数据库相似度检索
最相关的 N 个文档片段
↓ 拼入 Prompt
LLM 基于检索到的内容生成回答
8.3 RAG 的关键参数
| 参数 | 含义 | 调优方向 |
|---|---|---|
| Chunk Size | 每段文字的长度 | 太小丢失上下文,太大检索精度低 |
| Top-K | 检索几个片段 | 太少信息不足,太多噪音增加 |
| Embedding 模型 | 把文字转向量的模型 | 影响检索精度,中文需专用模型 |
| 重排序(Rerank) | 对检索结果二次排序 | 显著提升检索准确性 |
RAG小结
RAG = Retrieval-Augmented Generation
- Retrieval:检索
- Augmented:增强
- Generation:生成
直译:检索增强生成。
RAG 是指先根据离线文档查找出一些内容,然后作为提示词和用户问题一起给llm?这样做好处是啥?
⏺ 是的,理解完全正确。用一张图说清楚:
没有 RAG:
用户问题 → LLM → 回答
(LLM 只靠训练时学到的知识,可能幻觉)
有 RAG:
用户问题
↓
向量检索(从你的文档库里找最相关的片段)
↓
组装 Prompt:
"根据以下资料回答问题:
[检索到的文档片段]
用户问题:XXXX"
↓
LLM → 回答
(基于真实文档内容,有据可查)
---
好处
1. 解决知识截止问题
LLM 训练数据有截止日期,你的内部文档、最新资料它根本不知道。RAG 让模型能"看到"这些内容。
2. 减少幻觉
模型有参考资料时,倾向于基于资料回答,而不是自己发挥。可以进一步要求"只根据提供的资料回答,资料里没有
的就说不知道"。
3. 可追溯
回答可以附上"来源:第X段",用户可以去验证原文,企业场景下非常重要。
4. 不需要重新训练模型
更新知识库只需要更新文档,不需要重新训练或微调模型,成本极低。
5. 控制回答范围
客服机器人只回答产品相关问题,把产品文档放进知识库,模型自然只在这个范围内回答。
---
核心代价
检索质量决定回答质量——如果检索到的片段不相关,模型的回答也会跑偏。所以 RAG 的难点不在
LLM,在怎么把文档切好、检索准。
九、Function Calling 协议:LLM 突破文本边界的关键设计
9.1 为什么需要 Function Calling
LLM 有一个根本限制:它只能输出文本。
LLM 的本质:
输入:一段文字(Prompt)
输出:一段文字(预测的下一个 Token 序列)
仅此而已。
这意味着:
- 它无法执行代码
- 它无法读写文件
- 它无法调用 API 获取实时数据
- 它无法控制任何外部系统
如果没有任何扩展,LLM 就是个"纯文字生成机",用户问"北京现在天气怎样",它只能靠训练数据里的知识猜测一个合理的答案——幻觉不可避免。
Function Calling(函数调用)协议就是为了解决这个问题而诞生的:让 LLM 在需要时,用文本的形式"声明"它想调用某个函数,再由外部代码真正执行,把结果反馈给模型。
9.2 协议的核心思想:LLM 输出结构化指令
关键洞察是:
LLM 虽然只能输出文本,但文本可以是结构化的(JSON)。
把 JSON 当成"指令",由外部程序解析并执行,就相当于 LLM "调用"了外部功能。
传统对话:
用户问题 → LLM → 自然语言回答
Function Calling:
用户问题 + 工具列表 → LLM → 结构化调用指令(JSON)
↓
外部代码解析并执行
↓
执行结果注入 Context
↓
LLM 读取结果 → 生成最终回答
LLM 自己什么都没有"执行",它只是输出了一段 JSON,外部系统帮它完成了真正的动作。
9.3 完整协议流程
以"查北京天气"为例,走一遍完整的 Function Calling 协议:
第一步:开发者定义工具
在调用 LLM API 时,把可用工具的"说明书"传给模型:
tools: [
{
"name": "get_weather",
"description": "获取指定城市的实时天气。当用户询问天气信息时调用此工具。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如'北京'、'上海'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,默认 celsius"
}
},
"required": ["city"]
}
}
]
关键点:description 是给 LLM 看的,模型根据这段文字决定"什么时候该调哪个工具"。写得越准确,模型的判断越正确。
第二步:LLM 决策——输出调用指令
用户问:"北京今天天气怎么样?"
LLM 的回应不是自然语言,而是一段结构化指令(以 Claude API 为例):
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": {
"city": "北京",
"unit": "celsius"
}
}
这就是 LLM 输出的"文本",只不过格式是 JSON。LLM 本身并没有调用任何东西。
第三步:外部代码执行工具
你的代码接收到这个 JSON,解析出函数名和参数,真正去调 API:
# 伪代码
if response.type == "tool_use":
tool_name = response.name # "get_weather"
tool_input = response.input # {"city": "北京", "unit": "celsius"}
# 真正执行 API 调用
result = call_weather_api(city="北京", unit="celsius")
# result = {"temp": 22, "unit": "celsius", "weather": "晴", "humidity": "45%"}
第四步:把结果注入 Context,再次调用 LLM
把工具执行结果写入对话历史,重新发给 LLM:
messages: [
{"role": "user", "content": "北京今天天气怎么样?"},
{"role": "assistant", "content": [{"type": "tool_use", "name": "get_weather", ...}]},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "{\"temp\": 22, \"weather\": \"晴\", \"humidity\": \"45%\"}"
}
]
}
]
第五步:LLM 基于真实数据生成回答
LLM 读取到工具返回的结果,生成最终自然语言回答:
"北京今天天气晴朗,气温 22°C,湿度 45%,出门不需要带伞。"
这次回答有真实数据支撑,不是幻觉。
整个流程图
用户:"北京今天天气怎么样?"
↓
[LLM] 读取:用户问题 + 工具列表
↓
[LLM] 输出:{"type":"tool_use","name":"get_weather","input":{"city":"北京"}}
↓ (LLM 只输出了这段文字)
[外部代码] 解析 JSON,真正调用天气 API
↓
[外部代码] 把结果 {"temp":22,"weather":"晴"} 注入 Context
↓
[LLM] 读取真实数据,生成自然语言回答
↓
"北京今天晴,22°C"
9.4 LLM 如何"决定"调不调工具
LLM 并没有专门的"决策模块",它的决策机制和生成文字完全一样:预测下一个 Token。
训练时,Anthropic / OpenAI 给模型大量"什么时候该调工具"的示例数据,模型学会了:
Context 里有工具列表 + 用户问实时信息
→ 下一个 Token 大概率是 {"type":"tool_use"...}
Context 里有工具列表 + 用户问一般知识
→ 下一个 Token 大概率是自然语言回答
本质上,LLM 只是在"续写"一段包含工具调用格式的对话,和写普通文字没有区别。
9.5 多工具串联:一次任务,多次调用
Function Calling 的真正威力在于多工具串联:LLM 可以连续调用多个工具,每次把上一步的结果作为下一步的输入。
用户:"帮我查一下北京和上海的天气,然后推荐周末去哪个城市旅游。"
第一轮:
LLM 输出:调用 get_weather(city="北京")
外部执行:返回北京天气数据
第二轮(结果注入 Context):
LLM 输出:调用 get_weather(city="上海")
外部执行:返回上海天气数据
第三轮(两个结果都在 Context 里):
LLM 综合两个城市的数据,生成推荐:
"上海周末晴天 24°C,北京有轻度雾霾,建议去上海。"
这种模式,就是 AI Agent 的雏形——LLM 自主规划多个步骤,逐步完成目标。
9.6 工程注意事项
| 注意点 | 说明 |
|---|---|
| description 写清楚 | 模型根据 description 决定是否调用;描述模糊会导致误调用或漏调用 |
| 工具数量适中 | 工具越多,模型"选错"的概率越高;单次对话建议不超过 20 个 |
| 做好错误处理 | 工具调用可能失败(网络超时等),要有 fallback,不能让整个对话挂起 |
| 危险操作加人工确认 | 删除、支付、发送邮件等不可逆操作,不能由模型直接触发 |
| 工具结果控制大小 | 工具返回内容太长会占满 Context Window,要提前裁剪或摘要 |
| 幂等性 | 工具可能被重复调用(模型重试),设计时考虑幂等性 |
9.7 Function Calling 是 AI Agent 的基石
没有 Function Calling,LLM 永远困在文字的世界里。
有了 Function Calling,LLM 可以:
- 查询实时数据(不再幻觉)
- 写入文件、执行命令(从"说"到"做")
- 调用任意 API(连接整个互联网)
- 串联多步骤工具调用(完成复杂任务)
这正是下一章 AI Agent 的技术基础:Agent = LLM 大脑 + Function Calling 协议 + 外部工具集合。
十、LLM 的成本与速度
10.1 计费模型
主流 LLM API 按 Token 计费:
费用 = 输入 Token 数 × 输入单价 + 输出 Token 数 × 输出单价
Claude Sonnet 4.6(参考价):
输入:$3 / 百万 Token
输出:$15 / 百万 Token
一次对话(1000 输入 + 500 输出):
费用 = 1000/1,000,000 × $3 + 500/1,000,000 × $15
= $0.003 + $0.0075
= $0.0105 ≈ 约 7 分钱人民币
输出比输入贵:因为输出是自回归逐个生成,计算量更大。
10.2 速度瓶颈
影响响应速度的因素:
TTFT(首 Token 时间)
= 处理输入 Token 的时间
→ 输入越长,TTFT 越长
TPS(每秒输出 Token 数)
= 模型生成速度
→ 通常 50~100 Token/秒
→ 输出 1000 Token ≈ 10~20 秒
总延迟 = TTFT + 输出 Token 数 / TPS
10.3 降低成本和延迟的工程手段
| 手段 | 效果 | 代价 |
|---|---|---|
| 使用小模型 | 成本降低 10x,速度提升 5x | 能力下降 |
| 缩短 Prompt | 减少输入 Token | 可能降低质量 |
| 限制输出长度(max_tokens) | 控制输出成本 | 可能截断 |
| 缓存(Prompt Cache) | 相同前缀只计算一次 | 需要管理缓存 |
| 流式输出(Streaming) | 用户感知延迟降低 | 处理复杂度增加 |
| 批处理(Batching) | 吞吐量提升 | 响应延迟增加 |
十一、多模态:超越文字
11.1 现状
主流 LLM 已支持多种模态输入:
| 模态 | 代表模型 | 典型用途 |
|---|---|---|
| 文字 → 文字 | 所有 LLM | 对话、写作、代码 |
| 图片 → 文字 | GPT-4o, Claude 3.5 | 图片描述、OCR、图表分析 |
| 文字 → 图片 | DALL-E, Midjourney | 图片生成 |
| 语音 → 文字 | Whisper | 语音转录 |
| 文字 → 语音 | ElevenLabs, TTS | 语音合成 |
| 视频 → 文字 | Gemini 1.5 Pro | 视频理解 |
11.2 工程含义
- 图片处理比文字贵:一张图片通常消耗 1000~2000 Token
- 不同模态有不同的能力边界:模型看图的能力远弱于读文字
- 多模态输入需要特殊的 API 格式(base64 编码或 URL)
十二、AI Agent:让 LLM 从"问答"变成"行动"
以下分析基于两个有源码依据的真实系统:Claude Code(Anthropic 官方 CLI)和 OpenClaw(开源个人 AI 助手)。
12.1 什么是 AI Agent
纯 LLM 是一个"问答机器":输入 → 输出,一问一答,不能主动做事。
AI Agent 在 LLM 之上加了一个执行循环,让模型可以:
用户目标
↓
[Agent Loop 循环执行]
├── 思考:现在应该做什么? ← LLM 负责
├── 行动:调用工具(读文件/搜索/写代码/执行命令)← 外部工具执行
├── 观察:工具返回了什么结果? ← 结果注入 Context 给 LLM
└── 判断:目标完成了吗?→ 没有 → 继续循环 ← LLM 负责
↓
目标达成,输出结果
核心差异:
| 纯 LLM | AI Agent | |
|---|---|---|
| 交互方式 | 一问一答 | 自主多步执行 |
| 工具使用 | 无 | 可调用外部工具 |
| 状态保持 | 无(单次对话) | 有(跨步骤记忆) |
| 主动性 | 被动响应 | 可主动规划和执行 |
12.2 LLM 在 Agent 中扮演的角色
Agent 系统中 LLM 是大脑,工具是四肢,两者缺一不可。
┌──────────────────────────────────────────────────────┐
│ Agent 系统 │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ LLM(大脑) │ │
│ │ │ │
│ │ ① 理解用户意图 │ │
│ │ "帮我重构这个函数" → 拆解成具体步骤 │ │
│ │ │ │
│ │ ② 决策:下一步调用哪个工具?参数是什么? │ │
│ │ → 输出 Function Call 指令 │ │
│ │ │ │
│ │ ③ 解读工具结果 │ │
│ │ → 工具返回文件内容,LLM 理解并继续规划 │ │
│ │ │ │
│ │ ④ 判断任务是否完成 │ │
│ │ → 生成最终回答或继续下一轮循环 │ │
│ └──────────────────────────────────────────────┘ │
│ ↕ Function Call / Tool Result │
│ ┌──────────────────────────────────────────────┐ │
│ │ 工具层(四肢) │ │
│ │ │ │
│ │ 读文件 / 写文件 / 执行命令 / 搜索 / 调 API │ │
│ │ LLM 本身无法做这些,必须通过工具完成 │ │
│ └──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
LLM 具体负责哪些决策:
| Agent 阶段 | LLM 做的事 | 工具做的事 |
|---|---|---|
| 意图理解 | 把自然语言拆解成可执行计划 | 无 |
| 工具选择 | 决定调用哪个工具、传什么参数 | 无 |
| 工具执行 | 无 | 实际执行(读写文件、命令等) |
| 结果解读 | 理解工具返回的内容,判断下一步 | 无 |
| 错误处理 | 工具失败时决定重试还是换方案 | 无 |
| 任务完成判断 | 判断目标是否达成 | 无 |
关键理解:LLM 每次只处理当前 Context 内的信息,它看不到"过去的步骤",只能看到被注入 Context 的工具结果。Agent 框架的核心工作之一就是把每步的工具结果正确地写入 Context,让 LLM 能"记住"已经做过什么。
12.3 案例一:Claude Code
依据:
CLAUDE_CODE_ARCHITECTURE.md(基于官方 CLI 架构分析)
是什么
Claude Code 是 Anthropic 官方的命令行 AI 编程助手,采用工具增强的 Agent 架构,在本地运行,直接操作你的代码仓库。
核心架构
用户输入(自然语言)
↓
┌─────────────────────────────────────────┐
│ Claude Code Core │
│ │
│ 会话管理器 → 维护对话历史和上下文 │
│ 权限控制器 → 控制哪些工具可以执行 │
│ 上下文管理器 → 识别工作目录/Git状态 │
│ 记忆系统 → CLAUDE.md + Auto Memory │
└──────────────────┬──────────────────────┘
↓
┌──────────────────────────────────────────┐
│ Agent 执行引擎(Agent Loop) │
│ │
│ 思考 → 规划 → 执行 → 反思 → 迭代 │
│ │
│ 子 Agent 管理: │
│ Explore Agent → 探索代码库 │
│ Plan Agent → 制定实现计划 │
│ Bash Agent → 执行 Shell 命令 │
│ General Purpose → 通用任务 │
└──────────────────┬──────────────────────┘
↓
┌──────────────────────────────────────────┐
│ 工具管理器(34+ 个工具) │
│ │
│ 本地工具:Read/Write/Edit/Bash/Glob/Grep │
│ MCP 工具:动态加载外部 MCP Server 工具 │
│ 子 Agent 工具:派发子任务 │
└──────────────────────────────────────────┘
LLM 在 Claude Code 中的作用
Claude Code 使用 Claude 模型作为唯一的大脑,所有决策都由它完成:
用户说:"帮我把这个函数改成异步的"
↓
LLM 接收到:System Prompt(角色设定)
+ CLAUDE.md(项目规范记忆)
+ Git 状态(当前工作目录上下文)
+ 用户指令
LLM 决策输出(Function Call):
→ 先调用 Read 工具读取目标文件
↓ 文件内容注入 Context
→ LLM 理解代码结构,规划修改方案
→ 调用 Edit 工具写入修改
↓ 修改结果注入 Context
→ LLM 判断:是否需要同步修改调用方?
→ 调用 Grep 搜索所有调用处
↓ 搜索结果注入 Context
→ LLM 决策:逐一修改调用处
→ 调用 Bash 运行测试验证
↓ 测试结果注入 Context
→ LLM 判断:测试通过,任务完成
LLM 在每一步的角色:理解当前状态 → 决定下一步工具调用 → 解读结果 → 继续或结束。
Claude Code 能做什么
✅ 能做
──────────────────────────────────────────
读取、编写、编辑本地文件(Read/Write/Edit)
搜索代码库(Glob/Grep)
执行 Shell 命令(Bash)
Git 操作(status/diff/commit/push)
多文件联动修改(理解跨文件依赖)
派发子 Agent 并行处理子任务
调用 MCP Server 扩展工具能力
通过 CLAUDE.md 记忆项目规范
Hooks 机制:工具执行前后触发自定义脚本
⚠️ 有限制
──────────────────────────────────────────
不能访问互联网(除非配置了联网 MCP Server)
长任务中 Context Window 可能耗尽(需要手动压缩)
复杂架构决策仍需人工判断
跨会话不自动记忆(需要 CLAUDE.md 显式记录)
❌ 不能做
──────────────────────────────────────────
运行图形界面程序
实时监听文件变化(被动触发)
自主部署到生产环境(需要人工确认)
理解二进制文件内容
权限控制机制
Claude Code 的关键设计是权限分级,不是所有操作都自动执行:
工具调用权限等级:
自动执行(无需确认):
→ Read(读文件)
→ Glob(文件搜索)
→ Grep(内容搜索)
需要用户确认:
→ Write/Edit(写文件)
→ Bash(执行命令)
→ 网络请求
永远需要确认:
→ 危险命令(rm -rf、git push --force)
→ 生产环境操作
12.4 案例二:OpenClaw
依据:
openclaw_architecture.md(基于源码openclaw-main分析)
是什么
OpenClaw 是开源的个人 AI 助手网关,核心理念:
你在哪里聊天,AI 就在哪里。
把 Claude/GPT/Gemini 等 AI 接入 WhatsApp、Telegram、Slack、Discord、iMessage 等日常 IM 工具,在用户自己的设备上运行,数据不经第三方。
核心架构
IM 平台(Telegram/Discord/Slack...)
↓ Webhook / Bot API / 长连接
┌─────────────────────────────────────┐
│ OpenClaw Gateway │
│ (运行在用户本地设备) │
│ │
│ 接入层 → 消息规范化为 MsgContext │
│ 路由层 → 7级优先级匹配 → agentId │
│ 调度层 → HTTP/WS服务、认证、Cron │
│ Agent层 → 会话管理、Prompt构建 │
│ → Tool调用循环、流式输出 │
│ 插件层 → Memory/Canvas/联网搜索 │
│ Provider层→ 多模型统一封装+Failover │
└──────────────┬──────────────────────┘
↓ API 调用
外部 AI 模型(Claude/GPT/Gemini/本地Ollama)
LLM 在 OpenClaw 中的作用
OpenClaw 支持多个 LLM Provider(Claude/GPT/Gemini/本地 Ollama),LLM 是可替换的"引擎":
Telegram 用户:"帮我搜一下今天的 AI 新闻并总结"
↓
OpenClaw 流程:
接入层 → 消息规范化为 MsgContext
路由层 → 匹配到 agentId="default"
Agent层 → 加载会话历史 + 构建 Prompt:
System Prompt(人设)
+ Memory 技能注入(长期记忆)
+ 会话历史(短期记忆)
+ 可用工具列表(Web Search 等)
+ 用户消息
LLM 决策:需要调用 Web Search 工具
↓ Function Call 输出
Web Search 工具执行 → 返回新闻内容
↓ 结果注入 Context
LLM 生成摘要回答
↓ 流式输出
发回 Telegram 用户
LLM 在各层的具体角色:
接入层 → 与 LLM 无关,纯消息格式转换
路由层 → 与 LLM 无关,规则匹配
调度层 → 与 LLM 无关,网络服务
↓
Agent 层 ← LLM 在这里介入,是唯一的决策者
│
├── Prompt 构建完成后 → 发给 LLM
│ LLM 读取:系统人设 + 历史对话 + 工具列表 + 用户消息
│
├── LLM 输出①:普通文本回复
│ → 直接流式发回用户
│
└── LLM 输出②:Function Call(工具调用指令)
→ Agent 层解析,执行对应工具
→ 工具结果写回 Context
→ 再次调用 LLM,继续决策
→ 循环直到 LLM 不再发出 Function Call
Provider 层 → 封装各家 LLM 的 API 差异,LLM 对 Agent 层透明
LLM 在 OpenClaw 中的三个核心角色:
| 角色 | 具体表现 |
|---|---|
| 对话者 | 理解用户自然语言,生成自然语言回复 |
| 工具调度员 | 决定何时调用哪个工具(Web Search/Code Run 等),生成 Function Call 指令 |
| 内容生成器 | 基于工具返回的真实数据,组织成最终回答发给用户 |
OpenClaw 中 LLM 的特殊点:
- 可热切换:主模型失败时 Failover 自动换备用模型,LLM 对 Agent 层透明
- 多 Agent 差异化配置:不同 agentId 可配置不同 LLM(如个人助手用 Claude Opus,编程助手用 Claude Sonnet)
- 本地模型可选:隐私场景可换成 Ollama 本地运行,LLM 推理完全在用户设备上,零数据出网
- 流式输出:LLM 每生成一个 Token 就立即推送给 IM 平台,用户看到逐字输出效果
OpenClaw 能做什么
✅ 能做
──────────────────────────────────────────
接入 10+ 个 IM 平台(Telegram/Discord/Slack/
WhatsApp/iMessage/Signal/飞书/Matrix...)
多 Agent 路由:不同用户/群组路由到不同 Agent
跨会话记忆(Memory 技能)
联网搜索(Web Search 技能)
代码执行(Code Run 技能,沙箱环境)
Webhook 集成(外部系统注入消息)
Cron 定时任务(定时触发 Agent)
多模型自动故障转移(Failover)
本地模型支持(Ollama,数据不出网)
语音对话(Voice Call 技能)
插件扩展(自定义通道/工具/技能)
⚠️ 有限制
──────────────────────────────────────────
记忆依赖 Memory 技能,默认不持久化
联网能力依赖 Web Search 插件,非内置
跨平台会话默认隔离(需配置共享)
代码执行在沙箱内,无法访问宿主文件系统
❌ 不能做
──────────────────────────────────────────
直接操作用户设备的文件系统(设计如此)
访问 IM 平台的历史消息(只能接收新消息)
主动发起 IM 会话(只能被动响应)
实时感知用户上线状态
OpenClaw 的路由机制(7 级优先级)
来自源码分析:
消息进来时,按优先级从高到低匹配 Binding 规则:
1. binding.peer → 精确匹配特定用户/群组(最高优先)
2. binding.peer.parent → 匹配线程父级
3. binding.guild+roles → Discord 服务器 + 角色组合
4. binding.guild → 整个 Discord 服务器
5. binding.team → Slack 工作区
6. binding.account → 账号级别
7. binding.channel → 通道通配符(最低优先)
default → 默认 Agent(兜底)
12.5 两者对比
| Claude Code | OpenClaw | |
|---|---|---|
| 定位 | 编程助手(操作代码) | 个人助手网关(多平台接入) |
| 运行环境 | 终端命令行 | 本地后台服务 |
| 核心能力 | 读写文件、执行命令、Git | 多平台消息路由、AI 对话 |
| 工具来源 | 内置34+工具 + MCP Server | 插件系统(通道/技能/Provider) |
| 记忆机制 | CLAUDE.md + Auto Memory | 会话 JSONL + Memory 技能 |
| 多模型 | 单模型(Claude) | 多模型 + Failover |
| 数据隐私 | 本地运行,代码不上传 | 本地运行,数据不经第三方 |
| 扩展方式 | MCP Server | Plugin SDK(npm 包) |
| 适合场景 | 软件开发、代码任务 | 日常助手、自动化、多平台 |
12.6 AI Agent 的通用能力边界
无论哪种 Agent 系统,以下是共同的能力边界:
✅ Agent 比纯 LLM 强的地方
──────────────────────────────────────────
可以执行多步骤任务,不需要用户每步都参与
可以调用真实工具(文件、命令、API)
可以基于中间结果调整计划
可以并行派发子任务提高效率
可以跨会话保持记忆(需要显式设计)
⚠️ Agent 的固有限制
──────────────────────────────────────────
每一步仍然依赖 LLM 的推理质量
工具调用失败会导致任务中断(需要容错设计)
长任务会消耗大量 Context Window
自主执行有风险:错误操作难以撤回
调试困难:多步骤执行链路难以追踪
❌ Agent 做不到的事
──────────────────────────────────────────
真正的"理解"(仍是模式匹配)
100% 可靠的自主执行(必须有人工监督机制)
超出工具边界的操作(工具没给的能力,Agent 没有)
实时感知外部世界变化(被动触发,非主动监听)
12.7 Agent 系统设计的关键原则
原则一:Human-in-the-loop(人在回路)
高风险操作必须人工确认,Claude Code 的权限分级就是最好的实践。Agent 越自主,需要越严格的确认机制。
原则二:工具边界即能力边界
Agent 能做的事,完全由给它的工具决定。设计 Agent 系统 = 设计工具集合 + 设计工具的权限边界。
原则三:失败是常态,容错是必须
工具调用失败率在生产环境中不可忽视,需要:重试机制、降级策略、人工介入触发点。
原则四:Context 是稀缺资源
长任务中 Context Window 会耗尽,需要主动压缩历史、提炼关键信息,而不是等到溢出再处理。
十三、能力边界速查表
✅ LLM 做得好
──────────────────────────────────────────
文本生成、改写、摘要、翻译
代码生成、解释、重构
分类、情感分析、意图识别
结构化提取(从文字中提取 JSON)
问答(基于提供的上下文)
头脑风暴、创意写作
格式转换
⚠️ LLM 能做但要小心
──────────────────────────────────────────
数学推理(简单可以,复杂容易出错 → 用工具)
事实问答(可能幻觉 → 用 RAG + 要求引用)
长文档理解(中间部分注意力弱 → 分段处理)
复杂逻辑推理(多步骤 → 用 CoT)
代码调试(简单 Bug 可以,复杂系统问题不行)
❌ LLM 做不好
──────────────────────────────────────────
精确计算(→ 调用计算器工具)
实时信息(→ 调用 API)
持久记忆(→ 数据库 + RAG)
确定性输出(同样输入可能不同输出)
超长文档逐字检索
真正的"理解"(模型是模式匹配,不是真正理解)
十三、常见误解纠正
| 误解 | 真相 |
|---|---|
| "LLM 有意识/情感" | 没有。只是复杂的统计模式匹配 |
| "LLM 在搜索互联网" | 没有。知识在参数里,截止于训练日期(除非有工具) |
| "LLM 每次回答一样" | 不是。有随机性(Temperature),同样问题可能不同答案 |
| "更长的 Prompt 一定更好" | 不是。太长可能稀释关键指令,"Lost in the Middle" |
| "LLM 知道自己不知道什么" | 不知道。这是幻觉的根源 |
| "GPT-4 比 GPT-3.5 在所有任务都强" | 不一定。大模型在某些简单任务反而更慢更贵,不必然更准 |
| "Fine-tuning 能解决所有问题" | 不是。微调改变行为风格,RAG 才能注入新知识 |
十四、总结:工程师需要记住的核心认知
1. LLM 是"概率续写机器",不是"知识检索系统"
→ 会幻觉,需要 RAG 和工具调用来补充
2. Context Window 是硬限制
→ 超出即"失忆",设计时必须考虑
3. Token 是一切的基本单位
→ 成本、速度、长度限制都以 Token 计算
4. Temperature 控制随机性
→ 准确性要求高 → 低 Temperature
→ 创意要求高 → 高 Temperature
5. 幻觉是模型的固有特性,不是 Bug
→ 必须在系统层面设计验证机制
6. 工具调用是补充能力边界的正确方式
→ 计算用计算器,实时信息用 API,存储用数据库
7. Prompt 是"编程语言"
→ 清晰、具体、有示例的 Prompt 产生更好的输出
8. 能力在快速进化,边界在持续扩展
→ 今天做不好的事,3 个月后可能就能做好
→ 持续跟踪模型更新
本文档写于 2026 年,基于当前主流 LLM(Claude、GPT-4o、Gemini)的实际工程经验。