本文是《Java 后端 + AI 落地(Spring AI 主线)》系列第 6 篇。前五篇一路用的都是 Spring AI,但有一个问题一直没有正面回答:如果这个选择本身就是错的,前面五篇的收益都要打折。 这篇不讲"哪个框架更强"------2026 年它们都能做聊天、工具调用、RAG、流式、MCP,拿功能清单对比已经没有意义 。这篇讲一件更实际的事:选型真正决定的是"它对你已有的运行时提出什么要求",以及"未来三年你跟不跟得上它的版本节奏"。 文中会把同一件需求(聊天 + 工具 + RAG)在两套框架里各写一遍,用代码形态而不是营销话术来做判断;并给出十个维度的逐项对比、一张选型决策树、以及双向迁移的真实成本表。
📌 版本声明 :本文基于 2026-09-27 的实际版本状况撰写。Spring AI:2.0.1(2.0.0 于 2026-06-12 GA),要求 Spring Boot 4.x / Spring Framework 7 / Jackson 3 / JDK 21 ;Boot 3.5 对应维护线为 Spring AI 1.1.8 ,而 Spring Boot 3.5 与 Spring Framework 6.2 已于 2026-06-30 EOL 。LangChain4j:主线 1.20.x(1.20.0 于 2026-09-04,1.20.1 于 2026-09-25),最低 JDK 17 ,同时并行维护 1.19.x / 1.11.x / 1.5.x 多条回溯补丁线,官方 starter 覆盖 Spring Boot 3.5+ 与 Boot 4 ,并可运行在 Quarkus / Micronaut / 纯 Java 环境。框架版本迭代很快,本文所有版本号请在动手前用官方 Release Notes 复核一遍 ;文中性能与生态规模类描述均为量级判断,不作为选型唯一依据。
文章目录
-
- [1️⃣ 为什么"选哪个框架"比看起来难](#1️⃣ 为什么"选哪个框架"比看起来难)
-
- [1.1 四种常见的错误选法](#1.1 四种常见的错误选法)
- [1.2 2026 年的关键事实:功能已经不是差异点](#1.2 2026 年的关键事实:功能已经不是差异点)
- [1.3 真正的分野:它对"你已有的运行时"提什么要求](#1.3 真正的分野:它对"你已有的运行时"提什么要求)
- [1.4 一条被严重低估的成本:版本节奏](#1.4 一条被严重低估的成本:版本节奏)
- [2️⃣ 两个框架的设计哲学](#2️⃣ 两个框架的设计哲学)
-
- [2.1 Spring AI:把 AI 能力做成"Spring 的一个抽象层"](#2.1 Spring AI:把 AI 能力做成"Spring 的一个抽象层")
- [2.2 LangChain4j:一个"地道的 Java 库",而不是 Python 的移植](#2.2 LangChain4j:一个"地道的 Java 库",而不是 Python 的移植)
- [2.3 同一件需求,两套代码:聊天 + 工具 + RAG](#2.3 同一件需求,两套代码:聊天 + 工具 + RAG)
- [2.4 术语对照表:避免"同名不同义"的坑](#2.4 术语对照表:避免"同名不同义"的坑)
- [3️⃣ 环境准备:两套依赖与版本矩阵](#3️⃣ 环境准备:两套依赖与版本矩阵)
-
- [3.1 版本矩阵:先看那张表,再决定要不要往下读](#3.1 版本矩阵:先看那张表,再决定要不要往下读)
- [3.2 两套依赖](#3.2 两套依赖)
- [3.3 环境准备阶段就该做的三件事](#3.3 环境准备阶段就该做的三件事)
- [4️⃣ 十个维度逐项对比](#4️⃣ 十个维度逐项对比)
-
- [4.1 运行时自由度](#4.1 运行时自由度)
- [4.2 版本节奏与升级代价](#4.2 版本节奏与升级代价)
- [4.3 抽象层级与代码形态](#4.3 抽象层级与代码形态)
- [4.4 RAG 能力](#4.4 RAG 能力)
- [4.5 工具调用与 Agentic 编排](#4.5 工具调用与 Agentic 编排)
- [4.6 记忆与会话](#4.6 记忆与会话)
- [4.7 流式输出与并发模型](#4.7 流式输出与并发模型)
- [4.8 可观测性](#4.8 可观测性)
- [4.9 中文生态与信创适配](#4.9 中文生态与信创适配)
- [4.10 文档、社区与生态规模](#4.10 文档、社区与生态规模)
- [5️⃣ 选型决策树、迁移成本与混合策略](#5️⃣ 选型决策树、迁移成本与混合策略)
-
- [5.1 选型决策树](#5.1 选型决策树)
- [5.2 迁移成本:如果选错了,换回来要付多少](#5.2 迁移成本:如果选错了,换回来要付多少)
- [5.3 混合使用:可行,但不是免费的](#5.3 混合使用:可行,但不是免费的)
- [5.4 三年总成本模型](#5.4 三年总成本模型)
- [6️⃣ 适用边界与风险提示](#6️⃣ 适用边界与风险提示)
-
- [6.1 五个必须避开的选型风险](#6.1 五个必须避开的选型风险)
- [6.2 什么时候两个框架都不该用](#6.2 什么时候两个框架都不该用)
- [6.3 生产建议清单](#6.3 生产建议清单)
- [7️⃣ 总结](#7️⃣ 总结)
-
- [7.1 回到开头那个问题](#7.1 回到开头那个问题)
- [7.2 十个维度速查表](#7.2 十个维度速查表)
- [7.3 本系列的选择与下一步](#7.3 本系列的选择与下一步)
- 参考资料

1️⃣ 为什么"选哪个框架"比看起来难
1.1 四种常见的错误选法
我在不同团队里见过至少四种选框架的方式,它们有一个共同点:都是在回答一个错误的问题。
| 选法 | 看起来的依据 | 为什么它错了 | 真实代价会在什么时候暴露 |
|---|---|---|---|
| 看 Star 数 / 热度 | "大家都在用" | Star 反映的是关注度,不回答"它和我现有运维体系兼不兼容" | 上线后才发现它要求一个你的运行时没有的东西 |
| 看 Benchmark 与功能清单 | "它支持 30+ 模型,它支持 12 种向量库" | 两边的能力清单在 2026 年高度重叠,差异项多半用不到 | 半年后发现"用不到的功能"从来没被真正评估 |
| 看同事/社区偏好 | "我们组有人熟 LangChain4j" | 单个人的熟悉度换来的是全团队三年的跟随成本 | 第一个大版本升级时,那个同事可能已经不在这个项目 |
| "都是 Java 框架,应该差不多" | 直觉 | 它们在"对运行时提什么要求"上差别巨大 | 你只是想给一个服务加 AI,结果要动整个平台的大版本 |
第四种最危险,因为它不产生任何讨论。 我见过一个团队的真实决策过程:需求的表述是"给工单服务加一个智能分派",实现的表述是"引入一个 AI 依赖"。三个月后复盘时发现,这件事的真实后果是"整个平台从 Spring Boot 3.5 升到了 Boot 4" ------而这个结论从来没有被拿出来讨论过。不是升级不对,而是它是被"顺手带进来"的,没有人为它做过一次决策。
1.2 2026 年的关键事实:功能已经不是差异点
这是我在技术选型评审上最想先说清的一句话:到了 2026 年,功能清单已经不是选型依据了。
把两边最常被拿来对比的能力列出来,你会看到它们高度重合:
| 能力 | Spring AI 2.0.x | LangChain4j 1.20.x |
|---|---|---|
| 聊天抽象 | ChatModel / ChatClient |
ChatModel / AiServices |
| 工具调用(Function Calling) | @Tool + ToolCallingAdvisor |
@Tool + AiServices 自动装配 |
| 结构化输出 | .entity(Class) |
返回类型驱动 |
| RAG | QuestionAnswerAdvisor / ETL Pipeline |
EmbeddingStoreIngestor / ContentRetriever |
| 向量库 | 主流库覆盖完整 | 覆盖最广(含混合检索专用 API) |
| 会话记忆 | ChatMemory + Advisor |
ChatMemory 多实现 |
| 流式输出 | Flux / stream() |
TokenStream / Flow.Publisher |
| MCP 客户端 | 支持 | 支持,且速度最快 |
| 可观测性 | Micrometer / Actuator 原生 | OpenTelemetry 标准 + 自行埋点 |
这张表最重要的信息在最后两列的"看起来类似"上。 每一格都能填出东西,所以拿它做选型依据,你会得到"两个都行"这个没有用的结论。 真正有区别的,是每一格下面藏着什么约束 ------比如同样是"流式输出",LangChain4j 的非阻塞支持(1.20 起按返回类型选择同步 / TokenStream / CompletableFuture / Flow.Publisher)是为了在 WebFlux / Quarkus 的事件循环上不阻塞线程 ,而 Spring AI 的做法更贴近 Spring 自己的响应式体系。这两者对"你的服务是什么并发模型"的要求完全不同。
所以我在这篇里坚持一个原则:不比较功能有无,只比较约束与代价。 后面第四章的十个维度,每一个都会落到"什么情况下这一条会变成决定项",而不是"谁得分高"。
1.3 真正的分野:它对"你已有的运行时"提什么要求
这是本篇最核心的一张图,也是我给所有团队讲选型时用的第一张图。 它把两个框架放到"运行时自由度"与"生态整合深度"这两个轴上------注意这两者不是"能力",而是"承诺":

(图 1:运行时自由度与生态整合深度------这不是能力高低,而是两种承诺方向;每一处"强"都对应一处"必须付出")
这张图的关键在于那条双向虚线:两个方向不是"优劣",而是"你更愿意付哪一种代价"。
Spring AI 的深度整合不是免费的。 它换来的是:ChatClient 一行构建、Advisor 可以直接复用你对 Spring 拦截器的直觉、Actuator 天然暴露 LLM 调用链路、配置全部走 Spring 的 @ConfigurationProperties。 但这些便利的入场券是 Spring Boot 4 + Framework 7 + Jackson 3 + JDK 21。
LangChain4j 的自由度也不是免费的。 它换来的是:在一个 Boot 3.5 的服务里加一个 AI 依赖,不需要动平台版本;或者在一个 Quarkus 项目里用同一套 API。 但代价是:那些"Spring 帮你做好的事"(配置绑定、Bean 装配、观测端点)你要自己接一遍。
这就是我那句结论的来源:
Spring AI 的"运行时自由度弱"和它的"生态整合强"是同一个决策的两面。选型不是选一个更好的框架,而是选一个你更愿意在接下来三年里为之买单的承诺。
1.4 一条被严重低估的成本:版本节奏
上一节讲的是"入场券",这一节讲"月供"。 如果你把框架当成一次性选择,会漏掉成本里最大的一块:跟随它的版本节奏要花多少人力。
先看两个框架过去 14 个月的版本节奏(数据来自官方 Release 记录,截至 2026-09-27):
| 维度 | Spring AI | LangChain4j |
|---|---|---|
| 大版本策略 | 绑定 Spring Boot 大版本(2.0 需 Boot 4) | 不绑框架(框架无关) |
| 发布节奏 | 与 Boot 节奏同步,大版本间隔以年计 | 小步快跑,1.12 → 1.20 约 6 个月 |
| 维护线数量 | 主 +1 条维护线(1.1.x 服务 Boot 3.5) | 主 + 多条回溯线(1.19.x / 1.11.x / 1.5.x 并行) |
| 升级动作 | 大版本升级 = 平台级动作(Boot 一起动) | 升级 = 改一个依赖版本 |
| 主要风险 | 被"平台升级"的时间表绑架 | 版本迭代快,集成模块可能带 beta 后缀 |
这张表里我认为最需要解释的是 LangChain4j 那一行的"并行维护多条回溯线"。 它同时是优点和风险:优点是锁在旧版本上的生产系统能持续拿到修复 (1.11.x 与 1.5.x 在 2026 年 7、8 月都还有补丁);风险是你必须想清楚"我跟哪条线" ,而且大量集成模块(模型、向量库适配)仍以 beta 版本号迭代,升级时要留意 API 变化。 这不是缺陷,是"小步快跑"这个性格的必然结果 ------跟踪它需要的是持续的、小额的投入;而跟踪 Spring AI 需要的是间歇的、大额的投入。
两种投入形态对应两种团队。 我把它总结成一句话:
如果你有一个稳定的平台团队、能承受"每隔一两年做一次平台大版本升级",那 Spring AI 的节奏更省心------一次投入、长期受益。如果你的团队小而灵活、更接受"每季度花半天跟一下依赖版本",LangChain4j 的节奏更匹配。
2️⃣ 两个框架的设计哲学
2.1 Spring AI:把 AI 能力做成"Spring 的一个抽象层"
理解 Spring AI 最快的方式,是把它类比成 Spring Data。 两者的定位完全一致:不发明新概念,而是把一类外部能力(数据库 / 大模型)抽象成 Spring 自己的编程模型,让开发者用已有的 Spring 直觉去使用它。
它的三个设计特征:
第一,一切皆 Advisor。 这是 Spring AI 最有辨识度的设计。RAG 检索、会话记忆、工具执行、内容安全过滤,全都实现成 Advisor,串在一条链上。 如果你熟悉 Servlet Filter 或 AOP 拦截器,这套模型你会觉得非常自然------这正是它的目的:把你的既有心智模型复用过来。 代价是顺序变成了语义 (第 4 篇 6.1 节、第 5 篇 2.3 节都讲过顺序配错导致的行为变化),而在 2.0 里工具执行也变成了链上的一环,顺序的敏感面进一步扩大。
第二,构建器 + 默认值。 ChatClient.builder().defaultSystem(...).defaultTools(...).defaultAdvisors(...)------默认值集中在构建期,业务代码里只剩 prompt().user(q).call().content()。 这个设计的收益是业务代码极干净、能力集集中可审计 (第 5 篇 4.1 节);代价是配置的"隐式性"较强,出问题时要顺着构建器往回找。
第三,与 Boot 生命周期深度绑定。 自动配置、@ConfigurationProperties 绑定、Actuator 端点、条件装配全套齐全。这也是它要求 Boot 4 的原因:这些能力依赖 Boot 与 Framework 的底层机制,跨大版本兼容的代价太高。
顺便说一个 2.0 的取舍,它很能说明这个框架的性格: 2.0 对模型提供商做了精简 ------部分提供商只保留官方 SDK 变体,某些厂商(如 Google)移除了 Vertex 变体。这不是能力倒退,而是"维护可控"优先于"列表好看"。 这条取舍对选型的含义是:迁移到 2.0 之前,先确认你要用的提供商在支持列表里。 官方系框架的共同倾向是**"支持面窄一点、但每条线都长期维护"**。
2.2 LangChain4j:一个"地道的 Java 库",而不是 Python 的移植
关于 LangChain4j 有一个流传很广的误解必须先澄清:它不是 Python LangChain 的 Java 移植,而是从零构建的惯用 Java 库。 这个区别不是文字游戏------它的 API 形态是类型安全、POJO、注解、接口、流式 API 那一套 Java 的东西,而不是把 Python 的动态风格翻译过来。
它的两个设计特征:
第一,两级抽象,让你自己选深潜程度。 低层是原语(ChatModel、EmbeddingStore、EmbeddingModel),自由度拉满 ;高层是 AiServices 声明式接口 ------定义一个 Java 接口加注解,框架生成实现,把"调模型"写成像调用本地方法一样 。这个双层的价值在下面 2.3 节会直接体现出来:同一件事用高层写像 Spring AI 一样干净,用低层写能控制到很细。
第二,Agentic 原语走得比较靠前。 这条是我认为 Spring AI 与它差异最真实的地方。以 1.19 为例,它引入了:
AgenticScope:智能体之间共享状态的载体;- 并行 mapper:多步操作并行化;
- 工具补偿机制 :多个工具连续调用中途失败时自动执行补偿操作------本质是给 AI Agent 补上了 Saga 风格的事务语义。
最后这一条值得单独说,因为它正好接上了第 5 篇 5.4 节留下的问题。 我在第 5 篇里说,写类工具的风险来自"三个独立的重试源",解法是幂等键 + 关闭自动重试 ------那是"防止重复执行"的思路。而"工具补偿"是另一个思路:既然多步操作可能在中途失败,那就为已经执行成功的步骤预备一个补偿动作(Saga 模式)。 两个思路不冲突,但后者的工程化封装确实是 LangChain4j 先做的。 如果你的场景里有"调三个接口完成一个业务动作、中间失败要回滚前两步"这类需求,这一条会成为真实的加分项。
另外它的一个鲜明性格是"MCP 协议跟进速度最快" ------它是第一个支持 MCP 2026-07-28 新规范的 Java 框架。这个性格对选型的含义是:如果你在 MCP 生态上押得比较重、需要第一时间用上新协议能力,LangChain4j 的跟进速度是实打实的优势。
2.3 同一件需求,两套代码:聊天 + 工具 + RAG
理论说完,直接看代码。这是我认为最诚实的对比方式:不是看文档能做什么,而是看"同样一件事,我的代码会长成什么样"。
需求:一个带会话记忆、能查数据库(工具)、能查文档(RAG)的助手。
Spring AI 的写法:
java
// 形态:构建器 + Advisor 链。
// 记忆、RAG、工具都是"链上的一个环节",能力集集中在构建期声明。
@Bean
ChatClient assistant(ChatModel chatModel, VectorStore vectorStore,
ChatMemory chatMemory, ExpenseTools tools) {
return ChatClient.builder(chatModel)
.defaultSystem("你是企业费用助手。涉及数值一律调用工具,禁止自行计算。")
.defaultTools(tools)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
// 顺序纪律:RAG 必须在工具执行之前(第 4 篇 6.1 节)
QuestionAnswerAdvisor.builder(vectorStore).build())
.build();
}
// 业务代码只剩一行
String answer = assistant.prompt().user(question).call().content();
LangChain4j 的写法:
java
// 形态:接口 + 注解,框架生成实现。
// 记忆、RAG、工具都是"接口上的一个声明",能力集写在 AiServices 装配处。
interface ExpenseAssistant {
@SystemMessage("你是企业费用助手。涉及数值一律调用工具,禁止自行计算。")
String chat(@UserMessage String question);
// 流式只需换返回类型;1.20 起这一行同时决定了执行模式
TokenStream chatStream(@UserMessage String question);
}
@Bean
ExpenseAssistant expenseAssistant(ChatModel chatModel,
EmbeddingStore<TextSegment> store,
EmbeddingModel embeddingModel,
ExpenseTools tools) {
return AiServices.builder(ExpenseAssistant.class)
.chatModel(chatModel)
.chatMemory(MessageWindowChatMemory.withMaxMessages(20))
.tools(tools)
.contentRetriever(EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.embeddingModel(embeddingModel)
.maxResults(3)
.build())
.build();
}
// 业务代码:像调本地方法
String answer = assistant.chat(question);
两段代码放在一起,差异一眼可见,而且差异的性质很清楚:
| 对比项 | Spring AI | LangChain4j |
|---|---|---|
| 扩展方式 | 加一个 Advisor(横切能力串在链上) | 加一个配置/注解(能力挂在装配处) |
| 顺序敏感的显性度 | 高 :顺序就在构建器里,一眼可读,但也意味着必须读它 | 低 :由框架内部决定,不容易配错,但也难精细干预 |
| 业务代码形态 | prompt().user(q).call().content() |
assistant.chat(q) |
| 流式的开启方式 | 换调用方法(.stream()) |
换返回类型 (TokenStream) |
| 心智负担 | 需要理解 Advisor 的顺序语义 | 需要理解接口/注解 → 实现的生成规则 |
这张表的最后一行是我认为最实在的判断依据。 Spring AI 把"顺序"暴露给你了,LangChain4j 把它藏起来了。 如果你在第 4、5 篇那些顺序纪律上踩过坑、并且希望**"配错了能一眼看出来"**,那 Spring AI 的显性是优点;如果你更希望"框架别让我操这个心",LangChain4j 的隐藏是优点。
但要注意:被隐藏的东西不是消失了,只是不在你眼前。 一旦 LangChain4j 内部的默认顺序与你的预期不符,你需要读源码或文档才能调,而不像 Advisor 链那样改一行顺序就行。 这是"省心"的另一种代价形态。
2.4 术语对照表:避免"同名不同义"的坑
这一节很实用,因为两个框架有大量同名概念但行为不完全一致。 从一边迁到另一边的人,最容易踩的坑就是"以为 ChatMemory 是同一个东西"。
| 概念 | Spring AI | LangChain4j | 需要注意的差异 |
|---|---|---|---|
| 对话模型 | ChatModel |
ChatModel |
同名,都是低层原语,这点一致 |
| 高层门面 | ChatClient(构建器 + Fluent) |
AiServices(接口 + 注解) |
形态差异最大的一处 |
| 工具定义 | @Tool / @ToolParam |
@Tool / @P |
注解名接近,参数注解名不同 |
| 工具上下文 | ToolContext(模型不可见的可信旁路) |
参数注入 / 上下文字段 | 两边都能做到"模型不可见",但写法差别大 |
| 记忆 | ChatMemory + Advisor |
ChatMemory(多实现) |
一个是"链上的一环",一个是"装配时的一项" |
| RAG 检索 | QuestionAnswerAdvisor / VectorStore |
ContentRetriever / EmbeddingStore |
注意 VectorStore ↔ EmbeddingStore 是同一角色的两个名字 |
| 入库流程 | ETL Pipeline(DocumentReader / TokenTextSplitter) |
EmbeddingStoreIngestor(可插拔 splitter + transformer) |
LangChain4j 的入库封装更开箱 |
| 向量库抽象 | VectorStore |
EmbeddingStore |
语义一致,过滤表达式语法不通用 |
| 流式 | Flux / .stream() |
TokenStream / Flow.Publisher |
非阻塞支持的选择机制不同 |
| 观测 | Micrometer / Actuator | OpenTelemetry + 自行 Tracer | Spring AI 在 Spring 栈里更省事 |
这张表里我要特别标注两处最容易出问题的:
第一,ChatMemory 的接入方式差异,会直接影响你排查"上下文丢失"类问题的方式。 Spring AI 里记忆是 Advisor 链的一环,所以"记忆没生效"的第一排查点是"Advisor 有没有挂上、顺序对不对" ;LangChain4j 里它是装配参数,第一排查点是"AiServices 装配时有没有传" 。同样是"它忘了前面说的",两边的排查路径完全不同。
第二,向量库的过滤表达式不通用。 这一条在你们按第 4 篇做权限过滤时会直接撞上:acl_tags 这类过滤条件的写法,两边不共享。 也就是说,如果你从一边迁到另一边,向量库的过滤逻辑要重写 ------这属于迁移成本里被低估的一项(5.2 节会把它量化)。
3️⃣ 环境准备:两套依赖与版本矩阵
3.1 版本矩阵:先看那张表,再决定要不要往下读
如果这一篇只看一张表,请看我建议的这张。 因为它把"选型"直接翻译成了"你能不能接受这个要求":
| 项 | Spring AI 路线 | LangChain4j 路线 |
|---|---|---|
| JDK | 21(2.0 硬要求) | 17+(主线最低;无 Java 8 线) |
| Spring Boot | 必须 4.0 / 4.1 | 可选:Boot 3.5+ / Boot 4 都有 starter;也可不用 Boot |
| Spring Framework | 7.x | 跟随你的 Boot |
| Jackson | 3.x(Boot 4 默认线) | 默认线,Jackson 3 为可选模块 (langchain4j-jackson3) |
| 框架耦合 | 强(Boot 起) | 弱(一个普通依赖) |
| 非 Spring 环境 | 不适用 | Quarkus / Micronaut / 纯 Java 均可 |
| 大版本升级代价 | 平台级(Boot 一起升) | 依赖级(改版本号) |
这张表里我认为最关键的一格是"Spring Boot"那一行。 它把本文前面所有讨论压缩成了一个二元问题:
如果你的服务现在跑在 Boot 3.5 或更低,选 Spring AI 2.0 就意味着"给一个服务加 AI"变成"整个平台升 Boot 4"。 而选 LangChain4j,这件事就只是"加一个依赖"。
这不是说 Boot 4 不值得升 ------它可能是你本来就该做的平台动作。关键是这个决定应该在选型会上被明确提出来、被单独评估,而不是作为"引入 AI"的附带结果。 我在 1.1 节讲的那个真实案例,问题不在于升了 Boot 4,而在于没有人意识到自己做了这个决定。
如果你的项目是 Boot 3.5 且短期不打算升 ,那么 Spring AI 这边你只能停在 1.1.x 维护线 (对应 Boot 3.5),而这是本文要给你的第三个提醒:1.1.x 是维护线,不是发展线 ------第 5 篇讲的 2.0 工具调用重构(执行位置从 ChatModel 上移到 Advisor 链)在 1.1.x 上不适用 ,你学到的东西会有一部分要重写。 这一点在第 5 篇的版本声明里已经标过,在选型阶段它是很实际的成本项。
3.2 两套依赖
Spring AI(Boot 4 + JDK 21):
xml
<properties>
<java.version>21</java.version>
<spring-ai.version>2.0.1</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- OpenAI 兼容端点,也用于接通义千问(第 2 篇路线 B) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- 向量库:示例用 pgvector(第 4 篇) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>
</dependencies>
LangChain4j(不强制 Boot 4):
xml
<properties>
<java.version>17</java.version>
<!-- 注意:1.20.x 是当前主线;生产上大量系统仍锁 1.19.x -->
<langchain4j.version>1.20.1</langchain4j.version>
</properties>
<dependencies>
<!-- 核心 + 声明式 AI Services -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- OpenAI 兼容端点(同样可接通义千问兼容模式) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- Spring Boot 集成(注意:有 Boot 3.5 与 Boot 4 两条 starter 线) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- 可选:需要 Jackson 3 时显式引入(1.20 起为 opt-in) -->
<!--
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-jackson3</artifactId>
<version>${langchain4j.version}</version>
</dependency>
-->
</dependencies>
这两段 pom 放在一起,有一个细节很能说明问题:Spring AI 那边 你没有写 Boot 的版本**(它由 Boot 的父 POM 决定,而 spring-ai-bom 只约束自己的模块);LangChain4j 那边所有版本都是显式的 ,而且要注意 starter 有 Boot 3.5 与 Boot 4 两条线 ------选错线会出现"能编译、启动失败"的情况,且报错信息通常不指向真因。这是 3.1 节那张版本矩阵在工程上的具体表现。
3.3 环境准备阶段就该做的三件事
框架还没选完,有三件事可以先做,它们对两条路线都有价值:
第一件:确认你的 JDK 版本这一项是"决定项"还是"既有事实"。 如果团队已经在 JDK 21 上(我建议这是 2026 年的基线),那么 Spring AI 2.0 的 JDK 要求不构成门槛 ,选型就退回到"Boot 大版本"这一个问题上。如果还在 JDK 17,LangChain4j 的 17+ 门槛就变成一个真实的吸引力 。注意:JDK 版本是很少被纳入框架选型讨论、但实际影响很大的一项。
第二件:把"模型与向量库的支持列表"对着你的技术栈核一遍。 两边都支持主流选项,但你可能有非主流需求。一个具体的例子: 如果你的需求里有"必须支持某个向量库的 ANN + BM25 混合检索",那就去查那个库在两边适配器里的成熟度与版本号后缀(beta 与否)------我见过选型的真实胜负手就是这一条,而不是任何宏观对比。
第三件:把本文 1.4 节的版本节奏表,乘以你团队的人力,算成"年成本"。 公式保持简单就好:
| 路线 | 升级动作 | 频次 | 单次人力 | 年成本量级 |
|---|---|---|---|---|
| Spring AI | 平台大版本升级(Boot + Framework + AI 一起) | 约 1~2 年一次 | 高(含回归测试全平台) | 大额、间歇 |
| LangChain4j | 依赖版本跟随 + 回归 AI 相关用例 | 约每季度一次 | 低(局限在 AI 模块) | 小额、持续 |
这张表的用处不是算出精确数字,而是让你在选型会上能回答一句: "我们更愿意每年花 5 次小力气,还是两年花一次大力气?"这个问题的答案,往往比功能对比更能决定哪个框架更适合你的团队。
4️⃣ 十个维度逐项对比
说明一下这一章的组织方式:每个维度我都写成三段------"客观差异表"、"这一条什么时候会变成决定项"、"一个提醒"。 第三段很重要,因为选型里最容易犯的错是"把某个维度的优势无限放大"------而每个优势都只在特定条件下才是优势。
4.1 运行时自由度
| 项 | Spring AI | LangChain4j |
|---|---|---|
| 能否脱离 Spring | 不能(依赖 Boot 自动配置体系) | 可以(普通依赖) |
| 支持的框架 | Spring Boot 4.x(1.1.x 支持 Boot 3.5) | Boot 3.5+ / Boot 4 / Quarkus / Micronaut / 纯 Java |
| 给现有服务加 AI 的动作 | 可能触发平台大版本升级 | 加一个依赖 |
| JDK 要求 | 21 | 17+ |
这一条什么时候会变成决定项: 四种情况,满足任意一种就该把权重拉满------
- 你的项目不是 Spring(Quarkus / Micronaut / 纯 Java / 甚至 Android 后端)------这一条直接决定答案,无需再看其他维度。
- 你的服务在 Boot 3.5 或更低,且短期(半年内)没有升级窗口------这时候选 Spring AI 2.0 等于把"加 AI"和"升平台"捆在一起,而升平台通常要排期、要过评审。
- 大型多模块单体,只想给某一个模块加 AI------LangChain4j 的"加一个依赖"在这里的成本优势非常明显。
- 平台的升级窗口排不上期(架构组的时间表不由你定),但你有一个业务需求这个季度就要上线。
一个提醒:如果你已经是 Boot 4 + JDK 21,这一维度的权重应该直接归零。 我在评审里见过团队在"自由度"上纠结很久,而他们的栈本来就是 Boot 4 + JDK 21、也没有非 Spring 模块 ------这时候这个维度的所有讨论都是白费。 先确认你处在这四种情况的哪一种,再决定要不要讨论。
4.2 版本节奏与升级代价
#mermaid-svg-3PG5ShPu1p8I56pd{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-3PG5ShPu1p8I56pd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3PG5ShPu1p8I56pd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3PG5ShPu1p8I56pd .error-icon{fill:#552222;}#mermaid-svg-3PG5ShPu1p8I56pd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3PG5ShPu1p8I56pd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3PG5ShPu1p8I56pd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3PG5ShPu1p8I56pd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3PG5ShPu1p8I56pd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3PG5ShPu1p8I56pd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3PG5ShPu1p8I56pd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3PG5ShPu1p8I56pd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3PG5ShPu1p8I56pd .marker.cross{stroke:#333333;}#mermaid-svg-3PG5ShPu1p8I56pd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3PG5ShPu1p8I56pd p{margin:0;}#mermaid-svg-3PG5ShPu1p8I56pd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3PG5ShPu1p8I56pd .cluster-label text{fill:#333;}#mermaid-svg-3PG5ShPu1p8I56pd .cluster-label span{color:#333;}#mermaid-svg-3PG5ShPu1p8I56pd .cluster-label span p{background-color:transparent;}#mermaid-svg-3PG5ShPu1p8I56pd .label text,#mermaid-svg-3PG5ShPu1p8I56pd span{fill:#333;color:#333;}#mermaid-svg-3PG5ShPu1p8I56pd .node rect,#mermaid-svg-3PG5ShPu1p8I56pd .node circle,#mermaid-svg-3PG5ShPu1p8I56pd .node ellipse,#mermaid-svg-3PG5ShPu1p8I56pd .node polygon,#mermaid-svg-3PG5ShPu1p8I56pd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3PG5ShPu1p8I56pd .rough-node .label text,#mermaid-svg-3PG5ShPu1p8I56pd .node .label text,#mermaid-svg-3PG5ShPu1p8I56pd .image-shape .label,#mermaid-svg-3PG5ShPu1p8I56pd .icon-shape .label{text-anchor:middle;}#mermaid-svg-3PG5ShPu1p8I56pd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3PG5ShPu1p8I56pd .rough-node .label,#mermaid-svg-3PG5ShPu1p8I56pd .node .label,#mermaid-svg-3PG5ShPu1p8I56pd .image-shape .label,#mermaid-svg-3PG5ShPu1p8I56pd .icon-shape .label{text-align:center;}#mermaid-svg-3PG5ShPu1p8I56pd .node.clickable{cursor:pointer;}#mermaid-svg-3PG5ShPu1p8I56pd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3PG5ShPu1p8I56pd .arrowheadPath{fill:#333333;}#mermaid-svg-3PG5ShPu1p8I56pd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3PG5ShPu1p8I56pd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3PG5ShPu1p8I56pd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3PG5ShPu1p8I56pd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3PG5ShPu1p8I56pd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3PG5ShPu1p8I56pd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3PG5ShPu1p8I56pd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3PG5ShPu1p8I56pd .cluster text{fill:#333;}#mermaid-svg-3PG5ShPu1p8I56pd .cluster span{color:#333;}#mermaid-svg-3PG5ShPu1p8I56pd 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-3PG5ShPu1p8I56pd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3PG5ShPu1p8I56pd rect.text{fill:none;stroke-width:0;}#mermaid-svg-3PG5ShPu1p8I56pd .icon-shape,#mermaid-svg-3PG5ShPu1p8I56pd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3PG5ShPu1p8I56pd .icon-shape p,#mermaid-svg-3PG5ShPu1p8I56pd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3PG5ShPu1p8I56pd .icon-shape .label rect,#mermaid-svg-3PG5ShPu1p8I56pd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3PG5ShPu1p8I56pd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3PG5ShPu1p8I56pd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3PG5ShPu1p8I56pd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} LangChain4j:小步快跑
1.12(2026-03)
1.16(2026-06)
1.19(2026-08)
AgenticScope / 工具补偿 / MCP 新规范
1.20.x(2026-09)
非阻塞 AI Services / Jackson 3 可选
依赖级升级:
动作小、频次高(约季度)
时机由你定
并行维护回溯线:
1.19.x / 1.11.x / 1.5.x
旧版本仍持续拿修复
Spring AI:与 Boot 节奏绑定
Boot 3.5 + Spring AI 1.1.x
Boot 3.5 / Framework 6.2
2026-06-30 EOL
Spring AI 2.0 GA
2026-06-12
强制 Boot 4 + Framework 7 + JDK 21
平台级升级:
动作大、频次低
时机受 Boot EOL 时间表牵引
两种投入形态:
大额间歇 vs 小额持续
(图 2:两个框架的版本节奏对比------Spring AI 是"大额间歇",LangChain4j 是"小额持续";注意那条并行回溯维护线)
这一条什么时候会变成决定项:
- 你的团队有独立平台团队、能定期做平台大版本升级 → Spring AI 的节奏更省心(一次投入、长期受益)。
- 你的团队小而灵活、更接受"每季度花半天跟依赖" → LangChain4j 的节奏更匹配。
- 你不能接受"升级时机由别人的 EOL 时间表决定" → 这一条会明显偏向 LangChain4j。
- 你需要在旧版本上长期停留(例如锁在某个稳定版本三年) → LangChain4j 的多条回溯维护线是实打实的优势;Spring AI 的 1.1.x 维护线只服务 Boot 3.5 这一档,且不承载新特性。
一个提醒:大版本绑定的真实风险不是"升级难",而是"升级的时机不由你定"。 前面图上那行 Boot 3.5 / Framework 6.2 → 2026-06-30 EOL 是关键:当一个平台组件 EOL,它会沿着依赖链把压力传导到你的框架上 ------安全补丁、合规扫描、"必须使用受支持版本"的内部规范,都会变成推动你升级的外力。所以"绑定大版本"的代价要按"你是否能自主决定升级时机"来评估,而不是按"升级要花多少人天"。

4.3 抽象层级与代码形态
| 项 | Spring AI | LangChain4j |
|---|---|---|
| 高层门面 | ChatClient(构建器 + Fluent) |
AiServices(接口 + 注解) |
| 低层原语 | ChatModel / VectorStore / Advisor |
ChatModel / EmbeddingStore / Retriever |
| 扩展机制 | 加一个 Advisor(横切能力) | 加一个装配项 / 注解 |
| 业务代码 | prompt().user(q).call().content() |
assistant.chat(q) |
| 学习曲线 | 需要理解 Advisor 顺序语义 | 需要理解接口 → 实现的生成规则 |
| 对非 AI 背景同学 | 与 Spring 拦截器心智契合 | "像调本地方法",门槛更低 |
这一条什么时候会变成决定项:
- 同一个助手被团队里多人、多入口调用,且要求行为一致 → LangChain4j 的接口形态优势明显 :能力集写在接口和装配处,调用方只看到方法签名,不可能有人"顺手少挂一个 Advisor"。
- 每个业务场景需要一套不同的能力组合 (A 场景要 RAG 不要工具,B 场景反过来) → Spring AI 的构建器更自然,每个场景一个
ChatClientBean,边界清楚。 - 团队里 AI 经验少、Java 常规经验多 → 接口 + 注解的形态上手更快。
一个提醒:代码形态的差异会渗透到代码评审里。 Spring AI 的 Advisor 链把"顺序"暴露在代码中(第 4 篇 6.1 节的顺序纪律因此可以被 review 发现),但也意味着评审时你必须真的去看这个顺序 ------我见过不止一次"Advisor 挂上了但顺序不对",而这种问题在 LangChain4j 的装配式写法里根本不会出现(因为顺序由框架定)。 所以这一条的本质是:你希望这类风险"显式可见但需要人检查",还是"隐式处理但不可干预"。
4.4 RAG 能力
| 项 | Spring AI | LangChain4j |
|---|---|---|
| 入库流程 | ETL Pipeline(DocumentReader + TokenTextSplitter),需自己组合 |
EmbeddingStoreIngestor 开箱(可插拔 splitter / transformer) |
| 检索装配 | QuestionAnswerAdvisor 挂到 ChatClient |
ContentRetriever 注入 AiServices |
| 向量库覆盖 | 主流库完整 | 覆盖最广(Milvus / Qdrant / Weaviate / ES / pgvector / Redis 等) |
| 混合检索 | 逐步完善 | 有专用 API(关键词 + 向量融合) |
| 本地嵌入 | 通常委托 provider 或 Ollama | 自带 ONNX 本地嵌入模型,可完全离线 |
| 过滤表达式 | 各自语法,不通用 | 各自语法,不通用 |
这一条什么时候会变成决定项:
- 需求里明确写了某个特定向量库的混合检索 → 必须去核对该库在两边适配器的成熟度 。这是一个会直接决定胜负的项:如果某个库的"ANN + BM25 混合检索"在一边只有封装好的方法、在另一边要自己写
VectorStore实现,那这就是几周的差距。 - 需要"完全离线 / 数据不出机房"(第 3 篇的第三档合规) → LangChain4j 自带的 ONNX 本地嵌入模型让"连嵌入都不用调外部 API"更容易一步到位,这在合规上是很有说服力的实现路径。
- 已经在 Spring + pgvector 上、需求是标准的向量检索 → Spring AI 更顺,没有理由为了覆盖广度去换框架。

一个提醒:RAG 的真正难点两条路线都不解决。 第 4 篇讲的那些纪律------检索决定上限、文档解析是隐性天花板、相似度阈值比 top-k 重要、权限过滤必须写在检索请求里、越权率必须为 0 ------全部是"你怎么用",不是"框架给你什么"。 换框架能改变的只是"装配这段流程要写多少行",改变不了"你的分块策略对不对"和"你的权限过滤有没有漏洞"。 选型时不要把 RAG 效果的期待押在框架上。
4.5 工具调用与 Agentic 编排
| 项 | Spring AI | LangChain4j |
|---|---|---|
| 工具定义 | @Tool / @ToolParam |
@Tool / @P |
| 模型不可见的上下文 | ToolContext |
参数注入 / 上下文字段 |
| 循环托管 | 2.0 上移到 ToolCallingAdvisor,顺序显式可干预 |
AiServices 内部托管 |
| 手动接管 | internalToolExecutionEnabled(false) |
有对应控制 |
| 多步编排 | 需引入 graph 模块做图编排 | AgentExecutor(ReAct / Plan-and-Execute 等执行器)原生 |
| 失败补偿 | 需自己实现 | 工具补偿机制(Saga 风格语义) |
| 多 Agent 共享状态 | 需自行设计 | AgenticScope |
| 并行执行 | 自行编排 | 并行 mapper |
这一条什么时候会变成决定项:
- 只是"工具调用 + 单 Agent 一问一答" → 两边持平,这一维度不作为依据 。第 5 篇讲的工具设计十条原则、幂等、权限三层防御在两边都是同样的工作,框架只负责那六件事(第 5 篇 1.4 节)。
- 有多步编排需求 ("查订单 → 判断能否取消 → 执行取消 → 发通知",任一步失败要回退) → LangChain4j 的原语成熟度是真实的加分项 。第 5 篇 5.4 节我说过"写类工具的风险来自三个重试源",解法是幂等键;而"多步操作中途失败怎么办"这件事,Saga 风格的补偿是更系统的答案------LangChain4j 把它做成了框架能力,Spring AI 这边要自己搭。
- 要做多 Agent 协作 →
AgenticScope这种"智能体间共享状态"的原语能省掉不少设计工作。 - 需要精细干预工具循环的每一轮 (审批式写操作、向前端暴露进度) → Spring AI 的 Advisor 位置显式,"循环在哪一环、能不能插手"这件事更清楚。
一个提醒:Agentic 原语的丰富度与"生产稳定性"不是同一件事。 LangChain4j 在 Agentic 方向走得靠前,但要留意其中相当一部分能力标记为实验性(@Experimental)或带 beta 后缀。 选型时如果要把这些原语作为主要依据,请务必确认它们在你锁定的版本里的成熟度与 API 稳定性 ------用实验性原语承载核心业务,是把框架的迭代风险直接搬进了你的生产路径。
4.6 记忆与会话
| 项 | Spring AI | LangChain4j |
|---|---|---|
| 抽象 | ChatMemory + Advisor |
ChatMemory(多实现) |
| 装配方式 | 链上的一环(顺序敏感) | 装配时的一项(不易漏) |
| 常用策略 | 窗口记忆、持久化后端 | 窗口记忆、多存储后端 |
| 精细控制 | Advisor 形态便于横切改造(如按 token 裁剪) | 通过自定义实现替换 |
这一条什么时候会变成决定项:
- 只需标准窗口记忆 → 两边等价,不作为依据。
- 需要对记忆做精细横切干预 (例如"按 token 数裁剪而不是按条数"、跨会话摘要、按用户分级保留) → Spring AI 的 Advisor 形态更灵活,因为记忆是一个可以替换/叠加的链环节点。
- 团队容易"漏挂配置" → LangChain4j 的装配式写法更不容易漏(漏了接口行为会明显异常,而 Advisor 漏挂可能只是"记忆没生效"这种更隐蔽的表现)。
一个提醒:企业场景里"记忆"的真正难点是"存在哪",不是"用哪个 API"。 会话状态要外置到 Redis 还是留在进程内存、多实例怎么共享、重启后要不要恢复、摘要什么时候生成------这些是系列一第 8 篇和第 11 篇的主题,两个框架都只提供接口,不提供答案。 选型时不要把这一项想得太重。
4.7 流式输出与并发模型
这一节是本文技术含量最高的一节,因为它的差异不是"风格",而是"能不能用"。
#mermaid-svg-sHrsvx5BJI82qYtr{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-sHrsvx5BJI82qYtr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sHrsvx5BJI82qYtr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sHrsvx5BJI82qYtr .error-icon{fill:#552222;}#mermaid-svg-sHrsvx5BJI82qYtr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sHrsvx5BJI82qYtr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sHrsvx5BJI82qYtr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sHrsvx5BJI82qYtr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sHrsvx5BJI82qYtr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sHrsvx5BJI82qYtr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sHrsvx5BJI82qYtr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sHrsvx5BJI82qYtr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sHrsvx5BJI82qYtr .marker.cross{stroke:#333333;}#mermaid-svg-sHrsvx5BJI82qYtr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sHrsvx5BJI82qYtr p{margin:0;}#mermaid-svg-sHrsvx5BJI82qYtr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-sHrsvx5BJI82qYtr .cluster-label text{fill:#333;}#mermaid-svg-sHrsvx5BJI82qYtr .cluster-label span{color:#333;}#mermaid-svg-sHrsvx5BJI82qYtr .cluster-label span p{background-color:transparent;}#mermaid-svg-sHrsvx5BJI82qYtr .label text,#mermaid-svg-sHrsvx5BJI82qYtr span{fill:#333;color:#333;}#mermaid-svg-sHrsvx5BJI82qYtr .node rect,#mermaid-svg-sHrsvx5BJI82qYtr .node circle,#mermaid-svg-sHrsvx5BJI82qYtr .node ellipse,#mermaid-svg-sHrsvx5BJI82qYtr .node polygon,#mermaid-svg-sHrsvx5BJI82qYtr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sHrsvx5BJI82qYtr .rough-node .label text,#mermaid-svg-sHrsvx5BJI82qYtr .node .label text,#mermaid-svg-sHrsvx5BJI82qYtr .image-shape .label,#mermaid-svg-sHrsvx5BJI82qYtr .icon-shape .label{text-anchor:middle;}#mermaid-svg-sHrsvx5BJI82qYtr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sHrsvx5BJI82qYtr .rough-node .label,#mermaid-svg-sHrsvx5BJI82qYtr .node .label,#mermaid-svg-sHrsvx5BJI82qYtr .image-shape .label,#mermaid-svg-sHrsvx5BJI82qYtr .icon-shape .label{text-align:center;}#mermaid-svg-sHrsvx5BJI82qYtr .node.clickable{cursor:pointer;}#mermaid-svg-sHrsvx5BJI82qYtr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sHrsvx5BJI82qYtr .arrowheadPath{fill:#333333;}#mermaid-svg-sHrsvx5BJI82qYtr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sHrsvx5BJI82qYtr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sHrsvx5BJI82qYtr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sHrsvx5BJI82qYtr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sHrsvx5BJI82qYtr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sHrsvx5BJI82qYtr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sHrsvx5BJI82qYtr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sHrsvx5BJI82qYtr .cluster text{fill:#333;}#mermaid-svg-sHrsvx5BJI82qYtr .cluster span{color:#333;}#mermaid-svg-sHrsvx5BJI82qYtr 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-sHrsvx5BJI82qYtr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sHrsvx5BJI82qYtr rect.text{fill:none;stroke-width:0;}#mermaid-svg-sHrsvx5BJI82qYtr .icon-shape,#mermaid-svg-sHrsvx5BJI82qYtr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sHrsvx5BJI82qYtr .icon-shape p,#mermaid-svg-sHrsvx5BJI82qYtr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sHrsvx5BJI82qYtr .icon-shape .label rect,#mermaid-svg-sHrsvx5BJI82qYtr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sHrsvx5BJI82qYtr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sHrsvx5BJI82qYtr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sHrsvx5BJI82qYtr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Tomcat 线程池
(传统阻塞模型)
WebFlux / Vert.x / Quarkus
(事件循环)
非阻塞(按返回类型选择,1.20 起)
HTTP 请求进入
返回类型决定执行模式
CompletableFuture / Flow.Publisher
等待期间不占用线程
非阻塞覆盖 工具 / 记忆 / 护栏 / 检索图
回调用链上恢复
阻塞式 AI Services(经典模型)
T1 在整个工具循环期间被占住
HTTP 请求进入
工作线程 T1
调用模型(等待)
工具调用 → 执行(等待)
再调模型(等待)
返回
线程数 = 并发上限
循环越长,占线程越久
你的运行时是哪种?
阻塞式通常够用:
线程池是天然缓冲
阻塞整个循环会卡住事件循环
→ 这是非阻塞支持存在的全部理由
(图 3:阻塞式与非阻塞 AI Services 的线程占用差异------在事件循环模型(WebFlux / Vert.x / Quarkus)上,这条是硬要求)
这一条什么时候会变成决定项:
- 你的服务跑在事件循环模型上 (Spring WebFlux / Vert.x / Quarkus) → 这是硬要求,不是偏好。 阻塞式的 AI 调用会占住事件循环线程,而 AI 调用恰好是"长耗时 + 多轮"的典型形态 (第 5 篇 1.2 节:一轮工具调用至少两次模型请求),它比普通数据库调用更容易把事件循环堵死。
- 你需要在一次请求里跑多个工具/多步编排且要求高并发 → 非阻塞的能力(
CompletableFuture/Flow.Publisher)在尾延迟上的收益会体现出来。 - 你的服务是传统 Tomcat + 线程池 → 阻塞式通常够用,因为线程池本身就是缓冲。此时这一维度权重降低。
一个提醒:非阻塞的"默认语义与同步不同",切换时要按迁移处理而不是按开关处理。 具体有几处容易踩的差异:异步模式下工具可能默认并发执行 (与同步的顺序执行不同)、执行错误会直接让这次调用失败 (而同步模式下可能是返回结果里的一个错误标记)、解析错误会作为消息回给 LLM 由它重试 。另外非阻塞覆盖范围里"检索图"标注为实验性 。所以如果你的并发模型确实需要非阻塞,请把它当成一次有回归测试的迁移来做,而不是改一个返回类型就上线。
4.8 可观测性
| 项 | Spring AI | LangChain4j |
|---|---|---|
| 原生集成 | Micrometer / Actuator(Spring 栈内天然可用) | OpenTelemetry 标准 + Tracer 接口 |
| 调用链路可见度 | Actuator 可暴露 LLM 调用链路、Prompt 渲染结果 | 需自行实现日志埋点 |
| 工具调用入参/出参 | 有开关(include-content) |
自行埋点 |
| 在非 Spring 环境 | 不适用 | OTel 标准更通用 |
这一条什么时候会变成决定项:
- 你已有 Micrometer / Actuator 体系,且是 Spring 栈 → Spring AI 省事明显,观测端点几乎是"接上就有"。
- 你已有 OTel 体系,或者不是 Spring 栈 → LangChain4j 的 OTel 路线更标准,不用把一个 Spring 专用的观测抽象硬塞进非 Spring 系统。
- 你要做的是"按业务线分摊模型成本"这类自定义指标 → 两边都要自己埋(第 2 篇 6.4 节讲过:用量埋点是唯一无法事后补齐的东西)。
一个提醒:可观测性的关键在"记什么",不在"用哪个库"。 第 5 篇 5.7 节那五个字段(身份、工具名、参数摘要、耗时、结果体积)在两条路线上是完全一样的,而且都要你自己写。 框架能给你的是"链路和耗时自动有",但"哪个工具返回了 60KB 导致成本爆炸"这类信息,只有你自己的埋点能回答。 不要因为一边有现成端点就高估这一维度的差异。
4.9 中文生态与信创适配
| 项 | Spring AI | LangChain4j |
|---|---|---|
| 通义千问接入 | OpenAI 兼容端点(第 2 篇路线 B);另有 Spring AI Alibaba 原生 DashScope 集成 | OpenAI 兼容端点同样可行;社区有 DashScope 适配 |
| 版本对齐风险 | Spring AI Alibaba 需与 Spring AI 主线版本对齐(如 1.1.2.2 对应 1.1.x) | 适配模块版本独立,但成熟度需逐个核对 |
| 完全离线(第 3 篇第三档) | 可(Ollama + 本地嵌入) | 更容易一步到位(自带 ONNX 本地嵌入) |
| 官方支持可依 | 官方 Spring 项目,企业采购/合规上更好交代 | 社区驱动 |
这一条什么时候会变成决定项:
- 你需要某个国内平台的"原生独有能力" (而不是兼容协议能提供的部分) → 必须逐个核对适配模块的实际成熟度与版本状态 ,不能只看"有人做了适配"。第 2 篇讲过"兼容模式的上限是协议,原生 SDK 的上限是平台能力",这句话在框架选型里同样成立:适配层的成熟度决定了你能不能用上那些独有能力。
- 你的合规要求包含"完全离线" → LangChain4j 自带本地嵌入让这条路径更容易闭环(但注意第 3 篇那条纪律:许可证、训练数据来源、信创名录是三个独立且无法事后补救的问题,框架帮不上忙)。
- 企业采购/合规需要"官方支持方" → Spring AI 的官方属性在这里是实打实的优势,很多企业的技术选型评审表里"是否有官方支持"是独立一栏。
一个提醒:注意"版本锁链"在 Spring AI 这边的形态。 第 2 篇讲过的锁链是 Boot ↔ Framework ↔ Spring AI ↔ Alibaba 四条线联动。在选型阶段要把它算进来:Spring AI 2.0 刚 GA 不久(2026-06),生态适配通常滞后于主线 ,所以"升到 2.0"和"继续用国内平台原生集成"这两个目标在时间上可能不重合。 这不是缺陷,是官方系框架的普遍规律------但它会实实在在影响你的上线时间表。
4.10 文档、社区与生态规模
| 项 | Spring AI | LangChain4j |
|---|---|---|
| 起点 | Spring 官方项目,与 Spring 文档体系一致 | 社区驱动,2023 年初起步,更早 |
| 集成数量 | 主流覆盖完整 | 模型与向量库集成数量最多 |
| 文档风格 | 与 Spring 官方文档同构,有体系 | 教程丰富,但成熟度不齐(部分模块 beta) |
| 遇到问题时 | 多在 Spring 体系内可找到答案 | 更多依赖社区、GitHub issue 与源码 |
这一条什么时候会变成决定项:
- 团队需要"出问题能有人问/有体系可查" → Spring AI 的文档体系与官方属性是优势。
- 需要某个小众模型或向量库的现成集成 → LangChain4j 的集成广度是优势,但要核对那个具体模块的版本状态。
一个提醒,也是本文我最想说的一句反对意见:不要用 Star 数或集成数量代替架构判断。 Star 反映关注度,它不回答"它对我现有的运维体系提什么要求" ;集成数量是"清单长度",它不回答"我要用的那一个成熟不成熟"。 我在 1.1 节把这两种选法列成错误选法,原因就是它们用"容易获得的数字"替换了"真正难的那个判断"。
5️⃣ 选型决策树、迁移成本与混合策略
5.1 选型决策树
#mermaid-svg-zP4v9v8BpCrPhx5A{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-zP4v9v8BpCrPhx5A .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zP4v9v8BpCrPhx5A .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zP4v9v8BpCrPhx5A .error-icon{fill:#552222;}#mermaid-svg-zP4v9v8BpCrPhx5A .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zP4v9v8BpCrPhx5A .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zP4v9v8BpCrPhx5A .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zP4v9v8BpCrPhx5A .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zP4v9v8BpCrPhx5A .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zP4v9v8BpCrPhx5A .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zP4v9v8BpCrPhx5A .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zP4v9v8BpCrPhx5A .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zP4v9v8BpCrPhx5A .marker.cross{stroke:#333333;}#mermaid-svg-zP4v9v8BpCrPhx5A svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zP4v9v8BpCrPhx5A p{margin:0;}#mermaid-svg-zP4v9v8BpCrPhx5A .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zP4v9v8BpCrPhx5A .cluster-label text{fill:#333;}#mermaid-svg-zP4v9v8BpCrPhx5A .cluster-label span{color:#333;}#mermaid-svg-zP4v9v8BpCrPhx5A .cluster-label span p{background-color:transparent;}#mermaid-svg-zP4v9v8BpCrPhx5A .label text,#mermaid-svg-zP4v9v8BpCrPhx5A span{fill:#333;color:#333;}#mermaid-svg-zP4v9v8BpCrPhx5A .node rect,#mermaid-svg-zP4v9v8BpCrPhx5A .node circle,#mermaid-svg-zP4v9v8BpCrPhx5A .node ellipse,#mermaid-svg-zP4v9v8BpCrPhx5A .node polygon,#mermaid-svg-zP4v9v8BpCrPhx5A .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zP4v9v8BpCrPhx5A .rough-node .label text,#mermaid-svg-zP4v9v8BpCrPhx5A .node .label text,#mermaid-svg-zP4v9v8BpCrPhx5A .image-shape .label,#mermaid-svg-zP4v9v8BpCrPhx5A .icon-shape .label{text-anchor:middle;}#mermaid-svg-zP4v9v8BpCrPhx5A .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zP4v9v8BpCrPhx5A .rough-node .label,#mermaid-svg-zP4v9v8BpCrPhx5A .node .label,#mermaid-svg-zP4v9v8BpCrPhx5A .image-shape .label,#mermaid-svg-zP4v9v8BpCrPhx5A .icon-shape .label{text-align:center;}#mermaid-svg-zP4v9v8BpCrPhx5A .node.clickable{cursor:pointer;}#mermaid-svg-zP4v9v8BpCrPhx5A .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zP4v9v8BpCrPhx5A .arrowheadPath{fill:#333333;}#mermaid-svg-zP4v9v8BpCrPhx5A .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zP4v9v8BpCrPhx5A .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zP4v9v8BpCrPhx5A .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zP4v9v8BpCrPhx5A .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zP4v9v8BpCrPhx5A .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zP4v9v8BpCrPhx5A .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zP4v9v8BpCrPhx5A .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zP4v9v8BpCrPhx5A .cluster text{fill:#333;}#mermaid-svg-zP4v9v8BpCrPhx5A .cluster span{color:#333;}#mermaid-svg-zP4v9v8BpCrPhx5A 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-zP4v9v8BpCrPhx5A .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zP4v9v8BpCrPhx5A rect.text{fill:none;stroke-width:0;}#mermaid-svg-zP4v9v8BpCrPhx5A .icon-shape,#mermaid-svg-zP4v9v8BpCrPhx5A .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zP4v9v8BpCrPhx5A .icon-shape p,#mermaid-svg-zP4v9v8BpCrPhx5A .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zP4v9v8BpCrPhx5A .icon-shape .label rect,#mermaid-svg-zP4v9v8BpCrPhx5A .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zP4v9v8BpCrPhx5A .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zP4v9v8BpCrPhx5A .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zP4v9v8BpCrPhx5A :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否:Quarkus / Micronaut / 纯 Java
是
是
否:还在 Boot 3.5 或更低
能,且平台团队有计划
不能 / 不想动平台
有
没有
有,且愿意用实验性原语
没有,就是工具调用 + 单 Agent
要求
不要求
大额间歇(平台级升级)
小额持续(季度跟依赖)
Q1:项目是 Spring Boot 技术栈吗?
→ LangChain4j
(这一条直接决定,无需再看后面)
Q2:现在已在 Boot 4.x + JDK 21 吗?
Q3:有非主流向量库 / 混合检索 /
完全离线嵌入需求吗?
W1:半年内能完成 Boot 4 升级吗?
→ LangChain4j
(避免把加 AI 变成升平台)
→ 倾向 LangChain4j
(先去核对那个具体模块的成熟度)
Q4:有多步编排 + 失败补偿 /
多 Agent 协作需求吗?
→ 倾向 LangChain4j
(但其原语成熟度必须逐个确认)
Q5:企业的技术评审
要求官方支持方吗?
→ Spring AI
(官方属性在评审表上是硬项)
Q6:团队更接受哪种投入形态?
→ Spring AI
→ LangChain4j
(图 4:选型决策树------Q1 与 Q2 是门槛问题(直接决定),Q3 之后才是偏好问题)
这张决策树的设计意图很明确:Q1 和 Q2 是"门槛问题",Q3 之后才是"偏好问题"。
为什么把"是不是 Spring 栈"放在第一个问题? 因为它根本不是一个偏好问题,而是一个成本问题 :在 Quarkus 项目里用 Spring AI 需要引入整个 Spring 上下文,这不是"有点麻烦",而是"引入了一个平行的框架体系"。这一条不该和其他维度混在一起权衡。
为什么把"你现在是不是 Boot 4"放在第二个? 因为它是本项目里最容易被忽视、影响面最大的一条 。注意决策树里 W1 那个分支的问法:"能完成升级吗" 后面跟的是 "且平台团队有计划" ------这两个条件是 AND 关系。 我见过团队回答"能升",但实际是"如果我们自己动手,理论上能升",而平台团队的时间表里根本没这件事。这种情况下,"能升"是一个假答案。
5.2 迁移成本:如果选错了,换回来要付多少
这一节的目的是把"选错了也没关系"这句话变得可量化。 下面是双向迁移的成本分解(以中型项目、约 20 个接口、含 RAG 与工具调用为例):
#mermaid-svg-dmTFO3M7EqiXlf46{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-dmTFO3M7EqiXlf46 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dmTFO3M7EqiXlf46 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dmTFO3M7EqiXlf46 .error-icon{fill:#552222;}#mermaid-svg-dmTFO3M7EqiXlf46 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dmTFO3M7EqiXlf46 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dmTFO3M7EqiXlf46 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dmTFO3M7EqiXlf46 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dmTFO3M7EqiXlf46 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dmTFO3M7EqiXlf46 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dmTFO3M7EqiXlf46 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dmTFO3M7EqiXlf46 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dmTFO3M7EqiXlf46 .marker.cross{stroke:#333333;}#mermaid-svg-dmTFO3M7EqiXlf46 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dmTFO3M7EqiXlf46 p{margin:0;}#mermaid-svg-dmTFO3M7EqiXlf46 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dmTFO3M7EqiXlf46 .cluster-label text{fill:#333;}#mermaid-svg-dmTFO3M7EqiXlf46 .cluster-label span{color:#333;}#mermaid-svg-dmTFO3M7EqiXlf46 .cluster-label span p{background-color:transparent;}#mermaid-svg-dmTFO3M7EqiXlf46 .label text,#mermaid-svg-dmTFO3M7EqiXlf46 span{fill:#333;color:#333;}#mermaid-svg-dmTFO3M7EqiXlf46 .node rect,#mermaid-svg-dmTFO3M7EqiXlf46 .node circle,#mermaid-svg-dmTFO3M7EqiXlf46 .node ellipse,#mermaid-svg-dmTFO3M7EqiXlf46 .node polygon,#mermaid-svg-dmTFO3M7EqiXlf46 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dmTFO3M7EqiXlf46 .rough-node .label text,#mermaid-svg-dmTFO3M7EqiXlf46 .node .label text,#mermaid-svg-dmTFO3M7EqiXlf46 .image-shape .label,#mermaid-svg-dmTFO3M7EqiXlf46 .icon-shape .label{text-anchor:middle;}#mermaid-svg-dmTFO3M7EqiXlf46 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dmTFO3M7EqiXlf46 .rough-node .label,#mermaid-svg-dmTFO3M7EqiXlf46 .node .label,#mermaid-svg-dmTFO3M7EqiXlf46 .image-shape .label,#mermaid-svg-dmTFO3M7EqiXlf46 .icon-shape .label{text-align:center;}#mermaid-svg-dmTFO3M7EqiXlf46 .node.clickable{cursor:pointer;}#mermaid-svg-dmTFO3M7EqiXlf46 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dmTFO3M7EqiXlf46 .arrowheadPath{fill:#333333;}#mermaid-svg-dmTFO3M7EqiXlf46 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dmTFO3M7EqiXlf46 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dmTFO3M7EqiXlf46 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dmTFO3M7EqiXlf46 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dmTFO3M7EqiXlf46 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dmTFO3M7EqiXlf46 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dmTFO3M7EqiXlf46 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dmTFO3M7EqiXlf46 .cluster text{fill:#333;}#mermaid-svg-dmTFO3M7EqiXlf46 .cluster span{color:#333;}#mermaid-svg-dmTFO3M7EqiXlf46 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-dmTFO3M7EqiXlf46 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dmTFO3M7EqiXlf46 rect.text{fill:none;stroke-width:0;}#mermaid-svg-dmTFO3M7EqiXlf46 .icon-shape,#mermaid-svg-dmTFO3M7EqiXlf46 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dmTFO3M7EqiXlf46 .icon-shape p,#mermaid-svg-dmTFO3M7EqiXlf46 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dmTFO3M7EqiXlf46 .icon-shape .label rect,#mermaid-svg-dmTFO3M7EqiXlf46 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dmTFO3M7EqiXlf46 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dmTFO3M7EqiXlf46 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dmTFO3M7EqiXlf46 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 高成本(工作量大且有隐性坑)
装配与执行链路
Advisor 链 ↔ AiServices 装配
顺序语义要重新确认
测试体系重建
选择测试 / 契约测试
断言方式可能全变
回归与标定
工具选择准确率要重新测
分块与阈值可能要重标
中等成本(要重写但要按原逻辑)
工具方法定义
注解换名(@ToolParam ↔ @P)
ToolContext ↔ 参数注入
向量库过滤表达式
两边语法不通用
观测埋点
Micrometer ↔ OTel/Tracer
几乎零成本(可整体复用)
提示词与系统约束
· 系统提示词
· 工具描述
· RAG 提示词模板
业务逻辑
· Service / DAO / SQL
· 权限模型
· 幂等设计
结论:资产可复用,代码基本重写
量级:中型项目约 3~6 人周
其中约一半花在测试重建与回归标定
(图 5:迁移成本的三层分解------提示词与业务逻辑可整体复用,装配与测试体系必须重建)
这张图最重要的信息在 M1 那一块:提示词资产和业务逻辑(Service / SQL / 权限模型 / 幂等设计)是可以整体搬走的。
这意味着两件事。 第一,如果你已经按本系列的方法做了工程化(提示词独立管理、工具描述四段式、权限走可信旁路、幂等键与服务端生成),那么换框架的代价主要在"装配层" ------因为那些真正难的东西都是框架无关的。第二,反过来推,选型的紧迫性可以降低一档 :不要因为"怕选错"而迟迟不开始 ,因为你在这期间积累的提示词、工具设计、权限模型、测试集,换框架时基本都能带走。
但 M3 里的"C2 测试体系重建"必须单独强调。 第 5 篇 5.7 节讲的那四层测试里,L2 契约测试和 L3 选择测试是与框架绑定的 ------契约测试要从 ApplicationContext 取工具的方式要改,选择测试的埋点方式要改,而且换框架后工具选择的行为可能本来就不一样,所以要重新标定基线。 我在 5.2 节给的量级"3~6 人周、一半在测试"就是从这来的:很多人估迁移成本时只估了"改代码",漏掉了"重新建立信任"这件事。

5.3 混合使用:可行,但不是免费的
"能不能两个都用,各取所长"是评审里必被问到的问题。我的回答是:可以,但要付四份代价,而且不能把它当作"不做决定"的借口。
#mermaid-svg-jIObJvQiG5KNrzmL{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-jIObJvQiG5KNrzmL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jIObJvQiG5KNrzmL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jIObJvQiG5KNrzmL .error-icon{fill:#552222;}#mermaid-svg-jIObJvQiG5KNrzmL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jIObJvQiG5KNrzmL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jIObJvQiG5KNrzmL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jIObJvQiG5KNrzmL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jIObJvQiG5KNrzmL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jIObJvQiG5KNrzmL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jIObJvQiG5KNrzmL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jIObJvQiG5KNrzmL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jIObJvQiG5KNrzmL .marker.cross{stroke:#333333;}#mermaid-svg-jIObJvQiG5KNrzmL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jIObJvQiG5KNrzmL p{margin:0;}#mermaid-svg-jIObJvQiG5KNrzmL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jIObJvQiG5KNrzmL .cluster-label text{fill:#333;}#mermaid-svg-jIObJvQiG5KNrzmL .cluster-label span{color:#333;}#mermaid-svg-jIObJvQiG5KNrzmL .cluster-label span p{background-color:transparent;}#mermaid-svg-jIObJvQiG5KNrzmL .label text,#mermaid-svg-jIObJvQiG5KNrzmL span{fill:#333;color:#333;}#mermaid-svg-jIObJvQiG5KNrzmL .node rect,#mermaid-svg-jIObJvQiG5KNrzmL .node circle,#mermaid-svg-jIObJvQiG5KNrzmL .node ellipse,#mermaid-svg-jIObJvQiG5KNrzmL .node polygon,#mermaid-svg-jIObJvQiG5KNrzmL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jIObJvQiG5KNrzmL .rough-node .label text,#mermaid-svg-jIObJvQiG5KNrzmL .node .label text,#mermaid-svg-jIObJvQiG5KNrzmL .image-shape .label,#mermaid-svg-jIObJvQiG5KNrzmL .icon-shape .label{text-anchor:middle;}#mermaid-svg-jIObJvQiG5KNrzmL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jIObJvQiG5KNrzmL .rough-node .label,#mermaid-svg-jIObJvQiG5KNrzmL .node .label,#mermaid-svg-jIObJvQiG5KNrzmL .image-shape .label,#mermaid-svg-jIObJvQiG5KNrzmL .icon-shape .label{text-align:center;}#mermaid-svg-jIObJvQiG5KNrzmL .node.clickable{cursor:pointer;}#mermaid-svg-jIObJvQiG5KNrzmL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jIObJvQiG5KNrzmL .arrowheadPath{fill:#333333;}#mermaid-svg-jIObJvQiG5KNrzmL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jIObJvQiG5KNrzmL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jIObJvQiG5KNrzmL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jIObJvQiG5KNrzmL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jIObJvQiG5KNrzmL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jIObJvQiG5KNrzmL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jIObJvQiG5KNrzmL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jIObJvQiG5KNrzmL .cluster text{fill:#333;}#mermaid-svg-jIObJvQiG5KNrzmL .cluster span{color:#333;}#mermaid-svg-jIObJvQiG5KNrzmL 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-jIObJvQiG5KNrzmL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jIObJvQiG5KNrzmL rect.text{fill:none;stroke-width:0;}#mermaid-svg-jIObJvQiG5KNrzmL .icon-shape,#mermaid-svg-jIObJvQiG5KNrzmL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jIObJvQiG5KNrzmL .icon-shape p,#mermaid-svg-jIObJvQiG5KNrzmL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jIObJvQiG5KNrzmL .icon-shape .label rect,#mermaid-svg-jIObJvQiG5KNrzmL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jIObJvQiG5KNrzmL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jIObJvQiG5KNrzmL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jIObJvQiG5KNrzmL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 可行的混合形态:明确边界 + 同一服务 + 同一批人
边界清晰:
只在检索这一层调用
Spring AI 承载主链路
聊天 / 工具 / RAG / 记忆
LangChain4j 的某个模块补特殊能力
(如特定向量库的混合检索 API)
代价一:依赖体积
两套模型客户端 + 两套向量库抽象
代价二:配置双份
两套 Key / 超时 / 重试策略
且有『两套配置不一致』的风险
代价三:观测双体系
Micrometer 与 OTel 的指标要各自打通
代价四:团队心智双份
新人要学两套概念(且同名不同义,见 2.4 节)
结论:
混合是『为了某个具体能力』的工程决策
不是『两个都挺好』的逃避
边界必须写下来,且最好限定在一个模块内
(图 6:混合方案的可行形态与四份代价------混合是为某个具体能力的工程决策,不是"两个都挺好"的逃避)
这张图右侧那四份代价里,我认为真正会伤人的是"代价二"和"代价四"。
代价二的典型症状是"两套配置不一致" ------比如超时,一边配 30 秒、一边配 60 秒,结果同一类请求在不同路径上的表现不一样,而排查时你甚至不会想到"这是两条路径"。 这类问题在混合方案里几乎必然出现,因为它不产生编译错误、也不产生明显告警。
代价四则是长期的 :第 2.4 节那张术语对照表的存在本身就说明问题------ChatMemory、ChatModel、@Tool 这些同名不同义的词,会让新人在两个框架间反复串线。 一个团队同时维护两套 AI 抽象,知识是不会自动合并的。
所以我的建议是:如果一定要混合,就用"主 + 一点"的形态------主链路完整地走一个框架,另一个框架只在一个明确、狭窄、有注释说明的边界上补能力,并且这个边界要写进架构文档。 反过来,"两个都用"如果是因为"评审时决定不了",那就是把决策成本转嫁成了长期的维护成本。
5.4 三年总成本模型
把前面所有维度收成一张能算账的表。注意这里的量级是"相对"的,用来做比较而不是做预算:
| 成本项 | Spring AI | LangChain4j | 主导因素 |
|---|---|---|---|
| 初始接入 | 低(若是 Boot 4 + JDK 21) | 中(Spring 侧便利要自己接) | 你的起点 |
| 初始学习 | 中(Advisor 顺序语义) | 中(接口 → 实现的生成规则) | 团队背景 |
| 平台升级 | 高(Boot + Framework 一起动) | 无此项(不绑框架大版本) | 是否有平台团队 |
| 版本跟随 | 低(大版本间隔以年计) | 中高(约季度一次) | 团队是否接受小额持续投入 |
| 生态适配等待 | 有(2.0 刚 GA,适配滞后) | 低(适配模块独立迭代) | 你的上线时间表 |
| 迁移机会成本 | 选错后约 3~6 人周 | 同量级(反向) | 测试体系重建 |
| 三年主导项 | 平台升级(一次大额) | 版本跟随(多次小额) | --- |
这张表的结论是一句话:三年成本的主导项不是"初始接入",而是"你更愿意付一次大额、还是多次小额"。
之所以要强调这一点,是因为大多数团队的选型讨论把 90% 的时间花在"初始接入谁更省事"上 ,而那一项在三年账里往往是最小的一块。"加 AI 这件事本身要多久"是短期问题;"三年里你要为跟随它花多少人天、以及这些时间能不能自主安排"才是长期问题。
6️⃣ 适用边界与风险提示
6.1 五个必须避开的选型风险
#mermaid-svg-WXhSsPhuSfI4XwIQ{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-WXhSsPhuSfI4XwIQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WXhSsPhuSfI4XwIQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WXhSsPhuSfI4XwIQ .error-icon{fill:#552222;}#mermaid-svg-WXhSsPhuSfI4XwIQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WXhSsPhuSfI4XwIQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WXhSsPhuSfI4XwIQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WXhSsPhuSfI4XwIQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WXhSsPhuSfI4XwIQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WXhSsPhuSfI4XwIQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WXhSsPhuSfI4XwIQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WXhSsPhuSfI4XwIQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WXhSsPhuSfI4XwIQ .marker.cross{stroke:#333333;}#mermaid-svg-WXhSsPhuSfI4XwIQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WXhSsPhuSfI4XwIQ p{margin:0;}#mermaid-svg-WXhSsPhuSfI4XwIQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WXhSsPhuSfI4XwIQ .cluster-label text{fill:#333;}#mermaid-svg-WXhSsPhuSfI4XwIQ .cluster-label span{color:#333;}#mermaid-svg-WXhSsPhuSfI4XwIQ .cluster-label span p{background-color:transparent;}#mermaid-svg-WXhSsPhuSfI4XwIQ .label text,#mermaid-svg-WXhSsPhuSfI4XwIQ span{fill:#333;color:#333;}#mermaid-svg-WXhSsPhuSfI4XwIQ .node rect,#mermaid-svg-WXhSsPhuSfI4XwIQ .node circle,#mermaid-svg-WXhSsPhuSfI4XwIQ .node ellipse,#mermaid-svg-WXhSsPhuSfI4XwIQ .node polygon,#mermaid-svg-WXhSsPhuSfI4XwIQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WXhSsPhuSfI4XwIQ .rough-node .label text,#mermaid-svg-WXhSsPhuSfI4XwIQ .node .label text,#mermaid-svg-WXhSsPhuSfI4XwIQ .image-shape .label,#mermaid-svg-WXhSsPhuSfI4XwIQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-WXhSsPhuSfI4XwIQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WXhSsPhuSfI4XwIQ .rough-node .label,#mermaid-svg-WXhSsPhuSfI4XwIQ .node .label,#mermaid-svg-WXhSsPhuSfI4XwIQ .image-shape .label,#mermaid-svg-WXhSsPhuSfI4XwIQ .icon-shape .label{text-align:center;}#mermaid-svg-WXhSsPhuSfI4XwIQ .node.clickable{cursor:pointer;}#mermaid-svg-WXhSsPhuSfI4XwIQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WXhSsPhuSfI4XwIQ .arrowheadPath{fill:#333333;}#mermaid-svg-WXhSsPhuSfI4XwIQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WXhSsPhuSfI4XwIQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WXhSsPhuSfI4XwIQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WXhSsPhuSfI4XwIQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WXhSsPhuSfI4XwIQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WXhSsPhuSfI4XwIQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WXhSsPhuSfI4XwIQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WXhSsPhuSfI4XwIQ .cluster text{fill:#333;}#mermaid-svg-WXhSsPhuSfI4XwIQ .cluster span{color:#333;}#mermaid-svg-WXhSsPhuSfI4XwIQ 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-WXhSsPhuSfI4XwIQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WXhSsPhuSfI4XwIQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-WXhSsPhuSfI4XwIQ .icon-shape,#mermaid-svg-WXhSsPhuSfI4XwIQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WXhSsPhuSfI4XwIQ .icon-shape p,#mermaid-svg-WXhSsPhuSfI4XwIQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WXhSsPhuSfI4XwIQ .icon-shape .label rect,#mermaid-svg-WXhSsPhuSfI4XwIQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WXhSsPhuSfI4XwIQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WXhSsPhuSfI4XwIQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WXhSsPhuSfI4XwIQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 风险一:用 Star 数 / 集成数量
代替架构判断
共同后果:
决策看起来有依据,
但依据回答的不是真问题
风险二:把「加 AI」做成「升平台」
而不自知(1.1 节真实案例)
风险三:低估版本节奏的持续成本
只算初始接入的人天
风险四:为「万一要用」的宽覆盖买单
(支持 30+ 模型,你只用 2 个)
风险五:项目中期换框架
缓解方式:
把选型依据写成「约束清单」
(JDK / Boot / 并发模型 / 向量库 /
官方支持 / 升级窗口)
而不是「能力清单」
判断标准:
如果一份选型文档里全是「支持 XX」
而没有「要求 YY」,它就没做完
(图 7:五个选型风险与共同的缓解方式------把选型依据写成"约束清单"而不是"能力清单")
这五个风险里,我认为"风险二"和"风险五"最需要展开。
关于风险二 :1.1 节那个例子的教训是------"加 AI"和"升平台"是两个应该分别决策的动作 。规避方法很具体:在做 AI 选型时,先写下"当前运行时是什么"(JDK / Boot / Framework / 并发模型 / 部署形态),再逐条对比两个框架的"要求"。 如果发现任何一条要求会触发平台级动作,就把它单独拿出来评估,而不是让它混在"引入 AI 依赖"这个动作里。
关于风险五(项目中期换框架) :我的建议是不要换 ,除非满足下面两个条件之一------要么框架本身出现了阻断性问题(无法升级、关键能力缺失、项目停止维护),要么你的技术栈发生了根本变化(例如从 Boot 迁到 Quarkus)。 理由就是 5.2 节那张迁移成本图:换框架的真正成本不是改代码,而是"重新建立信任" ------四层测试要重建、工具选择准确率要重新标定、分块与阈值可能都要重调。在产品迭代期做这件事,等于把一段时间的交付能力压掉。

6.2 什么时候两个框架都不该用
这一节很短,但很实际。
场景一:你只是要"调一次模型"做一次性任务。 一个脚本、一次数据清洗、一次批量翻译------直接写 HTTP 调用就行,不要引入框架。 框架的价值在"长期维护的结构化代码",一次性任务里它只贡献依赖体积。
场景二:你的需求只是"批量离线文本分类或摘要"。 批处理场景没有会话、没有工具、没有流式、并发模型简单------一个 HTTP 客户端 + 一个信号量控制并发就够了。 第 7 篇正好会讲文本分类与摘要落地到后台工作流,那一篇里我会明确写出"哪些部分不需要框架"。
场景三:需求里没有"会话"这个维度。 框架的核心抽象(记忆、Advisor 链、AiServices 的会话装配)都是围绕会话设计的。如果你的场景是"无状态的一问一答、每次都是独立调用",那么框架带来的收益会明显下降。
场景四:团队里没有人愿意长期维护这套东西。 这一条听起来像废话,但它是最常见的事故来源 :框架是"需要跟随的资产",不是"装完就忘的依赖"。 如果组织上没有明确谁负责跟进版本、谁负责跑回归测试,那么无论选哪个框架,一年后都会变成一个不敢升、也说不清版本的模块。
6.3 生产建议清单
- 先写约束清单,再读对比表:JDK / Boot / 并发模型 / 向量库 / 官方支持 / 升级窗口,六项写完再看功能。
- 把"是否触发平台升级"单独评估,不要让它在"引入 AI 依赖"里被顺手带过。
- 确认你的向量库与模型在目标框架里的适配成熟度(含版本后缀是不是 beta),不要只看"支持"。
- 如果服务是事件循环模型(WebFlux / Vert.x / Quarkus),把非阻塞支持当硬要求,并按"迁移"对待而非"开关"。
- 确认候选框架的原语里有多少是实验性标记;核心业务不要压在实验性原语上。
- 把版本跟随写进团队职责:谁负责跟进、多久看一次、回归跑哪些用例。
- 选型文档里必须出现"要求"而不只是"支持"------没有"要求"的对比表等于没做完。
- 不要项目中期换框架;真要换,按"改代码 + 重建测试"两笔预算估。
7️⃣ 总结
7.1 回到开头那个问题
开头我说,"如果这个选择本身就是错的,前面五篇的收益都要打折"。现在可以给出结论了:这个选择不像很多人以为的那样"两个都行",但它的判据也不是功能清单,而是两条约束。
第一条是硬约束:你的运行时是什么。 不是 Spring 栈 → 直接选 LangChain4j。是 Spring 栈但在 Boot 3.5 且半年内不动平台 → Spring AI 2.0 需要 Boot 4 作为入场券,这件事必须被单独决策,否则就是 1.1 节那种"顺手把平台升了"的情况。
第二条是软约束:你更愿意付哪种代价。 Spring AI 用"平台级升级 + 强绑定"换来"深度整合的省心";LangChain4j 用"集成代码自己接 + 版本迭代更快"换来"运行时的自由"。这两者不是好坏,是两种承诺。
7.2 十个维度速查表
| 维度 | 明显偏向 Spring AI | 明显偏向 LangChain4j | 两边持平(不作为依据) |
|---|---|---|---|
| 运行时自由度 | --- | 非 Spring 栈 / Boot 3.5 不动 / 多模块单模块接入 | 已是 Boot 4 + JDK 21 |
| 版本节奏 | 有平台团队、能定期大版本升级 | 不接受升级时机被 EOL 时间表牵引 | 团队无明确偏好 |
| 代码形态 | 每场景一套能力组合 | 同一助手多入口调用、要求行为一致 | 单一入口 |
| RAG | 已用 Spring + 主流向量库 | 特定向量库混合检索 / 完全离线嵌入 | 标准向量检索 |
| 工具与 Agentic | 需要精细干预循环每一轮 | 多步编排 + 失败补偿 / 多 Agent 共享状态 | 工具调用 + 单 Agent |
| 记忆 | 需要对记忆做横切改造 | --- | 标准窗口记忆 |
| 流式与并发 | --- | 事件循环模型(WebFlux / Vert.x / Quarkus) | 传统 Tomcat 线程池 |
| 可观测性 | 已有 Micrometer / Actuator | 已有 OTel 体系 / 非 Spring 环境 | 都需自行埋业务指标 |
| 中文与信创 | 需要官方支持方、企业评审硬项 | 需要完全离线闭环 | 都用 OpenAI 兼容端点 |
| 文档与生态 | 需要成体系的文档与官方支持 | 需要某个小众集成的现成实现 | --- |
用这张表的方法是"数偏向",而不是"逐格打分" ------因为只要有一格落在"硬约束"里(比如不是 Spring 栈、或者服务是事件循环模型),它就直接决定答案,其他格子都不必看。
7.3 本系列的选择与下一步
说清我自己的选择,避免这篇文章显得"什么都对":
本系列后续文章继续走 Spring AI。 三个理由,按权重排序:一是本系列的读者画像就是 Java/Spring 后端 ,Spring AI 的 Advisor 语义、@Tool、自动配置这些内容对 Spring 工程师迁移成本最低 (第 1 篇的目标就是"Java 工程师的 AI 开发第一站");二是本系列从第 2 篇起就建立了"Spring AI 2.0.1 + Boot 4.1.x + JDK 21 + OpenAI 兼容端点"这条版本线 ,中途换框架会让前五篇的代码全部失效;三是 Spring AI 的官方属性,在企业落地与合规评审里更好交代。
但这个选择是有前提的 ------如果你的项目在 Boot 3.5 且半年内不动平台、或者不是 Spring 栈,请按 5.1 节的决策树选 LangChain4j,前面五篇里除代码外的所有内容(提示词设计、工具描述四段式、权限三层防御、幂等与重试纪律、四层测试策略)依然全部适用。 这也是 5.2 节那个结论的实际用法:可迁移的资产比框架本身值钱。
下一篇回到具体落地:用 Spring AI 做文本分类/摘要:落地到后台工作流。 那一篇会做一件前六篇都没做的事------把 AI 从"对话式交互"挪到"无人值守的批处理" 。场景变了,工程重点也完全变了:没有会话、没有流式、没有实时用户等结果,取而代之的是吞吐量、失败重试、幂等、以及"如何把不确定的输出接进一个要求确定的业务流程" 。我还会明确写出哪些部分其实不需要框架 (6.2 节的场景二),以及当模型输出被要求进入一条有状态的工作流时,那个"人怎么审"的环节该怎么设计。
如果你正在做框架选型,或者你们团队已经因为选型踩过坑,欢迎在评论区说说------尤其是"以为在加 AI,结果在升平台"这类经历,我很好奇这是不是普遍现象。 下一篇见。
参考资料
- Spring AI Reference Documentation(总入口,含 ChatClient / Advisor / Tool Calling / RAG / 观测):https://docs.spring.io/spring-ai/reference/
- Spring AI Release Notes 与版本说明(2.0 GA 与 2.0.x 补丁):https://github.com/spring-projects/spring-ai/releases
- Spring AI GitHub 仓库(提供商与向量库支持列表、升级说明):https://github.com/spring-projects/spring-ai
- Spring Boot 支持周期与 EOL 说明(Boot 3.5 / Framework 6.2 于 2026-06-30 EOL):https://spring.io/projects/spring-boot#support
- LangChain4j 官方文档(AiServices / RAG / Tools / 非阻塞教程 / Jackson 3 教程):https://docs.langchain4j.dev/
- LangChain4j 官方 Release 记录(1.19 / 1.20.x 特性与回溯维护线):https://github.com/langchain4j/langchain4j/releases
- LangChain4j 非阻塞与响应式教程(按返回类型选择同步 /
TokenStream/CompletableFuture/Flow.Publisher):https://docs.langchain4j.dev/tutorials/non-blocking/ - LangChain4j Maven Central 版本列表(用于核对当前主线与回溯线版本号):https://central.sonatype.com/artifact/dev.langchain4j/langchain4j
- MCP 规范(2026-07-28 版本,Streamable HTTP 无状态化与协议级 session 移除):https://modelcontextprotocol.io/
- 阿里云百炼 · 通义千问 OpenAI 兼容模式与 Function Calling 文档:https://help.aliyun.com/zh/model-studio/