AI 每日学习 — RAG 效果评估体系设计与实现

RAG 效果评估体系设计与实现

text 复制代码
学习日期:2026-08-04
今日主题:RAG 效果评估体系设计与实现
所属领域:RAG 与企业知识库
适合角色:Java 后端架构师、AI 应用架构师
预计阅读时间:15~30 分钟
适用版本:RAGAS 0.4.x(2026-01-13 发布;docs.ragas.io)、LangFuse 2.x+、OpenTelemetry GenAI SemConv 1.30+
资料检索日期:2026-08-04

免责声明:本文涉及的具体阈值、API、CLI 名称为 2026-08-04 当日检索结果,RAGAS 已从 0.3 迁移到 0.4 的 ragas.metrics.collections 命名空间,迁移过程中存在破坏性变更,生产环境使用前请以本地安装版本的官方文档为准。


1. 今日主题

RAG 效果评估体系设计与实现

为什么 AI 应用架构师 / Java 后端架构师必须掌握它

在企业落地 RAG 的过程中,"看上去答得还行"是最不可控的判断方式。检索质量、生成忠实度、答案相关性这三种失败模式往往耦合在同一段对话里,但根因完全不同:可能来自 Chunking、Embedding、Prompt、模型选型甚至文档版本治理。

很多团队的 RAG 项目在 0 → 1 阶段"看着能跑",进入生产后却不知道效果有没有退化、新上的 rerank 究竟有没有正向收益------根因就是没有评估体系 。评估体系不是给模型打分,而是给整个 RAG 流水线打分:它是 Chunking、Embedding、Rerank、Prompt、模型五个变量同时调节时唯一能告诉你"为什么变好 / 为什么变差"的度量基础。

对 Java 后端架构师来说,RAG 评估体系还有一层特殊价值:它把"AI 质量"翻译成了传统后端擅长的"指标 + 卡点 + 基线"------刚好可以和现有的 CI、监控、告警体系直接对齐。


2. 核心问题

学完后应能回答:

  1. 评估 RAG 时到底要测什么指标?检索层和生成层为什么必须分开?
  2. RAGAS / DeepEval / TruLens / LangFuse 在 2026 年各自承担什么职责,能力边界在哪里?
  3. 离线 gold set、在线采样、人工校验这三种评估模式各自解决什么问题?
  4. LLM-as-Judge 在生产评估中可信吗?什么时候必须替换或校准?
  5. 评估指标如何接进 CI 卡点和生产告警,形成"评估 → 决策 → 回归"的闭环?

3. 前置知识

阅读本文需要你已经理解:

  • RAG 基础流程:文档解析 → Chunking → Embedding → 向量检索 → 可选 Rerank → Prompt 组装 → 模型生成。
  • 向量检索和关键词检索的差异:为什么需要混合检索与 Rerank(在 2026-07-22《混合检索与 Rerank》一文中已覆盖)。
  • Spring AI / LangChain4j 基本调用范式:ChatClient、QuestionAnswerAdvisor、向量存储抽象。
  • CI、基线对比、可观测性基本概念:指标(Metric)、日志(Log)、追踪(Trace)、告警阈值。

如果你还不熟悉 RAG 的整体链路,建议先补一下基础流程,避免在评估时把链路环节和评估指标搞混。


4. 核心原理

4.1 RAG 为什么需要"单独"的评估体系

RAG 由"检索 + 生成"两部分组成,三类故障模式互相纠缠:

故障类型 现象 根因位置
检索失败(Retrieval Failure) 答案正确但 support 不到 context Retriever/Embedding/Chunking
生成失败(Generation Failure) 检索到正确 chunk,模型编造细节 Prompt/Model
覆盖失败(Coverage Failure) 正确答案在语料中,但从未被检索到 Embedding/Rerank/Index

只评估最终答案会把这三类故障混在一起------CI 无法判断应该改 Chunking 还是改 Prompt。RAG 评估的本质是把每类故障量化到独立指标,让优化闭环精准落在正确环节。

4.2 评估指标的诊断矩阵

2026 年业界主流依然是 RAGAS 四指标 + 几个 IR 指标的组合。指标不是越高越好,关键是配合使用

指标 数学含义 诊断什么 典型调优环节
Context Precision 检索到的 chunk 中"有用"的占比 检索器是否把噪声一并拽进来 top-k、Rerank、Metadata 过滤
Context Recall 答案需要的"有用 chunk"被检索到的占比 是否漏召 Embedding、Chunking、Query Rewriting
Faithfulness 答案中的"声明"被 context 支持的比例 模型是否在凭空捏造 Prompt、temperature、模型选型
Answer Relevancy 答案语义对齐问题的程度 是否答非所问 Prompt、Query Rewriting
Answer Correctness F1 + 语义相似度(与 ground truth 比) 答案的事实正确性 端到端,多变量
Precision@K / Recall@K / MRR / NDCG 经典 IR 指标 有标签时的检索质量 同 Context Precision/Recall
关键的"指标组合诊断"

这是评估体系最值钱的部分,单独看任何一个数字都不够。
#mermaid-svg-jIwgqNvzplXVmzPx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jIwgqNvzplXVmzPx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jIwgqNvzplXVmzPx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jIwgqNvzplXVmzPx .error-icon{fill:#552222;}#mermaid-svg-jIwgqNvzplXVmzPx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jIwgqNvzplXVmzPx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jIwgqNvzplXVmzPx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jIwgqNvzplXVmzPx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jIwgqNvzplXVmzPx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jIwgqNvzplXVmzPx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jIwgqNvzplXVmzPx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jIwgqNvzplXVmzPx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jIwgqNvzplXVmzPx .marker.cross{stroke:#333333;}#mermaid-svg-jIwgqNvzplXVmzPx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jIwgqNvzplXVmzPx p{margin:0;}#mermaid-svg-jIwgqNvzplXVmzPx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jIwgqNvzplXVmzPx .cluster-label text{fill:#333;}#mermaid-svg-jIwgqNvzplXVmzPx .cluster-label span{color:#333;}#mermaid-svg-jIwgqNvzplXVmzPx .cluster-label span p{background-color:transparent;}#mermaid-svg-jIwgqNvzplXVmzPx .label text,#mermaid-svg-jIwgqNvzplXVmzPx span{fill:#333;color:#333;}#mermaid-svg-jIwgqNvzplXVmzPx .node rect,#mermaid-svg-jIwgqNvzplXVmzPx .node circle,#mermaid-svg-jIwgqNvzplXVmzPx .node ellipse,#mermaid-svg-jIwgqNvzplXVmzPx .node polygon,#mermaid-svg-jIwgqNvzplXVmzPx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jIwgqNvzplXVmzPx .rough-node .label text,#mermaid-svg-jIwgqNvzplXVmzPx .node .label text,#mermaid-svg-jIwgqNvzplXVmzPx .image-shape .label,#mermaid-svg-jIwgqNvzplXVmzPx .icon-shape .label{text-anchor:middle;}#mermaid-svg-jIwgqNvzplXVmzPx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jIwgqNvzplXVmzPx .rough-node .label,#mermaid-svg-jIwgqNvzplXVmzPx .node .label,#mermaid-svg-jIwgqNvzplXVmzPx .image-shape .label,#mermaid-svg-jIwgqNvzplXVmzPx .icon-shape .label{text-align:center;}#mermaid-svg-jIwgqNvzplXVmzPx .node.clickable{cursor:pointer;}#mermaid-svg-jIwgqNvzplXVmzPx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jIwgqNvzplXVmzPx .arrowheadPath{fill:#333333;}#mermaid-svg-jIwgqNvzplXVmzPx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jIwgqNvzplXVmzPx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jIwgqNvzplXVmzPx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jIwgqNvzplXVmzPx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jIwgqNvzplXVmzPx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jIwgqNvzplXVmzPx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jIwgqNvzplXVmzPx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jIwgqNvzplXVmzPx .cluster text{fill:#333;}#mermaid-svg-jIwgqNvzplXVmzPx .cluster span{color:#333;}#mermaid-svg-jIwgqNvzplXVmzPx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jIwgqNvzplXVmzPx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jIwgqNvzplXVmzPx rect.text{fill:none;stroke-width:0;}#mermaid-svg-jIwgqNvzplXVmzPx .icon-shape,#mermaid-svg-jIwgqNvzplXVmzPx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jIwgqNvzplXVmzPx .icon-shape p,#mermaid-svg-jIwgqNvzplXVmzPx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jIwgqNvzplXVmzPx .icon-shape .label rect,#mermaid-svg-jIwgqNvzplXVmzPx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jIwgqNvzplXVmzPx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jIwgqNvzplXVmzPx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jIwgqNvzplXVmzPx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Recall 低

Precision 高
检索漏召

正确答案根本没进来
提高 top-k

换 Embedding

加 Rerank

改 Chunking
Precision 低

Recall 高
检索噪声多

模型被干扰
收紧 top-k

调阈值

优化 metadata 过滤
Faithfulness 低
模型胡编

prompt 宽松
强化 grounding prompt

temp=0

必要时换更强的 reasoning 模型
Answer Relevancy 低
答非所问
query 改写

system prompt 优化

问题理解前置
全部指标健康

p95 仍高
体感差
看 trace:

检索慢 / rerank 慢 /

context 拼接过长

生产参考阈值(经验值,需以本业务基线为锚)

这些是行业 2026 年的常见经验基线,不是普适真理:每个业务必须先测一遍自己的基线,再围绕基线设阈值。

指标 经验下限
Faithfulness ≥ 0.85(高风险域 ≥ 0.92)
Answer Relevancy ≥ 0.80
Context Precision ≥ 0.70
Context Recall ≥ 0.75
Citation Accuracy(业务自评) ≥ 0.95

4.3 评估的"三层闭环"

离线 + 在线 + 人工,构成完整的评估闭环:
#mermaid-svg-15LpViw8b76XEXIN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-15LpViw8b76XEXIN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-15LpViw8b76XEXIN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-15LpViw8b76XEXIN .error-icon{fill:#552222;}#mermaid-svg-15LpViw8b76XEXIN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-15LpViw8b76XEXIN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-15LpViw8b76XEXIN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-15LpViw8b76XEXIN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-15LpViw8b76XEXIN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-15LpViw8b76XEXIN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-15LpViw8b76XEXIN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-15LpViw8b76XEXIN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-15LpViw8b76XEXIN .marker.cross{stroke:#333333;}#mermaid-svg-15LpViw8b76XEXIN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-15LpViw8b76XEXIN p{margin:0;}#mermaid-svg-15LpViw8b76XEXIN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-15LpViw8b76XEXIN .cluster-label text{fill:#333;}#mermaid-svg-15LpViw8b76XEXIN .cluster-label span{color:#333;}#mermaid-svg-15LpViw8b76XEXIN .cluster-label span p{background-color:transparent;}#mermaid-svg-15LpViw8b76XEXIN .label text,#mermaid-svg-15LpViw8b76XEXIN span{fill:#333;color:#333;}#mermaid-svg-15LpViw8b76XEXIN .node rect,#mermaid-svg-15LpViw8b76XEXIN .node circle,#mermaid-svg-15LpViw8b76XEXIN .node ellipse,#mermaid-svg-15LpViw8b76XEXIN .node polygon,#mermaid-svg-15LpViw8b76XEXIN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-15LpViw8b76XEXIN .rough-node .label text,#mermaid-svg-15LpViw8b76XEXIN .node .label text,#mermaid-svg-15LpViw8b76XEXIN .image-shape .label,#mermaid-svg-15LpViw8b76XEXIN .icon-shape .label{text-anchor:middle;}#mermaid-svg-15LpViw8b76XEXIN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-15LpViw8b76XEXIN .rough-node .label,#mermaid-svg-15LpViw8b76XEXIN .node .label,#mermaid-svg-15LpViw8b76XEXIN .image-shape .label,#mermaid-svg-15LpViw8b76XEXIN .icon-shape .label{text-align:center;}#mermaid-svg-15LpViw8b76XEXIN .node.clickable{cursor:pointer;}#mermaid-svg-15LpViw8b76XEXIN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-15LpViw8b76XEXIN .arrowheadPath{fill:#333333;}#mermaid-svg-15LpViw8b76XEXIN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-15LpViw8b76XEXIN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-15LpViw8b76XEXIN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-15LpViw8b76XEXIN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-15LpViw8b76XEXIN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-15LpViw8b76XEXIN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-15LpViw8b76XEXIN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-15LpViw8b76XEXIN .cluster text{fill:#333;}#mermaid-svg-15LpViw8b76XEXIN .cluster span{color:#333;}#mermaid-svg-15LpViw8b76XEXIN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-15LpViw8b76XEXIN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-15LpViw8b76XEXIN rect.text{fill:none;stroke-width:0;}#mermaid-svg-15LpViw8b76XEXIN .icon-shape,#mermaid-svg-15LpViw8b76XEXIN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-15LpViw8b76XEXIN .icon-shape p,#mermaid-svg-15LpViw8b76XEXIN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-15LpViw8b76XEXIN .icon-shape .label rect,#mermaid-svg-15LpViw8b76XEXIN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-15LpViw8b76XEXIN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-15LpViw8b76XEXIN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-15LpViw8b76XEXIN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Layer 3 人工校验
Layer 2 在线流量采样
调整
Layer 1 离线 Gold Set
补数据集
100~500 条

人工标注 QA
运行流水线

产生 question/answer/contexts
RAGAS 跑分
CI 卡点:

超过阈值才合并
5~20% 生产请求
异步 judge 评分
接 OpenTelemetry span
触发漂移告警
每周抽 50~100 条

最低分 trace
领域专家复评
Cohen's kappa

≥ 0.6 视为校准通过

离线层解决"我这次改动有没有让事情变差",跑分稳定、可重放,是 CI 的灵魂。

在线层解决"线上用户在问什么、我们一直在哪些问题上失败",这是 gold set 永远覆盖不到的部分。常见做法是用 LangFuse / OpenLLMetry 等给每个生产 span 异步挂上评估分数,没有 ground truth 也能跑 answer_relevancy、context_relevance、faithfulness 等无参考指标。

人工层 是 LLM-as-Judge 的唯一可靠校准方式。LLM-as-Judge 与人类一致性通常能达到 0.8+,但位置偏差(position bias)、冗长度偏差(verbosity bias)、自我偏好偏差(self-preference) 都会让自动化打分失真。Human-in-the-loop 的真正作用不是打标,而是给 judge 模型出考卷。

4.4 LLM-as-Judge 的工程边界

这是评估体系最容易翻车的地方,必须把以下结论当硬约束:

  1. Judge 模型必须和生产模型解耦:用同一个模型打自己和别人,会系统性偏高;
  2. Judge 模型要定期校准:每季度用人工标注的子集算 Cohen's kappa,低于 0.6 必须重写 prompt 或换模型;
  3. 不要让 judge 评 judge:自反馈循环一定漂移;
  4. 高昂的 QPS 优先用小模型:用 GPT-4o 当 judge 一次 150 条评测就要 ~0.85 美元,规模化后会反噬成本控制;
  5. 置信度区间必须打印:单次跑分的 score 没有意义,至少要看"过去 30 天 95% 分位数 + 最大回撤"。

4.5 评估体系的工程化收益

把评估变成可工程化的能力后,它会"反向重塑"整个 RAG 系统:

  • Chunking 优化 不再凭直觉,而是看 Context Recall 提升曲线;
  • Embedding 模型 不再盲换,而是用 NDCG@10 + Context Precision 量化对比;
  • Prompt 版本治理 不再靠"感觉",而是 Faithfulness 数值化对比;
  • 多模型路由 不再拍脑袋,而是不同模型在 Answer Correctness / Cost 上的 Pareto 比较;
  • 新文档入库 不再"塞进去就行",而是监控 Context Recall 的滚动基线。

这是评估体系从"质量部门报表"变成"架构师工具盘"的过程。


5. Java 工程实践

案例版本:Spring Boot 3.4.x + Spring AI 1.0.x(Spring AI 在 2026 年已经稳定到 1.0 主版本),RAGAS 0.4.x 通过 Python Sidecar 调用(Java 侧用 HTTP/RPC 桥接)。这里给两条工程路径:(a) 纯 Java + Spring AI + 自研评估器;(b) Python RAGAS Sidecar。

5.1 数据结构与对象模型

java 复制代码
public record EvalSample(
        String question,
        String generatedAnswer,
        List<String> retrievedContexts,
        String referenceAnswer,        // 可空,但有它能跑 context_recall/answer_correctness
        Map<String, Object> metadata   // tenant, traceId, model, promptVersion, etc.
) {}

public record EvalResult(
        double faithfulness,
        double answerRelevancy,
        double contextPrecision,
        double contextRecall,
        double answerCorrectness,
        Map<String, Double> extras     // citation accuracy 等业务自评指标
) {}

metadata必须promptVersionembeddingModelVersion,否则基线回归无从谈。

5.2 调用 RAGAS 0.4 的 Python Sidecar

RAGAS 0.4 仍然是 Python 库。生产 Java 工程通常用一个轻量 Sidecar 提供 REST 接口,主进程通过 HTTP 客户端调用。

Sidecar 接口设计

python 复制代码
# app.py (FastAPI)
from fastapi import FastAPI
from ragas.metrics.collections import Faithfulness, AnswerRelevancy, ContextPrecision, ContextRecall
from ragas.llms import llm_factory
from openai import AsyncOpenAI

app = FastAPI()
llm = llm_factory("gpt-4o-mini", client=AsyncOpenAI())
faithfulness = Faithfulness(llm=llm)
answer_relevancy = AnswerRelevancy(llm=llm)
context_precision = ContextPrecision(llm=llm)
context_recall = ContextRecall(llm=llm)

@app.post("/evaluate")
async def evaluate(samples: list[dict]):
    results = []
    for s in samples:
        f = await faithfulness.ascore(
            user_input=s["question"],
            response=s["generatedAnswer"],
            retrieved_contexts=s["retrievedContexts"],
        )
        ar = await answer_relevancy.ascore(
            user_input=s["question"],
            response=s["generatedAnswer"],
            retrieved_contexts=s["retrievedContexts"],
        )
        # context_precision/recall 需要 reference_contexts
        results.append({
            "question": s["question"],
            "faithfulness": f.value,
            "answer_relevancy": ar.value,
        })
    return results

注意:RAGAS 0.4 移到了 ragas.metrics.collections,旧的 from ragas.metrics import faithfulness 在 0.4 中不再适用 ,必须在 pyproject.toml 锁定 ragas==0.4.3 并以本地安装版本为准。

5.3 Java 侧的评估 Job

java 复制代码
@Component
public class RagEvaluationJob {

    private final RestClient ragasClient;
    private final RagPipeline pipeline;             // 你的业务 RAG 流水线
    private final EvalDatasetStore datasetStore;   // gold set 仓库
    private final MeterRegistry meterRegistry;

    /**
     * 每周一凌晨 03:00 跑全量离线评估
     */
    @Scheduled(cron = "0 0 3 ? * MON")
    public void runWeeklyOfflineEval() {
        List<EvalSample> samples = datasetStore.loadActiveDataset();
        // 1) 用当前流水线生成答案和 context
        List<EvalSample> generated = samples.stream()
                .map(s -> runPipeline(s))
                .toList();

        // 2) 调 RAGAS Sidecar
        List<Map<String, Object>> raw = ragasClient.post()
                .uri("/evaluate")
                .body(generated)
                .retrieve()
                .body(new ParameterizedTypeReference<>() {});

        // 3) 聚合 + 写入指标
        EvalAggregate agg = aggregate(raw);
        publishMetrics(agg);

        // 4) 基线对比,差异超过阈值就告警
        EvalBaseline baseline = datasetStore.loadBaseline();
        diffAndAlert(agg, baseline);
    }

    private void publishMetrics(EvalAggregate agg) {
        meterRegistry.gauge("rag.eval.faithfulness", agg, a -> a.faithfulness());
        meterRegistry.gauge("rag.eval.answer_relevancy", agg, a -> a.answerRelevancy());
        meterRegistry.gauge("rag.eval.context_precision", agg, a -> a.contextPrecision());
        meterRegistry.gauge("rag.eval.context_recall", agg, a -> a.contextRecall());
        meterRegistry.gauge("rag.eval.answer_correctness", agg, a -> a.answerCorrectness());
    }
}

5.4 在 Spring AI 中自动产出 trace + 评估

要支撑在线采样评估,RAG 调用必须携带 trace ID 。Spring AI 的 QuestionAnswerAdvisor 配合 Micrometer Observation 可以直接给出检索和生成的可观测数据:

java 复制代码
@Configuration
public class RagConfig {

    @Bean
    public Advisor qaAdvisor(VectorStore store, EmbeddingModel embeddingModel) {
        return QuestionAnswerAdvisor.builder(store)
                .searchRequest(SearchRequest.builder()
                        .topK(8)                                  // 与评估指标 top-k 对齐
                        .similarityThreshold(0.55f)               // 阈值也是评估维度
                        .build())
                .build();
    }
}

在请求侧通过 ChatClient 触发,并把 trace 上下文绑进 metadata:

java 复制代码
@RestController
public class ChatController {

    private final ChatClient chatClient;
    private final Tracer tracer;
    private final EvalSamplePublisher publisher; // -> Kafka

    @PostMapping("/v1/chat")
    public ChatResponse chat(@RequestBody ChatRequest req, HttpServletRequest http) {
        String traceId = http.getHeader("X-Trace-Id");

        var span = tracer.nextSpan().name("rag.chat").start();
        try (var ignored = tracer.withSpan(span)) {
            String answer = chatClient.prompt()
                    .advisors(a -> a.param("traceId", traceId))
                    .user(req.question())
                    .call()
                    .content();

            // 抽样发到评估 topic
            if (shouldSample(req)) {
                publisher.publish(new EvalSample(
                        req.question(),
                        answer,
                        extractContexts(span),
                        null,
                        Map.of(
                                "tenant", req.tenant(),
                                "model", ragProps.getChatModel(),
                                "promptVersion", ragProps.getPromptVersion()
                        )
                ));
            }
            return new ChatResponse(answer);
        } finally {
            span.end();
        }
    }
}

5.5 CI 卡点

把评估接进 GitHub Actions / GitLab CI,PR 触碰到 RAG 代码就强制跑:

yaml 复制代码
- name: Run RAG evaluation
  run: |
    python scripts/run_ragas.py \
      --dataset held_out_v4 \
      --out result.json

- name: Compare to baseline
  run: |
    python scripts/compare_metrics.py \
      --new result.json \
      --baseline s3://evals/main/latest.json \
      --thresholds faithfulness=0.85,answer_relevancy=0.80,context_precision=0.70,context_recall=0.75 \
      --max_drop 0.02

--max_drop 是关键。即使 PR 指标高于阈值,下降超过 2 个百分点也要拒绝合并。这是防止"千次 0.5 分慢漂移"的唯一机制

5.6 与现有 Java 基础设施的对接

  • Kafka:评估样本流,作为异步 judge 的上游;
  • Redis:在线评估的语义缓存层("过去 24h 内答过同一个问题的评估分数如何");
  • MySQL / 指标库 :gold set、评估结果版本化(带 commit_shaprompt_versionembedding_version);
  • Prometheus + Grafana:评估指标成图,业务方和模型团队都能看。

6. 生产环境案例

业务背景

某 SaaS 客服机器人,上线 6 个月。技术栈 Spring Boot + Spring AI,retrieval 用 Qdrant,rerank 用开源 bge-reranker-v2-m3,Chat 模型按 prompt 复杂度路由到 GPT-4o-mini 或 GPT-4o。7 月初 PM 要求"接入飞书知识库中 8 月更新的 200 个新 FAQ"。

问题现象

上线飞书文档后,D 团队反馈:bot 在回答"如何申请退款"这类问题时,会出现前后不一致的答案------上午问一次说 30 天,下午问同一条就说 15 天。

可能原因(按概率排序)

  1. 新文档 chunking 策略与旧文档不一致:飞书的导出格式和旧 wiki 格式差异大,导致同一主题被切成不同长度的 chunk;
  2. Embedding 模型没变,但 chunk size 变了:向量空间分布迁移,旧 query 命中旧 chunk 的概率降低,新 chunk 也未被稳定命中;
  3. 没有引用溯源:多 chunk 拼接时模型随机选其一,造成答案漂移;
  4. 没有评估体系:根本没人知道 Context Recall 在新文档接入后是高了还是低了。

排查步骤

  1. 拉两周 trace:看"退款"相关 query 命中的 chunk 来源占比;
  2. 离线评测对比(幸亏此前 gold set 已有 80 条退款场景 QA):用旧索引跑一次、新索引跑一次,看指标;
  3. 指标诊断矩阵
    • Context Recall 旧 0.91 → 新 0.78 ← 关键信号:漏召
    • Faithfulness 旧 0.93 → 新 0.94 ← 模型没问题
    • 答案漂移 = 模型每次挑不同的 chunk,组合输出不同结论
  4. 检查 chunking :飞书导出是 HTML 转 Markdown,每个 <table> 后强制切分,导致一个完整退款流程被切成 4 段。

解决方案

  1. 统一 Chunking 策略:自定义 DocumentTransformer,按"标题 + 段落 + 表格"语义切分,不再按字符数硬切;
  2. 加 Rerank:用 bge-reranker 给"包含完整退款步骤"的 chunk 提权,提升 Context Precision;
  3. 加引用溯源:在 Prompt 模板里要求模型标注每个结论来自哪条 chunk,并在 user 端展示;
  4. 加评估卡点:CI 必须跑 RAGAS,任何 chunking / embedding / prompt 改动都触发离线评估。

验证方法

指标 修复前 修复后
Context Recall 0.78 0.90
Context Precision 0.62 0.78
Faithfulness 0.94 0.95
答案前后一致率(业务自定义) 71% 96%

如何避免再次发生

  • 评估数据集版本化:每接入一批新文档,先跑一次评估看 Context Recall 变化;
  • 新文档先影子模式:新索引先在 5% 流量上做 A/B,对照评估指标,达标后再切换;
  • Answer Correctness 不能只看均值:必须看分位数(p10、p50、p95),长尾问题往往最先暴露;
  • 人工校验每周一次:业务方 + AI 工程师一起 review 评估分数最低的 50 条 trace。

7. 架构选型与权衡

7.1 评估框架对比

框架 语言 主要职责 适合场景
RAGAS 0.4 Python LLM-as-Judge 指标四件套 + Testset Generator 离线 RAG 评估主引擎
DeepEval Python 多维度 + G-Eval 复杂 prompt 评分、合成测试集
TruLens Python trace 上挂反馈 已有 trace 体系、需要反馈函数
LangFuse TS/Python(支持 Java SDK) 可观测 + 在线评估 + 数据集 生产 trace + 在线评分一体化
自研 Java 评估器 Java 简单指标(Citation、覆盖率) 不愿引入 Python 依赖、要求低延迟

经验建议:离线用 RAGAS + 自研混合;在线用 LangFuse 或 OpenLLMetry + LLM-as-Judge;人工校验永远是最后一公里。

7.2 Prompt / RAG / 微调,如何选

这个三角在评估体系成熟之后才有真正的可比性。

选择 适用 不适用
仅 Prompt Engineering 简单事实问答、规则清晰 需要反复一致的高风险域
RAG + 评估 知识不断更新、答案需可溯源 写作 / 创造性任务
微调 风格 / 输出格式固定、需要低延迟 知识频繁更新、追求泛化

7.3 云端 vs 本地 judge 模型

维度 云端 GPT-4o / Claude Opus 本地 Llama 70B/405B
评估质量 显著高 与 prompt 工程和领域强相关
单次成本 高(核心成本来源) 中(GPU 摊销)
数据合规 受供应商政策约束 完全可控
工程复杂度
一致性 较高 需固定种子 + 全量校验

生产实践经验:日常跑分用 GPT-4o-mini 或本地 14B 模型,每个季度做一次"GPT-4o 高质量裁判样本"做校准。如果业务是金融、医疗,必须在私有化 judge 上手动做 kappa 校验。

7.4 评估体系要不要做"预发送门"

取舍
预发送门(每次回答前 judge) 阻止严重幻觉 显著增加延迟和成本
异步挂载 trace 不影响首字延迟 错误答案可能短时间内已经到达用户
抽样事后评估 影响最小 难以 SLA 兜底

推荐组合 :预发送门仅用于高风险问题(涉及金钱、法律、医疗),其余异步 + 抽样。

7.5 是否需要人工兜底

凡是"答案直接面向终端用户"且"失效会造成可见损失"的场景,都必须有规则兜底:

  • 检测"未命中 context" → 返回标准兜底文案;
  • 检测"Faithfulness 低于阈值" → 改走人工工单;
  • 检测"问题包含敏感操作动词"(删除、转账、关闭账户)→ 必须人工确认。

8. 安全、成本与可观测性

8.1 安全与权限

  • 评测数据集涉密:gold set 通常含敏感问答,必须与生产数据隔离存储;
  • 评测样本审计:评估样本流同样进审计日志,保留 ≥ 90 天;
  • Judge 模型越权风险:LLM-as-Judge 看到的也是真实问题,配置独立的访问控制和保留策略;
  • 不要把业务 Gold set 发给共享 judge:法律域、医疗域的 ground truth 不能交给 SaaS judge。

8.2 成本

  • LlamaIndex + RAGAS 在 150 条样本评测一次约 0.85 美元(GPT-4o-mini,2026 年实际数据);
  • 在线采样评估一旦 5% 流量就跑满 judge:单条 judge 调用 ≈ Chat 调用的 60%~80% 成本;
  • 建议:1) judge 与 chat 分账;2) 在线采样分层(5% 普通、1% 高风险细评);3) 优先用 mini / flash 小模型做初筛,仅"低分样本"才升级大模型复评。

8.3 可观测性指标

必监控:

  • rag.eval.faithfulness.p50/p95
  • rag.eval.answer_relevancy.p50/p95
  • rag.eval.context_recall.p50
  • rag.eval.answer_correctness.p10(长尾敏感)
  • rag.pipeline.retrieval_recall_at_k(离线打点)
  • rag.pipeline.ci.max_drop(CI 卡点自身状态)

必日志:

  • 每个 EvalSample 完整快照(含 traceId、promptVersion、embeddingVersion、retrievedContextIds);
  • CI 跑分异常与基线对比结果;
  • Judge LLM 的 prompt / model id / token 消耗;
  • 人工校验的 kappa 趋势。

8.4 异常降级与兜底

  • Judge 服务不可用:不阻塞主链路,回退到"结果可信但不评分"模式;
  • 评估数据集过期:每周自动检查基线距今超过 90 天就告警;
  • Judge 模型版本飘移:观察评估指标无理由抖动时,优先怀疑 judge 模型被上游悄悄切换;
  • Context Recall 暴跌:触发紧急回滚到上一个 baseline 的索引配置。

9. 常见错误

错误 1:只看最终答案对不对

现象:上线前随机挑 20 个问题人工 review,"感觉能用"就上线。

原因:忽视三类故障分离。答案对 ≠ Faithfulness 高;答案流畅 ≠ Recall 高。

正确做法:用指标矩阵分离检索层和生成层。Question 拆为"命中了哪些 chunk""chunk 是否支撑结论""结论是否切中问题"三层。

错误 2:用业务问答当 gold set,但永远不更新

现象:gold set 一年没动,但文档库已变更 30%。

原因:gold set 漂移是评估体系最常见的慢性病。

正确做法:(a) 在线采样低分样本自动回流;(b) 每季度抽 8% 样本人工复核,约 8% 通常是"问题变模糊/答案过期"的评估 bug;© 把 gold set 变更记入审计日志。

错误 3:用 Chat 模型自身当 judge

现象:直接拿同一个 LLM 当判官。

原因:自评打分系统性偏高。LLM 会偏好自己风格的输出(verbosity bias、self-preference)。

正确做法:judge 与 chat 必须解耦,并且用人类子集季度校准(kappa ≥ 0.6)。

错误 4:评估指标只跑一次,发布后再也不看

现象:上线前跑分 0.92,上线后从不复测。

原因:把评估当"准入门槛"而非"治理工具"。

正确做法:评估是 7×24 流程,CI 卡点 + 周离线跑分 + 在线采样 + 月人工核验四件事必须长期跑。

错误 5:盲目引入 N 个指标,CI 完全跑不动

现象:一口气接入 faithfulness、answer_relevancy、context_precision、context_recall、answer_correctness、citation_accuracy、toxicity、NDCG@10、MRR... 每个 PR 跑 30 分钟。

原因:评估不是越多越好。

正确做法:每个项目最多 5 个一级指标,CI 关注"准入门槛 + max_drop";其余指标走周离线 / 月离线。


10. 架构师视角总结

什么时候应该用

  • 任何对外服务超过 1 个月:必须建评估体系;
  • 多人协作的 RAG 项目:没有评估体系就无法跨人交接;
  • RAG-as-a-Service 形态:评估是 SLA 的核心证据。

什么时候不应该用(或要谨慎)

  • 纯内部 demo / 验证:可以手工 review,但需要明确"这是 demo 不是产品";
  • POC 阶段 :1 周内出活不必搭全套评估体系,但接口契约必须留扩展点(metadata + 版本号);
  • 业务量极小(< 100 QPS):在线评估成本可能超过实际业务价值。

哪些能力不应完全交给模型

  • 数据权限过滤:必须企业侧规则;
  • Citation Accuracy:必须可验证的引用,不能完全相信模型"我引用了 X";
  • 敏感操作的执行确认:永远人工兜底;
  • 评估指标本身的可信度:必须人工校准。

方案评审时应该重点询问

  1. gold set 在哪、谁维护、规模多大?
  2. 离线评估何时跑?基线版本怎么跟踪?
  3. CI 卡点的指标和阈值是多少?max_drop 策略?
  4. 在线采样比例多少?judge 与 chat 模型是否解耦?
  5. 人工校准频率是多少?kappa 阈值?
  6. 评估指标出问题时的应急回滚路径?

故障发生时首先检查什么

  1. 指标矩阵:哪个指标暴跌?快速定位是检索层还是生成层;
  2. 指标突变时间点:与最近一次 prompt / embedding / chunking / 索引版本变更对齐;
  3. trace 抽样:低分样本 vs 高分样本的 retrievedContexts 差异;
  4. gold set 状态:是否最近更新过?是否标注者换了?
  5. judge 模型:是否被上游悄悄切换?是否触发 rate limit 改用降级模型?

哪些风险必须提前设计兜底

  • Judge 服务不可用:必须不阻塞主链路;
  • Context Recall 长期下降:必须自动回滚索引;
  • CI 卡点自身漂移:阈值要随基线自动调整或人工复核;
  • 评估数据集被污染:必须版本化 + 审计;
  • 评估与生产 LLM 耦合:必须解耦并对 judge 单独做 SLO。

11. 今日练习

先独立思考,再翻到末尾看参考答案。

Q1 概念理解题

为什么 RAG 评估必须把"检索层指标"和"生成层指标"分开?为什么只看最终答案准确率不够?

Q2 生产故障分析题

业务方反馈 RAG 上线两周后"答案整体变差",但没有具体 bad case。指标显示:Faithfulness 不变、Context Precision 下降、Context Recall 持平、Answer Correctness 下降。

  1. 你怀疑根因是什么?
  2. 第一步排查动作是什么?
  3. 给出一个不需要换模型的优化方向。

Q3 AI 系统设计题

请设计一个"高风险问题(如:删除账户、转账、解除绑定)"的预发送评估与兜底架构。要求覆盖:触发条件、评估者、降级路径、审计日志、可观测性指标,并解释为什么此场景绝不接受纯 LLM-as-Judge 兜底。

Q4 Java 小实践

用 Spring AI + Kafka + RAGAS Python Sidecar,实现一个"上传 PDF → 入库 → 自动跑评估"的最小可用闭环。

具体要求:

  1. 上传接口接收 PDF,写 Kafka rag.documents topic;
  2. 消费者读取后调用 Spring AI 的 PagePdfDocumentReader → 自定义 Chunking → 写入 Milvus / Qdrant;
  3. 入库完成后发 rag.eval.request topic;
  4. 评估 Job 收到后调用 Python Sidecar 跑 RAGAS,并把结果写数据库 + Prometheus gauge。

12. 今日知识卡片

不超过 200 字。

  • 核心原理:RAG 评估体系把"答案对不对"拆成"Recall、Precision、Faithfulness、Relevancy、Correctness"五个独立指标,组成诊断矩阵,让 Chunking/Embedding/Rerank/Prompt/Model 五个变量可以精准调优。
  • 工程实践建议 :离线 RAGAS + 在线 LangFuse/OpenLLMetry + 每周人工核验,CI 卡点必须用 max_drop 防慢漂移,judge 模型与 chat 模型必须解耦并季度校准。
  • 主要风险:LLM-as-Judge 自评系统性偏高 + judge 与 chat 耦合会污染数据 + 在线评估一旦规模化极易吞噬成本。
  • 架构选型判断:超过 1 个月对外服务的 RAG 必建评估体系;评估成本不超过业务价值 5%;评估指标永远不与生产 LLM 同源。

参考答案

Q1 参考答案

RAG 由"检索"和"生成"两个子系统组成,三类故障模式独立:

  • 检索漏召导致答案无依据;
  • 检索到位但模型胡编导致 faithfulness 低;
  • 检索和生成都正常但答案飘走导致 relevancy 低。

只看最终答案准确率会把这些耦合在同一段对话里,无法定位根因。

正确的设计:检索层(Context Precision / Recall / NDCG@10)+ 生成层(Faithfulness / Answer Relevancy / Answer Correctness)+ 端到端评估,组成诊断矩阵。

Q2 参考答案

根因:检索噪声增加。新加入的文档让 top-k 里"看起来相关但实际与答案无关"的 chunk 比例上升,模型 Faithfulness 没掉(因为它可以忽略这些 chunk 并选择另一种说法),但多份不同 chunk 给出不同结论导致 Answer Correctness 下降。

第一步排查:抽样比对"修改前"和"修改后"的两份 trace,看 retrievedContexts 中"有用 chunk"占比是否显著下降;如果答案正确但 retrievedContexts 噪声大,根因就是 retriever / top-k。

不换模型的优化

  1. 降 top-k:从 10 降到 6,让噪声比例下降;
  2. 加 Metadata 过滤:把同一文档的多个 chunk 合并送入;
  3. 加 Rerank:让真正相关的 chunk 往上排;
  4. 强化 Prompt:要求模型只在被显式 support 的结论上回答。

Q3 参考答案

核心架构
#mermaid-svg-Odn4OPhaUuzxIUzc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Odn4OPhaUuzxIUzc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Odn4OPhaUuzxIUzc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Odn4OPhaUuzxIUzc .error-icon{fill:#552222;}#mermaid-svg-Odn4OPhaUuzxIUzc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Odn4OPhaUuzxIUzc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Odn4OPhaUuzxIUzc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Odn4OPhaUuzxIUzc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Odn4OPhaUuzxIUzc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Odn4OPhaUuzxIUzc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Odn4OPhaUuzxIUzc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Odn4OPhaUuzxIUzc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Odn4OPhaUuzxIUzc .marker.cross{stroke:#333333;}#mermaid-svg-Odn4OPhaUuzxIUzc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Odn4OPhaUuzxIUzc p{margin:0;}#mermaid-svg-Odn4OPhaUuzxIUzc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Odn4OPhaUuzxIUzc .cluster-label text{fill:#333;}#mermaid-svg-Odn4OPhaUuzxIUzc .cluster-label span{color:#333;}#mermaid-svg-Odn4OPhaUuzxIUzc .cluster-label span p{background-color:transparent;}#mermaid-svg-Odn4OPhaUuzxIUzc .label text,#mermaid-svg-Odn4OPhaUuzxIUzc span{fill:#333;color:#333;}#mermaid-svg-Odn4OPhaUuzxIUzc .node rect,#mermaid-svg-Odn4OPhaUuzxIUzc .node circle,#mermaid-svg-Odn4OPhaUuzxIUzc .node ellipse,#mermaid-svg-Odn4OPhaUuzxIUzc .node polygon,#mermaid-svg-Odn4OPhaUuzxIUzc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Odn4OPhaUuzxIUzc .rough-node .label text,#mermaid-svg-Odn4OPhaUuzxIUzc .node .label text,#mermaid-svg-Odn4OPhaUuzxIUzc .image-shape .label,#mermaid-svg-Odn4OPhaUuzxIUzc .icon-shape .label{text-anchor:middle;}#mermaid-svg-Odn4OPhaUuzxIUzc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Odn4OPhaUuzxIUzc .rough-node .label,#mermaid-svg-Odn4OPhaUuzxIUzc .node .label,#mermaid-svg-Odn4OPhaUuzxIUzc .image-shape .label,#mermaid-svg-Odn4OPhaUuzxIUzc .icon-shape .label{text-align:center;}#mermaid-svg-Odn4OPhaUuzxIUzc .node.clickable{cursor:pointer;}#mermaid-svg-Odn4OPhaUuzxIUzc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Odn4OPhaUuzxIUzc .arrowheadPath{fill:#333333;}#mermaid-svg-Odn4OPhaUuzxIUzc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Odn4OPhaUuzxIUzc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Odn4OPhaUuzxIUzc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Odn4OPhaUuzxIUzc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Odn4OPhaUuzxIUzc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Odn4OPhaUuzxIUzc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Odn4OPhaUuzxIUzc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Odn4OPhaUuzxIUzc .cluster text{fill:#333;}#mermaid-svg-Odn4OPhaUuzxIUzc .cluster span{color:#333;}#mermaid-svg-Odn4OPhaUuzxIUzc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Odn4OPhaUuzxIUzc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Odn4OPhaUuzxIUzc rect.text{fill:none;stroke-width:0;}#mermaid-svg-Odn4OPhaUuzxIUzc .icon-shape,#mermaid-svg-Odn4OPhaUuzxIUzc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Odn4OPhaUuzxIUzc .icon-shape p,#mermaid-svg-Odn4OPhaUuzxIUzc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Odn4OPhaUuzxIUzc .icon-shape .label rect,#mermaid-svg-Odn4OPhaUuzxIUzc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Odn4OPhaUuzxIUzc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Odn4OPhaUuzxIUzc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Odn4OPhaUuzxIUzc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 高风险
普通
通过
不通过
用户问
规则层:

关键词 + regex + 意图分类
进入预发送门
常规链路
调用小 judge LLM
Chat 生成
人工工单/拒绝
审计日志

含 traceId, promptVersion, modelId
异步评估流

触发条件:基于规则层(关键词 + 意图分类 + 用户角色权限)的"高风险标签"决定是否走预发送门,避免所有问题都跑评估(成本不可控)。

评估者:双裁判------小 judge LLM(GPT-4o-mini)做初筛 + 业务规则(白名单敏感字段)做最终决策。LLM 不直接拒绝,只输出"风险分"。

降级路径

  • judge 不可用 → 保守拒绝,要求人工确认;
  • judge 触发 rate limit → 退化为纯规则 + 保守拒绝;
  • chat 触发 rate limit → 返回业务降级文案 + 引导人工工单。

审计日志:必须包含 userId、tenantId、question、retrievedContexts、judge score、judge model id、final decision、human reviewer id(如果有人工介入)。

可观测性指标rag.sensitive.eval.precisionrag.sensitive.eval.recall(必须有人工标注子集)、rag.sensitive.override_raterag.sensitive.human_takeover_rate

为什么不能纯 LLM 兜底

  1. judge 与 chat 耦合 → 自评系统性偏高;
  2. 关键的"删除/转账/解除"操作即使 0.1% 误判率乘以巨大访问量也是不可接受的损失;
  3. 没有法规上的"可解释"路径,纯 LLM 拒绝无法提供合规证据;
  4. LLM 不应承担"执行确认"------这从来都是架构层面的人工兜底责任。

Q4 参考答案(要点)

java 复制代码
// 1. Producer
@PostMapping("/upload")
public void upload(@RequestParam MultipartFile pdf) {
    kafka.send("rag.documents", new DocumentEvent(
        UUID.randomUUID().toString(), pdf.getOriginalFilename(),
        tenantContext.current(), Instant.now()
    ));
}

// 2. Consumer
@KafkaListener(topics = "rag.documents")
public void onDoc(DocumentEvent evt) {
    Resource pdf = storage.get(evt.fileName());
    List<Document> docs = new PagePdfDocumentReader(pdf).read();
    docs = new CustomSectionChunker().apply(docs);
    vectorStore.accept(docs);
    kafka.send("rag.eval.request", new EvalRequest(evt.docId()));
}

// 3. Eval Job
@KafkaListener(topics = "rag.eval.request")
public void onEval(EvalRequest req) {
    List<EvalSample> samples = datasetStore.holdoutFor(req.docId());
    EvalResult result = ragasClient.evaluate(samples);
    db.save(req.docId(), result);
    meterRegistry.gauge("rag.eval.faithfulness", result.faithfulness());
    meterRegistry.gauge("rag.eval.context_precision", result.contextPrecision());
}

关键工程细节

  • Kafka topic 用分层避免循环:rag.documentsrag.eval.request
  • vectorStore 写入与评估异步,避免新文档写入阻塞上传链路;
  • Prometheus gauge 用 docId / 时段打 label,便于追溯单次入库后评估结果;
  • 评估 Sidecar 用 jvm 外部服务,Java 只做调度和指标归档,避免把 Python 嵌入主进程。

学习资料引用:本文涉及的 RAGAS、DeepEval、TruLens、LangFuse 等信息综合自各项目官网、PyPI、GitHub 与 2026 年上半年的工程实践文。请以具体使用时的 pip show ragas / pip show langfuse 与官方最新文档为准;本文不构成任何生产 SLA 承诺或商业背书。

相关推荐
智闲电子设计2 小时前
STM32 定时器 PWM 实战:从呼吸灯到舵机控制
c语言·stm32·单片机·嵌入式硬件·学习
世人万千丶9 小时前
鸿蒙日志体系高级应用:HiLog分级输出/隐私脱敏/远程日志采集/线上问题精准溯源方案
学习·harmonyos·鸿蒙
ctlover12 小时前
Python学习第 5 日
学习
六点_dn13 小时前
RabbitMQ学习笔记-部署和使用
笔记·学习·rabbitmq
MartinYeung515 小时前
[论文学习]OSReward:为跨平台计算机使用奖励模型建立标准化评估
人工智能·学习
dong13269715 小时前
Agent Skills学习笔记
笔记·学习
for_ever_love__16 小时前
iOS:网络请求再学习
网络·学习·ios·objective-c
世人万千丶16 小时前
鸿蒙Crash高级捕获与异常监控:全局异常兜底/崩溃栈解析/符号表还原/智能聚类/闭环修复
学习·机器学习·华为·数据挖掘·harmonyos·鸿蒙·聚类
Jul1en_17 小时前
【Claude Code Compact】源码级别的学习上下文压缩
java·前端·学习·github·ai编程