
9 月 7 日,Maven Central 的目录里多了一个新制品:dev.langchain4j:langchain4j:1.19.1。官方同日在 GitHub 发布这个版本的 Release 页。别家发版本,Release 标题写的是"What's New",这一版的标题是一句警告:published in error, do not use (发布有误,请勿使用)。紧接着 1.19.2 的 Release 页也发了出来,两个页面的时间戳 12:47 和 12:49,相隔整两分钟。
两分钟双发,本身就是事故信号。这个"正常"的补丁版本号里,装着的是整个下一个大版本的全部内容------而且,它永远删不掉了。Maven Central 没有撤回键,这颗事故制品会在这个生态里流动很多年。
本文基于 LangChain4j 1.20.2 (主线最新)与 1.19.3(1.19.x 补丁线最新,均截至 2026-09-30)编写,事故信息核对自 GitHub Releases 与 Maven Central 制品目录,文中依赖树输出均为本地实跑结果。框架迭代快,请以官方 Releases 为准。
先把速查表放这------你现在处在哪个状态,比一切都重要:
| 你的现状 | 正确动作 |
|---|---|
| 还在 1.19.0 或更早 | 留守 1.19.x 用 1.19.2 / 1.19.3;要追新就直接上 1.20.x |
| 已经升到 1.19.1 | 别降到 1.19.2------直升 1.20.0 或更高 |
| 用 langchain4j-bom 管版本 | 看一眼 BOM 版本号:只要不是 1.19.1,全家族模块都不会中招 |
下面按时间顺序拆:事故怎么发生的、我本地怎么复现的"物证"、为什么降级是最差选择、三分钟自查怎么做。
一个"补丁",怎么会装着整个大版本
官方在 1.19.1 的 Release 页里把事故说得很直白,原文值得逐句看:
This release was published by mistake. Do not use it.
It was cut from
maininstead ofrelease/1.19.x. Despite the version number, it is not a patch on top of1.19.0: it contains all of1.20.0plus three commits that landed after it --- 108 commits and 300 files ahead of1.19.0.
事故机理一句话:切割分支切错了 。LangChain4j 的正常发布流程是从 release/1.19.x 维护分支上切补丁,这次却从 main 主干直接切------相当于本该从货架取一件包裹,结果连货架带整个仓库一起端走了。main 上早合入了 1.20.0 的全部内容(9 月 4 日刚发布的非阻塞支持、Jackson 3 可选支持,详见我后面要写的 1.20 实测篇)。于是版本号说是 1.19.1,代码实际比 1.19.0 超前 108 个提交、300 个文件。
发现事故后,官方两分钟内发布了本该发布的 1.19.2(内容是 1.19.0 + MCP 补丁回移,除此之外什么都没有),并且在事故页上写了那句每个用 Maven Central 的人都该记住的话:
Maven Central is immutable, so these artifacts cannot be withdrawn. This note is the only way we can flag them.
仓库不可变,制品撤不回,唯一能做的是把 Release 页改成事故公告。我实跑验证过这件事有多彻底:本地起了个空项目依赖 1.19.1,dependency:tree 一路绿灯,下载日志里这行字看得我愣了几秒------
csharp
[INFO] Downloaded from aliyun-public: https://maven.aliyun.com/repository/public/dev/langchain4j/langchain4j/1.19.1/langchain4j-1.19.1.jar
一个被官方判了"请勿使用"的制品,正安安静静地从公共镜像流进我的本地仓库。通缉令都贴到 GitHub 门口了,仓库和各路镜像还在热情发货------Maven 不管好人坏人,只管打包递送。Central 删不掉,镜像就跟着删不掉------你的公司私服、云厂商镜像、CI 缓存,都是这颗制品的下一站。
给 1.19.1 做个亲子鉴定,依赖树不会说谎
官方说 1.19.1 里有 1.20.0 的全部内容,我不背数字,直接看树。同一个空项目,分别依赖四个版本各跑一遍 dependency:tree,结果摆在一起:
| 依赖 | 1.19.1 | 1.19.2 | 1.19.3 | 1.20.0 |
|---|---|---|---|---|
io.smallrye.reactive:mutiny-zero:1.3.1 |
有 | 无 | 无 | 有 |
org.jspecify:jspecify |
1.0.1 | 1.0.0 | 1.0.0 | 1.0.1 |
1.19.1 的依赖树和 1.20.0 完全同构,跟 1.19.2、1.19.3 这俩"同门师弟"反而不是一家人。多出来的 mutiny-zero 是 SmallRye 的响应式互操作库,正对上 1.20.0 的非阻塞支持------这就是"误吞 1.20.0"留在依赖树上的指纹。
不用读 release notes,光看依赖树就能识破这个版本号的伪装。这一招后面自查时还会用到。
升错的团队,为什么反而不能降级
这是整件事最反直觉、也最容易做错的地方。
直觉说:升错了,退回去啊,1.19.1 挪到 1.19.2,版本号还在往前走,面子上根本不算降级。官方的警告偏偏对着这份体面来,原文如下:
If you are already on
1.19.1, do not "upgrade" to1.19.2.1.19.2is numerically higher but contains less code --- it is the real1.19.0+ backports, while1.19.1accidentally shipped everything up to and beyond1.20.0. Moving1.19.1 → 1.19.2would silently remove ~108 commits of functionality from your build. Go to1.20.0or later instead.
数字更大的版本,内容反而更少------语义化版本的宇宙里,这近乎玄学:账面涨了一位,家底反而薄了。因为 1.19.2 才是那个"真正的 1.19.1"(1.19.0 + MCP 回移),而 1.19.1 早就越过它跑到了 1.20.0 前面。从 1.19.1 挪到 1.19.2,名义上是升级,实际是撤退,会静默丢掉大约 108 个提交的功能。
我原本以为这种"降级"最多回退几个 bugfix,树一对比才发现丢的是什么:对照表里 1.19.2 缺的那个 mutiny-zero,就是整个响应式栈的入口。假设你的代码已经用上了 1.20 线的类型(Flow.Publisher 相关),挪到 1.19.2 是编译期报错,还算能查;更阴险的是间接依赖------编译也过、测试也绿,直到某个运行时路径第一次摸到那个不存在的类。
两个连带坑一并交代:
- beta 模块同坑 :
langchain4j-spring的1.19.1-beta29出自同一次故障发布,官方口径是改用1.19.2-beta29或1.20.0-beta30。LangChain4j 的集成模块一直是 beta 双轨版本号(8 月底我在框架盘点里提醒过"升 beta 模块留意 API 变化"),这次连 beta 号都追加了事故。 - API 对比工具会被骗 :官方在事故页末尾补了一条很细的提醒------revapi 这类"取 X 以下最新版当基线"的工具(
oldVersion=RELEASE),会选中 1.19.1 当 1.19.x 的基线,然后对着它报一堆误导性的 API 差异。你的 CI 里如果挂着 revapi 报告,这阵子可能出现"看不懂的接口变动",根源就在这。
三分钟自查,顺手看清 BOM 为什么能防住
自查就一条命令,全树扫事故制品(Gradle 用户跑同名的 dependencies 任务):
csharp
$ mvn dependency:tree | grep -C 3 "1.19.1"
[INFO] \- dev.langchain4j:langchain4j:jar:1.19.1:compile
有输出就说明树里有它------注意往上看几行,确认是哪个模块引的(自家 pom 还是三方集成模块传递进来的)。没输出,恭喜,这条你可以划掉了。
比自查更值得看的是 BOM 的表现。LangChain4j 官方文档推荐的引入方式就是 langchain4j-bom(下面以留守 1.19.x 线为例,追主线就把版本号换成 1.20.2):
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>1.19.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
这里交代一个我自己的乌龙。写稿前我笃定 BOM 序列里不会有事故版本,理由充分到差点说服我------查了 Maven Central,有:langchain4j-bom:1.19.1 和 jar 同一分钟上传(09:37),内部 dependencyManagement 管理的正是 1.19.1 / 1.19.1-beta29。你看,事故从不挑制品类型。这次事故发布的波及面,连 BOM 本身都包括在内。
那 BOM 用户凭什么大概率没事?原因不在"没有事故 BOM",在决策收敛:引入 BOM 后,几十个 langchain4j 模块的版本都跟着 BOM 版本号走,你只需要在一个坐标上不写 1.19.1,全家族就都不会落到事故制品上。不引入 BOM 的团队反过来------每个模块各自声明版本号,每个坐标都是一次可以点错的机会,官方事故页那句"beta 模块改用 1.19.2-beta29 或 1.20.0-beta30"就是在替没收敛版本决策的团队挨个排雷。速查表第三行的底气在这。
官方处置链,值得单独学一遍
把 9 月的发布节奏排成表,能看到一条完整的事故处置链:
| 日期 | 版本 | 说明 |
|---|---|---|
| 9-04 | 1.20.0 | 非阻塞支持(#5527)+ Jackson 3 可选支持(#6184) |
| 9-07 | 1.19.1 | 事故版,官方标记 do not use |
| 9-07 | 1.19.2 | 替补补丁:1.19.0 + MCP 回移(#6329) |
| 9-15 | 1.19.3 | 补丁线继续:ClassLoader 配置(#6409)+ MCP 回移(#6412) |
| 9-25 | 1.20.1 | @McpClientSupplier 修复(#6505) |
| 9-28 | 1.20.2 | A2A 修复(#6512),当前最新 |
两分钟响应、release 页改写成事故公告、beta 模块同步提示、连 revapi 这种边角工具都写了提醒------这套处置在开源项目里算得上范本。两分钟出替补什么概念?贵司生产事故的 MTTR 若有这个数,CTO 做梦都能笑醒------当然,前提是别出这档子事故。同时它也说明另一件事:对一个月更 6 次的框架,发布事故不是"会不会"的问题,是"何时"的问题。1.19.3 还在证明补丁线的存活,留守 1.19.x 的团队依然有安全落点,但每次升 patch 之前看一眼 Release 页标题,这个习惯从这次起得算标配------标题也可能是警告。
版本号不是语义承诺,是信任接口
最后给三档判断:
- 没上 1.19.1 的团队 :BOM 锁版本打底,CI 里加一条
dependency:tree全树审计(扫事故制品只是最粗的用法,更值钱的是版本漂移检测),升级前跑一遍你的评测集------这条我在 Agent Evals 那篇 里展开过,升级就是最该跑 Evals 的时刻。 - 已经上了 1.19.1 的团队:直升 1.20.0 或更高,不要在 1.19.2 停留;升完跑全量回归,重点扫异步和流式路径------你身上已经带着 1.20 的依赖指纹了,退不回去。
- 1.19.x 留守团队:1.19.3 是当前安全落点,补丁线在维护,可以留;但"三条维护线并行"意味着三条线都得有人盯,盯不过来的话,跟上主线比锁旧线的总成本更低。
我在 模型月抛那篇 里写过"别追新"的治理逻辑,框架月更时代要补上后半句:追新可以,但要追得可回滚。版本号本身不提供这个保证------1.19.1 就是活例子,数字在涨、内容在退。真正提供保证的是你自己的治理动作:锁版本、审依赖、升级前跑评测。
这颗删不掉的 1.19.1 会在 Maven Central 和各家镜像里存在很多年,等到某个新同事翻旧文档把版本号抄进 pom 里,它还能再咬人一次。你的依赖树,是唯一能替你挡住它的东西。