从 FunctionCallback 迁移到 ToolCallback
本教程共 45 篇 · 第 26 篇 · 更新于 2026-08-16 · 约 9 分钟阅读
本节目标:对照旧写法完成迁移。学完你能把 1.x 的 FunctionCallback 代码逐行改写成 2.0 的 ToolCallback,并知道哪些方法已经废弃。
26.1 为什么要迁移
2.0 把工具调用 API 整体翻新了一遍。原因有两个:术语统一,以及能力扩展。业界普遍叫”工具调用”(Tool Calling),“函数”的叫法容易和编程里的函数混淆。同时新 API 把工具定义和执行拆开,为后续 MCP、动态工具解析这些能力铺路。
旧 API 的状态要说清楚:FunctionCallback 及相关方法在 2.0.0 起标记废弃,当前版本仍可用以保证向后兼容,但会在后续大版本移除。官方建议尽早迁移。至于注解,@Tool 自 1.0 起就是 Spring AI 官方的工具注解,2.0 延续并把它变成声明式工具的推荐写法,写新代码一律用 @Tool。你的项目还在 1.x?升级到 2.0 时顺手迁移即可。
迁移本身不复杂。绝大部分改动是机械替换,照着本章的对照表改就能编译通过。真正要动脑的只有方法式工具一处,它有对应的更优解。
26.2 新旧 API 的设计差异
动手改代码前,先理解两个 API 的差别。旧 API 里一个 FunctionCallback 对象同时承担两件事:告诉模型”有什么工具可用”,以及”收到调用怎么执行”。定义和执行绑死在一起。
新 API 把这两件事拆开了。ToolCallback 内部由两部分组成:
- ToolDefinition:工具的名称、描述、参数 JSON Schema,只给模型看。
- call() 方法:真正的执行逻辑,只给应用看。
拆开之后,同一份定义可以配不同的实现,工具的执行逻辑也可以脱离定义单独测试。这个设计是后面几章讲 MCP 的基础,MCP 客户端本质上就是一个从远端获取 ToolDefinition 的特殊 ToolCallback。
先看总览,七个对应关系:
| 1.x(废弃) | 2.0(新) |
|---|---|
| FunctionCallback | ToolCallback |
| FunctionCallback.builder().function() | FunctionToolCallback.builder() |
| FunctionCallback.builder().method() | MethodToolCallback.builder() |
| FunctionCallingOptions | ToolCallingChatOptions |
| ChatClient.Builder.defaultFunctions() | ChatClient.Builder.defaultTools() |
| ChatClient.functions() | ChatClient.tools() |
| FunctionCallingOptions.builder().functionCallbacks() | ToolCallingChatOptions.builder().toolCallbacks() |
26.3 场景一:函数式工具
最典型的写法,注册一个函数对象当工具。旧代码:
FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build();
新代码:
FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build();
变化很小:builder 从无参改为带两个参数(工具名 + 函数对象),名字从 FunctionCallback 换成 FunctionToolCallback。后面的链式调用基本原样保留。
26.4 场景二:ChatClient 用法
请求里注册工具的入口变了。旧代码:
String response = ChatClient.create(chatModel)
.prompt()
.user("What's the weather like in San Francisco?")
.functions(FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.call()
.content();
新代码:
String response = ChatClient.create(chatModel)
.prompt()
.user("What's the weather like in San Francisco?")
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.call()
.content();
只是 functions() 换成 tools()。记住这个入口:ChatClient.RequestSpec.functions() 已废弃,改用 tools()。
26.5 场景三:方法式工具
旧 API 用 method() 指定方法名和参数类型,框架内部通过反射找方法:
FunctionCallback.builder()
.method("getWeatherInLocation", String.class, Unit.class)
.description("Get the weather in location")
.targetClass(TestFunctionClass.class)
.build();
新 API 把这个过程拆开了。方法要自己用 ReflectionUtils 找出来,再交给 MethodToolCallback:
Method toolMethod = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherInLocation");
MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Get the weather in location")
.build())
.toolMethod(toolMethod)
.build();
非静态方法还要额外传目标对象:
MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Description")
.build())
.toolMethod(toolMethod)
.toolObject(targetObject)
.build();
这是迁移中变化最大的一处,也是最容易出错的一处。
Tip方法式工具强烈建议直接用声明式方案:@Tool 注解。不用反射、不用找方法,一行注解解决。见 26.8。
26.6 场景四:Options 配置
工具调用选项从 FunctionCallingOptions 换成 ToolCallingChatOptions。旧代码:
FunctionCallingOptions.builder()
.model(modelName)
.function("weatherFunction")
.build();
新代码:
ToolCallback weatherFunctionCallback = /* ToolCallback 实例,见场景一 */;
ToolCallingChatOptions.builder()
.model(modelName)
.toolCallbacks(weatherFunctionCallback)
.build();
传工具回调对象的对应关系是:functionCallbacks() → toolCallbacks()。注意 1.x 里按名引用工具的方式(function(“name”))在 2.0 已被移除:toolNames() 从所有 ChatOptions 类和 ChatClient 上删除,按 Bean 名解析 Function 的 SpringBeanToolCallbackResolver 一并废弃。工具要么显式传 ToolCallback 对象,要么用 @Tool 声明后通过 tools() 传入。
26.7 场景五:默认工具与 Bean 配置
Builder 的默认工具。旧代码:
ChatClient.builder(chatModel)
.defaultFunctions(FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.build();
新代码:
ChatClient.builder(chatModel)
.defaultTools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.build();
defaultFunctions(String…) 和 defaultFunctions(FunctionCallback…) 两个重载都已废弃,统一用 defaultTools()。
Bean 配置同理,返回类型从 FunctionCallback 换成 ToolCallback:
@Bean
public ToolCallback weatherFunctionInfo() {
return FunctionToolCallback.builder("WeatherInfo", new MockWeatherService())
.description("Get the current weather")
.inputType(MockWeatherService.Request.class)
.build();
}
类型换成 ToolCallback 后有个额外好处:Spring Boot 自动配置会把所有 ToolCallback Bean 收集进工具解析器,按名称动态解析,第 25 章讲过。
26.8 迁移首选:@Tool 声明式方案
前面五类迁移都是”对着改”。如果你从零写新代码,或者方法式工具较多,直接上 @Tool 注解,代码量最少:
class WeatherTools {
@Tool(description = "Get the weather in location")
public void getWeatherInLocation(String location, Unit unit) {
// ...
}
}
注册同样走 tools():
String response = ChatClient.create(chatModel)
.prompt()
.user("What's the weather like in San Francisco?")
.tools(new WeatherTools())
.call()
.content();
一个类里放多个 @Tool 方法,一次注册。工具名默认取方法名,描述写在注解里,参数 Schema 自动生成。旧 API 里所有手工配置,这里全自动了。
26.9 废弃方法清单与迁移步骤
官方明确废弃的方法,共三个:
- ChatClient.Builder.defaultFunctions(String…)
- ChatClient.Builder.defaultFunctions(FunctionCallback…)
- ChatClient.RequestSpec.functions()
全部改用 tools 系列。FunctionCallback、FunctionCallingOptions 两个类本身也废弃了。
迁移步骤,照着走:
- 全局搜索 FunctionCallback、FunctionCallingOptions、defaultFunctions、functions( 四个关键词,列出所有出现位置。
- 函数式工具:FunctionCallback.builder().function() 改 FunctionToolCallback.builder(name, fn)。
- 方法式工具:改 MethodToolCallback + ReflectionUtils,或直接换 @Tool 注解。
- ChatClient 调用处:functions() 改 tools(),defaultFunctions() 改 defaultTools()。
- Options:FunctionCallingOptions 改 ToolCallingChatOptions,functionCallbacks() 改 toolCallbacks()。
- Bean 返回类型:FunctionCallback 改 ToolCallback。
- 编译运行,把 org.springframework.ai 包日志调到 DEBUG,确认工具调用日志正常。
Note迁移后验证重点:工具名是否保持原样。模型依赖工具名识别工具,改名会导致对话历史里的工具调用失效。@Tool 默认用方法名,如果旧代码里工具名和 1.x 方法名不一致,记得显式指定 name。
26.10 新 API 带来的额外收益
迁移不只是改名字。新 API 顺手解决了旧 API 的几个痛点:
定义与实现分离。 ToolDefinition 独立成接口,同一份工具定义可以复用于不同实现。
错误处理更规范。 工具执行异常统一包装成 ToolExecutionException,配合 ToolExecutionExceptionProcessor 统一处理,第 25 章讲过。
扩展点更多。 ToolCallbackResolver 支持动态解析,ToolCallingManager 支持手动执行循环,这些在 1.x 里都没有对等的抽象。
术语统一。 全链路用 tool 而不是 function,和 OpenAI、Anthropic 等厂商的 API 术语对齐,查文档、看社区讨论都不再有翻译障碍。
26.11 常见问题
迁移后还能看到废弃警告吗? 能。只要代码里还残留 functions()、defaultFunctions() 或 FunctionCallback,编译时就有 Deprecation 警告。警告不报错,但建议清零,方便以后升级。
tools() 还能传工具名字符串吗? 不能。2.0 移除了 toolNames() 和按 Bean 名解析 Function 的 SpringBeanToolCallbackResolver,tools() 只接受 ToolCallback、ToolCallbackProvider 或带 @Tool 方法的对象。ToolCallback Bean 可以先注入再直接传入,工具名写错会在运行时才暴露,传对象则编译期就能发现问题。
1.x 的 MockWeatherService 这类函数类要改吗? 不用。函数对象本身没变,变的只是外面那层 Builder。
方法式工具非要反射吗? 不。用 @Tool 注解就不用碰 ReflectionUtils,这是官方推荐的写法。
26.12 小结
迁移的核心就一句话:functions 系列入口全部换成 tools 系列。函数式工具改动最小,方法式工具建议直接上 @Tool 注解,Options 和 Bean 按对照表替换。旧 API 在 2.0 里还能跑,但会随版本迭代被移除,新代码一律写新 API。
到这里,Spring AI 工具调用的基础、进阶、迁移三章就完整了。第 27 章开始进入 MCP(模型上下文协议),工具将不再局限于本地方法。