
上周同事拉我一起排查一个 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、ollamamodel 是具体的模型名。查询时按标签聚合就能把不同供应商、不同模型分开对比。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 Prometheus3.1 引入依赖与配置Spring Boot 天生自带监控三件套Actuator、Micrometer、Prometheus 注册表。Spring AI 只负责把指标喂给 Micrometer导出、采集、展示这套链路完全可以用现成的。Maven 依赖这么加dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependencySpring AI 的观测模块在 starter 里通常已经被带上如果没有专门拆分检查一下依赖树里是否存在 spring-ai-observability。然后配置 Actuator 暴露端口注意不要把管理层暴露到公网management: endpoints: web: exposure: include: health,info,prometheus,metrics metrics: tags: application: ${spring.application.name:default}启动应用后访问/actuator/prometheusCtrlF 搜索spring.ai或者spring_ai能看到一批框架自动生成的指标就说明链路已经通了。3.2 哪些指标是框架自动埋的哪些需要自己写这是最需要弄清的一点。Spring AI 框架内置的观测会自动记录聊天模型、嵌入模型、图像生成、语音转文字等调用的耗时和 token 数。也就是说基础的模型调用指标你一行代码都不用写。打开 Prometheus 端点能看到类似这样的输出spring_ai_model_chat_time_seconds_count{providerdashscope,modelqwen-plus,} 156.0 spring_ai_model_chat_time_seconds_sum{providerdashscope,modelqwen-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 ThreadLocalString CURRENT_USER new ThreadLocal(); public static final ThreadLocalString 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 手动记录。先注入 MeterRegistryService 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 ObservationHandlerChatModelObservationContext { 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{errortrue}[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 应用运行状态的一面镜子比出了故障再手忙脚乱翻日志要舒服得多。