一、一个让人抓狂的场景

你有没有遇到过这种情况:

你问大模型"公司最新的报销政策是什么",它给出了一个看起来非常专业的回答,连条目标号都排得整整齐齐。你一对照内部文档,发现全是编的——日期不对、金额不对、流程也不对。这就是大模型的"幻觉"问题。

大模型的知识截止于训练数据的时间点。对于企业内部文档、实时信息、专有知识库,它要么不知道,要么凭记忆"瞎编"。更要命的是,它编得还特别像真的——这就是"幻觉"最危险的地方。

RAG(Retrieval-Augmented Generation,检索增强生成)就是为解决这个问题而生的。它的核心思想非常简单:在让大模型回答问题之前,先把相关的背景资料"喂"给它。就像考试允许你带参考资料进考场,你不需要全部背下来,但知道自己去哪里查、怎么查。

用一句话概括RAG:先检索,再生成。让大模型在"翻书回答"和"凭记忆回答"之间,永远选择前者。

二、RAG的核心架构

RAG的架构可以拆成两大部分:离线索引阶段在线查询阶段

2.1 离线索引(Indexing)

这是RAG的"准备工作"。在这个阶段,你把所有知识文档处理好,建成一个可检索的向量索引。

文档 → 切片(Chunking) → 向量化(Embedding) → 存入向量数据库

每一步都很关键:

文档加载:支持PDF、Markdown、Word、网页、代码仓库等多种格式。LangChain提供了几十种文档加载器。

文本切片(Chunking):把长文档切成大小合适的片段。这个步骤直接决定检索质量——切太大了,检索精度低;切太小了,语义不完整。通常512-1024 tokens是一个比较好的范围,但具体怎么切要看文档类型。代码文档可能按函数切,技术文档可能按段落切,法律文档可能按条款切。

向量化(Embedding):把文本片段转换成高维向量。语义相近的文本,向量距离也相近。这一步是整个RAG系统的"翻译官"——把人类语言翻译成机器能理解的向量空间。

向量数据库存储:把向量和原始文本一起存入向量数据库,建立索引,支持高效的相似度检索。

2.2 在线查询(Retrieval & Generation)

这是RAG的"运行时"。

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

用户提问后,系统先把问题也转成向量,在向量数据库中检索最相似的文档片段(通常取Top-K,K=3~10),把这些片段作为上下文拼接到Prompt中,最后交给LLM生成答案。

整个流程看似简单,但每个环节都有大量优化空间——这就是为什么"RAG很简单,但做好RAG很难"。

三、向量数据库怎么选

向量数据库是RAG的核心基础设施。市面上有几十种选择,各有优劣。我按场景帮你梳理一下:

数据库 定位 适合场景 特点
Chroma 轻量级嵌入式 原型开发、本地小项目 零配置,pip install即用,API最友好
FAISS 高性能向量检索库 需要极致检索速度 Meta开源,纯C++实现,但不支持增删改
Milvus 分布式向量数据库 生产环境、海量数据 功能全面,支持混合检索,运维成本较高
Qdrant 高性能向量数据库 中等规模生产 Rust实现,性能优异,API设计优雅
Weaviate 全功能向量数据库 需要图谱+向量混合检索 原生支持GraphQL,有Hybrid Search
Pinecone 托管向量数据库 不想自己运维 全托管,按量付费,国内访问较慢
Elasticsearch 全文检索引擎+向量 已有ES基础设施 8.0+原生支持向量检索,生态成熟

我的推荐路径

  • 刚入门、做Demo → Chroma,5分钟上手
  • 个人项目、几千个文档 → Qdrant 或 FAISS
  • 公司生产环境 → Milvus 或 Elasticsearch

下面是一个用Chroma的极简示例:

import chromadb
from chromadb.utils import embedding_functions

# 初始化Chroma客户端(持久化模式)
client = chromadb.PersistentClient(path="./my_knowledge_base")

# 使用OpenAI Embedding
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
    api_key="your-api-key",
    model_name="text-embedding-3-small"
)

# 创建或获取集合
collection = client.get_or_create_collection(
    name="tech_docs",
    embedding_function=openai_ef
)

# 添加文档
collection.add(
    documents=[
        "Python的GIL(全局解释器锁)限制了多线程并行执行CPU密集型任务",
        "asyncio是Python的异步I/O框架,适用于IO密集型任务",
        "multiprocessing模块可以绕过GIL,实现真正的并行计算"
    ],
    ids=["doc_1", "doc_2", "doc_3"]
)

# 查询
results = collection.query(
    query_texts=["如何在Python中实现并行计算"],
    n_results=2
)

print(results["documents"])
# 输出:['multiprocessing模块可以绕过GIL...', 'Python的GIL...']

四、Embedding模型的选择

Embedding模型的选择直接影响检索质量。这里按语言和场景分类:

通用英文: - OpenAI text-embedding-3-small/large:综合表现最佳,1024/3072维 - Cohere embed-english-v3:企业级,支持多语言 - Voyage AI voyage-2:性价比高,检索场景表现好

中文优化: - BAAI bge-large-zh-v1.5:中文Embedding领域的标杆,开源可本地部署 - text2vec-large-chinese:老牌中文Embedding,社区活跃 - m3e-base/large:MokaAI出品,对中文短文本检索优化得很好 - 讯飞/阿里/百度也都有中文Embedding API

代码场景: - code-embedding系列:专门针对代码语义优化的Embedding - OpenAI text-embedding-3-large:256维下对代码也有不错的表现

选择建议

# 场景1:中文技术文档RAG,追求效果 → bge-large-zh-v1.5 本地部署
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("BAAI/bge-large-zh-v1.5")
embeddings = model.encode(["这是一段中文技术文档"], normalize_embeddings=True)

# 场景2:快速开发,不想管基础设施 → OpenAI API
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
    model="text-embedding-3-small",
    input="你的文本内容"
)
embeddings = response.data[0].embedding

# 场景3:中英文混合,开源方案 → bge-m3(多语言版本)
model = SentenceTransformer("BAAI/bge-m3")

五、实战:用LangChain搭建完整的RAG系统

光说不练假把式。我们用LangChain + Chroma + OpenAI搭建一个完整的技术文档问答系统。

5.1 安装依赖

pip install langchain langchain-openai langchain-chroma chromadb pypdf unstructured

5.2 完整代码

from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain_community.document_loaders import PyPDFLoader, TextLoader, DirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
import os

os.environ["OPENAI_API_KEY"] = "your-api-key"

# ==================== 步骤1:加载文档 ====================
# 支持多种格式
loaders = {
    ".pdf": PyPDFLoader,
    ".txt": TextLoader,
    ".md": TextLoader,
}

documents = []
# 加载docs目录下所有支持的文档
for ext, loader_class in loaders.items():
    loader = DirectoryLoader(
        "./docs/",
        glob=f"**/*{ext}",
        loader_cls=loader_class,
        show_progress=True
    )
    documents.extend(loader.load())

print(f"加载了 {len(documents)} 个文档")

# ==================== 步骤2:文本切片 ====================
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,          # 每个切片最多800字符
    chunk_overlap=150,       # 相邻切片重叠150字符,保证语义连贯
    separators=["\n\n", "\n", "。", ",", " ", ""],  # 中文友好的分隔符
    length_function=len,
)

chunks = text_splitter.split_documents(documents)
print(f"切分为 {len(chunks)} 个文本块")

# ==================== 步骤3:向量化并存储 ====================
embeddings = OpenAIEmbeddings(
    model="text-embedding-3-small",
    dimensions=512  # 可以指定更低维度来节省空间
)

vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db",
    collection_name="tech_knowledge"
)

# ==================== 步骤4:构建RAG链 ====================
# 自定义Prompt模板
prompt_template = """你是一个技术文档助手。请严格基于以下提供的上下文来回答问题。
如果上下文中没有相关信息,请如实说"我目前的知识库中没有找到相关信息",
不要编造任何内容。

上下文:
{context}

问题:{question}

请给出准确、详细的回答:"""

PROMPT = PromptTemplate(
    template=prompt_template,
    input_variables=["context", "question"]
)

# 创建检索器
retriever = vectorstore.as_retriever(
    search_type="mmr",         # 使用MMR检索,保证结果多样性
    search_kwargs={"k": 5, "fetch_k": 20}
)

# 创建RAG问答链
llm = ChatOpenAI(model="gpt-4o", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever,
    chain_type_kwargs={"prompt": PROMPT},
    return_source_documents=True  # 返回引用来源
)

# ==================== 步骤5:问答 ====================
result = qa_chain.invoke({"query": "Python中GIL是什么?如何绕过它?"})

print("回答:", result["result"])
print("\n引用来源:")
for i, doc in enumerate(result["source_documents"], 1):
    print(f"[{i}] {doc.page_content[:100]}...")

5.3 代码解读

这个示例涵盖了RAG从零到一的完整流程,有几个关键设计值得注意:

分隔符对中文友好separators=["\n\n", "\n", "。", ",", " ", ""]。LangChain默认的分隔符是针对英文设计的(按空格和换行),中文没有空格分词,所以必须加入中文标点。这两个标点的加入,让切片在中文文档中的语义完整性大幅提升。

chunk_overlap很重要:相邻切片之间保留150字符的重叠。这解决了"关键信息刚好被切在边界上"的问题。比如一段话的前半句在chunk_A的末尾,后半句在chunk_B的开头——没有overlap的话,这段语义就丢失了。

MMR检索优于纯相似度检索search_type="mmr"(Maximal Marginal Relevance)在保证相关性的同时,尽量选择内容不重复的文档。如果你用纯相似度检索,可能会返回5条几乎一样的内容,浪费了有限的上下文窗口。

必须加"不要编造"的指令:Prompt中明确要求"如果上下文中没有相关信息,请不要编造"。这看似简单的一句话,能显著减少幻觉——因为用户可能问的是知识库覆盖范围之外的问题。

六、高级RAG策略:从"能用"到"好用"

基础RAG跑起来不难,但要做到生产级别,还需要一系列优化手段。我把这些策略分为三个层次:

6.1 检索增强层

HyDE(Hypothetical Document Embeddings):先让LLM根据问题生成一个"假设性答案",再用这个假设答案去检索。这背后的逻辑是:用户问题和文档片段可能存在"语言风格差"——用户问得像口语,但文档写得像教科书。假设答案和文档是同一种语言风格,检索命中率更高。

# HyDE的核心思路
from langchain.chains import HypotheticalDocumentEmbedder
from langchain_openai import ChatOpenAI

hyde_embeddings = HypotheticalDocumentEmbedder.from_llm(
    llm=ChatOpenAI(model="gpt-4o-mini"),
    base_embeddings=OpenAIEmbeddings(),
    prompt_key="web_search"  # 让LLM生成网页风格的假设答案
)

Multi-Query Retrieval:把用户的一个问题,改写成3-5个不同角度的查询,每个查询独立检索,然后合并去重。这解决了"用户问得不够好"的问题。

from langchain.retrievers.multi_query import MultiQueryRetriever

retriever = MultiQueryRetriever.from_llm(
    retriever=vectorstore.as_retriever(),
    llm=ChatOpenAI(model="gpt-4o-mini"),
    num_queries=3  # 生成3个变体查询
)

Self-Query Retrieval:允许在语义检索的同时加上元数据过滤。比如"查询2024年之后发布的技术文档中关于Kubernetes的内容"——"关于Kubernetes"走语义检索,"2024年之后"走元数据过滤。

6.2 上下文优化层

Re-Ranking(重排序):初检索召回Top-20个文档片段,然后用更精准的重排序模型(如Cohere Rerank、bge-reranker)重新打分,只保留Top-5送入LLM。效果提升显著,因为向量相似度≠语义相关性。

from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank

compressor = CohereRerank(top_n=5)
compression_retriever = ContextualCompressionRetriever(
    base_retriever=retriever,
    base_compressor=compressor
)

上下文压缩:对于召回的每个长文档片段,只提取与问题相关的部分,去掉冗余内容。LangChain提供了LLMChainExtractor来做这件事。

6.3 索引优化层

父子文档(Parent Document Retriever):这是我最推荐的生产环境策略。思路是:索引时用小块做向量检索(保证精度),返回时给LLM用大块做上下文(保证完整性)。

索引阶段:文档 → 切大块(2000字符)→ 再切小块(400字符)→ 向量化小块
检索阶段:问题 → 检索小块 → 找到小块对应的大块 → 返回大块给LLM
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore

retriever = ParentDocumentRetriever(
    vectorstore=Chroma(embedding_function=embeddings),
    docstore=InMemoryStore(),
    child_splitter=RecursiveCharacterTextSplitter(chunk_size=400),
    parent_splitter=RecursiveCharacterTextSplitter(chunk_size=2000),
    search_kwargs={"k": 5}
)

七、RAG在AI Coding中的实战应用

回到我们关注的AI Coding领域,RAG的应用场景非常多:

代码库问答:把整个代码仓库索引进向量数据库,开发者可以用自然语言提问:"这个项目的认证流程是怎么实现的?""错误处理在哪些文件中定义的?"系统自动检索相关代码片段,结合LLM给出分析。

API文档智能助手:把API文档、变更日志、常见问题索引后,开发者输入"v2版本的user接口和v1有什么变化?"系统检索相关文档段落,生成准确的对比回答。

Bug定位辅助:把历史Issue、错误日志索引起来,当遇到新Bug时,用错误信息做检索,找到相似的历史Issue及其解决方案。

代码评审增强:把团队编码规范、最佳实践文档索引后,在做Code Review时自动检索相关规范,提示不合规的代码模式。

Onboarding知识库:新员工入职时最头疼的就是翻文档。把团队Wiki、设计文档、技术决策记录全部索引,新人直接用自然语言提问,RAG给出精准答案。

一个实际的代码库RAG项目结构通常是这样:

codebase-rag/
├── ingest.py          # 文档加载、切片、向量化
├── retriever.py       # 检索逻辑
├── qa_chain.py        # LLM问答链
├── ui.py              # Streamlit/Gradio前端
├── config.yaml        # 配置(模型、数据库参数)
└── chroma_db/         # 向量数据库持久化目录

八、RAG的常见坑与解决之道

做RAG项目时,这几个坑几乎人人都会踩:

坑1:切片策略不当

现象:检索结果看起来相关,但给LLM的上下文缺胳膊少腿。

解决:根据文档类型定制切片策略。代码文档按函数/类切,法律文档按条款切,技术文章按## 标题切。不要一刀切用固定长度。

# 代码文档:用Language文本分割器
from langchain_text_splitters import Language, RecursiveCharacterTextSplitter

python_splitter = RecursiveCharacterTextSplitter.from_language(
    language=Language.PYTHON, chunk_size=500, chunk_overlap=50
)

# Markdown文档:按标题层级切
from langchain_text_splitters import MarkdownHeaderTextSplitter

md_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=[
        ("#", "h1"),
        ("##", "h2"),
        ("###", "h3"),
    ]
)

坑2:检索精度不够

现象:用户问了一个明确的问题,返回的文档片段却牛头不对马嘴。

解决:三步走:1)检查Embedding模型是否适配你的数据语言和领域;2)加入Re-Ranking环节;3)尝试HyDE策略。

坑3:上下文窗口溢出

现象:召回了5个文档片段,总长度超过了模型的上下文窗口限制。

解决:控制Top-K的值(3-5通常够用),使用上下文压缩,或者用Map-Reduce/Refine等链类型分批处理。

坑4:RAG和Fine-tuning分不清

现象:团队讨论"要不要给模型做微调"和"要不要上RAG"时,把它俩当成二选一。

解决:RAG和Fine-tuning不是互斥的,它们是互补的: - RAG擅长知识注入——把外部知识实时接入模型 - Fine-tuning擅长行为塑造——改变模型的输出风格、格式、推理模式 - 成熟方案往往是 RAG + Fine-tuning 的组合

坑5:忽略了评估

现象:RAG系统上线后,没人知道它的回答质量到底怎么样。

解决:建立评估体系。用RAGAS这类框架对检索质量和生成质量做量化评估。

from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision

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

九、总结

RAG看起来简单——检索+生成,两个步骤。但要做好,需要理解其中每个环节的trade-off:

  • 切片大小在精度和完整性之间权衡
  • 检索数量在相关性和上下文限制之间权衡
  • Embedding维度在效果和效率之间权衡
  • 检索策略在相似度和多样性之间权衡

在AI Coding时代,RAG是一个"基础能力"级别的基础设施。无论你是做内部工具、开发者平台、还是技术产品,RAG都可能是你第一个需要搭建的AI组件。

最后用三句话记住RAG:

  1. RAG的本质是"先查资料再回答",让大模型从凭记忆变成靠证据。
  2. RAG的质量上限由你的数据质量决定——垃圾进,垃圾出。
  3. RAG不是银弹,它是AI系统中的一个组件,需要和Prompt工程、Agent、Fine-tuning等技术配合使用。

把RAG搭好,你的AI应用就从一个"会聊天的机器人"变成了"懂你业务的智能助手"。


延伸阅读与资源

  • LangChain RAG教程:https://python.langchain.com/docs/tutorials/rag/
  • LlamaIndex官方文档:https://docs.llamaindex.ai/
  • Chroma向量数据库:https://docs.trychroma.com/
  • RAGAS评估框架:https://docs.ragas.io/
  • BGE Embedding模型:https://huggingface.co/BAAI/bge-large-zh-v1.5
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐