SpringAI的Advisors-用拦截器的思维管Agent

Spring AI 的 Advisors:用拦截器的思维管 Agent

日志要打、记忆要读写、敏感词要拦,这些「每次对话都得做」的横切逻辑,散在业务代码里就到处都是 if。写过 Spring 的人有现成的思路------拦截器或者 AOP,把横切逻辑收到一条链上。Spring AI 的 Advisor 就是把这个思路用在对话循环上,这篇先把链看清,再亲手写一个护栏 Advisor。

Advisor 是什么

一次 ChatClient 的调用,从请求到响应,中间可以挂一串 Advisor,每个都能在调用前后插手,改请求、查响应、决定要不要放行。接口有两个:CallAroundAdvisor 管普通调用,StreamAroundAdvisor 管流式,签名都是「收请求、交下一棒、收响应」。

版本口径先说清(核验 2026-10-25):1.0 之前叫 AdvisedRequest 和 AdvisedResponse,1.0 起更名为 ChatClientRequest 和 ChatClientResponse,接口名 CallAroundAdvisor 不变。本文按 2.x 口径写。
#mermaid-svg-XOSDCh0LYq2WwhRZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XOSDCh0LYq2WwhRZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XOSDCh0LYq2WwhRZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XOSDCh0LYq2WwhRZ .error-icon{fill:#552222;}#mermaid-svg-XOSDCh0LYq2WwhRZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XOSDCh0LYq2WwhRZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XOSDCh0LYq2WwhRZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XOSDCh0LYq2WwhRZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XOSDCh0LYq2WwhRZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XOSDCh0LYq2WwhRZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XOSDCh0LYq2WwhRZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XOSDCh0LYq2WwhRZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XOSDCh0LYq2WwhRZ .marker.cross{stroke:#333333;}#mermaid-svg-XOSDCh0LYq2WwhRZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XOSDCh0LYq2WwhRZ p{margin:0;}#mermaid-svg-XOSDCh0LYq2WwhRZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XOSDCh0LYq2WwhRZ .cluster-label text{fill:#333;}#mermaid-svg-XOSDCh0LYq2WwhRZ .cluster-label span{color:#333;}#mermaid-svg-XOSDCh0LYq2WwhRZ .cluster-label span p{background-color:transparent;}#mermaid-svg-XOSDCh0LYq2WwhRZ .label text,#mermaid-svg-XOSDCh0LYq2WwhRZ span{fill:#333;color:#333;}#mermaid-svg-XOSDCh0LYq2WwhRZ .node rect,#mermaid-svg-XOSDCh0LYq2WwhRZ .node circle,#mermaid-svg-XOSDCh0LYq2WwhRZ .node ellipse,#mermaid-svg-XOSDCh0LYq2WwhRZ .node polygon,#mermaid-svg-XOSDCh0LYq2WwhRZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XOSDCh0LYq2WwhRZ .rough-node .label text,#mermaid-svg-XOSDCh0LYq2WwhRZ .node .label text,#mermaid-svg-XOSDCh0LYq2WwhRZ .image-shape .label,#mermaid-svg-XOSDCh0LYq2WwhRZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-XOSDCh0LYq2WwhRZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XOSDCh0LYq2WwhRZ .rough-node .label,#mermaid-svg-XOSDCh0LYq2WwhRZ .node .label,#mermaid-svg-XOSDCh0LYq2WwhRZ .image-shape .label,#mermaid-svg-XOSDCh0LYq2WwhRZ .icon-shape .label{text-align:center;}#mermaid-svg-XOSDCh0LYq2WwhRZ .node.clickable{cursor:pointer;}#mermaid-svg-XOSDCh0LYq2WwhRZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XOSDCh0LYq2WwhRZ .arrowheadPath{fill:#333333;}#mermaid-svg-XOSDCh0LYq2WwhRZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XOSDCh0LYq2WwhRZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XOSDCh0LYq2WwhRZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XOSDCh0LYq2WwhRZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XOSDCh0LYq2WwhRZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XOSDCh0LYq2WwhRZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XOSDCh0LYq2WwhRZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XOSDCh0LYq2WwhRZ .cluster text{fill:#333;}#mermaid-svg-XOSDCh0LYq2WwhRZ .cluster span{color:#333;}#mermaid-svg-XOSDCh0LYq2WwhRZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XOSDCh0LYq2WwhRZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XOSDCh0LYq2WwhRZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-XOSDCh0LYq2WwhRZ .icon-shape,#mermaid-svg-XOSDCh0LYq2WwhRZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XOSDCh0LYq2WwhRZ .icon-shape p,#mermaid-svg-XOSDCh0LYq2WwhRZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XOSDCh0LYq2WwhRZ .icon-shape .label rect,#mermaid-svg-XOSDCh0LYq2WwhRZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XOSDCh0LYq2WwhRZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XOSDCh0LYq2WwhRZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XOSDCh0LYq2WwhRZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ChatClient 请求
Advisor 1

日志
Advisor 2

记忆读写
Advisor 3

护栏
模型
最终响应

和 AOP 的对照

同一个思想,两副身位,放一起看很清楚。

AOP 的代理壳 Advisor 链
拦的粒度 任意 Bean 方法 一次对话调用
实现形态 运行时代理一层壳 链式 Around 拦截
切点表达 注解与切点表达式 按顺序挂 Advisor
典型用途 事务、鉴权 记忆、护栏、日志
顺序控制 @Order getOrder

区别在身位。AOP 站在对象外面套壳,Advisor 站在对话循环的路径上排队。写过拦截器的人半小时就能上手,这就是那套肌肉记忆带来的复利。

写一个护栏 Advisor

需求很具体,用户消息里出现敏感词就拦下,不发给模型。实现就是 Around 的标准形状,请求进来先检查,不干净就短路,干净就交下一棒。

java 复制代码
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.CallAroundAdvisor;
import org.springframework.ai.chat.client.advisor.CallAroundAdvisorChain;

public class GuardAdvisor implements CallAroundAdvisor {

    private final java.util.List<String> forbidden;

    public GuardAdvisor(java.util.List<String> forbidden) {
        this.forbidden = forbidden;
    }

    @Override
    public ChatClientResponse nextAroundCall(ChatClientRequest request,
                                             CallAroundAdvisorChain chain) {
        String userText = String.valueOf(request.prompt().getUserMessage());
        for (String word : forbidden) {
            if (userText.contains(word)) {
                // 短路:不交下一棒,直接返回兜底响应
                return new ChatClientResponse(
                        request.prompt().augmentSystemMessage("请求被拦截:包含敏感内容。"),
                        java.util.Map.of());
            }
        }
        return chain.nextAroundCall(request);   // 干净就放行
    }

    @Override
    public String getName() {
        return "GuardAdvisor";
    }

    @Override
    public int getOrder() {
        return 0;   // 数字小的先执行
    }
}

挂上去一行,链就生效。

java 复制代码
String answer = chatClient.prompt()
        .user(userMessage)
        .advisors(new GuardAdvisor(java.util.List.of("违禁词")))
        .call()
        .content();

(构造 ChatClientResponse 的具体签名随小版本微调,以你所用版本的文档为准,拦截与放行的控制流是稳的。)

内置的那些

Spring AI 自带一批现成的 Advisor,最常用的是记忆类,把对话历史的读写挂进链里,和「记忆落库」那篇的活对应,只是位置从你的业务代码挪到了链上。日志类的 Advisor 把请求响应的来龙去脉打进日志,是排查工单问题时的第一手资料。用法都是同一个动作:往链上挂,顺序靠 getOrder。

顺序有个类比可以记,请求像过滤器链一样按 order 从小到大走,响应回来逆序再走一遍,先挂的后拆。

记住一句话:横切逻辑住在链上,业务代码只写业务。

适用边界

这一篇讲 Advisor 的机制与护栏实例。流式的 StreamAroundAdvisor、Advisor 的错误处理与重试,以及把「记忆落库」整篇改造成 Advisor 形态,都是另外的题。接口签名按 2026-10-25 的 2.x 口径整理,从 1.x 迁移过来的项目注意更名。跑完护栏这个例子,把项目里散落各处的日志和校验挨个搬上链,搬完的那一刻,你的 agent 代码和你的 Spring 代码,就干净成同一种干净。

相关推荐
nice_lcj5201 小时前
【从零理解 Spring AI · 03】Spring AI 在 Spring 生态里的位置
java·spring boot·大模型·spring ai·ai应用开发
vjmap13 小时前
AI 智能体调用绘图工具画矢量地图:唯杰 AI 地图绘制上线,支持 MCP 接入 Cursor、Claude Code
gis·agent·mcp
review4454314 小时前
Codex Hooks 入门实战:生命周期事件、matcher 与5分钟跑通一个钩子
前端·agent·vibecoding
GoodTime14 小时前
一件一屏:用 AIUI 把「快递上门」做成一条会自己往下走的任务线
agent
武子康14 小时前
Agent 说“3 passed”就算修好了?先检查是谁判的通过
人工智能·agent
杨杨杨大侠16 小时前
AI 怎么知道自己改对了?从 Astra 修千问说到 Harness²
人工智能·agent
小七-七牛开发者17 小时前
Agent 支线知识:Personal Agent 如何跨应用完成任务?
ai·agent·工具调用·aiagent·mcp
文谦南宁AI普工19 小时前
CVSS 10.0 沙箱逃逸 + 假扩展后门:给 Agent 开发者的供应链与沙箱安全复盘(附可跑的配置审计器)
安全·agent·ai编程
vivo互联网技术19 小时前
把 Agent 的判断与行动变成可恢复的软件事实|KDC 系列 工程篇
架构·agent·ai编程