Spring AI (第三章) 自定义拦截器+系统提示词(基础总结)
文章目录
- Spring AI (第三章) 自定义拦截器+系统提示词(基础总结)
- 一、章节目的
- 二、拦截器
- 2.1 拦截器是什么
- 2.2 拦截器的作用
- 三、常见内置实现类(Spring AI 已经帮你做好了)
- 四、自定义拦截器
- 4.1 代码实现
- 五、系统提示词
- 5.1 系统提示词的作用
- 5.2 提示词编写格式
- 5.3 提示词指南
- 5.4 代码中引用系统提示词的方式
- 六、代码测试
- 6.1 构建AI大模型对象代码
- 6.2 测试代码
- 七、基础小结
- 7.1 ChatClient是什么?
- 7.2 ChatClient创建场景
- 7.3 ChatClient全局和局部配置
一、章节目的
- 了解拦截器
- 常见的拦截器
- 自定义拦截器
- 系统提示词的用法
- 小结第一章、第二章、第三章的基础知识
二、拦截器
2.1 拦截器是什么
- 在 Spring AI 中,Advisor(拦截器)是一种用于拦截并增强 AI 调用过程的机制。
- 它类似于 Spring AOP(面向切面编程),允许开发者在请求前、调用中、响应后插入自定义逻辑。
2.2 拦截器的作用
- 拦截器是 Spring AI 框架中用于扩展大模型应用功能的重要机制。
- 通过在模型调用链中注册多个拦截器,可以在请求前、响应中、响应后等阶段对模型交互过程进行干预和增强,从而实现更高的可控性与灵活性、增强AI大模型的回复质量、安全性、异常处理等。
- 常见的应用场景有:敏感词过滤、日志记录、权限校验、上下文管理、响应清洗、结构化解析等功能;
• 功能增强:在不修改核心逻辑的情况下,对模型进行功能扩展,例如自动补全上下文、增强回复逻辑等;
• 安全防护:实现敏感词过滤、权限校验、输入输出合规性检查等安全措施;
• 可观测性与运维支持:记录日志、跟踪调用链、监控响应时间等,便于系统调优与问题排查;
• 响应优化:在响应阶段对模型输出进行清洗、结构化解析或格式化处理,提高结果的可用性与一致性。 - 总的来说,拦截器在 Spring AI 调用链 中扮演“可插拔增强层”的角色,使开发者能够以解耦的方式构建更安全、智能、稳定的大模型应用。
三、常见内置实现类(Spring AI 已经帮你做好了)
| 实现类 | 功能说明 |
|---|---|
SimpleLoggerAdvisor | 打印请求与响应日志 |
RetryAdvisor | 出错时自动重试模型调用 |
ResponseParserAdvisor | 将模型输出解析成 JSON、对象等结构化数据 |
PromptTemplateAdvisor | 支持使用模板生成 prompt |
SimpleSafetyAdvisor | 检测和过滤敏感内容 |
ContextAdvisor | 管理对话上下文(记忆系统) |
FunctionCallingAdvisor | 支持函数调用(Function Calling)功能 |
SystemPromptAdvisor | 统一注入系统级提示词 |
CompositeAdvisor | 组合多个拦截器顺序执行 |
知道应用场景就行了、后面需要用到根据拦截器查官方示例代码即可;
四、自定义拦截器
- 接下来自定义实现一个日志打印拦截器;
- 这个拦截器方便新手调试AI大模型、可以打断点观察AI大模型请求前、请求后的数据;
- 包括后续AI大模型配置了Rag向量数据库检索、Mcp服务等、工具类方法、都可以在日志拦截器里手动调试、分析数据;
4.1 代码实现
自定义实现拦截器代码
importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.ai.chat.client.advisor.api.*;importorg.springframework.ai.chat.model.MessageAggregator;importreactor.core.publisher.Flux;/** * 通过实现 CallAroundAdvisor 和 StreamAroundAdvisor 两个接口,你就能 在 AI 调用的前后(包括流式和非流式请求)插入自定义逻辑。 * CallAroundAdvisor 处理普通同步调用(例如 .call())的拦截逻辑 * StreamAroundAdvisor 处理流式响应(例如 .stream())的拦截逻辑 * * 自动执行一些“前置”和“后置”逻辑,比如打印日志、记录性能、修改请求内容、存储响应等。 * */publicclassMyLoggerAdvisorimplementsCallAroundAdvisor,StreamAroundAdvisor{privatestaticfinalLoggerlog=LoggerFactory.getLogger(MyLoggerAdvisor.class);publicStringgetName(){returnthis.getClass().getSimpleName();// 返回当前类名作为拦截器名称}publicintgetOrder(){return100;// 从0开始按顺序从小往大执行}privateAdvisedRequestbefore(AdvisedRequestrequest){log.info("AI Request: {}",request.userText());// 打印用户的提示词returnrequest;}privatevoidobserveAfter(AdvisedResponseadvisedResponse){log.info("AI Response: {}",advisedResponse.response().getResult().getOutput().getText());}publicStringtoString(){returnorg.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor.class.getSimpleName();}/** * 普通调用的环绕拦截逻辑 * 在AI聊天普通调用前后执行日志记录 * * @param advisedRequest 请求对象 * @param chain 拦截器链,用于继续执行后续拦截器 * @return 处理后的响应对象 */publicAdvisedResponsearoundCall(AdvisedRequestadvisedRequest,CallAroundAdvisorChainchain){advisedRequest=this.before(advisedRequest);AdvisedResponseadvisedResponse=chain.nextAroundCall(advisedRequest);// 读取上下文、多个拦截器传递全局变量对象// String userMsg = advisedResponse.adviseContext().get("userMsg").toString();// log.info("全局变量对象 userMsg:{}", userMsg);this.observeAfter(advisedResponse);returnadvisedResponse;}/** * 流式调用的环绕拦截逻辑 * 在AI聊天流式调用前后执行日志记录 * * @param advisedRequest 请求对象 * @param chain 拦截器链,用于继续执行后续拦截器 * @return 处理后的流式响应 */publicFlux<AdvisedResponse>aroundStream(AdvisedRequestadvisedRequest,StreamAroundAdvisorChainchain){// 1. 请求前处理:记录请求日志advisedRequest=this.before(advisedRequest);// 2. 执行实际的AI聊天流式调用Flux<AdvisedResponse>advisedResponses=chain.nextAroundStream(advisedRequest);// 3. 使用MessageAggregator聚合流式响应,并在完成后记录日志return(newMessageAggregator()).aggregateAdvisedResponse(advisedResponses,this::observeAfter);}}五、系统提示词
5.1 系统提示词的作用
- 虽然代码中系统提示词是可选的参数、但是在实际应用中是必不可少;
5.2 提示词编写格式
- 角色扮演、定义AI大模型是干什么的、一般用一个具体的岗位去定义、比如:Python高级开发工程师;
- 回复格式、增加输出约束,比如:根据上一个问题引导用户去了解相关的信息;
- 限制条件、比如:敏感问题不回答;
- 语言风格、根据智能体角色去选择回复语气、比如智能客服可以选择:和谐、可爱、小甜妹的语气回复;
- 提示词里面可以定义模板占位符
{}、在代码中可以动态替换占位符、比如:替换成用户的信息、让大模型知道自己在跟谁进行对话;
5.3 提示词指南
由于提示词是根据需求场景去调整的、所以有些场景对提示词的要求比较高、需要自行研究学习
- Prompt Engineering Guide 提示工程指南
- OpenAI 提示词工程指南
- Spring AI 提示词工程指南
- 智谱 AI Prompt 设计指南
5.4 代码中引用系统提示词的方式
纯文本
privatestaticfinalStringEXI_PROMPT="扮演EXI-小洲的贴身顾问。开场向用户表明身份,告知用户可咨询关于小洲的一切信息。"+"围绕小洲喜欢的事物、工作、生活、兴趣、感情展开;主动引导用户了解小洲的各个方面;"+"我们的目的就是把小洲这个人跟用户介绍清楚仔细、让用户对小洲这个人有着非常清晰的了解;"+"回复用户的信息中要可爱、和谐、讨人喜欢;";ChatClientchatClient=ChatClient.builder(dashscopeChatModel).defaultSystem(SYSTEM_PROMPT)// 配置提示词.build();文件提示词:test_prompt.st
# 角色说明扮演EXI-小洲的贴身顾问。开场向用户表明身份,告知用户可咨询关于小洲的一切信息。## 回复格式1. 问题分析2. 相关依据3. 梳理和建议## 语言风格1.回复用户的信息中要可爱、和谐、讨人喜欢;## 限制条件1.字数不要过多、回复内容不要有过多的重复词、减少长篇大论的情况; **特别注意:** - 围绕小洲喜欢的事物、工作、生活、兴趣、感情展开;主动引导用户了解小洲的各个方面; - 我们的目的就是把小洲这个人跟用户介绍清楚仔细、让用户对小洲这个人有着非常清晰的了解; 当前服务的用户: 姓名:{name}, 年龄:{age}, 性别:{sex}// 加载自定义 提示词文件ResourcepromptResource=newClassPathResource("static/prompt/test_prompt.st");ChatClientchatClient=ChatClient.builder(dashscopeChatModel).defaultSystem(promptResource)// 配置自定义提示词.build();以上2种方式二选一即可、在学习阶段推荐使用文件的方式去调试更好好维护
六、代码测试
6.1 构建AI大模型对象代码
importlombok.extern.slf4j.Slf4j;importorg.exi.exiaiagent.advisor.MyLoggerAdvisor;importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;importorg.springframework.ai.chat.memory.ChatMemory;importorg.springframework.ai.chat.memory.InMemoryChatMemory;importorg.springframework.ai.chat.model.ChatModel;importorg.springframework.ai.chat.model.ChatResponse;importorg.springframework.core.io.ClassPathResource;importorg.springframework.core.io.Resource;importorg.springframework.stereotype.Component;importstaticorg.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;importstaticorg.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;/** * 构建自定义拦截大模型聊天对象 */@Component@Slf4jpublicclassChatAdvisorApp{privatefinalChatClientchatClient;/** * 构造器 初始化大模型聊天对象 * @param dashscopeChatModel */publicChatAdvisorApp(ChatModeldashscopeChatModel){// 加载自定义 提示词文件ResourcepromptResource=newClassPathResource("static/prompt/test_prompt.st");// 创建内存存储对象ChatMemorychatMemory=UseMemoryChatMemory();chatClient=ChatClient.builder(dashscopeChatModel).defaultSystem(promptResource)// 配置自定义系统提示词.defaultAdvisors(newMessageChatMemoryAdvisor(chatMemory),// 配置上下文存储拦截器newMyLoggerAdvisor()// 配置自定义日志拦截器).build();}/** * 基于计算机内存存储、实现读取、写入聊天上下文 * @return */publicChatMemoryUseMemoryChatMemory(){/** * 默认使用:MessageWindowChatMemory * 存储位置: JVM 内存 (InMemoryChatMemoryRepository) * 数据持久化: 不持久化(应用重启后丢失) * 消息窗口: maxMessages = 20(默认,可配置) */InMemoryChatMemorychatMemory=newInMemoryChatMemory();returnchatMemory;}/** * 根据用户消息和用户的聊天ID进行回复 * @param message * @param chatId * @return */publicStringdoChat(Stringmessage,StringchatId){ChatResponsechatResponse=chatClient.prompt().user(message)// 用户消息// 根据用户信息动态调整系统提示词模板(姓名:{name}, 年龄:{age}, 性别:{sex}).system(p->p.param("name","小美").param("age","21").param("sex","女")).advisors(advisorSpec->advisorSpec.param(CHAT_MEMORY_CONVERSATION_ID_KEY,chatId)// 指定聊天记忆存储ID.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY,10)// 获取最近10条聊天记忆).call().chatResponse();Stringcontent=chatResponse.getResult().getOutput().getText();// 获取chatResponse的结果数据log.info("【对话请求】用户消息:{}",message);log.info("【模型响应】内容:{}",content);returncontent;}}6.2 测试代码
importjakarta.annotation.Resource;importorg.junit.jupiter.api.Test;importorg.springframework.boot.test.context.SpringBootTest;@SpringBootTestclassChatAdvisorAppTest{/** * @Resource 注解的作用是先根据字段名后根据类型 来自动匹配注入对应的 Bean */@ResourceprivateChatAdvisorAppchatAdvisorApp;@TestvoiddoChat(){StringchatId="a5daa1bb-e5e2-427e-9a74-68650459511f";Stringmessage="自我介绍下";chatAdvisorApp.doChat(message,chatId);}}拦截器断点调试
大模型回复结果
上图通过拦截器打印了用户发送和模型响应的数据、并且通过系统提示词模板占位符替换让大模型知道当前在服务那个用户
七、基础小结
7.1 ChatClient是什么?
- ChatClient 是 Spring AI 提供的一个轻量级客户端,基于 ChatModel 的上层封装,用于构建、发送聊天请求并处理大模型的回复。
- 核心能力
- 易用封装: 基于 ChatModel 的 builder API,减少直接拼 JSON 的负担。
- 消息结构: 支持 system、user、assistant 多角色消息。
- 上下文与记忆: 可接入如 MessageChatMemoryAdvisor 之类的记忆/拦截器。
- 工具/函数调用: 可注册工具以启用函数调用(不同模型支持度不同)。
- 流式响应: 支持逐 token/段落流式消费。
- 可组合: 通过拦截器/Advisor 叠加安全、重试、日志等能力。
7.2 ChatClient创建场景
- 系统级创建:在系统启动时统一创建一个全局的 ChatClient 实例,所有用户共享同一个大模型服务。该方式适用于模型配置固定、对话上下文相对独立的业务场景,可提高资源复用性和响应效率。
- 用户级创建:根据用户自定义的模型配置信息(例如模型类型、温度、上下文策略等),在每次对话时动态创建对应的 ChatClient 实例并执行回复。该方式灵活性更高,适用于需要根据用户偏好或业务需求进行个性化模型配置的场景。
7.3 ChatClient全局和局部配置
- ChatClient 在初始化时可以配置全局参数,例如拦截器(Interceptors)、系统提示词(System Prompt)、模型参数等。这些配置在整个应用生命周期内持续生效,适用于所有调用场景。
- 同时,ChatClient 也支持在具体方法调用时进行局部配置,例如在某次对话中临时添加拦截器、修改系统提示词或调整模型参数等。这类配置仅在当前方法调用期间生效,调用结束后会自动释放,不会影响全局设置。