用AI代理自动订购麦当劳
为什么点击按钮时,代理可以为我们做这些?我是如何构建一个多代理系统,自动订购我的每周麦当劳套餐的,使用了谷歌的ADK、Selenium和太多工程热情

免责声明
发表的观点和意见仅代表我个人,不代表雇主(麦当劳)的官方政策或立场。
自动化与网站交互,尤其是涉及用户账户和交易的自动化,可能会引发道德和法律问题。始终确保您的自动化活动符合网站的服务条款及相关法律法规。此外,请考虑自动化任务与用户数据和隐私交互的伦理影响。
本项目仅用于说明性目的,旨在促进对基于代理系统的更深入了解。它严格用于测试和教育用途,不得用于任何商业或生产用途。
你可以在这里访问完整的代码库。
不会发生实际订购—— 该脚本在完成支付之前会停止——你需要手动完成结账。
1、周三麦乐鸡传统:一段个人旅程
每个星期三中午,我都会站在芝加哥麦当劳全球菜单餐厅的队伍中,满怀期待地等待他们轮换提供的国际美食精选。咬一口来自日本的辣黑蒜鸡块,再品尝意大利风味的开心果麦旋风,最后配上澳大利亚的芝士培根加载薯条,这种体验简直令人陶醉。

但作为一名软件工程师,有着固定的食物习惯:为什么还要手动操作,当你可以用AI代理来过度设计它呢?
从“我想知道是否能自动化我的麦当劳订单”的简单想法,很快演变成了一套完整的代理到代理(A2A)编排系统。
2、架构:AI代理的交响曲
与其构建一个单一的食品订购机器人,我设计了一个多代理生态系统,其中每个代理都有特定的责任,通过结构化协议进行通信,并共同实现复杂的自主食品订购目标。

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

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
该代理采用系统化的方法:
- 导航到UberEats.com
- 搜索芝加哥的麦当劳®(全球菜单餐厅)
- 选择全球菜单位置
- 添加项目到购物车基于解析的菜单意图
- 准备结账交接

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 要克服的挑战
- 网站更改:UberEats经常更新他们的UI。网页自动化代理需要适应性选择器和回退策略。
- 速率限制:过多的快速请求触发了反机器人措施。增加了智能延迟和类似人类的交互模式。
- 菜单可用性:全球菜单项目轮换不可预测。构建了当标志性项目不可用时的回退逻辑。
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
汇智网翻译整理,转载请标明出处
