拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Spring AI 多个 @Tool 谁被调用?把 defaultTools 贴给走 TaoToken 的 Codex 对照

1. 多个 Tool 到底谁被调用一个让人抓头的 Spring AI 排障现场Spring AI 里 Function Calling 的写法看着很优雅给 Java 方法挂上Tool再用builder.defaultTools(weatherTool, orderTool)把对象交给模型剩下的事模型自己挑。但真跑起来很多人会遇到一个很别扭的现象——明明注册了两个工具问「帮我查下订单 12345 到哪了」模型却去调天气工具或者更离谱两个工具一个都不调直接编一段回答糊弄过去。而 Spring AI 这边日志干干净净没有异常、没有报错你盯着ChatClient的调用链看半天也看不出问题出在哪。这个场景的核心矛盾在于工具选择是模型做的决策不是 Spring AI 做的路由。Spring AI 只负责把Tool的元信息方法名、description、参数描述序列化进请求模型根据这些文字描述和用户提问的语义匹配度来决定调谁。所以一旦 description 写得太泛、或者defaultTools只传了一个对象模型拿到的「菜单」本身就是残缺或模糊的它只能瞎猜。你以为是框架 bug其实是提示词层面的信息缺失。我试过最有效的排查方式不是反复重启 Spring Boot 看日志而是把Tool的描述和defaultTools那段 Java 代码原样丢给一个能读代码的模型通道让它逐条对照每句 description 和用户可能的提问意图能不能对上两个工具是不是都真的进了defaultTools这个「读代码的模型通道」我用 TaoToken 提供的 Key 和 Base URL 接到 Codex 上跑下面把整套流程拆开讲清楚你照着做就能定位到底是描述问题还是注册问题。2. 用 TaoToken 给 Codex 配一条读代码的模型通道TaoToken 在这里的角色很单纯它是一个模型 API 的接入入口负责给你一把 Key 和一个 Base URL让你的 Codex或者任何兼容 OpenAI 接口的客户端能跑起来。它不碰你的 Spring AI 项目不改你的ChatClient代码也不参与工具调用逻辑。你拿到的 Key 配通的是 Codex 这条「读代码、做对照分析」的通道分析完再回到 Spring AI 项目里改 description 或补defaultTools参数。先打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册账号进控制台创建 API Key。创建入口在 https://taotoken.net/console 里Key 管理页是 https://taotoken.net/api-keys 生成后复制保存后面配置要用。Base URL 填https://taotoken.net/api注意结尾不要带/v1也不要加任何 UTM 参数。这一点很多人踩坑客户端默认会自己在 Base URL 后面拼/v1/chat/completions你要是手动写成https://taotoken.net/api/v1最后请求路径就变成/api/v1/v1/chat/completions直接 404。配置文档在 https://taotoken.net/doc 有完整说明遇到路径问题先去对一遍。Codex 侧的配置核心就是两个环境变量或者配置文件字段OPENAI_API_KEY填你刚创建的 KeyOPENAI_BASE_URL填https://taotoken.net/api。如果你用的是 Codex 的配置文件形式大致长这样# ~/.codex/config.toml model gpt-4o-mini provider openai [providers.openai] name taotoken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY对应的环境变量export TAOTOKEN_API_KEYsk-你创建的Key配完之后先别急着贴 Spring AI 代码跑一条最小请求验证通道是否通。这一步很关键通道不通的话后面所有分析都是白搭。3. 可复制配置把 WeatherTool、OrderTool 和 defaultTools 原样喂给 Codex通道验证通过后进入正题。你要做的是把 Spring AI 项目里跟工具调用相关的代码片段整理成一段「待分析材料」然后让 Codex 逐条对照。材料分三块两个 Tool 类的完整定义、ChatClient构建时defaultTools的调用、以及你实际发出去的用户提问。先看 WeatherTool 和 OrderTool 的典型写法Component public class WeatherTool { Tool(description 根据城市名称查询天气) public String getWeather( ToolParam(description 城市名称例如北京) String city) { return city 今天晴气温 25℃; } } Component public class OrderTool { Tool(description 查询订单状态) public String queryOrder( ToolParam(description 订单号例如12345) String orderId) { return 订单已发货; } }再看ChatClient的构建这里是最容易出问题的地方Service public class AiService { private final ChatClient chatClient; public AiService(ChatClient.Builder builder, WeatherTool weatherTool, OrderTool orderTool) { this.chatClient builder .defaultTools(weatherTool, orderTool) .build(); } public String chat(String message) { return chatClient.prompt() .user(message) .call() .content(); } }把上面这两段代码加上你实际测试时用的提问比如「北京今天天气怎么样」和「帮我查订单 12345」一起贴给 Codex然后给它一个明确的对照任务。我用的提示词结构是这样的下面是一个 Spring AI 项目的工具调用代码。请逐条分析 1. 每个 Tool 的 description 描述和用户提问的意图能不能对上 列出每个工具可能被哪些提问触发以及哪些提问会误触发它。 2. defaultTools 里传了几个工具对象WeatherTool 和 OrderTool 是不是都进去了有没有漏传 3. 如果用户问「帮我查订单 12345 到哪了」按当前 description 模型最可能选哪个工具为什么 4. 给出具体的 description 修改建议要求能明确区分天气查询和订单查询。 代码 [粘贴 WeatherTool、OrderTool、AiService 三段代码] 用户提问示例 - 北京今天天气怎么样 - 帮我查订单 12345 到哪了这个提示词的关键是第 3 问——逼模型模拟一次真实的工具选择而不是泛泛地说「描述可以更清晰」。很多时候你看到它模拟出来的选择结果就立刻明白问题在哪了。4. 验证请求与成功结果从「模型挑错」到「精准命中」贴完代码后Codex 会返回一份对照分析。一份有效的分析结果应该包含几个可验证的结论。比如针对上面那段代码它大概率会指出WeatherTool的 description「根据城市名称查询天气」里带了「城市名称」这个限定而OrderTool的「查询订单状态」没有强调「订单号」这个参数特征导致用户说「帮我查订单 12345」时模型可能因为「查询」这个动词的泛化而犹豫甚至误判成某种查询类工具。更关键的是defaultTools那一行。如果原文里只写了.defaultTools(weatherTool)Codex 会直接告诉你 OrderTool 根本没注册进去模型压根看不到这个工具自然永远不会调它。这就是「模型不调用」类问题最常见的根因——不是模型不想调是菜单上没这道菜。拿到分析后回到 Spring AI 项目里改。description 的修改方向是让每个工具的描述带上「排他性关键词」。比如Tool(description 查询指定城市的实时天气输入必须是城市名称如北京、上海) public String getWeather( ToolParam(description 城市名称例如北京) String city) { ... } Tool(description 根据订单号查询订单物流状态输入必须是订单号如 12345) public String queryOrder( ToolParam(description 订单号例如12345) String orderId) { ... }改完 description再确认defaultTools里两个对象都在this.chatClient builder .defaultTools(weatherTool, orderTool) .build();然后重启服务用同样的提问再测一遍。成功的结果是问天气时只调getWeather问订单时只调queryOrder两个工具各司其职。你可以在Tool方法里加一行日志确认到底哪个方法被执行了Tool(description 查询指定城市的实时天气输入必须是城市名称如北京、上海) public String getWeather(ToolParam(description 城市名称例如北京) String city) { System.out.println([ToolCalled] getWeather, city city); return city 今天晴气温 25℃; }日志里出现[ToolCalled] getWeather就说明模型确实选中了它。如果问订单时日志里冒出的是getWeather那说明 description 还是没区分开回去继续改。5. 本篇常见错排查模型不调用、挑错工具、参数传空排障时按下面这个顺序查基本能覆盖九成情况。第一类模型压根不调用任何工具。先查defaultTools是不是只传了一个对象或者干脆没调这个方法。Spring AI 里工具注册有两种方式defaultTools是全局默认prompt().tools()是单次请求级。如果你在ChatClient构建时没写defaultTools又在prompt()里没加.tools()模型就一个工具都看不到。另外确认你的模型本身支持 Function Calling有些小模型或旧版本模型不支持工具调用会直接忽略工具定义。第二类模型挑错了工具。这是 description 的问题。两个工具的 description 如果都包含「查询」这种泛动词又没有各自的领域限定词模型就只能靠猜。解决办法是给每个 description 加上「输入特征」和「业务领域」比如天气工具强调「城市名称」订单工具强调「订单号」。让描述之间形成互斥模型才有明确的区分依据。第三类工具被调用了但参数是空的或者错的。检查ToolParam的 description 有没有写清楚参数格式。如果参数描述是空的模型不知道要传什么可能传个空字符串或者编一个。参数描述里最好带上示例值像「城市名称例如北京」这样。第四类Codex 分析结果和实际运行不一致。这通常是通道问题。先确认 Base URL 是https://taotoken.net/api结尾没有/v1也没有多余斜杠。如果请求报 401检查 Key 是不是复制完整了报 404 就是路径拼错了。接入文档 https://taotoken.net/doc 里有各客户端的配置示例对一遍就能排除。还有一个隐蔽的坑Tool注解的方法必须是 public 的而且所在类要被 Spring 扫描到加了Component。如果 Tool 类没被注册成 Bean你注入到AiService构造器里的时候就会启动失败这个反而容易发现。真正难查的是 Bean 注册了、defaultTools也传了但 description 写得太烂模型选择行为完全不可预测。6. 把这条通道用顺后续调试和长期编码的接入建议这套「贴代码给 Codex 做对照分析」的方法不只适用于两个工具的排障。你后面加第三个、第四个 Tool 的时候同样可以把所有Tool描述和defaultTools一起贴过去让它检查工具之间有没有语义重叠、有没有哪个工具的 description 过于宽泛会抢别人的活。工具越多description 之间的边界越重要靠人眼逐个比对很容易漏。如果你只是偶尔排一次工具调用的 bug用 API Keys 配 Codex 就够了Key 管理在 https://taotoken.net/api-keys 随用随建。如果你打算把这种「读代码 对照分析」变成日常开发流程的一部分比如每次改完 Tool 描述都跑一遍一致性检查那可以考虑 Coding Plan它在长期编码和 Agent 场景下更顺一些具体在 https://taotoken.net/coding-plan 看。想先直接跟模型对话验证某个 description 改法效果如何可以走模型对话入口 https://taotoken.net/chat 。Claude Code 相关的接入配置在 https://taotoken.net/ClaudeCodeAnthropic 有说明。回到 Spring AI 本身记住一个原则工具选择的准确性八成靠 description 的质量两成靠 defaultTools 有没有传全。模型不是读心术它只能根据你给的文字描述去匹配用户意图。描述写得越具体、越有排他性模型挑错的概率就越低。把 Codex 这条通道用起来每次改完 description 先让它模拟一遍选择结果比反复重启服务试错快得多。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门