将外部知识注入大语言模型,解决幻觉与知识过时问题,让模型"先检索,后回答"。
核心概念 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 生成,不保证正确,仅作参考