Spring AI Alibaba停更后,Java AI应用开发的工程化选型与落地实践 1. 一个停更消息引发的技术选型焦虑前几天在技术群里看到有人转了一张截图说 Spring AI Alibaba 这个项目停更了紧接着群里就炸了锅。有人感叹“Java 在 AI 这条路上果然走不通”有人开始盘算着要不要转 Python还有人直接甩出一句“Java 已死有事烧纸”。我当时正在用这套东西做一个内部知识库的检索增强生成服务看到消息的第一反应是先别慌把仓库的提交记录和 issue 区翻一遍再说。翻完之后我发现事情远没有标题党说得那么夸张。这个项目确实在一段时间内没有新的提交但它的代码还在、依赖还在、已经跑通的功能也还在。更重要的是它背后依赖的那套 AI 应用开发范式——也就是 Spring 生态里对接大模型能力的整体思路——并没有因为某一个具体项目的节奏变化而消失。真正值得聊的不是“Java 还有没有希望”这种情绪化的命题而是当我们在 Java 技术栈里做 AI 应用时到底应该把哪些东西当作长期资产哪些东西只是过渡期的脚手架。这篇文章想做的事情很明确把“Spring AI Alibaba 停更”这件事拆开来看讲清楚它到底意味着什么、Java 做 AI 应用的真实能力边界在哪里、以及如果你正在或者准备用 Java 做 AI 相关开发接下来应该怎么调整自己的技术选型和工程结构。不管你是刚接触 AI 应用开发的 Java 后端还是已经在项目里用上了大模型能力的架构师都能从下面这些内容里找到可以直接参考的判断依据和实操方案。2. 停更不等于失效先把事实和情绪分开2.1 一个开源项目的生命周期到底怎么看很多人看到“停更”两个字脑子里自动翻译成“这个项目废了”。但在开源世界里一个项目的状态其实有好几种完全不同的情况。第一种是活跃维护有固定的提交节奏、issue 响应及时、版本按计划发布。第二种是维护模式不再加新功能但关键 bug 还会修安全漏洞还会补。第三种是归档冻结代码不再变动但仓库保留任何人都可以 fork 继续做。第四种才是真正意义上的废弃连代码都找不到了。Spring AI Alibaba 当时的状态更接近第二种和第三种之间。它的核心能力——比如对接通义系列模型、提供 Spring 风格的 ChatClient 和 EmbeddingClient 抽象、支持向量库集成——这些代码都是完整可用的。你拿它来做一个对话服务或者文档检索服务跑起来没有任何问题。问题在于如果它不再跟进上游 Spring AI 的接口变化也不再加新的模型适配那你在做长期项目时就需要考虑后续的维护成本。这里有一个很关键的判断逻辑你依赖的是一个项目的“能力”还是“更新”。如果你只是需要它现在已经提供的功能那停更对你的影响很小因为代码不会自己坏掉。但如果你需要它持续跟进新模型、新协议、新标准那停更就是一个明确的信号说明你需要在架构上留出替换空间。2.2 Java 做 AI 应用的真实短板在哪里群里那些说“Java 没希望”的人其实大部分并没有真正用 Java 做过完整的 AI 应用。他们说的“没希望”往往指的是“不能用 Java 训练大模型”。这个判断本身没错但问题在于绝大多数做 AI 应用的人根本不需要训练模型。训练大模型是另一回事那是需要几千张加速卡、几百人团队、几个月时间才能做的事情。而 AI 应用开发的核心工作是调用模型接口、组织提示词、管理对话上下文、做检索增强、处理结构化输出、集成到业务系统里。这些事情的本质是工程问题不是算法问题。而在工程问题上Java 的优势非常明显强类型、成熟的依赖注入、完善的事务管理、丰富的企业级集成经验、庞大的现有系统存量。Java 真正的短板其实在另外几个地方。第一是 Python 生态里那些实验性的 AI 工具链更新太快Java 这边跟进总是慢半拍。第二是很多模型提供方的官方 SDK 优先支持 PythonJava 版本要么没有要么功能不全。第三是在数据处理和科学计算场景下Python 的库确实更顺手。但这些短板并不影响 Java 作为 AI 应用“后端服务层”的定位。你完全可以用 Python 做数据处理和模型微调用 Java 做面向业务的服务层两者通过接口或者消息队列协作。2.3 从“框架依赖”转向“协议依赖”的思路转变这次停更事件给我最大的启发是让我重新思考了 AI 应用架构里的依赖关系。以前我会很自然地引入某个框架然后用它的抽象来写业务代码。比如用某个 ChatClient 来发请求用某个 EmbeddingClient 来做向量化。这样做的好处是写起来快但坏处是一旦这个框架不维护了你的业务代码就跟它绑死了。更稳妥的做法是把依赖层次往下压一层。不要依赖某个具体框架的客户端抽象而是依赖模型服务本身的 HTTP 接口协议。现在主流的大模型服务基本都提供兼容 OpenAI 格式的接口这个格式已经成了事实上的行业标准。你只要按照这个格式发 HTTP 请求不管后面换哪个模型、哪个供应商你的代码都不用大改。这个思路转变带来的直接好处是框架变成了可替换的“便利层”而不是不可替代的“基础设施”。你可以继续用 Spring AI 或者任何其他封装来简化开发但心里要清楚真正让系统跑起来的是那些 HTTP 接口和 JSON 结构不是某个特定的注解或者接口名。3. Java AI 应用的核心能力拆解3.1 对话服务从接口调用到上下文管理用 Java 做一个对话服务最基础的部分就是发 HTTP 请求调用模型接口。这部分用 Java 的 HttpClient 或者 WebClient 都能做没有什么技术难度。真正需要花心思的是上下文管理。大模型本身是无状态的它不记得你上一句说了什么。所谓的“多轮对话”其实是每次请求时把之前的对话历史一起发过去。这就带来几个工程问题历史消息怎么存、存多久、超长了怎么截断、不同用户的会话怎么隔离。我在实际项目里的做法是会话历史存在 Redis 里用用户 ID 加会话 ID 作为 key设置一个合理的过期时间。每次请求时取出最近 N 条消息加上系统提示词和当前用户输入组装成完整的消息列表发给模型。N 的取值需要根据模型的上下文窗口大小和你的业务场景来定。比如模型支持 8K 上下文系统提示词占了 500 token那留给历史消息的空间大概就是 7000 token 左右按每条消息平均 100 token 算大概能放 70 条。但实际不会放这么多因为历史太长会让模型注意力分散通常保留最近 10 到 20 轮就够了。这里有一个容易踩的坑不要把整个对话历史原封不动地一直往后传。我见过有人为了“让模型记住更多”把几百条历史消息全塞进去结果不仅响应变慢、成本飙升模型的表现反而下降了。正确的做法是做一层摘要或者关键信息提取把久远的历史压缩成一段简短的背景描述。3.2 检索增强向量化与相似度检索的工程实现检索增强生成是目前 Java AI 应用里最有实用价值的方向之一。它的核心思路是把文档切块、向量化、存进向量库用户提问时先检索出最相关的几个文档块把它们作为上下文一起发给模型让模型基于这些内容来回答。在 Java 里做这件事关键环节有三个。第一个是文档切块。切块的大小和重叠度直接影响检索效果。切太大一个块里混了多个主题检索出来噪声多切太小信息不完整模型拿到的上下文不够。我的经验是中文文档按 300 到 500 字一块重叠 50 到 100 字这样既能保证语义完整又不会引入太多无关内容。第二个是向量化。你可以调用模型服务提供的 embedding 接口也可以用本地的向量化模型。调用接口的好处是简单、效果好坏处是每次都要发网络请求大批量文档处理时比较慢。本地模型的好处是快、免费、数据不出本地坏处是需要额外的部署和资源。我一般会在文档量不大的时候直接用接口文档量超过几万条时考虑本地部署。第三个是向量检索。Java 这边可以用的向量库有 Milvus、Weaviate、Qdrant 等它们都提供 Java 客户端。如果数据量不大用 Redis 的向量检索功能也够用。检索时要注意相似度阈值的设置太低会召回不相关的内容太高会漏掉有用的信息。我通常会把阈值设在 0.7 左右然后取 top 5 的结果再根据实际效果微调。3.3 结构化输出让模型返回可解析的数据很多业务场景需要模型返回结构化的数据比如从一段文本里提取姓名、电话、地址或者把自然语言转换成 SQL 查询。这时候就需要用到结构化输出能力。最直接的做法是在提示词里明确要求模型返回 JSON 格式然后在 Java 侧用 Jackson 或者 Gson 解析。但这样做有一个问题模型有时候会不听话返回的 JSON 格式不对或者多加了说明文字导致解析失败。更稳妥的做法是使用模型服务提供的结构化输出功能。现在很多模型都支持指定输出格式比如要求返回符合某个 JSON Schema 的数据。这样模型会在生成时保证格式正确你拿到之后直接反序列化就行。如果模型不支持这个功能那就需要在提示词里把格式要求写得非常明确并且在解析失败时做重试或者降级处理。我在项目里封装了一个通用的结构化输出工具类核心逻辑是先尝试直接解析失败后把错误信息拼回提示词让模型重新生成最多重试两次。这个方案在实际使用中能把解析成功率从 85% 左右提升到 99% 以上。4. 工程化落地的关键环节4.1 模型调用的超时、重试与降级策略调用外部模型服务网络抖动和服务波动是常态。如果不做任何保护一个超时就可能让整个请求链路卡住。我在项目里对模型调用做了三层保护。第一层是超时控制。连接超时设 5 秒读取超时设 60 秒。读取超时之所以设这么长是因为大模型生成一段几百字的回复确实需要几十秒设太短会导致正常请求被误杀。但也不能不设否则遇到服务端 hang 住的情况线程会被一直占用。第二层是重试。对于超时和 5xx 错误做最多两次重试每次间隔 1 秒。注意不要对 4xx 错误重试那通常是请求本身有问题重试也没用。重试时要确保请求是幂等的对于对话生成这种场景重试意味着可能生成两次需要在业务层做去重或者接受这种不确定性。第三层是降级。当模型服务完全不可用时不能直接给用户报错。我的做法是准备一个兜底回复比如“当前服务繁忙请稍后再试”同时把请求记录到队列里等服务恢复后异步处理。对于非实时场景比如后台的文档摘要生成降级策略就是直接跳过等下一轮再处理。4.2 提示词模板的管理与版本控制提示词是 AI 应用里最容易被忽视但又极其重要的部分。很多人把提示词硬编码在 Java 代码里改一次就要重新编译部署非常不方便。更好的做法是把提示词抽出来作为配置或者模板文件管理。我一般会在项目里建一个 prompts 目录每个提示词一个文件用模板引擎比如 FreeMarker 或者 StringTemplate来渲染变量。文件名带上版本号比如document-summary-v2.ftl。这样修改提示词不需要改代码只需要改文件重启服务或者刷新配置就能生效。更进一步的做法是把提示词也纳入版本控制每次修改都记录变更原因和效果对比。因为提示词的改动对输出质量的影响非常大有时候改一个词就能让效果天差地别。没有版本记录的话出了问题很难回溯。4.3 成本控制token 计量与缓存策略用大模型是要花钱的尤其是当你的应用有一定用户量之后token 消耗会非常可观。我在项目里做了两件事来控制成本。第一件是 token 计量。每次调用模型后记录输入 token 数、输出 token 数和对应的费用。这些数据按用户、按接口、按天汇总方便分析成本构成。很多模型服务的响应里会带 token 使用信息直接取来用就行。如果没有就需要自己估算中文大概一个字对应 1.5 到 2 个 token。第二件是缓存。对于相同或者相似的请求如果之前已经生成过结果直接返回缓存不再调用模型。缓存的 key 可以用请求内容的哈希值缓存的有效期根据业务场景来定。比如 FAQ 类的问答缓存可以设长一点实时性要求高的场景缓存就设短一点或者不用。我在一个内部知识库项目里加了缓存之后模型调用量下降了大约 40%效果非常明显。5. 常见问题与排查实录5.1 模型返回内容不完整或截断怎么办这个问题通常有两个原因。第一个是 max tokens 设置得太小模型生成到一半就被截断了。解决方法是根据业务需要调大 max tokens但要注意模型的上下文窗口上限输入加输出的总 token 数不能超过这个上限。第二个原因是网络传输中断尤其是在流式输出的场景下。这时候需要在客户端做好拼接和校验发现内容不完整时重新请求。5.2 向量检索召回率低怎么优化召回率低一般从三个方向排查。首先是切块策略检查块的大小和重叠度是否合理可以尝试不同的参数组合看效果。其次是 embedding 模型的选择不同模型对中文语义的理解能力差异很大可以换一个模型试试。最后是检索参数调整相似度阈值和 top K 的值有时候放宽阈值能召回更多相关内容。5.3 并发请求下响应变慢的排查思路并发变慢通常不是模型本身的问题而是你的服务端有瓶颈。按这个顺序排查先看线程池是不是太小模型调用是阻塞操作需要足够的线程来支撑并发再看连接池配置HTTP 连接复用没做好会导致频繁建连然后看 Redis 或者数据库有没有慢查询最后看模型服务端是不是有速率限制触发了限流会导致请求排队。问题现象可能原因排查方法解决方向返回内容截断max tokens 太小检查请求参数调大 max tokens检索结果不相关切块太大或阈值太低查看召回内容调整切块和阈值并发时响应慢线程池或连接池不足查看线程和连接状态扩容线程池和连接池解析 JSON 失败模型输出格式不稳定打印原始返回内容使用结构化输出或加重试成本增长过快无缓存或历史消息过长统计 token 消耗加缓存、压缩历史5.4 几个我踩过的坑和对应的解法第一个坑是在循环里调用模型。有一次我写了一个批量处理文档的逻辑在 for 循环里逐条调用模型做摘要结果几千个文档跑了几个小时还触发了限流。后来改成批量提交加异步处理速度提升了十几倍。第二个坑是忽略字符编码。模型返回的中文内容在某些情况下会出现乱码排查后发现是 HTTP 响应解析时没有指定 UTF-8 编码。这个问题在 Windows 环境下特别容易出现加上编码指定后就正常了。第三个坑是提示词里带了太多示例。为了让模型理解任务我在提示词里放了十几个示例结果输入 token 暴涨成本翻了好几倍效果却没有明显提升。后来精简到三个典型示例效果基本持平成本降了一半以上。6. 技术选型的长期判断回到最开始那个问题Java 还有希望吗我的判断是Java 在 AI 应用开发这个层面的希望从来就没有消失过只是很多人把“AI 开发”和“模型训练”混为一谈了。模型训练确实是 Python 的天下但 AI 应用开发是一个工程问题而工程问题恰恰是 Java 最擅长的领域。Spring AI Alibaba 的停更与其说是一个技术路线的失败不如说是一个提醒不要把业务架构建立在某一个具体框架的持续维护上。你应该依赖的是模型服务的接口协议、是向量检索的数学原理、是工程化的超时重试降级策略这些东西不会因为某个项目的停更而失效。如果你现在正在用 Java 做 AI 应用我的建议是保持现有的框架封装但把核心的模型调用逻辑抽出来做成不依赖特定框架的独立模块。这样即使将来换框架你的业务代码也不用动。如果你还在犹豫要不要用 Java 做 AI 应用我的建议是先想清楚你的场景是工程问题还是算法问题如果是工程问题Java 依然是那个最稳妥的选择。最后分享一个我在实际项目里验证过的做法把模型调用封装成一个独立的内部服务对外提供 HTTP 接口业务系统通过这个接口来使用 AI 能力。这样做的好处是模型相关的依赖、配置、密钥都集中在一个地方管理业务系统不需要关心用的是哪个框架、哪个模型。将来要换模型或者换框架只需要改这一个服务所有业务系统都不用动。这个架构思路在 Spring AI Alibaba 停更的消息出来之后让我少了很多焦虑因为我知道我的业务代码跟那个框架之间隔了一层换掉它只是改一个服务的实现而已。