Chapter 05 · Self-check

自测与辨析

前四章把可观测性的概念、机制、落地、工具讲完了;这一章用三层梯度题 + 五个跨章辨析场景 + 一次动手画,把零散知识逼成可调用的判断力。

读完不等于学会。下面的题目分三层:概念层查词汇与边界(记得 / 认得),机制层查"为什么这样工作"(会推 / 会算),应用判别层把多章概念混在一个真实场景里,逼出"该用哪个、看哪里"的判断。每题先合上教程独立作答,再展开对照——直接展开等于把这一章当再读一遍。

用法 · 别骗自己

展开答案前先把回答写在纸上或编辑器里。"大概知道"和"能完整说出来"之间的差距,正是这一章要暴露的。答得磕绊的题,回对应章节重读,不要靠多看一遍答案蒙混过去。

·三层梯度

题目从"记得"逐层走到"会判"。越往下,越需要把多章概念接起来用——这也是面试和真实排障里最常被问、最难绕过的一层。

易 难 ① 概念层 记得 · 认得(词汇 / 边界) ② 机制层 会推 · 会算(传播 / 归因 / instrument) ③ 应用判别层 会选 · 会判(obs/eval/APM · 选型 · 根因)
图 5.1三层梯度:自上而下,单题涉及的章节越多、越需要"接起来用"。注意:底层(应用判别)最宽不是因为它"基础",而是因为它把上两层全部吃进一个真实场景——这一层答得出,才算真的会。

①概念层 · 记得 / 认得

  1. 用一句话分别说出可观测性、eval、传统 APM 三者在时机和对象上的区别。
    展开答案

    可观测性:运行期,对象是单条真实运行。eval:开发期 / CI,对象是固定数据集。传统 APM:运行期,对象是单请求(但判据只到状态码 + 延迟,不进语义)。三者只在"在线评估"处相交(详见第 3 章)。

  2. 一个 span 的 parent_span_id 为空,意味着什么?它在 agent 的一次运行里通常是哪个 operation?
    展开答案

    它是这棵 trace 的根 span(root span),没有父节点。在 agent 运行里通常是 invoke_agent,代表一次完整运行的起点,其下挂着各 chat / execute_tool / retriever 子 span,全树共享同一 trace_id。

  3. 为什么 prompt / completion 文本放在 span 的 events 而不是 attributes?给两个原因。
    展开答案

    ① attributes 设计为低基数、可聚合的结构化键值,prompt 文本每条唯一(高基数),放进去会撑爆时序索引;② prompt 体积大(数 KB)且常含 PII,attributes 的批量索引无法做细粒度访问控制,且会被 SDK 默认的 4–64KB 截断阈值静默砍掉。events 是带时间戳的日志事件,为大块 / 敏感内容而设,且 opt-in。

  4. OTel GenAI semconv 里有 gen_ai.usage.input_tokens,却没有 gen_ai.cost.*。成本由谁、在什么时候算出来?
    展开答案

    由可观测性 backend 在 ingestion(落库)时算:用 span 里的 token 数 × 内置定价表。规范不定义成本,是因为同样 token 数的价格随账户折扣、厂商调价、模型版本变动,写进规范会让规范追着调价跑。后果:不同 backend 的成本数字可能因定价快照时间点不同而有差异,不代表 token 数据有误。

  5. 说出"三支柱"(traces / metrics / logs)在 LLM 可观测性里各自的角色变化。
    展开答案

    traces 主导——span 树是执行图,是"为什么"的直接载体。metrics 退化为直方图(token 用量、操作耗时、TTFT 分布);把 prompt / user-id 当 metric label 会引发基数爆炸。logs 降级为 span 上的 events,独立日志流不再是主要手段。

②机制层 · 会推 / 会算

  1. 两个独立进程里的 agent,怎么做到它们的 span 落在同一棵 trace 树上?关键是哪个 HTTP 头,它传的是什么?
    展开答案

    靠 context propagation:上游在跨进程调用时把 W3C traceparent 头(含 trace_id 和当前的 parent_span_id)附在请求里;下游读取它,用其中的 parent_span_id 作为自己根 span 的父,并把 trace_id 原样继承。于是两个进程的 span 共享同一 trace_id、父子关系正确相连。头没传,子 agent 的 span 就成孤儿,树断裂。

  2. 差旅助理并发调用 search_flights 和 retrieve_policy,怎么保证两个 execute_tool span 都挂在正确的父节点下?什么情况下会变成孤儿 span?
    展开答案

    每个 span 创建时从当前调用点的 context storage(如 Python 的 contextvars.ContextVar)读取当前 span id 作为自己的 parent_span_id。只要两个工具调用都在同一个决策 chat span 的执行上下文里发起,就都以它为父,互不干扰。孤儿出现在:把调用扔进新线程 / 新进程却没把 context 复制过去(如裸 threading.Thread 而非 OTel 的 context-propagating wrapper),新执行体看到空 context。

  3. 代理网关(HTTP 拦截)和进程内 SDK 相比,哪一类信息代理网关结构性地看不到?为什么这是架构决定的、不是配置问题?
    展开答案

    代理网关只看到进出 LLM API 的 HTTP 请求 / 响应(模型名、prompt、completion、latency、状态码),看不到进程内的编排状态:agent 为什么选这个工具、检索召回了哪些文档、路由决策的候选是什么。这些状态从不离开应用进程、不走那一跳 HTTP,所以代理在网络层无论怎么配置都拿不到。因此代理网关适合成本 / latency 监控,不适合调试 agent 决策链。

  4. 在线评估的步骤里,"异步"和"分层采样"分别解决什么问题?去掉它们各会怎样?
    展开答案

    异步:scorer 在 trace 落库后才跑,不阻塞热路径。去掉(同步打分)= 给每次 agent 运行的关键路径叠加一次额外 LLM 调用的延迟。分层采样:低分 / 可疑区域多采、正常区域少采。去掉(均匀采样)= 在 bug 密度低时几乎采不到失败 trace,scorer 看到的全是正常 case,监控形同虚设。

  5. 一棵 trace 里有 5 个 chat span,根 invoke_agent span 自己不调 LLM。trace 级的 token 总量怎么得到?两种实现方式分别在哪一层做聚合?
    展开答案

    每个 chat span 自记 gen_ai.usage.*(billable token),父 span 汇总子 span 的数值得到 trace 级总量。两种实现:① SDK 侧——父 span 关闭时遍历子 span 求和(OpenInference / Phoenix 路线);② backend 侧——ingestion 后由查询 API 提供 trace 级聚合(LangSmith / Langfuse 路线)。结果相同,区别只在聚合发生的位置。

③应用判别层 · 会选 / 会判(跨章)

每个场景都把多章概念混在一起,括号标出主要涉及哪些章。先判断"该用哪个工具 / 看哪里",再展开。

  1. 场景(第 1、3 章):线上 agent 给用户的答复是错的,但 HTTP 返回 200,Datadog 的错误率 / 延迟仪表盘一切正常。这是可观测性、eval 还是 APM 的活?下一步具体看什么?
    展开分析

    这是可观测性的活(运行期、还原单条真实运行)。APM 盲,因为语义失败对状态码不可见。下一步:打开这条 trace,沿父子链从最终答复 span 向上回溯,逐级检查每个 span 的中间状态——工具返回了什么、检索召回了哪些文档、路由 chat 选了哪个工具,找到第一个产出"语义上坏"输出的 span(根因 span)。eval 在这里帮不上:它对的是数据集,不是这一条线上运行。

  2. 场景(第 2、4 章):LangGraph 创业团队,数据不能出境(自托管),人力有限但想要在线评估。给出 emitter + backend 的组合,并说出五条判别轴各倾向哪端。
    展开分析

    组合:OpenLLMetry(emitter)+ 自托管 Langfuse(backend),OTLP 串联。五轴:开放标准(OTel,避免迁移成本);自托管(数据不出境,Langfuse 开源 MIT 自托管成熟);框架无关(妥协 LangSmith 的 LangChain 耦合换数据主权);all-in-one(Langfuse 2025-06 开源 LLM-as-judge 满足在线评估);进程内(要调试编排失败,必须进程内 instrumentation)。

  3. 场景(第 1、3 章):差旅助理某次运行成本是平时的 3 倍,但功能正常、用户拿到了答复。怎么用 trace 定位?这是 obs 还是 eval 的活?
    展开分析

    obs(运行期单条运行的成本归因)。定位:按 invoke_agent 根 span 找成本最高的运行,展开子树,比较各 chat span 的 gen_ai.usage.*;若看到同名 execute_tool(如 search_flights)并列出现多次,且其中有 status=ERROR,说明工具失败触发了 LLM 自发重试循环,每次把全量历史重喂、token 线性叠加。根因是工具层无重试上限,不是 prompt。eval 看不到这个——它不跑在这条真实 trace 上。

  4. 场景(第 1、3 章):团队声称"99.9% 可用性 + P95 延迟监控都正常,质量没问题"。这套监控有什么盲区?需要补什么?
    展开分析

    盲区:可用性和延迟是 APM 式指标,对语义失败和悄悄重试都不可见。一个 agent 可以 99.9% 可用、P95 正常,同时幻觉率在涨、工具在后台重试烧钱。需要补:① cost/run 告警(APM 没有的一等信号);② 子 span 工具失败率(根 span OK 掩盖工具层 ERROR);③ 在线评估——对采样生产 trace 打质量分,把"答得对不对"变成可监控的数。

  5. 场景(第 1、3 章):受 HIPAA 约束,团队对生产 trace 做脱敏,只对 gen_ai.output.messages(最终输出)脱了敏。够吗?还缺什么?
    展开分析

    不够。① 输入侧 gen_ai.input.messages 同样含 PII,也要脱;② 更隐蔽的是推理链 PII 泄漏——带思维链的模型(o1/R1 类)在 reasoning token 里会用到上下文 PII,即便最终输出干净。正确做法:把 input / output / reasoning 三类 events 全纳入脱敏管道,或对敏感业务直接禁用这三类 opt-in 捕获、改自托管私有存储。只脱输出是半截子工程。

·动手画:两张图

辨认一张图和亲手画出一张图,是两种不同强度的掌握。下面两张图都在前面章节出现过——合上教程,凭记忆画,再翻回对照。画得出来,才说明这套结构真的进了脑子。

动手画 · 1

差旅助理「成本翻 3 倍」的失败 trace 树

不许翻书。画出这次失败运行的 span 树,要求标注:

  • 根 span 是谁,它的 status 是什么(陷阱在这里);
  • 正常 5 个 chat + 多出来的 4 个重试 chat 的父子关系;
  • 哪个是根因 span,它的 status 和 status.message;
  • token 怎么从子 span 上卷到根,为什么总量翻倍。

画完翻回 图 1.2(span 树)和 图 3.1(根因回溯)对照。漏标了 status 或父子关系的地方,就是没真正吃透的地方。

动手画 · 2

obs / eval / APM 三方边界图

画一张二维定位图:横轴是时机(运行期 ↔ 开发期),纵轴或分区标出三者各自的对象与判据。然后把"在线评估"这条唯一接缝画在正确位置,并标出它的方向(数据从哪流向哪)。

画完翻回 起点的一句话本质 与 图 1.1 对照。如果"在线评估"的箭头方向画反了(应是可观测性 → eval:生产 trace 流入离线集),说明这条接缝还没真正理顺。

学完之后

这五层题答顺了,可观测性在脑子里就从"接个看板"变成了一套判断力:分得清 obs / eval / APM,看得懂 trace 树怎么由 context propagation 长出来,拿到失败 trace 能定位根因,面对工具和前沿能按场景选型并核对时效。下一步的自然延伸见 起点 · 学完之后——在线评估那条接缝把你引向 agent-eval,多 agent 的 trace 拓扑引向 multi-agent-patterns。