最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Spring AI Function Calling:使AI调用你的Java方法
时间:2026-07-01 10:09:00 编辑:袖梨 来源:一聚教程网
Spring AI Function Calling:让AI调用你的Java方法
前言
在大语言模型的应用中,有一个核心问题:AI虽然聪明,但它无法感知外部世界,也无法执行实际操作。
比如你问"今天北京的天气怎么样",AI只能根据训练数据猜测,而无法获取实时天气。
Function Calling(函数调用)解决了这个问题:让AI能够调用你提供的Java方法,获取实时数据,然后基于这些数据回答问题。
本文分享我在实际项目中使用Spring AI Function Calling的经验,包括踩过的坑和最佳实践。
一、Function Calling原理
1.1 问题场景
假设我们要实现一个智能助手,用户会问:
用户:帮我查一下北京今天的天气AI(无Function Calling):根据我的知识,北京今天可能是...AI(有Function Calling):调用getWeather("北京") → 获取实时数据 → 北京今天晴,25°C
1.2 工作流程
用户提问↓AI分析:需要调用工具吗?↓ 是AI决定调用哪个方法 + 参数↓Spring AI执行对应的Java方法↓将方法返回值返回给AI↓AI基于返回数据生成回答↓返回给用户
1.3 支持的方法类型
Spring AI支持以下几种方式定义工具方法:
| 方式 | 说明 | 推荐度 |
|---|---|---|
@Tool注解 | 最简洁,Spring AI 1.1+ | ⭐⭐⭐⭐⭐ |
Function接口 | 传统方式,兼容性好 | ⭐⭐⭐ |
ToolCallback | 更灵活,支持动态工具 | ⭐⭐⭐⭐ |
二、基础实战:天气查询助手
2.1 环境准备
<!-- pom.xml --><project xmlns="http://maven.apache.org/POM/4.0.0"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.0</version></parent><groupId>com.example</groupId><artifactId>function-calling-demo</artifactId><version>1.0.0</version><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-spring-boot-starter</artifactId><version>1.0.0-M3</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url></repository></repositories></project>
2.2 配置
# application.ymlserver:port: 8080spring:ai:alibaba:api-key: ${ALI_API_KEY}chat:options:model: qwen-plustemperature: 0.1# Function Calling 场景温度要低
2.3 定义工具方法
package com.example.demo.tool;import org.springframework.ai.tool.annotation.Tool;import org.springframework.ai.tool.annotation.ToolParam;import org.springframework.stereotype.Component;import java.time.LocalDate;import java.util.Map;import java.util.Random;/** * 天气查询工具 * 实际项目中应调用真实的天气API(如和风天气、OpenWeather等) */public class WeatherTool {private static final Map<String, String> CITY_WEATHER = Map.of("北京", "晴,25°C,空气质量良好","上海", "多云,22°C,空气质量优","广州", "雷阵雨,28°C,空气质量良","深圳", "多云转晴,27°C,空气质量优","杭州", "小雨,20°C,空气质量良");private final Random random = new Random();public String getWeather( String city) {System.out.println("[Tool Called] getWeather(city=" + city + ")");// 模拟网络延迟try {Thread.sleep(500);} catch (InterruptedException e) {Thread.currentThread().interrupt();}return CITY_WEATHER.getOrDefault(city, "抱歉," + city + "的天气数据暂不可用");}public String getWeatherCompare( String cities) {System.out.println("[Tool Called] getWeatherCompare(cities=" + cities + ")");StringBuilder result = new StringBuilder();String[] cityArray = cities.split("[,,]");for (String city : cityArray) {city = city.trim();String weather = CITY_WEATHER.getOrDefault(city, "数据暂不可用");result.append(city).append(":").append(weather).append("n");}return result.toString();}public String getTodayDate() {return LocalDate.now().toString();}}
2.4 注册工具并调用
package com.example.demo.service;import com.example.demo.tool.WeatherTool;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClient;import org.springframework.stereotype.Service;public class WeatherAssistantService {private final ChatClient chatClient;private final WeatherTool weatherTool;public WeatherAssistantService(ChatClient.Builder chatClientBuilder,WeatherTool weatherTool) {this.chatClient = chatClientBuilder.defaultTools(weatherTool)// 注册工具.build();this.weatherTool = weatherTool;log.info("WeatherAssistantService initialized with tools");}/** * 智能天气查询 */public String queryWeather(String userMessage) {log.info("User message: {}", userMessage);String response = chatClient.prompt().user(userMessage).call().content();log.info("AI response generated");return response;}}
2.5 Controller
package com.example.demo.controller;import com.example.demo.service.WeatherAssistantService;import lombok.RequiredArgsConstructor;import org.springframework.web.bind.annotation.*;public class WeatherController {private final WeatherAssistantService weatherService;public String query( QueryRequest request) {if (request.message() == null || request.message().isBlank()) {throw new IllegalArgumentException("消息不能为空");}return weatherService.queryWeather(request.message());}/** * 测试场景 */public String test() {return weatherService.queryWeather("北京今天天气怎么样?和上海比怎么样?");}}record QueryRequest(String message) {}
三、进阶实战:企业知识库助手
3.1 场景说明
在企业内部,我们可能需要一个助手,能够:
- 查询员工信息
- 查询项目进度
- 查询系统状态
- 执行简单的运维操作
3.2 定义多个工具
package com.example.demo.tool;import org.springframework.ai.tool.annotation.Tool;import org.springframework.ai.tool.annotation.ToolParam;import org.springframework.stereotype.Component;import java.util.List;import java.util.Map;/** * 企业系统工具集 */public class EnterpriseTools {public String getEmployeeInfo( String employeeId) {System.out.println("[Tool Called] getEmployeeInfo(employeeId=" + employeeId + ")");// 模拟数据库查询Map<String, String> employees = Map.of("张三", "工号:E001,部门:技术部,职位:高级工程师","李四", "工号:E002,部门:产品部,职位:产品经理","王五", "工号:E003,部门:运维部,职位:SRE工程师");return employees.getOrDefault(employeeId, "未找到员工:" + employeeId);}public String getProjectStatus( String projectId) {System.out.println("[Tool Called] getProjectStatus(projectId=" + projectId + ")");Map<String, String> projects = Map.of("项目A", "状态:进行中,完成度:65%,预计完成时间:2026-06-30","项目B", "状态:已上线,完成度:100%,上线时间:2026-03-15","项目C", "状态:需求评审中,完成度:10%,预计完成时间:2026-08-15");return projects.getOrDefault(projectId, "未找到项目:" + projectId);}public String getSystemStatus() {System.out.println("[Tool Called] getSystemStatus()");// 实际项目中应调用监控系统APIreturn """ 系统运行状态: - CPU使用率:45% - 内存使用率:62% - 磁盘使用率:78% - 网络状态:正常 - 活跃连接数:342 """;}public String restartService( String serviceName) {System.out.println("[Tool Called] restartService(serviceName=" + serviceName + ")");// 实际项目中应调用运维系统APIreturn "服务 " + serviceName + " 重启指令已发送,预计30秒后恢复";}}
3.3 多工具注册
package com.example.demo.service;import com.example.demo.tool.EnterpriseTools;import com.example.demo.tool.WeatherTool;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClient;import org.springframework.stereotype.Service;public class EnterpriseAssistantService {private final ChatClient chatClient;public EnterpriseAssistantService(ChatClient.Builder chatClientBuilder,WeatherTool weatherTool,EnterpriseTools enterpriseTools) {// 注册多个工具类this.chatClient = chatClientBuilder.defaultTools(weatherTool, enterpriseTools).build();log.info("EnterpriseAssistantService initialized with multiple tool classes");}public String chat(String message) {return chatClient.prompt().user(message).call().content();}}
四、动态工具:根据上下文加载
4.1 场景说明
在某些场景下,工具需要根据用户权限、当前会话等动态加载。
4.2 实现动态工具
package com.example.demo.service;import org.springframework.ai.tool.ToolCallback;import org.springframework.ai.tool.function.FunctionToolCallback;import org.springframework.ai.chat.client.ChatClient;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.List;public class DynamicToolService {private final ChatClient.Builder chatClientBuilder;public DynamicToolService(ChatClient.Builder chatClientBuilder) {this.chatClientBuilder = chatClientBuilder;}/** * 根据用户角色动态注册工具 */public String chatWithDynamicTools(String message, UserRole role) {List<ToolCallback> tools = new ArrayList<>();// 基础工具:所有用户可用tools.add(FunctionToolCallback.builder("getWeather", this::getWeather).description("获取天气信息").inputType(String.class).build());// 管理员专属工具if (role == UserRole.ADMIN) {tools.add(FunctionToolCallback.builder("restartService", this::restartService).description("重启服务(管理员专属)").inputType(String.class).build());}// 构建ChatClientChatClient client = chatClientBuilder.defaultTools(tools.toArray(new ToolCallback[0])).build();return client.prompt().user(message).call().content();}// 工具方法private String getWeather(String city) {return city + "今天天气晴,25°C";}private String restartService(String serviceName) {return "服务 " + serviceName + " 正在重启...";}public enum UserRole {USER, ADMIN}}
五、踩坑记录与解决方案
5.1 工具未被调用
问题现象:即使用户问题明显需要调用工具,AI也没有调用,而是直接回答。
原因分析:
- 工具描述不够清晰,AI不理解何时调用
- Temperature参数过高,AI过于"创造性"
- 模型不支持Function Calling(检查模型版本)
解决方案:
// 1. 改进工具描述public String getWeather(String city) { ... }// 2. 降低temperaturespring.ai.alibaba.chat.options.temperature=0.1// 3. 使用支持Function Calling的模型// qwen-plus以上版本支持
5.2 参数解析错误
问题现象:
AI尝试调用工具,但参数类型错误,导致调用失败
原因分析:AI生成的参数与Java方法签名不匹配。
解决方案:
// 1. 使用@ToolParam明确参数描述public String getEmployeeInfo( String name) { ... }// 2. 参数类型使用String(最灵活)// 避免用int、boolean等,让AI生成String再由你转换// 3. 添加参数校验public String getUsersByAge( String ageStr) {int age;try {age = Integer.parseInt(ageStr);} catch (NumberFormatException e) {return "年龄参数错误,请提供数字";}// 查询逻辑...return "找到 " + age + " 岁的用户共10人";}
5.3 工具执行超时
问题现象:工具方法执行时间过长,导致AI调用超时。
解决方案:
public String longRunningTask(String param) {// 方案1:异步执行,立即返回任务IDString taskId = submitAsyncTask(param);return "任务已提交,任务ID:" + taskId + ",请稍后查询结果";// 方案2:设置超时// 在application.yml中配置// spring.ai.alibaba.chat.options.timeout=60000}
5.4 工具返回数据过大
问题现象:工具返回了大量数据,导致Token超限。
解决方案:
public String getUsers( String pageStr) {int page = Integer.parseInt(pageStr);// 限制返回数量List<User> users = queryUsersByPage(page, 10);// 每页10条// 只返回摘要,不要完整对象return users.stream().map(u -> u.getName() + "(" + u.getAge() + "岁)").collect(Collectors.joining("n"));}
5.5 多个工具冲突
问题现象:注册了多个功能相似的工具,AI不知道调用哪个。
解决方案:
// 明确区分工具的职责public String getWeather(String city) { ... }public String compareWeather(String cities) { ... }
六、生产级最佳实践
6.1 工具设计原则
1. 单一职责:每个工具只做一件事2. 明确描述:description要详细,说明何时调用3. 参数简单:优先用String,避免复杂对象4. 快速返回:工具执行时间控制在3秒内5. 安全校验:所有参数都要校验
6.2 安全防护
public class SecureTools {public String executeCommand( String command,UserContext userContext) {// 从上下文获取用户信息// 权限校验if (!userContext.hasRole("ADMIN")) {return "权限不足,需要管理员权限";}// 命令白名单if (!isCommandAllowed(command)) {return "此命令不在允许列表中";}// 执行命令return executeSystemCommand(command);}private boolean isCommandAllowed(String command) {String[] allowed = {"ls", "pwd", "df -h", "top -b -n 1"};for (String allowedCmd : allowed) {if (command.startsWith(allowedCmd)) {return true;}}return false;}}
6.3 监控与日志
public class MonitoredTools {private final MeterRegistry meterRegistry;public MonitoredTools(MeterRegistry meterRegistry) {this.meterRegistry = meterRegistry;}public String getUserOrders(String userId) {long startTime = System.currentTimeMillis();try {// 执行查询String result = queryOrdersFromDb(userId);// 记录成功meterRegistry.counter("tools.user_orders.calls.success").increment();return result;} catch (Exception e) {// 记录失败meterRegistry.counter("tools.user_orders.calls.failure").increment();log.error("查询用户订单失败", e);return "查询失败:" + e.getMessage();} finally {// 记录耗时long duration = System.currentTimeMillis() - startTime;meterRegistry.timer("tools.user_orders.duration").record(duration, java.util.concurrent.TimeUnit.MILLISECONDS);log.info("getUserOrders executed in {} ms", duration);}}}
6.4 优雅降级
public String getInventory(String productId) {try {// 主数据源return queryFromMainDatabase(productId);} catch (Exception e) {log.warn("主数据源查询失败,尝试备用方案", e);try {// 降级:读缓存return queryFromCache(productId);} catch (Exception e2) {log.error("备用方案也失败", e2);// 返回友好提示return "库存查询暂时不可用,请稍后重试";}}}
七、完整项目示例
7.1 项目结构
function-calling-demo/├── src/main/java/com/example/demo/│ ├── DemoApplication.java│ ├── config/│ │ └── AiConfig.java│ ├── controller/│ │ ├── WeatherController.java│ │ └── AssistantController.java│ ├── service/│ │ ├── WeatherAssistantService.java│ │ ├── EnterpriseAssistantService.java│ │ └── DynamicToolService.java│ ├── tool/│ │ ├── WeatherTool.java│ │ └── EnterpriseTools.java│ └── model/│ └── QueryRequest.java├── src/main/resources/│ └── application.yml└── pom.xml
7.2 启动类
package com.example.demo;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);log.info("Function Calling Demo started!");log.info("Test URL: http://localhost:8080/api/weather/test");}}
7.3 测试用例
package com.example.demo;import com.example.demo.service.WeatherAssistantService;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import static org.junit.jupiter.api.Assertions.*;class FunctionCallingTest {private WeatherAssistantService weatherService;void testWeatherQuery() {String response = weatherService.queryWeather("北京今天天气怎么样?");assertNotNull(response);assertTrue(response.contains("北京") || response.contains("天气") || response.contains("°C"));System.out.println("AI回答:" + response);}void testMultiCityComparison() {String response = weatherService.queryWeather("北京和上海天气对比");assertNotNull(response);assertTrue(response.length() > 10);System.out.println("AI回答:" + response);}}
八、性能优化
8.1 工具结果缓存
public class CachedTools {public String getWeather(String city) {System.out.println("【执行查询】" + city);// 实际调用天气APIreturn callWeatherApi(city);}}
8.2 并发工具调用
public String getMultipleWeather(String cities) {List<CompletableFuture<String>> futures = Arrays.stream(cities.split(",")).map(city -> CompletableFuture.supplyAsync(() -> queryWeather(city.trim()))).toList();return futures.stream().map(CompletableFuture::join).collect(Collectors.joining("n"));}
九、总结
9.1 核心要点
- Function Calling让AI能够调用Java方法,获取实时数据
- 工具定义使用
@Tool注解,描述要清晰明确 - 参数设计优先使用String类型,避免复杂对象
- 安全防护必须做权限校验和命令白名单
- 监控日志记录工具调用情况,便于排查问题
9.2 适用场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 实时数据查询 | 天气、股票、新闻等 | 天气查询助手 |
| 数据库操作 | 根据用户意图查询数据库 | 智能客服 |
| 系统运维 | 执行运维命令、查询状态 | 运维助手 |
| 业务操作 | 下单、退款、审批等 | 企业助手 |
9.3 参考资源
- Spring AI官方文档:docs.spring.io/spring-ai/r…
- 通义千问Function Calling文档:help.aliyun.com/zh/model-st…
- OpenAI Function Calling指南:platform.openai.com/docs/guides…
如果有帮助,欢迎点赞、收藏、关注!如有问题,欢迎在评论区交流。
相关文章
- 天国拯救2马具代码大全:马匹装备代码汇总 07-04
- 黑色四叶草魔法帝之道支援者推荐 黑色四叶草魔法帝之道支援者角色推荐 07-04
- 武装博弈 新角色狐妖召唤小动物参战 07-04
- 黑色四叶草魔法帝之道阵容搭配推荐 黑色四叶草魔法帝之道配队教程 07-04
- 武装博弈:爱丽丝睡眠流实况视频 07-04
- 潜水员戴夫丛林DLC水龙如何打 07-04