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 可以在离线实验和线上灰度之间比较最重要的七条原则是:
- 一条 Trace 应表示一次完整、可解释的用户操作,通常是一个会话 Turn,而不是整个长期会话。
- Trace 要保存决策链,不要只保存最终 Prompt。
- 内容、身份和权限默认不应明文进入所有遥测;完整内容必须分级、脱敏、加密和限时保留。
- 稳定 ID、revision、模型、Prompt、索引和配置版本比大段文本更重要。
- Metrics 用于聚合,Trace 用于解释,Logs 用于事件细节,Artifact 用于受控回放,四者不能混成一个 JSON。
- 成功降级、空召回和策略拒答不一定是系统错误,但必须作为业务结果显式记录。
- 每一个线上坏例都应该能够回到 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_compacted2.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.completedEvent 适合表示“发生了什么”,而不是持续时间很长的独立阶段。
4.3 Log:记录可搜索的异常和业务事件
结构化 Log 应至少带:
trace_id
span_id
request_id
service.name
error.type
error.codeOpenTelemetry Logs Data Model 把 TraceId 和 SpanId 定义为日志顶层字段,使 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_id、trace_id、chunk_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.encSpan 中只记录:
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
tracestatetraceparent 携带 Trace ID、父 Span ID 和 Trace Flags;tracestate 用于厂商或系统扩展。它们的作用是让不同服务和不同观测工具能够参与同一条分布式 Trace。
5.1 traceparent 和 tracestate 不能放业务内容
W3C 规范明确要求不要把个人身份或敏感信息写入这些 Header。它们只用于 Trace 关联。
不要这样做:
tracestate: tenant=company-secret,user=email@example.com5.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 Backend7. 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_hashRerank 后再增加:
rerank_rank
rerank_score
truncated
selected
removed_reason
context_order8.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_revision 和 content_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 ordering9.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_count10.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_version10.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_msOpenTelemetry 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_reasonsupport_type 可以是:
direct
partial
multi_source
contradicted
unsupportedCitation 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
attempt12.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 response13.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 |
|---|---|---|---|
| Error | 100% | 100% 或高比例 | 按权限与严重度 |
| Safety / ACL | 100% | 100% | 短 TTL、严格审计 |
| 用户差评 | 100% | 100% | 经授权保留 |
| Degraded | 100% | 高比例 | 代表性样本 |
| P99 Slow | 100% | 高比例 | 代表性样本 |
| Canary Release | 100% | 中高比例 | 小比例 |
| 普通成功 | 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 Only | ID、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_basis16.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_tokens17.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_usd18. 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_satisfactionLangfuse 的 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_rate19.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_ms19.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_rate19.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_request19.6 Safety 与权限
acl_denied_count
cross_tenant_prevented_count
prompt_injection_detected_count
sensitive_content_redacted_count
policy_refusal_rate19.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 |
| 越权内容进入 Context | 0 |
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_cohort25.1 为什么一个 git_sha 不够
RAG 运行时大量配置并不一定随代码发布:
- Prompt 在后台更新;
- 模型路由动态切换;
- Index Alias 指向变化;
- Reranker 配置热更新;
- Query Rewrite Prompt 灰度;
- ACL Policy 独立发布;
- Price Table 更新。
因此需要一个可以完整描述运行组合的 release_manifest_hash。
25.2 Canary Dashboard
按 experiment_cohort 或 release_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 才能从“回答看起来不错”进入可定位、可回归、可发布和可治理的生产系统。
官方资料
- W3C:Trace Context
- OpenTelemetry:Traces
- OpenTelemetry:Logs Data Model
- OpenTelemetry:Log Correlation
- OpenTelemetry:Sampling
- OpenTelemetry:Baggage
- OpenTelemetry:Collector
- OpenTelemetry:Transforming Telemetry
- OpenTelemetry:Handling Sensitive Data
- OpenTelemetry GenAI Semantic Conventions
- OpenInference Specification
- OpenInference Semantic Conventions
- Langfuse:Observability Data Model
- Langfuse:What Does a Good Trace Look Like?
- Langfuse:Scores Data Model
- Langfuse:Experiments Data Model
- Langfuse:Tracing Data Immutability
讨论
继续讨论这篇笔记
有问题、补充案例或不同观点,可以通过 GitHub Discussions 继续交流。