Chico Notes
LLM Wiki / RAG

RAG 可观测性生产实战:Trace、OpenTelemetry、隐私治理与发布闭环

从 Trace 树、检索候选、Context Manifest、模型调用、引用映射到采样、脱敏、成本、SLO 和回放,设计一套可解释、可复现、可治理的生产级 RAG 可观测性体系。

持续修订的工程笔记

RAG 系统上线后,真正困难的问题往往不是“回答错了”,而是团队无法回答下面这些问题:

  • 用户原始问题被怎样补全和改写?
  • Tenant、ACL、知识库、语言和文档状态范围是怎样确定的?
  • BM25、向量检索和其他召回通道分别返回了什么?
  • 正确证据是在召回、融合、Rerank 还是 Context Pack 阶段丢失的?
  • 最终送给模型的到底是哪几个 Chunk、哪个 revision、什么顺序?
  • 模型看到的 Prompt、工具定义、参数和版本是什么?
  • 回答中的每条引用对应哪一段证据?
  • 这次回答为什么变慢、变贵、发生降级或拒答?
  • 用户反馈回来以后,能否定位到原始执行并进入回归评测?
  • 一个月后,团队还能不能解释这次回答是怎样产生的?

如果系统只保存最终问题、最终 Prompt 和最终回答,它仍然缺少可观测性。生产级 RAG 需要保存的是执行因果链:每一步输入什么、输出什么、基于哪个版本、花了多少资源、发生了什么决策,以及它与下一步之间怎样关联。

资料快照:本文依据 OpenTelemetry、W3C Trace Context、OpenInference 与 Langfuse 官方资料核查,截止日期为 2026-08-05。OpenTelemetry 核心 Semantic Conventions 当前为 1.44.0;GenAI 语义约定已经迁移到独立仓库,并仍标记为 Development。本文给出的 rag.* 字段是面向工程落地的内部契约示例,不是新的行业标准。实施时应冻结自己的 Schema Version,并按实际 SDK、Collector 与观测后端版本验证。

一句话结论

生产级 RAG 可观测性不是“把所有 Prompt 打进日志”,而是建立下面这套可治理的数据链路:

一个用户 Turn 对应一条 Trace
→ 每个阶段对应稳定命名的 Span
→ 高价值决策使用结构化 Attributes / Events
→ 大候选集和完整内容进入受控 Artifact Store
→ Logs 通过 TraceId / SpanId 关联
→ Metrics 只使用低基数字段聚合
→ Feedback 与 Eval 作为 Score 追加到 Trace
→ Bad Case 可以从线上 Trace 生成回归样本
→ Release Version 可以在离线实验和线上灰度之间比较

最重要的七条原则是:

  1. 一条 Trace 应表示一次完整、可解释的用户操作,通常是一个会话 Turn,而不是整个长期会话。
  2. Trace 要保存决策链,不要只保存最终 Prompt。
  3. 内容、身份和权限默认不应明文进入所有遥测;完整内容必须分级、脱敏、加密和限时保留。
  4. 稳定 ID、revision、模型、Prompt、索引和配置版本比大段文本更重要。
  5. Metrics 用于聚合,Trace 用于解释,Logs 用于事件细节,Artifact 用于受控回放,四者不能混成一个 JSON。
  6. 成功降级、空召回和策略拒答不一定是系统错误,但必须作为业务结果显式记录。
  7. 每一个线上坏例都应该能够回到 Trace,再进入评测集和发布门禁。

1. 先划清边界:可观测性不等于评测,也不等于发布门禁

这几件事经常被放在同一个平台里,但职责不同。

能力回答什么问题核心对象
Observability这一次请求实际发生了什么?Trace、Span、Event、Log、Metric、Artifact
Evaluation这次或这一批结果好不好?Dataset、Rubric、Score、Judge、Experiment
Bad Case Analysis为什么错,应该由谁修?Trace、Expected Evidence、Failure Type、Owner
Release Gate新版本能不能发布,如何灰度和回滚?Baseline、Candidate、Diff、Threshold、Canary

本文专注于第一层:怎样把一次 RAG 执行记录成可查询、可关联、可回放、可治理的数据

评测指标、样本标注和发布决策分别由下面几篇文章展开:

可观测性是它们共同依赖的数据基础。如果 Trace 缺失、字段不稳定或内容不可追溯,后面的评测和发布门禁都会退化为主观判断。


2. 什么应该算“一条 Trace”

OpenTelemetry 中,Trace 是由具有共同 trace_id 的 Span 组成的执行树。Langfuse 的数据模型也把一次请求或操作表示为 Trace,再用 Session 聚合多条 Trace。

对聊天式 RAG,推荐:

Session
→ 一段连续会话

Trace
→ 用户的一次 Turn:从输入到最终回答或拒答

Span
→ Query Plan、Embedding、Retrieval、Rerank、Context、LLM 等单个操作

Event / Log
→ Span 内某个时点发生的离散事件

Score
→ 对 Trace、Span 或 Session 追加的质量判断

2.1 为什么不建议整个会话只用一条 Trace

长会话可能持续数小时甚至数天。如果所有 Turn 都挂在同一条 Trace 下,会带来:

  • Span 数量持续增长;
  • 单条 Trace 过大,查询和渲染变慢;
  • 单个 Turn 的延迟、成本和错误边界不清楚;
  • 采样、保留和权限控制更困难;
  • 某一次发布版本无法独立比较;
  • 异步反馈和评测难以精确归属。

更合理的做法是用 session_id 聚合多条 Turn Trace,并在每条 Trace 上记录:

conversation_id
turn_index
previous_trace_id
history_snapshot_hash
history_compacted

2.2 异步任务不要伪装成同步子 Span

用户回答完成后,可能还有:

  • 异步 LLM Judge;
  • 人工标注;
  • 用户反馈;
  • Bad Case 分类;
  • 离线 Replay;
  • 数据回填和 Repair;
  • 评测集生成。

这些操作可以在原 Trace 上追加 Score,或者创建新的 Trace,并使用原 trace_id、稳定业务 ID 或 Span Link 关联。不要把几小时后执行的评测硬塞成一个尚未结束的同步子 Span。


3. 一棵可用于生产调试的 RAG Trace 树

推荐把根 Span 命名为稳定操作名,例如:

rag.request

不要把用户问题、模型名或动态 ID 放进 Span Name。动态信息应放在 Attributes 中,否则后端会产生大量不可聚合的名称。

下面是一棵相对完整的 Trace:

3.1 不要为每个函数都创建 Span

Span 应对应具有独立调试价值的操作,而不是代码调用栈的每一层。

适合创建 Span:

  • 跨服务或跨进程调用;
  • 模型、数据库、搜索或外部 API 调用;
  • 明确的候选变换阶段;
  • 可能超时、重试、降级或被独立评测的操作;
  • 需要独立延迟和成本预算的操作。

不适合创建大量 Span:

  • 简单 Getter;
  • 无业务意义的 JSON 编解码;
  • 每个候选的一次普通循环;
  • 框架内部无法用于归因的细粒度调用;
  • 与 HTTP、数据库自动埋点完全重复的手工 Span。

Langfuse 的官方实践也强调,Trace 结构不仅用于展示,Evaluator、Dashboard、Dataset Experiment 和 Saved View 都会依赖稳定的 Observation 名称与层级。过多无意义 Span 会让真正重要的步骤被噪声淹没。


4. Trace、Span、Event、Log、Metric 与 Artifact 的职责

4.1 Trace / Span:解释执行路径

Span 适合记录:

  • 操作开始和结束时间;
  • 父子关系;
  • 状态和错误类型;
  • 低到中等体积的结构化 Attributes;
  • 关键阶段输入输出摘要;
  • 版本、模型、候选数量、Token、成本和降级结果。

4.2 Event:记录 Span 内的离散时点

例如流式生成 Span 内可以记录:

request.sent
first_chunk.received
tool_call.received
stream.cancelled
stream.completed

Event 适合表示“发生了什么”,而不是持续时间很长的独立阶段。

4.3 Log:记录可搜索的异常和业务事件

结构化 Log 应至少带:

trace_id
span_id
request_id
service.name
error.type
error.code

OpenTelemetry Logs Data Model 把 TraceIdSpanId 定义为日志顶层字段,使 Log 可以直接与对应 Trace 和 Span 关联。

4.4 Metric:聚合趋势和告警

Metric 适合:

  • QPS;
  • Error Rate;
  • P50 / P95 / P99;
  • Token 和成本总量;
  • Cache Hit Rate;
  • Empty Retrieval Rate;
  • Degrade Rate;
  • Rerank Timeout Rate;
  • Citation Coverage;
  • Trace Export Drop Rate。

不要把 request_idtrace_idchunk_id、完整 Query、用户原文或任意文档 ID 放入 Metric Label。它们会制造高基数,增加存储和查询成本。

4.5 Artifact:保存大对象和可回放材料

大候选集、完整 Context、渲染后的 Prompt、长模型输出和原始工具结果,不适合全部塞进 Span Attributes。

可以保存到受控对象存储:

artifact://rag-traces/2026/08/05/{trace_id}/retrieval-candidates.json.zst
artifact://rag-traces/2026/08/05/{trace_id}/context-manifest.json
artifact://rag-traces/2026/08/05/{trace_id}/rendered-prompt.enc

Span 中只记录:

artifact.uri
artifact.sha256
artifact.content_type
artifact.byte_size
artifact.encryption_key_id
artifact.retention_class
artifact.redaction_level

这能让 Trace 保持轻量,同时保留在授权条件下回放一次执行的能力。


5. 使用 W3C Trace Context 串起分布式链路

W3C Trace Context 标准定义了两个主要 HTTP Header:

traceparent
tracestate

traceparent 携带 Trace ID、父 Span ID 和 Trace Flags;tracestate 用于厂商或系统扩展。它们的作用是让不同服务和不同观测工具能够参与同一条分布式 Trace。

5.1 traceparenttracestate 不能放业务内容

W3C 规范明确要求不要把个人身份或敏感信息写入这些 Header。它们只用于 Trace 关联。

不要这样做:

tracestate: tenant=company-secret,user=email@example.com

5.2 Baggage 也不是免费的全局 Metadata

OpenTelemetry Baggage 可以跨服务传播上下文,但敏感 Baggage 可能被无意发送到第三方 API。

Baggage 只适合少量、经过评估的字段,例如:

environment=production
release_id=rag-2026-08-05
experiment_cohort=rerank-v2

对 Tenant 和 User,优先传播不可逆或作用域内的内部标识:

tenant_hash
user_hash

不要传播:

  • Email;
  • 手机号;
  • 原始 Access Token;
  • 文档权限列表;
  • 用户问题全文;
  • Prompt 或模型输出;
  • 内部密钥和物理数据库地址。

6. 语义约定:标准优先,但必须冻结内部 Schema

6.1 OpenTelemetry GenAI Semantic Conventions

截至本文快照,OpenTelemetry 核心 Semantic Conventions 为 1.44.0,GenAI 约定已经迁移到独立的 open-telemetry/semantic-conventions-genai 仓库。该仓库覆盖:

  • GenAI Inference;
  • Embedding;
  • Retrieval;
  • Tool Execution;
  • Memory;
  • Agent;
  • Metrics 与 Events。

当前 GenAI Span 文档仍标记为 Development。其中已经定义或讨论了:

gen_ai.operation.name
gen_ai.provider.name
gen_ai.request.model
gen_ai.response.model
gen_ai.response.time_to_first_chunk
gen_ai.usage.input_tokens
gen_ai.usage.output_tokens
gen_ai.usage.cache_read.input_tokens
gen_ai.usage.reasoning.output_tokens
gen_ai.input.messages
gen_ai.output.messages
gen_ai.retrieval.documents

其中输入、输出和检索 Query 等内容字段可能含有 PII,被定义为 Opt-In 或明确标注敏感风险。

6.2 为什么还需要内部 rag.* 契约

行业约定仍在演进,而且通用 GenAI Schema 无法覆盖每个业务决策。建议:

标准字段
→ 用于模型、Token、Trace、HTTP、错误和通用 GenAI 操作

内部 rag.* 字段
→ 用于 Query Plan、候选预算、RRF、Parent 回填、Context Pack、引用和发布版本

同时记录:

rag.telemetry.schema_version = 3

这样字段改名、结构升级和后端迁移时,仍能解释旧 Trace。

6.3 OpenInference 可以作为 AI Span 分类参考

OpenInference 是建立在 OpenTelemetry 之上的 AI 可观测性约定,定义了常见 Span Kind:

LLM
EMBEDDING
CHAIN
RETRIEVER
RERANKER
TOOL
AGENT
GUARDRAIL
EVALUATOR
PROMPT

它非常适合表达 RAG 和 Agent 的操作类型。但团队应避免同时无规则地输出三套含义重叠的字段。推荐先确定一个内部 Canonical Model,再映射到:

OTLP + OpenTelemetry GenAI

OTLP + OpenInference

特定 LLM Observability Backend

7. Resource、Trace 与 Span Attributes 应怎样分层

7.1 Resource:描述谁产生了遥测

Resource Attributes 在一个进程或服务实例上相对稳定:

{
  "service.name": "rag-search-service",
  "service.version": "2026.08.05-3f91c2a",
  "deployment.environment.name": "production",
  "cloud.region": "ap-beijing",
  "container.image.id": "sha256:...",
  "rag.telemetry.schema_version": 3
}

7.2 Root Trace:描述这一次用户操作

{
  "rag.request.id": "req_01J4...",
  "rag.session.id": "conv_123",
  "rag.turn.index": 17,
  "rag.route": "knowledge_qa",
  "rag.query.type": "concept_with_constraint",
  "rag.release.id": "rag-2026-08-05.2",
  "rag.pipeline.version": "pipeline-v12",
  "rag.experiment.cohort": "hybrid-rerank-v3",
  "rag.tenant.hash": "t_5f2a...",
  "rag.user.hash": "u_91bc...",
  "rag.content.capture_level": "metadata_only"
}

7.3 Span:描述某一步操作

例如 Query Planning:

{
  "rag.query_plan.version": "qp-v6",
  "rag.query_plan.strategy": "original_plus_rewrite",
  "rag.query_plan.subquery_count": 3,
  "rag.query_plan.anchor_count": 2,
  "rag.query_plan.filter_count": 4,
  "rag.query_plan.validation_passed": true,
  "rag.query_plan.fallback": false
}

例如 Hybrid Retrieval:

{
  "rag.retrieval.mode": "hybrid",
  "rag.retrieval.channels": ["bm25", "knn"],
  "rag.retrieval.candidate_budget": 160,
  "rag.retrieval.candidate_count": 127,
  "rag.retrieval.unique_document_count": 42,
  "rag.retrieval.empty": false,
  "rag.retrieval.partial": false,
  "rag.retrieval.physical_index": "kb_chunks_v7",
  "rag.retrieval.alias": "kb_chunks"
}

例如 Reranker:

{
  "rag.rerank.model_key": "bge-reranker-v2-m3|gpu-pool-a",
  "rag.rerank.input_count": 50,
  "rag.rerank.output_count": 10,
  "rag.rerank.input_tokens": 18620,
  "rag.rerank.queue_ms": 7,
  "rag.rerank.inference_ms": 83,
  "rag.rerank.truncated_count": 4,
  "rag.rerank.applied": true,
  "rag.rerank.fallback": false
}

8. Retrieval Trace:候选集应该记录到什么粒度

Retrieval 是 RAG 最容易“有结果但无法解释”的阶段。

每个候选至少需要这些稳定字段:

chunk_id
knowledge_id
knowledge_base_id
parent_chunk_id
source_revision
projection_schema_version
physical_index
retrieval_channel
raw_rank
raw_score
fused_rank
fused_score
filter_scope_hash

Rerank 后再增加:

rerank_rank
rerank_score
truncated
selected
removed_reason
context_order

8.1 一份候选 Manifest

{
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "query_plan_id": "qp_001",
  "scope_hash": "sha256:8bb7...",
  "channels": {
    "bm25": {
      "latency_ms": 18,
      "total_hits": 80,
      "items": [
        {
          "chunk_id": "chunk_001",
          "knowledge_id": "doc_017",
          "source_revision": 42,
          "rank": 1,
          "score": 16.31
        }
      ]
    },
    "knn": {
      "latency_ms": 31,
      "k": 80,
      "num_candidates": 320,
      "items": [
        {
          "chunk_id": "chunk_009",
          "knowledge_id": "doc_021",
          "source_revision": 8,
          "rank": 1,
          "score": 0.847
        }
      ]
    }
  },
  "fusion": {
    "type": "rrf",
    "rank_constant": 60,
    "rank_window_size": 100,
    "output_count": 126
  }
}

8.2 Span 只保存摘要,Artifact 保存完整候选

Span Attributes 中建议保存:

candidate_count
latency
channel
config version
artifact hash
Top-3 candidate IDs

完整的 100–500 个候选放到 Artifact Store。原因是:

  • Span Attribute 大小有现实限制;
  • 大数组增加导出和存储成本;
  • 候选内容常含敏感文档;
  • 长列表不适合 Dashboard 聚合;
  • 需要单独设置访问权限和保留期限。

8.3 必须保存被排除的原因

很多坏例并不是搜索分数低,而是正确证据被提前排除了。

推荐原因枚举:

acl_denied
tenant_mismatch
doc_disabled
doc_archived
language_mismatch
version_stale
duplicate_chunk
duplicate_parent
rerank_below_threshold
context_budget_exceeded
citation_unavailable

如果只保存最终 Top-K,不保存筛选过程,权限错误和 Context 丢失问题很难区分。

相关检索阶段可以结合下面三篇文章:


9. Context Manifest:模型到底看到了什么

最终回答的真实性依赖模型实际收到的 Context,而不是检索系统曾经找到过什么。

推荐为每次生成保存 Context Manifest:

{
  "context_pack_version": "ctx-v8",
  "token_budget": 6000,
  "token_estimate": 4872,
  "documents": [
    {
      "context_order": 1,
      "citation_key": "S1",
      "knowledge_id": "doc_017",
      "chunk_id": "chunk_001",
      "parent_chunk_id": "parent_001",
      "source_revision": 42,
      "projection_schema_version": 3,
      "content_hash": "sha256:3cc1...",
      "title": "MySQL 到 Elasticsearch 的一致性",
      "source_uri_hash": "sha256:86aa...",
      "start_offset": 1280,
      "end_offset": 2496,
      "token_count": 912,
      "truncated": false,
      "retrieval_channels": ["bm25", "knn"],
      "fused_rank": 1,
      "rerank_rank": 1
    }
  ],
  "dropped": [
    {
      "chunk_id": "chunk_004",
      "reason": "duplicate_parent"
    },
    {
      "chunk_id": "chunk_022",
      "reason": "context_budget_exceeded"
    }
  ]
}

9.1 为什么必须有 source_revisioncontent_hash

用户看到回答时,源文档可能是 revision 42;几天后文档已经更新到 47。

如果 Trace 只保存 chunk_id,复盘时读取当前 Chunk 会得到不同内容。必须至少保存:

chunk_id
source_revision
content_hash

高合规场景还应保存受控快照或可访问的历史 revision。

9.2 Context 顺序也是执行事实

LLM 对上下文位置敏感。即使候选集合相同,顺序变化也可能改变回答。

因此应记录:

context_order
citation_key
section separator
truncation policy
conflict ordering

9.3 不要把 Context Manifest 当成全文副本

Manifest 主要保存身份、版本、位置和决策。完整内容可以:

  • 不保存;
  • 只保存脱敏摘要;
  • 保存加密快照;
  • 保存内容寻址对象;
  • 通过关系库 revision 表或对象存储历史版本恢复。

选择取决于数据敏感度、合规要求和 Bad Case 调试价值。


10. Prompt 与模型调用需要保存哪些版本

一次生成 Span 至少应记录:

provider
request_model
response_model
model_endpoint_key
prompt_name
prompt_version
prompt_hash
system_instruction_hash
input_message_count
input_tokens
output_tokens
cache_read_tokens
reasoning_tokens
streaming
first_chunk_ms
finish_reason
temperature
top_p
max_tokens
retry_count

10.1 Prompt Name、Version 与 Hash 都需要

prompt_name
→ 人类理解的稳定模板名

prompt_version
→ 发布与回滚版本

prompt_hash
→ 实际渲染前模板或渲染后内容的完整性校验

只记录 prompt_version=v4 不够。如果部署错误、变量渲染差异或后台配置被修改,Hash 才能证明实际使用的内容。

10.2 Request Model 与 Response Model 可能不同

部分服务会将请求模型路由到具体快照或部署版本,因此要同时保存:

gen_ai.request.model
gen_ai.response.model
rag.model.endpoint_key
rag.model.route_policy_version

10.3 Streaming 需要独立时间点

只记录总延迟会掩盖用户体验:

request_start
provider_request_sent
first_chunk_received
first_visible_token
answer_complete

对实时语音或 Voice Agent,还可以继续增加:

first_audio_frame_ms
tts_first_packet_ms
playback_start_ms
barge_in_detected_ms
cancel_propagation_ms

OpenTelemetry GenAI 约定已经包含 gen_ai.response.time_to_first_chunk,适合统一流式模型的首块延迟。


11. Citation Trace:答案正确不代表引用正确

引用应该是独立阶段,而不是模型回答中的装饰字符串。

建议记录:

claim_id
claim_text_hash
citation_key
chunk_id
source_revision
support_type
support_score
verification_result
failure_reason

support_type 可以是:

direct
partial
multi_source
contradicted
unsupported

Citation Span 的摘要 Attributes:

{
  "rag.citation.claim_count": 8,
  "rag.citation.cited_claim_count": 7,
  "rag.citation.coverage": 0.875,
  "rag.citation.invalid_count": 1,
  "rag.citation.verifier_version": "cite-v4"
}

完整 Claim–Evidence 对照放入 Artifact。这样引用错误可以与生成错误分开归因。

相关内容见:


12. 成功、降级、拒答和错误要区分

12.1 系统错误

例如:

  • Elasticsearch 超时;
  • Embedding 服务 500;
  • Rerank OOM;
  • JSON Schema 解析失败;
  • Prompt 渲染异常;
  • Collector Export 失败。

应记录:

span.status = ERROR
error.type
error.code
retryable
attempt

12.2 成功降级

例如 Embedding 超时后退化为 BM25,最终仍返回有证据答案。

根 Trace 可以保持成功,但要记录:

{
  "rag.result.status": "success_degraded",
  "rag.degraded": true,
  "rag.degrade.from": "hybrid",
  "rag.degrade.to": "bm25_only",
  "rag.degrade.reason": "embedding_timeout"
}

失败的 Embedding Span 仍标为 ERROR。

12.3 空召回

空召回不是基础设施错误:

{
  "rag.result.status": "no_evidence",
  "rag.retrieval.empty": true,
  "rag.answer.behavior": "refuse"
}

12.4 策略拒答

无权限、敏感请求或证据不足导致的拒答,是业务决策:

rag.result.status = refused
rag.refusal.reason = insufficient_evidence | acl_denied | safety_policy
rag.refusal.policy_version = refusal-v5

如果把所有拒答都算 Error Rate,Dashboard 会误导团队;如果完全不记录,又无法发现拒答率异常。


13. Logs、Metrics 与 Traces 怎样关联

13.1 Log 示例

{
  "timestamp": "2026-08-05T10:42:11.283Z",
  "severity": "WARN",
  "message": "reranker timed out; falling back to RRF",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "request_id": "req_01J4...",
  "service": "rag-search-service",
  "error_type": "deadline_exceeded",
  "fallback": "rrf"
}

13.2 Metric 维度要低基数

可以作为 Label:

environment
service
region
route
query_type
retrieval_mode
model_family
release_id
result_status
fallback_type

不应该作为 Label:

trace_id
request_id
user_id
query text
chunk_id
document URL
full model response

13.3 从 Metric 跳回 Trace

理想流程是:

P95 Rerank 延迟告警
→ 按 release、region、model_key 分组
→ 找到代表性 Trace
→ 查看 queue_ms 与 inference_ms
→ 确认是排队、模型变慢还是输入变长

这比“看一条 500 日志”更接近真正的可观测性。


14. 采样:不是所有成功请求都要永久保存全文

OpenTelemetry 区分 Head Sampling 与 Tail Sampling。

14.1 Head Sampling

在请求开始时决定是否采样。

优点:

  • 简单;
  • 成本低;
  • 容易按 Trace ID 做确定性概率采样。

缺点:

  • 做决定时还不知道最终是否错误、变慢或发生降级;
  • 可能恰好丢掉最有价值的坏例。

14.2 Tail Sampling

等一条 Trace 的大部分 Span 完成后,再根据完整结果决定保留。

适合保留:

  • 包含 Error Span 的 Trace;
  • 总延迟超过阈值;
  • 发生降级;
  • 用户点差评;
  • 权限或安全命中;
  • 新 Release / Canary;
  • 高价值租户或关键业务路由;
  • 随机抽样的成功请求。

代价:

  • Collector 需要暂存 Trace;
  • 分布式 Span 必须尽量汇聚到能做完整判断的 Collector;
  • 内存、网络和扩缩容更复杂;
  • 晚到 Span 和跨 Collector 路由需要处理。

14.3 推荐采样矩阵

Trace 类型Metadata Trace脱敏内容完整加密 Artifact
Error100%100% 或高比例按权限与严重度
Safety / ACL100%100%短 TTL、严格审计
用户差评100%100%经授权保留
Degraded100%高比例代表性样本
P99 Slow100%高比例代表性样本
Canary Release100%中高比例小比例
普通成功1%–10%更低比例默认不保存
Internal Health Check低比例或丢弃不保存不保存

采样率只是示例。真正策略应结合 QPS、数据敏感度、存储预算和问题发生率。

14.4 Metrics 不应跟随 Trace Sampling 丢失

即使只保存 5% Trace,QPS、Error Rate、Latency Histogram 和 Cost 等聚合 Metric 仍应覆盖全部请求,否则 Dashboard 会产生偏差。


15. OpenTelemetry Collector:在出口统一处理遥测

OpenTelemetry 官方通常建议生产环境通过 Collector 接收、处理和导出遥测。Collector 可以承担:

  • Batch;
  • Retry;
  • Memory Limiting;
  • Attribute 删除或替换;
  • Redaction;
  • Filter;
  • Tail Sampling;
  • 多后端导出;
  • Resource Enrichment。

下面是一份示意配置。不同 Collector Distribution 中 Processor 名称、稳定级别和配置项可能不同,必须用目标版本测试:

receivers:
  otlp:
    protocols:
      grpc: {}
      http: {}

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 1024

  attributes/rag_redact:
    actions:
      - key: user.email
        action: delete
      - key: user.full_name
        action: delete
      - key: gen_ai.input.messages
        action: delete
      - key: gen_ai.output.messages
        action: delete

  transform/rag_governance:
    error_mode: ignore
    trace_statements:
      - set(span.attributes["rag.telemetry.exported"], true)

  batch:
    send_batch_size: 1024
    timeout: 2s

exporters:
  otlphttp/traces:
    endpoint: https://trace-backend.example.com

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors:
        - memory_limiter
        - attributes/rag_redact
        - transform/rag_governance
        - batch
      exporters: [otlphttp/traces]

15.1 为什么在 Collector 再做一道治理

应用侧脱敏仍然是第一道防线,但 Collector 可以提供集中保护:

  • 某个服务错误地增加敏感 Attribute 时统一删除;
  • 不同语言 SDK 的字段统一重命名;
  • 按 Environment 和 Tenant Class 使用不同出口;
  • 防止 Debug 环境的内容配置被直接带进 Production 后端;
  • 对不可信服务使用 Allowlist Redaction。

OpenTelemetry 官方文档列出了 Attributes、Filter、Redaction 和 Transform 等 Processor,可用于删除、Hash、过滤和改写敏感遥测。


16. 隐私治理:建立内容采集等级,而不是一个全局开关

Prompt、Context、工具结果和模型输出可能包含:

  • PII;
  • 公司机密;
  • 用户上传文档;
  • Access Token;
  • 医疗、财务或法律信息;
  • 未公开代码;
  • 跨租户数据;
  • Prompt Injection 内容。

推荐定义内容等级:

Level保存什么默认用途建议保留
L0 Metadata OnlyID、Hash、版本、分数、数量、延迟全量生产 Trace相对较长
L1 Redacted Snippet脱敏 Query、短证据片段、错误摘要Bad Case 调试较短
L2 Encrypted Replay完整 Prompt、Context、输出和工具结果严格授权回放最短、审计访问
L3 Prohibited密钥、Token、明文敏感字段不采集不保存

Trace 上记录:

rag.content.capture_level
rag.content.redaction_policy_version
rag.content.retention_class
rag.content.legal_basis

16.1 Hash 不是匿名化的万能答案

对低熵信息,例如手机号、Email、工号,简单 SHA-256 仍可能被字典反查。应使用:

  • 作用域内 HMAC;
  • 定期轮换的密钥;
  • Tenant 隔离的 Salt;
  • Tokenization Service;
  • 业务不可逆代理 ID。

16.2 遥测后端同样需要租户隔离

不要因为是“日志平台”就忽略权限。至少要有:

  • Project / Tenant 隔离;
  • RBAC;
  • 审计日志;
  • 内容字段访问控制;
  • 导出限制;
  • Retention Policy;
  • 删除与合规流程;
  • 加密和密钥轮换。

16.3 不要把原始用户内容放入 Span Name、Metric Label 或 Baggage

这些字段更容易被广泛复制、索引和聚合,最难完成精确删除。


17. 成本观测:Provider Usage 与内部估算要分开

一次 RAG 请求的成本可能包括:

Query Rewrite LLM
Query Embedding
BM25 / Vector Search
Reranker
Context Compression
Answer LLM
Citation Verifier
Safety / Judge
Telemetry Export 与存储

建议同时保存:

provider_reported_tokens
provider_reported_cost
estimated_cost
price_table_version
currency
cache_read_tokens
cache_write_tokens
reasoning_tokens

17.1 为什么要有 Price Table Version

模型价格会变化,同一模型在不同地区、托管平台和购买方式下也可能不同。

rag.cost.price_table_version = pricing-2026-08-01

否则一个月后重新计算成本,结果可能与当时财务口径不一致。

17.2 不要只算 LLM Token

Reranker GPU、向量检索、跨地域流量、对象存储和 Trace 全文保存也可能成为主要成本。

推荐 Span 级成本摘要:

{
  "rag.cost.estimated_usd": 0.0028,
  "rag.cost.unit": "request",
  "rag.cost.price_table_version": "pricing-2026-08-01",
  "rag.cost.component": "reranker"
}

根 Trace 汇总:

rag.cost.total_estimated_usd
rag.cost.total_provider_usd
rag.cost.telemetry_usd

18. Feedback 与 Evaluation 应作为可追加 Score

用户反馈、人工标注和异步 Judge 通常在 Trace 完成后才出现。

推荐 Score 数据:

{
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "target": "trace",
  "name": "user_feedback",
  "type": "categorical",
  "value": "negative",
  "source": "end_user",
  "comment": "引用的政策已经过期",
  "created_at": "2026-08-05T11:00:00Z"
}

还可以挂到具体 Span:

retrieval_relevance
rerank_quality
context_precision
citation_accuracy
faithfulness
latency_satisfaction

Langfuse 的 Score 模型允许把 Numeric、Categorical、Boolean 或 Text Score 关联到 Trace、Observation、Session 或 Dataset Run。其 v4 数据模型还强调 Trace 与 Observation 应视为不可变数据,后续质量判断应通过 Score 增补,而不是重新写同一个 Trace ID 覆盖旧记录。

这是一条很重要的数据治理原则:

执行事实保持不可变,后续判断作为新事实追加。


19. Dashboard:从基础设施指标推进到 RAG SLI

19.1 Availability

rag_request_success_rate
rag_request_error_rate
rag_request_refusal_rate
rag_request_no_evidence_rate
rag_request_degraded_rate

19.2 Latency

rag_request_duration_ms
query_plan_duration_ms
embedding_duration_ms
retrieval_duration_ms
rerank_queue_ms
rerank_inference_ms
context_pack_duration_ms
generation_first_chunk_ms
generation_duration_ms

19.3 Retrieval Quality Proxy

线上没有标准答案时,可以监控代理指标:

empty_retrieval_rate
bm25_only_rate
vector_only_rate
hybrid_overlap_rate
rerank_score_distribution
context_document_count
context_token_count
duplicate_removal_rate

代理指标不是最终质量,但突然变化通常说明数据或配置漂移。

19.4 Citation 与 Grounding

answer_with_citation_rate
citation_coverage
citation_invalid_rate
unsupported_claim_rate
conflicting_evidence_rate

19.5 Cost

cost_per_request
cost_per_successful_answer
input_tokens_per_request
output_tokens_per_request
cache_read_ratio
rerank_pairs_per_request
telemetry_bytes_per_request

19.6 Safety 与权限

acl_denied_count
cross_tenant_prevented_count
prompt_injection_detected_count
sensitive_content_redacted_count
policy_refusal_rate

19.7 Telemetry 自身健康度

trace_export_success_rate
trace_export_queue_size
trace_dropped_count
collector_memory_usage
artifact_write_failure_rate
trace_completeness_rate
orphan_span_rate

观测系统失效时,业务可能仍然正常运行,但团队已经失去定位能力,因此遥测链路也需要自己的 SLO。


20. SLO 示例:把“快”和“稳”写成可执行约束

下面只是示例,不是通用标准。

SLI示例目标
RAG 请求成功或正确拒答率99.9%
检索链路 P95小于 350 ms
Rerank P95小于 150 ms
首模型 Chunk P95小于 900 ms
Error Trace 保存率100%
Degraded Trace 保存率100%
Trace 完整率大于 99.5%
生产内容泄露事件0
越权内容进入 Context0

20.1 Trace 完整率怎样定义

必须存在 root span
+
scope.resolve
+
query.plan
+
retrieval
+
context.pack
+
generation 或 refusal
+
answer.finalize

如果某一阶段按配置跳过,应记录:

rag.stage.skipped = true
rag.stage.skip_reason = exact_id_route

不能通过“没有 Span”来表达“主动跳过”,否则缺失埋点和正常路由无法区分。


21. Replay:精确回放与语义回放要区分

LLM 和外部数据会变化,真正的字节级重现并不总是可能。

21.1 精确回放需要的材料

原始输入或受控快照
会话上下文快照
System Scope
Query Plan
Prompt Template + Version + Hash
Model Provider + Endpoint + Snapshot
采样参数
工具 Schema
Context Manifest
Chunk Revision / Content Hash
索引 Manifest
Reranker Model
Context Packer Version
Policy Version
随机 Seed(如果模型支持)

即使这些都存在,托管模型的底层实现也可能变化。

21.2 语义回放

更实用的目标是:

同一个输入与权限范围
→ 使用当前候选版本重新运行
→ 与历史 Trace 比较候选、Context、回答和成本

它回答的是“新版本对历史请求会产生什么变化”,而不是要求每个 Token 完全相同。

21.3 Replay Manifest

{
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "release_id": "rag-2026-08-05.2",
  "query_plan_version": "qp-v6",
  "retrieval_config_version": "retrieval-v12",
  "embedding_model_key": "bge-m3|endpoint-a",
  "physical_index": "kb_chunks_v7",
  "index_manifest_hash": "sha256:64b1...",
  "rerank_model_key": "bge-reranker-v2-m3|gpu-a",
  "prompt_version": "answer-v9",
  "prompt_hash": "sha256:123a...",
  "context_manifest_hash": "sha256:c781...",
  "policy_version": "policy-v5",
  "content_capture_level": "encrypted_replay"
}

22. Go:手工埋点的最小可用结构

下面的代码展示如何使用 OpenTelemetry 创建稳定的 RAG Span。字段名是本文建议的内部契约示例。

package ragtrace

import (
	"context"
	"errors"
	"time"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/trace"
)

var tracer = otel.Tracer("github.com/example/rag/observability")

type RequestMeta struct {
	RequestID       string
	SessionID       string
	TurnIndex       int
	Route           string
	QueryType       string
	ReleaseID       string
	PipelineVersion string
	TenantHash      string
	UserHash        string
}

func StartRequest(
	ctx context.Context,
	meta RequestMeta,
) (context.Context, trace.Span) {
	return tracer.Start(
		ctx,
		"rag.request",
		trace.WithSpanKind(trace.SpanKindServer),
		trace.WithAttributes(
			attribute.String("rag.request.id", meta.RequestID),
			attribute.String("rag.session.id", meta.SessionID),
			attribute.Int("rag.turn.index", meta.TurnIndex),
			attribute.String("rag.route", meta.Route),
			attribute.String("rag.query.type", meta.QueryType),
			attribute.String("rag.release.id", meta.ReleaseID),
			attribute.String("rag.pipeline.version", meta.PipelineVersion),
			attribute.String("rag.tenant.hash", meta.TenantHash),
			attribute.String("rag.user.hash", meta.UserHash),
		),
	)
}

type RetrievalSummary struct {
	Mode                string
	CandidateBudget     int
	CandidateCount      int
	UniqueDocumentCount int
	PhysicalIndex       string
	ArtifactURI         string
	ArtifactSHA256      string
	Partial             bool
}

func TraceRetrieval(
	ctx context.Context,
	run func(context.Context) (RetrievalSummary, error),
) (RetrievalSummary, error) {
	ctx, span := tracer.Start(ctx, "retrieval.hybrid")
	defer span.End()

	started := time.Now()
	result, err := run(ctx)
	span.SetAttributes(
		attribute.String("rag.retrieval.mode", result.Mode),
		attribute.Int("rag.retrieval.candidate_budget", result.CandidateBudget),
		attribute.Int("rag.retrieval.candidate_count", result.CandidateCount),
		attribute.Int("rag.retrieval.unique_document_count", result.UniqueDocumentCount),
		attribute.String("rag.retrieval.physical_index", result.PhysicalIndex),
		attribute.Bool("rag.retrieval.partial", result.Partial),
		attribute.String("rag.artifact.uri", result.ArtifactURI),
		attribute.String("rag.artifact.sha256", result.ArtifactSHA256),
		attribute.Int64("rag.retrieval.duration_ms", time.Since(started).Milliseconds()),
	)

	if err != nil {
		span.RecordError(err)
		span.SetStatus(codes.Error, classifyError(err))
		return result, err
	}

	span.SetStatus(codes.Ok, "")
	return result, nil
}

func classifyError(err error) string {
	if errors.Is(err, context.DeadlineExceeded) {
		return "deadline_exceeded"
	}
	if errors.Is(err, context.Canceled) {
		return "cancelled"
	}
	return "retrieval_failed"
}

22.1 自动埋点与手工埋点怎样共存

HTTP、gRPC、数据库和 Elasticsearch Client 可以使用自动埋点;RAG 业务阶段需要手工 Span。

推荐结构:

retrieval.hybrid              业务 Span
└── HTTP POST /_search        自动 HTTP Span
    └── Elasticsearch request 自动 Client Span 或 Log

不要再手工创建一个完全重复的 http.request Span。


23. 一份端到端 Trace 摘要

{
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "request_id": "req_01J4...",
  "session_id": "conv_123",
  "turn_index": 17,
  "release_id": "rag-2026-08-05.2",
  "pipeline_version": "pipeline-v12",
  "result": {
    "status": "success_degraded",
    "degraded": true,
    "degrade_to": "bm25_only",
    "degrade_reason": "embedding_timeout"
  },
  "query_plan": {
    "type": "concept_with_constraint",
    "version": "qp-v6",
    "original_query_hash": "sha256:...",
    "standalone_query_hash": "sha256:...",
    "subquery_count": 2,
    "anchor_count": 1,
    "scope_hash": "sha256:..."
  },
  "retrieval": {
    "mode_requested": "hybrid",
    "mode_applied": "bm25_only",
    "candidate_count": 80,
    "unique_document_count": 31,
    "candidate_artifact": "artifact://...",
    "physical_index": "kb_chunks_v7"
  },
  "rerank": {
    "requested": true,
    "applied": true,
    "model_key": "bge-reranker-v2-m3|gpu-a",
    "input_count": 40,
    "output_count": 8,
    "latency_ms": 92
  },
  "context": {
    "manifest_hash": "sha256:...",
    "document_count": 5,
    "chunk_count": 8,
    "token_count": 4872
  },
  "generation": {
    "request_model": "answer-model",
    "response_model": "answer-model-snapshot",
    "prompt_version": "answer-v9",
    "input_tokens": 5220,
    "output_tokens": 431,
    "first_chunk_ms": 612,
    "finish_reason": "stop"
  },
  "citation": {
    "claim_count": 8,
    "coverage": 0.875,
    "invalid_count": 1
  },
  "cost": {
    "estimated_usd": 0.0148,
    "price_table_version": "pricing-2026-08-01"
  },
  "latency_ms": 1521,
  "content_capture_level": "metadata_only"
}

这份摘要适合列表和 Dashboard;完整阶段细节仍然来自 Span Tree 和 Artifact。


24. 从线上 Trace 到 Bad Case 和回归样本

Bad Case 样本至少应引用:

source_trace_id
source_release_id
expected_evidence
actual_context_manifest
failure_stage
severity
owner
privacy_class

这样下次实验可以直接关联回真实线上执行,而不是重新手工描述一次问题。


25. Release 与 Experiment 必须进入每一条 Trace

建议所有 Trace 都记录:

release_id
application_version
pipeline_version
prompt_version
query_plan_version
retrieval_config_version
embedding_model_key
index_manifest_hash
rerank_model_key
context_packer_version
policy_version
experiment_cohort

25.1 为什么一个 git_sha 不够

RAG 运行时大量配置并不一定随代码发布:

  • Prompt 在后台更新;
  • 模型路由动态切换;
  • Index Alias 指向变化;
  • Reranker 配置热更新;
  • Query Rewrite Prompt 灰度;
  • ACL Policy 独立发布;
  • Price Table 更新。

因此需要一个可以完整描述运行组合的 release_manifest_hash

25.2 Canary Dashboard

experiment_cohortrelease_id 对比:

Error Rate
P95 Latency
Degrade Rate
Empty Retrieval Rate
Rerank Score Distribution
Citation Coverage
User Negative Feedback
Cost per Successful Answer

这些线上指标再与离线 Dataset Experiment 结果一起进入发布决策。


26. Telemetry Schema 也需要版本、测试和门禁

埋点本身会发生回归:

  • Span 名称被修改,Dashboard 失效;
  • 某个 SDK 升级后字段变名;
  • 自动和手工埋点产生重复 Span;
  • Trace Context 在队列或 SSE 中断裂;
  • 内容采集开关意外打开;
  • Candidate Artifact 上传失败;
  • Token 或成本字段口径变化;
  • Parent–Child 关系错误;
  • Sampling 导致错误 Trace 丢失。

建议在 CI 中运行 Telemetry Contract Test:

执行固定 RAG 请求
→ 导出 OTLP 测试数据
→ 断言必需 Span 存在
→ 断言父子关系正确
→ 断言必需 Attribute 类型正确
→ 断言敏感字段不存在
→ 断言 Artifact Hash 可读取
→ 断言 Error / Degrade 状态符合预期
→ 断言 Schema Version 更新规则

26.1 Trace 完整性检查示例

{
  "required_spans": [
    "rag.request",
    "scope.resolve",
    "query.plan",
    "retrieval.hybrid",
    "context.pack",
    "answer.finalize"
  ],
  "one_of": [
    ["generation.chat"],
    ["answer.refuse"]
  ],
  "forbidden_attributes": [
    "user.email",
    "auth.token",
    "raw.access_token"
  ]
}

27. 十五个常见反模式

反模式一:只保存最终 Prompt

看不到 Query Plan、召回、Rerank 和 Context 筛选过程。

反模式二:一整个请求只有一个 Span

无法定位延迟、错误和降级发生在哪一层。

反模式三:每个函数都创建 Span

Trace 被框架噪声淹没,调试成本反而上升。

反模式四:Span Name 包含用户 Query 或 ID

名称高基数,Dashboard 和聚合失效,并可能泄露内容。

反模式五:把完整候选数组塞进 Attributes

Trace 过大、费用上升,敏感数据难治理。

反模式六:只记录当前 Chunk ID

文档更新后无法恢复当时证据,必须保存 revision 与 Hash。

反模式七:权限字段只出现在日志

无法证明实际 Retrieval Scope,Scope Span 和 Hash 都必须存在。

反模式八:成功降级被记录成普通成功

线上质量下降但 Error Rate 不变,团队看不到退化。

反模式九:策略拒答全部记成 Error

业务结果与基础设施故障混在一起,告警失真。

反模式十:Metric Label 包含 Trace ID 和 Query

产生高基数和高成本。

反模式十一:Baggage 携带 Email、权限列表或 Prompt

可能传播到无关服务和第三方 API。

反模式十二:为了调试默认保存全部明文

短期方便,长期形成安全、合规和删除风险。

反模式十三:Trace 被“更新”覆盖

执行事实应不可变,后续评测应作为 Score 或新记录追加。

反模式十四:没有 Telemetry Schema Version

字段演进后旧 Dashboard、评测和导出无法解释。

反模式十五:采样只保留随机成功请求

错误、差评、慢请求和安全事件反而被丢失。


28. 分阶段落地方案

P0:先做到每个坏例可定位

  • 一个 Turn 一条 Trace;
  • W3C Trace Context 跨 RAG API、Search、Rerank 和 Model Gateway 传播;
  • 固定 Root Span 与核心阶段 Span;
  • 记录 Query Plan、Scope Hash、候选数量、Context Manifest 和版本;
  • Logs 注入 TraceId / SpanId;
  • Metrics 覆盖延迟、错误、降级、空召回和成本;
  • 默认 Metadata Only;
  • Error、Degraded、Safety Trace 100% 保留;
  • 前端和客服可获得公开 request_id

验收标准:

任意一个用户坏例,都能在几分钟内定位到:
Scope、Query Plan、Retrieval、Rerank、Context、Generation、Citation 或 Policy。

P1:建立受控回放和质量闭环

  • Candidate Artifact 与 Context Manifest;
  • Prompt、模型、索引、Reranker 和 Policy 版本化;
  • PII Redaction 与内容采集等级;
  • Tail Sampling;
  • Feedback / Human Annotation / Judge Score;
  • Trace 到 Bad Case Dataset 的转换;
  • Release / Experiment Cohort Dashboard;
  • Telemetry Contract Test;
  • Trace 完整率和 Artifact 成功率 SLO。

验收标准:

线上 Trace 可以转成可运行的回归样本;
新旧版本可以在同一批历史请求上比较。

P2:高级治理与规模化

  • 多租户遥测隔离;
  • 内容字段级 RBAC;
  • 加密 Replay Artifact;
  • 自适应 Tail Sampling;
  • 成本归因和预算;
  • 自动 Bad Case 聚类;
  • Trace-driven Eval;
  • Canary 自动门禁;
  • OpenTelemetry GenAI / OpenInference Schema 映射;
  • 多后端和 Vendor Migration;
  • 长期 Retention Tiering。

前提是 P0、P1 的字段契约已经稳定,否则高级平台只会放大混乱。


29. 上线检查清单

  • 一个用户 Turn 是否对应一条独立 Trace?
  • Session 是否只用于聚合多条 Turn Trace?
  • traceparent 是否跨 HTTP、gRPC 和消息链路传播?
  • Span Name 是否稳定且不包含动态 Query 或 ID?
  • 是否有 rag.telemetry.schema_version
  • Root Trace 是否记录 Release、Pipeline 和 Experiment Cohort?
  • Scope Span 是否记录 Tenant / ACL / KB 范围摘要和 Hash?
  • Query Plan 是否记录 Original、Rewrite、Anchor、Filter 和 Validator 结果?
  • BM25、kNN、Web、SQL 等通道是否分别记录 Rank、Score 和延迟?
  • 完整 Candidate 是否进入受控 Artifact,而不是全部塞进 Span?
  • 候选被排除时是否记录原因?
  • Context Manifest 是否包含 Chunk ID、revision、Hash、顺序和 Token?
  • Prompt 是否有 Name、Version 和 Hash?
  • Request Model 与 Response Model 是否同时记录?
  • Streaming 是否记录 First Chunk,而不只记录总延迟?
  • Citation 是否有独立 Claim–Evidence 映射?
  • Success、Degraded、No Evidence、Refused 与 Error 是否分开?
  • Logs 是否带 TraceId 和 SpanId?
  • Metric Label 是否避免高基数 ID 和原始内容?
  • Error、Degraded、Safety 和 Negative Feedback Trace 是否优先保留?
  • Metrics 是否不受 Trace Sampling 影响?
  • Collector 是否执行 Batch、Redaction、Filter 和必要的 Sampling?
  • 是否定义 L0/L1/L2 内容采集等级?
  • Baggage 是否禁止携带敏感内容?
  • Artifact 是否加密、审计并有独立 TTL?
  • Cost 是否包含 Price Table Version?
  • Feedback 与 Eval 是否作为 Score 追加,而不是覆盖 Trace?
  • 是否监控 Trace Export、Drop、Completeness 和 Orphan Span?
  • 是否能够从 Trace 生成 Bad Case 和 Regression Dataset?
  • Telemetry Contract 是否进入 CI 和发布门禁?
  • 旧 Trace 是否能根据 Schema Version 正确解释?

总结

RAG 可观测性的目标不是收集最多的数据,而是用最小、稳定、可治理的数据回答下面这条因果链:

用户在什么会话和权限范围内提出了什么问题
→ 系统怎样理解、改写和拆解
→ 哪些数据源和检索通道被调用
→ 候选为什么被召回、融合、删除或保留
→ 模型最终看到了哪个版本的哪些证据
→ 使用了什么 Prompt、模型、参数和策略
→ 答案和引用怎样生成
→ 为什么发生成功、降级、拒答或错误
→ 后续反馈如何变成评测与发布决策

最重要的工程原则是:

把一次 RAG 回答视为一条可版本化的执行记录:Trace 保存因果关系,Manifest 保存证据身份,Artifact 保存受控内容,Score 保存后续判断,Metrics 保存整体趋势。只有这样,RAG 才能从“回答看起来不错”进入可定位、可回归、可发布和可治理的生产系统。

官方资料

讨论

继续讨论这篇笔记

有问题、补充案例或不同观点,可以通过 GitHub Discussions 继续交流。

On this page

一句话结论1. 先划清边界:可观测性不等于评测,也不等于发布门禁2. 什么应该算“一条 Trace”2.1 为什么不建议整个会话只用一条 Trace2.2 异步任务不要伪装成同步子 Span3. 一棵可用于生产调试的 RAG Trace 树3.1 不要为每个函数都创建 Span4. Trace、Span、Event、Log、Metric 与 Artifact 的职责4.1 Trace / Span:解释执行路径4.2 Event:记录 Span 内的离散时点4.3 Log:记录可搜索的异常和业务事件4.4 Metric:聚合趋势和告警4.5 Artifact:保存大对象和可回放材料5. 使用 W3C Trace Context 串起分布式链路5.1 traceparenttracestate 不能放业务内容5.2 Baggage 也不是免费的全局 Metadata6. 语义约定:标准优先,但必须冻结内部 Schema6.1 OpenTelemetry GenAI Semantic Conventions6.2 为什么还需要内部 rag.* 契约6.3 OpenInference 可以作为 AI Span 分类参考7. Resource、Trace 与 Span Attributes 应怎样分层7.1 Resource:描述谁产生了遥测7.2 Root Trace:描述这一次用户操作7.3 Span:描述某一步操作8. Retrieval Trace:候选集应该记录到什么粒度8.1 一份候选 Manifest8.2 Span 只保存摘要,Artifact 保存完整候选8.3 必须保存被排除的原因9. Context Manifest:模型到底看到了什么9.1 为什么必须有 source_revisioncontent_hash9.2 Context 顺序也是执行事实9.3 不要把 Context Manifest 当成全文副本10. Prompt 与模型调用需要保存哪些版本10.1 Prompt Name、Version 与 Hash 都需要10.2 Request Model 与 Response Model 可能不同10.3 Streaming 需要独立时间点11. Citation Trace:答案正确不代表引用正确12. 成功、降级、拒答和错误要区分12.1 系统错误12.2 成功降级12.3 空召回12.4 策略拒答13. Logs、Metrics 与 Traces 怎样关联13.1 Log 示例13.2 Metric 维度要低基数13.3 从 Metric 跳回 Trace14. 采样:不是所有成功请求都要永久保存全文14.1 Head Sampling14.2 Tail Sampling14.3 推荐采样矩阵14.4 Metrics 不应跟随 Trace Sampling 丢失15. OpenTelemetry Collector:在出口统一处理遥测15.1 为什么在 Collector 再做一道治理16. 隐私治理:建立内容采集等级,而不是一个全局开关16.1 Hash 不是匿名化的万能答案16.2 遥测后端同样需要租户隔离16.3 不要把原始用户内容放入 Span Name、Metric Label 或 Baggage17. 成本观测:Provider Usage 与内部估算要分开17.1 为什么要有 Price Table Version17.2 不要只算 LLM Token18. Feedback 与 Evaluation 应作为可追加 Score19. Dashboard:从基础设施指标推进到 RAG SLI19.1 Availability19.2 Latency19.3 Retrieval Quality Proxy19.4 Citation 与 Grounding19.5 Cost19.6 Safety 与权限19.7 Telemetry 自身健康度20. SLO 示例:把“快”和“稳”写成可执行约束20.1 Trace 完整率怎样定义21. Replay:精确回放与语义回放要区分21.1 精确回放需要的材料21.2 语义回放21.3 Replay Manifest22. Go:手工埋点的最小可用结构22.1 自动埋点与手工埋点怎样共存23. 一份端到端 Trace 摘要24. 从线上 Trace 到 Bad Case 和回归样本25. Release 与 Experiment 必须进入每一条 Trace25.1 为什么一个 git_sha 不够25.2 Canary Dashboard26. Telemetry Schema 也需要版本、测试和门禁26.1 Trace 完整性检查示例27. 十五个常见反模式反模式一:只保存最终 Prompt反模式二:一整个请求只有一个 Span反模式三:每个函数都创建 Span反模式四:Span Name 包含用户 Query 或 ID反模式五:把完整候选数组塞进 Attributes反模式六:只记录当前 Chunk ID反模式七:权限字段只出现在日志反模式八:成功降级被记录成普通成功反模式九:策略拒答全部记成 Error反模式十:Metric Label 包含 Trace ID 和 Query反模式十一:Baggage 携带 Email、权限列表或 Prompt反模式十二:为了调试默认保存全部明文反模式十三:Trace 被“更新”覆盖反模式十四:没有 Telemetry Schema Version反模式十五:采样只保留随机成功请求28. 分阶段落地方案P0:先做到每个坏例可定位P1:建立受控回放和质量闭环P2:高级治理与规模化29. 上线检查清单总结官方资料