将外部知识注入大语言模型,解决幻觉与知识过时问题,让模型"先检索,后回答"。

核心概念 Link to heading

RAG 的核心思路很直接:在 LLM 生成回答之前,先从外部知识库中检索相关文档,将检索结果作为上下文一并输入模型。

RAG 系统架构

flowchart LR A[文档库] -->|加载| B[文本分块] B -->|Embedding| C[向量数据库] D[用户提问] -->|Embedding| E[相似度检索] C --> E E -->|Top-K 文档| F[拼接 Prompt] F -->|输入| G[LLM] G --> H[回答]

为什么需要 RAG?

  • 知识更新:模型训练数据有截止日期,无法回答之后的事件
  • 领域知识:企业内部文档、专业资料不在训练数据中
  • 减少幻觉:用检索到的真实信息替代模型"编造"内容
  • 可溯源:每条回答都能追溯到具体文档来源

关键组件

文档加载 → 文本分块 → 向量化(Embedding) → 存储(向量数据库)
用户提问 → 向量化 → 相似度检索 → 拼接Prompt → LLM生成回答

搭建最小 RAG 系统 Link to heading

以下使用 LangChain + OpenAI 构建一个可运行的 RAG demo,从本地文本文件开始。

安装依赖 Link to heading

pip install langchain langchain-openai chromadb
  • langchain: 编排框架
  • langchain-openai: OpenAI 模型集成
  • chromadb: 轻量向量数据库(无需外部服务)

文档加载与分块 Link to heading

from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 加载文档
loader = TextLoader("knowledge.txt", encoding="utf-8")
docs = loader.load()

# 分块:每块 500 字符,重叠 50 字符
splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
)
chunks = splitter.split_documents(docs)
print(f"文档被切分为 {len(chunks)} 个块")

分块大小的选择是 trade-off:太小则信息碎片化,太大则检索精度下降。500-1000 字符是常见起点。

向量化与存储 Link to heading

from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

# 初始化 embedding(使用 OpenAI 的 text-embedding-3-small)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# 创建向量存储(自动分块并嵌入)
vector_store = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db",  # 持久化到磁盘
)

这一步将每个文本块转换为高维向量,存入 ChromaDB。后续检索时,用户问题也会被向量化,然后通过余弦相似度找出最接近的文档块。

检索与生成 Link to heading

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# 1. 检索相关文档
query = "LangChain 支持哪些类型的工具集成?"
relevant_docs = vector_store.similarity_search(query, k=3)

# 2. 构建 prompt 模板
prompt = ChatPromptTemplate.from_template("""你是一个技术助手。请根据以下参考资料回答问题。
如果参考资料中没有相关信息,请明确告知用户。

参考资料:
{context}

问题:{question}

回答:""")

# 3. 组装上下文
context = "\n\n".join(doc.page_content for doc in relevant_docs)

# 4. 调用 LLM 生成回答
llm = ChatOpenAI(model="gpt-4o-mini")
chain = prompt | llm | StrOutputParser()
response = chain.invoke({"context": context, "question": query})

print(response)

这就是一个完整的 RAG 流程:检索 → 注入上下文 → 生成

进阶:用 LangChain 简化编排 Link to heading

LangChain 提供了 create_retrieval_chain 将上述步骤封装为一个链:

from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain

# 检索器
retriever = vector_store.as_retriever(search_kwargs={"k": 3})

# 问答链
qa_prompt = ChatPromptTemplate.from_template("""根据参考资料回答问题:

参考资料:
{context}

问题:{input}""")

combine_docs_chain = create_stuff_documents_chain(llm, qa_prompt)
retrieval_chain = create_retrieval_chain(retriever, combine_docs_chain)

# 使用
result = retrieval_chain.invoke({"input": "什么是 embedding?"})
print(result["answer"])
# 可通过 result["context"] 获取引用的文档来源

RAG 的实际挑战 Link to heading

RAG 不是银弹,以下是生产环境中常见的痛点:

挑战说明
分块策略固定大小分块可能切断语义单元,需按段落/标题/表格智能分割
检索精度向量相似度不等于语义相关性,复杂查询可能召回无关文档
多跳推理单个问题需要多份文档组合回答,简单检索+合并不够
混合检索纯向量检索对关键词匹配弱,实践中常结合 BM25 + 向量检索
重排序检索 Top-K 后再用 Cross-Encoder 精排,提升精度但增加延迟

何时不该用 RAG Link to heading

  • 问题已有足够训练数据覆盖(如通用编程问题)
  • 对延迟极度敏感(检索+生成链路比直接调用 LLM 多 100-500ms)
  • 需要确定性输出(RAG 的回答仍受 LLM 随机性影响)

官方链接 Link to heading

[1] https://python.langchain.com/docs/tutorials/rag/

[2] https://www.pinecone.io/learn/series/langchain/rag/

[3] https://www.anthropic.com/research/building-effective-agents

Signature Link to heading

本文由 AI 生成,不保证正确,仅作参考