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 过长导致回答变差
相关推荐
BestAns38 分钟前
一文带你吃透 Java 反射机制
java·后端
wasp5201 小时前
AgentScope Java 核心架构深度解析
java·开发语言·人工智能·架构·agentscope
2501_916766541 小时前
【Springboot】数据层开发-数据源自动管理
java·spring boot·后端
半夏知半秋1 小时前
docker常用指令整理
运维·笔记·后端·学习·docker·容器
程序员码歌1 小时前
短思考第263天,每天复盘10分钟,胜过盲目努力一整年
android·前端·后端
自在极意功。1 小时前
MyBatis 动态 SQL 详解:从基础到进阶实战
java·数据库·mybatis·动态sql
软件管理系统1 小时前
基于Spring Boot的便民维修管理系统
java·spring boot·后端
源代码•宸2 小时前
Leetcode—620. 有趣的电影&&Q3. 有趣的电影【简单】
数据库·后端·mysql·算法·leetcode·职场和发展
百***78752 小时前
Step-Audio-2 轻量化接入全流程详解
android·java·gpt·php·llama