news 2026/10/6 18:43:21

Spring AI Alibaba 快速上手:使用 DashScope 实现 Tool Calling(函数调用)实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI Alibaba 快速上手:使用 DashScope 实现 Tool Calling(函数调用)实战

在 AI 应用开发中,让大模型调用外部工具、访问实时数据或执行业务逻辑是常见需求。Spring AI Alibaba 结合阿里云 DashScope(通义千问)提供了简洁的 Tool Calling(函数调用)能力,模型可以自动识别用户意图并调用注册的 Java 方法,再将结果融入对话。

本文以“查询天气”为案例,完整演示在 Spring Boot 项目中集成 DashScope,并分别使用注解(@Tool)、接口(Function)以及FunctionTool.builder + Lambda三种方式定义工具。同时,针对每种工具定义方式,均展示基于ChatModel(底层手动循环) 和ChatClient(自动工具闭环) 的调用实现,共六种组合,并解决ChatClient无法自动注入的问题。

重要前置提示:底层ChatModel#call()只负责和大模型网络通信,不会自动执行工具。如果直接调用,收到模型返回FunctionCall后直接返回JSON结构体,不会执行业务逻辑。想要完整工具调用闭环:
  • 方案A(推荐):使用ChatClient,内部ToolCallingAdvisor自动完成工具执行+多轮对话;
  • 方案B(底层API):使用ChatModel+DefaultToolCallingManager手写while循环驱动工具调用。

2. 环境准备

2.1 添加依赖

在pom.xml中引入 Spring AI Alibaba 的 DashScope 起步依赖:

<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<!-- 请使用最新版本,例如 1.0.0-M3 -->
</dependency>

提示:建议在<dependencyManagement>中引入 Spring AI Alibaba BOM 统一管理版本。

2.2 配置 application.properties

server.port=8013

# 设置全局编码格式
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8

spring.application.name=SAA-13ToolCalling

# ====SpringAIAlibaba Config=============
spring.ai.dashscope.api-key=${aliQwen-api}

请提前在阿里云开通 DashScope 服务并获取 API Key,设置环境变量aliQwen-api=你的key。

3. 定义工具:三种方式

3.1 方式一:使用 @Tool 注解(声明式工具)

使用@Tool注解标记 Java 方法,Spring AI 会自动解析方法签名、参数和描述,生成可供大模型调用的工具元数据。

import org.springframework.ai.tool.annotation.Tool;

public class WeatherTools {

/**
* 查询指定城市的天气
* returnDirect = false 表示工具结果会再次交给大模型,由大模型组织最终回复
*/
@Tool(description = "查询指定城市的天气情况", returnDirect = false)
public String getWeather(String city) {
// 实际项目中可调用第三方天气 API,这里用模拟数据演示
return String.format("%s:晴,气温 25℃,湿度 40%%", city);
}
}

关键参数说明:

  • description:工具的描述信息,大模型会根据它判断是否以及何时调用该函数。
  • returnDirect:
    • true:工具返回后直接作为最终响应,不再调用大模型。
    • false:工具结果会送回给大模型,由大模型结合上下文生成更自然的回答。

3.2 方式二:实现 Function 接口(编程式工具)

通过实现java.util.function.Function<T, R>接口,并包装为FunctionTool,可以更灵活地控制工具逻辑,适合复杂业务场景,便于做代理、鉴权、单元测试。

① 定义入参 record

public record WeatherRequest(String city) {}

② 实现 Function 接口

import org.springframework.stereotype.Component;
import java.util.function.Function;

@Component
public class WeatherTool implements Function<WeatherRequest, String> {

@Override
public String apply(WeatherRequest request) {
// 实际项目中可在此调用天气 API
String city = request.city();
return String.format("%s:多云,气温 22℃,风力 3 级", city);
}
}

③ 包装为 FunctionTool
⚠️不推荐直接new FunctionTool(weatherTool)无元数据构造,必须通过builder设置name、description,大模型才能识别工具。
生产最佳实践:不要在Controller方法内每次请求构建FunctionTool,统一在配置类注册为Bean复用。

3.3 方式三:FunctionTool.builder + Lambda 编程构建(进阶动态工具)

这种方式不需要编写注解,也不需要实现Function接口,直接使用 Lambda 表达式定义函数逻辑,并通过FunctionTool.builder构建工具。它最大的优势是灵活:可以动态生成工具、临时定义逻辑,尤其适合需要根据运行时条件生成不同工具的场景。

这里继续复用 3.2 中定义的WeatherRequestrecord 作为入参模型。

import org.springframework.ai.tool.function.FunctionTool;
import java.util.function.Function;

public class WeatherToolLambda {

/**
* 使用 Lambda 定义天气查询逻辑
*/
public static final Function<WeatherRequest, String> WEATHER_FUNCTION = request -> {
String city = request.city();
return String.format("%s:阴,气温 18℃,风力 2 级", city);
};

/**
* 通过 FunctionTool.builder 构建 FunctionTool
*/
public static FunctionTool createWeatherTool() {
return FunctionTool.builder("getWeather", WEATHER_FUNCTION)
.description("查询指定城市的天气情况")
.inputType(WeatherRequest.class)
.returnDirect(false)
.build();
}
}

关键参数说明:

  • name("getWeather"):工具名称,模型返回的函数调用请求会使用该名称。
  • description(...):工具描述,用于模型判断是否调用。
  • inputType(WeatherRequest.class):指定入参类型,Spring AI 会据此生成 JSON Schema。
  • returnDirect(false):工具结果是否直接返回,默认false。

4. 使用 ChatModel 进行 Tool Calling(底层手动循环)

使用底层ChatModel必须引入DefaultToolCallingManager手动驱动工具执行循环;否则只能拿到FunctionCall JSON,不会执行业务工具。

4.1 手动配置 ChatClient(解决自动注入问题)

在当前 Spring AI Alibaba 版本中,ChatClient默认不会自动注入,需要通过@Configuration显式注册 Bean。
同时将Function接口包装后的FunctionTool注册为Bean,Controller直接注入复用,避免每次请求重复构建对象。

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.tool.function.FunctionTool;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SaaLLMConfig {

@Bean
public ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel).build();
}

/**
* 将Function接口实现包装为FunctionTool,注册为单例Bean复用
*/
@Bean
public FunctionTool queryWeatherFunctionTool(WeatherTool weatherTool){
return FunctionTool.builder(weatherTool)
.name("queryWeather")
.description("查询指定城市天气情况")
.build();
}
}

4.2 注解方式 + ChatModel(手动循环)

import com.example.study.tools.WeatherTools;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.manager.DefaultToolCallingManager;
import org.springframework.ai.tool.manager.ToolCallingManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ToolCallingController {

@Resource
private ChatModel chatModel;
private final ToolCallingManager toolCallingManager = new DefaultToolCallingManager();

@GetMapping("/toolcall/chat-annotation")
public String chatWithAnnotation(@RequestParam(name = "msg", defaultValue = "北京天气怎么样") String msg) {
// 1. 将注解式工具注册到回调数组
ToolCallback[] tools = ToolCallbacks.from(new WeatherTools());

// 2. 构建带有工具回调的 ChatOptions
var options = ToolCallingChatOptions.builder()
.toolCallbacks(tools)
.build();

// 3. 组装 Prompt
Prompt prompt = new Prompt(msg, options);
var response = chatModel.call(prompt);

// 手动驱动工具调用循环,最大循环次数防止死循环
int maxRound = 5;
int round = 0;
while (response.hasToolCalls() && round < maxRound) {
var execResult = toolCallingManager.executeToolCalls(prompt, response);
prompt = execResult.conversationHistory();
response = chatModel.call(prompt);
round++;
}
return response.getResult().getOutput().getText();
}
}

4.3 接口方式 + ChatModel(手动循环)

import jakarta.annotation.Resource;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionTool;
import org.springframework.ai.tool.manager.DefaultToolCallingManager;
import org.springframework.ai.tool.manager.ToolCallingManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ToolCallingController {

@Resource
private ChatModel chatModel;
// 直接注入配置类构建完成的FunctionTool Bean,不再重复builder
@Resource
private FunctionTool queryWeatherFunctionTool;
private final ToolCallingManager toolCallingManager = new DefaultToolCallingManager();

@GetMapping("/toolcall/chat-function")
public String chatWithFunction(@RequestParam(name = "msg", defaultValue = "上海天气如何") String msg) {
ToolCallback[] tools = new ToolCallback[]{queryWeatherFunctionTool};

var options = ToolCallingChatOptions.builder()
.toolCallbacks(tools)
.build();

Prompt prompt = new Prompt(msg, options);
var response = chatModel.call(prompt);

int maxRound = 5;
int round = 0;
while (response.hasToolCalls() && round < maxRound) {
var execResult = toolCallingManager.executeToolCalls(prompt, response);
prompt = execResult.conversationHistory();
response = chatModel.call(prompt);
round++;
}
return response.getResult().getOutput().getText();
}
}

4.4 Lambda 方式 + ChatModel(手动循环)

import com.example.study.tools.WeatherToolLambda;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionTool;
import org.springframework.ai.tool.manager.DefaultToolCallingManager;
import org.springframework.ai.tool.manager.ToolCallingManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ToolCallingController {

@Resource
private ChatModel chatModel;
private final ToolCallingManager toolCallingManager = new DefaultToolCallingManager();

@GetMapping("/toolcall/chat-lambda")
public String chatWithLambda(@RequestParam(name = "msg", defaultValue = "杭州天气怎么样") String msg) {
FunctionTool functionTool = WeatherToolLambda.createWeatherTool();
ToolCallback[] tools = new ToolCallback[]{functionTool};

var options = ToolCallingChatOptions.builder()
.toolCallbacks(tools)
.build();

Prompt prompt = new Prompt(msg, options);
var response = chatModel.call(prompt);

int maxRound = 5;
int round = 0;
while (response.hasToolCalls() && round < maxRound) {
var execResult = toolCallingManager.executeToolCalls(prompt, response);
prompt = execResult.conversationHistory();
response = chatModel.call(prompt);
round++;
}
return response.getResult().getOutput().getText();
}
}

5. 使用 ChatClient 进行 Tool Calling(自动闭环,推荐)

ChatClient内置ToolCallingAdvisor,自动完成工具调用循环,不需要手动写while循环,支持流式返回。

5.1 注解方式 + ChatClient 调用

import com.example.study.tools.WeatherTools;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
public class ToolCallingController {

@Resource
private ChatClient chatClient; // 注入手动配置的 Bean

@GetMapping("/toolcall/chatclient-annotation")
public Flux<String> chatClientWithAnnotation(@RequestParam(name = "msg", defaultValue = "广州天气怎么样") String msg) {
return chatClient.prompt(msg)
.tools(new WeatherTools()) // 直接传入注解工具对象
.stream() // 启用流式调用
.content(); // 返回文本内容的 Flux
}
}

5.2 接口方式 + ChatClient 调用

import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.function.FunctionTool;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
public class ToolCallingController {

@Resource
private ChatClient chatClient;
// 直接复用配置类中已经构建好的FunctionTool Bean
@Resource
private FunctionTool queryWeatherFunctionTool;

@GetMapping("/toolcall/chatclient-function")
public Flux<String> chatClientWithFunction(@RequestParam(name = "msg", defaultValue = "深圳天气如何") String msg) {
return chatClient.prompt(msg)
.tools(queryWeatherFunctionTool) // 传入已经构建完成的FunctionTool
.stream()
.content();
}
}

5.3 Lambda 方式 + ChatClient 调用

import com.example.study.tools.WeatherToolLambda;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.function.FunctionTool;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
public class ToolCallingController {

@Resource
private ChatClient chatClient;

@GetMapping("/toolcall/chatclient-lambda")
public Flux<String> chatClientWithLambda(@RequestParam(name = "msg", defaultValue = "成都天气如何") String msg) {
FunctionTool functionTool = WeatherToolLambda.createWeatherTool();

return chatClient.prompt(msg)
.tools(functionTool) // 传入 FunctionTool
.stream()
.content();
}
}

6. 测试与效果

启动项目后,分别测试六个接口。
底层ChatModel接口返回完整文本;ChatClient系列接口为SSE流式输出,浏览器直接访问即可看到逐字输出。

① ChatModel + 注解工具

curl "http://localhost:8013/toolcall/chat-annotation?msg=北京天气怎么样"

响应示例:

北京:晴,气温 25℃,湿度 40%

② ChatModel + 接口工具

curl "http://localhost:8013/toolcall/chat-function?msg=上海天气如何"

响应示例:

上海:多云,气温 22℃,风力 3 级

③ ChatModel + Lambda 工具

curl "http://localhost:8013/toolcall/chat-lambda?msg=杭州天气怎么样"

响应示例:

杭州:阴,气温 18℃,风力 2 级

④ ChatClient + 注解工具(流式 SSE)

浏览器打开:

http://localhost:8013/toolcall/chatclient-annotation?msg=广州天气怎么样

会看到文本逐渐输出,例如:

广州:晴,气温 25℃,湿度 40%

⑤ ChatClient + 接口工具(流式 SSE)

浏览器打开:

http://localhost:8013/toolcall/chatclient-function?msg=深圳天气如何

会看到文本逐渐输出,例如:

深圳:多云,气温 22℃,风力 3 级

⑥ ChatClient + Lambda 工具(流式 SSE)

浏览器打开:

http://localhost:8013/toolcall/chatclient-lambda?msg=成都天气如何

会看到文本逐渐输出,例如:

成都:阴,气温 18℃,风力 2 级

注意:以上示例中工具返回结果后,因为returnDirect = false(注解方式默认)或FunctionTool默认行为,大模型会再次加工生成自然语言回复。若需直接返回工具结果,可调整配置或使用returnDirect选项。

7. 原理与关键点解析

7.1 Tool Calling 工作流程

  1. 用户提问→ 携带已注册工具的元数据发送给 DashScope 大模型。
  2. 模型判断→ 如果需要调用某个工具,返回一个“函数调用请求”,包含工具名和参数。
  3. 框架执行→ Spring AI 根据返回的工具名找到对应 Java 方法并执行,获取结果。
    👉 ChatClient:Advisor自动执行;原始ChatModel:必须通过ToolCallingManager手动执行。
  4. 二次生成→ 若returnDirect = false,框架将工具返回结果重新提交给模型,模型结合上下文生成最终回复;若为true,则直接返回工具结果。

7.2 三种工具定义方式对比

特性@Tool 注解方式Function 接口方式Lambda + FunctionTool.builder
定义方式在方法上添加注解实现Function<T, R>接口Lambda 表达式 + builder 构建
参数传递方法参数自动映射通过 record 封装入参通过 record 封装入参(inputType指定)
灵活度简单快速,适合单一方法更灵活,适合复杂业务逻辑、代理鉴权、单元测试最灵活,可动态构建,无需类定义
注册方式ToolCallbacks.from(obj)或.tools(obj)配置类Bean注册,Controller直接注入复用FunctionTool.builder(...).build()或.tools(functionTool)
推荐场景轻量级工具,快速接入需要依赖注入、复杂过滤或自定义逻辑动态工具、临时 Lambda、避免编写类

7.3 为什么 ChatClient 不能自动注入?

目前 Spring AI Alibaba 的自动配置还未将ChatClient纳入标准 Bean 管理,因此需要我们在@Configuration类中手动创建并返回。随着版本迭代,这个问题很可能会被解决,留意官方更新即可。

7.4 returnDirect 的选择

  • 需要大模型润色结果(例如:“北京今天天气晴朗,温度 25℃,建议穿短袖”) → 设为false。
  • 工具结果已是最终答案(例如:查询用户余额后直接返回数字) → 设为true,可以节省一次模型调用成本。

对于Function接口方式,通过builder设置returnDirect:

FunctionTool functionTool = FunctionTool.builder(weatherTool)
.name("queryWeather")
.description("查询指定城市天气情况")
.returnDirect(true)
.build();

对于FunctionTool.builder + Lambda方式,可在 builder 中设置:

FunctionTool functionTool = FunctionTool.builder("getWeather", WEATHER_FUNCTION)
.description("查询指定城市的天气情况")
.inputType(WeatherRequest.class)
.returnDirect(true) // returnDirect = true
.build();

8. 总结

本文以查询天气为例,完整演示了 Spring AI Alibaba 中 Tool Calling 的六种实现组合:

  1. 注解工具 + ChatModel(底层手动循环)
  2. 接口工具 + ChatModel(底层手动循环)
  3. Lambda 工具 + ChatModel(底层手动循环)
  4. 注解工具 + ChatClient(自动闭环,流式)
  5. 接口工具 + ChatClient(自动闭环,流式)
  6. Lambda 工具 + ChatClient(自动闭环,流式)

同时解决了ChatClient无法自动注入的问题,并说明了流式返回的实现方法。
生产优化点:静态工具对象不要在Controller接口方法内重复构建,统一在@Configuration注册单例Bean复用,减少对象创建开销。

生产建议:
  • 业务开发优先选择ChatClient,避免手写工具循环;
  • 只有需要完全接管工具执行流程、自定义鉴权拦截、需要用户确认后再执行工具场景,才使用原始ChatModel + DefaultToolCallingManager;
  • 编程式Function工具优先用builder,不要直接无元参数构造FunctionTool;
  • 安全:不要只在工具内部鉴权,优先外层动态裁剪ToolCallback,工具内部做兜底校验。
  • @Tool注解适合快速接入简单工具;
  • Function接口适合需要依赖注入或复杂业务逻辑的场景;
  • FunctionTool.builder + Lambda适合动态构建、临时定义工具,避免编写额外类。

希望这篇教程能帮助你快速上手 Spring AI Alibaba 的函数调用功能,为构建智能体应用打下坚实基础。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 18:41:08

八. SCL 模拟量平均值滤波(乞丐版)

一. 基本方法定时采集模拟量&#xff0c;将这个值放在一个数组里面。 每次定时接通&#xff0c;把新采集的值放在数组里的下一个变量里。二. 程序// 定时 #Timer(IN:NOT #Timer_Q,PT:t#1s,Q>#Timer_Q);IF #Timer_Q THEN#Ai_arr[#Count] : #Ai_in;#Count : #Count 1; END_IF…

作者头像 李华
网站建设 2026/10/6 18:31:24

思科3560三层交换机配置实战:VLAN间路由、SVI与排错指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 18:29:53

电机控制开源固件源码怎么读:从SimpleFOC到ODrive的进阶路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 18:29:24

ARS548 4D毫米波雷达实战:从硬件接线到点云数据解析与调试指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 18:27:25

六足机器人舵机控制器供电与PWM信号调试实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 18:26:22

中缀表达式求值实验全解析:栈与队列的实现与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华