欢迎光临
我们一直在努力

Spring AI 拦截器实战:Advisors 详解与代码示例

本文根据 Spring AI 官方文档 https://docs.spring.io/spring-ai/reference/api/advisors.html 整理,用大白话把原版内容讲清楚,代码可以直接复制使用。

一、这玩意儿是干啥的?

你在用 Spring AI 调用大模型的时候,是不是经常要干这些破事:

  • 把聊天记录翻出来塞进 Prompt
  • 从向量数据库搜点相关文档一起发给模型
  • 记录一下用户问了啥、模型回了啥
  • 检查一下模型有没有输出敏感词

如果每次调用都要手动写一遍这些逻辑,代码很快就没法看了。

Advisors 就是来解决这个问题的。它本质上是个拦截器,在请求到达大模型之前、响应返回之后,插一段自定义逻辑进去。

官网原话是:封装重复的生成式 AI 模式,让代码更干净、更可复用。

二、怎么用?先看个例子

// 先把需要的组件准备好
ChatMemory chatMemory = ...; // 记忆存储器
VectorStore vectorStore = ...; // 向量数据库

// 创建 ChatClient 的时候顺便把 Advisor 注册上
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(), // 记忆功能
QuestionAnswerAdvisor.builder(vectorStore).build() // RAG 功能
)
.build();

// 后面调用的时候,这些功能就自动生效了
String conversationId = "678";
String response = this.chatClient.prompt()
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId))
.user("你有什么功能?")
.call()
.content();

关键点:Advisor 推荐在构建 ChatClient 的时候通过 defaultAdvisors() 一次性注册,而不是每次调用的时候临时加。

三、核心执行流程

3.1 整体时序图

下面这张图是整个 Advisor 链的执行过程,建议仔细看:

大模型Advisor 3(order最大)Advisor 2Advisor 1(order最小)你的代码大模型Advisor 3(order最大)Advisor 2Advisor 1(order最小)你的代码#mermaid-svg-ZesB0u2WKPSLG0ib{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-ZesB0u2WKPSLG0ib .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZesB0u2WKPSLG0ib .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZesB0u2WKPSLG0ib .error-icon{fill:#552222;}#mermaid-svg-ZesB0u2WKPSLG0ib .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZesB0u2WKPSLG0ib .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZesB0u2WKPSLG0ib .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZesB0u2WKPSLG0ib .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZesB0u2WKPSLG0ib .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZesB0u2WKPSLG0ib .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZesB0u2WKPSLG0ib .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZesB0u2WKPSLG0ib .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZesB0u2WKPSLG0ib .marker.cross{stroke:#333333;}#mermaid-svg-ZesB0u2WKPSLG0ib svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZesB0u2WKPSLG0ib p{margin:0;}#mermaid-svg-ZesB0u2WKPSLG0ib .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZesB0u2WKPSLG0ib text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ZesB0u2WKPSLG0ib .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZesB0u2WKPSLG0ib .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ZesB0u2WKPSLG0ib .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ZesB0u2WKPSLG0ib .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ZesB0u2WKPSLG0ib #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ZesB0u2WKPSLG0ib .sequenceNumber{fill:white;}#mermaid-svg-ZesB0u2WKPSLG0ib #sequencenumber{fill:#333;}#mermaid-svg-ZesB0u2WKPSLG0ib #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ZesB0u2WKPSLG0ib .messageText{fill:#333;stroke:none;}#mermaid-svg-ZesB0u2WKPSLG0ib .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZesB0u2WKPSLG0ib .labelText,#mermaid-svg-ZesB0u2WKPSLG0ib .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ZesB0u2WKPSLG0ib .loopText,#mermaid-svg-ZesB0u2WKPSLG0ib .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ZesB0u2WKPSLG0ib .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZesB0u2WKPSLG0ib .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ZesB0u2WKPSLG0ib .noteText,#mermaid-svg-ZesB0u2WKPSLG0ib .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ZesB0u2WKPSLG0ib .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZesB0u2WKPSLG0ib .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZesB0u2WKPSLG0ib .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZesB0u2WKPSLG0ib .actorPopupMenu{position:absolute;}#mermaid-svg-ZesB0u2WKPSLG0ib .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ZesB0u2WKPSLG0ib .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZesB0u2WKPSLG0ib .actor-man circle,#mermaid-svg-ZesB0u2WKPSLG0ib line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ZesB0u2WKPSLG0ib :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}处理请求(如:记日志)处理请求(如:注入记忆)处理请求(如:RAG检索)处理响应处理响应处理响应1. 发送 ChatClientRequest2. chain.nextCall(request)3. chain.nextCall(request)4. 调用大模型5. 返回响应6. 返回响应7. 返回响应8. 返回 ChatClientResponse

关键理解:

  • 请求往下传(1→2→3→4),响应往回走(5→6→7→8)
  • 最后一个 Advisor 负责真正调用大模型
  • 越靠近用户的 Advisor,getOrder() 数值越小

3.2 Order 值的含义(容易搞反)

#mermaid-svg-cVMP6mA6VYQQOY78{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-cVMP6mA6VYQQOY78 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cVMP6mA6VYQQOY78 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cVMP6mA6VYQQOY78 .error-icon{fill:#552222;}#mermaid-svg-cVMP6mA6VYQQOY78 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cVMP6mA6VYQQOY78 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cVMP6mA6VYQQOY78 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cVMP6mA6VYQQOY78 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cVMP6mA6VYQQOY78 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cVMP6mA6VYQQOY78 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cVMP6mA6VYQQOY78 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cVMP6mA6VYQQOY78 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cVMP6mA6VYQQOY78 .marker.cross{stroke:#333333;}#mermaid-svg-cVMP6mA6VYQQOY78 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cVMP6mA6VYQQOY78 p{margin:0;}#mermaid-svg-cVMP6mA6VYQQOY78 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-cVMP6mA6VYQQOY78 .cluster-label text{fill:#333;}#mermaid-svg-cVMP6mA6VYQQOY78 .cluster-label span{color:#333;}#mermaid-svg-cVMP6mA6VYQQOY78 .cluster-label span p{background-color:transparent;}#mermaid-svg-cVMP6mA6VYQQOY78 .label text,#mermaid-svg-cVMP6mA6VYQQOY78 span{fill:#333;color:#333;}#mermaid-svg-cVMP6mA6VYQQOY78 .node rect,#mermaid-svg-cVMP6mA6VYQQOY78 .node circle,#mermaid-svg-cVMP6mA6VYQQOY78 .node ellipse,#mermaid-svg-cVMP6mA6VYQQOY78 .node polygon,#mermaid-svg-cVMP6mA6VYQQOY78 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cVMP6mA6VYQQOY78 .rough-node .label text,#mermaid-svg-cVMP6mA6VYQQOY78 .node .label text,#mermaid-svg-cVMP6mA6VYQQOY78 .image-shape .label,#mermaid-svg-cVMP6mA6VYQQOY78 .icon-shape .label{text-anchor:middle;}#mermaid-svg-cVMP6mA6VYQQOY78 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cVMP6mA6VYQQOY78 .rough-node .label,#mermaid-svg-cVMP6mA6VYQQOY78 .node .label,#mermaid-svg-cVMP6mA6VYQQOY78 .image-shape .label,#mermaid-svg-cVMP6mA6VYQQOY78 .icon-shape .label{text-align:center;}#mermaid-svg-cVMP6mA6VYQQOY78 .node.clickable{cursor:pointer;}#mermaid-svg-cVMP6mA6VYQQOY78 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cVMP6mA6VYQQOY78 .arrowheadPath{fill:#333333;}#mermaid-svg-cVMP6mA6VYQQOY78 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cVMP6mA6VYQQOY78 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cVMP6mA6VYQQOY78 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cVMP6mA6VYQQOY78 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cVMP6mA6VYQQOY78 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cVMP6mA6VYQQOY78 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cVMP6mA6VYQQOY78 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cVMP6mA6VYQQOY78 .cluster text{fill:#333;}#mermaid-svg-cVMP6mA6VYQQOY78 .cluster span{color:#333;}#mermaid-svg-cVMP6mA6VYQQOY78 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-cVMP6mA6VYQQOY78 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cVMP6mA6VYQQOY78 rect.text{fill:none;stroke-width:0;}#mermaid-svg-cVMP6mA6VYQQOY78 .icon-shape,#mermaid-svg-cVMP6mA6VYQQOY78 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cVMP6mA6VYQQOY78 .icon-shape p,#mermaid-svg-cVMP6mA6VYQQOY78 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cVMP6mA6VYQQOY78 .icon-shape .label rect,#mermaid-svg-cVMP6mA6VYQQOY78 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cVMP6mA6VYQQOY78 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cVMP6mA6VYQQOY78 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cVMP6mA6VYQQOY78 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

响应阶段

大模型

order=2最先处理响应

order=1

order=0最后处理响应

返回给用户

请求阶段

用户请求

order=0最先执行

order=1

order=2

大模型

记住:

  • 请求阶段:order=0 最先执行
  • 响应阶段:order=0 最后执行
  • 这就是栈结构的特点

3.3 完整流程图(包含阻断情况)

#mermaid-svg-LLuAMlPBHeWrq0qQ{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-LLuAMlPBHeWrq0qQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LLuAMlPBHeWrq0qQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LLuAMlPBHeWrq0qQ .error-icon{fill:#552222;}#mermaid-svg-LLuAMlPBHeWrq0qQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LLuAMlPBHeWrq0qQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LLuAMlPBHeWrq0qQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LLuAMlPBHeWrq0qQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LLuAMlPBHeWrq0qQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LLuAMlPBHeWrq0qQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LLuAMlPBHeWrq0qQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LLuAMlPBHeWrq0qQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LLuAMlPBHeWrq0qQ .marker.cross{stroke:#333333;}#mermaid-svg-LLuAMlPBHeWrq0qQ svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LLuAMlPBHeWrq0qQ p{margin:0;}#mermaid-svg-LLuAMlPBHeWrq0qQ .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-LLuAMlPBHeWrq0qQ .cluster-label text{fill:#333;}#mermaid-svg-LLuAMlPBHeWrq0qQ .cluster-label span{color:#333;}#mermaid-svg-LLuAMlPBHeWrq0qQ .cluster-label span p{background-color:transparent;}#mermaid-svg-LLuAMlPBHeWrq0qQ .label text,#mermaid-svg-LLuAMlPBHeWrq0qQ span{fill:#333;color:#333;}#mermaid-svg-LLuAMlPBHeWrq0qQ .node rect,#mermaid-svg-LLuAMlPBHeWrq0qQ .node circle,#mermaid-svg-LLuAMlPBHeWrq0qQ .node ellipse,#mermaid-svg-LLuAMlPBHeWrq0qQ .node polygon,#mermaid-svg-LLuAMlPBHeWrq0qQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LLuAMlPBHeWrq0qQ .rough-node .label text,#mermaid-svg-LLuAMlPBHeWrq0qQ .node .label text,#mermaid-svg-LLuAMlPBHeWrq0qQ .image-shape .label,#mermaid-svg-LLuAMlPBHeWrq0qQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-LLuAMlPBHeWrq0qQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LLuAMlPBHeWrq0qQ .rough-node .label,#mermaid-svg-LLuAMlPBHeWrq0qQ .node .label,#mermaid-svg-LLuAMlPBHeWrq0qQ .image-shape .label,#mermaid-svg-LLuAMlPBHeWrq0qQ .icon-shape .label{text-align:center;}#mermaid-svg-LLuAMlPBHeWrq0qQ .node.clickable{cursor:pointer;}#mermaid-svg-LLuAMlPBHeWrq0qQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LLuAMlPBHeWrq0qQ .arrowheadPath{fill:#333333;}#mermaid-svg-LLuAMlPBHeWrq0qQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LLuAMlPBHeWrq0qQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LLuAMlPBHeWrq0qQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LLuAMlPBHeWrq0qQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LLuAMlPBHeWrq0qQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LLuAMlPBHeWrq0qQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LLuAMlPBHeWrq0qQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LLuAMlPBHeWrq0qQ .cluster text{fill:#333;}#mermaid-svg-LLuAMlPBHeWrq0qQ .cluster span{color:#333;}#mermaid-svg-LLuAMlPBHeWrq0qQ 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-LLuAMlPBHeWrq0qQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LLuAMlPBHeWrq0qQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-LLuAMlPBHeWrq0qQ .icon-shape,#mermaid-svg-LLuAMlPBHeWrq0qQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LLuAMlPBHeWrq0qQ .icon-shape p,#mermaid-svg-LLuAMlPBHeWrq0qQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LLuAMlPBHeWrq0qQ .icon-shape .label rect,#mermaid-svg-LLuAMlPBHeWrq0qQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LLuAMlPBHeWrq0qQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LLuAMlPBHeWrq0qQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LLuAMlPBHeWrq0qQ :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

是

是

是

否

否

否

用户调用 ChatClient

Step 1: 框架创建ChatClientRequest + 空上下文

Advisor链开始

Advisor A: order=0比如: 记录请求日志

调用 chain.nextCall?

Advisor B: order=1比如: 注入对话记忆

调用 chain.nextCall?

Advisor C: order=2比如: RAG检索增强

调用 chain.nextCall?

最后一个内置Advisor调用大模型

大模型返回响应

Advisor C 处理响应

Advisor B 处理响应

Advisor A 处理响应

返回 ChatClientResponse 给用户

Advisor A 直接返回响应阻断后续执行

Advisor B 直接返回响应阻断后续执行

Advisor C 直接返回响应阻断后续执行

说明:

  • 绿色箭头是正常流程
  • 红色虚线表示某个 Advisor 可以直接返回(比如安全过滤直接拦截了)

四、核心组件

4.1 两大场景的接口设计

场景核心接口说明
非流式(一问一答) CallAdvisor + CallAdvisorChain 处理完整的请求和响应
流式(一个字一个字蹦) StreamAdvisor + StreamAdvisorChain 用 Reactor 的 Flux 处理数据流

另外还有两个数据载体:

  • ChatClientRequest:封装用户的 Prompt,附带一个共享上下文
  • ChatClientResponse:封装模型的返回结果,同样带着上下文

4.2 两个关键方法

// 非流式 Advisor 要实现的方法
ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain);

// 流式 Advisor 要实现的方法
Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain);

每个 Advisor 还需要实现:

  • getOrder():决定执行顺序,数字越小越靠前
  • getName():给 Advisor 起个唯一的名字

4.3 Order 值的官方常量

public interface Ordered {
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; // 最高优先级(最先执行)
int LOWEST_PRECEDENCE = Integer.MAX_VALUE; // 最低优先级(最后执行)
}

五、自己动手写一个 Advisor

示例1:日志记录 Advisor(只看不改)

这个 Advisor 什么都不改,就是看一眼请求和响应,打条日志。

public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {
private static final Logger log = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);

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

@Override
public int getOrder() {
return 0; // 希望它最先执行
}

// 非流式版本
@Override
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
log.info("请求内容:{}", request);
ChatClientResponse response = chain.nextCall(request);
log.info("响应内容:{}", response);
return response;
}

// 流式版本(需要聚合流中的数据)
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain) {
log.info("请求内容:{}", request);
Flux<ChatClientResponse> responses = chain.nextStream(request);
// 用工具类把流式数据聚合成一条,方便打日志
return new ChatClientMessageAggregator()
.aggregateChatClientResponse(responses, this::logResponse);
}

private void logResponse(ChatClientResponse response) {
log.info("响应内容:{}", response);
}
}

注意:用 MessageAggregator 聚合流式数据时,只能读不能改。

示例2:Re-Reading 增强 Advisor(动手改请求)

有一篇论文(Re-Reading Improves Reasoning in LLMs)发现:让模型把问题再读一遍,能提高推理准确率。用 Advisor 实现这个很简单。

public class ReReadingAdvisor implements CallAdvisor {

private static final String TEMPLATE = """
用户问题:{question}
请再把问题读一遍:{question}
"""
;

private int order = 0;

@Override
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
// 拿到原始问题
String originalText = request.prompt().getUserMessage().getText();

// 套上模板,让问题重复一遍
String augmentedText = TEMPLATE.replace("{question}", originalText);

// 创建一个新请求(注意:Request 是不可变的,要调用 mutate())
ChatClientRequest newRequest = request.mutate()
.prompt(request.prompt().augmentUserMessage(augmentedText))
.build();

// 传给下一个 Advisor 或最终的大模型
return chain.nextCall(newRequest);
}

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

public void setOrder(int order) {
this.order = order;
}

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

六、流式 vs 非流式的区别

#mermaid-svg-BGERQZevxFy8XEpj{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-BGERQZevxFy8XEpj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BGERQZevxFy8XEpj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BGERQZevxFy8XEpj .error-icon{fill:#552222;}#mermaid-svg-BGERQZevxFy8XEpj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BGERQZevxFy8XEpj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BGERQZevxFy8XEpj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BGERQZevxFy8XEpj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BGERQZevxFy8XEpj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BGERQZevxFy8XEpj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BGERQZevxFy8XEpj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BGERQZevxFy8XEpj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BGERQZevxFy8XEpj .marker.cross{stroke:#333333;}#mermaid-svg-BGERQZevxFy8XEpj svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BGERQZevxFy8XEpj p{margin:0;}#mermaid-svg-BGERQZevxFy8XEpj .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-BGERQZevxFy8XEpj .cluster-label text{fill:#333;}#mermaid-svg-BGERQZevxFy8XEpj .cluster-label span{color:#333;}#mermaid-svg-BGERQZevxFy8XEpj .cluster-label span p{background-color:transparent;}#mermaid-svg-BGERQZevxFy8XEpj .label text,#mermaid-svg-BGERQZevxFy8XEpj span{fill:#333;color:#333;}#mermaid-svg-BGERQZevxFy8XEpj .node rect,#mermaid-svg-BGERQZevxFy8XEpj .node circle,#mermaid-svg-BGERQZevxFy8XEpj .node ellipse,#mermaid-svg-BGERQZevxFy8XEpj .node polygon,#mermaid-svg-BGERQZevxFy8XEpj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BGERQZevxFy8XEpj .rough-node .label text,#mermaid-svg-BGERQZevxFy8XEpj .node .label text,#mermaid-svg-BGERQZevxFy8XEpj .image-shape .label,#mermaid-svg-BGERQZevxFy8XEpj .icon-shape .label{text-anchor:middle;}#mermaid-svg-BGERQZevxFy8XEpj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BGERQZevxFy8XEpj .rough-node .label,#mermaid-svg-BGERQZevxFy8XEpj .node .label,#mermaid-svg-BGERQZevxFy8XEpj .image-shape .label,#mermaid-svg-BGERQZevxFy8XEpj .icon-shape .label{text-align:center;}#mermaid-svg-BGERQZevxFy8XEpj .node.clickable{cursor:pointer;}#mermaid-svg-BGERQZevxFy8XEpj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BGERQZevxFy8XEpj .arrowheadPath{fill:#333333;}#mermaid-svg-BGERQZevxFy8XEpj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BGERQZevxFy8XEpj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BGERQZevxFy8XEpj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BGERQZevxFy8XEpj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BGERQZevxFy8XEpj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BGERQZevxFy8XEpj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BGERQZevxFy8XEpj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BGERQZevxFy8XEpj .cluster text{fill:#333;}#mermaid-svg-BGERQZevxFy8XEpj .cluster span{color:#333;}#mermaid-svg-BGERQZevxFy8XEpj 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-BGERQZevxFy8XEpj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BGERQZevxFy8XEpj rect.text{fill:none;stroke-width:0;}#mermaid-svg-BGERQZevxFy8XEpj .icon-shape,#mermaid-svg-BGERQZevxFy8XEpj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BGERQZevxFy8XEpj .icon-shape p,#mermaid-svg-BGERQZevxFy8XEpj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BGERQZevxFy8XEpj .icon-shape .label rect,#mermaid-svg-BGERQZevxFy8XEpj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BGERQZevxFy8XEpj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BGERQZevxFy8XEpj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BGERQZevxFy8XEpj :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

流式 StreamAdvisor

收到完整请求

处理请求

调用 chain.nextStream

拿到 Flux 数据流

逐个处理每个数据块

最终聚合或逐块输出

非流式 CallAdvisor

收到完整请求

处理请求

调用 chain.nextCall

等待完整响应

处理完整响应

返回 ChatClientResponse

流式 Advisor 的代码骨架(官网给的):

@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain) {
return Mono.just(request)
.publishOn(Schedulers.boundedElastic()) // 换一个能处理阻塞操作的线程池
.map(req -> {
// 请求前置处理(可能有阻塞操作)
return req;
})
.flatMapMany(chain::nextStream)
.map(response -> {
// 响应后置处理
return response;
});
}

注意:千万别在响应式流里直接做阻塞操作(比如 Thread.sleep、数据库查询),会出问题。

七、Spring AI 内置的 Advisor(拿来就用)

#mermaid-svg-W2enIZ63TFEG3aiG{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-W2enIZ63TFEG3aiG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W2enIZ63TFEG3aiG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W2enIZ63TFEG3aiG .error-icon{fill:#552222;}#mermaid-svg-W2enIZ63TFEG3aiG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W2enIZ63TFEG3aiG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W2enIZ63TFEG3aiG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W2enIZ63TFEG3aiG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W2enIZ63TFEG3aiG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W2enIZ63TFEG3aiG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W2enIZ63TFEG3aiG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W2enIZ63TFEG3aiG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W2enIZ63TFEG3aiG .marker.cross{stroke:#333333;}#mermaid-svg-W2enIZ63TFEG3aiG svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W2enIZ63TFEG3aiG p{margin:0;}#mermaid-svg-W2enIZ63TFEG3aiG .edge{stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .section–1 rect,#mermaid-svg-W2enIZ63TFEG3aiG .section–1 path,#mermaid-svg-W2enIZ63TFEG3aiG .section–1 circle,#mermaid-svg-W2enIZ63TFEG3aiG .section–1 polygon,#mermaid-svg-W2enIZ63TFEG3aiG .section–1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .section–1 text{fill:#ffffff;}#mermaid-svg-W2enIZ63TFEG3aiG .node-icon–1{font-size:40px;color:#ffffff;}#mermaid-svg-W2enIZ63TFEG3aiG .section-edge–1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .edge-depth–1{stroke-width:17;}#mermaid-svg-W2enIZ63TFEG3aiG .section–1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled,#mermaid-svg-W2enIZ63TFEG3aiG .disabled circle,#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:lightgray;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:#efefef;}#mermaid-svg-W2enIZ63TFEG3aiG .section-0 rect,#mermaid-svg-W2enIZ63TFEG3aiG .section-0 path,#mermaid-svg-W2enIZ63TFEG3aiG .section-0 circle,#mermaid-svg-W2enIZ63TFEG3aiG .section-0 polygon,#mermaid-svg-W2enIZ63TFEG3aiG .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-W2enIZ63TFEG3aiG .section-0 text{fill:black;}#mermaid-svg-W2enIZ63TFEG3aiG .node-icon-0{font-size:40px;color:black;}#mermaid-svg-W2enIZ63TFEG3aiG .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-W2enIZ63TFEG3aiG .edge-depth-0{stroke-width:14;}#mermaid-svg-W2enIZ63TFEG3aiG .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled,#mermaid-svg-W2enIZ63TFEG3aiG .disabled circle,#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:lightgray;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:#efefef;}#mermaid-svg-W2enIZ63TFEG3aiG .section-1 rect,#mermaid-svg-W2enIZ63TFEG3aiG .section-1 path,#mermaid-svg-W2enIZ63TFEG3aiG .section-1 circle,#mermaid-svg-W2enIZ63TFEG3aiG .section-1 polygon,#mermaid-svg-W2enIZ63TFEG3aiG .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .section-1 text{fill:black;}#mermaid-svg-W2enIZ63TFEG3aiG .node-icon-1{font-size:40px;color:black;}#mermaid-svg-W2enIZ63TFEG3aiG .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .edge-depth-1{stroke-width:11;}#mermaid-svg-W2enIZ63TFEG3aiG .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled,#mermaid-svg-W2enIZ63TFEG3aiG .disabled circle,#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:lightgray;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:#efefef;}#mermaid-svg-W2enIZ63TFEG3aiG .section-2 rect,#mermaid-svg-W2enIZ63TFEG3aiG .section-2 path,#mermaid-svg-W2enIZ63TFEG3aiG .section-2 circle,#mermaid-svg-W2enIZ63TFEG3aiG .section-2 polygon,#mermaid-svg-W2enIZ63TFEG3aiG .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .section-2 text{fill:#ffffff;}#mermaid-svg-W2enIZ63TFEG3aiG .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-W2enIZ63TFEG3aiG .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .edge-depth-2{stroke-width:8;}#mermaid-svg-W2enIZ63TFEG3aiG .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled,#mermaid-svg-W2enIZ63TFEG3aiG .disabled circle,#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:lightgray;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:#efefef;}#mermaid-svg-W2enIZ63TFEG3aiG .section-3 rect,#mermaid-svg-W2enIZ63TFEG3aiG .section-3 path,#mermaid-svg-W2enIZ63TFEG3aiG .section-3 circle,#mermaid-svg-W2enIZ63TFEG3aiG .section-3 polygon,#mermaid-svg-W2enIZ63TFEG3aiG .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .section-3 text{fill:black;}#mermaid-svg-W2enIZ63TFEG3aiG .node-icon-3{font-size:40px;color:black;}#mermaid-svg-W2enIZ63TFEG3aiG .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .edge-depth-3{stroke-width:5;}#mermaid-svg-W2enIZ63TFEG3aiG .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled,#mermaid-svg-W2enIZ63TFEG3aiG .disabled circle,#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:lightgray;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:#efefef;}#mermaid-svg-W2enIZ63TFEG3aiG .section-4 rect,#mermaid-svg-W2enIZ63TFEG3aiG .section-4 path,#mermaid-svg-W2enIZ63TFEG3aiG .section-4 circle,#mermaid-svg-W2enIZ63TFEG3aiG .section-4 polygon,#mermaid-svg-W2enIZ63TFEG3aiG .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .section-4 text{fill:black;}#mermaid-svg-W2enIZ63TFEG3aiG .node-icon-4{font-size:40px;color:black;}#mermaid-svg-W2enIZ63TFEG3aiG .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .edge-depth-4{stroke-width:2;}#mermaid-svg-W2enIZ63TFEG3aiG .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled,#mermaid-svg-W2enIZ63TFEG3aiG .disabled circle,#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:lightgray;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:#efefef;}#mermaid-svg-W2enIZ63TFEG3aiG .section-5 rect,#mermaid-svg-W2enIZ63TFEG3aiG .section-5 path,#mermaid-svg-W2enIZ63TFEG3aiG .section-5 circle,#mermaid-svg-W2enIZ63TFEG3aiG .section-5 polygon,#mermaid-svg-W2enIZ63TFEG3aiG .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .section-5 text{fill:black;}#mermaid-svg-W2enIZ63TFEG3aiG .node-icon-5{font-size:40px;color:black;}#mermaid-svg-W2enIZ63TFEG3aiG .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .edge-depth-5{stroke-width:-1;}#mermaid-svg-W2enIZ63TFEG3aiG .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled,#mermaid-svg-W2enIZ63TFEG3aiG .disabled circle,#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:lightgray;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:#efefef;}#mermaid-svg-W2enIZ63TFEG3aiG .section-6 rect,#mermaid-svg-W2enIZ63TFEG3aiG .section-6 path,#mermaid-svg-W2enIZ63TFEG3aiG .section-6 circle,#mermaid-svg-W2enIZ63TFEG3aiG .section-6 polygon,#mermaid-svg-W2enIZ63TFEG3aiG .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .section-6 text{fill:black;}#mermaid-svg-W2enIZ63TFEG3aiG .node-icon-6{font-size:40px;color:black;}#mermaid-svg-W2enIZ63TFEG3aiG .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .edge-depth-6{stroke-width:-4;}#mermaid-svg-W2enIZ63TFEG3aiG .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled,#mermaid-svg-W2enIZ63TFEG3aiG .disabled circle,#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:lightgray;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:#efefef;}#mermaid-svg-W2enIZ63TFEG3aiG .section-7 rect,#mermaid-svg-W2enIZ63TFEG3aiG .section-7 path,#mermaid-svg-W2enIZ63TFEG3aiG .section-7 circle,#mermaid-svg-W2enIZ63TFEG3aiG .section-7 polygon,#mermaid-svg-W2enIZ63TFEG3aiG .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .section-7 text{fill:black;}#mermaid-svg-W2enIZ63TFEG3aiG .node-icon-7{font-size:40px;color:black;}#mermaid-svg-W2enIZ63TFEG3aiG .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .edge-depth-7{stroke-width:-7;}#mermaid-svg-W2enIZ63TFEG3aiG .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled,#mermaid-svg-W2enIZ63TFEG3aiG .disabled circle,#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:lightgray;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:#efefef;}#mermaid-svg-W2enIZ63TFEG3aiG .section-8 rect,#mermaid-svg-W2enIZ63TFEG3aiG .section-8 path,#mermaid-svg-W2enIZ63TFEG3aiG .section-8 circle,#mermaid-svg-W2enIZ63TFEG3aiG .section-8 polygon,#mermaid-svg-W2enIZ63TFEG3aiG .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .section-8 text{fill:black;}#mermaid-svg-W2enIZ63TFEG3aiG .node-icon-8{font-size:40px;color:black;}#mermaid-svg-W2enIZ63TFEG3aiG .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .edge-depth-8{stroke-width:-10;}#mermaid-svg-W2enIZ63TFEG3aiG .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled,#mermaid-svg-W2enIZ63TFEG3aiG .disabled circle,#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:lightgray;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:#efefef;}#mermaid-svg-W2enIZ63TFEG3aiG .section-9 rect,#mermaid-svg-W2enIZ63TFEG3aiG .section-9 path,#mermaid-svg-W2enIZ63TFEG3aiG .section-9 circle,#mermaid-svg-W2enIZ63TFEG3aiG .section-9 polygon,#mermaid-svg-W2enIZ63TFEG3aiG .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .section-9 text{fill:black;}#mermaid-svg-W2enIZ63TFEG3aiG .node-icon-9{font-size:40px;color:black;}#mermaid-svg-W2enIZ63TFEG3aiG .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .edge-depth-9{stroke-width:-13;}#mermaid-svg-W2enIZ63TFEG3aiG .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled,#mermaid-svg-W2enIZ63TFEG3aiG .disabled circle,#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:lightgray;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:#efefef;}#mermaid-svg-W2enIZ63TFEG3aiG .section-10 rect,#mermaid-svg-W2enIZ63TFEG3aiG .section-10 path,#mermaid-svg-W2enIZ63TFEG3aiG .section-10 circle,#mermaid-svg-W2enIZ63TFEG3aiG .section-10 polygon,#mermaid-svg-W2enIZ63TFEG3aiG .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .section-10 text{fill:black;}#mermaid-svg-W2enIZ63TFEG3aiG .node-icon-10{font-size:40px;color:black;}#mermaid-svg-W2enIZ63TFEG3aiG .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .edge-depth-10{stroke-width:-16;}#mermaid-svg-W2enIZ63TFEG3aiG .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled,#mermaid-svg-W2enIZ63TFEG3aiG .disabled circle,#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:lightgray;}#mermaid-svg-W2enIZ63TFEG3aiG .disabled text{fill:#efefef;}#mermaid-svg-W2enIZ63TFEG3aiG .section-root rect,#mermaid-svg-W2enIZ63TFEG3aiG .section-root path,#mermaid-svg-W2enIZ63TFEG3aiG .section-root circle,#mermaid-svg-W2enIZ63TFEG3aiG .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-W2enIZ63TFEG3aiG .section-root text{fill:#ffffff;}#mermaid-svg-W2enIZ63TFEG3aiG .section-root span{color:#ffffff;}#mermaid-svg-W2enIZ63TFEG3aiG .section-2 span{color:#ffffff;}#mermaid-svg-W2enIZ63TFEG3aiG .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-W2enIZ63TFEG3aiG .edge{fill:none;}#mermaid-svg-W2enIZ63TFEG3aiG .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-W2enIZ63TFEG3aiG :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

Spring AI内置Advisor

对话记忆类

MessageChatMemoryAdvisor

PromptChatMemoryAdvisor

VectorStoreChatMemoryAdvisor

RAG问答类

QuestionAnswerAdvisor

RetrievalAugmentationAdvisor

推理增强类

ReReadingAdvisor

内容安全类

SafeGuardAdvisor

各 Advisor 的简要说明:

类别Advisor怎么工作的
对话记忆 MessageChatMemoryAdvisor 把历史消息以列表形式塞进 Prompt
对话记忆 PromptChatMemoryAdvisor 把历史消息拼到系统提示词里
对话记忆 VectorStoreChatMemoryAdvisor 从向量数据库搜索记忆(适合长期记忆)
RAG QuestionAnswerAdvisor 最简单 RAG:从向量库搜文档拼到 Prompt
RAG RetrievalAugmentationAdvisor 更灵活的模块化 RAG
推理增强 ReReadingAdvisor 让模型再读一遍问题
内容安全 SafeGuardAdvisor 过滤不合适的内容

八、最佳实践

  • 单一职责:一个 Advisor 只干一件事。记忆是记忆,日志是日志,别揉在一起。

  • 用上下文传数据:如果多个 Advisor 之间需要共享数据,用 ChatClientRequest.adviseContext()。注意从 1.0 M3 开始上下文是不可变的。

  • 同时实现两个接口:如果你的 Advisor 在流式和非流式场景下都需要,建议同时实现 CallAdvisor 和 StreamAdvisor。

  • 合理配置 Order:通常的顺序建议:

    • 日志(最高优先级)
    • 安全过滤
    • RAG 检索
    • 记忆注入
    • 调用模型(最后一个)
  • 不可变上下文的更新方式:

  • // 错误:直接改会报错
    request.context().put("key", "value");

    // 正确:创建新的不可变副本
    ChatClientRequest newRequest = request.updateContext(ctx ->
    MapUtils.put(ctx, "key", "value")
    );

    九、版本变化(如果你是从老版本升级的)

    版本变化
    1.0 M2 有 RequestAdvisor 和 ResponseAdvisor 两个接口
    1.0 M3 合并成 CallAroundAdvisor 和 StreamAroundAdvisor,删除了 StreamResponseMode
    1.0.0 改名为 CallAdvisor 和 StreamAdvisor;上下文从独立参数移到了 Request/Response 里,并且变成不可变

    如果你是从老版本升级上来的,这些变化要注意。

    总结

    Advisors API 本质上就是 AI 调用场景下的拦截器模式。它把那些跟核心业务无关但又不得不做的破事(记忆、检索、过滤、日志)抽出来,做成可插拔的组件。

    用官网的话说就是:encapsulating recurring Generative AI patterns(封装重复的生成式 AI 模式)。

    如果你发现自己每个 AI 调用前后都要写类似的代码,就该考虑用 Advisor 了。

    参考文档:https://docs.spring.io/spring-ai/reference/api/advisors.html

    赞(0)
    未经允许不得转载:171主机测评 » Spring AI 拦截器实战:Advisors 详解与代码示例
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址