Spring Ai 05 ChatClient Advisor 实战(日志、提示词增强、内容安全)

在 Spring AI 的 ChatClient 体系中,Advisor(顾问)是核心扩展点,能够在 AI 请求发送前、响应返回后插入自定义逻辑,比如日志记录、提示词增强、内容安全校验等。本文将通过完整的实战案例,讲解如何自定义和使用 Advisor,实现对 AI 交互全流程的精细化控制。

一、Advisor 核心概念

Advisor 是 Spring AI 为 ChatClient 设计的**AOP(面向切面编程)**扩展机制,主要分为两类:

  • CallAdvisor :针对同步调用(call())的切面增强
  • StreamAdvisor :针对流式调用(stream())的切面增强
  • BaseAdvisor:通用型顾问,可同时作用于 call/stream 两种调用方式

通过 Advisor,我们可以无侵入地实现:

  • 请求 / 响应日志记录
  • 提示词预处理(增强、格式化)
  • 内容安全校验(敏感词过滤)
  • 响应结果后处理
  • 调用链路监控等

二、实战案例:自定义 Advisor 实现

1. 案例 1:通用日志记录 Advisor

实现一个同时支持同步和流式调用的日志记录顾问,完整打印请求和响应信息。

java 复制代码
package com.easyai.advisor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientMessageAggregator;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import reactor.core.publisher.Flux;

/**
 * 日志记录Advisor:记录AI调用的请求和响应信息
 * 同时实现CallAdvisor(同步)和StreamAdvisor(流式)接口
 */
@Slf4j
public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {

    // 定义Advisor名称,用于日志和调试
    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

    // 执行顺序(0为最高优先级)
    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * 同步调用(call())的增强逻辑
     */
    @Override
    public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
        log.info("===== 同步调用(call)日志开始 =====");
        // 打印请求信息
        logRequest(chatClientRequest);
        
        // 执行下一个Advisor(或核心调用逻辑)
        ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
        
        // 打印响应信息
        logResponse(chatClientResponse);
        log.info("===== 同步调用(call)日志结束 =====\n");
        
        return chatClientResponse;
    }

    /**
     * 流式调用(stream())的增强逻辑
     */
    @Override
    public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
        log.info("===== 流式调用(stream)日志开始 =====");
        // 打印请求信息
        logRequest(chatClientRequest);
        
        // 执行下一个Advisor,获取流式响应
        Flux<ChatClientResponse> chatClientResponses = streamAdvisorChain.nextStream(chatClientRequest);
        
        // 聚合流式响应并打印(避免逐行打印碎片化日志)
        return new ChatClientMessageAggregator()
                .aggregateChatClientResponse(chatClientResponses, this::logResponse)
                .doFinally(signalType -> log.info("===== 流式调用(stream)日志结束 =====\n"));
    }

    /**
     * 打印请求详情
     */
    private void logRequest(ChatClientRequest request) {
        log.info("AI请求内容:{}", request);
    }

    /**
     * 打印响应详情
     */
    private void logResponse(ChatClientResponse chatClientResponse) {
        log.info("AI响应内容:{}", chatClientResponse);
    }

}

2. 案例 2:提示词增强 Advisor(重读提示)

实现一个自定义 Advisor,自动为用户输入添加 "再读一遍" 的提示词增强,提升 AI 回答的准确性。

java 复制代码
package com.easyai.advisor;

import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.prompt.PromptTemplate;

import java.util.Map;

/**
 * 提示词增强Advisor:自动为用户输入添加"再读一遍"的增强逻辑
 * 继承BaseAdvisor,通用适配call/stream调用
 */
public class ReReadingAdvisor implements BaseAdvisor {

    // 默认提示词模板
    private static final String DEFAULT_RE2_ADVISE_TEMPLATE = """
            {re2_input_query}
            再读一遍: {re2_input_query}
            """;

    private final String re2AdviseTemplate;
    private int order = 0;

    // 无参构造器,使用默认模板
    public ReReadingAdvisor() {
        this(DEFAULT_RE2_ADVISE_TEMPLATE);
    }

    // 自定义模板构造器
    public ReReadingAdvisor(String re2AdviseTemplate) {
        this.re2AdviseTemplate = re2AdviseTemplate;
    }

    /**
     * 请求发送前的增强逻辑:修改用户提示词
     */
    @Override
    public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
        // 获取原始用户输入
        String originalUserText = chatClientRequest.prompt().getUserMessage().getText();
        
        // 使用模板增强提示词
        String augmentedUserText = PromptTemplate.builder()
                .template(this.re2AdviseTemplate)
                .variables(Map.of("re2_input_query", originalUserText))
                .build()
                .render();
        
        // 修改请求中的用户提示词并返回新的请求对象
        return chatClientRequest.mutate()
                .prompt(chatClientRequest.prompt().augmentUserMessage(augmentedUserText))
                .build();
    }

    /**
     * 响应返回后的处理逻辑(此处无需修改,直接返回)
     */
    @Override
    public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
        return chatClientResponse;
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    // 链式设置执行顺序
    public ReReadingAdvisor withOrder(int order) {
        this.order = order;
        return this;
    }
}

三、Advisor 使用示例(Controller 层)

在 Controller 中集成自定义 Advisor 和 Spring AI 内置的SafeGuardAdvisor(敏感词校验),实现不同场景的 AI 调用。

java 复制代码
package com.easyai.advisor;

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SafeGuardAdvisor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;

/**
 * Advisor使用示例控制器
 * 演示不同Advisor的组合使用方式
 */
@RestController
@RequestMapping("/advisor")
@Slf4j
public class AdvisorController {

    // 注入之前配置的Ollama ChatClient
    @Resource
    private ChatClient ollamaChatClient;

    /**
     * 示例1:流式调用 + 日志Advisor
     */
    @GetMapping(value = "/chat1", produces = "text/stream;charset=utf-8")
    public Flux<String> chat1() {
        String userInput = "你是谁?";
        return ollamaChatClient.prompt()
                .advisors(new SimpleLoggerAdvisor()) // 添加日志顾问
                .user(userInput)
                .stream() // 流式响应
                .content(); // 提取响应内容
    }

    /**
     * 示例2:同步调用 + 日志Advisor
     */
    @GetMapping(value = "/chat2")
    public String chat2() {
        String userInput = "你是谁?";
        return ollamaChatClient.prompt()
                .advisors(new SimpleLoggerAdvisor()) // 添加日志顾问
                .user(userInput)
                .call() // 同步响应
                .content();
    }

    /**
     * 示例3:同步调用 + 内置敏感词校验Advisor
     * SafeGuardAdvisor:检测到敏感词时直接返回指定提示
     */
    @GetMapping(value = "/chat3")
    public String chat3(String userInput) {
        return ollamaChatClient.prompt()
                // 配置敏感词列表、触发敏感词后的提示语、执行顺序
                .advisors(new SafeGuardAdvisor(
                        List.of("老板","Boss"), // 敏感词列表
                        "提示词里有敏感词,禁止回答", // 敏感词触发提示
                        0 // 执行顺序
                ))
                .user(userInput)
                .call()
                .content();
    }

    /**
     * 示例4:同步调用 + 提示词增强Advisor
     * ReReadingAdvisor:自动为用户输入添加"再读一遍"增强
     */
    @GetMapping(value = "/chat4")
    public String chat4(String userInput) {
        return ollamaChatClient.prompt()
                .advisors(new ReReadingAdvisor()) // 添加提示词增强顾问
                .user(userInput)
                .call()
                .content();
    }

    /**
     * 扩展示例:多Advisor组合使用(日志 + 敏感词 + 提示词增强)
     */
    @GetMapping(value = "/chat5")
    public String chat5(String userInput) {
        return ollamaChatClient.prompt()
                // 多个Advisor按顺序执行(order越小越先执行)
                .advisors(
                        new SimpleLoggerAdvisor(), // 日志记录(order=0)
                        new SafeGuardAdvisor(List.of("老板","Boss"), "敏感词禁止回答", 1), // 敏感词校验(order=1)
                        new ReReadingAdvisor().withOrder(2) // 提示词增强(order=2)
                )
                .user(userInput)
                .call()
                .content();
    }
}

四、关键说明

1. Advisor 执行顺序

  • 通过getOrder()方法控制执行顺序,数值越小优先级越高
  • 多个 Advisor 组合时,按 order 从小到大依次执行
  • 可通过链式方法(如withOrder(2))动态调整顺序

2. 流式响应处理

  • 流式调用需使用ChatClientMessageAggregator聚合响应,避免打印碎片化日志
  • produce = "text/stream;charset=utf-8"必须配置,防止中文乱码

3. 内置 Advisor

Spring AI 提供了多个开箱即用的 Advisor:

  • SafeGuardAdvisor:敏感词校验
  • TokenCountingAdvisor:令牌计数
  • RetryAdvisor:失败重试
  • TimeoutAdvisor:超时控制

五、测试效果

  1. 日志 Advisor :调用/advisor/chat1/advisor/chat2,控制台会完整打印请求和响应日志

  2. 敏感词校验 :调用/advisor/chat3?userInput=老板在吗,直接返回 "提示词里有敏感词,禁止回答"

  3. 提示词增强 :调用/advisor/chat4?userInput=1+1等于几,AI 实际收到的提示词是:

    plaintext

    复制代码
    1+1等于几
    再读一遍: 1+1等于几

总结

  1. Spring AI 的 Advisor 是 ChatClient 的核心扩展机制,可无侵入实现请求 / 响应的全流程增强;
  2. 自定义 Advisor 可实现日志记录、提示词增强等个性化需求,内置 Advisor(如 SafeGuard)可快速实现敏感词校验等通用功能;
  3. 多个 Advisor 可组合使用,通过getOrder()控制执行顺序,流式调用需注意响应聚合处理。
相关推荐
hutengyi2 小时前
Spring Boot 实战篇(四):实现用户登录与注册功能
java·spring boot·后端
m0_502724952 小时前
腾讯地图tlbs-multi-marker动态更新marker图标
前端·javascript·vue.js·地图
tryCbest2 小时前
Python之FastAPI 开发框架(第三篇):高级特性与实战
开发语言·python·fastapi
IT_陈寒2 小时前
SpringBoot 项目启动慢?这5个优化技巧让你的应用快50%
前端·人工智能·后端
u0133945272 小时前
How to Run sample.war in a Tomcat Docker Container
java·docker·tomcat
2301_776508722 小时前
分布式系统监控工具
开发语言·c++·算法
splage2 小时前
Spring Framework 中文官方文档
java·后端·spring
Irissgwe2 小时前
Linux进程信号
linux·服务器·开发语言·c++·linux进程信号
暮冬-  Gentle°2 小时前
C++与区块链智能合约
开发语言·c++·算法