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 分三步:

  1. 索引(Indexing):把你的文档切碎,存进向量数据库
  2. 检索(Retrieval):用户提问时,找到最相关的文档片段
  3. 生成(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)} 个文档片段")

这里发生了什么?

  1. 切分文档:把长文本切成小块(chunk)。为什么要切?因为 LLM 的上下文窗口有限,你不能把整本书塞进去。
  2. 向量化:用 Embedding 模型把文本变成一串数字(向量)。语义相近的文本,向量也相近。
  3. 存入 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 的幻觉,让它的回答更可靠。

回顾一下今天的要点:

  1. RAG = 检索 + 生成,让 AI 基于事实回答而不是凭空编造
  2. 向量数据库是 RAG 的基础设施,ChromaDB 是入门首选
  3. 混合检索能结合语义搜索和关键词搜索的优势
  4. 查询改写来源引用能进一步提升效果
  5. 文档切分是关键细节,太大太小都不行

下次你的 AI 再一本正经地胡说八道时,给它装上 RAG 这个"参考书"吧。毕竟,会查资料的 AI,才是靠谱的 AI。


💡 本文代码基于 LangChain + OpenAI + ChromaDB,完整代码已放在 GitHub。如果觉得有帮助,欢迎点赞收藏!

关注我,持续分享 AI 实战干货 🚀

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐