RAG 工程与检索系统设计

第22章 从零构建一个生产级 RAG 系统

作者 杨艺韬 · 13,297 字 · 发布于 · 更新于

最好的 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_tenant payload 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 链路从尾到头查:

  1. 用户看到的答案 → 查 trace
  2. Trace 里的 prompt 和 context → 看检索结果合理吗
  3. 检索结果 → 看 query 是否合理
  4. 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 个问题就能大致定方向:

  1. 这个组件是不是你的核心差异化?

    • 是 → 可能要自建 / 定制
    • 否 → 直接用现成(商用或开源)
  2. 规模大到自托管有 TCO 优势吗?

    • < 小规模 → 商用(运维成本不划算)
    • 中大规模 → 开源自托管
    • 超大规模 → 可能要自建专用组件
  3. 数据 / 合规约束有多强?

    • 强(金融 / 医疗 / 政务) → 自托管优先
    • 中等 → 看区域 / 厂商承诺
    • 弱(纯内部工具) → 商用省事

三个问题拍板基本不会错。

详细 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 章的压缩版,其他细节按需回到对应章节查阅。