【全域智能营销实战】2、Spring AI 模块化架构深度解读:从 1.0 到 2.0 的演进与最佳实践

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 的私有实现里,根本插不上手
  • 想要做多模型路由,却发现自动配置只创建了一个 ChatModel Bean

这些问题不是"小瑕疵",而是架构设计层面的问题。

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 调用的底层接口 OpenAiChatModelAnthropicChatModel
ChatClient 高级对话 API,提供 Fluent 风格的调用体验 DefaultChatClient
EmbeddingModel 文本向量化接口 OpenAiEmbeddingModelOllamaEmbeddingModel
ImageModel 图像生成接口 OpenAiImageModel
ToolCallback 工具/函数调用的标准化接口 MethodToolCallbackFunctionToolCallback

关于 ChatModelChatClient 的定位差异

在 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,定义 ChatModelEmbeddingModel 等核心接口
  • 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 提供 ChatModelEmbeddingModelImageModel 等核心接口,以及消息类型、提示词模板、函数调用框架(ToolDefinitionToolCallback (核心能力)
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 的核心工作流程

  1. 工具注册 :通过 @Tool@McpTooljava.util.FunctionToolCallback 定义工具
  2. 初始注入:Advisor 提取工具的名称、描述和输入 JSON Schema,注入到初始上下文(与用户问题和系统提示一起)
  3. 迭代循环
    • 将累积的对话历史(用户消息、AI 工具调用请求、上一轮的工具响应)与当前上下文合并,发送给 LLM
    • LLM 生成响应,Advisor 检查:
      • 如果包含工具调用ToolCallingManager 查找并执行对应工具,将工具响应追加到对话历史,循环回去
      • 如果不包含工具调用:将最终答案返回给用户
  4. 支持两种模式 :同步(.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.xmlbuild.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-commonsspring-ai-modelspring-ai-client-chatspring-ai-vector-storespring-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 完全指南

相关推荐
AIGC小尼11 分钟前
Windows 本地 AI 漫剧全自动生产线部署完整教程(零基础、全指令、带源码、模型配置、排错方案)
人工智能·windows·ai漫剧
合米AI SOP系统15 分钟前
传统产线如何快速上马落地 AI 防错?合米科技 AI SOP 7天即可上线。
大数据·人工智能·科技
思录Echo15 分钟前
什么决定具身智能的最终走向?多技术路线与落地现实辨析
大数据·人工智能
ShallWeL34 分钟前
Orin 上多模型常驻与显存预算
人工智能·嵌入式硬件·nvidia·orin
xiaohaiAIgeo36 分钟前
【2026年】AI监控加行为分析守护实验室安全
大数据·人工智能·科普知识
IT_陈寒37 分钟前
Python的多线程就是个假把式,我算是体验到了
前端·人工智能·后端
集和诚JHCTECH1 小时前
案例分享 | BRAV-7721助力印刷电路板(PCB)智能缺陷复判系统
人工智能·嵌入式硬件·边缘计算
衡石科技1 小时前
ChatBI性能优化与流式响应衡石自然语言问数毫秒级体验技术解析
人工智能·科技·性能优化·企业级bi
林墨聊AIGC1 小时前
动漫AI视频创作工具在哪找到的?2026年最新动漫AI视频平台与软件指南
大数据·人工智能·ai作画·aigc·音视频