首页 / Spring AI 入门教程 / 从 FunctionCallback 迁移到 ToolCallback

Spring AI 入门教程

从 FunctionCallback 迁移到 ToolCallback

本教程共 45 篇 · 第 26 篇 · 更新于 2026-08-16 · 约 9 分钟阅读

Spring AI迁移FunctionCallbackToolCallbackMethodToolCallbackFunctionToolCallback升级@Tool

本节目标:对照旧写法完成迁移。学完你能把 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(新)
FunctionCallbackToolCallback
FunctionCallback.builder().function()FunctionToolCallback.builder()
FunctionCallback.builder().method()MethodToolCallback.builder()
FunctionCallingOptionsToolCallingChatOptions
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 两个类本身也废弃了。

迁移步骤,照着走:

  1. 全局搜索 FunctionCallback、FunctionCallingOptions、defaultFunctions、functions( 四个关键词,列出所有出现位置。
  2. 函数式工具:FunctionCallback.builder().function() 改 FunctionToolCallback.builder(name, fn)。
  3. 方法式工具:改 MethodToolCallback + ReflectionUtils,或直接换 @Tool 注解。
  4. ChatClient 调用处:functions() 改 tools(),defaultFunctions() 改 defaultTools()。
  5. Options:FunctionCallingOptions 改 ToolCallingChatOptions,functionCallbacks() 改 toolCallbacks()。
  6. Bean 返回类型:FunctionCallback 改 ToolCallback。
  7. 编译运行,把 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(模型上下文协议),工具将不再局限于本地方法。