LangChain4j vs Spring AI:Java AI 框架怎么选(对比表)

本文是《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+

这一条什么时候会变成决定项: 四种情况,满足任意一种就该把权重拉满------

  1. 你的项目不是 Spring(Quarkus / Micronaut / 纯 Java / 甚至 Android 后端)------这一条直接决定答案,无需再看其他维度。
  2. 你的服务在 Boot 3.5 或更低,且短期(半年内)没有升级窗口------这时候选 Spring AI 2.0 等于把"加 AI"和"升平台"捆在一起,而升平台通常要排期、要过评审。
  3. 大型多模块单体,只想给某一个模块加 AI------LangChain4j 的"加一个依赖"在这里的成本优势非常明显。
  4. 平台的升级窗口排不上期(架构组的时间表不由你定),但你有一个业务需求这个季度就要上线。

一个提醒:如果你已经是 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 的构建器更自然,每个场景一个 ChatClient Bean,边界清楚。
  • 团队里 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,结果在升平台"这类经历,我很好奇这是不是普遍现象。 下一篇见。


参考资料

相关推荐
悠仁さん1 小时前
【C++】set map 使用
java·前端·c++
逗溜网数智科技1 小时前
逗溜网数智科技:深耕AI应用服务,六大产品矩阵赋能产业数字化转型
人工智能·科技
第九序言T1 小时前
Python 数据处理进阶(二十一)相关性与分组对比:数据里藏着什么关系
大数据·人工智能·python·数据分析·pandas
艾莉丝努力练剑1 小时前
【AI大模型接入SDK】ChatServer问题修复
c++·学习·ai·大模型·sdk
u1301301 小时前
AI 日报(2026年11月1日)
人工智能
FL16238631291 小时前
芒果果实病害图像分类数据集862张5类别
人工智能·分类·数据挖掘
yuniko-n1 小时前
【JUC】Thread 内存泄露
java
freejackman1 小时前
【架构篇】双遍历 + 多栈协同:手撸 SQL 血缘引擎的架构思考
java·大数据·数据库·hive·sql·架构·spark
Zhao-Jichao2 小时前
AI Prompting for Everyone 课程笔记 - P06 模型提示词对比 AI Model Prompt Comparison
人工智能·笔记·prompt