Skip to content

Meter 指标采集

为什么需要指标

Agent 的一次运行涉及多种资源消耗:LLM 调用消耗 Token(直接对应费用),工具执行消耗时间和外部配额,底层硬件消耗算力和内存。这些消耗在开发阶段容易被忽视,但在生产环境中是运营决策的关键依据——成本分摊、容量规划、异常告警都离不开量化指标。

TFRobot 集成 OpenTelemetry Metrics 作为指标采集的基础设施。框架提供统一的 Meter 获取方式和装饰器模式,使得任何需要量化观测的环节都可以接入指标体系。当前已落地的指标聚焦于 LLM Token 消耗——这是 Agent 运行中成本最高、最需要实时观测的资源。未来,工具调用耗时、硬件资源占用等指标也会按相同模式逐步纳入。

LLM Token 指标(当前实现)

三个 Counter 指标

tfrobot/telemetry/meter.py 定义了三个 Counter 类型指标:

指标名称 说明 标签
llm.tokens.total 单次调用的总 Token 数 model_name
llm.tokens.prompt Prompt 部分的 Token 数 model_name
llm.tokens.completion Completion 部分的 Token 数 model_name

选择 Counter(只增不减的累加器)是因为 Token 消耗是一个天然的单调递增量——你不会「退还」已经消耗的 Token。

@report_llm_metrics 装饰器

指标采集通过装饰器自动完成,不需要在每个 LLM 子类中手动编写上报逻辑:

# tfrobot/telemetry/meter.py(简化)
from opentelemetry import metrics

meter = metrics.get_meter(__name__)

METER_LLM_TOTAL_TOKENS = "llm.tokens.total"
METER_LLM_PROMPT_TOKENS = "llm.tokens.prompt"
METER_LLM_COMPLETION_TOKENS = "llm.tokens.completion"


def report_llm_metrics(complete):
    @wraps(complete)
    async def wrapper(self, *args, **kwargs):
        result = await complete(self, *args, **kwargs)
        try:
            labels = {"model_name": self.name or "unknown"}
            meter.create_counter(METER_LLM_TOTAL_TOKENS).add(result.usage["total_tokens"], labels)
            meter.create_counter(METER_LLM_PROMPT_TOKENS).add(result.usage["prompt_tokens"], labels)
            meter.create_counter(METER_LLM_COMPLETION_TOKENS).add(result.usage["completion_tokens"], labels)
        except Exception as e:
            warnings.warn(f"上报LLM指标时出现异常: {e}")
        return result
    return wrapper

关键设计点:

  • 异常不传播:指标上报失败只 warn,不影响 LLM 调用本身
  • 同时支持同步和异步:装饰器内部检测被装饰函数是否为协程,分别生成对应的 wrapper
  • 标签维度:当前以 model_name 区分,可按模型粒度查看消耗

在 LLM 中的应用

装饰器应用于所有 LLM 子类的 completeasync_complete 方法。以 SglangDesk 为例:

# tfrobot/brain/chain/llms/generation_llms/desk_llm/sglang_desk.py
class SglangDesk(BaseDeskLLM):
    @report_llm_metrics      # ← 自动上报 Token 指标
    @retry(stop=stop_after_attempt(3), ...)
    def complete(self, msg, **kwargs):
        ...

目前已覆盖的 LLM 提供商包括:OpenAI、Anthropic、DeepSeek、ZhipuAI、Google Gemini、Ollama、DashScope、OpenRouter,以及所有 DeskLLM 变体,共计 17 个实现类。

MeterProvider 设计策略

上面的 LLM Token 指标是当前落地的第一个场景,但 Meter 的基础设施是面向整个框架的。无论未来新增工具调用耗时(Histogram)、并发 Chain 数量(UpDownCounter)还是硬件资源快照(ObservableGauge),都通过同一个全局 MeterProvider 汇聚和导出。

TFRobot 不在框架内部初始化 MeterProvider,而是使用 OpenTelemetry 的全局默认实例:

# tfrobot/telemetry/meter.py
meter = metrics.get_meter(__name__)  # 使用全局 MeterProvider

这是一个有意的设计决策:

graph LR
    A["调用方<br/>TFRobotServer / Celery"] -->|"set_meter_provider()"| B["全局 MeterProvider"]
    C["tfrobot 框架<br/>get_meter()"] -->|获取| B
    B -->|导出| E["Collector / Prometheus"]
角色 职责
TFRobot 框架 只负责使用 Meter 上报指标
外部调用方 负责配置 MeterProvider(选择导出器、设置采集间隔等)

这样做的好处是:

  • 关注点分离:框架不需要知道指标最终发往 Prometheus 还是 OTLP Collector
  • 安全降级:如果外部没有配置 MeterProvider,OpenTelemetry 默认使用 NoOp 实现,指标调用不会报错,只是不会导出数据
  • 灵活组合:不同部署环境(本地开发、CI、生产)可以配置不同的导出策略

外部配置示例

在 TFRobotServer 或 Celery Worker 的启动代码中配置 MeterProvider:

from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from opentelemetry.sdk.resources import Resource

resource = Resource.create({"service.name": "tfrobot-prod"})
exporter = OTLPMetricExporter(endpoint="otel-collector:4317")
provider = MeterProvider(
    resource=resource,
    metric_readers=[PeriodicExportingMetricReader(exporter)],
)
metrics.set_meter_provider(provider)

# 之后 TFRobot 框架中的 meter.create_counter(...).add(...) 就会将数据导出到 Collector

故障排查

当指标未出现在监控系统中时,按以下顺序检查:

  1. MeterProvider 是否已设置

    from opentelemetry.metrics import get_meter_provider
    
    print(type(get_meter_provider()))
    # 期望: <class 'opentelemetry.sdk.metrics.MeterProvider'>
    # 若为 NoOpMeterProvider 则说明未配置
    
  2. LLM 返回值是否包含 usage@report_llm_metricsresult.usage 读取 Token 数。如果 LLM 提供商未返回 usage 字段,装饰器会 warn 但不会上报数据。

  3. Collector 是否在线:检查 OTLP Collector 的连接状态和日志。

规划中的指标

以下指标尚未实现,但已纳入设计规划。它们将沿用与 LLM Token 指标相同的模式——装饰器自动采集、全局 MeterProvider 导出。

指标名称 类型 标签 说明
tool.invocation.count Counter tool_name, scene, chain_id 统计各工具被调用的次数
chain.failure.count Counter failure_reason, scene, chain_id 统计 Chain 执行失败次数,按原因分类