mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1504 字
4 分钟
让AI拥有知识:RAG检索增强生成详解
2025-11-24

小张是某公司的IT,老板让他做个AI客服,能回答公司内部问题。

他直接把公司文档喂给ChatGPT,问:“公司的报销流程是什么?”

ChatGPT回答:“根据一般公司的报销流程…”

小张傻眼了:公司有自己的报销流程,ChatGPT根本不知道。

这就是LLM的知识局限:它只知道训练数据里的内容,不知道你的私有信息。

怎么办?RAG(检索增强生成)就是解决方案——让AI像开卷考试一样,能查阅你的知识库。

本文要点#

  • 为什么需要RAG
  • RAG的工作原理(分块→向量化→检索→生成)
  • 向量数据库和Embedding模型选择
  • 六个优化技巧提升效果
  • 实战代码示例
  • RAG vs 微调怎么选
  • GraphRAG简介

一、为什么需要RAG?#

1.1 LLM的三大知识局限#

1. 知识截止日期
- GPT-4的知识截止于2023年10月
- 之后发生的事,它不知道
- Claude的知识也有截止日期
2. 私有知识缺失
- 企业内部文档、产品手册
- 个人笔记、专属资料
- 未公开的研究成果
- 它全不知道
3. 幻觉问题
- 不知道的它可能编
- 编得还挺像真的
- 关键场景风险大

1.2 RAG的解决思路#

不让模型”记住”,而是让它”查询”。

就像考试:

  • 闭卷考(纯LLM):靠记忆,可能忘记或记错
  • 开卷考(RAG):能查阅资料,答案更准确
flowchart TD N0["用户问题"] N1["LLM"] N0 --> N1 N1["LLM"] N2["回答(可能编造)"] N1 --> N2 N0["用户问题"] N3["检索知识库"] N0 --> N3 N3["检索知识库"] N4["LLM+知识"] N3 --> N4 N4["LLM+知识"] N5["准确回答"] N4 --> N5

1.3 RAG的优势#

知识可更新:更新文档即可,无需重新训练
可追溯来源:知道答案来自哪个文档
成本低:不需要微调,只需存储和检索
幻觉减少:基于真实文档回答
适合企业:私有数据不出本地

二、RAG是怎么工作的?#

2.1 整体流程#

┌─────────────────────────────────────────────────────────────┐
│ RAG工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【离线阶段:建知识库】 │
│ │
│ 文档 → 分块 → 向量化 → 存入向量数据库 │
│ │
│ │
│ 【在线阶段:回答问题】 │
│ │
│ 问题 → 向量化 → 检索相关文档 → 问题+文档喂给LLM → 回答 │
│ │
└─────────────────────────────────────────────────────────────┘

2.2 步骤详解#

第一步:文档分块(Chunking)

把长文档切成小块,因为:

  • 模型上下文窗口有限
  • 检索精度更高
  • 更灵活的组合
分块策略:
1. 固定长度分块
- 每块512/1024 Token
- 简单粗暴
- 可能切断语义
2. 语义分块
- 按段落、章节分
- 语义完整
- 块大小不均
3. 滑动窗口分块
- 块之间有重叠(10-20%)
- 减少边界信息丢失
- 推荐方案
最佳实践:
- 块大小:512-1024 Token
- 重叠:10-20%
- 保留元数据(来源、标题、页码)

第二步:向量化(Embedding)

把文本变成向量,让语义相似的文本距离更近:

flowchart TD N0[""猫是宠物""] N1["[0.2, 0.8, 0.1, ...]"] N0 --> N1 N2[""狗是宠物""] N3["[0.25, 0.75, 0.12, ...] ← 和"猫"距离近"] N2 --> N3 N4[""汽车是交通工具""] N5["[0.9, 0.1, 0.8, ...] ← 和前两者距离远"] N4 --> N5

第三步:相似度检索

用户问题变成向量,在数据库里找距离最近的文档块:

用户问题:"公司年假多少天?"
↓ 向量化
↓ 检索(计算余弦相似度)
找到最相似的3个块:
- "入职满一年享有5天年假..."
- "年假最多累计15天..."
- "年假需提前3天申请..."

第四步:生成回答

把问题和找到的文档一起喂给LLM:

根据以下参考信息回答问题:
参考信息:
1. "入职满一年享有5天年假..."
2. "年假最多累计15天..."
3. "年假需提前3天申请..."
问题:公司年假多少天?
答案:根据公司规定,入职满一年享有5天年假,最多累计15天。
如需休假,请提前3天申请。

三、关键组件选择#

3.1 向量数据库对比#

数据库特点部署方式适用场景
Pinecone托管、简单、扩展性好云服务快速上手、生产环境
Chroma轻量、开源、易集成本地/Docker本地开发、小型项目
Milvus分布式、高性能自部署/K8s大规模生产
Qdrant高效、开源、Rust编写自部署/Docker性能敏感场景
Weaviate语义搜索强、GraphQL自部署/云复杂查询场景

选择建议

  • 快速验证:Chroma(本地)
  • 生产环境:Pinecone(托管)或 Milvus(自部署)
  • 性能要求高:Qdrant

3.2 Embedding模型选择#

模型特点维度适用场景
OpenAI text-embedding-3-small性价比高、API简单1536通用、快速集成
OpenAI text-embedding-3-large效果更好3072追求效果
BGE-M3开源、中文好、多语言1024私有部署、中文
Cohere embed-v3效果好、多语言1024企业应用
Jina Embeddings v2长文本支持768长文档场景

选择建议

  • 快速上手:OpenAI text-embedding-3-small
  • 中文私有部署:BGE-M3
  • 追求效果:OpenAI text-embedding-3-large

四、实战代码示例#

4.1 使用LangChain构建RAG#

# 安装依赖
# pip install langchain langchain-openai chromadb
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import TextLoader
# 1. 加载文档
loader = TextLoader("company_policy.txt")
documents = loader.load()
# 2. 分块
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
length_function=len
)
chunks = text_splitter.split_documents(documents)
print(f"分成 {len(chunks)} 个块")
# 3. 向量化并存入数据库
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
# 4. 创建检索器
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 3} # 返回最相似的3个块
)
# 5. 创建问答链
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True
)
# 6. 提问
question = "公司的年假政策是什么?"
result = qa_chain({"query": question})
print("回答:", result["result"])
print("\n来源文档:")
for doc in result["source_documents"]:
print(f"- {doc.metadata.get('source', 'unknown')}")

4.2 使用LlamaIndex构建RAG#

# 安装依赖
# pip install llama-index llama-index-embeddings-openai llama-index-llms-openai
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
# 配置
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.llm = OpenAI(model="gpt-4o-mini")
# 1. 加载文档
documents = SimpleDirectoryReader("./docs").load_data()
# 2. 创建索引
index = VectorStoreIndex.from_documents(documents)
# 3. 创建查询引擎
query_engine = index.as_query_engine(
similarity_top_k=3,
response_mode="compact"
)
# 4. 提问
response = query_engine.query("公司的报销流程是什么?")
print(response)
# 5. 查看来源
for node in response.source_nodes:
print(f"来源: {node.node.metadata}")
print(f"相关度: {node.score}")

4.3 简洁版:使用ChromaDB直接实现#

import chromadb
from openai import OpenAI
# 初始化
client = OpenAI()
chroma_client = chromadb.Client()
collection = chroma_client.create_collection("documents")
def get_embedding(text):
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
# 添加文档
docs = [
"公司年假政策:入职满一年享有5天年假。",
"报销流程:先提交申请,领导审批后财务打款。",
"工作时间:上午9点到下午6点,午休12-1点。"
]
for i, doc in enumerate(docs):
collection.add(
ids=[f"doc_{i}"],
embeddings=[get_embedding(doc)],
documents=[doc]
)
# 检索
query = "年假有多少天?"
results = collection.query(
query_embeddings=[get_embedding(query)],
n_results=2
)
print("相关文档:", results["documents"])
# 生成答案
context = "\n".join(results["documents"][0])
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": f"根据以下信息回答问题:\n{context}"},
{"role": "user", "content": query}
]
)
print("回答:", response.choices[0].message.content)

五、六个优化技巧#

向量检索 + 关键词检索,取长补短:

向量检索:
语义相似性好
可能漏精确匹配(产品名、型号)
关键词检索(BM25):
精确匹配
可能漏语义相关
混合检索 = 两者结合
召回率提升10-30%
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
# 创建两种检索器
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 5
# 混合检索
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.4, 0.6] # BM25占40%,向量占60%
)

5.2 技巧2:重排序(Rerank)#

初检索后用更强的模型重新排序:

flowchart TD N0["初检索"] N1["Top 50 候选"] N0 --> N1 N1["Top 50 候选"] N2["Rerank模型"] N1 --> N2 N2["Rerank模型"] N3["Top 5 最终结果"] N2 --> N3
from cohere import Client
# 使用Cohere Rerank
cohere_client = Client("your-api-key")
def rerank(query, documents, top_n=5):
results = cohere_client.rerank(
query=query,
documents=documents,
model="rerank-multilingual-v2.0",
top_n=top_n
)
return [documents[r.index] for r in results]

5.3 技巧3:Query改写#

把模糊的问题变成精确的:

原问题:"这个怎么办"(太模糊)
改写后:"产品X的错误代码Y如何解决"(更精确)
方法:
1. 让LLM改写问题
2. 提取关键词
3. 补充上下文
def rewrite_query(original_query, chat_history):
prompt = f"""
对话历史:{chat_history}
当前问题:{original_query}
请将问题改写得更加具体和易于检索。
只输出改写后的问题,不要其他内容。
"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content

5.4 技巧4:HyDE(假设文档嵌入)#

用假设答案来检索:

flowchart TD N0["用户问题"] N1["LLM生成假设答案"] N0 --> N1 N1["LLM生成假设答案"] N2["用假设答案检索"] N1 --> N2
def hyde_retrieval(query, retriever, llm):
# 1. 生成假设答案
prompt = f"请为以下问题生成一个可能的答案:\n{query}"
hypo_answer = llm.invoke(prompt)
# 2. 用假设答案检索
results = retriever.get_relevant_documents(hypo_answer)
return results

5.5 技巧5:要求引用来源#

让答案可追溯:

在提示词中加入:
"请在回答中标注信息来源,格式:[来源1]
如果参考信息中没有答案,请直接说'根据现有信息无法回答'"

5.6 技巧6:处理”不知道”#

减少幻觉:

在提示词中加入:
"如果参考信息中没有答案,请直接说'根据现有信息无法回答',不要编造。
答案必须基于参考信息,不要使用你的预训练知识。"

六、GraphRAG简介#

6.1 什么是GraphRAG?#

传统RAG处理文档是扁平的,GraphRAG引入知识图谱来表示实体之间的关系。

flowchart TD N0["文档1"] N1["向量1"] N0 --> N1 N2["文档2"] N3["向量2"] N2 --> N3

6.2 适用场景#

适合GraphRAG:
- 需要理解实体关系的问题
- 多跳推理("A的老板的部门是谁?")
- 复杂知识结构(法律条文引用、产品依赖关系)
不需要GraphRAG:
- 简单问答
- 知识关系简单
- 成本敏感(GraphRAG成本更高)

6.3 实现框架#

主流框架:
- Microsoft GraphRAG:官方开源,功能完整
- LlamaIndex KnowledgeGraphIndex:集成在LlamaIndex中
- Neo4j + LangChain:用图数据库存储

七、RAG vs 微调怎么选?#

维度RAG微调
知识更新实时更新需重新训练
引用来源可追溯无法追溯
成本
风格定制效果有限效果好
部署复杂度简单复杂
响应延迟需检索直接生成

决策建议

flowchart TD N0["大多数场景"] N1["先用RAG"] N0 --> N1

可视化图解#

7.1 RAG架构图#

┌─────────────────────────────────────────────────────────────┐
│ RAG完整架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 离线:知识库构建 │ │
│ │ │ │
│ │ 原始文档 ──→ 分块 ──→ 向量化 ──→ 向量数据库 │ │
│ │ │ │ │ │ │
│ │ Chunker Embedding VectorDB │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 在线:问答流程 │ │
│ │ │ │
│ │ 用户问题 ──→ 向量化 ──→ 相似度检索 ──→ 构建Prompt │ │
│ │ │ │ │ │ │ │
│ │ │ Embedding VectorDB Context │ │
│ │ │ │ │ │
│ │ └──────────────────────────────────┘ │ │
│ │ ↓ │ │
│ │ LLM生成 │ │
│ │ ↓ │ │
│ │ 回答 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

常见问题 FAQ#

Q1: RAG和微调有什么区别?

A:

  • RAG:让模型”查阅”外部知识,知识可更新,成本低
  • 微调:让模型”内化”知识,知识固定,成本高
  • 关键区别:知识存储位置(外部 vs 模型内部)

Q2: 分块大小怎么选?

A:

  • 小块(256-512 Token):检索精确,但上下文可能不完整
  • 大块(1024-2048 Token):上下文完整,但可能引入噪音
  • 推荐:512-1024 Token,配合10-20%重叠

Q3: 召回数量多少合适?

A:

  • 简单问答:3-5个
  • 复杂问题:5-10个
  • 配合Rerank可以从更多候选(20-50个)中精选

Q4: GraphRAG什么时候用?

A:

  • 需要理解实体关系(“A和B是什么关系?”)
  • 多跳推理(“A的上级的部门?”)
  • 一般RAG够用就不用GraphRAG(成本更高)

Q5: 如何评估RAG效果?

A:

  • 准备测试问答集
  • 计算准确率、召回率
  • 检查答案相关性
  • 检查来源正确性

小结#

RAG的核心思想:给AI一本”参考书”,让它查阅后回答

关键步骤:分块 → 向量化 → 检索 → 生成。

优化方向:混合检索、Rerank、Query改写。

RAG是解决AI知识局限最实用的方案,大多数知识类场景首选RAG。


下篇预告#

RAG让AI能查知识,但AI能帮你”做事”吗?比如订票、查天气、发邮件?


参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

让AI拥有知识:RAG检索增强生成详解
https://blog.souloss.com/posts/machine-learning/llm/rag-knowledge-injection/
作者
Souloss
发布于
2025-11-24
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时