> ## Documentation Index
> Fetch the complete documentation index at: https://javaai.pig4cloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 聊天客户端 API

`ChatClient` 提供了一个流畅的 API 用于与 AI 模型进行通信。
它同时支持同步和流式编程模型。

<Note>
  请参阅本文档底部的[实现说明](#implementation-notes)，了解 `ChatClient` 中命令式和响应式编程模型结合使用的相关信息。
</Note>

流畅的 API 提供了构建 [Prompt](/spring4ai/api/prompt) 各个组成部分的方法，这些部分作为输入传递给 AI 模型。
`Prompt` 包含指导 AI 模型输出和行为的指令文本。从 API 的角度来看，提示由消息集合组成。

AI 模型处理两种主要类型的消息：用户消息（来自用户的直接输入）和系统消息（由系统生成以指导对话）。

这些消息通常包含占位符，这些占位符在运行时根据用户输入进行替换，以根据用户输入自定义 AI 模型的响应。

还有一些可以指定的提示选项，例如要使用的 AI 模型的名称和控制生成输出随机性或创造性的温度设置。

## 创建 ChatClient

`ChatClient` 使用 `ChatClient.Builder` 对象创建。
您可以为任何 [ChatModel](/spring4ai/api/chatmodel) Spring Boot 自动配置获取自动配置的 `ChatClient.Builder` 实例，或以编程方式创建一个。

### 使用自动配置的 ChatClient.Builder

在最简单的用例中，Spring AI 提供 Spring Boot 自动配置，为您创建一个原型 `ChatClient.Builder` bean，您可以将其注入到您的类中。
以下是一个简单的示例，展示如何获取简单用户请求的 `String` 响应：

```java theme={"system"}
@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();
    }
}
```

在这个简单的示例中，用户输入设置了用户消息的内容。
`call()` 方法向 AI 模型发送请求，`content()` 方法将 AI 模型的响应作为 `String` 返回。

### 使用多个聊天模型

在单个应用程序中使用多个聊天模型有几种场景：

<AccordionGroup>
  <Accordion title="多模型场景" icon="lightbulb">
    * 为不同类型的任务使用不同的模型（例如，使用强大的模型进行复杂推理，使用更快、更便宜的模型进行简单任务）
    * 当一个模型服务不可用时实现回退机制
    * 对不同的模型或配置进行 A/B 测试
    * 根据用户偏好提供模型选择
    * 组合专业模型（一个用于代码生成，另一个用于创意内容等）
  </Accordion>
</AccordionGroup>

默认情况下，Spring AI 自动配置单个 `ChatClient.Builder` bean。但是，您可能需要在应用程序中使用多个聊天模型。以下是处理这种情况的方法：

在所有情况下，您都需要通过设置属性 `spring.ai.chat.client.enabled=false` 来禁用 `ChatClient.Builder` 自动配置。

这允许您手动创建多个 `ChatClient` 实例。

#### 使用单个模型类型的多个 ChatClient

本节介绍一个常见用例，您需要创建多个使用相同底层模型类型但具有不同配置的 ChatClient 实例。

```java theme={"system"}
// 以编程方式创建 ChatClient 实例
ChatModel myChatModel = ... // 已由 Spring Boot 自动配置
ChatClient chatClient = ChatClient.create(myChatModel);

// 或使用构建器以获得更多控制
ChatClient.Builder builder = ChatClient.builder(myChatModel);
ChatClient customChatClient = builder
    .defaultSystemPrompt("你是一个乐于助人的助手。")
    .build();
```

#### 不同模型类型的 ChatClient

当使用多个 AI 模型时，您可以为每个模型定义单独的 `ChatClient` bean：

```java theme={"system"}
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 注入到应用程序组件中：

```java theme={"system"}
@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;
            
            // 模型选择
            System.out.println("\n选择您的 AI 模型：");
            System.out.println("1. OpenAI");
            System.out.println("2. Anthropic");
            System.out.print("输入您的选择（1 或 2）：");
            
            String choice = scanner.nextLine().trim();
            
            if (choice.equals("1")) {
                chat = openAiChatClient;
                System.out.println("使用 OpenAI 模型");
            } else {
                chat = anthropicChatClient;
                System.out.println("使用 Anthropic 模型");
            }
            
            // 使用选定的聊天客户端
            System.out.print("\n输入您的问题：");
            String input = scanner.nextLine();
            String response = chat.prompt(input).call().content();
            System.out.println("助手：" + response);
            
            scanner.close();
        };
    }
}
```

#### 多个 OpenAI 兼容的 API 端点

`OpenAiApi` 和 `OpenAiChatModel` 类提供了 `mutate()` 方法，允许您创建具有不同属性的现有实例的变体。这在需要处理多个 OpenAI 兼容 API 时特别有用。

```java theme={"system"}
@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 {
            // 为 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 = "法国的首都是什么？";
            
            String groqResponse = ChatClient.builder(groqModel).build().prompt(prompt).call().content();
            String gpt4Response = ChatClient.builder(gpt4Model).build().prompt(prompt).call().content();
            
            logger.info("Groq (Llama3) 响应：{}", groqResponse);
            logger.info("OpenAI GPT-4 响应：{}", gpt4Response);
        }
        catch (Exception e) {
            logger.error("多客户端流程中的错误", e);
        }
    }
}
```

## ChatClient 流畅 API

`ChatClient` 流畅 API 允许您使用重载的 `prompt` 方法以三种不同的方式创建提示：

<CardGroup cols={3}>
  <Card title="prompt()" icon="code">
    这个无参数方法让您开始使用流畅 API，允许您构建用户、系统和其他提示部分。
  </Card>

  <Card title="prompt(Prompt prompt)" icon="code-bracket">
    这个方法接受 `Prompt` 参数，让您传入使用 Prompt 的非流畅 API 创建的 `Prompt` 实例。
  </Card>

  <Card title="prompt(String content)" icon="text">
    这是一个类似于前一个重载的便捷方法。它接受用户的文本内容。
  </Card>
</CardGroup>

## ChatClient 响应

`ChatClient` API 提供了几种使用流畅 API 格式化 AI 模型响应的方法。

### 返回 ChatResponse

AI 模型的响应是一个由 [ChatResponse](/spring4ai/api/chatmodel#chatresponse) 类型定义的丰富结构。
它包括有关如何生成响应的元数据，还可以包含多个响应，称为 [Generation](/spring4ai/api/chatmodel#generation)s，每个都有自己的元数据。
元数据包括用于创建响应的令牌数量（每个令牌大约是一个单词的 3/4）。
这些信息很重要，因为托管 AI 模型根据每个请求使用的令牌数量收费。

下面通过调用 `call()` 方法后的 `chatResponse()` 方法展示了返回包含元数据的 `ChatResponse` 对象的示例：

```java theme={"system"}
ChatResponse chatResponse = chatClient.prompt()
    .user("给我讲个笑话")
    .call()
    .chatResponse();
```

### 返回实体

您通常希望返回一个从返回的 `String` 映射的实体类。
`entity()` 方法提供了这个功能。

例如，给定 Java 记录：

```java theme={"system"}
record ActorFilms(String actor, List<String> movies) {}
```

您可以使用 `entity()` 方法轻松地将 AI 模型的输出映射到这个记录，如下所示：

```java theme={"system"}
ActorFilms actorFilms = chatClient.prompt()
    .user("生成一个随机演员的电影作品。")
    .call()
    .entity(ActorFilms.class);
```

还有一个重载的 `entity` 方法，签名为 `entity(ParameterizedTypeReference<T> type)`，允许您指定泛型列表等类型：

```java theme={"system"}
List<ActorFilms> actorFilms = chatClient.prompt()
    .user("生成汤姆·汉克斯和比尔·默瑞的 5 部电影作品。")
    .call()
    .entity(new ParameterizedTypeReference<List<ActorFilms>>() {});
```

### 流式响应

`stream()` 方法让您获得异步响应，如下所示：

```java theme={"system"}
Flux<String> output = chatClient.prompt()
    .user("给我讲个笑话")
    .stream()
    .content();
```

您还可以使用方法 `Flux<ChatResponse> chatResponse()` 流式传输 `ChatResponse`。

在未来，我们将提供一个便捷方法，让您使用响应式 `stream()` 方法返回 Java 实体。
同时，您应该使用 [结构化输出转换器](/spring4ai/api/structured-output-converter#structuredoutputconverter) 显式转换聚合响应，如下所示。
这也演示了流畅 API 中参数的使用，这将在文档的后面部分详细讨论。

```java theme={"system"}
var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorsFilms>>() {});

Flux<String> flux = this.chatClient.prompt()
    .user(u -> u.text("""
                        生成一个随机演员的电影作品。
                        {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` 流畅 API 允许您提供带有变量的用户和系统文本作为模板，这些变量在运行时被替换。

```java theme={"system"}
String answer = ChatClient.create(chatModel).prompt()
    .user(u -> u
            .text("告诉我 5 部由 {composer} 作曲的电影原声带")
            .param("composer", "John Williams"))
    .call()
    .content();
```

在内部，ChatClient 使用 `PromptTemplate` 类来处理用户和系统文本，并使用给定的 `TemplateRenderer` 实现替换变量。
默认情况下，Spring AI 使用 `StTemplateRenderer` 实现，它基于 Terence Parr 开发的开源 [StringTemplate](https://www.stringtemplate.org/) 引擎。

Spring AI 还为不需要模板处理的情况提供了 `NoOpTemplateRenderer`。

<Note>
  直接在 `ChatClient` 上配置的 `TemplateRenderer`（通过 `.templateRenderer()`）仅适用于直接在 `ChatClient` 构建器链中定义的提示内容（例如，通过 `.user()`、`.system()`）。
  它不会影响 [Advisors](/spring4ai/api/retrieval-augmented-generation#_questionansweradvisor) 内部使用的模板，如 `QuestionAnswerAdvisor`，它们有自己的模板自定义机制（参见[自定义 Advisor 模板](/spring4ai/api/retrieval-augmented-generation#_custom_template)）。
</Note>

如果您想使用不同的模板引擎，您可以直接向 ChatClient 提供 `TemplateRenderer` 接口的自定义实现。您也可以继续使用默认的 `StTemplateRenderer`，但使用自定义配置。

例如，默认情况下，模板变量由 `{}` 语法标识。如果您计划在提示中包含 JSON，您可能想使用不同的语法以避免与 JSON 语法冲突。例如，您可以使用 `<` 和 `>` 分隔符。

```java theme={"system"}
String answer = ChatClient.create(chatModel).prompt()
    .user(u -> u
            .text("告诉我 5 部由 <composer> 作曲的电影原声带")
            .param("composer", "John Williams"))
    .templateRenderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
    .call()
    .content();
```

## call() 返回值

在 `ChatClient` 上指定 `call()` 方法后，响应类型有几种不同的选项：

<CardGroup cols={2}>
  <Card title="content()" icon="code">
    返回响应的 String 内容
  </Card>

  <Card title="chatResponse()" icon="reply">
    返回包含多个生成以及有关响应的元数据的 `ChatResponse` 对象，例如用于创建响应的令牌数量
  </Card>

  <Card title="chatClientResponse()" icon="reply-all">
    返回一个 `ChatClientResponse` 对象，其中包含 `ChatResponse` 对象和 ChatClient 执行上下文，让您可以访问在执行 advisors 期间使用的其他数据（例如，在 RAG 流程中检索的相关文档）
  </Card>

  <Card title="entity()" icon="object-group">
    使用以下方法之一返回 Java 类型：

    <ul>
      <li>`entity(ParameterizedTypeReference<T> type)`：用于返回实体类型的 `Collection`</li>
      <li>`entity(Class<T> type)`：用于返回特定的实体类型</li>
      <li>`entity(StructuredOutputConverter<T> structuredOutputConverter)`：用于指定 `StructuredOutputConverter` 的实例，将 `String` 转换为实体类型</li>
    </ul>
  </Card>
</CardGroup>

您也可以调用 `stream()` 方法而不是 `call()`。

## stream() 返回值

在 `ChatClient` 上指定 `stream()` 方法后，响应类型有几种选项：

<CardGroup cols={3}>
  <Card title="content()" icon="code">
    返回 AI 模型生成的字符串的 `Flux<String>`
  </Card>

  <Card title="chatResponse()" icon="reply">
    返回 `Flux<ChatResponse>` 对象，其中包含有关响应的其他元数据
  </Card>

  <Card title="chatClientResponse()" icon="reply-all">
    返回 `Flux<ChatClientResponse>` 对象，其中包含 `ChatResponse` 对象和 ChatClient 执行上下文，让您可以访问在执行 advisors 期间使用的其他数据（例如，在 RAG 流程中检索的相关文档）
  </Card>
</CardGroup>

## 使用默认值

在 `@Configuration` 类中创建带有默认系统文本的 `ChatClient` 可以简化运行时代码。
通过设置默认值，您只需要在调用 `ChatClient` 时指定用户文本，无需在运行时代码路径中为每个请求设置系统文本。

### 默认系统文本

在以下示例中，我们将配置系统文本始终以海盗的声音回复。
为了避免在运行时代码中重复系统文本，我们将在 `@Configuration` 类中创建一个 `ChatClient` 实例。

```java theme={"system"}
@Configuration
class Config {

    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("你是一个友好的聊天机器人，用海盗的声音回答问题")
                .build();
    }

}
```

以及一个调用它的 `@RestController`：

```java theme={"system"}
@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 = "给我讲个笑话") String message) {
        return Map.of("completion", this.chatClient.prompt().user(message).call().content());
    }
}
```

当通过 curl 调用应用程序端点时，结果是：

```bash theme={"system"}
❯ curl localhost:8080/ai/simple
{"completion":"为什么海盗去喜剧俱乐部？为了听一些 arrr-rated 笑话！啊，伙计！"}
```

### 带参数的默认系统文本

在以下示例中，我们将在系统文本中使用占位符，以便在运行时而不是设计时指定完成的声音。

```java theme={"system"}
@Configuration
class Config {

    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("你是一个友好的聊天机器人，用 {voice} 的声音回答问题")
                .build();
    }

}
```

```java theme={"system"}
@RestController
class AIController {
    private final ChatClient chatClient;

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

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

}
```

当通过 httpie 调用应用程序端点时，结果是：

```bash theme={"system"}
http localhost:8080/ai voice=='Robert DeNiro'
{
    "completion": "你在跟我说话吗？好吧，给你讲个笑话：为什么自行车不能自己站起来？因为它太累了！经典，对吧？"
}
```

### 其他默认值

在 `ChatClient.Builder` 级别，您可以指定默认提示配置。

<AccordionGroup>
  <Accordion title="defaultOptions(ChatOptions chatOptions)" icon="gear">
    传入 `ChatOptions` 类中定义的便携选项或特定于模型的选项，如 `OpenAiChatOptions` 中的选项。有关特定于模型的 `ChatOptions` 实现的更多信息，请参阅 JavaDocs。
  </Accordion>

  <Accordion title="defaultFunction(String name, String description, java.util.function.Function<I, O> function)" icon="code">
    `name` 用于在用户文本中引用函数。`description` 解释函数的用途，帮助 AI 模型选择正确的函数以获得准确的响应。`function` 参数是模型在需要时将执行的 Java 函数实例。
  </Accordion>

  <Accordion title="defaultFunctions(String... functionNames)" icon="code-branch">
    应用程序上下文中定义的 `java.util.Function` 的 bean 名称。
  </Accordion>

  <Accordion title="defaultUser(...)" icon="user">
    这些方法让您定义用户文本：

    <ul>
      <li>`defaultUser(String text)`</li>
      <li>`defaultUser(Resource text)`</li>
      <li>`defaultUser(Consumer<UserSpec> userSpecConsumer)`：`Consumer<UserSpec>` 允许您使用 lambda 来指定用户文本和任何默认参数。</li>
    </ul>
  </Accordion>

  <Accordion title="defaultAdvisors(Advisor... advisor)" icon="user-gear">
    Advisors 允许修改用于创建 `Prompt` 的数据。`QuestionAnswerAdvisor` 实现通过附加与用户文本相关的上下文信息来启用 `Retrieval Augmented Generation` 模式。
  </Accordion>

  <Accordion title="defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer)" icon="gears">
    此方法允许您定义一个 `Consumer` 来使用 `AdvisorSpec` 配置多个 advisors。Advisors 可以修改用于创建最终 `Prompt` 的数据。`Consumer<AdvisorSpec>` 让您指定一个 lambda 来添加 advisors，如 `QuestionAnswerAdvisor`，它通过附加与用户文本相关的相关上下文信息来支持 `Retrieval Augmented Generation`。
  </Accordion>
</AccordionGroup>

您可以使用不带 `default` 前缀的相应方法在运行时覆盖这些默认值。

<CardGroup cols={3}>
  <Card title="options(ChatOptions chatOptions)" icon="gear">
    覆盖默认选项
  </Card>

  <Card title="function(...)" icon="code">
    覆盖默认函数
  </Card>

  <Card title="functions(String... functionNames)" icon="code-branch">
    覆盖默认函数
  </Card>

  <Card title="user(...)" icon="user">
    覆盖默认用户
  </Card>

  <Card title="advisors(Advisor... advisor)" icon="user-gear">
    覆盖默认 advisors
  </Card>

  <Card title="advisors(Consumer<AdvisorSpec>...)" icon="gears">
    使用 consumer 覆盖默认 advisors
  </Card>
</CardGroup>

## Advisors

[Advisors API](/spring4ai/api/advisors) 提供了一种灵活而强大的方式来拦截、修改和增强 Spring 应用程序中的 AI 驱动交互。

在调用带有用户文本的 AI 模型时，一个常见的模式是附加或增强带有上下文数据的提示。

这种上下文数据可以是不同类型。常见类型包括：

<CardGroup cols={2}>
  <Card title="您自己的数据" icon="database">
    这是 AI 模型尚未训练过的数据。即使模型见过类似的数据，附加的上下文数据在生成响应时也会优先考虑。
  </Card>

  <Card title="对话历史" icon="comments">
    聊天模型的 API 是无状态的。如果您告诉 AI 模型您的名字，它不会在后续交互中记住它。必须随每个请求发送对话历史，以确保在生成响应时考虑先前的交互。
  </Card>
</CardGroup>

### ChatClient 中的 Advisor 配置

ChatClient 流畅 API 提供了 `AdvisorSpec` 接口用于配置 advisors。这个接口提供了添加参数、一次设置多个参数以及向链中添加一个或多个 advisors 的方法。

```java theme={"system"}
interface AdvisorSpec {
    AdvisorSpec param(String k, Object v);
    AdvisorSpec params(Map<String, Object> p);
    AdvisorSpec advisors(Advisor... advisors);
    AdvisorSpec advisors(List<Advisor> advisors);
}
```

<Warning>
  advisors 添加到链中的顺序至关重要，因为它决定了它们的执行顺序。每个 advisor 都以某种方式修改提示或上下文，一个 advisor 所做的更改会传递给链中的下一个。
</Warning>

```java theme={"system"}
ChatClient.builder(chatModel)
    .build()
    .prompt()
    .advisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build(),
        QuestionAnswerAdvisor.builder(vectorStore).build()
    )
    .user(userText)
    .call()
    .content();
```

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

[了解 Question Answer Advisor](/spring4ai/api/retrieval-augmented-generation#_questionansweradvisor)

### 检索增强生成

请参阅[检索增强生成](/spring4ai/api/retrieval-augmented-generation)指南。

### 日志记录

`SimpleLoggerAdvisor` 是一个记录 `ChatClient` 的 `request` 和 `response` 数据的 advisor。
这对于调试和监控您的 AI 交互很有用。

<Tip>
  Spring AI 支持 LLM 和向量存储交互的可观察性。有关更多信息，请参阅[可观察性](/spring4ai/observability)指南。
</Tip>

要启用日志记录，在创建 ChatClient 时将 `SimpleLoggerAdvisor` 添加到 advisor 链中。
建议将其添加到链的末尾：

```java theme={"system"}
ChatResponse response = ChatClient.create(chatModel).prompt()
        .advisors(new SimpleLoggerAdvisor())
        .user("给我讲个笑话？")
        .call()
        .chatResponse();
```

要查看日志，将 advisor 包的日志级别设置为 `DEBUG`：

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

将此添加到您的 `application.properties` 或 `application.yaml` 文件中。

您可以通过使用以下构造函数自定义从 `AdvisedRequest` 和 `ChatResponse` 记录的哪些数据：

```java theme={"system"}
SimpleLoggerAdvisor(
    Function<AdvisedRequest, String> requestToString,
    Function<ChatResponse, String> responseToString
)
```

使用示例：

```java theme={"system"}
SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
    request -> "自定义请求：" + request.userText,
    response -> "自定义响应：" + response.getResult()
);
```

这允许您根据特定需求定制记录的信息。

<Tip>
  在生产环境中记录敏感信息时要小心。
</Tip>

## 聊天内存

`ChatMemory` 接口表示聊天对话内存的存储。它提供了向对话添加消息、从对话中检索消息以及清除对话历史的方法。

目前有一个内置实现：`MessageWindowChatMemory`。

`MessageWindowChatMemory` 是一个聊天内存实现，它维护一个最多指定最大大小（默认：20 条消息）的消息窗口。当消息数量超过此限制时，较旧的消息会被逐出，但系统消息会被保留。如果添加了新的系统消息，所有先前的系统消息都会从内存中删除。这确保了对话始终可以使用最新的上下文，同时保持内存使用有界。

`MessageWindowChatMemory` 由 `ChatMemoryRepository` 抽象支持，它提供了聊天对话内存的存储实现。有几种实现可用，包括 `InMemoryChatMemoryRepository`、`JdbcChatMemoryRepository`、`CassandraChatMemoryRepository` 和 `Neo4jChatMemoryRepository`。

有关更多详细信息和用法示例，请参阅[聊天内存](/spring4ai/api/chat-memory)文档。

## 实现说明

`ChatClient` 中命令式和响应式编程模型的结合使用是 API 的一个独特方面。
通常，应用程序要么是响应式的，要么是命令式的，但不是两者都是。

<AccordionGroup>
  <Accordion title="HTTP 客户端配置" icon="globe">
    在自定义 Model 实现的 HTTP 客户端交互时，必须同时配置 RestClient 和 WebClient。

    <Warning>
      由于 Spring Boot 3.4 中的一个错误，必须设置 "spring.http.client.factory=jdk" 属性。否则，它默认设置为 "reactor"，这会破坏某些 AI 工作流，如 ImageModel。
    </Warning>
  </Accordion>

  <Accordion title="流式支持" icon="stream">
    * 流式处理仅通过响应式堆栈支持。命令式应用程序必须包含响应式堆栈（例如 spring-boot-starter-webflux）。
    * 非流式处理仅通过 Servlet 堆栈支持。响应式应用程序必须包含 Servlet 堆栈（例如 spring-boot-starter-web），并期望某些调用是阻塞的。
  </Accordion>

  <Accordion title="工具调用" icon="wrench">
    工具调用是命令式的，导致阻塞工作流。这也导致部分/中断的 Micrometer 观察（例如，ChatClient spans 和工具调用 spans 没有连接，第一个因此保持不完整）。
  </Accordion>

  <Accordion title="Advisors" icon="user-gear">
    内置的 advisors 对标准调用执行阻塞操作，对流式调用执行非阻塞操作。用于 advisor 流式调用的 Reactor Scheduler 可以通过每个 Advisor 类上的 Builder 进行配置。
  </Accordion>
</AccordionGroup>

<Card title="文档有误？请协助编辑" icon="pencil" href="https://github.com/javaai-pig4cloud-com/javaai-docs/edit/main/spring-ai/api/chatclient.mdx">
  发现文档问题？点击此处直接在 GitHub 上编辑并提交 PR，帮助我们改进文档！
</Card>
