主题
聊天响应元数据
使用 AI(例如 OpenAI 的 ChatGPT)会消耗资源,并由 AI 服务商基于通过 API 对 AI 发起的用量与请求返回相应指标。 消耗通常以某个时间范围(如每月)内发起的请求数或使用的令牌数来计量,AI 服务商以此衡量消耗并重置限额。 你的速率限额直接由你注册时所选择的 AI 服务商套餐决定。 例如,你可以通过链接查阅 OpenAI 的速率限额和套餐详情。
为了帮助你洞察自己的 AI(模型)消耗与整体用量,Spring AI 提供了一个 API,用于内省 AI 服务商在其 API 中返回的元数据。
Spring AI 定义了 3 个用于检视这些指标的主要类型:ChatResponseMetadata、RateLimit 和 Usage。 这些类型都可以通过程序方式,从由某个 AI 请求发起并返回的 ChatResponse 中访问。
ChatResponseMetadata
当通过 AI 服务商的 API 发起 AI 请求并返回 AI 响应时,Spring AI 会自动创建一个 ChatResponseMetadata 实例。 你可以用如下方式,从 ChatResponse 中访问 AI 服务商的元数据:
.从 ChatResponse 获取 ChatResponseMetadata
java
ChatResponse response = chatModel.call(prompt);
ChatResponseMetadata metadata = response.getMetadata();
String id = metadata.getId();
String model = metadata.getModel();
RateLimit rateLimit = metadata.getRateLimit();
Usage usage = metadata.getUsage();
PromptMetadata promptMetadata = metadata.getPromptMetadata();你或许可以设想,用 AI 为自己的 Spring 应用做速率限制,或以自动化、智能且实时的方式限制影响令牌用量的 Prompt 大小。 退一步说,你至少可以收集这些指标来监控并报告你的消耗情况。
速率限额
RateLimit 接口提供对 AI 服务商在发起 AI 请求时就你的 API 用量所返回的真实信息的访问。
.RateLimit 接口
java
interface RateLimit {
Long getRequestsLimit();
Long getRequestsRemaining();
Duration getRequestsReset();
Long getTokensLimit();
Long getTokensRemaining();
Duration getTokensReset();
}requestsLimit 和 requestsRemaining 让你知道:基于你注册时所选择的 AI 服务商套餐,你总共可以发起多少个 AI 请求,以及在给定时间范围内还剩多少余额。 requestsReset 返回一个 Duration,表示在时间范围到期、你的限额依据所选套餐重置之前还剩余的时间。 tokensLimit、tokensRemaining 和 tokensReset 这些方法与此类似,只是聚焦于令牌的限额、余额与重置。
.从 ChatResponseMetadata 获取 RateLimit
java
RateLimit rateLimit = metadata.getRateLimit();
Long tokensRemaining = rateLimit.getTokensRemaining();
// do something interesting with the RateLimit metadata诸如 OpenAI 这样的 AI 服务商,会在其 REST API 响应的 HTTP 头中返回速率限额信息。 当某个模型实现映射了这些头时,它们会通过 RateLimit 暴露出来。
支持程度因模型实现而异。 当速率限额信息不可用时,getRateLimit() 返回一个 EmptyRateLimit,其访问器返回零而非 null。 用 instanceof EmptyRateLimit 来区分「缺失的速率限额」与「真实的零值」。
用量
Usage 数据可以从 ChatResponseMetadata 对象获取。 Usage 接口定义如下:
.Usage 接口
java
interface Usage {
Integer getPromptTokens();
Integer getCompletionTokens();
default Integer getTotalTokens() { ... }
Object getNativeUsage();
}这些方法名不言自明:它们告诉你 AI 处理 Prompt 并生成响应所需的令牌数。 totalTokens 是 promptTokens 与 completionTokens 之和。 Spring AI 默认会计算这个值,但该信息也会随 OpenAI 等服务商的 AI 响应一并返回。
与 ChatModel 一起使用
下面是一个完整示例,展示如何用 OpenAI 的 ChatModel 跟踪用量:
java
@SpringBootConfiguration
public class Configuration {
@Bean
public OpenAiChatModel openAiChatModel() {
return OpenAiChatModel.builder()
.options(OpenAiChatOptions.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.build())
.build();
}
}
@Service
public class ChatService {
private final OpenAiChatModel chatModel;
public ChatService(OpenAiChatModel chatModel) {
this.chatModel = chatModel;
}
public void demonstrateUsage() {
// Create a chat prompt
Prompt prompt = new Prompt("What is the weather like today?");
// Get the chat response
ChatResponse response = this.chatModel.call(prompt);
// Access the usage information
Usage usage = response.getMetadata().getUsage();
// Get standard usage metrics
System.out.println("Prompt Tokens: " + usage.getPromptTokens());
System.out.println("Completion Tokens: " + usage.getCompletionTokens());
System.out.println("Total Tokens: " + usage.getTotalTokens());
// Access native OpenAI usage data with detailed token information
if (usage.getNativeUsage() instanceof com.openai.models.completions.CompletionUsage nativeUsage) {
// Detailed prompt token information
nativeUsage.promptTokensDetails().ifPresent(details -> {
System.out.println("Prompt Tokens Details:");
details.audioTokens().ifPresent(tokens -> System.out.println("- Audio Tokens: " + tokens));
details.cachedTokens().ifPresent(tokens -> System.out.println("- Cached Tokens: " + tokens));
});
// Detailed completion token information
nativeUsage.completionTokensDetails().ifPresent(details -> {
System.out.println("Completion Tokens Details:");
details.reasoningTokens().ifPresent(tokens -> System.out.println("- Reasoning Tokens: " + tokens));
details.acceptedPredictionTokens().ifPresent(tokens -> System.out.println("- Accepted Prediction Tokens: " + tokens));
details.audioTokens().ifPresent(tokens -> System.out.println("- Audio Tokens: " + tokens));
details.rejectedPredictionTokens().ifPresent(tokens -> System.out.println("- Rejected Prediction Tokens: " + tokens));
});
}
}
}与 ChatClient 一起使用
如果你使用的是 ChatClient,可以通过 ChatResponse 对象访问用量信息:
java
ChatResponse response = chatClient.prompt("What is the weather like today?")
.call()
.chatResponse();
Usage usage = response.getMetadata().getUsage();提示缓存用量指标
对于支持提示缓存(prompt caching)的服务商,Usage 接口提供对缓存指标的统一访问,无需进行服务商特定的强制转换:
java
Usage usage = response.getMetadata().getUsage();
// Unified cache metrics — works across all providers
Long cacheReadTokens = usage.getCacheReadInputTokens();
Long cacheWriteTokens = usage.getCacheWriteInputTokens();
if (cacheReadTokens != null && cacheReadTokens > 0) {
System.out.println("Cache hit: " + cacheReadTokens + " tokens read from cache");
}
if (cacheWriteTokens != null && cacheWriteTokens > 0) {
System.out.println("Cache write: " + cacheWriteTokens + " tokens written to cache");
}对于不支持提示缓存的服务商,这些方法返回 null。
下表按服务商展示了提示缓存指标的可用性:
| 服务商 | 缓存读取令牌 | 缓存写入令牌 |
|---|---|---|
| Anthropic | 是 | 是(cacheCreationInputTokens) |
| AWS Bedrock | 是 | 是 |
| OpenAI | 是(cachedTokens) | 否 |
| Google Gemini | 是(cachedContentTokenCount) | 否 |
| DeepSeek | 否 | 否 |
| Mistral | 否 | 否 |
| Ollama | 否 | 否 |
NOTE
关于服务商特定的详细缓存指标(例如 Gemini 中按模态划分的缓存细分),请使用 getNativeUsage() 来访问服务商的原生用量对象。
多步流程中的累计用量
当响应是通过多步流程(如工具调用循环)产出时,getUsage() 报告的是该轮交互中每一次模型调用的累计令牌用量,而不仅仅是最后一次。 例如,一次触发单个工具调用的 ChatClient 对话至少会进行两次模型调用,返回的 getUsage() 反映的是两者之和。
java
ChatResponse response = chatClient.prompt("What is the weather in Paris?")
.tools(new WeatherTools())
.call()
.chatResponse();
// Cumulative across all model calls in the tool-calling loop
Usage usage = response.getMetadata().getUsage();
int totalTokens = usage.getTotalTokens();累计总量由 org.springframework.ai.support.UsageCalculator 计算,它对标准令牌计数和统一缓存指标(getCacheReadInputTokens() / getCacheWriteInputTokens())求和。
DANGER
服务商特定的原生用量对象无法跨响应合并。
因此,一旦用量被聚合到超过一次模型调用之后(例如在工具调用循环之后),getNativeUsage() 会返回 null;它只在单次调用的响应中被保留。 如果你需要服务商的原生用量对象,请从单个 ChatModel 调用而非多步 ChatClient 交互中读取它。