Files
python-agent/docs/07-阶段七-工程化部署与运维/04-阶段七综合实战-监控与运维Agent.md
T
2026-09-22 15:42:25 +08:00

14 KiB
Executable File
Raw Blame History

阶段七 · 综合实战:Agent 观测台 + 告警 + Bad-case 闭环(离线可运行)

所属:阶段七 工程化、部署与运维 定位:把阶段七第 3 小点「监控与运维」的三大支柱、黄金四指标、告警分级、Bad-case 闭环,落成一份离线可运行的最小观测体系——模拟一批 Agent 请求,观察健康度、触发告警、把低分会话归类修复。

一句话看懂本 Demo

monitoring_agent.py 用纯 Python 标准库模拟一个 Agent 服务的"观测台":

graph TD
    A["阶段七综合实战 monitoring_agent.py"] --> B[Part1 三大支柱<br/>日志/指标/追踪]
    A --> C[Part2 黄金四指标 + 告警<br/>成功率/QPS/P95/token]
    A --> D[Part3 Bad-case 闭环<br/>周一归类→周三修复→回归关闭]

核心一句话:上线只是开始,能不能持续看到健康度、及时告警、闭环迭代,决定 Agent 能不能长期跑稳。这份 Demo 就是最小可用的观测闭环。

运行方式

python3 code/阶段七/monitoring_agent.py

不需要 API Key、不需要外网、不需要装任何第三方库(只依赖标准库)。

完整代码位置

code/阶段七/monitoring_agent.py


每个 Part 在讲什么

Part 1 · 三大支柱(Logs / Metrics / Traces)

三个收集器分别对应监控三支柱:

支柱 类 记录什么
日志 Logs LogCollector 谁、何时、发生了什么(agent start / done / failed)
指标 Metrics MetricsCollector 可聚合数值(耗时、成败、token)
追踪 Traces TraceCollector 一次请求的跨步骤链路(agent start → llm span)
def simulate_traffic(mat, logs, traces, n=60):
    """模拟 60 次请求, 产生日志/指标/追踪 + 一些失败样本"""
    # 用函数属性自增计数生成全局唯一 trace_id(等价真实环境的 UUID)
    simulate_traffic.i = getattr(simulate_traffic, "i", 0) + 1
    tid = f"t{simulate_traffic.i:03d}"
    logs.info(tid, "agent start", step="receive")
    traces.start(tid, "llm")
    ok = random.random() > 0.28                 # 约两成失败
    lat = random.uniform(0.4, 3.4) if ok else random.uniform(2.0, 8.0)
    mat.record_call(lat, ok, prompt_tk=..., completion_tk=...)

⏳ 短期可不深究:里面 simulate_traffic.i = getattr(...) 用"函数属性自增"生成全局唯一 trace_id 只是离线 Demo 省依赖的教学技巧——真实生产是在网关/中间件用 UUID/ULID 生成并经 header 透传,第一遍不必在这点抠细节。 ⏳ 短期可不深究:random.random()>0.28(成败比)和 random.uniform 的延迟区间只是随机模拟参数,为了让例子出现告警而设——第一遍理解"它造出失败/长尾样本供演示告警与闭环"即可,不用纠结这些概率/区间取值是否贴合真实分布。

运维铁律:一次请求可以从日志看到明细、从追踪看到路径、从指标看到聚合,三样对起来才能排障,缺一样就"盲"。

🔬 深度理解 · 三支柱为什么要"对起来"排障:

  1. 本质:日志、指标、追踪是同一个问题的三种"视图"——日志给"这一次发生了什么"的明细,指标给"整体长什么样"的可聚合数字,追踪给"请求跨了哪些步骤"的路径;单独看任何一个都是"盲人摸象",只有用同一个 trace_id 把三者串起来,才能完成"现象 → 定位 → 根因"的闭环。
  2. 机制:真实生产里 Prometheus 扛指标(每秒 QPS / 成功率 / 延迟分位)、Jaeger / LangFuse 扛 span(一次 Agent 会话的调用树)、日志库每行带 trace_id;排障动作为"看指标发现异常 → 用 trace_id 追到那一次 trace 看走到哪一步 → 再拉同一 trace_id 的日志明细",三者在同一请求 / 同一时间窗口上对齐。
  3. 为什么重要:Demo 里三个 Collectors 都在内存、天然"对得起来";到生产一分家就断——若不强制带 trace_id、不打通三套系统,你会看到"QPS 掉了(指标)"却不知是模型超时还是工具报错,因为追踪和日志无从翻起。这正是"缺一样就盲"的真正含义。
  4. 易错点:① 三套系统各自埋点却不用同一个 trace_id,跨系统对不上号;② 只存指标不存日志明细,异常窗口无法复盘;③ 把 span 当日志、把日志当 span,单一视图信息残缺;④ 忘了给 Bad-case 补 trace_id,导致后续闭环无从定位。

Part 2 · 黄金四指标 + 告警(Google SRE · Agent 版)

evaluate_golden() 从指标收集器里取数,逐条做可行动告警:

def evaluate_golden(mat) -> dict:
    r = mat.report()
    out = {"QPS": r["QPS"], "token累积": r["token累积"]}
    sr = r["成功率"]; fr = 1 - sr; p95 = r["P95延迟(s)"]
    alerts = []
    if sr < 0.90:
        alerts.append(("P0", "成功率跌破90%", "立即电话"))
    elif fr > 0.05:
        alerts.append(("P1", "失败率>5%", "工单"))
    if p95 > 2.0:
        alerts.append(("P2", "P95变慢", "次日"))
    return out                # 阈值"可行动", 告了不处理的等于噪音

Demo 里 p95 vs mean 同时打印,能直观看到 LLM 长尾——均值(2.77s)反映大多数请求较快,而 P95(6.76s)提示确有相当比例的慢请求;只看均值会低估极端延迟,这正是要盯 P95/P99 的原因。

🔬 深度理解 · 为什么"只看均值会低估极端延迟":

  1. 本质:均值把所有请求耗时代数平均,一个极端长尾就足以把整体均值抬高,既不能告诉"大多数用户多快"、也没暴露"究竟有多少人很慢",是信息量最差的一类汇总;分位数(P95 / P99)回答"有 X% 的请求比它快",直击体感。
  2. 机制:LLM 服务延迟几乎必然长尾(偶发慢调用、重试放大、排队使后面请求等更久),均值被右尾剧烈拖动;只有 min/P50/P95/P99/max 成组看,才能同时看清轮廓(P50)、普遍体感(P95)、极端长尾(P99 / max)。
  3. 为什么重要:它直接决定扩容 / 加超时 / 做缓存这类决策的质量——均值 2.9s 看着还行、P95 6.7s 说明确有相当比例的慢请求,若只看均值会误判"还不错"、漏掉该被优化的长尾用户;这也是容量告警要按分位数定的原因。
  4. 易错点:① 把生产 P95 当成精确排序第 95 位——它来自直方图分桶的近似估算;② 观察窗口混入非典型批次(上线一批重型任务)让 P95 失真;③ 单看均值或单看 P95 都片面,要成组看趋势。

Part 3 · Bad-case 闭环(周一归类 → 周三修复 → 回归关闭)

BadCaseLoop 实现每周迭代 SOP:

def weekly_sop(self):
    """周一: 过队列四分类归档 → 挑 Top3 高频"""
    cats = {}
    for item in self.queue:
        c = classify(item["case"])            # prompt / 模型 / 工具 / 检索
        cats[c] = cats.get(c, 0) + 1
    return sorted(cats.items(), key=lambda x: x[1], reverse=True)[:3]

闭环逻辑:问题进队列 → 四分类归档 → 挑 Top3 → 修复 → 补评测集 → 回归通过才关闭;回归不通过不关闭。

🔬 深度理解 · 失败四分类与 Bad-case 闭环:

  1. 本质:把"线上用户觉得不对"的坏例,先归到 prompt / 模型 / 工具 / 检索 四类根因之一,再按频率挑 Top 修复——本质上是一套"用错误样本驱动迭代"的 RAG / Agent 质量方法论,让改进有方向、而非凭感觉乱调。
  2. 机制:低分会话 / 点踩 / 解析失败自动进队列(带 trace_id)→ 按四分类归类(prompt 措辞歧义 / 模型能力不足 / 工具报错、参数错、超时 / 检索召回或排序不对)→ 统计频次挑 Top3 → 针对性修复(改 prompt / 修工具 / 调检索)→ 把修复样本补进评测集 → 回归通过才 close,不过进复查。
  3. 为什么重要:没有分类维度,"迭代"就退化成"反复改、无方向";四分类恰好对照阶段五评测的三个环节,让"线上坏例"与"离线评测"用同一套语言,坏例还能反哺评测集,形成"评测越来越准、线上越来越好"的正循环。
  4. 易错点:① 用"症状"当"根因"分类(模型答错其实可能是检索没喂对上下文,却归成模型类);② 只看数量不按影响力排,高频但影响小 vs 低频但致命要分开加权;③ 修复了不补评测集,回归只能靠感觉、无法防回归;④ 回归不通过也 close,导致闭环"假关闭"。
[周一归类] 失败四分类: {'tool': 17} → Top3: [('tool', 17)]
[修复] tool 类 17 条, fix=给工具加超时+重试, 回归通过, 已关闭

运行结果预览

>>> Part 1 三大支柱
  [追踪] 最新一次请求 spans: ['llm']
  [日志] 该请求日志: [('info','agent start'), ('info','agent done')]

>>> Part 2 黄金四指标 + 告警
  QPS: 1.0      token累积: 43967      成功率: 0.717
  告警: [('P0','成功率跌破90%','立即电话'), ('P2','P95变慢','次日')]
  P95 vs 均值: 约 7.0 vs 约 2.9  (示例值, 数值随随机延迟分布而变)

>>> Part 3 Bad-case 闭环
[周一归类] 失败四分类: {'tool': 17} → Top3: [('tool', 17)]
[修复] tool 类 17 条, fix=给工具加超时+重试, 回归通过, 已关闭

换真实生产只需对号入座

教学实现 生产替换
MetricsCollector 内存计数器 + 直方图 Prometheus(counter / histogram)
evaluate_golden 逐条告警判定 Prometheus 告警规则 / Alertmanager 分级通知
TraceCollector 内存 span LangFuse / LangSmith / Jaeger
BadCaseLoop 内存队列 线上低分会话自动入库(带 trace_id)
每周 SOP 手写 结合阶段五的 LLM-as-judge 批量回归

本节自检

  • 能说清日志 / 指标 / 追踪三支柱,知道排障为什么三者要对起来
  • 能默写黄金四指标,并解释为什么看 P95 而不是只看均值
  • 已实现至少一项可行动告警(成功率 / LLM 失败率)
  • 已跑通一次「Bad-case 收集 → 归类 → 修复 → 评测回归」闭环

本节常见面试题(深度解析)

针对本节核心知识的面试高频点,配合"本质+机制"式理解,能让你答得既有深度又有广度。

面试题 1:这份"内存版"观测 Demo 换到生产,要替换成什么?为什么能"一条线对应替换"?

  • 面试官想考察:能否把教学抽象正确映射到真实技术栈,是否理解"抽象 vs 实现"的关系,而不只是会跑 Demo。
  • 专业作答(含深度):
    1. 逐项替换:MetricsCollector(内存计数器+直方图)→ Prometheus(counter / histogram);evaluate_golden(逐条告警判定)→ Prometheus 告警规则 / Alertmanager 分级通知;TraceCollector(内存 span)→ OpenTelemetry + Jaeger / LangFuse / LangSmith;BadCaseLoop(内存队列)→ 线上低分会话自动入库(带 trace_id);每周 SOP 手写 → 用 LLM-as-judge 批量回归。
    2. 为何能对应:因为教学里的"类"本就是真实组件的共性抽象——Counter / Histogram、告警规则、span、闭环流程是同一套概念,只是从"内存实现"换成"生产实现"。
    3. 生产要点:三支柱要用同一个 trace_id 打通、指标与追踪走 OpenTelemetry 标准协议(一次埋点多后端),否则"对起来排障"在生产里会失效。
  • 加分亮点 / 深度追问:可主动点明"用 OTLP 标准协议是为了避免锁死某个厂商观测后端";被追问"Prometheus histogram 的 bucket 该怎么设计"时,答"按 LLM 延迟的典型区间(0.5s/1s/2s/5s/10s...)设计,桶太粗精度差、太细成本高"。

面试题 2:为什么说"只看均值会低估极端延迟"?你在实际观测里看什么?

  • 面试官想考察:对 LLM 长尾与分位数指标是否有实践层面理解,能否落地到决策。
  • 专业作答(含深度):
    1. 本质:均值被长尾剧烈拖夸、又掩盖了"有多少人很慢",是失真最严重的汇总;回到本 Demo 示例——均值 2.9s 但 P95 6.7s,正是长尾的诊断信号。
    2. 看什么:成组看 P50 / P95 / P99 / max,P99 远超 P95 说明长尾重,就该去优化慢请求(缓存、小模型、超时上限、异步化),而不是盲目加机器。
    3. 生产注意:P95 来自直方图分桶的近似估算;观察窗口要避开刚上线的非典型批次,否则 P95 会失真。
  • 加分亮点 / 深度追问:可补"长尾常来自偶发慢调用 + 重试放大 + 排队效应,P99 更适合做容量规划";被追问"长尾怎么量化判断是否改善"时,答"比较同口径同窗口的 P99 趋势,而不是均值"。

面试题 3:Bad-case 闭环里的"失败四分类"为什么重要?如何防止闭环"假关闭"?

  • 面试官想考察:是否有质量数据闭环意识,是否理解"用坏例驱动迭代、且用评测防回归"。
  • 专业作答(含深度):
    1. 四分类为何重要:prompt / 模型 / 工具 / 检索提供了改进的"根因定向",避免无方向乱改;它对照阶段五评测环节,让线上坏例与离线评测用同一套语言。
    2. 闭环流程:坏例自动进队列(带 trace_id)→ 四分类归档 → 按频次×影响挑 Top → 修复 → 补进评测集 → 回归通过才 close。
    3. 防"假关闭":回归不通过绝不 close、用症状当根因要纠正、先看影响再看频次——"补评测集 + 回归门禁"是防止假关闭的关键。
  • 加分亮点 / 深度追问:可主动讲"坏例反哺评测集形成正循环、用 LLM-as-judge 批量回归扩大覆盖";被追问"四分类边界模糊、某类内部还要细分"时,答"可在类内再拆子类,如工具类分超时/报错/参数错误,便于精准修复"。