RAG 入门到精通:构建你的第一个智能问答系统
RAG(检索增强生成)是当前最实用的 LLM 应用范式。本文从原理到代码,一步步教你构建生产级 RAG 系统。
RAG 入门到精通:构建你的第一个智能问答系统
RAG(Retrieval-Augmented Generation)是当下最实用的 LLM 应用范式。它让 LLM 能够基于你的私有知识回答问题,无需重新训练模型。
一、为什么需要 RAG?
LLM 有三大局限:
- 知识陈旧:训练数据有截止日期
- 不懂私域:不知道你的企业文档、产品手册
- 会胡说:对不知道的内容会编造
RAG 的解决方案:先用检索找到相关文档,再让 LLM 基于这些文档回答。
用户问题 → 检索相关文档 → 拼接 prompt → LLM → 答案
二、核心架构
# 最简化的 RAG 流程
def rag(query, documents):
# 1. 检索:找最相关的 k 个文档
relevant_docs = retrieve(query, documents, k=3)
# 2. 构造 prompt
context = "\n\n".join(relevant_docs)
prompt = f"""基于以下资料回答用户问题。如果资料中没有,请说"我不知道"。
资料:
{context}
问题:{query}
"""
# 3. 调用 LLM
return llm(prompt)
看起来简单,但每个环节都有坑。
三、检索的关键技术
3.1 Embedding 模型
把文本变成向量(一串数字),用向量相似度找相关文档。 常用模型:text-embedding-3-small(OpenAI, 1536 维, $0.02/百万 token)bge-large-zh-v1.5(BAAI, 中文优秀, 开源)text-embedding-v3(Cohere, 多语言)
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
model="text-embedding-3-small",
input="Python 怎么读取文件?"
)
vector = response.data[0].embedding # 1536 维向量
3.2 向量数据库
存向量、做相似度搜索。| 数据库 | 特点 |
|---|---|
| Chroma | 轻量、嵌入式、易上手 |
| Milvus | 国产、生产级、性能强 |
| Qdrant | Rust 写、性能好 |
| pgvector | PostgreSQL 插件,运维简单 |
| Pinecone | SaaS,托管方便 |
import chromadb
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_or_create_collection("docs")
# 存文档
collection.add(
documents=["Python 用 open() 读文件...", "Java 用 FileReader..."],
ids=["doc1", "doc2"]
)
# 检索
results = collection.query(
query_texts=["怎么读文件"],
n_results=2
)
四、Chunking:最容易被忽略的关键
文档不可能整篇塞给 LLM(超出 token 限制),必须切片。
4.1 切片策略
| 策略 | 适用 | 缺点 |
|---|---|---|
| 固定长度 | 快速实验 | 切断语义 |
| 按段落 | 一般文档 | 长段落仍超限 |
| 按语义 | 高质量场景 | 慢 |
| 按 Markdown 标题 | 结构化文档 | 不适用非 MD |
| 滑动窗口 | 长文档 | 信息重复 |
4.2 推荐实践
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每片字符数
chunk_overlap=50, # 重叠字符(避免切断)
separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)
chunks = splitter.split_text(long_document)
五、Prompt 工程
检索出来的内容不会自动成为好答案,需要 prompt 包装。
5.1 经典 RAG prompt 模板
你是 {role},专注于回答关于 {domain} 的问题。
规则:
1. 只使用参考资料中的信息回答
2. 如果参考资料中找不到答案,明确说"我不知道"
3. 回答简洁,引用参考资料编号
4. 不要编造信息
参考资料:
[1] {doc1}
[2] {doc2}
[3] {doc3}
用户问题:{query}
5.2 进阶:HyDE
让 LLM 先生成假设性答案,再拿假设答案去检索,比直接用原始问题检索效果好。六、生产环境最佳实践
6.1 检索质量评估
# 用 GPT-4 评估检索质量
def eval_retrieval(query, retrieved_docs, ground_truth):
prompt = f"""判断以下文档是否与问题相关:
问题:{query}
文档:{retrieved_docs}
相关:是 / 否"""
return llm(prompt)
6.2 缓存
相同问题直接返回缓存答案,节省 80% 调用成本。6.3 监控
记录每次调用:- 检索耗时 / LLM 耗时
- 检索命中率
- 用户反馈(点赞 / 点踩)
七、完整代码示例
用 LangChain 30 行代码搭建一个 RAG:
from langchain.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
# 1. 加载文档
loader = DirectoryLoader("./docs", glob="**/*.md")
docs = loader.load()
# 2. 切片
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)
# 3. 存向量
vectordb = Chroma.from_documents(chunks, OpenAIEmbeddings(), persist_directory="./chroma")
# 4. 问答
qa = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-4o"),
retriever=vectordb.as_retriever(k=3)
)
print(qa.run("什么是 RAG?"))
八、进阶方向
- Agentic RAG:让 LLM 自己决定何时检索、检索什么
- 多模态 RAG:检索图片、表格、PDF
- GraphRAG:用知识图谱增强检索
- Self-RAG:让 LLM 评估自己答案是否需要再检索
RAG 是当下 LLM 应用最成熟的范式,值得投入时间深入学习。
💬 评论 0 条
暂无评论 — 来做第一个发声的人 ✨