跳转至

深入专题:RAG、Eval 与线上治理

RAG Demo 很容易:

文档切块 → 向量化 → 检索 Top K → 交给模型回答

生产难点是:

答错时要知道是资料没进库、切块错误、检索没召回、排序不好、模型没遵循证据,还是权限过滤错了。

把 RAG 拆成两条流水线

入库流水线

数据源
→ 抽取
→ 清洗
→ 结构识别
→ 切块
→ 元数据
→ Embedding
→ 建立索引
→ 版本发布

查询流水线

用户问题
→ 身份和租户
→ 查询改写
→ 关键词 / 向量检索
→ Metadata Filter
→ 融合
→ 重排
→ 上下文组装
→ 模型生成
→ 引用和答案

只有看到两条链,才能定位故障。

入库不是“读取 PDF”

文档可能包含:

  • 页眉页脚;
  • 表格;
  • 多栏排版;
  • 扫描图片;
  • 章节层级;
  • 版本号和生效日期;
  • 重复附件;
  • 已废止规则。

如果抽取阶段把表格列顺序打乱,后面的 Embedding 再好也无法恢复事实。

因此每个 Chunk 至少需要:

{
  "chunk_id": "doc-17:v3:section-4:chunk-2",
  "document_id": "doc-17",
  "document_version": "v3",
  "title": "设备离线处理规范",
  "section_path": ["运维规范", "离线告警"],
  "effective_at": "2026-01-01",
  "tenant_id": "T-2",
  "access_level": "internal",
  "source_uri": "...",
  "content": "..."
}

元数据不是装饰,它参与权限、过滤、引用、更新和删除。

Chunk 大小没有万能答案

过小:

  • 语义不完整;
  • 条件和结论被拆开;
  • 表格标题与数据分离;
  • 检索到片段但无法回答。

过大:

  • 一个 Chunk 包含多个主题;
  • 相似度被稀释;
  • 上下文成本高;
  • Top K 中冗余严重。

应通过具体问题实验:

按固定字符切
vs
按标题/段落语义切
vs
父子块:小块检索,大块返回

记录:

  • 正确证据是否进入 Top K;
  • 每次上下文 Token;
  • 答案正确率;
  • 延迟和成本。

“500 字一块”只能是起点,不是结论。

为什么纯向量检索会漏

向量检索擅长语义相似:

“设备没有心跳” ≈ “终端长时间未上报”

但对精确词可能不稳定:

  • 设备型号 GW-XJ-210;
  • 告警码 E042;
  • 订单号;
  • 人名或缩写;
  • 法规条款号。

关键词检索对精确词更强。因此常用混合检索:

向量召回 ─┐
          ├→ 融合排序 → 重排 → Top N
全文检索 ─┘

融合可使用 Reciprocal Rank Fusion 等方法,再用 Cross Encoder 或其他重排模型评估问题与 Chunk 的相关性。

Metadata Filter 是安全步骤

错误流程:

全库召回
→ 把其他租户文档交给模型
→ 要求模型不要引用

敏感信息已经进入模型上下文,边界已经失守。

正确流程:

用户身份
→ 计算允许的数据范围
→ 检索阶段强制 tenant_id / ACL Filter
→ 只召回有权访问的 Chunk

权限过滤应由检索层或数据库执行,不能由模型自觉完成。

文档更新和删除为什么麻烦

文档 v2 发布后:

  • v1 Chunk 是否还在索引中?
  • 搜索是否同时命中旧版和新版?
  • 原文删除时向量是否同步删除?
  • 缓存答案是否仍引用旧内容?
  • 已运行任务能否重放当时版本?

建议使用稳定文档 ID 和版本:

先构建 v2 索引
→ 验证
→ 原子切换 active_version
→ 清理 v1

不要在生产索引里边删边写,导致用户看到半套资料。

生成答案必须区分证据和推断

建议输出结构:

{
  "answer": "...",
  "claims": [
    {
      "text": "离线超过 90 秒触发告警",
      "citations": ["chunk-21"]
    }
  ],
  "uncertainties": ["文档未说明弱网环境是否延长阈值"],
  "used_sources": ["doc-17:v3"]
}

引用不是只显示“来源 1”。系统需要验证:

  • 引用的 Chunk 确实存在;
  • 用户有权访问;
  • 引用内容支持对应主张;
  • 版本仍有效;
  • 模型没有编造 chunk_id。

RAG 的四类评测

不要把所有问题压成一个“答案得分”。

评测对象 问题 典型指标
检索 正确证据是否被召回 Recall@K、MRR、人工相关性
忠实度 答案是否由证据支持 Groundedness / Faithfulness
正确性 与已知正确答案是否一致 Exact/规则/语义评分
有用性 是否真正解决用户问题 人工评分、任务完成率

一个关键诊断矩阵

正确证据召回 最终答案正确 可能问题
否 否 入库、切块、查询或检索问题
是 否 上下文组装或生成问题
否 是 模型可能靠参数知识碰巧答对,仍不可靠
是 是 成功样本,但仍检查权限和引用

只看最终答案会掩盖“模型碰巧知道”的问题。

Eval 数据集怎样建立

第一批不必有 1000 条。先为每个关键能力人工挑 5~10 个高价值样本:

  • 正常高频问题;
  • 容易混淆的相似问题;
  • 必须拒答的问题;
  • 没有资料的问题;
  • 跨租户权限问题;
  • 精确型号或告警码;
  • 多跳问题;
  • 文档互相冲突;
  • Prompt Injection 文档;
  • 历史线上失败案例。

每条数据最好包括:

{
  "case_id": "rag-018",
  "question": "E042 如何处理?",
  "expected_document_ids": ["manual-v3"],
  "expected_facts": ["检查网关供电", "确认通信线"],
  "forbidden_facts": ["恢复出厂设置"],
  "expected_behavior": "answer",
  "tags": ["exact-code", "maintenance"]
}

数据集是一项产品资产,不是随手收集的聊天记录。

先用确定性评测

能够用代码判断的,不要先交给另一个模型:

  • JSON Schema 是否合法;
  • 必填字段是否存在;
  • 工具名称是否正确;
  • 参数是否精确;
  • 是否调用禁用工具;
  • 引用 ID 是否存在;
  • Token、延迟、费用是否超阈值;
  • 是否命中预期文档;
  • 是否泄露测试中的敏感标记。

这些结果稳定、便宜、容易定位。

LLM-as-Judge 的价值和限制

它适合评估:

  • 表达是否完整;
  • 解释是否有帮助;
  • 答案与参考答案语义是否一致;
  • 答案是否由上下文支持。

但 Judge 也会:

  • 受提示词和模型版本影响;
  • 对长答案有偏好;
  • 对某些语言或表达有偏差;
  • 被待评答案里的指令干扰;
  • 与人工专家意见不一致。

使用方法:

  1. 先定义清晰评分 Rubric;
  2. 使用结构化评分和理由;
  3. 用人工标注集校准 Judge;
  4. 定期抽查;
  5. 重要指标不只依赖一个 Judge;
  6. 版本升级后重新验证一致性。

Agent 不只评最终答案

Agent 可能给出正确答案,却走了危险或昂贵路径:

查询设备
→ 错误调用重启
→ 再查询
→ 最终回答正确

因此需要评估轨迹:

  • 是否选择正确工具;
  • 参数是否正确;
  • 调用顺序是否合理;
  • 是否有冗余调用;
  • 是否触碰高风险工具;
  • 是否在正确节点请求人工批准;
  • 总步骤、延迟和成本;
  • 最终任务是否完成。

回归测试要比较版本矩阵

一次发布可能同时变化:

模型版本
Prompt 版本
工具描述版本
检索参数
Embedding 版本
文档索引版本
工作流版本

如果全部一起改,结果变差时很难归因。

每次运行记录:

{
  "model": "provider/model-version",
  "prompt_version": "diagnosis-v7",
  "toolset_version": "tools-v4",
  "retriever_version": "hybrid-v3",
  "index_version": "kb-2026-07-20",
  "workflow_version": "wf-v5"
}

先单变量实验,再做整体候选版本回归。

发布门禁不是“平均分更高”

假设新版本:

  • 平均正确率从 86% 升到 88%;
  • 高风险操作误调用从 0% 升到 1%。

不能因为平均分变高就发布。

门禁可分层:

安全红线:必须 100% 通过
核心任务成功率:不得下降
长尾能力:允许小幅波动
P95 延迟:不得超过预算
平均成本:不得超过预算

不同失败的代价不同。

线上观测要记录完整因果链

一次运行至少能关联:

trace_id
run_id / task_id
user / tenant(脱敏后)
模型与版本
Prompt / Tool / Workflow / Index 版本
每一步输入输出摘要
工具参数与结果状态
检索文档 ID 和分数
Token、费用、首 Token 和总延迟
重试、错误、人工审批
用户反馈与最终状态

不要默认把完整 Prompt、用户原文和工具结果全部写日志。应:

  • 敏感字段脱敏;
  • 大对象只存引用;
  • 设置保留期限;
  • 按权限查看 Trace;
  • 区分调试环境和生产环境。

OpenTelemetry 的 Trace、Metric、Log 可以形成统一关联:

Trace:这次任务经过哪里
Metric:系统整体是否变差
Log:某个具体事件发生了什么

成本要按“完成一个任务”计算

单次模型调用便宜,不代表 Agent 任务便宜。

任务成本 =
规划调用
+ 每轮工具后的模型调用
+ Embedding
+ 重排
+ Judge / 评测
+ 重试
+ 外部 API

应记录:

  • 单任务模型轮数;
  • 输入/输出 Token;
  • 缓存命中;
  • 每类工具调用次数;
  • 成功任务成本;
  • 失败任务成本;
  • 不同租户或场景的预算。

降低成本的顺序通常是:

  1. 减少无意义步骤;
  2. 减少无用上下文;
  3. 确定性逻辑不调用模型;
  4. 简单任务路由到便宜模型;
  5. 缓存稳定结果;
  6. 最后才是单纯压缩答案字数。

Prompt Injection 的生产防线

RAG 文档、网页、邮件和 Tool 返回都属于不可信输入。

分层防御:

来源控制和清洗
→ 数据与指令明确隔离
→ 检索权限过滤
→ 工具最小权限
→ 参数和业务校验
→ 高风险人工审批
→ 输出与外传限制
→ Trace、告警和红队测试

没有一种 Prompt 能彻底解决注入。即使模型被诱导,也应因为工具权限不足而无法造成严重后果。

完整实验:优化“设备规范问答”

基线

固定 500 字切块
向量 Top 5
无重排
模型直接回答

建立 60 条数据集

  • 20 条语义问题;
  • 10 条精确告警码;
  • 10 条多步骤问题;
  • 10 条无答案/应拒答;
  • 5 条跨租户;
  • 5 条注入攻击。

先测检索

发现:

语义问题 Recall@5 = 90%
精确告警码 Recall@5 = 55%

说明不是生成问题,而是纯向量搜索不擅长精确代码。

改成混合检索

全文检索与向量召回融合,重排后:

精确告警码 Recall@5 = 92%
平均检索延迟 +35ms

再测生成

正确证据已召回,但一些答案把“建议”说成“必须”。于是:

  • 调整回答结构;
  • 要求区分规范原文、解释和不确定性;
  • 对关键事实做引用校验。

发布判断

检索召回:通过
答案正确性:通过
跨租户:100% 拒绝
注入攻击:高风险工具 0 调用
P95 延迟:在预算内
单任务成本:在预算内

这才是一轮可解释的 RAG 改进,不是“感觉回答变好了”。

开源方案的务实组合

个人学习和中小型项目可以从:

PostgreSQL + pgvector
+ PostgreSQL 全文检索
+ 自己维护 Eval 数据集
+ OpenTelemetry
+ LangGraph(需要状态工作流时)

开始。优点是组件少,能真正理解数据、检索、状态和观测。

当数据量、团队协作或搜索能力明确超出边界,再评估专用搜索引擎、向量数据库和托管观测平台。不要为了“像大平台”提前引入十几个组件。

权威延伸资料

自测

  1. 为什么 RAG 必须拆成入库链和查询链?
  2. Chunk 越小或越大分别会造成什么问题?
  3. 为什么设备型号、告警码更适合加入关键词检索?
  4. Metadata Filter 为什么属于安全控制,而不只是搜索优化?
  5. 检索到了正确证据但答案错了,应优先排查哪一段?
  6. 哪些 Eval 应优先使用确定性代码?
  7. LLM-as-Judge 为什么需要人工校准?
  8. 为什么 Agent 最终答案正确仍可能判为失败?
  9. 新版本平均正确率提高,为什么仍可能不能发布?
  10. Prompt Injection 为什么无法只靠 Prompt 防御?
参考答案要点
  1. 入库决定知识是否正确进入索引,查询决定是否正确找回并生成;不拆开就无法定位失败。
  2. 太小会失去上下文和语义完整性;太大会混合主题、稀释相似度并增加 Token。
  3. 这些是精确符号,语义向量可能不稳定,全文或关键词匹配更直接。
  4. 必须在不可信内容进入模型前排除无权数据,否则即使最终不展示也已越过数据边界。
  5. 上下文组装、提示要求、模型生成和引用绑定,而不是继续盲目调检索参数。
  6. Schema、字段、工具、参数、引用存在性、权限、成本和延迟等可精确判断的指标。
  7. Judge 同样是概率模型,存在版本、表达和长度偏差,必须和专家标注对比。
  8. 它可能走了危险、违规、昂贵或冗余的工具轨迹。
  9. 安全红线、核心场景、延迟或成本可能退化,平均值会掩盖高代价失败。
  10. 注入利用自然语言中指令与数据边界模糊;必须用权限、工具限制、审批和监控控制后果。

完成标准

  • 能画出 RAG 入库和查询两条完整流水线
  • 能用失败矩阵区分检索问题与生成问题
  • 能设计包含拒答、权限和注入样本的 Eval 集
  • 能区分确定性指标与 LLM-as-Judge
  • 能为候选版本设计安全、质量、延迟和成本门禁
  • 完成 Agent Eval 实验

回到主线总结:从 Demo 到生产系统