Spring AI 的 Tool Calling 失效,最坑的是它往往“静默”:编译不报错、启动不报错,只是模型死活不调你的工具。三个常见根因:
defaultOptions用了不支持工具调用的DefaultChatOptions;.tools(...)的重载把FunctionToolCallback吞了;工具执行阶段新建 options 实例导致 callback 丢失。先用“请求里有没有tools字段”这一条把范围切开,再对号入座。
Issue & Env(案发现场与环境)
- 业务场景:Spring Boot 应用用
ChatClient+@Tool声明了工具方法(查天气、查订单之类),期望模型在合适时机自动调用,实际模型一律不调,直接用自己的知识回答。 - 环境:Spring AI 1.1.x(按 1.1.8),Spring Boot 3.5.x(按 3.5.15),JDK 17。
ChatClient通过defaultOptions装配请求选项。 - 一个反直觉的地方:这类故障大多是静默失效。代码能编译、应用能启动、日志里没有 ERROR,只有模型的行为不对劲。三个根因里只有“执行阶段丢 callback”这一种会显式抛异常。
StackTrace(堆栈日志)
坑 1(defaultOptions 类型不对)和坑 2(.tools() 重载吞参数)都不抛异常,可观察到的输出只有正常 INFO 日志,没有任何堆栈:
(无异常,日志停在正常 INFO,模型直接给出不含工具调用的回答)
坑 3(执行阶段 callback 丢失)会显式抛错,报错原文:
java.lang.IllegalStateException: No ToolCallback found for tool name: get_weather
完整堆栈从框架找回调一路回到你的业务调用:
java.lang.IllegalStateException: No ToolCallback found for tool name: get_weather
at org.springframework.ai.model.tool.DefaultToolCallingManager.executeToolCalls(DefaultToolCallingManager.java:166)
at org.springframework.ai.chat.model.AbstractToolCallSupport.callWithToolSupport(AbstractToolCallSupport.java:245)
at org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$InternalChatClientRequest.call(DefaultChatClient.java:686)
at com.example.demo.assistant.WeatherAssistant.askWeather(WeatherAssistant.java:38)
at com.example.demo.assistant.WeatherController.ask(WeatherController.java:25)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
判断技巧:工具没被调用时,先看发给模型的请求里有没有 tools 字段。开 DEBUG 日志或临时打印请求体即可。没有 tools,问题在工具注册或 options 类型(坑 1、坑 2);有 tools、模型也发起了调用却抛 No ToolCallback found,问题在回调查找阶段(坑 3)。
Reproduce(复现代码)
最小复现只需要一个 ChatClient Bean,把 ChatOptions.builder() 的结果塞进 defaultOptions:
@Configuration
class ChatClientConfig {
@Bean
ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultOptions(ChatOptions.builder() // ❌ 生成 DefaultChatOptions
.model("gpt-4o-mini")
.temperature(0.7)
.build())
.build();
}
}
配一个 @Tool 方法:
@Component
class WeatherTools {
@Tool(description = "查询指定城市的天气")
String getWeather(String city) {
return city + ":晴,26 摄氏度";
}
}
调用侧:
String answer = chatClient.prompt()
.user("北京今天天气怎么样?")
.call()
.content();
预期模型调用 get_weather,实际它直接编一段回答;换成坑 3 的写法,则变成调用时抛 IllegalStateException。
Root Cause(根因分析)
坑 1:defaultOptions 用了 DefaultChatOptions
ChatOptions.builder() 生成的是 DefaultChatOptions,它不支持 Tool Calling。工具回调即便被包进去,构建请求时也会被丢弃(spring-ai issue #3392)。
// ❌ 工具调用失效
.defaultOptions(ChatOptions.builder()
.model(model)
.temperature(0.7)
.build())
// ✅ 用支持工具调用的选项
.defaultOptions(ToolCallingChatOptions.builder()
.model(model)
.temperature(0.7)
.build())
这是 Spring AI 1.x 的已知问题,需要换 ToolCallingChatOptions(或模型特化的 options,如 DeepSeekChatOptions)来搭 defaultOptions。
坑 2:.tools(Object…) 重载吞掉 FunctionToolCallback
ChatClient 的 .tools(...) 有多个重载。传 FunctionToolCallback 实例时,编译器可能匹配到 tools(Object... toolObjects),它内部走 ToolCallbacks.from(toolObjects),而后者只接受 Method tool,FunctionToolCallback 被静默忽略,且没有编译错误(spring-ai issue #2495)。
// ❌ 可能命中 tools(Object...),FunctionToolCallback 不生效
FunctionToolCallback cb1 = ...;
FunctionToolCallback cb2 = ...;
client.prompt().tools(cb1, cb2).call();
// ✅ 声明成 ToolCallback 类型,或逐个注册
ToolCallback cb1 = ...;
ToolCallback cb2 = ...;
client.prompt().tools(cb1, cb2).call();
// 或者
client.prompt().tools(cb1).tools(cb2).call();
坑 3:中途换了 options 实例,callback 丢失
工具回调只在 .call() 阶段被写进 ChatClient 初始的那个 options 对象。若在自定义 tool 执行逻辑里为模型换了一个全新的 options 实例,新实例里没有 callback,框架转成 ToolCallingOptions 取回调时就是空的,于是抛 No ToolCallback found(spring-ai issue #4734)。判定方法:看 options 是不是又新建了一份。
The Fix(解决方案)
1. 用 ToolCallingChatOptions 配 defaultOptions
需要 Tool Calling 就别用裸 ChatOptions.builder():
@Bean
ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultOptions(ToolCallingChatOptions.builder()
.model("gpt-4o-mini")
.temperature(0.7)
.build())
.build();
}
2. 工具注册用 ToolCallback 类型,避免重载歧义
ToolCallback weatherCallback = FunctionToolCallback.builder("get_weather", weatherFn)
.description("查询指定城市的天气")
.inputType(WeatherRequest.class)
.build();
String answer = chatClient.prompt()
.user("北京今天天气怎么样?")
.tools(weatherCallback)
.call()
.content();
声明变量时用 ToolCallback 而不是 FunctionToolCallback,或者把 .tools(...) 拆成逐个调用。
3. 别在 tool 执行链路里新建 options
复用最初构建 ChatClient 时的那个 options 对象;需要改动就 .copy() 复制它,回调仍在:
var base = ToolCallingChatOptions.builder()
.model("gpt-4o-mini")
.build();
// ✅ 复制而非新建,回调随 options 一起带过去
var adjusted = base.copy();
如果沿用旧的裸 ChatOptions 装配思路,先对照 ChatModel 与 ChatClient 的依赖配置 检查 options 类型。
小结
- Tool Calling 失效是典型的静默失效,先查请求里有没有
tools字段,用它切开“注册/选项”与“回调查找”两段。 - 三个高发坑:
DefaultChatOptions不支持工具、.tools(Object...)重载吞FunctionToolCallback、执行阶段新建 options 丢回调。 - 记一句:要函数调用,用
ToolCallingChatOptions,注册时声明成ToolCallback类型,不要中途换 options。 - 三种根因里只有第三种会显式抛
No ToolCallback found,前两种只能靠观察上游请求来定位。 - MCP 场景下工具不生效的排查另见 Spring AI MCP stdio 日志污染 stdout。
References(参考来源)
- configuring defaultOptions can cause defaultToolCallbacks failure — spring-ai issue #3392github
- The overload tools method causes FunctionToolCallback not taking effect — spring-ai issue #2495github
- No ToolCallback found for tool name — spring-ai issue #4734github
- Tool Calling — Spring AI 官方文档spring-docs
- Spring AI ChatClient 工具调用失效问题解析 — CSDNcsdn
