☰
Spring AI 指标监控实战:从 Token 成本到 Prometheus 全链路观测
2026/10/5 7:13:20 网站建设 项目流程

上周同事拉我一起排查一个 Spring AI 生产问题,现象是夜里某个定时任务会把大模型调用量顶上去,成本直接翻了几倍。当时项目里除了 HTTP 接口原有的 QPS、RT 监控之外,AI 调用完全裸奔,连每天消耗多少 token 都只能去模型厂商后台翻账单。

这可能是很多团队的共性状态:Spring AI 把模型调用封装得很顺手,但上线之后指标监控却往往被忘在脑后。网上聊 Spring AI 的教程绝大多数停在"怎么调通一个聊天模型",再往下就没有了,真正落到生产环境怎么观测、怎么量化成本、怎么追查异常链路的资料非常少。

这篇文章我打算把 Spring AI 指标监控这件事从头捋一遍,从指标维度的拆解、框架自带观测能力的挖掘,到结合 Micrometer、Prometheus 落到可操作的一整套方案,还包括我在实际项目里踩过的一些坑。正在用 Spring AI 做 Java AI 应用、又觉得"光看接口监控不踏实"的同行,可以直接照着抄。

1. 为什么 Spring AI 应用必须做指标监控

1.1 AI 应用与普通接口的监控差异

我们平时监控一个普通 REST 服务,看的东西很习惯:请求量、响应时间、错误码分布、JVM 内存。这套玩法搬到 AI 应用上远远不够。

原因有两条。第一,成本结构完全不同。普通接口一次数据库查询可能消耗 0.1 毫秒 CPU,成本约等于零;而一次大模型调用可能消耗几千 token,换算成真金白银。同样是 QPS 翻倍,普通服务只是多占点机器资源,AI 应用是直接烧钱。不同步监控 token 消耗,成本失控是迟早的事。

第二,错误形态完全不同。大模型接口不是你返回 HTTP 500 才算错,它可能正常返回 200,回复内容却是乱的、重复的、被截断的,甚至出现工具调用死循环。这种情况在普通接口监控里根本看不出来,必须在 AI 调用链路上专门埋点,把"输入输出 token 数量""工具调用次数""响应耗时分布"都变成可观测指标,才能还原真实状况。

1.2 一套完整指标需要回答的三个问题

我在生产环境里给 Spring AI 应用做指标监控,脑子里始终带着三个问题。

第一个问题是成本。今天消耗了多少输入 token、多少输出 token,折合成钱是多少,哪个模型最烧钱,哪个用户或哪个场景调用最频繁。这个问题不回答,月底账单来了只能干瞪眼。

第二个问题是质量。模型调用成功率多少,有多少次是因为限流、鉴权失败,有多少次是内容被安全策略拦截,Agent 执行了多少步工具调用有没有陷入死循环。质量指标能直接反映业务体验,也能暴露提示词设计问题。

第三个问题是稳定性。模型供应商的接口延迟波动很大,同一个模型白天和夜间响应时间能差一个数量级。我们需要知道 TP50、TP95、TP99 响应时间,需要知道错误率,才能设置合理的超时和告警阈值。

这三个问题对应的指标粒度完全不同,成本指标要到 token 级别,质量指标要到内容级别,稳定性指标要到毫秒级别。缺了任何一个维度,监控都是偏的。

2. Spring AI 指标监控的指标维度拆解

2.1 模型调用层指标:调用量、延迟与错误

Spring AI 在 Spring Boot 项目里默认集成了基于 Micrometer 的观测体系,聊天模型、嵌入模型、图像模型、语音转文字模型的调用都会被统一记录。以聊天模型为例,每一次模型调用会被包装成一个 Observation,自动生成采样时间、响应时间、错误信息等基础指标。

我实际最看重的是下面这几类:

  • 调用次数:精确知道每个模型接口被调了多少次。注意这里的"次数"不是用户请求数,而是模型请求数,一个 Agent 任务可能内部发起多次模型调用。
  • 响应时间:从发送请求到拿到完整回复的耗时,重点看 TP95 和 TP99,因为大模型服务的响应时间抖动非常严重,平均值掩盖问题。
  • 错误率:包含 HTTP 层面的错误、限流错误、鉴权错误、超时错误,最好按错误类型分开统计。
  • 输入输出 token 数:来自模型返回的 usage 信息,是成本核算的原始材料。

框架自动生成的指标会带上 provider、model 等标签,比如 provider 可能是 openai、dashscope、ollama,model 是具体的模型名。查询时按标签聚合,就能把不同供应商、不同模型分开对比。

2.2 成本与 Token 消耗指标

Token 维度的指标是整个 AI 监控的核心,因为它直接对应货币成本。每次对话模型调用返回结果里都带 usage,包含输入 token 数、输出 token 数、总 token 数,有些模型还支持缓存命中 token。这些数字必须被采集、聚合、存储,最好还要按天分桶。

成本计算其实是个很简单的乘法:单次调用成本等于输入 token 数乘输入单价,加上输出 token 数乘输出单价。不同模型输入单价和输出单价不一样,通常输出单价是输入的 3 到 5 倍,所以"输出 token 数"往往才是成本大头。

我在实践里发现一个特别容易被忽略的点:只看总 token 数是远远不够的,必须把输入和输出分开统计。有些团队优化提示词,把 prompt 从 3000 token 压缩到 800 token,但系统回复却从 200 token膨胀到 2000 token,总成本反而更高了。不拆开统计,这类问题根本发现不了。

成本指标还要和业务维度关联起来。比如给每个用户会话打上业务标签,按用户维度统计 token 消耗,就能定位到那些"特别烧钱"的异常会话。一个用户半小时内消耗了上万 token,这种消费 profile 和正常用户差异极大,背后大概率是提示词循环或者异常重试在作祟。

2.3 Agent 编排层指标

如果你的应用用了 Spring AI 的 Agent 能力,单看模型调用层指标就不够了。一个 Agent 任务本质上是一段多步推理:先调用模型,模型决定调工具,工具返回结果后再次调用模型,如此循环直到结束。这个过程中每一步都可能失败,也可能无限循环。

所以 Agent 编排层必须补上这几类指标:

  • 工具调用次数:每种工具被调了多少次。次数异常偏高往往意味着模型陷入了该工具的重复调用。
  • 单任务内的模型调用步数:一个 Agent 任务最终转化为多少次模型调用。这是评估成本消耗的重要统计量。
  • 工具执行耗时与结果大小:定位工具瓶颈,尤其是检索类工具返回大量结果导致上下文爆增的情况。
  • 上下文窗口使用率:随着工具结果不断追加,输入 token 会快速增长。监控这个指标能提前预警"超出上下文窗口"或者"上下文过长导致成本暴涨"的问题。

2.4 基础设施指标:别只看模型层

模型调用指标再完善,也离不开底层基础设施指标。JVM 内存、GC 频率、线程池状态、出站 HTTP 连接池的使用率,这些基本盘要持续监控。特别是如果你并发调用多个模型,HTTP 客户端连接池很容易成为瓶颈,连接池被打满的表现往往是"调用模型超时",但根因是你自己的连接池配置太小。

另外,模型供应商通常有自己的限流策略,限流响应会以特定错误码返回。建议为限流单独建一个计数器,当限流次数快速升高时要能第一时间感知,方便及时调整并发策略或切换模型。

3. 快速搭建 Spring AI 指标监控(基于 Actuator + Prometheus)

3.1 引入依赖与配置

Spring Boot 天生自带监控三件套:Actuator、Micrometer、Prometheus 注册表。Spring AI 只负责把指标喂给 Micrometer,导出、采集、展示这套链路完全可以用现成的。

Maven 依赖这么加:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>

Spring AI 的观测模块在 starter 里通常已经被带上,如果没有专门拆分,检查一下依赖树里是否存在 spring-ai-observability。

然后配置 Actuator 暴露端口,注意不要把管理层暴露到公网:

management: endpoints: web: exposure: include: health,info,prometheus,metrics metrics: tags: application: ${spring.application.name:default}

启动应用后访问/actuator/prometheus,Ctrl+F 搜索spring.ai或者spring_ai,能看到一批框架自动生成的指标,就说明链路已经通了。

3.2 哪些指标是框架自动埋的,哪些需要自己写

这是最需要弄清的一点。Spring AI 框架内置的观测会自动记录聊天模型、嵌入模型、图像生成、语音转文字等调用的耗时和 token 数。也就是说,基础的模型调用指标你一行代码都不用写。

打开 Prometheus 端点能看到类似这样的输出:

spring_ai_model_chat_time_seconds_count{provider="dashscope",model="qwen-plus",} 156.0 spring_ai_model_chat_time_seconds_sum{provider="dashscope",model="qwen-plus",} 180.32

这里 provider 是模型供应商,model 是模型名。框架把每次模型调用的耗时和 token 指标自动采集完成,Prometheus 格式里点号会转成下划线,这是正常的。

但业务标签和 Agent 层面的高级指标,框架就没法自动给你了。比如"这个调用来自哪个用户""属于哪个业务线""Agent 任务执行了多少步",这些必须自己在埋点处补充。我的经验是,默认指标解决"有没有"的问题,自定义指标解决"是谁的、为什么"的问题,两者缺一不可。

3.3 自定义指标的三个切入点

在 Spring AI 里自定义埋点有三个常用切入点。

第一个是自定义 ObservationHandler。可以注册一个处理器监听模型调用的开始和结束事件,在事件里读取请求和响应上下文,提取 token 数、模型名等信息,写入自定义指标。

第二个是给 Observation 添加标签。通过自定义 ObservationFilter 给模型调用附上业务标签,比如 userId、bizType,这样后续统计就可以按业务维度切分。

第三个是直接在业务代码里用 Micrometer 的 MeterRegistry 创建 Counter 和 Timer。这种方式最灵活,适合统计 Agent 步数、工具调用次数这类框架观测没覆盖的指标。

三种方式互相配合,才能在 Spring AI 应用里形成一套比较完整的指标体系。

4. 实操过程与核心环节实现

4.1 给模型调用添加业务标签

我推荐优先使用 ObservationFilter 给 AI 调用挂业务标签。它可以让标签在观测上下文里统一生效,后续不管框架生成多少指标,都会自动带上这些公共标签。

写法可以参考这样:

@Component public class BusinessObservationFilter implements ObservationFilter { private static final Logger log = LoggerFactory.getLogger(BusinessObservationFilter.class); public static final ThreadLocal<String> CURRENT_USER = new ThreadLocal<>(); public static final ThreadLocal<String> CURRENT_BIZ = new ThreadLocal<>(); @Override public Observation.Context map(Observation.Context context) { if (context instanceof ChatModelObservationContext) { String userId = CURRENT_USER.get(); String bizType = CURRENT_BIZ.get(); if (userId != null) { context.addLowCardinalityKeyValue(KeyValue.of("biz.user", userId)); } if (bizType != null) { context.addLowCardinalityKeyValue(KeyValue.of("biz.type", bizType)); } } return context; } }

在业务入口处,比如 Controller 拦截器或者 Service 方法里设置当前用户和业务类型:

BusinessObservationFilter.CURRENT_USER.set(userId); BusinessObservationFilter.CURRENT_BIZ.set("customer-service");

注意用 ThreadLocal 一定要在 finally 里清理,否则线程池复用时会串数据:

try { BusinessObservationFilter.CURRENT_USER.set(userId); // 业务逻辑 return chatService.chat(message); } finally { BusinessObservationFilter.CURRENT_USER.remove(); BusinessObservationFilter.CURRENT_BIZ.remove(); }

加了这个标签后,Prometheus 指标里就会出现biz.user、biz.type标签,查询时可以直接按用户聚合成本消耗。

4.2 自定义 Agent 执行步数与工具调用计数

Agent 步数和工具调用次数是框架不会替你统计的指标,我用 Micrometer 的 Counter 手动记录。先注入 MeterRegistry:

@Service public class AgentMetricsService { private final MeterRegistry meterRegistry; public AgentMetricsService(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } public void recordAgentStep(String taskType, String toolName, boolean succeeded) { Counter.builder("ai.agent.step.total") .tag("task.type", taskType) .tag("tool.name", toolName) .tag("result", succeeded ? "success" : "error") .register(meterRegistry) .increment(); } public void recordTaskToken(String taskType, double promptTokens, double completionTokens) { meterRegistry.counter("ai.agent.task.prompt.tokens", "task.type", taskType).increment(promptTokens); meterRegistry.counter("ai.agent.task.completion.tokens", "task.type", taskType).increment(completionTokens); } }

然后在 Agent 执行的循环里,每调用一次工具就记录一次,每个子任务完成后记录累计 token 数。这样 Prometheus 里就有了 Agent 粒度的计数,配合已有的框架指标,可以回答"一个任务到底烧了多少次模型调用、多少 token"。

4.3 利用框架自带的 ChatModelObservationContext 提取 Token

框架的观测上下文提供了响应信息,可以拿到 usage 数据。写一个自定义 ObservationHandler 输出的方式,比手动在业务代码里解析响应更干净:

@Component public class TokenUsageObservationHandler implements ObservationHandler<ChatModelObservationContext> { @Override public void onStop(ChatModelObservationContext context) { if (context.getResponse() == null || context.getResponse().getMetadata() == null) { return; } ChatResponse response = context.getResponse(); Usage usage = response.getMetadata().getUsage(); if (usage == null) { return; } // usage 里可能包含多个模型的聚合值 long promptTokens = usage.getPromptTokens(); long completionTokens = usage.getCompletionTokens(); long totalTokens = usage.getTotalTokens(); // 记录为每个模型的 token 消耗,单位是 token } @Override public boolean supportsContext(Observation.Context context) { return context instanceof ChatModelObservationContext; } }

在实际实现时,建议把 token 数据直接写到 InfluxDB、VictoriaMetrics 这类支持长时间序列存储的时序库里,Prometheus 本身不适合长期保存历史数据,做成本分析时会很难受。

4.4 常用 PromQL 查询模板

指标接进 Prometheus 之后,最常用的几个查询模板我给你列出来。

模型调用 QPS,按供应商和模型分组:

sum(rate(spring_ai_model_chat_time_seconds_count[5m])) by (provider, model)

模型调用平均耗时:

sum(rate(spring_ai_model_chat_time_seconds_sum[5m])) by (provider, model) / sum(rate(spring_ai_model_chat_time_seconds_count[5m])) by (provider, model)

错误率:

sum(rate(spring_ai_model_chat_time_seconds_count{error="true"}[5m])) by (provider) / sum(rate(spring_ai_model_chat_time_seconds_count[5m])) by (provider)

Agent 步数:

sum(increase(ai_agent_step_total[1h])) by (task_type, tool_name)

查询出结果之后,在 Grafana 里配置 dashboard 和告警规则。我通常至少配三个告警:调用错误率超过 5% 持续 5 分钟、单任务 token 消耗超过阈值、Agent 步数异常飙高。

5. 常见问题与排查技巧实录

5.1 常见问题速查表

我把实操中遇到的高频问题整理成了一张表,按症状、原因、解决办法三列写出来。

症状原因解决办法
/actuator/prometheus里搜不到 spring.ai 相关指标调用发生在 ObservationRegistry 初始化完成前,或者直连了 ChatModel 但没用自动装配的 Client检查 ChatModel Bean 是否来自 Spring AI 自动配置;查看依赖里有没有 spring-ai-observability
指标有耗时,但 usage token 全是 0模型供应商没有在响应里返回 usage,或者 agent 聚合响应取不到 usage打印原始响应确认字段;不同供应商兼容性需要单独适配
指标里没有业务标签ObservationFilter 未注册,或者 ThreadLocal 没传进去确认 Filter 是 Bean;确认调用上下文在线程切换后用户信息仍保留
Token 成本一天比一天高,但 QPS 没变化prompt 变长或者模型输出了更长内容,上下文窗口利用率在涨按 biz.type 分组查看 token 消耗趋势,重点排查上游数据组装逻辑
Agent 任务偶发卡死Agent 陷入了重复工具调用死循环通过ai.agent.step.total定位异常任务,确认是工具调用逻辑还是模型决策问题

5.2 独家避坑技巧

第一个技巧是不要把所有希望寄托在默认指标上。框架的自动观测确实省事,但生产环境里真正有用的往往是自定义标签和自定义指标。我见过不少团队依赖默认指标,结果出了问题还是无法定位到具体用户、具体场景,最后只能翻日志。

第二个技巧是 token 指标建议单独落一份历史存储。Prometheus 的默认保留期一般只有 15 天,成本分析动辄要看月粒度甚至季度粒度。可以单独用 VictoriaMetrics 或者 InfluxDB 保存 token 指标,甚至直接落一张 MySQL 表。成本分析需要的其实只是一张"日期 + 业务 + 模型 + token 数"的明细表。

第三个技巧是限流指标必须提前埋。模型供应商限流不是你一个实例能控制的,一旦触发限流,错误率会迅速上升。我在实践里会给限流单独建一个计数器,监控到限流次数突然增加时,优先检查是不是并发配置调太高了。

第四个技巧是谨慎暴露 Actuator 端点。Prometheus 采集需要访问/actuator/prometheus,但其他端点比如heapdump、threaddump、shutdown绝对不要暴露到公网。我用 Spring Security 做了权限控制,只允许采集器 IP 访问 metrics 相关端点。

5.3 从指标反推优化方向

指标建好了不只是为了看"系统挂了没有",更重要是用来反推优化。我在项目里总结了一条很实用的路径:先看成本指标,找出最烧钱的前十个会话,逐个分析它们的提示词长度、工具结果大小、调用步数,定位是不是存在提示词冗余或者工具返回过量的数据。

再看响应时间指标,如果某个模型在特定时段的 TP99 特别高,可以考虑做降级策略,比如切换到备选模型,或者给耗时业务走异步方式。

最后看错误指标,如果大量错误都是限流错误,就应该调整并发度,或者给不同模型供应商配置不同的超时和重试策略。

这套从指标出发的优化循环,比靠感觉调参要有效得多。做完一轮之后,成本通常能看到立竿见影的下降。

6. 最后再分享一点实操心得

做了一轮 Spring AI 指标监控下来,我的体会是:这套东西真正难的从来不是引入 Micrometer 和 Prometheus,而是想清楚你到底要回答哪些问题。

技术框架给你的是采集能力,但"哪些指标值得采、按什么维度聚合、阈值设多少"才是最有价值的部分。建议每个团队在动手之前先坐下来,把上线的 AI 功能列一遍,针对每个功能回答三个问题:它花钱花在哪里、什么情况算质量异常、哪些环节可能拖慢响应。

回答完这三个问题之后,需要配哪些指标基本就清晰了,配置和代码反而是水到渠成的步骤。最后留一个日常建议:指标监控不是上线当天配一次就完事,模型会换、提示词会调、Agent 工具会增加,指标口径也要跟着迭代。每隔一段时间回到 Grafana 看一眼,把这些指标当作 AI 应用运行状态的一面镜子,比出了故障再手忙脚乱翻日志要舒服得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询