Ohhnews

分类导航

$ cd ..
Spring Blog原文

Spring AI 2.1.0-RC2 现已发布

#spring ai#ai框架#版本发布#决策模型#mcp协议

我谨代表 Spring AI 团队和所有贡献者,很高兴宣布 Spring AI 2.1.0-RC2 已发布,并可从 Maven Central 获取!

发布说明 | 升级说明 | 参考文档

2.1.0-RC2 基于 2.1.0-RC1 构建,并交付了 DecisionModel——一个用于类型化的“是/否”、选择和评分决策的新 API,以及首批基于它构建的功能。它还新增了工具调用增量流式传输,将预计算嵌入支持扩展到另外五个向量存储,为新的 MCP 协议修订版带来服务器端支持,并升级到 Anthropic Java SDK 3.x。

本版本新增内容

决策模型

应用中的许多决策只有少量已知可能结果:是或否、少数几个类别之一,或某个量表上的等级。你可以询问聊天模型,但它会用自由文本回答,而你必须自己将这段文本转换为可靠的结果。新的 DecisionModel API 正是为这些决策而设计。你描述可能的结果,模型会选择其中一个,并告诉你每个结果的可能性,而不是生成文本。

它遵循其他模型 API 的形态:输入 DecisionPrompt,输出 DecisionResponse,并用 DecisionOptions 调整调用。一个提示词包含一个输入(一段文本、一组命名字段或一段对话)以及一个或多个命名问题:

  • YesNoQuestion,以“是”的概率作答。
  • ChoiceQuestion,以选定选项作答,并在提供方报告时给出每个选项的概率。
  • ScoreQuestion,以有序量表中的一个等级作答,例如 unhappy、neutral、happy。
$ java
DecisionResponse response = decisionModel.call(DecisionPrompt.builder()
    .text("My new headphones arrived yesterday, but the left side doesn't work. Can I get my money back?")
    .question("defect", YesNoQuestion.builder()
        .text("Does the customer report a defect?")
        .whenYes("Part of the item does not work as it should")
        .whenNo("The item works, and the message is about something else")
        .build())
    .question("topic", ChoiceQuestion.builder()
        .text("What is the message about?")
        .option("delivery", "Where an order is, or when it will arrive")
        .option("product", "A problem with an item the customer received")
        .option("account", "Login, address or payment details")
        .option("other", "None of the above")
        .build())
    .question("satisfaction", ScoreQuestion.of("How satisfied is the customer?", "unhappy", "neutral", "happy"))
    .build());

// open a repair ticket automatically only when the model is very sure
boolean defect = response.getYesNo("defect").isYes(0.9);
String topic = response.getChoice("topic").getValue();
int satisfaction = response.getScore("satisfaction").getNearestLevel();

由于答案携带概率,你的应用可以决定模型在采取行动前必须有多确定。决策模型仍然是机器学习模型,而不是规则引擎,因此其答案并非确定性的;参考文档说明了如何考虑阈值和错误答案。典型用途包括分类、护栏(例如在提示词注入到达聊天模型之前将其阻止)以及日常业务逻辑。

提供三种实现:

  • OpenAI:OpenAiDecisionModel,由 OpenAI Decisions API 支持,并由 OpenAI starter 在 spring.ai.openai.decision 下自动配置。
  • Ollama:OllamaDecisionModel,由 Ollama starter 自动配置。它默认使用 d1-omni:600m,并可通过 spring.ai.ollama.init.pull-model-strategy=when_missing 在启动时拉取模型。这是试用该 API 最快的方式,因为它在你自己的机器上运行且无需 API 密钥。
  • System One:SystemOneDecisionModel,基于 TypeSafe Java SDK 构建。一个模型即可服务所有使用 System One 协议的服务器,例如 TypeSafe Jev、Laya 和 Ollama。它不会自动配置:你需要从 TypeSafeClient 创建它。

决策模型调用以 decision 操作名进行观测,新的 spring-ai-autoconfigure-model-decision-observation 模块新增了 token 使用量指标。

其他 Spring AI 功能已经基于新 API 构建:

  • RAG 后处理器。 DecisionDocumentReranker 通过询问每篇文档是否回答查询来重新排序检索到的文档,DecisionDocumentFilter 则在文档进入提示词之前筛查提示词注入、矛盾和相关性问题。两者都会将针对每篇文档的调用分发到执行器,并可在虚拟线程上运行。它们采用 fail-open 策略,保留模型无法判断的文档并记录数量;过滤器则可改为 fail-closed。
  • 工具搜索。 DecisionToolIndex 是继正则、Lucene 和向量索引之后的第四种 ToolIndex 策略。它向决策模型询问哪个已索引工具最适合该请求,并在同一次调用中询问是否有任何工具适用,因此它可以报告没有工具适用,而不是返回最接近的错误工具。在工具搜索 Advisor 中通过 spring.ai.chat.client.tool-search-advisor.tool-index-type=decision 启用,并通过同一前缀下的 decision.applicability-threshold 和 decision.minimum-relevance 属性进行调整。

工具调用参数流式传输

模型会逐段流式输出工具调用的参数,而此前 Spring AI 只暴露完整调用。Anthropic、OpenAI(Chat Completions 和 Responses)、Amazon Bedrock Converse 和 DeepSeek 的工具调用参数增量现在会以标记为 partial 的 ToolCallPart 流式输出,因此 UI 可以在模型仍在生成工具调用时显示它。完整调用仍会随后出现,索引相同。

部分 ToolCallPart 不是工具调用:getToolCalls() 和 hasToolCalls() 会跳过它,ToolCallingManager 和 ToolCallingAdvisor 会忽略它,请求转换器不会将其发送回提供方。MessageAggregator 也会跳过部分片段,因此工具调用循环之外的 Advisor(例如聊天记忆)能看到增量,但绝不会持久化没有结果的工具调用;并且,在工具调用中途停止的流不再聚合为参数被截断的调用。

在更多向量存储中使用自己的嵌入

M1 新增了 VectorStore.upsert(),用于写入你自己计算的向量;RC1 新增了 similaritySearch(float[], SearchRequest),用于使用自己的查询向量进行搜索,最初面向 pgvector、Qdrant、Redis、Elasticsearch 和 OpenSearch。现在以下存储也实现了这两项功能:

  • Neo4j
  • MongoDB Atlas
  • Typesense
  • MariaDB
  • Chroma

请参阅参考文档中的使用用户提供的查询嵌入进行搜索。

MCP 2026-07-28(“现代”)服务器

Spring AI MCP 服务器现在可以与现有的 STDIO、SSE、STREAMABLE 和 STATELESS 服务器并列,支持 Model Context Protocol 的 2026-07-28 修订版。通过 spring.ai.mcp.server.protocol=MODERN 激活它。

该修订版移除了 initialize 和会话。server/discover 是可缓存的请求,每个请求都携带协议版本和客户端能力,服务器发起的输入(例如 elicitation、sampling 和 roots)变为多轮往返请求,变更通知通过 subscriptions/listen 传递。该集成基于 MCP Java SDK 2.2(当前为 2.2.0-M2)的现代 API 构建,并带来:

  • 通过单个 POST 端点提供该修订版的 WebMVC 和 WebFlux 传输,支持服务器发送事件流,并支持 STDIO。
  • 用于工具、提示词、资源和补全的内存存储库,支持运行时 add 和 remove、变更广播以及分页。
  • 为 @McpTool、@McpResource、@McpPrompt 和 @McpComplete 提供现代方法回调。方法可声明 McpModernSyncRequestContext 以读取请求数据和多轮往返字段,并可声明 McpSyncNotifier 以报告进度。
  • 三个注解上的 requiredCapabilities,用于列出方法无法缺少的客户端能力。服务器会在方法运行前返回错误。
  • 新的定义属性:@McpIcon、@McpArg(title)、@McpResource(size, ttlMs, cacheScope) 和 @McpToolParam(header = true)。
  • spring.ai.mcp.server.modern.* 下的新属性。
$ java
@McpTool(name = "delete_task", description = "Deletes a task after asking the user to confirm",
        requiredCapabilities = McpClientCapability.ELICITATION_FORM)
public CallToolOutcome deleteTask(McpSyncNotifier notifier, McpModernSyncRequestContext ctx,
        @McpToolParam(description = "Task id") String id) {
    // null on the first call, before the client has answered
    Map<?, ?> answer = ctx.inputResponse("confirm", Map.class);
    if (answer == null) {
        notifier.progress(10, 100.0, "asking for confirmation");
        Map<String, Object> schema = Map.of("type", "object",
                "properties", Map.of("confirmed", Map.of("type", "boolean")));
        return InputRequiredResult.builder()
            .elicit("confirm", ElicitFormRequest.builder("Delete task " + id + "?", schema).build())
            .requestState(id)
            .build();
    }
    ...
}

STATELESS 服务器的注解式工具、资源和提示词只需更改属性即可迁移。对于 STREAMABLE 服务器,绑定会话的 ctx.elicit 和 ctx.sample 调用会变为上述多轮往返请求,ctx.progress 变为 McpSyncNotifier。客户端日志记录、ctx.roots() 和 ctx.ping() 在现代上下文中尚无对应项。两种迁移的可用示例都在 spring-ai-examples 中。目前仅支持同步 API 类型,type=ASYNC 与 protocol=MODERN 组合会在启动时失败。请参阅现代服务器参考页面。

其他新增

  • SessionService 通过 appendEvents(sessionId, events, expectedVersion) 和 getEventVersion(sessionId) 暴露带版本检查的追加操作。从日志派生事件的代码现在可以在并发场景下安全写入它们,并且系统消息检查适用于批次中的每个事件。
  • Anthropic Java SDK 从 2.72.0 升级到 3.4.0(请参阅下方升级说明)。

升级说明

有一些变更可能需要你在代码中做出调整。Anthropic 和工具调用流式传输变更的详细信息见升级说明:

  • Anthropic Java SDK 3.x 默认使用 API 版本 2026-10-21,该版本会拒绝 temperature、topP 和 topK,即使将它们设置为默认值也是如此。Spring AI 保留采样方法和 Boot 属性,但将其标记为已弃用。作为模型默认值或请求覆盖设置的值会被丢弃且绝不会发送,并会通过 WARN 消息列出,因此请从配置中移除它们。扩展思考不再需要 temperature(1.0)。Anthropic 迁移指南涵盖了这些细节。
  • Anthropic API 密钥现在以 Authorization: Bearer 发送,而不是 X-Api-Key。
  • spring.ai.vectorstore.typesense.embedding-dimension 不再默认值为 1536。只有在设置时才传递给存储,因此向量大小来自现有集合的 schema;对于新集合,则来自嵌入模型。
  • 当读取集合因“未找到”以外的任何原因失败时,TypesenseVectorStore 会使初始化失败。此前,启动会继续。
  • Anthropic 和 Bedrock Converse 的提示词 token 现在包含从提示词缓存读取和写入的 token(见下方修复),因此对于使用提示词缓存的请求,它们以及 gen_ai.usage.input_tokens 观测属性会报告更高的值。对于 Anthropic,Usage.getTotalTokens() 也包含缓存 token。如果你的应用手动将缓存 token 加到提示词 token 上来补偿,请移除该调整,否则它们会被重复计算。
  • 仅以部分片段流式输出工具调用的自定义 ChatModel,现在还必须发出完整的 ToolCallPart,且索引相同。
  • DeepSeekChatModel 通过新的 DeepSeekApi.chatCompletionStreamRaw 进行流式传输。覆盖 chatCompletionStream 的 DeepSeekApi 子类不再被它调用,而仅打桩 chatCompletionStream 的测试 mock 会使流式传输因 NullPointerException 失败。请改为覆盖或打桩 chatCompletionStreamRaw。

修复

  • Anthropic 和 Bedrock Converse:提示词 token 以及由其派生的 gen_ai.usage.input_tokens 观测属性此前低估了缓存请求的输入,因为提供方的输入 token 不包含缓存读取和缓存写入 token。现在它们在调用和流式路径中都是三者之和。对于 Anthropic 流式传输,当后续增量省略 message_start 的缓存用量时,会保留该用量。
  • MCP:与另一个工具冲突的工具会保留其解析后的名称。在 tools/list_changed 刷新之后,两个暴露同一工具的连接可能获得相同名称,回调提供方会失败并报出“Multiple tools with the same name”。
  • Typesense:当 topK 大于 10 时,搜索最多只返回 10 个文档;自动配置的存储从未从集合 schema 读取向量大小。当 Typesense 返回的导入结果少于发送的文档数时(例如某个文档无法序列化为 JSON),新的 upsert 也会失败,而不是报告成功。

依赖与构建

  • Spring Boot 4.2.0-RC2
  • MCP Java SDK 2.2.0-M2
  • Anthropic Java SDK 3.4.0
  • TypeSafe Java SDK 0.4.0

贡献者

感谢所有参与此版本工作的人:

@Ajax-1、@iuliiasobolevska、@sobychacko、@ThomasVitale、@tzolov 和 @dimitarproynov

后续计划

接下来是 2.1.0 GA。在此之前,我们将重点收集对上述功能的反馈。

开始使用

在现有应用中试用该候选版本,或在 start.spring.io 上新建应用。DecisionModel 和新的 MCP“现代”服务器都是新 API,我们希望在 GA 之前了解它们如何契合你的用例——如果发现问题,请提交 issue;如果想告诉我们接下来希望看到什么,请发起讨论。

资源

项目页面 | GitHub | Issues | 参考文档