3.2 三原语编程:act、extract、observe 的 API 实战


3.2 三原语编程:act、extract、observe 的 API 实战

本节摘要:本节把 Stagehand 的三个核心 API 讲到能上手写代码的程度。初始化:Stagehand 实例的 env 选项区分本地浏览器与 Browserbase 云端会话,init 之后拿到的 page 兼容 Playwright 接口——这是「三原语是语义层」在 API 面的直接体现。act:指令书写三原则展开成好指令对照表,一次 act 一个意图单元。extract:指令加 zod 数据模式,模式设计的三条纪律(宁窄勿宽、字段自描述、嵌套分层)。observe:候选清单的两种消费法(人工检视与选中后执行)。错误处理:超时、候选为空、指令歧义三类失败的捕获与带退避的重试封装,以及「失败后降级到 observe」的排查路径。本节所有代码为 ES2020 模块形态(可直接做语法检查),类型以 JSDoc 标注;API 细节以官方文档为准。

学习目标

  • 完成 Stagehand 初始化,说出 env 选项与 page 的来历。
  • 按三原则写出高质量 act 指令,识别坏指令的病灶。
  • 用 zod 为 extract 设计收窄的数据模式。
  • 用两种方式消费 observe 结果,并实现带退避的重试封装。

一、环境与初始化

第 0.2 节已装好依赖(@browserbasehq/stagehand@3.7.3 与 zod)。初始化的最小形态:

// stagehand_init.mjs —— Stagehand 初始化骨架(写法示意,以官方文档为准) import { Stagehand } from "@browserbasehq/stagehand"; const stagehand = new Stagehand({ env: "LOCAL", modelName: "gpt-4o", }); await stagehand.init(); const page = stagehand.page; // 兼容 Playwright 的页面对象 await page.goto("http://127.0.0.1:8080/demo_todo.html"); console.log("标题:", await page.title()); await stagehand.close(); ​

四个要点:env 取 LOCAL 用本机浏览器、取 BROWSERBASE 走云端会话(账号与计费在 Browserbase,基建细节第 7 章);模型通过 modelName 与模型客户端选项指定,密钥走环境变量(第 0.2 节纪律);stagehand.page 兼容 Playwright page 接口——goto、title 这些普通调用照常可用,三原语是加在它上面的语义层,不剥夺原生能力(第 1.3 节结论的 API 证据);用完 close,无头模式下忘了关会残留浏览器进程。

二、act:指令书写三原则

第 1.1 节给过三原则(说目标不说路径、引用可见文本、一次一个意图),这里落成对照表:

坏指令 病灶 好指令
点击页面右下角的蓝色按钮 位置加外观,改版即失效 点击「添加」按钮
提交表单 无语义锚,候选过多 点击「登录」按钮
填写用户名、密码然后登录再关掉弹窗 一句塞三个分支意图 拆成三次 act,各自单一意图
在框里输入牛奶 「框」歧义 在「新待办」输入框填入 买牛奶

「一次一个意图单元」需要澄清:像「在输入框填入买牛奶,然后点击添加」这样的紧耦合序列算一个意图单元(填与点共同完成「添加待办」),可以合并;但「登录然后处理弹窗再跳转设置页」是三个意图单元,必须拆开——判据是任何一步都可能失败并需要单独重试。指令里引用的文本必须是页面上真实可见的(「添加」而不是你代码里的变量名),因为模型是按用户视角找元素的(第 1.2 节)。

// act_basic.mjs —— act 的基本形态(写法示意,以官方文档为准) import { Stagehand } from "@browserbasehq/stagehand"; const stagehand = new Stagehand({ env: "LOCAL", modelName: "gpt-4o" }); await stagehand.init(); const page = stagehand.page; await page.goto("http://127.0.0.1:8080/demo_todo.html"); await page.act("在「新待办」输入框填入 买牛奶,然后点击「添加」按钮"); await stagehand.close(); ​

三、extract:指令加 zod 模式

extract 的调用形态是「instruction + schema」,schema 用 zod 表达(序列化后随指令发给模型,约束输出结构):

// extract_basic.mjs —— extract 的数据模式设计(写法示意,以官方文档为准) import { Stagehand } from "@browserbasehq/stagehand"; import { z } from "zod"; const stagehand = new Stagehand({ env: "LOCAL", modelName: "gpt-4o" }); await stagehand.init(); const page = stagehand.page; await page.goto("http://127.0.0.1:8080/demo_todo.html"); const data = await page.extract({ instruction: "提取待办列表的全部条目,以及每条是否包含日期信息", schema: z.object({ items: z.array( z.object({ text: z.string().describe("条目原文"), hasDate: z.boolean().describe("是否包含日期"), }) ), }), }); console.log(JSON.stringify(data, null, 2)); await stagehand.close(); ​

模式设计三纪律,与第 1.1 节一脉相承并加一条:宁窄勿宽——类型写死,别用任意类型兜底;字段自描述——describe 注释写清字段含义,它会被模型读到,等于给抽取加了说明书;嵌套分层——复杂数据拆成对象数组,别让模型往一个字符串里塞结构化内容再让你解析。指令侧同样要具体:「提取待办列表的全部条目」优于「提取页面内容」——extract 的指令是裁剪刀,范围说得越准,返回越干净。

四、observe:候选清单的两种消费

observe 返回候选动作数组(第 1.3 节见过产物形态)。两种消费法:

// observe_use.mjs —— observe 的两种消费(写法示意,以官方文档为准) import { Stagehand } from "@browserbasehq/stagehand"; const stagehand = new Stagehand({ env: "LOCAL", modelName: "gpt-4o" }); await stagehand.init(); const page = stagehand.page; await page.goto("http://127.0.0.1:8080/demo_todo.html"); // 消费法一:人工检视(干跑排障、上线前确认命中面) const candidates = await page.observe("添加待办相关的操作"); for (const item of candidates) { console.log("-", JSON.stringify(item)); } // 消费法二:选中后执行(把裁决权拿回代码里) if (candidates.length > 0) { await page.act(candidates[0]); } await stagehand.close(); ​

消费法二值得强调:observe 把「候选」从模型手里拿到代码手里,代码挑中第几个再交给 act 执行——裁决逻辑(拿第一个、按条件过滤、随机挑)完全由你写。这是 3.3 节缓存机制的直接前提:候选里的定位信息可以被代码持有与复用,而不再每步问模型。

五、错误处理:三类失败与重试封装

act 与 extract 会失败,失败分三类,处理各不同:

失败类 现象 处理
超时 页面未就绪或模型往返过慢 捕获后按退避重试
候选为空 指令无命中(页面变了或指令写偏) 降级到 observe 检视,修正指令
指令歧义 命中多个目标,执行结果不稳 收紧指令(加可见文本锚),或 observe 后代码裁决

通用的带退避重试封装(本书 TS 代码块 JSDoc 形态的完整示例):

// act_with_retry.mjs —— 带退避的重试与降级(写法示意,以官方文档为准) import { Stagehand } from "@browserbasehq/stagehand"; /** @param {import("@browserbasehq/stagehand").Page} page 页面对象 @returns {Promise} 成功 resolve 为 true,失败为 false */ async function actWithRetry(page, instruction, attempts) { for (let i = 0; i < attempts; i++) { try { await page.act(instruction); return true; } catch (err) { console.warn(`第 ${i + 1} 次尝试失败: ${err.message}`); if (i === attempts - 1) { return false; } await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1))); } } return false; } const stagehand = new Stagehand({ env: "LOCAL", modelName: "gpt-4o" }); await stagehand.init(); const page = stagehand.page; await page.goto("http://127.0.0.1:8080/demo_todo.html"); const ok = await actWithRetry(page, "点击「添加」按钮", 3); if (!ok) { // 降级路径:observe 检视现场,把候选打出来人工研判 const candidates = await page.observe("页面上的可点击元素"); console.log("降级检视,候选数:", candidates.length); for (const item of candidates) { console.log("-", JSON.stringify(item)); } } await stagehand.close(); ​

两个封装纪律:重试要有上限与退避(线性或指数退避,别裸 for 三连击——连续失败大概率不是瞬时抖动);重试耗尽要降级而不是抛完就跑——降级到 observe 是最便宜的现场保全,日志里留着它,第 6 章的验证策略会在此基础上判断「该重试、该换路径还是该上报人工」。

本节要点回顾

  • 初始化四要点:env 分本地与云端、模型密钥走环境变量、page 兼容 Playwright、用完 close。
  • act 指令三原则的判据:任何一步都可能失败并需要单独重试的,拆成独立 act。
  • extract 模式三纪律:宁窄勿宽、字段自描述(describe 是给模型的说明书)、嵌套分层。
  • observe 的消费法二把裁决权拿回代码,是缓存机制的直接前提。
  • 错误三分类(超时、候选空、歧义):上限加退避重试,耗尽降级到 observe 保全现场。

武器齐了,但按判定树,act 的每一步都在花模型钱。下一节讲怎么把钱省下来:从 act 起步,经 observe 检视与缓存,逐步把稳定步骤固化成纯代码——以及哪些步骤永远不该固化。

作者与出处
原作者:灏天文库
整理人: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U