周会上被问住了
6 月底的架构周会,组里一个同学问:"Spring 官方和 DeepSeek 合作之后,我们要不要把模型调用层切到官方 starter?"
我当时答不上来。这两年"XX 与 XX 达成合作"的新闻太多了,大部分最后落在 PPT 上。我没有花时间验证过,就不能在会上拍板,于是给自己派了三天活:把官方文档、starter 源码和我们的实际调用链路过一遍。
这篇是那三天的结果,以及我们最后做的决策。
先搞清楚"官方集成"集成了什么
我把变化拆成了三层,只有第一层是新闻里说的那个。
| 层次 | 具体是什么 | 对我们的价值 |
|---|---|---|
| 模型接入 | Spring AI 提供官方 DeepSeek starter,配置即可用 | 中 |
| 能力对齐 | 函数调用、结构化输出、思考链在 starter 里对齐 | 高 |
| 生态位 | 国内模型进入 Spring 官方支持矩阵,不再是"社区维护" | 高 |
第一层其实一直有社区实现,差别不大。真正有分量的是第二层和第三层。
我们之前接 DeepSeek 是自己写的 ChatModel 实现,因为早期官方版本对 tool calling 的支持不完整,特别是并行工具调用(parallel tool calls)返回的结构社区版解析有 bug,我们被迫在业务侧做了一层兼容。这类兼容代码是纯负债。
接入官方 starter 之后:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>
spring:
ai:
deepseek:
api-key: ${DEEPSEEK_KEY}
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
temperature: 0.2
# 关键:官方 starter 支持这个开关,我们之前的自研实现不支持
internal-tool-execution-enabled: false
删掉了 640 行兼容代码。internal-tool-execution-enabled: false 这一项对我们是刚需——工具执行必须由我们自己的编排层控制,而不是让框架内部自动回调,因为要接幂等和审计。
三天的 POC 数据
光看功能不够,我们跑了一轮对比。测试集是我们自己的 640 条客服问答 + 180 条工具调用用例。
| 指标 | 现网模型(某闭源) | DeepSeek(官方 starter) |
|---|---|---|
| 问答准确率 | 89.4% | 87.2% |
| 工具调用正确率 | 94.1% | 91.8% |
| P99 首 token 延迟 | 860ms | 1,420ms |
| P99 完整响应延迟 | 3.4s | 4.9s |
| 输入单价(每百万 token) | ¥16 | ¥4 |
| 月成本(日均 300 万次) | ¥18.6 万 | ¥4.9 万 |
结论一目了然:能力差 2~3 个百分点,成本差 3.8 倍。但延迟也确实更差,完整响应 P99 从 3.4s 涨到 4.9s,这对客服场景是有感知的。
我们没打算全切,而是按场景拆。这里就引出了真正的重点。
真正值钱的不是 starter,是抽象层的可用性
如果只接一个模型,starter 的意义有限。它的价值在于:换模型的成本被压到了一行配置。
我们之前的教训是深刻的。去年从模型 A 切到模型 B,因为业务代码里散落着各种 provider 特有的参数(topP、responseFormat、自定义 header),改了 47 个文件,回归了两周。
这次我们借升级 Spring AI 2.0 的机会,强制加了一层自己的门面。所有业务代码只依赖业务语义,不依赖任何模型厂商:
public interface LlmPort {
/** 场景而非模型:业务方只说"我要做意图识别",不关心跑在哪个模型上 */
LlmReply complete(Task task, List<Message> messages, List<ToolSpec> tools);
}
public enum Task {
INTENT_CLASSIFY, // 轻量,走便宜模型
RAG_ANSWER, // 主力
TOOL_PLANNING, // 需要强推理,走最好的模型
SUMMARIZE, // 轻量
SAFETY_GUARD // 本地小模型优先
}
路由配置集中在一张表里,改模型不用发版:
@Bean
TaskRouter taskRouter(ChatModelRegistry reg) {
return TaskRouter.builder()
.route(INTENT_CLASSIFY, reg.get("deepseek-chat"))
.route(RAG_ANSWER, reg.get("deepseek-chat"))
.route(TOOL_PLANNING, reg.get("claude-sonnet"))
.route(SUMMARIZE, reg.get("qwen-turbo"))
.route(SAFETY_GUARD, reg.get("local-guard"))
.fallback(RAG_ANSWER, reg.get("qwen-plus")) // 主模型不可用时降级
.build();
}
这层做了之后,7 月 12 日 DeepSeek 那边出了一次 40 分钟的限流故障,我们把 RAG_ANSWER 的路由改到备用模型,改的是配置中心的一个值,30 秒生效,没有发版。
对选型的影响:三句话
这是我在周会上给出的结论。
如果你是新项目,直接用 Spring AI 2.0 + 官方 DeepSeek starter,别自己写适配层了。两年前自己写是无奈之举,现在是浪费时间。
如果你已经有自研适配层,评估一下维护成本。我们那 640 行代码里,真正有业务价值的是 0 行,全是协议兼容。删掉它本身就是收益。
别把"官方合作"当成能力背书。它只意味着"这条路走得通、有人维护",不意味着"这个模型适合你的场景"。我们的 POC 显示准确率差了 2.2 个百分点,这个差距对有些业务是致命的,对另一些业务无所谓。
我们的决策:跟进,但不绑定
最终方案是分层跟进:
- 框架层全量跟进。Spring AI 2.0 已 GA,我们从 1.x 升上来了,所有模型调用走官方 starter,删掉全部自研适配代码;
- 模型层按场景分。意图识别、摘要、RAG 问答切 DeepSeek,工具编排(
TOOL_PLANNING)保留原来的模型,因为 91.8% vs 94.1% 在涉及写操作的场景里风险太大; - 数据层不跟进。训练数据、微调语料、评测集全部留在自己手里,这是我们唯一的护城河,不会交给任何厂商。
成本结果:月支出从 ¥18.6 万降到 ¥9.7 万,降幅 48%。延迟方面,因为只有部分场景切换,整体 P99 从 3.4s 变成 3.9s,涨了 0.5 秒。业务方能接受,我们给了他们一个开关,投诉多的时候切回去。
观望清单
有几件事我暂时不动,列一下理由。
- 厂商专属特性。DeepSeek 的一些特有参数(比如思考链预算控制)官方 starter 支持了,但用了就绑死。我们目前一个都没用;
- 微调能力。我们没有足够的标注数据,微调的投入产出比算不过来,先观察一个季度;
- 私有化部署。合规上我们有诉求,但现在的推理服务成本还没压到能接受的水平。这块我在跟另一个项目一起看,有结论了再写。
小结
三天 POC 换来最大的收获不是省下的 8.9 万,是把"换模型"这件事从两周的项目变成了一个配置项。技术新闻会一波接一波,明年可能又是别的厂商、别的合作,但只要这层抽象在,我们面对任何合作都可以用三天验证完再决定跟不跟。
至于那 2.2 个百分点的准确率差距,我目前的判断是:它会随模型迭代快速收敛,不值得为它锁死供应商。这个判断对不对,年底再看。