RAG 工程与检索系统设计
第22章 从零构建一个生产级 RAG 系统
最好的 RAG 是那个能上线、并且能一直迭代下去的 RAG。这一章给出从 MVP 到 DAU 10 万的落地蓝图。
本章要点
- 生产 RAG 的参考架构:三条链路 + 四大服务——不绕不弯的落地蓝图
- 四阶段交付路线:MVP(2 周)→ 离线稳定(1 个月)→ 反馈闭环(2 个月)→ 持续优化——每阶段有明确 done 标准
- 关键代码骨架:ingest pipeline、检索链路、生成链路、评估 pipeline 四个核心 loop
- 从 0 → DAU 10 万 的演进:架构不变,只在每个组件上逐步加能力
- 团队配置:1-2 人起步够用,> 15 人时走专业化分工
22.1 参考架构
前 21 章的机制汇总到一个系统里,长这样:
flowchart TB
classDef app fill:#fef,stroke:#808
classDef svc fill:#def,stroke:#06c
classDef store fill:#efe,stroke:#080
classDef infra fill:#fed,stroke:#c60
subgraph CLIENT["客户端"]
UI[Web / App / API Caller]:::app
end
subgraph GATEWAY["API Gateway"]
AUTH[鉴权 + user_context 解析]:::svc
RATE[限流]:::svc
end
subgraph ONLINE["在线检索服务"]
QU[Query Understanding]:::svc
RET[Hybrid Retriever]:::svc
RR[Reranker]:::svc
PACK[Context Packer]:::svc
GEN[LLM Gateway]:::svc
CITE[Citation Verifier]:::svc
end
subgraph OFFLINE["离线索引服务"]
PARSE[Parser]:::svc
CHUNK[Chunker]:::svc
EMBED[Embedding Worker]:::svc
INDEX[Index Builder]:::svc
PUBLISH[Version Publisher]:::svc
end
subgraph FEEDBACK["反馈 / 评估"]
LOG[Trace Logger]:::svc
EVAL[Evaluator]:::svc
MEM[Memory Store]:::svc
end
subgraph STORES["存储层"]
VDB[(向量库 Qdrant)]:::store
PGDB[(Postgres metadata)]:::store
REDIS[(Redis cache)]:::store
S3[(对象存储)]:::store
OTEL[(Observability)]:::store
end
UI --> AUTH --> RATE --> QU
QU --> RET --> RR --> PACK --> GEN --> CITE --> UI
RET --> VDB & REDIS
GEN --> LOG
LOG --> EVAL
LOG --> MEM
PARSE --> CHUNK --> EMBED --> INDEX --> PUBLISH
PUBLISH --> VDB
四大服务群:
- Gateway:鉴权 / 限流 / user_context
- Online 检索链路:Query Understanding → Retrieve → Rerank → Pack → Generate → Cite
- Offline 索引链路:Parse → Chunk → Embed → Index → Publish
- Feedback:Logger → Evaluator → Memory
前 21 章每一章对应这架构里的某一段——这一章是给架构填具体选型和代码骨架。
22.2 关键组件选型:一套不出错的默认
下面这张表给的是 MVP 阶段的默认起点,不是"最优解"。用它的正确方式是:先按它跑通,之后每一格都用自己的数据重新验证。
需要提醒的是具体的模型名和版本会过时——表里写的是核实时(2026 年 9 月,见专栏首页的"版本与核实口径")各类别的常见选择,用之前请查当期的官方文档和榜单:
| 组件 | 推荐 | 备注 |
|---|---|---|
| Web 框架 | FastAPI / axum | Python / Rust 二选一 |
| Embedding | bge-m3(自托管)/ text-embedding-3-small(API) | 中文选前 |
| 向量库 | Qdrant(中小)/ pgvector(复用 DB) | 第 11 章 |
| BM25 | Qdrant 内置 / Elasticsearch | 选简单 |
| Rerank | bge-reranker-v2-m3 | 第 14 章 |
| LLM | Claude Sonnet / GPT / DeepSeek(均取当期代次) | 按质量成本选;本专栏代码示例里出现的具体 model id 只为把算式跑通,不等于推荐你用那一版 |
| Query rewrite | Claude Haiku 4.5 | 小模型够用 |
| 评估 | ragas + 自建 gold set | 第 20 章 |
| 可观测性 | OpenTelemetry + Grafana | 标准栈 |
| 缓存 | Redis | query / embedding / rerank 结果(第 21 章 §21.3、第 14 章 §14.11) |
| 消息队列 | Kafka / Redpanda | 离线 pipeline |
| 调度 | Prefect / Dagster | 离线 pipeline |
这套组合 MVP 跑得起来,扩展性也够。大规模时是在同一架构上换组件,而不是重写——这正是下面 §22.5 讲的演进方式。
选型时别在 MVP 阶段就纠结"哪个最好":这个阶段真正的瓶颈是"有没有 gold set 能判断好坏"(第 20 章),不是选了哪个向量库。等有了评估能力,换不换组件、换了好没好,才有据可依。
22.3 四阶段交付路线
阶段 1:MVP(2 周)
目标:能跑、能用、有 demo。
交付物:
- 离线索引:一次性脚本,从 S3 读 100 份样本文档 → chunk → embed → 入 Qdrant
- 在线检索:FastAPI 接口,hybrid 检索(两路各取 10 条,RRF 融合后取 5 条)→ 喂 Sonnet → 返答案
- 无 rerank、无 Contextual Chunk、无 rewrite
- 简易前端(Streamlit / 一个 React 页面)
Done 标准:内部 5-10 人连续使用一周,采集 50+ badcase。
代码骨架(本章的代码都是骨架:parse、chunk、embed、rrf、pack 这类函数和 qdrant、es、llm 这类客户端都是占位,要换成你自己的实现):
# ingest_mvp.py
async def ingest_one(doc_path):
doc = await parse(doc_path) # unstructured.io
chunks = chunk(doc, size=400, overlap=80)
for c in chunks:
c.vec = await embed(c.text)
await qdrant.upsert(chunks)
# retrieve_mvp.py
async def rag(query, user):
q_vec = await embed(query)
dense_hits = await qdrant.search(q_vec, filter=user_filter(user), limit=10)
bm25_hits = await es.search(query, filter=user_filter(user), limit=10)
merged = rrf([dense_hits, bm25_hits], k=60, top_k=5)
prompt = pack(query, merged)
answer = await llm.chat(prompt)
return {"answer": answer, "citations": [c.id for c in merged]}
把占位函数补齐,核心代码也不到 100 行,够跑 MVP。
阶段 2:离线稳定化(1 个月)
目标:知识库能稳定每日更新、能回滚、能扩展。
交付物:
- 离线 pipeline 用 Prefect / Dagster 编排,分段可重入(第 8 章 §8.5)
- 版本化索引、原子切换、蓝绿索引保留一段观察期以便回滚(第 4 章 §4.2、§4.10)
- 增量更新基于 content_hash(第 8 章 §8.2 方案 C)
- 基础监控(解析成功率、索引规模、QPS、延迟)
- 软删除 + 删除走独立事件(第 8 章 §8.2;§8.10 的"
deleted=true幽灵文档"事故就是反例)
Done 标准:回滚脚本演练过一次;交付后连续 4 周周度更新零事故("1 个月"指建设周期,这 4 周观察期可与阶段 3 的建设并行)。
阶段 3:反馈闭环和评估(2 个月)
目标:可观测、可定位、可迭代。
交付物:
- OpenTelemetry trace(第 3 章 §3.9)
- gold set 500 条,每日自动回归
- ragas 评估接入,faithfulness + answer_relevancy 每日看板
- 用户 feedback 收集(thumbs up/down + 自由文本)
- 四层评估指标(第 20 章)完整看板 + 告警
- rerank(bge-reranker-v2-m3)上线(hybrid 在 MVP 阶段已经有了)
Done 标准:每周能给出"本周 badcase 主要是什么类型、占比多少"的归因报告。
阶段 4:持续优化(永久)
目标:按归因报告迭代,质量和成本持续改善。
可能的优化路径:
- Query Rewrite 和 HyDE(第 15 章)
- Contextual Chunk(第 6 章 §6.6)
- Rerank 微调(第 14 章 §14.5)
- Prompt cache 和模型分层(第 21 章)
- Agent Memory(第 18 章)
- GraphRAG(第 19 章,如果有全局 / 多跳需求)
每次优化走归因 → 假设 → A/B → 决策 → 部署五步。数据驱动,不靠感觉。
22.4 关键代码骨架:四个 Loop
生产 RAG 的核心是四个 loop,每个 loop 独立可部署,独立演化。
Loop 1:Ingest Pipeline
# ingest.py (简化)
@flow(name="rag-ingest")
async def ingest_flow(doc_uris: list[str]):
for uri in doc_uris:
try:
# 1. 解析
doc = await parse_task(uri) # cached by content_hash
# 2. 分块
chunks = chunk_task(doc, strategy="structured", fallback="fixed")
# 3. Embedding (content_hash 复用 cache)
for c in chunks:
c.vec = await embed_task(c.text_with_context)
# 4. Upsert + 清理旧 chunk
await upsert_chunks(doc.id, chunks)
await cleanup_orphan_chunks(doc.id, current_chunk_ids=[c.id for c in chunks])
# 5. 发 event
await event_bus.publish("doc_indexed", {"doc_id": doc.id, "chunk_count": len(chunks)})
except Exception as e: # task 层的重试(如 Prefect 的 retries=3)耗尽后才会到这里
await dlq.put(uri, e)
关键点:每一步都可缓存,短暂失败先重试、重试耗尽再入 DLQ(第 8 章 §8.5),不阻塞其他文档。
Loop 2:Online Retrieval
# retrieve.py
import asyncio
async def retrieve(query: str, user_ctx: UserContext) -> list[Chunk]:
# 0. 先查缓存:key 至少含 query、user_ctx 和索引版本(第 21 章 §21.3)
key = cache_key(query, user_ctx)
if (cached := await cache.get(key)) is not None:
return cached
# 1. Query understanding(串行,排在检索之前)
rewritten = await rewrite_task(query, user_ctx) # 含 disambiguation + HyDE
# 2. 并行召回;原 query 也单独走一路,rewrite 跑偏时兜底(第 15 章 §15.12 防线 1)
dense_hits, orig_hits, bm25_hits = await asyncio.gather(
dense_retrieve(rewritten, user_ctx, top_k=50),
dense_retrieve(query, user_ctx, top_k=50),
bm25_retrieve(rewritten, user_ctx, top_k=50),
)
# 3. RRF 融合
# k=60 是自己实现融合时的常用取值;如果这一步改成走向量库内置的
# fusion(§22.2 推荐的 Qdrant 内置 BM25 就是这条路),k 的默认值
# 未必是 60——Qdrant 默认 k=2,rank 起点各家也不一样,见 §13.2 的对照表
fused = rrf([dense_hits, orig_hits, bm25_hits], k=60, top_k=30)
# 4. Rerank
top_k = await rerank(query, fused, top_k=5)
# 5. 写缓存,5 分钟
await cache.set(key, top_k, ttl=300)
return top_k
关键点:并行召回(asyncio.gather),统一的 user_ctx filter 下推到向量库和 BM25。
Loop 3:Generation + Citation
# generate.py
async def generate(query: str, chunks: list[Chunk], user_ctx: UserContext) -> Response:
# 1. Context pack(U 形排列 + 结构化)
packed = pack_context(chunks, strategy="u_shape")
# 2. Prompt 构造(cacheable prefix + dynamic suffix)
prompt = build_prompt(
system=SYSTEM_PROMPT, # cacheable
few_shot=FEWSHOT, # cacheable
context=packed, # dynamic
query=query, # dynamic
)
# 3. LLM 调用(流式)
# 注意:真实 SDK 里缓存标记是打在「内容块」上的(在 system / few-shot
# 这些稳定前缀的末尾设断点),不是这样一个请求级参数——这里是骨架简写,
# 实际写法见 §21.3 和厂商文档。另外前缀短于模型的最小可缓存长度时
# 不会被缓存、也不报错(§16.7),只有 system + 几条 few-shot 往往不够长
stream = llm.stream(prompt)
# 4. citation 验证(后处理)
answer, citations = await parse_answer_and_citations(stream)
grounding = await verify_grounding(answer, chunks, citations)
# 5. 组装 response
return Response(
answer=answer,
citations=citations,
confidence=compute_confidence(grounding, chunks),
trace_id=current_trace_id(),
)
关键点:prompt 结构化(可 cache + 引用锚点),grounding 后处理验证。
Loop 4:Evaluation
# eval.py
@schedule(cron="0 3 * * *") # 每天凌晨 3 点(示意:实际用编排器的定时调度,如 Prefect deployment 的 cron、Dagster 的 schedule)
async def daily_regression():
results = []
for qa in load_gold_set():
actual = await rag_full_pipeline(qa.query, qa.user_ctx)
# ragas_eval 是自己封装的:chunk id 级的召回指标自己算,faithfulness /
# answer_relevancy 交给 ragas——ragas 只看文本,不认 chunk id(第 17 章 §17.4)
metrics = await ragas_eval(
query=qa.query,
gold_chunk_ids=qa.gold_chunks,
actual_chunk_ids=[c.id for c in actual.retrieved],
actual_answer=actual.answer,
context=actual.packed_context,
)
results.append(metrics)
# 上报指标
emit_metrics(aggregate(results))
# 告警:阈值是你自己定的工程策略(第 20 章 §20.5),
# 用「相对基线的跌幅」比用绝对值更稳,因为 judge 一换分数就整体平移
if mean(results, "faithfulness") < BASELINE_FAITHFULNESS - TOLERANCE:
alert("Faithfulness 相对基线下跌超出容忍范围")
关键点:每日跑,多指标聚合,按相对基线的偏移告警而不是按某个写死的绝对分数。
22.5 从 0 → DAU 10 万 的演进
架构不变,每个组件逐步升级:
这里不给各档的月成本数字——它由你的 query 分布、context 长度、模型选择决定,同样 DAU 的两个项目账单能差一个数量级。要估自己的账,用第 21 章 §21.10 的算式:日请求数 × 单次成本,单次成本按你的 token 用量和当期单价算。
各档真正确定的是"该加什么能力":
种子期
- 复用现有 Postgres(pgvector)+ 单机应用服务
- 简单 hybrid,先不上 rerank
- 这个阶段的关键产出不是性能,是 gold set 和评估脚本
增长期
- 检索量上来后换专用向量库,加 reranker
- Redis 缓存 query 和 embedding 结果(第 21 章 §21.3 的 L1、L2)
- 离线 pipeline 用编排工具管起来,不再是脚本
规模期
- 向量库上集群,索引开始考虑量化压缩(§21.8)
- rerank 微调(第 14 章 §14.5)
- prompt cache 上线(前提是固定前缀够长、超过模型的最小可缓存长度,见 §16.7、§21.10)
- 离线 pipeline 多 worker
大规模
- 向量库分片,Embedding / Rerank 拆成独立服务集群
- 多区域部署(合规驱动,见 §22.14 的数据驻留)
- 按需引入 Agent Memory(第 18 章)、GraphRAG(第 19 章)
升级的触发条件是指标,不是 DAU:向量库该扩容的信号是 §21.4 那条饱和曲线的拐点被逼近,不是"用户数到了某个整数"。
每一档都不是推倒重来,是在同一架构上加组件、升级配置——这正是参考架构的价值。
22.6 团队配置演化
- 1-2 人(MVP):全栈工程师 + 产品
- 3-5 人(稳定期):+ 数据工程师(专门维护离线 pipeline)、+ ML 工程师(微调 rerank)
- 6-10 人(成熟期):+ 可观测性工程师、+ 安全 / 合规工程师
- > 15 人:按链路分组(online / offline / eval),专业化分工
团队小时不要乱分工——全栈搞定一切推进快。人多后按链路拆组,每组有明确 owner。
22.7 第一天的 checklist
从零启动一个生产 RAG 项目,第一周该做的:
- 明确业务目标(答对率、延迟、成本预算各自的硬约束)
- 锁定知识库范围(什么文档、多大规模、更新频率)
- 选栈(按第 22.2 节的推荐选)
- 搭 MVP pipeline(100 份文档、一条 end-to-end)
- 构造 50 条 gold set(真实用户问过的或人工编的)
- 写评估脚本(ragas 最小集)
- 画监控看板(最少 QPS、延迟、成本、faithfulness)
- 写事故响应 runbook(格式见 §22.15 的 playbook 模板)
前 8 项做完,你有一个能迭代的基础。少任何一项,都会成为后续的瓶颈。
22.8 常见失败模式和解药
生产 RAG 项目常见的失败模式:
失败 1:追求 SOTA 忽略稳定——项目组花 2 个月实现 GraphRAG / Agent Memory,产品还没上线。解药:先出 MVP 收真实反馈,高级技术按需引入。
失败 2:没有 gold set 盲优化——改了 prompt、改了 chunk、改了 embedding,都"感觉更好"——但没回归验证。解药:gold set 优先级 = P0,任何改动先过回归。
失败 3:忽视离线链路——团队所有精力在优化在线链路,离线 pipeline 用脚本凑合——3 个月后知识库更新一次出 bug。解药:阶段 2 的工程投入不能省。
失败 4:过早优化成本——MVP 阶段就纠结"这行代码会不会多花 $0.001"。质量都没到,省钱白搭。解药:先质量再成本——先证明系统有用,再降费用。
失败 5:不用真实数据——Demo 用 few-shot 精选例子跑,上线用真实用户 query 就崩。解药:MVP 开始就用真实数据(脱敏后)评估。
失败 6:没有回滚机制——索引 / 模型 / prompt 改完没办法回滚——出事故只能硬扛。解药:每项改动都有明确的回滚路径,部署前演练一次。
22.9 性能回归的常见触发器
生产 RAG 上线时间一长,性能回归很常见。提前知道触发器,减少事故:
- 知识库自然漂移:新文档质量下降,或旧文档失去时效相关性。召回命中率慢慢降,用户抱怨变多
- 用户查询模式变化:产品推新功能后,用户问法从"怎么用 A"转向"A vs B 的区别",多实体对比类 query 的短板就会暴露(第 13 章 §13.16)
- Embedding 模型变更:换模型或换到新版本后向量空间变了,老索引和新 query 不兼容(第 9 章 §9.13)。调 API 时要钉死带版本的模型名,并把它写进缓存 key 和索引元数据
- 依赖库升级:LangChain / LlamaIndex 主版本升级经常改 API;某个依赖 bump 可能改变 chunking 行为
- 部署环境变化:从 A100 迁 H100、CPU 容器换架构——推理数值精度略有差异,边缘 case 表现变化
- 数据源污染:爬虫引入一批错误文档(网页改版、PDF 解析器 bug),悄悄进索引后污染检索
预防靠持续的自动化评估(第 20 章)+ 关键依赖锁版本 + 变更审批流程。没有这三道防线,早晚中招。
22.10 生产 RAG 的组织学
技术之外,组织形态影响 RAG 项目能否持续。
产品 vs 工程的张力
RAG 有两条价值路径:
- 产品路径:做用户体验、算对的答案、降低挫败感
- 工程路径:压延迟、降成本、提吞吐
两条路经常冲突——为了 3 个点的答对率加 500ms 延迟值不值?没有产品经理 + 工程师共识,项目很容易卡在决策上。
成熟团队的做法:把三元约束(质量/延迟/成本)量化成指标,给每个指标定硬门槛。越过门槛的改动自动合入,跨门槛的停下来讨论。这比"感觉能不能上"高效。
反馈机制的组织成本
第 20 章讨论的三层评估(离线回归 / 在线采样 / 人工抽样)都需要持续人力投入:
- 人工抽样每周 4-8 小时
- Gold set 季度刷新 1-2 天
- Badcase 归因 review 每月一次 4 小时
这些工作没人干,评估就流于形式,数据质量会退化。团队里必须有明确的 owner 负责评估 pipeline,不能靠"大家有空看看"。
可持续的工作节奏
RAG 是马拉松,不是冲刺。推荐节奏:
- 周一 status review(看数据、定本周优先级)
- 周三 deep work 日(不开会、只做开发)
- 周五 deploy + 复盘
这套节奏让质量改善是连续的,而非大起大落。
22.11 和现有业务系统的集成
生产 RAG 很少是孤立服务——需要和现有业务栈集成。集成点和对应的设计决策:
集成点 1:鉴权系统
RAG 不自己做登录——继承业务的鉴权:
- SSO / OAuth:企业客户统一身份(Okta、Azure AD、飞书 / 钉钉)
- JWT 传递:业务 gateway 签 JWT,RAG 服务验签读 user_context
- 多租户边界:从 JWT 读 tenant_id,下推到向量库的租户隔离机制(Qdrant 的
is_tenantpayload index、Milvus 的 partition key、pgvector 的分区表,见第 7 章 §7.5、第 11 章 §11.12)
不要造轮子,复用现有鉴权——省下的不只是实现工作量,更重要的是合规审计可以直接继承:企业已经为现有鉴权系统做过的等保 / SOC2 / ISO 审查,鉴权这一块不必为 RAG 再审一遍。
集成点 2:客服 / 工单系统
客服场景 RAG 的典型集成:
- 客户发消息 → 客服系统路由 → RAG 答初稿 → 客服人工 review → 发出
- 命中率高的问题 → 自动闭环,不用人工
- 命中率低的问题 → 升级人工客服 + trace 用于改进
集成靠 webhook 或 event bus——RAG 订阅"新消息",发"候选答案"。
集成点 3:CMS / 知识库源
文档更新要驱动 RAG 索引更新:
- CMS webhook:Confluence / Notion / SharePoint 改动 → 推 event 到 RAG 队列
- 文件系统监听:S3 bucket 事件、OSS 对象存储通知
- 定期全扫:兜底防止 webhook 丢
集成点 4:产品 UI
RAG 的响应 UI 不是单独页面——通常嵌入产品某处:
- 客服聊天窗
- 帮助中心搜索框
- 产品内侧边栏 AI 助手
- IDE 插件(代码 RAG)
UI 集成要关注:流式渲染 / 引用高亮跳转 / 反馈按钮 / 对话历史持久化。这些属于前端工程,但 API 设计要支持。
集成点 5:可观测性栈
复用企业已有监控:
- 延迟 / 错误率进 Grafana + Prometheus
- 业务埋点进分析平台(Amplitude / Mixpanel / GrowingIO)
- 错误进 Sentry
- Trace 进 Jaeger / Tempo
不要为 RAG 单独搭监控栈——维护不起。
22.12 长期维护的心智模型
生产 RAG 上线后的工作不是"完成了"——是"开始了"。三个心智模型:
- 它是一个生命体:文档更新、用户行为变化、LLM 升级——系统要跟着变
- 它是一个团队项目:一个人搞不定长期运维——必须有团队 + 明确 onboarding 文档 + 交接流程
- 它是一个产品:不只是技术栈,还要看用户价值——功能要迭代,场景要扩展
长期健康的 RAG 有如下特征:
- 每周有明确的迭代目标(新 chunking 策略 / 新 rerank 模型 / 新 domain 接入)
- 每月有一次完整的归因 review
- 每季度有一次架构健康检查(延迟漂移、成本漂移、质量漂移)
- 每半年评估一次栈的新组件(新 Embedding 模型、新向量库版本、新 rerank 方案)
22.13 从这个专栏出发
这个专栏讲完了 RAG 的主要工程机制——但没有任何一份材料能讲完所有细节。真正的生产经验来自你在自己项目里的:
- 每一次 badcase 的归因
- 每一次架构选型的权衡
- 每一次线上事故的复盘
- 每一次和用户对话里发现的盲点
22.14 合规、审计与灾难恢复
RAG 系统处理的是企业最敏感的两类数据——内部知识和用户对话。这两样的合规和容灾在 POC 阶段可忽略,生产上线时是硬要求。这一节把前面章节散落的合规话题(第 7 章权限、第 18 章 memory 隐私、第 8 章增量)整合成上线前的综合视角。
合规框架的四个维度
flowchart TB
classDef gov fill:#fed,stroke:#c60
classDef tech fill:#def,stroke:#06c
classDef ops fill:#efe,stroke:#080
G[合规要求]:::gov
G --> D1[数据分级与访问控制]:::tech
G --> D2[审计日志]:::tech
G --> D3[用户权利 GDPR/PIPL]:::tech
G --> D4[行业/地区专项 HIPAA/等保/金融/AI Act]:::ops
D1 --> E1[chunk 级 ACL + 多租户隔离]
D2 --> E2[retrieval/generation 全链路 trace]
D3 --> E3[可删除/可导出/可追溯]
D4 --> E4[数据驻留/加密/审计通过]
- 数据分级:public / internal / confidential / restricted 四级,chunk 级别打标,权限下推到向量库 filter(第 7 章)
- 审计日志:谁在什么时间检索了什么 chunk、最终 LLM 看到了什么 context、生成了什么答案
- 用户权利:GDPR / PIPL 要求可删除、可导出、可审计——memory 和对话历史是重灾区
- 行业 / 地区专项:医疗 HIPAA、国内等保(网络安全等级保护)、各辖区的金融监管规定、欧盟 AI Act 等各有细则
生产上线前这四项都要有明确的实现负责人,不能全部"以后再说"。
审计日志的最小字段集
无论什么合规框架,审计日志至少要能回答 "谁在什么时候问了什么、看到了什么、答了什么":
| 字段 | 为什么必要 |
|---|---|
trace_id |
串联同一请求的 retrieval / generation |
user_id + tenant_id |
定位"谁"、多租户边界 |
query + timestamp |
原始问题 + 时间 |
retrieved_chunk_ids + source_docs |
LLM 看到了哪些内部资料 |
prompt_hash + model + prompt_cache_hit |
生成阶段的决策证据 |
answer + citations |
系统给出的最终答案 |
user_feedback |
用户事后反馈(如有) |
日志进对象存储(S3 / OSS)+ 索引进 OpenSearch / ClickHouse——按需查询。
保留周期由法务给,不是工程拍的。注意 GDPR 并没有规定一个统一的保留期限,它要求的是"存储期限不超过实现处理目的所必需"(存储限制原则)——也就是说你要能说清"为什么留这么久";而行业法规才会给出明确年限(例如美国 HIPAA 对相关文档要求保留六年,金融行业各辖区另有规定)。别照抄任何一份年限表,去问自己业务所在辖区的法务。
日志本身也含敏感信息——日志的访问也要审计,这不是套娃,是合规底线。
用户权利的工程落地
GDPR / PIPL 赋予用户的权利里,有三项直接对应工程能力:
-
删除权(right to be forgotten):用户请求 → 清除所有相关数据
- 包括 memory facts、对话历史、日志里的 user content、以及向量索引里的对应条目(这一项最容易漏——删了元数据但向量还在,检索仍能命中)
- GDPR 对这类请求的响应期限是一个月(复杂情形可延长,需告知用户)——注意别和"72 小时"混淆,那是数据泄漏的通报期限,两件事
- 工程实现:给每个用户一份 data map,删除时按 map 一次性清扫
-
导出权(data portability):用户获取自己的所有数据副本
- 包括历史对话、memory、profile
- 通常 JSON 或 CSV 格式,打包下载链接
- 响应期限:GDPR 下同样是一个月
-
访问权(right of access):用户能查看系统持有自己的哪些数据、用于什么目的、提供给了哪些接收方
- 企业客户往往要得更多:"谁在什么时候访问过我的数据"("给我看谁用 AI 助理查过我的合同")——这已超出法定最低要求,多来自合同约定
- 需要预留访问日志的用户可见视图
这三项如果事后补,会发现同一个用户的数据散落在向量库、PG、Redis、S3、日志系统各处,而且各系统的主键还不一定对得上。初始架构设计时就要统一 user_id 路径——设计阶段这是一句约定,事后补则要给每个存储都加一遍反查索引,还要回填历史数据。
灾难恢复:RPO 与 RTO 设计
灾难恢复计划要明确两个指标:
- RPO(Recovery Point Objective):事故后能容忍丢失多少数据。常见的目标设定:15 分钟到 1 小时
- RTO(Recovery Time Objective):事故后多久能恢复服务。常见的目标设定:30 分钟到 4 小时
达成这些目标靠三层备份:
| 层级 | 内容 | 恢复速度 | 成本 |
|---|---|---|---|
| L1 元数据 | Postgres(chunk metadata / user profile) | 分钟级 WAL 流复制 | 低 |
| L2 向量索引 | Qdrant / Milvus snapshot | 比从头重建快,随 snapshot 大小增长 | 中 |
| L3 原始文档 | S3 跨区复制 | 分钟级 | 低 |
L2 是重点——从头重建向量索引要重跑 embedding 和建索引,耗时随规模增长(按第 8 章 §8.6 的算式估)。策略是保留最近 3 个 snapshot(日级),事故时拉最近一份恢复。业务数据用 L1+L3 兜底,L2 丢了大不了重建。
灾难演练不能省
备份有但没演练过 = 相当于没备份。典型事故模式:备份 6 个月里一直跑,但从没验证过能恢复——真到需要时发现 snapshot 格式不兼容,恢复脚本半年没更新已失效。
生产 RAG 的演练节奏:
- 月度:单组件恢复(向量库从 snapshot 恢复,PG 用 WAL 做时间点恢复)
- 季度:完整链路恢复(从全站宕机到恢复服务)
- 年度:多区域故障切换(primary region 整挂时切 secondary)
演练结果写进 runbook——下次事故时按 runbook 操作,而不是现场推理。
数据驻留与跨区域
欧盟客户的数据不能出欧盟,国内部分客户的数据不能出国——数据驻留要求在架构早期就决定:
- 单区域部署 + 多租户共享存储:简单,但不支持全球客户
- 多区域隔离部署:每个区域独立全栈(向量库、Redis、PG),用户数据 sticky 到所属区域
- 混合模式:元数据跨区,敏感数据本区——工程复杂
多区域的难点不是部署,是数据一致性 + 版本同步:知识库更新时所有区域索引要一致。主从复制 + 事件总线是主流方案。
合规的反模式
生产 RAG 里常见的合规反模式:
- "先上线再补合规":初期不打 audit log、不做权限分级——等合规审查才补,代价是重写半个系统
- 权限只在 UI 层:后端 API 不做 user_ctx filter 下推,聪明用户绕过 UI 拿到别人数据
- 日志里留明文 PII:身份证、银行卡直接写 log——合规审计第一条就挂
- 备份从不演练:以为 backup 脚本跑了就稳——真出事时发现不能恢复
生产上线前做一次合规 checklist 审查(ISO 27001 或公司内部 checklist)。和事后补的区别不在于工作量的倍数,而在于性质:上线前是改设计,上线后是在有真实用户数据的系统上改设计——后者要停机、要迁移、要通知,还可能已经踩了线。
22.15 事故响应 playbook:RAG 的 on-call 手册
§22.8 列了失败模式,§22.9 讲了性能回归——都是事前防御。这一节补上事中响应:线上出事了怎么办。RAG 的事故特性和传统 Web 服务不同——一个 RAG 事故可能是检索错、生成错或两者组合,用户感知是"答案不对"而非"服务 500"。没有专门的 playbook,on-call 同学只能现场摸索,MTTR 容易拉长。
事故的典型分类
flowchart TB
classDef sev fill:#fed,stroke:#c60
classDef cat fill:#def,stroke:#06c
E[RAG 事故分类]
E --> S1[P0 合规/安全]:::sev
E --> S2[P1 服务不可用]:::sev
E --> S3[P2 答案质量降]:::sev
E --> S4[P3 成本异常]:::sev
S1 --> C1[数据泄漏 / 越权访问 / 法务引用错]:::cat
S2 --> C2[LLM API 全挂 / 向量库宕 / 高错误率]:::cat
S3 --> C3[recall 跌 / faithfulness 降 / 大量幻觉]:::cat
S4 --> C4[token 消耗翻倍 / 成本告警]:::cat
按严重度的响应时限
| 级别 | 响应时限 | 处置团队 | 通知范围 |
|---|---|---|---|
| P0 | 15 分钟 | 核心团队 + 安全 + 法务 | CTO + 合规 + 相关业务 |
| P1 | 30 分钟 | on-call + 团队 lead | 团队全员 + 业务接入方 |
| P2 | 2 小时 | on-call | 团队内部 |
| P3 | 24 小时 | 指派专人 | 定期 review |
P0 事故不等"定位清楚再动手"——先立即止损(关 feature flag、切 fallback、封用户等),事后再慢慢查。
响应三阶段
检测:监控指标 + 告警 + 用户反馈三源头。关键是不要漏报——降级 + 告警宁可假阳性多,也别出假阴性。
定位:按 RAG 链路从尾到头查:
- 用户看到的答案 → 查 trace
- Trace 里的 prompt 和 context → 看检索结果合理吗
- 检索结果 → 看 query 是否合理
- query → 看请求参数
多数事故卡在前两步就能定位。第 3、4 步属于深度事故。
修复:分两档——
- 紧急缓解:关 feature、切 fallback、回滚版本——分钟级
- 根因修复:找到 bug、补代码、加测试——几天级
两档并行,缓解优先。
Playbook 模板
每类常见事故都应该有 playbook。例子 "LLM API 高错误率":
# Playbook: LLM API 错误率 > 5%
## 症状
- generation_error_rate > 5% 持续 2 分钟
- 用户报告"答不出来"
## 立即动作(5 分钟内)
1. 确认告警真实性:看 Grafana 的 error 分布
2. 切到 fallback 模型(Sonnet → Haiku):`config.llm.primary = "haiku"`
3. 通知业务接入方"可能质量降级"
## 深度定位(30 分钟内)
1. 查 error 类型:429 / 500 / timeout
2. 查 Anthropic status page
3. 查是否刚上线了新 prompt 或模型参数
4. 查是否被上游流量打崩(QPS 突增)
## 可能根因
- Anthropic 端故障 → 等恢复 + fallback
- rate limit 触发 → 联系 sales 扩额度 + 限流
- 新 prompt 导致返回异常 → 回滚 prompt
- 上游流量异常 → 排查上游
## 回滚步骤
- Prompt 回滚:`git revert <commit>` + deploy
- 模型回滚:config 切回
- 完全失败:启动 static fallback
## 复盘要求
- 24 小时内产出 timeline
- 1 周内补修复 + test
每个 P1+ 事故都应该有对应 playbook——没有就写,事故结束就维护。Playbook 库是团队运维能力的沉淀。
常见定位套路
- recall 降、用户投诉答不准:查 embedding 服务版本、向量库索引版本、最近是否重建
- 延迟涨、但错误率正常:查下游服务(向量库、rerank)的 P99,多半某个服务变慢了
- 成本暴涨但 QPS 稳定:查 token / request 的分布,通常是 prompt 变长或 context top-k 调大
- 某个租户频繁报错:查该租户的索引 size、metadata、是否数据异常
- 某类 query 答错:query classifier 可能误路由,检查路由分布
这几条覆盖的是高频事故——先按它们过一遍,不中再往深里查。真正难的是多因叠加的事故,那种只能靠 trace 一层层剥。
事后复盘(postmortem)的要求
每个 P0/P1 事故都要写复盘文档:
- Timeline:从事故发生到完全恢复的分钟级时间线
- 根因:不是"XXX 坏了",是"为什么 XXX 坏了",五问到底
- 影响:多少用户、多少请求、多少金额、合规影响
- 做对的:这次响应里哪些机制帮了忙(告警准、playbook 有效等)
- 做错的:哪些机制没起作用、或决策慢了
- Action items:分 P0 立即修 / P1 一月内 / P2 季度内
复盘不对人——对系统和流程。目标是"下次更快更准",不是追责。追责文化会让事故被隐藏,损害长期运维。
On-call 值班的组织
- 轮值:团队人均每 4-6 周值一次周,防止疲劳
- 交接:周交接会过一遍上周事件、pending 修复项、playbook 更新
- 培训:新 on-call 先跟班 2 周,熟悉所有主要 playbook
- 权限:on-call 有"宣告降级"的权限,不用等 lead 批准
on-call 的核心诉求是"授权和 playbook"——没有这两样,on-call 同学被困在"不敢动" + "不知道动什么"的死循环。
事故驱动的系统演化
成熟 RAG 团队的演化有相当一部分是事故驱动的——很多防御机制不是设计出来的,是被某次事故逼出来的:
- 事故 → 复盘 → Action item → 代码/流程改进
- 新 playbook → 下次事故 MTTR 压缩
- Action item 积累 → 季度架构演进
"这个季度一个事故都没有"未必是好消息——它可能意味着告警不灵、或者用户根本懒得反馈。判断的方法是看事故是怎么被发现的:由监控告警发现的多,说明系统在正常工作;靠用户投诉才发现的多,说明可观测性有缺口。这个比例比事故的绝对数量更能说明团队健康度。
22.16 自建 vs 商用 vs 开源:组件选择的决策框架
§22.2 给了具体组件选型推荐——但每个组件都有自建 / 商用托管 / 开源自托管 三条路。选错一个方向,后期切换代价巨大。这节给出跨组件通用的决策框架——不只是"选哪个具体产品",而是"选哪种部署方式"。
三条路的本质区别
flowchart TB
classDef build fill:#fed,stroke:#c60
classDef managed fill:#def,stroke:#06c
classDef oss fill:#efe,stroke:#080
C[组件选择]
C --> B[自建<br/>从头写代码]:::build
C --> M[商用托管<br/>SaaS / Cloud]:::managed
C --> O[开源自托管<br/>Docker / K8s]:::oss
B --> B1[最灵活 / 最贵 / 最慢]
M --> M1[最快 / 最轻 / 锁定厂商]
O --> O1[中等 / 自运维 / 可迁移]
每条路的深层特征:
- 自建:代码 100% 自控,工程成本极高。除非是核心差异化能力,否则不建议
- 商用托管:上手快,几乎不用运维——但规模大了 TCO 可能远超自托管,且被厂商锁定
- 开源自托管:需要 DevOps 能力,但规模上去后更经济,且可迁移
按组件的推荐默认选择
| 组件 | 默认推荐 | 原因 |
|---|---|---|
| LLM | 商用 API | 模型更新快、自托管 GPU 贵 |
| Embedding | 开源自托管(小规模用商用) | 可批量、成本可控 |
| 向量库 | 开源自托管(MVP 可用托管) | 数据所有权敏感 |
| Rerank | 开源自托管 | 模型小、推理便宜 |
| BM25 | 开源(Elasticsearch / Tantivy) | 工程成熟、无需商用 |
| Memory | 商用托管(Zep / Letta) | 生态年轻、自建代价高 |
| Observability | 开源栈(Prometheus + Grafana) | 和现有运维一致 |
| Evaluation | 开源(ragas / trulens) | 框架为主、不值得商用 |
这只是默认起点——按下面决策表微调。
决策表:三个问题
问 3 个问题就能大致定方向:
-
这个组件是不是你的核心差异化?
- 是 → 可能要自建 / 定制
- 否 → 直接用现成(商用或开源)
-
规模大到自托管有 TCO 优势吗?
- < 小规模 → 商用(运维成本不划算)
- 中大规模 → 开源自托管
- 超大规模 → 可能要自建专用组件
-
数据 / 合规约束有多强?
- 强(金融 / 医疗 / 政务) → 自托管优先
- 中等 → 看区域 / 厂商承诺
- 弱(纯内部工具) → 商用省事
三个问题拍板基本不会错。
详细 TCO 对比
不给具体报价(理由同 §21.8:托管定价按维度 / 副本 / QPS 分档,各家口径不同且改得勤)。要比就自己列这个式子,把两边的当期数字代进去:
商用托管年成本 ≈ 按规模的订阅费 × 12 + 少量对接运维
自托管年成本 ≈ 机器成本 × 12 + 运维 FTE 比例 × 工程师年薪
结论的形状和 §21.19 的自托管 TCO 是同一个,因为成本结构一样:
- 商用是线性的——规模翻十倍,账单大致翻十倍
- 自托管的大头是运维人力,而人力基本是固定的——同一个人维护 100 万条和 1000 万条,工作量差别不大;机器成本虽然涨,但基数低
两条线必然相交。小规模商用赢(自托管的固定人力摊不动),大规模自托管赢。拐点在哪,把你自己的订阅报价和工程师成本代进上面两个式子解一下就知道,不需要相信任何人给的规模数字。
有一个容易漏算的项要提醒:自托管的运维 FTE 不能按 0 算,也不能只算"正常运行时"。真正吃时间的是版本升级、扩容、故障排查和数据迁移——这些是脉冲式的,平摊下来通常比直觉高。
厂商锁定的真实成本
商用路线的隐藏成本:锁定。
典型锁定形式:
- API 兼容性:换厂商 SDK 要重写接入代码(经验估算 2-4 周)
- 数据格式:Pinecone 的向量导出到 Milvus 需要格式转换
- 功能特性:某厂商独有的 filter / reranking 一旦用了,换走要找替代
- 定价:厂商涨价时你只能接受(或承担迁移成本)
锁定成本不是 0——在做商用选型时要有退出计划:
- 数据能导出吗?(能 import 其他厂商)
- API 有标准协议吗?(如 OpenAI-compatible API)
- 定价历史稳定吗?(看 2-3 年历史,有无频繁涨价)
看起来悲观,但在 3-5 年的 RAG 项目周期内,厂商可能被收购、停服、涨价——每一样都可能发生。
什么时候应该自建
自建是最激进选项——一般不推荐,但某些场景值得:
- 极度规模化:运营全球性 RAG,单位成本的 5% 优化都值
- 独有算法:用了某篇前沿论文的方法,开源实现不存在
- 合规硬要求:某些国家 / 行业不允许任何外部组件
- 核心 IP:这个组件的技术本身是公司价值
大多数 RAG 项目没有这些条件——自建只是徒增复杂度。
混合策略:分层选型
最成熟的 RAG 项目常是 混合:
- LLM:商用 API(Claude + GPT 双厂商,互相备份)
- Embedding:开源自托管(bge-m3)
- 向量库:开源自托管(Qdrant 集群)
- Rerank:开源自托管(bge-reranker)
- 其他:按需商用 / 开源
混合策略的好处:
- 成本可控(大头组件自托管)
- 核心能力不锁死(LLM 双厂商)
- 工程复杂度 balanced
迁移的真实难度
不同组件迁移难度天差地别:
| 组件 | 迁移难度 | 典型工期(经验估算,随规模差异很大) |
|---|---|---|
| LLM(Claude ↔ GPT) | 易(prompt 改、API 改) | 1-2 周 |
| Embedding 模型 | 中(需要全量重 embed、§9.13) | 2-4 周 |
| 向量库 | 中(数据导出 + schema 映射) | 4-8 周 |
| Rerank | 易(模型替换) | 1 周 |
| BM25 引擎 | 中(Elasticsearch → Tantivy 等) | 3-6 周 |
| Memory 系统 | 难(状态迁移) | 8-12 周 |
规划选型时考虑迁移难度权重——难迁移的组件选型要更保守。
更新这些决策的频率
技术格局每年变化——决策不是一劳永逸:
- LLM 市场:每季度 review(新模型 / 新定价)
- Embedding:每半年 review(SOTA 更新)
- 向量库:每年 review(新版本特性)
- 其他:每年一次
不 review 就是"被过时的决策绑架"——三年前选的组件现在可能有更好的替代。
选型的组织纪律
这些决策不能一个人拍板——典型流程:
- 小决策(单个组件):Tech Lead + 团队讨论
- 中决策(组件换厂商):Eng Manager + 架构师 review
- 大决策(整体技术栈方向):CTO + 工程 VP + 业务代表
有明确决策文档(ADR、Architecture Decision Record)——记录为什么选、考虑过什么替代、什么时候 review。没文档的决策 6 个月后没人记得为什么——再改又从头来。
22.17 RAG 项目的 KPI 设计:北极星 + 平衡指标
前 16 节讲了架构、交付、运营——但团队怎么知道项目做得好不好?技术指标很多(recall、P99、faithfulness)——哪个才是"真北"?很多 RAG 团队理不清自己的 KPI——一会儿追 recall、一会儿追延迟、一会儿追成本——没有统一方向。这节给 RAG 项目的 KPI 设计一套体系。
为什么 RAG 项目常没有北极星
flowchart TB
classDef bad fill:#fdd,stroke:#c00
classDef ok fill:#dfd,stroke:#080
R[RAG 项目常见 KPI 问题]
R --> P1[工程指标太多<br/>recall/NDCG/faithfulness/latency/cost]:::bad
R --> P2[业务指标太模糊<br/>用户满意度]:::bad
R --> P3[不同角色看不同指标<br/>各说各话]:::bad
R --> P4[工程和业务脱节<br/>recall 涨没人用更多]:::bad
R --> FIX[三层指标 + 北极星]:::ok
没有统一北极星的后果:团队 7 个人、4 个方向,每个都觉得"我的方向最对"——效率低、决策慢。
三层 KPI 结构
flowchart TB
classDef top fill:#def,stroke:#06c
classDef mid fill:#fed,stroke:#c60
classDef bot fill:#efe,stroke:#080
L1[业务层<br/>北极星]:::top
L2[产品层<br/>体验指标]:::mid
L3[工程层<br/>系统指标]:::bot
L1 --> L1a[总采纳答案数 / 月]
L1 --> L1b[节省的业务时间]
L1 --> L1c[付费 / 留存]
L2 --> L2a[答案满意度]
L2 --> L2b[CTR / 追问率]
L2 --> L2c[响应延迟]
L3 --> L3a[recall / NDCG / faithfulness]
L3 --> L3b[P99 延迟 / 错误率]
L3 --> L3c[成本 / 吞吐]
三层的关系:工程指标改善 → 产品指标改善 → 业务指标改善。任一链条断了,说明定义有问题。
北极星指标的选择
北极星是业务层唯一最重要的指标——代表项目成败。RAG 项目的典型候选:
- 客服 RAG:人工转接率(越低越好)、AHT(平均处理时间)、客户满意度
- 内部知识问答:员工月均使用次数、替代人工咨询的比例
- 研发 Copilot:代码采纳率、开发提速百分比
- 销售 assistant:辅助成交的 deal 数、销售生产力提升
共同特点:直接和业务价值挂钩,不是工程指标的代理。
平衡指标:防止走偏
单一北极星有副作用——容易被"gaming"。平衡指标防止这个:
| 北极星 | 风险 | 平衡指标 |
|---|---|---|
| 采纳率 | 系统拍用户马屁、满意度虚高 | faithfulness(答案必须基于证据) |
| 使用次数 | 刷次数、答案质量不重要 | 用户满意度 |
| 转接率 | 系统乱答、用户不转接但不满 | 后续 NPS / 复购 |
| 代码采纳 | 采纳了但 bug 多 | PR merge 率 / 后续 revert |
北极星 + 至少 2 个平衡指标 = 完整 KPI 体系。单看北极星会被游戏。
按角色的 KPI
不同角色看不同指标是正常的——但都要能链回北极星:
- 工程师:recall / P99 / cost(工程层)
- ML 工程师:faithfulness / NDCG(工程到产品)
- 产品:CTR / 满意度 / 追问率(产品层)
- 业务:采纳数 / 节省时间(业务层 = 北极星)
- CEO / 管理层:ARR / 毛利 / 留存
团队每周 review 时,每个角色看自己的层,但能说清自己指标和北极星的关系。没法说清的指标——可能不值得关注。
KPI 的演化
RAG 项目所处的时期不同,KPI 重心也不同(这里按项目整体所处的时期划分,和 §22.3 的工程交付阶段不是同一套刻度):
第 1 期 MVP(项目第 1-2 月):
- 北极星:能跑通 demo、内部能用
- 工程:基础可用性(上线时间、基础 recall)
第 2 期 内部推广(第 3-6 月):
- 北极星:内部 DAU 增长、使用频次
- 工程:P99 延迟、错误率
第 3 期 商业化(第 6-12 月):
- 北极星:付费用户数、ARR
- 工程:成本 / 用户、单位经济学
第 4 期 规模化(12 个月以后):
- 北极星:LTV / CAC、毛利
- 工程:多租户、全球化、合规
KPI 跟着阶段换——不是一套终身用。
OKR 的具体写法
季度 OKR(Objectives + Key Results)示例:
Objective: Q2 把企业内部 RAG 采纳率从 30% 提到 50%
Key Results:
1. 答案满意度(thumbs up rate)从 70% 提到 82%
2. P99 延迟从 2.5s 降到 1.8s
3. 覆盖知识库从 80% 部门扩展到 95%
不是 Key Results:
× 升级到 rerank v2(这是 action、不是 result)
× recall 提 3 点(这是工程指标、不直接对应 objective)
OKR 要求 Key Results 是可测量结果——不是"做了什么",是"达到了什么"。
KPI 的反模式
- 指标越多越好:20 个 KPI,团队不知道优先级,全都做不好
- 北极星选工程指标:recall 是工程指标,不是北极星——业务不关心
- 不和 compensation 挂钩:KPI 停在口头,没约束力
- 不更新:半年前定的 KPI,项目已经换方向,指标还没变
- 只看均值:均值漂亮但 P99 崩,长尾用户流失
数据驱动的组织文化
好的 KPI 只是起点——需要组织文化配合:
- 每周 review,面对数据不回避
- 错了就承认,不粉饰
- 数据指向什么,就做什么——不是先做再找数据支持
这种文化不是技术问题,是领导力问题——但没有它,最好的 KPI 设计也落不了地。
从评估到 KPI 的桥接
§20.18 讨论了评估到 action 的闭环——KPI 是这个闭环的上层目标:
业务 KPI(如采纳率)
↑
产品指标(如满意度)
↑
工程指标(如 faithfulness)
↑
评估 gold set + badcase
↑
Action items
每层都要能链到上一层——否则就是"工程自 high"。
不要忘了成本维度
很多 KPI 讨论只谈"好"——但好的代价同样关键。KPI 里必须有:
- 单位成本(cost per useful answer)
- 团队效率(features per quarter)
- 系统复杂度(components / tech debt)
光看"好"不看"代价"的 KPI 会导致过度工程——做了很多技术先进的东西,但性价比低。
最终:KPI 是沟通工具
KPI 不只是度量——是跨角色沟通工具。定好 KPI 后:
- 工程师知道"我改这个能提升业务"
- 产品知道"工程做了什么、对业务多少帮助"
- 业务知道"系统进步到哪、下一步在哪"
没有 KPI,团队的每次讨论都要从头对齐"我们到底在优化什么"——争论会反复回到同一个起点。这是成熟团队的分水岭之一。
22.18 新人 onboarding 到 RAG 项目
RAG 项目的新人 onboarding 难在它不是学一套 API,而是理解一套横跨多个领域的系统。做得好的团队新人一两周能上手,做得差的两三个月还进不了状态。
flowchart TB
classDef hard fill:#fdd,stroke:#c00
C[RAG onboarding 挑战]
C --> C1[涉及多个领域<br/>IR / ML / 后端 / 产品]:::hard
C --> C2[工具链多<br/>向量库/LLM API/监控]:::hard
C --> C3[概念不标准<br/>每家术语不同]:::hard
C --> C4[上下文密集<br/>为什么这么选需要背景]:::hard
C --> C5[文档经常过时]:::hard
三阶段,每阶段有交付物
第 1 周打基础:读本专栏 ch1–4 建立全貌,听产品经理一小时讲业务,把本地环境搭到能跑通 demo,看架构图和组件清单,读核心 loop 的代码骨架(不是全部代码)。
第 2 周进细节:按方向深入(后端 / ML / 前端各读对应章节),翻最近的事故 postmortem 学团队踩过的坑,跟班观察 on-call,动手修一个已知的小 bug。
第 3–4 周开始产出:独立做一个功能、参与 code review 和 design review、加入轮值、有 mentor 带。
每个阶段都要有明确交付物,不能是「自己看看」。
第一批任务要挑对:修小 bug(接触真实代码、风险低)、改一处小 UI(快速有成就感)、加一个监控指标(了解可观测性)、补一个单元测试(了解 CI)。不该给新人的是改核心 prompt(需要大量背景)、升级依赖(容易引入回归)、做重构(没背景一定会走偏)。
配套要有安全网,它保护的是新人也是系统:先在 staging 试、不直连生产,所有改动过 code review,三个月内不给生产写权限,改动要能快速回滚。
节奏上可以拿这条线对照——第 2 周第一个 PR,第 1 个月独立完成小 feature,第 3 个月和 mentor 搭档负责一个组件,第 6 个月能独立 own 一块并帮新人 onboard,第 1 年可以做 mentor。明显偏离说明 onboarding 或者岗位设置有问题。
要给的材料,和要配的人
材料按清单给,不是「有空再看」:本专栏按角色圈定重点章节、本项目的内部架构文档、过去一年的关键 postmortem、on-call runbook、内外部 API spec,以及 Lost in the Middle 这类关键论文、Contextual Retrieval 这类厂商工程博客。
工具也要先过一遍再接任务:代码环境(IDE、git、CI 流程)、数据工具(SQL、Python / pandas)、监控工具(Grafana、Jaeger 看 trace)、云工具(K8s、云厂商 CLI)、以及 LLM playground 一类的 AI 工具。
同时每个新人配一名 mentor,强度递减:第一个月每天 15 分钟 check-in,第二个月每周一小时 sync,第三个月起随时可问。mentor 的价值不在答疑,而在传「为什么」——为什么选这个向量库、为什么 chunk size 是 400、过去在哪里栽过跟头。
这一点值得单独说:有些东西写不进文档。为什么这个场景选 Claude 而不选 GPT,靠的是实验积累的手感;哪类 query 容易答错,靠的是 on-call 经验;哪个团队好协作,靠的是人际历史。这些只能通过 mentor、code review 和日常对话传下去。
能写进文档的那部分要有人维护,每季度 review 一次:ADR(每个重要决策的背景和 trade-off)、按时间和类型索引的 postmortem 库、on-call runbook、术语表(内部黑话是新人最大的隐形门槛)、以及 FAQ。文档不更新比没有更糟——它会主动误导新人。
把 onboarding 当成要迭代的产品
新人入职 1 个月、3 个月、6 个月各做一次 retro,问四件事:哪些文档有用、哪些缺失、哪些过时、哪些工具拖慢了你。收上来的反馈直接改 onboarding 材料。
团队从 5 人扩到 20 人时会集中暴露三个瓶颈:mentor 不够(老人都在带人、没时间做项目)、文档跟不上系统变化、以及文化稀释(老人的「常识」新人根本不知道)。应对是指定 onboarding owner 专人负责材料和流程、把 mentor 时间算作正式工作而不是义务、以及建立「新人带更新的人」的传承链。
对应的反模式也很清楚:只给个仓库地址让新人自学、把 mentor 当成没有认可的额外负担、文档没有 owner 导致缺失和过时无人修、从不问新人反馈、以及给新人过宽的生产权限。
成本这笔账大致是:mentor 每周 3–5 小时持续三个月(合 40–60 人时),文档维护每季度 1–2 人天,加上新人产出的爬坡(粗估:第 1 个月接近零,第 2–3 个月约一半,第 4 个月约八成)。做得好三个月能到满产出,做得差可能拖到半年到一年,中途还有走人的风险——一次成功的 onboarding 换来的是一个长期贡献者,这是它回报高的原因。
顺带一提,onboarding 质量本身就是对外的信号。候选人面试时会问「你们 onboarding 怎么样」,答案里能看出团队是否愿意在人身上投资。而在内部,人员流动必然发生、系统持续变化,onboarding 因此不是「来人了做一次」,是长期要维护的一项组织工程。
22.19 全专栏回顾
22 章的核心骨架:
flowchart LR
classDef p1 fill:#fee,stroke:#c00
classDef p2 fill:#fed,stroke:#c60
classDef p3 fill:#def,stroke:#06c
classDef p4 fill:#efe,stroke:#080
classDef p5 fill:#fef,stroke:#808
classDef p6 fill:#eef,stroke:#066
P0["开篇: 为什么是 RAG ch1"]
P1["第一部分: RAG 系统全景 ch2-4"]:::p1
P2["第二部分: 知识进入系统 ch5-8"]:::p2
P3["第三部分: 表示与索引 ch9-12"]:::p3
P4["第四部分: 召回排序上下文 ch13-17"]:::p4
P5["第五部分: Agent Memory 与 GraphRAG ch18-19"]:::p5
P6["第六部分: 生产化 ch20-22"]:::p6
P0 --> P1 --> P2 --> P3 --> P4 --> P5 --> P6
开篇加六个部分,串起一个完整知识体系。每个部分的核心 take-away 在各章的"本章小结"里——本专栏的末尾不再复述。
22.20 跨专栏关联和延伸阅读
本专栏和系列其他专栏的呼应:
- 《LangGraph 设计与实现》:RAG 作为 Agent 的一个 node,和其他工具节点编排
- 《Tokio 源码深度解析》:异步 runtime 底层——RAG 的在线链路大量依赖 async
- 《Hyper 与 Tower:工业级 HTTP 栈》:RAG 的 API gateway 和 HTTP 中间件模式
- 《Serde 元编程》:trace / schema / 数据契约的序列化基础
- 《vLLM 推理内核深度解析》:LLM serving 侧优化——降 RAG 推理成本
- 《MCP 协议设计与实现》:Agent 如何标准化地访问 RAG 作为 tool / resource
- 《Axum 设计与实现》:如果用 Rust 写 RAG 服务,axum 是常见选择
推荐的外部资源:
- 论文:BEIR、MTEB、ColBERT v2、Lost in the Middle、HyDE、MemGPT、Microsoft GraphRAG
- 框架:LangChain、LlamaIndex、Haystack、ragas、trulens
- 博客:Anthropic 的 Contextual Retrieval 工程博客、Anthropic Research、LangChain blog、LlamaIndex blog、Qdrant blog、Cohere blog
22.21 结语
RAG 由 Lewis 等人在 2020 年提出,到 2026 年已是 AI 应用里常见的基础设施。它还会随 LLM 能力、Embedding 质量、向量库性能、Agent 架构的变化而调整;但本专栏的判断(属于预测)是,下面这条核心原则会保持稳定:
- 知识带边界进入索引、按业务语义检索、按证据生成、按引用验证
这四条是本专栏 22 章的压缩版,其他细节按需回到对应章节查阅。