聊天客户端 API

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

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

有关 ChatClient 中命令式与响应式编程模型结合使用的实现,请参阅本文档底部的 说明

Fluent API 提供构建 Prompt 组件的方法,这些组件将作为输入传递给 AI 模型。Prompt 包含指导 AI 模型输出和行为的指令文本。从 API 角度看,提示词由消息集合构成。

AI 模型处理两类主要消息:用户消息(直接来自用户的输入)和系统消息(由系统生成以引导对话)。

这些消息通常包含占位符,运行时根据用户输入进行替换,从而定制 AI 模型对用户输入的响应。

还可以指定一些 Prompt 选项,如要使用的 AI 模型名称,以及控制生成输出随机性/创造性的 temperature 参数。

创建 ChatClient

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

使用自动配置的 ChatClient.Builder

在最简单的使用场景中,Spring AI 通过 Spring Boot 自动配置生成 ChatClient.Builder Prototype Bean,可直接注入类中使用。以下是获取用户简单请求 String 响应的基础示例:

@RestController
class MyController {

    private final ChatClient chatClient;

    public MyController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

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

此示例中,userInput 为用户消息内容。call() 方法向 AI 模型发送请求, content() 方法以 String 形式返回模型响应。

多聊天模型协作

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

  • 不同任务类型选用不同模型(如复杂推理用高性能模型,简单任务用快速经济型模型)

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

  • 不同模型或配置的 A/B 测试

  • 根据用户偏好提供可选的模型

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

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

所有场景均需通过设置属性 spring.ai.chat.client.enabled=false 来禁用 ChatClient.Builder 自动配置。

该设置允许手动创建多个 ChatClient 实例。

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

本节涵盖常见场景:需创建多个使用相同底层模型类型但配置不同的 ChatClient 实例。

// 以编程式创建 ChatClient实 例
ChatModel myChatModel = ... // 已由Spring Boot自动配置完成
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:

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);
    }
}

随后可通过 @Qualifier 注解将这些 Bean 注入应用组件:

@Configuration
public class ChatClientExample {

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

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

            // Model selection
            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");
            }

            // Use the selected chat client
            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 端点

OpenAiApiOpenAiChatModel 类提供的 mutate() 方法,支持基于现有实例创建不同属性的变体,特别适用于需对接多个 OpenAI 兼容 API 的场景。

@Service
public class MultiModelService {

    private static final Logger logger = LoggerFactory.getLogger(MultiModelService.class);

    @Autowired
    private OpenAiChatModel baseChatModel;

    @Autowired
    private OpenAiApi baseOpenAiApi;

    public void multiClientFlow() {
        try {
            // Derive a new OpenAiApi for Groq (Llama3)
            OpenAiApi groqApi = baseOpenAiApi.mutate()
                .baseUrl("https://api.groq.com/openai")
                .apiKey(System.getenv("GROQ_API_KEY"))
                .build();

            // Derive a new OpenAiApi for OpenAI GPT-4
            OpenAiApi gpt4Api = baseOpenAiApi.mutate()
                .baseUrl("https://api.openai.com")
                .apiKey(System.getenv("OPENAI_API_KEY"))
                .build();

            // Derive a new OpenAiChatModel for Groq
            OpenAiChatModel groqModel = baseChatModel.mutate()
                .openAiApi(groqApi)
                .defaultOptions(OpenAiChatOptions.builder().model("llama3-70b-8192").temperature(0.5).build())
                .build();

            // Derive a new OpenAiChatModel for GPT-4
            OpenAiChatModel gpt4Model = baseChatModel.mutate()
                .openAiApi(gpt4Api)
                .defaultOptions(OpenAiChatOptions.builder().model("gpt-4").temperature(0.7).build())
                .build();

            // Simple prompt for both models
            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);
        }
    }
}

ChatClient Fluent 风格 API

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

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

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

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

ChatClient 响应

ChatClient API 通过 Fluent 式接口提供多 种AI 模型响应格式化方式。

返回 ChatResponse

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

  • 元数据:响应生成详情

  • 多响应支持Generations 数组(每个含独立元数据)

  • Token 统计:约 3/4 单词计为 1 个 Token(计费依据)

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

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

返回实体

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

例如,给定一个 Java record:

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

如下所示,通过 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 中参数的使用,我们将在后面的文档中详细讨论。

var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorsFilms>>() {});

Flux<String> flux = this.chatClient.prompt()
    .user(u -> u.text("""
                        Generate the filmography for a random actor.
                        {format}
                      """)
            .param("format", this.converter.getFormat()))
    .stream()
    .content();

String content = this.flux.collectList().block().stream().collect(Collectors.joining());

List<ActorFilms> actorFilms = this.converter.convert(this.content);

提示模版

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

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"))
    .call()
    .content();

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

Spring AI 还提供 NoOpTemplateRenderer,用于无需模板处理的场景。

Spring AI 还提供 NoOpTemplateRenderer 实现。

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

如需改用其他模板引擎,可直接向 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() 返回值

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

  • 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):通过 StructuredOutputConverterString 转为实体类型。

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

stream() 返回值

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

  • 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 实例。

@Configuration
class Config {

    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a Pirate")
                .build();
    }

}

并通过 @RestController 调用:

@RestController
class AIController {

	private final ChatClient chatClient;

	AIController(ChatClient chatClient) {
		this.chatClient = chatClient;
	}

	@GetMapping("/ai/simple")
	public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
		return Map.of("completion", this.chatClient.prompt().user(message).call().content());
	}
}

通过 curl 调用应用端点时,返回结果如下:

❯ curl localhost:8080/ai/simple
{"completion":"Why did the pirate go to the comedy club? To hear some arrr-rated jokes! Arrr, matey!"}

带参数的默认系统消息

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

@Configuration
class Config {

    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a {voice}")
                .build();
    }

}
@RestController
class AIController {
	private final ChatClient chatClient;

	AIController(ChatClient chatClient) {
		this.chatClient = chatClient;
	}

	@GetMapping("/ai")
	Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message, String voice) {
		return Map.of("completion",
				this.chatClient.prompt()
						.system(sp -> sp.param("voice", voice))
						.user(message)
						.call()
						.content());
	}

}

通过 httpie 调用应用端点时,返回结果如下:

http localhost:8080/ai voice=='Robert DeNiro'
{
    "completion": "You talkin' to me? Okay, here's a joke for ya: Why couldn't the bicycle stand up by itself? Because it was two tired! Classic, right?"
}

其他默认配置

可在 ChatClient.Builder 层级指定默认提示词配置。

  • defaultOptions(ChatOptions chatOptions):支持传入 ChatOptions 定义的通用选项或 OpenAiChatOptions 等模型专属选项。详见各模型 ChatOptions 实现的 JavaDoc 文档。

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

    • name:函数在用户文本中的引用标识

    • description:描述函数用途,辅助AI模型精准选择函数

    • function:模型按需调用的 Java Function 实例

  • defaultFunctions(String…​ functionNames): 应用上下文中定义的 java.util.Function 的 Bean 名称列表。

  • defaultUser(String text)defaultUser(Resource text)defaultUser(Consumer<UserSpec> userSpecConsumer): 这些方法用于定义用户消息。Consumer<UserSpec> 支持通过 lambda 指定用户消息及默认参数。

  • defaultAdvisors(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)

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

  • functions(String…​ functionNames)

  • user(String text)user(Resource text)user(Consumer<UserSpec> userSpecConsumer)

  • advisors(Advisor…​ advisor)

  • advisors(Consumer<AdvisorSpec> advisorSpecConsumer)

Advisor

Advisor API 为 Spring 应用中的 AI 驱动交互提供灵活强大的拦截、修改和增强能力。

调用 AI 模型时,常见模式是在用户消息基础上追加或增强上下文数据。

此类上下文数据可分为多种类型,常见包括:

  • 自有数据:AI 模型未训练过的数据。即使模型接触过类似数据,追加的上下文数据仍会优先影响响应生成。

  • 对话历史:聊天模型 API 是无状态的。若告知 AI 模型你的姓名,后续交互中它不会记住。必须每次请求都发送对话历史,确保生成响应时考虑先前交互。

ChatClient 中的 Advisor 配置

ChatClient Fluent 式 API 提供 AdvisorSpec 接口用于配置 Advisor。该接口支持添加参数、批量设置参数以及向链中添加单个或多个 Advisor。

interface AdvisorSpec {
    AdvisorSpec param(String k, Object v);
    AdvisorSpec params(Map<String, Object> p);
    AdvisorSpec advisors(Advisor... advisors);
    AdvisorSpec advisors(List<Advisor> advisors);
}
Advisor 加入链的顺序至关重要,它决定了它们的执行顺序。每个 Advisor 都会以某种方式修改提示词或上下文,且一个 Advisor 所做的更改会传递给链中的下一个 Advisor。
ChatClient.builder(chatModel)
    .build()
    .prompt()
    .advisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build(),
        QuestionAnswerAdvisor.builder(vectorStore).build()
    )
    .user(userText)
    .call()
    .content();

在此配置中,MessageChatMemoryAdvisor 将首先执行,将对话历史添加到提示词中。随后,QuestionAnswerAdvisor 将基于用户问题和添加的对话历史执行搜索,可能提供更相关的结果。

检索增强生成

日志

SimpleLoggerAdvisor 是一个记录 ChatClient 请求和响应数据的 Advisor,可用于调试和监控 AI 交互。

Spring AI 支持对 LLM 和向量存储交互的可观测性。详情请参阅 可观测性指南

启用日志记录需在创建 ChatClient 时向 Advisor 链添加 SimpleLoggerAdvisor,建议将其添加至链的末端:

ChatResponse response = ChatClient.create(chatModel).prompt()
        .advisors(new SimpleLoggerAdvisor())
        .user("Tell me a joke?")
        .call()
        .chatResponse();

要查看日志,请将 advisor 包的日志级别设为 DEBUG

logging.level.org.springframework.ai.chat.client.advisor=DEBUG

将此配置添加到 application.propertiesapplication.yaml 文件中。

可通过以下构造函数自定义 AdvisedRequestChatResponse 的日志记录内容:

SimpleLoggerAdvisor(
    Function<AdvisedRequest, String> requestToString,
    Function<ChatResponse, String> responseToString
)

示例用法:

SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
    request -> "Custom request: " + request.userText,
    response -> "Custom response: " + response.getResult()
);

这允许你根据具体需求定制日志信息。

在生产环境中需谨慎记录敏感信息。

聊天记忆

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

当前内置实现为:MessageWindowChatMemory

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

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

  • InMemoryChatMemoryRepository(内存存储)

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

  • CassandraChatMemoryRepository(Cassandra 存储)

  • Neo4jChatMemoryRepository(Neo4j 图数据库存储)

详见 聊天记忆功能 文档获取详细说明和使用示例。

实现说明

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

  • 定制 Model 实现的 HTTP 客户端交互时,需同时配置 RestClientWebClient

由于 Spring Boot 3.4 的缺陷,必须设置 spring.http.client.factory=jdk 属性。默认值 "reactor" 会导致 ImageModel 等 AI 工作流异常。

  • 流式传输仅通过响应式技术栈支持。因此命令式应用需引入响应式依赖(如 spring-boot-starter-webflux)。

  • 非流式传输仅通过 Servlet 技术栈支持。因此响应式应用需引入 Servlet 依赖(如 spring-boot-starter-web),且需知悉部分调用将出现阻塞。

  • 工具调用采用命令式设计,会导致工作流阻塞。这将产生不完整的 Micrometer 观测数据(例如 ChatClient 的追踪 span 与工具调用的 span 未关联,且前者因此保持未完成状态)。

  • 内置 Advisor 对标准调用执行阻塞操作,对流式调用执行非阻塞操作。用于 Advisor 流式调用的 Reactor Scheduler 可通过各 Advisor 类的 Builder 进行配置。