【全域智能营销实战】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 完全指南

相关推荐
HavenlonLabs1 小时前
Havenlon 对抗性完整(十七):安全不是“防住攻击”,而是控制失败方式
网络·人工智能·架构·安全威胁分析·安全架构·havenlon
leoZ2311 小时前
Claude 全面解析:从基础原理到实战应用指南
人工智能·游戏
doiito(Do It Together)1 小时前
media_agent 进化之路:把 Gliding Horse 的 Agent 超能力注入 ComfyUI,让图片生成自己“学会”优化
人工智能·架构·rust·knowledge graph
Code_Artist2 小时前
Trae AI 创造力大赛创意作品:AI 数字克隆人——让你有无数个分身!
人工智能·llm·aigc
涛声依旧-底层原理研究所2 小时前
Agent 长任务可靠性设计:实现暂停、恢复、续跑与崩溃重启的完整方案
人工智能·python·系统架构
AC赳赳老秦2 小时前
防火墙规则批量配置实战:OpenClaw 自动生成模板、批量下发与合规性校验全解析
java·开发语言·人工智能·python·github·php·openclaw
8Qi82 小时前
HelloAgents:RAG——让 Agent 学会检索知识
人工智能·llm·agent·ai编程·vibecoding
触底反弹2 小时前
🔥 从点积到 Transformer:我终于搞懂大模型是怎么"猜"出下一个词的了
人工智能·机器学习·架构
2601_962502902 小时前
服装点胶点钻设备的算法架构与工艺适配分析
架构