Lovely Router文档
获取密钥
浏览器智能体

实时事件与屏幕

在运行过程中流式获取智能体正在做什么、每个完成的步骤以及浏览器屏幕画面,而不必等待结果。

一次运行需要几秒到几分钟。与其等它结束,不如流式获取:你能看到智能体此刻在做什么、每一步完成的情况、运行状态的每次变化,以及(如果需要)浏览器屏幕画面。事件流以已结束的运行收尾,其中包含 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
  }'

事件流大致如下(有删节):

SSE
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
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 帧;页面静止时不会发送新帧。如果你的读取速度跟不上,会跳过画面帧,但不会跳过其他事件。

要在网页上显示,把帧放进图片:

JavaScript
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 后即可停止。