数据截至 (上游 commit 1386791923ca)
Midscene — 架构与原理(视觉驱动的计算机操作)
30 秒导读: Midscene 让你用大白话指挥 AI 操作界面——"点登录按钮""在搜索框输入 coffee"。 它不读 DOM、不认 CSS 选择器,而是截一张图丢给多模态大模型,模型看图回答"下一步做什么、 点哪个坐标",引擎把这个坐标精确映射回真实屏幕像素并落成一次点击,然后再截一张图问模型 下一步——如此循环直到任务完成。同一套机制既能自动化浏览器,也能操作整台电脑桌面(computer-use)。
1. 这是什么(零基础也能懂)
一句话定义: Midscene 是一个视觉驱动的 UI 自动化 / 测试引擎——你用自然语言描述要做的事, 它靠"看屏幕截图"的多模态模型来完成操作,而不是靠页面结构。
解决什么问题 / 给谁用。 传统 UI 自动化(包括读 DOM、读无障碍树的 AI 工具)都依赖页面结构, 而结构既脆弱又不全:
- 选择器一重构就失效,维护成本高;
- 图标按钮、自定义控件、
<canvas>这类没有语义标记的元素,结构层根本"看不见"; - 原生 App、跨域 iframe 够不着;
- 它判断不了"东西到底长得对不对"(颜色、高亮、布局)。
Midscene 换了个思路:只要人眼能看见,它就能操作。因为它只从截图工作,你只需一句句地用自然 语言描述每一步。这段"为什么"来自 README 的 "Why Midscene" 一节 1。
它能做什么(功能):
- 用 JS SDK 或 YAML 写自动化脚本,或交给 AI agent 自主执行;
- 覆盖 Web 浏览器、Android、iOS、HarmonyOS、桌面电脑,以及任意自定义界面——一套 API;
- 核心方法:
aiAct(执行一段自然语言任务)、aiQuery(从页面抽数据)、aiAssert(断言页面状态)等。
用起来什么样。 面向"整台电脑"的最小用法(computer-use):拿到一个操作本机桌面的 agent, 然后用一句话下命令 2。
// 示意,非源码:core 用法一瞥
import { agentForComputer } from '@midscene/computer';
const agent = await agentForComputer(); // 连接到本机主显示器
await agent.aiAct('打开浏览器,搜索 "midscene",点第一个结果');
// 引擎内部:截图 → 问模型 → 点击/输入 → 再截图 → …… 直到完成
一句话直觉 / 类比。 把它想成一个只会看屏幕、只会用鼠标键盘的实习生:你不给它页面源码, 只给它"你现在看到的这张屏幕",它看图、决定动哪、你替它动手,然后你再拍一张新屏幕给它看。 它的全部智能都建立在"看图 → 说下一步"这一个动作上。
本节不涉及底层代码。目标:完全不懂的人读完知道"它是干嘛的、凭什么不看 DOM 也能操作"。
2. 顶层全景(它大概怎么转)
2.1 一张图看懂主循环
Midscene 的心脏是一个**「计划 → 执行 → 再截图 → 重规划」的闭环**。怎么读这张图:从上往下是一轮, 最后一步"没做完就回到顶上再来一轮",做完就退出。
用户一句话任务:"点登录按钮"
│
▼
┌─────────────────────────────┐
│ ① 截图 │ 截当前屏幕 → base64 (Device.screenshotBase64)
└─────────────────────────────┘
│ 截图 + 任务 + 可用动作清单(actionSpace)
▼
┌─────────────────────────────┐
│ ② 计划(问多模态模型) │ 模型看图,输出:下一步动作 + 目标坐标 + 是否完成
└─────────────────────────── ──┘ (genericXmlPlan / 自定义 planFn)
│ 模型给的坐标是"归一化 bbox"(如 0–1000)
▼
┌─────────────────────────────┐
│ ③ 视觉定位:坐标 → 真实像素 │ 按图像尺寸把归一化坐标映射回像素、取中心点
└─────────────────────────────┘ (mapLocateResultToPixelBboxByCoordinates)
│ 得到 {x, y} 像素中心
▼
┌─────────────────────────────┐
│ ④ 执行:落成真实点击/输入 │ {x,y} → 移动鼠标 → 按下 (InputPrimitives.pointer.tap)
└─────────────────────────────┘
│
▼
模型说"完成了"吗?
├── 否 → 回到 ①,再截一张图,带着历史重新规划(replan)
└── 是 → 结束,返回结果
这套循环的代码主线分三层:Agent.aiAct 3 收下任务 → 调 TaskExecutor.action(公开入口,
内部委托私有 runAction)4,runAction 里是一个 while (true) 的统一 plan/replan 循环 5;
每轮先问模型拿计划,再把计划"翻译成可执行任务"交给 TaskRunner 跑掉,若模型没说"完成"
(shouldContinuePlanning)就再转一圈 6。
2.2 部件一句话职责
| 部件 | 干什么 | 在哪(包/文件) |
|---|---|---|
Agent (PageAgent) | 门面层:对外提供 aiAct/aiQuery/aiAssert,管模型运行时与缓存 | packages/core/src/agent/agent.ts |
TaskExecutor | 编排层:runAction 主循环——plan → convert → 执行 → 判断是否 replan | packages/core/src/agent/tasks.ts |
TaskRunner | 执行层:带状态机的任务队列,逐个跑 Planning/Action/Insight 任务并管截图与快照 | packages/core/src/task-runner.ts |
规划器 (genericXmlPlan / planFn) | 把"截图+任务+动作空间"喂给模型,解析出下一步动作 | packages/core/src/ai-model/llm-planning.ts |
| 模型适配层 (model-adapter / models) | 每个模型家族一套:如何组 prompt、如何解析坐标、坐标怎么归一化 | packages/core/src/ai-model/models/* |
| 视觉定位映射 | 归一化 bbox → 真实像素 bbox → 中心点 | packages/core/src/ai-model/shared/model-locate-result/* |
AbstractInterface + InputPrimitives | 设备抽象:声明有哪些原子动作(tap/type/scroll…) | packages/core/src/device/index.ts |
ComputerDevice / RDPDevice | 具体设备:把 {x,y} 落成真实鼠标键盘(本地桌面 / 远程 RDP) | packages/computer/src/* |
agent-tools(computer_*) | 把 agent 暴露成 MCP / CLI 工具 | packages/computer/src/agent-tools.ts |