[特殊字符]️ Spring AI Alibaba Advisor基础应用

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 交互的详细请求和响应信息,用于调试、审计或性能监控 。
  • 对话记忆管理 :自动维护和管理多轮对话的上下文历史,确保模型能理解当前的对话背景 。MessageChatMemoryAdvisorPromptChatMemoryAdvisor 是内置的实现。
  • 检索增强生成 (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 核心功能接口

根据处理方式的不同,主要分为两个子接口:

  1. CallAroundAdvisor :用于处理非流式(同步)调用。

    java 复制代码
    public interface CallAroundAdvisor extends Advisor {
        AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);
    }
    • aroundCall 方法是核心。它接收当前的 AdvisedRequestCallAroundAdvisorChain 链对象。
    • 在此方法中,你可以处理 AdvisedRequest,然后通过调用 chain.nextAroundCall(modifiedRequest) 将控制权传递给链中的下一个 Advisor
    • 获取到 AdvisedResponse 后,可以对其进行处理,最后返回。
  2. StreamAroundAdvisor :用于处理流式(异步)调用。

    java 复制代码
    public 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 :代表处理链本身。通过调用其 nextAroundCallnextAroundStream 方法,可以将请求传递给链中的下一个 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

相关推荐
今日说"法"2 小时前
线性代数与矩阵运算:向量、矩阵、特征值、SVD 在 AI 中的全面应用
人工智能·线性代数·矩阵
Fate_I_C2 小时前
实战案例:用 Kotlin 重写一个 Java Android 工具类
android·java·kotlin
Jet7692 小时前
2026年API中转平台选型笔记:稳定性、兼容性、成本怎么一起看
java·网络·笔记
实在智能RPA2 小时前
电商运营自动化AI工具有哪些?哪个最好用?——2026年全链路智能体选型深度指南
大数据·人工智能·ai·自动化
刘佬GEO2 小时前
没时间写内容还能做 GEO:方法、流程与可操作方案
大数据·网络·人工智能·搜索引擎·ai
星速云2 小时前
开源AI工具生态全景:20+工具如何对接统一API网关
人工智能·gpt·开源·api·claude
今日说"法"2 小时前
线性代数与矩阵运算:AI 背后的数学基石
人工智能·线性代数·矩阵
X journey2 小时前
机器学习实践(18.5):特征工程补充
人工智能·算法·机器学习
糯米团子7492 小时前
蓝桥杯javaB组赛前四天复习-1
java·windows·蓝桥杯