新闻详情

新闻详情

首页 / 资讯中心 / 详情

Spring AI 2.0.1 Function Calling实战:从Qwen集成到Dify迁移完整指南

发布时间:2026/10/2 3:58:36来源:尧图网络
Spring AI 2.0.1 Function Calling实战:从Qwen集成到Dify迁移完整指南
最近一周我把手头的项目从Dify工作流迁到Spring AI 2.0.1顺手把Function Calling从头到尾捋了一遍。这个东西看起来简单但真正落到业务里容易踩的坑一个都不少。这篇文章我尽量用实战的方式把从0到1跑通的完整过程写出来怎么选版本、怎么配百炼、怎么定义工具函数、怎么让Qwen3.7在需要时自动调用你的Java方法以及从Dify迁移过来的思路。如果你是刚接触Spring AI的开发者或者已经在写ChatClient但工具一直调不起来的同学这篇文章应该能帮上忙。1. Function Calling 到底是什么先搞懂模型在帮你做什么1.1 别把模型当成API调用器它只是决策者很多同学第一次接触Function Calling时会误以为它是让大模型去请求接口的魔法。其实不是。大模型本身不执行任何代码它只会根据你的描述判断这个问题该调用哪个工具、参数应该填什么然后把调用意图返回给你。真正发HTTP请求、查数据库、拼返回值都是你的应用程序在干。我用一个比较接地气的类比大模型像一个刚来公司实习的聪明新人。他脑子里有大量常识但不知道你们公司的订单库里有什么数据也不知道天气接口的URL是什么。你给他一份工具清单上面写着每个工具的名字、作用、需要什么参数。用户问杭州适合穿短袖吗这个新人不会自己编一个天气而是看清单后发现有个工具叫查询城市天气于是告诉你请帮我调用这个工具参数城市杭州。你把查询结果给他他再结合结果组织出人话回复。这个机制的核心价值在于模型不需要知道实现细节只负责决策你的Java代码负责执行。两者通过一套约定好的协议配合这就是Function Calling。1.2 一次完整调用背后的三轮对话Function Calling看起来像一次普通的chat请求实际在底层走了三轮交互。我画不出图但用文字描述也很清楚第一轮客户端把用户消息加上所有工具的定义名称、描述、参数Schema一起发给模型。第二轮模型返回一个特殊的Assistant消息里面带tool_calls字段内容类似于我要调用get_weather_by_city参数是{city: 杭州}。注意这时候模型通常不会直接回答用户问题它是在请求执行工具。你的服务端解析出工具调用意图真正执行对应的Java方法然后把这个执行结果以tool角色的消息追加进对话再次发给模型。第三轮模型拿到真实工具结果后生成最终的自然语言回复。在Spring AI里面后两轮的拼接和调度被框架封装好了。你往往只需要写一个带Tool注解的方法再通过ChatClient.defaultTools()注册进去剩下的循环调用、角色切换、结果回传框架都会自动处理。这也是Spring AI比直接拼HTTP请求省心的原因。如果你刚开始调不通我建议先别急着怀疑框架而是把上面这个三轮对话的流程印在脑子里。后面排查问题时很多困惑都能对应到具体某一轮出了问题。2. Spring AI 实战环境准备版本、依赖和配置2.1 版本怎么选Spring AI 2.0.1、Spring AI Alibaba、Qwen3.7先解决一个很多人问的问题Spring AI、Spring AI Alibaba、百炼、DashScope这些到底什么关系简单说Spring AI是Spring官方出的AI框架类似Spring生态里的JDBC标准Spring AI Alibaba是阿里团队基于Spring AI做的starter专门对接阿里云百炼平台和DashScope API。你写了Spring AI标准的ChatClient代码底层通过Spring AI Alibaba的自动配置把请求发到百炼模型就能用Qwen系列。这套关系搞明白后选版本就不纠结了。我用的是Spring Boot 3.3.x Java 17 Spring AI Alibaba starter 2.0.1。为什么不用1.x因为1.x的ChatClientAPI还不够稳工具调用的写法偏绕2.0以后Tool注解、ToolParam注解、ChatClient的构建方式都清晰了很多。Qwen模型我直接用的百炼上的qwen3.7你在控制台开通后换成实际可用的模型名即可。这里提醒一句网上有些教程还是老的Spring AI 1.0写法ToolCallback注册方式和2.x完全不同。你照着抄之前先看一眼自己项目里的版本别怪代码跑不通。2.2 最小依赖与配置文件创建一个Spring Boot工程后只加一个核心starter就能跑通百炼连接。我在pom.xml里加了这些dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter/artifactId version2.0.1/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency如果你的项目还需要对工具结果做额外处理可以再引入spring-ai-starter-model-qwen之类的模块但最小闭环其实用不上。配置文件application.yml这样写spring: application: name: function-calling-demo ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen3.7 temperature: 0.3api-key用环境变量注入不要硬编码。temperature我习惯调到0.3左右工具调用的场景需要模型更“理性”温度太高容易让模型自己瞎编参数。不同小版本的前缀可能有细微差别如果启动后报配置不生效用CtrlF在你的starter jar里搜一下DashscopeChatProperties看它实际绑定了哪些前缀。2.3 先跑通最简单的对话在写工具之前先确认最基础的对话链路是通的。我建了一个极简的ControllerRestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatModel chatModel) { this.chatClient ChatClient.builder(chatModel).build(); } PostMapping(/chat) public String chat(RequestBody String message) { return chatClient.prompt(message) .call() .content(); } }启动后用Postman发一个POST请求body随便写你好能正常返回就说明百炼连接没问题。这一步很多人忽略结果一上来就写工具函数最后分不清是配置错了还是工具代码错了。先跑通最简单的再叠加复杂度排查范围会小很多。3. 手写第一个Function Calling从工具类到ChatClient3.1 用 Tool 定义一个查询天气工具Spring AI 2.x里定义工具特别简单就是把普通Java方法加上Tool注解。我写了一个天气查询工具类Component public class WeatherTools { Tool(name get_weather_by_city, description 查询某个城市当前的实时天气包含温度、天气现象、湿度和风力) public String getWeatherByCity( ToolParam(description 城市中文名例如杭州) String city) { // 实际项目里这里对接天气服务示例先返回固定数据 return String.format(当前%s天气多云气温27℃东南风3级湿度60%%, city); } }注意几个细节。第一ToolParam里的description非常重要模型就是靠这个描述来理解参数该填什么。我见过有人不写参数描述结果模型把城市名和日期搞混。第二返回值最好是一个完整的字符串把模型需要的信息都说清楚不要只返回OK或查询成功这种没有任何信息量的状态。第三工具方法名最好用下划线分隔和模型的tool命名习惯保持一致。有人会问方法能不能返回一个对象可以Spring AI会把返回对象序列化成JSON文本回传给模型但我不建议在第一个工具里就这么干。字符串可读性强、日志排查方便等后面熟练了再玩复杂类型。3.2 把工具注册到 ChatClient定义好工具类后关键一步是把它注册到ChatClient。这里我用了一个配置类Configuration public class ChatConfig { Bean public ChatClient chatClient(ChatModel chatModel, WeatherTools weatherTools) { return ChatClient.builder(chatModel) .defaultTools(weatherTools) .build(); } }defaultTools的意思是每次会话默认都会把这批工具定义塞给模型。你也可以在每次prompt()后面用.tools(weatherTools)临时指定效果类似。我习惯用.defaultTools()因为在一个业务范围内工具集合基本是固定的。注册完之后上面的ChatController不用改原来的chatClient注入进去后就已经具备工具调用能力了。完整调用链路变成用户提问 - 模型判断需要天气 - Spring AI执行getWeatherByCity- 结果回传 - 模型组织回答。全程你不需要手写任何循环。3.3 验证模型真的调用了工具我直接把前后端跑起来用Postman发了一句杭州今天适合穿短袖吗返回内容大致是这样的根据查询结果杭州当前多云气温27℃湿度60%风力3级。这个天气比较舒适适合穿短袖不过早晚可能有点凉建议带一件薄外套。看着像一次普通对话但为了确认工具真的被调了我把Spring AI的日志级别打开logging: level: org.springframework.ai: DEBUG日志里能看到类似这样的关键信息ToolExecutionRequest、calling function get_weather_by_city甚至能看到传给工具的参数。这一步验证很重要因为有时候模型会“假装”调用工具或者干脆跳过工具直接编答案。看到日志里的工具调用记录你才能真正确认Function Calling链路是通的。另外建议给WeatherTools里加一句业务日志比如用log.info(调用天气工具城市{}, city)这样联调时一眼就知道工具执行了不用去翻框架的底层日志。4. 把函数调用做得更可靠参数、异常与多工具4.1 参数Schema设计越简单越可靠工具调用最容易翻车的地方不是工具本身写错而是模型传参传错。模型是根据你给的描述来“猜”参数的参数越复杂、嵌套越深猜错的概率就越高。我总结了一条经验一个工具的入参尽量控制在两三个字段以内能传字符串就传字符串别让模型去构造嵌套对象。比如查询天气只传city一个字符串模型很难出错。但如果你搞一个WeatherQueryRequest里面有city、date、granularity、unit这些字段模型很可能漏填某个字段或者在字段名上跟你较劲。如果业务确实需要多个参数可以把它们拼成一个JSON字符串传给工具在Java方法里自己解析。但前提是ToolParam的描述里写清楚格式比如传入JSON字符串格式为{city:杭州,date:2025-01-01}。模型对这类说明一般也能理解但工作量会多一点能简单就简单。另外参数描述不要写太多与参数本身无关的信息。比如city参数就写城市中文名别写城市名来自收货地址如果为空则取默认城市。模型看到一堆附加条件容易产生误解。4.2 工具执行异常如何反馈给模型工具方法内部调外部API总会有失败的时候。这里有一个很重要的原则不要直接把异常抛给Spring AI。一旦抛出异常模型就不知道发生了什么用户只会收到一片错误信息。正确的做法是在工具方法内部捕获异常把错误信息当作正常返回值返回给模型。比如Tool(name get_weather_by_city, description 查询某个城市当前的实时天气) public String getWeatherByCity(ToolParam(description 城市中文名) String city) { try { // 调用真实天气接口 return weatherClient.query(city); } catch (Exception e) { return 查询天气失败原因 e.getMessage() 请提示用户稍后再试; } }这样做的好处是模型拿到一个失败结果后还能基于这个结果组织出一句友好的话目前天气服务暂时不可用建议稍后再查。 而不是抛出一个堆栈让用户一脸懵。我实际踩过这个坑早期工具方法直接throw new RuntimeException结果整个对话直接中断后来改成返回错误描述模型反而能处理得很好。4.3 多工具并行调用与Agent初体验一个复杂的业务往往不止一个工具。比如用户问杭州天气怎么样顺便帮我订一束花送到西湖边你可能需要天气工具和鲜花下单工具同时工作。Spring AI 2.x支持模型返回多个tool_calls框架会循环执行并收集所有结果再把结果统一回传。你不需要自己处理并行逻辑只要把所有工具都注册到ChatClient里就行。我建议把多个工具放在同一个工具类里或者按业务域拆成多个Component但注册时都传给.defaultTools()。比如.defaultTools(weatherTools, orderTools, logisticsTools)当工具数量变多后模型就有了“编排”能力它可以自己决定先查物流再算赔付还是先看天气再推荐穿搭。从某种意义上说这就是最朴素的Agent了。不过别高兴太早工具越多模型调用出错的可能性也越高。我的建议是新加一个工具时单独测一遍这个工具能否被正确触发确认无误后再和其他工具合体。一次性上七八个工具出了问题你根本不知道是工具定义的问题还是模型理解的问题。5. 从Dify工作流迁移到Spring AI Agent的实战思路5.1 为什么我建议把节点翻译成工具我这次之所以折腾Spring AI很大原因是要把一个跑得不错的Dify工作流迁到Java代码里。Dify里你拖节点比如开始节点、LLM节点、HTTP请求节点、条件分支节点它们通过连线形成画布。而Spring AI没有画布只有代码和模型。我一开始也想着把Dify的每个节点一比一翻译成一个Java函数后来发现完全没必要甚至翻译出来非常别扭。Dify里的HTTP请求节点、知识库检索节点、工具节点本质上就是“某个动作的封装”。这些动作放到Spring AI里直接变成一个Tool方法就可以。但Dify里的条件分支节点不是工具那是“流程控制”逻辑应该让模型根据工具结果自己做判断而不是用if/else写死。所以我的迁移第一步是把Dify画布上的节点重新分类哪些是“动作”哪些是“判断”。动作变成工具判断交给模型和提示词。这个思路一旦转过来后续拆代码就顺畅了。5.2 迁移步骤分组、抽象、重组我以一个电商售后场景为例。原来的Dify工作流大概是用户提问 - 获取订单信息 - 查询物流状态 - 判断是否超时 - 生成安抚话术。这个流程用Spring AI怎么改把“获取订单信息”写成一个get_order_info工具方法入参是订单号。把“查询物流状态”写成一个query_logistics_info工具方法入参是订单号。把“是否超时”这个判断逻辑直接放在query_logistics_info的返回值里比如返回订单已签收或物流已超过5天未更新。写一段系统提示词告诉模型当用户咨询订单时效时先调用订单工具再根据物流工具返回的结果判断是否需要补偿。模型收到用户消息后会自动决定调用哪些工具、调用顺序是什么。你根本不需要像Dify那样手动连线。刚开始可能不习惯觉得“失去控制”但跑几轮后会发现这比写死分支的适应性更强。GitHub上确实有一些dify工作流转成spring ai java代码的参考仓库我也看过几个。但我要提醒一句不要直接照搬。因为每个工作流的业务逻辑不同而且不同Spring AI版本之间的API差异很大。正确姿势是参考它们的工具拆分思路对照你自己项目的依赖版本逐行理解后再改。5.3 Spring AI Alibaba没停更只是换了姿势搜spring ai alibaba停更了吗的同学大概率是把某些旧仓库或旧版本的下载量当成了死亡信号。我实际跟踪下来项目并没有停更只是从1.x升级到2.x时包结构、starter命名、自动配置类位置都做了比较大的调整。很多人还在用旧的spring-ai-alibaba坐标或者老的ToolCallback写法升级失败后误以为项目不维护了。我这次用com.alibaba.cloud.ai:spring-ai-alibaba-starter:2.0.1是正常的百炼连接和工具调用都跑得通。如果你在Maven中央仓库看到的版本号比较新但网上旧教程还是1.x的代码请以你当前引入版本的官方文档为准。真不是它停了是它变化太快教程还没跟上。6. 常见问题排查与避坑清单6.1 模型为什么不调用工具这个是我被问得最多的问题。工具写好了、注册了但用户问相关问题时模型还是直接回答完全不碰工具。我总结下来最常见的原因有三个第一模型本身或配置不支持Function Calling。虽然现在主流大模型基本都支持但个别旧版本或轻量模型能力偏弱需要确认百炼上你开通的模型确实支持工具调用。第二工具描述写得太模糊。比如你写获取天气模型无法确定什么时候该用。最好写成当用户询问某个城市的当前天气、温度、穿衣建议时使用该工具这种带触发条件的描述。第三温度参数太高。模型在“自由发挥”模式下可能会忽略工具。把temperature调到0.3以下工具调用的稳定性会明显提升。排查时先打开DEBUG日志看请求发出时工具列表有没有被真正带上。Spring AI的日志里如果看不到任何工具定义的输出说明问题在注册环节而不是模型判断环节。6.2 工具结果没生效 / 二次调用失败还有一类问题工具方法确实执行了日志也打了但模型最终的回答还是错的或者干脆报错。这种情况多半出在工具返回结果和对话历史之间的衔接上。工具返回结果要尽量完整。比如你查订单不要只返回一个订单号要返回模型需要的所有信息订单状态已发货物流公司顺丰物流单号SF123456预计送达明天18点前。如果返回的信息太少模型只能凭猜一猜就容易错。如果二次调用失败还要检查工具方法是否被重复注册。我踩过一次ChatClient里用了defaultTools()结果我在某个Service里又手动拼了工具定义两个一样的工具名冲突导致模型调用时框架不知道选哪个。工具名一定保持全局唯一同一个工具不要通过两种方式同时注册。6.3 避坑速查表我把这段时间遇到的典型问题整理成一张表方便你对照排查症状可能原因处理方案模型完全不理工具工具描述不清晰 / 模型不支持优化描述降低temperature确认模型版本工具调用了但返回结果是旧的方法里缓存/写死了返回值检查工具方法体改成真实调用参数总是传错参数描述不清晰或参数过多精简参数用字符串传JSON描述写具体工具抛异常导致对话中断异常直接抛出未捕获在工具方法内catch返回错误描述字符串多个工具时模型乱调用工具粒度太粗/职责重叠拆分工具确保每个工具单一职责启动后工具没加载工具类没加Component检查Spring扫描路径配置不生效配置前缀与版本不匹配查看starter里的Properties类6.4 内存与并发下的工具调用如果你的服务需要高并发工具调用还有一个容易被忽略的问题工具内部是否线程安全。Tool方法通常会通过Spring容器管理可能是单例如果里面用了有状态的成员变量并发场景下就会出乱子。我在一个工具里用了SimpleDateFormat作为成员变量结果并发一上来解析日期偶尔会错。后来改成在方法内部创建局部变量才解决。工具方法最好保持无状态所有临时对象都在方法内部创建不要用共享的可变对象。另外如果工具方法内部调用了慢接口整个对话的响应时间会被拖长。建议给外部HTTP调用设置超时和重试但重试逻辑要轻不然模型等到不耐烦直接“放弃治疗”。我在实际项目里工具方法内部的最长执行时间控制在5秒以内超过就返回超时错误描述。7. 最后再分享一点实际体会Function Calling用得好不好很大程度取决于你把业务拆成什么粒度的工具。工具越接近人类能理解的一个动作模型就越不容易出错。我这边的做法是先给每个工具写一句话描述自己读一遍如果这句话里包含超过两个并且就说明粒度太粗需要拆。这个习惯帮我少踩了很多坑。如果你也在做从Dify到Spring AI的迁移或者刚在项目里集成百炼的Qwen我建议先拿一个最小闭环跑起来比如就做一个天气查询工具确认整个链路通了再加第二个、第三个工具。跑通了后面就快了一上来就想复刻整个工作流很可能卡在配置和版本上反而拖慢进度。希望这些踩坑记录能帮你少走弯路。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

VS Code集成Claude API实战:从零构建安全可控的AI编程副驾 2026/10/2 5:52:23

VS Code集成Claude API实战:从零构建安全可控的AI编程副驾

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

阅读更多 →
网络安全入门学习路线:从网络协议基础到实战靶场 2026/10/2 5:52:17

网络安全入门学习路线:从网络协议基础到实战靶场

1. 入行之前,先搞清楚“网络安全”到底在做什么这些年经常有人问我:“我想学网络安全,但网上一搜全是互相矛盾的内容,有人说门槛高,有人说初中毕业就能干,我该信谁?”说实话,这种混乱…

阅读更多 →
FACT:用细粒度跨变量卷积建模动态交互,破解多变量时序预测难题 2026/10/2 5:52:17

FACT:用细粒度跨变量卷积建模动态交互,破解多变量时序预测难题

做多变量时间序列预测这几年,我最头疼的不是把单条曲线的趋势拉准,而是变量和变量之间的关系。一开始我也迷信 Transformer 那套,觉得让注意力自己去“发现”变量间的相关性就完事了,结果跑电力负荷、交通流这类数据时很快发现&am…

阅读更多 →
数字黄金:个人内容资产的确权与长期保存技术 2026/10/2 5:52:17

数字黄金:个人内容资产的确权与长期保存技术

我无法根据当前输入生成符合要求的博文。原因在于:您提供的输入内容中,项目正文为空、关键词未列出、摘要描述缺失,仅有一个项目标题“CSW博客《数字黄金》”和若干无关的占位字段(如“最新网络热词:”后无实际内容&am…

阅读更多 →
XCTF总决赛解题赛全解析:从CTF赛制到安全能力实战进阶 2026/10/2 5:52:16

XCTF总决赛解题赛全解析:从CTF赛制到安全能力实战进阶

1. 为什么说 XCTF 总决赛值得每个安全人关注第九届 XCTF 总决赛来了。这两天我的朋友圈基本被刷屏,不只是因为比赛本身的奖金和名次,而是因为“全球网安大神齐聚”这几个字背后,站着一批真正代表国内甚至国际顶尖水平的安全选手和战队。作为一…

阅读更多 →
openrig 配置实战:Claude Code 与 Codex 多模型接入指南 2026/10/2 5:52:10

openrig 配置实战:Claude Code 与 Codex 多模型接入指南

1. openrig 到底是个什么东西第一次看到 openrig 这个名字,我下意识以为是某个硬件机架项目,毕竟 rig 在英文里常指设备支架、测试台架。翻了翻社区讨论和几个相关仓库之后才反应过来,它更像是围绕 Claude Code、Codex 这类命令行 AI 编程工具…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉