跳到主要内容

Nanobrowser — 这是什么 · 全景 · 主线 · 阅读地图

30 秒导读: Nanobrowser 是一个 Chrome 扩展,让 AI 帮你操作网页——你在侧边栏里打一句"帮我在 HuggingFace 上找出下载量最高的模型",它就自己点链接、填表单、翻页、读内容,最后把答案回给你。它是付费产品 OpenAI Operator 的免费开源替代:跑在你自己的浏览器里、用你自己的 LLM API key,只按你实际用量付模型费。核心是两个 agent 分工——一个动脑(Planner),一个动手(Navigator)。

本章只做入口和路由:说清它是什么、画出顶层全景、把主线走一遍、澄清一个常见误解,然后把你送到对应的深入章节。不深入任何单一机制的代码——那是后面各章的事。


1. 这是什么(零基础也能懂)

一句话定义

Nanobrowser 是一个在浏览器里运行 AI 网页自动化的开源 Chrome 扩展。你用自然语言描述一个网页任务,它驱动真实的浏览器标签页替你完成。

解决什么问题 / 给谁用

想象你要做一件"机械但繁琐"的网页操作:比价、批量填表、从几十个页面里抄数据、在某网站里一路点到某个设置。人做很枯燥,写爬虫脚本又太重、页面一改就崩。

Nanobrowser 想填的就是这个空档——你说人话,它替你点鼠标。它面向:

  • 想要 Operator 那种"AI 帮我操作浏览器"能力,但不想每月付 $200的人(README.md:35 明确对标 OpenAI Operator);
  • 完全掌控、数据不经过第三方托管服务、用自己的 API key(OpenAI / Anthropic / Gemini / Ollama 等)的人(README.md:37);
  • 想读一个"真实、完整、不算太大"的浏览器 agent 源码来学习的工程师。

它能做什么

  • 在侧边栏用聊天界面下达任务,实时看到每一步状态(README.md:48);
  • 自主导航:打开网址、点击、输入、翻页、读取页面内容、跨标签页操作;
  • 多 LLM 供应商任选,并可给不同 agent 配不同模型(README.md:80,例如 Planner 用强模型、Navigator 用便宜快模型);
  • 录制一次成功任务并重放(见 05-history-replay)。

用起来什么样(一个最小场景)

[侧边栏聊天框]
你: 帮我在 news.ycombinator.com 上找出当前排第一的帖子标题

Nanobrowser:
🧠 Planner → 需要打开 HN 首页,读取第一条列表项的标题
🖐 Navigator → go_to_url(news.ycombinator.com)
🖐 Navigator → 读取页面,定位第 1 号可交互元素…
🧠 Planner → 已拿到标题,任务完成
✅ 答案: "……(第一条帖子的标题)"

你只输入了一句话;剩下的"打开→读页面→定位元素→给答案"全是两个 agent 轮流跑出来的。

一句话直觉

把它想成"你雇了一个实习生 + 一个主管": 主管(Planner)听你的目标、拆解成下一步该干嘛;实习生(Navigator)拿着主管的指示,真的去页面上点点填填,并把看到的情况汇报回来。主管定期检查"是不是做完了"。这就是 Nanobrowser 的全部灵魂。


2. 顶层全景(它大概怎么转)

一张图看懂大盘

整个系统是一条从 UI → 后台 → 两个 agent → 真实网页 的链路。怎么读这张图:上半部是"控制流"(谁调谁),下半部 Navigator 通过 BrowserContext/Page 摸到真实 DOM,再把"看到什么"喂回给模型。

┌─────────────────────────────────────────────────────────────┐
│ 侧边栏 UI (pages/side-panel) │
│ 聊天框输入任务,展示每步事件 │
└───────────────┬─────────────────────────────────────────────┘
│ Port 消息 {type:'new_task', task, tabId}

┌─────────────────────────────────────────────────────────────┐
│ 后台 service worker (chrome-extension/.../index.ts) │
│ 收消息 → setupExecutor 装配 LLM → executor.execute() │
└───────────────┬─────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Executor(总指挥,executor.ts) │
│ 一个 for 循环,轮流调度下面两个 agent │
│ │
│ ┌────────────────┐ ┌──────────────────┐ │
│ │ Planner │ 策略 │ Navigator │ │
│ │ 定下一步/判完成│───────▶│ 选动作/落到元素 │ │
│ └────────────────┘ └────────┬─────────┘ │
└──────────────────────────────────────┼─────────────────────┘
│ 调用动作

┌──────────────────────────────────────────┐
│ BrowserContext / Page │
│ (browser/context.ts、browser/page.ts) │
│ 用 chrome.debugger / puppeteer 驱动标签页 │
└──────────────────┬───────────────────────┘
│ DOMService 提取

┌──────────────────────────────────────────┐
│ 真实网页 DOM │
│ → 带编号的可交互元素清单(喂回给模型) │
└──────────────────────────────────────────┘

旁挂两条贯穿全局的暗线(不在主控制流上,但处处生效):MessageManager 管"喂给模型的对话记忆",guardrails 管"防提示注入"。详见 04-memory-and-security

部件一句话职责

部件一句话职责在哪个文件(相对克隆根)
Executor总指挥:一个循环里轮流调度 Planner 与 Navigator,判完成、报事件、存历史chrome-extension/src/background/agent/executor.ts
Planner动脑:观察当前状态、给出下一步策略、判断任务是否完成(PlannerAgent)chrome-extension/src/background/agent/agents/planner.ts
Navigator动手:把模型输出的动作落到真实网页元素上(NavigatorAgent)chrome-extension/src/background/agent/agents/navigator.ts
BrowserContext管理标签页、切换、取整页状态(BrowserContext)chrome-extension/src/background/browser/context.ts
Page单个标签页的操作面:截图、取可交互元素、点击/输入(Page)chrome-extension/src/background/browser/page.ts
DOMService把网页 DOM 提炼成"带编号的可交互元素清单"(getClickableElements)chrome-extension/src/background/browser/dom/service.ts
MessageManager管理喂给模型的对话记忆:系统提示、任务、状态、计划(MessageManager)chrome-extension/src/background/agent/messages/service.ts
ActionBuilder注册所有可用动作、生成动作的 structured-output schema(ActionBuilder)chrome-extension/src/background/agent/actions/builder.ts
guardrails提示注入防御:清洗外部网页内容里的可疑指令(SecurityGuardrails)chrome-extension/src/background/services/guardrails/index.ts

3. 主线走一遍(high-level,不进代码)

一次任务从"你敲回车"到"拿到答案",大致是这样一条路(每步给了锚点,想看细节按引用跳):

  1. UI 发消息。 侧边栏把任务打包成一条 Port 消息 {type:'new_task', task, tabId} 发给后台。后台在 index.ts:98case 'new_task' 接住它。

  2. 装配执行器。 后台调 setupExecutor(index.ts:268):读出你配置的 LLM 供应商与各 agent 用哪个模型,为 Navigator、Planner 分别用 createChatModel 造好 LLM 实例,套上防火墙/通用设置,new Executor(...)(index.ts:322)。注意:Planner 若没单独配模型,就复用 Navigator 的模型(index.ts:323plannerLLM ?? navigatorLLM)。

  3. Executor 起循环。 executor.execute()(executor.ts:128)进入一个 for 循环,最多跑 maxSteps 步。循环里:

    • 周期性跑 Planner:每隔 planningInterval 步、或 Navigator 刚汇报"我做完了",就调一次 Planner 拿新策略(判定在 executor.ts:157);
    • 每步跑 Navigator:调 this.navigate()(定义见 executor.ts:274)让它选一个/几个动作并真的操作页面;
    • Planner 说 done:truebreak(executor.ts:162,判定逻辑见 checkTaskCompletion,executor.ts:112)。
  4. 输出 final_answer。 循环因"完成"退出时,Executor 把 Planner 给的 final_answer 作为 TASK_OK 事件发回 UI(executor.ts:177-185);若是撞上限步/失败/取消,则发对应的 TASK_FAIL / TASK_CANCEL 事件。

这条主线的深入拆解在 01-agent-loop——那一章才进循环内部,讲 Planner/Navigator 各自怎么跑、怎么交接。


4. 澄清一个易错点:没有独立的 Validator agent

如果你先读了 README 或仓库自带的 CLAUDE.md,会以为 Nanobrowser 有三个 agent:Navigator、Planner、Validator这在当前源码里是过时的。 真源码只有两个 agent。

证据:

  • Agent 枚举里只有两个成员——AgentNameEnum 只定义了 PlannerNavigator(packages/storage/lib/settings/types.ts:2-5),没有 Validator;
  • Executor 也只实例化这两个(executor.ts:39-40navigator / planner 两个字段);
  • 装配阶段主动清理遗留的 validator 配置——setupExecutor 一开头就调 cleanupLegacyValidatorSettings()(index.ts:276),该方法把存储里 key 为 'validator' 的旧条目删掉(packages/storage/lib/settings/agentModels.ts:114-117)。

那"验证任务是否完成"这件事去哪了? 并入了 Planner。原本 Validator 干的"检查是不是真做完"的活,现在由 Planner 的输出直接承担:Planner 的 schema 里有 donefinal_answer 字段(planner.ts:22-46),Executor 用 checkTaskCompletion 读 Planner 的 done 来判定收尾(executor.ts:112-121)。

一句话记住: README/CLAUDE.md 说三个 agent,是历史文档;当前架构是 Planner + Navigator 两个,validation 已折叠进 Planner。读文档时以源码为准。01-agent-loop §6 从"完成判定完全依赖 Planner 自评"的角度进一步展开了这一点。


5. 巧妙之处清单(读者要带走的精华)

这里只点名,每条的深挖都在对应章节:

巧妙之处妙在哪(一句话)深入章节
双 agent 分工动脑(策略)和动手(操作)解耦,可分别配"贵而聪明"和"便宜而快"的模型01-agent-loop
周期性 planning不是每步都规划,而是每隔 N 步或"自称做完时"才规划,省 token 又能自我纠偏01-agent-loop
validation 折叠进 Planner少一个 agent、少一轮调用,用同一次 Planner 输出的 done 兼做完成判定本章 §4 · 01-agent-loop
给模型"装眼睛"把整棵 DOM 提炼成带编号的可交互元素清单(highlightIndex),模型只需说"点第 5 号"02-perception-dom
动作 = structured output动作用 schema 约束模型输出,再由注册表把"第 5 号"精确落到真实元素03-actions-execution
记忆管理MessageManager 精心组织喂给模型的上下文(系统提示/任务/状态/计划)04-memory-and-security
提示注入防御guardrails 清洗网页里夹带的恶意指令,防止"页面反过来指挥 agent"04-memory-and-security
录制与重放一次成功操作可存成历史、逐步重放,不必再调 LLM05-history-replay

6. 阅读地图(各章讲什么、建议顺序)

由浅入深,建议顺着编号读:

章节讲什么什么时候读
index.md(本章)这是什么 · 全景 · 主线 · 阅读地图先读它建立全局认知
01-agent-loop.md双 agent 主循环:Planner 定策略、Navigator 执行,以及二者如何交接想懂"大脑怎么转"
02-perception-dom.md给模型装眼睛:DOM 树 → 带编号的可交互元素清单想懂"模型怎么看见页面"
03-actions-execution.md给模型装手脚:动作注册表、structured output、落到真实元素想懂"模型怎么真的点下去"
04-memory-and-security.md记忆管理与提示注入防御:两条贯穿全局的暗线想懂"上下文和安全"
05-history-replay.md录制与重放:让一次成功的操作可复现想懂"如何复用一次成功"

如果你只想快速上手源码: 读完本章 → 直接看 01-agent-loop,它带你走进 executor.ts 的循环;那是整个项目的心脏。


7. 代码地图(导航索引)

主题文件(相对克隆根)关键符号
后台消息入口chrome-extension/src/background/index.tsonConnect 监听、case 'new_task'setupExecutor
总指挥循环chrome-extension/src/background/agent/executor.tsExecutorexecuterunPlannernavigatecheckTaskCompletion
动脑 agentchrome-extension/src/background/agent/agents/planner.tsPlannerAgentplannerOutputSchema
动手 agentchrome-extension/src/background/agent/agents/navigator.tsNavigatorAgentNavigatorActionRegistry
标签页管理chrome-extension/src/background/browser/context.tsBrowserContextgetStateswitchTab
单页操作面chrome-extension/src/background/browser/page.tsPagegetClickableElementstakeScreenshot
DOM 感知chrome-extension/src/background/browser/dom/service.tsgetClickableElementsinjectBuildDomTreeScripts
记忆管理chrome-extension/src/background/agent/messages/service.tsMessageManagerinitTaskMessagesaddStateMessage
动作构建chrome-extension/src/background/agent/actions/builder.tsActionBuilderbuildDefaultActionsbuildDynamicActionSchema
安全防御chrome-extension/src/background/services/guardrails/index.tsSecurityGuardrailssanitize
清理遗留 Validatorpackages/storage/lib/settings/agentModels.tscleanupLegacyValidatorSettings
Agent 枚举(仅两个)packages/storage/lib/settings/types.tsAgentNameEnum(Planner / Navigator)