当大多数人还在讨论“要不要让AI接入浏览器”时,我已经在键盘上敲下了最后一轮调试代码。三个月前,我立下一个flag:把一个原本运行在终端里的AI Agent完整迁移到浏览器中,让用户打开网页就能用。如今,这个项目终于上线,而回顾这90天的经历,每一步都是血泪铸成的“踩坑指南”。
为什么要做这件事? 终端AI Agent拥有强大的工具调用能力和动态上下文管理,但门槛太高——用户需要安装Python环境、配置API密钥、熟悉命令行操作。浏览器则是最低成本的入口,如果能将Agent“塞”进一个网页,人人点开即用,那将真正实现AI工具的民主化。然而,理想很丰满,现实却很骨感。
第一个坑:WebAssembly的“暴力拆迁”
终端Agent依赖大量原生Python库,比如requests、asyncio以及本地文件系统操作。浏览器环境下,这些库统统“失灵”。我尝试用Pyodide(Python的WebAssembly端口)来跑核心逻辑,但很快发现:支持异步I/O的网络请求在沙箱里被严重阉割,而subprocess(子进程调用)更是直接报错“不允许”。这意味着Agent原本的“执行本地命令”能力必须全部重写——用JavaScript的Fetch API模拟网络请求,用IndexedDB替代文件读写。光是这一改造,就花掉了整整三周。
第二个坑:内存管理的“定时炸弹”
Agent在终端里可以随心所欲地缓存对话历史和工具调用结果,但浏览器内存有限。我在测试时发现,用户连续对话20分钟后,页面直接崩溃——内存溢出。排查后发现,每轮交互Agent都会加载一份完整的上下文Token序列,加上WebAssembly的堆内存无法自动释放,导致“内存泄漏”式爆炸。解决方案是引入分页缓存机制,只保留最近10轮对话的完整数据,更早的历史压缩成摘要存入IndexedDB。这一改,又耗费两周。
第三个坑:工具调用的“身份危机”
原本Agent可以调用系统的curl、grep、甚至启动本地数据库客户端。但在浏览器中,这些外部工具统统不存在。我不得不重新设计一套“浏览器原生工具集”:用fetch替代curl,用document.querySelector模拟爬虫抓取,用setTimer实现异步调度。更头疼的是,某些工具需要用户授权(比如访问剪贴板或摄像头),而浏览器的安全策略要求每次调用都得弹窗确认——用户交互体验瞬间支离破碎。最后我采用“一次性授权+静默白名单”模式,在首次加载时申请必要权限,之后默认允许。
第四个坑:长连接与断线重连
Agent需要与LLM API保持连续对话流,而浏览器WebSocket在切换标签页或锁屏时会自动断开。我试过心跳包保活,但苹果Safari的休眠策略极其激进——直接杀掉所有后台连接。为此我改用Service Worker做中转,让它在后台维持WebSocket,即便主页面进入休眠也能持续接收消息,再通过BroadcastChannel推送回页面。这个方案调试了整整五天,因为Service Worker在HTTPS下才能注册,而本地开发时经常忘记开SSL证书。
第五个坑:UI与Agent的“竞速困境”
终端Agent是纯文本输出,浏览器却是视觉交互。我期望Agent能一边生成回复,一边实时展示思考过程(比如“正在调用API获取天气数据……”)。但Agent的异步流式输出与前端渲染之间存在严重竞争:如果每收到一个Token就更新DOM,页面会陷入卡顿;如果批量更新,用户又感受不到实时性。最终妥协的方案是:使用虚拟滚动列表,只渲染可见区域;同时将Agent的内部状态(如当前步骤、耗时)单独提取并高频更新,而大段文本采用200ms的防抖渲染。
90天后,当第一个用户在我的网站上连续提问50次而没有崩溃时,我长舒一口气。把终端Agent搬进浏览器,本质上是一场“降维适应”——用浏览器的规矩,重新定义Agent的能力边界。如今它虽然失去了直接控制系统的“神力”,却获得了零安装、跨平台、一键分享的“普惠”优势。如果你也想做类似的事,我的建议是:先列一个终端独占功能清单,然后问自己——“这个功能真的必须在浏览器里实现吗?”如果答案是否定的,就果断砍掉。毕竟,在浏览器里做AI Agent,“能用”远比“完美”重要。