
在接触过不少业务系统、写过不少自动化脚本之后我越来越确定一件事真正耐用的Web Agent核心不在模型选得多强而在外围的架构怎么搭。模型迭代太快了今天用的方案三个月后可能就被新出的模型碾压。但如果架构设计得足够“外挂”、足够“万金油”那模型换血只是换一个模块的事业务逻辑、工具调用、上下文管理这些骨架可以原封不动地复用。这篇笔记想分享的就是我目前在自用的一套Web Agent架构。它不是某个框架的二次封装而是我从零开始搭的一套“外挂式”方案。所谓“外挂式”指的是这套Agent并不需要深度侵入你的业务系统也不要求在目标网站里埋任何SDK它就是一个独立运行的旁路系统通过可观测性去“看”通过协议去“操作”通过记忆去“沉淀”。整套东西的核心目标就三个字不绑定。不绑定具体模型、不绑定具体业务场景、不绑定具体网页结构。如果你正在做网页自动化、智能客服、RPA替代方案或者单纯想给自己的业务系统加一个“能自己干活”的智能层这篇文章应该能给你一些启发。我会把架构设计的思路、核心模块的实现细节、以及我在实际使用中踩过的坑尽量完整地记录在这里。1. 内容整体设计与思路拆解1.1 为什么是“外挂式”而不是嵌入式一开始我尝试过嵌入式方案——直接在业务系统里加Agent逻辑让Agent能直接调用内部函数。但很快发现了几个难以回避的问题第一侵入性太强。业务系统的代码里要揉进Agent调度逻辑、上下文管理、工具注册代码耦合度直线上升。今天想让Agent多一个能力就得改业务代码明天想换一个Agent框架几乎等于重构。第二维护成本太高。业务系统一升级Agent的逻辑可能就挂了。因为你跟的是业务系统的内部实现而不是对外暴露的稳定接口。业务代码一改内部函数Agent那边的调用关系全断。第三多系统复用时特别难受。如果你手上有三套系统——一套是内部管理系统、一套是客户门户、一套是数据分析后台——嵌入式方案意味着要在三个系统里分别埋Agent逻辑同样的代码写三遍改起来也要改三遍。所以后来我换了思路Agent完全独立运行通过“外挂”的方式与目标网站交互。它不关心目标网站的代码是怎么写的只关心两件事——能观察到什么可观测性、能操作什么可操作性。这就好比你在教一个人使用一台仪器你不需要把这个人装进仪器里你只需要让他能看到仪表盘、能按到按钮、能拿到读数。这个思路的本质是把Agent的能力跟业务系统的实现解耦。1.2 “万金油”式的通用性从哪里来“万金油”意味着这套架构不能只解决一个特定网站的问题而是要能快速适配不同类型的Web应用传统多页应用、现代SPA单页应用、复杂表单流程、多标签页协作、需要登录鉴权的后台系统最好都能覆盖。要让架构具备这种通用性核心在于抽象层设计。我在实践里总结出一个三层抽象模型资源抽象把网页中的按钮、输入框、下拉框、弹窗、表格统一抽象为“可操作资源”通过语义化ID和只读DOM结构暴露给Agent。操作抽象把点击、输入、选择、滚动、等待统一抽象为“原子动作”配套一个协议层把Agent的语言指令翻译成真实可执行的浏览器操作。对话抽象把一次任务拆解为多轮人机协作过程每轮包含感知观察当前状态、决策确定下一步动作、执行调用工具操作页面、记忆记录有用信息四个环节。这三层抽象一旦建立好换什么模型、跑什么业务都只是配置问题而不是架构问题。我在实际使用中也验证了这点同一套架构接GPT-4o可以跑接Claude可以跑接开源的Qwen-VL也能跑业务部分完全不用动。1.3 这套架构解决的三个核心矛盾第一是“Agent能力”与“系统安全”的矛盾。外挂式设计可以做到完全只读操作生成、写操作需授权在系统层面加一道安全闸门而不是让Agent直接在业务系统里裸奔。第二是“任务复杂”与“过程可控”的矛盾。Agent跑着跑着跑偏了怎么办外挂式架构允许你在每一步动作前插入人工确认环节也可以随时打断Agent的决策循环让系统从“自动驾驶”切回“手动驾驶”。第三是“一次性任务”与“持续优化”的矛盾。Agent执行完之后产生的轨迹日志、页面状态、决策序列都是可以被重新回放和分析的。做一次任务留下一次可复用的经验做十次任务几乎就沉淀出了一个针对该系统的操作知识库。2. 核心细节解析与实操要点2.1 可观测层如何让Agent“看懂”网页可观测层是整套架构的地基。Agent再聪明如果它拿到的页面信息是残缺的、失真的、过时的那决策就不可能对。这块我的设计思路是不是直接把整页HTML丢给模型而是做一次信息蒸馏提取出一个精简的语义化页面快照。首先是可见区域的智能识别。我开发了一套轻量级的重点元素提取器会遍历DOM树过滤掉脚本标签、样式标签、隐藏元素display:none 或 visibility:hidden只保留可见的、可交互的元素。然后给每个元素打上语义化ID比如btn_submit_order、input_username、link_help_center这样Agent在决策时不需要面对一长串DOM路径只需要引用语义化ID。其次是状态标注。提取器会给元素追加“可交互状态”的描述例如按钮是可用状态还是禁用状态disabled属性判断、输入框是否已预填内容、下拉菜单当前选中了哪一项、弹窗是否在视口内用getBoundingClientRect()判断。这样Agent就能感知到“现在页面处于什么状态”而不是像一个盲人一样在迷雾里瞎猜。再就是动态区域的增量更新。SPA页面的DOM变化特别频繁如果每次都给Agent发完整快照不仅token消耗大而且信息噪音会稀释关键信息。我用MutationObserver监听DOM变化只对变化区域生成增量补丁只有在任务关键节点比如提交表单、页面跳转、弹窗出现才发送完整快照。这套策略实测下来单次决策的token消耗能下降60%以上而且决策准确率反而因为噪音变少而提升了。2.2 操作层如何让Agent“操控”网页光能看懂还不够Agent必须能把决策变成真实动作。操作层我用的方案是通过CDPChrome DevTools Protocol与无头浏览器通信由Agent生成结构化的操作指令再通过指令执行器解析并驱动浏览器执行。操作指令我定义为JSON格式每个命令包含三个核心字段action动作类型、target目标元素的语义ID、params动作参数。比如{ action: click, target: btn_submit_order, params: {} }{ action: input, target: input_username, params: {text: test_user, clear_first: true} }{ action: select, target: ddl_city, params: {option_label: 上海, option_value: shanghai} }这里有一个关键的细节Agent不直接接触DOM元素而是通过语义ID间接操作。语义ID是操作层的“中间语言”它解耦了Agent与页面结构的依赖——页面的CSS类名变了、DOM层级变了只要语义ID没有变化Agent的操作逻辑就完全不用调整。在执行策略上我遵循“先验证后执行”的原则。指令执行器在真实操作之前会先校验目标元素是否存在、是否可见、是否可交互校验不通过就返回错误码让Agent重新决策而不是盲目执行导致操作落空。2.3 调度层多Agent协作怎么组织单个Agent处理简单的线性任务很顺畅但遇到复杂任务时我的经验是拆成多个Agent协作更合理。调度层的设计借鉴了经典的“主从模式”一个Planner Agent负责全局拆解多个Worker Agent负责具体执行。Planner Agent的任务分解逻辑我调了很多轮才稳定。最早是让Planner直接输出步骤列表但效果不理想因为任务边界不清晰子任务之间往往有隐含的依赖关系。后来我改成让Planner先输出一个任务依赖图——每个节点是一个子任务每条边是依赖关系然后再根据依赖图生成执行队列。这个改动让复杂任务的完成率提升了一截因为Agent能知道“哪些步骤可以在填完表单之后并行推进”这件事了。Worker Agent之间通过共享黑板模式通信。所谓黑板就是一个共享的信息存储区每个Worker可以把中间结果、关键信息写到黑板上后续的Worker从黑板上读取前序结果继续操作。这个模式的好处是解耦Worker之间不需要直接通信各自只需要面向黑板工作新增一个Worker只需要约定好黑板上的读写格式非常利于横向扩展。调度器本身还内置了失败重试和降级策略某个Worker如果连续失败三次调度器会根据失败原因判断是换一种策略重试还是直接将子任务回退给Planner重新规划。3. 实操过程与核心环节实现3.1 整体代码架构怎么组织我习惯把整套系统的代码分成几个清晰独立的模块工程结构大概是这样的web-agent/ ├── agent_core/ # Agent核心逻辑感知、决策、执行、记忆循环 ├── tools/ # 工具集浏览器操作、页面解析、数据提取 ├── memory/ # 记忆系统短期上下文、长期向量记忆 ├── scheduler/ # 任务调度器任务分解、worker协作、重试降级 ├── protocol/ # 指令协议层JSON指令定义、校验器 ├── adapters/ # 模型适配层兼容不同LLM接口 ├── browser/ # 浏览器控制层CDP封装、无头浏览器管理 └── dashboard/ # 可视化控制台任务监控、人工干预入口模块边界我用了一个“强制依赖方向”的规则tools → protocol → agent_core → scheduler。也就是说tools只依赖protocol定义的数据结构不依赖agent_coreagent_core只调用tools的接口但不了解tools内部的具体实现。这样的依赖方向保证了一个关键属性——替换任何一个底层模块都不会影响上层模块。3.2 Agent Runtime感知-决策-执行的核心循环Agent Runtime是整个系统的引擎室核心是一个循环这个循环每一轮都要回答一个问题当前状态下下一步做什么。循环的第一步是“感知”。感知模块从可观测层拿到当前页面的状态快照并结合记忆模块提供的上下文信息比如这个任务已经进行到哪一步了、之前在某个页面遇到过什么异常组装成一个结构化的状态描述。第二步是“决策”。决策模块把状态描述、任务目标、可选工具列表一起塞给LLM让模型输出一个结构化指令。这里我把决策拆成了两个子阶段先用一个轻量级模型做意图分类判断当前需要哪种能力填表点按钮查数据还是需要外部API再把完整状态交给重量级模型做细粒度决策。这个两阶段设计在成本控制上效果很明显大约有三成左右的决策轮次只需要走轻量级模型就够了。第三步是“执行”。执行模块拿到LLM生成的指令先做格式校验和参数合法性检查防止模型生成幻觉参数检查通过后交给底层浏览器控制层执行。执行完会把结果反馈给感知模块——页面变了开始下一轮循环。第四步是“记忆”。每一轮循环结束时系统会把这一轮的关键信息页面状态摘要、决策指令、执行结果压缩成一条结构化记忆写入短期上下文当检测到任务里程碑时比如成功提交了一条订单还会把这条经验总结后写入长期记忆。这个循环的执行节奏我调成了一秒级间隔每轮循环结束后系统会根据当前任务的进度评估——任务是否接近完成、是否需要继续下一步、还是需要停下来等待人工确认。节奏太快容易产生无效轮次浪费token节奏太慢又会拉长整体完成时间。1秒的间隔在绝大多数网页场景下已经足够从容。3.3 关键实测同一套架构适配三个不同类型的系统为了验证“万金油”这个说法不是自嗨我拿这套架构实际跑了三个完全不同的系统。第一个是传统的后台管理系统页面是服务端渲染的多页应用靠表单提交完成数据流转。这类系统DOM结构稳定但对操作时序要求严格——表单没加载完就提交是典型的失败场景。这套架构接入使用的方案是“预检指令”每次表单操作前先发一个“waitFor”指令等待关键表单控件全部处于可交互状态再继续后续操作。实测下来原本人工操作需要5分钟的表单录入流程Agent跑完约1分20秒且连续跑20次没有出现一次时序竞态错误。第二个是现代的SPA单页应用用的是Vue框架页面大量依赖动态渲染。第一次接入时Agent总是点击不到动态加载的按钮因为按钮在DOM里还没挂载完语义ID提取器根本找不到目标。后来我在可观测层加了“元素等待补偿逻辑”当提取器发现某个预期的语义ID缺失时会先轮询等待2秒并重新提取一次快照如果二次提取还是找不到才把“元素缺失”作为状态上报给决策层。这个补偿逻辑基本解决了SPA的动态加载问题。第三个是一个第三方SaaS系统登录需要短信验证码。这里我没让Agent直接处理验证码而是设计了一个“人工介入点”Agent检测到验证码输入框出现时会暂停任务流转通过控制台推送通知给人工人工输入验证码后按“继续”键Agent自动从暂停点接着往下执行。这个功能看起来简单但在实际部署中极其重要——因为你永远不知道验证码会以什么形式出现短信、邮件、扫码、滑块与其训练Agent适配每一种不如设计好暂停与恢复机制让人在必要的节点做一次低成本干预。三次测试下来我比较满意的不是Agent“什么都能干”而是接入成本确实降到了一个合理的水平——三个系统从零到跑通首条任务平均耗时都在一天以内。这个成绩的关键正是“外挂式”架构带来的不需要改目标系统任何一行代码只需要约定语义ID的提取规则和操作协议即可。4. 常见问题与排查技巧实录这套架构从初版跑通到现在踩过的坑不少。我挑几个典型的问题连同排查思路一起记录下来希望能帮你少走弯路。4.1 Agent“看错”了页面操作落空最早遇到的频率最高的问题就是Agent明明决策了一个操作但执行层反馈“目标元素不存在”。排查之后发现绝大多数情况不是模型的问题而是可观测层给模型的信息是过期的——Agent决策所依赖的页面快照跟它决策之后实际执行时的真实DOM状态已经不一致了。尤其是网络慢的页面一个简单的按钮点击可能触发了路由跳转等到下一个指令执行时页面已经换了。这个问题的根治办法是“指令-快照绑定”每次执行指令前执行层都会重新检查目标元素是否存在如果发现元素状态与Agent决策时看到的快照不一致就把最新的状态反馈给Agent强制Agent基于最新状态重新决策而不是盲目执行一个基于旧状态生成的指令。4.2 模型开始“幻觉”生成不存在的操作指令LLM在长任务中容易产生幻觉尤其是上下文被压缩过之后模型可能会“记错”之前的操作状态从而生成一个跟当前状态完全不匹配的操作指令。比如表单已经提交成功了模型还在尝试输入表单内容。处理这个问题我建议在决策环节对模型的输出做约束首先决策模块不直接输出操作指令而是先输出一个“状态确认字段”让模型先描述它认为的当前页面状态再生成下一步指令其次操作指令生成后必须经过“前置校验器”校验器会检查指令中的目标元素是否在最新的语义索引中存在、操作参数是否符合元素类型。这两道关卡叠加下来幻觉指令的触发率能降低到之前的十分之一以下。4.3 长任务跑着跑着上下文爆炸或者遗忘Agent跑三四十分钟的长任务时上下文管理是最大的挑战。如果每一轮循环都把完整页面快照塞进上下文很快上下文就满了但如果不塞Agent又会遗忘早期的关键信息比如任务开始时用户输入的约束条件。我的实践经验是用三级记忆架构第一级是“工作记忆”只保留最近五轮的完整操作记录保证连贯性第二级是“里程碑记忆”当检测到任务有重要进展时成功新增了一条数据、成功通过了一项校验把这段经验提炼成摘要写入长期记忆第三级是“核心约束记忆”在任务开始时就把用户的硬性要求比如“不要删除任何数据”“只能用管理员账户操作”写入一个固定区域每轮循环都会作为前缀重新注入。4.4 排查技巧整理我把这些排查经验整理成了一张速查表方便在实际使用中快速定位问题问题现象可能原因排查思路解决措施操作落空元素找不到页面快照与真实DOM不一致检查执行层是否做了元素预检启用指令-快照绑定机制指令报参数非法模型生成了幻觉参数在决策输出后增加参数校验严格校验每类动作的必填参数和值域任务中途上下文溢出页面快照写入太多统计每轮上下文的token消耗开启增量更新只发送变化区域Agent反复执行相同动作状态感知里缺少“结果确认”环节检查上一动作执行后是否更新了状态执行完成后必须确认DOM变化再进入下轮长任务早期信息遗忘上下文压缩策略不合理检查里程碑记忆是否正常写入使用三级记忆架构保持核心约束页面动态加载导致首次提取失败语义ID提取器提取过快确认是否开启了元素等待补偿逻辑开启轮询等待并二次提取快照4.5 我在实际使用中的几个贴心建议最后分享几个不在架构图里的、但实战中特别有用的小经验。第一Agent的浏览器环境尽量保持干净。我在跑任务之前会自动清理Cookie、LocalStorage、IndexedDB避免上一次任务的残留状态干扰下一次任务。别小看这一步很多诡异的前后依赖问题其实都源于状态污染。第二人工干预入口一定要做得足够显眼。无论你的Agent多智能总会有需要人拍板的瞬间。我在控制台上做了一个“任务心跳”设计如果Agent连续三次决策都没有推进任务进度比如一直在同一个页面打转控制台会把任务挂起弹出一条人工介入请求。刚开始我担心这会增加人工负担实际跑下来发现这个设计反而大大提升了人工对Agent的信任度——因为你知道系统在关键节点会请你确认而不是自己横冲直撞。第三操作日志要带“重放功能”。我把Agent每轮的页面快照、决策指令、执行结果全部落盘存储并且可以在控制台像播放录像一样重放整个操作过程。这个功能在调试时帮了大忙——不用靠猜直接回放日志一眼就能看出Agent在哪一步出了问题。5. 这套架构后续还能怎么扩展写到这里我自己回头看了一下这套架构的成长路径第一版只能做一个站点的简单点击任务第二版能跑复杂表单流程第三版能适配多系统协作到现在已经成了一个能独立承接“交钥匙任务”的通用底座。这个过程里变化最大的其实不是模型能力的提升而是外围架构对模型能力的利用效率。后续我还计划在这套架构上做三件事。第一件事是引入主动校验机制让Agent在关键操作提交、删除、支付之后主动向界面“提问”——通过计算机视觉或者DOM状态对比确认操作是否真的生效了而不是只关注操作本身是否执行成功。第二件事是前端新增一种“策略热更新”能力让语义ID的提取规则可以动态下发这样遇到页面改版时运维人员只改配置、不动代码就能让Agent继续工作。第三件事是尝试把知识沉淀做得更强一些让Agent能真正从过去的失败操作中学习自动总结出“这类表单不要直接回车提交”之类的隐性经验而不是每次都在同一个地方栽跟头。根据我多次从零搭Agent架构的经验有一个体会特别深不要一上来就追求模型最强、功能最全先跑通最小闭环再逐步加模块。因为Agent系统的复杂度是指数上升的一次加太多模块出了问题你根本不知道是模型决策问题还是架构问题。先让一个最简单的“看-想-做”循环跑通再往上面叠加记忆、调度、协作、校验每加一层都回归一遍基准任务这套节奏会让你省下很多排查时间。这套“外挂式”架构对我个人来说目前足够顺手也希望这篇笔记里的思路和细节能给你在构建自己的Web Agent时提供一些参考。