news 2026/11/3 16:50:25

Spring AI 0.8.1 升级到 1.0.0:资本市场估值问答服务的 Starter 迁...

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI 0.8.1 升级到 1.0.0:资本市场估值问答服务的 Starter 迁...

Spring AI 0.8.1 升级到 1.0.0:资本市场估值问答服务的 Starter 迁移与兼容性处理

上周三下午四点,风控组的同事把一张截图甩进群:内部估值问答页面又吐了半句就断流,用户看到的最后一行是「根据您提供的 3 年期 IRS 现金流」。这不是第一次了。

项目背景

我们维护的是一套面向机构客户的利率与外汇风险对冲辅助系统。业务侧高频问题就三类:某笔 IRS 在当前曲线下的 DV01 是多少、当前对冲比例偏离目标多少、某个利率情景下保证金缺口多大。技术栈是 Spring Boot 3.4.5 + JDK 21.0.5,估值引擎是团队自己用 Java 重写的 OIS 折现,检索侧用 PostgreSQL 16.4 加 pgvector 0.7.4。整个后端 7 个人,其中 2 人负责这条 AI 问答链路。

2025 年初第一次接入大模型能力时,选的是当时 GA 的 Spring AI 0.8.1,坐标spring-ai-openai-spring-boot-starter。跑了一年半,2026 年国庆前排期做 1.0.0 的迁移。写这篇文章是 2026-10-06,1.0.0 这条线在我们生产环境已经稳定运行了几个月,数据是真实压测出来的。

旧版本卡在哪

三个痛点,按严重程度倒着说。

最要命的是依赖树冲突。0.8.1 时代spring-ai-openai-spring-boot-starter和spring-ai-spring-boot-starter同时存在,传递依赖里拉进了两个spring-ai-core。mvn dependency:tree一跑,0.8.1 和 0.8.0 并排躺在那里。本地开发机类加载顺序凑巧对,灰度机器启动直接NoSuchMethodError。这种问题排查一次要半天。

其次是工具注册。0.8.1 的函数回调走FunctionCallbackWrapper加@Bean全局注册,本质是单例。我们做的是多租户 SaaS,A 客户不该看到 B 客户的持仓工具,当时靠一段 200 行的 if-else 在调用前过滤工具名白名单。难维护,且容易漏。

第三个是流式响应丢尾包。工具调用完成后,最后一个 chunk 的 finishReason 处理在 0.8.1 的 advisor 链路里存在竞态,1000 次压测丢 37 次,前端 SSE 收到的是半句话。这个只能靠业务侧兜底拼接,很别扭。

选型决策

摆在面前的路有三条。

第一条是留在 0.8.1,只把依赖冲突手工 掉。成本最低,但工具 API 的老化和流式缺陷没法根治,等于把技术债再滚一年。

第二条是绕过 Spring AI,直接用 OpenAI 官方 Java SDK 自己封装一层。控制力最强,但对话记忆、向量检索抽象、Advisor 链路全得自己写,按我们两个人的投入,至少三周起步,而且以后换模型供应商要重写。

第三条是升级到 Spring AI 1.0.0。这个版本 2025 年 5 月 GA,是第一个 API 稳定版,Starter 命名收敛成spring-ai-starter-model-和spring-ai-starter-vector-store-,工具回调统一到ToolCallback,基线要求 Spring Boot 3.4.x。我们主工程本来就在 3.4.5 上,迁移面可控。

选了第三条。核心判断是:这一年半我们真正依赖的是 ChatClient、ChatMemory、VectorStore、Advisor 这四层抽象,而 1.0.0 恰好把它们全定型了。

1.0.0 改了什么

对比表先放上来,这是迁移时最常翻的一页。

| 维度 | Spring AI 0.8.1 | Spring AI 1.0.0 | 迁移动作 |
|---|---|---|---|
| OpenAI Starter 坐标 | spring-ai-openai-spring-boot-starter | spring-ai-starter-model-openai | 改 artifactId,删掉手写版本号 |
| 版本管理 | 各模块单独声明版本 | spring-ai-bom:1.0.0 | dependencyManagement 中 import |
| 工具回调 | FunctionCallback / FunctionCallbackWrapper | ToolCallback / MethodToolCallbackProvider | 重写注册 Bean |
| 消息取文本 | AssistantMessage#getContent() | AssistantMessage#getText() | 全局替换,靠编译期兜底 |
| 对话记忆 | 直接 new ChatMemory 实现 | MessageWindowChatMemory + ChatMemoryRepository | 补 Repository Bean |
| 向量检索请求 | new SearchRequest(...) | SearchRequest.builder() | 改构造方式 |
| Spring Boot 基线 | 3.2.x | 3.4.x | 同步对齐 |

pgvector 的 Starter 同样改名,从spring-ai-pgvector-store-spring-boot-starter变成spring-ai-starter-vector-store-pgvector。这个点我在迁移时差点漏掉,因为它不在 OpenAI 那条依赖链上。

迁移步骤

第一步:先锁 BOM,再换坐标

顺序很重要。先换坐标再锁 BOM,中间会有一段时间依赖树是脏的,编译报错信息会很难读。

```xml



org.springframework.ai
spring-ai-bom
1.0.0
pom
import




org.springframework.ai
spring-ai-starter-model-openai


org.springframework.ai
spring-ai-starter-vector-store-pgvector


```

顺手把仓库配置里spring-milestones的地址删掉。GA 版本从中央仓库拉,留着 milestone 源反而可能拉到同版本的快照,我们就踩过一次。

第二步:配置属性对齐

配置项主体结构没变,但spring.ai.vectorstore.pgvector下的子键名有几个调整。

```yaml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: ${CHAT_MODEL_NAME}
temperature: 0.1
embedding:
options:
model: ${EMBEDDING_MODEL_NAME}
vectorstore:
pgvector:
index-type: HNSW
distance-type: COSINE_DISTANCE
dimensions: 1536
initialize-schema: false
```

模型名走环境变量注入,不写死在仓库里。原因是估值问答对模型版本的敏感度很高,换个快照模型回答风格就可能漂,这类变更必须走配置中心而不是发版。

第三步:重写工具注册

这是改动量最大的一块。

```java
@Configuration
class ToolConfig {

@Bean
ToolCallbackProvider riskToolProvider(RiskQueryService svc) {
return MethodToolCallbackProvider.builder()
.toolObjects(new RiskTools(svc))
.build();
}
}

@Component
class RiskTools {

private final RiskQueryService svc;

RiskTools(RiskQueryService svc) {
this.svc = svc;
}

@Tool(description = "查询某笔利率互换在当前曲线下的 DV01,入参为交易编号")
Dv01Result queryDv01(
@ToolParam(description = "交易编号,如 IRS-2026-0001") String tradeId) {
return svc.dv01(tradeId);
}
}
```

调用侧的关键变化是工具可以按请求传入了,不再依赖全局 Bean:

```java
ChatClient client = ChatClient.builder(chatModel)
.defaultSystem("你是利率风险助手,只基于检索到的估值数据作答")
.build();

String answer = client.prompt()
.user(question)
.advisors(a -> a
.param(ChatMemory.CONVERSATION_ID, tenantId + ":" + sessionId)
.param(QuestionAnswerAdvisor.FILTER_EXPRESSION,
"tenant == '" + tenantId + "'"))
.toolCallbacks(tenantToolCallbacks(tenantId))
.call()
.content();
```

流式路径把.call().content()换成.stream().content()即可,返回Flux。我们那 200 行 if-else 白名单,到这里压缩成 30 行的tenantToolCallbacks。

第四步:灰度验证

新旧两条链路并行跑了 5 天。做法是把同一批问题双写,用 0.8.1 的答案和新答案做语义相似度比对,相似度低于 0.85 的捞出来人工看。总共 1200 条问题,人工复核 46 条,其中 3 条确认是新版本工具描述文案变更导致的调用差异,改完描述就对齐了。

兼容性坑

Advisor 的全局注册这里要单独说。官方文档推荐用ChatClient.builder().defaultAdvisors(...)统一挂载,理由是代码干净。但在我们多租户场景下反而更糟——租户过滤条件必须随请求变化,挂在全局就意味着每个请求进来还得再覆盖一次,白白多一层参数合并,而且容易写出「忘了覆盖」的静默 bug。我们最后放弃了全局 Advisor,改成每次prompt()时显式传。这个方案不算优雅,但可读性明显更好。

另一个坑是AssistantMessage#getContent()换成getText()。方法还在,只是标记了废弃,所以编译不报错,运行时返回 null 的地方才暴露。建议迁移时直接全仓替换,别留着。

效果数据

流式丢尾包:压测 1000 次,迁移前丢 37 次(3.7%),迁移后 0 次。

首 token 延迟 P95(含一次工具调用):1.24s 降到 1.11s。提升不算大,主要来自 advisor 链路少了一层包装。

依赖树:spring-ai-core从重复出现 2 次收敛到 1 次,打进可执行 jar 的第三方 jar 数量从 214 降到 197。

启动时间:8.9s 降到 8.2s,本地 MacBook Pro M3 上测的。

工具白名单代码量:200 行降到 30 行。

感悟

如果重来,两件事会调整。一是 Starter 改名单独开一个 PR,不要和 API 适配混在一起——那次 diff 有 1400 多行,评审的人根本看不出哪里是真正的逻辑改动。二是先写兼容性测试再动代码,我们那 46 条人工复核的问题,如果提前有一组固定回归问题集,能省掉大半时间。

#后端 #Java #SpringBoot #SpringAI #版本迁移


你在实际项目中有遇到类似问题吗?欢迎在评论区分享你的经验和解决方案。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 20:45:18

Ponytail:用Skill插件把信息收集整理自动化

第一次看到“Ponytail”这个项目名,我差点把它当成一个发型教程。直到同事甩过来一个 GitHub 链接,我才意识到,这不是扎头发的马尾辫,而是一套把散落信息扎起来的效率插件。用项目里的话说,Ponytail 是一个轻量的 Skil…

作者头像 李华
网站建设 2026/10/21 19:45:58

基于时空卷积神经网络GL-GCN的交通流异常检测算法解析

简介:一份面向交通流异常检测研究者的学术PDF,系统阐述GL-GCN算法——融合图卷积网络与深度神经网络DeepGLO,同时捕捉道路空间结构与时间依赖性,构建预测交通流模型,并利用异常分数判别异常事件。资源适合深度学习、机…

作者头像 李华
网站建设 2026/10/25 13:33:33

Python零基础实战路线:环境配置、核心语法与常用库全解析

1. 这可能是你最后一次需要收藏的Python入门路线我先说个得罪人的大实话:市面上90%的Python教程都在浪费你的时间。你不需要背完所有语法才能写程序,也不需要先啃完两本厚书才有资格打开编辑器。你需要的是——搞清楚自己为什么学Python,然后…

作者头像 李华
网站建设 2026/10/23 10:12:05

基于Simscape的电动/混动飞机组件尺寸建模与迭代仿真

做电动和混合动力飞机组件尺寸设计,最怕的不是电机选型手册翻得不够多,而是把一堆规格书拼在一起之后,发现系统级需求根本对不上。我这两年在用MATLAB/Simulink/Simscape搭这类模型时最深的体会是:组件尺寸问题本质上不是"查…

作者头像 李华
网站建设 2026/10/23 15:06:21

分流器与霍尔电流采样选型实战指南

1. 电流采样这件事,远比你想象的更“较真”电流采样听起来是个基础到不能再基础的电子测量动作——不就是把电流信号变成电压信号,再送进单片机或仪表读数吗?但我在电源模块调试、电机驱动板返修、工业PLC信号链排查这十多年里,反…

作者头像 李华
网站建设 2026/10/23 12:06:55

汉语词义自动消歧实战:Python实现与避坑指南

简介:这份资源是面向计算机相关专业学生的自然语言处理课程设计完整项目,聚焦汉语词义自动消歧(WSD)这一经典NLP任务,适合正在做期末大作业、课程设计或需要项目实战练习的学习者参考。项目经导师指导并认可&#xff0…

作者头像 李华