10-大模型智能体开发工程师:RAG检索增强生成

系列文章导航:AI系列文章导航目录-持续更新中

第10课:RAG检索增强生成

📝 本文摘要:本文详解RAG技术------解决LLM知识局限(训练数据截止、私有数据未知、专业领域不精)的核心方案,梳理RAG流程(离线索引:文档→分块→嵌入→向量数据库;在线查询:嵌入→向量检索→Top-K→拼接到Prompt→LLM生成),详解关键技术(分块策略、嵌入模型选择、向量数据库选型、混合检索、重排序),提供最简RAG和生产级RAG架构代码,以及常见问题优化。
RAG是大模型应用最核心的技术之一。它解决了一个根本问题:模型不知道你公司的数据。RAG让模型"查资料"后再回答,大幅减少幻觉。


一、为什么需要RAG

1.1 LLM的知识局限

一句话理解:LLM只知道它训练时看过的内容,不知道你公司的数据、今天的新闻、你的私有文档。RAG让模型先"查资料"再回答,就像你开卷考试一样。

复制代码
问题1: 训练数据截止
  模型的知识停留在训练数据的时间点
  "今天的新闻" → 不知道
  类比: 就像一个2024年毕业的学生,不知道2025年发生了什么

问题2: 私有数据
  模型不知道你公司的内部文档、产品手册、客户数据
  "我们的退货政策是什么" → 不知道
  类比: 就像一个新员工,还没看过公司内部文档

问题3: 专业领域
  模型在特定领域的知识不够精确
  "这个医疗器械的型号X的使用规范" → 可能编造(幻觉)
  类比: 就像一个通才,什么都知道一点但不够精

RAG的解法: 让模型先"查资料",再基于资料回答
  类比: 开卷考试 ------ 不需要模型记住所有知识,只需要能找到并理解相关资料

1.2 RAG vs 微调

维度 RAG 微调
知识更新 实时更新检索库 需要重新训练
成本 低(只改检索) 高(需要GPU训练)
可解释性 高(可以展示来源) 低(知识融入权重)
适用场景 事实性问答、文档查询 风格适配、领域适配
数据量 无限制 受训练预算限制

结论:90%的"让模型了解更多知识"的需求,用RAG而非微调。


二、RAG的核心流程

复制代码
              ┌──────────────────────────────────┐
              │          离线索引阶段              │
              │                                  │
              │  文档 → 分块 → 嵌入 → 向量数据库  │
              └──────────────────────────────────┘
                      
              ┌──────────────────────────────────┐
              │          在线查询阶段              │
              │                                  │
用户问题 → 嵌入 → 向量检索 → Top-K文档 → 拼接到Prompt → LLM → 回答

2.1 详细步骤

类比理解:RAG就像图书馆的工作流程:

  • 离线阶段 = 图书馆建设(买书、编目录、上架)

  • 在线阶段 = 读者查书(提问→查目录→找到书→阅读→回答)

    === 离线阶段(只做一次,或文档更新时重做) ===

    Step 1: 文档加载
    PDF/Word/HTML/Markdown → 纯文本
    类比: 把各种格式的书籍都拆封取出内容

    Step 2: 文本分块 (Chunking)
    长文本 → 多个小块(每块约200-500字)
    类比: 把一本书切成一页一页的卡片
    为什么要分块: 因为检索时我们只需要找到最相关的那几块,而不是整篇文档

    Step 3: 嵌入 (Embedding)
    每个文本块 → 向量 (如1024维的数字数组)
    类比: 给每张卡片贴上一个"语义指纹",意思相近的卡片指纹也相近
    模型: BGE-M3, GTE, text-embedding-3-small

    Step 4: 存储
    向量 + 原文 → 向量数据库
    类比: 把卡片和指纹一起存入图书馆系统

    === 在线阶段(每次用户提问时执行) ===

    Step 5: 查询嵌入
    用户问题 → 向量(用同一个嵌入模型)
    类比: 把用户的问题也转成"指纹"

    Step 6: 向量检索
    查询向量 vs 数据库所有向量 → 余弦相似度 → Top-K最相关
    类比: 用问题的指纹去图书馆找指纹最像的卡片

    Step 7: 上下文构建
    Top-K文档块 + 用户问题 → 完整Prompt
    类比: 把找到的卡片和问题一起交给"回答专家"

    Step 8: LLM生成
    基于检索到的上下文回答问题
    类比: 专家阅读卡片后回答你的问题


三、关键技术详解

3.1 文本分块(Chunking)

复制代码
方法1: 固定大小分块
  chunk_size=500, overlap=50
  优点: 简单
  缺点: 可能切断语义完整性

方法2: 按段落/章节分块
  按Markdown标题、段落边界分
  优点: 语义完整
  缺点: 块大小不均匀

方法3: 语义分块(Semantic Chunking,基于语义的文本分块)
  用嵌入计算相邻句子的相似度
  相似度骤降处 → 分块边界
  优点: 语义最完整
  缺点: 计算成本高

方法4: 递归分块(LangChain默认)
  尝试按["\n\n", "\n", "。", " "]依次分割
  直到块大小合适
  优点: 兼顾语义和大小
python 复制代码
# LangChain递归分块示例
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", "。", "!", "?", ".", " ", ""]
)

chunks = splitter.split_text(long_text)

3.2 嵌入模型(Embedding)

复制代码
嵌入模型把文本映射为向量,语义相似的文本 → 相似的向量

"机器学习" → [0.12, -0.34, 0.56, ...]  (1024维)
"深度学习" → [0.11, -0.32, 0.58, ...]  ← 很接近!
"红烧肉"   → [-0.45, 0.23, -0.12, ...] ← 很远

常用模型:
┌─────────────────────┬──────────┬──────────┐
│ 模型                │ 维度     │ 特点     │
├─────────────────────┼──────────┼──────────┤
│ BGE-M3              │ 1024     │ 中文最强 │
│ GTE-Qwen2           │ 768/1024 │ 阿里出品 │
│ text-embedding-3    │ 1536     │ OpenAI   │
│ Cohere embed v3     │ 1024     │ 多语言   │
└─────────────────────┴──────────┴──────────┘

3.3 向量数据库

复制代码
┌──────────────────┬──────────────┬─────────────────────┐
│ 数据库           │ 类型         │ 特点                │
├──────────────────┼──────────────┼─────────────────────┤
│ Chroma           │ 嵌入式       │ 最简单,开发测试用   │
│ FAISS            │ 库           │ Meta出品,纯内存     │
│ Milvus           │ 分布式       │ 生产级,云原生       │
│ Qdrant           │ 分布式       │ Rust写,性能好      │
│ pgvector         │ PG扩展       │ 已有PG的直接用      │
│ Weaviate         │ 分布式       │ 支持混合搜索        │
└──────────────────┴──────────────┴─────────────────────┘

3.4 检索策略

基础: 向量相似度检索
python 复制代码
# 余弦相似度
similarity = cos(query_vector, doc_vector)
# 范围: [-1, 1],越大越相似
进阶: 混合检索(Hybrid Search)
复制代码
向量检索: 找语义相似的
关键词检索(BM25, Best Matching 25,经典信息检索算法): 找精确匹配的

混合 = 向量检索结果 ∪ 关键词检索结果 → 重排序 → Top-K

为什么需要混合:
  "找RFC 2616文档" → 关键词检索更准(精确匹配"RFC 2616")
  "HTTP协议的设计理念" → 向量检索更准(语义匹配)
进阶: 重排序(Reranking)
复制代码
初始检索: Top-20 (宽松,宁可多找,K=20即返回前20个最相似结果)
    ↓
Reranker模型: 对20个结果精细打分排序
    ↓
最终结果: Top-5 (精准,K=5即只取前5个最相关结果)

常用Reranker: BGE-Reranker, Cohere Rerank, Cross-Encoder

四、RAG实战代码

4.1 最简RAG

python 复制代码
from openai import OpenAI
import chromadb
from chromadb.utils import embedding_functions

# 1. 初始化
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
chroma_client = chromadb.Client()

# 使用Ollama的嵌入模型
embed_fn = embedding_functions.OllamaEmbeddingFunction(
    url="http://localhost:11434",
    model_name="bge-m3"
)

# 2. 创建集合
collection = chroma_client.get_or_create_collection(
    name="docs",
    embedding_function=embed_fn
)

# 3. 添加文档
docs = [
    "Python是一种高级编程语言,由Guido van Rossum于1991年创建。",
    "Java是由James Gosling在1995年发布的面向对象编程语言。",
    "Rust是一种系统编程语言,注重安全性和性能,由Mozilla研发。"
]
collection.add(
    documents=docs,
    ids=["doc1", "doc2", "doc3"]
)

# 4. 查询
query = "谁创建了Python?"
results = collection.query(query_texts=[query], n_results=2)

# 5. 构建Prompt并生成回答
context = "\n".join(results["documents"][0])
prompt = f"""基于以下参考资料回答问题。如果资料中没有答案,请说"我不知道"。

参考资料:
{context}

问题:{query}"""

response = client.chat.completions.create(
    model="qwen2.5:7b",
    messages=[{"role": "user", "content": prompt}],
    temperature=0.0
)
print(response.choices[0].message.content)
# 预期: "Python由Guido van Rossum于1991年创建。"

4.2 生产级RAG架构

复制代码
用户查询
  ↓
Query改写/扩展
  ↓
┌──────────┐
│ 向量检索  │ ← 嵌入模型 + 向量数据库
│ BM25检索  │ ← 全文检索引擎
└────┬─────┘
     ↓ 结果合并
  Reranker重排序
     ↓
  Top-K文档
     ↓
  上下文压缩/过滤
     ↓
  Prompt构建
     ↓
  LLM生成
     ↓
  答案 + 来源引用

五、RAG的常见问题与优化

5.1 检索不到相关文档

复制代码
原因:
  - 分块太大,关键信息被稀释
  - 嵌入模型不适合你的领域
  - 用户问题表述与文档差异大

优化:
  - 查询扩展: 把用户问题改写为多个查询
  - 假设性文档嵌入(HyDE, Hypothetical Document Embeddings): 先让LLM生成"假设答案",用假设答案去检索
  - 调整分块大小: 256-512 tokens通常效果最好

5.2 检索到但没用好

复制代码
原因:
  - 塞入太多无关文档
  - 文档排序不对
  - Prompt没引导模型关注检索结果

优化:
  - 用Reranker精排
  - Prompt中强调"基于参考资料回答"
  - 要求模型引用来源

5.3 幻觉仍然存在

复制代码
原因:
  - 模型忽略检索结果,用自身知识回答
  - 检索结果不完整

优化:
  - Prompt约束: "只能基于参考资料回答,不要使用外部知识"
  - 引用机制: 要求每个论断标注出自哪个文档
  - 置信度评估: 让模型评估自己的答案可靠性

📝 作业

作业1:构建一个简单的文档问答系统

  1. 准备3-5段技术文档(可以复制自网络)
  2. 用ChromaDB构建向量索引
  3. 实现查询功能,返回答案+来源

参考答案

python 复制代码
# save as: rag_demo.py
import chromadb
from chromadb.utils import embedding_functions
from openai import OpenAI

# 初始化
llm_client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
chroma = chromadb.Client()

# 用Ollama嵌入,如果Ollama没有bge-m3,用默认的
try:
    embed_fn = embedding_functions.OllamaEmbeddingFunction(
        url="http://localhost:11434",
        model_name="nomic-embed-text"  # 轻量嵌入模型,先ollama pull nomic-embed-text
    )
except:
    embed_fn = embedding_functions.DefaultEmbeddingFunction()

# 知识库文档
documents = [
    {
        "id": "k8s_1",
        "text": "Kubernetes(K8s)是Google开源的容器编排系统。它于2014年首次发布,"
                "基于Google内部运行了15年的Borg系统设计。K8s的核心组件包括API Server、"
                "etcd、Scheduler、Controller Manager和Kubelet。",
        "source": "K8s入门文档"
    },
    {
        "id": "docker_1", 
        "text": "Docker是一个开源的容器化平台,由Solomon Hykes于2013年创建。"
                "Docker使用Linux容器的技术来实现应用隔离,核心概念包括镜像(Image)、"
                "容器(Container)和仓库(Registry)。",
        "source": "Docker入门文档"
    },
    {
        "id": "go_1",
        "text": "Go语言(Golang)由Google的Robert Griesemer、Rob Pike和Ken Thompson"
                "于2007年开始设计,2012年发布1.0版本。Go语言的设计目标是简单、高效、"
                "并发友好,内置goroutine和channel支持。Docker和Kubernetes都是用Go语言编写的。",
        "source": "Go语言教程"
    },
    {
        "id": "prometheus_1",
        "text": "Prometheus是SoundCloud开源的监控和告警系统,于2016年加入CNCF。"
                "它采用拉取式数据采集、多维数据模型和PromQL查询语言。"
                "Prometheus与Grafana是云原生监控的标准组合。",
        "source": "Prometheus文档"
    }
]

# 创建集合并添加文档
collection = chroma.get_or_create_collection("tech_docs", embedding_function=embed_fn)
collection.add(
    documents=[d["text"] for d in documents],
    ids=[d["id"] for d in documents],
    metadatas=[{"source": d["source"]} for d in documents]
)

def ask(question: str) -> str:
    # 检索
    results = collection.query(query_texts=[question], n_results=2)
    context_parts = []
    for doc, meta in zip(results["documents"][0], results["metadatas"][0]):
        context_parts.append(f"[来源: {meta['source']}]\n{doc}")
    context = "\n\n".join(context_parts)
    
    # 生成
    prompt = f"""基于以下参考资料回答问题。请标注信息来源。如果资料中没有答案,请说"根据现有资料无法回答"。

参考资料:
{context}

问题:{question}"""
    
    response = llm_client.chat.completions.create(
        model="qwen2.5:7b",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.0
    )
    return response.choices[0].message.content

# 测试
print(ask("Docker是谁创建的?"))
print("---")
print(ask("K8s和Docker有什么关系?"))

🎉 Part 2完成! 你已经掌握了Prompt、Context、结构化输出、RAG这些应用开发核心技能。

下一篇文章见:AI系列文章导航目录-持续更新中

相关推荐
guyoung2 小时前
BoxAgnts介绍(7)——OpenAI-API与Anthropic-API
openai·agent·ai编程
FIT2CLOUD飞致云2 小时前
支持AI网关和Skills Hub,1Panel企业版正式发布
ai·开源·1panel
追光者♂2 小时前
【测评系列5】CSDN AI数字营销实测体验官——Claude 大模型深度评测:从参数解析到实战边界
人工智能·ai·大模型·大语言模型·claude·模型幻觉·架构参数
swipe3 小时前
DeepAgents middleware 工程实战:把复杂 Agent 的运行时基建交给可组合中间件
前端·面试·llm
zhangshuang-peta3 小时前
MCP 如何重新定义 Skill:从“能力函数”变成“可治理行为”
人工智能·ai·ai agent·mcp·peta
阿里云大数据AI技术3 小时前
基于阿里云 DataWorks Data Agent 进行大模型热度分析
人工智能·agent·nvidia
VIP_CQCRE3 小时前
面部静态活体检测(高精度版)API集成指南
ai
卡次卡次14 小时前
vibecoding起步之注意点:如何做一个聊天机器人
python·ai
weixin_468466854 小时前
图像连通域分析新手实战指南
图像处理·人工智能·深度学习·ai·机器视觉·连通域