精控Spring AI日志
还在为 Spring AI 默认的日志抓狂吗?想看日志却看不到,一开 DEBUG 就刷屏... 别慌!
今天 NEO 带你解锁一个神级操作:自定义 Advisor,让你轻松掌控 AI 调用的每一个细节!
Advisor 是什么?Spring AI 的“拦截器”
如果你玩过 Servlet 的 Filter 或者 Spring AOP 的切面,那 Advisor 对你来说就是老朋友了。
简单来说,Spring AI 的 Advisor 就是一个调用拦截器。它能在你的代码调用大模型之前和之后“插一脚”,执行一些额外的操作。
想在调用前做个权限校验?或者在调用后记个详细日志?用 Advisor 就对了!
官方虽然提供了一些现成的 Advisor,但实际业务场景千变万化,总有不满足需求的时候。这时候,我们就需要自己动手,丰衣足食!
四步搞定!定制你的专属 Advisor
想拥有自己的 Advisor?跟着下面四步走,轻松搞定!
1)选择“岗哨”接口
根据你的需求,选择实现一个或两个接口:
CallAroundAdvisor
:处理普通的同步请求(非流式)。StreamAroundAdvisor
:处理酷炫的流式请求。
强烈建议两个都实现,全方位无死角!
public class MyCustomAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
// 实现方法...
}
2)实现核心“拦截”方法
这是 Advisor 的灵魂所在,你可以在这里对请求和响应为所欲为。
- 非流式处理 (
aroundCall
) :
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
// 1. 请求到达,先处理一下(前置处理)
AdvisedRequest modifiedRequest = processRequest(advisedRequest);
// 2. 放行,让请求继续前进
AdvisedResponse response = chain.nextAroundCall(modifiedRequest);
// 3. 响应返回,再处理一下(后置处理)
return processResponse(response);
}
- 流式处理 (
aroundStream
) :
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
// 1. 处理请求
AdvisedRequest modifiedRequest = processRequest(advisedRequest);
// 2. 调用链并处理流式响应
return chain.nextAroundStream(modifiedRequest)
.map(response -> processResponse(response)); // 对流中每个元素进行处理
}
3)排个队,定个序
通过 getOrder()
方法告诉 Spring AI 你的 Advisor 应该在什么时候执行。数字越小,优先级越高,越先被执行。
@Override
public int getOrder() {
// 值越小优先级越高,越先执行
return 100;
}
4)取个独一无二的名字
给你的 Advisor 一个响亮的名号!
@Override
public String getName() {
return "NEO自定义的 Advisor";
}
下面,进入实战环节!
实战:告别 DEBUG!打造 INFO 级日志神器
Spring AI 自带的 SimpleLoggerAdvisor
日志拦截器,看似贴心,实则有点“坑”——它用的是 Debug 级别输出日志。
而 Spring Boot 项目默认的日志级别是 Info,导致我们根本看不到任何日志输出!
(默认 Info 级别,看不到任何日志)
当然,你可以粗暴地修改配置文件,把日志级别调成 Debug:
logging:
level:
org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor: debug
日志是出来了,但又带来了新的问题:信息太杂乱!
(Debug 级别日志,信息过于繁杂)
为了更优雅地解决问题,我们来自己实现一个日志 Advisor:默认打印 Info 级别日志,并且只输出我们最关心的用户提问和 AI 回复。
在自己项目根包下新建 advisor
包,编写我们的日志神器 MyLoggerAdvisor
:
/**
* 自定义日志 Advisor 打印 info 级别日志、只输出单次用户提示词和 AI 回复的文本
**/
@Slf4j
public class MyLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
/**
* 获取 Advisor 的唯一名称
*/
@NotNull
@Override
public String getName() {
return this.getClass().getSimpleName();
}
/**
* 设置执行顺序,0 表示较高优先级
*/
@Override
public int getOrder() {
return 0;
}
/**
* 调用前置处理:记录用户请求
*/
private AdvisedRequest before(AdvisedRequest request) {
log.info("AI Request: {}", request.userText());
return request;
}
/**
* 调用后置处理:记录 AI 响应
*/
private void observeAfter(AdvisedResponse advisedResponse) {
log.info("AI Response: {}", advisedResponse.response().getResult().getOutput().getContent());
}
/**
* 环绕处理(非流式)
*/
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
// 1. 调用前
advisedRequest = this.before(advisedRequest);
// 2. 放行
AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
// 3. 调用后
this.observeAfter(advisedResponse);
return advisedResponse;
}
/**
* 环绕处理(流式)
*/
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
// 1. 调用前
advisedRequest = this.before(advisedRequest);
// 2. 放行
Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);
// 3. 调用后,使用 MessageAggregator 聚合流式响应,然后统一记录
return (new MessageAggregator())
.aggregateAdvisedResponse(
advisedResponses,
this::observeAfter
);
}
}
代码小贴士:在流式处理
aroundStream
中,我们用MessageAggregator
工具类将零散的Flux
响应聚合成一个完整的响应,这样就能在日志中打印出最终的、完整的 AI 回复,而不是一堆零散的数据块。
最后,在 App
中“装备”上我们刚出炉的日志神器:
public App(ChatModel ollamaChatModel) {
// 初始化基于内存的对话记忆
ChatMemory chatMemory = new InMemoryChatMemory();
chatClient = ChatClient.builder(ollamaChatModel)
.defaultSystem(SYSTEM_PROMPT)
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory),
// 替换掉官方的 SimpleLoggerAdvisor
// new SimpleLoggerAdvisor()
// 使用我们自定义的日志 Advisor
new MyLoggerAdvisor()
)
.build();
}
现在再运行程序,看看效果如何?
(效果拔群!清爽的 Info 级别日志)
看!日志变得如此清爽,只留下了我们最需要的信息。
通过自定义 Advisor,我们不仅解决了日志记录的痛点,更解锁了 Spring AI 的一个强大扩展点。无论是鉴权、计费、还是更复杂的业务逻辑,都可以通过 Advisor 优雅地实现,让你的 AI 应用更加健壮和灵活。
你还有哪些使用 Advisor 的奇思妙想?欢迎在评论区留言讨论!
如果觉得这篇文章对你有帮助,别忘了点赞、在看、分享三连哦!
精控Spring AI日志的更多相关文章
- 精尽Spring Boot源码分析 - 日志系统
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- Spring Boot 日志配置
Spring Boot 日志配置 默认日志 Logback: 默认情况下,Spring Boot会用Logback来记录日志,并用INFO级别输出到控制台.在运行应用程序和其他例子时,你应该已经看到很 ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerExceptionResolver 组件
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - ViewResolver 组件
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring Boot源码分析 - 文章导读
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - Condition 接口的扩展
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
随机推荐
- AI可解释性 II | Saliency Maps-based 归因方法(Attribution)论文导读(持续更新)
AI可解释性 II | Saliency Maps-based 归因方法(Attribution)论文导读(持续更新) 导言 本文作为AI可解释性系列的第二部分,旨在以汉语整理并阅读归因方法(Attr ...
- 🎀MySQL-关键字执行顺序
简介 在MySQL中,SQL查询语句的执行遵循一定的逻辑顺序,即使这些关键字在SQL语句中的物理排列可能有所不同. 语句顺序 (8) SELECT (9) DISTINCT<select_lis ...
- fidder抓包微信小程序的方法
想获取小程序的请求和返回数据,要么通过抓包工具抓包,要么使用小程序调试工具直接查看 总结下怎样使用fidder抓包 第一步,各种配置,把下面一系列图片里该勾的都勾上,够好了重启fidder 第二步,打 ...
- 解决github页面打不开 页面加载慢,注册不了显示Unable to verify your captcha response...
解决国内打开Github页面.注册等问题 下列方法可以解决: github网站页面打不开: github页面打开慢,偶尔打不开,部分页面链接也打不开: 注册不了github账号,找回密码失败等,显示U ...
- mac使用pptp的正确方式
环境:macos mojave 10.14.6 尝试的解决方案: mac自带vpn 结论:已经不支持pptp协议 使用shimo 结论:无用,连接的时候没反应 为了解决不能连接的问题,某老外写的ppt ...
- k8s-1.18.0版本-kubeadmin部署(提供阿里云镜像)(二)master节点
k8s-1.18.0版本-kubeadmin部署 (提供阿里云镜像) 个人服务器地址:http://101.201.140.7/wp-blog/ 系统开启kube-proxy的ipvs前置条件 从k8 ...
- [HTB] 靶机学习(一)Heal
[HTB] 靶机学习(一)Heal 概要 学习hackthebox的第一天,本人为初学者,将以初学者的角度对靶机渗透进行学习,中途可能会插入一些跟实操关系不大的相关新概念的学习和解释,尽量做到详细,不 ...
- GStreamer开发笔记(三):测试gstreamer/v4l2+sdl2/v4l2+QtOpengl打摄像头延迟和内存
前言 前面测试了多种技术路线,本篇补全剩下的2种主流技术,v4l2+sdl2(偏底层),v4l2+QtOpengl(应用),v4l2+ffmpeg+QtQImage(Image的方式转图低于1ms ...
- Vue3的14种组件通信方式
对于日常使用vue3开发项目的前端小伙伴来说,组件通信方式可以说是必会的基本功,今天带大家一起盘下vue3的通信方式. 我们这里按照组件的关系来划分.总共包含14中组件通信方式. 一.父子通信 pro ...
- vue3 基础-API-响应式 ref, reactive
上篇咱介绍了 CompositionAPI, 其核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题. 然后初介绍了 setup 函数的作用, 即其是在 cr ...