RAG 不只是搜索:用 Python 让你的 AI 学会查字典,告别一本正经地胡说八道
RAG 不只是搜索:用 Python 让你的 AI 学会"查字典",告别一本正经地胡说八道
你有没有遇到过这种情况:问 AI 一个技术问题,它自信满满地回答了一大段,结果你一查——完全是编的?今天我们就来聊聊 RAG(检索增强生成),教你的 AI 在回答之前先翻翻资料,做一个有据可查的"好学生"。
前言:AI 的"自信"是种病
你一定见过这种场景:
你:请问 Python 的 requests 库怎么发送 POST 请求?
AI:使用 requests.post_json() 方法即可,例如:
requests.post_json(url, data=body)
你:……这个方法不存在吧?
AI:抱歉,我记错了。让我重新回答。
这就是大语言模型(LLM)的经典症状——幻觉(Hallucination)。它不是故意骗你,而是因为它"记性太好"又"记性太差":训练数据里的模式它记得很清楚,但具体细节它经常自己脑补。
就像一个考试前不复习、全靠临场发挥的学生——有时候能蒙对,有时候就翻车了。
那怎么办? 答案就是今天要聊的主角——RAG(Retrieval-Augmented Generation,检索增强生成)。
简单说,就是让 AI 在回答问题之前,先去"查资料"。就像你考试时允许带一本参考书——虽然你不一定能考满分,但至少不会说出"Python 有 post_json() 方法"这种离谱的话。
什么是 RAG?用人话解释
RAG 的核心思路就一句话:先检索,再生成。
想象你是一个公司的客服,客户问你:“你们的退货政策是什么?”
- 没有 RAG 的 AI:凭印象回答,“我们支持 30 天无理由退货”(实际政策是 15 天)
- 有 RAG 的 AI:先翻一下公司手册,找到退货政策章节,然后回答,“我们支持 15 天无理由退货,商品需保持原包装”
技术上,RAG 分三步:
- 索引(Indexing):把你的文档切碎,存进向量数据库
- 检索(Retrieval):用户提问时,找到最相关的文档片段
- 生成(Generation):把检索到的内容和问题一起丢给 LLM,让它基于事实回答
听起来很简单?确实,原理就这么简单。但魔鬼在细节里。
动手!用 Python 搭建你的第一个 RAG 系统
好了,理论讲够了,我们来写代码。毕竟程序员的世界里,talk is cheap, show me the code。
第一步:准备环境
pip install langchain langchain-openai chromadb tiktoken
你需要一个 OpenAI API Key(或者你也可以换成其他 LLM,比如用 Ollama 本地跑一个)。
第二步:准备知识库
假设我们有一些技术文档,比如 Python 的一些最佳实践:
# knowledge_base.py
documents = [
"Python 的 GIL(全局解释器锁)意味着同一时刻只有一个线程能执行 Python 字节码。"
"但 I/O 密集型任务仍然适合用多线程,因为 I/O 等待时会释放 GIL。",
"使用 f-string 比 format() 和 % 格式化更快更可读。"
"Python 3.6+ 推荐优先使用 f-string。",
"requests 库发送 POST 请求应使用 requests.post() 方法,"
"数据可以通过 data 参数(表单)或 json 参数(JSON)传递。",
"Python 虚拟环境推荐使用 venv 或 conda。"
"不要用系统 Python 直接装包,否则迟早会遇到依赖冲突的噩梦。",
"装饰器本质上是一个接收函数作为参数并返回新函数的高阶函数。"
"常用场景包括日志记录、权限校验、缓存等。",
]
实际项目中,你的知识库可能是 PDF 文档、Markdown 文件、数据库记录等。这里为了演示方便,我们用简单的字符串列表。
第三步:文档向量化 + 存入 ChromaDB
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.schema import Document
# 将文本转为 Document 对象
docs = [Document(page_content=text) for text in documents]
# 切分文档(实际项目中很重要,但这里文本较短可以跳过)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=20
)
splits = text_splitter.split_documents(docs)
# 创建向量数据库
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(
documents=splits,
embedding=embeddings,
persist_directory="./chroma_db"
)
print(f"已索引 {len(splits)} 个文档片段")
这里发生了什么?
- 切分文档:把长文本切成小块(chunk)。为什么要切?因为 LLM 的上下文窗口有限,你不能把整本书塞进去。
- 向量化:用 Embedding 模型把文本变成一串数字(向量)。语义相近的文本,向量也相近。
- 存入 ChromaDB:一个轻量级向量数据库,专门用来存和检索向量。
第四步:构建 RAG 链
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# 初始化 LLM
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 构建检索器
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# 构建 Prompt 模板
template = """你是一个技术助手。请根据以下参考资料回答用户的问题。
如果参考资料中没有相关信息,请坦诚说"我不确定",不要编造。
参考资料:
{context}
用户问题:{question}
回答:"""
prompt = ChatPromptTemplate.from_template(template)
# 构建 RAG 链
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 测试!
answer = rag_chain.invoke("Python 的 GIL 是什么?对多线程有什么影响?")
print(answer)
输出大概是这样的:
Python 的 GIL(全局解释器锁)意味着同一时刻只有一个线程能执行 Python 字节码。但这并不意味着多线程完全没用——I/O 密集型任务(如网络请求、文件读写)仍然适合用多线程,因为在等待 I/O 时 GIL 会被释放。
注意到了吗?AI 的回答是有据可查的,它参考了我们提供的资料,而不是凭空编造。
第五步:对比一下有 RAG 和没有 RAG 的区别
# 没有 RAG 的普通 LLM
plain_answer = llm.invoke("Python 的 GIL 是什么?").content
print("普通 LLM:", plain_answer)
# 有 RAG 的 LLM
rag_answer = rag_chain.invoke("Python 的 GIL 是什么?")
print("RAG LLM:", rag_answer)
普通 LLM 可能也会答对这个问题(因为 GIL 是常见知识),但如果你问的是你们公司内部的技术文档、私有 API 的用法、或者最新发布的库的特性——普通 LLM 就开始"编"了,而 RAG 能给出准确的答案。
进阶:让 RAG 更聪明的几个技巧
技巧 1:混合检索(Hybrid Search)
单纯的向量检索有时候会"跑偏"——语义相似但关键词不匹配。解决方案是混合使用向量检索和关键词检索:
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever
# BM25 关键词检索
bm25_retriever = BM25Retriever.from_documents(docs)
bm25_retriever.k = 3
# 向量检索
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# 混合检索(各占 50% 权重)
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.4, 0.6]
)
技巧 2:查询改写(Query Rewriting)
用户的问题有时候很模糊,直接检索效果不好。可以先让 LLM 改写查询:
rewrite_prompt = ChatPromptTemplate.from_template(
"请将以下用户问题改写为更适合搜索的形式,保持原意但更具体:\n"
"原始问题:{question}\n改写后的查询:"
)
rewriter = rewrite_prompt | llm | StrOutputParser()
# 先改写,再检索
def rag_with_rewrite(question):
rewritten = rewriter.invoke({"question": question})
return rag_chain.invoke(rewritten)
技巧 3:来源引用(Source Attribution)
让 AI 回答时引用来源,增强可信度:
template_with_citation = """你是一个技术助手。请根据以下参考资料回答用户的问题。
在回答中引用信息来源(如 [来源1]、[来源2])。
如果参考资料中没有相关信息,请坦诚说"参考资料中未找到相关信息"。
参考资料:
{context}
用户问题:{question}
回答(请引用来源):"""
实战场景:RAG 能用来干嘛?
说了这么多理论和代码,RAG 到底能用在哪些实际场景?
1. 企业知识库问答
把公司的文档、Wiki、Slack 记录都喂进去,新员工可以直接问:“我们的部署流程是什么?”——比翻文档快 10 倍。
2. 代码助手
把你的代码仓库索引起来,AI 就能回答:“这个项目的认证逻辑是怎么实现的?”——比你自己 grep 代码快多了。
3. 客服机器人
把产品手册、FAQ、历史工单都塞进去,客服机器人就能给出准确的回答,而不是"请联系人工客服"。
4. 法律/医疗助手
这些领域容不得半点胡说。RAG 能确保 AI 的回答基于真实的法律条文或医学指南。
踩坑指南:我走过的弯路你别走
坑 1:文档切分太粗或太细
切太大 → 检索不精准,塞进 prompt 的内容太多噪音
切太小 → 上下文丢失,AI 看到的是支离破碎的信息
建议:chunk_size 在 200-500 tokens 之间,chunk_overlap 在 20-50 tokens。
坑 2:Embedding 模型选择
不同语言用不同的 Embedding 模型。中文场景推荐:
text-embedding-3-small(OpenAI,性价比高)bge-large-zh-v1.5(开源,中文效果好)
坑 3:忽略文档更新
知识库不是建好就不管了。文档更新了,向量库也要更新。建议定期重建索引。
坑 4:Prompt 太简单
"根据资料回答问题"这种 Prompt 太粗糙。好的 Prompt 应该:
- 明确告诉 AI 资料中没有就不要编
- 指定回答的语言和格式
- 要求引用来源
总结
RAG 不是什么黑科技,它的核心就是让 AI 在回答前先"查字典"。但就是这么简单的一步,能大幅减少 AI 的幻觉,让它的回答更可靠。
回顾一下今天的要点:
- RAG = 检索 + 生成,让 AI 基于事实回答而不是凭空编造
- 向量数据库是 RAG 的基础设施,ChromaDB 是入门首选
- 混合检索能结合语义搜索和关键词搜索的优势
- 查询改写和来源引用能进一步提升效果
- 文档切分是关键细节,太大太小都不行
下次你的 AI 再一本正经地胡说八道时,给它装上 RAG 这个"参考书"吧。毕竟,会查资料的 AI,才是靠谱的 AI。
💡 本文代码基于 LangChain + OpenAI + ChromaDB,完整代码已放在 GitHub。如果觉得有帮助,欢迎点赞收藏!
关注我,持续分享 AI 实战干货 🚀
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)