Spring AI接入Actuator后看不到Token和耗时指标?Micrometer观测完整排查
文章摘要
Spring AI 2.0已经为ChatClient、Advisor、ChatModel、Tool Calling、EmbeddingModel和VectorStore提供Micrometer观测能力,但很多项目接入Actuator和Prometheus后,只能看到JVM指标,看不到gen_ai_client_token_usage_total、gen_ai_chat_client_operation_seconds或Trace。常见原因包括依赖不完整、自建ChatClient绕过自动配置、调用只返回字符串、指标尚未完成、Prometheus名称转换、流式调用上下文断裂以及Provider不返回Usage。本文给出从依赖、配置、Bean、接口、端点到PromQL的完整排查链路。
一、先确认你要找的是哪一层指标
Spring AI 2.0至少有两层调用观测。
ChatClient层
反映整个应用调用链:
Prompt模板
→ Advisor
→ Memory
→ RAG
→ Tool Calling
→ ChatModel
常见Prometheus指标:
gen_ai_chat_client_operation_seconds_count
gen_ai_chat_client_operation_seconds_sum
gen_ai_chat_client_operation_seconds_max
gen_ai_chat_client_operation_active_count
ChatModel层
反映实际模型Provider调用:
gen_ai_client_operation_seconds_count
gen_ai_client_operation_seconds_sum
gen_ai_client_operation_seconds_max
gen_ai_client_operation_active_count
gen_ai_client_token_usage_total
如果只看:
spring_ai_chat_client
可能永远找不到,因为Prometheus命名会把点号转换为下划线,并附加单位或统计后缀。
二、最小依赖是否完整
至少需要:
org.springframework.boot
spring-boot-starter-actuator
io.micrometer
micrometer-registry-prometheus
如果要导出分布式Trace,还需要对应Tracing Bridge和Exporter,例如OpenTelemetry或Brave方案。
不要只引入:
micrometer-core
却没有Registry。Micrometer可以在内存中记录,但Prometheus端点不会自动出现。
三、Actuator端点是否开放
配置:
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: when_authorized
检查:
curl http://localhost:8080/actuator
curl http://localhost:8080/actuator/metrics
curl http://localhost:8080/actuator/prometheus
如果/actuator/prometheus返回404,先不要排查Spring AI。
需要检查:
- Prometheus Registry是否在ClassPath;
- Actuator端点是否暴露;
- 安全配置是否拦截;
- 应用是否使用不同管理端口;
- Context Path是否变化。
四、最常见问题:手工创建ChatClient丢失ObservationRegistry
错误做法:
@Bean
ChatClient customChatClient(ChatModel chatModel) {
return ChatClient.create(chatModel);
}
在复杂多模型配置中,手工创建客户端可能绕过自动配置的Customizer和Observability。
官方建议在需要自定义Builder时注入:
ChatClientBuilderConfigurer
示意:
@Bean
ChatClient customChatClient(
ChatModel chatModel,
ChatClientBuilderConfigurer configurer
) {
ChatClient.Builder builder =
ChatClient.builder(chatModel);
configurer.configure(builder);
return builder
.defaultSystem("你是企业AI助手")
.build();
}
这样可以保留:
- ObservationRegistry;
- 已注册Customizer;
- 框架自动配置能力。
如果你直接new一个ChatModel,也要检查它是否使用了正确的ObservationRegistry。
五、为什么调用成功却没有Token指标
Token指标依赖Provider返回Usage数据。
模型响应必须包含类似:
input_tokens
output_tokens
total_tokens
如果使用OpenAI兼容接口,但服务端没有返回Usage,Spring AI无法凭空知道真实Token数。
常见场景:
- 自建OpenAI兼容网关忽略usage;
- 流式接口没有在最终事件返回usage;
- 第三方Provider字段格式不同;
- 代理层删除响应字段;
- 模型SDK未实现Usage映射。
先通过完整ChatResponse检查:
ChatResponse response = chatClient.prompt()
.user("解释RAG")
.call()
.chatResponse();
Usage usage = response.getMetadata().getUsage();
System.out.println(usage.getPromptTokens());
System.out.println(usage.getCompletionTokens());
System.out.println(usage.getTotalTokens());
如果这里就是0或null,Prometheus没有Token指标是结果,不是原因。
六、为什么.content()不方便调试Usage
String answer = chatClient.prompt()
.user(message)
.call()
.content();
这种写法只返回内容。
排查期间改为:
ChatResponse response = chatClient.prompt()
.user(message)
.call()
.chatResponse();
然后检查:
- Response Metadata;
- Usage;
- Finish Reason;
- Model;
- Native Usage。
Spring AI 2.0的Usage接口还提供getNativeUsage(),用于访问Provider原始用量对象。
七、Prometheus名称为什么和文档不一样
Micrometer内部Meter名称可能是:
gen_ai.client.token.usage
Prometheus导出后可能显示:
gen_ai_client_token_usage_total
并带标签:
gen_ai_token_type="input"
gen_ai_token_type="output"
gen_ai_token_type="total"
查询:
sum by (gen_ai_token_type) (
rate(gen_ai_client_token_usage_total[5m])
)
平均模型调用耗时:
sum(rate(gen_ai_client_operation_seconds_sum[5m]))
/
sum(rate(gen_ai_client_operation_seconds_count[5m]))
ChatClient平均端到端耗时:
sum(rate(gen_ai_chat_client_operation_seconds_sum[5m]))
/
sum(rate(gen_ai_chat_client_operation_seconds_count[5m]))
两个耗时不同是正常的。
ChatClient还包含Advisor、Memory、RAG和工具链开销。
八、为什么只有active_count,没有completed指标
active_count表示当前正在进行的调用。
完成类指标只有在请求结束后才会更新:
_seconds_count
_seconds_sum
_seconds_max
流式连接长期不结束时,你可能看到:
active_count = 1
completed count未增加
这不是指标丢失,而是调用还未完成。
检查:
- 前端是否一直保持连接;
- Flux是否收到完成信号;
- 是否因异常没有正确终止;
- Nginx是否中断但上游仍运行;
- 客户端取消是否传播到Provider。
九、流式调用的Trace为什么断开
Spring AI文档特别说明:
OpenAI与Anthropic的流式HTTP调用可能在线程切换后丢失父Observation上下文,导致HTTP Span没有正确挂到ChatModel Span下面。
表现:
ChatClient Trace存在
ChatModel Trace存在
HTTP请求Span存在
但三者不是父子链
这不一定表示调用没有被观测,而是Trace树不完整。
处理思路:
- 以
gen_ai.client.operation为模型层主Span; - 使用业务
requestId和traceId辅助关联; - 不要只依赖HTTP Span树判断模型调用是否存在;
- 关注当前Spring AI补丁版本的修复情况;
- 避免在自定义异步线程中继续丢失上下文。
十、日志内容为什么默认看不到Prompt
Spring AI默认不会导出Prompt和Completion内容,因为其中可能包含:
- 用户隐私;
- API密钥;
- 商业数据;
- RAG证据;
- 工具参数;
- 内部System Prompt。
调试时可以临时配置:
spring:
ai:
chat:
observations:
log-prompt: true
log-completion: true
include-error-logging: true
ChatClient层还有独立配置:
spring:
ai:
chat:
client:
observations:
log-prompt: true
log-completion: true
生产环境不建议直接开启全文日志。
更安全的记录方式:
prompt_hash
prompt_length
template_id
template_version
input_token
output_token
十一、Tool Calling为什么Token比预期高
一次用户请求触发工具调用时,可能执行多次模型调用:
第一次模型调用
→ 选择工具
→ 执行工具
→ 第二次模型调用
→ 生成最终回答
如果继续调用第二个工具,模型调用次数更多。
最终ChatResponse中的Usage可能是整个工具循环的累计值。
所以:
1个HTTP请求
≠ 1次模型请求
建议同时监控:
业务请求数
ChatClient调用数
ChatModel调用数
Tool调用数
Token数
十二、多模型项目标签是否爆炸
低基数标签可以放入Meter:
- Provider;
- 模型层级;
- 业务场景;
- 成功或失败;
- 流式与否。
高基数信息不要放Meter标签:
- userId;
- conversationId;
- requestId;
- Prompt全文;
- 文档ID;
- Tool参数。
否则Prometheus时间序列数量会迅速膨胀。
高基数数据应放:
Trace
日志
审计表
十三、建议的Dashboard指标
流量
ChatClient QPS
ChatModel QPS
当前并发
流式连接数
延迟
ChatClient P50/P95/P99
ChatModel P50/P95/P99
VectorStore查询耗时
Tool耗时
成本
输入Token
输出Token
缓存写入Token
缓存读取Token
按模型估算费用
稳定性
429
超时
5xx
解析失败
工具失败
熔断次数
降级次数
十四、完整排查清单
□ 已引入spring-boot-starter-actuator
□ 已引入Prometheus Registry
□ prometheus端点已暴露
□ 使用正确管理端口和Context Path
□ ChatClient没有绕过自动配置
□ 自定义Builder保留ObservationRegistry
□ Provider响应包含Usage
□ 通过ChatResponse验证Usage
□ 使用Prometheus转换后的指标名
□ 流式调用已经结束
□ 没把Trace断链误判为无观测
□ 没把高基数数据放Meter标签
□ Tool Calling多次模型调用已纳入成本
总结
Spring AI看不到Token和耗时指标,通常集中在四类原因:
Actuator与Registry未接通
自建客户端绕过自动配置
Provider没有返回Usage
查询了错误的Prometheus指标名
从端点、Bean、完整响应、Meter名称和流式生命周期逐层检查,通常可以快速定位。
延伸阅读
如果你正在关注企业级 AI 应用、Spring AI、RAG、Agent 与 MCP 工程化落地,欢迎访问 智元界:
https://www.zyentor.com/
智元界将持续分享可运行的技术实战、架构设计、问题排查与企业应用案例。