Spring AI[02]:核心API入门-使用ChatClient与大模型对话

内容分享2小时前发布
0 0 0

Spring AI[02]:核心API入门-使用ChatClient与大模型对话

本章目标:深入掌握 Spring AI 最核心的 ChatClient API,学会发送结构化消息、使用提示模板、处理流式响应,为构建复杂 AI 应用打下坚实基础。

2.1 本章核心概念预览

概念

说明

ChatClient

Spring AI 提供的统一聊天接口,类似 WebClient,用于与大模型对话

Model

大语言模型(如 GPT-4、qwen-plus),负责生成文本

Prompt

发送给模型的输入指令,决定模型的行为

Response

模型返回的结果,可以是文本、结构化数据等

Starter

Spring Boot 自动配置模块,简化集成(如
spring-ai-starter-model-openai)

2.2 ChatClient API 设计理念

Spring AI 中的 ChatClient API 的设计核心,是致力于将 AI 能力无缝、优雅地集成到成熟的 Spring 生态中,为 Java 开发者提供一套符合 Spring 哲学(如”约定优于配置”、依赖注入)的统一编程模型.

ChatClient 作为核心接口,定义了与AI模型交互的统一方式。无论后端是 OpenAI、Azure AI、Anthropic,还是国内的通义千问,开发者面对的都是同一套简洁的 API(如 call() 和 stream() 方法)。这意味着,当需要更换模型时,你一般只需修改配置文件中的 API 密钥和模型名称即可,业务代码无需任何改动。这种设计极大地降低了模型锁定的风险和技术切换的成本。

API 的设计深受现代 Java 和 Spring 风格的影响,提供了流畅的(Fluent)DSL 来构建请求,使得代码超级清晰易读:

String response = chatClient.prompt()
    .user("请将'Hello World'翻译成法语。") // 设置用户消息
    .call() // 执行调用
    .content();
  • org.springframework.ai.chat.client.ChatClient 被定义为了一个接口SpringAI 提供了默认的实现org.springframework.ai.chat.client.DefaultChatClient,实则例对象由org.springframework.ai.chat.client.ChatClient.Builder来构建
  • 得益于SpringBoot 的自动配置, 在spring-ai-autoconfigure-model-chat-client-1.0.2.jar中有org.springframework.ai.model.chat.client.autoconfigure.ChatClientAutoConfiguration自动配置类,它配置了org.springframework.ai.chat.client.ChatClient.Builder这个Bean对象,它会读取application.yml中的配置,设置模型参数,列如 key和所使用的模型。所以在使用的时候,可以直接注入后使用。

实战 1:最小使用样例

新建一个子模块 chapter02, 在application.yml中配置大模型参数:

# chapter02/src/main/resources/application.yml
spring:
  ai:
    dashscope:
    # 必填,在操作系统环境变量中设置这个变量后,重启IDEA才能生效。由于IDEA启动的时候会缓存这个变量
      api-key: ${AI_BAI_LIAN_API_KEY}
      chat:
        options:
          model: qwen-plus
          # 这个值0~1,值越大代表生成的结果随机性越强。如果是一个聊天,这个值可以大一点。如果是一些严谨的规划,则这个值可以设置小一些
          temperature: 0.7

maven依赖:

<!-- chapter02/pom.xml -->
...
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 模型依赖:根据选型替换(如openai/deepseek/qwq) -->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
...

目前在chapter02 项目中增加一个Spring Controller:

// chapter02/src/main/java/com/kaifamiao/chapter02/controller/ChatController.java
@RestController
@Slf4j
public class ChatController {
    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    // http://localhost:8080/chat/sync?question=什么是SpringAI
    @GetMapping("/chat/sync")
    public String syncChat(@RequestParam(defaultValue = "推荐10本经典的Java书籍")
                           String question) {

        return chatClient.prompt().user(question).call().content();
    }
}
  1. 自动配置中已经可以直接使用ChatClient.Builder这个bean了,所以在构造方法中直接注入这个bean,上面代码为了简洁,直接调用了 build()方法来构建 chatClient对象,并没有对chatClient做过多的配置。更多的配置项,后面会讲到。
  2. Controller中提供了一个 GET请求,参数是 question为了测试方便,提供了默认值
  3. 启动应用测试: 打开浏览器,地址栏中输入[http://localhost:8080/chat/sync?question=](http://localhost:8080/chat/sync?question=)什么是SpringAI

当输入完回车后,会发现要等一段时间才看到有结果出现,而不是平时看到了马上就会有文字输出。这是由于我们使用的是同步调用的方式,即 chatClient.prompt().user(question).call().content(), call()方法的调用就是同步调用。

2.3 同步 vs 流式响应(Streaming)

Spring AI 支持两种调用模式:

模式

特点

适用场景

同步调用

等待模型生成完整响应后返回

简单问答、结构化输出

流式调用

逐字/逐 token 返回,实现“打字机效果”

聊天界面、长文本生成

实战 2:流式调用

在Controller中增加一个流式调用的方法:

// chapter02/src/main/java/com/kaifamiao/chapter02/controller/ChatController.java
@RestController
@Slf4j
public class ChatController {
    ...
    // http://localhost:8080/chat/stream?question=什么是SpringAI
    @GetMapping(value = "/chat/stream", produces ="text/html;charset=UTF-8")
    public Flux<String> streamChat(@RequestParam(defaultValue = "推荐10本经典的Java书籍")
                                   String question) {

        return chatClient.prompt().user(question).stream().content();
    }
    ...
}
  • produces=text/html;charset=UTF-8 定义了输出的是文本,并指定字符集为 UTF-8
  • Flux<String>:响应式流,逐段返回内容
  • 前端可用 EventSource 接收并实时显示

流式响应超级适合 Web 聊天界面,用户体验更自然。

重新启动项目后,访问
http://localhost:8080/chat/stream?question=什么是SpringAI 会发现,页面上显示的内容就类似于打字机的效果

2.4 消息类型:prompt,user,assistant,system

在 Spring AI 中,prompt、system、user 和 assistant 是与大语言模型(LLM)交互的核心概念,它们共同构成了一次完整的对话请求。下面这个表格能帮你快速把握它们的核心区别。

概念

角色与功能

类比与特点

Prompt (提示词)

一次完整请求的容器,包含所有消息和配置选项

类似于一次 HTTP 请求,包含了请求头(选项)和请求体(消息列表)。

System (系统角色)

在后台设定 AI 的角色、行为和规则,不直接参与对话

类似于操作系统的内核,隐藏在幕后却决定了系统的整体行为风格。

User(用户角色)

提问者,代表最终用户向 AI 提出的具体问题或指令

类似于向搜索引擎输入的关键词,是触发AI响应的直接缘由。

Assistant(助手角色)

回答者,代表 AI 给出的回复,也用于在连续对话中提供历史上下文

类似于对话中的记忆单元,保证了多轮对话的连贯性。

实战 3:Prompt1

下面通过代码来理解它们。 在ChatController类中再添加一个方法:

// chapter02/src/main/java/com/kaifamiao/chapter02/controller/ChatController.java
@RestController
@Slf4j
public class ChatController {
    ...
    // http://localhost:8080/chat/prompt
    @GetMapping(value = "/chat/prompt1", produces = "text/html;charset=UTF-8")
    public Flux<String> prompt1(@RequestParam(defaultValue = "推荐10本经典的Java书籍")
                               String question) {

        SystemMessage systemMessage = new SystemMessage("请按照以下格式输出:
" +
                "1. 输出格式为:序号. 标题
" +
                "2. 输出的序号为1-10,从1开始
" +
                "3. 输出的标题为10个字以内的中文标题");
        UserMessage userMessage = new UserMessage(question);

        // 将消息封装成Prompt
        Prompt prompt = new Prompt(systemMessage, userMessage);

        return chatClient.prompt(prompt)
                .stream().content();
    }
    ...
}
GET  http://localhost:8080/chat/prompt
1. Java核心技术 2. 深入理解Java 3. Java编程思想 4. Effective Java 5. Java并发编程 6. 重构改善程序 7. 设计模式之禅 8. JVM虚拟机详解 9. Spring实战指南 10. 算法(第4版)

2.5 核心概念详解

1. Prompt:请求的容器

你可以把 Prompt 理解为一个信封。它主要包含两部分:

  • 消息序列(List<Message>:信封里按顺序排列的信纸,即 system、user、assistant 等不同角色的消息。
  • 请求选项(ChatOptions:信封上的一些附加说明,如设置本次请求的模型参数(温度值 temperature、最大生成长度 max_tokens 等

Prompt 类将所有这些信息打包,然后通过模型的 call或者stream 方法发送给 AI 模型进行处理

2. Message 的角色分工

不同的 Message 角色在对话中扮演着截然不同的角色,理解它们是如何协作的至关重大。

  • System:设定舞台背景

System 消息用于在对话开始前对 AI 进行“背景设定”和“角色扮演”。它一般是不可见的,但会深刻影响 AI 后续所有的回复风格和内容边界,例如:

// 设定一个专业的翻译官角色
SystemMessage systemMsg = new SystemMessage("你是一名专业的翻译官,负责将中文翻译成英文。翻译时应准确、流畅,符合英文表达习惯。");
  • User & Assistant:驱动对话进行

User 和 Assistant 消息构成了对话的可见部分,它们一问一答,推动对话前进,

- UserMessage 是用户当前的问题或指令。
- AssistantMessage 一般是 AI 对上一个 UserMessage 的回复。但在构造请求时,它更重大的用途是提供对话历史,这对于实现连续对话(多轮对话)至关重大。AI 模型本身是无状态的,它需要依靠我们提供的完整历史记录来理解上下文。

官方文档(
https://docs.spring.io/spring-ai/reference/api/prompt.html)中提供了 Spring AI 的Message继承模式:

Spring AI[02]:核心API入门-使用ChatClient与大模型对话

Spring AI基于Message接口类,最终衍生出SystemMessage、UserMessage、AssistantMessage和ToolResponseMessage。

实战 4:Prompt2

模拟多轮对话,体验 AssistantMessage, 在Controller中再添加一个方法:

// chapter02/src/main/java/com/kaifamiao/chapter02/controller/ChatController.java
@RestController
@Slf4j
public class ChatController {
    ...
    // http://localhost:8080/chat/prompt2
    @GetMapping(value = "/chat/prompt2", produces = "text/html;charset=UTF-8")
    public Flux<String> prompt2(@RequestParam(defaultValue = "推荐10本经典的Java书籍")
                                String question) {
        List<Message> messages = new ArrayList<>();
        //1. 第一设置系统指令,定义AI的行为(例如:作为一个友善的助手)
        SystemMessage systemMessage = new SystemMessage("你是一个友善的助手,熟悉Java相关的书记,乐于用中文协助用户回答Java书籍相关问题。" +
                "以列表的形式输出");
        messages.add(systemMessage);
    
        //2. 第一轮对话:用户提问
        UserMessage userMessage1 = new UserMessage(question);
        messages.add(userMessage1);
    
        //3. 构建Prompt并发送请求
        Prompt prompt = new Prompt(messages);
        // 获取AI的回复
        String responseText = chatClient.prompt(prompt).call().content();
        log.info("第一轮对话输出:{}", responseText);
    
        // 将AI的回复(AssistantMessage)加入历史记录,以备下一轮使用
        messages.add(new AssistantMessage(responseText));
    
        // 4. 第二轮对话:用户基于上一轮的内容继续提问
        messages.add(new UserMessage("列表中第一个书名是什么?它主要讲解了哪些Java知识?"));
    
        //再次构建Prompt,此时messages中包含了完整的对话上下文
        prompt = new Prompt(messages);
        return chatClient.prompt(prompt)
                .stream().content();
    }
    ...
}
GET http://localhost:8080/chat/prompt2

控制台输出第一轮对话(...省略其它输出):
1. 《Java核心技术 卷I:基础知识》(原书第11版)—— Cay S. Horstmann  
   Java经典教材,系统讲解Java基础与核心概念,适合初学者和中级开发者。

2. 《Effective Java》(第3版)—— Joshua Bloch  
   Java领域必读之作,深入讲解最佳实践和设计原则,适合中高级开发者。

3. 《Java编程思想》(Thinking in Java,第4版)—— Bruce Eckel  
   深入剖析Java语言的设计理念与面向对象思想,内容详实,适合进阶学习。
...

这些书籍覆盖了Java语言基础、高级特性、并发、JVM、设计模式和工程实践,是Java开发者成长路上的重大资源。

浏览器上输出第二轮对话结果(...省略其它输出):

列表中第一本书是: 《Java核心技术 卷I:基础知识》(原书第11版)—— Cay S. Horstmann ....

3.ChatOptions

ChatOptions表明Prompt的选项,它可以准确控制模型行为, Prompt构造方法最后一个参数可以配置ChatOptions

参数

说明

推荐值

temperature

创造性 vs 确定性

0.0(确定)~ 1.0(值越大,随机性越强)

maxTokens

最大输出 token 数

512 ~ 2048

topP

核采样概率

0.9

frequencyPenalty

重复惩罚

0.5

presencePenalty

新话题鼓励

0.5

ChatOptions options = ChatOptions.builder()
        .withModel("gpt-3.5-turbo")          // 指定模型
        .withTemperature(0.3f)               // 低温度:更确定
        .withMaxTokens(200)                  // 限制长度
        .withTopP(0.9f)
        .build();

    Prompt prompt = new Prompt(
        new UserMessage("用三句话总结微服务架构的优点"),
        options
    );

2.6 Prompt 模板

硬编码提示词(prompt)不利于维护。Spring AI 提供 PromptTemplate 支持动态填充。实则现类包括:SystemPromptTemplate、AssistantPromptTemplate和FunctionPromptTemplate可以生成对应的Message。

Spring AI[02]:核心API入门-使用ChatClient与大模型对话

实战 5:使用 PromptTemplate 生成周报

创建模板文件
chapter02/src/main/resources/prompt/weekly-report.tpl:

你是一个专业的技术团队负责人。
请根据以下信息生成本周工作周报:

项目名称:{projectName}
完成事项:
{accomplishments}
遇到问题:
{challenges}

要求:
- 语言正式,结构清晰
- 分“项目进展”、“问题与风险”、“下周计划”三部分
- 不超过 300 字

在Controller中再添加一个方法:

// chapter02/src/main/java/com/kaifamiao/chapter02/controller/ChatController.java
@RestController
@Slf4j
public class ChatController {
    ...
   // http://localhost:8080/chat/promptTpl
    @GetMapping(value = "/chat/promptTpl", produces = "text/html;charset=UTF-8")
    public Flux<String> promptTpl(@RequestParam(defaultValue = "请生成一个周报")
                                  String question) {
        // 创建一个PromptTemplate
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("classpath:/prompt/weekly-report.tpl");
        List<String>         acc                  = Arrays.asList("完成用户模块开发", "修复登录 Bug");
        List<String>         cha                  = Arrays.asList("第三方接口响应慢");
        // 设置占位符实际内容并返回system角色的message
        Message systemMessage = systemPromptTemplate.createMessage(Map.of("projectName", "喵星球知识库",
                "accomplishments", String.join("
", acc),
                "challenges", String.join("
", cha)));
        // 设置用户的问题message
        Message userMessage = new UserMessage(question);
        // 组装Prompt
        Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
        return chatClient.prompt(prompt).stream().content();
    }
    ...
}
GET http://localhost:8080/chat/promptTpl

当然可以!以下是一份通用的周报模板,您可以根据具体岗位(如:技术、运营、市场、行政等)进行调整。如果您提供更详细的信息(如部门、工作内容、本周完成事项、下周计划等),我可以为您定制更具体的周报。
...

2.7 默认角色

上面可以看到Prompt中可以设置多个角色Message,但是不是必须的。如果在单次聊天会话中没有指定角色,可以在构建chatClient对象的时候,指定默认的角色Message。

// chapter02/src/main/java/com/kaifamiao/chapter02/controller/ChatController.java
@RestController
@Slf4j
public class ChatController {
    ...
    public ChatController(ChatClient.Builder builder) {
        // this.chatClient = builder.build();
        this.chatClient = builder
                .defaultSystem("你是一个精通Java的工程师,专门解决Java遇到的问题。")// 设定默认角色
                .defaultUser("你是谁?")//设置默认问题
                .build();
    }
    ...

这样在chatClient送出 Prompt 的时候,如果没有指定角色Message,就会使用默认的角色Message。

2.7 本章小结

  • ChatClient 是 Spring AI 的核心入口,API 设计简洁且强劲。
  • 支持同步和流式调用,满足不同场景需求。
  • 通过 UserMessage、AssistantMessage、SystemMessage 构建结构化对话。
  • 使用 PromptOptions 精细控制模型生成行为。
  • PromptTemplate 实现提示词与代码分离,提升可维护性。

下一章预告:第3章《数据结构化输出-让 AI 返回 Java 对象》

在下一章中,我们将学习如何让 AI 返回结构化的 Java 对象,而不是原始字符串。例如:

  • 让 AI 解析用户输入并返回 UserRegistration 对象
  • 提取会议纪要中的时间、地点、参会人等字段

我们将使用 @Schema 注解和 JSON Mode 实现这一目标,彻底告别字符串解析!

继续前进吧!

源代码地址:
https://github.com/kaiwill/kaifamiao

© 版权声明

相关文章

暂无评论

none
暂无评论...