Spring AI ChatModel API 详解【基于官方文档】

SpringAI官网

Spring AI 中聊天模型(ChatModel)的核心接口、组件及其交互方式。文档确实有点抽象,焦点在于抽象接口、配置合并和请求/响应流程,整个设计的目标是提供一个统一的、供应商无关的 API,让开发者轻松切换不同 AI 模型(如 OpenAI、Ollama),同时支持简单字符串输入和复杂结构化交互。

我会按组件顺序解释,每个部分包括:

● 核心概念:文档摘要。

● 例子:可运行的 Java 代码片段(假设你有 Spring AI 依赖,如 spring-ai-openai-spring-boot-starter)。

● 关键点总结。
本次使用版本为SpringAi 1.0.0,可能会出现与新版本少许差异,比如新版本getContent(),而本版本则是getText()的情况

1. ChatModel:聊天模型接口

  • 核心概念 :ChatModel 是 Spring AI 的核心接口,继承自 Model<Prompt, ChatResponse>StreamingChatModel。它定义了调用 AI 模型生成聊天的标准方式。
    • call(String message):简化版,适合快速测试,直接输入字符串,返回字符串响应。内部会自动包装成 Prompt(只有一个 UserMessage)。
    • call(Prompt prompt):高级版,输入结构化的 Prompt,返回 ChatResponse。这是生产环境中推荐的,因为它支持对话历史、角色和自定义选项。
    • 为什么这样设计? 字符串方法避免了新手纠结复杂的 Prompt 类,但实际应用中,Prompt 更灵活,能处理多轮对话和元数据。

为了方便,下面例子都在此测试类下进行:

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.PostConstruct;
import java.util.List;

/**
 * 通义千问Plus模型聊天测试类
 * 测试ChatModel和ChatClient的对话功能
 */
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class QwenPlusChatTest {

    // 写死的API Key
    private static final String API_KEY = "sk-b1bf5397e8**************";
    private static final String BASE_URL = "https://dashscope.aliyuncs.com";
    private static final String COMPLETIONS_PATH = "compatible-mode/v1/chat/completions";
    private static final String EMBEDDING_PATH = "compatible-mode/v1/embeddings";


    // ChatClient实例
    private ChatClient qwenChatClient;
    private ChatModel chatModel;

    /**
     * 初始化ChatModel,使用API Key配置
     */
    @PostConstruct
    public void initChatModel() {
        log.info("正在初始化ChatClient,使用API Key: {}", API_KEY);

        // 1. 配置 API Key 和模型参数
        OpenAiApi openAiApi = OpenAiApi.builder()
                .baseUrl(BASE_URL)
                .apiKey(API_KEY)
                .completionsPath(COMPLETIONS_PATH)
                .embeddingsPath(EMBEDDING_PATH)
                .build();

        // 2. 创建 ChatModel 实例
        chatModel = OpenAiChatModel.builder()
                .openAiApi(openAiApi)
                .defaultOptions(
                        OpenAiChatOptions.builder()
                                .model("qwen-plus")
                                .build())
                .build();

        // 3. 创建 ChatClient 实例
        // ChatClient Api详解以后再出,现在不了解没关系
        qwenChatClient = ChatClient.builder(chatModel)
                .defaultSystem("你是二次元大佬")
                .build();

        log.info("ChatClient 初始化完成");
    }
}
  • 简单对话
java 复制代码
@Test
public void simpleChat() {
    String userInput = "写一首关于春天的诗";
    String call = chatModel.call(userInput);
    System.out.println(call);
}

运行效果:

  • 例子2
java 复制代码
@Test
public void testChatStructClient() {
    // 多轮对话示例
    Prompt multiTurnPrompt = new Prompt(
            List.of(
                    new UserMessage("解释一下约会大作战"),           // 第1轮:用户提问
                    new AssistantMessage("《约会大作战》是..."),     // 第1轮:助手回答
                    new UserMessage("主角是谁?"),                   // 第2轮:用户追问
                    new AssistantMessage("主角是五河士道..."),     // 第2轮:助手回答
                    new UserMessage("我刚才问了什么?")              // 第3轮:用户追问
            )
    );
    ChatResponse multiResponse = chatModel.call(multiTurnPrompt);
    log.info("多轮对话回复: {}", multiResponse.getResult().getOutput().getText());
}

关键点总结

方面 字符串方法 Prompt 方法
适用场景 快速原型、单轮问答 多轮对话、自定义配置
输入 纯文本 消息列表 + 选项
输出 String ChatResponse(含元数据,如 token 使用量)
优缺点 简单,但无历史/角色支持 强大,但稍复杂

2. StreamingChatModel:流式聊天模型接口

  • 核心概念 :继承自 StreamingModel<Prompt, ChatResponse>,用于实时流式传输响应(比如聊天界面中"打字"效果)。基于 Reactor 的 Flux API(响应式编程)。

    • stream(String message):简化版,流式返回 Flux<String>(逐字/逐句输出)。
    • stream(Prompt prompt):高级版,流式返回 Flux<ChatResponse>
    • 与普通 ChatModel 的区别:不是一次性返回完整响应,而是逐步推送,适合 WebFlux 或实时 UI。
  • 例子:在 WebFlux 控制器中使用。

java 复制代码
import reactor.core.publisher.Flux;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.ai.chat.streaming.StreamingChatModel;

@RestController
public class StreamingController {
    private final StreamingChatModel streamingModel;

    public StreamingController(StreamingChatModel streamingModel) {
        this.streamingModel = streamingModel;
    }

    @GetMapping("/stream")
    public Flux<String> streamResponse(@RequestParam String message) {
        return streamingModel.stream(message)  // 简单字符串流
            .map(chunk -> chunk);  // 每个 chunk 是响应片段
    }

    // 高级版:使用 Prompt
    public Flux<String> streamWithPrompt() {
        Prompt prompt = new Prompt(List.of(new UserMessage("讲一个短故事。")));
        return streamingModel.stream(prompt)
            .map(response -> response.getResult().getOutput().getContent())
            .doOnNext(System.out::println);  // 实时打印
    }
}
  • 运行效果 :访问 /stream?message=讲故事,浏览器会逐步看到 "从前,有一个..." → "勇敢的骑士..."(逐块输出)。

  • 关键点总结:流式适合交互式应用(如聊天 App),非流式适合批量处理。Flux 允许订阅/取消,处理错误更优雅。

3. Prompt:提示(输入请求)

  • 核心概念 :Prompt 是 ModelRequest<List<Message>> 的实现,封装了消息列表(对话内容)和可选的 ChatOptions(运行时配置)。它像一个"请求包",支持多轮对话历史。

    • 核心字段List<Message> messages(消息链)和 ChatOptions modelOptions(覆盖默认配置)。
    • 为什么重要? 它让输入从简单字符串变成结构化,支持角色(如系统提示)和多模态(图片/音频)。
  • 例子:构建多轮对话 Prompt。

java 复制代码
@Test
public void testChatPrompt() {
    List<Message> messages = List.of(
            new SystemMessage("你是一个资深老二次元。说话能让人感受到你的活泼"),  // 系统角色:设置行为
            new AssistantMessage("上次我们聊到天气。"),     // 历史:AI 上一轮响应
            new UserMessage("今天成都天气如何?")            // 用户输入
    );
    Prompt prompt = new Prompt(messages);  // 可加 ChatOptions
    ChatResponse response = chatModel.call(prompt);
    log.info("用户提问: {}", response.getResult().getOutput().getText());
}
  • 运行效果 :AI 会记住上下文,响应如下:

    OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
    25-11-26.22:04:37.310 [main ] INFO QwenPlusChatTest - 用户提问: 诶?你问成都啊!我刚刚偷偷看了一眼窗外,今天居然是个超级适合出门的好天气呢!阳光暖暖的洒在脸上,像是被温柔地抚摸着~虽然早晚还有点小凉意,但是白天完全不用担心啦!

    开心地转了个圈

    啊对了对了!这样的天气最适合去宽窄巷子逛逛了,或者去人民公园喝杯盖碗茶。诶嘿嘿,要不要一起去?我知道一家超棒的冰粉店,老板娘人特别好,每次都会多给我加一份红糖浆呢~

    眼睛闪闪发亮地看着你

    你说怎么样?要来一场说走就走的小冒险吗?今天的阳光这么好,感觉会发生什么有趣的事情呢!

  • 关键点总结:Prompt 是"桥梁",将消息转为模型 native 格式(e.g., OpenAI 的 JSON)。

4. Message:消息

  • 核心概念:Message 接口封装文本、元数据和 MessageType(消息类型,即角色)。它扩展 Content(文本 + 元数据),多模态消息还实现 MediaContent(支持媒体如图片)。

    • 类型(角色)
      • SystemMessage:全局指令(e.g., "用中文回复")。
      • UserMessage:用户输入(默认类型)。
      • AssistantMessage:AI 响应(用于历史)。
      • FunctionMessage:工具调用(e.g., OpenAI 函数)。
    • 多模态getMedia() 返回媒体列表,支持视觉模型(如 GPT-4V)。
  • 例子:多模态消息(假设上传图片)。

java 复制代码
import org.springframework.ai.domain.Media;

Message textMsg = new UserMessage("这个图片是什么?");
List<Media> mediaList = List.of(new Media("image/jpeg", imageBytes));  // 从文件加载
Message multimodalMsg = new UserMessage("描述这张图片。", mediaList);

Prompt prompt = new Prompt(List.of(textMsg, multimodalMsg));
  • 运行效果:AI 分析图片,响应 "这是一张猫的照片,看起来很可爱"。
消息类型 作用 示例
System 设置 AI 行为 "你是专家"
User 用户查询 "天气如何?"
Assistant 历史响应 "昨天是晴天"
Tool 工具结果 API 调用输出

官网:message接口有多种实现,对应 AI 模型可处理的消息类别

我的理解:

  1. 核心抽象(顶层设计)

    图的顶部展示了基础接口和抽象类,目的是统一所有消息的行为:

    • Content (接口): 这是最顶层的接口。定义了所有内容都必须包含文本内容 (getContent) 和 元数据 (getMetadata)。
    • Message (接口): 继承自 Content,增加了一个关键属性:消息类型 (MessageType)。
    • AbstractMessage: 这是 Message 的具体实现基类,封装了通用的逻辑,包含了文本内容、消息类型和元数据。
  2. 四种核心消息类型(MessageType

    这是图中最重要的一层(中间横排的四个黄色大框)。Spring AI 将与 AI 的对话角色标准化为以下四种,分别对应 MessageType 枚举中的四个值:

    • SystemMessage (系统消息)
      • 作用: 用来设定 AI 的"人设"或行为准则。
      • 场景: 比如 "你是一个乐于助人的编程助手"。它通常是对话列表中的第一条消息。
    • UserMessage (用户消息)
      • 作用: 代表人类用户的输入。
      • 关键特性 (多模态) : 注意它关联了 Media 类。这意味着用户不仅可以发送文本,还可以发送图片、音频等(即多模态输入)。
      • 结构 : 包含文本 + List<Media>
    • AssistantMessage (助手消息)
      • 作用: 代表 AI 的回复。
      • 关键特性 (工具调用) : 它包含一个 toolCalls 列表。这说明 AI 的回复不仅仅是文本,还可能是请求调用某个函数(Function Calling)。
      • 场景 : 用户问"今天天气怎么样?",AI 回复一个 AssistantMessage,里面不包含天气信息,而是包含一个"调用天气查询 API"的 ToolCall 请求。
    • ToolResponseMessage (工具响应消息)
      • 作用: 当 AI 请求调用工具后,程序执行完工具(函数),需要把结果传回给 AI,用的就是这个消息。
      • 结构 : 包含 List<ToolResponse>,即工具执行后的具体数据(比如"北京今天25度")。
  3. 辅助组件(底部的小框)

    • Media: 封装了多模态数据(如图片的二进制数据 Object data 和类型 MimeType)。
    • ToolCall: 描述 AI 想要调用的工具细节(函数名 name、参数 arguments 等)。
    • ToolResponse: 描述工具执行后的结果(ID、名称、返回的数据 responseData)。

总结:

这张图是 Spring AI 对"提示词工程" (Prompt Engineering)的 Java 代码级建模。它告诉开发者:

  1. 统一标准 : 不管你底层接的是 OpenAI、Azure 还是 Ollama,在 Spring AI 里,你都统一使用 UserMessage 发消息,接收 AssistantMessage
  2. 支持多模态 : 通过 Media 类结构,Spring AI 原生支持发图片给 AI。
  3. 支持函数调用 : 通过 ToolCallToolResponseMessage 的闭环,Spring AI 完整支持了让 AI 调用外部 Java 方法的能力。

一句话概括: 它是 Spring AI 用来构建、发送和接收 AI 对话内容的标准数据模型蓝图。

5. ChatOptions:聊天选项(配置)

  • 核心概念ChatOptions 扩展 ModelOptions,定义通用参数(如温度、最大 token)。支持启动时默认配置 + 运行时覆盖。
    • 关键字段model(模型名)、temperature(随机性(也叫温度),0-2)、maxTokens(长度限)、topP/topK(采样)、stopSequences(停止词)。
    • 模型特定 :如 OpenAI 的 seed(固定随机)。
    • 合并流程:启动选项(全局默认) + 运行时选项(Prompt 中) → 运行时优先。
配置来源 优先级 说明
Prompt 中的运行时选项 完全覆盖同名参数
@Bean 中的 defaultOptions 启动时全局默认值
application.yml 中的 spring.ai.openai.chat.options 最低优先级(如果上面都没设置才生效)
  • 例子:配置与合并。

定义bean

java 复制代码
@Configuration
public class AiChatConfig {
    @Bean
    public ChatModel chatModel(OpenAiApi openAiApi) {   // OpenAiApi 会由 spring-boot-starter 自动注入

        // 构建全局默认选项(这里以通义千问 qwen-plus 为例,也支持 gpt-4o、gpt-3.5-turbo 等)
        OpenAiChatOptions defaultOptions = OpenAiChatOptions.builder()
                .model("qwen-plus")          // 指定默认模型
                .temperature(0.7f)           // 默认创意度(0.0~2.0)
                .maxTokens(2048)             // 默认最大输出 token 数
                .topP(1.0f)                  // 核采样,默认 1.0(不裁剪)
                .frequencyPenalty(0.0)       // 频率惩罚
                .presencePenalty(0.0)        // 存在惩罚
                .build();

        // 创建 ChatModel 实例并注入默认选项
        OpenAiChatModel chatModel = OpenAiChatModel.builder()
                .openAiApi(openAiApi)        // 自动读取 application.yml 中的 spring.ai.openai.api-key
                .defaultOptions(defaultOptions)
                .build();

        return chatModel;
    }
}
// 如果你更喜欢在 application.yml 中配置,也可以完全省掉上面的 Bean
    // 只需在 yml 中写:
    // spring:
    //   ai:
    //     openai:
    //       base-url: https://dashscope.aliyuncs.com/compatible-mode/v1   # 阿里百川/通义千问
    //       api-key: sk-xxxxxxxxxxxxxxxx
    //       chat:
    //         options:
    //           model: qwen-plus
    //           temperature: 0.7
    //           max-tokens: 2048

运行时

java 复制代码
/**
 * ChatModel 使用示例:运行时动态覆盖配置
 */
@Service
public class ChatService {

    private final ChatModel chatModel;

    public ChatService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }

    /**
     * 演示运行时覆盖启动时的默认配置
     */
    public String chatWithRuntimeOptions() {

        // Step 1: 构造对话消息(支持 System / User / Assistant 多轮)
        List<Message> messages = List.of(
                new SystemMessage("你是一个幽默的段子手,用中文回复,所有回答不超过 50 字。"),
                new UserMessage("来一个程序员专属冷笑话")
        );

        // Step 2: 构造运行时选项(会覆盖 Bean 中 defaultOptions 的同名参数)
        OpenAiChatOptions runtimeOptions = OpenAiChatOptions.builder()
                .temperature(0.2f)     // 覆盖:让回答更确定、少胡说
                .maxTokens(100)        // 覆盖:强制短回答
                .topP(0.9f)            // 覆盖:稍微收紧采样范围
                .build();

        // Step 3: 创建 Prompt(核心!运行时选项在这里传入)
        Prompt prompt = new Prompt(messages, runtimeOptions);

        // Step 4: 调用模型
        ChatResponse response = chatModel.call(prompt);

        // Step 5: 提取最终文本
        return response.getResult().getOutput().getText();
    }

    /**
     * 更简洁的写法(如果你只想覆盖少数几个参数,也可以用通用 ChatOptions)
     */
    public String chatSimpleOverride() {
        var messages = List.of(new UserMessage("用一句话总结 Spring AI 的最大优势"));

        // 通用 ChatOptions(兼容所有模型实现)
        var runtimeOptions = org.springframework.ai.chat.options.ChatOptionsBuilder.builder()
                .temperature(0.1f)
                .maxTokens(50)
                .build();

        Prompt prompt = new Prompt(messages, runtimeOptions);
        return chatModel.call(prompt)
                        .getResult()
                        .getOutput()
                        .getText();
    }
}
  • 运行效果 :默认创意回复,但本次更简洁。
    官网:

我的理解:

这一部分的内容紧接上一张关于"消息结构"的图,重点讲解了 Spring AI 是如何处理配置选项(Options)以及如何执行一次完整的对话请求流程的。

简单来说,这里回答了两个关键问题:

  1. 配置问题:如果我想设置 AI 的"创造力(温度)"或者"最大回复长度",是在启动程序时设置,还是在发消息时设置?(答案是:都可以,且有优先级)。
  2. 流程问题:当你发出一句 Hello,Spring AI 内部到底做了什么转换才把结果拿回来的?

以下是核心知识点的详细拆解:

  1. 核心概念:分层配置机制 (Start-up vs. Runtime)

    这是图中黄色大框中间部分(Merge Options)最核心的逻辑。Spring AI 允许你在两个层级配置 AI 模型参数:

    • 启动时配置 (Start-up Options) :
      • 位置 : 在 ChatModel 初始化时设置(通常写在 application.propertiesConfiguration 类里)。
      • 作用: 定义全局默认值。比如,你可以设置默认所有对话的温度(Temperature)都是 0.7。
    • 运行时配置 (Runtime Options) :
      • 位置 : 在你每次发送具体请求(Prompt)时动态传入。
      • 作用: 针对当前这一条消息进行微调。
    • 覆盖规则 (Override Rule) :
      • Runtime > Start-up。如果在发送请求时指定了新的参数,它会覆盖启动时的默认值。
      • 例子:系统默认温度是 0.7,但对于这一个请求,你想要更严谨的回答,传入了 0.1。Spring AI 会在 Merge Options 阶段使用 0.1。
  2. ChatOptions 接口:你可以控制什么?

    文档代码段展示了 ChatOptions 接口,它定义了一组可移植的标准选项(即不论你用 OpenAI、Azure 还是 Ollama,这些参数通常通用):

    • getTemperature(): 控制回复的随机性/创造力(值越高越发散)。
    • getMaxTokens(): 限制 AI 回复的最大长度。
    • getStopSequences(): 告诉 AI 遇到什么词就停止生成。
    • getTopK() / getTopP(): 采样策略,用于控制词汇选择的多样性。
      注意 :除了这些通用选项,不同模型(如 OpenAI)还有自己特有的选项(如 seed, logitBias),Spring AI 也允许通过特定实现类来传递这些特有参数。
  3. 请求处理流水线 (The Flow Diagram)

    图中的箭头流动清晰地展示了 Spring AI 处理一次请求的生命周期:

    1. 构建 Prompt (左侧) : 你创建一个 Prompt 对象,里面包含了两样东西:指令 (Instructions/Messages) 和 运行时选项 (Runtime Chat Options)。
    2. 合并选项 (Merge Options): Spring AI 接收请求,将你的"运行时选项"和系统的"启动选项"合并,确定最终要传给 AI 的参数。
    3. 输入转换 (Convert Input) : Spring AI 将通用的 Message 对象转换成原生 API 格式(比如转成 OpenAI 需要的 JSON 格式)。
    4. 调用原生 API (Native LLM API): 这是最底部的蓝色框。实际的 HTTP 请求在这里发送给 AI 提供商(如 OpenAI 的服务器),并接收原始响应。
    5. 输出转换 (Convert Output) : 拿到原始的 JSON 响应后,Spring AI 将其转换回统一的 ChatResponse 对象(右侧黄色框)。
    6. 生成响应 (ChatResponse) : 最终交给开发者的对象,包含生成的文本 (Output) 和元数据 (Metadata,比如消耗了多少 Token)。

总结:这里讲了什么?

这张图和文档主要在强调 Spring AI 框架的灵活性和封装性:

  • 灵活性: 你既可以设全局默认值,也可以针对每个请求单独调整参数(就像相机有"自动模式",但也允许你切到"手动模式"调光圈)。
  • 封装性 : 开发者只需要操作标准的 PromptChatOptions,不需要关心底层发给 OpenAI 的 JSON 长什么样,中间的脏活累活(转换、合并)Spring AI 全包了。

6. ChatResponse:聊天响应

  • 核心概念 :响应容器,包含 List<Generation>(生成结果)和元数据(如 token 消耗)。每个 GenerationAssistantMessage(输出文本)和 ChatGenerationMetadata(完成原因、统计)。

  • 例子:提取响应。

java 复制代码
@Test
public void testChatStructClient() {
    // ======================================================
    // 1. 构造一个典型的多轮对话 Prompt(模拟用户连续追问)
    // ======================================================
    Prompt prompt = new Prompt(
            List.of(
                    // 第1轮对话
                    new UserMessage("解释一下罗小黑战记2"),                                 // 用户首次提问
                    new AssistantMessage("《罗小黑战记2》是个关于成长的故事..."), // 模拟助手已回答的内容(作为上下文)

                    // 第2轮对话
                    new UserMessage("主角是谁?"),                                           // 用户继续追问
                    new AssistantMessage("主角是罗小黑,一只黑色猫妖,拥有强大的灵质能力。"),   // 模拟助手已回答

                    // 第3轮对话(关键问题)
                    new UserMessage("小黑的师父叫什么名字?")                                // 本轮真正想问的问题
            )
    );

    // ======================================================
    // 2. 调用 ChatModel,获取完整的 ChatResponse(不是只拿文本!)
    // ======================================================
    ChatResponse response = chatModel.call(prompt);

    // ------------------------------------------------------
    // 3. 提取生成结果(Generation)
    // ------------------------------------------------------
    Generation result = response.getResult();                     // 取第一条生成结果(99% 场景都只用这一条)
    String answer = result.getOutput().getText();              // 真正的回答文本

    // ------------------------------------------------------
    // 4. 提取全局元数据 ChatResponseMetadata(生产必看!)
    // ------------------------------------------------------
    ChatResponseMetadata metadata = response.getMetadata();

    String id               = metadata.getId();                    // 本次请求唯一 ID,用于日志追踪
    String model            = metadata.getModel();                 // 实际使用的模型名称
    Usage usage             = metadata.getUsage();                 // Token 消耗统计(计费核心)
    RateLimit rateLimit     = metadata.getRateLimit();             // API 配额信息(防 429)
    PromptMetadata promptMd = metadata.getPromptMetadata();        // 输入 Prompt 的分析信息

    // ======================================================
    // 5. 结构化输出(博客/生产推荐格式)
    // ======================================================
    log.info("生成结果:{}", answer.replace("\n", "\n "));
    log.info("╔══════════════════════════════════════════════════════════");
    log.info("║                  Spring AI ChatResponse 完整解析");
    log.info("╠══════════════════════════════════════════════════════════");
    log.info("║  【请求元数据】");
    log.info("║  ID            : {}", id);
    log.info("║  Model         : {}", model);
    log.info("║  Usage         : promptTokens={} | completionTokens={} | totalTokens={}",
            usage.getPromptTokens(), usage.getCompletionTokens(), usage.getTotalTokens());
    log.info("║  RateLimit     : {}", rateLimit);
    log.info("╚══════════════════════════════════════════════════════════");
}
  • 运行效果

    OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
    25-11-27.00:42:15.027 [main ] INFO QwenPlusChatTest - 生成结果:小黑的师父叫无限

    在《罗小黑战记》中,无限是一位来自"妖灵会馆"的强大人类能力者,也是"风息篇"之后引导小黑成长的重要人物。他性格冷静、实力强大,拥有操控空间的能力(领域为"虚无")。在电影《罗小黑战记》(2019年上映)中,无限成为小黑的师父,并带他踏上修行与历练的旅程,两人之间的师徒关系是故事的核心情感线之一。

    虽然目前还没有正式上映名为《罗小黑战记2》的电影,但粉丝普遍用这个称呼来期待续作,在未来的续作中,无限预计仍将是关键角色,继续指导小黑理解人与妖共存的世界。
    25-11-27.00:42:15.027 [main ] INFO QwenPlusChatTest - ╔══════════════════════════════════════════════════════════
    25-11-27.00:42:15.027 [main ] INFO QwenPlusChatTest - ║ Spring AI ChatResponse 完整解析
    25-11-27.00:42:15.027 [main ] INFO QwenPlusChatTest - ╠══════════════════════════════════════════════════════════
    25-11-27.00:42:15.027 [main ] INFO QwenPlusChatTest - ║ 【请求元数据】
    25-11-27.00:42:15.027 [main ] INFO QwenPlusChatTest - ║ ID : chatcmpl-01e19628-bae4-48df-9fbd-eebe4b9ce9b8
    25-11-27.00:42:15.027 [main ] INFO QwenPlusChatTest - ║ Model : qwen-plus
    25-11-27.00:42:15.027 [main ] INFO QwenPlusChatTest - ║ Usage : promptTokens=77 | completionTokens=166 | totalTokens=243
    25-11-27.00:42:15.027 [main ] INFO QwenPlusChatTest - ║ RateLimit : { @type: org.springframework.ai.openai.metadata.OpenAiRateLimit, requestsLimit: null, requestsRemaining: null, requestsReset: null, tokensLimit: null; tokensRemaining: null; tokensReset: null }
    25-11-27.00:42:15.027 [main ] INFO QwenPlusChatTest - ╚══════════════════════════════════════════════════════════

  • 关键点总结 :总是用 getResult().getOutput().getText() 取文本;检查 metadata 优化性能。

一张图记住层级关系:

复制代码
ChatResponse
├── getResult() → Generation (第一条答案)
│   ├── getOutput() → AssistantMessage
│   │   └── getContent() → 最终文本答案
│   └── getMetadata() → ChatGenerationMetadata (单条答案的元数据)
│
└── getMetadata() → ChatResponseMetadata (全局元数据)
    ├── id
    ├── model
    ├── usage → promptTokens + generationTokens
    ├── rateLimit → remaining requests/tokens
    └── promptMetadata → 输入分析

为什么要看元数据:

场景 不看元数据会怎样? 看了元数据能做什么?
成本控制 不知道花了多少钱 精确统计每日 token 消耗
异常排查 只能看到"请求失败" 通过 ID 精准定位问题请求
智能限流 直接被 429 打死 提前预警,自动降级或等待
用户体验优化 不知道上下文有多长 避免因 prompt 过长导致回答变差
相关推荐
BD_Marathon1 小时前
【IDEA】IDEA的详细设置
java·ide·intellij-idea
忘记9261 小时前
重复注解的机制是什么
java
喜欢流萤吖~1 小时前
Servlet 生命周期详解
java·servlet
刘一说1 小时前
JDK 25新纪元:技术革新与老项目迁移的冷思考
java·开发语言
无限进步_1 小时前
C语言双向循环链表实现详解:哨兵位与循环结构
c语言·开发语言·数据结构·c++·后端·算法·链表
小帅学编程1 小时前
Java基础
java·开发语言
思密吗喽1 小时前
如何完全清除Node.js环境重装 Node.js彻底卸载指南
java·开发语言·node.js·毕业设计·课程设计
summer__77771 小时前
38-第七章:集合(7.1-7.4)
java
7ioik1 小时前
Spring框架整合MyBatis框架?(超级详细)
java·spring·mybatis