
在之前的文章中,我们已经掌握了 Agent 的记忆能力,实现了能够记住用户偏好的智能行程规划 Agent。然而,你可能会发现一个问题:当用户问 “北京明天天气怎么样?” 时,Agent 只能回复 “抱歉,我无法获取实时天气信息”。当用户问 “帮我推荐一些北京的热门景点” 时,Agent 只能依赖训练数据中的旧信息,无法提供最新、最准确的推荐。
这是因为 Agent 虽然具备强大的推理和记忆能力,但缺乏 “手脚”——无法主动获取外部信息或执行实际操作。本文将带你深入了解 Agent 的工具调用能力,通过 Spring AI 的 Tool 机制,让 Agent 从 “信息提供者” 升级为 “任务执行者”。
本系列文章源码:https://gitee.com/weizhong1988/weiz-spring-ai
大语言模型(LLM)虽然具备强大的认知能力,但存在天然局限:
这就像一个 “身怀绝技但被困在密室的天才”——有满腹经纶,却无法触及外界。
工具调用(Tool Calling)为 Agent 提供了 “手脚”,使其能够:
有了工具,Agent 从 “只能对话的 AI” 进化为 “能完成任务的智能助手”。
场景 | 调用工具类型 | 实现效果 |
|---|---|---|
智能客服 | 订单查询、退款处理、转人工 | 自动处理售后请求,无需人工介入 |
智能行程规划 | 天气查询、景点推荐、酒店预订 | 结合实时数据生成个性化行程方案 |
智能投研 | 股票行情、财报检索、新闻聚合 | 实时市场数据辅助投资分析与决策 |
智能办公 | 邮件发送、日程查询、报表生成 | 自动化日常办公任务,提升效率 |
Tool(工具) 是 Agent 可以调用的外部函数或 API,用于扩展 Agent 的能力边界。每个 Tool 通常包含:
核心流程如下:

工具调用的核心原理可以概括为 “三轮对话” 模式:
第一轮:用户提问 → 模型判断是否需要工具
用户:北京明天天气怎么样?
模型:我需要调用 getWeather 工具
返回:{ "tool_calls": [{ "name": "getWeather", "arguments": { "city": "北京" } }] }
第二轮:应用层执行工具 → 返回结果给模型
应用层:调用天气 API → { "city": "北京", "weather": "晴", "temp": "18-25°C" }
返回:{ "tool_call_id": "xxx", "content": "{ \"city\": \"北京\", \"weather\": \"晴\", \"temp\": \"18-25°C\" }" }
第三轮:模型基于工具结果生成最终回复
模型:北京明天晴,气温 18-25°C,适合出行。建议携带防晒用品。
这个 “判断 → 执行 → 回复” 的闭环,正是工具调用的核心流程。
定义工具的入口,标注在 Spring Bean 方法上,Spring AI 自动识别并注册为可调用工具Spring AI 已经把工具调用流程封装得非常简单,整体流程只有 5 步:

@Tool(description = "获取指定城市的实时天气信息,返回天气状况、温度范围、空气质量及出行建议")
public String getWeather(
@ToolParam(description = "城市名称,支持中文") String city
) { ... }
// 自动生成的 JSON Schema:
{
"type": "function",
"function": {
"name": "getWeather",
"description": "获取指定城市的实时天气信息,返回天气状况...",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名称,支持中文" }
},
"required": ["city"]
}
}
}Spring AI 对工具的定义方式设计得十分简洁易用,开发者在使用过程中无需复杂配置,仅需通过@Tools注解即可快速完成工具的声明与注册,极大简化了工具接入与集成的流程。
@Component
public class WeatherTool {
@Tool(description = "获取指定城市的实时天气信息")
public String getWeather(
@ToolParam(description = "城市名称") String city
) {
return "{\"city\": \"北京\", \"weather\": \"晴\"}";
}
}4.1 案例目标
延续前面的智能行程规划 Agent 案例。Agent 已具备记忆能力,本次目标是为其集成两个实用工具:
实现效果:用户说"帮我规划北京周末游,看看天气,再推荐几个人文景点",Agent 自动判断需要调用天气和景点两个工具,获取实时数据后整合生成个性化行程。
技术模块 | 选型方案 |
|---|---|
开发框架 | Spring Boot 3.5.3 + Spring AI 1.0.0 |
大模型 | 智谱 AI(Chat:GLM-4-Flash) |
核心依赖 | Spring Web、Spring AI 智普 AI Starter、Lombok |
工具实现 | 模拟天气 API + 模拟景点数据(可替换为真实 API) |
4.3 项目初始化与配置
1. 创建 Spring Boot 项目
Weiz-SpringAI-Agent-Tool2. 配置 pom.xml 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>Weiz-SpringAI-Agent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>Weiz-SpringAI-Agent-Tools</artifactId>
<name>Weiz-SpringAI-Agent-Tools</name>
<description>Weiz-SpringAI-Agent-Tools</description>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-client-chat</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>3. 配置 application.properties
spring.application.name=Weiz-SpringAI-Agent-Tools
server.port=8080
spring.ai.zhipuai.api-key=你的智谱api key
spring.ai.zhipuai.base-url=https://open.bigmodel.cn/api/paas
spring.ai.zhipuai.chat.options.model=GLM-4-Flash
logging.level.org.springframework.ai=INFO
logging.level.com.example=DEBUG创建 com.example.weizspringai.tool.WeatherTool类,使用 @Tool 注解定义天气查询工具:
package com.example.weizspringai.tools;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
/**
* 天气查询工具
* 实际项目中可替换为真实天气 API(如和风天气、彩云天气)
*/
@Component
public class WeatherTool {
@Tool(description = "获取指定城市的实时天气信息,返回天气状况、温度范围、空气质量、出行建议")
public String getWeather(
@ToolParam(description = "城市名称,支持中文,如:北京、上海、广州、成都、西安、杭州") String city
) {
// 模拟天气 API 调用(实际项目替换为真实 API)
return simulateWeatherApi(city);
}
/**
* 模拟天气 API(演示用)
* 实际项目可接入:和风天气 API、彩云天气 API 等
*/
private String simulateWeatherApi(String city) {
String weatherData;
if (city.contains("北京")) {
weatherData = "晴,气温 18-25°C,空气质量优,紫外线较强,建议防晒";
} else if (city.contains("上海")) {
weatherData = "多云,气温 20-28°C,空气质量良,有轻微雾霾,建议佩戴口罩";
} else if (city.contains("广州")) {
weatherData = "小雨,气温 22-26°C,湿度 85%,建议携带雨具";
} else if (city.contains("成都")) {
weatherData = "阴天,气温 16-22°C,空气质量良,适合室内外活动";
} else if (city.contains("西安")) {
weatherData = "晴朗,气温 15-23°C,空气质量良,适合户外观光";
} else if (city.contains("杭州")) {
weatherData = "多云转晴,气温 19-27°C,空气质量优,西湖游览佳日";
} else {
weatherData = "晴,气温 20-25°C,空气质量良,适合出行";
}
return String.format(
"{\"city\": \"%s\", \"weather\": \"%s\", \"source\": \"实时天气数据\"}",
city, weatherData
);
}
}创建com.example.weizspringai.tool.AttractionTool 类,使用@Tool 注解定义景点推荐工具:
package com.example.weizspringai.tools;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 景点推荐工具
* 实际项目中可对接旅游平台 API 或自建景点数据库
*/
@Component
public class AttractionTool {
// 模拟景点数据库
private static final Map<String, List<AttractionInfo>> ATTRACTIONS_DB = Map.of(
"北京", List.of(
new AttractionInfo("故宫", "人文", "5.0", "3-4小时", "世界文化遗产,明清皇家宫殿"),
new AttractionInfo("长城(八达岭)", "人文", "5.0", "半天", "世界七大奇迹之一"),
new AttractionInfo("颐和园", "人文", "4.8", "2-3小时", "皇家园林博物馆"),
new AttractionInfo("天坛", "人文", "4.7", "2小时", "明清祭天场所"),
new AttractionInfo("北京环球影城", "娱乐", "4.9", "全天", "顶级主题乐园")
),
"上海", List.of(
new AttractionInfo("外滩", "人文", "4.9", "1-2小时", "万国建筑博览群"),
new AttractionInfo("上海迪士尼", "娱乐", "4.8", "全天", "亚洲顶级迪士尼乐园"),
new AttractionInfo("豫园", "人文", "4.6", "2小时", "江南古典园林"),
new AttractionInfo("东方明珠", "人文", "4.7", "2小时", "上海地标建筑")
),
"成都", List.of(
new AttractionInfo("大熊猫繁育研究基地", "自然", "4.9", "半天", "近距离观赏大熊猫"),
new AttractionInfo("宽窄巷子", "人文", "4.7", "2-3小时", "成都历史文化街区"),
new AttractionInfo("都江堰", "人文", "4.8", "半天", "世界文化遗产水利工程"),
new AttractionInfo("青城山", "自然", "4.7", "全天", "道教名山,天然氧吧")
),
"西安", List.of(
new AttractionInfo("兵马俑", "人文", "5.0", "半天", "世界第八大奇迹"),
new AttractionInfo("大雁塔", "人文", "4.8", "2小时", "唐代佛教建筑"),
new AttractionInfo("古城墙", "人文", "4.7", "2-3小时", "中国现存最完整古城墙"),
new AttractionInfo("大唐不夜城", "人文", "4.8", "晚上", "沉浸式唐文化体验")
),
"杭州", List.of(
new AttractionInfo("西湖", "自然", "5.0", "半天", "世界文化遗产,人间天堂"),
new AttractionInfo("灵隐寺", "人文", "4.7", "2小时", "千年古刹"),
new AttractionInfo("宋城", "娱乐", "4.6", "全天", "大型演艺主题公园"),
new AttractionInfo("西溪湿地", "自然", "4.5", "半天", "城市湿地公园")
),
"广州", List.of(
new AttractionInfo("广州塔", "人文/娱乐", "4.9", "2-3小时", "广州地标,小蛮腰,高空观光与游乐项目"),
new AttractionInfo("广州长隆野生动物世界", "自然/娱乐", "4.9", "全天", "国家级野生动物园,亲子游玩首选"),
new AttractionInfo("广州杜莎夫人蜡像馆", "娱乐/室内", "4.7", "2小时", "室内明星蜡像打卡,全空调舒适游览"),
new AttractionInfo("广东省博物馆", "人文/室内", "4.8", "2-3小时", "岭南历史文化全室内展厅,免费需预约"),
new AttractionInfo("广东省博物馆", "人文", "4.8", "2-3小时", "了解岭南历史文化,免费参观需预约"),
new AttractionInfo("永庆坊", "人文", "4.7", "2小时", "老广州骑楼街区,粤剧艺术网红打卡地"),
new AttractionInfo("白云山", "自然", "4.8", "半天", "广州城市绿肺,登高俯瞰全城美景")
)
);
@Tool(description = "根据城市和景点类型偏好推荐热门景点,返回景点名称、类型、评分、建议游览时长、景点简介")
public String recommendAttractions(
@ToolParam(description = "城市名称,如:北京、上海、成都、西安、杭州") String city,
@ToolParam(description = "景点类型偏好:人文、自然、娱乐;不填则推荐全部类型", required = false) String preference
) {
List<AttractionInfo> attractions = ATTRACTIONS_DB.getOrDefault(city, List.of());
if (attractions.isEmpty()) {
return String.format("{\"error\": \"暂无 %s 的景点数据,请尝试其他城市\"}", city);
}
// 根据偏好筛选
if (preference != null && !preference.isEmpty()) {
attractions = attractions.stream()
.filter(a -> a.type.contains(preference) || preference.contains(a.type))
.collect(Collectors.toList());
}
// 构建 JSON 返回结果
StringBuilder result = new StringBuilder();
result.append("{\"city\": \"").append(city).append("\", ");
if (preference != null && !preference.isEmpty()) {
result.append("\"preference\": \"").append(preference).append("\", ");
}
result.append("\"attractions\": [");
for (int i = 0; i < attractions.size(); i++) {
AttractionInfo a = attractions.get(i);
result.append(String.format(
"{\"name\": \"%s\", \"type\": \"%s\", \"rating\": \"%s\", \"duration\": \"%s\", \"intro\": \"%s\"}",
a.name, a.type, a.rating, a.duration, a.intro
));
if (i < attractions.size() - 1) {
result.append(", ");
}
}
result.append("], \"count\": ").append(attractions.size()).append("}");
return result.toString();
}
// 景点信息内部类
private static class AttractionInfo {
String name;
String type;
String rating;
String duration;
String intro;
AttractionInfo(String name, String type, String rating, String duration, String intro) {
this.name = name;
this.type = type;
this.rating = rating;
this.duration = duration;
this.intro = intro;
}
}
}创建com.example.weizspringai.service.ToolTripAgentService 类,封装带工具调用能力的行程规划逻辑:
package com.example.weizspringai.service;
import com.example.weizspringai.tools.AttractionTool;
import com.example.weizspringai.tools.WeatherTool;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.stereotype.Service;
/**
* 带工具调用能力的智能行程规划 Agent
* 核心能力:自动判断并调用天气、景点工具,生成个性化行程
*/
@Service
@RequiredArgsConstructor
public class ToolTripAgentService {
private final ChatModel chatModel;
private final WeatherTool weatherTool;
private final AttractionTool attractionTool;
/**
* 带工具调用的行程规划
* @param demand 用户出行需求(可包含天气查询、景点推荐、行程规划)
* @return 结合实时数据的个性化行程规划
*/
public String planTripWithTools(String demand) {
// 定义 Agent 行为规则:引导模型合理使用工具
String systemPrompt = """
你是一个具备工具调用能力的智能行程规划 Agent,核心规则如下:
1. 当用户询问天气时,必须调用 getWeather 工具获取实时天气数据;
2. 当用户需要景点推荐时,必须调用 recommendAttractions 工具获取景点信息;
3. 结合天气数据和景点信息,生成完整的行程规划建议;
4. 行程安排要考虑天气因素(如雨天推荐室内景点,晴天推荐户外景点);
5. 输出结构清晰,包含天气概况、推荐景点、行程安排、实用提示;
6. 所有实时信息必须通过工具获取,严禁编造天气或景点数据。
回复风格:简洁专业,突出实用信息,适合移动端阅读。
""";
// 克隆一个全新Builder,避免共享污染
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem(systemPrompt)
.defaultTools(weatherTool, attractionTool)
.build();
// 调用大模型(自动判断是否需要工具调用)
return chatClient.prompt()
.user(demand)
.call()
.content();
}
}创建 com.example.weizspringai.controller.AgentToolController 类,提供带工具调用的行程规划 HTTP 接口:
package com.example.weizspringai.controller;
import com.example.weizspringai.service.ToolTripAgentService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 带工具调用的 Agent 接口
*/
@RestController
@RequestMapping("/agent/tool")
@RequiredArgsConstructor
public class AgentToolController {
private final ToolTripAgentService toolTripAgentService;
/**
* 带工具调用的行程规划接口
* @param demand 用户出行需求
* @return 智能行程规划结果(自动调用天气、景点工具)
*/
@GetMapping("/plan")
public Map<String, String> planTrip(@RequestParam("demand") String demand) {
String tripPlan = toolTripAgentService.planTripWithTools(demand);
return Map.of(
"userDemand", demand,
"tripPlan", tripPlan,
"agentType", "带工具调用能力的智能行程规划 Agent"
);
}
}访问接口 http://localhost:8080/agent/tool/plan?demand=北京明天天气怎么样?适合出行吗?
Agent 识别到用户询问天气问题,自动调用 getWeather 工具获取北京实时天气数据,返回结果:
{
"userDemand": "北京明天天气怎么样?适合出行吗?",
"tripPlan": "根据实时天气数据,北京明天的天气是晴,气温在18-25°C之间,空气质量优,紫外线较强,建议出行时注意防晒。",
"agentType": "带工具调用能力的智能行程规划 Agent"
}Agent 基于工具返回数据,生成包含天气概况和出行建议的回答。测试通过,验证了单工具调用的正确性。
访问接口http://localhost:8080/agent/tool/plan?demand=推荐几个北京的人文景点,最好评分高的
Agent 识别到用户需要景点推荐,自动调用 recommendAttractions 工具获取北京热门人文景点,返回结果:
{
"agentType": "带工具调用能力的智能行程规划 Agent",
"userDemand": "推荐几个北京的人文景点,最好评分高的",
"tripPlan": "根据您的需求,我为您推荐了北京的人文景点。经过API调用,我找到了以下四个评分较高的景点:故宫、长城(八达岭)、颐和园和天坛。故宫是世界文化遗产,明清皇家宫殿,建议游览时长为3-4小时;长城(八达岭)是世界七大奇迹之一,建议游览半天;颐和园是皇家园林博物馆,建议游览时长为2-3小时;天坛是明清祭天场所,建议游览时长为2小时。希望这些信息能对您的旅行有所帮助。"
}Agent 基于工具返回数据,整理为结构化的景点推荐列表。测试通过,验证了景点工具的参数传递和结果解析正确性。
访问接口http://localhost:8080/agent/tool/plan?demand=帮我规划北京周末两天游,先看看天气,再推荐几个人文景点,生成完整行程
Agent 识别到这是一个综合请求,自动先后调用两个工具。首先调用 getWeather 工具获取北京天气数据,然后调用 recommendAttractions 工具获取北京人文景点列表,最后综合两部分数据生成完整行程规划。
{
"agentType": "带工具调用能力的智能行程规划 Agent",
"userDemand": "帮我规划北京周末两天游,先看看天气,再推荐几个人文景点,生成完整行程",
"tripPlan": "您好,根据实时天气数据,北京本周末天气晴好,气温18-25°C,空气质量优,紫外线较强,建议外出时注意防晒。根据您的需求,我为您推荐了以下四个人文景点:故宫、长城(八达岭)、颐和园、天坛。故宫是世界文化遗产,明清皇家宫殿,建议游览3-4小时;长城(八达岭)是世界七大奇迹之一,建议游览半天;颐和园是皇家园林博物馆,建议游览2-3小时;天坛是明清祭天场所,建议游览2小时。以下是您的行程安排:第一天:上午游览故宫,下午游览长城(八达岭);第二天:上午游览颐和园,下午游览天坛。希望对您有所帮助。"
}测试通过,验证了多工具组合调用的正确性,以及 Agent 综合多源数据生成完整回答的能力。
访问接口http://localhost:8080/agent/tool/plan?demand=广州天气怎么样?如果下雨的话推荐室内景点
Agent 自动调用天气工具获取广州天气数据,返回 "小雨,气温 22-26°C,湿度 85%"。结合天气结果,Agent 智能调整推荐策略,优先展示室内景点:
{
"agentType": "带工具调用能力的智能行程规划 Agent",
"tripPlan": "当前广州的天气情况为:小雨,气温 22-26°C,湿度 85%,建议携带雨具。根据您的要求,我为您推荐以下室内景点:广州杜莎夫人蜡像馆和广东省博物馆。广州杜莎夫人蜡像馆是一处娱乐/室内景点,评分高达4.7分,建议游览时长为2小时,您可以在这里室内打卡,感受明星的魅力。广东省博物馆则是一处人文/室内景点,评分高达4.8分,建议游览时长为2-3小时,这里可以了解岭南历史文化。希望这些信息能够帮助您制定行程计划。",
"userDemand": "广州天气怎么样?如果下雨的话推荐室内景点"
}通过上述功能测试验证可确认,Agent 调用工具的相关功能已实现并验证通过。
本文通过 Spring AI 的工具调用能力,为智能行程规划 Agent 新增了实时信息获取能力,解决了 "依赖训练数据" 和 "无法执行操作" 两大核心问题。Spring AI 封装了 @Tool 注解、ChatClient 自动编排、JSON Schema 自动生成等复杂机制,开发者只需专注于定义工具的业务逻辑,极大降低了工具调用的开发门槛。
回顾 Agent 能力演进路径:基础 Agent(System Prompt 定义行为)→ 记忆型 Agent(ChatMemory 存储偏好)→ 工具型 Agent(Tool 调用获取实时数据)→ (未来)MCP 型 Agent(标准化协议实现跨系统交互)。
后续文章将继续深入 Agent 的高级特性,讲解如何结合记忆能力与工具调用能力(如记住用户偏好的景点类型后自动筛选推荐),以及 Agent + MCP(模型上下文协议) 的标准化交互模式,构建真正可投入生产环境的智能 Agent 系统。