1. Spring AI Alibaba 的 Function Calling 到底卡在哪
如果你正在用 Spring Boot 3.3.x + JDK 17 写 Java 服务,又想接阿里云百炼的通义大模型做工具调用,大概率会遇到一个很具体的卡点:模型能聊天,但一到 Function Calling 就报错或者干脆不触发函数。Spring AI Alibaba 的spring-ai-alibaba-starter目前还在 M6.x 里程碑阶段,依赖仓库、API Key 注入方式、withFunction的注册链路,任何一环没对齐,工具调用就跑不通。
这篇聚焦的是「Spring AI 在阿里巴巴生态下的 Function Calling 落地」,面向用 Spring Boot 与 JDK 的 Java 开发者。我会先给出一套可复制的统一 Key/API 通道配置骨架,包含settings.json与config.toml示例,再给出在 Cline 中接入后的验证动作,最后回到 Spring AI Alibaba 的@Bean Function注册与withFunction调用链路,帮你把工具调用从「模型知道有函数」到「真的执行并返回结果」整条链路跑通。
适合谁看:已经能跑通 Spring Boot 基础工程、手里有通义千问 API Key、想快速验证 Function Calling 的 Java 开发者。如果你还没配 Key,或者 Key 散落在多个工具里管理混乱,下面的统一通道配置会先帮你把入口收敛掉。
2. 前置准备:统一 Key 与 API 通道配置
Spring AI Alibaba 默认走 DashScope 的 API Key,但实际开发中你往往同时在 Cline、Spring Boot 工程、脚本里调用模型,Key 分散管理很容易乱。我试过把 Key 收敛到一个统一通道,工程里只引用环境变量,工具侧用配置文件指向同一入口,切换和排障都省事。
TaoToken 提供统一 Key 与 API 通道,官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (不加 UTM)。你可以在控制台创建 Key,然后在不同工具里复用同一个 Key。
2.1 在 Cline 中配置 settings.json
Cline 的配置走settings.json,把 API 通道指向统一入口,Key 用你创建的那把。下面是一份可直接改的骨架:
{ "cline.apiProvider": "openai-compatible", "cline.apiBaseUrl": "https://taotoken.net/api", "cline.apiKey": "sk-你的统一Key", "cline.model": "qwen-plus", "cline.temperature": 0.3, "cline.maxTokens": 2048 }注意apiBaseUrl只写到/api,不要自己拼/v1/chat/completions,兼容层会处理路径。model字段填你要验证的模型名,Function Calling 场景建议先用qwen-plus或qwen-max,多模态识别再换qwen-vl-max-latest。
2.2 命令行侧 config.toml 示例
如果你同时用命令行工具做快速验证,config.toml可以这样写:
[provider] name = "taotoken" base_url = "https://taotoken.net/api" api_key = "sk-你的统一Key" model = "qwen-plus" timeout_seconds = 60 [function_calling] enabled = true max_rounds = 5max_rounds控制工具调用的最大轮次,防止模型反复触发同一个函数导致死循环。这个值在 Spring AI Alibaba 侧对应的是DashScopeChatOptions里的调用轮次控制,后面会讲。
2.3 Spring Boot 工程侧的环境变量
工程里不要把 Key 写死在application.yml,用环境变量注入:
spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus启动前设置:
export DASHSCOPE_API_KEY=sk-你的统一Key这样 Cline、命令行、Spring Boot 三处共用同一把 Key,排障时只需要确认一处配置。
3. 可复制配置:Spring AI Alibaba Function Calling 骨架
这一节是核心。Spring AI Alibaba 的 Function Calling 依赖三步:定义Function实现类、在配置类注册 Bean、通过withFunction告诉模型何时调用。下面给出完整可复制的代码骨架。
3.1 Maven 依赖与仓库配置
spring-ai-alibaba-starter的部分依赖还没进 Maven 中央仓库,需要在pom.xml里额外加 Spring 的快照仓库:
<dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> <version>1.0.0-M6.1</version> </dependency><repositories> <repository> <id>spring-snapshots</id> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories>JDK 至少 17,Spring Boot 3.3.x 或更高。低于这个版本会在ChatClient.Builder注入时直接报NoSuchMethodError。
3.2 定义 Function 实现类
函数调用的本质是:模型判断需要外部数据时,不直接回答,而是触发你注册的函数。模型怎么知道有哪些函数可调、每个函数干什么、入参出参是什么?靠@Description和@JsonPropertyDescription注解描述。
package com.example.springai; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import org.springframework.web.client.RestTemplate; import java.util.function.Function; public class FinanceService implements Function<FinanceService.FinanceRequest, String> { @Override public String apply(FinanceRequest request) { RestTemplate restTemplate = new RestTemplate(); String url = "https://stock.xueqiu.com/v5/stock/finance/cn/income.json?symbol=" + request.getSymbol() + "&type=all&is_detail=true&count=1"; // 实际项目里解析 response,这里仅示意 return "解析后的数据及简要分析:业务很好,继续保持"; } public static class FinanceRequest { @JsonProperty(required = true, value = "公司代码") @JsonPropertyDescription("上市公司的股票代码") private String symbol; public FinanceRequest() {} public FinanceRequest(String symbol) { this.symbol = symbol; } public String getSymbol() { return symbol; } public void setSymbol(String symbol) { this.symbol = symbol; } } }@JsonProperty(required = true)告诉模型这个参数必填,@JsonPropertyDescription告诉模型这个参数的含义。模型就是靠这些注解生成调用参数的。
3.3 在配置类注册 Bean
package com.example.springai; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Description; import java.util.function.Function; @Configuration public class AppConfig { @Bean @Description("查询指定公司代码的财务信息") public Function<FinanceService.FinanceRequest, String> financeFunction() { return new FinanceService(); } }@Description是给模型看的函数说明,写得越清楚,模型判断何时调用的准确率越高。Bean 名称financeFunction就是后面withFunction里要引用的名字。
3.4 通过 withFunction 触发调用
@GetMapping(value = "/chatStream", produces = "text/html;charset=UTF-8") public Flux<String> chatStream(@RequestParam String input) { PromptTemplate promptTemplate = new PromptTemplate("我想知道{company}的最新财务状况"); DashScopeChatOptions ops = DashScopeChatOptions.builder() .withFunction("financeFunction") .build(); Map<String, Object> map = Map.of("company", input); Prompt prompt = promptTemplate.create(map, ops); return chatClient.prompt(prompt).stream().content(); }withFunction("financeFunction")里的字符串必须和@Bean方法名一致。写错了不会报错,只是模型永远不触发函数,这是最常见的坑。
3.5 参数对照表
| 配置项 | 作用 | 常见错误值 |
|---|---|---|
withFunction | 指定可调用的函数名 | 与 Bean 名不一致 |
@Description | 函数功能说明 | 留空或过于笼统 |
@JsonProperty(required) | 标记必填参数 | 漏标导致模型不传参 |
max_rounds | 最大调用轮次 | 设太大导致循环 |
apiBaseUrl | API 通道地址 | 多拼了/v1路径 |
4. 验证请求与成功结果
配置写完后,先别急着上 Spring Boot,用 Cline 或命令行做一次最小验证,确认 Key 和通道是通的。
4.1 Cline 侧验证动作
在 Cline 里发一条会触发工具调用的消息,比如「帮我查一下 600519 的财务信息」。如果配置正确,你会看到 Cline 先输出一段思考,然后触发函数调用,最后把函数返回结果整合成自然语言回答。整个过程在对话流里能看到tool_call和tool_result两个节点。
如果只看到模型直接编造答案、没有tool_call节点,说明函数没注册成功,回到 3.3 检查 Bean 名和@Description。
4.2 Spring Boot 侧验证
启动工程后请求:
curl "http://localhost:8080/chatStream?input=600519"成功时返回的是流式文本,内容里会包含函数返回的「解析后的数据及简要分析」。如果返回的是模型直接编的财务数据,说明withFunction没生效。
4.3 成功结果的判断标准
真正的 Function Calling 成功,是模型输出里包含了你函数返回的原始字符串片段。如果模型输出的是它自己「想象」的财务数据,哪怕看起来很像,也是失败的。这一点在验证时一定要盯住。
5. 本篇常见错误排查
5.1 函数不触发
最常见。按顺序查三处:@Bean方法名和withFunction字符串是否完全一致;@Description是否写了且语义清晰;@JsonProperty(required = true)是否标在必填参数上。三处都对还不触发,把max_rounds调到 3 以上再试。
5.2 依赖拉不下来
spring-ai-alibaba-starter报Could not resolve,检查pom.xml里 Spring 快照仓库是否加了,snapshots.enabled是否为true。公司内网环境还要确认仓库地址没被镜像覆盖。
5.3 API Key 注入失败
api-key读不到,先确认环境变量名和application.yml里的${DASHSCOPE_API_KEY}一致。用echo $DASHSCOPE_API_KEY确认变量在当前 shell 生效。IDEA 里跑的话,要在 Run Configuration 的 Environment variables 里单独加。
5.4 流式响应乱码
produces = "text/html;charset=UTF-8"必须加,否则中文会乱码。如果用了Flux<String>还乱码,检查HttpServletResponse的setCharacterEncoding("UTF-8")是否在写响应前调用。
5.5 模型反复调用同一函数
max_rounds设太大,或者函数返回结果里包含了会再次触发调用的关键词。把max_rounds降到 3,并在函数返回里避免出现和用户问题高度相似的表述。
6. 接入与排障入口
Function Calling 跑通后,下一步通常是把它接到长期编码或 Agent 流程里。如果你在排障阶段卡在 Key 或接入配置,直接去 API Keys 页面创建和核对:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
想先验证模型本身对工具调用的判断能力,用模型对话页面快速试:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。如果你要把这套 Function Calling 链路放进长期编码或 Agent 工作流,Coding Plan 更适合:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
最后补一个实操细节:@Description里不要写「查询财务信息」这种笼统描述,写成「根据股票代码查询该公司最近一期利润表数据,返回营收、净利润、同比增速」,模型触发准确率会明显提升。这个改动我实测下来比调temperature有效得多。