Spring AI 用类型化接口处理分类决策
Spring AI 2.1.0-RC2 引入 DecisionModel,让开发者预先限定结果范围,直接获得布尔值、类别或评分,减少聊天文本解析环节。接口支持 OpenAI、Ollama 等实现,目前仍处于候选发布阶段。
AI 深度解读
让 AI 判断一条工单是不是产品故障,业务代码需要的是布尔值或分类,模型却可能回一段解释。Spring AI 2.1.0-RC2 新增的 DecisionModel,试图把这类任务变成独立接口:先声明允许的答案,再读取有类型的结果。
这篇文章值得看的地方,不只是少写一个解析器,而是把三个问题拆开:模型判断了什么,业务允许做什么,判断不可靠时怎么办。接口能让边界更清楚,但原文没有提供准确率、延迟或成本对照,不能据此认定分类效果更好。
为什么要把分类从聊天接口里拆出来
用户说「耳机拆箱当天左耳就坏了,能退款吗」,系统可能需要三个结果:是否报告故障、分给哪个团队、用户情绪如何。它未必需要模型写一段回复。
普通 Chat Model 能完成分类,也能配合 JSON mode、schema 或 parser 输出结构化数据。问题在于,如果代码仍靠自由文本推断结果,额外解释、格式变化和提示词调整,就可能让解析逻辑出错。
DecisionModel 的设计是先确定答案空间。工单只能进入指定队列,故障判断只有 yes/no,情绪则落在定义好的尺度上。开发者审核的对象因此更明确:有哪些选项,每个选项代表什么,下游怎么使用。
类型约束解决的是结果如何被程序消费,不能保证结果判断正确。
按原文的版本信息,Spring AI 在 2026 年 11 月 5 日发布 2.1.0-RC2,加入 DecisionModel API,提供 OpenAI、Ollama 实现,以及面向兼容服务的 System One 集成,同时接入 RAG 文档处理和工具索引。原文发表时,文档列出的最新稳定版仍是 2.0.2,RC2 属于预发布版本。这些示例适合评估,不能直接当作生产升级建议。
一个明确的问题,比一句「帮我分类」多了什么
这套 API 提供三种问题:YesNoQuestion 回答是否成立,ChoiceQuestion 从有限选项中选择,Score 则选择定义尺度上的等级,例如 unhappy、neutral、happy。
区别不只在返回类型,还在于必须把分类口径写出来。「product」究竟包括普通商品咨询,还是只指收到商品后的问题?yes 是「提到了商品」,还是「收到的商品无法正常工作」?描述越含糊,模型越容易自行补全业务定义。
原文用本地 Ollama 演示,默认决策模型为 d1-omni:600m,建议使用 Ollama 0.40.4 或更高版本。模型通过 ollama pull d1-omni:600m 拉取,依赖通过 Spring AI BOM 管理,Gradle 添加 implementation 'org.springframework.ai:spring-ai-starter-model-ollama'。使用前还要执行 ollama show <model-name>,确认模型支持 decision 能力。
下面的示例把「工单主题」和「是否故障」放进同一次调用,并为选项写出解释:
@Service
class SupportTriage {
private final DecisionModel decisionModel;
SupportTriage(DecisionModel decisionModel) {
this.decisionModel = decisionModel;
}
DecisionResponse classify(String message) {
return decisionModel.call(DecisionPrompt.builder()
.text(message)
.question("topic", ChoiceQuestion.builder()
.text("What is this support message about?")
.option("delivery", "Order status or arrival time")
.option("product", "A problem with an item received")
.option("account", "Login, address, or payment details")
.option("other", "None of the categories above")
.build())
.question("defect", YesNoQuestion.builder()
.text("Does the customer report that an item is defective?")
.whenYes("A received item does not work as expected")
.whenNo("The message is not reporting a product defect")
.build())
.build());
}
}
重点看 .option(...)、.whenYes(...) 和 .whenNo(...):它们把业务含义放到了调用现场,便于评审,也便于围绕同一组定义准备测试样本。
消费结果时,不再从一段自然语言里寻找「是」或某个分类词:
String topic = response.getChoice("topic").getValue();
boolean defect = response.getYesNo("defect").isYes();
这里要关注 "topic" 和 "defect",它们对应前面的问题名称。类型访问器让接口契约更清楚,但超时、空输出和未知分类仍需要业务处理。
输入也应尽量收窄。判断一条消息是否报告故障,默认不需要发送整份客户档案;确实依赖历史信息时,再挑选相关上下文。这样既减少不必要的数据暴露,也让分类任务更容易定义。
模型可以帮你分流,但不能替你批准退款
原文反复强调:DecisionModel 是机器学习模型,不是规则引擎。输出有类型,照样可能出错;提供方返回概率,也不等于提供了正确性证明。
退款就是清楚的分界。模型把消息标成 defect,可以用来推荐售后队列;能否退款,则要由订单状态、业务规则和权限决定。对于有财务、账号访问或安全影响的动作,分类结果不应直接成为授权依据。
设置「超过 0.8 才执行」也不能跳过验证。误报和漏报的代价不同,各模型的分数未必经过适合当前任务的校准,同样的 0.8 不能直接横向比较。需要用代表性样本确定阈值,并记录高置信度却判断错误的情况。
RAG 和工具选择遵循同一个边界。DecisionDocumentFilter 可以在生成答案前剔除文档,DecisionDocumentReranker 可以按文档是否回答查询重新排序。相关性、矛盾信息、可疑指令,都是适合定义为有限问题的检查项,但检测提示词注入并不因此获得可靠性保证。
工具索引则可以从大量工具中筛选候选,或者判断没有合适工具,减少每次提示词携带全部描述的需要。选中了工具,仍要检查授权、参数和执行副作用。
模型负责理解语义,确定性代码负责执行权限与业务约束。
我的判断
我认可把有限分类建模成独立操作。对于大量「读文本、选标签」的服务,它能把选项定义、结果读取和业务处理放在更容易检查的位置,也能减少自由文本解析带来的脆弱环节。
但原文给出的是接口示例和评估建议,没有作者团队的实测结果,更没有与 Chat Model 加 schema、parser 的对照数据。准确率更高、响应更快、费用更低,都不能从接口形态推导出来。它的优势目前主要是设计层面的判断。
已有稳定结构化输出的团队,不必为了接口更整齐就迁移到 RC。若现有实现已经明确分类集合、失败回退和下游权限,迁移收益还要看维护成本与模型表现。用户需要解释、讨论或细腻回复时,聊天接口仍然合适,两种调用可以组合使用。
本地 Ollama 路径允许不依赖外部模型 API key 做实验,但原文没有说明 d1-omni:600m 的中文分类能力、硬件要求或吞吐量。面对国内客服文本、行业缩写和中英混输,仍得拿自己的样本评估。
能拿来做什么
- 先检查现有 AI 调用中只需要 yes/no、类别或等级的部分,把允许的结果、分类口径和失败行为写清楚。前端或 Node 服务即使暂时不使用 Spring AI,也可以先明确这份接口契约。
- 已在评估 Spring AI 2.1 的团队,可以选非关键工单分流,在分支中用几十条标注样本比较 DecisionModel 与聊天模型加解析器,覆盖模糊消息、范围外输入,并分别统计各类别准确率。
- 为分类调用补上超时、空输出、未知分类和结果不确定时的回退,记录输入、模型及版本、预测结果和最终动作。尤其要留下高置信度误判,作为调整阈值的依据。
- 对 RAG 过滤或工具筛选,先验证分类是否改善实际结果,再决定接入范围;退款、账号操作和工具执行继续经过现有权限与确认检查。