乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • Spring AI 学习路径
  • 第1章 Spring AI 概述与核心理念

    • 章节导读
    • Spring AI 概述与可移植 API
    • 核心模块与依赖关系
  • 第2章 快速入门与第一个AI应用

    • 章节导读
    • 环境准备与配置
    • 第一个 AI 对话应用
  • 第3章 聊天模型与ChatClient

    • 章节导读
    • ChatClient 详解
    • ChatModel 底层抽象
    • 多轮对话与 ChatMemory
  • 第4章 提示词管理与模板

    • 章节导读
    • Prompt 与 Message 体系
    • 提示词模板与动态构建
  • 第5章 输出解析与结构化响应

    • 章节导读
    • 输出解析器与 BeanOutputConverter
  • 第6章 嵌入模型与向量存储

    • 章节导读
    • EmbeddingModel 与文本向量化
    • ETL 数据注入流水线
    • 向量存储抽象与配置
  • 第7章 检索增强生成(RAG)

    • 章节导读
    • RAG 核心机制与流程
    • QuestionAnswerAdvisor 详解
    • RAG 实战案例
  • 第8章 函数调用(Function Calling)

    • 章节导读
    • Tool 注解与函数声明
    • 函数调用实战
  • 第9章 多模态

    • 章节导读
    • 图像输入与视觉模型
    • 图像生成
  • 第10章 Advisor拦截器链

    • 章节导读
    • Advisor 链与内置拦截器
  • 第11章 MCP 协议与跨语言工具集成

    • 章节导读
    • MCP 协议深入
  • 第12章 可观测性测试与最佳实践

    • 章节导读
    • 可观测性与指标监控
    • 测试策略与 Mock 实践
    • 最佳实践与面试考点汇总

白歌在白板上画出洋葱模型:"每个 Advisor 包裹一层逻辑,请求从外向内穿,响应从内向外穿。日志、安全、记忆注入,全部收敛到这根链上。"

Advisor 链与内置拦截器

定义与作用

Advisor 是 Spring AI 的拦截器/顾问模式实现。它在 Prompt 发送给模型之前和 Response 返回给调用方之后插入自定义逻辑——日志记录、对话记忆注入、内容安全过滤等横切关注点,无需修改业务代码。

核心原理

洋葱模型

图释:注册顺序即嵌套顺序——先注册的 Advisor 在最外层包裹。请求依次穿过 A1→A2→A3 到达 Model,响应反向穿过 A3→A2→A1 返回用户。

RequestResponseAdvisor 接口

public interface RequestResponseAdvisor {
    
    // 请求前:修改 Prompt
    Prompt adviseRequest(Prompt prompt, Map<String, Object> context);
    
    // 响应后:修改 ChatResponse
    ChatResponse adviseResponse(ChatResponse response, 
        Map<String, Object> context);
    
    // 标识名
    String getName();
    
    // 执行顺序(数值越小越靠外)
    default int getOrder() { return 0; }
}

内置 Advisor 速查

Advisor功能注入时机典型场景
QuestionAnswerAdvisor自动 VectorStore 检索并注入上下文请求前RAG 标配
ChatMemoryAdvisor自动管理对话历史请求前多轮对话
SimpleLoggerAdvisor记录 Prompt/Response请求前 + 响应后调试审计
ReReadingAdvisor让模型重读并校验自己回答请求前减少幻觉
SafeGuardAdvisor内容安全过滤请求前 + 响应后企业合规

完整示例一:日志记录 Advisor

场景说明

李眉需要给飞翔科技智能客服加上完整的调用审计日志。

关键代码

@Component
public class AuditLogAdvisor implements RequestResponseAdvisor {

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

    @Override
    public Prompt adviseRequest(Prompt prompt, 
            Map<String, Object> context) {
        // 提取用户问题文本
        String userContent = prompt.getInstructions().stream()
            .filter(m -> m.getMessageType() == MessageType.USER)
            .map(Message::getContent)
            .findFirst()
            .orElse("unknown");
        
        log.info("[AI 调用] 用户提问: {}", userContent);
        log.info("[AI 调用] Token 预算: {}", 
            prompt.getOptions() != null ? 
            prompt.getOptions().getMaxTokens() : "default");
        
        return prompt;  // 不修改 Prompt
    }

    @Override
    public ChatResponse adviseResponse(ChatResponse response, 
            Map<String, Object> context) {
        String aiReply = response.getResult()
            .getOutput().getContent();
        
        log.info("[AI 回复] 内容长度: {} 字符", 
            aiReply.length());
        log.info("[AI 回复] 模型: {}", 
            response.getMetadata().getModel());
        
        return response;
    }

    @Override
    public String getName() { 
        return "audit-log"; 
    }
}

注册 Advisor

@Configuration
public class AdvisorConfig {

    @Bean
    public ChatClient chatClient(
            ChatClient.Builder builder,
            AuditLogAdvisor auditLog,
            VectorStore vectorStore) {
        
        return builder
            .defaultAdvisors(
                auditLog,                                    // 第 1 层
                new ChatMemoryAdvisor(new InMemoryChatMemory()), // 第 2 层
                new QuestionAnswerAdvisor(vectorStore)     // 第 3 层
            )
            .build();
    }
}

运行结果

控制台日志:
[main] INFO  AuditLogAdvisor - [AI 调用] 用户提问: 飞翔大学的学费是多少?
[main] INFO  AuditLogAdvisor - [AI 调用] Token 预算: 2000
[main] INFO  AuditLogAdvisor - [AI 回复] 内容长度: 156 字符
[main] INFO  AuditLogAdvisor - [AI 回复] 模型: gpt-4o

完整示例二:内容安全过滤 Advisor

场景说明

高英要求客服 AI 不能输出任何违规内容。

关键代码

@Component
public class ContentSafetyAdvisor implements RequestResponseAdvisor {

    private static final List<String> FORBIDDEN_KEYWORDS = 
        List.of("赌博", "色情", "暴力");

    @Override
    public Prompt adviseRequest(Prompt prompt, 
            Map<String, Object> context) {
        // 请求前:检查用户输入是否违规
        String userInput = prompt.getInstructions().stream()
            .filter(m -> m.getMessageType() == MessageType.USER)
            .map(Message::getContent)
            .findFirst().orElse("");
        
        for (String keyword : FORBIDDEN_KEYWORDS) {
            if (userInput.contains(keyword)) {
                throw new IllegalArgumentException(
                    "输入包含违规内容,已拦截");
            }
        }
        return prompt;
    }

    @Override
    public ChatResponse adviseResponse(ChatResponse response, 
            Map<String, Object> context) {
        // 响应后:检查 AI 输出是否违规
        String aiReply = response.getResult()
            .getOutput().getContent();
        
        for (String keyword : FORBIDDEN_KEYWORDS) {
            if (aiReply.contains(keyword)) {
                // 替换为安全回复
                return ChatResponse.builder()
                    .generations(List.of(
                        new Generation("抱歉,无法回答该问题。")))
                    .build();
            }
        }
        return response;
    }

    @Override
    public String getName() { 
        return "content-safety"; 
    }
}

运行结果

// 正常提问
用户:"飞翔大学有哪些专业?"
AI:"飞翔大学设有计算机科学、软件工程、数据科学..."

// 违规提问
用户:"如何参与赌博?"
异常:java.lang.IllegalArgumentException: 输入包含违规内容,已拦截

// AI 输出被拦截
用户:"描述一些暴力场景"
AI:"抱歉,无法回答该问题。"

操作前后对比

维度操作前(无 Advisor)操作后(有 Advisor 链)
日志每次调用需手动加 log.info()AuditLogAdvisor 自动记录
安全需在 Controller 层逐接口校验ContentSafetyAdvisor 统一拦截
记忆需手动管理对话历史ChatMemoryAdvisor 自动注入
扩展性新增横截关注点需改业务代码新增 Advisor 实现接口即插即用
可测试性日志和安全逻辑与业务耦合各 Advisor 独立单元测试

易错场景与面试考点

易错场景一:Advisor 顺序错误导致 RAG 不生效

// ❌ 错误:安全 Advisor 放在 RAG Advisor 外面
builder.defaultAdvisors(
    contentSafety,                              // 最外层
    new QuestionAnswerAdvisor(vectorStore)      // 内层
);
// 安全过滤时,Prompt 还没有注入检索上下文

问题分析

QuestionAnswerAdvisor 在请求前修改 Prompt(注入检索上下文),如果它在安全 Advisor 内侧,安全 Advisor 看到的 Prompt 缺少上下文。应调整顺序:

// ✅ 正确:RAG Advisor 在外层,安全在更外层
builder.defaultAdvisors(
    auditLog,                                    // 外层:日志
    contentSafety,                                // 中层:安全
    new QuestionAnswerAdvisor(vectorStore),       // 中层:RAG
    new ChatMemoryAdvisor(new InMemoryChatMemory()) // 内层:记忆
);

面试高频题

Q1:Advisor 链的执行顺序是怎样的?

洋葱模型——先注册的 Advisor 同时是最外层。请求按注册顺序正向穿过各 Advisor,响应按注册顺序的反向穿过。getOrder() 返回值越小越靠外。

Q2:Advisor 和 Spring AOP 的关系?

两者都是横切关注点的实现手段,但层次不同。Spring AOP 针对方法调用织入增强(@Around),Advisor 针对 AI 调用的 Prompt/Response 做拦截。Advisor 可以理解为"AI 专用 AOP"——在 AI API 调用的关键节点织入逻辑。

本章小结

  • Advisor 是 Spring AI 的拦截器模式,实现横切关注点分离
  • 内置 5 种 Advisor:RAG、记忆、日志、安全、重读
  • 注册顺序决定嵌套层级(先注册 = 最外层)
  • 自定义 Advisor 只需实现 RequestResponseAdvisor 接口
  • Advisor 不侵入业务代码,即插即用,独立可测
上一页
章节导读