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_totalgen_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;
  • 使用业务requestIdtraceId辅助关联;
  • 不要只依赖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/

智元界将持续分享可运行的技术实战、架构设计、问题排查与企业应用案例。