Spring AI Alibaba Advisor
1. 核心功能与作用
Advisor 的核心灵感来源于责任链模式 和面向切面编程(AOP)的思想 ,专门针对 AI 交互场景进行了优化。
1.1 核心功能
- 请求/响应拦截与修改 :
Advisor可以拦截发送给 LLM 的请求 (AdvisedRequest) 和从 LLM 接收的响应 (AdvisedResponse)。你可以在请求被发送前修改其内容(例如添加系统提示、用户历史或进行数据增强),也可以在响应返回后对其进行处理(例如解析、过滤敏感信息、记录日志或转换格式)。 - 上下文共享 :通过
AdvisorContext(一个Map<String, Object>)对象,数据可以在整个Advisor链中传递和共享。这使得多个Advisor能够协同工作,例如第一个Advisor计算的值可以被链中后续的Advisor使用 。 - 链式处理 :多个
Advisor可以按优先级(order)组成一条处理链。请求会依次通过链中的每个Advisor进行处理,响应则按相反的顺序返回。这种设计允许你组合多种功能,并且可以控制它们的执行顺序 。 - 多模型兼容 :
Advisor封装了通用的 AI 交互模式(如记忆管理、日志记录、安全过滤),这些模式通常与底层模型无关。这意味着同一套Advisor逻辑在经过适当配置后,可以用于不同的 LLM(如通义千问、GPT 等),增强了代码的可移植性和复用性 。
1.2 主要作用
- 日志记录与监控:记录 AI 交互的详细请求和响应信息,用于调试、审计或性能监控 。
- 对话记忆管理 :自动维护和管理多轮对话的上下文历史,确保模型能理解当前的对话背景 。
MessageChatMemoryAdvisor和PromptChatMemoryAdvisor是内置的实现。 - 检索增强生成 (RAG) :在查询 LLM 之前,先从外部知识库(如向量数据库)中检索相关信息,并将其附加到提示中,使模型能基于更丰富的上下文生成更准确的答案。
QuestionAnswerAdvisor是内置的实现 。 - 安全检查与过滤 :对用户输入和模型输出进行内容安全检查,例如过滤敏感词、防止提示词注入攻击等。
SafeGuardAdvisor是内置的实现 。 - 动态提示词工程 :根据特定条件或业务规则,在运行时动态修改或增强发送给模型的提示词 (
Prompt) 。
2. 核心接口与常用方法
Advisor 的核心接口定义在 org.springframework.ai.chat.client.advisor.api 包中 。
2.1 基础接口:Advisor
所有 Advisor 的根接口,它继承了 Spring 的 Ordered 接口。
java
public interface Advisor extends Ordered {
String getName(); // 返回此Advisor的唯一名称
}
2.2 核心功能接口
根据处理方式的不同,主要分为两个子接口:
-
CallAroundAdvisor:用于处理非流式(同步)调用。javapublic interface CallAroundAdvisor extends Advisor { AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain); }aroundCall方法是核心。它接收当前的AdvisedRequest和CallAroundAdvisorChain链对象。- 在此方法中,你可以处理
AdvisedRequest,然后通过调用chain.nextAroundCall(modifiedRequest)将控制权传递给链中的下一个Advisor。 - 获取到
AdvisedResponse后,可以对其进行处理,最后返回。
-
StreamAroundAdvisor:用于处理流式(异步)调用。javapublic interface StreamAroundAdvisor extends Advisor { Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain); }aroundStream方法是核心。它返回一个Flux<AdvisedResponse>响应流。- 处理逻辑与
CallAroundAdvisor类似,但需要注意响应流的处理和聚合(例如,使用MessageAggregator来聚合流式响应以便于完整记录)。
2.3 关键对象
AdvisedRequest:封装了要发送给 LLM 的请求信息,包括提示词 (Prompt)、用户参数 (userParams) 以及共享的AdvisorContext。可以使用AdvisedRequest.from()builder 模式来创建修改后的请求 。AdvisedResponse:封装了从 LLM 接收到的响应信息,包含响应内容 (ChatResponse) 和共享的AdvisorContext。AdvisorContext:一个Map<String, Object>,用于在Advisor链中跨步骤共享数据 。CallAroundAdvisorChain/StreamAroundAdvisorChain:代表处理链本身。通过调用其nextAroundCall或nextAroundStream方法,可以将请求传递给链中的下一个Advisor。
2.4 顺序控制
getOrder()方法(从Ordered接口继承)返回一个整数值,用于决定Advisor在链中的执行顺序。- 数值越小,优先级越高,越先执行(在请求阶段)。在响应阶段,顺序则是反向的 。
3. 实战:advisor应用
3.1 项目环境
3.2 项目配置 (pom.xml)
确保你的 pom.xml 包含 Spring AI Alibaba 依赖和必要的仓库。
xml
<?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.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>spring-ai-advisor</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-ai-advisor</name>
<description>spring-ai-advisor</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.4.0</spring-boot.version>
<spring-ai-alibaba.version>1.0.0.3</spring-ai-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>1.0.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-model-chat-memory</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-core</artifactId>
<version>1.0.0.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 注意:
spring-ai-alibaba-starter的版本请根据实际情况调整至1.0.0.3或官方最新的稳定版本。Spring Boot 版本也需要兼容。
3.3 应用配置 (application.properties)
在 src/main/resources/application.properties 中配置你的 DashScope API Key:
properties
# 替换为你从阿里云百炼获取的实际 API Key
spring.ai.dashscope.api-key=your_actual_api_key_here
# 可选:设置默认的聊天模型,例如 qwen-max
spring.ai.dashscope.chat.options.model=qwen-max
3.4 多层对话拦截器Advisor
这是核心实现,同时支持流式和非流式调用。
java
package org.example.springaiadvisor.advisor;
import org.aopalliance.aop.Advice;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.List;
/**
* 多层对话拦截器Advisor
* 组合切入点(Pointcut)和通知(Advice),实现对多轮对话的拦截
*/
@Component
public class MultiTurnChatAdvisor extends AbstractPointcutAdvisor {
// 定义切入点 - 拦截ChatClient的chat方法
private final StaticMethodMatcherPointcut pointcut = new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
// 匹配ChatClient接口的chat方法,且参数为Prompt类型
return ChatClient.class.isAssignableFrom(targetClass) &&
"chat".equals(method.getName()) &&
method.getParameterCount() == 1 &&
method.getParameterTypes()[0].equals(Prompt.class);
}
};
// 定义通知 - 实际的拦截逻辑
private final MultiTurnChatAdvice advice;
// 构造函数注入通知
public MultiTurnChatAdvisor(MultiTurnChatAdvice advice) {
this.advice = advice;
}
// 返回切入点
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
// 返回通知
@Override
public Advice getAdvice() {
return this.advice;
}
}
3.5 多层对话通知实现
java
package org.example.springaiadvisor.advisor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 多层对话通知实现
* 记录和拦截多轮对话的每一轮交互
*/
@Component
public class MultiTurnChatAdvice implements MethodInterceptor {
private static final Logger logger = LoggerFactory.getLogger(MultiTurnChatAdvice.class);
// 存储对话会话信息,key为会话ID,value为当前对话轮次
private final ConcurrentMap<String, AtomicInteger> conversationTurns = new ConcurrentHashMap<>();
// 生成唯一会话ID
private String generateConversationId() {
return "conv_" + UUID.randomUUID().toString().substring(0, 8);
}
// 获取当前对话轮次
private int getCurrentTurn(String conversationId) {
return conversationTurns.computeIfAbsent(conversationId, k -> new AtomicInteger(0)).incrementAndGet();
}
/**
* 拦截ChatClient的chat方法,实现多层对话的监控
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取方法参数
Object[] args = invocation.getArguments();
if (args == null || args.length == 0 || !(args[0] instanceof Prompt)) {
// 如果不是Prompt参数,直接执行原方法
return invocation.proceed();
}
// 1. 处理请求前逻辑
Prompt prompt = (Prompt) args[0];
List<Message> messages = prompt.getInstructions();
// 获取或生成会话ID(从最后一条用户消息中提取,如没有则生成)
String conversationId = extractConversationId(messages);
int currentTurn = getCurrentTurn(conversationId);
// 记录当前轮次的用户请求
logRequest(conversationId, currentTurn, messages);
// 2. 执行原方法(调用AI服务)
long startTime = System.currentTimeMillis();
Object result = invocation.proceed(); // 调用实际的AI聊天方法
long endTime = System.currentTimeMillis();
// 3. 处理响应后逻辑
if (result instanceof ChatResponse response) {
logResponse(conversationId, currentTurn, response, endTime - startTime);
}
return result;
}
// 从消息列表中提取会话ID(实际应用中可根据业务逻辑调整)
private String extractConversationId(List<Message> messages) {
// 查找最后一条用户消息
return messages.stream()
.filter(m -> m instanceof UserMessage)
.map(m -> (UserMessage) m)
.findFirst()
.map(userMessage -> {
// 实际应用中可以从消息元数据中提取会话ID
// 这里简化处理,如果是第一条消息则生成新ID,否则使用已有ID
String messageContent = userMessage.getText();
if (messageContent.contains("会话ID:")) {
return messageContent.split("会话ID:")[1].trim().split(" ")[0];
} else {
return generateConversationId();
}
})
.orElse(generateConversationId());
}
// 记录请求信息
private void logRequest(String conversationId, int turn, List<Message> messages) {
logger.info("\n===== 对话轮次 [{}] - 会话ID: {} - 请求开始 =====", turn, conversationId);
// 打印所有消息内容(用户消息和系统消息)
for (Message message : messages) {
logger.info("{}: {}",
message.getMessageType().name(),
message.getText().length() > 100 ?
message.toString().substring(0, 100) + "..." :
message.toString());
}
}
// 记录响应信息
private void logResponse(String conversationId, int turn, ChatResponse response, long duration) {
logger.info("\n===== 对话轮次 [{}] - 会话ID: {} - 响应结束 =====", turn, conversationId);
logger.info("AI响应: {}", response.getResult().getOutput().toString());
logger.info("处理耗时: {}ms", duration);
logger.info("===========================================\n");
}
}
3.6多层对话服务类
java
package org.example.springaiadvisor.service;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 多层对话服务类
* 实现与AI的多轮对话交互
*/
@Service
public class MultiTurnChatService {
// AI聊天客户端
private final ChatClient chatClient;
// 存储对话历史消息
private final List<Message> conversationHistory = new ArrayList<>();
// 构造函数注入ChatClient
public MultiTurnChatService(ChatClient.Builder chatClient) {
this.chatClient = chatClient.build();
}
/**
* 发送消息并获取响应(多轮对话)
* @param userInput 用户输入内容
* @return AI响应内容
*/
public String chat(String userInput) {
// 1. 创建用户消息并添加到对话历史
UserMessage userMessage = new UserMessage(userInput);
conversationHistory.add(userMessage);
// 2. 创建包含历史消息的请求
Prompt prompt = new Prompt(new ArrayList<>(conversationHistory));
// 3. 调用AI服务(这里会被我们的Advisor拦截)
ChatResponse response = chatClient.prompt(prompt).call().chatResponse();//chat(prompt);
// 4. 将AI响应添加到对话历史,用于下一轮对话
conversationHistory.add(response.getResult().getOutput());
// 5. 返回AI响应内容
return response.getResult().getOutput().toString();
}
/**
* 重置对话历史
*/
public void resetConversation() {
conversationHistory.clear();
}
}
3.7主应用类 (SpringAiAdvisorApplication)
java
package org.example.springaiadvisor;
import org.example.springaiadvisor.service.MultiTurnChatService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SpringAiAdvisorApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAiAdvisorApplication.class, args);
}
/**
* 启动后执行的测试代码
*/
@Bean
public CommandLineRunner run(MultiTurnChatService chatService) {
return args -> {
System.out.println("===== 开始多层对话测试 =====");
// 第一轮对话
String response1 = chatService.chat("我叫陈霸先,是南朝陈朝开国皇帝");
System.out.println("AI回应1: " + response1);
// 第二轮对话(基于上一轮的上下文)
String response2 = chatService.chat("我是谁?");
System.out.println("AI回应2: " + response2);
// 第三轮对话
String response3 = chatService.chat("陈朝现在怎么样了?");
System.out.println("AI回应3: " + response3);
System.out.println("\n===== 多层对话测试结束 =====");
};
}
}
3.7 运行与测试
启动应用 :在项目根目录下运行 mvn spring-boot:run。