十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Web智能体开发:Plan-Then-Execute范式如何解决复杂网页任务执行难题

Web智能体开发:Plan-Then-Execute范式如何解决复杂网页任务执行难题 1. 为什么“先计划后执行”是Web智能体的必然选择最近在折腾各种基于大语言模型的Web智能体Web Agents时我反复踩进一个坑里让智能体直接去操作浏览器它经常像个无头苍蝇要么卡在登录页面不知所措要么在复杂的多步骤任务中迷失方向执行到一半就忘了最初要干嘛。这让我开始重新审视一个看似基础但在实践中被严重低估的范式——Plan-Then-Execute即“先计划后执行”。简单来说Plan-Then-Execute要求智能体在执行任何具体操作如点击、输入、导航之前必须先制定一个完整的、结构化的行动计划。这个计划不是模糊的“去购物网站买个东西”而应该是“1. 导航至某电商网站首页2. 在搜索框输入‘无线鼠标’并回车3. 在结果页筛选‘品牌A’且‘价格低于200元’的商品4. 点击第一个符合条件的商品进入详情页5. 点击‘加入购物车’6. 进入购物车页面点击‘结算’”。这个范式与我们熟知的ReActReasoning and Acting框架有联系但也有本质区别。ReAct强调在行动中穿插思考是“思考-行动-观察-再思考”的循环而Plan-Then-Execute更倾向于在循环开始前先进行一次全局性的、高层级的“宏观思考”来制定蓝图后续的执行更多是遵循这个蓝图去进行微观的“操作-观察-校验”。为什么对于Web智能体来说这个范式如此关键核心原因在于Web环境的复杂性和不确定性远超封闭的文本环境。一个网页可能动态加载、元素可能随机出现或消失、操作可能触发意想不到的跳转或弹窗。如果没有一个预先制定的计划作为“行动纲领”和“纠偏基准”智能体很容易陷入局部最优或在遇到意外时彻底失去方向。从网络热词中频繁出现的“WebArena”这个评测环境就能看出社区正在构建越来越复杂、贴近真实世界的网页任务来评估智能体这恰恰放大了直接执行策略的弊端。因此采纳Plan-Then-Execute不是一种可选的优化而是构建可靠、鲁棒Web智能体的架构基石。2. 剖析ReAct框架在Web场景中的局限性很多开发者包括早期的我会自然而然地想到用ReAct框架来构建Web智能体。毕竟ReAct将推理Reason和行动Act结合让LLM根据环境反馈Observation一步步思考下一步听起来非常灵活且适应性强。在诸如HotpotQA这类问答任务上它确实表现惊艳。但当我们把战场转移到浏览器时ReAct的“步进式”特性反而成了它的阿喀琉斯之踵。2.1 任务复杂性与上下文断裂Web任务往往是多步骤、长流程的。例如“在招聘网站上找到某公司在某城市的所有数据分析师岗位并汇总其薪资范围”。使用纯ReAct模式智能体可能会这样进行思考“我需要先打开招聘网站”然后执行导航动作观察页面加载完成后思考“现在需要登录”执行登录操作登录后思考“需要搜索公司名”……这个过程看似合理但问题在于每一步的“思考”都严重依赖于上一步的“观察”是典型的马尔可夫决策过程。一旦某一步的观察因为网络延迟、元素加载慢、或者页面结构微调而出现偏差后续的整个推理链就可能跑偏。更致命的是在长达十几步的操作中智能体很容易“忘记”最初的整体目标陷入某个子任务的细节里比如反复尝试一个无法点击的按钮而缺乏一个全局视图来将自己拉回正轨。2.2 对动态环境的过度敏感与冗余操作Web页面是高度动态的。一个搜索操作可能触发页面刷新、局部AJAX更新或打开新标签页。在ReAct的“行动-观察”循环中每次行动后智能体都需要重新观察整个或部分页面状态并基于此进行推理。这导致了两个问题一是观察成本高每次都需要解析DOM或截图消耗大量计算资源和时间二是推理冗余对于计划中明确、连续的操作如“输入用户名”后紧接着“输入密码”ReAct可能仍会进行两次独立的观察和推理而实际上第二次推理输入密码在计划阶段就可以确定为必然动作。这种“走一步看一步”的模式在动态环境中显得效率低下且脆弱。2.3 缺乏预见性与错误恢复能力当操作失败时例如点击了一个不存在的按钮ReAct框架依赖当前的观察和推理来尝试修复比如“刚才点击失败也许按钮ID变了我试试用XPath定位”。这种修复是反应式的、局部的。而Plan-Then-Execute范式下的智能体则可以回溯到最初的计划。它能意识到“我的计划第三步是点击‘提交’按钮但现在失败了。让我检查是整个计划的前提如页面状态错了还是这一步的执行方式如定位器错了”。计划作为一个显式的、可审查的中间产物为错误诊断和恢复提供了一个更高层次的锚点。智能体可以对比“预期状态”计划中该步骤执行后应到达的页面和“实际状态”从而更系统地进行故障排查而不是在局部修修补补。注意这里并非全盘否定ReAct。在计划Plan阶段内部完全可以采用ReAct式的推理来生成计划。核心区别在于Plan-Then-Execute将这个“推理”过程前置并产出结构化计划而将“执行”过程后置并尽可能简化。你可以理解为ReAct是“边想边做”而Plan-Then-Execute是“先想好再做”。3. 构建一个高效的“计划”生成模块既然计划如此重要那么如何让LLM为我们生成一个高质量、可执行的计划呢这绝不是简单地对LLM说“请为‘预订机票’任务生成一个计划”。一个鲁棒的计划生成模块需要解决以下几个核心问题3.1 计划的结构化与要素定义一个合格的Web操作计划应该至少包含以下几个结构化要素步骤序列Step Sequence有序的操作步骤列表。操作类型Action Type如NAVIGATE,CLICK,TYPE,SELECT,EXTRACT等。目标描述Target Description用自然语言或结构化查询如CSS Selector, XPath的生成依据描述要操作的元素。例如“搜索框其placeholder属性包含‘搜索商品’”。预期结果Expected Outcome执行该步骤后期望发生的状态变化用于后续验证。例如“页面跳转到搜索结果页标题包含‘无线鼠标’”。备选策略Fallback Strategy可选当主要操作失败时的备用方案如“如果找不到‘登录’按钮则尝试寻找‘Sign In’链接”。在实践中我们可以设计一个JSON Schema来约束LLM的输出确保计划的机器可读性。例如{ plan_name: 在电商网站购买无线鼠标, steps: [ { step_id: 1, description: 导航至电商网站主页, action: NAVIGATE, target: https://www.example.com, expected_outcome: 成功加载网站首页页面标题为Example商城 }, { step_id: 2, description: 在顶部搜索框输入关键词, action: TYPE, target: CSS选择器: header input[typesearch], value: 无线鼠标, expected_outcome: 搜索框内文本变为无线鼠标 }, // ... 更多步骤 ] }3.2 利用工具调用Function Calling增强计划可行性现代LLM的Function Calling能力是计划生成的利器。我们可以将常见的Web操作抽象成“工具”暴露给LLM。在计划阶段LLM不是生成最终的操作指令而是生成一个“工具调用序列”。例如工具库可能包含navigate(url),find_element(description),click(element),type_text(element, text),extract_text(element)等。LLM在制定计划时实际上是在组合这些工具。这样做的好处是边界清晰LLM只在它擅长的“规划”领域工作无需理解底层浏览器驱动的具体API。可行性高计划中调用的工具都是已实现、可执行的避免了计划天马行空无法落地。易于验证每个工具调用都可以有明确的成功/失败状态返回。3.3 融入领域知识与常见模式对于特定领域如电商、社交、办公我们可以为计划生成模块注入领域知识。例如在电商任务计划中可以预设“浏览商品-查看详情-加入购物车-结算-支付”这样的通用流程模板。LLM在生成具体计划时可以在这个模板骨架上填充血肉具体的商品名、筛选条件等。这能显著提高计划的质量和生成速度也降低了LLM的认知负担。这些模式可以从人类演示、历史成功任务日志中挖掘出来。4. “执行”引擎的设计从僵化执行到动态适配有了一个完美的计划并不意味着执行就能一帆风顺。Web世界充满变数一个僵化的、逐条执行计划而不顾反馈的引擎注定会失败。一个健壮的执行引擎需要具备以下能力4.1 状态感知与计划校验在执行每一步之前和执行之后引擎都必须感知当前页面的状态。这通常通过分析DOM树、截取屏幕截图并用视觉模型分析或监听网络请求等方式实现。关键动作在于将当前状态与计划中该步骤的“预期结果”进行比对。例如计划中第二步的预期结果是“页面跳转到登录页出现用户名输入框”。执行了“点击登录链接”后引擎需要检查1页面URL是否变化2DOM中是否出现了input[typetext]且其name或id包含user、login等字样如果校验通过则继续下一步如果失败则触发错误处理流程。4.2 条件分支与循环处理好的计划应该能处理简单的条件逻辑。例如计划可能是“步骤3检查页面是否有‘库存充足’标签。如果有执行步骤4加入购物车否则执行步骤3.1刷新页面等待或步骤3.2结束任务”。执行引擎需要能解析这种条件语句并根据当前页面状态决定执行路径。对于“循环”操作如“翻页直到找到目标商品”引擎需要能判断循环终止条件找到商品或到达末页并管理循环变量页码避免无限循环。4.3 优雅降级与实时重规划这是执行引擎最核心的“智能”部分。当某一步执行失败或状态校验未通过时引擎不应直接报错退出而应启动降级策略。一个分层的错误处理机制可能是重试相同的操作立即重试1-2次应对瞬时网络问题或元素加载延迟。备选策略执行执行该步骤计划中预定义的备选方案如用不同的定位器点击同一元素。局部重规划如果预定义策略都失败将当前任务上下文原始目标、已执行步骤、当前状态、错误信息反馈给一个轻量级的“规划器”可以是一个更小、更快的LLM请求它对剩余步骤进行微调或重新规划从当前步骤开始的后继路径。这不同于从头开始的全量规划效率更高。人工干预请求如果局部重规划仍无法解决则暂停任务记录日志并请求人类提供指导。人类的反馈又可以作为新的学习数据用于优化未来的计划生成。4.4 执行痕迹与可解释性引擎必须详细记录每一步的执行操作、页面快照、状态校验结果和任何错误信息。这份“执行日志”对于调试、优化计划模型以及向用户解释智能体“做了什么”和“为什么失败”至关重要。它使得整个智能体的行为变得透明、可审计。5. 实战从零搭建一个Plan-Then-Execute Web智能体原型理论说了这么多我们来动手搭建一个简单的原型以“在豆瓣网搜索电影《星际穿越》并获取其评分”为例。我们将使用Python结合Playwright进行浏览器自动化并利用一个LLM API如OpenAI GPT-4或本地部署的类似模型作为规划核心。5.1 环境准备与工具定义首先安装必要库并定义我们的工具集。# 环境准备pip install playwright openai import asyncio from playwright.async_api import async_playwright import openai import json # 定义工具函数这些将被暴露给LLM用于规划 async def navigate(page, url): 导航到指定URL await page.goto(url) return f已导航至 {url} async def find_element(page, description): 根据描述查找元素。这里简化处理实际应用需更复杂的元素定位逻辑。 # 这里可以集成基于描述的智能元素定位例如使用LLM生成CSS选择器。 # 为简化我们假设description就是CSS选择器。 element await page.query_selector(description) if element: return {status: found, element_info: description} else: return {status: not_found, element_info: description} async def click(page, element_info): 点击元素 element await page.query_selector(element_info) if element: await element.click() return f已点击元素 {element_info} else: raise Exception(f无法找到元素进行点击: {element_info}) async def type_text(page, element_info, text): 在元素中输入文本 element await page.query_selector(element_info) if element: await element.fill(text) return f已在元素 {element_info} 中输入文本 {text} else: raise Exception(f无法找到元素进行输入: {element_info}) async def extract_text(page, element_info): 从元素中提取文本 element await page.query_selector(element_info) if element: text await element.text_content() return f从元素 {element_info} 提取到文本: {text.strip()} else: raise Exception(f无法找到元素进行文本提取: {element_info})5.2 计划生成器实现接下来我们实现一个函数让LLM根据用户指令生成结构化计划。我们使用System Prompt来引导LLM扮演规划者的角色。async def generate_plan(user_task, available_tools): 调用LLM生成执行计划。 user_task: 用户任务描述如“在豆瓣网搜索电影《星际穿越》并获取其评分” available_tools: 可用的工具列表描述 client openai.OpenAI(api_keyyour-api-key) # 请替换为你的API Key system_prompt f 你是一个Web操作任务规划专家。你的目标是将用户用自然语言描述的任务分解成一个可执行的、步骤清晰的计划。 你可以调用的工具如下 {json.dumps(available_tools, indent2)} 请以JSON格式输出计划结构如下 {{ plan_name: 任务名称, steps: [ {{ step_id: 序号, description: 步骤描述, action: 工具名如 navigate, find_element, click, type_text, extract_text, target: 工具的目标参数对于navigate是URL对于其他通常是元素描述/CSS选择器, value: 可选type_text工具需要的输入文本, expected_outcome: 执行此步骤后期望看到的结果 }} ] }} 请确保计划逻辑正确步骤完整。对于元素定位尽量使用稳定且具描述性的CSS选择器。 user_prompt f用户任务{user_task} response client.chat.completions.create( modelgpt-4, # 或使用其他合适的模型 messages[ {role: system, content: system_prompt}, {role: user, content: user_prompt} ], temperature0.1, # 低温度保证输出稳定性 response_format{type: json_object} # 要求JSON格式输出 ) plan_json json.loads(response.choices[0].message.content) return plan_json # 定义可用工具描述 available_tools_desc [ {name: navigate, description: 导航到指定URL, parameters: {url: string}}, {name: find_element, description: 根据CSS选择器查找元素, parameters: {selector: string}}, {name: click, description: 点击一个元素, parameters: {selector: string}}, {name: type_text, description: 向输入框等元素输入文本, parameters: {selector: string, text: string}}, {name: extract_text, description: 从元素中提取文本内容, parameters: {selector: string}}, ]5.3 计划执行器实现执行器负责按计划调用工具并进行简单的状态校验。async def execute_plan(plan, page): 执行生成的计划 results [] for step in plan[steps]: print(f执行步骤 {step[step_id]}: {step[description]}) try: if step[action] navigate: result await navigate(page, step[target]) elif step[action] find_element: # find_element通常作为click/type的前置检查这里简化处理 result await find_element(page, step[target]) if result[status] not_found: raise Exception(f未找到元素: {step[target]}) result 元素查找成功 elif step[action] click: result await click(page, step[target]) elif step[action] type_text: result await type_text(page, step[target], step[value]) elif step[action] extract_text: result await extract_text(page, step[target]) else: raise Exception(f未知操作: {step[action]}) results.append({step_id: step[step_id], status: success, result: result}) print(f 成功: {result}) # 简单等待确保页面稳定 await page.wait_for_timeout(1000) except Exception as e: results.append({step_id: step[step_id], status: failed, error: str(e)}) print(f 失败: {e}) # 此处可加入更复杂的错误处理逻辑如重试、重规划等 break # 简单起见失败则中断 return results5.4 主流程与测试最后我们将所有部分串联起来。async def main(): user_task 在豆瓣网搜索电影《星际穿越》并获取其评分 # 1. 生成计划 print( 正在生成任务计划 ) plan await generate_plan(user_task, available_tools_desc) print(生成计划如下) print(json.dumps(plan, indent2, ensure_asciiFalse)) # 2. 启动浏览器并执行计划 print(\n 开始执行计划 ) async with async_playwright() as p: browser await p.chromium.launch(headlessFalse) # 设为True可无头运行 page await browser.new_page() execution_results await execute_plan(plan, page) print(\n 执行结果汇总 ) for res in execution_results: print(f步骤 {res[step_id]}: {res[status]} - {res.get(result, res.get(error, ))}) # 保持浏览器打开一段时间以便观察 await page.wait_for_timeout(5000) await browser.close() if __name__ __main__: asyncio.run(main())运行这个脚本LLM可能会生成类似如下的计划实际输出取决于模型和提示词{ plan_name: 豆瓣电影搜索与评分获取, steps: [ { step_id: 1, description: 导航至豆瓣电影主页, action: navigate, target: https://movie.douban.com, expected_outcome: 成功加载豆瓣电影首页 }, { step_id: 2, description: 在搜索框输入电影名称, action: type_text, target: input#inp-query, value: 星际穿越, expected_outcome: 搜索框内文本变为‘星际穿越’ }, { step_id: 3, description: 点击搜索按钮, action: click, target: input[type\submit\], expected_outcome: 跳转到搜索结果页面 }, { step_id: 4, description: 在结果列表中点击第一个匹配的电影条目, action: click, target: div.item-root a[href*\/subject/\]:first-child, expected_outcome: 进入电影《星际穿越》的详情页 }, { step_id: 5, description: 提取电影评分, action: extract_text, target: strong.rating_num, expected_outcome: 获取到评分数字如‘9.4’ } ] }这个原型清晰地展示了Plan-Then-Execute范式的完整流程任务输入 - LLM规划 - 结构化计划 - 引擎执行。虽然它还很基础例如元素定位依赖LLM生成稳定的CSS选择器这本身就是一个挑战错误处理也很简单但它已经具备了核心骨架。在实际项目中你需要强化每一个环节使用更鲁棒的元素定位策略如结合视觉和DOM、实现更精细的状态校验和错误恢复机制、优化提示工程以生成更可靠的计划。6. 关键挑战与未来演进方向尽管Plan-Then-Execute范式优势明显但在实际落地中我们仍需面对并攻克一系列挑战。6.1 计划的质量与泛化能力计划的可靠性完全依赖于LLM的规划能力。对于它未见过的网站布局或复杂交互如拖拽、滑块、验证码LLM可能生成错误或不可行的计划。解决方案包括提供网站导航在规划时为LLM提供目标网站的关键页面URL结构和主要元素的描述作为规划的背景知识。少样本学习Few-shot Learning在提示词中提供几个类似任务的成功计划示例引导LLM遵循正确的格式和逻辑。分层规划Hierarchical Planning先进行高层级、抽象的任务分解如“登录-搜索-下单”再对每个子任务进行具体的操作规划。这降低了单次规划的复杂度。6.2 元素定位的鲁棒性这是Web自动化的经典难题在智能体场景下更为突出。计划中“target”字段的稳定性直接决定执行成功率。纯CSS选择器或XPath非常脆弱。未来的方向是结合多模态模型视觉定位使用屏幕截图和“指向性描述”如“右上角的蓝色按钮”来定位元素这更接近人类的方式对UI变化的容忍度更高。混合定位策略优先使用稳定的属性如>
返回列表