统一命令接口与人机协作
本章介绍当前 AI Browser 工程的开发方式,替代前文 Controller 多端点示例中的接口约定。前文仍可用于理解 DOM 构建和浏览器操作原理,新接入不要继续使用 /api/v1/playwright/<方法名>。
项目源码:deepseek-browser-use。完整命令说明位于项目根目录 SKILL.md;该文件也是智能体的操作指南。Skill 提供使用规则,后端服务仍需单独启动。
一、启动与实例生命周期
当前工程使用 Java 21 或以上环境。在 playwright-server 目录运行 mvn spring-boot:run,或使用平台对应的发行包运行 java -jar <发行包文件名>.jar。通过 GET http://localhost:10049/playwright/health 检查服务状态。
开发时重点区分以下两层生命周期:
- Playwright driver 由服务共享;每个任务使用独立的持久化浏览器上下文和 profile。
- profile 位于
~/.config/browseruse/profiles/<id>,同一 ID 可以复用保存的登录态,但网站仍可能使登录失效。 start不传 ID 时由nexus.io.tio.utils.snowflake.SnowflakeIdUtils.id()生成;已有运行实例不能重复start,需要先close。- 同一实例的操作按顺序执行,不能并发使用同一个 ID。关闭某个实例不应关闭共享 driver。
- 需要人工登录或验证时使用
headless=false。等待人工协助时保留浏览器,任务完成后再关闭。
启动参数由 PlaywrightService.chromiumArgs() 和 chromiumSandbox() 管理。Windows/macOS 开启 Chromium 沙箱;Linux 当前为兼容常见容器运行环境关闭沙箱,并添加 --disable-dev-shm-usage。
不要添加 --disable-blink-features=AutomationControlled:该参数会触发浏览器顶部的“不受支持的命令行标志”提示。也不要通过 --disable-infobars 隐藏问题。修改参数后需要重启更新后的后端并重新创建浏览器实例,旧窗口不会自动应用新参数。
读取环境配置使用 EnvUtils.get。生成任务或请求的雪花 ID 使用 SnowflakeIdUtils.id(),对外以字符串传递,避免 JavaScript 数字精度损失。
二、统一请求与响应
所有浏览器操作使用 POST http://localhost:10049/playwright/command,请求头为 Content-Type: application/json。方法参数放在 params,不再使用 query/form 绑定。
{"method":"start","params":{"headless":false}}
取出返回的 data.id 后,后续请求沿用该 ID:
{"id":"1001","method":"go_to_url","params":{"url":"https://example.com"}}
{"id":"1001","method":"get_browser_state","params":{}}
这里的 1001 仅为占位示例,实际调用应替换为启动响应中的 ID。
统一响应外层保持兼容,包括成功时的空字段:
{"data":{},"code":1,"ok":true,"error":null,"msg":null}
失败时 code=0、ok=false,通过 msg 读取原因。缺少参数、未知方法等受控错误通过响应体表达,不能再按旧示例假定缺参数一定返回 HTTP 500。动作错误在可分类时提供 data.errorCode:
| 错误码 | 处理方式 |
|---|---|
ELEMENT_READ_ONLY | 使用日期选择器等页面控件,不要反复填只读输入框 |
ELEMENT_DISABLED | 检查前置条件,等待控件启用 |
ELEMENT_OBSCURED | 检查遮挡层或弹窗 |
ELEMENT_HIDDEN | 重新定位当前可见控件 |
ELEMENT_NOT_EDITABLE | 检查元素类型和编辑状态 |
STALE_ELEMENT | 重新获取页面状态和元素索引 |
ACTION_TIMEOUT / ACTION_FAILED | 结合完整错误原因和页面状态判断,不统一归因于页面已变化 |
可选精简响应
在请求顶层添加 responseMode:"compact",可以减少协议中的重复字段:
{"id":"1001","method":"get_browser_state","params":{},"responseMode":"compact"}
默认仍返回完整响应。精简模式保留 ok/code/error/msg,包括空值;去掉本地截图路径、重复的页面描述,以及默认不需要的点击诊断字段。顶层 diagnostics:true 可保留点击诊断字段,但不会恢复其他被精简的字段。转换只处理协议拥有的字段,不递归删改网站响应体或脚本执行结果。
三、页面状态和动作回执
阅读页面优先使用 get_browser_state 的 data.text 与 data.tabs。索引来自最近一次快照,导航或动态更新后应重新获取,不能沿用旧索引。
表单状态必须读取 DOM property,而不能只使用初始 HTML attribute:
- 输出实时
value、checked、selected、selected-text和readonly/disabled/editable。 - 密码输入框的值输出为
[redacted]。 - 可见的只读或禁用表单控件即使没有交互索引,也应展示状态;没有索引不代表元素不存在。
- 不再把所有属性统一截断为 15 个字符。标识和表单值完整保留,展示类长属性限制长度并转义。
- 压缩无语义容器的缩进,最多保留 6 层语义缩进;缩进不再对应原始 DOM 的每一层。
点击回执的 changed 根据 URL、标签页、正文、表单状态及 DOM 结构指纹观察变化,可识别等长文本替换等情况。changeStatus 为 observed 或 not_observed;observationComplete 表示探测是否完整,observationWindowMs 为 500。浏览器调用自身耗时不包含在这个观察窗口内。
ok=true 只表示动作执行未报错,changed=true 也不证明业务已成功。changed=false 仅表示短观察窗口内没有发现变化,不能据此重复提交;应读取结果文本、等待目标状态或检查相关响应。
截图在执行层统一附加:单条和批量动作共用相同路径;成功的动作类命令截图一次,失败动作不附加成功截图。get_browser_state 自己保存截图和结构化文本,避免重复截图。默认读取文本,只有文本无法表达必要信息时才查看图片。
四、批量执行和网络关联
批量仍使用同一个端点,method=commands:
{
"id":"1001",
"method":"commands",
"params":{
"stopOnError":true,
"commands":[
{"go_to_url":{"url":"https://example.com"}},
{"get_browser_state":{}}
]
}
}
每项只有一个命令键,最多 200 项,不允许嵌套 commands。stopOnError 默认 true;只有后续步骤不依赖前一步成功时才考虑设为 false。结果保留在 data.results,同时返回成功、失败和停止计数信息。批量不会因为页面变化自动中断,依赖新索引的动作应放到下一批,在重新读状态后决定。
get_requests 返回请求元数据,包括字符串 requestId、requestedAt、URL、方法及状态;有请求体时附加 postData、postDataLength、postDataTruncated。响应到达后补充 respondedAt,失败请求记录 failure、finishedAt。
读取响应体使用 get_response_body 或 wait_for_response,响应包含请求关联信息,以及 bodyAvailable、bodyLength、truncated 等字段。同一个 URL 被重复请求时,用 get_response_body 的 params.requestId 精确匹配,避免将旧请求的响应当作本次结果。请求体和响应体都可能截断,调用方必须检查标记。
五、人类协助必须进入任务流程
遇到无法自行处理的登录、验证码、短信码、扫码、滑块验证、点击验证、设备确认等环节,智能体必须主动请求人类帮助。不得反复猜测、提交验证答案,也不能把等待人工处理视为任务完成。
- 明确告知当前网站、受阻原因和需要用户做什么。例如:“请在已打开的浏览器中完成登录和滑块验证,完成后告诉我,我会继续查询。”
- 使用有头浏览器,通过
bring_to_front或request_human_input展示当前页签。保留 ID、页面和浏览器,不在等待期间刷新、关闭或继续操作验证控件。 - 宿主必须实际展示求助信息。
request_human_input只是创建请求记录,接口成功不等于用户已收到通知。 - 用户可直接在浏览器操作,或通过
submit_human_input提交答案。登录优先由用户在浏览器中完成,不要求用户在对话里提供密码。 - 用户处理后重新调用
get_browser_state,确认已登录或验证通过、取得新索引,再继续原任务。请求过期、用户答复或一般页面变化本身都不能替代业务状态确认。
{
"id":"1001",
"method":"request_human_input",
"params":{"prompt":"请在浏览器中完成登录和滑块验证,完成后告诉我。","timeoutSeconds":300}
}
返回的 data.requestId 用于 get_human_input 或 submit_human_input。需要展示验证图片时可传元素 index 或 selector,取得 imageBase64。直接在浏览器操作不会自动更新请求记录,状态可能仍是 pending;确认页面已通过验证后即可继续,不必死等该字段变为 answered。
无头实例需要切换到有头模式时,先说明并记录当前 URL,再按同一 ID 关闭、重新启动并打开页面;这可能丢失未提交表单和当前验证进度。需要人工协助的流程应尽早选择有头模式。
六、开发验证要点
回归验证应覆盖:实时表单值与密码脱敏、只读错误分类、等长内容变化、异步弹出标签页、无变化点击、单条和批量截图一致性、重复 URL 的请求关联,以及精简模式不删改业务数据。相关测试位于 BrowserResponseIntegrationTest、ResponseFormatterTest、ActionErrorTest 和 PlaywrightServiceTest。
查询任务还要区分“没有记录”和“金额为零”。先确认主体、时间口径、筛选条件与查询是否成功;不能仅凭空表格推断税额或其他业务金额为零。
