第十八章 Agent 智能体与今日菜单应用
版本标注
- Spring AI:
1.1.2- Spring AI Alibaba:
1.1.2.0章节定位
- 本章在 Agent 入口上使用
ReactAgent。- 同时把本地菜单工具与外部 MCP 工具合并注册,形成“本地能力 + 外部能力”的组合示例。
s01 > s02 > s03 > s04 > s05 > s06 > s07 > s08 > s09 > s10 > s11 > s12 > s13 > s14 > s15 > s16 > s17 > [ s18 ]
一、本章要做什么
这一章的目标不是再做一个“只有本地工具”的 Agent,而是把两类工具放进同一个 ReactAgent:
- 本地工具:菜单推荐能力。
- 外部工具:通过 MCP Client 自动发现到的第三方工具。
这样可以直接验证:Agent 在一次对话里,可以同时调度本地 @Tool 和外部 MCP 工具。
二、和前面 ChatModel / ChatClient 的区别
前面章节里,ChatModel / ChatClient 更偏“发起一次对话调用”。
本章里的 ReactAgent 更偏“执行任务”:
- 理解用户意图。
- 判断要调用哪个工具。
- 调用工具并观察结果。
- 基于结果组织最终答案。
对于简单问题,这三者体感差异可能不大;但一旦涉及多工具组合,ReactAgent 的优势会更明显。
三、Saa18 当前可运行代码
3.1 配置类:合并本地工具与 MCP 工具
package cn.edu.nnu.opengms.config;
import cn.edu.nnu.opengms.service.MenuTools;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.stream.Stream;
@Configuration
public class DashScopeConfig {
@Bean
public ReactAgent menuAgent(ChatModel chatModel, MenuTools menuTools, ToolCallbackProvider mcpTools)
{
ToolCallback[] localTools = ToolCallbacks.from(menuTools);
ToolCallback[] externalTools = mcpTools.getToolCallbacks();
ToolCallback[] allTools = Stream.concat(Arrays.stream(localTools), Arrays.stream(externalTools))
.toArray(ToolCallback[]::new);
return ReactAgent.builder()
.name("menu-agent")
.description("根据用户问题推荐菜单、解释菜品或查询天气")
.model(chatModel)
.tools(allTools)
.build();
}
}
这段代码是本章核心。
它做了三件事:
ToolCallbacks.from(menuTools):加载本地菜单工具。mcpTools.getToolCallbacks():加载外部 MCP 工具。- 合并后通过
.tools(allTools)注册到同一个 Agent。
这就是“本地 + 外部”组合能力的关键实现。
3.2 本地工具类:菜单推荐
package cn.edu.nnu.opengms.service;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
@Service
public class MenuTools {
@Tool(description = "根据口味推荐菜单")
public String recommendMenu(String taste) {
if (taste == null || taste.isBlank()) {
return "请先告诉我口味偏好(甜/咸/辣)。";
}
if (taste.equalsIgnoreCase("甜")) {
return "推荐菜单:蛋挞、奶茶、甜甜圈";
} else if (taste.equalsIgnoreCase("咸")) {
return "推荐菜单:炸鸡、薯条、汉堡";
} else if (taste.equalsIgnoreCase("辣")) {
return "推荐菜单:麻辣香锅、辣子鸡、火锅";
} else {
return "抱歉,暂时没有相关口味的菜单推荐。";
}
}
@Tool(description = "根据天气状况和温度推荐菜单,参数示例:weather=小雨, temperature=12")
public String recommendByWeather(String weather, Integer temperature) {
if (weather == null || weather.isBlank() || temperature == null) {
return "请提供完整信息,例如:weather=小雨, temperature=12。";
}
String w = weather.toLowerCase();
if (temperature <= 5) {
return "当前" + weather + ",约" + temperature + "度,推荐暖身菜单:羊肉汤、番茄牛腩、砂锅米线。";
}
if (temperature <= 15) {
if (w.contains("雨") || w.contains("雪")) {
return "当前" + weather + ",约" + temperature + "度,推荐热汤类:酸辣汤面、菌菇鸡汤、馄饨。";
}
return "当前" + weather + ",约" + temperature + "度,推荐家常热菜:土豆炖牛肉、青椒肉丝、米饭。";
}
if (temperature <= 25) {
if (w.contains("雨")) {
return "当前" + weather + ",约" + temperature + "度,推荐温和口味:鲜虾粥、番茄鸡蛋面、清蒸鱼。";
}
if (w.contains("晴")) {
return "当前" + weather + ",约" + temperature + "度,推荐轻食搭配:鸡胸沙拉、玉米浓汤、全麦三明治。";
}
return "当前" + weather + ",约" + temperature + "度,推荐均衡菜单:宫保鸡丁、时蔬、紫菜蛋花汤。";
}
if (w.contains("雨") || w.contains("闷")) {
return "当前" + weather + ",约" + temperature + "度,推荐清爽低负担:绿豆粥、凉拌鸡丝、蒸南瓜。";
}
return "当前" + weather + ",约" + temperature + "度,推荐夏季清凉菜单:凉面、手撕鸡、冬瓜排骨汤。";
}
}
这个版本比“甜咸辣”单一推荐更适合测试 Agent 的决策路径,尤其是当问题里同时出现天气和温度时。
3.3 控制器:统一入口
package cn.edu.nnu.opengms.controller;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MenuController {
@Resource(name = "menuAgent")
private ReactAgent menuAgent;
@GetMapping(value = "/eatAgent")
public String eatAgent(@RequestParam(name = "msg", defaultValue = "今天吃什么") String msg) throws GraphRunnerException {
return menuAgent.call(msg).getText();
}
}
这一层保持简单:把用户输入交给 Agent,后续工具决策由 Agent 运行期处理。
四、application.yml 与依赖
4.1 当前配置
server:
port: 8001
spring:
application:
name: Saa09
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY}
mcp:
client:
request-timeout: 20s
toolcallback:
enabled: true
stdio:
servers-configuration: classpath:/mcp-server.json5
说明:
- 本章除了模型 API Key,还需要 MCP Client 配置。
servers-configuration指向外部 MCP 服务清单文件。
4.2 关键依赖
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
前者提供 Agent 运行能力,后者提供外部 MCP 工具接入能力。
五、测试建议
启动后可先测:
http://localhost:8001/eatAgent?msg=我想吃辣的
http://localhost:8001/eatAgent?msg=今天小雨12度,推荐晚饭
http://localhost:8001/eatAgent?msg=今天的南京天气适合吃什么?
观察点:
- 是否会触发菜单推荐工具。
- 当问题涉及天气时,是否能利用外部工具结果再给菜单建议。
- 返回是否明显比纯对话更“任务化”。
六、本章小结
当前 Saa18 的价值在于跑通了一个实用结构:
ReactAgent作为统一执行入口。- 本地
MenuTools提供业务逻辑。 ToolCallbackProvider注入外部 MCP 工具。- 在一个 Agent 中完成“本地能力 + 外部能力”的协同调用。
这个结构是后续扩展多工具、多步骤 Agent 的基础。
本章重点:
- 读懂本章不是单工具示例,而是工具合并示例。
- 理解
.tools(allTools)里的本地与外部工具来源。 - 用更真实菜单逻辑验证 Agent 的任务执行能力。
📝 编辑者:Flittly
📅 更新时间:2026年4月
🔗 相关资源:Spring AI 官方文档 | Spring AI Alibaba