从零构建你的第一个RAG应用:完整实战教程(LangChain + ChromaDB)

你是否遇到过这样的问题:ChatGPT回答关于你公司产品的问题时总是胡说八道?或者你希望AI能基于你自己的文档、知识库来回答问题,而不是依赖它训练时的通用知识?这就是RAG(检索增强生成)要解决的核心问题。

本文将带你从零开始,用Python构建一个完整的RAG应用。无需机器学习背景,跟着做就能拥有自己的智能知识库问答系统。

什么是RAG?为什么它如此重要?

RAG(Retrieval-Augmented Generation,检索增强生成)的核心思想非常直观:在让大语言模型回答问题之前,先从你的知识库中检索出最相关的文档片段,然后把这些片段作为上下文提供给模型参考。

🧠 一句话理解RAG

不开卷考试(纯LLM):学生凭记忆答题,可能记错或编造。
开卷考试(RAG):学生可以翻阅资料,根据参考资料作答,准确率大幅提升。

RAG解决了LLM的三大痛点:

  • 知识过时:模型的训练数据有截止日期,RAG可以引入最新信息
  • 幻觉问题:有据可查的参考文档大幅减少编造内容
  • 领域知识:让通用模型变成你的专业领域专家

RAG的核心架构

一个完整的RAG系统包含两个阶段:

⚙️ 阶段一:索引构建(Indexing)— 离线处理

1. 文档加载:读取PDF、Word、网页等各类文档

2. 文本分块:将长文档切分为合适大小的片段(chunk)

3. 向量化:使用Embedding模型将文本转为向量

4. 存入向量数据库:建立高效的相似度检索索引

⚙️ 阶段二:问答生成(Retrieval + Generation)— 在线处理

1. 问题向量化:将用户问题转为向量

2. 相似度检索:在向量库中找到最相关的文档片段

3. 构建Prompt:将检索到的内容和问题组合成提示词

4. LLM生成回答:基于参考文档生成准确回答

实战:用Python构建RAG应用

下面我们用 LangChain + ChromaDB + OpenAI 构建一个可以对本地文档进行智能问答的RAG系统。

Step 1:环境准备

# 创建虚拟环境并安装依赖
python -m venv rag-env
source rag-env/bin/activate  # Windows: rag-env\Scripts\activate

pip install langchain langchain-openai langchain-community
pip install chromadb unstructured python-dotenv

Step 2:加载文档并分块

from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 加载指定目录下的所有文档
loader = DirectoryLoader(
    "./docs",
    glob="**/*.txt",
    loader_cls=TextLoader,
    loader_kwargs={"encoding": "utf-8"}
)
documents = loader.load()
print(f"加载了 {len(documents)} 个文档")

# 文本分块 - chunk_size控制每块大小,chunk_overlap控制重叠
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", "。", ",", " "]
)
chunks = text_splitter.split_documents(documents)
print(f"分块后共 {len(chunks)} 个片段")

💡 分块技巧:chunk_size一般设为256-1024个token。太小会丢失上下文,太大会引入噪音。chunk_overlap确保重要信息不会因为分块位置不好而被截断。中文文档建议用句子边界作为分隔符。

Step 3:向量化并存入ChromaDB

import os
from dotenv import load_dotenv
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

load_dotenv()  # 从 .env 文件加载 OPENAI_API_KEY

# 初始化Embedding模型
embeddings = OpenAIEmbeddings(
    model="text-embedding-3-small"  # 性价比最高的嵌入模型
)

# 创建向量数据库
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"  # 持久化到本地
)

print("✅ 向量数据库构建完成!")

Step 4:构建RAG问答链

from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate

# 加载已有的向量数据库
vectorstore = Chroma(
    persist_directory="./chroma_db",
    embedding_function=OpenAIEmbeddings(model="text-embedding-3-small")
)

# 创建检索器,返回最相关的5个文档片段
retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 5}
)

# 自定义Prompt模板
prompt_template = PromptTemplate(
    template="""基于以下参考资料回答用户的问题。如果参考资料中没有相关信息,请诚实说明不知道。
不要编造信息。

参考资料:
{context}

用户问题:{question}

回答:""",
    input_variables=["context", "question"]
)

# 构建RAG链
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever,
    chain_type_kwargs={"prompt": prompt_template},
    return_source_documents=True
)

Step 5:交互式问答

print("🤖 RAG问答系统已启动!输入 'quit' 退出\n")

while True:
    question = input("你的问题:")
    if question.lower() == 'quit':
        break

    result = qa_chain.invoke({"query": question})

    print(f"\n📝 回答:{result['result']}\n")
    print("📚 参考来源:")
    for i, doc in enumerate(result['source_documents'], 1):
        print(f"  {i}. {doc.metadata.get('source', '未知来源')}")
        print(f"     {doc.page_content[:100]}...")
    print()

进阶优化:提升RAG效果的5个技巧

优化方向 具体方法 效果
混合检索 结合向量检索 + BM25关键词检索 召回率提升20-30%
重排序 用Cross-Encoder对检索结果重排序 精准度显著提升
查询改写 用LLM将用户问题改写为更精准的检索查询 解决口语化查询问题
父文档检索 检索小块,返回其父级大块文档 兼顾精准度与上下文完整性
引用标注 让LLM标注每个论点对应的来源文档 可验证性,提升用户信任度

替代方案:不用LangChain的极简实现

如果你觉得LangChain太重,也可以用最基础的库实现一个极简RAG:

import chromadb
from openai import OpenAI

client = OpenAI()
db = chromadb.PersistentClient(path="./my_db")
collection = db.get_or_create_collection("docs")

# 添加文档(首次运行)
texts = ["文档内容1...", "文档内容2..."]
embeddings = client.embeddings.create(
    input=texts, model="text-embedding-3-small"
).data
collection.add(
    documents=texts,
    embeddings=[e.embedding for e in embeddings],
    ids=[f"doc_{i}" for i in range(len(texts))]
)

# 查询
query = "你的问题"
query_emb = client.embeddings.create(
    input=query, model="text-embedding-3-small"
).data[0].embedding
results = collection.query(query_embeddings=[query_emb], n_results=3)

# 生成回答
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)

📊 性能对比:LangChain方案约150行代码,适合复杂场景;极简方案约30行代码,适合快速原型和学习理解。生产环境推荐使用LlamaIndex或Haystack等更成熟的框架。

RAG vs 微调:什么时候该用哪个?

很多人会问:为什么不用微调(Fine-tuning)让模型学习我的知识?两种方案各有适用场景:

选择指南

用RAG:知识频繁更新、需要引用来源、数据量大(GB-TB级别)

用微调:需要改变模型的风格/格式、领域术语理解、固定知识集

最佳实践:两者结合——先微调让模型理解领域语言,再用RAG引入实时知识

📝 总结

RAG是当前最实用的LLM应用架构之一。它不需要昂贵的模型训练,不改变原始模型参数,却能显著提升回答的准确性和时效性。从个人知识助手到企业级智能客服,RAG的应用场景几乎无处不在。希望这篇教程能帮助你迈出构建自己RAG系统的第一步!

🚀 动手试试吧!

关注 xlx.baby,获取更多 AI 实战教程和技术深度分析。
有问题?欢迎在评论区交流!

→ 访问 xlx.baby

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注