ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

大模型Agent浏览器自动化:从RPA工程实践中提炼的5个反直觉设计

大模型Agent浏览器自动化:从RPA工程实践中提炼的5个反直觉设计 1. 项目概述当Agent Loop遇上浏览器自动化最近在折腾一个基于大语言模型的自动化项目核心是让一个Agent智能体去操作浏览器完成一些重复性任务。做着做着我忽然有种强烈的既视感——这不就是RPA机器人流程自动化吗而且很多设计思路和实现细节和传统RPA工具的思路惊人地相似甚至在一些关键节点上用大模型驱动的Agent Loop会做出一些看起来“反直觉”的选择。这种“殊途同归”的现象很有意思它背后反映的其实是自动化任务本身的内在逻辑而非技术栈的差异。所谓Agent Loop简单说就是一个“感知-思考-执行”的循环。Agent通过某种方式比如视觉、DOM解析感知当前浏览器页面的状态然后由大语言模型LLM根据任务目标进行“思考”生成下一步的操作指令如点击、输入、滚动再通过自动化工具如Playwright、Selenium去执行。这个循环会一直持续直到任务完成或失败。听起来很智能对吧但当你真正去实现它尤其是在处理复杂的、动态的网页时你会发现为了稳定和可靠你不得不引入很多RPA领域早已验证过的“笨办法”。这五个“反直觉”的点正是我在项目实践中踩过坑、交过学费后总结出来的。它们挑战了我们对于“智能Agent”应该更灵活、更“人性化”的想象反而指向了更确定、更结构化、甚至更“笨”的路径。下面我就结合具体场景把这五个点掰开揉碎了讲清楚。2. 反直觉一追求“稳定定位”远胜于“智能理解”刚开始做的时候我的想法很“AI”让大模型去理解页面它看到“登录按钮”就应该去点看到“搜索框”就应该去输入。我尝试让模型直接输出基于视觉或自然语言描述的指令比如“点击那个蓝色的、写着‘Submit’的按钮”。结果惨不忍睹。页面布局稍一变蓝色按钮变成绿色或者旁边多出一个类似的按钮Agent就懵了点击错误率飙升。为什么“智能理解”在浏览器自动化里这么不靠谱网页的视觉呈现CSS和文本内容A/B测试、多语言是高度易变的。一个按钮今天用#007bff蓝色明天可能就改成#28a745绿色。“提交”这个词可能显示为“Submit”、“提交”、“确认”。依赖这些表层特征稳定性无从谈起。大模型虽然能理解语义但它对像素级的变化没有容忍度也无法保证每次“看到”的都是一样的东西。RPA的启示选择器才是王道所有成熟的RPA工具如影刀RPA、UiPath其核心基石都是稳定、唯一的元素选择器Selector。无论是CSS Selector、XPath还是它们自己封装的各种定位方式本质都是通过DOM文档对象模型的结构化路径来锁定元素。DOM结构相对于视觉样式要稳定得多。我们的“反直觉”实践降级使用选择器在Agent Loop中我们做了一个关键的架构调整将“元素定位”和“决策执行”解耦。感知阶段不再让LLM直接“看”页面截图或描述来定位。而是通过Playwright等工具获取页面的DOM信息并预先计算或配置好一套关键元素的稳定选择器。这个选择器库可以手动维护也可以通过录制工具半自动生成。思考阶段LLM的任务被简化。它接收的不再是“一张图片”而是“当前页面的状态描述URL、标题、关键元素是否存在”和“一个可操作的元素列表附带其业务语义如‘登录按钮’、‘用户名输入框’”。LLM只需要根据任务目标从已知的可操作列表中选择下一步要操作哪个“语义化”的元素。执行阶段根据LLM选择的元素语义映射到预先定义好的、稳定的选择器然后执行自动化操作。注意这里的选择器需要精心设计。优先使用具有id属性的其次是用包含>// 示例一个预定义的元素映射表 const elementRegistry { ‘login_button’: ‘#loginBtn’, // 稳定的ID选择器 ‘search_input’: ‘input[name“q”]’, // 稳定的属性选择器 ‘first_search_result’: ‘.search-results li:first-child a’, // 相对稳定的结构选择器 }; // LLM的决策输出可能是{“action”: “click”, “target”: “login_button”} // 执行引擎则翻译为await page.click(elementRegistry[‘login_button’]);这个转变看似让Agent“变笨”了——它不能自由发现新元素了。但实际上它把智能用在了更擅长的地方流程决策和异常判断而把需要绝对稳定的环节元素定位交给了更可靠的传统方法。系统的整体成功率从最初的不到50%提升到了95%以上。3. 反直觉二“流程固化”比“自由探索”更高效另一个美好的幻想是给Agent一个高级目标比如“帮我查一下明天北京到上海的航班并找出上午出发的最便宜的那个”它就能像人一样打开浏览器搜索航司官网或OTA平台浏览筛选最终完成任务。这要求Agent具备强大的自主探索和规划能力。现实是骨感的。让Agent在开放的网页环境中自由探索效率极低且极易迷失。它会陷入无关信息的漩涡在网站导航、弹窗广告、推荐内容中反复横跳却迟迟无法到达核心任务页面。每一次探索都伴随着网络请求、页面加载和状态判断成本非常高。RPA的启示录制与回放传统RPA之所以能高效处理确定性的业务流程核心在于“录制”。开发者通过录制一次正确的操作路径RPA工具会将其转化为一个包含具体步骤、等待条件和分支判断的固化脚本。这个脚本是可重复、可预测的。我们的“反直觉”实践设计“导航锚点”与“子流程模块”我们放弃了让Agent从零开始探索整个互联网的想法转而采用了一种“有限状态机”与“模块化流程”结合的方式。定义任务边界与入口我们不再给Agent一个模糊的自然语言指令。而是为它定义好一系列“可完成任务”及其对应的“入口URL”。例如“查询航班”任务的入口就是https://www.example-flights.com一个具体的OTA网站。Agent的初始动作不是“打开百度搜索航班”而是“导航至预设的航班查询入口”。固化关键导航路径从入口到核心功能页面如航班搜索页的路径被固化为一个“导航子流程”。这个子流程可能包含关闭初始弹窗、点击“机票”标签、等待页面加载完成等步骤。这个子流程用确定性的选择器和操作序列实现不依赖LLM决策仅在需要判断如“是否有弹窗”时才让LLM参与一个简单的二分类判断。LLM负责核心决策与数据提取当Agent成功抵达航班搜索页面后LLM的智能才被充分调用。它根据用户指令“明天”、“北京到上海”、“上午”、“最便宜”来操作页面上的筛选控件、点击搜索并理解搜索结果列表的结构从中提取出符合条件的信息。即使在这一步我们也会通过预先定义的“数据提取模式”来约束LLM的输出格式确保结构化。# 伪代码示例航班查询任务的Agent Loop结构 async def flight_search_agent_loop(user_query): # 1. 固化导航进入任务页面 await goto(“https://www.example-flights.com”) await close_popup_if_exists() # 固化操作 await click(‘flight_tab’) # 固化操作 # 2. LLM解析用户指令填充搜索表单 search_params llm_parse_query_to_form_data(user_query) await fill_form(‘departure_city’, search_params[‘from’]) await fill_form(‘arrival_city’, search_params[‘to’]) # ... 填充日期等其他字段 # 3. 执行搜索固化操作 await click(‘search_button’) await wait_for_results() # 4. LLM理解结果页面并提取数据 page_html await get_page_content() extraction_schema {‘flights’: [{‘time’: ‘’, ‘price’: ‘’, ‘airline’: ‘’}]} flight_data llm_extract_structured_data(page_html, extraction_schema) # 5. LLM根据用户条件上午、最便宜进行过滤和决策 final_choice llm_filter_and_decide(flight_data, user_query) return final_choice这种方式下Agent Loop不再是漫无目的的探索而是在我们预设的“轨道”上运行只在关键的决策点释放LLM的智能。这极大地提高了任务完成的效率和可靠性非常像RPA中先录制主干流程再在关键节点加入判断逻辑的模式。4. 反直觉三多模态LLM并非“银弹”文本DOM仍是主力当前多模态大模型如GPT-4V, Gemini Pro Vision能力强大能直接理解截图。一个很直觉的想法是把整个浏览器截图喂给模型让它“看到”一切然后指挥操作岂不完美这避免了复杂的DOM解析似乎更接近人类。实践下来这条路在复杂自动化场景下成本高、速度慢且精度不足。首先截图和调用视觉模型的成本远高于处理文本。其次模型对截图的理解存在延迟对于动态加载的内容需要频繁截图效率低下。最关键的是视觉模型对于精细定位如精确点击一个特定复选框或链接的能力远不如基于DOM的选择器准确。它可能告诉你“点击右下角的那个按钮”但这个描述在自动化指令中是无法被稳定执行的。RPA的启示基于对象的操作RPA工具底层操作的都是浏览器提供的编程接口如DOM元素。点击、输入等动作最终都转化为对某个特定元素对象的方法调用。这是精确且可靠的。我们的“反直觉”实践视觉作为辅助验证DOM作为操作基础我们采用了“文本为主视觉为辅”的混合策略。主信息流可访问性树Accessibility Tree与关键DOM文本浏览器提供的可访问性树A11y Tree是比纯DOM更高级的语义化表示它包含了元素的角色role、名称name、状态等信息比原始HTML更接近LLM能理解的语义。我们将A11y Tree或经过清洗的关键DOM文本如按钮文字、输入框提示、主要段落内容作为LLM感知页面的主要输入。这速度快、成本低且包含了足够的语义信息供LLM做决策。视觉作为“异常检测器”和“兜底方案”多模态模型用在两个地方验证与异常处理当基于DOM的操作失败例如元素找不到或点击后无预期变化我们会截取当前屏幕让视觉模型判断“页面是否出现了意外的弹窗”、“目标元素是否确实可见但属性变了”。这相当于给Agent加了一个“眼睛”用于故障诊断。处理纯视觉内容对于验证码、图表、或是某些极度依赖CSS渲染才能理解的UI状态如一个用颜色表示进度的条视觉模型是必要的。我们会局部截取相关区域进行分析。// 示例混合感知策略 async function perceivePage(page) { // 主要感知源获取页面的语义化信息 const a11ySnapshot await page.accessibility.snapshot(); const mainContent extractSemanticInfo(a11ySnapshot); // 提取角色、名称、状态等 // 次要感知源获取关键输入框、按钮的DOM状态以备精确操作 const interactiveElements await page.$$eval(‘input, button, a’, els els.map(el ({ selector: computeStableSelector(el), // 计算一个稳定选择器 tagName: el.tagName, type: el.type, placeholder: el.placeholder, innerText: el.innerText, }))); return { semanticDescription: mainContent, // 送给LLM做决策 elementRegistry: interactiveElements // 用于执行引擎映射和操作 }; } // 当操作失败时启动视觉诊断 async function diagnoseWithVision(page, failedAction) { const screenshot await page.screenshot({ fullPage: false }); const visionPrompt 页面操作“${failedAction}”失败。当前截图区域可能是什么情况是否有弹窗、错误提示或页面布局重大变化; const diagnosis await callVisionModel(screenshot, visionPrompt); // 根据诊断结果决定重试、调整选择器或终止流程 }这个架构承认了当前技术的局限性文本/语义处理成熟可靠视觉处理强大但昂贵且略有不准。将两者结合用正确的工具做正确的事是构建稳健Agent系统的关键。5. 反直觉四复杂的“状态管理”无法逃避如果你认为有了LLM这个“大脑”它就能自己记住所有事情那又错了。浏览器自动化本质上是与一个有状态的应用Web Page交互。页面状态会随着操作而改变登录后出现用户菜单搜索后列表更新勾选选项后出现新的表单字段。LLM本身是无状态的每次调用都是独立的。如果你只是简单地将当前页面信息扔给LLM让它决定下一步它很快就会丢失上下文。例如它可能忘记自己已经登录了再次发出登录指令或者不记得已经筛选了“仅看有货”的商品重复操作。RPA的启示明确的变量与状态跟踪在RPA开发中开发者会显式地定义变量如isLoggedIn True,searchResultsCount 50来跟踪流程状态并根据这些变量值决定后续分支如If isLoggedIn Then ... Else ...。我们的“反直觉”实践为Agent构建外部状态记忆我们必须为Agent Loop设计一个外部的、持久化的状态管理机制。这通常包括会话上下文Conversation Context将整个交互历史包括LLM的指令、系统的观察、执行的结果维护在一个上下文窗口中随着每次调用LLM时一并送入。这利用了LLM自身的上下文理解能力来维持短期记忆。但上下文长度有限且成本随长度增加。关键状态变量State Variables定义一组对业务流程至关重要的核心状态并显式地维护它们。这些状态通常由LLM的输出来更新或由系统根据页面变化来检测。页面级状态当前URL、页面主要标题/头部文字用于判断所处位置。业务级状态auth_status已登录/未登录、current_step在多步表单中的第几步、selected_items已选中的商品列表、filters_applied已应用的筛选条件。状态检测与验证在每次循环开始或关键操作后主动检测页面状态并与记忆中的状态进行比对验证。例如执行“登录”操作后去检测页面是否出现了用户头像或“退出”链接来验证登录是否真正成功并据此更新auth_status变量。# 状态管理模块示例 class AgentState: def __init__(self): self.context_history [] # 会话上下文 self.variables { ‘current_page’: ‘home’, ‘is_logged_in’: False, ‘search_performed’: False, ‘cart_item_count’: 0, } def update_from_observation(self, page_info): 根据页面观察更新状态变量 if ‘用户中心’ in page_info.title: self.variables[‘current_page’] ‘user_center’ if ‘退出登录’ in page_info.body_text: self.variables[‘is_logged_in’] True # ... 其他状态更新逻辑 def get_context_for_llm(self): 构建包含历史上下文和当前状态的Prompt state_summary f“当前状态位于{self.variables[‘current_page’]}页登录状态{self.variables[‘is_logged_in’]}...” full_context self.context_history[-10:] [state_summary] # 保留最近10条历史 return “\n”.join(full_context)通过引入这套状态管理Agent才能真正拥有“记忆”知道“我从哪里来现在在哪儿接下来该去哪儿”避免重复或矛盾的操作。这完全是RPA脚本中状态变量和条件分支思想的体现。6. 反直觉五“容错与恢复”机制必须显式设计指望LLM一次就生成完全正确的指令序列或者自动化操作在复杂网络环境中永远成功是不现实的。元素加载稍慢、网络波动、意外弹窗、网站反爬策略都会导致步骤失败。一个没有容错能力的Agent一次失败就会导致整个流程崩溃。RPA的启示异常处理与重试逻辑所有企业级RPA工具都强调健壮性Robustness。它们提供了完善的异常捕获Try-Catch机制、元素等待Wait For策略、条件重试Retry逻辑甚至完整的故障恢复Recovery流程。我们的“反直觉”实践将“容错”作为Agent Loop的核心组成部分我们必须把错误处理和恢复逻辑深度编织到Agent的循环中而不是事后补救。每一步都有“健康检查”在执行任何一个操作指令如点击、输入前系统会先检查目标元素是否存在、是否可见、是否可交互。这通过Playwright等工具提供的waitForSelector配合状态选择器如:visible来实现。这避免了在元素未就绪时盲目操作导致的失败。操作失败后的分层处理策略立即重试对于网络超时等瞬时错误自动重试1-2次。状态重评估如果重试失败触发一次完整的“感知”步骤获取最新的页面状态。将“操作失败”这一事实和新的页面观察一起作为输入提交给LLM。Prompt可能是“尝试点击登录按钮失败当前页面状态如下[最新页面描述]。可能的原因是什么我们应该继续尝试还是执行其他操作如刷新页面”LLM驱动的恢复决策让LLM根据最新情况决定恢复策略。它可能会建议“刷新页面后重试”或者“弹窗遮挡了按钮先尝试关闭弹窗其选择器可能是.modal-close”。预设安全网对于某些已知的致命错误如“页面无法访问”、“账号被锁定”设置预设的失败处理流程如记录日志、发送通知、优雅终止任务。引入“检查点Checkpoint”对于长流程任务在完成关键里程碑如“成功登录”、“提交订单成功”后将当前状态包括cookies、state variables进行持久化保存。如果后续流程崩溃可以从最近的检查点恢复而不是从头开始。# 一个操作步骤的容错配置示例概念性 - step: “点击提交订单按钮” selector: “button[data-testid‘submit-order’]” action: “click” retry_policy: max_attempts: 3 delay_between_attempts: “2s” retry_on: [“TimeoutError”, “ElementNotVisibleError”] recovery_strategies: - trigger: “SelectorNotFound” actions: - “call_llm_for_diagnosis” # 让LLM看看页面怎么了 - “if_suggested, try_alternative_selector” - trigger: “StillFailingAfterRetries” actions: - “save_error_screenshot” - “notify_operator” - “graceful_shutdown”这种设计使得Agent系统不再是脆弱的“玻璃栈”而是具备一定韧性的“橡胶栈”。它能应对部分意外从错误中学习通过LLM分析并尝试自我恢复。这背后的思想与设计高可用RPA流程时考虑的异常处理场景如出一辙。7. 实战心得从“Agent幻想”到“工程现实”走完这一圈我的核心体会是用LLM构建浏览器自动化Agent其挑战主要不在AI模型本身而在传统的软件工程和自动化测试领域。我们不是在创造一个通用人工智能而是在利用LLM的语义理解和规划能力来增强一个传统的、需要极高稳定性的自动化系统。几个关键的实操心得始于RPA终于RPA在开始设计Agent系统之前先用传统RPA的思路录制、选择器、线性流程把核心任务跑通。这个“基线”版本可能很笨但它是稳定的。然后再思考哪些环节可以用LLM来“软化”和“智能化”比如将硬编码的判断条件如“如果价格大于100则跳过”换成由LLM根据更复杂的描述如“跳过性价比不高的商品”来决策。Prompt工程是新的API设计你如何为LLM描述页面状态、定义可操作选项、规定输出格式本质上就是在为你的“AI模块”设计接口。这部分需要像设计软件API一样精心构思确保清晰、无歧义、易于LLM理解。一个结构化的Prompt模板远比一段自由文本有效。监控与评估至关重要你需要建立一套监控系统来跟踪Agent的成功率、每一步的耗时、LLM调用的成本和频率、常见的失败模式。这些数据是迭代优化你的选择器、Prompt、状态管理和容错策略的唯一依据。没有度量就没有改进。混合智能Hybrid Intelligence是出路不要试图用LLM解决所有问题。将确定性的部分导航、定位、数据提取模板用代码固化将需要模糊判断、语义理解、小样本学习的部分交给LLM。让两者各司其职才能构建出既智能又可靠的系统。最终这个项目让我深刻理解到技术浪潮来来去去但许多工程问题的本质是相通的。RPA在过去十年积累的最佳实践——稳定的元素定位、模块化的流程设计、显式的状态管理、健壮的异常处理——在AI时代依然闪耀着价值。Agent Loop不是来取代RPA的而是为它装上了一颗更强大的“大脑”。两者的融合正在催生下一代更智能、更易用的自动化工具。而作为开发者我们需要做的就是放下对“纯智能”的幻想脚踏实地用好每一件工具解决真实世界的问题。
返回列表