
构建可用于生产环境的 RAG 智能体:开发者完整指南
你有没有纳闷过,为什么你的 RAG 系统总是返回一堆无关的结果,或者漏掉显而易见的答案?你不是一个人!很多开发者一开始用 vector search,然后一脸懵地发现,他们的“智能”AI 连一个简单的产品代码都找不到。
如果你试过搭建一个 Retrieval-Augmented Generation (RAG) 系统,可能也撞过我一样的南墙。你的 chatbot 有时候回答得很棒,但有时候完全答非所问,返回一些概念上相似但实际没用的信息。
问题出在哪儿?大部分 RAG 教程只关注那些酷炫的东西——embeddings、vector databases 和 LLMs,却忽略了一个朴实无华的真相:搜索其实很难,而 relevance 才是王道。
今天,我要带你一步步打造一个在生产环境中真正好用的 RAG 智能体。我们会聊聊不同的搜索策略,什么时候用哪种,以及怎么组合它们来达到最高准确率。
RAG 现实检查:为什么单靠 Vector Search 不够
先讲个可能你也觉得眼熟的故事。
我第一次建 RAG 系统时,用的是标准套路:把文档切块,用 OpenAI 生成 embeddings,存到 vector database,检索 top-k 相似块。看起来优雅又现代。
然后我问它:“P0420 错误代码的解决办法是什么?”
系统回了三段关于排气系统和排放的文字——技术上有点相关,但完全没提到 P0420。而我需要的那个具体公告,埋在第47个 chunk 里。
这就是 vector search 的陷阱。语义相似不等于 relevance。
完整的搜索策略工具箱
我学到的经验是:不同的问题需要不同的搜索策略。来逐一拆解什么时候用哪种方法。
1. Keyword Search:精准的冠军
是什么:传统的文本匹配,用像 BM25 这样的算法。想象一下2005年的 Google 搜索。
什么时候用:
- 搜索具体术语、代码或 ID
- 法律文档这种需要精确措辞的场景
- 用户知道具体术语的时候
用例:比如“查找安全公告 SB-2024-003”
# 简单关键词搜索实现
def keyword_search(query, documents):
query_terms = query.lower().split()
scored_docs = []
for doc in documents:
score = sum(1 for term in query_terms if term in doc.lower())
if score > 0:
scored_docs.append((doc, score))
return sorted(scored_docs, key=lambda x: x[1], reverse=True)
注意:它很“死脑筋”。你搜“car repair”,它不会找到“automobile maintenance”。
2. Vector Search:语义侦探
是什么:把文本转成高维向量,捕捉语义。相似的概念会在向量空间里聚在一起。
什么时候用:
- 开放式问题
- 用户的措辞和文档不完全一致时
- 知识库内容风格多变
用例:比如“怎么修车上怪响?” → 能找到关于引擎故障排查的文档
import chromadb
from chromadb.utils import embedding_functions
# Vector search 设置
client = chromadb.Client()
embedding_fn = embedding_functions.OpenAIEmbeddingFunction(
api_key="your-api-key",
model_name="text-embedding-3-small"
)
collection = client.create_collection(
name="knowledge_base",
embedding_functinotallow=embedding_fn
)
defvector_search(query, top_k=5):
results = collection.query(
query_texts=[query],
n_results=top_k
)
return results["documents"][0]
注意:有时候会返回“相关”但没用的结果。recall 高,precision 低。
3. Hybrid Search:两全其美
是什么:结合 keyword 和 vector search,然后合并结果。
什么时候用:
- 生产系统,不能漏掉任何关键信息
- 查询类型混合(有的很具体,有的很开放)
- 准确性比简单性更重要
秘诀:用 Reciprocal Rank Fusion (RRF) 合并排名:
def reciprocal_rank_fusion(keyword_results, vector_results, k=60):
"""用 RRF 算法合并两个排名列表"""
scores = {}
# 给关键词结果打分
for rank, doc inenumerate(keyword_results, 1):
scores[doc] = scores.get(doc, 0) + 1 / (k + rank)
# 给向量结果打分
for rank, doc inenumerate(vector_results, 1):
scores[doc] = scores.get(doc, 0) + 1 / (k + rank)
# 按综合得分排序
returnsorted(scores.items(), key=lambda x: x[1], reverse=True)
4. Database Search:事实核查员
是什么:对结构化数据进行 SQL 查询。
什么时候用:
- 当前价格、库存、用户数据
- 需要精确、实时的数字
- 结合文本搜索给完整答案
5. Graph Search:关系专家
是什么:查询知识图谱,找相关联的信息。
什么时候用:
- “谁是 Alice 的上司的汇报对象?”
- 复杂多跳推理
- 关系比内容更重要
这里有个决策流程图帮你选:
Pure Vector Search 的隐藏问题
在建智能体之前,先看看会出什么问题。我遇到过的十大失败模式:
打造生产级 RAG 智能体
现在来实战。我会教你怎么用 ChromaDB 做 vector search,加上 reranking,用《回到未来》电影剧本作为知识库。
步骤 1:设置知识库
import chromadb
import openai
import tiktoken
from pathlib import Path
from typing importList
# 智能切块函数
defsmart_chunk(text: str, max_tokens: int = 200) -> List[str]:
"""按语义切分文本,限制 token 数"""
tokenizer = tiktoken.get_encoding("cl100k_base")
words = text.split()
chunks = []
current_chunk = []
for word in words:
# 检查加这个词会不会超 token 限制
test_chunk = " ".join(current_chunk + [word])
iflen(tokenizer.encode(test_chunk)) > max_tokens:
if current_chunk: # 不加空块
chunks.append(" ".join(current_chunk))
current_chunk = [word]
else:
current_chunk.append(word)
# 加最后一块
if current_chunk:
chunks.append(" ".join(current_chunk))
return chunks
# 加载并处理剧本
script_text = Path("back_to_the_future.txt").read_text(encoding="utf-8")
chunks = smart_chunk(script_text, max_tokens=200)
print(f"从剧本创建了 {len(chunks)} 个块")
步骤 2:创建向量存储
from chromadb.utils import embedding_functions
import uuid
# 初始化带持久化的 ChromaDB
client = chromadb.Client(chromadb.config.Settings(
persist_directory="./movie_knowledge_base"
))
# 设置 embedding 函数
embedding_fn = embedding_functions.OpenAIEmbeddingFunction(
api_key=openai.api_key,
model_name="text-embedding-3-small"
)
# 创建或获取集合
collection_name = "bttf_script"
try:
collection = client.get_collection(collection_name)
print("找到已有集合")
except:
collection = client.create_collection(
name=collection_name,
embedding_functinotallow=embedding_fn
)
print("创建新集合")
# 如果集合为空,添加文档
if collection.count() == 0:
print("正在添加文档到集合...")
collection.add(
ids=[str(uuid.uuid4()) for _ in chunks],
documents=chunks,
metadatas=[{"chunk_index": i} for i inrange(len(chunks))]
)
client.persist()
print("文档已添加并持久化")
步骤 3:实现带 Reranking 的智能搜索
def smart_search(query: str, top_k: int = 5) -> str:
"""
多阶段检索:
1. 用 vector search 广撒网
2. 按关键词重叠 rerank
3. 返回最佳结果
"""
# 第一阶段:vector search 提高 recall
initial_results = collection.query(
query_texts=[query],
n_results=min(top_k * 3, 15) # 获取更多候选
)
docs = initial_results["documents"][0]
ifnot docs:
return"未找到相关信息。"
# 第二阶段:按关键词重叠 rerank
query_words = set(query.lower().split())
scored_docs = []
for doc in docs:
doc_words = set(doc.lower().split())
keyword_score = len(query_words.intersection(doc_words))
scored_docs.append((doc, keyword_score))
# 按关键词重叠排序,保留最佳结果
reranked = sorted(scored_docs, key=lambda x: x[1], reverse=True)
best_docs = [doc for doc, score in reranked[:top_k]]
return"\n\n---\n\n".join(best_docs)
步骤 4:用 OpenAI Agents SDK 打造 RAG 智能体
这里是魔法发生的地方。OpenAI Agents SDK 让构建会用工具的智能体变得超简单。下面是一个完整的工作示例:
import uuid
from pathlib import Path
import chromadb
import tiktoken
from agents import Agent, Runner, function_tool
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# 加载并切分剧本
script_text = Path("back_to_the_future.txt").read_text(encoding="utf-8")
defsimple_chunk(text, max_tokens=200):
tokenizer = tiktoken.get_encoding("cl100k_base")
words, chunk, chunks = text.split(), [], []
for w in words:
iflen(tokenizer.encode(" ".join(chunk + [w]))) > max_tokens:
chunks.append(" ".join(chunk))
chunk = [w]
else:
chunk.append(w)
if chunk:
chunks.append(" ".join(chunk))
return chunks
docs = simple_chunk(script_text, max_tokens=200)
# 设置带持久化的 ChromaDB
client = chromadb.PersistentClient(path="./chroma_script_store")
collection_name = "bttf_script"
# 获取或创建集合
try:
collection = client.get_collection(collection_name)
except Exception:
collection = client.create_collection(name=collection_name)
# 如果集合为空,添加文档
if collection.count() == 0:
collection.add(
ids=[str(uuid.uuid4()) for _ in docs],
documents=docs
)
# 定义搜索工具,带正确的装饰器
@function_tool
defsearch_script(query: str, top_k: int = 3) -> str:
"""搜索《回到未来》剧本中的相关片段"""
res = collection.query(query_texts=[query], n_results=top_k)
if res and"documents"in res and res["documents"] and res["documents"][0]:
return"\n\n".join(res["documents"][0])
return"未找到相关文档。"
# 创建智能体
agent = Agent(
name="Script Agent",
instructinotallow=(
"你回答关于《回到未来》电影的问题。\n"
"需要时调用 `search_script` 工具获取片段,"
"然后引用或改述它们来回答。"
),
tools=[search_script],
)
# 测试智能体
query = "Doc 在哪里让 Marty 见他,几点钟?"
result = Runner.run_sync(agent, query)
print("\n--- 答案 ---\n", result.final_output)
query = "凌晨 1:15 发生了什么?"
result = Runner.run_sync(agent, query)
print("\n--- 答案 ---\n", result.final_output)
优雅之处:
- @function_tool 装饰器自动把你的 Python 函数变成智能体能用的工具
- 智能体指令告诉 LLM 什么时候、怎么用搜索工具
- Runner.run_sync() 管理整个对话流程——智能体决定什么时候搜索,处理结果,生成最终答案
- 持久化存储让你不用每次重启都重新嵌入文档
秘诀:让它达到生产级
以下是区分业余项目和生产系统的关键:
1. 智能切块策略
别光按 token 数切分,还要考虑:
- 句子边界
- 段落分隔
- 主题转换
- 重叠块保留上下文
2. 多阶段检索
# 生产级检索管道
def production_search(query: str):
# 第一阶段:快速检索(广撒网)
candidates = vector_search(query, k=20)
# 第二阶段:关键词加权
keyword_boosted = boost_keyword_matches(candidates, query)
# 第三阶段:交叉编码器 rerank(如果预算够)
final_results = cross_encoder_rerank(keyword_boosted, query, k=5)
return final_results
3. 评估与监控
跟踪这些指标:
- Hit Rate:检索到相关文档的问题百分比
- Answer Quality:人工评分或用 LLM 做裁判
- Latency:端到端响应时间
- Cost:嵌入和生成成本
4. 错误处理
def robust_search(query: str):
try:
return smart_search(query)
except Exception as e:
# 回退到简单搜索
logging.error(f"智能搜索失败: {e}")
return simple_keyword_search(query)
什么时候用什么:你的决策矩阵
这是我的速查表,帮你选对方法:
从 Vector Search 开始,如果:
- 内容多变,偏自然语言
- 用户提开放式问题
- 想要开箱即用的好结果
加 Keyword Search,如果:
- 用户搜具体术语、代码、名字
- 有结构化或一致的术语
- 精准度比召回率更重要
用 Hybrid Search,如果:
- 建生产系统
- 不能漏掉重要结果
- 有工程带宽
考虑 Graph/SQL,如果:
- 需要关系查询
- 有结构化数据
- 实时准确性关键
总结
打造一个牛逼的 RAG 系统,不是用最新的 embedding 模型或最炫的 vector database,而是要懂你的用户、你的数据,选对每种场景的检索策略。
从简单的 vector search 开始,衡量关键指标,逐步增加复杂度。最重要的是,永远用真实用户查询测试——别光用演示里完美的例子。
你的 RAG 翻车故事是啥?在下面留言吧!我想听听你的经历和解决办法。
想深入了解?我正在写一个完整的 RAG 实现指南,包含生产级示例。关注我获取更新,告诉我你想让我下次讲啥具体话题。
本文转载自PyTorch研习社,作者:AI研究生
