Google ADK Java 1.14.0 把官方 Spring Boot 客服样例做成了「ADK FunctionTool + Spring AI @Tool」同跑:SpringAiToolset 把 @Tool(returnDirect = true) 映射成 ADK 的 skipSummarization=true,工具结果可以直接回给用户,不必再进一轮模型总结。本地用 Spring Boot 4.0.2、google-adk-spring-ai 1.14.0 和 Spring AI 2.0.1 起了一个无 Web 进程,直接调用工具层;代码已编译验证,未实际调用外部模型 API。

1.14.0 的 GitHub Release 发布时间是 2026-11-02 23:21:36(Asia/Shanghai)。Maven Central 上 com.google.adk:google-adk 与 google-adk-spring-ai 的 1.14.0 均可下载,metadata lastUpdated 换算为 2026-11-03 00:33:03(Asia/Shanghai)。Release Features 里和本文相关的三条是:Spring Boot chat backed by ADK、streams replies and mixes ADK and Spring AI tools、bidirectional conversion between ADK and Spring AI。对应提交分别是 bca0591(2026-10-29 19:36:36 Asia/Shanghai)、7391fa8(2026-10-29 23:35:38 Asia/Shanghai)、9949265(2026-10-27 21:00:07 Asia/Shanghai)。同版本还带了工作流暂停/恢复等能力,本文只核工具混接与 returnDirect 映射,不展开那些路径。

ADK Java 1.13 工作流图那篇停在图调度;1.14.0 的 examples/spring_chat_advanced 则面向 Spring Boot 开发者:订单服务用 ADK FunctionTool,物流工具用 Spring AI @Tool,再由 SpringAiToolset 交给同一个 LlmAgent。

样例怎么拼工具

官方高级样例的配置大致是这样:

ImmutableList<Object> tools =
    ImmutableList.of(
        FunctionTool.create(orderService, "getOrderStatus"),
        FunctionTool.create(orderService, "getDeliveryEstimate"),
        new SpringAiToolset(
            MethodToolCallbackProvider.builder().toolObjects(shippingTools).build()));
return LlmAgent.builder()
    .name("order_support_agent")
    .model(model)
    .instruction("... {" + ShippingTools.LAST_TRACKED_ORDER + "?}")
    .tools(tools)
    .build();

ShippingTools.getTracking 会从 Spring AI 的 ToolContext 里取出 ToolConverter.ADK_TOOL_CONTEXT_KEY(常量值 adk_tool_context),再写入 ADK session state。官方注释写明:带 returnDirect 的工具结果会直接给用户,同轮其它工具仍会执行,但不再走模型回复。SpringAiTool 的实现里,当 callback metadata 的 returnDirect() 为 true 时,会调用 toolContext.actions().setSkipSummarization(true)。

有一点要小心:如果 Spring 给 bean 做了代理,FunctionTool.create(bean, "methodName") 可能读不到具体类上的 @Schema。官方 README 建议改传 OrderService.class 上的 Method。本地验证里用的是 FunctionTool.create(orderService, OrderService.class.getMethod(...)),调用才正常返回。

本地验证

环境:JDK 21.0.12.1、Maven 3.9.9、Spring Boot 4.0.2、ADK 1.14.0、Spring AI 2.0.1。应用是 web-application-type=none,注入一个从不调用的 stub ChatModel,只在 CommandLineRunner 里跑工具。

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>4.0.2</version>
</parent>

<dependency>
  <groupId>com.google.adk</groupId>
  <artifactId>google-adk-spring-ai</artifactId>
  <version>1.14.0</version>
</dependency>
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-model</artifactId>
  <!-- managed by spring-ai-bom 2.0.1 -->
</dependency>
mvn -q -DskipTests package
mvn -q spring-boot:run

启动后的关键输出如下(原文摘录):

=== ADK Java 1.14.0 SpringAiToolset + FunctionTool offline verify ===
adk.version=1.14.0 spring-ai.version=2.0.1 spring-boot=4.0.2
no remote model API call
--- tool mix ---
adk.FunctionTool names=[getOrderStatus, getDeliveryEstimate]
SpringAiToolset names=[talkToHuman, getTracking]
FunctionTool declaration name=getOrderStatus desc=Looks up the status and item of an order.
FunctionTool declaration name=getDeliveryEstimate desc=Returns the delivery estimate of an order.
SpringAiTool declaration name=talkToHuman desc=Hands the conversation to a human support agent when the user asks for one.
SpringAiTool declaration name=getTracking desc=Returns the carrier and tracking number of a shipped order.
--- getTracking via SpringAiTool ---
result={orderId=1001, tracking=DHL JD014600001001}
ADK_TOOL_CONTEXT_KEY=adk_tool_context
session.state.lastTrackedOrder=1001
skipSummarization.after.getTracking=Optional.empty
--- talkToHuman returnDirect ---
result={result=A human support agent will join this chat within 5 minutes.}
skipSummarization.after.talkToHuman=Optional[true]
--- FunctionTool getOrderStatus ---
result={orderId=1002, status=processing, item=USB-C hub}
=== DONE ===

returnDirect 落成 skipSummarization

同一次进程里,getTracking(没有 returnDirect)跑完后 skipSummarization 仍是 Optional.empty;talkToHuman(returnDirect = true)跑完后变成 Optional[true]。工具返回值被包成 {result=A human support agent will join this chat within 5 minutes.}。这是源码层的映射结果,与模型对话日志无关。

@Tool(
    description = "Hands the conversation to a human support agent when the user asks for one.",
    returnDirect = true)
String talkToHuman() {
  return "A human support agent will join this chat within 5 minutes.";
}

Spring AI 工具也能改 ADK session state

getTracking("1001") 返回 tracking=DHL JD014600001001,同时把 lastTrackedOrder=1001 写进 ADK ToolContext.state()。官方高级样例的 agent instruction 用 {lastTrackedOrder?} 读这个键,所以物流工具改的状态可以直接进下一轮提示词。键名常量是 ToolConverter.ADK_TOOL_CONTEXT_KEY = "adk_tool_context"。

if (toolContext.getContext().get(ToolConverter.ADK_TOOL_CONTEXT_KEY)
    instanceof com.google.adk.tools.ToolContext adkToolContext) {
  adkToolContext.state().put(LAST_TRACKED_ORDER, orderId);
}

以下是笔者对官方说明的归纳,与 Release 原文区分开:已经有 Spring AI @Tool 资产的团队,可以用 SpringAiToolset 挂进 ADK agent,而不必先把工具全部改写成 FunctionTool;需要「工具结果直接出给用户」时,继续用 Spring AI 的 returnDirect,ADK 侧会落到 skipSummarization。

— 感谢阅读 —
广告

一起交流

分享你的思考,让讨论更进一步。