RAG 系统部署实战:从文档索引到 Agent 工具集成
前三篇文章搭建了 Agent、加上监控、部署了 Gateway。但 Agent 的知识完全依赖 LLM 的训练数据——遇到内部文档、运维手册、私有知识就无能为力。RAG(检索增强生成)填上这个缺口。
这篇文章从文档加载到 Agent 集成,完整走一遍 RAG 流水线。
RAG 流水线
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 1. 加载 │ → │ 2. 切分 │ → │ 3. 嵌入 │ → │ 4. 存储 │
│ 文档 │ │ chunk │ │ 向量化 │ │ Chroma │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│
▼
用户提问 → 嵌入向量 → 相似度检索 → 拼接上下文 → LLM 生成回答
四个步骤,每个都有工程决策。我们逐一拆解。
Step 1:文档加载
from langchain_core.documents import Document
import glob
def load_documents(data_dir: str = "data") -> list[Document]:
docs = []
for filepath in glob.glob(os.path.join(data_dir, "*.md")):
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
docs.append(Document(
page_content=content,
metadata={"source": os.path.basename(filepath)},
))
return docs
关键决策:把文件名存在 metadata 中。后续检索时可以直接引用来源,回答”这个信息来自哪篇文档”。
Step 2:Chunk 切分策略
这是 RAG 系统最重要的工程决策之一。chunk 太大 → 检索噪音多,LLM 上下文浪费;chunk 太小 → 语义不完整,检索不到。
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个 chunk 最多 500 字符
chunk_overlap=100, # 相邻 chunk 重叠 100 字符
separators=["\n\n", "\n", "。", " ", ""], # 优先按自然边界切分
)
chunks = splitter.split_documents(docs)
三个参数的选择逻辑:
| 参数 | 值 | 为什么 |
|---|---|---|
chunk_size | 500 | 中文技术文档,500 字符约等于 2-3 个段落,是语义完整性和检索精度的平衡点 |
chunk_overlap | 100 | 20% 重叠率,保证跨 chunk 的上下文不被切断 |
separators | \n\n → \n → 。→ 空格 | 优先按段落切,其次是自然句号,最后才硬切——最大程度保留语义 |
生产环境建议:chunk 策略没有万能解。应该根据文档类型评估——代码文档用 200-300 字符,长文用 800-1000 字符。RAGAS 等框架可以量化评估不同 chunk 策略的检索质量。
Step 3 & 4:嵌入向量化 + Chroma 存储
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db", # 持久化到磁盘
)
为什么选 Chroma:
| 向量数据库 | 适用场景 | 不适用场景 |
|---|---|---|
| Chroma | 本地开发、小规模(< 10 万 chunk)、快速原型 | 高并发生产环境 |
| Pinecone | 全托管、大规模、零运维 | 预算有限、数据需本地化 |
| Qdrant | 自托管、高性能、过滤查询强 | 简单场景过度设计 |
| Milvus | 十亿级向量、分布式 | 小数据集起步 |
我选 Chroma 原因:本地开发零配置、持久化简单、API 跟 LangChain 深度集成。后续迁移到生产环境时,LangChain 的向量存储抽象层让切换成本很低——换一个 from_documents 调用即可。
RAG 问答链
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
prompt = ChatPromptTemplate.from_messages([
("system", """你是运维知识库助手。根据以下文档内容回答。
规则:
- 只使用提供的文档内容,不要编造
- 如果文档中没有,直接说「未找到相关内容」
- 回答时引用文档来源
文档内容:
{context}"""),
("user", "{question}"),
])
chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
Prompt 设计的关键:明确告诉 LLM「只使用文档内容,不要编造」。这是 RAG 防幻觉的第一道防线。没有这个约束,LLM 会用自己的训练数据补充,产生”看起来合理但文档里没有”的回答。
集成到 LangGraph Agent
RAG 系统可以独立运行,但最强大的用法是作为 Agent 的一个工具。LLM 自主判断什么时候需要检索知识库:
@tool
def search_knowledge_base(query: str) -> str:
"""从运维知识库中检索相关信息。当用户询问 K8s、Docker 等运维问题时使用。"""
docs = retriever.invoke(query)
parts = []
for doc in docs:
source = doc.metadata.get("source", "unknown")
parts.append(f"[来源: {source}]\n{doc.page_content}")
return "\n\n---\n\n".join(parts)
Agent 的 bind_tools 会自动把这个函数注册为可用工具。LLM 遇到运维问题时,会生成 tool_calls: [{name: "search_knowledge_base", args: {query: "CrashLoopBackOff 排查"}}]。
Agent 模式 vs 直接 RAG 的区别:
| 直接 RAG | Agent 模式 |
|---|---|
| 每次提问都检索 | LLM 自主决定是否检索 |
| 只做检索+生成 | 可以检索 + 调用其他工具 + 多次检索 |
| 答案固定来自文档 | 可以结合文档 + 自身知识 + 工具结果 |
检索质量评估
检索质量决定了 RAG 系统的好坏。最基础的评估方式是人工审查检索结果:
test_queries = [
{"query": "容器启动失败怎么排查", "keywords": ["CrashLoopBackOff"]},
{"query": "镜像安全扫描", "keywords": ["Trivy", "漏洞"]},
]
for item in test_queries:
docs = retriever.invoke(item["query"])
for doc in docs:
hits = [kw for kw in item["keywords"] if kw in doc.page_content]
if not hits:
print(f"⚠️ 漏检: {doc.page_content[:80]}")
生产环境建议接入 RAGAS:自动评估 faithfulness(回答是否忠于文档)、context_recall(检索是否召回相关内容)、context_precision(检索结果是否精确)。
实际效果
👤 什么是 CrashLoopBackOff?怎么排查?
🔧 [工具] search_knowledge_base: "CrashLoopBackOff" → 检索到 4 个 chunk
🤖 根据知识库文档,CrashLoopBackOff 是 Kubernetes 中 Pod 的一种状态,
表示容器启动后立即退出。常见原因包括:
1. 配置错误
2. 依赖服务不可达
3. 端口冲突
排查步骤:
- kubectl describe pod 查看 Events
- kubectl logs 查看容器日志
(来源:kubernetes-ops-guide.md)
👤 Docker 镜像优化和 Kubernetes 资源管理有什么关系?
🤖 [Agent 自主调用两次 search_knowledge_base]
镜像优化可以减少容器启动时间和资源占用,
这与 Kubernetes 的资源管理(Limits/Requests)直接相关...
生产部署考量
| 层面 | 当前实现 | 生产增强 |
|---|---|---|
| 文档更新 | 手动重建 | 增量索引 + 定时同步 |
| Chunk 策略 | 固定 500 | 按文档类型自适应 |
| 向量存储 | 本地 Chroma | Chroma Server / Qdrant 集群 |
| 检索 | 相似度 top-4 | 混合检索(向量 + BM25) + 重排序 |
| 评估 | 人工抽查 | RAGAS 自动化 + CI/CD 集成 |
| 监控 | 无 | 检索延迟、召回率、LLM 引用率 |
完整代码
- RAG 系统:demo/rag-system/rag_system.py
- Agent 集成:demo/rag-system/rag_agent.py
- 示例文档:demo/rag-system/data/
下一步
RAG 让 Agent 有了私有知识。但调用 LLM 是要花钱的——Token 成本怎么控制?下一篇文章深挖 Token 成本控制:从账单分析到模型路由再到缓存策略,让 AI 系统的运营成本可控。