Spring AI 工具调用:让 Agent 自己动手查天气

发布时间:2026-08-25 22:05  浏览量:1

模型再聪明,也有个致命短板:它没有实时数据。训练数据有截止日期,你问它"北京今天多少度",它要么瞎编,要么老实说"没有实时信息"。

工具调用(Tool Calling)就是用来补这块短板的——

让 Agent 在你定义的函数里"动手"查数据,再把结果组织成回答。

天气查询,是这个能力最经典、也最好懂的演示场景。

做这件事的理由也很直接:Gartner 预测到 2028 年,33% 的企业软件将内嵌 Agentic AI,而一个"只会聊天、不会动手"的 Agent,离真正有用还差得远。工具调用,正是 Agent 从"嘴"到"手"的那一步。

天气查询这个例子之所以经典,是因为它恰好卡在模型能力的边界上:模型知道"天气"这个概念,但没有"今天、此刻、具体城市"的数据。这种"知识在、数据缺"的场景,正是工具调用最擅长解决的——让模型调用一个能拿到实时数据的函数,补上它缺的那一块。

先理解机制,代码才好懂。整个工具调用,是模型和框架之间的一次"你来我往":

用户问"北京天气怎么样"。模型判断:这个问题需要实时数据,得调用一个工具,于是返回一条指令——"调用 getWeather,参数 city=北京"。框架收到指令,替它执行 getWeather("北京"),拿到真实的天气结果。框架把结果回传给模型,模型组织成一句人话:"北京今天晴,25 摄氏度"。

关键在第二步:模型不自己查数据,它只负责"决定调用什么、传什么参数",真正执行的是框架,结果也是框架喂回去的。

这一来一回,就是工具调用的全部。

官方把工具分成了几类,天气查询属于"信息检索类"——从数据库、Web 服务、搜索引擎这些外部源拿数据,增强模型回答不了的问题。除此之外还有"执行操作类"(发邮件、下单)等。不同类别的工具,设计时的侧重点不一样,但机制是同一套。

先定义"天气查询"这个工具。用 @Tool 注解标在一个方法上,告诉模型"有这么一个能力可用":

public class WeatherTools { @Tool(description = "查询指定城市的实时天气") public String getWeather(String city) { // 实际项目里,这里调用真实的天气 API(如和风天气、OpenWeatherMap) // 下面是示意返回 return city + ":晴,25 摄氏度,东南风 2 级"; }}

description 这一句是灵魂

——模型就是靠它判断"用户这句话要不要调这个工具"。写清楚、写具体("查询指定城市的实时天气"),模型才会在正确的时候调用;写得含糊,模型要么乱调、要么不调。

方法参数也很重要:getWeather(String city) 里的 city,Spring AI 会自动生成对应的 JSON Schema,让模型知道"调用时要传一个 city 参数"。

这套 JSON Schema 的自动生成,是工具调用能"开箱即用"的关键。开发者不用手动写复杂的参数定义,只要方法签名写清楚,模型就能理解"这个工具需要哪些参数、参数是什么类型"。方法参数越规范,模型调用就越准。

定义好了,还得告诉 Agent"你有这个工具可用"。注册很简单,用 .tools 传入工具实例:

@GetMapping("/weather")public String weather(@RequestParam String question) { return chatClient.prompt .user(question) .tools(new WeatherTools) .call .content;}

就这一行 .tools(new WeatherTools),工具就挂上去了。现在访问 /weather?question=北京天气怎么样,Agent 就会自己判断、自己调用工具、自己组织回答。

整个过程,开发者只需要做两件事:定义一个 @Tool 方法、注册它。

中间的"判断、调用、回传、循环"全由 Spring AI 处理。

这一步值得展开看看,因为它是理解 Agent 的关键。Spring AI 内置了完整的 ReAct(推理-行动)循环:

模型先

推理

:"这个问题需要实时天气,该调 getWeather,参数是北京。"框架执行这个

行动

,拿到结果。模型拿到结果,再判断:"信息够了,可以回答了。" 于是输出最终回复。

如果一次工具调用不够,模型可以连续调多次

——比如"查完北京的天气,再查上海的",模型会自己串起来,直到信息够了才给最终答案。

这就是 Agent 和普通聊天机器人的本质区别:

聊天机器人是"一口答完",Agent 是"边想边做"

。而这个"边想边做"的循环,Spring AI 已经帮你写好了,你一行循环代码都不用写。

如果把这段循环拆开看,每一轮都是一次独立的模型调用:模型看到"用户问题 + 历史工具调用结果",决定下一步是"再调一个工具"还是"该回答了"。这个判断会一直重复,直到模型认为信息够了。开发者看到的,只是最终那一句回答。

真实项目里,还会用到三个进阶能力。

参数描述

:用 @ToolParam 给参数加说明,帮助模型更准确地传参:

@Tool(description = "查询指定城市的天气")public String getWeather( @ToolParam(description = "城市名称,如 北京、上海") String city) { return city + ":晴,25 摄氏度";}

异常处理

:工具执行失败时,把错误信息返回给模型,让它知道"这次没查到",从而换种方式或如实告知用户:

@Tool(description = "查询指定城市的天气")public String getWeather(String city) { try { return weatherApi.query(city); } catch (Exception e) { return "查询失败:" + e.getMessage; }}

多工具

:一个 Agent 可以有多个工具,模型按需选择。比如再加一个"查空气质量"的工具:

chatClient.prompt .user(question) .tools(new WeatherTools, new AirQualityTools) .call .content;

模型会根据问题内容,自己决定调哪个工具、调几个工具。

这里有个值得留意的点:工具越多,模型"选错工具"的概率越高。所以工具列表不是越多越好,而是每个工具职责清晰、description 明确,让模型能快速判断。工具数量和质量之间,要取一个平衡。

工具调用这套机制不难,但有几个坑容易踩:

description 写得太笼统

:模型判断"要不要调工具"全靠它,写"查询天气"比写"获取信息"准确得多。

参数没描述

:复杂参数不加 @ToolParam 说明,模型容易传错。

工具不幂等

:如果工具会"下单""扣款"这类有副作用,要特别小心——模型可能在一次对话里重复调用它。

把这些处理好,工具调用就能从"能跑"变成"敢在生产里用"。

核心始终是三件事:@Tool 定义、.tools 注册、剩下的交给 ReAct 循环。

有了这个能力,你的 Agent 就不再是"只会聊天的模型",而是一个能查数据、能调接口、能动手干活的助手——而天气查询,只是它迈出的第一步。

顺着这条思路往下走,天气查询可以换成任何能力:查数据库、调内部接口、发通知、跑报表。工具调用这套机制不变,变的只是 @Tool 方法里那段业务逻辑。这也是它最有价值的地方——把 Agent 的"动手能力",变成了一个可无限扩展的插槽。

回顾这一整套机制,会发现工具调用其实把一个很朴素的道理工程化了:让模型在"它知道的"和"它需要的"之间,多了一条"动手去查"的通道。有了这条通道,Agent 的能力边界就不再受限于模型的训练数据,而是取决于愿意给它接多少个工具。天气查询是起点,但它的终点,是一整个能连接外部世界的 Agent——到那一步,模型就不再是孤立的"大脑",而是有了"手脚"的行动者。