跳转到正文
技术教程·

RAG 教程:从零搭建检索增强生成系统

RAG 教程:从零开始搭建检索增强生成系统。本文使用 LangChain 和 Chroma 向量数据库,手把手教你实现一个基于私有知识库的 AI 问答系统。

这篇教程适合谁

你会 Python,调过 LLM API,现在想让模型基于你自己的文档回答问题——而不是靠模型的通用知识瞎猜。RAG(检索增强生成)就是干这个的。

不适合:零编程基础。不想写代码的,用 Dify 可以零代码搭 RAG。 最终产物:一个能加载文档、分块、向量化、检索、生成答案的完整 RAG 系统。

环境假设

  • Python 3.9+
  • OpenAI API Key(用于 Embedding 和生成,换成其他模型改两行代码)
  • 以当前常见版本为准

RAG 的完整流程

文档 → 分块 → 向量化(Embedding)→ 存入向量数据库

用户问题 → 向量化 → 相似度检索 → 取出相关片段

相关片段 + 用户问题 → LLM → 生成答案

核心思想:先从你的文档里找到最相关的几段,和问题一起喂给 LLM,让它基于这些内容回答。

安装依赖

pip install langchain langchain-openai chromadb tiktoken

import os

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

文档加载与分块

from langchain_community.document_loaders import TextLoader, DirectoryLoader

from langchain.text_splitter import RecursiveCharacterTextSplitter

# 加载单个文件

loader = TextLoader("knowledge.txt", encoding="utf-8")

documents = loader.load()

# 或加载整个目录

loader = DirectoryLoader("./docs", glob="*/.txt", loader_cls=TextLoader)

documents = loader.load()

# 分块

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 太大,检索出来的片段不精准;太小,片段没有完整语义。中文文档建议 300-800 字符。chunk_overlap 是防止一句话被切成两半,设为 chunk_size 的 10%-20%。 separators 里的中文标点很重要——不加的话,RecursiveCharacterTextSplitter 按英文标点切,中文文档会被切成奇怪的片段。

向量化与存储

from langchain_openai import OpenAIEmbeddings

from langchain_community.vectorstores import Chroma

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

vectorstore = Chroma.from_documents(

documents=chunks,

embedding=embeddings,

persist_directory="./chroma_db" # 持久化,下次不用重新索引

)

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

Embedding 模型怎么选:OpenAI 的 text-embedding-3-small 性价比高,但对中文的语义理解不如专门的中文 Embedding 模型(如 BGE 系列)。如果你的文档全是中文,可以考虑用 HuggingFace 上的 BGE 模型。

测试检索

results = vectorstore.similarity_search("什么是 RAG?", k=3)

for i, doc in enumerate(results):

print(f"结果 {i+1}: {doc.page_content[:100]}...")

检索结果不好怎么办:先检查分块质量——手动看切出来的片段是否语义完整。如果分块没问题但检索不精准,换 Embedding 模型或调 k 值。

构建 RAG Chain

from langchain_openai import ChatOpenAI

from langchain.prompts import ChatPromptTemplate

from langchain.schema.runnable import RunnablePassthrough

from langchain.schema.output_parser import StrOutputParser

retriever = vectorstore.as_retriever(

search_type="similarity",

search_kwargs={"k": 3}

)

template = """你是一个知识库助手。请根据以下参考内容回答用户的问题。

如果参考内容中没有相关信息,请如实告知用户,不要编造答案。

参考内容:

{context}

用户问题:{question}

回答:"""

prompt = ChatPromptTemplate.from_template(template)

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

rag_chain = (

{"context": retriever, "question": RunnablePassthrough()}

| prompt

| llm

| StrOutputParser()

)

answer = rag_chain.invoke("RAG 和微调有什么区别?")

print(answer)

为什么 temperature=0:RAG 场景要求答案忠实于文档内容,不需要创造性。高温度会让模型"发挥",可能编造文档里没有的信息。 Prompt 里的"不要编造答案"为什么重要:不加这句,LLM 在检索不到相关内容时会用自己的通用知识回答,用户以为是文档里写的——这是 RAG 系统最常见的问题。

完整问答系统

def ask_question(question: str) -> dict:

"""RAG 问答函数,返回答案和来源文档。"""

relevant_docs = retriever.invoke(question)

answer = rag_chain.invoke(question)

return {

"question": question,

"answer": answer,

"sources": [doc.page_content[:200] for doc in relevant_docs]

}

result = ask_question("向量数据库的作用是什么?")

print(f"问题:{result['question']}")

print(f"答案:{result['answer']}")

print(f"来源:{result['sources']}")

返回来源文档很重要——让用户知道答案是从哪段文档来的,方便验证。

最容易踩的坑

  • 分块质量差:这是 RAG 效果不好的头号原因。分块太大检索不精准,太小丢失上下文。手动检查分块结果。
  • Embedding 模型不匹配:用英文 Embedding 模型处理中文,语义相似度计算不准。中文文档用中文 Embedding。
  • Prompt 没约束:不告诉 LLM "基于参考内容回答",它就自由发挥。
  • Top-K 太小k=3 可能漏掉相关信息。复杂问题可以试 k=5
  • 没有处理"找不到"的情况:检索到的内容和问题不相关时,系统应该说"我不知道"而不是硬答。

什么时候用 RAG,什么时候用微调

场景推荐方案
知识经常更新RAG(更新文档即可)
需要引用来源RAG(可以返回原文)
改变模型的输出风格微调
改变模型的领域能力微调
数据量小(<100 条)RAG
数据量大(>10 万条)微调 + RAG

进阶学习

---

本文最后更新于 2026-07-08。代码示例基于 LangChain 0.2+,请根据最新版本调整。

保障 API 调用稳定性

调用 OpenAI、Anthropic 等海外 API 时,网络波动可能导致请求超时或失败。稳定的网络环境有助于提升开发效率。

⚠️ 请遵守所在地的法律法规和服务条款。使用 AI API 时请遵守各平台的使用政策。

了解安全上网方案 →

常见问题

相关推荐

获取更多 AI 内容

订阅更新,第一时间获取新教程和工具推荐。