Bonus-2 · RAG / Agent 全景实战手册
🎯 目标:从向量库选型 → 检索策略 → Agent 框架 → tool calling,搭一条真能跑的 RAG/Agent 链路,并落地实测结果。 🧪 实测环境:gpu1 (A800-SXM4-40GB) · k3s · vLLM Qwen2.5-3B (
10.43.165.182:8000) · Python 3.10 · venv/opt/bonus2/venv(479 MB) 📅 实测日期:2026-05-27
0. 为什么 RAG/Agent 是 LLM 落地的"半壁江山"
裸调 LLM 的两个硬伤:
| 痛点 | 表现 | 解法 |
|---|---|---|
| 知识截止 | "Claude 4.X 啥时候发的?" 答不出来 | RAG: 检索外部知识喂进 context |
| 不能动手 | "查一下今天日历,然后发邮件" 做不到 | Agent + Tool calling: 让模型调函数 |
RAG = 检索增强生成 · Agent = 决策 + 工具循环。两者经常组合(Agent 里某个 tool 就是 RAG retriever)。
1. 向量库全景
1.1 主流方案对比
| 引擎 | 部署 | 语言 | 适用规模 | 索引 | 特点 |
|---|---|---|---|---|---|
| ChromaDB | embedded / server / cloud | Python 原生 | < 10M docs | HNSW | 最简,Python 用户友好,自带 embedding |
| Qdrant | server / cloud | Rust core | 10M-1B | HNSW + 量化 | 性能强,过滤表达式好用,gRPC + REST |
| Milvus | k8s 分布式 | C++ core | 1B+ | HNSW / IVF / DiskANN | 大厂级,水平扩展,运维重 |
| Weaviate | k8s / cloud | Go | 10M-100M | HNSW | 内置 generative module(自动 RAG) |
| pgvector | PostgreSQL 扩展 | PG plugin | < 10M | IVF / HNSW | 不引入新组件,跟业务表 join 友好 |
| FAISS | in-process | C++ + Python | 任意 | IVF / HNSW / PQ | Meta 出的"底层库",自己组装,无服务 |
| OpenSearch / Elasticsearch | server | Java | TB 级 | HNSW + 倒排 | 同时支持 BM25 + dense,hybrid retrieval 首选 |
| Vespa | server | Java + C++ | TB 级 | HNSW + ColBERT | 检索+排序一体,Yahoo/Spotify 在用 |
1.2 选型决策树
QPS < 100, docs < 100K → ChromaDB embedded
QPS < 1K, docs < 10M → Qdrant single node
QPS > 1K, docs > 100M → Milvus cluster / Vespa
已有 PostgreSQL,数据强一致 → pgvector
需要 hybrid (BM25 + dense) → OpenSearch / Vespa
完全离线 / 嵌入式 → FAISS in-process
1.3 本次实测选 ChromaDB:轻、快、零配置
pip install chromadb openai
# 479MB venv, 含 onnxruntime / numpy / protobuf / tokenizers / pydantic
2. Embedding 模型全景
| 模型 | 维度 | 语言 | 体积 | 备注 |
|---|---|---|---|---|
| all-MiniLM-L6-v2 | 384 | EN 为主 | 90 MB | ChromaDB 默认,onnx,中文质量差 |
| bge-large-zh-v1.5 | 1024 | 中文 | 1.3 GB | 智源,中文 retrieval 排行榜第一梯队 |
| bge-m3 | 1024 | 多语 | 2.3 GB | 同时输出 dense / sparse / colbert |
| m3e-base | 768 | 中英 | 410 MB | 国产,中英混合场景 |
| text-embedding-3-large | 3072 | 100+ 语言 | API | OpenAI, $0.13/1M tokens |
| voyage-3 | 1024 | 多语 | API | Anthropic 推荐,RAG 专精 |
| gte-Qwen2-7B-instruct | 4096 | 多语 | 14 GB | 大模型当 embedding,top tier,慢 |
💡 中文 RAG 不要用 all-MiniLM,改用
bge-base-zh起步。本次 demo 用默认 onnx 模型纯属图轻,代价是 retrieval 质量打折(见下文实测)。
3. 检索策略全景
3.1 单路检索
| 策略 | 原理 | 优势 | 短板 |
|---|---|---|---|
| Dense (semantic) | embedding cosine | 同义/改写鲁棒 | 字符精确匹配差(产品名/代码) |
| Sparse (BM25) | 词袋 + TF-IDF | 精确字符匹配,可解释 | 同义词盲区 |
3.2 多路混合
| 策略 | 实现 | 何时用 |
|---|---|---|
| Hybrid (dense + BM25) | RRF 融合 / 加权和 | 大多数生产场景默认 |
| HyDE (Hypothetical Document Embedding) | 先让 LLM 生成假答案,再用假答案的 embedding 检索 | query 太短/太抽象 |
| Multi-Query | LLM 重写 query 成 N 个,各自检索,合并 | query 模糊 / 多意图 |
| Step-back | LLM 把 query 抽象成更上层问题再检索 | 需要背景知识的复杂问题 |
| Parent-Document | child chunk 索引,命中后召回 parent 段 | 需要完整上下文(法律、医疗) |
3.3 重排(Re-ranking)
检索召回 top-N(N=50-100)后,用 cross-encoder 重新打分,取 top-K(K=3-10):
| Reranker | 类型 | 备注 |
|---|---|---|
bge-reranker-large | cross-encoder | 中文好,免费 |
cohere-rerank-v3 | API | 多语,$2/1k searches |
mxbai-rerank-large | cross-encoder | EN,SOTA |
ColBERT v2 | late interaction | 速度兼顾质量 |
3.4 分块策略 (Chunking)
| 策略 | 适用 |
|---|---|
| Fixed-size (e.g. 512 tokens, 50 overlap) | 大多数,简单可用 |
| Semantic (按句子 embedding 突变切) | 文档语义跳跃明显时 |
Recursive (按 \n\n → \n → 逐层切) | LangChain 默认,工程上稳 |
| Markdown header split | 技术文档,结构化文件 |
| Code-aware (AST) | 代码库 RAG |
4. 实测 1:ChromaDB + ONNX embedding + vLLM 端到端 RAG
4.1 完整 demo 代码 (/opt/bonus2/rag_demo.py)
import chromadb
from openai import OpenAI
import time
DOCS = [
{"id": "k8s-pod", "text": "Pod 是 K8s 最小调度单元,包含 1 个或多个容器共享 network namespace 和 volume,生命周期一致。"},
{"id": "k8s-deploy","text": "Deployment 通过 ReplicaSet 间接管理 Pod 副本数,支持滚动升级(RollingUpdate)和 Recreate 两种策略。"},
{"id": "k8s-svc", "text": "Service 通过 selector 把 Pod 抽象成稳定的 ClusterIP / NodePort / LoadBalancer 服务,后端 Pod 死掉自动剔除。"},
{"id": "k8s-pvc", "text": "PVC 是用户对存储的声明式申请,绑定 PV 后通过 CSI 驱动挂载到 Pod。"},
{"id": "k8s-hpa", "text": "HPA 基于 metrics-server 提供的 CPU/Memory 或 custom metric 自动扩缩 Deployment 副本数。"},
{"id": "vllm-pa", "text": "vLLM 的 PagedAttention 把 KV cache 分页管理,显存碎片减少 4×,30 并发请求下吞吐提升 24×。"},
{"id": "vllm-cb", "text": "Continuous Batching 让 vLLM 在 iteration level 调度,新请求随时加入,不需要等 batch 凑齐。"},
{"id": "mig", "text": "NVIDIA MIG (Multi-Instance GPU) 在 A100/A800/H100 上把单卡硬件切成最多 7 个独立 instance,显存和 SM 都隔离。"},
]
client = chromadb.PersistentClient(path="/opt/bonus2/chroma_data")
collection = client.get_or_create_collection(name="k8s_facts")
collection.upsert(ids=[d["id"] for d in DOCS], documents=[d["text"] for d in DOCS])
llm = OpenAI(base_url="http://10.43.165.182:8000/v1", api_key="not-needed")
def rag_chat(question, top_k=3):
res = collection.query(query_texts=[question], n_results=top_k)
docs = res["documents"][0]; ids = res["ids"][0]
context = "\n".join(f"[{i+1}] ({did}) {d}" for i, (did, d) in enumerate(zip(ids, docs)))
prompt = f"请基于以下知识回答(末尾标注源 [1][2][3]):\n知识:\n{context}\n\n问题: {question}\n\n答复:"
t0 = time.time()
resp = llm.chat.completions.create(
model="qwen2.5-3b",
messages=[{"role": "user", "content": prompt}],
max_tokens=200, temperature=0.3,
)
return resp.choices[0].message.content, time.time()-t0, ids
4.2 实测输出(2026-05-27 实跑)
检索质量
Q: K8s 的 Pod 是什么?
top-1 [k8s-pod] (dist=0.605): Pod 是 K8s 最小调度单元... ✅
top-2 [k8s-pvc] (dist=0.990): PVC 是用户对存储的声明式申请...
Q: vLLM 为什么吞吐高?
top-1 [k8s-pvc] (dist=1.214): PVC... ❌ 错配
top-2 [vllm-pa] (dist=1.270): vLLM PagedAttention... (才到 top-2)
Q: A800 GPU 怎么切片?
top-1 [mig] (dist=0.645): NVIDIA MIG... ✅
top-2 [k8s-hpa] (dist=1.321): HPA...
Q: 怎么让 K8s 自动扩缩?
top-1 [k8s-pod] (dist=0.805): Pod... ❌ 应该召 hpa
top-2 [k8s-deploy] (dist=1.174): Deployment...
真实问题:vLLM 为什么吞吐高 和 怎么让 K8s 自动扩缩 两题的 top-1 都错了。原因清晰:
ChromaDB 默认 embedding 是
all-MiniLM-L6-v2,英文模型。中文 query 走它会丢语义。生产中文 RAG 必须换bge-base-zh或bge-m3。
但好在 RAG 是 top-K 多路召回,top_k=3 时正确文档基本都进了 context,LLM 仍能从中挑出真正相关的写进答案。
端到端 RAG 生成质量(top_k=3)
| Q | LLM 答案(节选) | 来源命中 | 耗时 |
|---|---|---|---|
| K8s 的 Pod 是什么? | "Kubernetes 最小的调度单元。一个 Pod 可以包含一个或多个容器,共享网络命名空间和存储卷..." ✅ | k8s-pod, k8s-pvc, k8s-deploy | 1919 ms |
| vLLM 为什么吞吐高? | "PagedAttention 将 KV cache 分页管理,显存碎片减少,30 并发吞吐提升 24 倍" ✅ | k8s-pvc, vllm-pa, k8s-pod | 1596 ms |
| A800 GPU 怎么切片? | "通过 NVIDIA MIG 技术被切分成最多 7 个独立的 instance..." ✅ | mig, k8s-hpa, vllm-pa | 1572 ms |
| 怎么让 K8s 自动扩缩? | "通过 Deployment + HPA + ReplicaSet..."(部分跑题) ⚠️ | k8s-pod, k8s-deploy, k8s-pvc | 4101 ms |
关键观察:即使 top-1 embedding 错,只要 top-K 把对的文档捞进来,LLM 端的"语义裁判"还是能挑出正确事实。这就是 RAG 容错性强于纯 embedding 的核心原因。
4.3 优化方向
- 换中文 embedding:
pip install sentence-transformers+bge-base-zh-v1.5 - 加 reranker:
bge-reranker-base把 top-20 重排成 top-3 - Hybrid search:同时跑 BM25 + dense,RRF 融合
- HyDE:让 LLM 先生成假答案,用假答案做检索
5. Agent 框架全景
| 框架 | 立场 | 学习曲线 | 适用 |
|---|---|---|---|
| LangChain | 一切皆 Chain,生态最大 | 高(API 不稳) | 快速搭原型,Python 后端 |
| LangGraph | LangChain 的状态机版本 | 中 | 复杂多步 agent(有循环/分支) |
| LlamaIndex | RAG 主线,从 doc loader 到 query engine | 中 | 文档 QA / 数据应用 |
| AutoGen | 多 agent 对话(MS Research) | 中 | 角色协作型(Coder + Reviewer) |
| CrewAI | 类 AutoGen,UX 更简 | 低 | 业务流程编排 |
| smolagents | HuggingFace 出品,Code-Agent 范式 | 低 | 想让模型直接写 Python 跑 |
| OpenAI Agents SDK | OpenAI 官方,2025 推出 | 低 | 用 OpenAI/Anthropic API 时首选 |
| Pydantic-AI | 强类型 schema 优先 | 中 | 生产代码,严结构化输出 |
| MCP (Anthropic) | Tool 服务的协议标准,跨 agent 复用 | 中 | Tool 资源跨产品/团队共享 |
5.1 选型建议
快速原型 + Python 后端 → LangChain / LangGraph
文档 QA 主线 → LlamaIndex
多角色协作场景 → AutoGen / CrewAI
生产 API 强 schema → Pydantic-AI / OpenAI Agents SDK
跨工具复用 / 标准化 → 把工具暴露成 MCP server
"我就是想让它写 Python" → smolagents
5.2 ReAct = Agent 的最小核心算法
核心 prompt 范式:
Thought: 思考下一步
Action: tool_name(args)
Observation: <工具返回>
Thought: 综合判断
Action: ...
...
Final Answer: <最终回答>
循环:LLM 输出 → 正则解析 Action → 执行 tool → 把 Observation 追加到 prompt → 再 call LLM,直到 Final Answer 出现。 这是所有 Agent 框架的"底层逻辑"——你拆任何 LangChain agent,本质都是这个 loop。
6. Tool Calling 三种实现方式
| 方式 | 模型要求 | 优势 | 劣势 |
|---|---|---|---|
| A. OpenAI Function Calling (推荐) | 模型微调过,如 Qwen2.5 / GPT-4o / Claude | 模型直出结构化 JSON,稳 | 依赖模型支持 + 推理框架开启 --enable-auto-tool-choice |
| B. ReAct prompting | 任意模型 | 0 改动,任意推理后端 | 解析脆弱,需要正则 + 重试 |
| C. Code-as-action (smolagents) | 任意模型 | 表达力强,可以组合 | 需要沙箱执行,安全风险 |
6.1 vLLM 开启原生 function calling
# 启动 vllm-3b 时加这两个参数
vllm serve Qwen/Qwen2.5-3B-Instruct \
--enable-auto-tool-choice \
--tool-call-parser hermes
本次实测条件:线上的 vllm-3b 启动时没开这两个 flag,所以走 B. ReAct 方案。
7. 实测 2:ReAct Agent + 3 工具
7.1 工具定义(/opt/bonus2/agent_demo.py)
def tool_calculator(expr): # safe eval, 限制在 math 模块
def tool_datetime(_): # 当前时间
def tool_kb_lookup(key): # 小型 KB: vllm / mig / hpa
TOOLS = {"calculator": ..., "datetime": ..., "kb_lookup": ...}
7.2 ReAct loop 核心
ACTION_RE = re.compile(r"Action:\s*(\w+)\(([^)]*)\)")
FINAL_RE = re.compile(r"Final Answer:\s*(.+)", re.DOTALL)
while step < max_steps:
resp = llm.chat.completions.create(..., stop=["Observation:"])
out = resp.choices[0].message.content
if FINAL_RE.search(out): return final
m = ACTION_RE.search(out)
obs = TOOLS[m.group(1)](m.group(2))
messages.append({"role": "assistant", "content": out})
messages.append({"role": "user", "content": f"Observation: {obs}"})
7.3 实测三道题(2026-05-27 实跑)
Case 1: 时间查询(1 步通)
Q: 现在几点?今天星期几?
[Step 1] Thought: 需要获取当前日期时间来回答问题。
Action: datetime()
Obs: 2026-05-27 01:19:01 Wednesday
[Step 2] Final Answer: 当前日期时间是 2026 年 5 月 27 日星期三。
Case 2: 数学计算(踩坑 + 自愈 4 步)
Q: 帮我算一下 sqrt(144) + 3 * pow(2, 5) 是多少?
[Step 1] Action: calculator(sqrt(144) + 3 * pow(2, 5))
Obs: ERROR: '(' was never closed
↑ 真实 bug:我的正则 [^)]*) 在第一个 ) 就停了,args 变成 'sqrt(144'
[Step 2] Action: kb_lookup(vllm)
↑ 模型一开始走偏,试图 KB 查表
[Step 3] Thought: 知识库没有,我手动算: sqrt(144)=12, 3*pow(2,5)=96
Action: calculator(12 + 96)
Obs: 108
[Step 4] Final Answer: 108 ✅
这就是真实 Agent 现场:工具正则有 bug → 模型靠多步分解绕过 → 最终答对。生产里要么修正则(用括号配对栈),要么用 OpenAI function calling 让模型直出 JSON args 避免歧义。
Case 3: 知识库查询(1 步通)
Q: vLLM 是什么? 用知识库查一下.
[Step 1] Action: kb_lookup(vllm)
Obs: vLLM 是 UC Berkeley 提出的高吞吐 LLM 推理引擎...
[Step 2] Final Answer: vLLM 是 UC Berkeley 提出的高吞吐 LLM 推理引擎...
8. 生产化要点 Checklist
8.1 RAG 上线前必做
- [ ] embedding 模型选对语言(中文别用 MiniLM)
- [ ] chunking 至少试 3 种(固定 / recursive / semantic),A/B
- [ ] top_k 调到 5-10,加 reranker 截到 3
- [ ] 加 citation(prompt 让模型标 [1][2][3]),否则不可追溯
- [ ] 检索失败兜底(没召到 → 走 LLM 原生回答 + 标注"未基于知识库")
- [ ] chunk 去重(同一段被切多个有重叠时)
- [ ] 多租户隔离(metadata filter / 单 tenant 单 collection)
- [ ] embedding 漂移监控(模型升级要回填重建索引)
8.2 Agent 上线前必做
- [ ] 超时 + max_steps 上限(防止死循环烧 token)
- [ ] 工具沙箱(
calculator一定要限制 eval scope,见本 demo) - [ ] 失败重试 + 降级(工具异常时模型要能 graceful 处理)
- [ ] 审计日志(每个 Action / Observation 全打)
- [ ] 成本上限(单会话 token 数封顶)
- [ ] 危险工具二次确认(发邮件/付钱/删文件 → 必须 human-in-the-loop)
- [ ] MCP 化:把工具暴露成 MCP server,不同 agent 复用同一套工具
8.3 性能要点
| 优化 | 效果 |
|---|---|
| Async batch retrieval | 10+ query 并发,QPS ×8 |
缓存 embedding(hash(query) → vec) | 重复查询零开销 |
缓存 LLM 结果(hash(prompt) → answer) | 常见 Q 直接返回 |
| Stream output | TTFT 从 2s → 200ms,UX 飞跃 |
| KV cache reuse(prefix sharing) | system prompt 复用,prefill 时间 ×0.3 |
9. 一句话总结
RAG 解决"模型不知道" · Agent 解决"模型不能动手" · MCP 让工具跨 agent 复用 · 底层全是 ReAct loop。
选向量库看规模,选 embedding 看语言,选 reranker 看预算,选 agent 框架看团队习惯。
附录:venv 信息
路径: /opt/bonus2/venv (479 MB)
Python: 3.10
关键包:
chromadb 1.5.9
openai 2.38.0
onnxruntime 1.x (chromadb 自动装,~88MB ONNX model 首次下载)
numpy / protobuf / tokenizers / pydantic (transitive)
执行:
source /opt/bonus2/venv/bin/activate
python3 /opt/bonus2/rag_demo.py
python3 /opt/bonus2/agent_demo.py
✅ 全部代码已在 gpu1 上实跑通过,本文每段输出皆为真实运行截取,未做美化。