Spring AI 入门后第一个坑:为什么我的 Tool Calling 死活不触发?我排查了 5 个原因
你的模型支持吗?你的工具是挂在默认配置里,还是挂在这次请求里?你是不是以为默认工具和运行时工具会自动合并?写得是否足够具体?参数 schema 是否能让模型看懂?你有没有把打开?如果关闭了自动执行,你有没有自己继续跑 tool loop?工具方法有没有使用OptionalMonoFlux之类的类型?你现在看到的是“没触发”,还是“触发了但你没观察到”?把这 9 个点过一遍,大多数 Spring A
刚开始用 Spring AI 的时候,我以为 Tool Calling 这件事很简单:写个 @Tool,再把工具挂到 ChatClient 上,模型自然就会调。
结果真上手以后,最常见的场面是这样的:
- 代码能跑
- 模型也正常返回
- 但是工具就是不调用
- 你明明觉得这一步“应该”触发工具,它偏偏开始一本正经地胡说八道
这个坑很适合作为 Spring AI 入门后的第一篇排障文,因为它不是单一原因造成的。很多时候,不是 Spring AI 坏了,而是你以为“已经把工具接上了”,实际上只完成了一半。
这篇我不讲大而全的原理,只讲最容易卡人的 5 个排查点。你可以把它当成一份速查表。
先看一个最小例子
Spring AI 官方文档里给了一个很经典的例子:问“明天星期几”,模型本身不知道今天是哪天,所以它需要先拿到当前时间,再继续回答。
一个最小工具可以这么写:
import java.time.LocalDateTime;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
return LocalDateTime.now()
.atZone(LocaleContextHolder.getTimeZone().toZoneId())
.toString();
}
}
调用侧类似这样:
String response = ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
按官方文档的说明,这类问题需要实时信息,模型本身答不出来。只有把工具定义带进这次请求,它才有机会请求工具,再利用工具结果生成最终回答。
如果你现在的代码结构和这个例子差不多,但工具还是不触发,通常就往下看这 5 个点。
排查点 1:你选的模型根本不支持 Tool Calling
这是第一个该查的,而且很多人反而最后才查。
Spring AI 官方文档里有一张 Chat Models Comparison 表,里面单独列了 Tools/Function Calling 这一列。意思很明确:不是所有接进 Spring AI 的模型都支持工具调用。
也就是说,下面这件事要先确认:
- Spring AI 支持这个模型接入
- 这个模型本身也支持
Tools/Function Calling
如果模型不支持,你把工具注册得再漂亮也没用。模型压根不会返回 tool call 请求。
我自己的建议是,排查 Tool Calling 问题时先别怀疑 Spring 代码,先看模型能力表。这个动作能省掉你后面一大截无效调试。
排查点 2:工具没有真正挂到“这一次请求”上
这个坑特别像“看起来已经配了,实际上没生效”。
Spring AI 的文档里有一个很容易被忽略的点:如果你同时配置了默认工具和运行时工具,那么运行时工具会完全覆盖默认工具,不是合并。
比如你这么写:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new CommonTools())
.build();
String response = chatClient.prompt("帮我查一下今天日期")
.tools(new OrderTools())
.call()
.content();
很多人会下意识以为这次请求同时拥有 CommonTools 和 OrderTools。
其实不是。按官方文档的说明,这里的运行时工具会把默认工具整个顶掉。结果就是:
- 你以为日期工具还在
- 实际上这次请求只拿到了
OrderTools - 模型自然不会调用日期工具
如果你现在的项目里既用了 defaultTools() / defaultToolCallbacks(),又在某些请求上单独用了 .tools(),这里一定要查。
这是我见过最像“死活不触发”的假象之一。
排查点 3:工具描述太烂,模型根本不知道什么时候该用它
这个问题不算框架 bug,但非常常见。
Spring AI 官方文档在 @Tool 那一节写得很直白:description 对模型理解工具用途非常关键。如果你不给,默认就会退回到方法名;如果描述写得太差,模型可能不会在该用的时候用,或者用错。
很多入门代码都是这种写法:
@Tool
String query() {
...
}
从 Java 角度看没问题,从模型角度看问题很大。query 到底查什么?什么时候查?返回什么?模型不知道。
更靠谱一点的写法应该是把“用途”和“触发条件”写进去:
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
...
}
如果你的工具还有参数,最好把参数语义也说清楚。Spring AI 的文档还提到,方法和函数工具最终都会生成输入 schema。schema 和描述都太模糊时,模型就更容易放弃调用。
这里我补一个工程判断:很多人说“模型怎么这么笨,明明该调工具却不调”,本质上不是模型笨,而是你给它的工具定义信息不够用。这一点从官方文档对 description 和 input schema 的强调里是能推出来的。
排查点 4:其实已经触发了,只是你把自动执行关掉了
这也是一个很容易误判的点。
Spring AI 默认会帮你做完工具执行闭环:
- 模型返回 tool call 请求
- Spring AI 执行工具
- 把工具结果再喂回模型
- 模型生成最终回答
按官方文档的说法,这个过程默认是透明的,由 ToolCallingManager 驱动。
但文档也明确写了,工具是否会自动执行,取决于 ToolCallingChatOptions.internalToolExecutionEnabled。默认值是 true。
如果你把它显式改成了 false,那就不是“框架自动帮你调用工具”了,而是“模型把工具请求还给你,你自己继续驱动后续循环”。
像这样:
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(ToolCallbacks.from(new DateTimeTools()))
.internalToolExecutionEnabled(false)
.build();
这个时候,如果你只是简单 chatModel.call(prompt) 一次,然后就盯着结果看,很容易得出一个错误结论:
“Tool Calling 没触发。”
实际上更可能是:
- 模型已经请求了工具
- 只是你没继续执行 tool loop
官方文档对这点给了完整示例:当你选择 user-controlled tool execution 时,需要自己检查 chatResponse.hasToolCalls(),再调用 ToolCallingManager.executeToolCalls(...),然后把新的会话历史继续送回模型。
所以这类问题更准确的描述不是“没触发”,而是“没自动执行完”。
排查点 5:你的工具签名本身就不适合 Spring AI 解析
这一点在一开始不一定最显眼,但很值得早点检查。
Spring AI 官方文档列了方法工具和函数工具的限制:
- 方法工具不支持
Optional - 不支持
CompletableFuture、Future - 不支持
Mono、Flux - 函数工具还不支持 primitive 和集合类型作为输入输出
如果你平时写 Spring 业务代码已经习惯了响应式类型、异步类型或者 Optional,很容易顺手把这些类型塞进工具方法。
比如下面这种写法,我就不建议直接当工具暴露:
@Tool(description = "Query user info")
Mono<UserInfo> queryUserInfo(Long userId) {
...
}
你可能觉得这是正常 Spring 风格,但从 Spring AI 的工具定义和 schema 生成视角看,这种签名并不在推荐范围内。
更稳妥的做法是先把工具边界收窄,返回一个简单、可序列化、语义明确的对象或字符串。别一上来就把业务层最复杂的响应类型扔给工具层。
为什么这个问题特别难排
因为 Spring AI 默认把工具调用过程包起来了。
官方文档明确提到,框架默认模式下,工具执行过程中那些内部消息不会直接暴露给你。你最后常常只能看到一个“模型给了你答案”,却看不到中间有没有请求工具、请求了什么、哪一步断掉了。
这也是为什么很多人会有一种很强的错觉:代码没报错,所以工具应该没问题。
其实不是。很多 Tool Calling 问题根本不报错,它只是静悄悄地退回普通聊天回答。
如果你想把这条链路看清楚,官方文档给的建议是走 ToolCallAdvisor 这条路。它的一个直接好处就是可观察性更好,advisor 链可以看到每次工具调用迭代。
对排障来说,这个信息很值钱。
我会怎么排这个问题
如果是我自己接手一个“工具就是不触发”的项目,我会按这个顺序查:
- 先看模型是否支持
Tools/Function Calling - 再确认这次请求到底挂了哪些工具
- 再看工具描述和参数 schema 是否像人话
- 再确认是不是把
internalToolExecutionEnabled关掉了 - 最后再检查工具方法签名是不是超出了 Spring AI 支持范围
这个顺序的好处是,前面几步成本最低,而且命中率很高。
最后给一份速查清单
- 你的模型支持
Tools/Function Calling吗? - 你的工具是挂在默认配置里,还是挂在这次请求里?
- 你是不是以为默认工具和运行时工具会自动合并?
@Tool(description = "...")写得是否足够具体?- 参数 schema 是否能让模型看懂?
- 你有没有把
internalToolExecutionEnabled(false)打开? - 如果关闭了自动执行,你有没有自己继续跑 tool loop?
- 工具方法有没有使用
Optional、Mono、Flux、CompletableFuture之类的类型? - 你现在看到的是“没触发”,还是“触发了但你没观察到”?
把这 9 个点过一遍,大多数 Spring AI Tool Calling 的入门坑都能缩小到很小范围。
结尾
Tool Calling 这件事最烦的地方,不是它难,而是它太像“已经接好了”。很多时候你离跑通只差一个细节,但如果没有排查顺序,就会一直在错误方向上怀疑框架、怀疑模型、怀疑网络,最后把时间耗光。
下一篇我准备继续写另一个高频坑:Java Agent 上下文总是丢?我排查了 Spring AI 会话记忆的 5 个误区。这两个问题经常连在一起出现,前一个没理顺,后一个也很难看明白。
参考资料
- Spring AI Tool Calling Reference: https://docs.spring.io/spring-ai/reference/api/tools.html
- Spring AI Chat Models Comparison: https://docs.spring.io/spring-ai/reference/2.0/api/chat/comparison.html
- Spring AI GitHub Repo: https://github.com/spring-projects/spring-ai
说明
文中的以下结论直接来自 Spring AI 官方文档:
- 不是所有模型都支持
Tools/Function Calling - 运行时工具会覆盖默认工具,而不是和默认工具合并
description对模型正确使用工具非常关键internalToolExecutionEnabled默认是true,关闭后需要自己执行 tool loop- 方法工具和函数工具存在明确的类型限制
文中的这些表述属于我的工程归纳,不是官方原句:
- “看起来没触发,实际上是没自动执行完”
- “工具描述太烂,模型不知道什么时候该用它”
- “很多 Tool Calling 问题根本不报错,只会静悄悄退回普通回答”
更多推荐



所有评论(0)