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

补充自测题的参考要点和动手练习的预期结果。
This commit is contained in:
CyC2018
2026-10-30 02:57:58 +08:00
parent b53cdaf129
commit e4066172f2
+328 -6
View File
@@ -37,7 +37,7 @@
适合会一门编程语言(Python 或 Java)、准备大模型应用开发面试的读者,每天约 1~1.5 小时。动手部分需要一个兼容 OpenAI 接口的模型服务(例如 DeepSeek)或本地的 Qwen2.5 小模型;学过 Java 后端路线的读者可以跳过算法题。
每天先读完阅读范围,再不看笔记回答自测题,答完打开链接核对。选读可以跳过。
每天先读完阅读范围,再不看笔记回答自测题,答完展开参考要点核对;动手练习对照预期的结果。自测答得上来、动手结果与预期一致、算法题提交通过,这一天就完成了。选读可以跳过。
## 二、开始之前
@@ -96,10 +96,23 @@ Token 是模型处理文本的单位,也是计费和上下文长度的单位
2. 大模型的参数主要分布在哪里?(见[参数都在哪里](大模型应用.md#2-参数都在哪里))
3. 什么是 Token?同样的内容,中文和英文的 Token 数一样吗?(见[Token 与分词](大模型应用.md#token-与分词))
<details>
<summary>参考要点</summary>
1. 目前的大模型基本是只保留解码器的 Decoder-only 结构;Token 经词嵌入变成向量(Qwen2.5-7B 为 3584 维),依次经过几十个相同的层(28 层),每层先用注意力从前文收集信息,再用前馈网络逐位置加工;子层都带残差连接和归一化(多为 Pre-Norm 的 RMSNorm);最后一个位置经线性层得到 logits,softmax 后得到下一个 Token 的概率。
2. 几乎都在每层注意力和前馈网络的矩阵中;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 为准。
</details>
动手:下载 Qwen2.5-7B-Instruct 的 config.json,按[参数都在哪里](大模型应用.md#2-参数都在哪里)一节的方法算出一层中注意力和前馈网络的参数量,再与[附录 A16](大模型应用.md#a16-qwen25-7b-与-qwen3-30b-a3b-的参数量)对照。
预期:每层注意力 29,364,736 个参数、前馈网络 203,685,888 个(加归一化共 233,057,792 个),28 层加两个嵌入共 7,615,616,512 个,与附录 A16 一致。
算法(哈希表,简单):[1. 两数之和](https://leetcode.cn/problems/two-sum/),题解见[数组中两个数的和为给定值](Leetcode%20题解%20-%20哈希表.md#1-数组中两个数的和为给定值)。
明天:第 2 天,注意力机制(大模型应用:注意力机制)。
### 第 2 天:注意力机制
注意力是 Transformer 的核心,对着“饿”的例子把 Q、K、V 的计算走一遍。FlashAttention 一节说明了长上下文时计算的瓶颈在哪里。
@@ -112,10 +125,23 @@ Token 是模型处理文本的单位,也是计费和上下文长度的单位
2. 多头注意力有什么作用?(见[多头注意力](大模型应用.md#1-多头注意力))
3. FlashAttention 解决了什么问题?(见[计算量与 FlashAttention](大模型应用.md#2-计算量与-flashattention))
<details>
<summary>参考要点</summary>
1. 公式为 softmax(QK<sup>T</sup> / √d) V:Q 与各 K 点积得到分数,softmax 成权重后对 V 加权求和;分量独立、均值 0、方差 1 时点积的方差为 d,维度大时 softmax 几乎集中到一个 Token 上,梯度接近 0;除以 √d 使方差恢复为 1。
2. 单头注意力中每个 Token 只有一组权重,只能按一种标准挑选信息;多头把 Q、K、V 切成 h 份分别计算,拼接后乘 W<sub>O</sub>,不同的头可以关注指代、相邻词等不同关系;每头维度为 d / h,计算量与单头基本相同。
3. 标准注意力的计算量为 O(n<sup>2</sup>d),n × n 的分数矩阵占 O(n<sup>2</sup>) 显存,长上下文时显存读写成为瓶颈;FlashAttention 分块在 GPU 片上缓存中计算,分数矩阵不写回显存,用在线 softmax 逐块累积;结果与标准注意力完全相同,省的是显存读写而不是计算量。
</details>
动手:用 NumPy 实现单头注意力 softmax(QK<sup>T</sup> / √d) V,输入 3 个 Token、维度为 4 的随机矩阵,打印注意力权重并检查每一行的和是否为 1。
预期:打印出 3 × 3 的权重矩阵,元素都在 0 到 1 之间,每一行之和为 1(只差浮点误差),输出的形状为 3 × 4。
算法(双指针,中等):[15. 三数之和](https://leetcode.cn/problems/3sum/),题解见[三数之和](Leetcode%20题解%20-%20双指针.md#8-三数之和)。
明天:第 3 天,生成、KV Cache 与采样(大模型应用:自回归生成与 KV Cache~采样参数)。
### 第 3 天:生成、KV Cache 与采样
模型每次只生成一个 Token。KV Cache 避免重复计算前面 Token 的 K 和 V,也是推理时显存的主要来源。采样参数决定每一步怎样从概率分布中选出 Token。
@@ -128,10 +154,23 @@ Token 是模型处理文本的单位,也是计费和上下文长度的单位
2. MHA、MQA 和 GQA 有什么区别?(见[自回归生成与 KV Cache](大模型应用.md#自回归生成与-kv-cache))
3. temperature 和 top-p 分别控制什么?(见[采样参数](大模型应用.md#采样参数))
<details>
<summary>参考要点</summary>
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 中所有查询头共享一组,缓存最小,但效果下降更明显。
3. temperature 把分数除以 T 再 softmax,T < 1 时分布更尖、T > 1 时更平,例如最高概率 0.50 在 T = 0.5 时升到 0.75;top-p 只在累计概率刚达到 p 的高概率 Token 中采样,候选数随分布变化;抽取字段、调用工具用低温度,写作可调高。
</details>
动手:按[显存:权重与 KV Cache](大模型应用.md#3-显存权重与-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. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/),题解见[剑指 Offer 48](48.%20最长不含重复字符的子字符串.md)。
明天:第 4 天,训练过程(大模型应用:训练过程)。
### 第 4 天:训练过程
按预训练、监督微调、偏好对齐三个阶段读。PPO、DPO 和 GRPO 的区别是常见的追问,比较时看每种方法需要哪些模型。
@@ -144,10 +183,23 @@ Token 是模型处理文本的单位,也是计费和上下文长度的单位
2. PPO、DPO 和 GRPO 有什么区别?(见[DPO](大模型应用.md#3-dpo)、[GRPO 与三者的选择](大模型应用.md#4-grpo-与三者的选择))
3. RLHF 中的奖励模型是怎样训练的?(见[RLHF 与 PPO](大模型应用.md#2-rlhf-与-ppo))
<details>
<summary>参考要点</summary>
1. 预训练:在海量文本上预测下一个 Token,获得语言能力和知识,但只会续写;监督微调:用指令和人工编写的回答训练,学会按对话格式回答、遵循指令;偏好对齐:用“两个回答哪个更好”的数据让回答更有帮助、更安全,这类目标难写标准答案,但比较容易。
2. PPO 在线生成回答,奖励模型打分、价值模型给出预期,加载 4 个模型、训练 2 个;DPO 不训练奖励模型、不在训练中生成回答,直接用离线偏好对训练,只需策略模型和参考模型,简单稳定;GRPO 去掉价值模型,每题生成一组回答、以组内平均分为预期,适合能用规则判对错的推理训练;PPO 和 GRPO 能探索出训练数据之外的更好回答。
3. 标注员把同一提示词的 4~9 个回答按好坏排序,两两组成偏好对(9 个回答 36 对);奖励模型输入提示词和回答、输出一个分数,损失为 -log σ(r<sub>w</sub> - r<sub>l</sub>),使较好回答得分更高,例如分数 1.2 和 0.3 时损失约 0.34。
</details>
动手:用 Python 按[DPO](大模型应用.md#3-dpo)一节的公式,对几组给定的 chosen 和 rejected 对数概率计算 DPO 损失,观察 β 取 0.1 和 0.5 时损失的变化。
预期:两个回答相对参考模型的变化都为 0 时损失为 log 2 ≈ 0.693;较好回答高 2、较差回答低 1 时,β = 0.1 的损失约 0.554,β = 0.5 约 0.201,β 越大损失对同样的概率差越敏感。
算法(链表,简单):[206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/),题解见[链表反转](Leetcode%20题解%20-%20链表.md#2-链表反转)。
明天:第 5 天,微调与量化(大模型应用:微调~量化)。
### 第 5 天:微调与量化
先判断什么时候该微调、什么时候用 RAG 或提示词就够了。LoRA 是最常用的微调方法;量化一节中 GPTQ 与 AWQ 可以略读。
@@ -160,10 +212,23 @@ Token 是模型处理文本的单位,也是计费和上下文长度的单位
2. 什么时候用微调,什么时候用 RAG?(见[什么时候微调](大模型应用.md#1-什么时候微调))
3. 什么是灾难性遗忘?怎样缓解?(见[灾难性遗忘](大模型应用.md#5-灾难性遗忘))
<details>
<summary>参考要点</summary>
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 降到约 4 GB。
2. 需要私有资料或经常更新的知识、要给出依据时用 RAG;要固定风格和格式、学会某种专业任务,提示词仍常出错时用微调;只是补少量规则和示例时用提示词;一般先提示词、再 RAG、最后微调,写进参数的知识难更新、无法给出来源。
3. 模型在新数据上训练后原有能力明显下降,例如只用客服对话微调后写代码变差,模型越大越严重;缓解:混入通用指令数据,用 LoRA、较小的学习率和较少的轮数并早停,训练前后都在通用评测集上评测。
</details>
动手:按[附录 A17](大模型应用.md#a17-sft-样本的损失掩码)的方法,用 Qwen2.5 的对话模板构造一条 SFT 样本,标出哪些 Token 参与计算损失。
预期:只有 assistant 的回答及其后的 <|im_end|> 和换行计入损失,system 和 user 部分的标签为 -100;附录中的样本共 48 个 Token,提示部分 31 个,计入损失 17 个。
算法(链表,简单):[21. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/),题解见[归并两个有序的链表](Leetcode%20题解%20-%20链表.md#3-归并两个有序的链表)。
明天:第 6 天,幻觉与推理模型(大模型应用:幻觉~推理模型)。
### 第 6 天:幻觉与推理模型
幻觉一节按定义、原因、缓解的顺序读,Agent 中的幻觉是调用工具后出现的新问题。推理模型一节很短,说明它和普通模型的区别。
@@ -176,12 +241,36 @@ Token 是模型处理文本的单位,也是计费和上下文长度的单位
2. 怎样检测和缓解幻觉?(见[检测与缓解](大模型应用.md#3-检测与缓解))
3. Agent 调用工具时会出现哪些幻觉?(见[Agent 中的幻觉](大模型应用.md#4-agent-中的幻觉))
动手:阅读[附录 A19](大模型应用.md#a19-agent-中的工具幻觉),说出模型在什么情况下编造了工具或参数,以及校验是怎样拦住它的。
<details>
<summary>参考要点</summary>
1. 幻觉指生成看似合理、但与事实或给定资料不符的内容,分事实性幻觉和忠实性幻觉;根本原因是训练目标是生成通顺的下一个 Token 而不是核对事实,只按答对比例打分的评测又鼓励猜测;此外还有知识缺失、采样出错后顺着错误生成、上下文过长或资料有噪声。
2. 检测:有资料时把回答拆成结论逐条检查依据,引文和数字用代码核对;没有资料时多次采样比较,编造的内容每次不同;缓解:用 RAG 提供资料并允许回答不知道,降低温度、要求引用,用程序或另一个模型校验,展示来源、高风险结果人工确认。
3. 编造参数,例如用户没给订单号就自己编一个去查;编造或误读工具结果,例如接口超时仍说“正在运输中”、把 updated_at 当成送达时间;声称做了没做的事,例如没调用退款工具却说已退款;应对要靠工具和代码:结果字段写清楚、校验参数来源、用代码核对关键信息。
</details>
动手:阅读[附录 A19](大模型应用.md#a19-agent-中的工具幻觉),说出模型在什么情况下编造了工具结果中没有的信息,以及改写提示词和工具结果后怎样消除。
预期:缺订单号时模型 10 次都先询问,没有编造参数;物流接口超时时,只写流程的提示词下 3/10 次编造“运输中”,加上如实告知的要求后为 0;结果中没有送达时间时多数把 updated_at 当作送达时间,改为 location_updated_at 并给出 "eta": null 后为 0。
算法(链表,简单):[141. 环形链表](https://leetcode.cn/problems/linked-list-cycle/),题解见[判断链表是否存在环](Leetcode%20题解%20-%20双指针.md#6-判断链表是否存在环)。
综合题:用户问“我们公司的年假有几天”,模型回答了一个错误的天数:可能的原因有哪些?用提示词、RAG、微调分别能解决哪一部分?(见[为什么会产生](大模型应用.md#2-为什么会产生)、[什么时候微调](大模型应用.md#1-什么时候微调)、[检测与缓解](大模型应用.md#3-检测与缓解))
<details>
<summary>答题思路</summary>
1. 先找原因:年假天数是公司私有资料,模型没有这部分知识,只能按通用知识“合理地”补全;接入了资料时,也可能检索到旧版制度、资料有噪声或上下文过长而用错资料。
2. 提示词:要求只依据给定资料回答、资料不足时回答不知道,降低温度、要求引用原文,解决的是“猜答案”的倾向,补不了知识。
3. RAG:年假制度属于私有且会更新的知识,检索制度原文放进上下文并给出来源,解决知识缺失这一主要原因;旧版本靠元数据过滤。
4. 微调:不适合灌输这类事实,写进参数后难以更新、无法给出来源,还可能增加编造,只适合固定话术和格式。
5. 校验:用代码检查回答中的天数是否出现在检索到的原文中,并展示来源。
</details>
明天:第 7 天,提示词与结构化输出(大模型应用:提示词~结构化输出)。
## 五、提示词与上下文
### 第 7 天:提示词与结构化输出
@@ -196,10 +285,23 @@ Token 是模型处理文本的单位,也是计费和上下文长度的单位
2. 怎样让模型稳定地输出合法的 JSON?(见[约束解码](大模型应用.md#1-约束解码)、[输出无法解析时](大模型应用.md#2-输出无法解析时))
3. 输出格式正确,内容就一定正确吗?(见[格式正确不等于内容正确](大模型应用.md#3-格式正确不等于内容正确))
<details>
<summary>参考要点</summary>
1. 写清任务、约束、输出格式和示例,要求具体,例如“不超过 100 字”而不是“简洁一些”;说明信息不足时怎么处理,否则模型倾向编造;用少样本示例说明格式,多步推理让模型先写步骤(CoT);修改后在固定评测集上回归。
2. 优先用约束解码:每生成一个 Token 前按 Schema 屏蔽会让输出不合法的 Token;JSON 模式只保证是合法 JSON,不保证字段;不支持时先修复(去掉代码块标记、截取首个 { 到末个 }),再用 JSON Schema 校验,失败时带着具体错误重试 1~2 次,仍失败就返回默认值或转人工。
3. 不一定,约束解码和 Schema 校验只保证格式;订单是否属于当前用户、退款是否超过实付金额这类业务规则要在代码中校验;还要检查 finish_reason 是否为 length,以及字段类型是否与代码一致。
</details>
动手:按[附录 A22](大模型应用.md#a22-模型输出-json-的修复校验与重试)的方法,让模型从一段订单描述中抽取 JSON,用 JSON Schema 校验,失败时把错误信息发回模型重试。
预期:常见的失败是 JSON 包在代码块里,修复后即可解析(附录中 40 次输出直接解析成功 26 次,修复后 40 次,通过校验 38 次);一条消息含两个订单号这类超出 Schema 设计的输入,带着错误重试后仍失败,需要修改 Schema。
算法(设计,中等):[146. LRU 缓存](https://leetcode.cn/problems/lru-cache/),题解见[LRU](缓存.md#七lru)。
明天:第 8 天,上下文工程(大模型应用:上下文工程)。
### 第 8 天:上下文工程
上下文工程关心每次调用放进什么、按什么顺序放。压缩是重点:什么时候压缩、怎样压缩,以及怎样保证关键事实不丢。
@@ -212,12 +314,36 @@ Token 是模型处理文本的单位,也是计费和上下文长度的单位
2. 对话历史太长时怎样压缩?什么时候压缩?(见[压缩的方法与时机](大模型应用.md#3-压缩的方法与时机))
3. 压缩后怎样保证关键事实不丢失?(见[压缩后不丢关键事实](大模型应用.md#4-压缩后不丢关键事实))
<details>
<summary>参考要点</summary>
1. 提示词工程关注怎样写好指令,尤其是系统提示词;上下文工程决定每次调用前把哪些内容、按什么顺序放进窗口,包括规则、工具定义、检索资料、对话历史和记忆;Agent 每一步都有新的工具结果,每次调用前都要重新挑选和压缩。
2. 方法有截断(只留最近几轮,丢失早期内容)、摘要(多一次调用,可能漏掉或改写细节)、清理旧的工具结果(风险最小)和外置到文件,工具调用和结果要成对删除;常组合成“系统提示词 + 摘要 + 最近几轮原文 + 当前问题”;时机按 Token 数而不是轮数,约 70% 时在后台生成摘要,到上限时必须先压缩。
3. 订单号、金额、地址等由代码从工具参数和结果中提取单独保存,承诺通过工具写进记录;摘要分项输出、编号和金额照原文抄写,新摘要在旧摘要的基础上合并;用关键事实覆盖和探针问题检查,实测 3B 的结构化摘要保留 9.7 条事实,普通摘要只有 4.3 条(共 14 条)。
</details>
动手:按[附录 A4](大模型应用.md#a4-普通摘要与结构化摘要)的方法,对一段 20 轮的客服对话分别做普通摘要和结构化摘要,检查订单号、金额等关键事实是否保留。
预期:结构化摘要保留的关键事实明显多于普通摘要(附录中 Qwen2.5-3B 平均 9.7 对 4.3 条,共 14 条),但手机尾号这类只提过一次的信息仍可能丢失。
算法(栈和队列,简单):[20. 有效的括号](https://leetcode.cn/problems/valid-parentheses/),题解见[用栈实现括号匹配](Leetcode%20题解%20-%20栈和队列.md#4-用栈实现括号匹配)。
综合题:一个客服机器人的每次请求都要带上系统提示词、工具定义、用户资料和最近的对话:怎样安排它们的顺序?Token 超出预算时先裁掉什么?(见[放什么、按什么顺序](大模型应用.md#1-放什么按什么顺序)、[Token 预算](大模型应用.md#2-token-预算)、[让前缀缓存命中](大模型应用.md#3-让前缀缓存命中))
<details>
<summary>答题思路</summary>
1. 不变的内容放最前:系统提示词、工具定义按固定顺序在开头,保证前缀缓存命中,当前时间、用户名不要插在开头。
2. 变化的内容放后面:用户资料、对话摘要、最近几轮原文、当前问题依次追加,历史原样追加不重新拼接;重要规则放在开头或结尾,避免 Lost in the Middle。
3. 先算预算:历史可用 = 窗口 − 预留输出 − 固定部分 − 检索资料上限 − 当前问题,例如 32K 窗口、预留 2K 输出、检索 4K 时历史约 25K。
4. 超出时先清理旧的工具结果,再把较早的对话并入摘要,保留最近几轮原文,工具调用和结果成对删除;系统规则和当前问题不裁。
5. 工具定义占比大时按需加载工具(例子中 6 个工具占 701 个 Token),但动态增减工具和频繁压缩都会破坏前缀缓存,要权衡。
</details>
明天:第 9 天,RAG 的流程、文档解析与切片(大模型应用:RAG 的流程~RAG 的切片)。
## 六、检索增强生成
### 第 9 天:RAG 的流程、文档解析与切片
@@ -232,10 +358,23 @@ Token 是模型处理文本的单位,也是计费和上下文长度的单位
2. 有哪些切片策略?切片大小怎么确定?(见[常用做法](大模型应用.md#2-常用做法)、[怎么确定切片大小](大模型应用.md#4-怎么确定切片大小))
3. PDF 为什么难解析?表格怎么处理?(见[PDF 为什么难解析](大模型应用.md#1-pdf-为什么难解析)、[版面分析](大模型应用.md#2-版面分析))
<details>
<summary>参考要点</summary>
1. 离线:解析文档、切片,用 Embedding 模型转为向量存入向量数据库,同时建立关键词索引;在线:改写问题,向量和关键词两路召回,合并后重排,把最相关的几段和问题放进提示词,生成带引用的回答;文档更新时只重建内容哈希变化的文档,并删除旧切片。
2. 按标题、段落等结构切,表格和代码块保持完整,相邻切片保留重叠,切片带上标题路径;也可以用小切片检索、返回所在的完整段落(父子切片);大小没有通用最优值,以 Embedding 模型的最大长度为上限,事实类问题用小切片,最后在评测集上比较召回率。
3. PDF 只记录“在某坐标画某字”,没有段落和表格,直接读文本层会混入页眉页脚、句子在折行处断开;扫描件要先 OCR,表格可能被逐列输出;用版面分析识别标题、正文、表格和阅读顺序,表格转为 Markdown,按行切分时每行带上表头。
</details>
动手:用 pypdf 提取一份 PDF 的文本,分别按固定长度和按段落切片,比较两种切片的边界是否切断了句子。
预期:固定长度切片的边界常落在句子中间,按段落切的边界都在段落结尾、句子完整,但各片段长度不均。
算法(二分查找,中等):[33. 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/)。
明天:第 10 天,Embedding 与向量索引(大模型应用:Embedding 与相似度~向量索引)。
### 第 10 天:Embedding 与向量索引
Embedding 把文本变成向量,相似度检索靠它完成。向量索引回答数据量大时为什么要用近似检索,重点是 HNSW,IVF 与 PQ 可以略读。
@@ -248,10 +387,23 @@ Embedding 把文本变成向量,相似度检索靠它完成。向量索引回
2. HNSW 的原理是什么?(见[HNSW](大模型应用.md#2-hnsw))
3. 为什么需要近似最近邻检索?(见[为什么需要近似检索](大模型应用.md#1-为什么需要近似检索))
<details>
<summary>参考要点</summary>
1. 分词后输入 Transformer 编码器,每个 Token 得到一个向量,再池化(取 [CLS] 或平均值,与训练时一致)成固定长度的向量并归一化;对比学习使意思相近的文本向量靠近;余弦相似度看方向是否接近,归一化后等于点积、计算更快,排序与欧氏距离等价。
2. 把向量组织成多层图,每个向量连接附近的若干向量,层越高向量越少、连线跨度越大,最下层包含全部向量;查询从最上层的入口开始,每层贪心走向更近的节点,走不动就下降一层,思路类似跳跃表;M、efConstruction、efSearch 在速度、内存和召回率之间权衡。
3. 逐个比较时,100 万个 1024 维向量每次查询约 10 亿次乘加,数据量和并发大时太慢;ANN 索引用少量精度换速度,可能漏掉个别真正最近的向量。
</details>
动手:用 bge-small-zh 等 Embedding 模型计算几组句子的余弦相似度,参照[附录 A1](大模型应用.md#a1-embedding-相似度与-bm25),找出一组语义相近但字面不同、向量检索优于关键词检索的例子。
预期:附录中“怎么申请年假”与“休假的流程是什么”几乎没有相同的字,余弦相似度 0.603,高于与“今天股价上涨了”的 0.194,这类例子关键词检索匹配不上而向量检索能找到。
算法(排序与堆,中等):[215. 数组中的第 K 个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/),题解见[Kth Element](Leetcode%20题解%20-%20排序.md#1-kth-element)。
明天:第 11 天,向量数据库、混合检索与重排(大模型应用:向量数据库~重排)。
### 第 11 天:向量数据库、混合检索与重排
向量数据库除了检索还要处理过滤和更新。混合检索和重排分别弥补向量检索的两个不足:精确的关键词匹配不准,相似度排序不够精细。
@@ -264,10 +416,23 @@ Embedding 把文本变成向量,相似度检索靠它完成。向量索引回
2. 向量检索已经计算了相似度,为什么还要重排?(见[重排](大模型应用.md#重排))
3. 向量数据库中的数据怎样更新和删除?(见[更新与删除](大模型应用.md#2-更新与删除))
<details>
<summary>参考要点</summary>
1. 向量检索擅长匹配措辞不同的近义内容,但精确匹配不准,E1024 和 E1025 文档的相似度 0.762 与 0.743 几乎分不开;BM25 按词频、逆文档频率和文档长度打分,罕见词 E1024 权重高,两篇得分 1.088 与 0.130;两路同时召回,分数尺度不同,用 RRF 按排名融合。
2. Embedding 模型分别编码问题和文档,文档向量能提前算好,速度快,但细节的对应关系在压成一个向量时丢失;重排模型(交叉编码器)把问题和文档拼在一起编码,每个词之间都能算注意力,更准,但每一对都要重算;所以先快速召回几十条,再重排选出几条。
3. HNSW 删除一般只做标记、检索时跳过,之后统一清理,更新等于删除加插入;Milvus 的删除在位图中标记,Compaction 时才真正去掉,所以更新要批量进行;文档修改后按文档 ID 删除全部旧切片再写新切片,按切片 ID 幂等写入,并与关系数据库定期对账。
</details>
动手:写一个函数,按[混合检索](大模型应用.md#混合检索)一节的 RRF 公式把 BM25 和向量检索返回的两个排序列表融合成一个,用几组数据验证结果。
预期:用笔记表中的排名、k = 60 计算,顺序为 A 0.0325、C 0.0323、B 0.0161、E 0.0159、D 0.0156,两路都靠前的 A、C 排在最前。
算法(排序与堆,中等):[912. 排序数组(手写快速排序)](https://leetcode.cn/problems/sort-an-array/),题解见[快速排序](算法%20-%20排序.md#快速排序)。
明天:第 12 天,Top-K 与查询改写(大模型应用:Top-K 与阈值~查询改写)。
### 第 12 天:Top-K 与查询改写
注意区分召回的 K 和放进提示词的 K。查询改写处理用户的提问与文档措辞不一致的情况。
@@ -280,10 +445,23 @@ Embedding 把文本变成向量,相似度检索靠它完成。向量索引回
2. 相似度阈值怎么设置?(见[相似度阈值](大模型应用.md#4-相似度阈值))
3. 为什么要改写用户的查询?有哪些方法?(见[查询改写](大模型应用.md#查询改写))
<details>
<summary>参考要点</summary>
1. 召回的 K 是每路交给重排的候选数(如 50),在评测集上取召回率不再明显上升的位置,有重排时可以取大些;放进提示词的 K 是重排后保留的数量(如 3~5),太大增加成本、混入无关或旧版本资料,太小答不全,按端到端正确率和上下文预算确定。
2. 向量相似度的绝对值很难定阈值:实测阈值 0.50 时无答案的问题放行 40%,0.60 时有答案的问题误拒 10%;所以只用较低的阈值过滤明显无关的结果,有没有答案靠重排分数和“资料不足时回答不知道”的提示词判断;阈值在评测集上确定,换模型后重新确定。
3. 原始问题常不适合直接检索:有指代(“那 ConcurrentHashMap 呢”)时结合历史补全;问题太短时用 HyDE 先生成假设的回答再检索;多个子问题拆开分别检索再合并,RAG-Fusion 改写成多个查询后用 RRF 合并;代价是多一次模型调用。
</details>
动手:阅读[附录 A12](大模型应用.md#a12-切片方式top-k-与相似度阈值),说出切片方式、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. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/),题解见[剑指 Offer 32.2](32.2%20把二叉树打印成多行.md)。
明天:第 13 天,RAG 的评测与扩展(大模型应用:RAG 的评测与优化~多模态 RAG)。
### 第 13 天:RAG 的评测与扩展
评测分检索和生成两部分,排查顺序一节把前几天的内容串成了一个排查流程。长上下文、GraphRAG 和多模态 RAG 可以略读。
@@ -296,12 +474,36 @@ Embedding 把文本变成向量,相似度检索靠它完成。向量索引回
2. RAG 回答得不好,按什么顺序排查?(见[排查顺序](大模型应用.md#4-排查顺序))
3. 评测集怎样构建?(见[评测集](大模型应用.md#3-评测集))
<details>
<summary>参考要点</summary>
1. 检索看 Recall@K(相关切片有多少在前 K 个中)、MRR(第一个相关结果排名倒数的平均)和 NDCG(分级相关、按排名打折);生成看忠实度(结论能否在资料中找到依据)和答案相关性(是否回应了问题),通常由模型裁判拆成小步骤打分,两者要一起看。
2. 按数据流动的顺序从前往后查:解析、切片、检索、重排、生成,前面的错误后面无法弥补;每步分别看解析结果与原文是否一致、答案是否完整在一个切片中、相关切片在两路检索和重排前后的排名、直接给正确资料能否答对;也可以先做最后一步,区分问题在检索还是生成。
3. 优先从真实用户问题中抽样,不够时用模型生成再人工改写措辞,避免照抄原文高估向量检索;包含没有答案、需要多个切片和新旧版本冲突的问题;标注标准答案和所需的原文片段而不是切片 ID,线上的错误回答持续补充进来。
</details>
动手:为 10 个问题标注相关的文档,用自己的检索结果计算 Recall@5 和 MRR,对照[检索指标](大模型应用.md#1-检索指标)一节核对公式。
预期:Recall@5 为相关文档出现在前 5 个结果中的问题比例,MRR 为第一个相关结果排名倒数的平均;用笔记的例子(排名 1、3、未出现)核对,应得到 Recall@5 = 2/3、MRR = 0.44。
算法(二叉树,中等):[236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/),题解见[二叉树的最近公共祖先](Leetcode%20题解%20-%20树.md#5-二叉树的最近公共祖先)。
综合题:公司内部的知识库问答上线后,用户反馈经常答非所问:按什么顺序排查?每一步看什么指标?分别可能改哪个环节?(见[排查顺序](大模型应用.md#4-排查顺序)、[检索指标](大模型应用.md#1-检索指标)、[切片](大模型应用.md#切片)、[重排](大模型应用.md#重排))
<details>
<summary>答题思路</summary>
1. 先区分环节:把正确的资料直接交给模型,答得对说明问题在检索,答不对说明问题在生成。
2. 解析与切片:抽查解析结果与原文对比,表格错乱、页眉混入时改用版面分析;看答案是否完整落在一个切片中,改为按结构切、加重叠、加标题路径。
3. 检索:在评测集上看 Recall@K 和 MRR,相关切片排名靠后时加入混合检索、查询改写,用元数据过滤旧版本,排在 K 之外时增大 K。
4. 重排:对比重排前后相关切片的排名,被排到后面时换重排模型或调整保留的数量。
5. 生成:看忠实度和答案相关性,资料对但答错时减少放入的切片、要求逐条引用、换更强的模型;每次修改后在同一评测集上回归。
</details>
明天:第 14 天,Agent 与工具调用(大模型应用:Agent 是什么~Agent 与工作流)。
## 七、Agent
### 第 14 天:Agent 与工具调用
@@ -316,10 +518,23 @@ Embedding 把文本变成向量,相似度检索靠它完成。向量索引回
2. Function Calling 的原理是什么?(见[工具调用](大模型应用.md#工具调用))
3. Agent 和工作流有什么区别?分别适合什么场景?(见[Agent 与工作流](大模型应用.md#agent-与工作流))
<details>
<summary>参考要点</summary>
1. 模型负责理解任务、决定下一步;工具用于获取信息和执行操作,记忆保存对话历史和用户偏好,规划把复杂任务拆成步骤;控制循环是应用代码中反复调用模型、执行工具的循环,并负责限制步数、校验参数和权限;模型只输出文本,执行都由应用完成。
2. 应用在请求中附上工具的名称、用途和参数 JSON Schema,推理服务按对话模板拼进提示词;模型需要时生成特定格式的文本(如 <tool_call> 中的 JSON),服务解析成 tool_calls 返回;应用校验后执行,用 tool 消息和 tool_call_id 发回结果;本质仍是生成文本,必须先校验再执行。
3. 工作流的步骤和顺序由代码事先写好,模型只做其中某些步骤,可预测、成本可控,适合步骤固定的业务;Agent 由模型决定下一步,适合无法事先确定步骤的开放任务,但成本高、错误会累积(每步 95% 时连续 10 步约 60%);实际常把两者组合,退款这类固定流程走工作流,商品咨询交给 Agent。
</details>
动手:用兼容 OpenAI 接口的模型服务定义一个 query_order 工具(返回模拟的物流信息),按[Agent 循环](大模型应用.md#agent-循环)一节手写循环,完成“订单 1024 到哪了”的问答,打印每一轮的消息。
预期:第 1 轮模型返回 query_order 的 tool_calls(arguments 为 {"order_id": "1024"}),追加 tool 消息后第 2 轮返回包含物流位置的文本,循环结束;每一轮发送的消息列表都比上一轮长。
算法(回溯与搜索,中等):[200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/),题解见[矩阵中的连通分量数目](Leetcode%20题解%20-%20搜索.md#2-矩阵中的连通分量数目)。
明天:第 15 天,意图识别与规划(大模型应用:意图识别与路由~Agent 的规划)。
### 第 15 天:意图识别与规划
意图识别决定一个请求交给哪个流程处理。规划一节比较 ReAct、先规划后执行等方式,重点是各自适合什么任务。
@@ -332,10 +547,23 @@ Embedding 把文本变成向量,相似度检索靠它完成。向量索引回
2. 先规划后执行(Plan-and-Execute)和 ReAct 有什么区别?(见[先规划后执行](大模型应用.md#3-先规划后执行))
3. 怎样保证意图识别和路由准确?(见[怎样保证路由准确](大模型应用.md#2-怎样保证路由准确))
动手:阅读[附录 A6](大模型应用.md#a6-意图路由的三种做法),比较三种路由做法的准确率、延迟和成本,说出各自适合的场景。
<details>
<summary>参考要点</summary>
1. ReAct 让推理和行动交替:先思考,再调用工具,把真实结果作为观察放回上下文再思考;与思维链相比,事实来自外部而不是模型的记忆,HotpotQA 的分析中思维链失败案例 56% 是编造事实,ReAct 为 0%;适合下一步取决于上一步结果、步数不多的任务,缺点是只能串行、长任务容易偏离目标。
2. 先规划后执行先一次生成带依赖的步骤列表,按依赖执行、无依赖的步骤并行,失败或结果不符时重规划(次数设上限),进度容易展示和恢复,但计划可能与实际不符;ReAct 每步根据中间结果调整,但每步都要调用一次模型,长任务容易偏离;实际常结合使用,每个步骤内部用 ReAct。
3. 类别定义互斥并写明边界,输出限制为枚举值,不在列表中的按“其它”处理;分层:规则只处理“转人工”、带订单号这类精确率高的情况,其余交给模型;置信度低时反问澄清或转人工,多轮对话结合最近几轮;用评测集和混淆矩阵衡量,修改后回归。
</details>
动手:阅读[附录 A6](大模型应用.md#a6-意图路由的三种做法),比较三种路由做法的准确率,再结合[三种做法](大模型应用.md#1-三种做法)一节说出各自的延迟、成本和适合的场景。
预期:规则准确率 73.3%,毫秒级、没有成本,但换个说法就漏,适合“转人工”这类精确的情况;分类模型 78.3%,毫秒级、有置信度,需要标注数据;Qwen2.5-7B 95%(3B 90%),每次多一次模型调用,能理解没见过的说法,但可能输出不存在的类别,适合说法多样的消息。
算法(回溯与搜索,中等):[46. 全排列](https://leetcode.cn/problems/permutations/),题解见[排列](Leetcode%20题解%20-%20搜索.md#5-排列)。
明天:第 16 天,记忆与多轮对话(大模型应用:Agent 的记忆~多轮对话)。
### 第 16 天:记忆与多轮对话
短期记忆就是放进上下文的对话历史,长期记忆要解决写什么、怎样检索和怎样更新。多轮对话一节处理指代、追问和话题切换。
@@ -344,14 +572,27 @@ Embedding 把文本变成向量,相似度检索靠它完成。向量索引回
自测:
1. Agent 的短期记忆和长期记忆分别怎样实现?(见[短期记忆](大模型应用.md#1-短期记忆)、[长期记忆的写入](大模型应用.md#2-长期记忆的写入))
1. Agent 的短期记忆和长期记忆分别怎样实现?(见[短期记忆](大模型应用.md#1-短期记忆)、[长期记忆的写入](大模型应用.md#2-长期记忆的写入)、[长期记忆的检索](大模型应用.md#3-长期记忆的检索))
2. 长期记忆中的信息过时或冲突时怎样处理?(见[更新、冲突与遗忘](大模型应用.md#4-更新冲突与遗忘))
3. 多轮对话中的指代和省略怎样处理?(见[指代与省略](大模型应用.md#1-指代与省略))
<details>
<summary>参考要点</summary>
1. 短期记忆是当前会话的原始消息(含工具调用和结果)、摘要和任务状态,常以会话 ID 为键存在 Redis,请求时放入“摘要 + 最近几轮 + 任务状态”;长期记忆由后台提取或模型调用 save_memory 写入,只写用户明确说过、以后有用、业务系统查不到的事实;使用时按用户 ID 过滤,按相似度和时间检索最相关的几条。
2. 写入前检索相似的旧记忆,由模型选择 ADD、UPDATE、DELETE 或 NOOP,用户 ID、时间等字段由代码填写;冲突时用户本次说的优先,亲口说的优先于推断,同一来源以新的为准,只对这一次有效的说法不覆盖,仍无法判断就问用户;旧记忆标记失效而不物理删除,并按时间衰减排序、定期清理。
3. 完整历史在上下文中时模型一般能理解指代,问题出在检索、压缩和多个候选;检索前结合最近几轮和任务状态改写成完整的问题,例如“它什么时候发货”改为“订单 1026 什么时候发货”;任务状态记录当前和提到过的订单,有多个候选时追问而不是猜。
</details>
动手:按[附录 A5](大模型应用.md#a5-长期记忆的提取与更新)的方法,让模型从对话中提取用户偏好写入长期记忆,用户改口后再更新这条记忆。
预期:提取出新地址后,模型对已有的地址记忆选择 UPDATE 而不是重复新增(附录中 m1 更新为江南大道 588 号),订单、物流这类信息不被提取;记忆中的日期由代码填写,不采用模型写的日期。
算法(动态规划,简单):[121. 买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/),题解见[买卖股票最大的收益](Leetcode%20题解%20-%20贪心思想.md#5-买卖股票最大的收益)。
明天:第 17 天,MCP 与 Skills(大模型应用:MCP~Skills)。
### 第 17 天:MCP 与 Skills
MCP 规定应用怎样接入外部的工具和数据,Skills 规定怎样按需加载完成某类任务的方法,两者常被放在一起比较。
@@ -363,10 +604,22 @@ MCP 规定应用怎样接入外部的工具和数据,Skills 规定怎样按需
1. MCP 是什么?MCP 和 Function Calling 有什么区别?(见[MCP](大模型应用.md#mcp))
2. Skill 和 MCP、Tool 有什么区别?(见[Skills](大模型应用.md#skills))
<details>
<summary>参考要点</summary>
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),需要时才读取正文。
</details>
动手:按[附录 A24](大模型应用.md#a24-mcp-服务端的最小实现)写一个提供 query_order 工具的 MCP 服务端并调用它;再写一个 SKILL.md,描述“按模板写周报”的步骤。
预期:tools/list 返回 query_order 的名称、描述和由类型注解生成的参数 Schema;查询 1024 返回模拟的物流信息、isError 为 false,查询不存在的订单返回错误信息、isError 为 true;SKILL.md 开头有 name(小写字母、数字、连字符)和写清做什么、何时使用的 description。
算法(动态规划,简单):[70. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/),题解见[爬楼梯](Leetcode%20题解%20-%20动态规划.md#1-爬楼梯)。
明天:第 18 天,多 Agent 与 Harness(大模型应用:多 Agent~Harness)。
### 第 18 天:多 Agent 与 Harness
多 Agent 一节说明什么时候值得拆成多个 Agent。AI 编程助手和 Harness 讲的是怎样让 Agent 可靠地完成长任务。
@@ -379,10 +632,23 @@ MCP 规定应用怎样接入外部的工具和数据,Skills 规定怎样按需
2. 什么是 Agent Harness?由哪些部分组成?(见[Harness](大模型应用.md#harness))
3. 长任务怎样在多次会话之间交接?(见[长任务的交接](大模型应用.md#3-长任务的交接))
<details>
<summary>参考要点</summary>
1. 子任务互相独立、需要读大量资料或反复试错时值得拆,好处是隔离上下文,子 Agent 只返回精简的结论;代价是 Token 约为普通对话的 15 倍,只传摘要会丢失细节;协作方式有主管分派(子 Agent 包装成工具、可并行)、流水线(固定顺序)和讨论(互看答案后投票或裁决)。
2. Harness 指模型之外的所有代码、配置和执行逻辑;组成包括 Agent 循环与停止条件、上下文组装与压缩、工具与权限、记忆与状态交接、子 Agent、钩子与校验、评测与可观测,MCP 和 Skill 也属于它;核心是动手前的引导(规范、文档、Skill)和动手后的反馈(编译、测试、lint、模型审查)。
3. 第一次会话只做准备:把需求拆成带测试步骤和 passes 字段的 JSON 功能清单,写好启动脚本并提交到 git;之后每次会话先读进度文件和 git log、做冒烟测试,只实现一个未通过的功能,端到端测试通过才把 passes 改为 true,再提交并更新进度;只允许修改 passes,防止 Agent 删掉测试来“完成”任务。
</details>
动手:为自己的一个项目写一份给 AI 编程助手看的说明文件(例如 AGENTS.md),写明构建和测试命令、代码规范和提交前要运行的检查,对照[引导与反馈](大模型应用.md#2-引导与反馈)一节检查是否完整。
预期:文件中有可直接运行的构建和测试命令、代码规范(引导),并列出提交前要运行的编译、测试、lint 等检查(反馈);能用程序检查的规范(如分层规则)配有对应的检查,而不只写在文件里。
算法(动态规划,中等):[53. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/),题解见[子数组最大的和](Leetcode%20题解%20-%20贪心思想.md#10-子数组最大的和)。
明天:第 19 天,开发框架、可靠性与评测(大模型应用:开发框架~可靠性与评测)。
### 第 19 天:开发框架、可靠性与评测
开发框架一节说明 LangChain、LangGraph 和 Spring AI 各自解决什么问题。可靠性与评测讲工具失败时怎样容错,以及怎样评测一个 Agent。
@@ -391,16 +657,40 @@ MCP 规定应用怎样接入外部的工具和数据,Skills 规定怎样按需
自测:
1. LangChain 和 LangGraph 有什么区别?为什么用图来编排?(见[为什么用图编排](大模型应用.md#1-为什么用图编排))
1. LangChain 和 LangGraph 有什么区别?为什么用图来编排?(见[为什么用图编排](大模型应用.md#1-为什么用图编排)、[怎样选择](大模型应用.md#4-怎样选择))
2. 工具调用失败时怎样容错?(见[容错](大模型应用.md#1-容错))
3. 怎样评测一个 Agent?用模型做裁判有哪些偏差?(见[评测](大模型应用.md#2-评测)、[模型裁判的偏差](大模型应用.md#3-模型裁判的偏差))
<details>
<summary>参考要点</summary>
1. LangChain 提供模型、工具、检索器等组件和现成的 Agent,早期核心是按固定顺序串起来的链;LangGraph 是更底层的编排运行时,用共享状态、节点和条件边组成状态图,LangChain 1.0 的 create_agent 就运行在它之上;Agent 和业务流程需要循环、分支、暂停等人工确认和从中间恢复,用链表达很别扭,图加 checkpoint 可以直接表达。
2. 执行前先按 Schema 校验参数,再做业务校验;超时、限流、5xx 由代码按指数退避重试 2~3 次,不交给模型(实测把超时回传给 7B 模型,10 次都没有重新调用);参数错误把错在哪、正确格式是什么回传给模型修正;仍失败就如实告知或转人工,同时限制步数、检测重复调用,有副作用的操作要幂等、人工确认。
3. 主要检查结果(例如数据库中是否真有一条退款记录),轨迹只检查必须满足的约束,优先用代码检查,开放内容才用模型裁判;指标有任务成功率、工具调用正确率和效率,用 pass^k 衡量稳定性;裁判有位置偏差、长度偏差和自我偏好,缓解靠交换顺序评两次、给出参考、拆成逐项判断的问题、与人工标注核对一致率。
</details>
动手:按[附录 A8](大模型应用.md#a8-langgraph-的-checkpoint-与人工确认)的方法,在退款节点之前加入人工确认,中断后从 checkpoint 恢复执行。
预期:第一次调用在 confirm 节点暂停,返回 {'action': 'refund', 'order_id': '1024', 'amount': 59.9},状态中 next 为 ('confirm',);用 Command(resume=True) 恢复后 confirm 节点从头再执行一次(开头的打印出现两次),随后退款完成,退款记录只有一条 refund-1024: 59.9。
算法(动态规划,中等):[5. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/),题解见[最长回文子串](Leetcode%20题解%20-%20字符串.md#10-最长回文子串)。
综合题:用“订单 1024 到哪了”这个例子,说出客服 Agent 从收到问题到给出回答的完整过程:怎样识别意图、调用哪些工具、对话历史怎样保存、工具超时怎样处理。(见[Agent 循环](大模型应用.md#agent-循环)、[意图识别与路由](大模型应用.md#意图识别与路由)、[记忆](大模型应用.md#记忆)、[容错](大模型应用.md#1-容错))
<details>
<summary>答题思路</summary>
1. 意图识别:消息带订单号格式,可由规则直接路由到物流流程,其余交给模型分类并限制为枚举值,物流流程只拿到查订单和查物流的工具。
2. Agent 循环:发送系统提示词、历史和工具定义,模型先返回 get_order(1024),应用校验订单属于当前用户后执行,结果作为 tool 消息追加;下一轮模型调用 get_logistics(运单号),再下一轮返回文本,循环结束,全程限制最大步数。
3. 对话历史:以会话 ID 为键存进 Redis,List 保存消息(包括工具调用和结果),Hash 保存任务状态(当前订单 1024),设置过期时间;下一次请求放入摘要、最近几轮原文和任务状态。
4. 工具超时:由代码按指数退避重试 2~3 次,模型不感知;仍失败就把写清楚的错误作为工具结果返回,让模型如实告知暂时查不到,不能编造“运输中”。
5. 回答前校验:用户问的是位置,就要求调用过物流查询,回答中的地点出现在物流结果中。
</details>
明天:第 20 天,流式输出、成本与部署(大模型应用:流式输出~推理服务部署)。
## 八、工程实践
### 第 20 天:流式输出、成本与部署
@@ -411,14 +701,27 @@ MCP 规定应用怎样接入外部的工具和数据,Skills 规定怎样按需
自测:
1. 流式输出是怎样实现的?SSE 的格式是怎样的?(见[SSE 的格式](大模型应用.md#1-sse-的格式))
1. 流式输出是怎样实现的?SSE 的格式是怎样的?(见[流式输出](大模型应用.md#流式输出)、[SSE 的格式](大模型应用.md#1-sse-的格式))
2. 怎样降低一次 Agent 任务的 Token 消耗?(见[让前缀缓存命中](大模型应用.md#3-让前缀缓存命中)、[减少 Token 消耗](大模型应用.md#4-减少-token-消耗))
3. PagedAttention 解决了什么问题?(见[PagedAttention](大模型应用.md#4-pagedattention))
<details>
<summary>参考要点</summary>
1. 接口用 SSE:响应类型为 text/event-stream,连接保持打开,每生成一段内容写一条事件;事件由若干行“字段: 值”组成、以空行结束,data 是内容、id 是编号、event 是类型,冒号开头的行是注释(常作心跳);OpenAI 兼容接口以 data: [DONE] 结束,断线后 EventSource 带着 Last-Event-ID 重连。
2. 让前缀缓存命中:不变的系统提示词和工具定义放最前、工具顺序固定、历史原样追加,例子中费用从 0.296 元降到 0.053 元;工具先返回片段,需要时再读全文;设置 max_tokens,输出单价是未命中缓存输入的 4 倍;简单问题交给小模型,及时压缩历史、避免重复调用。
3. 早期系统按最大长度为每个请求预留连续的显存,真正存放 KV Cache 的只有 20.4%~38.2%;PagedAttention 借鉴操作系统的分页,把 KV Cache 切成固定大小的块(vLLM 默认 16 个 Token)按需分配,用块表记录位置,只浪费最后一块的空余;同样 7 GB 显存可服务的请求从 16 个增至 64 个,吞吐是 FasterTransformer、Orca 的 2~4 倍。
</details>
动手:用 curl -N 调用模型服务的流式接口,观察 data: 行的格式和最后的 [DONE],再用代码逐行读取并拼接出完整回答。
预期:终端中陆续出现以 data: 开头、以空行分隔的事件,每条 JSON 中带一小段增量文字,最后一条是 data: [DONE];代码跳过空行和注释、读到 [DONE] 停止,按顺序拼接各段得到完整回答。
算法(动态规划,中等):[300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/),题解见[最长递增子序列](Leetcode%20题解%20-%20动态规划.md#1-最长递增子序列)。
明天:第 21 天,稳定性与安全(大模型应用:稳定性~大模型应用的安全)。
### 第 21 天:稳定性与安全
稳定性一节把超时、重试、限流和熔断用到模型调用上,做法与后端服务一致。安全一节的重点是限制模型能造成的后果。
@@ -431,6 +734,25 @@ MCP 规定应用怎样接入外部的工具和数据,Skills 规定怎样按需
2. 流式输出中途断开怎么办?(见[流式输出中途断开](大模型应用.md#4-流式输出中途断开))
3. 怎样防御提示词注入?(见[大模型应用的安全](大模型应用.md#安全))
<details>
<summary>参考要点</summary>
1. 非流式请求的总超时要大于 TTFT + 最大输出 Token 数 × TPOT,例如 1000 个 Token、30 毫秒时要 30 多秒;流式请求分别设首 Token 超时、间隔超时和总时长上限,心跳不算内容;429、5xx、超时可以重试,400、401 直接失败,按指数退避加随机抖动、遵守 Retry-After,次数设上限且只在一层重试。
2. 还没输出任何内容时直接重试,用户感知不到;已输出部分文字时,把已输出的部分作为 assistant 消息的开头请求续写(如 DeepSeek 的对话前缀续写),不支持就丢弃重新生成并通知前端清空;工具调用的参数只输出了一半时不能执行,丢弃后重新请求。
3. 提示词注入指通过输入的内容改变模型原本的指令,指令也可能藏在网页、文档或工具结果中(间接注入);模型无法可靠区分指令和数据,提示词只能降低风险,重点是限制后果;外部内容标注为不可信,工具按最小权限提供、在代码中按当前用户校验,高风险操作人工确认;输出转义、查询参数化,提示词中不放密钥,记录审计日志。
</details>
算法(链表,困难):[25. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/),题解见[K 个一组翻转链表](Leetcode%20题解%20-%20链表.md#11-k-个一组翻转链表)。
综合题:设计一个企业内部的智能客服:用户的问题怎样分流到知识库问答和工具调用?怎样评测效果?怎样控制延迟和成本?出现提示词注入时怎样限制损失?(见[意图识别与路由](大模型应用.md#意图识别与路由)、[评测与优化](大模型应用.md#评测与优化)、[延迟与成本](大模型应用.md#延迟与成本)、[安全](大模型应用.md#安全))
<details>
<summary>答题思路</summary>
1. 分流:意图路由,规则处理“转人工”、带订单号这类精确率高的情况,其余交给分类模型或大模型并限制为枚举值,低置信度时反问或转人工;制度、产品类问题走 RAG,查询和办理走带工具的 Agent,退款这类固定流程写成工作流。
2. 评测:路由用分类评测集和混淆矩阵;RAG 看 Recall@K、MRR 和忠实度、答案相关性,效果差时按解析、切片、检索、重排、生成的顺序排查;Agent 看任务成功率和 pass^k;线上失败的会话加入评测集回归。
3. 延迟:限制输出长度,流式输出缩短用户看到第一个字的时间,互不依赖的工具并行执行;成本:不变的内容放在前面命中前缀缓存,控制工具返回的内容,简单问题交给小模型。
4. 注入:外部资料标注为不可信;用户身份取自登录会话,工具在代码中校验订单属于当前用户并按最小权限提供;退款只生成待确认的申请,由用户确认后走普通业务接口执行;输出转义,记录审计日志。
</details>