一句话先说结论:Spring AI 的 Tool Calling 失效,最坑的是它往往“静默”——编译不报错、启动不报错,只是模型死活不调你的工具。常见三因:
defaultOptions用了不支持的DefaultChatOptions;.tools(...)的重载把FunctionToolCallback吞了;以及工具执行阶段换了个新的 options 实例导致 callback 丢失。
背景
Tool Calling(函数调用)让 LLM 能反过来调用你定义的方法:@Tool 注解声明方法,框架收集成 ToolCallback,把工具描述发给模型,模型决定要不要调、调哪个,框架再执行对应方法回传结果。这条链路里任何一环断掉,表现都是“模型不调工具”,而且没有显式报错。
现象
典型表现:
- 配置了
defaultOptions之后,工具回调再也触发不了;删掉defaultOptions又恢复正常。 - 注册了
FunctionToolCallback,但模型完全无视它。 - 工具明明注册了,模型一调用就抛
IllegalStateException: No ToolCallback found for tool name: xxx。
根因分析
坑 1:defaultOptions 用了 DefaultChatOptions
ChatOptions.builder() 生成的是 DefaultChatOptions,而它不支持 Tool Calling。把工具回调包进去后,构建请求时 tools 列表被丢弃。
// ❌ 工具调用失效
.defaultOptions(ChatOptions.builder()
.model(model)
.temperature(0.7)
.build())
// ✅ 用支持工具调用的选项
.defaultOptions(ToolCallingChatOptions.builder()
.model(model)
.temperature(0.7)
.build())
这是 Spring AI 1.0.0 的一个已知问题(issue #3392)。需要用 ToolCallingChatOptions(或模型特化的 options,如 DeepSeekChatOptions)来搭 defaultOptions。
坑 2:.tools(Object…) 重载吞掉 FunctionToolCallback
ChatClient 的 .tools(...) 有多个重载。当你传的是 FunctionToolCallback 实例时,编译器可能匹配到 tools(Object... toolObjects) 这个重载——它内部 ToolCallbacks.from(toolObjects) 只接受 Method tool,FunctionToolCallback 被静默忽略,且没有编译错误(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(issue #4734)。排查这种问题要看“options 是不是又新建了一份”。
解决方案
快速诊断:看请求里有没有 tools 字段
工具调用失效时的第一件事,是确认发给模型的请求里带没带 tools。最简单是开 DEBUG 日志,或临时打印请求。没带 tools,问题在注册/选项;带了 tools 但没执行,问题在解析或回调。
用 ToolCallingChatOptions 配 defaultOptions
需要 tool calling 就别用裸 ChatOptions.builder(),换 ToolCallingChatOptions。
工具注册用 ToolCallback 类型,避免重载歧义
声明变量时用 ToolCallback 类型,而不是 FunctionToolCallback;或者干脆 .tools(...) 逐个调用。
别在 tool 执行链路里新建 options
复用最初构建 ChatClient 时的那个 options 对象(或通过 .copy() 复制它保留回调),不要 new 一个全新的。
小结
- Tool Calling 失效是典型的“静默失效”,先查请求里有没有 tools 字段。
- 三个高发坑:
DefaultChatOptions不支持工具、.tools(Object...)重载吞 FunctionToolCallback、换 options 丢回调。 - 记一句:要函数调用,用
ToolCallingChatOptions,注册时用ToolCallback类型,别中途换 options。
来源
- configuring defaultOptions can cause defaultToolCallbacks failure — spring-ai issue #3392
- The overload tools method causes FunctionToolCallback not taking effect — spring-ai issue #2495
- No ToolCallback found for tool name — spring-ai issue #4734
- Tool Calling — Spring AI 官方文档
- Spring AI ChatClient 工具调用失效问题解析 — CSDN