[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-jiyuspring-ai-alibabagoujianzhinengdiancanzhushouagent":3},{"id":4,"title":5,"slug":6,"summary":7,"content":8,"contentHtml":9,"wordCount":10,"readingTime":11,"categoryId":12,"tags":13,"coverImage":13,"thumbnail":13,"status":14,"isTop":15,"isRecommended":15,"allowComments":15,"password":13,"viewCount":16,"likeCount":15,"commentCount":15,"seoTitle":5,"seoKeywords":17,"seoDescription":18,"source":13,"sourceUrl":13,"publishTime":19},"2054207490390814721","基于Spring AI Alibaba构建智能点餐助手Agent","jiyuspring-ai-alibabagoujianzhinengdiancanzhushouagent","> 手把手带你入门Agent开发，实现一个能理解自然语言、自动推荐菜品的AI点餐助手 ## 一、背景：从规则到智能 传统的点餐推荐通常由硬编码逻辑实现——根据人数计算菜品数量，按荤素比例筛选，最后返回结果。这种方式虽然确定、高效，但缺乏**灵活性**：用户无法用自然语言表达“今晚4个人，想吃辣一点的，预算200左右”，更无法追问调整。 随着大模型和Agent技术的成熟，我们可以构建一个**智能点餐Agent**：用户用平常说话的方式提出需求，Agent自动调用工具函数完成推荐，并以友好的格式返回结果。 Spring AI Alibaba 提供了完整的 Agent 开发框架（基于 `ReactAgent`），结合阿里云 DashScope 大模型，可以快速搭建此类应用。 本文将带你从零开始，开发一个功能完整的智能点餐助手。 --- ## 二、环境准备 ### 2.1 基础要求 - JDK 1...","> 手把手带你入门Agent开发，实现一个能理解自然语言、自动推荐菜品的AI点餐助手\n\n## 一、背景：从规则到智能\n\n传统的点餐推荐通常由硬编码逻辑实现——根据人数计算菜品数量，按荤素比例筛选，最后返回结果。这种方式虽然确定、高效，但缺乏**灵活性**：用户无法用自然语言表达“今晚4个人，想吃辣一点的，预算200左右”，更无法追问调整。\n\n随着大模型和Agent技术的成熟，我们可以构建一个**智能点餐Agent**：用户用平常说话的方式提出需求，Agent自动调用工具函数完成推荐，并以友好的格式返回结果。\n\nSpring AI Alibaba 提供了完整的 Agent 开发框架（基于 `ReactAgent`），结合阿里云 DashScope 大模型，可以快速搭建此类应用。\n\n本文将带你从零开始，开发一个功能完整的智能点餐助手。\n\n---\n\n## 二、环境准备\n\n### 2.1 基础要求\n\n- JDK 17+\n- Maven 3.6+\n- 阿里云 DashScope API Key（[免费申请](https:\u002F\u002Fdashscope.aliyun.com\u002F)）\n\n### 2.2 创建Spring Boot项目\n\n使用 Spring Initializr 或 IDE 创建项目，依赖如下：\n\n```xml\n\u003Cparent>\n    \u003CgroupId>org.springframework.boot\u003C\u002FgroupId>\n    \u003CartifactId>spring-boot-starter-parent\u003C\u002FartifactId>\n    \u003Cversion>3.2.0\u003C\u002Fversion>\n\u003C\u002Fparent>\n\n\u003Cproperties>\n    \u003Cjava.version>17\u003C\u002Fjava.version>\n\u003C\u002Fproperties>\n\n\u003Cdependencies>\n    \u003C!-- Spring Boot Web（可选，测试用） -->\n    \u003Cdependency>\n        \u003CgroupId>org.springframework.boot\u003C\u002FgroupId>\n        \u003CartifactId>spring-boot-starter-web\u003C\u002FartifactId>\n    \u003C\u002Fdependency>\n\n    \u003C!-- Spring AI Alibaba Agent Framework -->\n    \u003Cdependency>\n        \u003CgroupId>com.alibaba.cloud.ai\u003C\u002FgroupId>\n        \u003CartifactId>spring-ai-alibaba-agent-framework\u003C\u002FartifactId>\n        \u003Cversion>1.1.2.0\u003C\u002Fversion>\n    \u003C\u002Fdependency>\n\n    \u003C!-- DashScope ChatModel 支持 -->\n    \u003Cdependency>\n        \u003CgroupId>com.alibaba.cloud.ai\u003C\u002FgroupId>\n        \u003CartifactId>spring-ai-alibaba-starter-dashscope\u003C\u002FartifactId>\n        \u003Cversion>1.1.2.0\u003C\u002Fversion>\n    \u003C\u002Fdependency>\n\u003C\u002Fdependencies>\n```\n\n### 2.3 配置文件 `application.yml`\n\n```yaml\nspring:\n  ai:\n    dashscope:\n      api-key: ${QWEN_API_KEY}   # 替换为你的真实API Key，或配置环境变量\n      chat:\n        options:\n          model: qwen-plus        # 推荐使用 qwen-plus，推理能力强\n          temperature: 0.7        # 适度随机性，让回复更自然\n```\n\n---\n\n## 三、项目代码实现\n\n我们将分三步完成：\n\n1. **定义菜单数据与工具类**（`MenuTools`）\n2. **配置Agent**（`AgentConfig`）\n3. **测试运行**（`Controller`）\n\n### 3.1 定义菜品实体（Record）\n\n在 `MenuTools.java` 内部定义一个 `Dish` record，包含 id、名称、菜系、辣度、适合人数、价格。\n\n```java\npublic record Dish(\n    int id,\n    String name,\n    String type,      \u002F\u002F 川菜、粤菜等\n    int spicy,        \u002F\u002F 0-5 辣度\n    int people,       \u002F\u002F 建议份量对应人数\n    int price\n) { }\n```\n\n### 3.2 核心推荐工具 `getRecommendDishes`\n\n这是Agent调用的主要工具。方法上使用 `@Tool` 注解，Spring AI 会自动生成工具描述，供大模型理解。\n\n**关键逻辑**：\n\n- 根据人数计算推荐菜品总数（经验公式：2人→3菜，4人→4菜，6人→5菜，8人→7菜，9人+→9菜）。\n- 根据口味偏好（如“辣”“清淡”“川菜”）预筛选菜单。\n- 将筛选后的菜品分为**大菜**（≥50元或硬菜）、**普通荤菜**、**素菜**。\n- 按荤素6:4比例 + 大菜数量规则，选取菜品。\n- 返回 JSON 数组，每个菜品包含 `id`、`name`、`price` 等信息（理想情况应包含 `count`，此处简化）。\n\n```java\n@Tool(name = \"getRecommendDishes\", description = \"根据用餐人数和口味偏好（如：辣、清淡、粤菜、川菜等）从菜单中检索合适的菜品，并生成包含菜品ID、名称和份数的点餐清单\")\n    public String getRecommendDishes(@ToolParam(description = \"用餐的人数\", required = true) int numOfPeople, int peopleCount,\n                                     @ToolParam(description = \"口味偏好，如：辣、清淡、粤菜、川菜等\", required = true) String tastePreference) {\n        if (peopleCount \u003C= 0) {\n            return \"[{\\\"error\\\":\\\"用餐人数必须大于0\\\"}]\";\n        }\n\n        \u002F\u002F 0. 根据口味偏好筛选菜单\n        List\u003CDish> filteredMenu = filterMenuByTaste(menu, tastePreference);\n        if (filteredMenu.isEmpty()) {\n            \u002F\u002F 完全没有匹配项时，降级为全量菜单\n            filteredMenu = new ArrayList\u003C>(menu);\n        }\n\n        \u002F\u002F 1. 从筛选后的菜单中分离大菜、普通荤菜、素菜\n        List\u003CDish> bigDishes = new ArrayList\u003C>();\n        List\u003CDish> normalMeat = new ArrayList\u003C>();\n        List\u003CDish> vegetarian = new ArrayList\u003C>();\n\n        for (Dish dish : filteredMenu) {\n            if (isBigDish(dish)) {\n                bigDishes.add(dish);\n            } else if (isMeatDish(dish)) {\n                normalMeat.add(dish);\n            } else {\n                vegetarian.add(dish);\n            }\n        }\n\n        \u002F\u002F 按价格降序排序，优先推荐高价位菜品\n        bigDishes.sort((d1, d2) -> Double.compare(d2.price(), d1.price()));\n        normalMeat.sort((d1, d2) -> Double.compare(d2.price(), d1.price()));\n        vegetarian.sort((d1, d2) -> Double.compare(d2.price(), d1.price()));\n\n        \u002F\u002F 2. 确定推荐菜品总数（荤素合理、避免浪费）\n        int totalCount = calculateTotalDishes(peopleCount);\n        totalCount = Math.min(totalCount, filteredMenu.size());\n\n        \u002F\u002F 3. 确定大菜数量（根据人数调整）\n        int bigCount = Math.min(bigDishes.size(), calculateBigDishCount(peopleCount, totalCount));\n\n        \u002F\u002F 4. 荤素比例（荤菜约占60%，素菜约占40%）\n        int targetMeatCount = (int) Math.round(totalCount * 0.6);\n        int normalMeatNeeded = Math.max(0, targetMeatCount - bigCount);\n        normalMeatNeeded = Math.min(normalMeatNeeded, normalMeat.size());\n\n        int vegNeeded = totalCount - bigCount - normalMeatNeeded;\n        if (vegNeeded \u003C 0) {\n            normalMeatNeeded += vegNeeded;\n            vegNeeded = 0;\n        }\n        vegNeeded = Math.min(vegNeeded, vegetarian.size());\n        if (vegNeeded \u003C totalCount - bigCount - normalMeatNeeded) {\n            int extraMeat = totalCount - bigCount - normalMeatNeeded - vegNeeded;\n            normalMeatNeeded = Math.min(normalMeatNeeded + extraMeat, normalMeat.size());\n            vegNeeded = totalCount - bigCount - normalMeatNeeded;\n        }\n\n        \u002F\u002F 5. 选取菜品\n        List\u003CDish> recommendation = new ArrayList\u003C>();\n        recommendation.addAll(bigDishes.subList(0, Math.min(bigCount, bigDishes.size())));\n        recommendation.addAll(normalMeat.subList(0, Math.min(normalMeatNeeded, normalMeat.size())));\n        recommendation.addAll(vegetarian.subList(0, Math.min(vegNeeded, vegetarian.size())));\n\n        \u002F\u002F 6. 若数量不足，从剩余菜品中补充\n        if (recommendation.size() \u003C totalCount) {\n            Set\u003CInteger> selectedIds = recommendation.stream().map(Dish::id).collect(Collectors.toSet());\n            List\u003CDish> remaining = filteredMenu.stream()\n                    .filter(d -> !selectedIds.contains(d.id()))\n                    .collect(Collectors.toList());\n            int need = totalCount - recommendation.size();\n            for (int i = 0; i \u003C need && i \u003C remaining.size(); i++) {\n                recommendation.add(remaining.get(i));\n            }\n        }\n        return JSONArray.toJSONString(recommendation);\n    }\n```\n\n引用方法如下：\n\n```java\n\u002F**\n     * 根据口味偏好筛选菜单\n     *\n     * @param menu            原始菜单\n     * @param tastePreference 口味偏好字符串，例如：\"辣\", \"清淡\", \"川菜,湘菜\", \"不辣 粤菜\"\n     * @return 符合偏好的菜品列表（可能为空）\n     *\u002F\n    private List\u003CDish> filterMenuByTaste(List\u003CDish> menu, String tastePreference) {\n        if (tastePreference == null || tastePreference.trim().isEmpty()) {\n            return new ArrayList\u003C>(menu);\n        }\n\n        String lowerPref = tastePreference.toLowerCase().trim();\n        \u002F\u002F 分割关键词（支持中英文逗号、空格）\n        String[] keywords = lowerPref.split(\"[\\\\s,，]+\");\n\n        List\u003CDish> result = new ArrayList\u003C>();\n        for (Dish dish : menu) {\n            if (matchesPreference(dish, keywords)) {\n                result.add(dish);\n            }\n        }\n        return result;\n    }\n\n    \u002F**\n     * 判断单个菜品是否匹配用户的偏好关键词\n     *\u002F\n    private boolean matchesPreference(Dish dish, String[] keywords) {\n        for (String kw : keywords) {\n            if (kw.isEmpty()) continue;\n            switch (kw) {\n                case \"辣\":\n                case \"微辣\":\n                case \"中辣\":\n                case \"重辣\":\n                    if (dish.spicy() > 0) return true;\n                    break;\n                case \"不辣\":\n                case \"清淡\":\n                    if (dish.spicy() == 0) return true;\n                    break;\n                case \"川菜\":\n                case \"粤菜\":\n                case \"湘菜\":\n                case \"京菜\":\n                case \"浙菜\":\n                case \"苏菜\":\n                case \"鲁菜\":\n                case \"家常菜\":\n                case \"素菜\":\n                    if (dish.type().equals(kw)) return true;\n                    break;\n                default:\n                    \u002F\u002F 其他关键词尝试匹配菜名或分类\n                    if (dish.name().toLowerCase().contains(kw) ||\n                            dish.type().toLowerCase().contains(kw)) {\n                        return true;\n                    }\n            }\n        }\n        return false;\n    }\n\n    \u002F\u002F 判断是否为大菜（价格≥50 或 特定大菜）\n    private boolean isBigDish(Dish dish) {\n        return dish.price() >= 50 ||\n                dish.name().contains(\"烤鸭\") ||\n                dish.name().contains(\"鱼头\") ||\n                dish.name().contains(\"烧鹅\");\n    }\n\n    \u002F\u002F 判断是否为荤菜（根据名称和分类）\n    private boolean isMeatDish(Dish dish) {\n        String name = dish.name();\n        String cat = dish.type();\n        \u002F\u002F 素菜系列全部是素\n        if (\"素菜\".equals(cat)) return false;\n        \u002F\u002F 明确素食菜名\n        return !name.contains(\"生菜\") && !name.contains(\"西兰花\") && !name.contains(\"茄子\") &&\n                !name.contains(\"四季豆\") && !name.contains(\"包菜\") && !name.contains(\"黄瓜\") &&\n                !name.contains(\"豆腐\") && !name.contains(\"土豆丝\") && !name.contains(\"西红柿炒蛋\") &&\n                !name.contains(\"地三鲜\") && !name.contains(\"臭豆腐\");\n        \u002F\u002F 蛋类归为半荤，这里简单视为荤（丰富口感）\n    }\n\n    \u002F\u002F 根据人数确定推荐菜品总数（经验公式）\n    private int calculateTotalDishes(int peopleCount) {\n        if (peopleCount \u003C= 2) return 3;\n        if (peopleCount \u003C= 4) return 4;\n        if (peopleCount \u003C= 6) return 5;\n        if (peopleCount \u003C= 8) return 7;\n        return 9;  \u002F\u002F 9人以上最多推荐9道菜，避免浪费\n    }\n\n    \u002F\u002F 根据人数和总菜数计算大菜数量\n    private int calculateBigDishCount(int peopleCount, int totalCount) {\n        if (peopleCount \u003C= 2) return Math.min(1, totalCount);\n        if (peopleCount \u003C= 4) return Math.min(2, totalCount \u002F 2);\n        return Math.min(3, totalCount \u002F 2);\n    }\n```\n测试菜单列表数据如下：\n\n```java\n\u002F\u002F 初始化菜单数据（35个菜品）\n        this.menu = Arrays.asList(\n                \u002F\u002F 川菜系列 (8个)\n                new Dish(1, \"宫保鸡丁\", \"川菜\", 3, 2, 38),\n                new Dish(2, \"麻婆豆腐\", \"川菜\", 4, 1, 18),\n                new Dish(3, \"水煮牛肉\", \"川菜\", 5, 3, 58),\n                new Dish(4, \"回锅肉\", \"川菜\", 4, 2, 42),\n                new Dish(5, \"鱼香肉丝\", \"川菜\", 2, 2, 32),\n                new Dish(6, \"夫妻肺片\", \"川菜\", 3, 2, 36),\n                new Dish(7, \"辣子鸡\", \"川菜\", 5, 3, 48),\n                new Dish(8, \"口水鸡\", \"川菜\", 3, 2, 35),\n\n                \u002F\u002F 粤菜系列 (6个)\n                new Dish(9, \"清蒸鲈鱼\", \"粤菜\", 0, 4, 68),\n                new Dish(10, \"白切鸡\", \"粤菜\", 0, 3, 58),\n                new Dish(11, \"烧鹅\", \"粤菜\", 0, 4, 78),\n                new Dish(12, \"叉烧\", \"粤菜\", 0, 2, 45),\n                new Dish(13, \"蚝油生菜\", \"粤菜\", 0, 2, 28),\n                new Dish(14, \"煲仔饭\", \"粤菜\", 0, 1, 32),\n\n                \u002F\u002F 家常菜系列 (7个)\n                new Dish(15, \"西红柿炒蛋\", \"家常菜\", 0, 1, 22),\n                new Dish(16, \"酸辣土豆丝\", \"家常菜\", 2, 1, 16),\n                new Dish(17, \"地三鲜\", \"家常菜\", 1, 2, 28),\n                new Dish(18, \"红烧肉\", \"家常菜\", 1, 3, 52),\n                new Dish(19, \"糖醋排骨\", \"家常菜\", 0, 2, 48),\n                new Dish(20, \"木须肉\", \"家常菜\", 0, 2, 35),\n                new Dish(21, \"青椒肉丝\", \"家常菜\", 1, 2, 30),\n\n                \u002F\u002F 素菜系列 (5个)\n                new Dish(22, \"蒜蓉西兰花\", \"素菜\", 0, 2, 26),\n                new Dish(23, \"红烧茄子\", \"素菜\", 1, 2, 24),\n                new Dish(24, \"干煸四季豆\", \"素菜\", 2, 2, 25),\n                new Dish(25, \"手撕包菜\", \"素菜\", 1, 2, 20),\n                new Dish(26, \"凉拌黄瓜\", \"素菜\", 0, 1, 15),\n\n                \u002F\u002F 湘菜系列 (4个)\n                new Dish(27, \"剁椒鱼头\", \"湘菜\", 4, 4, 72),\n                new Dish(28, \"辣椒炒肉\", \"湘菜\", 4, 2, 38),\n                new Dish(29, \"农家小炒肉\", \"湘菜\", 4, 2, 40),\n                new Dish(30, \"臭豆腐\", \"湘菜\", 3, 1, 18),\n\n                \u002F\u002F 其他特色菜 (5个)\n                new Dish(31, \"北京烤鸭\", \"京菜\", 0, 4, 128),\n                new Dish(32, \"东坡肉\", \"浙菜\", 1, 3, 58),\n                new Dish(33, \"狮子头\", \"苏菜\", 1, 3, 55),\n                new Dish(34, \"黄焖鸡\", \"鲁菜\", 2, 2, 42),\n                new Dish(35, \"酸菜鱼\", \"川菜\", 3, 3, 58)\n        );\n```\n\n\n### 3.3 辅助工具（可选）\n\n为了增强Agent能力，还可以提供获取全量菜单、按菜系筛选、按价格筛选等工具。示例：\n\n```java\n@Tool(description = \"获取完整菜单\")\npublic String getAllMenuItems() { return JSONArray.toJSONString(menu); }\n\n@Tool(description = \"根据菜系类型（如：川菜、粤菜、家常菜、素菜等）筛选菜品\")\n    public String getDishesByType(String cuisineType) {\n        if (cuisineType == null || cuisineType.trim().isEmpty()) {\n            return \"请指定菜系类型\";\n        }\n\n        List\u003CDish> filtered = menu.stream()\n                .filter(dish -> dish.type().contains(cuisineType.trim()))\n                .collect(Collectors.toList());\n\n        if (filtered.isEmpty()) {\n            return \"未找到\" + cuisineType + \"类型的菜品\";\n        }\n\n        return JSONArray.toJSONString(filtered);\n    }\n```\n\n### 3.4 配置 ReactAgent\n\n在 `AgentConfig` 中，使用 `ReactAgent.builder()` 创建一个能调用工具的 Agent。`ReactAgent` 是 Spring AI Alibaba 提供的实现 **ReAct（Reasoning + Acting）** 模式的智能体，它能根据用户问题自动决定调用哪个工具，并将结果整合后回复。\n\n```java\n@Configuration\npublic class AgentConfig {\n\n    @Bean\n    public ReactAgent orderingAgent(DashScopeChatModel chatModel, MenuTools menuTools) {\n        \u002F\u002F 注册工具\n        ToolCallbackProvider toolProvider = MethodToolCallbackProvider.builder()\n                .toolObjects(menuTools)\n                .build();\n\n        \u002F\u002F 设定系统指令（prompt）\n        String instruction = \"\"\"\n                你是点餐助手。直接调用 getRecommendDishes 工具（参数：人数、口味偏好），\n                并将工具返回的 JSON 数组原样输出，不要添加任何解释或额外格式。\n                \"\"\";\n\n        return ReactAgent.builder()\n                .name(\"restaurant_ordering_agent\")\n                .model(chatModel)\n                .toolCallbackProviders(toolProvider)\n                .instruction(instruction)\n                .build();\n    }\n}\n```\n\n### 3.5 提供 HTTP 接口（便于测试）\n\n创建 `OrderController`，接收用户自然语言请求，转发给 Agent 处理。\n\n```java\n@RestController\npublic class OrderController {\n    private final ReactAgent agent;\n\n    public OrderController(ReactAgent orderingAgent) {\n        this.agent = orderingAgent;\n    }\n\n    @PostMapping(\"\u002Frecommend\")\n    public String recommend(@RequestBody String userInput) {\n        \u002F\u002F Agent 直接返回字符串结果\n        return agent.chat(userInput);\n    }\n}\n```\n\n---\n\n## 四、运行与测试\n\n启动 Spring Boot 应用，用 `curl` 或 Postman 测试：\n\n```bash\ncurl -X POST http:\u002F\u002Flocalhost:8080\u002Frecommend \\\n  -H \"Content-Type: text\u002Fplain\" \\\n  -d \"3个人，想吃辣一点的，不要猪肉\"\n```\n\nAgent 会调用 `getRecommendDishes(3, \"辣 不要猪肉\")`，工具内部会智能过滤含猪肉的菜品（如回锅肉、红烧肉等），返回类似这样的结果：\n\n```json\n[\n  {\"id\":3,\"name\":\"水煮牛肉\",\"type\":\"川菜\",\"spicy\":5,\"people\":3,\"price\":58},\n  {\"id\":27,\"name\":\"剁椒鱼头\",\"type\":\"湘菜\",\"spicy\":4,\"people\":4,\"price\":72},\n  {\"id\":24,\"name\":\"干煸四季豆\",\"type\":\"素菜\",\"spicy\":2,\"people\":2,\"price\":25}\n]\n```\n\n你也可以去掉“原样输出”的指令，让 Agent 对结果进行自然语言解释，例如：“根据您3人、辣味的偏好，为您推荐：水煮牛肉（58元）、剁椒鱼头（72元）、干煸四季豆（25元），总计155元。”\n\n---\n\n## 五、核心原理解析\n\n### 5.1 ReAct Agent 工作流程\n\n1. **用户输入** → Agent 获得自然语言请求。\n2. **推理（Reasoning）**：大模型分析需要调用哪个工具以及参数。\n3. **行动（Acting）**：调用注册的工具函数（如 `getRecommendDishes`）并得到返回值。\n4. **整合输出**：模型根据工具结果和原始指令生成最终回复。\n\n整个过程完全自动化，无需手动编写 if-else 判断。\n\n### 5.2 `@Tool` 注解的作用\n\n- 自动将 Java 方法转换为 OpenAI 兼容的 **Function Calling** 描述。\n- 方法参数上的 `@ToolParam` 会生成参数说明，帮助大模型准确提取信息。\n- 支持返回 String、Map、List 等类型，框架会自动序列化。\n\n### 5.3 为什么需要传统逻辑代码？\n\n有人可能会问：为什么有了大模型，还要写 `calculateTotalDishes`、`filterMenuByTaste` 这些规则？  \n因为**大模型不擅长精确的数值计算和复杂的约束优化**（比如保证荤素比例 60:40）。最佳实践是：  \n- 大模型负责**意图理解**（提取人数、偏好、禁忌）  \n- 传统代码负责**确定性计算**（搭配逻辑）  \n- 二者通过工具调用无缝结合\n\n---\n\n## 六、进阶优化建议\n\n1. **支持份数控制**  \n   在 `getRecommendDishes` 返回时给每个菜品加上 `count` 字段（大菜1份，小菜也可1份，人数过多可增加）。\n\n2. **多工具协作**  \n   添加 `calculateTotalPrice` 工具，让 Agent 在推荐完菜品后询问用户是否预算超支，并自动调整。\n\n3. **对话记忆**  \n   ReactAgent 支持 `chatWithMemory` 方法，配合 `InMemoryChatMemory` 实现多轮对话，例如用户说“把那个水煮牛肉去掉，换一个便宜的”。\n\n4. **自定义输出格式**  \n   修改 `instruction`，让 Agent 以表格或自然语言返回，同时保留原始 JSON 给前端。\n\n---\n\n## 七、总结\n\n通过本文，你已经掌握了：\n\n- 使用 Spring AI Alibaba 快速搭建 Agent 应用\n- 通过 `@Tool` 注解将业务逻辑暴露给大模型\n- 如何编写一个兼顾规则与智能的点餐推荐助手\n\n整个开发过程不需要复杂的 prompt 工程，也不需要维护对话状态框架，Spring AI Alibaba 的 Agent 模块让我们可以**像开发普通 Spring Bean 一样开发智能应用**。\n\n你可以基于这个模板，轻松扩展到点餐、问药、推荐、客服等各类场景。快去试试吧！\n\n> 项目完整代码已附于文中，若需获取最新依赖版本，请参考 [Spring AI Alibaba 官方文档](https:\u002F\u002Fgithub.com\u002Falibaba\u002Fspring-ai-alibaba)。\n\n**作者简介**：本文作者为一名Java开发者，热衷于探索AI工程化落地。欢迎交流。","\u003Cblockquote data-line=\"0\">\n\u003Cp data-line=\"0\">手把手带你入门Agent开发，实现一个能理解自然语言、自动推荐菜品的AI点餐助手\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2 data-line=\"2\" id=\"一、背景：从规则到智能\">一、背景：从规则到智能\u003C\u002Fh2>\n\u003Cp data-line=\"4\">传统的点餐推荐通常由硬编码逻辑实现——根据人数计算菜品数量，按荤素比例筛选，最后返回结果。这种方式虽然确定、高效，但缺乏\u003Cstrong>灵活性\u003C\u002Fstrong>：用户无法用自然语言表达“今晚4个人，想吃辣一点的，预算200左右”，更无法追问调整。\u003C\u002Fp>\n\u003Cp data-line=\"6\">随着大模型和Agent技术的成熟，我们可以构建一个\u003Cstrong>智能点餐Agent\u003C\u002Fstrong>：用户用平常说话的方式提出需求，Agent自动调用工具函数完成推荐，并以友好的格式返回结果。\u003C\u002Fp>\n\u003Cp data-line=\"8\">Spring AI Alibaba 提供了完整的 Agent 开发框架（基于 \u003Ccode>ReactAgent\u003C\u002Fcode>），结合阿里云 DashScope 大模型，可以快速搭建此类应用。\u003C\u002Fp>\n\u003Cp data-line=\"10\">本文将带你从零开始，开发一个功能完整的智能点餐助手。\u003C\u002Fp>\n\u003Chr data-line=\"12\">\n\u003Ch2 data-line=\"14\" id=\"二、环境准备\">二、环境准备\u003C\u002Fh2>\n\u003Ch3 data-line=\"16\" id=\"2.1 基础要求\">2.1 基础要求\u003C\u002Fh3>\n\u003Cul data-line=\"18\">\n\u003Cli data-line=\"18\">JDK 17+\u003C\u002Fli>\n\u003Cli data-line=\"19\">Maven 3.6+\u003C\u002Fli>\n\u003Cli data-line=\"20\">阿里云 DashScope API Key（\u003Ca href=\"https:\u002F\u002Fdashscope.aliyun.com\u002F\">免费申请\u003C\u002Fa>）\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 data-line=\"22\" id=\"2.2 创建Spring Boot项目\">2.2 创建Spring Boot项目\u003C\u002Fh3>\n\u003Cp data-line=\"24\">使用 Spring Initializr 或 IDE 创建项目，依赖如下：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"26\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">xml\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-xml\" language=xml>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">parent\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>org.springframework.boot\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>spring-boot-starter-parent\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">version\u003C\u002Fspan>&gt;\u003C\u002Fspan>3.2.0\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">version\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">parent\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">properties\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">java.version\u003C\u002Fspan>&gt;\u003C\u002Fspan>17\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">java.version\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">properties\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">dependencies\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-comment\">&lt;!-- Spring Boot Web（可选，测试用） --&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">dependency\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>org.springframework.boot\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>spring-boot-starter-web\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">dependency\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\n    \u003Cspan class=\"hljs-comment\">&lt;!-- Spring AI Alibaba Agent Framework --&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">dependency\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>com.alibaba.cloud.ai\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>spring-ai-alibaba-agent-framework\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">version\u003C\u002Fspan>&gt;\u003C\u002Fspan>1.1.2.0\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">version\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">dependency\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\n    \u003Cspan class=\"hljs-comment\">&lt;!-- DashScope ChatModel 支持 --&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">dependency\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>com.alibaba.cloud.ai\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">groupId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>spring-ai-alibaba-starter-dashscope\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">artifactId\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">version\u003C\u002Fspan>&gt;\u003C\u002Fspan>1.1.2.0\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">version\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">dependency\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">dependencies\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"60\" id=\"2.3 配置文件 application.yml\">2.3 配置文件 \u003Ccode>application.yml\u003C\u002Fcode>\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"62\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">yaml\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-yaml\" language=yaml>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-attr\">spring:\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">ai:\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-attr\">dashscope:\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-attr\">api-key:\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">${QWEN_API_KEY}\u003C\u002Fspan>   \u003Cspan class=\"hljs-comment\"># 替换为你的真实API Key，或配置环境变量\u003C\u002Fspan>\n      \u003Cspan class=\"hljs-attr\">chat:\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-attr\">options:\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-attr\">model:\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">qwen-plus\u003C\u002Fspan>        \u003Cspan class=\"hljs-comment\"># 推荐使用 qwen-plus，推理能力强\u003C\u002Fspan>\n          \u003Cspan class=\"hljs-attr\">temperature:\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">0.7\u003C\u002Fspan>        \u003Cspan class=\"hljs-comment\"># 适度随机性，让回复更自然\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Chr data-line=\"73\">\n\u003Ch2 data-line=\"75\" id=\"三、项目代码实现\">三、项目代码实现\u003C\u002Fh2>\n\u003Cp data-line=\"77\">我们将分三步完成：\u003C\u002Fp>\n\u003Col data-line=\"79\">\n\u003Cli data-line=\"79\">\u003Cstrong>定义菜单数据与工具类\u003C\u002Fstrong>（\u003Ccode>MenuTools\u003C\u002Fcode>）\u003C\u002Fli>\n\u003Cli data-line=\"80\">\u003Cstrong>配置Agent\u003C\u002Fstrong>（\u003Ccode>AgentConfig\u003C\u002Fcode>）\u003C\u002Fli>\n\u003Cli data-line=\"81\">\u003Cstrong>测试运行\u003C\u002Fstrong>（\u003Ccode>Controller\u003C\u002Fcode>）\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 data-line=\"83\" id=\"3.1 定义菜品实体（Record）\">3.1 定义菜品实体（Record）\u003C\u002Fh3>\n\u003Cp data-line=\"85\">在 \u003Ccode>MenuTools.java\u003C\u002Fcode> 内部定义一个 \u003Ccode>Dish\u003C\u002Fcode> record，包含 id、名称、菜系、辣度、适合人数、价格。\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"87\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">record\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\n    \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> id,\n    String name,\n    String type,      \u002F\u002F 川菜、粤菜等\n    \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> spicy,        \u002F\u002F 0-5 辣度\n    \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> people,       \u002F\u002F 建议份量对应人数\n    \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> price\n)\u003C\u002Fspan> { }\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"98\" id=\"3.2 核心推荐工具 getRecommendDishes\">3.2 核心推荐工具 \u003Ccode>getRecommendDishes\u003C\u002Fcode>\u003C\u002Fh3>\n\u003Cp data-line=\"100\">这是Agent调用的主要工具。方法上使用 \u003Ccode>@Tool\u003C\u002Fcode> 注解，Spring AI 会自动生成工具描述，供大模型理解。\u003C\u002Fp>\n\u003Cp data-line=\"102\">\u003Cstrong>关键逻辑\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul data-line=\"104\">\n\u003Cli data-line=\"104\">根据人数计算推荐菜品总数（经验公式：2人→3菜，4人→4菜，6人→5菜，8人→7菜，9人+→9菜）。\u003C\u002Fli>\n\u003Cli data-line=\"105\">根据口味偏好（如“辣”“清淡”“川菜”）预筛选菜单。\u003C\u002Fli>\n\u003Cli data-line=\"106\">将筛选后的菜品分为\u003Cstrong>大菜\u003C\u002Fstrong>（≥50元或硬菜）、\u003Cstrong>普通荤菜\u003C\u002Fstrong>、\u003Cstrong>素菜\u003C\u002Fstrong>。\u003C\u002Fli>\n\u003Cli data-line=\"107\">按荤素6:4比例 + 大菜数量规则，选取菜品。\u003C\u002Fli>\n\u003Cli data-line=\"108\">返回 JSON 数组，每个菜品包含 \u003Ccode>id\u003C\u002Fcode>、\u003Ccode>name\u003C\u002Fcode>、\u003Ccode>price\u003C\u002Fcode> 等信息（理想情况应包含 \u003Ccode>count\u003C\u002Fcode>，此处简化）。\u003C\u002Fli>\n\u003C\u002Ful>\n\n        \u003Cdetails  data-line=\"110\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-meta\">@Tool(name = &quot;getRecommendDishes&quot;, description = &quot;根据用餐人数和口味偏好（如：辣、清淡、粤菜、川菜等）从菜单中检索合适的菜品，并生成包含菜品ID、名称和份数的点餐清单&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> String \u003Cspan class=\"hljs-title function_\">getRecommendDishes\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\u003Cspan class=\"hljs-meta\">@ToolParam(description = &quot;用餐的人数&quot;, required = true)\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> numOfPeople, \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> peopleCount,\n                                     \u003Cspan class=\"hljs-meta\">@ToolParam(description = &quot;口味偏好，如：辣、清淡、粤菜、川菜等&quot;, required = true)\u003C\u002Fspan> String tastePreference)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (peopleCount &lt;= \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) {\n            \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;[{\\&quot;error\\&quot;:\\&quot;用餐人数必须大于0\\&quot;}]&quot;\u003C\u002Fspan>;\n        }\n\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 0. 根据口味偏好筛选菜单\u003C\u002Fspan>\n        List&lt;Dish&gt; filteredMenu = filterMenuByTaste(menu, tastePreference);\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (filteredMenu.isEmpty()) {\n            \u003Cspan class=\"hljs-comment\">\u002F\u002F 完全没有匹配项时，降级为全量菜单\u003C\u002Fspan>\n            filteredMenu = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ArrayList\u003C\u002Fspan>&lt;&gt;(menu);\n        }\n\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 1. 从筛选后的菜单中分离大菜、普通荤菜、素菜\u003C\u002Fspan>\n        List&lt;Dish&gt; bigDishes = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ArrayList\u003C\u002Fspan>&lt;&gt;();\n        List&lt;Dish&gt; normalMeat = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ArrayList\u003C\u002Fspan>&lt;&gt;();\n        List&lt;Dish&gt; vegetarian = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ArrayList\u003C\u002Fspan>&lt;&gt;();\n\n        \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (Dish dish : filteredMenu) {\n            \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (isBigDish(dish)) {\n                bigDishes.add(dish);\n            } \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (isMeatDish(dish)) {\n                normalMeat.add(dish);\n            } \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> {\n                vegetarian.add(dish);\n            }\n        }\n\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 按价格降序排序，优先推荐高价位菜品\u003C\u002Fspan>\n        bigDishes.sort((d1, d2) -&gt; Double.compare(d2.price(), d1.price()));\n        normalMeat.sort((d1, d2) -&gt; Double.compare(d2.price(), d1.price()));\n        vegetarian.sort((d1, d2) -&gt; Double.compare(d2.price(), d1.price()));\n\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 2. 确定推荐菜品总数（荤素合理、避免浪费）\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">totalCount\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> calculateTotalDishes(peopleCount);\n        totalCount = Math.min(totalCount, filteredMenu.size());\n\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 3. 确定大菜数量（根据人数调整）\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">bigCount\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> Math.min(bigDishes.size(), calculateBigDishCount(peopleCount, totalCount));\n\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 4. 荤素比例（荤菜约占60%，素菜约占40%）\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">targetMeatCount\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> (\u003Cspan class=\"hljs-type\">int\u003C\u002Fspan>) Math.round(totalCount * \u003Cspan class=\"hljs-number\">0.6\u003C\u002Fspan>);\n        \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">normalMeatNeeded\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> Math.max(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, targetMeatCount - bigCount);\n        normalMeatNeeded = Math.min(normalMeatNeeded, normalMeat.size());\n\n        \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">vegNeeded\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> totalCount - bigCount - normalMeatNeeded;\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (vegNeeded &lt; \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) {\n            normalMeatNeeded += vegNeeded;\n            vegNeeded = \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>;\n        }\n        vegNeeded = Math.min(vegNeeded, vegetarian.size());\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (vegNeeded &lt; totalCount - bigCount - normalMeatNeeded) {\n            \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">extraMeat\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> totalCount - bigCount - normalMeatNeeded - vegNeeded;\n            normalMeatNeeded = Math.min(normalMeatNeeded + extraMeat, normalMeat.size());\n            vegNeeded = totalCount - bigCount - normalMeatNeeded;\n        }\n\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 5. 选取菜品\u003C\u002Fspan>\n        List&lt;Dish&gt; recommendation = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ArrayList\u003C\u002Fspan>&lt;&gt;();\n        recommendation.addAll(bigDishes.subList(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, Math.min(bigCount, bigDishes.size())));\n        recommendation.addAll(normalMeat.subList(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, Math.min(normalMeatNeeded, normalMeat.size())));\n        recommendation.addAll(vegetarian.subList(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, Math.min(vegNeeded, vegetarian.size())));\n\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 6. 若数量不足，从剩余菜品中补充\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (recommendation.size() &lt; totalCount) {\n            Set&lt;Integer&gt; selectedIds = recommendation.stream().map(Dish::id).collect(Collectors.toSet());\n            List&lt;Dish&gt; remaining = filteredMenu.stream()\n                    .filter(d -&gt; !selectedIds.contains(d.id()))\n                    .collect(Collectors.toList());\n            \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">need\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> totalCount - recommendation.size();\n            \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">i\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>; i &lt; need &amp;&amp; i &lt; remaining.size(); i++) {\n                recommendation.add(remaining.get(i));\n            }\n        }\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> JSONArray.toJSONString(recommendation);\n    }\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"190\">引用方法如下：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"192\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F**\n     * 根据口味偏好筛选菜单\n     *\n     * \u003Cspan class=\"hljs-doctag\">@param\u003C\u002Fspan> menu            原始菜单\n     * \u003Cspan class=\"hljs-doctag\">@param\u003C\u002Fspan> tastePreference 口味偏好字符串，例如：&quot;辣&quot;, &quot;清淡&quot;, &quot;川菜,湘菜&quot;, &quot;不辣 粤菜&quot;\n     * \u003Cspan class=\"hljs-doctag\">@return\u003C\u002Fspan> 符合偏好的菜品列表（可能为空）\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> List&lt;Dish&gt; \u003Cspan class=\"hljs-title function_\">filterMenuByTaste\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(List&lt;Dish&gt; menu, String tastePreference)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (tastePreference == \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan> || tastePreference.trim().isEmpty()) {\n            \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ArrayList\u003C\u002Fspan>&lt;&gt;(menu);\n        }\n\n        \u003Cspan class=\"hljs-type\">String\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">lowerPref\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> tastePreference.toLowerCase().trim();\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 分割关键词（支持中英文逗号、空格）\u003C\u002Fspan>\n        String[] keywords = lowerPref.split(\u003Cspan class=\"hljs-string\">&quot;[\\\\s,，]+&quot;\u003C\u002Fspan>);\n\n        List&lt;Dish&gt; result = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ArrayList\u003C\u002Fspan>&lt;&gt;();\n        \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (Dish dish : menu) {\n            \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (matchesPreference(dish, keywords)) {\n                result.add(dish);\n            }\n        }\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> result;\n    }\n\n    \u003Cspan class=\"hljs-comment\">\u002F**\n     * 判断单个菜品是否匹配用户的偏好关键词\n     *\u002F\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">boolean\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">matchesPreference\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(Dish dish, String[] keywords)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (String kw : keywords) {\n            \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (kw.isEmpty()) \u003Cspan class=\"hljs-keyword\">continue\u003C\u002Fspan>;\n            \u003Cspan class=\"hljs-keyword\">switch\u003C\u002Fspan> (kw) {\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;辣&quot;\u003C\u002Fspan>:\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;微辣&quot;\u003C\u002Fspan>:\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;中辣&quot;\u003C\u002Fspan>:\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;重辣&quot;\u003C\u002Fspan>:\n                    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (dish.spicy() &gt; \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>;\n                    \u003Cspan class=\"hljs-keyword\">break\u003C\u002Fspan>;\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;不辣&quot;\u003C\u002Fspan>:\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;清淡&quot;\u003C\u002Fspan>:\n                    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (dish.spicy() == \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>;\n                    \u003Cspan class=\"hljs-keyword\">break\u003C\u002Fspan>;\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;川菜&quot;\u003C\u002Fspan>:\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;粤菜&quot;\u003C\u002Fspan>:\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;湘菜&quot;\u003C\u002Fspan>:\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;京菜&quot;\u003C\u002Fspan>:\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;浙菜&quot;\u003C\u002Fspan>:\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;苏菜&quot;\u003C\u002Fspan>:\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;鲁菜&quot;\u003C\u002Fspan>:\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;家常菜&quot;\u003C\u002Fspan>:\n                \u003Cspan class=\"hljs-keyword\">case\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;素菜&quot;\u003C\u002Fspan>:\n                    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (dish.type().equals(kw)) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>;\n                    \u003Cspan class=\"hljs-keyword\">break\u003C\u002Fspan>;\n                \u003Cspan class=\"hljs-keyword\">default\u003C\u002Fspan>:\n                    \u003Cspan class=\"hljs-comment\">\u002F\u002F 其他关键词尝试匹配菜名或分类\u003C\u002Fspan>\n                    \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (dish.name().toLowerCase().contains(kw) ||\n                            dish.type().toLowerCase().contains(kw)) {\n                        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>;\n                    }\n            }\n        }\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>;\n    }\n\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 判断是否为大菜（价格≥50 或 特定大菜）\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">boolean\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">isBigDish\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(Dish dish)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> dish.price() &gt;= \u003Cspan class=\"hljs-number\">50\u003C\u002Fspan> ||\n                dish.name().contains(\u003Cspan class=\"hljs-string\">&quot;烤鸭&quot;\u003C\u002Fspan>) ||\n                dish.name().contains(\u003Cspan class=\"hljs-string\">&quot;鱼头&quot;\u003C\u002Fspan>) ||\n                dish.name().contains(\u003Cspan class=\"hljs-string\">&quot;烧鹅&quot;\u003C\u002Fspan>);\n    }\n\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 判断是否为荤菜（根据名称和分类）\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">boolean\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">isMeatDish\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(Dish dish)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-type\">String\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">name\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> dish.name();\n        \u003Cspan class=\"hljs-type\">String\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">cat\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> dish.type();\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 素菜系列全部是素\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (\u003Cspan class=\"hljs-string\">&quot;素菜&quot;\u003C\u002Fspan>.equals(cat)) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>;\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 明确素食菜名\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> !name.contains(\u003Cspan class=\"hljs-string\">&quot;生菜&quot;\u003C\u002Fspan>) &amp;&amp; !name.contains(\u003Cspan class=\"hljs-string\">&quot;西兰花&quot;\u003C\u002Fspan>) &amp;&amp; !name.contains(\u003Cspan class=\"hljs-string\">&quot;茄子&quot;\u003C\u002Fspan>) &amp;&amp;\n                !name.contains(\u003Cspan class=\"hljs-string\">&quot;四季豆&quot;\u003C\u002Fspan>) &amp;&amp; !name.contains(\u003Cspan class=\"hljs-string\">&quot;包菜&quot;\u003C\u002Fspan>) &amp;&amp; !name.contains(\u003Cspan class=\"hljs-string\">&quot;黄瓜&quot;\u003C\u002Fspan>) &amp;&amp;\n                !name.contains(\u003Cspan class=\"hljs-string\">&quot;豆腐&quot;\u003C\u002Fspan>) &amp;&amp; !name.contains(\u003Cspan class=\"hljs-string\">&quot;土豆丝&quot;\u003C\u002Fspan>) &amp;&amp; !name.contains(\u003Cspan class=\"hljs-string\">&quot;西红柿炒蛋&quot;\u003C\u002Fspan>) &amp;&amp;\n                !name.contains(\u003Cspan class=\"hljs-string\">&quot;地三鲜&quot;\u003C\u002Fspan>) &amp;&amp; !name.contains(\u003Cspan class=\"hljs-string\">&quot;臭豆腐&quot;\u003C\u002Fspan>);\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 蛋类归为半荤，这里简单视为荤（丰富口感）\u003C\u002Fspan>\n    }\n\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 根据人数确定推荐菜品总数（经验公式）\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">calculateTotalDishes\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> peopleCount)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (peopleCount &lt;= \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>;\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (peopleCount &lt;= \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>;\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (peopleCount &lt;= \u003Cspan class=\"hljs-number\">6\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">5\u003C\u002Fspan>;\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (peopleCount &lt;= \u003Cspan class=\"hljs-number\">8\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">7\u003C\u002Fspan>;\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">9\u003C\u002Fspan>;  \u003Cspan class=\"hljs-comment\">\u002F\u002F 9人以上最多推荐9道菜，避免浪费\u003C\u002Fspan>\n    }\n\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 根据人数和总菜数计算大菜数量\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">calculateBigDishCount\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> peopleCount, \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> totalCount)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (peopleCount &lt;= \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> Math.min(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, totalCount);\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (peopleCount &lt;= \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>) \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> Math.min(\u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, totalCount \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>);\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> Math.min(\u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, totalCount \u002F \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>);\n    }\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"295\">测试菜单列表数据如下：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"297\" class=\"md-editor-code\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F 初始化菜单数据（35个菜品）\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-built_in\">this\u003C\u002Fspan>.menu = Arrays.asList(\n                \u003Cspan class=\"hljs-comment\">\u002F\u002F 川菜系列 (8个)\u003C\u002Fspan>\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;宫保鸡丁&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;川菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">38\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;麻婆豆腐&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;川菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">18\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;水煮牛肉&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;川菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">5\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">58\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;回锅肉&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;川菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">42\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">5\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;鱼香肉丝&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;川菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">32\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">6\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;夫妻肺片&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;川菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">36\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">7\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;辣子鸡&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;川菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">5\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">48\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">8\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;口水鸡&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;川菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">35\u003C\u002Fspan>),\n\n                \u003Cspan class=\"hljs-comment\">\u002F\u002F 粤菜系列 (6个)\u003C\u002Fspan>\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">9\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;清蒸鲈鱼&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;粤菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">68\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">10\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;白切鸡&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;粤菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">58\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">11\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;烧鹅&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;粤菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">78\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">12\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;叉烧&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;粤菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">45\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">13\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;蚝油生菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;粤菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">28\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">14\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;煲仔饭&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;粤菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">32\u003C\u002Fspan>),\n\n                \u003Cspan class=\"hljs-comment\">\u002F\u002F 家常菜系列 (7个)\u003C\u002Fspan>\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">15\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;西红柿炒蛋&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;家常菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">22\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">16\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;酸辣土豆丝&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;家常菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">16\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">17\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;地三鲜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;家常菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">28\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">18\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;红烧肉&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;家常菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">52\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">19\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;糖醋排骨&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;家常菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">48\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">20\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;木须肉&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;家常菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">35\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">21\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;青椒肉丝&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;家常菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">30\u003C\u002Fspan>),\n\n                \u003Cspan class=\"hljs-comment\">\u002F\u002F 素菜系列 (5个)\u003C\u002Fspan>\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">22\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;蒜蓉西兰花&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;素菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">26\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">23\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;红烧茄子&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;素菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">24\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">24\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;干煸四季豆&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;素菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">25\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">25\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;手撕包菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;素菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">20\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">26\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;凉拌黄瓜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;素菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">15\u003C\u002Fspan>),\n\n                \u003Cspan class=\"hljs-comment\">\u002F\u002F 湘菜系列 (4个)\u003C\u002Fspan>\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">27\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;剁椒鱼头&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;湘菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">72\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">28\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;辣椒炒肉&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;湘菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">38\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">29\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;农家小炒肉&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;湘菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">40\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">30\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;臭豆腐&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;湘菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">18\u003C\u002Fspan>),\n\n                \u003Cspan class=\"hljs-comment\">\u002F\u002F 其他特色菜 (5个)\u003C\u002Fspan>\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">31\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;北京烤鸭&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;京菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">128\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">32\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;东坡肉&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;浙菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">58\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">33\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;狮子头&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;苏菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">55\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">34\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;黄焖鸡&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;鲁菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">42\u003C\u002Fspan>),\n                \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Dish\u003C\u002Fspan>(\u003Cspan class=\"hljs-number\">35\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;酸菜鱼&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;川菜&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">58\u003C\u002Fspan>)\n        );\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"350\" id=\"3.3 辅助工具（可选）\">3.3 辅助工具（可选）\u003C\u002Fh3>\n\u003Cp data-line=\"352\">为了增强Agent能力，还可以提供获取全量菜单、按菜系筛选、按价格筛选等工具。示例：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"354\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-meta\">@Tool(description = &quot;获取完整菜单&quot;)\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> String \u003Cspan class=\"hljs-title function_\">getAllMenuItems\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">()\u003C\u002Fspan> { \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> JSONArray.toJSONString(menu); }\n\n\u003Cspan class=\"hljs-meta\">@Tool(description = &quot;根据菜系类型（如：川菜、粤菜、家常菜、素菜等）筛选菜品&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> String \u003Cspan class=\"hljs-title function_\">getDishesByType\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(String cuisineType)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (cuisineType == \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan> || cuisineType.trim().isEmpty()) {\n            \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;请指定菜系类型&quot;\u003C\u002Fspan>;\n        }\n\n        List&lt;Dish&gt; filtered = menu.stream()\n                .filter(dish -&gt; dish.type().contains(cuisineType.trim()))\n                .collect(Collectors.toList());\n\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (filtered.isEmpty()) {\n            \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;未找到&quot;\u003C\u002Fspan> + cuisineType + \u003Cspan class=\"hljs-string\">&quot;类型的菜品&quot;\u003C\u002Fspan>;\n        }\n\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> JSONArray.toJSONString(filtered);\n    }\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"376\" id=\"3.4 配置 ReactAgent\">3.4 配置 ReactAgent\u003C\u002Fh3>\n\u003Cp data-line=\"378\">在 \u003Ccode>AgentConfig\u003C\u002Fcode> 中，使用 \u003Ccode>ReactAgent.builder()\u003C\u002Fcode> 创建一个能调用工具的 Agent。\u003Ccode>ReactAgent\u003C\u002Fcode> 是 Spring AI Alibaba 提供的实现 \u003Cstrong>ReAct（Reasoning + Acting）\u003C\u002Fstrong> 模式的智能体，它能根据用户问题自动决定调用哪个工具，并将结果整合后回复。\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"380\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-meta\">@Configuration\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">AgentConfig\u003C\u002Fspan> {\n\n    \u003Cspan class=\"hljs-meta\">@Bean\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> ReactAgent \u003Cspan class=\"hljs-title function_\">orderingAgent\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(DashScopeChatModel chatModel, MenuTools menuTools)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 注册工具\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-type\">ToolCallbackProvider\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">toolProvider\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> MethodToolCallbackProvider.builder()\n                .toolObjects(menuTools)\n                .build();\n\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 设定系统指令（prompt）\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-type\">String\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">instruction\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;&quot;&quot;\n                你是点餐助手。直接调用 getRecommendDishes 工具（参数：人数、口味偏好），\n                并将工具返回的 JSON 数组原样输出，不要添加任何解释或额外格式。\n                &quot;&quot;&quot;\u003C\u002Fspan>;\n\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> ReactAgent.builder()\n                .name(\u003Cspan class=\"hljs-string\">&quot;restaurant_ordering_agent&quot;\u003C\u002Fspan>)\n                .model(chatModel)\n                .toolCallbackProviders(toolProvider)\n                .instruction(instruction)\n                .build();\n    }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"407\" id=\"3.5 提供 HTTP 接口（便于测试）\">3.5 提供 HTTP 接口（便于测试）\u003C\u002Fh3>\n\u003Cp data-line=\"409\">创建 \u003Ccode>OrderController\u003C\u002Fcode>，接收用户自然语言请求，转发给 Agent 处理。\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"411\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-meta\">@RestController\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">OrderController\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">final\u003C\u002Fspan> ReactAgent agent;\n\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">OrderController\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(ReactAgent orderingAgent)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-built_in\">this\u003C\u002Fspan>.agent = orderingAgent;\n    }\n\n    \u003Cspan class=\"hljs-meta\">@PostMapping(&quot;\u002Frecommend&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> String \u003Cspan class=\"hljs-title function_\">recommend\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\u003Cspan class=\"hljs-meta\">@RequestBody\u003C\u002Fspan> String userInput)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F Agent 直接返回字符串结果\u003C\u002Fspan>\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> agent.chat(userInput);\n    }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Chr data-line=\"428\">\n\u003Ch2 data-line=\"430\" id=\"四、运行与测试\">四、运行与测试\u003C\u002Fh2>\n\u003Cp data-line=\"432\">启动 Spring Boot 应用，用 \u003Ccode>curl\u003C\u002Fcode> 或 Postman 测试：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"434\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">bash\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-bash\" language=bash>\u003Cspan class=\"md-editor-code-block\">curl -X POST http:\u002F\u002Flocalhost:8080\u002Frecommend \\\n  -H \u003Cspan class=\"hljs-string\">&quot;Content-Type: text\u002Fplain&quot;\u003C\u002Fspan> \\\n  -d \u003Cspan class=\"hljs-string\">&quot;3个人，想吃辣一点的，不要猪肉&quot;\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"440\">Agent 会调用 \u003Ccode>getRecommendDishes(3, &quot;辣 不要猪肉&quot;)\u003C\u002Fcode>，工具内部会智能过滤含猪肉的菜品（如回锅肉、红烧肉等），返回类似这样的结果：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"442\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">json\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-json\" language=json>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-punctuation\">[\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-punctuation\">{\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;id&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;name&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-string\">&quot;水煮牛肉&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;type&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-string\">&quot;川菜&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;spicy&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-number\">5\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;people&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;price&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-number\">58\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">}\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-punctuation\">{\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;id&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-number\">27\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;name&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-string\">&quot;剁椒鱼头&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;type&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-string\">&quot;湘菜&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;spicy&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;people&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-number\">4\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;price&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-number\">72\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">}\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-punctuation\">{\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;id&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-number\">24\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;name&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-string\">&quot;干煸四季豆&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;type&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-string\">&quot;素菜&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;spicy&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;people&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">,\u003C\u002Fspan>\u003Cspan class=\"hljs-attr\">&quot;price&quot;\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">:\u003C\u002Fspan>\u003Cspan class=\"hljs-number\">25\u003C\u002Fspan>\u003Cspan class=\"hljs-punctuation\">}\u003C\u002Fspan>\n\u003Cspan class=\"hljs-punctuation\">]\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"450\">你也可以去掉“原样输出”的指令，让 Agent 对结果进行自然语言解释，例如：“根据您3人、辣味的偏好，为您推荐：水煮牛肉（58元）、剁椒鱼头（72元）、干煸四季豆（25元），总计155元。”\u003C\u002Fp>\n\u003Chr data-line=\"452\">\n\u003Ch2 data-line=\"454\" id=\"五、核心原理解析\">五、核心原理解析\u003C\u002Fh2>\n\u003Ch3 data-line=\"456\" id=\"5.1 ReAct Agent 工作流程\">5.1 ReAct Agent 工作流程\u003C\u002Fh3>\n\u003Col data-line=\"458\">\n\u003Cli data-line=\"458\">\u003Cstrong>用户输入\u003C\u002Fstrong> → Agent 获得自然语言请求。\u003C\u002Fli>\n\u003Cli data-line=\"459\">\u003Cstrong>推理（Reasoning）\u003C\u002Fstrong>：大模型分析需要调用哪个工具以及参数。\u003C\u002Fli>\n\u003Cli data-line=\"460\">\u003Cstrong>行动（Acting）\u003C\u002Fstrong>：调用注册的工具函数（如 \u003Ccode>getRecommendDishes\u003C\u002Fcode>）并得到返回值。\u003C\u002Fli>\n\u003Cli data-line=\"461\">\u003Cstrong>整合输出\u003C\u002Fstrong>：模型根据工具结果和原始指令生成最终回复。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp data-line=\"463\">整个过程完全自动化，无需手动编写 if-else 判断。\u003C\u002Fp>\n\u003Ch3 data-line=\"465\" id=\"5.2 @Tool 注解的作用\">5.2 \u003Ccode>@Tool\u003C\u002Fcode> 注解的作用\u003C\u002Fh3>\n\u003Cul data-line=\"467\">\n\u003Cli data-line=\"467\">自动将 Java 方法转换为 OpenAI 兼容的 \u003Cstrong>Function Calling\u003C\u002Fstrong> 描述。\u003C\u002Fli>\n\u003Cli data-line=\"468\">方法参数上的 \u003Ccode>@ToolParam\u003C\u002Fcode> 会生成参数说明，帮助大模型准确提取信息。\u003C\u002Fli>\n\u003Cli data-line=\"469\">支持返回 String、Map、List 等类型，框架会自动序列化。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 data-line=\"471\" id=\"5.3 为什么需要传统逻辑代码？\">5.3 为什么需要传统逻辑代码？\u003C\u002Fh3>\n\u003Cp data-line=\"473\">有人可能会问：为什么有了大模型，还要写 \u003Ccode>calculateTotalDishes\u003C\u002Fcode>、\u003Ccode>filterMenuByTaste\u003C\u002Fcode> 这些规则？\u003Cbr>\n因为\u003Cstrong>大模型不擅长精确的数值计算和复杂的约束优化\u003C\u002Fstrong>（比如保证荤素比例 60:40）。最佳实践是：\u003C\u002Fp>\n\u003Cul data-line=\"475\">\n\u003Cli data-line=\"475\">大模型负责\u003Cstrong>意图理解\u003C\u002Fstrong>（提取人数、偏好、禁忌）\u003C\u002Fli>\n\u003Cli data-line=\"476\">传统代码负责\u003Cstrong>确定性计算\u003C\u002Fstrong>（搭配逻辑）\u003C\u002Fli>\n\u003Cli data-line=\"477\">二者通过工具调用无缝结合\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Chr data-line=\"479\">\n\u003Ch2 data-line=\"481\" id=\"六、进阶优化建议\">六、进阶优化建议\u003C\u002Fh2>\n\u003Col data-line=\"483\">\n\u003Cli data-line=\"483\">\n\u003Cp data-line=\"483\">\u003Cstrong>支持份数控制\u003C\u002Fstrong>\u003Cbr>\n在 \u003Ccode>getRecommendDishes\u003C\u002Fcode> 返回时给每个菜品加上 \u003Ccode>count\u003C\u002Fcode> 字段（大菜1份，小菜也可1份，人数过多可增加）。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli data-line=\"486\">\n\u003Cp data-line=\"486\">\u003Cstrong>多工具协作\u003C\u002Fstrong>\u003Cbr>\n添加 \u003Ccode>calculateTotalPrice\u003C\u002Fcode> 工具，让 Agent 在推荐完菜品后询问用户是否预算超支，并自动调整。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli data-line=\"489\">\n\u003Cp data-line=\"489\">\u003Cstrong>对话记忆\u003C\u002Fstrong>\u003Cbr>\nReactAgent 支持 \u003Ccode>chatWithMemory\u003C\u002Fcode> 方法，配合 \u003Ccode>InMemoryChatMemory\u003C\u002Fcode> 实现多轮对话，例如用户说“把那个水煮牛肉去掉，换一个便宜的”。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli data-line=\"492\">\n\u003Cp data-line=\"492\">\u003Cstrong>自定义输出格式\u003C\u002Fstrong>\u003Cbr>\n修改 \u003Ccode>instruction\u003C\u002Fcode>，让 Agent 以表格或自然语言返回，同时保留原始 JSON 给前端。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Chr data-line=\"495\">\n\u003Ch2 data-line=\"497\" id=\"七、总结\">七、总结\u003C\u002Fh2>\n\u003Cp data-line=\"499\">通过本文，你已经掌握了：\u003C\u002Fp>\n\u003Cul data-line=\"501\">\n\u003Cli data-line=\"501\">使用 Spring AI Alibaba 快速搭建 Agent 应用\u003C\u002Fli>\n\u003Cli data-line=\"502\">通过 \u003Ccode>@Tool\u003C\u002Fcode> 注解将业务逻辑暴露给大模型\u003C\u002Fli>\n\u003Cli data-line=\"503\">如何编写一个兼顾规则与智能的点餐推荐助手\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp data-line=\"505\">整个开发过程不需要复杂的 prompt 工程，也不需要维护对话状态框架，Spring AI Alibaba 的 Agent 模块让我们可以\u003Cstrong>像开发普通 Spring Bean 一样开发智能应用\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp data-line=\"507\">你可以基于这个模板，轻松扩展到点餐、问药、推荐、客服等各类场景。快去试试吧！\u003C\u002Fp>\n\u003Cblockquote data-line=\"509\">\n\u003Cp data-line=\"509\">项目完整代码已附于文中，若需获取最新依赖版本，请参考 \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Falibaba\u002Fspring-ai-alibaba\">Spring AI Alibaba 官方文档\u003C\u002Fa>。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp data-line=\"511\">\u003Cstrong>作者简介\u003C\u002Fstrong>：本文作者为一名Java开发者，热衷于探索AI工程化落地。欢迎交流。\u003C\u002Fp>\n",14914,49,"0","",2,0,491,"&quot,Dish,菜品,Agent,new","\n手把手带你入门Agent开发，实现一个能理解自然语言、自动推荐菜品的AI点餐助手\n\n一、背景：从规则到智能\n传统的点餐推荐通常由硬编码逻辑实现——根据人数计算菜品数量，按荤素比例筛选，最后返回结果。这种方式虽然确定、高效，但缺乏灵活性：用户无法用自然语言表达“今晚4个人，想吃辣一点的，预算200左右”，更无法追问调整。","2026-05-12 22:29:59"]