
Spring AI 模块化架构深度解读:从 1.0 到 2.0 的演进与最佳实践
为什么 Spring AI 2.0 是一次"架构重构"而非"版本升级"?
📑 目录
- 一、引言:从"能用"到"好用"的跨越
- [二、Spring AI 核心抽象层设计](#二、Spring AI 核心抽象层设计)
- [2.1 设计哲学:AI 能力的 Spring 化抽象](#2.1 设计哲学:AI 能力的 Spring 化抽象)
- [2.2 核心接口详解](#2.2 核心接口详解)
- [2.3 抽象层的价值](#2.3 抽象层的价值)
- [三、从 1.0 到 2.0:模块化架构的演进](#三、从 1.0 到 2.0:模块化架构的演进)
- [3.1 1.x 时代:单体核心之痛](#3.1 1.x 时代:单体核心之痛)
- [3.2 2025 年的模块化重构](#3.2 2025 年的模块化重构)
- [3.3 Spring AI 2.0 的模块依赖拓扑](#3.3 Spring AI 2.0 的模块依赖拓扑)
- [3.4 模块职责详解](#3.4 模块职责详解)
- [四、Function Calling 机制:从"私有实现"到"一等公民"](#四、Function Calling 机制:从“私有实现”到“一等公民”)
- [4.1 1.x 时代的问题](#4.1 1.x 时代的问题)
- [4.2 2.0 的革新:ToolCallingAdvisor](#4.2 2.0 的革新:ToolCallingAdvisor)
- [4.3 工具定义:@Tool 注解](#4.3 工具定义:@Tool 注解)
- [4.4 工具搜索:按需发现](#4.4 工具搜索:按需发现)
- [4.5 与 OpenClaw Skill 系统的对接](#4.5 与 OpenClaw Skill 系统的对接)
- [五、Spring Boot AutoConfiguration:开箱即用的 AI 能力](#五、Spring Boot AutoConfiguration:开箱即用的 AI 能力)
- [六、与 Spring Cloud 生态的深度集成](#六、与 Spring Cloud 生态的深度集成)
- [七、2025 路线图与 2.0 展望](#七、2025 路线图与 2.0 展望)
- [八、实战:搭建 Spring AI 多模型路由的最小项目](#八、实战:搭建 Spring AI 多模型路由的最小项目)
- 九、总结
一、引言:从"能用"到"好用"的跨越
2026 年 6 月 12 日,Spring 官方正式发布了 Spring AI 2.0.0 GA 。这是 Spring AI 项目自 1.0.0 GA 以来最大的一次版本升级。但这次升级的意义,远不止于版本号的递增。
如果你是一个 Java 开发者,在过去两年里尝试过用 Spring AI 1.x 构建 AI 应用,你大概率会遇到这些问题:
- 引入
spring-ai-core后,项目里多出了一大堆你根本用不到的依赖 - 想要自定义工具调用的中间过程,却发现工具循环被"埋"在
ChatModel的私有实现里,根本插不上手 - 想要做多模型路由,却发现自动配置只创建了一个
ChatModelBean
这些问题不是"小瑕疵",而是架构设计层面的问题。
Spring AI 2.0 的目标,正是解决这些问题。正如官方博客所说:"Spring AI 2.0 的设计目标是与 Spring Boot 4.0 / 4.1 和 Spring Framework 7.0 一起使用,但它带来的好处远不止升级依赖版本和修复兼容性问题。 "
本文将深入拆解 Spring AI 从 1.0 到 2.0 的架构演进,帮助你理解:为什么这次升级值得你认真对待。
二、Spring AI 核心抽象层设计
2.1 设计哲学:AI 能力的 Spring 化抽象
Spring AI 的核心定位不是"又一个 AI 框架",而是 Spring 生态的 AI 层 。它建立在 Spring Boot 4.0 + Spring Framework 7.0 的基线之上,深度融入 Spring 的 IoC、自动配置、可观测性体系。
如果用一句话概括 Spring AI 的设计哲学,那就是:让 Java/Spring 开发者用他们熟悉的方式调用 AI 能力------就像使用 Spring Data 访问数据库、使用 Spring Cloud 调用微服务一样自然。
#mermaid-svg-lRuPq57hw7cg0p6W{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-lRuPq57hw7cg0p6W .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lRuPq57hw7cg0p6W .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lRuPq57hw7cg0p6W .error-icon{fill:#552222;}#mermaid-svg-lRuPq57hw7cg0p6W .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lRuPq57hw7cg0p6W .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lRuPq57hw7cg0p6W .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lRuPq57hw7cg0p6W .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lRuPq57hw7cg0p6W .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lRuPq57hw7cg0p6W .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lRuPq57hw7cg0p6W .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lRuPq57hw7cg0p6W .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lRuPq57hw7cg0p6W .marker.cross{stroke:#333333;}#mermaid-svg-lRuPq57hw7cg0p6W svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lRuPq57hw7cg0p6W p{margin:0;}#mermaid-svg-lRuPq57hw7cg0p6W .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lRuPq57hw7cg0p6W .cluster-label text{fill:#333;}#mermaid-svg-lRuPq57hw7cg0p6W .cluster-label span{color:#333;}#mermaid-svg-lRuPq57hw7cg0p6W .cluster-label span p{background-color:transparent;}#mermaid-svg-lRuPq57hw7cg0p6W .label text,#mermaid-svg-lRuPq57hw7cg0p6W span{fill:#333;color:#333;}#mermaid-svg-lRuPq57hw7cg0p6W .node rect,#mermaid-svg-lRuPq57hw7cg0p6W .node circle,#mermaid-svg-lRuPq57hw7cg0p6W .node ellipse,#mermaid-svg-lRuPq57hw7cg0p6W .node polygon,#mermaid-svg-lRuPq57hw7cg0p6W .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lRuPq57hw7cg0p6W .rough-node .label text,#mermaid-svg-lRuPq57hw7cg0p6W .node .label text,#mermaid-svg-lRuPq57hw7cg0p6W .image-shape .label,#mermaid-svg-lRuPq57hw7cg0p6W .icon-shape .label{text-anchor:middle;}#mermaid-svg-lRuPq57hw7cg0p6W .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lRuPq57hw7cg0p6W .rough-node .label,#mermaid-svg-lRuPq57hw7cg0p6W .node .label,#mermaid-svg-lRuPq57hw7cg0p6W .image-shape .label,#mermaid-svg-lRuPq57hw7cg0p6W .icon-shape .label{text-align:center;}#mermaid-svg-lRuPq57hw7cg0p6W .node.clickable{cursor:pointer;}#mermaid-svg-lRuPq57hw7cg0p6W .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lRuPq57hw7cg0p6W .arrowheadPath{fill:#333333;}#mermaid-svg-lRuPq57hw7cg0p6W .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lRuPq57hw7cg0p6W .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lRuPq57hw7cg0p6W .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lRuPq57hw7cg0p6W .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lRuPq57hw7cg0p6W .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lRuPq57hw7cg0p6W .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lRuPq57hw7cg0p6W .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lRuPq57hw7cg0p6W .cluster text{fill:#333;}#mermaid-svg-lRuPq57hw7cg0p6W .cluster span{color:#333;}#mermaid-svg-lRuPq57hw7cg0p6W 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-lRuPq57hw7cg0p6W .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lRuPq57hw7cg0p6W rect.text{fill:none;stroke-width:0;}#mermaid-svg-lRuPq57hw7cg0p6W .icon-shape,#mermaid-svg-lRuPq57hw7cg0p6W .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lRuPq57hw7cg0p6W .icon-shape p,#mermaid-svg-lRuPq57hw7cg0p6W .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lRuPq57hw7cg0p6W .icon-shape .label rect,#mermaid-svg-lRuPq57hw7cg0p6W .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lRuPq57hw7cg0p6W .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lRuPq57hw7cg0p6W .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lRuPq57hw7cg0p6W :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 模型提供商实现
spring-ai-openai
OpenAI SDK
spring-ai-anthropic
Anthropic SDK
spring-ai-google-genai
Google GenAI
spring-ai-ollama
Ollama
spring-ai-bedrock
Amazon Bedrock
Spring AI 抽象层
ChatClient
对话统一接口
EmbeddingClient
向量化统一接口
ImageClient
图像生成统一接口
ToolCallback
工具调用统一接口
你的 Spring 应用
Controller
Service
Repository
2.2 核心接口详解
Spring AI 的核心抽象层围绕以下几个关键接口展开:
| 接口 | 职责 | 典型实现 |
|---|---|---|
ChatModel |
对话式 AI 调用的底层接口 | OpenAiChatModel、AnthropicChatModel |
ChatClient |
高级对话 API,提供 Fluent 风格的调用体验 | DefaultChatClient |
EmbeddingModel |
文本向量化接口 | OpenAiEmbeddingModel、OllamaEmbeddingModel |
ImageModel |
图像生成接口 | OpenAiImageModel |
ToolCallback |
工具/函数调用的标准化接口 | MethodToolCallback、FunctionToolCallback |
关于 ChatModel 与 ChatClient 的定位差异:
在 Spring AI 2.0 中,两者的职责被进一步明确:
ChatClient是面向普通用户的主要 API,提供 Fluent 风格的调用体验ChatModel是更低层的构建块,通常不被直接使用
这种分层设计让 ChatClient 可以专注于提供丰富的功能(Advisor 链、记忆管理、工具调用),而 ChatModel 则专注于与底层模型的协议交互。
2.3 抽象层的价值
这种抽象设计的核心价值在于:换模型就像换数据库驱动一样简单。
java
// 只需要改一行配置,不需要改动业务代码
spring.ai.openai.api-key=sk-xxx
// 换成
spring.ai.anthropic.api-key=sk-xxx
对于企业级应用来说,这意味着:
- 避免厂商锁定:可以根据成本、效果灵活切换模型
- 灰度发布:可以按百分比将流量切换到新模型
- 灾备切换:主模型故障时自动切换到备用模型
三、从 1.0 到 2.0:模块化架构的演进
3.1 1.x 时代:单体核心之痛
在 Spring AI 1.x 时代,spring-ai-core 包含了所有核心接口和通用实现。这种"大而全"的设计在项目早期快速迭代时是合理的,但随着 AI 模型生态的爆发式增长,问题逐渐暴露:
- 依赖膨胀 :即使你只用 OpenAI 的 Chat 能力,也需要引入整个
spring-ai-core,其中包含大量你用不到的 Embedding、Image 相关代码 - 维护困难 :
spring-ai-core包不断膨胀,代码耦合度越来越高 - 升级风险:任何一个模块的变更都可能影响到其他模块
Spring AI 2.0.0-M1 版本彻底摒弃了单体模式,转而采用领域驱动模块化(Domain-Driven Modularization) 策略,将 AI 能力按技术边界拆分为多个核心领域模块。
3.2 2025 年的模块化重构
2025 年 4 月,Spring AI 主分支的模块和构件结构发生了重大变化:
#mermaid-svg-ARGzgPTfUkCW6Gih{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-ARGzgPTfUkCW6Gih .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ARGzgPTfUkCW6Gih .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ARGzgPTfUkCW6Gih .error-icon{fill:#552222;}#mermaid-svg-ARGzgPTfUkCW6Gih .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ARGzgPTfUkCW6Gih .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ARGzgPTfUkCW6Gih .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ARGzgPTfUkCW6Gih .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ARGzgPTfUkCW6Gih .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ARGzgPTfUkCW6Gih .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ARGzgPTfUkCW6Gih .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ARGzgPTfUkCW6Gih .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ARGzgPTfUkCW6Gih .marker.cross{stroke:#333333;}#mermaid-svg-ARGzgPTfUkCW6Gih svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ARGzgPTfUkCW6Gih p{margin:0;}#mermaid-svg-ARGzgPTfUkCW6Gih .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ARGzgPTfUkCW6Gih .cluster-label text{fill:#333;}#mermaid-svg-ARGzgPTfUkCW6Gih .cluster-label span{color:#333;}#mermaid-svg-ARGzgPTfUkCW6Gih .cluster-label span p{background-color:transparent;}#mermaid-svg-ARGzgPTfUkCW6Gih .label text,#mermaid-svg-ARGzgPTfUkCW6Gih span{fill:#333;color:#333;}#mermaid-svg-ARGzgPTfUkCW6Gih .node rect,#mermaid-svg-ARGzgPTfUkCW6Gih .node circle,#mermaid-svg-ARGzgPTfUkCW6Gih .node ellipse,#mermaid-svg-ARGzgPTfUkCW6Gih .node polygon,#mermaid-svg-ARGzgPTfUkCW6Gih .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ARGzgPTfUkCW6Gih .rough-node .label text,#mermaid-svg-ARGzgPTfUkCW6Gih .node .label text,#mermaid-svg-ARGzgPTfUkCW6Gih .image-shape .label,#mermaid-svg-ARGzgPTfUkCW6Gih .icon-shape .label{text-anchor:middle;}#mermaid-svg-ARGzgPTfUkCW6Gih .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ARGzgPTfUkCW6Gih .rough-node .label,#mermaid-svg-ARGzgPTfUkCW6Gih .node .label,#mermaid-svg-ARGzgPTfUkCW6Gih .image-shape .label,#mermaid-svg-ARGzgPTfUkCW6Gih .icon-shape .label{text-align:center;}#mermaid-svg-ARGzgPTfUkCW6Gih .node.clickable{cursor:pointer;}#mermaid-svg-ARGzgPTfUkCW6Gih .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ARGzgPTfUkCW6Gih .arrowheadPath{fill:#333333;}#mermaid-svg-ARGzgPTfUkCW6Gih .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ARGzgPTfUkCW6Gih .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ARGzgPTfUkCW6Gih .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ARGzgPTfUkCW6Gih .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ARGzgPTfUkCW6Gih .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ARGzgPTfUkCW6Gih .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ARGzgPTfUkCW6Gih .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ARGzgPTfUkCW6Gih .cluster text{fill:#333;}#mermaid-svg-ARGzgPTfUkCW6Gih .cluster span{color:#333;}#mermaid-svg-ARGzgPTfUkCW6Gih 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-ARGzgPTfUkCW6Gih .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ARGzgPTfUkCW6Gih rect.text{fill:none;stroke-width:0;}#mermaid-svg-ARGzgPTfUkCW6Gih .icon-shape,#mermaid-svg-ARGzgPTfUkCW6Gih .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ARGzgPTfUkCW6Gih .icon-shape p,#mermaid-svg-ARGzgPTfUkCW6Gih .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ARGzgPTfUkCW6Gih .icon-shape .label rect,#mermaid-svg-ARGzgPTfUkCW6Gih .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ARGzgPTfUkCW6Gih .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ARGzgPTfUkCW6Gih .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ARGzgPTfUkCW6Gih :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 架构重构
2.0 时代(模块化架构)
spring-ai-commons
基础模块
spring-ai-model
AI功能抽象
spring-ai-vector-store
向量存储抽象
spring-ai-client-chat
高级对话API
spring-ai-rag
RAG综合框架
1.x 时代(单体架构)
spring-ai-core
包含所有核心接口
OpenAI实现
Anthropic实现
Ollama实现
向量存储实现
图像生成实现
3.3 Spring AI 2.0 的模块依赖拓扑
Spring AI 2.0 的模块设计遵循严格的分层依赖原则,底层模块禁止向上依赖,形成清晰的无环有向图(DAG) :
#mermaid-svg-Ud82nridW5g0f3UA{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-Ud82nridW5g0f3UA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ud82nridW5g0f3UA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ud82nridW5g0f3UA .error-icon{fill:#552222;}#mermaid-svg-Ud82nridW5g0f3UA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ud82nridW5g0f3UA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ud82nridW5g0f3UA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ud82nridW5g0f3UA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ud82nridW5g0f3UA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ud82nridW5g0f3UA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ud82nridW5g0f3UA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ud82nridW5g0f3UA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ud82nridW5g0f3UA .marker.cross{stroke:#333333;}#mermaid-svg-Ud82nridW5g0f3UA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ud82nridW5g0f3UA p{margin:0;}#mermaid-svg-Ud82nridW5g0f3UA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ud82nridW5g0f3UA .cluster-label text{fill:#333;}#mermaid-svg-Ud82nridW5g0f3UA .cluster-label span{color:#333;}#mermaid-svg-Ud82nridW5g0f3UA .cluster-label span p{background-color:transparent;}#mermaid-svg-Ud82nridW5g0f3UA .label text,#mermaid-svg-Ud82nridW5g0f3UA span{fill:#333;color:#333;}#mermaid-svg-Ud82nridW5g0f3UA .node rect,#mermaid-svg-Ud82nridW5g0f3UA .node circle,#mermaid-svg-Ud82nridW5g0f3UA .node ellipse,#mermaid-svg-Ud82nridW5g0f3UA .node polygon,#mermaid-svg-Ud82nridW5g0f3UA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ud82nridW5g0f3UA .rough-node .label text,#mermaid-svg-Ud82nridW5g0f3UA .node .label text,#mermaid-svg-Ud82nridW5g0f3UA .image-shape .label,#mermaid-svg-Ud82nridW5g0f3UA .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ud82nridW5g0f3UA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ud82nridW5g0f3UA .rough-node .label,#mermaid-svg-Ud82nridW5g0f3UA .node .label,#mermaid-svg-Ud82nridW5g0f3UA .image-shape .label,#mermaid-svg-Ud82nridW5g0f3UA .icon-shape .label{text-align:center;}#mermaid-svg-Ud82nridW5g0f3UA .node.clickable{cursor:pointer;}#mermaid-svg-Ud82nridW5g0f3UA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ud82nridW5g0f3UA .arrowheadPath{fill:#333333;}#mermaid-svg-Ud82nridW5g0f3UA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ud82nridW5g0f3UA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ud82nridW5g0f3UA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ud82nridW5g0f3UA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ud82nridW5g0f3UA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ud82nridW5g0f3UA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Ud82nridW5g0f3UA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ud82nridW5g0f3UA .cluster text{fill:#333;}#mermaid-svg-Ud82nridW5g0f3UA .cluster span{color:#333;}#mermaid-svg-Ud82nridW5g0f3UA 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-Ud82nridW5g0f3UA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ud82nridW5g0f3UA rect.text{fill:none;stroke-width:0;}#mermaid-svg-Ud82nridW5g0f3UA .icon-shape,#mermaid-svg-Ud82nridW5g0f3UA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ud82nridW5g0f3UA .icon-shape p,#mermaid-svg-Ud82nridW5g0f3UA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Ud82nridW5g0f3UA .icon-shape .label rect,#mermaid-svg-Ud82nridW5g0f3UA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ud82nridW5g0f3UA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Ud82nridW5g0f3UA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Ud82nridW5g0f3UA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基础设施层
存储抽象层
模型抽象层
客户端抽象层
应用层
spring-ai-rag
RAG综合框架
spring-ai-vector-store-advisor
向量存储Advisor
spring-ai-client-chat
对话客户端
spring-ai-model
核心模型接口
ChatModel/EmbeddingModel/ToolCallback
spring-ai-vector-store
向量存储抽象
spring-ai-commons
基础工具与领域模型
零外部依赖
关键设计约束:
spring-ai-commons作为唯一的基础层,禁止依赖任何上层模块spring-ai-model仅依赖commons,定义ChatModel、EmbeddingModel等核心接口spring-ai-client-chat依赖model层,提供ChatClient的 Fluent API 实现- Vector Store 模块独立存在,通过 Advisor 机制与 Client 层桥接
3.4 模块职责详解
| 模块 | Maven 坐标 | 职责边界 | 是否必须 |
|---|---|---|---|
| spring-ai-commons | spring-ai-commons |
定义 AI 领域的通用领域模型(Message、Prompt、Document、Media 等),提供 JSON 工具、资源处理和可观测性支持 | 是(所有模块依赖) |
| spring-ai-model | spring-ai-model |
提供 ChatModel、EmbeddingModel、ImageModel 等核心接口,以及消息类型、提示词模板、函数调用框架(ToolDefinition、ToolCallback) |
是(核心能力) |
| spring-ai-vector-store | spring-ai-vector-store |
统一的向量数据库抽象:VectorStore 接口、高级过滤(SQL 表达式)、批处理支持 |
否(仅 RAG 需要) |
| spring-ai-client-chat | spring-ai-client-chat |
高级对话 API:ChatClient 接口、会话持久化(ChatMemory)、Advisor 拦截机制 |
否(仅高级应用需要) |
| spring-ai-vector-store-advisor | spring-ai-vector-store-advisor |
桥接 ChatClient 与 Vector Store,实现 QuestionAnswerAdvisor 等 RAG 组件 |
否(显式 RAG 需要) |
| spring-ai-rag | spring-ai-rag |
综合 RAG 框架:模块化架构、函数式编程原则、可组合组件 | 否(高级 RAG 需要) |
架构师视角解读:
这种拆分使得仅使用基础模型调用能力的应用无需引入 client-chat 层的复杂性 。例如,一个仅需调用 OpenAI API 进行简单问答的微服务,只需依赖
spring-ai-model和具体模型实现(如spring-ai-openai),而不必引入整个 Client 生态。
四、Function Calling 机制:从"私有实现"到"一等公民"
4.1 1.x 时代的问题
在 Spring AI 1.x 中,每个 ChatModel 实现都包含自己的私有工具执行循环。功能是有的,但存在严重问题:
- 无法观测:工具循环被埋在模型实现内部,无法看到中间步骤
- 无法组合:无法将工具调用与其他行为(如日志、校验、重试)组合
- 无法拦截:无法在工具调用前后插入自定义逻辑
你能调用工具,但你无法在工具调用之上构建任何东西。
4.2 2.0 的革新:ToolCallingAdvisor
Spring AI 2.0 将工具循环提升到 Advisor 链中,作为一等公民、可组合的组件。
ChatClient 通过有序的 Advisor 链 处理每个请求,并支持循环------允许 Advisor 重新进入下游链。同样的机制驱动着工具调用循环、结构化输出重试循环和评估循环。
#mermaid-svg-032FPzmSbiXMoJ6v{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-032FPzmSbiXMoJ6v .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-032FPzmSbiXMoJ6v .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-032FPzmSbiXMoJ6v .error-icon{fill:#552222;}#mermaid-svg-032FPzmSbiXMoJ6v .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-032FPzmSbiXMoJ6v .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-032FPzmSbiXMoJ6v .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-032FPzmSbiXMoJ6v .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-032FPzmSbiXMoJ6v .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-032FPzmSbiXMoJ6v .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-032FPzmSbiXMoJ6v .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-032FPzmSbiXMoJ6v .marker{fill:#333333;stroke:#333333;}#mermaid-svg-032FPzmSbiXMoJ6v .marker.cross{stroke:#333333;}#mermaid-svg-032FPzmSbiXMoJ6v svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-032FPzmSbiXMoJ6v p{margin:0;}#mermaid-svg-032FPzmSbiXMoJ6v .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-032FPzmSbiXMoJ6v .cluster-label text{fill:#333;}#mermaid-svg-032FPzmSbiXMoJ6v .cluster-label span{color:#333;}#mermaid-svg-032FPzmSbiXMoJ6v .cluster-label span p{background-color:transparent;}#mermaid-svg-032FPzmSbiXMoJ6v .label text,#mermaid-svg-032FPzmSbiXMoJ6v span{fill:#333;color:#333;}#mermaid-svg-032FPzmSbiXMoJ6v .node rect,#mermaid-svg-032FPzmSbiXMoJ6v .node circle,#mermaid-svg-032FPzmSbiXMoJ6v .node ellipse,#mermaid-svg-032FPzmSbiXMoJ6v .node polygon,#mermaid-svg-032FPzmSbiXMoJ6v .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-032FPzmSbiXMoJ6v .rough-node .label text,#mermaid-svg-032FPzmSbiXMoJ6v .node .label text,#mermaid-svg-032FPzmSbiXMoJ6v .image-shape .label,#mermaid-svg-032FPzmSbiXMoJ6v .icon-shape .label{text-anchor:middle;}#mermaid-svg-032FPzmSbiXMoJ6v .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-032FPzmSbiXMoJ6v .rough-node .label,#mermaid-svg-032FPzmSbiXMoJ6v .node .label,#mermaid-svg-032FPzmSbiXMoJ6v .image-shape .label,#mermaid-svg-032FPzmSbiXMoJ6v .icon-shape .label{text-align:center;}#mermaid-svg-032FPzmSbiXMoJ6v .node.clickable{cursor:pointer;}#mermaid-svg-032FPzmSbiXMoJ6v .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-032FPzmSbiXMoJ6v .arrowheadPath{fill:#333333;}#mermaid-svg-032FPzmSbiXMoJ6v .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-032FPzmSbiXMoJ6v .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-032FPzmSbiXMoJ6v .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-032FPzmSbiXMoJ6v .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-032FPzmSbiXMoJ6v .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-032FPzmSbiXMoJ6v .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-032FPzmSbiXMoJ6v .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-032FPzmSbiXMoJ6v .cluster text{fill:#333;}#mermaid-svg-032FPzmSbiXMoJ6v .cluster span{color:#333;}#mermaid-svg-032FPzmSbiXMoJ6v 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-032FPzmSbiXMoJ6v .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-032FPzmSbiXMoJ6v rect.text{fill:none;stroke-width:0;}#mermaid-svg-032FPzmSbiXMoJ6v .icon-shape,#mermaid-svg-032FPzmSbiXMoJ6v .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-032FPzmSbiXMoJ6v .icon-shape p,#mermaid-svg-032FPzmSbiXMoJ6v .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-032FPzmSbiXMoJ6v .icon-shape .label rect,#mermaid-svg-032FPzmSbiXMoJ6v .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-032FPzmSbiXMoJ6v .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-032FPzmSbiXMoJ6v .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-032FPzmSbiXMoJ6v :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Advisor 链(有序执行)
是
否
用户请求
ChatClient
LoggingAdvisor
日志记录
ValidationAdvisor
参数校验
ToolCallingAdvisor
工具调用循环
RetryAdvisor
重试机制
需要调用工具?
执行工具
ToolCallingManager
LLM 处理
追加工具结果
返回最终响应
ToolCallingAdvisor 的核心工作流程:
- 工具注册 :通过
@Tool、@McpTool、java.util.Function或ToolCallback定义工具 - 初始注入:Advisor 提取工具的名称、描述和输入 JSON Schema,注入到初始上下文(与用户问题和系统提示一起)
- 迭代循环 :
- 将累积的对话历史(用户消息、AI 工具调用请求、上一轮的工具响应)与当前上下文合并,发送给 LLM
- LLM 生成响应,Advisor 检查:
- 如果包含工具调用 :
ToolCallingManager查找并执行对应工具,将工具响应追加到对话历史,循环回去 - 如果不包含工具调用:将最终答案返回给用户
- 如果包含工具调用 :
- 支持两种模式 :同步(
.call())和流式(.stream())都完全支持
关键设计 :ToolCallingAdvisor 是一个递归 Advisor------它会反复重新进入下游链,直到满足停止条件(模型产生没有工具调用的响应)。
4.3 工具定义:@Tool 注解
Spring AI 2.0 提供了最简洁的工具定义方式------在任何方法上添加 @Tool 注解:
java
class WeatherTools {
@Tool(description = "Get the current weather for a given city")
public String getWeather(String city) {
return weatherService.fetch(city);
}
@Tool(description = "Book a flight between two cities on a given date")
public BookingConfirmation bookFlight(
String origin,
String destination,
@ToolParam(description = "Date in YYYY-MM-DD format") String date) {
return flightService.book(origin, destination, date);
}
}
关键特性:
- Spring AI 自动生成输入参数的 JSON Schema
@ToolParam支持每个参数的描述和可选/必填提示- 用
@Nullable注解的参数默认被视为可选
工具调用:
java
String response = ChatClient.create(chatModel)
.prompt("What's the weather in Amsterdam? Book a flight from London if it's sunny.")
.tools(new WeatherTools())
.call()
.content();
4.4 工具搜索:按需发现
Spring AI 2.0 还引入了一个创新特性------ToolSearchToolCallingAdvisor ,支持 LLM 按需发现和调用工具,而不是一次性加载所有工具定义。
它提供了三种 ToolIndex 实现:
- 向量存储(Vector Store) :语义搜索工具
- Lucene:全文检索工具
- 正则表达式(Regex) :模式匹配工具
这意味着当你有成百上千个工具时,LLM 可以先搜索再调用,大幅减少上下文中的工具定义数量。
4.5 与 OpenClaw Skill 系统的对接
在 Uni-MDP 的架构中,Spring AI 的 ToolCallback 机制是与 OpenClaw Skill 系统对接的关键桥梁:
#mermaid-svg-oeY6nNlC0BGUUMJa{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-oeY6nNlC0BGUUMJa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oeY6nNlC0BGUUMJa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oeY6nNlC0BGUUMJa .error-icon{fill:#552222;}#mermaid-svg-oeY6nNlC0BGUUMJa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oeY6nNlC0BGUUMJa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oeY6nNlC0BGUUMJa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oeY6nNlC0BGUUMJa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oeY6nNlC0BGUUMJa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oeY6nNlC0BGUUMJa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oeY6nNlC0BGUUMJa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oeY6nNlC0BGUUMJa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oeY6nNlC0BGUUMJa .marker.cross{stroke:#333333;}#mermaid-svg-oeY6nNlC0BGUUMJa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oeY6nNlC0BGUUMJa p{margin:0;}#mermaid-svg-oeY6nNlC0BGUUMJa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oeY6nNlC0BGUUMJa .cluster-label text{fill:#333;}#mermaid-svg-oeY6nNlC0BGUUMJa .cluster-label span{color:#333;}#mermaid-svg-oeY6nNlC0BGUUMJa .cluster-label span p{background-color:transparent;}#mermaid-svg-oeY6nNlC0BGUUMJa .label text,#mermaid-svg-oeY6nNlC0BGUUMJa span{fill:#333;color:#333;}#mermaid-svg-oeY6nNlC0BGUUMJa .node rect,#mermaid-svg-oeY6nNlC0BGUUMJa .node circle,#mermaid-svg-oeY6nNlC0BGUUMJa .node ellipse,#mermaid-svg-oeY6nNlC0BGUUMJa .node polygon,#mermaid-svg-oeY6nNlC0BGUUMJa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oeY6nNlC0BGUUMJa .rough-node .label text,#mermaid-svg-oeY6nNlC0BGUUMJa .node .label text,#mermaid-svg-oeY6nNlC0BGUUMJa .image-shape .label,#mermaid-svg-oeY6nNlC0BGUUMJa .icon-shape .label{text-anchor:middle;}#mermaid-svg-oeY6nNlC0BGUUMJa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oeY6nNlC0BGUUMJa .rough-node .label,#mermaid-svg-oeY6nNlC0BGUUMJa .node .label,#mermaid-svg-oeY6nNlC0BGUUMJa .image-shape .label,#mermaid-svg-oeY6nNlC0BGUUMJa .icon-shape .label{text-align:center;}#mermaid-svg-oeY6nNlC0BGUUMJa .node.clickable{cursor:pointer;}#mermaid-svg-oeY6nNlC0BGUUMJa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oeY6nNlC0BGUUMJa .arrowheadPath{fill:#333333;}#mermaid-svg-oeY6nNlC0BGUUMJa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oeY6nNlC0BGUUMJa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oeY6nNlC0BGUUMJa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oeY6nNlC0BGUUMJa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oeY6nNlC0BGUUMJa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oeY6nNlC0BGUUMJa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oeY6nNlC0BGUUMJa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oeY6nNlC0BGUUMJa .cluster text{fill:#333;}#mermaid-svg-oeY6nNlC0BGUUMJa .cluster span{color:#333;}#mermaid-svg-oeY6nNlC0BGUUMJa 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-oeY6nNlC0BGUUMJa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oeY6nNlC0BGUUMJa rect.text{fill:none;stroke-width:0;}#mermaid-svg-oeY6nNlC0BGUUMJa .icon-shape,#mermaid-svg-oeY6nNlC0BGUUMJa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oeY6nNlC0BGUUMJa .icon-shape p,#mermaid-svg-oeY6nNlC0BGUUMJa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oeY6nNlC0BGUUMJa .icon-shape .label rect,#mermaid-svg-oeY6nNlC0BGUUMJa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oeY6nNlC0BGUUMJa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oeY6nNlC0BGUUMJa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oeY6nNlC0BGUUMJa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 适配层
Spring AI 工具层
OpenClaw Skill 系统
Skill 定义
SKILL.md
Skill 执行器
TypeScript
ToolCallback
统一接口
MethodToolCallback
方法适配
FunctionToolCallback
函数适配
OpenClawSkillAdapter
将 Skill 包装为 ToolCallback
具体做法 :通过一个适配器类,将 OpenClaw 的 Skill 调用封装为 Spring AI 的 ToolCallback,使 Hermes 决策引擎可以通过 Spring AI 统一调用 OpenClaw 的 Skill 能力。
五、Spring Boot AutoConfiguration:开箱即用的 AI 能力
Spring AI 深度利用了 Spring Boot 的自动配置(AutoConfiguration) 机制。你只需要在 pom.xml 或 build.gradle 中添加对应的 Starter 依赖,Spring AI 就会自动配置好所有必要的 Bean。
官方支持的模型 Starter:
| Starter | 说明 |
|---|---|
spring-ai-openai-spring-boot-starter |
OpenAI SDK(从 3 个变体合并为 1 个) |
spring-ai-anthropic-spring-boot-starter |
Anthropic SDK(从 2 个变体合并为 1 个) |
spring-ai-bedrock-spring-boot-starter |
Amazon Bedrock |
spring-ai-google-genai-spring-boot-starter |
Google GenAI |
spring-ai-ollama-spring-boot-starter |
Ollama 本地模型 |
配置示例 (application.yml):
yaml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o
temperature: 0.7
anthropic:
api-key: ${ANTHROPIC_API_KEY}
chat:
options:
model: claude-3-opus-20240229
使用方式:
java
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@PostMapping("/chat")
public String chat(@RequestBody String message) {
return chatClient.prompt(message).call().content();
}
}
六、与 Spring Cloud 生态的深度集成
Spring AI 2.0 不仅是一个独立的 AI 框架,更是 Spring 生态的自然延伸 。它与 Spring Cloud 生态的集成让构建分布式 AI 应用变得异常简单:
#mermaid-svg-D7vSlkWmdxgYPU7v{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-D7vSlkWmdxgYPU7v .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-D7vSlkWmdxgYPU7v .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-D7vSlkWmdxgYPU7v .error-icon{fill:#552222;}#mermaid-svg-D7vSlkWmdxgYPU7v .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-D7vSlkWmdxgYPU7v .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-D7vSlkWmdxgYPU7v .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-D7vSlkWmdxgYPU7v .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-D7vSlkWmdxgYPU7v .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-D7vSlkWmdxgYPU7v .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-D7vSlkWmdxgYPU7v .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-D7vSlkWmdxgYPU7v .marker{fill:#333333;stroke:#333333;}#mermaid-svg-D7vSlkWmdxgYPU7v .marker.cross{stroke:#333333;}#mermaid-svg-D7vSlkWmdxgYPU7v svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-D7vSlkWmdxgYPU7v p{margin:0;}#mermaid-svg-D7vSlkWmdxgYPU7v .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-D7vSlkWmdxgYPU7v .cluster-label text{fill:#333;}#mermaid-svg-D7vSlkWmdxgYPU7v .cluster-label span{color:#333;}#mermaid-svg-D7vSlkWmdxgYPU7v .cluster-label span p{background-color:transparent;}#mermaid-svg-D7vSlkWmdxgYPU7v .label text,#mermaid-svg-D7vSlkWmdxgYPU7v span{fill:#333;color:#333;}#mermaid-svg-D7vSlkWmdxgYPU7v .node rect,#mermaid-svg-D7vSlkWmdxgYPU7v .node circle,#mermaid-svg-D7vSlkWmdxgYPU7v .node ellipse,#mermaid-svg-D7vSlkWmdxgYPU7v .node polygon,#mermaid-svg-D7vSlkWmdxgYPU7v .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-D7vSlkWmdxgYPU7v .rough-node .label text,#mermaid-svg-D7vSlkWmdxgYPU7v .node .label text,#mermaid-svg-D7vSlkWmdxgYPU7v .image-shape .label,#mermaid-svg-D7vSlkWmdxgYPU7v .icon-shape .label{text-anchor:middle;}#mermaid-svg-D7vSlkWmdxgYPU7v .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-D7vSlkWmdxgYPU7v .rough-node .label,#mermaid-svg-D7vSlkWmdxgYPU7v .node .label,#mermaid-svg-D7vSlkWmdxgYPU7v .image-shape .label,#mermaid-svg-D7vSlkWmdxgYPU7v .icon-shape .label{text-align:center;}#mermaid-svg-D7vSlkWmdxgYPU7v .node.clickable{cursor:pointer;}#mermaid-svg-D7vSlkWmdxgYPU7v .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-D7vSlkWmdxgYPU7v .arrowheadPath{fill:#333333;}#mermaid-svg-D7vSlkWmdxgYPU7v .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-D7vSlkWmdxgYPU7v .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-D7vSlkWmdxgYPU7v .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D7vSlkWmdxgYPU7v .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-D7vSlkWmdxgYPU7v .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D7vSlkWmdxgYPU7v .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-D7vSlkWmdxgYPU7v .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-D7vSlkWmdxgYPU7v .cluster text{fill:#333;}#mermaid-svg-D7vSlkWmdxgYPU7v .cluster span{color:#333;}#mermaid-svg-D7vSlkWmdxgYPU7v 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-D7vSlkWmdxgYPU7v .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-D7vSlkWmdxgYPU7v rect.text{fill:none;stroke-width:0;}#mermaid-svg-D7vSlkWmdxgYPU7v .icon-shape,#mermaid-svg-D7vSlkWmdxgYPU7v .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D7vSlkWmdxgYPU7v .icon-shape p,#mermaid-svg-D7vSlkWmdxgYPU7v .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-D7vSlkWmdxgYPU7v .icon-shape .label rect,#mermaid-svg-D7vSlkWmdxgYPU7v .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D7vSlkWmdxgYPU7v .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-D7vSlkWmdxgYPU7v .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-D7vSlkWmdxgYPU7v :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Spring Cloud 基础设施
微服务集群
API Gateway
Spring Cloud Gateway
统一入口/路由/限流
营销策略服务
Spring AI + OpenAI
内容生成服务
Spring AI + Anthropic
推荐服务
Spring AI + Ollama
服务发现
Nacos/Consul
配置管理
Spring Cloud Config
熔断降级
Resilience4j
链路追踪
Micrometer Tracing
关键集成点:
| Spring Cloud 组件 | 与 Spring AI 的集成方式 | 业务价值 |
|---|---|---|
| 服务发现 | 将 AI 推理服务注册到 Nacos/Consul | 多实例负载均衡,水平扩展 |
| 配置管理 | 通过 Spring Cloud Config 管理模型配置 | 模型参数动态调整,无需重启 |
| 熔断降级 | 用 Resilience4j 包裹 AI 调用 | 模型服务故障时自动降级到备用模型 |
| 链路追踪 | Micrometer Tracing 自动注入 TraceId | 全链路追踪 AI 调用,快速定位问题 |
| 网关路由 | Spring Cloud Gateway 路由 AI 请求 | 按模型类型/成本路由到不同服务 |
示例:带熔断降级的 AI 调用:
java
@Service
public class ResilientAIService {
private final ChatClient primaryClient;
private final ChatClient fallbackClient;
@CircuitBreaker(name = "aiService", fallbackMethod = "fallbackChat")
public String chat(String message) {
return primaryClient.prompt(message).call().content();
}
public String fallbackChat(String message, Throwable t) {
// 主模型故障时,自动切换到备用模型
return fallbackClient.prompt(message).call().content();
}
}
七、2025 路线图与 2.0 展望
Spring AI 的 2025 年路线图明确了 2.0 版本的演进方向:
7.1 技术基线升级
Spring AI 2.0 将技术基线全面迁移至:
- Spring Boot 4.0 / 4.1
- Spring Framework 7.0
- Jakarta EE 11
- Java 21+
7.2 Jackson 3 升级
从 Jackson 2 升级到 Jackson 3,JSON 序列化能力大幅提升。新的 JsonHelper 类提供了完全自定义的 JsonMapper。
7.3 Null-Safety(空安全)
整个代码库使用 JSpecify 注解进行了全面标注。这不仅能防止运行时的空指针异常,还提供了 Kotlin 惯用 API,清晰区分了代码库中哪些值是可选、哪些是必填。
7.4 Options 处理的重构
Spring AI 2.0 对 Options 和配置属性的处理进行了重大重构:
- 明确哪些 Options 是必填、哪些是可选的
- 默认值现在统一在 Options 层定义(而非 Model 或配置属性层)
- Options 通过 Builder 创建,实例化后不可变
- Builder 提供一致的、无需反射的合并能力
- Options 与配置属性的解耦,去掉了
.options这个冗余的配置段
7.5 MCP(模型上下文协议)原生支持
Spring AI 2.0 提供了 MCP Client 和 MCP Server 的 Boot Starter。这意味着 Spring 开发者可以:
- 消费 MCP 服务器提供的工具、资源和提示词
- 创建 MCP 服务器,将 Spring 服务暴露给更广泛的 AI 社区
7.6 社区反馈驱动
Spring AI 2.0 的设计充分吸收了来自 Issue 和 Pull Request 的用户反馈。官方明确表示,这次重构的目的是 "为项目可持续演进设定新的基线" 。
八、实战:搭建 Spring AI 多模型路由的最小项目
8.1 项目结构
spring-ai-multi-model-demo/
├── pom.xml
└── src/
└── main/
├── java/
│ └── com/
│ └── example/
│ ├── SpringAiMultiModelApplication.java
│ ├── config/
│ │ └── MultiModelConfig.java
│ ├── service/
│ │ ├── ChatService.java
│ │ └── ModelRouterService.java
│ └── controller/
│ └── ChatController.java
└── resources/
└── application.yml
8.2 Maven 依赖(pom.xml)
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-ai-multi-model-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>21</java.version>
<spring-ai.version>2.0.0</spring-ai.version>
</properties>
<dependencies>
<!-- Spring Boot 4.x -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI 2.0 核心依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-client-chat</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- OpenAI 模型实现 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- Anthropic 模型实现 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- Ollama 本地模型实现 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
</dependencies>
</project>
8.3 配置文件(application.yml)
yaml
spring:
ai:
# OpenAI 配置
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o
temperature: 0.7
# Anthropic 配置
anthropic:
api-key: ${ANTHROPIC_API_KEY}
chat:
options:
model: claude-3-opus-20240229
temperature: 0.7
# Ollama 本地配置
ollama:
base-url: http://localhost:11434
chat:
options:
model: llama3.2:latest
# 自定义多模型路由配置
app:
router:
default-model: openai
fallback-model: anthropic
# 按任务类型路由
task-routing:
simple: ollama # 简单问答用本地模型
complex: openai # 复杂推理用 GPT-4o
creative: anthropic # 创意内容用 Claude
8.4 多模型配置(MultiModelConfig.java)
java
package com.example.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.anthropic.AnthropicChatModel;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class MultiModelConfig {
@Bean
@Primary
public ChatClient primaryChatClient(OpenAiChatModel openAiChatModel) {
return ChatClient.builder(openAiChatModel).build();
}
@Bean
public ChatClient anthropicChatClient(AnthropicChatModel anthropicChatModel) {
return ChatClient.builder(anthropicChatModel).build();
}
@Bean
public ChatClient ollamaChatClient(OllamaChatModel ollamaChatModel) {
return ChatClient.builder(ollamaChatModel).build();
}
/**
* 路由策略:根据任务复杂度选择模型
*/
@Bean
public ModelRouter modelRouter(
ChatClient primaryChatClient,
ChatClient anthropicChatClient,
ChatClient ollamaChatClient) {
return new ModelRouter(primaryChatClient, anthropicChatClient, ollamaChatClient);
}
}
8.5 模型路由服务(ModelRouterService.java)
java
package com.example.service;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class ModelRouterService {
private final Map<String, ChatClient> modelClients = new ConcurrentHashMap<>();
private final String defaultModel;
private final String fallbackModel;
public ModelRouterService(
ChatClient primaryChatClient,
ChatClient anthropicChatClient,
ChatClient ollamaChatClient,
@Value("${app.router.default-model:openai}") String defaultModel,
@Value("${app.router.fallback-model:anthropic}") String fallbackModel) {
this.modelClients.put("openai", primaryChatClient);
this.modelClients.put("anthropic", anthropicChatClient);
this.modelClients.put("ollama", ollamaChatClient);
this.defaultModel = defaultModel;
this.fallbackModel = fallbackModel;
}
/**
* 根据任务类型路由到对应的模型
*/
public String routeAndChat(String message, String taskType) {
String modelKey = resolveModel(taskType);
ChatClient client = modelClients.get(modelKey);
try {
return client.prompt(message).call().content();
} catch (Exception e) {
// 自动降级到备用模型
ChatClient fallbackClient = modelClients.get(fallbackModel);
return fallbackClient.prompt(message).call().content();
}
}
/**
* 解析模型选择策略
*/
private String resolveModel(String taskType) {
if (taskType == null) {
return defaultModel;
}
switch (taskType.toLowerCase()) {
case "simple":
return "ollama"; // 简单问答用本地模型,成本最低
case "complex":
return "openai"; // 复杂推理用 GPT-4o
case "creative":
return "anthropic"; // 创意内容用 Claude
default:
return defaultModel;
}
}
/**
* 手动切换模型
*/
public String chatWithModel(String message, String modelKey) {
ChatClient client = modelClients.getOrDefault(modelKey, modelClients.get(defaultModel));
return client.prompt(message).call().content();
}
}
8.6 Controller(ChatController.java)
java
package com.example.controller;
import com.example.service.ModelRouterService;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/chat")
public class ChatController {
private final ModelRouterService routerService;
public ChatController(ModelRouterService routerService) {
this.routerService = routerService;
}
/**
* 自动路由:根据任务类型选择模型
*/
@PostMapping("/route")
public Map<String, String> routeChat(
@RequestParam String message,
@RequestParam(defaultValue = "complex") String taskType) {
String response = routerService.routeAndChat(message, taskType);
return Map.of(
"message", message,
"taskType", taskType,
"response", response
);
}
/**
* 手动指定模型
*/
@PostMapping("/{model}")
public Map<String, String> chatWithModel(
@PathVariable String model,
@RequestParam String message) {
String response = routerService.chatWithModel(message, model);
return Map.of(
"message", message,
"model", model,
"response", response
);
}
/**
* 健康检查
*/
@GetMapping("/health")
public Map<String, String> health() {
return Map.of("status", "UP", "version", "2.0.0");
}
}
8.7 运行与测试
bash
# 启动应用
mvn spring-boot:run
# 测试自动路由
curl -X POST "http://localhost:8080/api/chat/route?message=解释一下微服务架构&taskType=complex"
# 测试指定模型
curl -X POST "http://localhost:8080/api/chat/ollama?message=什么是Docker"
九、总结
Spring AI 从 1.0 到 2.0 的演进,核心变化可以总结为以下五点:
9.1 从"单体核心"到"领域模块"
spring-ai-core 被拆分为 spring-ai-commons、spring-ai-model、spring-ai-client-chat、spring-ai-vector-store、spring-ai-rag 等独立模块。开发者可以按需引入,大幅减少不必要的依赖。
9.2 从"私有工具循环"到"可组合 Advisor"
工具调用从埋藏在 ChatModel 内部的私有实现,提升为 Advisor 链中的一等公民。开发者可以观测、拦截、组合工具调用行为。
9.3 从"硬编码模型"到"多模型路由"
通过模块化的设计和统一的抽象接口,Spring AI 2.0 天然支持多模型路由、灰度发布和故障降级。
9.4 从"Java 17"到"Java 21+"
技术基线升级到 Spring Boot 4.0 + Spring Framework 7.0 + Jakarta EE 11,拥抱 Java 21 的最新特性。
9.5 从"能用"到"好用"
Null-Safety 注解、Options Builder 模式、Jackson 3 升级、MCP 原生支持------这些看似"小"的改进,大幅提升了开发体验和代码质量。
下一篇预告:我们将深入 OpenClaw 的源码,拆解 Gateway、Agent、Skill、Memory 四大模块的设计与实现。
敬请期待!🚀
📌 本文收录于专栏 :从 OpenClaw 到 Hermes:自改进 Agent 完全指南