Agent Engineering 课程阅读
首页/课程九 · Agent 生产可靠性/轨迹级可观测:一次运行一行体检报告

在 GitHub 查看原文

本页目录

Lesson 07 — 轨迹级可观测:一次运行一行体检报告

本课目标:把 L01–L06 治理机制的运行状况变成可看、可比、可告警的指标。每次运行结束输出一行结构化 run summary(步数/循环刹车/token与预算比/降级次数/熔断开闭/审批等待/恢复次数/结局),超阈值打 WARNING 告警。

学完你能回答面试官那句:「你的 Agent 上线后怎么知道它健康?」——答案是每次运行一行 run summary + 阈值告警;翻日志是事后侦查,run summary 是实时体检,超阈值当场告警,不等用户投诉。


0. 起点:请求级观测管不了轨迹健康

ops-L01/L02 给 kb-qa 做了请求级观测:trace_id 贯穿一次请求、结构化日志、Langfuse tracing。但这些看的是 span 级(单步发生了什么),回答不了「这次跑得健康吗」。

🎯 核心认知:Agent 的健康问题不在「某一步」,而在「整条轨迹的聚合」——步数是不是太高(疑似卡死)、token 是不是烧太多(超支)、降级是不是太多(检索质量差)、熔断是不是打开了(工具挂了)。这些是运行级聚合指标,span 级日志回答不了,要 run summary。


1. run summary:一次运行一行体检报告

1.1 字段设计(对齐 frontier-L08 评估,以便复用分析脚本)

@dataclass
class RunSummary:
    run_id, thread_id, topic
    # ── 步数(L01)──
    total_steps          # 全局步数
    loop_brakes          # 循环检测触发次数
    # ── 成本(L02)──
    total_tokens         # 累计 token
    budget_ratio         # token / max_budget
    cost_mode            # normal/frugal/over_budget
    # ── 降级(L03)──
    degraded_subtopics   # 检索失败子题数
    breaker_tripped      # 熔断器是否打开过
    # ── 副作用(L04/L05)──
    published, publish_replayed, approval_waited, approval_rejected
    # ── 恢复(L06)──
    resumed              # 是否恢复运行
    # ── 结局 ──
    outcome              # completed/truncated/failed/awaiting_approval
    elapsed
    # ── 告警 ──
    alerts               # 阈值触发的告警列表

字段刻意对齐 frontier-L08 的 MetricCard,这样 L08 的分析脚本能复用——只是语义不同:

维度 frontier-L08(评估) L07(观测)
算什么 质量指标(任务成功率/步数效率/循环) 健康指标(步数/成本/降级/熔断)
时机 事后、离线、批量 实时、在线、单次
用途 比较机制收益(加记忆好多少) 监控运行健康(这次跑得稳不稳)

1.2 一行格式(日志友好)

✅ [completed] run=api-1234 steps=8 tokens=1200(2%) mode=normal degraded=0 breaker=off
published=Y approval=- resumed=N 3.2s
🟡 [truncated] run=api-5678 steps=30 tokens=47500(95%) mode=over_budget degraded=3 ...
| ⚠️ 步数过高(30); ⚠️ 预算将耗尽(95%); ⚠️ 降级子题多(3)

2. 阈值告警:超阈值当场 WARNING

def _check_alerts(s):
    alerts = []
    if s.total_steps >= settings.alert_steps_high:           # 默认 25
        alerts.append("⚠️ 步数过高,疑似卡死或低效")
    if s.budget_ratio >= settings.alert_budget_ratio_high:   # 默认 0.9
        alerts.append("⚠️ 预算将耗尽,超支风险")
    if s.degraded_subtopics >= settings.alert_degraded_high: # 默认 2
        alerts.append("⚠️ 降级子题多,检索质量预警")
    if s.breaker_tripped:
        alerts.append("⚠️ 熔断器打开过,工具持续故障")
    return alerts

💡 生产上这些 WARNING 接告警系统(Prometheus/PagerDuty),本课落日志。关键区别:翻日志是事后侦查(出事了去挖 span),run summary 告警是实时体检(跑完就知道这次不健康,不等用户投诉)。


3. 六类故障 × run summary 对比

场景      结局         步数  token   降级  告警
pure    ✅ completed    7    142    0    —
slow    ✅ completed    7    124    1    —       ← L03 兜住(降级声明)
flaky   ✅ completed    7    162    1    —       ← L03 兜住
loop    ✅ completed   11    220    0    —       ← L01 兜住(步数有上限)
crash   ✅ completed    8    115    0    —       ← L06 兜住(续跑)
bomb    ✅ completed    7  70714    0    —       ← L02 触发 over_budget

🎯 核心认知:这张表一眼看出每类故障被哪个机制兜住、代价多少。这就是 run summary 的价值——可比较。L08 的混沌收益矩阵正是基于这种 summary 跑批出来的。


4. 方案对比:怎么观测 Agent 健康?

方案 做法 取舍
翻原始日志(现状) 出事了 grep trace_id 挖 span ✅ 细节全;🚫 事后侦查,不告警;轨迹一长就淹没在 span 里
显式 run summary(本课主路线) 每次运行结束输出一行聚合 + 阈值告警 ✅ 实时体检、可比较、超阈值告警;🚫 字段要设计(对齐评估一次性设计好)
全量 APM / Langfuse ops-L02 已有 tracing,看 span 树 ✅ 可视化好;🚫 看的是 span 不是运行级聚合,轨迹健康仍要 run summary 补

选 ② 的理由:方案 ① 是被动响应(出事再挖),方案 ③ 是 span 级(看单步不是运行)。run summary 是运行级聚合,回答「这次跑得健康吗」,三者互补不替代。

💡 紧还是松?(自主-控制主线):告警阈值是「紧松」的核心。alert_steps_high=25 太紧正常长任务也告警(狼来了),太松(如 100)卡死了才发现。判断依据是「正常路径的步数 × 安全系数」——和 L01 的 max_total_steps 联动设(告警阈值 < 截断阈值,先告警再截断)。


5. 跑起来

code.py 演示

cd portfolio-projects/research-assistant
python ../../agent-ops-lessons/07_observability/code.py

演示: 1. 六类故障 × run summary 对比:六行表格,一眼看出每类故障被哪个机制兜住。 2. 阈值告警:问题跑触发 3 条告警(步数/预算/降级)。 3. 与 ops 请求级日志的分层:三层观测互补。


6. 落地清单

文件 改动 如何验证
src/research_assistant/run_summary.py 新增RunSummary + build_summary + _check_alerts + format_summary_line + emit_summary pytest tests/test_run_summary.py -q
src/research_assistant/config.py 新增enable_run_summary/alert_steps_high/alert_budget_ratio_high/alert_degraded_high(默认关) .env
src/research_assistant/service.py invoke 结束时 emit_summary(enable_run_summary 时);加 import time 见下
tests/test_run_summary.py 新增:15 个测试(字段提取/阈值告警/六类故障对比) pytest tests/test_run_summary.py -q

验收

cd portfolio-projects/research-assistant

# 1. 全部测试绿(204 + 15 = 219)
python -m pytest -q
# 预期:219 passed

# 2. 阈值告警触发
python -m pytest tests/test_run_summary.py::test_alert_steps_high -q
# 预期:passed

# 3. 六类故障 summary 对比
python -m pytest tests/test_run_summary.py::test_six_faults_summary_comparison -q
# 预期:passed(bomb 场景 token 最高)

# 4. 开 enable_run_summary 后每次运行出 summary 行(看日志)
python -c "
from research_assistant import config
config.settings.__dict__['enable_run_summary'] = True
from research_assistant.run_summary import build_summary, format_summary_line
s = build_summary({'step_count':8,'token_usage':1200,'failed_subtopics':[],'truncated':False,'publish_result':{},'cost_mode':'normal'}, run_id='demo')
print(format_summary_line(s))
"
# 预期:✅ [completed] run=demo steps=8 tokens=1200...

7. 本课在两条主线上的位置

  • 爆炸半径主线:观测本身不缩小爆炸半径,但它让「半径有没有被兜住」可见。没有 run summary,治理机制(L01–L06)的效果是「黑箱」——开了步数预算不知道有没有触发、开了熔断不知道有没有打开。run summary 把这些变成可看的数字。
  • 自主-控制主线:告警阈值的「紧松」判断依据是「正常路径指标 × 安全系数」——太紧正常任务也告警(狼来了),太松出事了才发现。和 L01 的 max_total_steps 联动:告警阈值 < 截断阈值,先告警再截断。

🎯 面试话术

「我的 Agent 每次运行出一行体检报告:步数、token 与预算比、降级次数、熔断状态、审批、恢复、结局分类。翻日志是事后侦查——出事去挖 span;run summary 是实时体检——跑完就知道这次健不健康,超阈值当场打 WARNING 告警,不等用户投诉。

字段设计对齐了我 frontier 课程的轨迹评估,但语义不同:评估算质量指标(事后离线批量),观测算健康指标(实时在线单次)。这样 L08 的混沌收益矩阵能复用同一套字段做跑批分析。

三层观测互补:ops 的请求级日志/tracing 看单步细节,run summary 看运行级健康,frontier 的评估看质量。告警阈值的紧松判断依据是『正常路径步数 × 安全系数』,和 L01 的 max_total_steps 联动——告警阈值小于截断阈值,先告警再截断。」