AI 系统可观测性:从 Token 用量追踪到模型推理延迟的全链路监控

一、AI 服务的监控盲区:当传统 APM 无法覆盖模型层

传统微服务的监控体系围绕 HTTP 请求延迟、错误率、吞吐量三个黄金指标构建。但 AI 服务引入了新的可观测性维度:Token 消耗量、模型推理延迟分布、Prompt 与 Completion 的质量指标、成本归因分析。某 AI 客服平台上线后,月度 API 费用从预算的 5 万飙升到 20 万,却无法定位是哪个业务场景、哪类请求消耗了最多的 Token。

更棘手的是,AI 服务的"错误"定义与传统服务不同。HTTP 200 但返回了幻觉内容的请求,比 HTTP 500 更危险。传统 APM 无法捕获这类"逻辑错误",需要专门的 AI 可观测性体系。本文将系统阐述 AI 服务的全链路监控架构,从 Token 用量追踪到推理质量评估,给出可落地的实现方案。

二、AI 可观测性的分层架构与核心指标体系

AI 服务的可观测性需要覆盖四个层次:基础设施层(GPU 利用率、模型服务健康度)、模型调用层(延迟、Token 用量、错误率)、业务语义层(回答质量、用户满意度)、成本归因层(按业务线/场景的 Token 成本分摊)。

flowchart TB
    subgraph 基础设施层
        A1[GPU 利用率]
        A2[模型服务实例健康]
        A3[内存与显存占用]
    end

    subgraph 模型调用层
        B1[推理延迟 P50/P95/P99]
        B2[Token 消耗量 Input/Output]
        B3[调用错误率与类型]
        B4[限流与熔断触发次数]
    end

    subgraph 业务语义层
        C1[回答质量评分]
        C2[幻觉检测率]
        C3[用户反馈正负比]
        C4[上下文窗口利用率]
    end

    subgraph 成本归因层
        D1[按业务线 Token 成本]
        D2[按模型类型成本分布]
        D3[预算消耗趋势]
        D4[异常成本告警]
    end

    A1 --> B1
    B1 --> C1
    C1 --> D1

核心指标定义:

  • Token Throughput:每分钟处理的 Token 数量,衡量模型服务的吞吐能力
  • Inference Latency:从请求发出到首 Token 返回的时间(TTFT)和总完成时间(TTFCT),分别衡量首字延迟和端到端延迟
  • Token Efficiency:有效输出 Token 占总消耗 Token 的比例,衡量 Prompt 设计效率
  • Cost Per Query:单次查询的平均成本,按模型定价和 Token 用量计算
sequenceDiagram
    participant Client as 业务客户端
    participant GW as AI Gateway
    participant Model as 模型服务
    participant Metrics as 指标采集
    participant Alert as 告警系统

    Client->>GW: 请求(带 traceId + bizTag)
    GW->>GW: 记录请求开始时间
    GW->>Model: 模型推理请求
    Model-->>GW: 返回结果(含 usage 字段)
    GW->>Metrics: 上报指标
    Note right of Metrics: latency, tokenUsage,<br/>modelId, bizTag, traceId
    Metrics->>Alert: 检查阈值规则
    Alert-->>Metrics: 触发/不触发告警
    GW-->>Client: 返回结果

三、生产级 AI 可观测性代码实现与最佳实践

以下代码展示了 AI 服务全链路监控的核心实现,涵盖指标采集、成本归因和告警规则。

/**
 * AI 可观测性核心组件 - 全链路指标采集与成本归因
 * 集成 Micrometer 指标框架,支持 Prometheus + Grafana 体系
 */
@Component
@Slf4j
public class AiObservabilityService {

    private final MeterRegistry meterRegistry;
    private final TokenCostCalculator costCalculator;
    private final AiQualityEvaluator qualityEvaluator;

    // 推理延迟分布统计(直方图 + 百分位)
    private final DistributionSummary inferenceLatency;

    // Token 用量计数器
    private final Counter inputTokenCounter;
    private final Counter outputTokenCounter;

    // 成本归因计数器(按业务标签分维度)
    private final Counter costCounter;

    public AiObservabilityService(MeterRegistry meterRegistry,
                                   TokenCostCalculator costCalculator,
                                   AiQualityEvaluator qualityEvaluator) {
        this.meterRegistry = meterRegistry;
        this.costCalculator = costCalculator;
        this.qualityEvaluator = qualityEvaluator;

        // 初始化延迟分布统计,配置百分位桶
        this.inferenceLatency = DistributionSummary.builder("ai.inference.latency")
            .description("模型推理延迟分布")
            .tag("metric", "latency")
            .publishPercentiles(0.5, 0.95, 0.99)
            .publishPercentileHistogram()
            .register(meterRegistry);

        // Token 用量计数器,按模型 ID 分维度
        this.inputTokenCounter = Counter.builder("ai.token.usage")
            .description("Token 消耗量")
            .tag("direction", "input")
            .register(meterRegistry);

        this.outputTokenCounter = Counter.builder("ai.token.usage")
            .description("Token 消耗量")
            .tag("direction", "output")
            .register(meterRegistry);

        // 成本计数器
        this.costCounter = Counter.builder("ai.cost.total")
            .description("AI 调用总成本")
            .baseUnit("USD")
            .register(meterRegistry);
    }

    /**
     * 记录一次模型调用的完整指标
     * @param context 调用上下文,包含模型、业务标签、延迟、Token 用量等
     */
    public void recordModelCall(AiCallContext context) {
        // 1. 记录推理延迟
        inferenceLatency.record(context.getLatencyMs());

        // 2. 记录 Token 用量(按模型 + 业务标签分维度)
        Counter.builder("ai.token.usage")
            .tag("direction", "input")
            .tag("model", context.getModelId())
            .tag("bizTag", context.getBizTag())
            .register(meterRegistry)
            .increment(context.getInputTokens());

        Counter.builder("ai.token.usage")
            .tag("direction", "output")
            .tag("model", context.getModelId())
            .tag("bizTag", context.getBizTag())
            .register(meterRegistry)
            .increment(context.getOutputTokens());

        // 3. 计算并记录成本
        double cost = costCalculator.calculate(
            context.getModelId(),
            context.getInputTokens(),
            context.getOutputTokens()
        );

        Counter.builder("ai.cost.total")
            .tag("model", context.getModelId())
            .tag("bizTag", context.getBizTag())
            .baseUnit("USD")
            .register(meterRegistry)
            .increment(cost);

        // 4. 异步评估回答质量(不阻塞主流程)
        CompletableFuture.runAsync(() -> {
            double qualityScore = qualityEvaluator.evaluate(
                context.getPrompt(),
                context.getCompletion(),
                context.getContext()
            );

            // 质量评分低于阈值时记录告警
            if (qualityScore < 0.6) {
                log.warn("AI 回答质量评分过低: score={}, model={}, bizTag={}, traceId={}",
                    qualityScore, context.getModelId(),
                    context.getBizTag(), context.getTraceId());

                meterRegistry.counter("ai.quality.low_score",
                    "model", context.getModelId(),
                    "bizTag", context.getBizTag()
                ).increment();
            }
        });

        // 5. 检查异常成本模式
        checkAnomalousCost(context, cost);
    }

    /**
     * 异常成本检测:单次调用成本超过阈值时告警
     * 防止因 Prompt 注入或异常请求导致成本失控
     */
    private void checkAnomalousCost(AiCallContext context, double cost) {
        double singleCallThreshold = 1.0; // 单次调用成本上限 1 美元
        if (cost > singleCallThreshold) {
            log.error("异常成本告警: cost={}, model={}, bizTag={}, traceId={}",
                cost, context.getModelId(),
                context.getBizTag(), context.getTraceId());

            meterRegistry.counter("ai.cost.anomaly",
                "model", context.getModelId(),
                "bizTag", context.getBizTag()
            ).increment();
        }
    }
}

/**
 * Token 成本计算器:按模型定价计算调用成本
 */
@Component
public class TokenCostCalculator {

    // 模型定价表(每千 Token 价格,单位:美元)
    private final Map<String, ModelPricing> pricingTable = Map.of(
        "gpt-4", new ModelPricing(0.03, 0.06),
        "gpt-3.5-turbo", new ModelPricing(0.0005, 0.0015),
        "qwen-max", new ModelPricing(0.004, 0.012),
        "qwen-turbo", new ModelPricing(0.0006, 0.0006)
    );

    public double calculate(String modelId, long inputTokens, long outputTokens) {
        ModelPricing pricing = pricingTable.getOrDefault(
            modelId, new ModelPricing(0.01, 0.03)
        );
        double inputCost = (inputTokens / 1000.0) * pricing.inputPricePerK();
        double outputCost = (outputTokens / 1000.0) * pricing.outputPricePerK();
        return inputCost + outputCost;
    }

    record ModelPricing(double inputPricePerK, double outputPricePerK) {}
}

关键设计点:第一,指标按模型 ID 和业务标签(bizTag)分维度采集,支持按业务线进行成本归因。第二,推理延迟使用 DistributionSummary 配合百分位直方图,可以精确统计 P50/P95/P99 延迟分布。第三,质量评估异步执行,不阻塞主调用链路。第四,异常成本检测在每次调用时执行,单次成本超过阈值立即告警,防止 Prompt 注入攻击导致成本失控。

四、AI 可观测性建设的代价与适用边界

构建完整的 AI 可观测性体系并非没有成本。

指标膨胀问题:按模型、业务标签、方向(input/output)分维度采集指标,在高基数场景下(如 bizTag 为用户 ID)会导致指标爆炸,压垮 Prometheus 等时序数据库。建议业务标签使用有限枚举值(如业务线名称),而非高基数值(如用户 ID)。

质量评估的主观性:回答质量评分依赖评估模型或规则引擎,本身存在准确性问题。生产环境中建议采用"模型评估 + 用户反馈"双通道机制,以用户反馈为金标准,模型评估为辅助信号。

延迟开销:指标采集和上报会增加每次调用的额外延迟(通常 1—3ms),在延迟敏感场景中需要评估是否可接受。异步质量评估虽然不阻塞主流程,但会增加后台计算资源消耗。

适用边界:当 AI 服务调用量低于日均千次时,简单的日志记录即可满足监控需求,无需引入完整的可观测性体系。当日均调用量超过万次,或需要成本管控、质量监控时,系统化的可观测性建设才有投入价值。对于 PoC 阶段的项目,建议先从 Token 用量统计和延迟监控两个维度起步,后续再逐步扩展。

五、总结

AI 服务的可观测性需要覆盖基础设施、模型调用、业务语义、成本归因四个层次,每个层次有独立的核心指标。Token 用量追踪和推理延迟分布是最基础的两个维度,成本归因和回答质量评估是更高阶的能力。生产级实现应基于 Micrometer + Prometheus 体系,按模型和业务标签分维度采集指标,异步执行质量评估,并设置异常成本告警。可观测性建设应分阶段推进:先覆盖基础指标,再扩展质量与成本维度,避免一步到位的过度建设。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐