
很多朋友在 AI Agent 的项目踩坑到一定程度后都会产生一个很自然的疑问模型既然能理解任务能不能让它自己打开浏览器把网页看完然后替我做一系列操作本系列第 12 篇我们不聊提示词也不聊单次 API 调用专门来拆解“AI 正式接管浏览器”这条链路。Deepseek Harness 这个称呼在社区讨论里经常和 DeepSeek、Agent、浏览器插件、桌面端一起出现。Harness 直译是“线束 / 控制装置”在 AI 工程里它代表一种把模型、工具、任务流程装配到一起的执行框架。浏览器恰恰是这套框架里最容易产生实际价值的工具之一。本文将围绕它的浏览器接管场景给出一套最小可运行闭环原理、环境、代码、排错、安全边界都会有。适合谁呢想搞 AI 自动化测试、想用大模型总结网页、想做受控的浏览器 Agent或者单纯好奇“AI 怎么操作浏览器”的开发者都适合读。读完你能亲手跑通一个会自己打开网页并总结内容的 Agent也理解它背后的工具调用循环。1. 为什么需要“AI 接管浏览器”1.1 从“聊天”到“动手操作”大模型本身只是一个“文本进、文本出”的系统。你问它一个问题它给你一段回答你让它写一段代码它也可以生成代码。但现实世界的任务往往不只是“生成文本”而是需要真实地触发一个动作打开某个网页、读取页面信息、填写一个表单、点击一个按钮、抓取一段数据。传统做法是写爬虫脚本或者自动化测试脚本。比如用 Python 的 requests 抓接口、用 Selenium 模拟点击。这当然可行但缺点也很明显每个目标网站都要定制选择器页面结构一改脚本就得跟着改维护成本很高。如果 AI 来操作浏览器流程就变成了你只说一句“打开 example.com看看这个页面主要讲了什么”模型理解任务后自己决定调用什么工具浏览器真的打开页面模型再读取返回的页面内容最终整理成自然语言回答。这就是“AI 接管浏览器”的核心价值它把原来需要大量编码的固定逻辑变成了一种由模型动态规划的执行过程。1.2 Deepseek Harness 是什么不是什么在社区里Deepseek Harness 相关的讨论经常与 DeepSeek 模型、Agent、浏览器插件、桌面端等词一起出现。坦白讲这个项目的形态迭代很快不同仓库、不同版本对应的安装命令、插件入口甚至核心概念都可能不同。为了避免误导本文不把某个版本的功能写死而是以它代表的“AI 浏览器自动化”技术方案为主线。无论你最后拿到的是桌面端、浏览器插件还是 CLI 工具下面的浏览器控制、工具调用、Agent 循环、安全边界这些概念都是通用的。需要强调一点Harness 并不是某个单独模型也不是一个类似“聊天框”的小工具。它更接近一个“执行框架”负责把大模型的规划能力和外部工具的真实执行能力连接起来。浏览器只是它工具箱里的一种真正让 AI 显得“聪明”的是框架对工具调用的处理方式这部分才是我们这篇文章要重点理解的。1.3 哪些场景最能体现价值“AI 操作浏览器”听起来很酷但实际工程里不是所有场景都适合立刻上。从投入产出比来看下面几类场景最容易落地场景类型典型需求传统方案痛点AI 接管后的优势信息收集与摘要打开几个页面整理关键信息每个页面写一套抓取逻辑自然语言描述目标模型自主选页面页面监控定时检查页面状态、内容变化针对每个站点写轮询脚本可让模型判断变化是否重要表单填报填写标准化表单、提交定位器脆弱页面改版就失效模型根据界面文本自动找输入项数据前处理从网页提取列表、表格规则解析复杂模型读取正文后结构化输出不过要注意这些场景都建立在“你有权操作目标站点”的前提下。抓取、自动化操作都涉及网站条款和隐私边界生产环境一定要遵守目标网站的 robots 协议和相关法律法规不要对没有授权的系统做自动化操作。1.4 “接管”的正确理解“AI 接管浏览器”这个标题很容易让人误以为是全自动无人管理模型想点哪里就点哪里。真实工程中必须反过来理解接管不是替换人的决策而是在用户授权和监督下由 AI 代替人完成低价值、重复性高的浏览器操作。所以一套合格的浏览器 Agent至少要包含三样东西权限控制不是所有站点、所有操作都允许执行。人工确认遇到删除、提交、支付等高风险动作应该停下来问用户。日志审计每一步操作都有记录出了问题能回溯。本文后面的实战案例会跑通“打开网页并总结”的闭环然后把权限、确认、审计这些能力作为最佳实践来补充说明。2. 环境准备与版本说明2.1 使用到的技术栈这一节我们用 Node.js 生态来实现一个极简的 AI 浏览器 Agent所选组件都是当前比较通用的方案组件作用参考版本说明Node.js运行 JavaScript 代码建议 18 及以上20 LTS 更稳pnpm包管理器建议 8 及以上也可用 npm 替代Playwright浏览器自动化控制库以 npm 安装时最新稳定版为准Chromium被控制的浏览器内核通过 Playwright 自动下载安装OpenAI 兼容 API提供大模型对话与工具调用能力DeepSeek 等模型服务通常兼容 OpenAI 格式版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果你使用的是 Python 后端也可以用 Playwright 的 Python 版本核心逻辑是一样的。2.2 安装 Node.js 与 pnpmNode.js 的安装方式按操作系统选一种即可Windows到 Node 官网下载 LTS 安装包一路 next。macOS推荐用 Homebrew执行brew install node。LinuxDebian/Ubuntu 系可以使用apt install nodejs npm但版本可能偏旧建议用 nvm 安装新版本。安装完 Node.js 后我们把 pnpm 装上npm install -g pnpm安装完检查版本node -v pnpm -v如果pnpm命令找不到可能是 npm 全局 bin 目录没有加入 PATH。Windows 下通常安装 Node 后会自动配置macOS/Linux 下可以重新打开终端或者把 npm prefix 目录加入 PATH。2.3 初始化项目并安装依赖创建一个新项目目录并初始化 package.jsonmkdir ai-browser-agent cd ai-browser-agent pnpm init然后安装 Playwright 和 OpenAI 官方 SDKpnpm add playwright openai pnpm exec playwright install chromium这里拆开解释一下playwright负责启动浏览器、打开页面、读取页面内容。openaiOpenAI 官方 Node.js SDK它也可以用于兼容 OpenAI 协议的其他大模型服务。pnpm exec playwright install chromium下载 Chromium 浏览器内核。如果不执行这一步运行时大概率会报“浏览器可执行文件找不到”的错误。如果你的网络环境下载浏览器内核比较慢可以把下载超时时间调长例如pnpm exec playwright install chromium --with-deps--with-deps会在 Linux 环境下自动安装系统级依赖避免缺少动态链接库导致的启动失败。2.4 确认浏览器与模型接口可用先验证 Playwright 是否安装成功node -e const { chromium } require(playwright); console.log(playwright ok);能正常打印playwright ok说明依赖基本没问题。接下来准备模型接口。为了方便不同服务迁移我们把连接信息放到环境变量里。创建一个.env.example文件作为模板# .env.example LLM_BASE_URLhttps://your-llm-service.example.com LLM_API_KEYsk-xxxx LLM_MODELyour-model-name这里不把某个模型服务的地址写死是因为 Deepseek Harness 相关项目在不同部署环境里可能对接 DeepSeek 官方接口、本地 vLLM、或者其他 OpenAI 兼容网关。核心思路是一样的通过baseURL、apiKey、model三个参数切换模型服务。为了读取环境变量我们再安装一个小的工具包pnpm add dotenv后续所有示例代码都会通过dotenv来加载.env中的配置。3. 核心原理拆解3.1 一条自然语言如何变成浏览器操作先来看整个链路的全貌用户自然语言任务 ↓ LLM 理解任务生成工具调用意图 ↓ Agent 解析工具调用参数 ↓ 浏览器控制器打开页面 / 提取内容 ↓ 执行结果返回给 LLM ↓ LLM 基于结果生成最终回答或发起下一次工具调用这个循环的核心是“工具调用”。模型并不直接操作浏览器它只是生成一个结构化的调用指令真正去操作浏览器的是我们的代码。也就是说浏览器权限始终掌握在本地代码手中模型只是“建议”执行什么操作。3.2 CDP 与 Playwright 的角色浏览器自动化底层依赖 CDP 协议。CDP 全称 Chrome DevTools Protocol是 Chrome/Chromium 提供的一套调试接口通过 WebSocket 与浏览器通信可以控制页面跳转、模拟点击、提取 DOM、执行 JavaScript 等。Playwright 是对 CDP 的上层封装它解决了开发者的几个常见痛点跨浏览器统一 API不只支持 Chromium还支持 Firefox、WebKit。自动等待元素出现减少手工 sleep 带来的不稳定。简化了截图、PDF、网络拦截、移动端模拟等高频能力。在“AI 接管浏览器”这个场景里我们最关心的是两件事可靠地打开页面、可靠地拿到页面内容。Playwright 在这两个点上都表现稳定。3.3 工具调用让模型“看见”浏览器大模型本身没有视觉也没有手。要让模型“看见”浏览器返回的内容需要把工具定义成模型能理解的 JSON Schema。下面是一个最简单的工具定义作用是“打开一个网页并返回正文文本”[ { type: function, function: { name: open_page, description: 打开一个网页并返回页面内容和标题, parameters: { type: object, properties: { url: { type: string, description: 要打开的完整网址包含协议头 } }, required: [url] } } } ]当模型认为需要打开网页时它会返回类似下面的结构{ tool_calls: [ { id: call_123, function: { name: open_page, arguments: {\url\: \https://example.com\} } } ] }注意arguments是一个 JSON 字符串不是对象。Agent 代码里需要先JSON.parse解析再调用真正的函数。工具执行完需要把结果作为一条tool消息回传给模型。模型拿到结果后就可以据此生成最终回答或者继续发起新的工具调用。3.4 为什么需要 Agent 循环一次“模型生成工具调用 代码执行工具 结果回传”的过程通常只能完成一个步骤。现实任务往往需要多步例如先打开列表页然后根据链接进入详情页最后汇总多个页面的内容。所以 Agent 必须是一个循环模型生成意图 → 执行工具 → 结果回传模型 → 模型判断是否继续 → 结束时输出最终回答这个循环必须设置两个安全阀最大轮数防止模型在一个问题上反复调用工具白白消耗 Token。超时控制单个工具执行时间过长时要能中断。下面实战案例会完整实现这个循环。4. 完整实战案例让 AI 打开网页并总结内容4.1 案例目标我们的目标是实现一个极简 Agent支持输入一句自然语言任务例如打开 https://example.com并总结这个页面主要讲了什么Agent 的工作流程是把用户任务发送给模型。模型决定调用open_page工具。本地代码用 Playwright 打开页面返回标题和正文。模型根据正文内容生成总结。控制台输出最终回答。这个案例麻雀虽小五脏俱全它包含了浏览器控制、工具调用、Agent 循环三个关键部分。4.2 项目结构我们在ai-browser-agent目录下创建如下结构ai-browser-agent/ ├── package.json ├── .env └── src/ ├── index.js ├── browser.js ├── llm.js └── agent.js每个文件的职责文件职责src/browser.js封装 Playwright提供打开网页并提取正文的能力src/llm.js封装模型调用提供一个稳定的 Chat Completions 方法src/agent.js实现 Agent 主循环负责工具调用与结果回传src/index.js程序入口读取命令行任务并启动 Agent4.3 编写浏览器控制层创建src/browser.js// src/browser.js const { chromium } require(playwright); async function openPage(url) { // 有头模式方便在演示时看到浏览器被打开 const browser await chromium.launch({ headless: false }); const page await browser.newPage(); try { await page.goto(url, { waitUntil: domcontentloaded, timeout: 15000, }); const title await page.title(); const bodyText await page.locator(body).innerText(); // 防止正文过长截断到前 3000 个字符 const trimmedText bodyText.slice(0, 3000); return { title: title, content: trimmedText, url: url, }; } finally { // 关闭浏览器避免进程残留 await browser.close(); } } module.exports { openPage };这里有几个设计细节使用headless: false是为了让你在演示阶段能直观看到浏览器被 AI“接管”。如果跑在服务器上改成headless: true即可。waitUntil: domcontentloaded比networkidle更快更稳因为很多页面有持续的网络请求等networkidle容易超时。返回前截断正文避免一次性把几十万字符塞给模型导致 Token 超限。使用finally关闭浏览器保证即使页面打开失败也不会残留僵尸进程。4.4 编写模型调用层创建src/llm.js// src/llm.js const OpenAI require(openai); const client new OpenAI({ baseURL: process.env.LLM_BASE_URL, apiKey: process.env.LLM_API_KEY, }); async function askLLM(messages, tools) { const response await client.chat.completions.create({ model: process.env.LLM_MODEL, messages: messages, tools: tools, }); return response.choices[0].message; } module.exports { askLLM };这段代码很简单但要注意以下几点OpenAI要求baseURL和apiKey不能为空所以运行前必须先设置环境变量。调用时传入了tools这样模型才知道当前有哪些工具可用。返回的是message对象它里面可能包含content也可能包含tool_calls需要由 Agent 循环去判断。4.5 编写 Agent 主循环创建src/agent.js// src/agent.js const { askLLM } require(./llm); const { openPage } require(./browser); // 工具注册表把工具名映射到真实函数 const toolMap { open_page: openPage, }; // 工具定义让模型知道有哪些函数可以调用 const tools [ { type: function, function: { name: open_page, description: 打开一个网页并返回页面标题和正文内容, parameters: { type: object, properties: { url: { type: string, description: 要打开的完整网址必须包含 https:// 或 http://, }, }, required: [url], }, }, }, ]; async function runAgent(userTask) { const messages [ { role: user, content: userTask }, ]; const MAX_ROUNDS 5; for (let round 0; round MAX_ROUNDS; round) { const assistantMessage await askLLM(messages, tools); // 把模型回复保存到上下文 messages.push({ role: assistant, content: assistantMessage.content || , tool_calls: assistantMessage.tool_calls, }); // 如果模型没有请求调用工具说明可以输出最终答案 if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length 0) { console.log(最终回答); console.log(assistantMessage.content || 模型没有返回内容); return; } // 逐个执行模型请求的工具调用 for (const call of assistantMessage.tool_calls) { const fn toolMap[call.function.name]; if (!fn) { console.error(未知工具: ${call.function.name}); continue; } const args JSON.parse(call.function.arguments || {}); console.log(正在执行工具: ${call.function.name}(${JSON.stringify(args)})); const result await fn(args); // 把工具执行结果回传给模型 messages.push({ role: tool, tool_call_id: call.id, content: JSON.stringify(result), }); } } console.error(已达到最大轮数Agent 停止执行。); } module.exports { runAgent };这个文件是整个 Agent 的核心。解释一下几个关键点toolMap是工具注册表新增能力时只需在字典里加一个键值对。MAX_ROUNDS 5是安全阀防止模型反复调用工具不结束。每次把assistantMessage完整保存到messages是必要的OpenAI 兼容接口要求上下文里保留完整的 assistant 消息否则会报错。工具执行结果必须用role: tool回传并且tool_call_id要对应模型返回的 call id否则接口会认为消息链不完整。4.6 组装入口创建src/index.js// src/index.js require(dotenv).config(); const { runAgent } require(./agent); const task process.argv[2]; if (!task) { console.error(用法: node src/index.js 你的任务描述); process.exit(1); } runAgent(task).catch((err) { console.error(Agent 执行失败:, err); process.exit(1); });这个入口文件只做三件事加载.env环境变量。从命令行参数读取用户任务。调用runAgent并捕获异常。4.7 运行与验证先创建.env文件填入真实的模型服务配置# .env LLM_BASE_URLhttps://your-llm-service.example.com LLM_API_KEYsk-xxxx LLM_MODELyour-model-name然后执行node src/index.js 打开 https://example.com并总结这个页面主要讲了什么预期输出类似正在执行工具: open_page({url:https://example.com}) 最终回答 这个页面主要展示了 example.com 的示例内容说明该域名通常用于文档、教程和示例链接的演示页面……这里有一个重要提示不同模型对工具调用的返回格式可能略有差异如果你的模型服务兼容 OpenAI 的tools参数那么上面的代码可以直接运行如果模型服务不支持工具调用就需要换一个支持 tool calling 的模型。5. 常见问题与排查思路5.1 浏览器没有正常打开如果程序没有报错但浏览器窗口没有出现最常见的原因有两个代码跑在了没有图形界面的服务器上headless: false无法弹出窗口。解决方法是改成headless: true。浏览器内核没有安装。执行pnpm exec playwright install chromium重新安装。如果启动时报错提示缺少系统依赖在 Linux 上可以执行pnpm exec playwright install --with-deps5.2 模型一直不调用工具模型返回的内容是一段文本而不是工具调用通常有三个原因原因说明解决方案当前模型不支持 tool calling部分模型只支持普通对话更换支持 function calling 的模型任务描述不清晰模型不知道应该调用工具在任务里明确说“打开这个网址”工具定义有问题description 和参数说明不够具体增加 description 示例明确触发条件另外如果你的模型服务没有开启 function calling 能力需要确认服务端配置是否允许tools参数。5.3 Agent 陷入无限循环如果 Agent 不断调用同一个工具却一直拿不到最终答案大概率是结果回传格式不正确或者模型没有从工具结果中得到足够信息。排查步骤检查messages里是否正确保存了assistant消息。检查tool消息的tool_call_id是否与模型返回的一致。检查工具返回的content是否过大模型上下文被截断。代码里已经加了MAX_ROUNDS最大轮数限制即使出问题也不会无限跑下去。5.4 页面内容太长导致 Token 超限网页正文可能非常大直接把完整 innerText 返回给模型很容易触发上下文超限。我们在browser.js里已经截断到 3000 字符但这只是一个简单的方案。更好的做法是只提取页面的关键区域比如正文容器、标题、描述。用模型先做一次摘要再把摘要放回上下文。对长页面做分块处理多次调用工具读取不同区块。5.5 pnpm 安装相关命令卡住这类问题更多出现在你使用 Harness 发行版或复杂项目时。比如本地启动命令类似pnpm dsh web时卡住不一定代表代码有问题更可能是以下原因问题现象常见原因解决思路命令执行后长时间无输出pnpm 正在后台安装依赖或下载浏览器内核检查网络清空缓存后重试启动后端口被占用Web 服务端口冲突用lsof -i :端口或 Windows 的netstat -ano查看占用提示 pnpm 版本不支持项目 engine 字段限制了版本检查 package.json 中的引擎要求切换 pnpm 版本这里要特别说明不同项目里dsh只是子命令名具体行为要以项目 README 为准。遇到卡住时先确认 node_modules 是否完整再考虑网络和端口问题。5.6 问题汇总表问题现象最常见原因快速解决浏览器启动报错未安装 Chromiumpnpm exec playwright install chromium模型不调用工具模型不支持或提示词不清换支持 tool calling 的模型优化工具描述Agent 不结束缺少停止条件设置最大轮数Token 超限页面内容太大截断或摘要安装依赖卡住网络或缓存问题切换镜像源、清空 pnpm 缓存6. 最佳实践与工程建议6.1 最小权限原则AI 接管浏览器不等于给模型所有权限。生产环境必须画好边界只允许访问白名单域名。只暴露必要工具比如open_page、click、fill_form。高风险操作必须单独授权。最小权限原则的核心是即使模型被误导或恶意提示词攻击它能造成的破坏也有限。比如一个只负责读页面摘要的 Agent完全没有必要暴露填表、提交按钮的工具。6.2 增加人工确认机制涉及删除、修改、提交、支付等操作时建议增加人工确认。下面是一个简单的命令行确认示例// tools/confirm.js const readline require(readline); async function waitForConfirm(stepDesc) { const rl readline.createInterface({ input: process.stdin, output: process.stdout, }); return new Promise((resolve) { rl.question(即将执行${stepDesc}输入 y 继续, (answer) { rl.close(); resolve(answer.trim().toLowerCase() y); }); }); } module.exports { waitForConfirm };在实际工具函数里可以这样插入if (args.action submit) { const ok await waitForConfirm(提交当前表单); if (!ok) { return { status: cancelled_by_user }; } }这样就把最终决策权保留给了人AI 只负责建议和操作不负责拍板。6.3 日志与审计浏览器 Agent 的每一步操作都应该有日志方便问题回溯和成本分析。推荐至少记录以下信息会话 ID用户输入任务每次工具调用的名称、参数、耗时模型返回的关键内容摘要最终答案如果是本地脚本可以直接用结构化 JSON 输出到文件const logs []; function logEvent(type, payload) { logs.push({ time: new Date().toISOString(), type, ...payload, }); } // 在 Agent 循环中调用 logEvent(tool_call, { name: open_page, args: { url } });生产环境建议接入统一的日志服务并为每条日志关联请求 ID方便定位单次任务的全链路执行记录。6.4 让模型只看到需要的页面内容浏览器页面里有大量噪音比如导航栏、广告、推荐位。让模型读完整页面既浪费 Token也容易干扰判断。工程上可以这样做用 CSS 选择器定位正文区域而不是读取整个 body。优先读取标准化字段比如title、meta description、h1。对页面做简化渲染去掉 script、style、隐藏元素后再提取文本。这就像给模型戴上了一副“过滤眼镜”它看到的内容少而精准确率会高很多。6.5 从单页面走向多步流程本文的案例只实现了“打开页面并总结”的单步工具。真实场景里Agent 往往需要多个工具协同工具名职责search_web用搜索引擎查关键词返回候选链接列表open_page打开指定页面返回文本内容click_element点击页面中的按钮或链接fill_input在输入框中填写内容extract_table把页面表格结构化为 JSON新增工具时只需要在toolMap注册真实函数在tools数组补充定义Agent 循环并不需要改动。这也是 Agent 架构里值得投入的一点把工具层做稳定上层才能灵活扩展。7. 总结与学习路线本篇围绕“Deepseek Harness 与 AI 接管浏览器”从概念、原理到代码跑通了一个最小闭环理解了 Harness 在 AI 工程中的定位是“模型能力与外部工具的装配框架”。知道了浏览器自动化底层依赖 CDPPlaywright 是最常用的封装库。掌握了 tool calling 的完整流程定义工具、模型返回调用意图、本地执行、结果回传。实现了带最大轮数控制、网页截断、浏览器自动关闭的 Agent 示例。下一步可以继续深入的方向包括把单工具扩展成多工具协同加入网页截图给模型做视觉理解接入更长程的任务规划和记忆能力或者探索 Spring AI 这类 Java 生态中的 Agent/Tool 方案。如果你在业务里做 AI 自动化测试或受控 RPA还可以把人工确认、操作审计、权限白名单这些能力逐步补上。我的建议是先从一个低频、低风险的页面开始跑通闭环再逐步放开操作权限。AI 接管浏览器这件事最难的从来不是把代码跑起来而是给 Agent 划好边界、装好刹车。希望这篇第 12 篇能帮你把闭环跑通也把边界想清楚。