跳到主要内容

数据截至 (上游 commit a675d6d61c41)

Fara — 架构与原理

30 秒导读: Fara 是微软 AI Frontiers 开源的浏览器 computer-use agent。它的模型只看浏览器截图,直接吐出"点 (x, y)"这样的鼠标键盘动作;这个仓库提供的是跑这个模型的运行时(agent 主循环 + Playwright 浏览器环境 + 轨迹记录)和一套评测框架。核心卖点是小模型(4B/9B/27B)也能干这活,因为整条链路砍掉了可访问性树和单独的元素定位模型。


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

一句话定义

Fara 是一个让 AI 用鼠标键盘替你开浏览器办事的开源项目:仓库里是运行时代码,模型权重在 Hugging Face / Microsoft Foundry 上。

解决什么问题、给谁用

假设你想让 AI 帮你在网上订一张电影票。传统做法有两条路,都不太好走:

路线做法麻烦在哪
调 API让 AI 调网站开放的接口绝大多数网站根本没有开放接口
读 DOM / 可访问性树把网页结构抽成文本喂给模型文本长、贵;还得再训一个"元素定位"模型把"那个蓝色按钮"翻译成元素 id

Fara 走第三条:模型直接看截图,直接说坐标。README(README.md:57README.md:133)把这条路线叫做 "no accessibility trees or separate parsing models"。

用户画像有两类:

  • 想跑一个能自动上网办事的 agent 的工程师 —— 装包、指一个模型端点、fara-cli --task "..."
  • 做 CUA(computer-use agent)研究的人 —— 要复现基准分数、要研究"怎么给 agent 轨迹打分",用仓库里的 webeval/

它能做什么

  • 打开浏览器、搜索、点击、输入、滚动、拖拽、右键/双击/三击。
  • 读当前页面并回答一个问题(内部再调一次模型做抽取)。
  • 把一条事实"记下来"备用(pause_and_memorize_fact)。
  • 在关键节点停下来问用户 —— 缺个人信息、任务有歧义、马上要做不可逆操作时(见 §3 的 critical point)。
  • 把整条轨迹(每一步的截图、动作、观察)落盘成结构化 JSON。

用起来什么样

装好之后写一个端点配置 JSON,然后一行命令(依据:README.md:79-92src/fara/run_fara.py:214-340):

# azure_foundry_config.json 里放 {"model", "base_url", "api_key"}
fara-cli --task "whats the weather in new york now" \
--endpoint_config azure_foundry_config.json --headful

终端里看到的大致是这样(依据:src/fara/run_fara.py:61-134 的 print 语句):

Initializing Browser...
Browser Running... Starting Fara-1.5 Agent...
##########################################
Task: whats the weather in new york now
##########################################
Running Fara...

Final Answer: It's 22°C and partly cloudy in New York right now.
Trajectory saved to: ./fara_runs/task_1_9f3a2b7c

如果 agent 中途觉得信息不够,它不会瞎猜,而是把控制权交回来:

Fara asks: I asked the user: Which airport should I depart from?
Your response (Enter to abandon): _

一句话直觉

把 Fara 想成一个只有一只眼睛和一双手的实习生。 眼睛 = 浏览器截图,手 = 鼠标键盘。它看不到网页的"源代码",只能像人一样看画面、判断"那个按钮大概在屏幕的哪个位置",然后伸手去点。

本节到此为止,不碰任何代码细节。往下开始拆机器。


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

一张图看懂在线跑任务的链路

怎么读这张图:从上到下是一步(step)之内的数据流向;方框里的 [n] 编号对应下面那张表的同名行。表里多出的「[3] 内层」是图里没单独画的一层 —— 它藏在 [3] 里面。

用户任务(一句话)

v
┌────────────────────────────┐ 截图 + 对话历史 ┌───────────────────────┐
│ [1] 主循环 │ ────────────────> │ [2] Fara-1.5 模型 │
│ Fara15Agent.run() │ <──────────────── │ (OpenAI 兼容端点) │
└────────────┬───────────────┘ 思考 + 一个动作 └───────────────────────┘
│ 动作(坐标在 1000×1000 归一空间)
v
┌────────────────────────────┐ Playwright ┌───────────────────────┐
│ [3] 浏览器环境 │ ────────────────> │ [4] 真实浏览器 │
│ PlaywrightEnvironment │ <──────────────── │ (Chromium) │
└────────────┬───────────────┘ 新的截图 └───────────────────────┘
│ 每一步都落盘
v
┌────────────────────────────┐
│ [5] 轨迹 │ events.jsonl + screenshot_N_{pre,post}.png
│ RunContext / DataPoint │
└────────────────────────────┘

部件一句话职责

编号部件干什么主文件
[1]Fara15Agent观察-思考-行动主循环;拼提示词、解析模型输出、分发动作、判断何时停src/fara/agents/fara/fara15_agent.py
[2]ChatCompletionClient对 OpenAI 兼容端点的极薄封装,把消息里的 PIL 图转成 base64 image_urlsrc/fara/clients/wrapper.py
[3]PlaywrightEnvironment把抽象动作(left_click/type/scroll…)翻译成 Playwright 调用src/fara/environments/playwright/environment.py
[3] 内层PlaywrightController更底层的浏览器操作,带页面崩溃恢复、弹窗捕获、下载处理;图里没单独画,它被 [3] 持有src/fara/environments/playwright/playwright_controller.py
[4]真实浏览器(Chromium)Playwright 驱动的浏览器进程,agent 看到的每一张截图都来自它;本地起 Chromium,评测时可换成 BrowserBase 云端会话外部进程,非本仓库代码
[5]RunContext + DataPointWriter逐事件追加写盘的轨迹记录器src/fara/core/run_context.pysrc/fara/core/data_point_io.py

提示词与动作定义单独放在 src/fara/agents/fara/_prompts.py,这是整个项目最不能改一个字的地方(模型是按那串文本训出来的),第 2 章会讲。

还有第二条链路:离线评测

仓库里有第二个 Python 包 webeval/(webeval/pyproject.toml),负责"跑完之后给这条轨迹打分"。它是一条离线链路,和上面在线跑任务的链路只通过磁盘上的轨迹目录相连:

轨迹目录 ┌────────────┐ ┌──────────────┐ ┌───────────────┐ ┌────────┐
(截图+日志) ────> │ Trajectory │──> │ DataPoint │──> │ MMRubricAgent │──> │ 分数 │
│ 读盘解析 │ │ 统一数据结构 │ │ 九步打分 │ │ 0 或 1 │
└────────────┘ └──────────────┘ └───────────────┘ └────────┘

MMRubricAgent 就是 README 里说的 Universal Verifier(README.md:186),第 5 章专讲。

主线走一遍(高层,不进代码)

  1. 起浏览器。 CLI 建一个 PlaywrightEnvironment,视口固定 1440×900,默认开在 Bing 首页。
  2. 起 agent + 轨迹。 每个任务建一个 RunContext,对应磁盘上一个目录。
  3. 循环开始。 截当前屏 → 缩放 → 和历史对话、系统提示词拼成一次 chat completion 请求。
  4. 模型回一段文本,格式是"思考文字 + <tool_call>{json}</tool_call>"。
  5. 解析出一个动作,坐标由 agent 层从 1000×1000 空间换算成真实像素,再交给环境执行。
  6. 执行完再截一屏,把"动作 + 观察"写进轨迹文件,进入下一步。
  7. 停机有三种:模型调 terminate(给出最终答案)、模型调 ask_user_question(把控制权还给用户)、步数用完。

3. 三个必须先建立的概念

下面三个词在后面每一章都会出现,先各用一段话点破。

归一化坐标空间(1000×1000)

模型被告知"屏幕分辨率是 1000×1000",但真实视口是 1440×900,送进去的图还被缩放成 1440×896。三套尺寸都不一样。

这不是 bug,是设计:模型只在一个固定的抽象平面上报坐标,换算的活由 agent 层干(Fara15Agent._proc,src/fara/agents/fara/fara15_agent.py:495-503),环境层收到的一律是真实像素。好处是换视口尺寸不用重训模型。第 2 章有完整的换算图。

critical point(关键节点)

这是 Fara 安全设计的承重概念,指的是"agent 必须停下来问人"的三类处境:

类型触发条件例子
Case 1 缺信息任务需要用户没给的个人信息表单要手机号,用户只给了姓名邮箱
Case 2 任务欠定义当前这步做不出决定"帮我订张机票",没说去哪
Case 3 不可逆动作马上要做撤不回来的事,且用户没明确授权提交表单、下单、发消息、删数据

这三条是写死在系统提示词里的(src/fara/agents/fara/_prompts.py:45-69),同时也是打分器判断 agent 是否越界的标准(webeval/src/webeval/rubric_agent/critical_point_types.yaml)。

轨迹 / DataPoint

一次任务运行的全部产物:每步的前后截图、模型原始输出、动作参数、执行后的文字观察、最终状态。它有一个 pydantic 定义(src/fara/core/data_point.py)和一种"逐事件追加"的落盘格式(src/fara/core/data_point_io.py)。

它同时是三样东西:调试日志、训练数据、评测输入。第 4 章专讲。


4. 阅读地图

建议按顺序读;每章都能独立打开,但 01 → 02 → 03 是理解运行时的最短路径。

章节讲什么什么时候读
01-agent-loop.md主循环一步都干了什么、验证码闸门与它的三档降级、三种停机、上下文里的截图怎么裁想搞懂"它到底怎么转"
02-prompt-and-action-space.md系统提示词的三段式拼装、18 个动作的 schema、1000×1000 坐标换算想改动作空间或换模型
03-browser-environment.md动作怎么落到 Playwright、弹窗/新标签页怎么接管、页面崩了怎么恢复、BrowserBase 云端会话与验证码信号想接自己的环境或调浏览器层的坑
04-trajectory-and-resume.mdDataPoint 数据结构、事件流怎么分组成 step、问完用户怎么续跑想读轨迹、做数据分析或续跑
05-webeval-universal-verifier.md评测框架骨架 + Universal Verifier 的九步打分想复现基准分或研究 LLM 裁判
06-highlights-and-limits.md可借鉴技术、诚实的边界、横向对比、全局代码地图读完想带走点什么

验证码这件事被拆在两章:闸门本身(agent 每步开头等什么、超时怎么降级)在第 1 章;信号从哪来(BrowserBase 往 console 打的两条消息)在第 3 章。


5. 仓库布局速查

fara/
├── src/fara/ # 运行时(pip 包 `fara`)
│ ├── run_fara.py # CLI 入口,fara-cli
│ ├── agents/fara/ # Fara-1.5 agent + 提示词
│ ├── agents/computer_agent/ # 共用工具(读页面抽取、上下文压缩)
│ ├── environments/ # 环境抽象基类 + Playwright 实现
│ ├── core/ # Agent 基类、RunContext、DataPoint
│ ├── clients/ # OpenAI 兼容客户端封装
│ ├── qwen_helpers/ # 从 Qwen-Agent 搬来的 fn-call 模板与 smart_resize
│ └── fara_7b/ # 上一代 Fara-7B 的独立实现(保留)
├── webeval/ # 评测框架(独立 pip 包 `webeval`)
├── tests/test_fara15.py # 提示词字节级一致性 + 轨迹往返测试
├── docs/eval_reproducibility.md # 复现基准分的说明
└── endpoint_configs/ # 端点配置样例

注意 src/fara/fara_7b/上一代模型的完整旧实现,和 src/fara/agents/fara/ 是两套平行代码,只由 CLI 的 --fara-7b 开关二选一(src/fara/run_fara.py:315-340)。两者的关键差异见第 6 章的横向对比。


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

主题文件路径符号名
CLI 入口与交互循环src/fara/run_fara.pymainrun_fara15_agent
agent 主循环src/fara/agents/fara/fara15_agent.pyFara15Agent.run
agent 配置项src/fara/agents/fara/fara15_agent.pyFara15AgentConfig
系统提示词组装src/fara/agents/fara/_prompts.pyget_computer_use_system_promptbuild_fara_fn_call_template
动作 schemasrc/fara/agents/fara/_prompts.pyFaraBrowserComputerUse
归一坐标常量src/fara/agents/coord_spaces.pyFARA_DISPLAY_SIZE
坐标换算(agent 层)src/fara/agents/fara/fara15_agent.py_procproc_coords
浏览器环境src/fara/environments/playwright/environment.pyPlaywrightEnvironment
浏览器底层控制src/fara/environments/playwright/playwright_controller.pyPlaywrightController
环境抽象接口src/fara/environments/computer.pyComputerEnvironmentBrowserEnvironment
轨迹数据结构src/fara/core/data_point.pyDataPointSolverLogAction
轨迹落盘src/fara/core/data_point_io.pyDataPointWriterDataPointReader
运行上下文src/fara/core/run_context.pyRunContext
评测框架骨架webeval/src/webeval/benchmark.pyBenchmark
Universal Verifierwebeval/src/webeval/rubric_agent/mm_rubric_agent.pyMMRubricAgent
WebTailBench 接入webeval/src/webeval/benchmarks/webtailbench/webtailbench.pyWebTailBenchBenchmark
提示词一致性测试tests/test_fara15.pytest_system_prompt_byte_identical_to_training