Skip to content

RAG(Retrieval-Augmented Generation,检索增强生成)是当前 AI 应用开发中最实用的技术之一,能够让 LLM 访问外部知识库,解决知识时效性和幻觉问题,无需微调即可实现领域专业化。

1. RAG 核心概念

1.1 什么是 RAG?

RAG 将传统的信息检索系统与生成式 LLM 结合,工作流程如下:

用户问题 → 向量化 → 检索相关文档 → 拼接到 Prompt → LLM 生成答案

"2024年春节是哪天?"

[向量: [0.23, -0.45, ...]]

检索到:"2024年春节是2月10日(农历正月初一)"

Prompt: "根据以下信息回答:【检索内容】\n\n问题:2024年春节是哪天?"

LLM 输出:"2024年春节是2月10日。"

1.2 RAG vs 微调 vs 纯 LLM

方案成本知识更新可解释性适用场景
纯 LLM❌ 知识截止日期固定通用对话、创意写作
RAG✅ 实时更新文档即可高(可追溯来源)企业知识库、客服、文档问答
微调❌ 需重新训练特定风格、行为模式
RAG + 微调最高✅ 分离知识与风格垂直领域专家系统

1.3 RAG 解决的核心问题

  1. 幻觉问题:LLM 常常一本正经地胡说八道。RAG 通过提供事实依据,显著降低幻觉。
  2. 知识时效性:LLM 的训练数据是过去的,比如 GPT-4 可能不知道昨天发生的新闻。RAG 可以连接最新的互联网搜索结果或数据库。
  3. 私有知识访问:LLM 无法访问企业内部文档。RAG 相当于给 LLM 开了个“外挂”,让它能查阅私有资料。

2. RAG 工作流详解

一个完整的 RAG 系统分为**离线(构建知识库)在线(检索生成)**两个阶段。

2.1 离线阶段:知识库构建

  1. 文档加载 (Load):读取 PDF, Word, Markdown, 网页等源文件。
  2. 文本分块 (Chunking):将长文档切分成小的片段(Chunks),例如每块 500 字。这是为了适应 Embedding 模型的窗口限制,并提高检索精准度。
  3. 向量化 (Embedding):使用 Embedding 模型将文本块转化为高维向量(Vector)。
  4. 存储 (Indexing):将向量和原始文本存入向量数据库 (Vector DB)。

2.2 在线阶段:检索生成

  1. 用户提问
  2. 查询向量化:将用户问题转化为向量。
  3. 相似度检索 (Retrieval):在向量库中查找与问题向量最接近的 Top-K 个 Chunks。
  4. Prompt 组装:将检索到的上下文 (Context) 填充到 Prompt 模板中。
  5. LLM 生成 (Generation):LLM 根据 Context 回答问题。

2.3 代码示例:基于 LangChain 的简单 RAG

python
from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI

# 1. 加载
loader = TextLoader('state_of_the_union.txt')
documents = loader.load()

# 2. 分块
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)

# 3. 向量化并存储
embeddings = OpenAIEmbeddings()
db = Chroma.from_documents(texts, embeddings)

# 4. 构建问答链
qa = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=db.as_retriever())

# 5. 提问
query = "What did the president say about Ketanji Brown Jackson"
print(qa.run(query))

3. RAG 核心组件详解

3.1 Embedding 模型

决定了检索的准确度。

  • OpenAI Ada-002 / text-embedding-3:闭源首选,简单稳定,成本低。
  • BGE (BAAI General Embedding):智源开源,中文效果极佳,支持本地部署。
  • M3E (Moka Massive Mixed Embedding):另一款优秀的开源中文 Embedding。

3.2 向量数据库 (Vector Database)

  • Chroma / FAISS:轻量级,适合本地开发或小规模应用。
  • Milvus / Zilliz:云原生分布式,适合海量数据(亿级向量)。
  • Pinecone:SaaS 全托管,易用但收费。
  • Elasticsearch / PostgreSQL (pgvector):利用现有基础设施扩展向量检索能力。

3.3 检索策略优化

单纯的向量检索有时不够准确,比如搜索专有名词。

  • 混合检索 (Hybrid Search):同时结合 向量检索 (Dense)关键词检索 (Sparse / BM25),加权融合结果 (Reciprocal Rank Fusion, RRF)。
  • 重排序 (Reranking):先检索 Top 50 粗排,再用 Rerank 模型(如 BGE-Reranker)精细打分,取 Top 5 给 LLM。这能显著提升准确率。

4. RAG 评估指标

如何知道你的 RAG 系统好不好?需要关注检索和生成两个环节。

指标说明改善方向
召回率 (Recall)相关文档是否被检索到了?优化 Embedding,尝试混合检索
精确率 (Precision)检索到的文档中有多少是有用的?引入 Rerank 重排序
忠实度 (Faithfulness)答案是否忠实于检索到的上下文?优化 Prompt,降低 Temperature
答案相关性 (Answer Relevance)答案是否回答了用户的问题?优化 Query 理解和改写

工具推荐RAGAS (RAG Assessment) 是一个使用 LLM 来自动评估 RAG 系统的框架。


5. 常见问题与优化技巧

问题 1:检索不准(Missed Content)

检索回来的 Chunk 完美避开了答案。

  • 优化分块 (Chunking Strategy)
    • 固定大小:简单,但可能切断语义。
    • 语义分块:按段落、句号切分。
    • 递归分块 (Recursive):LangChain 默认策略,优先按段落切,太长再按句子切。
    • 滑动窗口 (Overlap):保留前后一定的重叠(如 Chunk 500字,Overlap 50字),保持上下文连贯。
  • 查询改写 (Query Rewriting):将用户模糊的问题改写得更具体,或者生成多个相关问题进行“多路召回”。

问题 2:Context 包含干扰信息(Noise)

检索到了文档,但里面包含 irrelevant 内容,干扰了 LLM。

  • Rerank (重排序):把最相关的排在最前面。
  • Context Compression:让 LLM 或小模型提取 Chunk 中的关键句子,压缩后再喂给大模型。

问题 3:回答产生幻觉

即使给了 Context,模型还是胡编。

  • Prompt 约束:明确指令“请根据提供的上下文回答,如果不知道,请说不知道,不要编造。”
  • 引用标注:要求模型在回答时标注引用来源(如 [Source 1]),这能倒逼模型“言之有据”。

6. 实战建议与学习路径

Week 1:基础 RAG 实现

  1. 使用 LangChain + ChromaDB + OpenAI 构建一个简单的文档问答 Demo。
  2. 尝试加载自己的 PDF 或 Markdown 笔记。

Week 2-3:优化与生产化

  1. 分块调优:测试不同 chunk_size (256, 512, 1024) 对效果的影响。
  2. 尝试 Rerank:引入 BGE-Reranker 提升排序质量。
  3. 混合检索:如果某些关键词搜不到,加入 BM25 检索。

最佳实践清单:

  • 小步迭代:从几十个文档开始,跑通流程再扩展。
  • 重视数据清洗:PDF 解析出的乱码、页眉页脚是干扰源,必须要清洗。
  • Prompt 是关键:精心设计的 Prompt 能解决大部分幻觉问题。
  • 持续评估:建立一套测试集(问题 + 标准答案),每次改动后自动跑一遍分。

7. 现代 RAG 实战代码(2026 版)

上面的 LangChain 示例使用了旧版 API。以下是 2026 年生产级 RAG 的推荐写法。

方式一:LangChain v0.3+(推荐框架方案)

python
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import PGVector
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# 1. 初始化组件
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 2. 连接向量数据库(pgvector 生产推荐)
vectorstore = PGVector(
    connection="postgresql://user:pass@localhost:5432/mydb",
    collection_name="knowledge_base",
    embedding_function=embeddings
)
retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 5}
)

# 3. RAG Prompt 模板
rag_prompt = ChatPromptTemplate.from_template("""
基于以下检索到的上下文回答用户问题。
如果上下文中没有相关信息,请直接说"我没有找到相关信息",不要编造。
在关键信息后标注引用来源编号 [1] [2] 等。

上下文:
{context}

问题:{question}
""")

# 4. 构建 RAG Chain(LCEL 链式表达)
def format_docs(docs):
    return "\n\n".join(
        f"[{i+1}] {doc.page_content}" 
        for i, doc in enumerate(docs)
    )

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | rag_prompt
    | llm
    | StrOutputParser()
)

# 5. 使用
answer = rag_chain.invoke("MCP 协议的核心架构是什么?")
print(answer)

# 流式输出
async for chunk in rag_chain.astream("RAG 和微调有什么区别?"):
    print(chunk, end="", flush=True)

方式二:原生 OpenAI(无框架,最轻量)

python
from openai import AsyncOpenAI
import asyncpg
import json

client = AsyncOpenAI()

async def rag_query(question: str) -> str:
    """无框架的原生 RAG 实现"""
    
    # 1. 查询向量化
    embedding_resp = await client.embeddings.create(
        model="text-embedding-3-small",
        input=question
    )
    query_vector = embedding_resp.data[0].embedding
    
    # 2. 向量检索(pgvector)
    conn = await asyncpg.connect("postgresql://user:pass@localhost/mydb")
    rows = await conn.fetch("""
        SELECT content, metadata,
               1 - (embedding <=> $1::vector) AS similarity
        FROM documents
        ORDER BY embedding <=> $1::vector
        LIMIT 5
    """, json.dumps(query_vector))
    await conn.close()
    
    # 3. 组装上下文
    context = "\n\n".join(
        f"[{i+1}] {row['content']}" 
        for i, row in enumerate(rows)
    )
    
    # 4. LLM 生成
    response = await client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": f"""基于以下检索结果回答问题。
如果信息不足,说明"未找到相关信息"。引用格式:[1] [2]

检索结果:
{context}"""},
            {"role": "user", "content": question}
        ],
        temperature=0
    )
    
    return response.choices[0].message.content

# 使用
answer = await rag_query("向量数据库和传统数据库有什么区别?")

8. RAGAS 自动评估实战

python
from ragas import evaluate
from ragas.metrics import (
    faithfulness,       # 答案是否基于检索内容(防幻觉)
    answer_relevancy,   # 答案是否回答了问题
    context_precision,  # 检索结果是否精准
    context_recall      # 是否检索到了关键信息
)

# 准备评估数据集
eval_dataset = {
    "question": [
        "RAG 和微调有什么区别?",
        "如何优化 RAG 的检索质量?"
    ],
    "answer": [
        rag_chain.invoke("RAG 和微调有什么区别?"),
        rag_chain.invoke("如何优化 RAG 的检索质量?")
    ],
    "contexts": [
        [retriever.invoke("RAG 和微调有什么区别?")[0].page_content],
        [retriever.invoke("如何优化 RAG 的检索质量?")[0].page_content]
    ],
    "ground_truth": [
        "RAG 通过检索外部知识增强生成,微调通过训练改变模型行为...",
        "可以通过混合检索、Reranker、查询改写等方式优化..."
    ]
}

result = evaluate(dataset=eval_dataset, metrics=[
    faithfulness, answer_relevancy, context_precision, context_recall
])

print(result)
# {'faithfulness': 0.92, 'answer_relevancy': 0.88, ...}

9. 生产级 RAG 架构

┌─────────────────────────────────────────────────┐
│                  生产级 RAG 系统                  │
├─────────────────────────────────────────────────┤
│                                                  │
│  用户查询                                        │
│     ↓                                            │
│  ┌─────────────┐                                 │
│  │ 查询理解     │ ← 意图分类 + 查询改写           │
│  └──────┬──────┘                                 │
│         ↓                                        │
│  ┌─────────────┐   ┌─────────────┐               │
│  │ 向量检索     │ + │ 关键词检索   │ ← 混合检索    │
│  │ (pgvector)  │   │ (BM25)      │               │
│  └──────┬──────┘   └──────┬──────┘               │
│         └────────┬────────┘                      │
│                  ↓                                │
│  ┌─────────────────────────┐                     │
│  │ Reranker 重排序          │ ← BGE-Reranker     │
│  │ Top 20 → Top 5          │                     │
│  └───────────┬─────────────┘                     │
│              ↓                                    │
│  ┌─────────────────────────┐                     │
│  │ LLM 生成 + 引用标注      │                     │
│  └───────────┬─────────────┘                     │
│              ↓                                    │
│  ┌─────────────────────────┐                     │
│  │ 输出后处理               │ ← 幻觉检测 + 格式化  │
│  └─────────────────────────┘                     │
│                                                  │
├─────────────────────────────────────────────────┤
│  离线管线:文档解析 → 清洗 → 分块 → Embedding → 入库 │
│  评估系统:RAGAS 自动评测 → 质量仪表盘              │
└─────────────────────────────────────────────────┘

坚持是一种品格