🎯 目标:掌握大模型应用开发的核心技能——LLaMA架构、提示词工程、LangChain框架、RAG系统构建、OpenAI API,能够独立开发AI应用。 📋 前置要求:阶段三(Transformer架构、BERT/GPT原理、HuggingFace使用)


本阶段知识依赖图

flowchart TD
    A["阶段三基础(Transformer + BERT/GPT + HuggingFace)"]
    A --> B["LLaMA架构(理解现代大模型)"]
    B -->|归一化改进| B1[RMSNorm]
    B -->|旋转位置编码| B2[RoPE]
    B -->|激活函数改进| B3[SwiGLU]
    B -->|推理加速| B4[KV Cache]
    B -->|注意力优化| B5[GQA]
    A --> C["提示词工程(高效使用大模型)"]
    C -->|输出格式、指令设计| C1[基础技巧]
    C -->|CoT、ToT、Few-shot| C2[高级技巧]
    C -->|Temperature、Top-P| C3[参数调优]
    A --> D["LangChain框架(应用开发框架)"]
    D -->|统一接口| D1[LLM调用]
    D -->|结构化提示| D2[提示模板]
    D -->|组合多个组件| D3["链(Chain)"]
    D -->|自主决策| D4[Agent]
    A --> E["RAG系统(检索增强生成)"]
    E -->|Milvus/FAISS/Chroma| E1[向量数据库]
    E -->|PDF/Markdown解析| E2[文档加载]
    E -->|语义切分| E3[文本切片]
    E -->|BGE-Large/BM25| E4[Embedding]
    E -->|高级检索/自适应RAG| E5[检索策略]
    A --> F["OpenAI API(模型调用接口)"]
    F -->|文本向量化| F1[Embedding]
    F -->|对话接口| F2[Chat Completion]
    F -->|工具调用| F3[Function Calling]

模块一:LLaMA架构深入——理解现代大模型

LLaMA为什么重要?

类比:如果Transformer是"汽车的发明",那LLaMA就是"现代汽车的标准设计"。

LLaMA(Large Language Model Meta AI)是 Meta 发布的开源大模型系列。 它是目前大多数开源大模型的基础架构——几乎所有主流开源模型都是基于 LLaMA 微调而来:

flowchart TD
    L["LLaMA(原始)"]
    L --> Alpaca["Alpaca(斯坦福微调)<br/>用指令数据微调"]
    L --> Vicuna["Vicuna(LMSYS微调)<br/>用对话数据微调"]
    L --> Chinese["Chinese-LLaMA(中文适配)<br/>加入中文词表"]
    L --> Code["CodeLLaMA(代码能力)<br/>用代码数据微调"]
    L --> Meta["LLaMA 2/3(Meta官方迭代)<br/>更大、更强"]

理解 LLaMA = 理解当前大模型的核心设计思想。 它在 Transformer 基础上做了 5 个关键改进,每个改进都解决了特定问题。

RMSNorm——改进的归一化

归一化为什么重要?

类比:考试成绩标准化

假设两个班级的考试:

  • A 班:平均分 90 分,最高 95,最低 85(分数集中在 90 附近)
  • B 班:平均分 60 分,最高 100,最低 20(分数很分散)

如果直接用原始分数比较两个班的学生,分布差异会干扰判断。 归一化的作用:把两个班的分数都“拉”到同一个范围(比如均值 0,标准差 1) → 现在可以公平比较了。

神经网络中也一样:如果每层的输入分布差异很大,网络很难学习。 归一化让每层的输入分布稳定,训练更高效。

LayerNorm vs RMSNorm——详细对比

LayerNorm(4步)RMSNorm(3步)
1. 计算均值 $\mu = (1/d)\sum x_i$
2. 计算方差 $\sigma^2 = (1/d)\sum (x_i - \mu)^2$
3. 归一化 $\hat{x} = (x - \mu) / \sqrt{\sigma^2 + \epsilon}$
4. 缩放 $y = \gamma \cdot \hat{x} + \beta$
1. 计算均方根 $RMS = \sqrt{(1/d)\sum x_i^2}$
2. 归一化 $\hat{x} = x / RMS$
3. 缩放 $y = \gamma \cdot \hat{x}$

核心区别:RMSNorm 去掉了“减均值”(re-centering)和“偏置 $\beta$”。

为什么去掉"减均值"没问题?

类比:你在调节收音机的音量

  • LayerNorm = 先把音量归零(减均值),再调到合适大小(缩放)
  • RMSNorm = 直接调到合适大小(只缩放,不归零)

实验发现:先归零再调,和直接调,效果差别很小。 但省去“归零”这一步,计算量减少了约 15%。 在大模型中(几十亿参数),15% 的计算节省 = 巨大的成本节约!

哪些模型使用RMSNorm?

  • ✅ LLaMA / LLaMA 2 / LLaMA 3
  • ✅ Qwen / Qwen2
  • ✅ Mistral / Mixtral
  • ✅ Gemma
  • ✅ DeepSeek

RMSNorm 已经成为现代大模型的标配。

RoPE——旋转位置编码

为什么需要新的位置编码?

正弦位置编码的三个局限

  1. 固定不变:正弦编码是预先计算好的,不参与训练
    • 模型无法根据任务自适应调整位置表示
  2. 只编码绝对位置:PE(3) 只告诉你“这是第 3 个词”
    • 不能直接知道“第 3 个词和第 7 个词之间隔了 4 个词”
    • 但语言理解往往更依赖相对位置(“主语在谓语前面 2 个词”)
  3. 外推能力有限:训练时最长 512 个词,推理时超过 512 效果急剧下降
    • 无法处理更长的文档

RoPE的核心思想——把位置编码变成"旋转"

类比:时钟的指针

想象一个时钟:

  • 1 点钟:指针转了 30°
  • 2 点钟:指针转了 60°
  • 3 点钟:指针转了 90°

每个时刻的位置 = 指针旋转的角度。两个时刻之间的“距离” = 角度之差。

RoPE 做的是同样的事:

  • 把词向量的每两个维度看作一个二维平面上的点
  • 位置 m 的词向量旋转 $m\times\theta$ 角度
  • $\theta$ 是一个预设的旋转速度(不同维度不同)

RoPE的数学本质

对于位置 $m$ 的 query 向量 $q$ 和位置 $n$ 的 key 向量 $k$:

应用 RoPE 后的注意力分数:

$$ q_m^T \cdot k_n = (R_m \cdot q)^T \cdot (R_n \cdot k) = q^T \cdot R_{(n-m)} \cdot k $$

关键性质:注意力分数只依赖于相对位置 $(n-m)$,而不是绝对位置 $m$ 和 $n$。

这意味着:

  • 模型天然理解“距离”(相隔几个词)
  • 不管句子从哪个位置开始,相对关系不变
  • 可以外推到更长的序列(因为只依赖相对距离)

RoPE vs 正弦位置编码

特性正弦位置编码RoPE
编码方式加到输入上乘到 Q/K 上
位置类型绝对位置相对位置
是否可训练固定不变可通过缩放因子调整
外推能力有限较好
使用模型原始 TransformerLLaMA、Qwen、Mistral

SwiGLU——改进的激活函数

从ReLU到SwiGLU的进化

传统 FFN(Transformer 原版):

  • $FFN(x) = ReLU(x\cdot W_1 + b_1)\cdot W_2 + b_2$
  • 维度变化:$d_{model} \rightarrow 4\times d_{model} \rightarrow d_{model}$

SwiGLU FFN(LLaMA 使用):

  • $FFN(x) = (Swish(x\cdot W_1) \odot x\cdot W_3)\cdot W_2$
  • 维度变化:$d_{model} \rightarrow (8/3)\times d_{model} \rightarrow d_{model}$

其中:

  • $Swish(x) = x \cdot \sigma(x)$($\sigma$ 是 Sigmoid 函数)
    • 直觉:Swish 是一个“平滑的 ReLU”——在 $x<0$ 时不完全关闭,而是留一点“缝隙”
  • $\odot$ 是逐元素相乘(门控机制)
    • 直觉:$W_3$ 产生的值像一个“阀门”,控制 $W_1$ 的信息通过多少

GLU(Gated Linear Unit)的核心思想——门控

SwiGLU = Swish + GLU(门控线性单元)

门控的意思是:不是简单地“全部通过”或“全部阻断”,而是对信息的每个维度独立地“调节流量”。

类比:

  • ReLU = 一个水龙头,要么全开($x>0$),要么全关($x\leq 0$)
  • SwiGLU = 一个可调节的阀门,可以控制每个出水孔的流量

效果:SwiGLU 在多个基准测试上优于 ReLU,训练更稳定。 代价:多了一个权重矩阵 $W_3$,参数量增加约 50% (所以 LLaMA 把隐藏维度从 $4d$ 降到 $8/3d$ 来补偿)。

KV Cache——推理加速的关键

为什么自回归生成很慢?

类比:翻译一本书

  • 翻译第 1 个词时:需要读完整本书(完整前向传播)
  • 翻译第 2 个词时:又要读完整本书(但大部分内容和上次一样)
  • 翻译第 3 个词时:又要读完整本书
  • 翻译第 1000 个词时:还是要读完整本书

问题:每次都重新计算所有位置的 K 和 V,但之前计算的结果完全可以复用。

KV Cache的解决方案

KV Cache = “笔记本”:把之前算过的 K 和 V 记下来,下次直接用。

  1. 生成第 1 个词(Prefill 阶段):
    • 计算所有位置的 K 和 V,全部存入 Cache
    • 输出第 1 个词
  2. 生成第 2 个词(Decode 阶段):
    • 只计算新位置的 $K_2, V_2$(1 次计算)
    • 从 Cache 读取 $K_1, V_1$(直接读取,不需要计算)
    • 拼接后计算注意力
    • 输出第 2 个词
  3. 生成第 3 个词:
    • 只计算 $K_3, V_3$
    • 从 Cache 读取 $[K_1, K_2], [V_1, V_2]$
    • 拼接后计算注意力
    • 输出第 3 个词

效果:每个新词只需要 1 次前向传播(而不是 $seq\_len$ 次) → 推理速度提升 $seq\_len$ 倍。

KV Cache的显存开销

KV Cache 大小:

$$ 2 \times num\_layers \times num\_heads \times d\_{head} \times seq\_{len} \times batch\_size \times dtype\_size $$

示例(LLaMA-7B,float16,单条序列):

  • $2 \times 32$ 层 $\times 32$ 头 $\times 128$ 维 $\times 2048$ 长度 $\times 2$ bytes $\approx 1GB$

示例(LLaMA-70B,float16,单条序列):

  • $2 \times 80$ 层 $\times 64$ 头 $\times 128$ 维 $\times 4096$ 长度 $\times 2$ bytes $\approx 20GB$

结论:

  • 大模型的 KV Cache 可以占到模型本身显存的 30%-50%
  • 长上下文(100K+token)需要大量显存
  • GQA 的出现就是为了减少 KV Cache 的大小

GQA——Grouped Query Attention

为什么要优化注意力的KV?

问题:KV Cache太大了!

标准 Multi-Head Attention (MHA):

  • Q:32 个头,每个头有独立的 $W_Q$
  • K:32 个头,每个头有独立的 $W_K$(32 组 KV)
  • V:32 个头,每个头有独立的 $W_V$

KV Cache 大小 $\propto num\_heads$(头数越多,Cache 越大)。

如何减小 KV Cache?→ 减少 KV 的“头数”。

三种方案的对比

方案Q 头数 / K 头数 / V 头数特点
MHA(标准多头注意力)Q: 32 / K: 32 / V: 32每个 Q 头有自己的 KV,互不共享;质量最好,但 KV Cache 最大
MQA(多查询注意力)Q: 32 / K: 1 / V: 1所有 Q 头共享同一个 KV;KV Cache 最小,但质量下降明显
GQA(分组查询注意力)Q: 32 / K: 8 / V: 8每 4 个 Q 头共享一组 KV;质量接近 MHA,速度接近 MQA

类比:

  • MHA = 每个人都有自己的参考资料(32 份)
  • MQA = 所有人共用一份参考资料(1 份)
  • GQA = 每 4 人一组,每组一份参考资料(8 份)

GQA的效果

  • LLaMA 1:使用 MHA(标准多头注意力)
  • LLaMA 2:使用 GQA(分组查询注意力,8 个 KV 组)
  • LLaMA 3:使用 GQA(进一步优化)

GQA 让 KV Cache 减小了约 4 倍,同时模型质量几乎不变。

  • 可以在相同显存下处理更长的序列
  • 可以用更大的 batch_size,提高吞吐量

LLaMA推理策略

Temperature——控制输出的"随机性"

类比:选择餐厅

  • Temperature = 0(极度保守):每次都去评分最高的餐厅 → 确定性最高,但可能无聊
  • Temperature = 0.7(平衡):大概率去评分高的,偶尔尝试新餐厅 → 既有质量又有惊喜
  • Temperature = 1.0(随机):随机选一家 → 完全不可预测

Temperature的数学原理

  • 原始 logits:[2.0, 1.0, 0.1]
  • Temperature = logits / T

示例:

  • T=0.5: [4.0, 2.0, 0.2] → softmax 后 [0.84, 0.12, 0.04] → 非常确定
  • T=1.0: [2.0, 1.0, 0.1] → softmax 后 [0.66, 0.24, 0.10] → 原始分布
  • T=2.0: [1.0, 0.5, 0.05] → softmax 后 [0.50, 0.30, 0.20] → 更均匀

结论:T 越小 → 分布越尖锐 → 输出越确定;T 越大 → 分布越平坦 → 输出越随机。

Top-K和Top-P采样

  • Top-K 采样:只从概率最高的 K 个词中采样
    • K=1:等价于贪心搜索(永远选概率最高的词)
    • K=50:从 50 个候选词中随机选
    • 问题:K 是固定的,简单问题和复杂问题用同一个 K
  • Top-P(Nucleus Sampling):动态选择候选集
    • 按概率从高到低排序,累加直到概率之和超过 P
    • 简单问题:可能只需要前 3 个词就超过 P=0.9 → 候选集小
    • 复杂问题:可能需要前 50 个词才超过 P=0.9 → 候选集大

实际使用推荐:

  • 代码生成:Temperature=0,Top-P=1.0(确定性输出)
  • 一般对话:Temperature=0.7,Top-P=0.9(平衡)
  • 创意写作:Temperature=1.0,Top-P=0.95(多样输出)

模块二:提示词工程——高效使用大模型

为什么提示词工程重要?

类比:和外国人交流

  • 你说:“帮我写个东西”
    • 外国人(大模型):写什么?写给谁?什么风格?多长?
    • 输出:一段泛泛而谈的文字
  • 你说:“你是一位资深电商文案专家。请为一款售价 299 元的蓝牙耳机写一段小红书种草文案。要求:标题吸引眼球,突出降噪功能,使用 emoji,200 字以内。”
    • 外国人(大模型):明白了!
    • 输出:一篇精准、专业的文案

提示词工程 = 学会如何清晰、精确地表达你的需求。

基础技巧

结构化提示词的四要素

  1. 角色(Role):告诉模型“你是谁”
    • 设定专业背景,让模型调用相关知识
    • 例:“你是一位有 10 年经验的 Python 高级工程师”
  2. 任务(Task):告诉模型“做什么”
    • 明确、具体的任务描述
    • 例:“请帮我写一个 FastAPI 接口,实现用户登录功能”
  3. 格式(Format):告诉模型“怎么输出”
    • 指定输出的格式和结构
    • 例:“输出为完整的 Python 代码,包含注释和错误处理”
  4. 约束(Constraint):告诉模型“边界在哪”
    • 限制条件、质量要求
    • 例:“使用异步函数,返回 JSON 格式,包含 token 过期时间”

四要素的组合效果

  • 只有任务:“帮我写个 API” → 输出不确定
  • 任务 + 格式:“帮我写个 API,输出 Python 代码” → 稍好
  • 任务 + 格式 + 约束:“帮我写个 FastAPI 用户登录 API,用 Python 代码输出,包含 JWT 认证” → 更好
  • 全部四个:“你是一位 Python 高级工程师。请帮我写一个 FastAPI 用户登录接口。输出完整的 Python 代码,包含注释。要求:使用异步函数,实现 JWT 认证,添加输入验证和错误处理,返回标准 JSON 响应。” → 最好

高级技巧

零样本思维链(Zero-shot CoT)——激活推理能力

  • 普通提示:“小明有 5 个苹果,给了小红 2 个,又买了 3 个,现在有几个?”
    • 模型可能直接猜“6”(可能错)
  • 加一句“让我们一步一步思考”:
    • “小明有 5 个苹果,给了小红 2 个,又买了 3 个,现在有几个?让我们一步一步思考。”
    • 模型会展示中间步骤:
        1. 小明开始有 5 个苹果
        1. 给了小红 2 个,剩下 5-2=3 个
        1. 又买了 3 个,变成 3+3=6 个
      • 答案:6 个

为什么加一句话就能提升准确率?

  • “让我们一步一步思考”激活了模型的“慢思考”模式
  • 模型被迫展示中间步骤,减少了“跳步”导致的错误
  • 类似于人类“打草稿”比“心算”更准确

少样本提示(Few-shot)——用示例教会模型

  • 零样本(Zero-shot):直接让模型做任务
    • “请判断情感:这家餐厅很好吃 → ?”
    • 模型可能理解你要什么,也可能不理解
  • 少样本(Few-shot):给几个示例,让模型学会模式
    • “请判断以下评论的情感:
      • 评论:这家餐厅太好吃! → 正面
      • 评论:服务态度很差 → 负面
      • 评论:菜品种类丰富 → 正面
      • 评论:等了一个小时才上菜 → ?”
    • 模型学会了“判断情感”的模式,准确率大幅提升

关键:示例的质量和多样性很重要

  • 至少 2-3 个正例和 2-3 个反例
  • 示例要覆盖典型情况
  • 示例的格式要和目标任务一致

思维树(Tree of Thought)——多路径推理

思维链(CoT):一条推理路径(线性)

  • A → B → C → D → 答案
  • 问题:如果 B 错了,后面全错

思维树(ToT):多条推理路径(树状)

graph TD
    A[问题] --> B1[路径 1]
    A --> B2[路径 2]
    A --> B3[路径 3]
    B1 --> C1[答案 1]
    B2 --> C2[答案 2]
    B3 --> C3[答案 3]

适用场景:需要多步推理的复杂问题(数学证明、策略规划、代码调试)。

参数调优——调参的艺术

  • Temperature(温度)
    • 0.0 → 完全确定性,每次输出相同(适合代码、数学)
    • 0.3 → 高度确定性,偶尔有小变化(适合翻译、摘要)
    • 0.7 → 平衡模式(适合对话、写作)
    • 1.0 → 高随机性(适合创意、头脑风暴)
  • Top-P(核采样)
    • 0.1 → 只从最可能的几个词中选(极度保守)
    • 0.9 → 从累积概率 90% 的词中选(推荐默认值)
    • 1.0 → 从所有词中选(最多样)
  • Frequency Penalty(频率惩罚)
    • 0.0 → 不惩罚重复(默认)
    • 0.5 → 轻微减少重复
    • 1.0 → 强烈避免重复(适合长文本生成)
  • Presence Penalty(存在惩罚)
    • 0.0 → 不鼓励新话题(默认)
    • 0.5 → 轻微鼓励新话题
    • 1.0 → 强烈鼓励新话题(适合头脑风暴)

模块三:LangChain框架——AI应用开发的标准工具

LangChain是什么?

类比:Spring Boot之于Java Web开发,LangChain之于LLM应用开发

没有 LangChain 时,开发一个 RAG 应用需要:

  1. 手动调用 OpenAI API
  2. 手动加载和切分文档
  3. 手动实现向量搜索
  4. 手动组装提示词
  5. 手动处理输出解析
  6. 手动管理对话历史

→ 每个项目都要重复造轮子。

有了 LangChain:

  1. 统一的 LLM 调用接口(支持 OpenAI、本地模型、各种 API)
  2. 内置的文档加载和切分工具
  3. 内置的向量存储和检索
  4. 标准化的提示模板
  5. 自动化的输出解析
  6. 内置的记忆管理

→ 专注于业务逻辑,不用重复造轮子。

核心组件1:LLM调用——统一接口

from langchain_openai import ChatOpenAI
from langchain_community.llms import Ollama

# OpenAI API
llm = ChatOpenAI(model="gpt-4", temperature=0.7)

# 本地模型(Ollama部署的Qwen3)
llm = Ollama(model="qwen3:8b")

# 智谱AI
from langchain_community.chat_models import ChatZhipuAI
llm = ChatZhipuAI(model="glm-4")

# 统一接口:不管底层是什么模型,调用方式完全一样
response = llm.invoke("什么是RAG?")
print(response.content)

# 这就是LangChain的核心价值之一:
# 一行代码切换模型,不需要修改业务逻辑

核心组件2:提示模板——结构化提示词

from langchain_core.prompts import ChatPromptTemplate

# 创建模板(类似填空题)
template = ChatPromptTemplate.from_messages([
    ("system", "你是一位{role},请用{style}的方式回答问题。"),
    ("user", "{question}")
])

# 填充模板(把空填上)
prompt = template.invoke({
    "role": "AI专家",
    "style": "简洁明了",
    "question": "什么是Transformer?"
})

# 发送给模型
response = llm.invoke(prompt)

为什么需要模板?

类比:邮件模板

  • 没有模板:每次写邮件都要从头写
  • 有模板:填入姓名、日期等变量,自动生成完整邮件

提示模板同理:

  • 定义一次模板,多次复用
  • 变量可以在运行时动态填充
  • 保证提示词的一致性

核心组件3:链(Chain)——LCEL管道语法

from langchain_core.output_parsers import StrOutputParser

# LCEL(LangChain Expression Language)管道语法
chain = template | llm | StrOutputParser()

# 等价于:
# 1. template处理输入 → 生成提示词
# 2. llm处理提示词 → 生成回答
# 3. StrOutputParser处理回答 → 提取纯文本

# 执行链
result = chain.invoke({
    "role": "AI专家",
    "style": "简洁明了",
    "question": "什么是Transformer?"
})

# 管道语法的魔力:可以用 | 把任意组件串联起来
# 就像Unix的管道:cat file | grep "error" | sort

核心组件4:文档加载与向量存储

from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS

# 1. 加载文档(把PDF变成可处理的文本)
loader = PyPDFLoader("document.pdf")
documents = loader.load()

# 2. 文本切分(把长文档切成小块)
splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,     # 每块500个字符
    chunk_overlap=50    # 相邻块重叠50个字符
)
chunks = splitter.split_documents(documents)

# 3. 向量化(把文本变成数字向量)
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(chunks, embeddings)

# 4. 检索(找到最相关的文档块)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
docs = retriever.invoke("什么是机器学习?")

为什么要切分?为什么要有overlap?

为什么切分:

  • 大模型有上下文长度限制(如 4K、8K、128K tokens)
  • 一次塞入整本文档不现实
  • 检索时需要精确匹配,整本文档太粗糙

为什么 overlap(重叠):

  • 如果在句子中间切断,前后两块的语义都不完整
  • overlap 让相邻块有重叠部分,确保信息的连续性
  • 类比:看书时翻页,上一页的最后一行和下一页的第一行能衔接上

核心组件5:RAG链——检索增强生成

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough

# RAG提示模板
rag_prompt = ChatPromptTemplate.from_template("""
请根据以下参考信息回答用户的问题。
如果参考信息中没有相关内容,请说明你不确定。

参考信息:
{context}

用户问题:
{question}
""")

# RAG链的构建(LCEL管道语法)
rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | rag_prompt
    | llm
    | StrOutputParser()
)

# 使用
answer = rag_chain.invoke("什么是注意力机制?")
# 流程:
# 1. "什么是注意力机制?" → retriever检索相关文档
# 2. 检索到的文档 + 问题 → 填入提示模板
# 3. 填充后的提示 → 发送给LLM
# 4. LLM的回答 → 提取纯文本 → 返回

核心组件6:Agent——让模型自主决策

from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.tools import tool

# 定义工具(给模型"武器")
@tool
def search_web(query: str) -> str:
    """搜索网页获取最新信息"""
    return "搜索结果..."

@tool
def calculate(expression: str) -> str:
    """计算数学表达式"""
    return str(eval(expression))

# 创建Agent(给模型"大脑")
tools = [search_web, calculate]
agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# 使用
result = agent_executor.invoke({"input": "今天北京的天气怎么样?"})
# Agent会自动:
# 1. 分析问题 → "需要查天气"
# 2. 选择工具 → search_web
# 3. 调用工具 → search_web("北京今天天气")
# 4. 整合结果 → 生成自然语言回答

Chain vs Agent的区别

  • Chain(链):固定流程,A → B → C
    • 类比:工厂流水线——每一步都是预设的
    • 适合:流程明确的任务(RAG 问答、文本翻译)
  • Agent(智能体):动态决策,根据情况选择下一步
    • 类比:真人客服——根据问题类型灵活应对
    • 适合:需要判断和选择的任务(多工具调用、复杂查询)

模块四:OpenAI API与Embedding

Embedding——把文本变成"语义坐标"

类比:给每个词/句子在"语义地图"上定位

  • Embedding = 把文本转换为一个固定长度的数字向量
  • “猫” → [0.2, 0.8, -0.1, 0.5, …](1536 维)
  • “狗” → [0.3, 0.7, -0.2, 0.4, …](和“猫”接近)
  • “汽车” → [-0.5, 0.1, 0.9, -0.3, …](和“猫”很远)

语义相似的文本 → 向量接近(余弦相似度接近 1) 语义不同的文本 → 向量远离(余弦相似度接近 0)

from openai import OpenAI
import numpy as np

client = OpenAI()

# 获取Embedding
response = client.embeddings.create(
    model="text-embedding-3-small",
    input="什么是机器学习?"
)
embedding = response.data[0].embedding  # 1536维向量

# 计算相似度
def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# "机器学习是什么" 和 "什么是机器学习" → 几乎相同的问题 → 高相似度
sim1 = cosine_similarity(embedding1, embedding2)  # ≈ 0.95

# "什么是机器学习" 和 "今天天气怎么样" → 完全不同的话题 → 低相似度
sim2 = cosine_similarity(embedding1, embedding3)  # ≈ 0.1

Embedding是RAG的基础

  • RAG 的检索步骤 = 把用户问题和所有文档都转为 Embedding,然后找最相似的
  • 用户问题:“什么是注意力机制?” → Embedding → [0.1, 0.5, -0.3, …]
  • 文档 1:“注意力机制是一种让模型关注重要信息的技术”
    • Embedding → [0.1, 0.5, -0.2, …] ← 高相似度
  • 文档 2:“今天天气很好”
    • Embedding → [-0.4, 0.1, 0.8, …] ← 低相似度
  • 返回文档 1 给模型作为参考

Chat Completion API

from openai import OpenAI
client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4",
    messages=[
        {"role": "system", "content": "你是一位AI助手"},
        {"role": "user", "content": "解释什么是RAG"}
    ],
    temperature=0.7,
    max_tokens=500,
    stream=True  # 流式输出(逐字显示,而不是等全部生成完)
)

# 流式输出——提升用户体验
for chunk in response:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")
# 输出:RAG(Retrieval-Augmented Generation)是一种...

模块五:RAG系统深入——检索增强生成

为什么需要RAG?——大模型的三大硬伤

  • 硬伤 1:知识截止日期
    • GPT-4 的知识截止到 2024 年 4 月
    • 问它“今天的新闻” → 完全不知道
  • 硬伤 2:幻觉(Hallucination)
    • 大模型会“一本正经地胡说八道”
    • 问它一个它不知道的事实 → 可能编造看起来合理但错误的答案
  • 硬伤 3:企业私有数据
    • 大模型从未见过你公司的内部文档、产品手册、客户数据
    • 问它“我们公司的退货政策是什么” → 完全不知道

RAG 的解决方案:不让大模型“凭记忆回答”,而是先帮它“查资料”,再让它“基于资料回答”。

  • 知识可以实时更新(查最新的资料)
  • 减少幻觉(有据可依)
  • 可以访问私有数据(先检索企业文档)

RAG的完整流程——每一步详解

Step 1:文档处理(离线,一次性完成)

  • PDF/Word/Markdown → 文本提取 → 文本切分 → Embedding → 存入向量数据库

Step 2:检索(在线,每次查询时执行)

  • 用户问题 → 问题 Embedding → 向量数据库中搜索最相似的文档块

Step 3:生成(在线,每次查询时执行)

  • 检索到的文档块 + 用户问题 → 组装提示词 → 发送给 LLM → 生成回答

类比:

  • Step 1 = 把图书馆的书整理好,建立索引
  • Step 2 = 根据读者的问题,从图书馆找出相关的书
  • Step 3 = 让 AI 助手阅读这些书,然后回答读者的问题

数据加载与切片——决定RAG质量的关键

# PDF解析
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("enterprise_doc.pdf")
docs = loader.load()

# Markdown解析
from langchain_community.document_loaders import UnstructuredMarkdownLoader
loader = UnstructuredMarkdownLoader("readme.md")

# 语义切分(推荐)
from langchain_experimental.text_splitter import SemanticChunker
splitter = SemanticChunker(OpenAIEmbeddings())
chunks = splitter.split_documents(docs)

切分方式对比

  • 固定长度切分:每 500 个字符切一刀
    • 优点:简单
    • 缺点:可能在句子中间切断,语义不完整
  • 递归切分(RecursiveCharacterTextSplitter):按段落 → 句子 → 词的优先级切
    • 优点:尽量在自然边界处切分
    • 缺点:块大小不均匀
  • 语义切分(SemanticChunker):根据语义相似度自动找到切分点
    • 优点:每个块语义完整
    • 缺点:计算量较大(需要调用 Embedding 模型)

实际推荐:

  • 快速原型:用递归切分
  • 生产环境:用语义切分
  • 特定格式:用专门的解析器(如 Markdown 用 MarkdownHeaderTextSplitter)

向量数据库——存储和检索的"仓库"

数据库类型适用场景特点
FAISS内存型快速原型/小数据速度快,不能持久化(关机就没了)
Chroma嵌入型本地开发/小项目简单易用,自动持久化到磁盘
Milvus服务型生产环境/大数据分布式、高性能、企业级
Pinecone云服务无需运维托管服务,按量付费

类比:

  • FAISS = 纸质便签——快速方便,但容易丢
  • Chroma = 笔记本——持久保存,但容量有限
  • Milvus = 数据中心——高性能、高可靠,但需要运维
  • Pinecone = 云存储——不用操心基础设施,但要付费

高级检索策略——提升RAG效果的关键

# 1. 混合检索(Hybrid Search):语义 + 关键词
# 类比:搜索时同时考虑"意思相近"和"关键词匹配"
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

bm25_retriever = BM25Retriever.from_documents(chunks)  # 关键词检索
vector_retriever = vectorstore.as_retriever()            # 语义检索

# 70%语义 + 30%关键词
ensemble_retriever = EnsembleRetriever(
    retrievers=[vector_retriever, bm25_retriever],
    weights=[0.7, 0.3]
)

# 为什么需要混合检索?
# 纯语义检索的问题:可能检索到语义相似但关键词不匹配的文档
# 纯关键词检索的问题:可能漏掉语义相关但用词不同的文档
# 混合检索取长补短
# 2. 多查询检索(Multi-Query):一个问题,多种表述
# 类比:搜索时同时用多个关键词
from langchain.retrievers import MultiQueryRetriever
retriever = MultiQueryRetriever.from_llm(
    retriever=vectorstore.as_retriever(),
    llm=llm
)
# 原始问题:"什么是RAG?"
# 自动生成多个变体:
#   "解释检索增强生成技术"
#   "RAG的定义和原理"
#   "描述RAG的工作流程"
# 合并所有查询的检索结果 → 召回率大幅提升
# 3. 上下文压缩(Contextual Compression):只保留相关内容
# 类比:从一本书中只摘抄和问题相关的段落
from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank
compressor = CohereRerank()
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=vectorstore.as_retriever()
)
# 检索到的文档块可能包含大量无关信息
# 压缩后只保留与问题最相关的部分 → 减少噪音,提高回答质量

Corrective RAG——自我纠正

标准 RAG 的问题:检索到的文档可能不相关,但模型仍会基于这些文档生成回答(“垃圾进,垃圾出”)。

Corrective RAG 的改进流程:

  1. 检索文档
  2. 评估文档与问题的相关性(用 LLM 判断)
  3. 如果相关性低 → 用网络搜索补充更相关的信息
  4. 如果相关性高 → 直接使用
  5. 生成回答后,再评估回答的质量
  6. 如果质量不达标 → 重新检索或重新生成

类比:一个负责任的研究员

  • 普通 RAG = 随便找几本书就回答
  • Corrective RAG = 先检查找的书是否相关,不相关就换一批,回答后还要检查质量

Adaptive RAG——自适应检索

核心思想:不同类型的问题需要不同的处理方式。

  • 简单事实问题:“法国的首都是哪?”
    • 大模型自己就知道,不需要检索
    • 直接回答,省时省力
  • 复杂知识问题:“比较 RAG 和微调的优劣”
    • 需要检索相关文档
    • 基于检索结果回答
  • 推理问题:“如果 A > B,B > C,那么 A 和 C 的关系?”
    • 不需要检索(这不是知识问题,是推理问题)
    • 让模型用思维链推理

Adaptive RAG = 先判断问题类型 → 再选择最合适的处理方式 → 效率最高、效果最好。


模块六:向量数据库深入

向量搜索的底层原理

类比:在图书馆找"最相似的书"

  • 传统数据库(MySQL):精确匹配
    • SELECT * FROM books WHERE title = '深度学习'
    • 只能找到标题完全匹配的书
  • 向量数据库:语义相似度搜索
    • “找到和‘深度学习’含义最接近的书”
    • 能找到“神经网络”“机器学习入门”“TensorFlow 实战”等相关书籍

向量搜索的核心算法

  • 暴力搜索(Brute Force)
    • 计算查询向量和所有向量的距离 → 取最近的 k 个
    • 精度:100%(不会漏掉任何结果)
    • 速度:$O(n\times d)$,$n$=向量数量,$d$=维度
    • 问题:数据量大时极慢(100 万个 1536 维向量需要计算 15 亿次)
  • 近似最近邻(ANN)
    • 用一些“聪明的策略”加速搜索,牺牲少量精度换取大量速度

HNSW——最常用的ANN算法

HNSW(Hierarchical Navigable Small World)= 分层可导航小世界图。

类比:找人的社交网络

  • 第 1 层(稀疏层):只有“超级节点”(明星、大 V)→ 快速定位大致区域
  • 第 2 层(中等层):普通节点 → 缩小范围
  • 第 3 层(密集层):所有节点 → 精确定位

搜索过程:

  1. 从最顶层的某个节点开始
  2. 在当前层找到最近的邻居
  3. 跳到下一层,继续找最近的邻居
  4. 重复直到最底层 → 找到最近的向量

精度:95-99%(偶尔会漏掉,但几乎不影响结果)。 速度:$O(\log n)$,比暴力搜索快几个数量级。

IVF——另一种常用算法

IVF(Inverted File Index)= 倒排文件索引。

类比:图书馆的分区

  • 把所有书按主题分成 100 个区域
  • 找书时先确定在哪个区域,再在区域内搜索
  • 不需要翻遍整个图书馆

实现:

  1. 训练时:用 K-Means 把向量分成若干个聚类(Voronoi cells)
  2. 搜索时:先找到查询向量最近的几个聚类,只在这些聚类中搜索
  3. 参数 nprobe:搜索多少个聚类(越大越精确,越慢)

量化压缩

PQ(Product Quantization)= 乘积量化。

类比:用“代表色”代替所有颜色

  • 一张图片有 1600 万种颜色
  • 用 256 种“代表色”来近似 → 存储空间大幅减少

实现:

  1. 把 1536 维向量切成多段(如 8 段,每段 192 维)
  2. 每段用一个“码本”(codebook)中的最近码字代替
  3. 原始向量:1536 × 4 bytes = 6 KB
  4. 量化后:8 × 1 byte = 8 bytes → 压缩 768 倍

向量数据库选型深入

数据库索引算法持久化分布式适用场景
FAISSHNSW/IVF/PQ❌ 内存❌ 单机快速原型、小数据
ChromaHNSW✅ 磁盘❌ 单机本地开发、小项目
MilvusHNSW/IVF/DiskANN✅ 分布式生产环境、大数据
QdrantHNSW✅ 磁盘✅ 分布式新兴选择,API 友好
WeaviateHNSW✅ 磁盘✅ 分布式GraphQL 接口
Pinecone托管✅ 云✅ 云无需运维,按量付费

选型建议:

  • 学习和原型:FAISS(最快上手)
  • 小型生产:Chroma 或 Qdrant
  • 大型生产:Milvus(最成熟)或 Qdrant
  • 不想运维:Pinecone

模块七:GraphRAG

什么是GraphRAG?

类比:从"图书馆"到"知识图谱"

  • 传统 RAG = 图书馆找书
    • 把文档切成片段,用向量搜索找最相关的片段
    • 问题:片段之间没有“关系”——不知道 A 片段和 B 片段有什么联系
  • GraphRAG = 知识图谱 + 向量搜索
    • 不仅找到相关片段,还找到片段之间的“关系”
    • 能回答需要“综合多个信息源”的复杂问题

GraphRAG的核心思想

  1. 从文档中提取实体和关系
    • 文档:“张三是百度的 CTO,百度是李彦宏创办的公司”
    • 实体:张三、百度、李彦宏
    • 关系:(张三, 是 CTO, 百度), (百度, 创办者, 李彦宏)
  2. 构建知识图谱
  3. 社区检测
    • 把紧密相关的实体聚成“社区”
    • {张三, 百度, 李彦宏} 是一个社区
  4. 为每个社区生成摘要
    • “百度是一家由李彦宏创办的公司,张三担任 CTO”
  5. 检索时同时搜索向量和图谱
    • 问题:“张三在哪家公司工作?”
    • 向量搜索找到相关文档片段
    • 图谱搜索找到张三 → 百度的关系
    • 综合回答:“张三在百度担任 CTO”
flowchart TD
    A[提取实体与关系] --> B[构建知识图谱]
    B --> C[社区检测]
    C --> D[社区摘要]
    D --> E["检索:向量 + 图谱"]
    E --> F[综合回答]

GraphRAG vs 传统RAG

特性传统 RAGGraphRAG
数据结构文档片段(扁平)知识图谱(结构化)
检索方式向量相似度向量 + 图遍历
多跳推理困难自然支持
全局摘要不支持支持(社区摘要)
适用场景单文档问答多文档、复杂关系推理
复杂度高(需要构建图谱)

GraphRAG的实现

Microsoft 的 GraphRAG 实现:

  1. 使用 LLM 从文档中提取实体和关系
  2. 构建知识图谱
  3. 使用 Leiden 算法进行社区检测
  4. 为每个社区生成 LLM 摘要
  5. 检索时:局部搜索(向量 + 图谱)+ 全局搜索(社区摘要)

适用场景:

  • “这个公司的组织架构是什么?” → 需要从多份文档中综合信息
  • “这些产品的共同竞争对手是谁?” → 需要跨文档推理
  • “总结一下这个领域的最新进展” → 需要全局视角

模块八:Advanced RAG工程实践

为什么需要"高级"RAG?

基础 RAG 的问题:

  1. 检索质量不稳定——有时找到的文档不相关
  2. 生成质量参差——有时回答不准确或“幻觉”
  3. 系统鲁棒性差——输入格式变化就可能出错
  4. 评估困难——不知道效果好不好,怎么改进

Advanced RAG = 在每个环节都做优化。

检索优化策略

  1. 查询改写(Query Rewriting)
    • 原始问题:“这个东西怎么用?”
    • 改写后:“XX 产品的使用方法和操作步骤”
    • 让问题更明确,检索更精准
  2. 查询扩展(Query Expansion)
    • 原始问题:“RAG”
    • 扩展为:[“RAG”, “检索增强生成”, “Retrieval Augmented Generation”]
    • 多角度检索,提高召回率
  3. 假设文档嵌入(HyDE)
    • 先让 LLM 生成一个“假设的回答”
    • 用这个假设回答去检索(而不是用问题检索)
    • 假设回答和真实文档的语义更接近
  4. 上下文压缩(Contextual Compression)
    • 检索到的文档块可能很长,只有部分与问题相关
    • 用 LLM 提取出与问题最相关的部分
    • 减少噪音,提高回答质量

生成优化策略

  1. 提示词优化
    • 明确告诉模型“只基于提供的资料回答”
    • 要求模型“如果不确定就说不知道”
    • 指定输出格式(如“先总结再详细说明”)
  2. 引用追溯
    • 要求模型在回答中标注信息来源
    • “根据文档 A 第 3 段…”、“根据文档 B…”
    • 用户可以验证回答的准确性
  3. 多轮验证
    • 生成回答后,用另一个 LLM 调用检查回答是否与原文一致
    • 类似于“编辑审查”流程

RAG/Agent评估体系

面试必问:“你怎么衡量RAG的效果?”

RAG 评估的三个维度:

  1. 检索质量(Retrieval Quality)
    • Recall@k:前 k 个检索结果中,包含正确答案的比例
    • Precision@k:前 k 个检索结果中,真正相关的比例
    • MRR(Mean Reciprocal Rank):正确答案排第几(越靠前越好)
    • NDCG:考虑排名位置的评估指标
  2. 生成质量(Generation Quality)
    • Faithfulness(忠实度):回答是否基于检索到的文档(没有编造)
    • Relevance(相关性):回答是否和问题相关
    • Correctness(正确性):回答是否正确
    • Completeness(完整性):回答是否完整
  3. 端到端质量(End-to-End)
    • Answer Correctness:最终回答是否正确
    • Answer Relevance:最终回答是否和问题相关

评估工具

  • RAGAS:最流行的 RAG 评估框架
    • 自动生成评估数据集
    • 计算 Faithfulness、Relevance、Context Precision 等指标
    • 代码示例:
from ragas import evaluate
result = evaluate(dataset, metrics=[faithfulness, answer_relevancy])

Agent评估

Agent 评估比 RAG 更复杂,因为 Agent 涉及多步决策:

  1. 任务完成率:Agent 是否完成了用户交给它的任务?
  2. 工具调用准确率:Agent 是否选择了正确的工具?
  3. 参数正确率:Agent 传给工具的参数是否正确?
  4. 步骤效率:Agent 用了多少步完成任务?(越少越好)
  5. 错误恢复:Agent 遇到错误时能否自动修正?

评估方法:

  • 人工评估:找人来判断 Agent 的表现(金标准,但成本高)
  • 自动评估:用另一个 LLM 来评判 Agent 的表现(成本低,但可能不准)
  • 基准测试:用标准化的测试集来评估(可比较,但覆盖有限)