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、Log | Dataset、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 比较不同版本的结果
线上失败继续反哺离线回归
真正有价值的目标不是“收集更多数据”,而是让一次失败可以被定位、被解释、被加入测试集,并在下一次发布前被稳定复现。
参考资料
- OpenTelemetry Documentation
- OpenTelemetry GenAI Semantic Conventions
- OpenAI Evals: Build an Eval
- Anthropic: Demystifying Evals for AI Agents
- Amazon Bedrock: Evaluate Knowledge Bases
- Google Cloud: Evaluate Model-based Judges
- Langfuse: OpenTelemetry Integration
- Langfuse: Evaluation Core Concepts
- Langfuse: Self-hosting