CS-Notes全部笔记在阅读器中打开

学习路线 - 大模型应用开发

本文目录

一、说明

适合会一门编程语言(Python 或 Java)、准备大模型应用开发面试的读者,每天约 1~1.5 小时。动手部分需要一个兼容 OpenAI 接口的模型服务(例如 DeepSeek)或本地的 Qwen2.5 小模型;学过 Java 后端路线的读者可以跳过算法题。

每天先读完阅读范围,再不看笔记回答自测题,答完展开参考要点核对;动手练习对照预期的结果。自测答得上来、动手结果与预期一致、算法题提交通过,这一天就完成了。选读可以跳过。

AI 应用岗的面试也常问后端和计算机基础,最常见的是 MySQL 索引、分布式锁、WebSocket 与服务器推送事件(Server-Sent Events, SSE)、限流和熔断。没有后端基础的读者,还要学 Java 后端路线中的这些天:

投递大模型算法、Agent 算法岗位时,训练相关的题会问得更深,例如损失函数怎样计算、手写多头注意力,要再读进阶篇,并把其中的代码自己写一遍。

二、开始之前

本路线不讲语法。动手部分大多用 Python,开始前先确认能独立写出下面 5 个程序:

  1. 调用 https://api.github.com/repos/CyC2018/CS-Notes ,解析返回的 JSON,打印 stargazers_count;
  2. 读取一个 JSON Lines 文件(每行一个订单,含 user_id 和 amount),统计每个用户的订单数和总金额,写入 JSON 文件;
  3. 把一段长文本切成不超过 200 字的片段,只在句号处切分;
  4. 用列表保存对话历史,只保留最近 10 条消息;
  5. 请求超时时重试,最多 3 次,每次等待的时间翻倍。

写不出来的,先补对应的语法:HTTP 请求与 JSON、文件读写、字符串、列表、异常与循环。用 Java 写同样的程序也可以。

三、每天的安排

天 主题 阅读 算法
大模型基础
1 Token 与 Transformer 大模型应用:Token 与分词~Transformer 结构 1. 两数之和
2 注意力机制 大模型应用:注意力机制 15. 三数之和
3 生成、KV Cache 与采样 大模型应用:自回归生成与 KV Cache~采样参数 3. 无重复字符的最长子串
4 训练过程 大模型应用:训练过程 206. 反转链表
5 微调与量化 大模型应用:微调~量化 21. 合并两个有序链表
6 幻觉与推理模型 大模型应用:幻觉~推理模型 141. 环形链表
提示词与上下文
7 提示词与结构化输出 大模型应用:提示词~结构化输出 146. LRU 缓存
8 上下文工程 大模型应用:上下文工程 20. 有效的括号
检索增强生成
9 RAG 的流程、文档解析与切片 大模型应用:RAG 的流程~RAG 的切片 33. 搜索旋转排序数组
10 Embedding 与向量索引 大模型应用:Embedding 与相似度~向量索引 215. 数组中的第 K 个最大元素
11 向量数据库、混合检索与重排 大模型应用:向量数据库~重排 912. 排序数组(手写快速排序)
12 Top-K 与查询改写 大模型应用:Top-K 与阈值~查询改写 102. 二叉树的层序遍历
13 RAG 的评测与扩展 大模型应用:RAG 的评测与优化~多模态 RAG 236. 二叉树的最近公共祖先
Agent
14 Agent 与工具调用 大模型应用:Agent 是什么~Agent 与工作流 200. 岛屿数量
15 意图识别与规划 大模型应用:意图识别与路由~Agent 的规划 46. 全排列
16 记忆与多轮对话 大模型应用:Agent 的记忆~多轮对话 121. 买卖股票的最佳时机
17 MCP 与 Skills 大模型应用:MCP~Skills 70. 爬楼梯
18 多 Agent 与 Harness 大模型应用:多 Agent~Harness 53. 最大子数组和
19 开发框架、可靠性与评测 大模型应用:开发框架~可靠性与评测 5. 最长回文子串
工程实践
20 流式输出、成本与部署 大模型应用:流式输出~推理服务部署 300. 最长递增子序列
21 稳定性与安全 大模型应用:稳定性~大模型应用的安全 25. K 个一组翻转链表

四、大模型基础

第 1 天:Token 与 Transformer

Token 是模型处理文本的单位,也是计费和上下文长度的单位。Transformer 一节先看整体结构,再看参数分布在哪里;混合专家可以略读。

阅读:《大模型应用》从Token 与分词读到Transformer 结构。

自测:

  1. Transformer 的结构是怎样的?(见整体结构)
  2. 大模型的参数主要分布在哪里?(见参数都在哪里)
  3. 什么是 Token?同样的内容,中文和英文的 Token 数一样吗?(见Token 与分词)
参考要点
  1. 目前的大模型基本是只保留解码器的 Decoder-only 结构;Token 经词嵌入变成向量(Qwen2.5-7B 为 3584 维),依次经过几十个相同的层(28 层),每层先用注意力从前文收集信息,再用前馈网络逐位置加工;子层都带残差连接和归一化(多为 Pre-Norm 的 RMSNorm);最后一个位置经线性层得到 logits,softmax 后得到下一个 Token 的概率。
  2. 几乎都在矩阵中:每层的注意力和前馈网络,以及词嵌入和输出层(Qwen2.5-7B 中各约 7%);Qwen2.5-7B 的前馈网络三个矩阵(3584 → 18944 → 3584)占全部参数的 75%;GQA 使 k_proj、v_proj 只有 q_proj 的七分之一,总计约 76 亿个参数。
  3. Token 是分词器切出的文本片段,模型只处理它在词表中的编号,常用字节级 BPE 反复合并最高频的相邻片段;上下文窗口和费用都按 Token 计算;中英文不同,DeepSeek 估算 1 个中文字符约 0.6 个 Token、1 个英文字符约 0.3 个,准确数量以 usage 为准。

动手:下载 Qwen2.5-7B-Instruct 的 config.json,按参数都在哪里一节的方法算出一层中注意力和前馈网络的参数量,再与附录 A16对照。

预期:每层注意力 29,364,736 个参数(含 q、k、v 的偏置 4,608 个)、前馈网络 203,685,888 个(加两个 RMSNorm 共 233,057,792 个),28 层、两个嵌入加最后的归一化共 7,615,616,512 个,与附录 A16 一致。

算法(哈希表,简单):1. 两数之和,题解见数组中两个数的和为给定值。

明天:第 2 天,注意力机制(大模型应用:注意力机制)。

第 2 天:注意力机制

注意力是 Transformer 的核心,对着“饿”的例子把 Q、K、V 的计算走一遍。注意力本身不区分先后,位置编码一段看 RoPE 怎样让分数只取决于内容和相对距离。计算量与 FlashAttention 一节说明上下文变长后注意力的开销怎样增长;FlashAttention 结果不变,省的是显存读写。

阅读:《大模型应用》的注意力机制。选读:进阶:注意力机制。

自测:

  1. 注意力的计算公式是什么?为什么要除以 √d?(见注意力机制)
  2. 多头注意力有什么作用?(见多头注意力)
  3. FlashAttention 解决了什么问题?(见计算量与 FlashAttention)
参考要点
  1. 公式为 softmax(QKT / √d) V:Q 与各 K 点积得到分数,softmax 成权重后对 V 加权求和;分量独立、均值 0、方差 1 时点积的方差为 d,维度大时 softmax 几乎集中到一个 Token 上,梯度接近 0;除以 √d 使方差恢复为 1。
  2. 单头注意力中每个 Token 只有一组权重,只能按一种标准挑选信息;多头把 Q、K、V 切成 h 份分别计算,拼接后乘 WO,不同的头可以关注指代、相邻词等不同关系;每头维度为 d / h,计算量与单头基本相同。
  3. 标准注意力的计算量为 O(n2d),n × n 的分数矩阵占 O(n2) 显存,长上下文时显存读写成为瓶颈;FlashAttention 分块在 GPU 片上缓存中计算,分数矩阵不写回显存,用在线 softmax 逐块累积;结果在数学上与标准注意力相同,不是近似,省的是显存读写而不是计算量。

动手:用 NumPy 实现单头注意力 softmax(QKT / √d) V,输入 3 个 Token、维度为 4 的随机矩阵,打印注意力权重并检查每一行的和是否为 1。

预期:打印出 3 × 3 的权重矩阵,元素都在 0 到 1 之间,每一行之和为 1(只差浮点误差),输出的形状为 3 × 4。

算法(双指针,中等):15. 三数之和,题解见三数之和。

明天:第 3 天,生成、KV Cache 与采样(大模型应用:自回归生成与 KV Cache~采样参数)。

第 3 天:生成、KV Cache 与采样

模型每次只生成一个 Token。KV Cache 避免重复计算前面 Token 的 K 和 V,代价是占用显存,它的大小决定了一张显卡能同时服务多少请求。采样参数决定每一步怎样从概率分布中选出 Token。

阅读:《大模型应用》从自回归生成与 KV Cache读到采样参数。选读:进阶:自回归生成与 KV Cache。

自测:

  1. KV Cache 是什么?为什么能加快生成?(见自回归生成与 KV Cache)
  2. MHA、MQA 和 GQA 有什么区别?(见自回归生成与 KV Cache)
  3. temperature 和 top-p 分别控制什么?(见采样参数)
参考要点
  1. 因果掩码下前文 Token 的 K、V 不再变化,缓存起来复用就是 KV Cache;预填充一次算好输入的 K、V,解码每步只为新 Token 计算 Q、K、V,再与缓存做注意力,不再重算前文;代价是显存,Qwen2.5-7B 每个 Token 56 KB,32K Token 约 1.75 GB。
  2. MHA 中每个查询头都有自己的 K、V;GQA 让多个查询头共享一组 K、V,Qwen2.5-7B 的 28 个查询头共享 4 组,每个 Token 缓存 56 KB,MHA 要 392 KB;MQA 中所有查询头共享一组,缓存最小,但效果下降更明显;GQA 减少的是缓存和解码每步读取的数据量,计算量基本不变,每个查询头仍有自己的 Q,所以效果接近 MHA。
  3. temperature 把分数除以 T 再 softmax,T < 1 时分布更尖、T > 1 时更平,例如最高概率 0.50 在 T = 0.5 时升到 0.75;top-p 只在累计概率刚达到 p 的高概率 Token 中采样,候选数随分布变化;抽取字段、调用工具用低温度,写作可调高。

动手:按显存:权重与 KV Cache一节的方法,算出 Qwen2.5-7B 在 32K 上下文、同时处理 8 个请求时 KV Cache 占用的显存。

预期:每个 Token 2 × 28 × 4 × 128 × 2 = 56 KB,一个 32K 请求 1.75 GB,8 个请求共 14 GB,与约 14.2 GB 的权重相当。

算法(滑动窗口,中等):3. 无重复字符的最长子串,题解见无重复字符的最长子串。

明天:第 4 天,训练过程(大模型应用:训练过程)。

第 4 天:训练过程

按预训练、监督微调、偏好对齐三个阶段读。PPO、DPO 和 GRPO 的区别是常见的追问,比较时看每种方法需要哪些模型。

阅读:《大模型应用》的训练过程。选读:进阶:训练过程。

自测:

  1. 大模型的训练分为哪几个阶段?(见三个阶段)
  2. PPO、DPO 和 GRPO 有什么区别?(见DPO、GRPO 与三者的选择)
  3. RLHF 中的奖励模型是怎样训练的?(见RLHF 与 PPO)
参考要点
  1. 预训练:在海量文本上预测下一个 Token,获得语言能力和知识,但只会续写;监督微调:用指令和人工编写的回答训练,学会按对话格式回答、遵循指令;偏好对齐:用“两个回答哪个更好”的数据让回答更有帮助、更安全,这类目标难写标准答案,但比较容易。
  2. PPO 在线生成回答,奖励模型打分、价值模型给出预期,加载 4 个模型、训练 2 个;DPO 不训练奖励模型、不在训练中生成回答,直接用离线偏好对训练,只需策略模型和参考模型,简单稳定;GRPO 去掉价值模型,每题生成一组回答、以组内平均分为预期,适合能用规则判对错的推理训练;PPO 和 GRPO 能探索出训练数据之外的更好回答。
  3. 标注员把同一提示词的 4~9 个回答按好坏排序,两两组成偏好对(9 个回答 36 对);奖励模型输入提示词和回答、输出一个分数,损失为 -log σ(rw - rl),使较好回答得分更高,例如分数 1.2 和 0.3 时损失约 0.34。

动手:用 Python 按DPO一节的公式,对几组给定的 chosen 和 rejected 对数概率计算 DPO 损失,观察 β 取 0.1 和 0.5 时损失的变化。

预期:两个回答相对参考模型的变化都为 0 时损失为 log 2 ≈ 0.693;较好回答高 2、较差回答低 1 时,β = 0.1 的损失约 0.554,β = 0.5 约 0.201,β 越大,损失对同样的概率差越敏感:较好回答的变化大于较差回答时损失更低,小于时更高,相等时不变。

算法(链表,简单):206. 反转链表,题解见链表反转。

明天:第 5 天,微调与量化(大模型应用:微调~量化)。

第 5 天:微调与量化

先判断什么时候该微调、什么时候用 RAG 或提示词就够了。LoRA 是最常用的微调方法;量化一节中 GPTQ 与 AWQ 可以略读。

阅读:《大模型应用》从微调读到量化。选读:进阶:微调、进阶:量化。

自测:

  1. LoRA 的原理是什么?LoRA 和 QLoRA 有什么区别?(见LoRA、QLoRA 与全参数微调)
  2. 什么时候用微调,什么时候用 RAG?(见什么时候微调)
  3. 什么是灾难性遗忘?怎样缓解?(见灾难性遗忘)
参考要点
  1. LoRA 冻结原矩阵 W,只训练小矩阵 B、A,h = Wx + (α / r)·BAx,B 初始化为 0,训练开始时与原模型相同;可训练参数很少(Qwen2.5-7B 全部线性层 r = 16 时占 0.53%),训练后合并进 W,推理无额外开销;QLoRA 再把冻结的权重量化为 4 位 NF4 存储、计算时还原为 BF16,7B 权重从 15.2 GB 降到约 5.5 GB(嵌入层和输出层仍为 BF16)。
  2. 需要私有资料或经常更新的知识、要给出依据时用 RAG;要固定风格和格式、学会某种专业任务,提示词仍常出错时用微调;只是补少量规则和示例时用提示词;一般先提示词、再 RAG、最后微调,写进参数的知识难更新、无法给出来源。
  3. 模型在新数据上训练后原有能力明显下降,例如只用客服对话微调后写代码变差,1B~7B 模型的实验中模型越大越严重;缓解:混入通用指令数据,用 LoRA、较小的学习率和较少的轮数并早停,训练前后都在通用评测集上评测。

动手:按附录 A17的方法,用 Qwen2.5 的对话模板构造一条 SFT 样本,标出哪些 Token 参与计算损失。

预期:只有 assistant 的回答及其后的 <|im_end|> 和换行计入损失,system、user 消息和助手回答的开头(<|im_start|>assistant 和换行)标签为 -100;附录中的样本共 48 个 Token,提示部分 31 个,计入损失 17 个。

算法(链表,简单):21. 合并两个有序链表,题解见归并两个有序的链表。

明天:第 6 天,幻觉与推理模型(大模型应用:幻觉~推理模型)。

第 6 天:幻觉与推理模型

幻觉一节按定义、原因、缓解的顺序读,Agent 中的幻觉出现在调用工具的过程中,重点看为什么要在工具结果和代码上约束,只靠提示词不够。推理模型一节说明它和普通模型的区别,以及小模型怎样通过蒸馏获得推理能力。

阅读:《大模型应用》从幻觉读到推理模型。

自测:

  1. 什么是大模型的幻觉?为什么会产生?(见什么是幻觉、为什么会产生)
  2. 怎样检测和缓解幻觉?(见检测与缓解)
  3. Agent 调用工具时会出现哪些幻觉?(见Agent 中的幻觉)
参考要点
  1. 幻觉指生成看似合理、但与事实或给定资料不符的内容,分事实性幻觉和忠实性幻觉;根本原因是训练目标是生成通顺的下一个 Token 而不是核对事实,只按答对比例打分的评测又鼓励猜测;此外还有知识缺失、采样出错后顺着错误生成、上下文过长或资料有噪声。
  2. 检测:有资料时把回答拆成结论逐条检查依据,引文和数字用代码核对(只能确认它们存在,是否支持结论仍要逐条判断);RAG 答错时先看正确的资料是否在提示词中,区分检索和生成的问题;没有资料时多次采样比较,编造的内容往往每次不同,但始终一致的错误查不出来;缓解:用 RAG 提供资料并允许回答不知道,降低温度、要求引用,用程序或另一个模型校验,展示来源、高风险结果人工确认。
  3. 编造参数,例如用户没给订单号就自己编一个去查;编造或误读工具结果,例如接口超时仍说“正在运输中”、把 updated_at 当成送达时间;声称做了没做的事,例如没调用退款工具却说已退款;应对要靠工具和代码:结果字段写清楚、校验参数来源、用代码核对关键信息。

动手:阅读附录 A19,说出模型在什么情况下编造了工具结果中没有的信息,以及改写提示词和工具结果后怎样消除。

预期:缺订单号时模型 10 次都先询问,没有编造参数;物流接口超时时,只写流程的提示词下 3/10 次编造“运输中”,加上如实告知的要求后为 0;结果中没有送达时间时,只写流程 10 次都给出了送达时间,加上如实告知后仍有 9 次,其中 8 次把 updated_at 当成送达时间;改为 location_updated_at,附上“承运商暂未提供预计送达时间”的说明并给出 "eta": null 后,两种提示词下都为 0。

算法(链表,简单):141. 环形链表,题解见判断链表是否存在环。

综合题:用户问“我们公司的年假有几天”,模型回答了一个错误的天数:可能的原因有哪些?用提示词、RAG、微调分别能解决哪一部分?(见为什么会产生、什么时候微调、检测与缓解)

答题思路
  1. 先找原因:年假天数是公司私有资料,模型没有这部分知识,只能按通用知识“合理地”补全;接入了资料时,也可能检索到旧版制度、资料有噪声或上下文过长而用错资料。
  2. 提示词:要求只依据给定资料回答、资料不足时回答不知道,降低温度、要求引用原文,解决的是“猜答案”的倾向,补不了知识。
  3. RAG:年假制度属于私有且会更新的知识,检索制度原文放进上下文并给出来源,解决知识缺失这一主要原因;旧版本靠元数据过滤。
  4. 微调:不适合灌输这类事实,写进参数后难以更新、无法给出来源,还可能增加编造,只适合固定话术和格式。
  5. 校验:用代码检查回答中的天数是否出现在检索到的原文中,并展示来源。

明天:第 7 天,提示词与结构化输出(大模型应用:提示词~结构化输出)。

五、提示词与上下文

第 7 天:提示词与结构化输出

提示词一节讲系统消息与用户消息的分工、提示词的写法,以及改动后怎样在评测集上比较。结构化输出讲怎样让程序可靠地解析模型的回答:约束解码从源头保证格式,不支持时靠修复和带着错误重试兜底,业务规则仍要在代码中校验。

阅读:《大模型应用》从提示词读到结构化输出。

自测:

  1. 怎样写好一个提示词?(见提示词)
  2. 怎样让模型稳定地输出合法的 JSON?(见约束解码、输出无法解析时)
  3. 输出格式正确,内容就一定正确吗?(见格式正确不等于内容正确)
参考要点
  1. 身份、规则和输出格式放在系统消息中,写清任务、约束、输出格式和示例;要求具体,例如“不超过 100 字”而不是“简洁一些”;说明信息不足时怎么处理,否则模型倾向编造;用少样本示例说明格式,多步推理让普通模型先写步骤(CoT),推理模型不必再这样要求;改动在评测集上比较,每条运行多次,从失败的样例入手补规则或示例。
  2. 优先用约束解码:每生成一个 Token 前按 Schema 屏蔽会让输出不合法的 Token;JSON 模式只保证是合法 JSON,不保证字段;不支持时先修复(去掉代码块标记、截取首个 { 到末个 }),再用 JSON Schema 校验,失败时带着具体错误重试 1~2 次,仍失败就返回默认值或转人工。
  3. 不一定,约束解码和 Schema 校验只保证格式;订单是否属于当前用户、退款是否超过实付金额这类业务规则要在代码中校验;还要检查 finish_reason 是否为 length,以及字段类型是否与代码一致。

动手:按附录 A22的方法,让模型从 10 条左右的客服消息(其中一条同时提到两个订单)中抽取订单号、意图和金额,先修复再用 JSON Schema 校验,失败时把错误信息发回模型重试。

预期:常见的失败是 JSON 包在代码块里,修复后即可解析(附录中 40 次输出直接解析成功 26 次,修复后 40 次,通过校验 38 次);一条消息含两个订单号这类超出 Schema 设计的输入,带着错误重试后仍失败,需要修改 Schema。

算法(设计,中等):146. LRU 缓存,题解见LRU。

明天:第 8 天,上下文工程(大模型应用:上下文工程)。

第 8 天:上下文工程

上下文工程关心每次调用放进什么、按什么顺序放。压缩是重点:什么时候压缩、怎样压缩,以及怎样保证关键事实不丢。

阅读:《大模型应用》的上下文工程。

自测:

  1. 上下文工程和提示词工程有什么区别?(见上下文工程)
  2. 对话历史太长时怎样压缩?什么时候压缩?(见压缩的方法与时机)
  3. 压缩后怎样保证关键事实不丢失?(见压缩后不丢关键事实)
参考要点
  1. 提示词工程关注怎样写好指令,尤其是系统提示词;上下文工程决定每次调用前把哪些内容、按什么顺序放进窗口,包括规则、工具定义、检索资料、对话历史和记忆;Agent 每一步都有新的工具结果,每次调用前都要重新挑选和压缩。
  2. 方法有截断(只留最近几轮,丢失早期内容)、摘要(多一次调用,可能漏掉或改写细节)、清理旧的工具结果(风险最小)和外置到文件,工具调用和结果要成对删除;常组合成“系统提示词 + 摘要 + 最近几轮原文 + 当前问题”;时机按 Token 数而不是轮数,约 70% 时在后台生成摘要,到上限时必须先压缩。
  3. 订单号、金额、地址等由代码从工具参数和结果中提取单独保存,承诺通过工具写进记录;摘要分项输出、编号和金额照原文抄写,“已办理”只写有工具结果确认的操作,新摘要在旧摘要的基础上合并;用关键事实覆盖和探针问题检查,实测 3B 的结构化摘要保留 9.7 条事实,普通摘要只有 4.3 条(共 14 条)。

动手:按附录 A4的方法,自己写一段 10 轮左右、包含订单号、金额、地址和取件时间的客服对话,分别做普通摘要和结构化摘要,检查这些关键事实是否保留。

预期:结构化摘要保留的关键事实明显多于普通摘要(附录中 Qwen2.5-3B 平均 9.7 对 4.3 条,共 14 条),但手机尾号这类只提过一次的信息仍可能丢失。

算法(栈和队列,简单):20. 有效的括号,题解见用栈实现括号匹配。

综合题:一个客服机器人的每次请求都要带上系统提示词、工具定义、用户资料和最近的对话:怎样安排它们的顺序?Token 超出预算时先裁掉什么?(见放什么、按什么顺序、Token 预算、压缩的方法与时机、让前缀缓存命中)

答题思路
  1. 不变的内容放最前:系统提示词、工具定义按固定顺序在开头,让每次请求的前缀相同,提高前缀缓存的命中率,当前时间、用户名不要插在开头。
  2. 其后按变化频率放:用户资料在会话内不变时紧跟工具定义,再依次追加对话摘要、最近几轮原文和当前问题,历史原样追加不重新拼接;重要规则放在开头或结尾,避免 Lost in the Middle。
  3. 先算预算:历史可用 = 窗口 − 预留输出 − 固定部分 − 检索资料上限 − 当前问题,例如 32K 窗口、预留 2K 输出、检索 4K 时历史约 25K。
  4. 超出时先清理旧的工具结果,再把较早的对话并入摘要,保留最近几轮原文,工具调用和结果成对删除;最近几轮中某条工具结果或粘贴的文档过大时先外置,只留相关片段,仍放不下再减少保留的轮数;系统规则、任务状态和当前问题不裁。
  5. 工具定义占比大时按需加载工具(例子中 6 个工具占 701 个 Token),但动态增减工具和频繁压缩都会破坏前缀缓存,要权衡。

明天:第 9 天,RAG 的流程、文档解析与切片(大模型应用:RAG 的流程~RAG 的切片)。

六、检索增强生成

第 9 天:RAG 的流程、文档解析与切片

先看 RAG 的完整流程,再看前两步:文档解析和切片。切片一节细读递归切分和对比实验,后者说明了不同切法对召回率的影响。

阅读:《大模型应用》从RAG 的流程读到RAG 的切片。

自测:

  1. RAG 的完整流程是怎样的?(见RAG 的流程)
  2. 有哪些切片策略?切片大小怎么确定?(见常用做法、怎么确定切片大小)
  3. PDF 为什么难解析?表格怎么处理?(见PDF 为什么难解析、版面分析)
参考要点
  1. 离线:解析文档、切片,用 Embedding 模型转为向量存入向量数据库,同时建立关键词索引;在线:改写问题,向量和关键词两路召回,合并后重排,把最相关的几段和问题放进提示词,生成带引用的回答;提示词中给切片编号,要求每个结论标注来源,资料中没有答案时如实回答没找到。
  2. 按标题、段落等结构切,表格和代码块保持完整,相邻切片保留重叠,切片带上标题路径;一节超过上限时递归切分,按空行、换行、句号、逗号由粗到细尝试,LangChain 默认的分隔符没有中文标点,要补上;没有标题的长文本可以语义切分,在相邻两句相似度明显下降处切开;也可以用小切片检索、返回所在的完整段落(父子切片);大小没有通用最优值,以 Embedding 模型的最大长度为上限,事实类问题用小切片,最后在评测集上比较召回率。
  3. PDF 只记录“在某坐标画某字”,没有段落和表格,直接读文本层会混入页眉页脚、句子在折行处断开;扫描件要先 OCR,表格可能被逐列输出;用版面分析识别标题、正文、表格和阅读顺序,表格转为 Markdown,按行切分时每行带上表头;跨页的表格在列数、列宽一致且中间没有新标题时拼成一张,去掉重复的表头。

动手:用 pypdf 提取一份 PDF 的文本,看页眉页脚是否混入正文、句子是否在折行处断开;再参照附录 A12,把一份中文 Markdown 文档分别按固定 200 字和递归切分(200 字,分隔符补上中文标点)切片,数一数各有多少切片在句子中间断开。

预期:PDF 的文本层常混入页眉页脚,句子在折行处断开;附录的语料中,固定 200 字有 88% 的切片在句子中间断开,补上中文标点的递归切分为 0%;换一份文档数字会不同,递归切分仍断在句中的,多是句子中间有换行或单句超过上限。

算法(二分查找,中等):33. 搜索旋转排序数组。

明天:第 10 天,Embedding 与向量索引(大模型应用:Embedding 与相似度~向量索引)。

第 10 天:Embedding 与向量索引

Embedding 把文本变成向量,相似度检索靠它完成。向量索引回答数据量大时为什么要用近似检索,重点是 HNSW,IVF 与 PQ 可以略读。

阅读:《大模型应用》从Embedding 与相似度读到向量索引。

自测:

  1. 文本是怎样变成向量的?为什么用余弦相似度?(见向量与余弦相似度)
  2. HNSW 的原理是什么?(见HNSW)
  3. 为什么需要近似最近邻检索?(见为什么需要近似检索)
参考要点
  1. 分词后输入 Transformer 编码器,每个 Token 得到一个向量,再池化(取 [CLS] 或平均值,与训练时一致)成固定长度的向量并归一化;对比学习使意思相近的文本向量靠近;余弦相似度看方向是否接近,归一化后等于点积、计算更快,排序与欧氏距离等价。
  2. 把向量组织成多层图,每个向量连接附近的若干向量,层越高向量越少、连线跨度越大,最下层包含全部向量;查询从最上层的入口开始,上面各层只保留一个最近节点,贪心地走向查询,走不动就下降一层,最下层保留 efSearch 个候选扩展邻居,从中取前 K 个,思路类似跳跃表;上层的长连线让查询几步就到目标附近,12.4 万个向量上每次只算约 1300 次距离(暴力检索的 1.1%);过滤掉大部分向量、大量删除和内存放不下时会变差;M、efConstruction、efSearch 在速度、内存和召回率之间权衡。
  3. 逐个比较时,100 万个 1024 维向量每次查询约 10 亿次乘加,数据量和并发大时太慢;ANN 索引用少量精度换速度,可能漏掉个别真正最近的向量。

动手:用 bge-small-zh 等 Embedding 模型计算几组句子的余弦相似度,参照附录 A1,找出一组语义相近但字面不同、向量检索优于关键词检索的例子。

预期:附录中“怎么申请年假”与“休假的流程是什么”几乎没有相同的字,余弦相似度 0.603,高于与“今天股价上涨了”的 0.194,这类例子关键词检索匹配不上而向量检索能找到。

算法(排序与堆,中等):215. 数组中的第 K 个最大元素,题解见Kth Element。

明天:第 11 天,向量数据库、混合检索与重排(大模型应用:向量数据库~重排)。

第 11 天:向量数据库、混合检索与重排

向量数据库除了检索还要处理过滤和更新。混合检索和重排分别弥补向量检索的两个不足:精确的关键词匹配不准,相似度排序不够精细。

阅读:《大模型应用》从向量数据库读到重排。

自测:

  1. 为什么要把向量检索和关键词检索结合起来?(见混合检索)
  2. 向量检索已经计算了相似度,为什么还要重排?(见重排)
  3. 向量数据库中的数据怎样更新和删除?(见更新与删除)
参考要点
  1. 向量检索擅长匹配措辞不同的近义内容,但精确匹配不准,E1024 和 E1025 文档的相似度 0.762 与 0.743 几乎分不开;BM25 按词频、逆文档频率和文档长度打分,罕见词 E1024 权重高,两篇得分 1.088 与 0.130;两路同时召回,分数尺度不同,用 RRF 按排名融合;混合不一定比单路好,口语化问题上等权 RRF 的 Recall@3(0.950)低于单独的向量检索(0.975),BM25 的权重要在评测集上确定。
  2. Embedding 模型分别编码问题和文档,文档向量能提前算好,速度快,但细节的对应关系在压成一个向量时丢失;重排模型(交叉编码器)把问题和文档拼在一起编码,每个词之间都能算注意力,更准,但每一对都要重算;所以先快速召回几十条,再重排选出几条;混入 12.5 万条干扰文本时,重排 RRF 的前 50 个候选使 Recall@1 从 0.275 升到 0.700。
  3. HNSW 删除一般只做标记、检索时跳过,之后统一清理,更新等于删除加插入;Milvus 的删除只记下被删除的主键、查询时过滤,Compaction 时才真正去掉,大量删除和更新会累积未清理的数据,占用内存并拖慢检索;文档修改后按文档 ID 和内容哈希找出变化的文档,删除它的全部旧切片再写新切片,只改权限或版本状态时只更新元数据;按切片 ID 幂等写入,并与关系数据库定期对账;更换 Embedding 模型或切片方式时,在新集合中重建索引并评测,再把别名切过去,查询用的模型同时切换。

动手:写一个函数,按混合检索一节的 RRF 公式把 BM25 和向量检索返回的两个排序列表融合成一个,用几组数据验证结果。

预期:用笔记表中的排名、k = 60 计算,顺序为 A 0.0325、C 0.0323、B 0.0161、E 0.0159、D 0.0156,两路都靠前的 A、C 排在最前。

算法(排序与堆,中等):912. 排序数组(手写快速排序),题解见快速排序。

明天:第 12 天,Top-K 与查询改写(大模型应用:Top-K 与阈值~查询改写)。

第 12 天:Top-K 与查询改写

注意区分召回的 K 和放进提示词的 K。查询改写处理原始问题不适合直接检索的情况,例如省略了上文的追问和只有几个字的问题。

阅读:《大模型应用》从Top-K 与阈值读到查询改写。

自测:

  1. 召回的 Top-K 和放进提示词的 Top-K 怎么定?(见Top-K 与阈值)
  2. 相似度阈值怎么设置?(见相似度阈值)
  3. 为什么要改写用户的查询?有哪些方法?(见查询改写)
参考要点
  1. 召回的 K 是每路交给重排的候选数(如 50),在评测集上取召回率不再明显上升的位置,有重排时可以取大些;放进提示词的 K 是重排后保留的数量(如 3~5),太大增加成本、混入无关或旧版本资料,太小答不全,按端到端正确率和上下文预算确定。
  2. 向量相似度的绝对值很难定阈值:实测阈值 0.50 时无答案的问题放行 40%,0.60 时有答案的问题误拒 10%;所以只用较低的阈值过滤明显无关的结果,有没有答案靠重排分数和“资料不足时回答不知道”的提示词判断;阈值在评测集上确定,换模型后重新确定。
  3. 原始问题常不适合直接检索:有指代(“那 ConcurrentHashMap 呢”)时结合历史补全;问题太短时用 HyDE 先生成假设的回答再检索;多个子问题拆开分别检索再合并,RAG-Fusion 改写成多个查询后用 RRF 合并;代价是多一次模型调用,改写也可能变差,已有较强的 Embedding 和重排时 HyDE 这类扩展收益有限,要在评测集上比较。

动手:阅读附录 A12,说出切片方式、Top-K 和阈值分别怎样影响召回率和放进提示词的 Token 数。

预期:按小节切召回最高(Recall@3 0.975)且前 3 个切片只有 278 字,800 字的切片召回也高但要放约 2000 字;K 从 1 增到 4 时召回从 0.85 升到 1.0 后不变;阈值从 0.5 提到 0.6,误拒从 0 升到 10%,放行从 40% 降到 10%。

算法(二叉树,中等):102. 二叉树的层序遍历,题解见剑指 Offer 32.2。

明天:第 13 天,RAG 的评测与扩展(大模型应用:RAG 的评测与优化~多模态 RAG)。

第 13 天:RAG 的评测与扩展

评测分检索和生成两部分,排查顺序一节把前几天的内容串成了一个排查流程。长上下文、GraphRAG 和多模态 RAG 可以略读。

阅读:《大模型应用》从RAG 的评测与优化读到多模态 RAG。

自测:

  1. 怎样评测一个 RAG 系统?(见检索指标、生成指标)
  2. RAG 回答得不好,按什么顺序排查?(见排查顺序)
  3. 评测集怎样构建?(见评测集)
参考要点
  1. 检索看 Recall@K(相关切片有多少在前 K 个中)、Precision@K(前 K 个中相关的比例)、MRR(第一个相关结果排名倒数的平均)和 NDCG(分级相关、按排名打折),K 增大时召回率不降、精确率往往下降,放进提示词的切片还要看精确率和端到端正确率;生成看忠实度(结论能否在资料中找到依据)和答案相关性(是否回应了问题),通常由模型裁判拆成小步骤打分,两者要一起看。
  2. 按数据流动的顺序从前往后查:解析、切片、检索、重排、生成,前面的错误后面无法弥补;每步分别看解析结果与原文是否一致、答案是否完整在一个切片中、相关切片在两路检索和重排前后的排名、直接给正确资料能否答对;也可以先做最后一步,区分问题在检索还是生成。
  3. 优先从真实用户问题中抽样,不够时用模型生成再人工改写措辞,避免照抄原文的词语让检索的评测结果偏高;包含没有答案、需要多个切片和新旧版本冲突的问题,没有答案的问题回答不知道算对、编出答案算错;标注标准答案和所需的原文片段而不是切片 ID,线上的错误回答持续补充进来。

动手:为 10 个问题标注相关的文档,用自己的检索结果计算 Recall@5 和 MRR,对照检索指标一节核对公式。

预期:Recall@5 为每个问题前 5 个结果中的相关文档数除以它的相关文档总数,再对问题取平均(每题只有一个相关文档时就是找到的问题所占的比例),MRR 为第一个相关结果排名倒数的平均;用笔记的例子(3 个问题各 1 个相关切片,排名 1、3、未出现)核对,应得到 Recall@5 = 2/3、MRR = 0.44。

算法(二叉树,中等):236. 二叉树的最近公共祖先,题解见二叉树的最近公共祖先。

综合题:公司内部的知识库问答上线后,用户反馈经常答非所问:按什么顺序排查?每一步看什么指标?分别可能改哪个环节?(见排查顺序、检索指标、切片、重排)

答题思路
  1. 先区分环节:把正确的资料直接交给模型,答得对说明问题在检索,答不对说明问题在生成。
  2. 解析与切片:抽查解析结果与原文对比,表格错乱、页眉混入时改用版面分析;看答案是否完整落在一个切片中,改为按结构切、加重叠、加标题路径。
  3. 检索:在评测集上看 Recall@K 和 MRR,相关切片排名靠后时加入混合检索、查询改写,用元数据过滤旧版本,排在 K 之外时增大 K。
  4. 重排:对比重排前后相关切片的排名,被排到后面时换重排模型或调整保留的数量。
  5. 生成:看忠实度和答案相关性,资料对但答错时减少放入的切片、要求逐条引用、换更强的模型;每次修改后在同一评测集上回归。

明天:第 14 天,Agent 与工具调用(大模型应用:Agent 是什么~Agent 与工作流)。

七、Agent

第 14 天:Agent 与工具调用

模型只输出文本,工具由应用代码执行,这是理解 Agent 的出发点。Agent 循环一节给出了完整的代码,最后一节说明什么时候不需要 Agent。

阅读:《大模型应用》从Agent 是什么读到Agent 与工作流。

自测:

  1. Agent 由哪些部分组成?(见Agent 是什么)
  2. Function Calling 的原理是什么?(见工具调用)
  3. Agent 和工作流有什么区别?分别适合什么场景?(见Agent 与工作流)
参考要点
  1. 模型负责理解任务、决定下一步;工具用于获取信息和执行操作,记忆保存对话历史和用户偏好,规划把复杂任务拆成步骤;控制循环是应用代码中反复调用模型、执行工具的循环,并负责限制步数、校验参数和权限;模型只输出文本,执行都由应用完成。
  2. 应用在请求中附上工具的名称、用途和参数 JSON Schema,推理服务按对话模板拼进提示词;模型需要时生成特定格式的文本(如 Qwen2.5 包在 tool_call 标签里的 JSON),服务解析成 tool_calls 返回;应用校验后执行,用 tool 消息和 tool_call_id 发回结果;本质仍是生成文本,模型可能调用不存在的工具、写错参数或编造订单号,严格模式只保证参数符合 Schema,所以必须先校验再执行。
  3. 工作流的步骤和顺序由代码事先写好,模型只做其中某些步骤,可预测、成本可控,适合步骤固定的业务;Agent 由模型决定下一步,适合无法事先确定步骤的开放任务,但成本高、错误会累积(每步 95% 时连续 10 步约 60%),所以先尝试单次调用或工作流;判断看步骤和步骤之间的转移规则能否事先写清,写不全才交给 Agent;实际常把两者组合,退款这类固定流程走工作流,商品咨询交给 Agent。

动手:用兼容 OpenAI 接口的模型服务定义一个 query_order 工具(返回模拟的物流信息),按Agent 循环一节手写循环,完成“订单 1024 到哪了”的问答,打印每一轮的消息。

预期:第 1 轮模型返回 query_order 的 tool_calls(arguments 为 {"order_id": "1024"}),追加 tool 消息后第 2 轮返回包含物流位置的文本,循环结束;每一轮发送的消息列表都比上一轮长。

算法(回溯与搜索,中等):200. 岛屿数量,题解见矩阵中的连通分量数目。

明天:第 15 天,意图识别与规划(大模型应用:意图识别与路由~Agent 的规划)。

第 15 天:意图识别与规划

意图识别决定一个请求交给哪个流程处理。规划一节比较 ReAct、先规划后执行等方式,重点是各自适合什么任务。

阅读:《大模型应用》从意图识别与路由读到Agent 的规划。

自测:

  1. ReAct 是什么?为什么用它?(见思维链、ReAct)
  2. 先规划后执行(Plan-and-Execute)和 ReAct 有什么区别?(见先规划后执行)
  3. 怎样保证意图识别和路由准确?(见怎样保证路由准确)
参考要点
  1. ReAct 让推理和行动交替:先思考,再调用工具,把真实结果作为观察放回上下文再思考;与思维链相比,事实来自外部而不是模型的记忆,HotpotQA 的分析中思维链失败案例 56% 是编造事实,ReAct 为 0%;适合下一步取决于上一步结果、步数不多的任务,缺点是只能串行、长任务容易偏离目标。
  2. 先规划后执行先一次生成带依赖的步骤列表,按依赖执行、无依赖的步骤并行,失败或结果不符时重规划(次数设上限),进度容易展示和恢复,但计划可能与实际不符;ReAct 每步根据中间结果调整,但每步都要调用一次模型,长任务容易偏离;实际常结合使用,每个步骤内部用 ReAct。
  3. 类别定义互斥并写明边界,输出限制为枚举值,不在列表中的按“其它”处理;分层:规则只处理“转人工”、带订单号这类精确率高的情况,其余交给模型;置信度低时反问澄清或转人工,阈值在验证集上扫一遍再定,分错代价高的类别(如退款)单独设更高的阈值;多轮对话结合最近几轮;用评测集按类别看精确率、召回率和混淆矩阵,修改后回归。

动手:阅读附录 A6,比较三种路由做法的准确率,再结合三种做法一节说出各自的延迟、成本和适合的场景。

预期:规则准确率 73.3%,毫秒级、没有成本,但换个说法就漏,适合“转人工”这类精确的情况;Embedding 相似度 78.3%,毫秒级、有置信度,每类只需几条示例,但示例少时容易被字面相似误导;Qwen2.5-7B 95%(3B 90%),每次多一次模型调用,能理解没见过的说法,但可能输出不存在的类别,适合说法多样的消息。

算法(回溯与搜索,中等):46. 全排列,题解见排列。

明天:第 16 天,记忆与多轮对话(大模型应用:Agent 的记忆~多轮对话)。

第 16 天:记忆与多轮对话

先分清两件事:记忆是应用存下来的数据,上下文是某一次调用实际带上的内容。短期记忆管当前会话的消息、摘要和任务状态,长期记忆要解决写什么、怎样检索和怎样更新。多轮对话一节处理指代、追问和话题切换。

阅读:《大模型应用》从Agent 的记忆读到多轮对话。

自测:

  1. Agent 的短期记忆和长期记忆分别怎样实现?(见短期记忆、长期记忆的写入、长期记忆的检索)
  2. 长期记忆中的信息过时或冲突时怎样处理?(见更新、冲突与遗忘)
  3. 多轮对话中的指代和省略怎样处理?(见指代与省略)
参考要点
  1. 短期记忆是当前会话的原始消息(含工具调用和结果)、摘要和任务状态,常以会话 ID 为键缓存在 Redis(需要保留的消息和要恢复的任务状态先写入数据库),请求时放入“摘要 + 最近几轮 + 任务状态”;长期记忆由后台提取或模型调用 save_memory 写入,只写用户明确说过、以后有用、业务系统查不到的事实;使用时按用户 ID 过滤,按相似度和时间检索最相关的几条。
  2. 写入前检索相似的旧记忆,由模型选择 ADD、UPDATE、DELETE 或 NOOP(Mem0 论文的做法,开源版后来改为只新增),用户 ID、时间等字段由代码填写;冲突时用户本次说的优先,亲口说的优先于推断,同一来源以新的为准,只对这一次有效的说法不覆盖,仍无法判断就问用户;旧记忆标记失效而不物理删除,并按时间衰减排序、定期清理。
  3. 完整历史在上下文中时模型一般能理解指代,问题出在检索、压缩和多个候选;检索前结合最近几轮和任务状态改写成完整的问题,例如“它什么时候发货”改为“订单 1026 什么时候发货”;任务状态记录当前和提到过的订单,有多个候选时追问而不是猜。

动手:按附录 A5的方法,让模型从对话中提取收货地址这类长期有效的用户信息写入长期记忆,用户换了地址后再更新这条记忆。

预期:提取出新地址后,模型对已有的地址记忆选择 UPDATE 而不是重复新增(附录中 m1 更新为江南大道 588 号),订单、物流这类信息不被提取;记忆中的日期由代码填写,不采用模型写的日期。

算法(动态规划,简单):121. 买卖股票的最佳时机,题解见买卖股票最大的收益。

明天:第 17 天,MCP 与 Skills(大模型应用:MCP~Skills)。

第 17 天:MCP 与 Skills

MCP 规定应用怎样接入外部的工具和数据,Skills 规定怎样按需加载完成某类任务的方法,两者常被放在一起比较。

阅读:《大模型应用》从MCP读到Skills。

自测:

  1. MCP 是什么?MCP 和 Function Calling 有什么区别?(见MCP)
  2. Skill 和 MCP、Tool 有什么区别?(见Skills)
参考要点
  1. MCP 是 AI 应用与外部工具、数据之间的统一协议,工具实现一次服务端就能被所有支持 MCP 的应用使用,消息用 JSON-RPC 2.0,经 stdio 或 Streamable HTTP 传输,tools/list 发现工具、tools/call 调用;Function Calling 是模型侧的能力,模型按工具描述给出调用;两者配合:主机把 MCP 工具转成工具定义交给模型,再通过 MCP 执行模型给出的调用。
  2. Tool 是可调用的函数,提供查询订单这类原子能力;MCP 是连接工具和数据的协议,解决工具怎么接入;Skill 是包含 SKILL.md、脚本和资料的文件夹,提供完成某类任务的步骤和规范,执行时仍要调用工具,平时只加载名称和描述(约 100 个 Token),需要时才读取正文。

动手:按附录 A24写一个提供 get_order 工具的 MCP 服务端并调用它;再写一个 SKILL.md,描述“按模板写周报”的步骤。

预期:tools/list 返回 get_order 的名称、描述和由类型注解生成的参数 Schema;查询 1024 返回订单状态和运单号、isError 为 false,查询不存在的订单返回错误信息、isError 为 true;SKILL.md 开头有 name(小写字母、数字、连字符)和写清做什么、何时使用的 description。

算法(动态规划,简单):70. 爬楼梯,题解见爬楼梯。

明天:第 18 天,多 Agent 与 Harness(大模型应用:多 Agent~Harness)。

第 18 天:多 Agent 与 Harness

多 Agent 一节说明什么时候值得拆成多个 Agent,以及几种协作方式的区别。AI 编程助手一节以 Claude Code 为例讲编程 Agent 怎样工作、怎样用好;Harness 一节讲模型之外的代码怎样通过引导、反馈和交接,让 Agent 可靠地完成长任务。

阅读:《大模型应用》从多 Agent读到Harness。

自测:

  1. 什么时候需要多个 Agent?有哪些协作方式?(见为什么要多个 Agent、协作方式、适用场景)
  2. 什么是 Agent Harness?由哪些部分组成?(见Harness)
  3. 长任务怎样在多次会话之间交接?(见长任务的交接)
参考要点
  1. 子任务互相独立、需要读大量资料或反复试错时值得拆,需要隔离权限时也可以拆,好处是隔离上下文,子 Agent 只返回精简的结论;代价是 Token 消耗成倍增加(Anthropic 的数据中多 Agent 系统约为普通对话的 15 倍),只传摘要会丢失细节;依赖紧密的任务和对延迟、成本敏感的在线请求用单个 Agent;协作方式有主管分派(子 Agent 包装成工具、可并行)、流水线(固定顺序)、讨论(互看答案后投票或裁决)和转交(把控制权和上下文交给另一个 Agent,由它直接回复用户,处理完不自动返回)。
  2. Harness 指模型之外的所有代码、配置和执行逻辑;组成包括 Agent 循环与停止条件、上下文组装与压缩、工具与权限、记忆与状态交接、子 Agent、钩子与校验、评测与可观测,MCP 和 Skill 也属于它;核心是动手前的引导(规范、文档、Skill)和动手后的反馈(编译、测试、lint、模型审查)。
  3. 第一次会话只做准备:把需求拆成带测试步骤和 passes 字段的 JSON 功能清单,写好启动脚本并提交到 git;之后每次会话先读进度文件和 git log、做冒烟测试,只实现一个未通过的功能,端到端测试通过才把 passes 改为 true,再提交并更新进度;只允许修改 passes,防止 Agent 删掉测试来“完成”任务。

动手:为自己的一个项目写一份给 AI 编程助手看的说明文件(例如 AGENTS.md),写明构建和测试命令、代码规范和提交前要运行的检查,对照引导与反馈一节检查是否完整。

预期:文件中有可直接运行的构建和测试命令、代码规范(引导),并列出提交前要运行的编译、测试、lint 等检查(反馈);能用程序检查的规范(如分层规则)配有对应的检查,而不只写在文件里。

算法(动态规划,中等):53. 最大子数组和,题解见子数组最大的和。

明天:第 19 天,开发框架、可靠性与评测(大模型应用:开发框架~可靠性与评测)。

第 19 天:开发框架、可靠性与评测

开发框架一节说明 LangChain、LangGraph 和 Spring AI 各自解决什么问题。可靠性与评测讲工具失败时怎样容错,以及怎样评测一个 Agent。

阅读:《大模型应用》从开发框架读到可靠性与评测。

自测:

  1. LangChain 和 LangGraph 有什么区别?为什么用图来编排?(见为什么用图编排、怎样选择)
  2. 工具调用失败时怎样容错?(见容错)
  3. 怎样评测一个 Agent?用模型做裁判有哪些偏差?(见评测、模型裁判的偏差)
参考要点
  1. LangChain 提供模型、工具、检索器等组件和现成的 Agent,早期核心是按固定顺序串起来的链;LangGraph 是更底层的编排运行时,用共享状态、节点和条件边组成状态图,LangChain 1.0 的 create_agent 就运行在它之上;Agent 和业务流程需要循环、分支、暂停等人工确认和从中间恢复,用链表达很别扭,图加 checkpoint 可以直接表达。
  2. 执行前先按 Schema 校验参数,再做业务校验;超时、限流、5xx 由代码按指数退避重试 2~3 次,不交给模型(实测把超时回传给 7B 模型,10 次都没有重新调用),接口持续失败时熔断;参数错误把错在哪、正确格式是什么回传给模型修正;仍失败就如实回传,由模型换办法或告知用户、转人工,不能编造结果;同时限制步数和预算、检测有没有进展(比较工具名、参数和结果,而不只数调用次数),有副作用的操作要幂等,只对涉及资金、无法撤销或对外可见的操作人工确认(确认太频繁,人会不看内容就点同意)。
  3. 主要检查结果(例如数据库中是否真有一条退款记录),轨迹只检查必须满足的约束,优先用代码检查,开放内容才用模型裁判;指标有任务成功率、工具调用正确率和效率,用 pass^k 衡量稳定性;裁判有位置偏差、长度偏差和自我偏好,缓解靠交换顺序评两次、给出参考、拆成逐项判断的问题、与人工标注核对一致率。

动手:按附录 A8的方法,在退款节点之前加入人工确认,中断后从 checkpoint 恢复执行。

预期:第一次调用在 confirm 节点暂停,返回 {'action': 'refund', 'order_id': '1024', 'amount': 59.9},状态中 next 为 ('confirm',);用 Command(resume=True) 恢复后 confirm 节点从头再执行一次(开头的打印出现两次),随后退款完成,退款记录只有一条 refund-1024: 59.9。

算法(动态规划,中等):5. 最长回文子串,题解见最长回文子串。

综合题:用“订单 1024 到哪了”这个例子,说出客服 Agent 从收到问题到给出回答的完整过程:怎样识别意图、调用哪些工具、对话历史怎样保存、工具超时怎样处理。(见Agent 循环、意图识别与路由、记忆、容错)

答题思路
  1. 意图识别:消息带订单号格式,可由规则直接路由到物流流程,其余交给模型分类并限制为枚举值,物流流程只拿到查订单和查物流的工具。
  2. Agent 循环:发送系统提示词、历史和工具定义,模型先返回 get_order(1024),应用校验订单属于当前用户后执行,结果作为 tool 消息追加;下一轮模型调用 get_logistics(运单号),再下一轮返回文本,循环结束,全程限制最大步数。
  3. 对话历史:以会话 ID 为键存进 Redis,List 保存消息(包括工具调用和结果),Hash 保存任务状态(当前订单 1024),设置过期时间;下一次请求放入摘要、最近几轮原文和任务状态。
  4. 工具超时:由代码按指数退避重试 2~3 次,模型不感知;仍失败就把写清楚的错误作为工具结果返回,让模型如实告知暂时查不到,不能编造“运输中”。
  5. 回答前校验:用户问的是位置,就要求调用过物流查询,回答中的地点出现在物流结果中。

明天:第 20 天,流式输出、成本与部署(大模型应用:流式输出~推理服务部署)。

八、工程实践

第 20 天:流式输出、成本与部署

流式输出决定用户多快看到第一个字。延迟与成本一节估算了一次 Agent 任务的费用;推理服务部署讲批处理和 PagedAttention 怎样提高吞吐、前缀缓存怎样省掉重复的预填充,投机解码和张量并行可以略读。

阅读:《大模型应用》从流式输出读到推理服务部署。

自测:

  1. 流式输出是怎样实现的?SSE 的格式是怎样的?(见流式输出、SSE 的格式)
  2. 怎样降低一次 Agent 任务的 Token 消耗?(见让前缀缓存命中、减少 Token 消耗)
  3. PagedAttention 解决了什么问题?(见PagedAttention)
参考要点
  1. 接口用 SSE:响应类型为 text/event-stream,连接保持打开,每生成一段内容写一条事件;事件由若干行“字段: 值”组成、以空行结束,data 是内容、id 是编号、event 是类型,冒号开头的行是注释(常作心跳);OpenAI 兼容接口以 data: [DONE] 结束,断线后 EventSource 带着 Last-Event-ID 重连。
  2. 前缀缓存降低的是单价:不变的系统提示词和工具定义放最前、工具顺序固定、历史原样追加;减少 Token 的数量:工具先返回摘要或片段,需要时再读全文;设置 max_tokens,以 deepseek-flash 为例,输出单价是未命中缓存输入的 4 倍;简单问题交给小模型;历史过长时压缩,但压缩会改变前缀,不宜过于频繁;避免模型反复调用同一个工具。
  3. 早期系统按最大长度为每个请求预留连续的显存,真正存放 KV Cache 的只有 20.4%~38.2%;PagedAttention 借鉴操作系统的分页,把 KV Cache 切成固定大小的块(vLLM 默认 16 个 Token)按需分配,用块表记录位置,只浪费最后一块的空余;同样 7 GB 显存按 8K 预留只能服务 16 个请求,请求平均只用到 2K Token 时按块分配可服务 64 个;延迟相近时吞吐是 FasterTransformer、Orca 的 2~4 倍。

动手:用 curl -N 调用模型服务的对话接口(请求体中设置 "stream": true),观察 data: 行的格式和最后的 [DONE],再用代码逐行读取并拼接出完整回答。

预期:终端中陆续出现以 data: 开头、以空行分隔的事件,文字增量在 JSON 的 choices[0].delta.content 中,第一条(只有角色)和带 finish_reason 的最后一条可能没有文字,最后是 data: [DONE];代码跳过空行和注释,只拼接非空的 content,读到 [DONE] 停止,得到完整回答。

算法(动态规划,中等):300. 最长递增子序列,题解见最长递增子序列。

明天:第 21 天,稳定性与安全(大模型应用:稳定性~大模型应用的安全)。

第 21 天:稳定性与安全

稳定性一节把超时、重试、限流、降级和熔断用到模型调用上,重点是模型调用与普通接口的不同:生成慢,超时要按输出长度估算,流式输出还可能中途断开。安全一节先解释为什么靠提示词挡不住注入,再讲怎样限制模型被诱导后能造成的后果。

阅读:《大模型应用》从稳定性读到大模型应用的安全。

自测:

  1. 调用模型接口时怎样设置超时和重试?(见超时、重试与指数退避)
  2. 流式输出中途断开怎么办?(见流式输出中途断开)
  3. 怎样防御提示词注入?(见限制模型能造成的后果)
参考要点
  1. 非流式请求的总超时要大于 TTFT + 最大输出 Token 数 × TPOT,例如 1000 个 Token、30 毫秒时要 30 多秒;流式请求分别设首 Token 超时、间隔超时和总时长上限,心跳不算内容;临时限流的 429、5xx、超时可以重试,400、401 和花费上限导致的 429 直接失败,按指数退避加随机抖动、遵守 Retry-After,次数设上限且只在一层重试。
  2. 还没输出任何内容时直接重试,用户感知不到;已输出部分文字时,把已输出的部分作为 assistant 消息的开头请求续写(如 DeepSeek 的对话前缀续写),不支持就丢弃重新生成并通知前端清空;工具调用的参数只输出了一半时不能执行,丢弃后重新请求。
  3. 提示词注入指通过输入的内容改变模型原本的指令,指令也可能藏在网页、文档或工具结果中(间接注入);指令和资料最终拼成同一个 Token 序列,模型无法可靠区分,提示词只能降低风险,重点是限制后果;外部内容标注为不可信,工具按最小权限提供、在代码中按当前用户校验,高风险操作人工确认;读过不可信内容的任务不给对外发送的工具,前端不自动加载输出中的外部图片,防止数据外泄;输出转义、查询参数化,提示词中不放密钥,记录审计日志。

动手:参照重试与指数退避一节的代码,用 Python 写一个最多尝试 3 次的重试函数,用模拟接口测试:前两次返回 429、第三次返回结果时,打印每次等待的时间;再让模拟接口返回 400,确认没有重试。

预期:两次 429 后分别等待 0.25~0.5 秒和 0.5~1 秒(基准间隔 0.5 秒、1 秒,随机抖动取它的 0.5~1 倍),第 3 次拿到结果;返回 400 时只调用 1 次就抛出异常。

算法(链表,困难):25. K 个一组翻转链表,题解见K 个一组翻转链表。

综合题:设计一个企业内部的智能客服:用户的问题怎样分流到知识库问答和工具调用?怎样评测效果?怎样控制延迟和成本?出现提示词注入时怎样限制损失?(见意图识别与路由、RAG 的评测、Agent 的评测、延迟与成本、安全)

答题思路
  1. 分流:意图路由,规则处理“转人工”、带订单号这类精确率高的情况,其余交给分类模型或大模型并限制为枚举值,低置信度时反问或转人工;制度、产品类问题走 RAG,查询和办理走带工具的 Agent,退款这类固定流程写成工作流。
  2. 评测:路由用分类评测集和混淆矩阵;RAG 看 Recall@K、MRR 和忠实度、答案相关性,效果差时按解析、切片、检索、重排、生成的顺序排查;Agent 看任务成功率和 pass^k;线上失败的会话加入评测集回归。
  3. 延迟:限制输出长度,流式输出缩短用户看到第一个字的时间,互不依赖的工具并行执行;成本:不变的内容放在前面命中前缀缓存,控制工具返回的内容,简单问题交给小模型。
  4. 注入:外部资料标注为不可信;用户身份取自登录会话,工具在代码中校验订单属于当前用户并按最小权限提供;退款只生成待确认的申请,由用户确认后走普通业务接口执行;输出转义,记录审计日志。

在阅读器中可以用目录和搜索、切换深色模式、练习这篇笔记中的面试题:打开《学习路线 - 大模型应用开发》