Spring AI 教程

Spring AI 核心接口 ChatClient

ChatClient 是 Spring AI 框架中用于与大型语言模型(LLM)进行交互的核心接口,它提供了与聊天模型通信的标准化方式。通过 ChatClient,你可以向模型发送提示(Prompts)并接收生成的回复。

ChatClient 通过 Fluent API 与 AI 模型交互,同时支持同步和流式(Stream)编程模型。

Fluent API 提供了构建提示词(Prompt)组件的方法,这些组件将作为输入参数传递给 AI 模型。提示词包含指导 AI 模型输出内容和行为的指令文本。

从 API 角度来看,一个完整的提示词(Prompt)由一组消息集合构成,Prompt 部分源码:

public class Prompt implements ModelRequest<List<Message>> {
    private final List<Message> messages;
    @Nullable
    private ChatOptions chatOptions;
    //....
}

注意,AI 模型主要处理两类消息:

  • 用户消息:也称“用户提示词”,直接来自用户的输入。

  • 系统消息:也称“系统提示词”,由开发系统的人员提前设置,用于引导对话走向、角色定义、输出限制等等。例如,开发一个高考分数查询 AI 机器人,那么系统提示词就会给出限制,仅能查询分数,不能处理非高考相关的内容。

提示词中的这些消息中通常包含占位符(如 ${content}),会在运行时根据实际用户输入内容进行动态替换,从而定制 AI 模型对用户输入的响应内容。

此外,你还可以指定一些提示词选项,如要使用的 AI 模型名称,以及控制生成输出随机性 / 创造性的 temperature(温度)、top-k 参数。

  

创建 ChatClient

ChatClient 需通过 ChatClient.Builder 对象来创建。你可以获取 Spring Boot 自动配置的 ChatModel 对应的 ChatClient.Builder 实例,也可以通过编程方式自行构建。

使用自动配置的 ChatClient.Builder

在最简单的使用场景中,Spring AI 通过 Spring Boot 自动配置生成 ChatClient.Builder Bean。注意,该 Bean 是 Prototype 类型的,每次请求时都会创建一个新的 ChatClient.Builder Bean 实例,可直接注入类中使用。

以下是获取用户简单请求 String 响应的基础示例:

package com.hxstrive.springai.springai_demo1.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
class MyController {

    private final ChatClient chatClient;

    // 自动注入,自动配置的 ChatClient.Builder
    public MyController(ChatClient.Builder chatClientBuilder) {
        System.out.println("chatClientBuilder = " + chatClientBuilder);
        // chatClientBuilder = org.springframework.ai.chat.client.DefaultChatClientBuilder@73113098
        this.chatClient = chatClientBuilder.build();
    }

    @GetMapping("/ai")
    String generation(String userInput) {
        return this.chatClient.prompt()
            .user(userInput) // 用户提示词
            .call()
            .content();
    }
}

上述代码采用同步方式调用大模型,其中,userInput 为用户输入的消息内容。call() 方法向 AI 模型发送请求, content() 方法以 String 形式返回模型响应。运行效果:

Spring AI 核心接口 ChatClient

  

多聊天模型协作

什么是多聊天模型协作?可以理解为,我们在处理一个业务时,需要多种类型模型配合完成,而不是一种类型模型完成。例如,我们要对用户上传的资料进行数据提取,而资料分为很多种(有合同、发票等等),此时,我们可以选择擅长分类的模型进行分类(即使是同一种分类模型,也存在大小,如 8B、32B 等等),选择多模态模型进行数据提取。

什么是多模态模型?多模态模型指能够同时处理多种类型数据(如图像、文本、音频、视频等)并理解它们之间关联的人工智能模型。与传统单模态模型(如仅处理文本的 GPT)不同,多模态模型可以整合不同模态的信息,实现更复杂的任务。代表性模型有:

  • GPT-4V:OpenAI 的多模态版本,支持图像输入

  • Llama-3:Meta 的多模态大模型

  • Gemini Pro Vision:Google 的多模态模型

  • CLIP(OpenAI):连接图像和文本的基础模型

  • Stable Diffusion:文本到图像生成模型

在单一应用中需使用多个聊天模型的典型场景包括:

  • 不同任务类型选用不同模型(如复杂推理用高性能模型,更准确;简单任务用快速经济型模型,节约成本)

  • 主模型服务不可用时启用备用机制

  • 不同模型或配置的 A/B 测试。什么是 A/B 测试?A/B 测试(A/B Testing)是一种通过对比不同版本方案的效果,来确定最优解的实验方法。在大模型中,通过对比不同模型配置、提示词设计或输出策略的效果,优化模型性能和业务目标的实验方法,最终选出最优模型。

  • 根据用户偏好提供可选的模型,如下图:

Spring AI 核心接口 ChatClient

上图是,FastGPT 中,AI 对话节点,用户就可以选择自己喜欢的模型。

  • 组合专用模型(如代码生成与创意内容分别使用不同模型)

注意,Spring AI 默认自动配置单个 ChatClient.Builder Bean,但应用中可能需要使用多个聊天模型。处理方法如下:

spring.ai.chat.client.enabled=false

通过上述配置, 禁用 Spring Boot 自动配置 ChatClient.Builder。此时,允许用户手动创建多个 ChatClient 实例。

  

单一模型类型下的多 ChatClient 实例

如果需创建多个使用相同底层模型类型,但配置不同的 ChatClient 实例。可以这样做:

// ChatModel 已由 Spring Boot 自动配置完成,注入即可使用
ChatModel myChatModel = ...;

// 以编程式创建 ChatClient 实例
ChatClient chatClient = ChatClient.create(myChatModel);

// 或使用 Builder 实现更精细控制,允许设置更多参数
ChatClient.Builder builder = ChatClient.builder(myChatModel);
ChatClient customChatClient = builder
    .defaultSystemPrompt("You are a helpful assistant.")
    .build();

  

不同模型类型的 ChatClient 配置

使用多个 AI 模型时,可为每个模型定义独立的 ChatClient Bean,如下:

(1)创建配置类,创建两个 ChatClient:

import org.springframework.ai.chat.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatClientConfig {

    @Bean
    public ChatClient openAiChatClient(OpenAiChatModel chatModel) {
        return ChatClient.create(chatModel);
    }

    @Bean
    public ChatClient anthropicChatClient(AnthropicChatModel chatModel) {
        return ChatClient.create(chatModel);
    }
}

(2)通过 @Qualifier 注解,将这些 Bean 注入应用组件:

@Configuration
public class ChatClientExample {

    @Bean
    public CommandLineRunner cli(
        @Qualifier("openAiChatClient") ChatClient openAiChatClient,
        @Qualifier("anthropicChatClient") ChatClient anthropicChatClient) {

        return args -> {
            var scanner = new Scanner(System.in);
            ChatClient chat;

            // 模型选择
            System.out.println("\nSelect your AI model:");
            System.out.println("1. OpenAI");
            System.out.println("2. Anthropic");
            System.out.print("Enter your choice (1 or 2): ");

            String choice = scanner.nextLine().trim();

            if (choice.equals("1")) {
                chat = openAiChatClient;
                System.out.println("Using OpenAI model");
            } else {
                chat = anthropicChatClient;
                System.out.println("Using Anthropic model");
            }

            // 使用选定的聊天客户端
            System.out.print("\nEnter your question: ");
            String input = scanner.nextLine();
            String response = chat.prompt(input).call().content();
            System.out.println("ASSISTANT: " + response);

            scanner.close();
        };
    }
}

  

多 OpenAI 兼容 API 端点

OpenAiApi 与 OpenAiChatModel 类提供的 mutate() 方法,该方法返回一个预先使用当前配置填充的生成器,以便进行修改。支持基于现有实例创建不同属性的变体,特别适用于需对接多个 OpenAI 兼容 API 的场景。

mutate() 方法定义如下:

/**
 * Returns a builder pre-populated with the current configuration for mutation.
 */
public Builder mutate() {
    return new Builder(this);
}

Builder 部分代码如下:

public static final class Builder {
    // Copy constructor for mutate()
    public Builder(OpenAiChatModel model) {
        this.openAiApi = model.openAiApi;
        this.defaultOptions = model.defaultOptions;
        this.toolCallingManager = model.toolCallingManager;
        this.toolExecutionEligibilityPredicate = model.toolExecutionEligibilityPredicate;
        this.retryTemplate = model.retryTemplate;
        this.observationRegistry = model.observationRegistry;
    }
    //...
}

简单示例:

@Service
public class MultiModelService {
    private static final Logger logger = LoggerFactory.getLogger(MultiModelService.class);

    // 基础 OpenAiChatModel
    @Autowired
    private OpenAiChatModel baseChatModel;

    // 基础 OpenAiApi
    @Autowired
    private OpenAiApi baseOpenAiApi;

    public void multiClientFlow() {
        try {
            // 为Groq(Llama3)派生一个新的 OpenAiApi
            OpenAiApi groqApi = baseOpenAiApi.mutate()
                .baseUrl("https://api.groq.com/openai")
                .apiKey(System.getenv("GROQ_API_KEY"))
                .build();

            // 为OpenAI GPT-4 派生一个新的 OpenAiApi
            OpenAiApi gpt4Api = baseOpenAiApi.mutate()
                .baseUrl("https://api.openai.com")
                .apiKey(System.getenv("OPENAI_API_KEY"))
                .build();

            // 为 Groq 派生一个新的 OpenAiChatModel
            OpenAiChatModel groqModel = baseChatModel.mutate()
                .openAiApi(groqApi)
                .defaultOptions(OpenAiChatOptions.builder().model("llama3-70b-8192").temperature(0.5).build())
                .build();

            // 为 GPT-4 派生一个新的 OpenAiChatModel
            OpenAiChatModel gpt4Model = baseChatModel.mutate()
                .openAiApi(gpt4Api)
                .defaultOptions(OpenAiChatOptions.builder().model("gpt-4").temperature(0.7).build())
                .build();

            // 适用于两种模型的简单提示词
            String prompt = "What is the capital of France?";

            // 分别调用不同的模型
            String groqResponse = ChatClient.builder(groqModel).build().prompt(prompt).call().content();
            String gpt4Response = ChatClient.builder(gpt4Model).build().prompt(prompt).call().content();

            logger.info("Groq (Llama3) response: {}", groqResponse);
            logger.info("OpenAI GPT-4 response: {}", gpt4Response);
        }
        catch (Exception e) {
            logger.error("Error in multi-client flow", e);
        }
    }
}

  

简单实例

创建 MyController 控制器,提供 /ai 接口,接收 userInput 用户输入信息,如果用户要询问编程相关问题,则使用 gpt-3.5-turbo-16k 模型进行回答,否则使用默认的模型进行回答。如下:

package com.hxstrive.springai.chatclient2.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
class MyController {

    @Autowired
    private ChatModel chatModel;


    @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8")
    String generation(String userInput) {
        // 以编程式创建 ChatClient 实例
        ChatClient chatClient = ChatClient.create(chatModel);
        String type = chatClient.prompt(
            "用户输入内容 `" + userInput + "` 是关于编程相关问题,返回 A001;否则,返回 A000。").call().content();
        System.out.println("type = " + type);

        if("A001".equals(type)) {
            // 或使用 Builder 实现更精细控制
            ChatClient.Builder builder = ChatClient.builder(chatModel);
            ChatClient customChatClient = builder
            .defaultSystem("你是一名软件工程师,根据用户提问回答问题,并且给出简单的代码示例。")
            .defaultOptions(ChatOptions.builder().model("gpt-3.5-turbo-16k").temperature(0.7D).build())
            .build();
            return type + "<br/>" + customChatClient.prompt(userInput).call().content();
        } else {
            return type + "<br/>" + chatClient.prompt(userInput).call().content();
        }
    }

}

重启应用,访问 /ai 接口,效果如下图:

Spring AI 核心接口 ChatClient

Spring AI 核心接口 ChatClient

  

ChatClient Fluent 风格 API

什么是 Fluent 风格?Fluent 风格(Fluent Interface)是一种面向对象的编程风格,它通过方法链(Method Chaining) 的方式让代码更加简洁易读。这种风格允许你在同一个对象上连续调用多个方法,而不需要每次都显式引用对象本身。例如:

String result = new StringBuilder()
    .append("Hello")
    .append(" ")
    .append("World")
    .toString();

Fluent 风格的每个方法返回当前对象实例,从而支持链式调用。且代码看起来像自然语言句子,逻辑更加清晰,无需创建中间对象来存储结果。

ChatClient Fluent 式 API 通过重载 prompt() 方法提供三种提示词创建方式:

  • prompt():无参方法启动 Fluent 式 API,支持逐步构建用户消息、系统消息等提示词组件。

  • prompt(Prompt prompt):接收 Prompt 参数,支持通过非 Fluent 式 API 构建的 Prompt 实例。

  • prompt(String content):便捷方法,接收用户文本内容,功能类似前项重载。

prompt() 方法源码如下:

// DefaultChatClientRequestSpec 用于构建和配置聊天客户端(ChatClient)的请求参数。
// 它属于 Spring AI 的流式 API(Fluent API)设计的一部分,主要作用是为开发者提供
// 一种链式调用的方式,以声明式语法定义 AI 模型的交互请求。
public ChatClient.ChatClientRequestSpec prompt() {
    return new DefaultChatClientRequestSpec(this.defaultChatClientRequest);
}

// 实际调用了 prompt(Prompt prompt),帮我们构建了一个 Prompt对象
public ChatClient.ChatClientRequestSpec prompt(String content) {
    Assert.hasText(content, "content cannot be null or empty");
    return this.prompt(new Prompt(content));
}

public ChatClient.ChatClientRequestSpec prompt(Prompt prompt) {
    Assert.notNull(prompt, "prompt cannot be null");
    DefaultChatClientRequestSpec spec = new DefaultChatClientRequestSpec(this.defaultChatClientRequest);
    if (prompt.getOptions() != null) {
        spec.options(prompt.getOptions());
    }

    if (prompt.getInstructions() != null) {
        spec.messages(prompt.getInstructions());
    }

    return spec;
}

简单示例,使用 prompt() 返回一个 ChatClient.ChatClientRequestSpec,通过链式调用构建请求参数,如系统提示词、用户提示词、温度、模型等等参数。如下:

@GetMapping(value = "/ai")
public String generation(String userInput) {
    ChatClient chatClient = ChatClient.create(chatModel);
    return chatClient.prompt().system("你是编码助手,仅回答编码相关的内容。")
            .user(userInput)
            .options(ChatOptions.builder().temperature(0.6D).model("gpt-3.5-turbo-16k").build())
            .call()
            .content();
}

  

ChatClient 响应

ChatClient API 运用 Fluent 式接口,为 AI 模型的响应提供了多种格式化的途径。具体而言,它能够以不同的形式来呈现 AI 模型给出的响应内容,方便用户根据自身需求获取和处理这些响应。

例如,它可以返回 ChatResponse 元数据,让用户了解响应生成的详细情况;支持多响应,以 Generations 数组形式提供每个响应独立的元数据;还能进行 Token 统计,按照约 3/4 单词计为 1 个 Token 的规则,为计费等场景提供依据。又比如在实际代码中,通过在 call () 方法后调用 chatResponse (),可以返回包含元数据的 ChatResponse 对象,从而实现对 AI 模型响应的一种格式化输出。

返回 ChatResponse

AI 模型返回的 ChatResponse 是一个包含了丰富信息的结构化响应:

  • 元数据:响应生成详情

  • 多响应支持:Generation 数组,每个 Generation 包含独立元数据

  • Token 统计:1 个 Token 约等于 3/4 个单词 ,可用来计算费用,作为计费依据

ChatResponse 部分源码如下:

public class ChatResponse implements ModelResponse<Generation> {
    // 元数据
    private final ChatResponseMetadata chatResponseMetadata;

    // 多响应支持
    /**
     * List of generated messages returned by the AI provider.
     */
    private final List<Generation> generations;
    //...
}

下面我们通过 Debug 调试,查看 Generations 内部数据情况,如下图:

Spring AI 核心接口 ChatClient

继续查看 ChatResponseMetadata 内部数据,如下图:

Spring AI 核心接口 ChatClient

下面是在 call() 方法后调用 chatResponse() 方法,返回包含元数据的 ChatResponse 对象的示例:

ChatResponse chatResponse = chatClient.prompt()
        .user("Tell me a joke")
        .call()
        .chatResponse(); // 返回 ChatResponse


返回实体

通常需要将返回的 String 映射为实体类,entity() 方法正提供此功能。

例如:给定一个 Java record:

record ActorFilms(String actor, List<String> movies) {}

什么是 Java record?

Java record 这是 Java 14(预览版)及后续版本引入的一种特殊类,其主要功能是作为不可变数据的透明载体。上面的代码定义了一个名为 ActorFilms 的记录,它包含以下内容:

  • 一个名为 actor 的 String 类型字段。

  • 一个名为 movies 的 List<String> 类型字段。

记录(record)的所有字段都是 final 的,一旦实例化,其状态就不能再改变。编译器会自动生成以下方法:

  • 构造函数

  • equals()、hashCode()方法

  • toString()方法

  • 访问器方法( 如 actor() 和 movies() )

和普通类相比,记录(record)的语法更加简洁,这样可以减少样板代码。

下面是 Record 的简单示例:

// 需要使用 jdk16+
public class RecordDemo1 {

    // 定义一个记录类型
    record ActorFilms(String actor, List<String> movies) {}

    public static void main(String[] args) {
        ActorFilms actorFilms = new ActorFilms("张无忌", List.of("倚天屠龙记", "九阴真经"));
        System.out.println(actorFilms);
        //ActorFilms[actor=张无忌, movies=[倚天屠龙记, 九阴真经]]
        System.out.println(actorFilms.actor); //张无忌
        System.out.println(actorFilms.movies); //[倚天屠龙记, 九阴真经]
        System.out.println(actorFilms.actor()); //张无忌
        System.out.println(actorFilms.movies()); //[倚天屠龙记, 九阴真经]
    }

}

更多关于 Record 知识,请查阅 Java 文档。

如下所示,通过 entity() 方法可轻松将 AI 模型输出映射至该 record 类:

ActorFilms actorFilms = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorFilms.class);

另提供 entity 重载方法 entity(ParameterizedTypeReference<T> type),支持泛型集合等复杂类型指定:

List<ActorFilms> actorFilms = chatClient.prompt()
    .user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
    .call()
    .entity(new ParameterizedTypeReference<List<ActorFilms>>() {});

  

流式响应

stream() 方法支持异步获取响应,示例如下:

Flux<String> output = chatClient.prompt()
    .user("Tell me a joke")
    .stream()
    .content();

也可通过 chatResponse() 方法以 Flux<ChatResponse> 流式获取响应。

未来版本将提供便捷方法支持响应式流直接返回 Java 实体。当前版本需通过结构化输出转换器(Structured Output Converter)显式聚合响应,如下所示。这也演示了 Fluent API 中参数的使用:

package com.hxstrive.springai.springai_openai.chatclient4.controller;

import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.stream.Collectors;

@RestController
class MyController {

    @Autowired
    private ChatModel chatModel;

    // 定义名为 ActorFilms 的记录
    record ActorsFilms(String actor, List<String> movies) {}

    @GetMapping(value = "/ai")
    public String generation(String userInput) {
        ChatClient chatClient = ChatClient.create(chatModel);

        // var 是在 Java 10 及以后的版本中,引入了一个新的特性,即局部变量类型推断
        // 编译器会自动推断出 converter 的类型是 BeanOutputConverter
        // BeanOutputConverter 是一个自定义转换器,
        // 用于将 AI 返回的文本响应转换为 Java 对象列表(List<ActorFilms>)
        var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorsFilms>>() {});

        // 构建一个 AI 请求,
        Flux<String> flux = chatClient.prompt()
                .user(u -> u.text("""
                        Generate the filmography for a random actor.
                        {format}
                      """)
                        // 上面的 {format} 会被替换为 converter.getFormat() 返回的内容(如 "JSON")
                        .param("format", converter.getFormat())
                )
                .stream() // 流式响应
                .content();

        // 使用 block() 将异步流转换为同步列表(阻塞操作)
        // 然后,将列表中的所有字符串连接成一个完整文本
        String content = flux.collectList().block().stream().collect(Collectors.joining());

        // 调用转换器将文本内容解析为List<ActorFilms>
        List<ActorsFilms> actorFilms = converter.convert(content);

        return JSONObject.toJSONString(actorFilms, JSONWriter.Feature.PrettyFormat);
    }

}

上述代码,需要额外添加 fastjson2 的依赖:

<!-- fastjson2 -->
<dependency>
  <groupId>com.alibaba.fastjson2</groupId>
  <artifactId>fastjson2</artifactId>
  <version>2.0.34</version>
</dependency>

运行示例,访问 http://localhost:8080/ai 地址,输出如下图:

Spring AI 核心接口 ChatClient

  

提示模版

ChatClient Fluent 式 API 支持提供含变量的用户/系统消息模板,运行时进行替换:

String answer = ChatClient.create(chatModel).prompt()
    .user(u -> u
            // {composer} 是模板占位符
            .text("Tell me the names of 5 movies whose soundtrack was composed by {composer}")
            .param("composer", "John Williams")) // 参数
    .call()
    .content();

ChatClient 内部使用 PromptTemplate 类处理用户/系统文本,依赖 TemplateRenderer 实现运行时变量替换。Spring AI 默认采用基于 Terence Parr 开发的 StringTemplate 引擎的 StTemplateRenderer 实现。

Spring AI 还提供 NoOpTemplateRenderer,用于无需模板处理的场景。源码如下:

package org.springframework.ai.template;

import java.util.Map;
import org.springframework.util.Assert;

public class NoOpTemplateRenderer implements TemplateRenderer {
    public NoOpTemplateRenderer() {
    }

    public String apply(String template, Map<String, Object> variables) {
        Assert.hasText(template, "template cannot be null or empty");
        Assert.notNull(variables, "variables cannot be null");
        Assert.noNullElements(variables.keySet(), "variables keys cannot be null");
        return template;
    }
}

注意:通过 .templateRenderer() 在 ChatClient  上直接配置的 TemplateRenderer 仅作用于构建链(Builder Chain)中直接定义的提示内容(如 .user() / .system())。它不会影响 Advisor(如 QuestionAnswerAdvisor)内部使用的模板 — 这些模板有独立的定制机制。

如需改用其他模板引擎,可直接向 ChatClient 提供 TemplateRenderer 接口的自定义实现。也可保留默认 StTemplateRenderer 但进行自定义配置。

例如,默认模板变量采用 {} 语法。若提示词中包含 JSON,建议改用 <> 等分隔符避免冲突。示例如下:

String answer = ChatClient.create(chatModel).prompt()
    .user(u -> u
            .text("Tell me the names of 5 movies whose soundtrack was composed by <composer>")
            .param("composer", "John Williams"))
    .templateRenderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
    .call()
    .content();

  

call() 方法返回值

ChatClient 的 call() 方法调用后,响应类型有以下几种处理选项:

  • String content():返回响应的字符串内容

  • ChatResponse chatResponse():返回包含多组生成结果及元数据(如消耗的 Token 数)的 ChatResponse 对象。

  • ChatClientResponse chatClientResponse():返回包含 ChatResponse 对象和 ChatClient 执行上下文的 ChatClientResponse 对象,可访问 Advisor 执行期间的附加数据(如 RAG 流程中检索的相关文档)。

  • entity() 返回 Java 类型

    •  entity(ParameterizedTypeReference<T> type):用于返回实体类型的集合(Collection)。

    •  entity(Class<T> type):用于返回特定实体类型。

    •  entity(StructuredOutputConverter<T> structuredOutputConverter):通过 StructuredOutputConverter 将 String 转为实体类型。

也可使用 stream() 方法替代 call()。

  

stream() 方法返回值

ChatClient 的 stream() 方法调用后,响应类型有以下处理选项:

  • Flux<String> content():返回 AI 模型生成字符串的 Flux 流。

  • Flux<ChatResponse> chatResponse():返回包含响应元数据的 ChatResponse 对象 Flux 流。

  • Flux<ChatClientResponse> chatClientResponse():返回包含 ChatResponse 对象和 ChatClient 执行上下文的 ChatClientResponse 对象 Flux 流,可访问 Advisor 执行期间的附加数据(如 RAG 流程检索的相关文档)。

 

使用默认值

在 @Configuration 类中为 ChatClient 配置默认 system(系统提示词)消息可简化运行时代码。预设默认值后,调用时仅需指定 user 消息(用户提示词),无需每次请求重复设置系统消息。

默认的系统消息(系统提示词)

以下示例将系统消息配置为始终以海盗口吻回复。为避免在运行时代码中重复系统消息,我们将在 @Configuration 类中创建 ChatClient 实例。

package com.hxstrive.springai.springai_openai.chatclient5.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder
            // 默认的系统提示词
            .defaultSystem("你是一个友好的聊天机器人,能以海盗的口吻回答问题")
            .build();
    }

}

并通过 @RestController 调用:

package com.hxstrive.springai.springai_openai.chatclient5.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;

@RestController
public class AIController {
    private final ChatClient chatClient;

    // 注入 ChatClient
    AIController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    @GetMapping("/ai/simple")
    public Map<String, String> completion(
        @RequestParam(value = "message", defaultValue = "给我讲个笑话") String message) {
        return Map.of("completion", this.chatClient.prompt().user(message).call().content());
    }
}

使用浏览器访问 http://localhost:8080/ai/simple,效果如下:

Spring AI 核心接口 ChatClient

  

带参数的默认系统消息

以下示例将在系统消息中使用占位符,以便在运行时(而非设计时)指定回复语气。

(1)创建 Config 配置类

package com.hxstrive.springai.springai_openai.chatclient6.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder
                // 设置默认系统提示词,并且使用 {voice} 占位符
                .defaultSystem("你是一个友好的聊天机器人,能用{voice}回答问题。")
                .build();
    }

}

(2)创建 AIController,提供一个 /ai/simple 接口,用于聊天:

package com.hxstrive.springai.springai_openai.chatclient6.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;
import java.util.Objects;

@RestController
public class AIController {
    private final ChatClient chatClient;

    // 注入 ChatClient
    AIController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    @GetMapping("/ai/simple")
    public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "给我讲个笑话") String message,
                                          @RequestParam("voice") String voice) {
        return Map.of("completion",
                Objects.requireNonNull(this.chatClient.prompt()
                        .system(sp -> sp.param("voice", voice))
                        .user(message)
                        .call()
                        .content()));
    }
}

使用浏览器访问 http://localhost:8080/ai/simple?voice=幽默的,效果如下:

Spring AI 核心接口 ChatClient

  

其他默认配置

可在 ChatClient.Builder 层级指定默认提示词配置。方法如下:

defaultOptions(ChatOptions chatOptions)

为聊天客户端设定默认的配置选项。借助传入 ChatOptions 类型的对象,能够对聊天过程中的通用参数进行配置。若要使用特定模型的专属选项,像 OpenAI 模型的特殊设置,可传入 OpenAiChatOptions 这类继承自 ChatOptions 的具体实现类。要了解各模型支持的详细选项,可查看对应 ChatOptions 实现类的 JavaDoc 文档。

  

defaultFunction(String name, String description, java.util.function.Function<I, O> function)

该方法的功能是注册一个默认的工具函数,这个函数可在对话里被调用。

  • name:这是函数的名称,在后续调用时会用到。

  • description:用于描述函数的功能,帮助模型理解函数的用途。

  • function:是一个 Java 函数式接口,它定义了函数的输入参数类型 I 和返回值类型 O。

  

defaultFunctions(String… functionNames)

此方法允许批量注册已存在的工具函数。通过传入函数名称数组,能将多个已定义的函数设置为默认可用的函数。需要注意的是,这些函数必须事先已经被注册过。

 

defaultUser(String text)

该方法用于设置默认的用户输入文本。当发起聊天请求时,如果没有特别指定用户输入,就会使用这个默认文本。

 

defaultUser(Resource text)

这是设置默认用户输入的另一种方式,它支持从资源文件中读取用户输入内容。Resource 对象可用于加载类路径下的文件或者外部资源。

 

defaultUser(Consumer<UserSpec> userSpecConsumer)

此方法提供了更灵活的方式来配置用户输入。通过传入一个 UserSpec 的消费者函数,可以对用户输入的各种属性进行定制,比如设置输入的格式、添加元数据等。

 

defaultAdvisors(Advisor… advisor)

该方法用于注册默认的顾问(Advisor)列表。顾问能够对聊天交互进行拦截和处理,例如记录日志、转换输出格式等。可以同时传入多个 Advisor 实例。

Advisor 机制支持修改用于创建 Prompt 的数据。QuestionAnswerAdvisor 实现通过追加与用户消息相关的上下文信息,启用检索增强生成(RAG)模式。

 

defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer)

该方法允许通过 Consumer 使用 AdvisorSpec 配置多个 Advisor。Advisor 可修改最终 Prompt 的生成数据。Consumer<AdvisorSpec> 支持以 lambda 形式添加 Advisor(如 QuestionAnswerAdvisor),该 Advisor 基于用户消息追加相关上下文信息,实现检索增强生成(RAG)模式。

注意,还可以通过不带 default 前缀的对应方法在运行时覆盖这些默认配置。方法如下:

options(ChatOptions chatOptions)

该方法对聊天客户端的全局配置选项进行设置。ChatOptions 类涵盖了众多配置项,像超时时间、重试策略、认证信息以及日志级别等都包含在内。一旦调用该方法设置了选项,这些选项就会应用于后续所有的聊天交互。

 

function(String name, String description, java.util.function.Function<I, O> function)

用于向聊天客户端注册一个可调用的函数。其中:

  • name 是函数的标识符,在后续的交互里会用到。

  • description 对函数的功能和用途进行了说明,聊天模型会依据这个说明来决定是否调用该函数。

  • function 是一个 Java 函数式接口,它定义了函数的具体实现逻辑,包括输入参数和返回值。

 

functions(String… functionNames)

该方法用于激活特定的函数。通过传入已注册函数的名称数组,能够限制在当前对话中可以使用的函数范围。这一功能很实用,比如在特定的业务场景下,只允许调用部分函数。

 

user(String text)

直接将字符串形式的用户消息发送给聊天模型。

 

user(Resource text)

可以发送文件资源,例如 JSON、XML 等格式的文件内容。

 

user(Consumer<UserSpec> userSpecConsumer)

通过 UserSpec 接口,能够更灵活地构建用户输入,比如添加元数据、设置参考资料等。

 

advisors(Advisor… advisor)

该方法用于注册一个或多个 Advisor 组件。Advisor 可以对聊天交互进行拦截和增强,例如添加日志记录、实现访问控制、进行数据转换等功能。多个 Advisor 会按照注册的顺序依次执行。

 

advisors(Consumer<AdvisorSpec> advisorSpecConsumer)

此方法提供了一种流式 API 风格的方式来注册 Advisor。通过 AdvisorSpec 接口,能够更便捷地对多个 Advisor 进行链式配置,让代码的可读性更强。


聊天记忆

ChatMemory 接口定义了聊天对话记忆的存储机制,提供添加消息、检索消息及清空对话历史的方法。

当前内置实现为:MessageWindowChatMemory。

MessageWindowChatMemory 是聊天记忆实现,维护最多指定数量(默认 20 条)的消息窗口。当消息超出限制时,旧消息会被移除(系统消息除外)。若添加新系统消息,则清除所有旧系统消息,确保始终保留最新上下文的同时控制内存占用。

MessageWindowChatMemory 基于 ChatMemoryRepository 抽象层实现,该抽象层提供多种聊天记忆存储方案,包括:

  • InMemoryChatMemoryRepository(内存存储)

  • JdbcChatMemoryRepository(JDBC 关系型数据库存储)

  • CassandraChatMemoryRepository(Cassandra 存储)

  • Neo4jChatMemoryRepository(Neo4j 图数据库存储)

更详细的聊天记忆功能后续将详细介绍。


实现说明

ChatClient API 的独特之处在于融合了命令式与响应式编程模型。通常应用会选择一种使用,而不是两者都使用:

  • 定制 Model 实现的 HTTP 客户端交互时,需同时配置 RestClient 和 WebClient。注意:由于 Spring Boot 3.4 的缺陷,必须设置 spring.http.client.factory=jdk 属性。默认值“reactor”会导致 ImageModel 等 AI 工作流异常。

  • 流式传输仅通过响应式技术栈支持,因此命令式应用需引入响应式依赖,例如:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
  • 非流式传输仅通过 Servlet 技术栈支持。因此响应式应用需引入 Servlet 依赖,例如:

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

并且部分调用将出现阻塞。

  • 工具调用采用命令式设计,会导致工作流阻塞。这将产生不完整的 Micrometer 观测数据。例如,ChatClient 的跨度(可能指相关操作的范围或持续时间等概念)和工具调用的跨度没有关联,正因如此,ChatClient 的跨度会保持不完整状态。在相关操作中,ChatClient 的操作范围或持续时间等方面,与工具调用的相应方面没有建立连接,导致 ChatClient 的这部分状态处于不完整,可能影响相关功能或数据分析等。

  • 系统中内置的 Advisor 组件,在处理标准调用时,会采用阻塞操作方式,即调用过程中会暂停程序的其他操作,直到该调用完成。而在处理流式调用时,Advisor 则会执行非阻塞操作,允许程序在流式调用进行的同时,继续执行其他任务。另外,针对 Advisor 的流式调用所使用的 Reactor Scheduler(一种用于管理异步操作调度的工具),开发人员可以通过各个 Advisor 类的 Builder(一种用于创建对象的设计模式,可方便地进行对象属性配置)来对其进行相关配置,以满足不同的业务需求。例如,开发人员可以根据实际业务场景,通过 Builder 来调整 Reactor Scheduler 的线程池大小、调度策略等参数,使得流式调用在异步执行过程中能更高效地利用系统资源。

参考页面:https://docs.spring.io/spring-ai/reference/api/chatclient.html

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号