RAG 系统的可观测性与评估体系设计

写在前面

一个 RAG 系统能够返回 HTTP 200,不代表它给出了正确答案;一次回答看起来合理,也不代表检索、引用和权限控制都没有问题。

普通应用的监控通常关注延迟、错误率和资源使用。RAG 系统还需要回答另一组问题:

  • 这次回答使用了哪些文档和 Chunk?
  • Dense Retrieval、Sparse Retrieval、融合和重排分别返回了什么?
  • 正确证据没有被召回,还是召回后被排到了后面?
  • 模型是否忠实于证据,引用是否真的支持结论?
  • Prompt、模型或检索参数改变后,系统整体变好还是变差?

这些问题不能只靠日志,也不能只靠一组离线分数解决。更完整的设计需要把 可观测性评估 连接起来:前者记录系统实际做了什么,后者判断它做得好不好。

如果需要先理解 Trace、Metric、Log、Collector 和 Context Propagation,可以先阅读:OpenTelemetry 入门:面向 AI 应用与 AI Agent 开发者的通俗指南。本文不重复解释 OpenTelemetry 的全部组件,而是集中讨论它们怎样服务于 RAG 质量闭环。


1. 可观测性与评估解决的是两类问题

可观测性和评估关系密切,但不能混为一谈。

维度可观测性评估
核心问题系统实际发生了什么系统做得好不好
主要对象Trace、Span、Metric、LogDataset、Expected Output、Score、Experiment
典型内容调用路径、延迟、Token、错误、候选文档Recall、nDCG、正确性、忠实度、引用质量
时间范围线上请求与运行状态离线回归与线上质量判断
主要用途定位故障和性能瓶颈比较方案并阻止质量回退

例如,一条 Trace 可以说明:

retrieve.dense 返回了 20 个候选
retrieve.sparse 返回了 20 个候选
reranker.rank 耗时 180ms
llm.generate 使用了 3,200 个输入 Token

但它不能单独说明:

应该出现的文档是否真的被召回
排在前面的 Chunk 是否与问题相关
生成结果是否忠实于证据
引用是否支持对应结论

因此,Trace 是评估的重要数据基础,却不是质量结论本身。


2. 先把一次 RAG 请求拆成可诊断阶段

设计 RAG Trace 时,不要为每个小函数都创建 Span。Span 应对应具有独立诊断价值、可以比较延迟或质量的阶段。

一条典型链路可以设计成:

Trace: rag.answer
├── request.validate
├── query.prepare
├── query.embed
├── retrieve
│   ├── retrieve.dense
│   ├── retrieve.sparse
│   └── retrieve.fuse
├── reranker.rank
├── context.build
├── llm.generate
├── citation.validate
└── response.finalize

如果系统包含权限过滤、查询改写或多轮 Agent,还可以增加:

query.rewrite
acl.filter
agent.plan
tool.call
guardrail.check

推荐让一次用户请求对应一条根 Trace,并通过 Context Propagation 把 API、队列和 Worker 串起来。异步任务如果没有传递 Context,入口请求和后台处理就会变成互不关联的两段记录。

每个阶段记录什么

适合记录的结构化属性包括:

service.name
service.version
deployment.environment.name
app.release.git_sha
app.prompt.version
app.knowledge_base.version
app.retrieval.config_hash

gen_ai.provider.name
gen_ai.request.model
gen_ai.usage.input_tokens
gen_ai.usage.output_tokens

app.rag.top_k
app.rag.candidate_count
app.rag.document_ids
app.rag.chunk_ids
app.rag.reranker_model

这里的 app.* 是业务自定义命名空间,不是 OpenTelemetry 官方字段。自定义属性应保持稳定、明确,并避免伪装成官方语义约定。

版本信息尤其重要。没有 Prompt、知识库、Embedding 模型、检索配置和 Git SHA,一条失败 Trace 很可能只能告诉你“出错了”,却不能回答“哪一个版本开始出错”。


3. 用四层指标理解 RAG 质量

RAG 指标最好按运行、检索、生成和业务结果分层。只看最终回答分数,会掩盖问题究竟发生在哪一层。

3.1 运行指标

运行指标反映系统是否稳定、快速和可承受:

P50 / P95 / P99 Latency
Error Rate
Timeout Rate
Token Usage
Cost per Query
Empty Retrieval Rate
Index Freshness
Ingestion Failure Rate

这些指标适合进入 Metric 和 Dashboard,用于趋势、告警和容量判断。trace_id、用户 ID、问题正文等高基数或敏感内容不应成为 Metric Label。

3.2 检索指标

检索评估回答“证据有没有找对”:

Recall@K
Precision@K
MRR
nDCG@K
Document Hit Rate
ACL Leakage Rate

如果正确文档根本没有进入候选集,应检查 Chunking、Embedding、查询改写和召回通道;如果它进入候选集却没有排到前面,应检查融合与 Reranking。分阶段保存候选结果,才能区分这两类问题。

3.3 生成指标

生成评估回答“模型是否正确使用了证据”:

Answer Correctness
Groundedness / Faithfulness
Answer Relevance
Citation Correctness
Citation Completeness
Refusal Accuracy

这些指标可能来自确定性规则、人工标注或经过校准的 LLM-as-a-Judge。涉及主观判断时,不应直接把模型评分当作绝对真值;应先用人工样本检查评分标准、一致性和偏差。

3.4 业务结果

系统最终是否有价值,还要看真实任务结果:

用户是否解决问题
是否继续追问或改写问题
点赞、点踩和人工纠错
任务完成率
转人工率
高风险错误数量

业务结果通常比单一离线指标更接近真实目标,但反馈可能延迟出现。应通过稳定的 Trace ID、Session ID 或业务任务 ID,把后续反馈关联回当时的运行版本和证据链。


4. 评测数据集必须有独立的版本真源

可观测平台可以提供 Dataset、Experiment 和 Score 界面,但评测数据不应只存在于某个网页中。

一种稳妥做法是把仓库内的 JSONL、JSON 或其他可审查文件作为版本真源:

{
  "id": "refund-policy-001",
  "question": "退款申请需要哪些材料?",
  "expected_behavior": "列出必要材料并给出政策出处",
  "expected_sources": ["refund-policy"],
  "tags": ["policy", "citation"]
}

每次评测运行至少记录:

dataset_hash
git_sha
prompt_version
knowledge_base_version
embedding_provider_and_model
retrieval_parameters
reranker_model
generation_model
evaluation_code_version

dataset_hash 用于确认测试内容是否改变;git_sha 和配置指纹用于确认被测系统版本。只有把输入数据、系统配置、执行结果和评分规则一起冻结,两个 Experiment 才真正可比较。

平台中的 Dataset 更适合作为协作、标注和实验入口。同步失败时,本地数据集和运行产物仍应保留,不能因为控制面暂时不可用而丢失评测历史。


5. 建立线上与离线相互反馈的闭环

离线评测和线上观测不应是两套彼此独立的系统。成熟的流程通常是:

生产 Trace
规则、用户反馈、人工审核或 LLM Judge 评分
发现失败样本与边界案例
加入版本化 Evaluation Dataset
修改 Prompt、模型、Chunking、Retriever 或 Reranker
运行离线 Experiment
比较基线并执行 CI Regression Gate
灰度发布并继续观察

这个闭环可以避免两种常见问题:

  • 离线数据集长期不更新,与真实用户问题脱节;
  • 线上只积累大量 Trace,却没有把失败转化为可重复测试。

对于 RAG,建议先闭环确定性较强的检索指标,再扩展生成质量和 LLM-as-a-Judge。检索阶段通常更容易建立明确的 Ground Truth,也更容易定位改动与指标变化之间的因果关系。

更完整的 RAG 分层架构和评估指标,可以参考:企业级 RAG 系统搭建指南


6. 可观测与评估控制面的架构位置

一种通用架构是让业务应用负责产生结构化遥测和评测产物,再由不同后端负责存储、展示和分析:

RAG Application
├── HTTP / Worker / Evaluation Runner
├── OpenTelemetry Instrumentation
└── Versioned Evaluation Dataset
            ├── Trace / Metric / Log
            │        ↓
            │   Collector(可选)
            │        ↓
            │   Observability Backend
            └── Dataset / Experiment / Score
                Evaluation Control Plane

这里的 Observability Backend 和 Evaluation Control Plane 可以由一个平台同时承担,也可以拆开。选择时应关注能力边界,而不是先绑定产品:

  • 是否能展示 RAG 各阶段的父子 Trace;
  • 是否能记录 Token、成本和模型信息;
  • 是否支持 Dataset、Experiment、Score 和人工标注;
  • 是否能与 OpenTelemetry 互操作;
  • 是否满足数据驻留、权限、备份和升级要求;
  • 后端不可用时是否会影响业务请求。

Langfuse、Phoenix 等属于这一层的实现选择。以 Langfuse 为例,它可以把 Trace、Dataset、Experiment 和 Score 放在同一个控制面中;当前 Python SDK 基于 OpenTelemetry,可以与其他 OTel Instrumentation 共用上下文。

如果采用自托管,产品选择会同时变成运维选择。以 Langfuse v3 的低规模 Docker Compose 部署为例,需要维护 Web、Worker、PostgreSQL、ClickHouse、Redis/Valkey 和对象存储。Docker Compose 适合测试或低规模环境,但不提供完整的高可用、水平扩展和自动备份能力。

这说明“数据留在自己的环境”并不等于“没有成本”,而是把托管成本转换成了容量、备份、恢复、升级和安全责任。


7. 避免重复上报和不完整的调用链

一个应用可能同时接入框架回调、模型网关回调、OpenTelemetry 自动插桩和平台 SDK。如果它们都为同一次模型调用创建 Generation,很容易产生重复 Trace、重复 Token 和重复成本。

接入前应先明确:

谁创建根 Trace
谁创建 Retrieval Span
谁创建 Generation Observation
谁负责自动插桩 HTTP、数据库和队列
哪些 Span 发送到 AI 可观测后端
哪些信号发送到基础设施可观测后端

如果 AI 控制面和基础设施后端共享一个 TracerProvider,可以通过不同 Span Processor 或 Exporter 分发数据,但要明确过滤规则,避免把所有低价值基础设施 Span 都发送到 AI 平台。

如果使用彼此隔离的 Provider,则要验证父子关系和 Context 是否仍然完整。错误的 Provider 边界可能导致孤立 Span,最终只看到零散的模型调用,看不到完整的 RAG 请求。


8. 默认不采集正文,把隐私边界放在应用侧

RAG Trace 很容易包含用户问题、私有文档、Prompt、证据正文和模型回答。为了调试方便而默认记录全部内容,会迅速形成新的敏感数据副本。

更安全的默认策略是:

capture_content = false

默认只发送允许列表中的结构化信息,例如:

模型和提供商
Token 与时延
候选数量
Document ID / Chunk ID
检索和重排参数
状态、错误类型和业务结果

以下内容应默认关闭,只有经过明确授权和保留策略评审后才开启:

用户问题正文
私有文档与证据正文
完整 Prompt
模型输入与输出
认证信息和个人信息

第一层脱敏应发生在应用或 SDK 侧,确保敏感内容在发送前已经被移除。Collector 或平台侧的二次过滤可以作为安全网,但不能替代客户端边界;数据一旦离开应用,后续删除和访问控制的复杂度会显著增加。


9. 可观测系统必须是可降级旁路

遥测和评估控制面用于理解业务,不应反过来成为业务的硬依赖。

推荐遵守以下原则:

  • 使用批处理和异步导出,避免在主请求中等待遥测后端;
  • Exporter 网络错误记录日志,但不改变正常业务返回;
  • 短生命周期进程在退出前显式 Flush 或 Shutdown;
  • 本地评测结果先落盘,再异步同步到远端控制面;
  • 监控队列容量、丢弃量、重试次数和磁盘占用;
  • 凭据配置错误应在启用遥测时尽早暴露,不能长期静默丢数据。

“后端不可用时业务继续运行”和“配置错误时快速失败”并不矛盾:前者处理运行期外部故障,后者防止系统在看似启用、实际上没有任何数据的状态下长期运行。


10. 推荐的渐进式落地顺序

不必第一天就搭建完整平台。可以按以下顺序逐步建设:

第一阶段:统一日志与请求身份

  • 为请求分配稳定的 Request ID;
  • 使用结构化 JSON 日志;
  • 记录模型、Token、时延、错误和行为结果;
  • 不采集敏感正文。

第二阶段:建立核心 Trace

  • 一次 RAG 请求对应一条根 Trace;
  • 为 Retrieval、Reranking 和 Generation 创建手动 Span;
  • 打通 HTTP、队列和 Worker 的 Context;
  • 写入 Git SHA、Prompt、知识库和检索配置版本。

第三阶段:增加指标与控制面

  • 聚合延迟、错误率、Token、成本和空检索率;
  • 接入 Collector 或可观测后端;
  • 验证采样、过滤、重试和故障降级;
  • 建立备份、容量和升级责任。

第四阶段:建立可重复评测

  • 准备版本化 Golden Dataset;
  • 先完成 Recall、MRR、nDCG 等检索指标;
  • 保存样例级结果和聚合指标;
  • 记录 Dataset Hash、Git SHA 和运行配置。

第五阶段:形成持续改进闭环

  • 把线上失败样本加入 Dataset;
  • 引入人工反馈和经过校准的 LLM Judge;
  • 对 Prompt、模型和检索方案运行 Experiment;
  • 在 CI 中设置经过验证的回归门槛。

11. 设计检查清单

上线前可以逐项确认:

Trace

  • 一次请求能否看到从查询处理到最终回答的完整链路?
  • API、队列和 Worker 是否共享同一 Context?
  • 是否能从失败回答定位到具体检索候选和运行版本?

Evaluation

  • 是否同时评估检索、生成和业务结果?
  • 数据集是否可版本化、可审查和可重复运行?
  • Experiment 是否记录 Dataset Hash、Git SHA、模型和检索配置?

Privacy

  • 是否默认关闭问题、证据、Prompt 和回答正文采集?
  • 敏感内容是否在离开应用前完成过滤?
  • 是否有数据保留、删除和访问控制策略?

Reliability

  • 遥测后端不可用时,业务是否仍能正常响应?
  • 本地评测结果是否独立保存?
  • 队列、丢弃、磁盘、备份和恢复是否有人负责?

结语

RAG 可观测性不只是给每次模型调用加一条 Trace,Evaluation 也不只是运行一次 LLM-as-a-Judge。

更完整的体系应当形成这样的关系:

Trace 记录一次请求经历了什么
Metric 展示系统整体表现如何
Log 保存具体事件和错误细节
Dataset 固化需要重复验证的问题
Score 表达质量判断
Experiment 比较不同版本的结果
线上失败继续反哺离线回归

真正有价值的目标不是“收集更多数据”,而是让一次失败可以被定位、被解释、被加入测试集,并在下一次发布前被稳定复现。


参考资料