Skip to content

第 13 章 · Observability 可观测性

本章目标:区分 Flue 的两个事件面,学会用 observe() 订阅运行时事件、读取 token 用量,并把遥测导出到 OpenTelemetry、Sentry 与 Braintrust。

13.1 两个事件面

Flue 把"agent 干了什么"暴露为类型化的运行时事件:模型回合、工具调用、结构化日志、压缩(compaction)、结算(settlement)。注意它与聊天 UI 读的消息流是两个不同的面

text
① 运行时事件面(本章):observe() 订阅,面向运维与调试
② 会话消息面:Routing + Flue Agent SDK,面向聊天界面的逐条消息
typescript
// 面向运维的事件订阅:observe()
import { observe } from '@flue/runtime';

const stop = observe((event) => {
  // 每一个事件都是可判别的联合类型,switch 处理
  switch (event.type) {
    case 'model_turn':
      console.log('[model]', event.model, 'tokens:', event.usage);
      break;
    case 'tool_call':
      console.log('[tool]', event.name, '耗时:', event.durationMs, 'ms');
      break;
    case 'log':
      console.log('[log ]', event.level, event.message);
      break;
  }
});

// 不再需要时停止订阅
// stop();

13.2 事件流里有什么

模型回合事件自带 token 用量与 provider 诊断信息,这是成本监控的原始数据源:

typescript
// 成本统计:聚合 model_turn 事件的 usage
import { observe } from '@flue/runtime';

const usageByDay = new Map<string, { input: number; output: number }>();

observe((event) => {
  if (event.type !== 'model_turn') return;
  const day = new Date().toISOString().slice(0, 10); // 按天聚合
  const cur = usageByDay.get(day) ?? { input: 0, output: 0 };
  usageByDay.set(day, {
    input: cur.input + (event.usage?.inputTokens ?? 0),
    output: cur.output + (event.usage?.outputTokens ?? 0),
  });
});

// 每小时打印一次用量报表
setInterval(() => {
  for (const [day, u] of usageByDay) {
    console.log(day, 'input:', u.input, 'output:', u.output);
  }
}, 60 * 60 * 1000);

13.3 导出到 OpenTelemetry

@flue/opentelemetry 把运行时事件转换为 OTel trace/span,接入任意兼容后端(Jaeger、Grafana Tempo 等):

bash
npm install @flue/opentelemetry @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http
typescript
// src/otel.ts
// OpenTelemetry 导出适配
import { flueOtel } from '@flue/opentelemetry';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';

const sdk = new NodeSDK({
  // Flue 提供的适配器把 agent 活动映射为 span
  traceExporter: new OTLPTraceExporter({
    url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, // 例如 http://localhost:4318/v1/traces
  }),
});

// 注册 Flue 事件 → OTel 的桥接
flueOtel({ sdk });
sdk.start();

13.4 Sentry 与 Braintrust

两类现成集成覆盖"报错追踪"与"LLM 评估"两种需求:

typescript
// Sentry:把 agent 异常与上下文送进错误追踪
import * as Sentry from '@sentry/node';
import { observe } from '@flue/runtime';

Sentry.init({ dsn: process.env.SENTRY_DSN });

observe((event) => {
  // 工具失败或会话错误时上报,附带会话 id 便于排查
  if (event.type === 'error') {
    Sentry.captureException(event.error, {
      tags: { sessionId: event.sessionId, agent: event.agent },
    });
  }
});
typescript
// Braintrust:把模型回合导出为评估样本
import { braintrustExporter } from '@flue/runtime/observers'; // 以文档导出器为例
import { observe } from '@flue/runtime';

observe(braintrustExporter({
  project: 'flue-triage', // Braintrust 项目名
  apiKey: process.env.BRAINTRUST_API_KEY!,
}));

13.5 自定义 observer 与生产监控

任何 observe() 回调都是一个 observer——写一个写文件的审计日志只需几行:

typescript
// 自定义 observer:审计日志落盘(JSON Lines 格式)
import { observe } from '@flue/runtime';
import { appendFileSync } from 'node:fs';

observe((event) => {
  // 每条事件一行 JSON,便于 jq/grep 分析
  appendFileSync(
    'audit.jsonl',
    JSON.stringify({ ts: Date.now(), ...event }) + '\n',
  );
});

Cloudflare 平台可观测性

部署在 Workers 上时,agent 活动还会自然出现在 Cloudflare 平台自身的观测面板(logs/metrics)中——无需额外接线即可看到基础指标。

生产监控的最小清单:

  1. 错误率:error 事件 / 总事件,接 Sentry 告警;
  2. 成本:按天聚合的 token 用量(13.2 的报表);
  3. 延迟:model_turn 的首字延迟与 tool_call 耗时分位数。

13.6 本章小结

  • 两个事件面:observe() 的运行时事件面(运维)与 Routing/SDK 的会话消息面(聊天 UI);
  • 事件是类型化联合:model_turn(含 usage)、tool_call、log、error、settlement 等;
  • @flue/opentelemetry 一行桥接任意 OTel 后端;Sentry 管报错、Braintrust 管评估;
  • 自定义 observer 就是 observe 回调,JSONL 审计日志是最简单的落地方式。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. Flue 的运行时事件面与会话消息面的区别是?

2. 想统计每个会话的 token 成本,应该从哪类事件取数据?

3. @flue/opentelemetry 包的作用是?

4. 写一个把事件追加到 audit.jsonl 的函数,本质上是在使用什么机制?

🛠️ 动手实践

  1. 写一个 observer 统计每个 agent 的平均回合数与 token 成本,输出 Top 5 成本会话。
  2. 本地起一个 Jaeger(docker run jaegertracing/all-in-one),接入 @flue/opentelemetry,截图一次会话的 trace 瀑布图。
  3. 把 13.5 的 JSONL 审计日志用 jq 做一次"昨天所有 tool_call 耗时 > 2s"的查询。