Spring AI Alibaba Function Calling:外部工具集成与业务函数注册

Spring AI Alibaba Function Calling:外部工具集成与业务函数注册 Spring AI Alibaba Function Calling外部工具集成与业务函数注册导读Function Calling工具调用是让 AI 从纸上谈兵走向真正干活的关键能力。本文深入讲解如何用 Spring AI Alibaba 1.1 版让大模型调用你的 Java 方法、查询数据库、访问第三方 API并覆盖并行调用、参数校验、权限控制等生产级细节。一、Function Calling 的本质Function Calling 解决的问题很具体大模型的知识有截止日期也无法直接访问你的系统。但通过 Function Calling你可以告诉模型有哪些工具可以用当它判断需要某个工具时会输出一个结构化的函数调用请求你的代码执行后把结果返回给模型模型再基于结果给出最终答案。用户现在上海的天气怎么样 [无 Function Calling] 模型我无法获取实时天气信息我的训练数据截止到... [有 Function Calling] 模型需要调用天气查询工具 ↓ 你的代码调用天气 API得到上海 25°C晴 ↓ 模型目前上海天气晴朗气温 25°C适合户外活动...这个能力让 AI 真正成为能执行任务的智能体而不仅仅是知识检索器。二、依赖与配置Function Calling 能力包含在spring-ai-alibaba-starter-dashscope中无需额外依赖dependencygroupIdcom.alibaba.cloud.ai/groupIdartifactIdspring-ai-alibaba-starter-dashscope/artifactId/dependency!-- 参数校验JSR-303 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-validation/artifactId/dependency三、三种函数注册方式3.1 方式一Bean Function 接口推荐这是最标准的注册方式函数实现与 Spring 容器解耦packagecom.example.ai.tools;importcom.fasterxml.jackson.annotation.JsonProperty;importcom.fasterxml.jackson.annotation.JsonPropertyDescription;importlombok.extern.slf4j.Slf4j;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Description;importjava.time.LocalDateTime;importjava.time.format.DateTimeFormatter;importjava.util.function.Function;Slf4jConfigurationpublicclassToolsConfiguration{/** * 工具一获取当前时间 * 模型会在需要知道当前时间时调用此工具 */BeanDescription(获取当前的日期和时间当用户询问时间相关问题时调用)publicFunctionCurrentTimeRequest,CurrentTimeResponsegetCurrentTime(){returnrequest-{log.info(工具调用getCurrentTime时区{},request.timezone());StringnowLocalDateTime.now().format(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss));returnnewCurrentTimeResponse(now,Asia/Shanghai);};}/** * 工具二天气查询模拟实现 */BeanDescription(查询指定城市的实时天气信息。当用户询问某城市天气时调用此工具。)publicFunctionWeatherRequest,WeatherResponsegetWeather(){returnrequest-{log.info(工具调用getWeather城市{},request.city());// 实际项目调用天气 API如 OpenWeatherMap、和风天气等returnsimulateWeather(request.city());};}privateWeatherResponsesimulateWeather(Stringcity){// 模拟数据实际需调用真实 APIreturnnewWeatherResponse(city,晴,22.5,65,适宜户外活动);}// ---- 请求/响应 DTOJSON 描述是 Prompt 工程的关键----publicrecordCurrentTimeRequest(JsonProperty(timezone)JsonPropertyDescription(时区如 Asia/Shanghai)Stringtimezone){}publicrecordCurrentTimeResponse(JsonProperty(current_time)JsonPropertyDescription(当前时间格式yyyy-MM-dd HH:mm:ss)StringcurrentTime,JsonProperty(timezone)Stringtimezone){}publicrecordWeatherRequest(JsonProperty(city)JsonPropertyDescription(城市名称如上海、北京、广州)Stringcity){}publicrecordWeatherResponse(Stringcity,Stringcondition,doubletemperature,inthumidity,Stringsuggestion){}}3.2 方式二Tool 注解1.1版推荐的简化写法Spring AI 1.1 版引入了Tool注解可以直接标注方法更简洁packagecom.example.ai.tools;importorg.springframework.ai.tool.annotation.Tool;importorg.springframework.stereotype.Component;/** * 数据库查询工具类 * 使用 Tool 注解无需额外的 Function Bean 注册 */ComponentpublicclassDatabaseTools{privatefinalOrderRepositoryorderRepository;privatefinalProductRepositoryproductRepository;publicDatabaseTools(OrderRepositoryorderRepository,ProductRepositoryproductRepository){this.orderRepositoryorderRepository;this.productRepositoryproductRepository;}/** * 查询订单状态 * 模型描述决定了模型何时会调用此工具 */Tool(description根据订单号查询订单的状态、金额、收货地址等详细信息)publicStringqueryOrderStatus(JsonPropertyDescription(订单号格式如ORD20250320001)StringorderId){log.info([Tool] 查询订单{},orderId);OrderorderorderRepository.findById(orderId).orElse(null);if(ordernull){returnString.format(未找到订单号为 %s 的订单请确认订单号是否正确,orderId);}returnString.format(订单 %s 状态%s金额%.2f 元下单时间%s收货地址%s,orderId,order.getStatus(),order.getAmount(),order.getCreatedAt(),order.getShippingAddress());}/** * 查询商品库存 */Tool(description查询指定商品的当前库存数量用于回答用户关于商品是否有货的问题)publicStringqueryProductStock(JsonPropertyDescription(商品 SKU 编码或商品名称)StringproductIdentifier){log.info([Tool] 查询库存{},productIdentifier);// 先尝试按 SKU 查询再按名称模糊查询ProductproductproductRepository.findBySku(productIdentifier).orElseGet(()-productRepository.findByNameContaining(productIdentifier).stream().findFirst().orElse(null));if(productnull){return未找到商品productIdentifier;}returnString.format(商品 [%s] 当前库存%d 件状态%s,product.getName(),product.getStock(),product.getStock()0?有货:缺货);}}3.3 在 ChatClient 中注册工具ServiceRequiredArgsConstructorSlf4jpublicclassToolChatService{privatefinalChatClientchatClient;// 注入 Tool 注解的工具类privatefinalDatabaseToolsdatabaseTools;// 通过 Bean 注册的工具Qualifier(getCurrentTime)privatefinalFunctionToolsConfiguration.CurrentTimeRequest,ToolsConfiguration.CurrentTimeResponsegetCurrentTime;Qualifier(getWeather)privatefinalFunctionToolsConfiguration.WeatherRequest,ToolsConfiguration.WeatherResponsegetWeather;/** * 带工具的对话模型自主决定是否调用工具 */publicStringchatWithTools(StringuserMessage){log.info(用户消息{},userMessage);returnchatClient.prompt().user(userMessage)// 方式一注册 Bean Function 工具.tools(FunctionToolCallback.builder(getCurrentTime,getCurrentTime).description(获取当前时间).inputType(ToolsConfiguration.CurrentTimeRequest.class).build(),FunctionToolCallback.builder(getWeather,getWeather).description(查询城市天气).inputType(ToolsConfiguration.WeatherRequest.class).build())// 方式二注册 Tool 注解的工具类Spring AI 1.1// .tools(databaseTools) // 自动发现类中所有 Tool 方法.call().content();}/** * 智能客服场景使用数据库查询工具 */publicStringcustomerService(Stringquestion){returnchatClient.prompt().system( 你是一个智能客服助手可以帮助用户查询订单状态和商品信息。 当用户询问订单时调用查询工具获取实时信息。 回答要友好、简洁并在适当时主动提供帮助。 ).user(question).tools(databaseTools)// 注入数据库查询工具.call().content();}}四、完整工具调用链分析理解调用链有助于排查问题用户请求 → Spring Controller | v [ChatClient] | (携带工具描述的 Prompt) | v [DashScope API] 模型判断需要调用工具 | v 模型输出 { tool_calls: [{ function: { name: getWeather, arguments: {city: 上海} } }] } | v [Spring AI 内部处理] 自动解析工具调用 | v [你的 Java 函数] WeatherResponse {city上海, condition晴, ...} | v [结果回传模型] 模型收到工具结果 | v [模型生成最终回答] 上海今天天气晴朗气温约 22.5°C... | v [返回给用户]五、MyBatis 数据库查询集成工具调用最常见的场景之一是让 AI 查询数据库。以下是结合 MyBatis 的完整示例ComponentSlf4jpublicclassMyBatisQueryTools{AutowiredprivateSqlSessionFactorysqlSessionFactory;AutowiredprivateUserMapperuserMapper;AutowiredprivateOrderMapperorderMapper;/** * 查询用户积分余额 */Tool(description查询用户的积分余额当用户询问自己有多少积分时调用)publicStringgetUserPoints(JsonPropertyDescription(用户 ID 或用户名)StringuserIdentifier){try{UseruseruserMapper.findByIdOrName(userIdentifier);if(usernull){return未找到用户userIdentifier;}returnString.format(用户 %s 当前积分%d 分等级%s,user.getName(),user.getPoints(),user.getLevel());}catch(Exceptione){log.error(查询用户积分失败,e);return查询失败请稍后重试;}}/** * 查询历史订单列表 */Tool(description查询用户最近的历史订单列表最近10条当用户询问历史购买记录时调用)publicStringgetRecentOrders(JsonPropertyDescription(用户 ID)StringuserId){ListOrderordersorderMapper.findRecentByUserId(userId,10);if(orders.isEmpty()){return该用户暂无订单记录;}StringBuildersbnewStringBuilder(最近订单\n);orders.forEach(o-sb.append(String.format(- 订单号%s商品%s金额%.2f元状态%s\n,o.getId(),o.getProductName(),o.getAmount(),o.getStatus())));returnsb.toString();}}六、并行工具调用当一次对话需要调用多个工具时模型可以并行发起多个调用Spring AI 会并行执行并聚合结果/** * 并行调用示例同时查询多个数据源 * 用户帮我看看北京和上海的天气还有告诉我现在几点了 */publicStringparallelToolsDemo(Stringmessage){returnchatClient.prompt().user(message)// 注册多个工具模型可以并行调用.tools(FunctionToolCallback.builder(getWeather,getWeather).description(查询城市天气支持并行调用多个城市).inputType(WeatherRequest.class).build(),FunctionToolCallback.builder(getCurrentTime,getCurrentTime).description(获取当前时间).inputType(CurrentTimeRequest.class).build()).call().content();// 模型会一次性输出// tool_calls: [// {name: getWeather, args: {city: 北京}},// {name: getWeather, args: {city: 上海}},// {name: getCurrentTime, args: {timezone: Asia/Shanghai}}// ]// Spring AI 并行执行三个工具调用再将结果聚合返回给模型}七、安全控制权限白名单与参数校验工具调用涉及真实的业务操作必须有安全防护7.1 参数校验JSR-303ComponentpublicclassSecureQueryTools{Tool(description查询指定日期范围内的销售报表)publicStringgetSalesReport(NotBlank(message开始日期不能为空)Pattern(regexp\\d{4}-\\d{2}-\\d{2},message日期格式必须为 yyyy-MM-dd)JsonPropertyDescription(开始日期格式yyyy-MM-dd)StringstartDate,NotBlank(message结束日期不能为空)JsonPropertyDescription(结束日期格式yyyy-MM-dd)StringendDate){// 业务逻辑校验LocalDatestartLocalDate.parse(startDate);LocalDateendLocalDate.parse(endDate);if(end.isBefore(start)){thrownewIllegalArgumentException(结束日期不能早于开始日期);}if(ChronoUnit.DAYS.between(start,end)90){thrownewIllegalArgumentException(查询范围不能超过 90 天);}// 执行查询...return查询成功日期范围startDate 至 endDate;}}7.2 工具执行超时控制ComponentpublicclassTimeoutSafeTools{privatefinalExecutorServiceexecutorExecutors.newFixedThreadPool(10);Tool(description调用外部 API 获取数据含超时保护)publicStringcallExternalApi(StringapiName,Stringparams){FutureStringfutureexecutor.submit(()-{// 实际的 API 调用逻辑returnexternalApiService.call(apiName,params);});try{// 工具执行超时5秒returnfuture.get(5,TimeUnit.SECONDS);}catch(TimeoutExceptione){future.cancel(true);log.warn(工具 [{}] 执行超时,apiName);return查询超时请稍后重试;}catch(Exceptione){log.error(工具执行失败{},e.getMessage());return执行失败e.getMessage();}}}八、工具描述的 Prompt 工程工具描述Description的质量直接决定模型何时调用工具这是被很多人忽视的Prompt 工程// ❌ 差的描述模糊不清Description(查询信息)publicFunctionRequest,ResponsequeryInfo(){...}// ✅ 好的描述明确触发条件 输入输出说明Description(查询指定城市的实时天气信息包括温度、天气状况、湿度等。当用户询问某城市天气、是否需要带雨伞、户外活动是否合适等问题时调用此工具。输入城市名称支持中文城市名返回当前天气信息。)publicFunctionWeatherRequest,WeatherResponsegetWeather(){...}好的工具描述需要包含工具做什么功能说明什么时候调用触发条件输入什么参数说明返回什么输出说明。九、总结Function Calling 是 AI 应用从对话机器人进化为智能助手的关键能力三种注册方式Bean Function接口标准、Tool注解1.1版简化、手动FunctionToolCallback调用链理解模型 → 工具调用请求 → Spring AI 执行 → 结果回传模型 → 最终回答数据库集成结合 MyBatis让 AI 直接查询业务数据库并行调用多工具并行执行结果自动聚合安全防护JSR-303 参数校验 超时控制 权限白名单。下一篇将深入 AI 应用的可观测性Token 消耗监控、链路追踪、Prometheus 指标接入打造生产级运维能力。参考资料Spring AI Function Calling 文档Spring AI Alibaba Tool CallingDashScope 工具调用文档