《Spring AI 实战系列 入门篇》第 5 篇

0 阅读4分钟

Spring AI 实战系列 | 第 5 篇

Advisors:自定义 AI 中间件

系列说明:本文为《Spring AI 实战系列 入门篇》第 5 篇

前置知识:完成第 1-4 篇

预计阅读时间:15 分钟


📖 目录

  1. 什么是 Advisors?
  2. 工作原理
  3. 内置 Advisors
  4. 自定义 Advisors
  5. 实战案例
  6. 系列预告

一、什么是 Advisors?

1.1 生活中的类比

 Advisors = AI 应用的"中间件"

 Web 开发中的 Filter:
 请求 → Filter1 → Filter2 → Controller → Filter2 → Filter1 → 响应

 AI 开发中的 Advisor:
 Prompt → Advisor1 → Advisor2 → LLM → Advisor2 → Advisor1 → 响应

1.2 Advisors 能做什么?

功能说明
📝 日志记录记录请求与响应
🔒 安全检查内容审核、敏感词过滤
💬 对话记忆维护多轮对话上下文
🔄 重试机制失败自动重试
📊 监控统计请求次数、耗时统计
🎯 RAG检索增强生成(QuestionAnswerAdvisor)

二、工作原理

2.1 执行流程

┌────────────────────────────────────────────────────────────────────┐
│                        Advisor 执行链                                │
├────────────────────────────────────────────────────────────────────┤
│                                                                    │
│  用户请求                                                            │
│      ↓                                                              │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │ Advisor1 (order=0, LOW)                                     │   │
│  │ • 处理请求                                                   │   │
│  │ • 可修改 Prompt                                              │   │
│  │ • 调用下一个 →                                               │   │
│  └─────────────────────────────────────────────────────────────┘   │
│      ↓                                                              │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │ Advisor2 (order=100)                                         │   │
│  │ • 处理请求                                                   │   │
│  │ • 可修改 Prompt                                              │   │
│  │ • 调用下一个 →                                               │   │
│  └─────────────────────────────────────────────────────────────┘   │
│      ↓                                                              │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │ LLM(最后一个环节,实际调用 AI)                              │   │
│  └─────────────────────────────────────────────────────────────┘   │
│      ↓                                                              │
│  响应 ← Advisor2(处理响应)← Advisor1(处理响应)← 用户            │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

2.2 核心接口

package org.springframework.ai.chat.client.advisor.api;

public interface CallAdvisor extends Advisor {

    /** Advisor 名称 */
    String getName();

    /** 执行顺序(值越小越先执行) */
    int getOrder();

    /** 处理请求 */
    ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain);
}

// 拦截器链
public interface CallAdvisorChain {
    ChatClientResponse nextCall(ChatClientRequest request);
}

2.3 顺序规则

┌────────────────────────────────────────┐
│           执行顺序说明                   │
├────────────────────────────────────────┤
│                                        │
│  Integer.MIN_VALUE → HIGHEST_PRECEDENCE │
│                                        │
│  ┌─────────────────────────────┐       │
│  │ Advisor A (order = 0)       │ ← 先执行
│  │  └→ 修改请求                 │       │
│  │     调用下一个               │       │
│  │  ←─ 处理响应(最后返回)     │       │
│  └─────────────────────────────┘       │
│                ↓                       │
│  ┌─────────────────────────────┐       │
│  │ Advisor B (order = 100)    │       │
│  │  └→ 修改请求                 │       │
│  │     调用下一个               │       │
│  │  ←─ 处理响应                │       │
│  └─────────────────────────────┘       │
│                ↓                       │
│  ┌─────────────────────────────┐       │
│  │ LLM(系统内置,最后执行)    │       │
│  └─────────────────────────────┘       │
│                                        │
│  Integer.MAX_VALUE → LOWEST_PRECEDENCE │
│                                        │
└────────────────────────────────────────┘

三、内置 Advisors

3.1 常用内置 Advisors

Advisor功能
QuestionAnswerAdvisorRAG 检索增强
ReReadingAdvisor重新阅读问题,提升准确性
LoggerAdvisor日志记录
MemoryPreservationAdvisor记忆保留

3.2 使用内置 Advisors

@RestController
public class AdvisorController {

    private final ChatClient chatClient;
    private final VectorStore vectorStore;

    public AdvisorController(ChatClient.Builder builder, VectorStore vectorStore) {
        this.chatClient = builder.build();
        this.vectorStore = vectorStore;
    }

    @GetMapping("/rag")
    public String ragAsk(@RequestParam String question) {
        return chatClient.prompt()
            .user(question)
            .advisors(
                new QuestionAnswerAdvisor(vectorStore)  // RAG Advisor
            )
            .call()
            .content();
    }

    @GetMapping("/log")
    public String logAsk(@RequestParam String question) {
        return chatClient.prompt()
            .user(question)
            .advisors(
                new LoggerAdvisor()  // 日志 Advisor
            )
            .call()
            .content();
    }
}

3.3 配置 Advisor 参数

// 传入配置
chatClient.prompt()
    .user(question)
    .advisors(new QuestionAnswerAdvisor(vectorStore,
        SearchRequest.builder()
            .topK(5)
            .similarityThreshold(0.8)
            .build()))
    .call()
    .content();

四、自定义 Advisors

4.1 日志记录 Advisor

package com.example.demo.advisor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.core.Ordered;

public class LoggingAdvisor implements CallAdvisor {

    private static final Logger log = LoggerFactory.getLogger(LoggingAdvisor.class);

    @Override
    public String getName() {
        return "logging-advisor";
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE - 100;  // 较低优先级
    }

    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        // 记录请求
        log.info("=== AI 请求 ===");
        log.info("用户消息: {}", request.prompt().getUserMessage().getText());

        // 继续调用链
        ChatClientResponse response = chain.nextCall(request);

        // 记录响应
        log.info("=== AI 响应 ===");
        log.info("响应内容: {}", response.chatResponse().getResult().getOutput().getText());

        return response;
    }
}

4.2 安全检查 Advisor

package com.example.demo.advisor;

import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.core.Ordered;

import java.util.List;

public class SecurityAdvisor implements CallAdvisor {

    private static final List<String> SENSITIVE_WORDS = List.of(
        "密码", "银行卡", "身份证", "信用卡", "验证码", "手机号"
    );

    @Override
    public String getName() {
        return "security-advisor";
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 100;  // 较高优先级
    }

    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        // 获取用户输入
        String userText = request.prompt().getUserMessage().getText();

        // 检查敏感词
        for (String word : SENSITIVE_WORDS) {
            if (userText.contains(word)) {
                throw new SecurityException("包含敏感词: " + word);
            }
        }

        // 继续调用链
        return chain.nextCall(request);
    }
}

4.3 对话记忆 Advisor

public class SimpleMemoryAdvisor implements CallAdvisor {

    private final List<Message> history = new ArrayList<>();

    @Override
    public String getName() {
        return "simple-memory";
    }

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

    @Override
    public ChatClientRequest adviseCall(
            ChatClientRequest request, 
            CallAdvisorChain chain
    ) {
        // 添加历史消息到请求
        ChatClientRequest modifiedRequest = new ChatClientRequest(
            request.user(),
            MessageUtils.appendMessages(
                history,
                MessageUtils.fromUser(request.user())
            ),
            request.options()
        );
        
        // 继续执行
        ChatClientResponse response = chain.nextCall(modifiedRequest);
        
        // 记录对话历史
        history.add(MessageUtils.fromAssistant(response.result().getText()));
        
        // 限制历史长度
        if (history.size() > 20) {
            history.subList(0, 10).clear();
        }
        
        return response;
    }

    @Override
    public Flux<ChatClientResponse> adviseStream(
            Flux<ChatClientResponse> flux, 
            StreamAdvisorChain chain
    ) {
        return chain.next(flux);
    }
}

4.4 重试机制 Advisor

public class RetryAdvisor implements CallAdvisor {

    private final int maxRetries;
    private final long retryDelayMs;

    public RetryAdvisor(int maxRetries, long retryDelayMs) {
        this.maxRetries = maxRetries;
        this.retryDelayMs = retryDelayMs;
    }

    @Override
    public String getName() {
        return "retry-advisor";
    }

    @Override
    public int getOrder() {
        return Integer.MAX_VALUE;  // 最后执行
    }

    @Override
    public ChatClientRequest adviseCall(
            ChatClientRequest request, 
            CallAdvisorChain chain
    ) {
        Exception lastException = null;
        
        for (int i = 0; i < maxRetries; i++) {
            try {
                return chain.nextCall(request);
            } catch (Exception e) {
                lastException = e;
                if (i < maxRetries - 1) {
                    try {
                        Thread.sleep(retryDelayMs);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
        }
        
        throw new RuntimeException("重试失败", lastException);
    }

    @Override
    public Flux<ChatClientResponse> adviseStream(
            Flux<ChatClientResponse> flux, 
            StreamAdvisorChain chain
    ) {
        return chain.next(flux);
    }
}

五、实战案例

5.1 组合多个 Advisors

@RestController
@RequestMapping("/ai")
public class AdvisorDemoController {

    private final ChatClient chatClient;

    public AdvisorDemoController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @GetMapping("/secure-chat")
    public String secureChat(@RequestParam String question) {
        return chatClient.prompt()
            .user(question)
            .advisors(
                new SecurityAdvisor(),    // 1. 安全检查(最先)
                new LoggingAdvisor(),      // 2. 日志记录
                new RetryAdvisor(3, 1000) // 3. 重试机制(最后)
            )
            .call()
            .content();
    }

    @GetMapping("/memory-chat")
    public String memoryChat(@RequestParam String question) {
        return chatClient.prompt()
            .user(question)
            .advisors(
                new SimpleMemoryAdvisor()  // 对话记忆
            )
            .call()
            .content();
    }
}

5.2 带参数的 Advisor

@Configuration
public class AdvisorConfig {

    @Bean
    public CallAdvisor customLoggerAdvisor() {
        return new CallAdvisor() {
            @Override
            public String getName() {
                return "custom-logger";
            }

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

            @Override
            public ChatClientRequest adviseCall(
                    ChatClientRequest request, 
                    CallAdvisorChain chain
            ) {
                System.out.println(">>> 请求: " + request.user());
                ChatClientResponse response = chain.nextCall(request);
                System.out.println("<<< 响应: " + response.result().getText());
                return response;
            }

            @Override
            public Flux<ChatClientResponse> adviseStream(
                    Flux<ChatClientResponse> flux, 
                    StreamAdvisorChain chain
            ) {
                return chain.nextStream(flux);
            }
        };
    }
}

六、系列预告

本篇小结

  • ✅ 理解了 Advisors 原理与执行链
  • ✅ 掌握了内置 Advisors 用法
  • ✅ 学会了自定义 Advisors

完整系列

篇目内容状态
第 1 篇核心概念 + 快速上手✅ 已完成
第 2 篇Tool Calling + 工具调用✅ 已完成
第 3 篇VectorStore + RAG✅ 已完成
第 4 篇结构化输出✅ 已完成
第 5 篇Advisors 中间件✅ 本文
第 6 篇国产模型集成🔜 (终篇)

下篇预告

第 6 篇:国产模型集成指南

  • 通义千问 (Qwen)
  • 智谱 GLM
  • 百度文心一言
  • Ollama 本地部署

📚 参考资料

  1. Spring AI Advisors 文档

📌 引用说明:本文核心概念与技术描述参考自 Spring AI 官方文档(docs.spring.io/spring-ai/r… 相关内容来自官方 Advisors API 章节。


关注公众号「AI日撰」,点击菜单「获取源码」获取完整代码(Gitee 仓库)。


系列:《Spring AI 实战系列 入门篇》第 5 篇(共 6 篇)