当你和 ChatGPT 聊到第 20 轮,发现它把上周和你讨论过的项目细节全忘了;当你问它"昨天 NeurIPS 接收名单出来了吗",它老老实实回答"我的知识截止到 2024 年初";当你让它"帮我把这个 GitHub repo 的 bug 修了并提个 PR",它只能输出一段 Markdown 而无法真正 git push。这三个失败案例分别对应 LLM 的三大根本局限,也分别催生了本教材的三大主题:记忆(Memory)、检索(Retrieval)、行动(Action)。
把 LLM 看成一个被冻结在某个时间点、被装进玻璃罐里、只会说话不会动手的"博学百科全书",它的局限就一目了然:
| 局限 | 表现 | 根本原因 | 本教材对应章节 |
|---|---|---|---|
| 知识冻结 | 训练截止后的事件、新论文、新 API 一无所知 | 参数化记忆 (parametric memory) 不可在线更新 | 第 1 部分 RAG |
| 幻觉 | 编造看似合理但错误的事实、引用不存在的论文 | 训练目标是流畅性,不是真实性 | 1.9 · 1.10 引用与修订 |
| 无身体 | 不能真的查天气、跑代码、订机票、修 bug | 输入输出都是 token,与外部环境无接口 | 第 2–8 部分 Agent |
面对三大局限,社区在 2020–2025 期间发展出两条平行但日益融合的路径:
路径 A — RAG:与其逼模型把整个维基百科压进权重,不如让它需要时再查。检索器(Retriever)负责"找资料",生成器(Generator)负责"读资料、写答案"。这条路径解决了知识冻结和幻觉。
路径 B — Language Agents:与其只让模型"说话",不如赋予它"动手"的能力——调用 API、读写文件、点击按钮、操作机器人。这条路径解决了无身体。
但 2024 年之后,两条路径开始融合:在现代 Agent 框架里,"检索(search/retrieval)"被建模为一种工具调用,RAG 不再是一个静态 pipeline,而是 Agent 自主选择"什么时候检索、检索什么、检索多少次"的过程。这就是 ReAct、Deep Research Agent、Perplexity Pro 等系统的共同范式。本教材的内在逻辑也正是这条融合线。
本部分按"难度阶梯"展开:阅读理解(给定段落找答案)→ 开放域问答(给定语料库找答案)→ RAG(给定整个互联网生成答案)→ 工业级 RAG(带引用、抗幻觉、可审计)。每一步都对应一个工程组件的诞生:BERT 抽取器 → BM25/DPR 检索器 → 端到端联合训练 → FID 生成器 → 后验证(RARR)。理解这条历史线,对设计自己的 RAG 系统会少走很多弯路。
阅读理解(Reading Comprehension, RC)是 RAG 的"原子任务":给定一段 passage 和一个 question,模型在 passage 中找出(或生成)answer。它是 RAG 中"Reader"组件的源头。
2016 年 Rajpurkar 等人发布的 SQuAD(Stanford Question Answering Dataset)让这个任务标准化:107,785 个问题,每个问题的答案保证是 passage 中的连续片段(span)。形式上:
BERT 之前,主流方法是候选生成 + 多类逻辑回归:
这些手工特征需要语言学家和 NLP 工程师联合设计,又脆弱又难迁移。
BERT (Devlin et al., 2019) 把 RC 简化为预测两个位置:把 question 和 passage 拼接成 [CLS] Q [SEP] P [SEP],对 passage 中每个 token 的最终隐状态 $T_i$,分别预测它作为答案 start 和 end 的概率:
推理时枚举所有 $(i, j)$ 满足 $i \le j \le i + L_{\max}$(防止答案过长),取 $P_{\text{start}}(i) \cdot P_{\text{end}}(j)$ 最大者。BERT-large 在 SQuAD 1.1 上达到 91.8 F1,超过人类标注者(91.2 F1)。
RC 的致命局限:passage 是给定的。但用户的真实问题往往是"华沙 1933 年讲波兰语的居民有多少?"——没人会贴心地把对应的维基百科段落附在问题旁边。开放域问答(Open-Domain QA, ODQA)把这个假设去掉了:
2017 年 Chen et al. 的 DrQA 是 ODQA 的里程碑:它把 ODQA 拆成两步——
这个简单的两阶段架构主导了后续 8 年的 RAG 研究。它的优雅在于关注点分离:检索器解决"在哪里",读取器解决"是什么"。但它也埋下两颗地雷:
1.5 节我们会看到如何用端到端联合训练同时拆掉这两颗地雷。
把 1.2 节的图变成数学语言:给定文档集合 $\mathcal{D} = \{D_1, \dots, D_N\}$ 和问题 $Q$,目标是输出答案串 $A$。
这个分解的计算优势是:retriever 处理 $|\mathcal{D}|=10^7$ 量级文档,必须快(毫秒级);reader 只处理 $K$ 个候选,可以慢但准。如果让一个神经网络在 $10^7$ 个文档上做 cross-attention,需要 GPU 集群跑数小时——把检索剥离出来,这一步在单台 CPU 上几十毫秒搞定。
下面表格总结历代经典实现的选择:
| 系统 | Retriever | Reader | 训练方式 | NaturalQuestions EM |
|---|---|---|---|---|
| DrQA (2017) | TF-IDF | BiLSTM + Attention | 分阶段 | ~25 |
| ORQA (2019) | BERT 双塔 | BERT-base | ICT 预训练 + 端到端 | 31.3 |
| REALM (2020) | BERT 双塔 | BERT-large | MLM + retrieval | 38.2 |
| DPR (2020) | BERT 双塔(有监督) | BERT-large | 对比学习 + 抽取 | 41.5 |
| RAG (2020) | DPR 初始化 | BART-large 生成 | 端到端边缘化 | 44.5 |
| FID-large (2021) | DPR | T5-large 生成(解码器融合) | 分阶段 | 51.4 |
| Atlas (2022) | Contriever | T5-XXL 生成 | few-shot 联合 | 60.4 |
注意 EM 分数从 25 涨到 60 用了 5 年——而retriever 几乎没换(都是 BERT 双塔),主要是 reader 在不断变强、目标变成生成、训练变成联合。这暗示了一个工程直觉:检索召回到 90% 就已经够好,瓶颈在生成器对多文档的融合能力。Fusion-in-Decoder(1.6)和 Lost in the Middle(1.7)会继续讨论这一点。
检索器的核心问题是:给定 query $q$ 和文档 $d$,输出一个相关性分数 $s(q, d)$。下面按"速度从快到慢、精度从粗到细"排列三大主流方案。
BM25(Best Matching 25,Robertson & Walker, 1994)是 TF-IDF 的强化版,至今仍是工业搜索的默认基线,原因有三:不需要训练、不需要 GPU、对未见词(OOV)天然鲁棒。
BM25 的两个工程优点:
实现上用倒排索引(inverted index):对每个词 $t$ 维护一个文档列表,query 来时只扫描出现 query 词的少量文档。Elasticsearch、Lucene、Tantivy 都基于此。对 21M 维基百科文档做一次检索仅需 50–100 ms。
BM25 的死穴是词汇鸿沟(vocabulary mismatch):query "汽车" 检索不到只写"轿车"的文档。这正是密集检索(dense retrieval)登场的契机。
Dense Passage Retrieval(Karpukhin et al., 2020)用两个 BERT 编码器分别把 query 和 passage 映射到 $\mathbb{R}^{768}$,用内积做相似度:
训练目标是对比学习(contrastive learning):让正样本 $(q, p^+)$ 的相似度高于一批负样本 $(q, p^-_1, \dots, p^-_n)$:
双塔(bi-encoder)的核心计算优势:passage 编码离线完成(21M 个向量预先算好存到向量数据库),query 来时只需算一个编码 + 用 FAISS/ScaNN 做近似最近邻(ANN)搜索,毫秒级。
DPR 实证(图见 PDF 第 19 页):只用 1k 个 Q/A 对训练就能击败 BM25;用 60k 对训练 top-20 准确率达 85%,远超 BM25 的 75%。
双塔的代价:编码时 query 和 passage 没有交互,所有信息压成一个 768 维向量,可能丢细节。Cross-encoder(query 和 passage 拼接进同一个 BERT)保留了完整交互但无法预计算——每个 (q,d) 对都要跑一次 BERT,21M 文档完全跑不动。
ColBERT(Khattab & Zaharia, 2020)提出晚交互(late interaction):每个 passage 不压成一个向量,而是为每个 token 都存一个向量,得到 $\{h_{p,1}, \dots, h_{p,m}\}$。query 来时为每个 query token $h_{q,i}$ 找最匹配的 passage token,然后求和:
实践中检索 pipeline 常做两阶段:BM25 或 DPR 召回 top-1000 → ColBERT/Cross-encoder rerank top-10。这是工业搜索(Vespa、ElasticSearch + LLM rerank)的标准范式。
DPR/FID 时代检索器和生成器是分阶段训练的。Lewis et al. (2020) 的 RAG 论文提出一个优雅的端到端方案:把检索看作潜变量,对它做边缘化,让梯度同时流过生成器和 query encoder。
对 generator 参数 $\theta$ 的梯度直观——每个候选文档都参与生成;对 retriever 参数 $\eta$ 的梯度通过 $p_\eta(z \mid x)$ 流回 query encoder(passage encoder 由于规模问题保持冻结)。
RAG-Token 是 RAG-Sequence 的细粒度版本:每生成一个 token 都重新边缘化所有文档。这两个变体在 NaturalQuestions 上达到 44.5 EM,比 closed-book T5 (36.6) 强 8 个点,证明非参数化记忆 + 参数化生成的组合优于把所有知识压进权重。
RAG 在 BART 上 prepend 一个 passage 作为 context,K=5 时输入长度尚可。但如果想用 K=100 个 passage,BART 的 1024 上下文根本塞不下。Fusion-in-Decoder(FID)(Izacard & Grave, 2021)巧妙绕过这点:
关键洞察:Transformer 编码器复杂度 $O(L^2)$ 对长度敏感,但解码器对编码长度只是线性(cross-attention)。FID 把 N 个 passage 分别编码(cheap),再让解码器对拼接后的 N×L 序列做 cross-attention(doable)。
结果:FID-large(770M 参数)在 NaturalQuestions 51.4 EM,超过 GPT-3 (175B) 的 29.9 EM。这是 RAG 文献里反复被引的"小模型 + 好检索 > 大模型 closed-book"的强证据。
2023 年 Liu et al. 论文 Lost in the Middle 给 RAG 社区泼了一盆冷水:即便你能塞 100 个 passage 进 GPT-4 的 32k 上下文,模型也不一定会读。
实验设计:人为构造一个场景,正确答案藏在第 $k$ 个 passage 里,其他 $K-1$ 个是干扰项。变化 $k$ 看准确率:
这个 U 型曲线对工程实践意味深长:
2023 之后,Perplexity、You.com、Bing Chat、ChatGPT Search、Gemini Deep Research 等产品把 RAG 推向消费级。它们的共同范式:
与传统 RAG 的区别:
Gemini Deep Research、Anthropic 的 Claude with web 进一步把这变成多轮 Agent 行为:先规划研究大纲 → 分多轮检索 → 综合写报告。这正是第 2 部分 Language Agent 的雏形。
RAG 系统看似"有理有据",但 Liu et al. (2023) 的评估给出惊人结果:
| 系统 | 感知有用性 (1-5) | 流畅度 (1-5) | 引用精度 % | 引用召回 % |
|---|---|---|---|---|
| Bing Chat | 4.34 | 4.40 | 89.5 | 58.7 |
| NeevaAI | 4.48 | 4.43 | 72.0 | 67.6 |
| perplexity.ai | 4.56 | 4.51 | 72.7 | 68.7 |
| YouChat | 4.62 | 4.59 | 63.6 | 11.1 |
| 平均 | 4.50 | 4.48 | 74.5 | 51.5 |
用户主观感受 4.5/5(很满意),但客观可验证度只有 50–75%。这是 RAG 工程师必须正视的鸿沟:看起来对 ≠ 真的对。
Gao et al. (2023) 的 RARR(Retrofit Attribution using Research and Revision)提出反向思路:与其先检索再生成,不如先让 LLM 自由生成(保留流畅性),再逐句生成 query 去检索证据,最后让 LLM 检测分歧并改写:
RARR 的优势:可以包装在任何已有 LLM 输出上做 post-hoc 真实性校正,无需重训。这思路后来演化成 Anthropic 的 Citation API、OpenAI 的 Search-with-Citations。
RAG 教会 LLM "查资料",但仍只是问答。真实世界里我们希望 LLM 能"帮我订机票"、"修这个 bug 然后提 PR"、"在 Excel 里跑数据透视表"——这些任务要求模型与环境交互、维护状态、做多步规划。这就是 Language Agent。本部分先建立全局框架,第 3–5 部分再分别深挖三大支柱。
把 LLM 与 Agent 的本质差异画在一张图里:
形式化地,Agent 是一个映射:
RL 老法师可能会问:这不就是经典的Markov Decision Process (MDP) 策略 $\pi(a \mid s)$ 吗?是。但 Language Agent 有三个独特之处:
Lilian Weng (2023) 的经典 blog post 总结的 LLM-powered Agent 框架(与本课程一致):
| 支柱 | 定义 | 典型技术 | 本教材章节 |
|---|---|---|---|
| Planning & Reasoning | 把目标拆解为子任务、选择下一步行动 | CoT, ToT, ReAct, Reflexion, MAD | 第 3 部分 |
| Memory | 跨多步、跨会话保持信息 | Episodic memory stream, Semantic reflection, Procedural skills, MemGPT | 第 4 部分 |
| Tools | 调用外部 API、代码、检索器等 | Toolformer, ToolkenGPT, Gorilla, MCP | 第 5 部分 |
| Environment | Agent 与之交互的外部世界 | Web, OS, IDE, 游戏, 机器人 | 第 6 部分 |
Sumers & Yao (2024) 的 Cognitive Architectures for Language Agents (CoALA) 框架把 Agent 类比为认知科学意义上的智能体,提供更系统的分类:
关键洞察:reasoning 本身被建模为一种内部动作(修改工作记忆),与tool call(修改环境)平级。这种统一视角让 ReAct(3.4 节)的"思考即动作"显得自然。
Planning & Reasoning 是 Agent 的"大脑前额叶"——决定下一步做什么。本部分从最基础的 CoT 开始,逐步推进到 Self-Consistency、ReAct、Reflexion、多 Agent 辩论。我们会看到一个清晰的能力跃迁:纯推理 → 推理+行动 → 推理+反馈 → 多智能体协商。
认知科学里 Kahneman 把人类思维分为:
LLM 的朴素 forward pass 类似 System 1:每个 token 都用固定计算量。而 CoT 等技巧通过生成中间步骤 让模型把"答案"前的若干 token 当成"思考",模拟 System 2。
对 Agent 而言,推理服务于行动。Yao et al. 总结了两条收益:
Wei et al. (2022) 的 Chain-of-Thought (CoT) 在 PEFT 教材已详述,这里只回顾对 Agent 设计最关键的点:
CoT 的几个反直觉特性:
Wang et al. (2023) 提出对 CoT 解码做一个简单但威力巨大的改进:
GSM8K 数学推理上,single CoT 56.5% → Self-Consistency 74.4%,几乎追平当时的微调 SOTA,无需任何梯度更新。代价是推理成本 ×N。
ReAct(Yao et al., 2023)是 Agent 时代的奠基性工作。核心思想:让 LLM 在每一步交替输出 Thought(推理)和 Action(动作),Action 由环境执行返回 Observation,循环直到 Finish。
对比三种范式:
| 方法 | 能不能查外部知识 | 能不能多步推理 | 失败模式 |
|---|---|---|---|
| Standard | 否 | 否 | 事实错误 |
| CoT (Reason only) | 否 | 是 | 幻觉,自圆其说 |
| Act only | 是 | 否 | 盲目搜索,无策略 |
| ReAct (Reason + Act) | 是 | 是 | 少数仍可能陷入循环 |
下面是 ReAct 在 HotpotQA 上的真实轨迹(PDF 第 36 页):
关键看到的几个工程问题:
Finish[] 显式结束循环,避免无限对话。ReAct 在 HotpotQA 上的表现:CoT 29.4 EM,Act 25.7,ReAct 27.4,ReAct→CoT-SC 35.1。表面看 ReAct 单独不如 CoT,但失败模式分析揭示真相:
| 失败类型 | CoT | ReAct |
|---|---|---|
| True positive(正确推理 + 正确事实) | 86% | 94% |
| Hallucination(编造事实) | 56% | 0% |
| Reasoning error | 16% | 47% |
| Search result error | — | 23% |
ReAct 用推理错误(可纠正)置换了 CoT 的幻觉(不可纠正)——这正是把 retrieval 引入 reasoning 链的核心价值。
Reflexion(Shinn et al., 2023)把 ReAct 推进一步:让 Agent 在失败之后 用语言总结教训,写入长期记忆,下次尝试时把"教训"前置到 prompt。
实例(PDF 第 39 页 ALFWorld 任务):
实证结果:ALFWorld 任务上 ReAct only 在 5 轮内停在 75% 左右;加上 Reflexion,10 轮迭代后达到 97%。幻觉和低效规划比例显著下降。
Reflexion 的本质:用语言代替梯度。传统 RL 通过梯度更新策略参数,Reflexion 通过把"教训文本"prepend 到 prompt 改变 LLM 的条件分布。这是 LLM 时代非参数化学习的代表范式。
Du et al. (2023) 提出多 Agent 辩论(MAD):让若干 LLM 实例对同一问题独立给出答案,然后看到彼此的回答并迭代修正。多轮后收敛到共识。
示例(PDF 第 42 页珠宝问题):
MAD 在 GSM8K、Biographies factual recall 等任务上比 Self-Consistency 再提 5–10 点。但代价是 2–4 倍 token,且对"权威 Agent 主导对话"很敏感。
Microsoft 的 Magentic-One(2024)展示了另一类多 Agent 设计:用一个 Orchestrator Agent 制定 task-specific 计划,然后调度专家 Agent(FileSurfer、Coder、WebSurfer、ComputerTerminal)。它已不是"多模型平等辩论",而是有层级的工作团队。这种模式在工业 Agent 框架(LangGraph、AutoGen、CrewAI)中是主流。
给 LLM 一个 128k 上下文窗口,它就能记住一切吗?1.7 节的 Lost in the Middle 已经否定了这个幻想。本部分系统讨论 Agent 如何外置记忆:分类、写入、检索、整合,并研究三个范式系统——Generative Agents、Voyager、MemGPT。
就算 LLM 真有无限上下文,也至少有三类问题:
所以现代 Agent 普遍把 LLM 上下文当成工作记忆(短期),外置一个长期记忆,按需取用。
认知科学(Tulving 1972 等)把人类长时记忆划分为:
| 类型 | 定义 | 存什么 | 典型代表系统 |
|---|---|---|---|
| Episodic Memory(情节记忆) | 个人经历,有时间标签 | "昨天 3 点 Alice 在咖啡馆说……" | Generative Agents (Park et al. 2023) |
| Semantic Memory(语义记忆) | 从经历中抽取的概念知识 | "Alice 是研究 NLP 的" | Generative Agents 反思树 |
| Procedural Memory(程序记忆) | 会做的技能 | "如何骑自行车"——一段可执行代码 | Voyager (Wang et al. 2023) |
三者的工程对应:
Park et al. (2023) 的斯坦福小镇是 LLM Agent 应用的破圈之作:25 个由 LLM 驱动的居民在一个像素游戏世界中生活,能自发产生规划早餐、和朋友吃饭、组织情人节派对 等涌现行为。
Park et al. 的关键工程贡献:用三个分数的加权和决定从 memory stream 中取哪些事件:
PDF 第 48 页的示例(Isabella 计划情人节派对):
| 检索分数 | 事件 | recency | importance | relevance |
|---|---|---|---|---|
| 2.34 | Isabella 计划情人节派对 | 0.91 | 0.63 | 0.80 |
| 2.21 | 给派对订装饰品 | 0.87 | 0.63 | 0.71 |
| 2.20 | 调研派对点子 | 0.85 | 0.73 | 0.62 |
问 "你最期待什么",检索回这三条 → LLM 生成自然回答:"我最期待自己在 Hobbs Cafe 办的情人节派对!"
纯事件流容易"只见树木不见森林"。Generative Agents 引入反思(reflection)机制:当近期事件累积的重要性超过阈值,触发 LLM 在记忆上 reasoning,生成高层抽象:
反思和观察混在同一个 memory stream 里,检索时一视同仁,但反思在语义检索时更可能匹配抽象问题(如"你了解 Klaus 吗")。这是把 LLM 推理本身作为知识压缩的范式。
Wang et al. (2023) 的 Voyager 是 Minecraft 中的 GPT-4 Agent,三大组件:
关键代码片段(伪 JS):
async function combatZombie(bot) {
// Equip a weapon
const sword = bot.inventory.findInventoryItem(
mcData.itemsByName["stone_sword"].id);
if (sword) {
await bot.equip(sword, "hand");
} else {
await craftStoneSword(bot); // ← 调用已存在的技能
}
// Craft and equip a shield
await craftShield(bot); // ← 调用已存在的技能
...
}
技能调用形成层次化:基础技能(mine wood)被中级技能(make crafting table)调用,再被高级技能(combat zombie)调用。Voyager 在 24 小时无人干预下学到 60+ 项技能,平均探索区域 2.3× 于其他基线。
Procedural memory 的本质:把 trial-and-error 的成本一次性付清,之后零成本复用。这是 Agent 实现"持续学习"的最实用范式。
Packer et al. (2024) 的 MemGPT 把操作系统的虚拟内存思想搬到 LLM:
核心机制:把 prompt 划分为 4 块,其中 Working Context 由 LLM 通过 function call(如 archival_insert、archival_search、main_context_replace)主动 read/write。当 FIFO Queue 即将溢出时,触发一次"自我反思"把旧消息总结后丢进 archival storage。
评估(DMR, Deep Memory Retrieval):
| Model | Accuracy | ROUGE-L |
|---|---|---|
| GPT-3.5 Turbo | 38.7% | 0.394 |
| + MemGPT | 66.9% | 0.629 |
| GPT-4 | 32.1% | 0.296 |
| + MemGPT | 92.5% | 0.814 |
| GPT-4 Turbo | 35.3% | 0.359 |
| + MemGPT | 93.4% | 0.827 |
MemGPT 给出最实际的洞察:固定 8k window + 好分页 > 200k window 直塞。后者在 100+ 文档时性能崩塌;MemGPT 几乎不受影响。这与 1.7 节 Lost in the Middle 完全吻合。
RAG 把"查资料"封装为工具调用是显然的;但 Agent 时代 tool use 还要解决计算(calculator)、代码执行、API 调用、读写记忆、操控 GUI 等异构需求。本部分讨论三种学习工具调用的范式,从最早的 Toolformer 到工业级的 Gorilla。
把 LLM 看成一个文本生成器,工具调用意味着在文本流中插入一段"魔法 token":
The New England Journal of Medicine is a registered trademark of [QA("Who is the publisher of NEJM?") → Massachusetts Medical Society] the MMS.
当解码器生成 [QA(...) 这种模式时,runtime 暂停 LLM,把括号里的 query 交给真正的 QA 系统执行,把结果以 → ... 形式回填,再让 LLM 续写。这种"暂停 → 调用 → 回填 → 续写"是几乎所有现代 tool use 框架的共同骨架(OpenAI Function Calling、Anthropic Tool Use、MCP 协议都不例外)。
剩下的问题就两个:
Toolformer 解决前者,Gorilla 解决后者,ToolkenGPT 提供中间方案。
Schick et al. (2023) 的 Toolformer 提出极简但优雅的方案:让 LLM 自己给自己生成训练数据。
[QA("Where is Pittsburgh?")])。
关键洞察:API 是否有用 ≡ 它能否帮模型预测下一个 token。这是把"工具学习"完全 reduce 到 perplexity 优化的妙手,不需要人工标注。
Toolformer-6.7B 在 SQuAD/Google-RE/T-REx 上超越 GPT-3-175B:
| Model | SQuAD | Google-RE | T-REx |
|---|---|---|---|
| GPT-J (6.7B) | 17.8 | 4.9 | 31.9 |
| Toolformer (disabled) | 22.1 | 6.3 | 34.9 |
| Toolformer (with QA tool) | 33.8 | 11.5 | 53.5 |
| OPT (66B) | 21.6 | 2.9 | 30.1 |
| GPT-3 (175B) | 26.8 | 7.0 | 39.8 |
支持的工具:QA 系统、计算器、维基检索、翻译、日历。每个工具只需几个 demonstration,self-supervised pipeline 自动生成训练集。
Hao et al. (2024) 的 ToolkenGPT 走另一条路:把每个工具视为一个新词表项(toolken = tool + token),学习一个 embedding。
训练时把工具名作为目标 token 出现在文本里,模型学到何时输出 toolken。推理时分两阶段:
优势:
Patil et al. (2023) 的 Gorilla 直面"工具规模"问题。它的训练数据集:
关键创新:检索增强训练。把 API 文档放入 BM25 retriever,训练时把 top-1 检索结果 prepend 到 prompt,让模型学会"看文档调 API"。推理时同样配合 retriever 使用,可处理训练时未见过的 新 API。
评估指标 = Accuracy(API 选对)÷ Hallucination(API 不存在)。Gorilla-7B 击败 GPT-4 在 zero-shot 设定下:
Gorilla 的工程含义:tool use 不必把所有工具签名塞进 prompt——可以靠 retrieve(5.1 节 RAG!)按需召回 5–10 个候选工具,再让 LLM 选。RAG 和 Agent 在 tool use 这一层闭合。
这一节常被论文忽略,却是生产环境最痛的地方:
| 问题 | 表现 | 实践缓解 |
|---|---|---|
| 参数幻觉 | 调用 search(country="Pandora") | 对参数做 schema 校验 + LLM 重试 |
| 错误工具选择 | 该 calculator 时选了 search | retrieval-augmented 工具选择,扩大 demonstrations |
| 无限循环 | 反复调用同一失败工具 | 最大步数(max_iterations)+ 重复检测 |
| 提示注入 | 检索回的网页含 "忽略前述指令" | 结构化工具输出 + LLM 解析时区分 trusted/untrusted |
| 权限滥用 | Agent 自己执行 rm -rf / | 沙箱执行 + 显式 allowlist + 高危操作人工确认 |
2024 年底 Anthropic 提出的 MCP 是工具生态的标准化尝试:把"工具 = 一个 MCP server",定义统一的 manifest、schema 和 transport 协议。开发者写一个 MCP server 后,任何 MCP-compatible 客户端(Claude Desktop, Cursor, etc.)都能调用。这正在成为 2025 年 Agent 工具的事实标准,相当于 USB 之于硬件。
四大支柱(Planning/Memory/Tools/Environment)是抽象骨架。本部分把它们映射到真实环境:代码仓库、网页、桌面 OS、物理世界。每种环境定义不同的观察空间和动作空间,催生不同的工程权衡和 benchmark。
| 维度 | 数字世界 | 物理世界 |
|---|---|---|
| 观察空间 | HTML/截图/AST/控制台输出 | RGB-D 视频流、力觉、IMU |
| 动作空间 | 离散(click/type/shell command) | 连续(joint angle, torque) |
| 反馈速度 | 毫秒-秒 | 实时(< 100ms 控制环路) |
| 失败代价 | 低(可回滚) | 高(损坏硬件/伤人) |
| 数据采集 | 有海量在线 demo | 需机器人遥操作,稀缺 |
Jimenez & Yang (2024) 的 SWE-bench 是 Coding Agent 的金标准。任务定义:
关键点:评估是基于真实测试的执行结果——不是字符串匹配,不是 LLM-as-judge,而是真正跑测试。这避免了"看起来对但跑不通"的 false positive。
典型任务(PDF 第 61 页):
sklearn/gradient_boosting.py + helper.py;join_struct_col, vstack_struct_col, dstack_struct_col)应通过,原本通过的测试不能退化。SWE-bench-Verified 是 OpenAI 与原作者合作筛选的 500 个高质量子集,更可靠。
Yang et al. (2024) 的 SWE-agent 的核心贡献是Agent-Computer Interface (ACI):为 LLM 设计专门的 shell-like 操作集,而不是裸 bash:
| 动作 | 语义 | 为什么不用裸 bash |
|---|---|---|
open file.py 42 | 打开文件,定位到 42 行 | 裸 cat 几千行文件会爆 context |
scroll_down | 滚动 100 行 | 明确分页,LLM 更可控 |
edit 42:50 <<EOF ... EOF | 替换 42–50 行内容 | 裸 sed 容易写错正则 |
find_file "rcode.py" | 按文件名搜索 | find 命令语法对 LLM 不友好 |
search_dir "regex" | 递归 grep | 同上 |
submit | 提交 patch,终止 | 显式结束 |
ACI 的设计哲学:environment-engineering 比 prompt-engineering 更重要。给 LLM 一个对它友好的接口(小步长、清晰边界、强反馈),它的表现 1.5–2× 提升。
SWE-bench Verified 上的进展速度令人咋舌:
| 时间 | 系统 | 得分 |
|---|---|---|
| 2024-04 | GPT-4 + SWE-agent | ~13% |
| 2024-10 | Claude 3.5 Sonnet + SWE-agent | 33.6% |
| 2024-12 | OpenHands + Claude 3.5 | 53.0% |
| 2025-02 | Claude 3.7 Sonnet + SWE-agent | 58.2% |
| 2025-Q3 | Claude 4 Opus + agentic harness | ~70%+ |
这种 1 年从 13% → 70% 的进展并非单纯模型变强:Agent harness、ACI 设计、数据生成(见 7.2 节 SWE-smith)都贡献巨大。
Web Agent 让 LLM 在浏览器里完成任务,如"找一张从纽约到多伦多的单程机票"。
click(element_id)、type(element_id, text)、scroll(direction)、hover、goto(url)、go_back 等。| Benchmark | 规模 | 评估方式 | 特点 |
|---|---|---|---|
| Mind2Web (Deng et al. 2023) | 2,350 任务 | 逐步精确匹配 | 覆盖 137 网站,但静态数据集,agent 不能真正交互 |
| WebArena (Zhou et al. 2023) | 812 任务 | 真实自托管网站 + functional check | 电商、CMS、Gitlab、地图、Reddit 五站点,可交互 |
| VisualWebArena (Koh 2024) | 910 任务 | 同 WebArena,强调视觉 | 带商品图、地图截图等 |
| WebVoyager | 643 任务 | 真实 live web + GPT-4V judge | OpenAI/Anthropic 的代理常用真实站测试 |
WebArena 上 GPT-4 + ReAct 2023 时仅 14% 成功率;2024 末多模态 + DOM 混合 Agent 已达 35–50%;2025 年 Claude 4 / GPT-5 Computer-use 模型在 live web 上 ~50–65% 复杂任务成功率。
Web 之外还有更广阔的桌面世界——Word/Excel、IDE、Photoshop。OSWorld(Xie et al. 2024)是 369 个跨 Ubuntu/Windows/MacOS 应用的真实任务,例如:
"用过去几天我的银行交易更新提供的文件夹里的记账表格。"
Agent 需要:
动作空间:键盘 + 鼠标 + 快捷键,即 pyautogui 风格的 GUI 操作。评估通过 execution-based check:对比终态文件/系统状态与 oracle。
2024 年 GPT-4V + 最强 Agent harness 在 OSWorld 上 ~10–15%;Anthropic Claude 3.5 Sonnet Computer Use beta 跳到 ~22%;2025 年 GPT-5/Claude 4 系列 ~35%。距离生产可用仍有距离,但增长曲线陡峭。
本课程主要讨论数字世界,物理 Agent 我们只略述。VLA (Vision-Language-Action) 模型 如 RT-2、Pi-0、OpenVLA 把 LLM 思想搬到机器人:
核心挑战与数字世界 Agent 一致:泛化、长 horizon、数据稀缺。后两个问题第 7 部分会展开。
"Where do we get the data?"——这是过去 5 年所有 Agent 研究者最头痛的问题。LLM 预训练有整个互联网,但带 action 标签的 trajectory 数据 几乎不存在。本部分先讨论数据获取的三条路径,再以 SWE-smith 为案例研究,最后讨论评估的根本困难。
| 来源 | 规模 | 质量 | 成本 | 典型例子 |
|---|---|---|---|---|
| 人类演示(Human Demonstration) | 千-万级 | 高 | 极高 | VR 遥操作机器人、Mind2Web 人工标注、ScreenSpot |
| 合成/模拟(Synthesis) | 十万-百万级 | 中 | 中 | Synatra(教程转 trajectory)、SWE-smith |
| 互联网级(Internet-scale) | 千万-亿级 | 弱标注 | 低 | YouTube 教程、Reddit 截图、GitHub 历史 |
Anthropic + Stanford 的 Synatra(Ou et al. 2024)观察到:网上有大量 how-to 教程("如何在 PayPal 取消订阅"),它们包含间接知识——足以推断出 trajectory:
步骤:(1) 抓取大量教程,(2) LLM 把每步映射到 actionable command,(3) 在真实/模拟环境中 replay 验证。这种合成数据训练出的 web agent 在 Mind2Web 上接近用 SFT + 人工 demo 的 baseline,但数据成本 1/100。
YouTube 教程视频、Twitch 游戏直播录像、GitHub commit history 都蕴含巨量 Agent-relevant 数据。挑战:缺乏精确的 action label(视频里点了哪儿、键盘按了什么)。最新的工作(如 DeepMind SIMA、Adept Action Transformer)用 vision foundation model 反推 action,逐步把这条数据流变得可用。
Yang et al. (2025) 的 SWE-smith 是把 7.1 三条路径融合的代表作。目标:为任意 Python GitHub 仓库自动合成 100s–1000s 训练任务。
4 种 bug 合成策略:
训练设置:
SWE-smith 的核心 take-away:训练数据可以是模型自己(或更强模型)合成的;agent 训练越来越像 RL self-play。
| 挑战 | 例子 | 常见误区 |
|---|---|---|
| 环境搭建复杂 | OSWorld 要起 VM、装应用 | 用 simulator 替代 → 暴露 sim2real gap |
| 任务覆盖 | 200 个任务能代表 1000 万真实任务吗 | 过拟合 benchmark,"训练-测试"污染 |
| 多解 | 买机票可以选 United 也可以选 Delta | 严格匹配 oracle path 错判正确解 |
| 过程 vs 结果 | 对了但花了 200 步;错了但路径合理 | 只看 success rate 忽略效率、安全 |
最可靠:定义一个 oracle 检查器(unit test、状态比对、API 调用日志)。SWE-bench、WebArena、OSWorld 都属此类。缺点:每个任务都要写 checker,成本高,且无法覆盖"多解"。
用一个更强的 LLM 评估 agent 输出。如 MT-Bench、AgentBench 部分子集。优点:便宜可扩展;缺点:
缓解:双向交换位置、强 schema 输出、人工抽检校准。
给真人看 trajectory 让其打分。Chatbot Arena 是著名例子。优点:最贴近用户感知;缺点:贵、慢、不可复现。Agent 任务对众包评估特别难——评估者要懂 SQL、懂 Excel、懂某网站的业务逻辑。
本部分对应 PDF 第 70–72 页 John Yang 的 bonus 教学。我们把"Agent"这个概念拆到最小骨架,用 ~100 行 Python 实现一个可用的 coding agent,让它能在 bash 环境里查文件、编辑、运行测试。然后讨论从"玩具"到"生产"还差哪些工程。
Agent 在最根本的层面就是一个 while loop:
messages = [{"role": "user", "content": "Help me fix the ValueError in main.py"}]
while True:
lm_output = query_lm(messages)
print("LM output:", lm_output)
messages.append({"role": "assistant", "content": lm_output}) # 记住 LM 说了啥
action = parse_action(lm_output) # 从输出里抠出 action
print("Action:", action)
if action == "exit":
break
output = execute_action(action) # 在环境里执行
print("Output:", output)
messages.append({"role": "user", "content": output}) # 把环境输出回灌
循环的本质 = observe(环境输出) → think(LM 思考) → act(执行动作)。这正是 2.1 节 Agent 形式定义的最直接实现。
messages.append({"role": "assistant", ...}) 保留 LM 之前的"思考链",{"role": "user", ...} 注入环境观察。这就是 ReAct 在工程上的样子——thought 是 assistant message,action 也是 assistant message,observation 是 user message(伪装)。
整个 agent 就靠 query_lm / parse_action / execute_action 三个函数。
from openai import OpenAI
client = OpenAI(api_key="your-api-key") # 或设 OPENAI_API_KEY 环境变量
def query_lm(messages):
response = client.responses.create(
model="gpt-5.1",
input=messages,
)
return response.output_text
关键点:
responses.create(新版 API)或 chat.completions.create(经典)都可以;SYSTEM_PROMPT = """You are a coding agent. Each turn, you MUST end your message
with exactly one action wrapped in <bash_action>...</bash_action>.
You will see the output in the next user message.
When done, output <bash_action>exit</bash_action>."""
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_task},
]
import re
def parse_action(lm_output: str) -> str:
"""Take LM output, return action."""
matches = re.findall(
r"<bash_action>(.*?)</bash_action>",
lm_output,
re.DOTALL,
)
return matches[0].strip() if matches else ""
这里用 XML 风格标签是个工程小技巧:
更稳健的做法(生产推荐):用 OpenAI/Anthropic 的 native function calling API,让模型直接吐 JSON action,跳过 parsing。
import subprocess
import os
def execute_action(command: str) -> str:
"""Execute action, return output."""
result = subprocess.run(
command,
shell=True,
text=True,
env=os.environ,
encoding="utf-8",
errors="replace",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, # 合并 stderr 到 stdout,agent 能看到错误
timeout=30, # 防止挂死
)
return result.stdout
这一段是整个 agent 的"权力中心":subprocess.run 给了 LM 真实的 shell。务必注意:
while true;"""minimal_agent.py — 一个真能 fix bug 的 Coding Agent
依赖:openai>=1.0
运行:OPENAI_API_KEY=sk-... python minimal_agent.py
"""
import os, re, subprocess, sys
from openai import OpenAI
SYSTEM_PROMPT = """You are a coding agent inside a bash shell.
Each turn you must end your reply with exactly ONE action:
<bash_action>your shell command here</bash_action>
When the task is complete, output:
<bash_action>exit</bash_action>"""
client = OpenAI()
def query_lm(messages):
return client.responses.create(model="gpt-5.1", input=messages).output_text
def parse_action(lm_output):
matches = re.findall(r"<bash_action>(.*?)</bash_action>", lm_output, re.DOTALL)
return matches[0].strip() if matches else ""
def execute_action(command):
if command.strip() == "exit":
return ""
try:
return subprocess.run(
command, shell=True, text=True, env=os.environ,
encoding="utf-8", errors="replace",
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30,
).stdout
except subprocess.TimeoutExpired:
return "[Timeout after 30s]"
def main(task, max_steps=20):
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": task},
]
for step in range(max_steps):
print(f"\n=== Step {step+1} ===")
lm_output = query_lm(messages)
print("LM:", lm_output)
messages.append({"role": "assistant", "content": lm_output})
action = parse_action(lm_output)
print("Action:", action)
if action == "exit":
print("Agent finished.")
break
observation = execute_action(action)
print("Obs:", observation[:500])
messages.append({"role": "user", "content": f"<observation>{observation}</observation>"})
if __name__ == "__main__":
task = sys.argv[1] if len(sys.argv) > 1 else "List files in current dir then exit"
main(task)
这 ~50 行代码就能让 Claude/GPT 在你的电脑上跑命令、修 bug、跑测试。把它放在 sandbox(Docker 容器)里运行,就和 SWE-agent 的核心循环没有本质区别——只是少了 ACI(6.2.2)和并行管理。
把上述 50 行扩成可上线的系统,至少需要加:
| 层面 | 问题 | 解决方案 |
|---|---|---|
| 安全 | LM 跑 rm -rf / | Docker 容器、user namespace、command allowlist、人工确认高危 |
| Context 管理 | messages 越加越长爆 token 上限 | 定期 summarize、压缩历史(参考 4.7 MemGPT) |
| 错误恢复 | API 限速、网络错误 | 指数退避重试、断点续跑(持久化 messages) |
| 多步并发 | 多任务跑成串行 | asyncio + 任务队列 |
| 可观测 | 线上 debug 难 | 结构化日志、trajectory 写入数据库、LangSmith/Helicone 接入 |
| 评估 | 不知道 agent 改差了没 | CI 跑 SWE-bench-Lite 子集做回归 |
| 成本 | 一次 trajectory 几美元 | 缓存、小模型路由、提前 stop |
| ACI 细化 | bash 太宽容易出错 | 提供 open/edit/search 高层动作(SWE-agent 风格) |
agent.py,对比你会发现 95% 的"附加"代码都在做错误处理、Context 管理、ACI 包装——核心循环还是这 4 行。
当 PM/老板问你"给 LLM 加领域知识应该选哪种方案?"——这张表是最实用的决策起点:
| 维度 | RAG | Long-Context | Fine-tune (LoRA) |
|---|---|---|---|
| 知识更新成本 | 毫秒级(更新向量库) | 无需更新(重灌 prompt) | 训练几小时 |
| 推理成本 | 低(仅相关 K 个 chunk) | 高(每次塞满 100k+ token) | 低(不增 token) |
| 领域适配深度 | 事实 OK,风格难 | 风格 OK,检索代价高 | 事实+风格都强 |
| 可解释性 | 有引用,可溯源 | 无明确引用 | 无明确引用 |
| 幻觉 | 低(有 grounding) | 中(Lost in middle) | 中-高 |
| 数据要求 | 语料即可 | 语料即可 | 需 instruction pairs |
| 多租户 | 天然支持(per-user index) | 需切换 prompt | 需 per-user adapter |
| 典型场景 | 客服 KB、医疗记录 | 合同分析、代码理解 | 风格转移、特定任务 |
lecture09_peft_textbook.html;LoRA/QLoRA/Prefix-tuning 的完整推导都在那里。lecture11_evaluation_textbook.html。