> ## Content Index
> Fetch the complete content index at: https://www.hubwiz.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# 用AI代理自动订购麦当劳
- URL: https://www.hubwiz.com/blog/order-macdonalds-with-ai-agents/
- Published: 2025-06-13T22:22:28.000Z
- Updated: 2025-06-13T22:22:28.000Z
- Description: 为什么点击按钮时，代理可以为我们做这些？我是如何构建一个多代理系统，自动订购我的每周麦当劳套餐的，使用了谷歌的ADK、Selenium和太多工程热情
- Author: admin
- Tags: APPLICATION, AGENT, GOOGLE ADK, #Import 2026-10-14 14:41

> 免责声明  
>  
> 发表的观点和意见仅代表我个人，不代表雇主（麦当劳）的官方政策或立场。  
>  
> 自动化与网站交互，尤其是涉及用户账户和交易的自动化，可能会引发**道德**和**法律问题**。始终确保您的自动化活动符合网站的服务条款及相关法律法规。**此外，请考虑自动化任务与用户数据和隐私交互的伦理影响。**  
>  
> 本项目仅用于**说明性目的**，旨在促进对基于代理系统的更深入了解。它严格用于**测试**和**教育**用途，**不得用于任何商业或生产用途**。  
>  
> 你可以在[这里](https://github.com/adriensieg/AI-Agents-McDonalds?ref=hubwiz.com)访问完整的代码库。  
>  
> **不会发生实际订购——** 该脚本在完成支付之前会停止——你需要手动完成结账。

## 1、周三麦乐鸡传统：一段个人旅程

每个星期三中午，我都会站在芝加哥麦当劳全球菜单餐厅的队伍中，满怀期待地等待他们轮换提供的国际美食精选。咬一口来自日本的**辣黑蒜鸡块**，再品尝意大利风味的**开心果麦旋风**，最后配上澳大利亚的**芝士培根加载薯条**，这种体验简直令人陶醉。

![](https://www.hubwiz.com/blog/content/images/2025/06/image-190.png)

但作为一名软件工程师，有着固定的食物习惯：*为什么还要手动操作，当你可以用AI代理来过度设计它呢？*

从“我想知道是否能自动化我的麦当劳订单”的简单想法，很快演变成了一套完整的**代理到代理（A2A）编排系统**。

## 2、架构：AI代理的交响曲

与其构建一个单一的食品订购机器人，我设计了一个**多代理生态系统**，其中每个代理都有特定的责任，通过结构化协议进行通信，并共同实现复杂的自主食品订购目标。

![](https://www.hubwiz.com/blog/content/images/2025/06/image-191.png)

以下是序列图中的主要步骤简要分解：

- **定时触发器（周三中午12:00）**：`SchedulerAgent`检测到预定时间并触发UserProxy开始每周订单。
- **用户请求**：`User`发送自然语言命令（“订购我通常的周三特别套餐”），该命令被记录并传递给`OrderAgent`。
- **发现与委派**：`OrderAgent`查询`MCP`以获取可用工具/代理，然后相应地委派子任务。
- **菜单解析**：`MenuAgent`使用`Gemini`将短语“通常的周三特别套餐”解析为具体的菜单项。
- **网页自动化**：`WebAgent`利用`Selenium`自动化UberEats网站，将选定的物品添加到购物车中。
- **预算与结账**：`CheckoutAgent`验证订单是否符合预算限制和配送地址。
- **付款处理**：安全登录和付款由系统处理，订单在UberEats上提交。
- **结果与确认**：系统记录结果，汇总结果，并向`User`发送最终确认。
- **监控**：`LoggerAgent`持续跟踪系统健康状况、性能和分析。

![](https://www.hubwiz.com/blog/content/images/2025/06/image-192.png)

[来源](https://github.com/adriensieg/AI-Agents-McDonalds/blob/master/README.md?ref=hubwiz.com)

## 3、代理卡：AI的名片

我们生态系统中的每个代理都通过**代理卡**展示其能力——JSON描述符，描述代理可以做什么、如何联系它以及它提供的技能。这遵循了谷歌的A2A协议标准：

```
agent_card = AgentCard(  
    name="菜单理解代理",  
    description="AI驱动的菜单解析器和推荐器",  
    url=f"http://localhost:9004/",  
    version="1.0.0",  
    defaultInputModes=["text"],  
    defaultOutputModes=["json"],  
    capabilities=AgentCapabilities(streaming=True),  
    skills=[  
        AgentSkill(  
            id="parse_menu_intent",  
            name="解析菜单意图",  
            description="理解自然语言食物请求",  
            tags=["nlp", "菜单", "解析", "gemini"],  
            examples=["something spicy", "my usual", "healthy option"]  
        )  
    ]  
)

```

这些卡片在`/.well-known/agent.json`端点上可发现，从而实现动态代理发现和组合。

## 4、深入探讨：代理编排

### 4.1 UserProxy代理：对话的起点

**UserProxy代理**是我们的系统入口。无论是由周三中午12:00的计划任务触发，还是由手动的“I'm hungry”消息触发，这个代理都会标准化输入并将其转发给主协调器。

```
async def process_message(self, message: Dict[str, Any]) -> Dict[str, Any]:  
    self.logger.info(f"收到订单请求：{message}")  

    order_request = {  
        "type": "order_request",  
        "user_input": message.get("content", ""),  
        "timestamp": datetime.now().isoformat(),  
        "source": "user_proxy"  
    }  

    return await self._send_to_order_agent(order_request)

```

### 4.2 菜单理解代理：语言天才

这里变得有趣起来。我没有硬编码菜单项，而是构建了一个由**Gemini 2.0 Flash**驱动的代理，它可以理解自然语言的食物请求：

- “Something spicy” → 辣黑蒜鸡块
- “My usual Wednesday special” → 国际轮换菜品
- “Surprise me” → 基于全球菜单的精选

```
menu_agent = LlmAgent(  
    model='gemini-2.0-flash',  
    name='menu_parser',  
    instruction=(  
        "你是一位麦当劳全球菜单专家。解析用户的请求为具体的菜单项。 "  
        "可用的项目包括：日本的辣黑蒜鸡块， "  
        "意大利的开心果麦旋风，澳大利亚的芝士培根加载薯条， "  
        "以及标准的麦当劳菜单。返回一个包含具体项目的JSON列表。"  
    ),  
    tools=[]  
)

```

这种方法的美妙之处在于**语义理解**。代理不仅仅是匹配关键词——它理解上下文、偏好，甚至可以在项目不可用时做出智能替换。

### 4.3 网页自动化代理：数字木偶师

这就是关键所在。**网页自动化代理**利用**Selenium通过MCP（模型上下文协议）服务器**与UberEats互动，就像人类一样，但具有超人的精确度和速度。

```
async def get_selenium_tools(self):  
    """初始化Selenium MCP工具"""  
    if not self.selenium_tools:  
        mcp_params = StdioServerParameters(  
            command="selenium-mcp-server",  
            args=["--headless", "--ubereats-mode"]  
        )  

        toolset = MCPToolset("selenium_automation", mcp_params)  
        self.selenium_tools, _ = await toolset.get_tools_async()  

    return self.selenium_tools

```

该代理采用系统化的方法：

1. **导航**[到UberEats.com](http://xn--ubereats-nw7m.com/?ref=hubwiz.com)
2. **搜索**芝加哥的麦当劳®（全球菜单餐厅）
3. **选择**全球菜单位置
4. **添加项目**到购物车基于解析的菜单意图
5. **准备**结账交接

![](https://www.hubwiz.com/blog/content/images/2025/06/image-193.png)

### 4.4 结账代理：安全金库

安全性在处理付款信息时至关重要。**结账代理**是隔离的，并处理：

- **认证**存储的凭据
- **地址验证**（我的芝加哥位置）
- **付款处理**通过安全API
- **订单确认**

```
async def process_message(self, message: Dict[str, Any]) -> Dict[str, Any]:  
    checkout_steps = [  
        "验证用户凭据",  
        "验证芝加哥的配送地址",   
        "处理付款方式",  
        "确认订单详情",  
        "向餐厅提交订单"  
    ]  

    for step in checkout_steps:  
        self.logger.info(f"结账步骤：{step}")  
        await asyncio.sleep(0.5)  # 模拟处理时间

```

### 4.5 调度代理：时间管理员

**调度代理**体现了我的周三传统。它运行一个连续循环，检查那个神奇的时刻：*周三中午12:00*。

```
async def start_scheduling(self):  
    self.is_running = True  

    while self.is_running:  
        now = datetime.now()  

        # 检查是否是周三且午餐时间（中午12:00）  
        if now.weekday() == 2 and now.hour == 12 and now.minute == 0:  
            self.logger.info("周三午餐时间！触发订单...")  
            await self._trigger_weekly_order()  

        await asyncio.sleep(60)

```

## 5、技术深度解析：A2A协议的实现

### 5.1 消息传递架构

每个代理通过结构化的JSON消息进行通信，实现松耦合并便于调试：

```
order_request = {  
    "type": "order_request",  
    "user_input": "给我一些辣的东西",  
    "timestamp": "2025-06-08T12:00:00",  
    "source": "user_proxy"  
}

```

### 5.2 异步处理

整个系统基于**async/await**模式构建，允许多个代理并发工作而不阻塞彼此：

```
# 步骤1：解析意图（非阻塞）  
parsed_order = await menu_agent.process_message(parse_request)  

# 步骤2：网页自动化（与日志记录并发）  
automation_task = asyncio.create_task(web_agent.process_message(automation_request))  
logging_task = asyncio.create_task(logger.log_activity("web_automation_started"))  
automation_result = await automation_task

```

### 5.3 错误处理与弹性

每个代理都实现了全面的错误处理和优雅降级：

```
try:  
    result = await web_agent.process_message(automation_request)  
    if result.get("status") == "ready_for_checkout":  
        return await self._proceed_to_checkout(result)  
    else:  
        return self._handle_automation_failure(result)  
except Exception as e:  
    self.logger.error(f"严重故障：{str(e)}")  
    return {"status": "system_error", "fallback": "manual_order_required"}

```

### 5.4 协调器：指挥交响乐

**McDonaldsA2AOrchestrator**充当指挥家，管理代理生命周期和代理间通信：

```
class McDonaldsA2AOrchestrator:  
    def __init__(self):  
        self.agents = {  
            "user_proxy": UserProxyAgent(),  
            "order_agent": OrderAgent(),  
            "web_automation": WebAutomationAgent(),  
            "menu_understanding": MenuUnderstandingAgent(),  
            "checkout": CheckoutAgent(),  
            "scheduler": SchedulerAgent(),  
            "logger": LoggerAgent()  
        }  

    async def start_system(self):  
        print("🍟 启动麦当劳A2A订购系统...")  
        await self.agents["scheduler"].process_message({"command": "start_scheduling"})  
        print("✅ 所有代理已初始化并准备就绪！")

```

### 5.5 可观察性：日志代理

在分布式系统中，可观察性至关重要。**日志代理**提供集中式日志记录和分析：

```
def get_analytics(self) -> Dict[str, Any]:  
    total_logs = len(self.log_storage)  
    error_logs = len([log for log in self.log_storage if log["level"] == "error"])  

    return {  
        "total_events": total_logs,  
        "error_rate": error_logs / total_logs if total_logs > 0 else 0,  
        "agents_active": len(set(log["agent"] for log in self.log_storage)),  
        "weekly_orders": self._count_successful_orders()  
    }

```

### 5.6 要克服的挑战

1. **网站更改**：UberEats经常更新他们的UI。网页自动化代理需要适应性选择器和回退策略。
2. **速率限制**：过多的快速请求触发了反机器人措施。增加了智能延迟和类似人类的交互模式。
3. **菜单可用性**：全球菜单项目轮换不可预测。构建了当标志性项目不可用时的回退逻辑。

## 6、从.env到CIBA：在浏览器之外保护A2A身份验证

在这个早期的不安全版本中，我天真地通过`.env`文件暴露敏感凭据——接下来，**我将重点转向在浏览器之外保护A2A身份验证**，使用CIBA，这是一种无需浏览器参与的身份验证流程，客户端直接向OpenID提供者发起身份验证请求。

```
import os  
from dotenv import load_dotenv  

load_dotenv() # 加载变量从.env  
ubereats_username = os.getenv("DATABASE_URL")  
ubereats_password = os.getenv("SECRET_KEY")  
debug_mode = os.getenv("DEBUG")

```

在我的下一篇文章中——我将探索**客户端启动的后通道身份验证（CIBA）**，它不需要客户端应用程序通过浏览器重定向用户来进行登录/身份验证过程。相反，客户端应用程序直接通过后通道请求向OpenID提供者发起身份验证流。

## 7、结束语

这个项目是过度工程和个人享受的完美结合。我现在有了一个AI驱动的仪式，满足了我的灵魂（和胃）。快乐餐操作，确实如此。

但价值不在此处——

> **为什么我们应该关心AI代理？这不是另一个技术热潮吗？** 实际上，不是——这感觉像是一个真正的转折点。

AI模式正在改变一切。**谷歌不再是单纯的搜索引擎**；它正在进化成完全不同的东西。它不再只是显示**链接列表**，而是现在生成这些**AI制作的“微型网站”**，直接在结果页面上构建，以**回答你的问题**。所以猜猜怎么着？**人们点击进入实际网站的次数减少了**。他们直接从AI版本的内容中获取所需的信息。

这是个大事。这意味着我们需要**重新思考如何创建和组织内容**。这不再仅仅是关键词和SEO——**这是关于让你的内容易于大型语言模型理解**。这就是MCPs（模型可消费页面）的来源。这些页面是为了让AI代理能够轻松消化和使用你的数据，而无需加载你的完整站点。

这是网络的未来吗？**老实说，我觉得可能是这样**。时间会证明，但我对此充满希望。

想想像mcdonalds.com这样的网站——**人们可能不会再直接访问它**。**相反，AI只是从站点中提取它需要的内容，并给用户答案**。没有点击，没有页面浏览量——只有机器到机器的交互。

---

原文链接：[How I Built an AI Agent to Order McDonald’s for Me Every Wednesday](https://medium.com/google-cloud/how-i-built-an-ai-agent-to-order-mcdonalds-for-me-every-wednesday-2814fabb17f7?ref=hubwiz.com)

汇智网翻译整理，转载请标明出处