AI 服务可观测性设计
AI 服务特有的可观测性需求:LLM 调用的结构化日志、token 用量追踪、延迟指标、SSE 管道的 trace ID 串联、告警规则设计。
[!info] related notes
- 所属 MOC: BodySense MOC
- 平台级可观测: Go 可观测性
- 链路追踪: 多服务分布式追踪
- 质量门禁: [[ai-quality-gates|AI 质量门禁]]
- 降级策略: [[ai-service-graceful-degradation|AI 服务优雅降级]]
AI 服务可观测性设计
这篇解决什么问题
传统服务的可观测性关注”请求是否成功、延迟多少”。AI 服务在此基础上还需要回答:
- LLM 花了多少钱 — 每次调用消耗了多少 token?
- LLM 回答得好不好 — faithfulness 检查通过了吗?red flag 触发了吗?
- 流式管道断在哪 — SSE 事件流在哪个环节中断了?
- RAG 检索质量如何 — 检索到的知识和用户问题相关吗?
本篇补充模型调用、检索与生成质量信号;通用可观测性也覆盖性能、业务行为与正确性。以下是设计示例,不表示 BodySense 当前已采集这些字段。
LLM 调用的结构化日志
以下为结构化事件字段示意,使用支持关键字字段的 logger(如 structlog),不是 Python 标准 logging 的直接调用方式。按产品需要和数据策略采集;检查未执行、usage 未返回时记录 unknown / null,不伪造成功或零用量:
logger.info("llm_call_completed",
# 请求标识
request_id=req_id,
session_id=session_id,
turn_index=turn_index,
# 模型信息
model=model_name,
provider=provider_name,
# Token 用量
prompt_tokens=usage.prompt_tokens,
completion_tokens=usage.completion_tokens,
total_tokens=usage.total_tokens,
# 延迟
latency_ms=latency_ms,
time_to_first_token_ms=ttft_ms,
# 质量指标
faithfulness_passed=faithfulness_result.faithful,
red_flag_triggered=len(red_flags) > 0,
red_flag_categories=[f.category for f in red_flags],
# 工具调用
tool_calls=[tc.name for tc in tool_calls],
tool_call_count=len(tool_calls),
)
关键设计:
request_id贯穿整个请求链路(前端 → Go → Python),用于跨服务关联session_id+turn_index用于定位会话位置,不足以重建完整上下文;还需获准保留的输入、配置与证据版本- 质量判定另关联 evaluator / policy 版本,未检查不等于通过;red flag 触发本身不说明模型出错
- token 用量是计费输入之一,费用还取决于模型、价格版本、缓存类别与其他计费项;缺失 usage 标为未知
- 日志按需脱敏、控制访问和保留期,不默认保存完整对话或健康信息
Token 用量追踪
# 按维度聚合 token 用量
class TokenTracker:
def record(self, session_id: str, model: str, usage: TokenUsage):
# TOKEN_COUNTER 为预先注册的 counter;按实际消费增量计数
TOKEN_COUNTER.labels(
model=model,
direction="prompt"
).inc(usage.prompt_tokens)
TOKEN_COUNTER.labels(
model=model,
direction="completion"
).inc(usage.completion_tokens)
示例按调用累计 prompt / completion token;如果流事件提供累计 usage,只计最终值或差值,不能逐事件重复累加。session / request ID 放日志或 trace,避免作为 Prometheus 高基数 label。
以下阈值只是演示,必须按工作负载、预算与基线设置:
| 维度 | 用途 | 告警阈值示例 |
|---|---|---|
| 每次调用 token 数 | 发现异常大请求 | > 8000 tokens |
| 每会话总 token 数 | 成本控制 | > 50k tokens/session |
| 每日总 token 数 | 预算告警 | > 1M tokens/day |
| prompt vs completion 比例 | 观察输入输出构成,结合任务判断 | completion > 3x prompt(并非普遍异常) |
LLM 延迟指标
# 两个关键延迟
LLM_LATENCY.labels(model=model).observe(latency_ms) # 总延迟
LLM_TTFT.labels(model=model).observe(time_to_first_token_ms) # 首 token 延迟
| 指标 | 含义 | 用户感知 |
|---|---|---|
| 模型调用 TTFT | 应用发出模型请求到首个有效输出 token | 不等于前端可见首字时间 |
| 总调用延迟 | 应用发出模型请求到约定结束 | 前端还可能等待传输、校验和渲染 |
| 流式吞吐 | 每秒 token 数 | ”打字速度” |
SSE 场景特殊处理:流式响应的延迟不是”请求-响应”的单点,而是一个流。需要记录:
- 连接建立、首事件和首个有效回答 token 分别记录
- 流结束时间
- 流是否被中断
SSE 管道的追踪与关联
React → Go → Python 可采用 W3C Trace Context / OpenTelemetry 传播:入口提取合法 traceparent,各服务创建自己的 span,调用下游时注入当前 context。X-Request-ID 只是应用关联标识;仅透传一个 ID 或设置 span attribute 不会自动建立父子 span。
SSE 连接的 trace 与业务 run / event identity 也应分开:重连可能产生新的 trace,但仍恢复同一个 run。前端可从响应头或连接上下文关联 trace;只有需要跨连接独立关联时,才按事件合同附带相应 ID,不要求每个 token 事件重复携带。
代理应保留 SSE 的 event、id、多行 data 和空行边界;不能逐行读取 data: 后就插入空行并丢掉其他字段。若需要改写事件,必须先完整解析并遵循事件 schema,否则直接透传。结束、取消和断流分别记录,并在资源释放时结束对应 span。
跨服务传播原则见 distributed-tracing-multi-service;用户可见延迟见 ai-assistant-first-token-latency-optimization;恢复游标见 durable-sse-recovery-with-after-seq。
AI 特有的告警规则
以下为候选阈值,需指定统计窗口、最小样本数、分母和服务 SLO。告警是调查线索,不是根因证明。
| 告警 | 条件 | 严重程度 | 含义 |
|---|---|---|---|
| LLM 延迟飙升 | p95 > 10s 持续 5min | warning | 可能排队、网络或推理变慢,需 trace 定位 |
| LLM 错误率 | > 5% 持续 3min | critical | 调用失败增加,核实错误类型与实际可用率 |
| Faithfulness 失败率 | > 20% 持续 10min | warning | 检索、生成、样本分布或 evaluator 都可能变化 |
| Red Flag 触发率突增 | > 2x 基线 | info | 可能是输入分布、规则版本或检测行为变化 |
| Token 用量异常 | > 2x 日均 | warning | 可能有循环调用或滥用 |
| SSE 流中断率 | > 10% 持续 5min | critical | 先区分主动停止、网络断开与服务端失败 |
与通用可观测性的关系
| 维度 | 通用可观测性 | AI 特有 |
|---|---|---|
| 日志 | 请求级 | 需要 LLM 调用级(token、model、工具调用) |
| 指标 | QPS、延迟、错误率 | + token 用量、faithfulness 率、red flag 率 |
| 追踪 | 服务间调用链 | + LLM 内部的 tool call 链、RAG 检索链 |
| 告警 | 服务健康 | + 质量退化、成本异常 |
通用可观测性是基础,AI 可观测性是在此之上增加 AI 特有的信号。两者共用同一套 trace ID 体系。
设计要点
- 将 request / run / trace / span 的关联规则写清楚,不能用一个 ID 混代所有身份。
- 明确 usage 来源、单位、价格版本与缺失值;成本控制还需预算和配额。
- 质量判定携带版本与依据,模型自评或单个 checker 通过不代表答案已证实正确。
- 流建立、首输出、正常完成、取消与异常断开分别记录;采样与日志保留决定事后能重建多少信息。
- 结构化事件便于关联和聚合,但默认只收必要字段。
常见错误
只记录成功调用
# ❌ 只在成功时记录
logger.info("llm_call", tokens=usage.total_tokens)
# ✅ 成功和失败都记录,失败时带上错误信息
logger.info("llm_call",
success=True,
tokens=usage.total_tokens,
error=None,
)
logger.error("llm_call",
success=False,
error_code=error_code, # 从异常映射的安全分类,避免输出原始敏感信息
tokens=None,
)
误把每个 SSE 事件都带 trace_id 当作完整追踪
事件上的 ID 只能帮助关联。完整追踪还依赖 context 提取 / 注入、span 生命周期、采样与导出;SSE 业务恢复则依赖稳定 run ID 和事件游标。
Token 用量只记总数不分维度
# ❌ 只记总数,无法分析哪个模型/哪个功能消耗最多
TOKEN.inc(usage.total_tokens)
# ✅ 使用有界的模型和功能 label;会话 ID 放日志 / trace
TOKEN.labels(model=model, feature="diagnosis").inc(usage.total_tokens)
来源
- W3C Trace Context:
traceparent字段与传播规则。