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
