SpringAI Chat Client (四)

在前面"Chat Models"小节内容中,我们在与各个模型进行对话时使用的是对应模型的ChatModel对象,例如:DeepSeekChatModel、OllamaChatModel。然后通过chatModel.call(...)、chatModel.stream(...)直接调用模型,这其中我们需要手动构造Prompt、处理流响应(例如将返回封装成对象、从模型响应中获取回复内容)、拼装调用链(RAG需要自己构建流程)等操作。

以上使用ChatModel与模型进行对话方式中,如果在项目中使用大模型涉及到记忆上下文、Prompt模版化、RAG开发、返回内容映射为实体等操作时,单纯的ChatModel代码量很多,维护成本高,为了简化这个流程,Spring AI 中提供了ChatClient对象,该对象可以看做一个更高级的"客户端 API",建立在ChatModel之上,可以用链式的方式快速搭配 Prompt、系统设定、变量替换、上下文记忆等,并支持文本/JSON/实体对象等多种形式的输出。

ChatModel和Chat Client对象对比如下:

特别注意:ChatClient 目前只支持 Chat(对话)模型,不包括 Embedding、Image、Audio 等多模态模型要使用 Embedding、Image 或 Audio 等模型,需要直接使用 Spring AI 提供的对应 API,比如 EmbeddingModel、ImageModel、AudioModel。

使用ChatClient

下面以使用DeepSeek为例来演示Spring AI中如何使用ChatClient。涉及ChatClient创建、设置提示词、流式回复、回复映射到对象操作。

  1. 创建SpringBoot项目,命名为"SpringAIChatClient
  1. 配置项目pom.xml
java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>SpringAIChatClient</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringAIChatClient</name>
    <description>SpringAIChatClient</description>

    <properties>
        <java.version>17</java.version>
    </properties>

    <!-- 导入 Spring AI BOM,用于统一管理 Spring AI 依赖的版本,
    引用每个 Spring AI 模块时不用再写 <version>,只要依赖什么模块 Mavens 自动使用 BOM 推荐的版本 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>1.0.0-SNAPSHOT</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-deepseek</artifactId>
        </dependency>
    </dependencies>


    <!-- 声明仓库, 用于获取 Spring AI 以及相关预发布版本-->
    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
        <repository>
            <name>Central Portal Snapshots</name>
            <id>central-portal-snapshots</id>
            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>
java 复制代码
package com.example.springaichatclient.controller;

import com.example.springaichatclient.pojo.Student;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/ai")
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder builder) {
        // 在 controller 构造函数中直接建立 ChatClient,
        // 并设置默认 system 提示词
        this.chatClient = builder
                .defaultSystem("你是一个聊天助手,名字叫小智。")
                .build();
    }

    //使用文本响应用户问题、设置Prompt提示词
    @GetMapping("/chat")
    public Map<String, String> chatText(@RequestParam("message") String message) {
        String reply = chatClient.prompt("如果用户让你讲故事,只能讲解神话故事,不能讲其他的。")
                .user(message)
                .call()
                .content();
        return Map.of("reply", reply);
    }


    @GetMapping("/response")
    public ChatResponse chatResponse(@RequestParam("message") String message) {
        return chatClient.prompt()
                .user(message)
                .call()
                .chatResponse();
    }

    @GetMapping("/getOneStudent")
    public Student getOneEntity() {
        return chatClient.prompt()
                .user("生成1个Student对象,输出单个JSON对象,字段:id(Long),name(String),age(Integer)")
                .call()
                .entity(Student.class);
    }

    @GetMapping("/getStudentList")
    public List<Student> getStudentList() {
        return chatClient.prompt()
                .user("生成3个Student对象,JSON数组格式,字段:id(Long),name(String),age(Integer)")
                .call()
                .entity(new ParameterizedTypeReference<List<Student>>() {});
    }

    @GetMapping(path = "/chatStream")
    public Flux<String> chatStream(@RequestParam("message") String message, HttpServletResponse response) {

        // 避免返回乱码
        response.setCharacterEncoding("UTF-8");

        return chatClient.prompt()
                .user(message)
                .stream()// 开启流式响应
                .content();
    }
}

以上代码中需要注意:defaultSystem(...)是设置全局系统提示词;prompt(...)是设置当前对话提示词;user(...)为用户输入消息内容。

**6) 启动项目并测试**

启动项目后,浏览器输入如下内容进行测试:

python 复制代码
# http://localhost:8080/ai/chat?message=给我讲个故事
{
  "reply": "好的,我很高兴为您讲一个神话故事。今天我要讲的是中国上古神话《夸父追日》:\n\n很久很久以前,在北方的大荒之中,住着一个名叫夸父的巨人。他是后土神的孙子,信神的儿子。夸父身高如山,力大无穷,耳朵上挂着两条黄蛇,手里也握着两条黄蛇。\n\n那时候,太阳每天从东边升起,西边落下,夸父觉得太阳跑得太快了,大地上的时间太短暂。于是他决定要追上太阳,让它慢下来。\n\n夸父迈开巨大的步伐,开始追逐太阳。他跑啊跑啊,跨过一座座高山,越过一条条大河。太阳在天上跑,夸父在地上追。眼看就要在禺谷追上太阳了,夸父却感到口渴难忍。\n\n他俯下身来,一口气喝干了黄河的水,又喝干了渭河的水,还是觉得口渴。于是他向北跑去,想要喝大泽的水。可是还没跑到大泽,夸父就因为极度干渴而倒下了。\n\n临死前,夸父扔出了他的手杖。那手杖化作了一片桃林,结满了鲜美的桃子,为后来路过的人解渴。\n\n这个故事展现了古人征服自然的雄心壮志,也告诉我们做事要量力而行。您觉得这个神话故事怎么样?"
}


# http://localhost:8080/ai/response?message=你是谁
{
... ...
"results": [
    {
      "metadata": {
        "finishReason": "STOP",
        "contentFilters": [],
        "empty": true
      },
      "output": {
        "messageType": "ASSISTANT",
        "metadata": {
          "finishReason": "STOP",
          "index": 0,
          "id": "f68527d6-6a26-4bbb-89c0-ca849e17b900",
          "role": "ASSISTANT",
          "messageType": "ASSISTANT"
        },
        "toolCalls": [],
        "media": [],
        "prefix": null,
        "reasoningContent": null,
        "text": "你好!我是小智,一个智能聊天助手,随时为你提供帮助和解答问题。无论是日常疑问、学习辅导,还是闲聊放松,我都可以陪你聊聊!有什么我可以帮你的吗?😊"
      }
    }
  ]
}

# http://localhost:8080/ai/getOneStudent
{
  "id": 1,
  "name": "张三",
  "age": 20
}

# http://localhost:8080/ai/getStudentList
[
  {
    "id": 1,
    "name": "张三",
    "age": 20
  },
  {
    "id": 2,
    "name": "李四",
    "age": 21
  },
  {
    "id": 3,
    "name": "王五",
    "age": 22
  }
]

一个项目使用多个聊天模型

在"SpringAIChatClient"项目中,如果此刻我们需要只用智普AI进行图片的识别,那么就需要在项目pom.xml中引入智普AI相关依赖,然后再在"resources/application.properties"中配置智普AI相关的URL/ApiKey/Model等信息,这样就不能再使用DeepSeek模型,因为默认Spring AI只配置一个ChatClient.Builder。

如果在一个项目中需要使用多个模型,我们可以通过如下方式手动管理多个ChatClient实例,步骤如下:

  1. 在SpringBoot项目中引入多个模型的依赖包

  2. 关闭ChatClient.Builder 自动创建,改为手动管理ChatClient创建

在resources/application.properties配置文件中配置"spring.ai.chat.client.enabled=false",该配置项默认为true,Spring Boot 会自动创建一个 ChatClient.Builder Bean 可以直接注入使用,手动管理 ChatClient 的创建需要设置为false,即不再使用ChatClient.Builder Bean 注入

  1. 自定义配置类实现多模型ChatClient创建

创建项目并配置多模型

  1. 配置项目pom.xml

在项目的pom.xml中引入了deepseek和zhipuai相关的依赖包。

java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>SpringAIMutiModelClient</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringAIMutiModelClient</name>
    <description>SpringAIMutiModelClient</description>

    <properties>
        <java.version>17</java.version>
    </properties>

    <!-- 导入 Spring AI BOM,用于统一管理 Spring AI 依赖的版本,
    引用每个 Spring AI 模块时不用再写 <version>,只要依赖什么模块 Mavens 自动使用 BOM 推荐的版本 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>1.0.0-SNAPSHOT</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 引入 DeepSeek 模型 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-deepseek</artifactId>
        </dependency>

        <!-- 引入 智普AI 模型-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-zhipuai</artifactId>
        </dependency>

    </dependencies>


    <!-- 声明仓库, 用于获取 Spring AI 以及相关预发布版本-->
    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
        <repository>
            <name>Central Portal Snapshots</name>
            <id>central-portal-snapshots</id>
            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>

多模型聊天示例

  1. 创建controller包,并创建MultiModelController.java文件

在该文件中,引入配置文件中配置的两个ChatClient,可以在不同的controller方法中使用对应模型的ChatClient来进行对话。

java 复制代码
package com.example.springaimutimodelclient.controller;

import com.example.springaimutimodelclient.pojo.IdCardInfo;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

@RestController
@RequestMapping("/ai")
public class MultiModelController {
    private final ChatClient deepseekClient;
    private final ChatClient zhipuaiClient;

    public MultiModelController(
            @Qualifier("deepseekClient") ChatClient deepseekClient,
            @Qualifier("zhipuaiClient") ChatClient zhipuaiClient) {
        this.deepseekClient = deepseekClient;
        this.zhipuaiClient = zhipuaiClient;
    }

    @GetMapping("/deepseek")
    public String chatWithDeepseek(@RequestParam("message") String message) {
        return deepseekClient.prompt()
                .user(message)
                .call()
                .content();
    }

    @GetMapping("/zhipuai")
    public String chatWithZhipuai(@RequestParam("message") String message) {
        return zhipuaiClient.prompt()
                .user(message)
                .call()
                .content();
    }
}

图片内容识别示例

  1. 准备图片

将图片"img.png"和"身份证.jpg"上传至项目"SpringAIMutiModelClient"的resources资源目录下。

  1. 创建pojo包并创建IdCardInfo.java类

由于后续与大模型对话记性身份证识别时需要自动映射为实体对象,这里创建IdCardInfo.java类:

java 复制代码
package com.example.springaimutimodelclient.pojo;

public class IdCardInfo {
    private String name;
    private String sex;
    private String nation;
    private String birth;
    private String address;
    private String idNo;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getNation() {
        return nation;
    }

    public void setNation(String nation) {
        this.nation = nation;
    }

    public String getBirth() {
        return birth;
    }

    public void setBirth(String birth) {
        this.birth = birth;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getIdNo() {
        return idNo;
    }

    public void setIdNo(String idNo) {
        this.idNo = idNo;
    }

    @Override
    public String toString() {
        return "IdCardInfo{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", nation='" + nation + '\'' +
                ", birth='" + birth + '\'' +
                ", address='" + address + '\'' +
                ", idNo='" + idNo + '\'' +
                '}';
    }
}
  1. 创建controller包,并创建 MultiModelController.java文件
java 复制代码
package com.example.springaimutimodelclient.controller;

import com.example.springaimutimodelclient.pojo.IdCardInfo;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

@RestController
@RequestMapping("/ai")
public class MultiModelController {
    private final ChatClient deepseekClient;
    private final ChatClient zhipuaiClient;

    public MultiModelController(
            @Qualifier("deepseekClient") ChatClient deepseekClient,
            @Qualifier("zhipuaiClient") ChatClient zhipuaiClient) {
        this.deepseekClient = deepseekClient;
        this.zhipuaiClient = zhipuaiClient;
    }

    @GetMapping("/deepseek")
    public String chatWithDeepseek(@RequestParam("message") String message) {
        return deepseekClient.prompt()
                .user(message)
                .call()
                .content();
    }

    @GetMapping("/zhipuai")
    public String chatWithZhipuai(@RequestParam("message") String message) {
        return zhipuaiClient.prompt()
                .user(message)
                .call()
                .content();
    }

}
  1. 启动项目并测试

启动项目后,浏览器输入如下内容进行测试:

相关推荐
奔跑中的小象30 分钟前
统信UOS + 天数AI卡部署SGLang服务手册
人工智能·uos·sglang·天数智芯
DevSecOps选型指南32 分钟前
中国版Mythos,为何是悬镜安全灵脉CodeAI?
人工智能·安全
Shockang43 分钟前
LangGraph 状态机实战
人工智能
刹那芳华19921 小时前
循环神经网络的从零开始实现(RNN)
人工智能·rnn·深度学习
阿童木写作1 小时前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
科技之门1 小时前
AI3D从建模到贴图、绑骨和动画的完整流程怎么做?V2Fun完整工作流指南
人工智能·3d·贴图
宇宙第一小趴菜1 小时前
二、机器学习的应用领域和发展史
人工智能·机器学习
前端开发江鸟1 小时前
我写过 MCP Server,却一直以为 MCP 只有 Tool
人工智能
天国梦2 小时前
自习室智能化升级避坑指南:天学网AI智习室方案实测与选型建议
大数据·人工智能
阿里云云原生2 小时前
可用性从 99.9% 跃升至 99.995%:畅捷通如何用 AI 重塑运维底座?
运维·网络·人工智能