> ## 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

Spring AI 增强器 API 提供了一种灵活而强大的方式来拦截、修改和增强 Spring 应用程序中的 AI 驱动交互。
通过利用增强器 API，开发者可以创建更复杂、可重用和可维护的 AI 组件。

主要优势包括封装重复出现的生成式 AI 模式、转换发送到大型语言模型（LLM） 的数据，以及提供跨各种模型和用例的可移植性。

您可以使用 [ChatClient API](/spring4ai/api/chatclient#advisor-configuration-in-chatclient) 配置现有增强器，如下例所示：

```java theme={"system"}
var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build(), // chat-memory 增强器
        QuestionAnswerAdvisor.builder((vectorStore).builder() // RAG 增强器
    )
    .build();

var conversationId = "678";

String response = this.chatClient.prompt()
    // 在运行时设置增强器参数	
    .advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId))
    .user(userText)
    .call()
	.content();
```

建议在构建时使用 builder 的 `defaultAdvisors()` 方法注册增强器。

增强器也参与可观察性堆栈，因此您可以查看与其执行相关的指标和跟踪。

* [了解问题回答增强器](/spring4ai/api/retrieval-augmented-generation#questionansweradvisor)
* [了解聊天内存增强器](/spring4ai/api/chat-memory#memory-in-chat-client)

## 核心组件

API 包含用于非流式场景的 `CallAroundAdvisor` 和 `CallAroundAdvisorChain`，以及用于流式场景的 `StreamAroundAdvisor` 和 `StreamAroundAdvisorChain`。
它还包括 `AdvisedRequest` 用于表示未密封的 Prompt 请求，`AdvisedResponse` 用于 Chat Completion 响应。两者都包含一个 `advise-context` 用于在增强器链中共享状态。

<Frame caption="Advisors API 类">
  <img src="https://mintcdn.com/pig/43t3K3HuqTJIvmwg/images/advisors-api-classes.jpg?fit=max&auto=format&n=43t3K3HuqTJIvmwg&q=85&s=134386af04ef2da2f9c1cde929aeb90d" width="600" data-path="images/advisors-api-classes.jpg" />
</Frame>

`nextAroundCall()` 和 `nextAroundStream()` 是关键增强器方法，通常执行诸如检查未密封的 Prompt 数据、自定义和增强 Prompt 数据、调用增强器链中的下一个实体、可选地阻止请求、检查聊天完成响应以及抛出异常以指示处理错误等操作。

此外，`getOrder()` 方法确定增强器在链中的顺序，而 `getName()` 提供唯一的增强器名称。

由 Spring AI 框架创建的增强器链允许按 `getOrder()` 值排序的多个增强器顺序调用。
较低的值首先执行。
最后一个增强器（自动添加）将请求发送到 LLM。

以下流程图说明了增强器链与聊天模型之间的交互：

<Frame caption="Advisors API 流程">
  <img src="https://mintcdn.com/pig/43t3K3HuqTJIvmwg/images/advisors-flow.jpg?fit=max&auto=format&n=43t3K3HuqTJIvmwg&q=85&s=24cb3d83382479d8f2843c922fc527c0" width="400" data-path="images/advisors-flow.jpg" />
</Frame>

<Steps>
  <Step title="创建请求">
    Spring AI 框架从用户的 `Prompt` 创建一个 `AdvisedRequest`，同时创建一个空的 `AdvisorContext` 对象。
  </Step>

  <Step title="处理请求">
    链中的每个增强器处理请求，可能会修改它。或者，它可以选择通过不调用下一个实体来阻止请求。在后一种情况下，增强器负责填写响应。
  </Step>

  <Step title="发送请求">
    框架提供的最后一个增强器将请求发送到 `Chat Model`。
  </Step>

  <Step title="创建响应">
    聊天模型的响应然后通过增强器链传回并转换为 `AdvisedResponse`。后者包括共享的 `AdvisorContext` 实例。
  </Step>

  <Step title="处理响应">
    每个增强器可以处理或修改响应。
  </Step>

  <Step title="返回响应">
    通过提取 `ChatCompletion` 将最终的 `AdvisedResponse` 返回给客户端。
  </Step>
</Steps>

### 增强器顺序

增强器在链中的执行顺序由 `getOrder()` 方法决定。需要理解的关键点：

<AccordionGroup>
  <Accordion title="执行顺序" icon="sort-numeric-down">
    * 具有较低顺序值的增强器首先执行。
    * 增强器链作为堆栈运行：
      * 链中的第一个增强器首先处理请求。
      * 它也是最后一个处理响应的。
  </Accordion>

  <Accordion title="控制执行顺序" icon="sliders-h">
    * 要控制执行顺序：
      * 将顺序设置为接近 `Ordered.HIGHEST_PRECEDENCE` 以确保增强器在链中首先执行（首先处理请求，最后处理响应）。
      * 将顺序设置为接近 `Ordered.LOWEST_PRECEDENCE` 以确保增强器在链中最后执行（最后处理请求，首先处理响应）。
    * 较高的值被解释为较低的优先级。
    * 如果多个增强器具有相同的顺序值，它们的执行顺序不能保证。
  </Accordion>
</AccordionGroup>

<Note>
  执行顺序和顺序值之间的表面矛盾是由于增强器链的堆栈性质：

  * 具有最高优先级（最低顺序值）的增强器被添加到堆栈顶部。
  * 当堆栈展开时，它将首先处理请求。
  * 当堆栈重绕时，它将最后处理响应。
</Note>

作为提醒，以下是 Spring `Ordered` 接口的语义：

```java theme={"system"}
public interface Ordered {

    /**
     * 最高优先级值的常量。
     * @see java.lang.Integer#MIN_VALUE
     */
    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

    /**
     * 最低优先级值的常量。
     * @see java.lang.Integer#MAX_VALUE
     */
    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

    /**
     * 获取此对象的顺序值。
     * <p>较高的值被解释为较低的优先级。因此，
     * 具有最低值的对象具有最高优先级（有点类似于 Servlet {@code load-on-startup} 值）。
     * <p>相同的顺序值将导致受影响对象的任意排序位置。
     * @return 顺序值
     * @see #HIGHEST_PRECEDENCE
     * @see #LOWEST_PRECEDENCE
     */
    int getOrder();
}
```

<Tip>
  对于需要在输入和输出端都是链中第一个的用例：

  1. 为每一端使用单独的增强器。
  2. 使用不同的顺序值配置它们。
  3. 使用增强器上下文在它们之间共享状态。
</Tip>

## API 概述

主要增强器接口位于包 `org.springframework.ai.chat.client.advisor.api` 中。以下是创建自己的增强器时会遇到的关键接口：

```java theme={"system"}
public interface Advisor extends Ordered {
	String getName();
}
```

同步和响应式增强器的两个子接口是：

```java theme={"system"}
public interface CallAroundAdvisor extends Advisor {
	/**
	 * 环绕通知，包装 ChatModel#call(Prompt) 方法。
	 * @param advisedRequest 建议的请求
	 * @param chain 增强器链
	 * @return 响应
	 */
	AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);
}
```

和

```java theme={"system"}
public interface StreamAroundAdvisor extends Advisor {
	/**
	 * 环绕通知，包装建议请求的调用。
	 * @param advisedRequest 建议的请求
	 * @param chain 要执行的增强器链
	 * @return 建议请求的结果
	 */
	Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);
}
```

要继续建议链，在您的建议实现中使用 `CallAroundAdvisorChain` 和 `StreamAroundAdvisorChain`：

接口是：

```java theme={"system"}
public interface CallAroundAdvisorChain {
	AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);
}
```

和

```java theme={"system"}
public interface StreamAroundAdvisorChain {
	Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest);
}
```

## 实现增强器

要创建增强器，实现 `CallAroundAdvisor` 或 `StreamAroundAdvisor`（或两者）。要实现的关键方法是用于非流式的 `nextAroundCall()` 或用于流式增强器的 `nextAroundStream()`。

### 示例

我们将提供一些实践示例来说明如何实现用于观察和增强用例的增强器。

#### 日志增强器

我们可以实现一个简单的日志增强器，在调用链中的下一个增强器之前记录 `AdvisedRequest`，之后记录 `AdvisedResponse`。
注意，增强器只观察请求和响应，不修改它们。
此实现同时支持非流式和流式场景。

```java theme={"system"}
public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {

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

	@Override
	public String getName() { // (1)
		return this.getClass().getSimpleName();
	}

	@Override
	public int getOrder() { // (2)
		return 0; 
	}

	@Override
	public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {

		logger.debug("BEFORE: {}", advisedRequest);

		AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);

		logger.debug("AFTER: {}", advisedResponse);

		return advisedResponse;
	}

	@Override
	public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {

		logger.debug("BEFORE: {}", advisedRequest);

		Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);
		
        return new MessageAggregator().aggregateAdvisedResponse(advisedResponses, 
                    advisedResponse -> logger.debug("AFTER: {}", advisedResponse)); // (3)
	}
}
```

1. 为增强器提供唯一名称。
2. 您可以通过设置顺序值来控制执行顺序。较低的值首先执行。
3. `MessageAggregator` 是一个实用类，将 Flux 响应聚合为单个 AdvisedResponse。这对于记录或观察整个响应而不是流中的单个项目的其他处理很有用。注意，您不能在 `MessageAggregator` 中修改响应，因为它是只读操作。

#### 重读 (Re2) 增强器

"[Re-Reading Improves Reasoning in Large Language Models](https://arxiv.org/pdf/2309.06275)" 文章介绍了一种称为重读 (Re2) 的技术，可以提高大型语言模型的推理能力。
Re2 技术要求像这样增强输入提示：

```
{Input_Query}
Read the question again: {Input_Query}
```

实现一个将 Re2 技术应用于用户输入查询的增强器可以这样做：

```java theme={"system"}
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {

	private AdvisedRequest before(AdvisedRequest advisedRequest) { // (1)

		Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());
		advisedUserParams.put("re2_input_query", advisedRequest.userText());

		return AdvisedRequest.from(advisedRequest)
			.userText("""
			    {re2_input_query}
			    Read the question again: {re2_input_query}
			    """)
			.userParams(advisedUserParams)
			.build();
	}

	@Override
	public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { // (2)
		return chain.nextAroundCall(this.before(advisedRequest));
	}

	@Override
	public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { // (3)
		return chain.nextAroundStream(this.before(advisedRequest));
	}

	@Override
	public int getOrder() { // (4)
		return 0; 
	}

    @Override
    public String getName() { // (5)
		return this.getClass().getSimpleName();
	}
}
```

1. `before` 方法通过应用重读技术增强用户的输入查询。
2. `aroundCall` 方法拦截非流式请求并应用重读技术。
3. `aroundStream` 方法拦截流式请求并应用重读技术。
4. 您可以通过设置顺序值来控制执行顺序。较低的值首先执行。
5. 为增强器提供唯一名称。

### Spring AI 内置增强器

Spring AI 框架提供了几个内置增强器来增强您的 AI 交互。以下是可用增强器的概述：

<AccordionGroup>
  <Accordion title="聊天内存增强器" icon="memory">
    这些增强器在聊天内存存储中管理对话历史：

    <CardGroup cols={1}>
      <Card title="MessageChatMemoryAdvisor">
        检索内存并将其作为消息集合添加到提示中。这种方法保持了对话历史的结构。注意，并非所有 AI 模型都支持这种方法。
      </Card>

      <Card title="PromptChatMemoryAdvisor">
        检索内存并将其合并到提示的系统文本中。
      </Card>

      <Card title="VectorStoreChatMemoryAdvisor">
        从 VectorStore 检索内存并将其添加到提示的系统文本中。此增强器对于从大型数据集中高效搜索和检索相关信息很有用。
      </Card>
    </CardGroup>
  </Accordion>

  <Accordion title="问题回答增强器" icon="question-circle">
    <CardGroup cols={1}>
      <Card title="QuestionAnswerAdvisor">
        此增强器使用向量存储来提供问答功能，实现 RAG（检索增强生成）模式。
      </Card>
    </CardGroup>
  </Accordion>

  <Accordion title="内容安全增强器" icon="shield-alt">
    <CardGroup cols={1}>
      <Card title="SafeGuardAdvisor">
        一个简单的增强器，旨在防止模型生成有害或不适当的内容。
      </Card>
    </CardGroup>
  </Accordion>
</AccordionGroup>

### 流式与非流式

<Frame caption="增强器流式与非流式流程">
  <img src="https://mintcdn.com/pig/43t3K3HuqTJIvmwg/images/advisors-non-stream-vs-stream.jpg?fit=max&auto=format&n=43t3K3HuqTJIvmwg&q=85&s=871e7a4e5510861fe6c01c397dd76afe" width="800" data-path="images/advisors-non-stream-vs-stream.jpg" />
</Frame>

* 非流式增强器处理完整的请求和响应。
* 流式增强器将请求和响应作为连续流处理，使用响应式编程概念（例如，用于响应的 Flux）。

实现带有阻塞和非阻塞代码的流式增强器的示例：

```java theme={"system"}
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
    
    return  Mono.just(advisedRequest)
            .publishOn(Schedulers.boundedElastic())
            .map(request -> {
                // 这可以由阻塞和非阻塞线程执行。
                // 增强器在下一个部分之前
            })
            .flatMapMany(request -> chain.nextAroundStream(request))
            .map(response -> {
                // 增强器在下一个部分之后
            });
}
```

### 最佳实践

<CardGroup cols={2}>
  <Card title="专注责任" icon="crosshairs">
    保持增强器专注于特定任务以获得更好的模块化。
  </Card>

  <Card title="共享状态" icon="share-alt">
    必要时使用 `adviseContext` 在增强器之间共享状态。
  </Card>

  <Card title="支持两种模式" icon="sync-alt">
    实现增强器的流式和非流式版本以获得最大灵活性。
  </Card>

  <Card title="考虑顺序" icon="sort">
    仔细考虑增强器在链中的顺序以确保正确的数据流。
  </Card>
</CardGroup>

## 向后兼容性

<Warning>
  `AdvisedRequest` 类已移动到新包。
</Warning>

## API 重大变更

Spring AI 增强器链从版本 1.0 M2 到 1.0 M3 经历了重大变化。以下是主要修改：

### 增强器接口

<Tabs>
  <Tab title="1.0 M2">
    * 单独的 `RequestAdvisor` 和 `ResponseAdvisor` 接口
    * `RequestAdvisor` 在 `ChatModel.call` 和 `ChatModel.stream` 方法之前调用
    * `ResponseAdvisor` 在这些方法之后调用
    * 使用 `StreamResponseMode` 作为 `ResponseAdvisor` 的一部分
  </Tab>

  <Tab title="1.0 M3">
    * 替换为 `CallAroundAdvisor` 和 `StreamAroundAdvisor`
    * 已移除 `StreamResponseMode`
  </Tab>
</Tabs>

### 上下文映射处理

<Tabs>
  <Tab title="1.0 M2">
    * 上下文映射是一个单独的方法参数
    * 映射是可变的并沿链传递
  </Tab>

  <Tab title="1.0 M3">
    * 上下文映射现在是 `AdvisedRequest` 和 `AdvisedResponse` 记录的一部分
    * 映射是不可变的
    * 要更新上下文，使用 `updateContext` 方法，它创建一个具有更新内容的新不可变映射
  </Tab>
</Tabs>

在 1.0 M3 中更新上下文的示例：

```java theme={"system"}
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {

    this.advisedRequest = advisedRequest.updateContext(context -> {
        context.put("aroundCallBefore" + getName(), "AROUND_CALL_BEFORE " + getName());  // 添加多个键值对
        context.put("lastBefore", getName());  // 添加单个键值对
        return context;
    });

    // 方法实现继续...
}
```

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