首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >别再只会发 Prompt 啦!用 Spring AI Advisors API 打造超靠谱的 AI 应用

别再只会发 Prompt 啦!用 Spring AI Advisors API 打造超靠谱的 AI 应用

作者头像
HELLO程序员
发布2026-06-26 21:16:40
发布2026-06-26 21:16:40
1380
举报

构建稳健的AI应用,可远不止“发个提示词就完事”这么简单。你得把上下文拿捏得死死的,还得能摸清对话的来龙去脉。而Spring AI的Advisors API,就是解决这些头疼问题的“神兵利器”。

这篇教程就来扒一扒Advisors API的底:我们会看看怎么拦截并修改AI交互过程,还会在Spring Boot项目里实现日志记录和对话记忆功能,让你的AI应用瞬间“有脑子、可追溯”。

无状态交互的坑,谁踩谁知道

大语言模型(LLM)确实牛得一批,但有个致命短板:天生无状态。在它眼里,每一个请求都是全新的“初次见面”,压根不记得你上一秒问了啥。

这就导致用户体验稀碎:想象一下,你刚跟机器人自我介绍完,转头问个跟进问题,它反手就给你一句“你谁啊?”,换谁谁不崩溃?

上下文才是王道

要解决这个问题,就得给模型喂上下文——把相关数据拼到每个Prompt里。一般来说,这些上下文数据分两类:

第一类是你的私有数据。模型训练时根本没见过你的业务文档,所以你得动态把这些信息传过去。就算模型懂相关知识点,你的私有数据也得是“老大”,优先级最高。

第二类是对话历史。也就是用户和AI一来一回的聊天内容,每一次新请求都得把这段历史带上,不然AI的逻辑就会断片,直接“语无伦次”。

而Spring AI Advisors就是来帮你自动化这个重复劳动的。它就像个“中间商”,夹在你的代码和AI模型之间,默默帮你搞定这些繁琐操作。话不多说,咱们直接上实操!

搞懂Advisor架构:其实就是个“过滤器”

你可以把Advisor理解成Java Web里的Servlet Filter,功能几乎一模一样:在请求传给模型之前,它先把请求拦下来;等模型生成响应后,在你的代码拿到响应之前,它还能再修改一波响应。这种架构相当于给AI交互逻辑找了个“中央管控台”,贼方便。

这种方案的好处,肉眼可见

  1. 业务代码干干净净:不用每次写Prompt都手动拼接字符串,一次配置Advisor,它就会自动帮你扛下所有脏活累活。
  2. 复用性拉满:你可以写一个自定义Advisor做安全校验,然后直接套在多个ChatClient实例上用,不用重复造轮子。

配置ChatClient:你的AI交互主入口

ChatClient是你和AI打交道的核心接口,它提供了流畅的链式API,配置起来贼顺手。我们可以通过AdvisorSpec接口,把咱们的自定义逻辑“挂”到ChatClient上。

你可以直接单个添加参数,也可以批量配置,灵活性拉满,能精准控制每一次AI交互。

下面是个基础配置示例,我们在标准的Spring配置类里创建一个ChatClient Bean:

代码语言:javascript
复制

package com.example.ai.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
publicclass AiConfig {

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder
                .defaultSystem("你是一个精通Spring技术的贴心助手。")
                .build();
    }
}

这只是打了个基础,还没加Advisor呢,咱们得给它升级一下,添点实用功能。

日志记录:排查问题的“照妖镜”

调试AI响应简直是个大工程,很多时候你得搞清楚:模型到底收到了啥?Prompt里的上下文对不对?

Spring AI提供的SimpleLoggerAdvisor就是解决这个问题的神器,它能自动记录请求和响应数据,是监控AI应用的必备工具,谁用谁知道。

来,给ChatClient加上日志功能

咱们更新下配置,把SimpleLoggerAdvisor加到拦截链里:

代码语言:javascript
复制

package com.example.ai.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
publicclass AiConfig {

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder
                .defaultSystem("你是一个精通Spring技术的贴心助手。")
                .defaultAdvisors(new SimpleLoggerAdvisor())
                .build();
    }
}

再配置下application.properties,开启调试日志:

代码语言:javascript
复制

logging.level.org.springframework.ai.chat.client.advisor=DEBUG

现在你再看控制台日志,就能看到原汁原味的交互数据了——包括Prompt文本和AI生成的响应,排查问题瞬间变得so easy!

对话记忆:让AI不再“鱼的记忆”

前面咱们吐槽了AI的无状态问题,现在就来解决它!Spring AI提供的MessageChatMemoryAdvisor,就是给AI装“脑子”的关键。

这个Advisor会管理一个对话存储库:发送请求前,它会先读取历史对话;AI生成新响应后,它又会把新的对话内容写回存储库,循环往复,让AI记住你们的“过往”。

把记忆Advisor接入系统

接下来,咱们把这个Advisor挂到ChatClient上,顺便指定一个对话ID,用来跟踪特定用户的会话(毕竟不能把用户A和用户B的对话搞混了):

代码语言:javascript
复制

@Bean
public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
    return builder
            .defaultSystem("你是一个贴心的助手。")
            .defaultAdvisors(
                    MessageChatMemoryAdvisor.builder(chatMemory).build(),
                    new SimpleLoggerAdvisor()                 // 记录交互日志
            )
            .build();
}

搞定!现在你的系统就变成有状态的了,MessageChatMemoryAdvisor会自动把历史消息拼到Prompt里,AI终于能记住你是谁了。

运行时Advisor配置:灵活切换“技能”

有时候,你并不想让Advisor作用于每一次请求——比如,只在某个接口里开启日志记录。这时候,AdvisorSpec接口就能实现动态配置,在交互时灵活添加或覆盖默认Advisor。

动态配置示例(控制器里搞事情)

比如在控制器方法里,我们只给这一个请求开启对话记忆参数配置:

代码语言:javascript
复制

@RestController
@RequestMapping("/ai")
publicclass ChatController {

    privatefinal ChatClient chatClient;

    // 构造函数注入ChatClient
    public ChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        return chatClient.prompt()
                .user(message)
                .advisors(a -> a
                    .param("chat_memory_conversation_id", "user-123")  // 动态指定用户123的对话ID
                    .param("chat_memory_response_size", 100)           // 限制记忆的响应长度
                )
                .call()
                .content();
    }
}

这里我们用了Lambda表达式动态设置参数,这样服务器就能同时处理多个用户的会话,互不干扰,简直完美!

高级玩法:自定义Advisor,实现专属逻辑

默认的Advisor已经很强了,但有时候你需要一些定制化功能——比如脱敏敏感数据、校验内容安全等。这时候,你可以实现CallAdvisor接口,打造属于自己的“专属拦截器”。

手把手教你写一个脱敏Advisor(隐藏手机号、邮箱等隐私数据)

结构很简单,核心就是实现around方法,话不多说,上代码:

代码语言:javascript
复制

import lombok.extern.slf4j.Slf4j;
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.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

@Slf4j
publicclass PiiRedactionAdvisor implements CallAdvisor {

    // 正则匹配邮箱
    privatestaticfinal Pattern EMAIL = Pattern.compile(
            "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}",
            Pattern.CASE_INSENSITIVE
    );

    // 正则匹配手机号
    privatestaticfinal Pattern PHONE = Pattern.compile(
            "\\b(?:\\+?86[-\\s]?)?(1[3-9]\\d{9})\\b([0-9]{3})[-.]?([0-9]{4})
"
    );

    // 正则匹配身份证号
    privatestaticfinal Pattern ID_CARD = Pattern.compile(
            "\\b(\\d{15}|\\d{17}([0-9]|X|x))\\b"
    );

    // 正则匹配信用卡号
    privatestaticfinal Pattern CREDIT_CARD = Pattern.compile(
            "\\b(?:\\d{4}[-\\s]?){3}\\d{4}\\b"
    );

    // 正则匹配IP地址
    privatestaticfinal Pattern IP_ADDRESS = Pattern.compile(
            "\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b"
    );

    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        List<Message> redactedMessages = new ArrayList<>();

        // 遍历用户消息,进行脱敏处理
        for (Message message : request.prompt().getUserMessages()) {
            if (message instanceof UserMessage) {
                String redacted = redactPii(message.getText());
                if(redacted != null) {
                    redactedMessages.add(new UserMessage(redacted));
                }

                // 打印原始消息和脱敏后的消息,方便验证
                log.info("原始消息:{}", message);
                log.info("脱敏后消息:{}", redacted);
            } else {
                // 非用户消息直接原样保留
                redactedMessages.add(message);
            }
        }

        // 构建脱敏后的Prompt
        Prompt promptWithHistory = new Prompt(redactedMessages);
        ChatClientRequest modifiedRequest = ChatClientRequest.builder()
                .prompt(promptWithHistory)
                .context(Map.copyOf(request.context())) // 保留原有上下文和工具信息
                .build();

        // 继续执行Advisor链
        return chain.nextCall(modifiedRequest);
    }

    // 核心脱敏方法:替换各类隐私数据
    private String redactPii(String text) {
        if (text == null) {
            returnnull;
        }

        String result = text;
        result = EMAIL.matcher(result).replaceAll("[邮箱]");
        result = PHONE.matcher(result).replaceAll("[手机号]");
        result = ID_CARD.matcher(result).replaceAll("[身份证号]");
        result = CREDIT_CARD.matcher(result).replaceAll("[信用卡号]");
        result = IP_ADDRESS.matcher(result).replaceAll("[IP地址]");

        return result;
    }

    @Override
    public String getName() {
        return"PiiRedactionAdvisor"; // 给Advisor起个名字,方便日志排查
    }

    @Override
    public int getOrder() {
        return0; // 指定执行顺序,数字越小越先执行
    }

}

有了这个自定义Advisor,你就能完全掌控AI交互流水线,想怎么定制就怎么定制!

给脱敏Advisor做个全面测试

光写代码不行,还得验证效果,咱们整个集成测试:

代码语言:javascript
复制

import com.example.spring_ai.config.PiiRedactionAdvisor;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
@Slf4j
class PiiRedactionAdvisorIntegrationTest {

    @Autowired
    private ChatModel chatModel;

    @Test
    void shouldRedactPiiInRealChatClient() {
        // 给定:创建脱敏Advisor和ChatClient
        PiiRedactionAdvisor advisor = new PiiRedactionAdvisor();
        ChatClient chatClient = ChatClient.builder(chatModel)
                .defaultAdvisors(advisor)
                .build();

        // 当:发送包含隐私数据的请求
        String response = chatClient.prompt()
                .user("我的邮箱是test@example.com,手机号是555-1234")
                .call()
                .content();

        // 打印响应,验证效果
        log.info("AI响应:{}", response);
    }
}

运行测试后,你会在日志里看到这样的输出,说明脱敏成功啦:

代码语言:javascript
复制

原始消息:UserMessage{content='我的邮箱是test@example.com,手机号是555-1234', metadata={messageType=USER}, messageType=USER}
脱敏后消息:我的邮箱是[邮箱],手机号是[手机号]

Advisor使用最佳实践:别踩这些坑

  1. 别滥用Advisor:Advisor会增加一点性能开销,按需使用就好。如果把一堆Advisor串起来,AI响应速度会变慢,得不偿失。
  2. 关注Token消耗:拼太多历史对话会占用大量Token,不仅增加成本,还会压缩AI回答的空间(毕竟Token有上限)。
  3. 监控Token使用情况:SimpleLoggerAdvisor能帮你查看token_usage指标,记得多关注日志。如果Token不够用,可以调整MessageChatMemoryAdvisor的窗口大小,限制发送的历史消息数量。

总结

咱们今天聊了不少干货:从AI模型的无状态痛点,到用Spring AI Advisors API解决问题;从配置ChatClient,到实现日志记录和对话记忆;还解锁了自定义Advisor的高级玩法。

未来可期

现在就可以动手试试啦:先整个自定义Advisor玩玩,再对接向量数据库实现RAG(检索增强生成)功能。Advisors API就是你打造智能、上下文感知AI应用的“金钥匙”。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-12-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 HELLO程序员 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 无状态交互的坑,谁踩谁知道
  • 上下文才是王道
  • 搞懂Advisor架构:其实就是个“过滤器”
  • 这种方案的好处,肉眼可见
  • 配置ChatClient:你的AI交互主入口
  • 日志记录:排查问题的“照妖镜”
    • 来,给ChatClient加上日志功能
  • 对话记忆:让AI不再“鱼的记忆”
    • 把记忆Advisor接入系统
  • 运行时Advisor配置:灵活切换“技能”
    • 动态配置示例(控制器里搞事情)
  • 高级玩法:自定义Advisor,实现专属逻辑
    • 手把手教你写一个脱敏Advisor(隐藏手机号、邮箱等隐私数据)
  • 给脱敏Advisor做个全面测试
  • Advisor使用最佳实践:别踩这些坑
  • 总结
  • 未来可期
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档