手把手带你入门 Agent 开发,实现一个能理解自然语言、自动推荐菜品、支持多轮追问的 AI 点餐助手。
一、背景:从规则到智能
传统的点餐推荐通常由硬编码逻辑实现——根据人数计算菜品数量,按荤素比例筛选,最后返回结果。这种方式虽然确定、高效,但缺乏灵活性:用户无法用自然语言表达"今晚 4 个人,想吃辣一点的,预算 200 左右",更无法追问调整。
随着大模型和 Agent 技术的成熟,我们可以构建一个智能点餐 Agent:用户用平常说话的方式提出需求,Agent 自动调用工具函数完成推荐,并以友好的格式返回结果。
Spring AI Alibaba 提供了完整的 Agent 开发框架(基于 ReactAgent),结合阿里云 DashScope 大模型,可以快速搭建此类应用。本文将带你从零开始,开发一个功能完整的智能点餐助手。
二、环境准备
2.1 基础要求
- JDK 17+
- Maven 3.6+ / Gradle 7+
- 阿里云 DashScope API Key(开通百炼平台即可获取)
2.2 引入依赖
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0</version>
</dependency>
2.3 配置文件 application.yml
spring:
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen-plus
temperature: 0.7
server:
port: 8080
三、定义工具函数(Function Calling)
Agent 的核心在于工具调用:大模型负责理解意图并规划,实际的业务动作由我们编写的工具函数完成。点餐助手需要两个工具:查询菜单、计算推荐。
3.1 菜单查询工具
@Component
public class MenuQueryTool implements Function<MenuQueryTool.Request, MenuQueryTool.Response> {
/** 模拟的菜品数据库 */
private static final List<Dish> DISHES = List.of(
new Dish("水煮鱼", "川菜", 88.0, 4, true),
new Dish("麻婆豆腐", "川菜", 28.0, 2, true),
new Dish("番茄牛腩", "家常菜", 58.0, 3, false),
new Dish("清蒸鲈鱼", "粤菜", 78.0, 3, false),
new Dish("蒜蓉西兰花", "素菜", 22.0, 1, false),
new Dish("酸辣土豆丝", "素菜", 18.0, 1, true)
);
@Override
public Response apply(Request request) {
List<Dish> result = DISHES.stream()
.filter(d -> request.spicy() == null || d.spicy() == request.spicy())
.filter(d -> request.category() == null || d.category().equals(request.category()))
.toList();
return new Response(result);
}
/** 输入:大模型自动从用户话术中提取 */
public record Request(Boolean spicy, String category) {}
public record Response(List<Dish> dishes) {
public record Dish(String name, String category,
double price, int servings, boolean spicy) {}
}
}
3.2 推荐计算工具
@Component
public class RecommendTool implements Function<RecommendTool.Request, RecommendTool.Response> {
@Override
public Response apply(Request request) {
// 预算约束 + 荤素搭配的推荐逻辑
List<Dish> picked = new ArrayList<>();
double budget = request.budget();
List<Dish> candidates = request.candidates().stream()
.filter(d -> d.price() <= budget * 0.6)
.sorted(Comparator.comparingDouble(Dish::price).reversed())
.toList();
for (Dish dish : candidates) {
if (picked.size() >= request.people() + 1) break;
if (budget - dish.price() < 0) continue;
picked.add(dish);
budget -= dish.price();
}
double total = request.budget() - budget;
return new Response(picked, total);
}
public record Request(int people, double budget, List<Dish> candidates) {}
public record Response(List<Dish> dishes, double totalPrice) {}
}
四、组装 Agent 并对话
4.1 注册工具
@Configuration
public class AgentConfig {
@Bean
public ToolCallback menuQueryTool() {
return ToolCallbacks.from(MenuQueryTool.class);
}
@Bean
public ToolCallback recommendTool() {
return ToolCallbacks.from(RecommendTool.class);
}
}
4.2 使用 ReactAgent 完成一轮对话
@RestController
public class OrderController {
private final ChatClient chatClient;
public OrderController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@PostMapping("/order")
public String order(@RequestBody String userMessage) {
return chatClient.prompt()
.system("你是一位餐厅点餐助手。" +
"根据用户需求查询菜单并给出荤素搭配、预算内的推荐。" +
"输出格式:推荐理由 + 菜品列表(含价格)+ 合计金额。")
.user(userMessage)
.call()
.content();
}
}
4.3 运行效果
POST /order
{"message": "今晚4个人吃饭,想吃辣一点的,预算200左右"}
// 输出:
// 根据您的需求,为您推荐以下菜品(荤素搭配,预算内):
// 1. 水煮鱼(川菜)88元 —— 主菜,麻辣过瘾
// 2. 麻婆豆腐(川菜)28元 —— 下饭神器
// 3. 酸辣土豆丝(素菜)18元 —— 解腻
// 4. 蒜蓉西兰花(素菜)22元
// 合计:156 元,在 200 元预算内 ✓
五、多轮追问与记忆
Agent 的另一个优势是支持多轮对话:用户可以说"水煮鱼换成清蒸鲈鱼吧""再加一个汤",Agent 结合历史上下文自动调整推荐。实现上只需要维护会话记忆:
@Service
public class ConversationService {
private final Map<String, ChatMemory> memories = new ConcurrentHashMap<>();
public String chat(String sessionId, String message) {
ChatMemory memory = memories.computeIfAbsent(sessionId,
k -> new ChatMemory() {
private final List<Message> history = new ArrayList<>();
public List<Message> get() { return history; }
public void add(Message m) { history.add(m); }
public void clear() { history.clear(); }
});
ChatClient client = chatClientBuilder.build()
.mutate()
.defaultAdvisors(new MessageChatMemoryAdvisor(memory))
.build();
return client.prompt()
.user(message)
.call()
.content();
}
}
这样每次调用都自动携带历史消息,大模型就能理解"换一个""再加"这类指代性表达。
六、总结与扩展
通过这个案例可以看到,构建一个业务 Agent 只需要三步:
- 定义工具:把业务能力封装为 Function,交给大模型调度;
- 编写提示词:明确角色、任务与输出格式;
- 接入记忆:让 Agent 具备多轮对话能力。
这套模式可以复用到客服、工单、审批、数据分析等大量场景。后续可以扩展:接入真实菜品数据库、增加图片识别点菜、结合企业微信实现对话式下单等,让 Agent 真正融入业务流程。