实时事件与屏幕
在运行过程中流式获取智能体正在做什么、每个完成的步骤以及浏览器屏幕画面,而不必等待结果。
一次运行需要几秒到几分钟。与其等它结束,不如流式获取:你能看到智能体此刻在做什么、每一步完成的情况、运行状态的每次变化,以及(如果需要)浏览器屏幕画面。事件流以已结束的运行收尾,其中包含 answer、data 和 charge_usd。
事件流使用 Server-Sent Events(text/event-stream),与 LLM API 流式输出的格式相同。观看运行不收费。
流式启动新运行#
创建运行时加上 "stream": true。响应就是事件流,状态码为 200,而不是 202 JSON。再加上 "screen": true 即可同时获取浏览器屏幕画面。
curl -N https://lovelyrouter.com/v1/browser/runs \
-H "Authorization: Bearer $LOVELY_API_KEY" \
-H "content-type: application/json" \
-d '{
"goal": "Find the price of the book \"A Light in the Attic\" and whether it is in stock.",
"start_url": "https://books.toscrape.com/",
"stream": true
}'事件流大致如下(有删节):
event: run
data: {"id":"brun_3f9a1c7e0b2d4e6f8a9b0c1d","object":"browser_run","status":"running","steps":0,"trace":[],"poll":"/v1/browser/runs/brun_3f9a1c7e0b2d4e6f8a9b0c1d?wait=30","events":"/v1/browser/runs/brun_3f9a1c7e0b2d4e6f8a9b0c1d/events","charge_usd":null,"reserved_usd":0.445,…}
event: activity
data: {"step":0,"kind":"starting","text":"Opening https://books.toscrape.com/"}
event: activity
data: {"step":1,"kind":"reading","text":"Reading the page"}
event: activity
data: {"step":1,"kind":"acting","text":"click link \"A Light in the ...\" → https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"}
event: step
data: {"step":1,"url":"https://books.toscrape.com/","action":"click:e54","description":"click link \"A Light in the ...\" → https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html","ms":2140,"error":null}
event: run
data: {"id":"brun_3f9a1c7e0b2d4e6f8a9b0c1d","status":"running","steps":1,…}
event: activity
data: {"step":2,"kind":"answering","text":"Reading the result and writing the answer"}
event: step
data: {"step":2,"url":"https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html","action":"done","description":"Read the result and wrote the answer","ms":2875,"error":null}
event: run
data: {"id":"brun_3f9a1c7e0b2d4e6f8a9b0c1d","status":"succeeded","answer":"\"A Light in the Attic\" costs £51.77 and is in stock (22 available).","charge_usd":0.012,"reserved_usd":0,…}
event: end
data: {"id":"brun_3f9a1c7e0b2d4e6f8a9b0c1d"}第一个 run 事件包含 202 响应中会有的内容:poll、events,以及传入 webhook 时的 webhook_secret。运行开始之前的错误(400、402、429 等)与不使用 stream 时一样,以 JSON 和对应状态码返回。
跟踪已在进行的运行#
随时打开 GET /v1/browser/runs/{id}/events:可以从另一个进程打开、在连接断开后重新打开,也可以用于未使用 stream 启动的运行。加上 ?screen=1 获取屏幕画面。多个事件流可以同时观看同一个运行。
curl -N "https://lovelyrouter.com/v1/browser/runs/brun_3f9a1c7e0b2d4e6f8a9b0c1d/events?screen=1" \
-H "Authorization: Bearer $LOVELY_API_KEY"每个事件流都以一个带完整 trace 的 run 事件开头,因此重新连接不会丢失任何内容。对于已结束的运行,会发送该 run 事件(使用 screen 时还有最后一帧画面),随后是 end。
事件#
| 事件 | 何时发送 | data |
|---|---|---|
run | 最先发送;每一步之后、暂停开始或结束时;以及最后 | 带 charge_usd 和 reserved_usd 的运行对象。第一个包含 trace。 |
activity | 智能体开始做某件事 | step(进行中的步骤)、kind 和 text |
step | 一个步骤完成 | 一条 trace 记录:step、url、action、description、ms、error |
frame | 使用 screen 时:连接时发送,之后页面每次变化时发送 | data(浏览器的 base64 JPEG 画面)和 at(ISO 8601) |
end | 运行已结束;随后关闭事件流 | id |
活动类型:
kind | 智能体正在 |
|---|---|
starting | 启动浏览器或打开 start_url |
reading | 读取页面 |
waiting | 等待尚未加载完的页面,或重新加载页面 |
deciding | 选择下一步操作 |
thinking | 在决定之前仔细查看(精准模式可能会使用截图) |
checking | 提交前检查表单中的值 |
acting | 执行 text 描述的操作:点击、输入、选择、上传 |
answering | 读取结果并撰写答案 |
extracting | 把读到的内容整理为 data |
以 : 开头的行是保活注释,在没有其他事件时每 15 秒发送一次。忽略它们即可。
观看屏幕#
使用 "screen": true(或 ?screen=1)时,frame 事件以 base64 JPEG 携带浏览器屏幕画面,每帧通常 20 到 60 KB。连接时会收到一帧,之后页面每次变化时发送,最多约每秒 4 帧;页面静止时不会发送新帧。如果你的读取速度跟不上,会跳过画面帧,但不会跳过其他事件。
要在网页上显示,把帧放进图片:
img.src = `data:image/jpeg;base64,${frame.data}`;如果只需要一张图片而不需要事件流,GET /v1/browser/runs/{id}/screenshot 会返回当前的屏幕画面。
等待人工的暂停#
运行处于 waiting_for_human 时,事件流保持打开。开始暂停的 run 事件中带有 needs;在任何地方用 POST /v1/browser/runs/{id}/reply 回复后,事件流会继续,并发送状态回到 running 的 run 事件。使用 screen 时,接管期间你还能看到人在浏览器中的操作。参见人工介入。
在 Web 应用中使用事件流#
把 API 密钥留在你的服务器上。浏览器的 EventSource 无法发送 Authorization 请求头,因此请在你的服务器上打开事件流,并以 content-type: text/event-stream 原样转发给你的页面。之后页面就可以对你自己的 URL 使用 EventSource。
重新连接#
事件流可能会中断:网络波动、代理超时,或我们这边的部署。运行会继续进行。重新打开 GET /v1/browser/runs/{id}/events,并用第一个 run 事件替换你的状态。EventSource 会在 3 秒后自动重连。收到 end 或状态为最终状态的 run 后即可停止。