Spring AI Advisors的具体定义和用途,怎么自定义业务Advisors?


Spring AI 中的 Advisors(顾问) 是一个关键概念,用于在模型交互过程中动态调整或增强提示词(Prompt)、控制生成过程,或注入业务逻辑。它的核心思想是对 AI 模型的输入/输出进行拦截和增强,类似于 AOP(面向切面编程)中的拦截器。以下是详细解析:


1. Advisor 的核心作用

  • 动态修改提示词:在发送给模型前,自动添加上下文、示例或格式化内容。
  • 结果后处理:对模型生成的文本进行过滤、校验或结构化解析。
  • 上下文管理:跨多次对话维护状态(如历史记录、用户偏好)。
  • 业务规则注入:根据业务需求限制或引导模型的输出。

2. Advisor 的工作原理

Advisor 在请求处理链中的位置:

用户输入 → [Advisor 预处理] → 模型处理 → [Advisor 后处理] → 返回用户
  • 预处理阶段:修改或增强输入的 Prompt
  • 后处理阶段:处理模型的 Response

3. 创建自定义 Advisor

3.1 实现 Advisor 接口
import org.springframework.ai.advisor.Advisor;
import org.springframework.ai.model.Prompt;
import org.springframework.ai.model.ModelResponse;

public class LoggingAdvisor implements Advisor {

    // 预处理:记录请求日志
    @Override
    public Prompt preProcess(Prompt prompt) {
        System.out.println("发送请求至模型,内容:" + prompt.getContents());
        return prompt;
    }

    // 后处理:记录响应日志
    @Override
    public ModelResponse postProcess(ModelResponse response) {
        System.out.println("收到模型响应:" + response.getResult().getOutput().getContent());
        return response;
    }
}
3.2 注册 Advisor 到 Spring 上下文
@Configuration
public class AdvisorConfig {

    @Bean
    public Advisor loggingAdvisor() {
        return new LoggingAdvisor();
    }
}

4. 内置 Advisor 示例

Spring AI 提供了一些预定义的 Advisor,可直接使用:

4.1 RetryAdvisor:自动重试失败请求
@Bean
public Advisor retryAdvisor() {
    return new RetryAdvisor(
        3,  // 最大重试次数
        List.of(OpenAiApiException.class)  // 触发重试的异常类型
    );
}
4.2 ContentFilterAdvisor:内容过滤
@Bean
public Advisor contentFilterAdvisor() {
    return new ContentFilterAdvisor(keywords -> {
        // 自定义过滤逻辑(如屏蔽敏感词)
        return keywords.stream()
            .anyMatch(word -> word.contains("暴力"));
    });
}

5. 高级用法:动态上下文注入

场景:在每次对话中自动添加用户的历史记录。
public class HistoryAdvisor implements Advisor {

    private final ThreadLocal<List<String>> conversationHistory = new ThreadLocal<>();

    @Override
    public Prompt preProcess(Prompt prompt) {
        List<String> history = conversationHistory.get();
        if (history == null) {
            history = new ArrayList<>();
            conversationHistory.set(history);
        }

        // 将历史记录添加到 Prompt
        String historyContext = String.join("\n", history);
        String enhancedPrompt = "历史对话:\n" + historyContext + "\n当前问题:" + prompt.getContents();
        
        return new Prompt(enhancedPrompt, prompt.getOptions());
    }

    @Override
    public ModelResponse postProcess(ModelResponse response) {
        // 将当前对话加入历史
        conversationHistory.get().add(response.getResult().getOutput().getContent());
        return response;
    }
}

6. 组合多个 Advisor

通过 AdvisorChain 控制执行顺序:

@Bean
public AdvisorChain advisorChain() {
    return new AdvisorChain(
        List.of(
            new HistoryAdvisor(),  // 先执行
            new LoggingAdvisor(),  // 后执行
            new RetryAdvisor()
        )
    );
}

7. 应用场景

  • 企业知识库问答:自动在问题前附加企业文档片段。
  • 多轮对话:维护对话历史,实现上下文连贯。
  • 合规检查:过滤模型生成的敏感内容。
  • 性能监控:统计请求耗时和成功率。

8. 注意事项

  • 执行顺序:Advisor 的执行顺序可能影响结果,需谨慎设计链式调用。
  • 性能开销:复杂的预处理/后处理可能增加延迟。
  • 状态管理:避免在 Advisor 中存储用户级状态(应结合会话机制)。

完整示例:自动添加系统指令

public class SystemInstructionAdvisor implements Advisor {
    
    @Override
    public Prompt preProcess(Prompt prompt) {
        String systemInstruction = """
            你是一个严谨的科技助手,回答需遵循以下规则:
            1. 答案必须基于事实,不确定时明确告知。
            2. 避免使用 Markdown 格式。
            3. 用中文回答。
            """;
        
        String modifiedContent = systemInstruction + "\n\n用户问题:" + prompt.getContents();
        return new Prompt(modifiedContent, prompt.getOptions());
    }

    @Override
    public ModelResponse postProcess(ModelResponse response) {
        return response; // 无后处理
    }
}

通过 Advisors,可以灵活控制 AI 交互的全流程,而无需修改核心业务代码。以下是一个结合 多轮对话业务场景 的详细示例,展示如何通过 Spring AI Advisors 实现上下文管理、敏感词过滤和日志记录,并附上完整代码:


场景描述

假设我们正在开发一个 客服对话系统,需求如下:

  1. 上下文管理:保留最近3轮对话历史,确保模型理解上下文。
  2. 敏感词过滤:拦截用户输入和模型输出中的敏感词(如“赌博”)。
  3. 请求日志:记录每次对话的请求参数和响应耗时。
  4. 异常重试:当模型调用失败时,自动重试最多2次。

1. 项目结构

src/main/java
├── com.example
│   ├── config
│   │   ├── AdvisorConfig.java      // Advisor配置
│   ├── advisor
│   │   ├── HistoryAdvisor.java     // 上下文管理
│   │   ├── ContentFilterAdvisor.java // 敏感词过滤
│   │   ├── LoggingAdvisor.java     // 请求日志
│   │   ├── RetryAdvisor.java       // 异常重试
│   ├── controller
│   │   ├── ChatController.java     // 对话接口

2. 详细代码实现

2.1 上下文管理 Advisor (HistoryAdvisor)
package com.example.advisor;

import org.springframework.ai.advisor.Advisor;
import org.springframework.ai.model.Prompt;
import org.springframework.ai.model.ModelResponse;
import org.springframework.stereotype.Component;
import java.util.Deque;
import java.util.LinkedList;

@Component
public class HistoryAdvisor implements Advisor {

    // 使用 ThreadLocal 保证线程安全(假设每个请求一个线程)
    private static final ThreadLocal<Deque<String>> HISTORY = ThreadLocal.withInitial(() -> new LinkedList<>());
    private static final int MAX_HISTORY = 3; // 保留最近3轮对话

    @Override
    public Prompt preProcess(Prompt prompt) {
        Deque<String> history = HISTORY.get();
        String historyContext = String.join("\n", history);
        
        // 将历史对话添加到当前 Prompt
        String enhancedPrompt = """
            历史对话记录:
            %s
            当前用户问题:
            %s
            """.formatted(historyContext, prompt.getContents());
        
        return new Prompt(enhancedPrompt, prompt.getOptions());
    }

    @Override
    public ModelResponse postProcess(ModelResponse response) {
        Deque<String> history = HISTORY.get();
        String currentResponse = response.getResult().getOutput().getContent();
        
        // 维护固定长度的历史队列
        if (history.size() >= MAX_HISTORY) {
            history.removeFirst();
        }
        history.addLast(currentResponse);
        
        return response;
    }

    // 清理 ThreadLocal(例如在请求结束时)
    public static void clearHistory() {
        HISTORY.remove();
    }
}
2.2 敏感词过滤 Advisor (ContentFilterAdvisor)
package com.example.advisor;

import org.springframework.ai.advisor.Advisor;
import org.springframework.ai.model.Prompt;
import org.springframework.ai.model.ModelResponse;
import org.springframework.stereotype.Component;
import java.util.List;

@Component
public class ContentFilterAdvisor implements Advisor {

    private static final List<String> BANNED_WORDS = List.of("赌博", "色情", "诈骗");

    @Override
    public Prompt preProcess(Prompt prompt) {
        String userInput = prompt.getContents();
        
        // 检查用户输入是否包含敏感词
        if (containsBannedWords(userInput)) {
            throw new IllegalArgumentException("输入包含敏感内容,请修改后重试");
        }
        return prompt;
    }

    @Override
    public ModelResponse postProcess(ModelResponse response) {
        String modelOutput = response.getResult().getOutput().getContent();
        
        // 过滤模型输出中的敏感词
        String filteredOutput = modelOutput;
        for (String word : BANNED_WORDS) {
            filteredOutput = filteredOutput.replace(word, "***");
        }
        
        response.getResult().getOutput().setContent(filteredOutput);
        return response;
    }

    private boolean containsBannedWords(String text) {
        return BANNED_WORDS.stream().anyMatch(text::contains);
    }
}
2.3 请求日志 Advisor (LoggingAdvisor)
package com.example.advisor;

import org.springframework.ai.advisor.Advisor;
import org.springframework.ai.model.Prompt;
import org.springframework.ai.model.ModelResponse;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component
public class LoggingAdvisor implements Advisor {

    private static final Logger logger = LoggerFactory.getLogger(LoggingAdvisor.class);
    private long startTime;

    @Override
    public Prompt preProcess(Prompt prompt) {
        startTime = System.currentTimeMillis();
        logger.info("收到用户请求: {}", prompt.getContents());
        return prompt;
    }

    @Override
    public ModelResponse postProcess(ModelResponse response) {
        long duration = System.currentTimeMillis() - startTime;
        logger.info("请求处理完成,耗时 {} ms | 响应内容: {}", duration, response.getResult().getOutput().getContent());
        return response;
    }
}
2.4 异常重试 Advisor (RetryAdvisor)
package com.example.advisor;

import org.springframework.ai.advisor.Advisor;
import org.springframework.ai.model.Prompt;
import org.springframework.ai.model.ModelResponse;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.stereotype.Component;

@Component
public class RetryAdvisor implements Advisor {

    private final RetryTemplate retryTemplate;

    public RetryAdvisor() {
        this.retryTemplate = new RetryTemplate();
        
        // 配置重试策略:最多2次,间隔1秒
        SimpleRetryPolicy policy = new SimpleRetryPolicy();
        policy.setMaxAttempts(2);
        
        FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
        backOffPolicy.setBackOffPeriod(1000); // 1秒
        
        retryTemplate.setRetryPolicy(policy);
        retryTemplate.setBackOffPolicy(backOffPolicy);
    }

    @Override
    public Prompt preProcess(Prompt prompt) {
        return prompt; // 不做处理
    }

    @Override
    public ModelResponse postProcess(ModelResponse response) {
        // 如果响应包含异常,触发重试
        return retryTemplate.execute(context -> {
            // 实际调用模型的逻辑(示例中简化)
            return response;
        });
    }
}

3. Advisor 配置与注册

package com.example.config;

import com.example.advisor.*;
import org.springframework.ai.advisor.AdvisorChain;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;

@Configuration
public class AdvisorConfig {

    @Bean
    public AdvisorChain advisorChain(
        HistoryAdvisor historyAdvisor,
        ContentFilterAdvisor contentFilterAdvisor,
        LoggingAdvisor loggingAdvisor,
        RetryAdvisor retryAdvisor
    ) {
        // 注意:Advisor 的执行顺序很重要!
        return new AdvisorChain(List.of(
            loggingAdvisor,     // 1. 先记录日志
            contentFilterAdvisor, // 2. 过滤敏感词
            historyAdvisor,     // 3. 添加上下文
            retryAdvisor        // 4. 异常重试
        ));
    }
}

4. Controller 实现

package com.example.controller;

import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    @PostMapping("/chat")
    public String chat(@RequestBody String userMessage) {
        Prompt prompt = new Prompt(userMessage);
        ChatResponse response = chatClient.call(prompt);
        return response.getResult().getOutput().getContent();
    }
}

5. 完整业务流程演示

用户请求示例:
POST /chat
Body: "我想咨询如何防止网络诈骗"
处理流程:
  1. LoggingAdvisor 记录请求开始时间。
  2. ContentFilterAdvisor 检查输入是否含敏感词(如“诈骗”被拦截)。
  3. HistoryAdvisor 添加上下文(假设是第一次对话,无历史)。
  4. 模型处理 生成响应:“您可以安装反诈APP,并注意不要点击陌生链接。”
  5. RetryAdvisor 检查是否需要重试(假设成功,无需重试)。
  6. ContentFilterAdvisor 过滤输出中的敏感词(无敏感词,保留原样)。
  7. HistoryAdvisor 将当前响应加入历史队列。
  8. LoggingAdvisor 记录响应耗时和内容。
输出结果:
您可以安装反诈APP,并注意不要点击陌生链接。
日志输出:
收到用户请求: 我想咨询如何防止网络诈骗
请求处理完成,耗时 450 ms | 响应内容: 您可以安装反诈APP,并注意不要点击陌生链接。

6. 关键点解析

  • 执行顺序控制AdvisorChain 的顺序决定了处理流程,例如先过滤敏感词再记录日志,避免记录非法内容。
  • 线程安全设计:使用 ThreadLocal 管理对话历史,确保多用户并发时的数据隔离。
  • 业务无侵入:所有横切关注点(日志、过滤、重试)通过 Advisors 实现,Controller 保持简洁。
  • 灵活扩展:新增功能只需添加新的 Advisor,无需修改现有业务代码。

7. 测试异常场景

测试输入敏感词:
POST /chat
Body: "哪里有赌博平台?"
处理流程:
  1. ContentFilterAdvisor 检测到“赌博”,直接抛出异常,中断后续流程。
  2. LoggingAdvisor 记录异常信息。
  3. 用户收到错误响应:"输入包含敏感内容,请修改后重试"

通过这个完整案例,可以清晰看到 Advisors 如何将 横切关注点 模块化,并与核心业务逻辑解耦。如果需要进一步优化(如异步处理敏感词过滤),可在此基础上扩展。

你可能感兴趣的:(spring,人工智能,SpringAI)