跳转到内容

Chats

面向接入 OpenBot 客户端或扩展服务端的开发者。示例需要本页说明的访问权限。从开发者手册开始。

Chat(聊天)是和一个 Bot 的对话。你不用创建、选择或关闭 Chat。你给一个 Bot 发消息,OpenBot 就为你和这个 Bot 保留一个 Chat。Bot 总是马上回答。长的工作会交给其他 Chat 并行运行,结果之后会回到你的 Chat。本页介绍如何和 Bot 说话,以及如何读取 Chat。每个路由都接受用户令牌或 session cookie。见 身份验证。

术语 含义
Turn Bot 的一次运行:它读取新的输入,执行模型步骤和工具调用,然后给出回答。
Item Chat 历史中的一条记录,例如一条消息、一个模型回答、一个工具调用或一个工具结果。
收件箱 等待 Turn 的输入。见 收件箱。
通知 来自 OpenBot 而不是来自人的输入,例如 Bot 交出去的工作的结果。见 通知。
Revision chat.revision。一个数字,Chat 每次变化它都会增加。

每个 Chat 都由 OpenBot 自己创建。kind 说明这个 Chat 为什么存在:

种类 OpenBot 什么时候创建它 归谁所有
direct 一个人第一次给某个 Bot 发消息时。 这个人。
channel Channel 的某个聊天中的第一条消息。 Channel 的 actor。
task 每次一个 Bot 把工作交给一个 Bot,或者一个触发器触发时。见 委派。 交出这项工作的 Chat 的所有者。
review 一个 Bot 必须决定一个 request 时。 这个请求为之服务的人。
live 每次一个人调用 POST /v1/bots/:botId/live 时。见 Live 模式。 这个人。

每个人和每个 Bot 只有一个 direct Chat。Chat 没有终点。只要人们还在和 Bot 说话,它就一直继续,除非你删除它的历史。review Chat 在做出决定后关闭。


用 POST /v1/bots/:botId/messages 给一个 Bot 发消息:

Terminal window
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"input":"Plan three days in Rome.","commandKey":"trip-1"}' \
https://api.example.com/v1/bots/$BOT/messages

消息会进入你和这个 Bot 的 direct Chat。第一条消息会创建这个 Chat。你需要在这个 Bot 上拥有 delegate。要知道先和哪个 Bot 说话,请从 GET /v1/me/settings 读取 defaultBot。见 你的默认 Bot。

字段 含义
input 必填。文本,或者一个用户消息列表(见下文)。
commandKey 必填。此命令的键。重试时保留相同的键和内容。
queue 可选。true 等待新的 Turn,不向当前 Turn 追加引导。

没有 expected revision。消息总是会被接收。

input 接受文本。下面三种形式是一样的:

"Plan three days in Rome."
[{ "role": "user", "content": "Plan three days in Rome." }]
[
{
"type": "message",
"role": "user",
"content": [{ "type": "input_text", "text": "Plan three days in Rome." }]
}
]

未知字段会返回 400。

消息里也可以有你上传的文件,用 input_image 和 input_file 部分。见把文件交给 Bot。

POST /v1/chats/:chatId/messages 接受同样的请求体。它用于 direct Chat、live Chat,或者当你想给一个 Bot 的工作补充内容时,用于 task Chat。在 task Chat 中,Bot 把你的消息当作你发来的消息来读。在 live Chat 中,消息会等到有 Live 会话打开。其他种类的 Chat 会返回 409 和 ChatConflict。

OpenBot 保存命令后返回 202:

{
"executionId": "<executionId>",
"commandKey": "trip-1",
"sequence": 2,
"chat": "chat_7d21…",
"turn": "t-19aa"
}

回执包含已保存的执行和 Turn,即使输入仍需等待。相同的命令键和内容返回相同回执。相同键配上不同内容会返回 409 和 ExecutionKeyConflict。

用 GET /v1/executions/:executionId 读取状态。用 GET /v1/chats/:chatId/turns/:turnId 读取保存的回答。发送消息不会等待回答。SDK 的 Chats.message 和 Chats.botMessage 只准备一次命令,网络重试复用其键。

用回执打开执行事件流。Server-Sent Events(SSE)是通过 HTTP 发送事件的格式:

Terminal window
curl -N -H "Authorization: Bearer $OPENBOT_TOKEN" \
"https://api.example.com/v1/executions/$EXECUTION_ID/observe?afterSequence=0"

每个 execution 事件都有保存的 sequence 和 body。保存序号,在重连时用 afterSequence 传回。Text 包含步骤和 attempt,让重新开始的模型调用与原有输出分开。State 记录状态,Result 指向保存的业务结果。

事件先保存,再发送。慢读者从事件日志补读。断线后也能重读保存的终态事件。关闭观察不会取消工作。取消须使用执行取消命令。 GET /v1/executions/:executionId/events 分页返回同一日志。

状态码 错误 何时出现
400 ChatModelInvalid 这个 Chat 没有模型。Bot 需要一个 model。见 Models。
400 FileInvalid 文件部分指向的不是你自己的、已就绪的文件,或者不是图片。
403 Forbidden 你不能把工作交给这个 Bot。
404 BotNotFound 没有你可以使用的、这个 ID 的 Bot。
404 ChatNotFound 你没有这个 ID 的 Chat。
409 ChatConflict 这种 Chat 不接受人发来的消息。
503 ChatExecutionUnavailable OpenBot 没有可以运行这个 Turn 的模型。
503 ChatStorageUnavailable OpenBot 无法连接到它的数据库。

每个 Chat 都有一个收件箱(inbox)。每条输入都先进入收件箱:可以是人发来的消息,也可以是 OpenBot 发来的通知。收件箱按下面的规则把每条输入交给一个 Turn:

  1. Chat 空闲。 输入马上启动一个新 Turn。收件箱中同一个 author 的所有等待输入都进入这个 Turn。
  2. 同一个 author 的 Turn 正在运行。 输入会引导(steer)这个 Turn:Bot 在下一个模型步骤读取它。
  3. 另一个 author 的 Turn 正在运行。 输入会等待。正在运行的 Turn 结束后,它启动自己的 Turn。这种情况出现在 Channel 的群聊中,那里有很多人在说话。
  4. 一个 Turn 结束。 收件箱中的下一条输入启动下一个 Turn。每种结束之后都会这样:Completed、Failed 和 Interrupted。

author 是这条输入为之服务的人。对于消息,它是发送消息的人,加上这个人登录的方式。所以同一个人用另一个用户令牌发来的消息,算作另一个 author。通知的 author 是它所说的那项工作的 author。

如果一个 Turn 在读到 steer 消息之前就结束了,它会把这条消息放回收件箱,所以输入不会丢失。Chat 的视图在 inbox 中显示等待的输入数量。


Turn 从不等待。任何需要外部时间的事情,之后都会作为通知(notice)回来:这是一条来自 OpenBot、而不是来自人的新输入。

  • 一个 Bot 把工作交给一个 Bot。工具调用马上返回。工作结束时,结果作为通知回来。见 委派。
  • 一个工具调用需要决定。这个工具调用马上得到一个待定的结果。回答作为通知回来。调用被允许时,它就在那时运行。见 Requests。
  • 一个 Bot 请求一个提醒。到了那个时间,备注作为通知回来。

通知是带有 origin.notice 的输入 Item。它的 kind 说明它是关于什么的,ref 指出那件东西,例如一个委派的 ID:

kind 它带来什么
delegation 这个 Chat 交出去的工作的结果。
task task Chat 的任务。
request 对一个请求的回答。被允许的调用会和它一起运行。
review review Chat 要决定的请求。
plan 对一个计划的回答。见 计划模式。
reminder 一个提醒的备注。
code 一个等待过的 Code Mode 程序结束了。
app 来自 App 视图的一条消息。
todos 关于待办列表中未完成项的提醒。

通知的文字以 [OpenBot] 开头。Bot 知道这样的文字来自 OpenBot,不是来自人,它把它当作数据来读,而不是当作指令。记忆 Bot 永远不会把通知当作人说的话。

通知和消息一样经过收件箱。所以你和 Bot 说话时,一个结果可能同时到达。它会引导你的 Turn,或者等待下一个 Turn。


未完成的工作(open work)是一个 Chat 还没有结束的工作:它交给 Bots 的工作、它等待决定的请求,以及它的提醒。之后每一项都会作为通知回来。

Chat 视图中的 open 会统计它们:

{ "delegations": 2, "requests": 0, "reminders": 1 }

每个 Turn 开始时,Bot 会看到一个未完成工作的列表:每个委派的 ID、Bot 和第一行,每个请求,以及每个提醒和它的时间。所以它知道什么会回来。

未完成的工作也决定 task Chat 的工作什么时候结束。见 委派。


每个工具调用都有时间预算。预算内结束时,模型在同一个 Turn 中得到结果。否则它保留自己的执行并在后台继续:

{ "executionId": "0f3c…", "status": "running", "background": true }

等待审批也可以让 Turn 继续。工具结束后,已保存的 tool 类通知把结果送回Chat。重启会恢复已保存的工作。外部结果未知时等待决策,不会当作调用完成。

工具 作用
openbot_execution_status 读取工具执行及其保存结果。
openbot_execution_wait 等待指定执行中的一个结束,最多 30 秒。
openbot_execution_cancel 请求取消。cancelling 表示仍在释放资源。

改变自己 Turn 的工作工具留在前台。App 调用、Code Mode 和委派工作有持久执行。Computer 命令保留自己的会话。组织设置时间预算、最长运行时间和活动工具数。见组织设置。

路由 作用
GET /v1/chats/:chatId/tool-calls 按时间顺序列出有独立执行的工具。
GET /v1/chats/:chatId/tool-calls/:id 读取工具记录和保存的输出。
GET /v1/executions/:executionId/observe 从序号读取已保存的事件。
POST /v1/executions/:executionId/cancel 发送带 commandKey 的取消命令。

工具记录有自己的 id、executionId 和 position 中准确的 Chat、Turn、item 和步骤。它们不同于模型的 callId,后者可能在另一步骤中重复使用。保存的输出不能替代执行状态或安全错误。App 调用还保留独立的 App 资源 ID。


GET /v1/chats 列出你的 direct Chat、channel Chat 和 live Chat,最早的排在前面。加上 bot=<id> 可以列出某个 Bot 的 Chat。加上 kind=<kind> 可以只列出一种,例如 task 或 review。每个摘要包含 chat、status、turn、一个简短的 response 和 executionId。

GET /v1/chats/:chatId 返回一个 Chat 的视图。读取永远不会启动工作。下面是它的简短形式:

{
"chat": {
"id": "chat_7d21…",
"revision": 4,
"kind": "direct",
"bot": "bot_3f9a…",
"parent": null,
"settings": { "mode": "build" },
"createdAt": 1792486800000
},
"status": "Running",
"turn": { "id": "t-19aa", "status": { "_tag": "Running" } },
"response": { "id": "t-19aa", "status": "in_progress" },
"executionId": "<executionId>",
"inbox": 0,
"open": { "delegations": 1, "requests": 0, "reminders": 0 }
}
字段 含义
chat 这个 Chat:它的 kind、bot 和 settings。parent 指出把工作交给 task Chat 的 Chat 和委派。channel Chat 有一个 title:这个聊天在平台上的名字。
status 第一个 Turn 之前是 Idle。之后是最后一个 Turn 的状态:Running、Completed、Failed 或 Interrupted。
turn 当前的 Turn,或者最后一个 Turn。
response 这个 Turn 的回答。
executionId 这个 Turn 已保存的执行 ID。没有关联执行时为 null。使用执行 API 读取状态和事件。
inbox 等待 Turn 的输入数量。
open 未完成的工作。

视图不包含历史。用这些路由读取历史:

路由 返回什么
GET /v1/chats/:chatId/items 这个 Chat 的 Items,最新的排在前面。使用 limit(1 到 100,默认 50)。把 next 作为 before 发送,可以读取更早的 Items。这是读取长 Chat 的主要方式。
GET /v1/chats/:chatId/turns Turn 记录,最早的排在前面。使用 limit 和 after(一个 Turn ID)。回答是 { "items": [...], "next": "<turnId>" | null }。
GET /v1/chats/:chatId/turns/:turnId { "turn": {...}, "response": {...} }。
GET /v1/chats/:chatId/turns/:turnId/items 一个 Turn 的 Items,使用 after(一个 Item ID)和 limit。

Turn 记录包含 status、settings、steps、usage、input(它的输入 Items 的 ID)、items、startedAt 和 completedAt。author 是这个 Turn 为之工作的人。在 Channel 中,它是写下这条消息的人。每个 Item 包含 id、seq(它在历史中的位置)、content 和 origin(chat、turn、step,通知还有 notice)。

要跟踪工作,请使用已保存的 executionId。事件带有序号。断线后把最后一个序号作为 afterSequence 发送。关闭流只停止观察。取消工作需要提交明确的执行命令。旧的 Chat 等待路由和本地 activity 字段已移除。

Terminal window
curl -N -H "Authorization: Bearer $OPENBOT_TOKEN" \
"https://api.example.com/v1/executions/$EXECUTION/observe?afterSequence=4"

读取 Chat 不会打开成员 runtime。列表在数据库查询中应用当前读取权限。只能读取部分 Chat 的 token 只会得到这些 Chat。有效 token 没有匹配的 Chat 时得到空列表。

还有一些路由读取一个 Chat 的工作:

路由 列出什么
GET /v1/chats/:chatId/delegations 这个 Chat 交给 Bots 的工作。见 委派。
GET /v1/chats/:chatId/confessions Bot 用 confide 说的话。见 展示告解。
GET /v1/chats/:chatId/enrichments 记忆 Bot 如何重写每个 Turn 的输入。见 记忆。
GET /v1/chats/:chatId/code-runs 这个 Chat 的 Code Mode 程序。
GET /v1/chats/:chatId/app-operations 这个 Chat 的 App 调用。

POST /v1/chats/:chatId/interrupt 停止正在运行的 Turn:

Terminal window
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"commandKey":"stop-1"}' \
https://api.example.com/v1/chats/$CHAT/interrupt

API 返回 202 和执行回执。通过 executionId 观察完成状态。Turn 释放资源期间,停止命令仍未完成。之后 Turn 变为 Interrupted,响应变为 cancelled。没有运行中的 Turn 时,Chat 状态不变。重试使用同一个命令键。

中断只停止 Turn。这个 Chat 交给 Bots 的工作会继续,结果仍然会回来。它的请求和提醒也仍然未完成。要全部停止,请发送 { "commandKey": "stop-all-1", "delegations": true }。这时 OpenBot 还会取消这个 Chat 交出去的工作、这些工作又交出去的工作,以及这个 Chat 的请求和提醒。

中断之后,收件箱中的下一条输入会启动下一个 Turn。


每个 Chat 都有一个待办列表。当工作不止一步时,Bot 用工具 openbot_todos 来维护它。每一项都有 id、content 和 status。status 是 pending、in_progress 或 done。同一时间只能有一项是 in_progress。

只要还有一项不是 done,Bot 就会继续工作。如果 Bot 想在还有未完成项时结束 Turn,OpenBot 会给它一条通知,列出未完成的项,Bot 就会再做一步。对列表的每一种状态,OpenBot 只这样做一次。当 Chat 有未完成的工作时,它不会这样做,因为那些工作会作为通知回来。如果在 Turn 结束时每一项都是 done,列表就会变空,所以旧的列表不会一直留在一个没有终点的 Chat 中。

用 GET /v1/chats/:chatId/todos 读取列表:

{
"items": [
{ "id": "read", "content": "Read the open issues", "status": "done" },
{ "id": "fix", "content": "Fix the login bug", "status": "in_progress" }
]
}

用 PUT /v1/chats/:chatId/todos 替换整个列表。请发送你读到的 Chat 的 revision:

{
"revision": 7,
"items": [
{ "id": "fix", "content": "Fix the login bug", "status": "pending" }
]
}

返回的是 Chat 的视图。旧的 revision 会返回 409 和 ChatConflict。如果两项的 id 相同,或者不止一项是 in_progress,请求校验会失败并返回 400。


Bot 可以用工具 openbot_remind 之后再回到某件事上。它给出 seconds,或者给出 until 和一个日期时间,例如 2026-10-23T09:00:00Z,再加上一个 note(备注)。这个工具马上返回 { "reminder": "rem_…", "executionId": "…", "at": "<time>" },Turn 接着运行。到了那个时间,备注会作为通知回到这个 Chat。

每个提醒都有持久执行和持久定时器。持久定时器把唤醒时间保存在 PostgreSQL 中。重启后,执行按保存的时间唤醒;时间已过则立即唤醒。发送备注前,OpenBot 会重新检查当前权限。备注、提醒状态和执行结果在同一个事务中提交,重试不会添加第二条备注。

使用执行 ID 可以读取事件或取消这个提醒。取消先进入 cancelling,执行结束后进入 cancelled。

提醒是未完成的工作。带 { "delegations": true } 的中断会取消它。取消它所在 task Chat 的工作,或者删除它所在的 Chat,也会取消它。


在 plan 模式下,Bot 可以查资料,但还不改动任何东西。它只能用只读取的工具:列出和读取 Bots 的 Bot 工具、App 的资源和 prompt,以及组织在 appReadRules 中列出的 App 工具。它也能用待办工具、提醒工具和 openbot_submit_plan。

计划写好后,Bot 调用 openbot_submit_plan,把步骤作为待办列表,再加一段简短的总结。OpenBot 会保存这个列表,并像任何 request 一样检查能力 plans.run。

  • 计划马上被允许时,Chat 切换到 build 模式。这个 Turn 之后会开始一个带着所有工具的新 Turn,Bot 执行计划。
  • 计划需要决定时,工具马上返回一个待定的结果,Turn 结束。之后计划被允许时,Chat 切换到 build 模式,一条通知会启动一个新 Turn 来执行计划。
  • 计划被拒绝时,一条通知把原因告诉 Bot。Chat 留在 plan 模式。
  • OpenBot 无法检查计划时,计划不会执行。工具把原因告诉 Bot,Chat 留在 plan 模式。

批准设置决定由谁回答,以及没人及时回答时会怎样:

{ "approver": "person", "timeoutMs": 600000, "onTimeout": "run" }
字段 取值 作用
approver chain、person chain 问 Bot 的上一级决策者,所以 Grant 或 Rule 可以回答。person 总是问人。
timeoutMs 1 秒到 7 天 请求等待多久。
onTimeout run、stay run 表示没人及时回答时就执行计划。stay 表示 Chat 留在 plan 模式。

你可以在四个地方设置它。设置了的地方里,最具体的那个生效,即使它更宽松:

创建 Chat、修改设置或待办列表不会打开模型或成员 runtime。这些写入直接使用持久记录,并检查当前权限。修改设置或待办时必须提供当前 revision。

  1. Chat:用 PATCH /v1/chats/:chatId 设置 planApproval。发送 null 可以移除它。
  2. Bot:Bot 上的 planApproval。
  3. 你自己:PATCH /v1/me/settings,内容是 { "expectedVersion": 0, "planApproval": { ... } }。用 GET /v1/me/settings 读取你的设置。同一份设置里也有你的默认 Bot。
  4. 组织:设置中的 planApproval。它只是默认值,不能禁止更宽松的设置。

新的 Chat(包括评审 Chat)使用 Bot 的 model。测试或开发平台可以在 Bot 没有模型时提供 builtinModels.defaultModel。关联 Bot 的模型在接收组织中不可用时,也使用这个默认值。没有有效模型时,创建失败,不留下 Chat 或 Turn。实时 Chat 使用专用的实时模型目录。已保存的评审在重放时保留模型;每次新调用仍检查当前模型权限。GET /v1/chats/:chatId/models 显示你可以选择的模型:

{
"selected": {
"_tag": "Config.Selection",
"provider": "development",
"model": "mock-text"
},
"providers": [
{ "id": "development", "name": "Development scenarios (simulated)" }
],
"models": [
{
"_tag": "Config.Model",
"provider": "development",
"id": "mock-text",
"name": "mock-text (simulated)",
"reasoningEfforts": [],
"serviceTiers": []
}
]
}

用 PATCH /v1/chats/:chatId 修改一个 Chat 的设置:

{
"expected": 4,
"mode": "plan",
"model": {
"_tag": "Config.Selection",
"provider": "development",
"model": "mock-slow"
}
}
字段 含义
expected 必填。当前的 chat.revision。旧的 revision 会返回 409 和 ChatConflict。
mode 可选。build 或 plan。
model 可选。这个 Chat 的模型。它还需要 Model 上的 use。null 让 Chat 重新使用它的 Bot 的模型。
planApproval 可选。见 计划模式。null 会移除它。

错误的模型会返回 400 和 ChatModelInvalid。reason 是 provider-not-found、model-not-found 或 unsupported-option。OpenBot 如何得到它的模型,见 Models。

执行模式是 build 或 plan。build 是普通模式:Bot 直接做事。在 plan 模式下,Bot 先写一个计划。OpenBot 把模式保存在 Chat 上,并把它复制到每个新 Turn 的 settings 中。


Chat 没有终点,所以它的历史可能变得比模型的上下文窗口还大。上下文窗口是模型一次最多能读的文字。压缩(compaction)会让旧的部分变短,好让它放得下。

在每个模型步骤之前,OpenBot 会估计这个 Chat 的输入 token 数。大约四个字符的 JSON 算一个 token。当估计值达到模型上下文窗口的 90% 时,这个 Chat 的模型会为较早的上下文写一段摘要。最新的消息保持原样。

  • 上下文窗口来自目录中的模型。见 模型 中的 contextWindow。
  • 在代码中给出的模型,例如内置模型,可以用 autoCompactTokenLimit 设一个更低的限制。
  • 不知道上下文窗口的模型不会被压缩。

历史总是保留每个 Item,GET /v1/chats/:chatId/items 会显示全部。只有模型读到的内容会变小。写摘要的调用的用量来源是 compaction。见 用量。

OpenBot 默认不使用提供方的原生压缩,例如 OpenAI 的 /responses/compact。运行 core 库 的程序可以给出自己的 ContextCompaction 服务,例如 ResponsesCompaction.layer()。


DELETE /v1/chats/:chatId 接收 { "commandKey": "delete-1" },返回 202 和执行回执。它先关闭 Chat 的新工作入口,取消未完成工作,等待资源释放,再删除历史。用同一个键重试会得到同一回执,即使 Chat 已删除。可以删除 direct、channel 和 live Chat;task 和 review Chat 返回 409 和 ChatConflict。

之后这个 Chat 不再保留它指向的文件。

删除一个 direct Chat 后,你给这个 Bot 的下一条消息会开始一个新的、空的 Chat,它有新的 ID。同样,channel Chat 被删除后,那个聊天里的下一条消息会开始一个新的 Chat。Bot 从这个 Chat 学到的东西仍然留在它的 Bot 记忆中。要把这些也删除,请清除 Tag chat:<id>。见 读取和修改。


执行引擎每次只将工作交给一个服务器。服务器停止后,另一个服务器从保存的步骤恢复。新服务器接管后,旧服务器不能再推进状态。

已保存的外部结果会被复用。未完成的模型调用可开始新的 attempt,其输出单独标记。结果未知且没有安全重试键的外部写入进入 outcome_unknown 等待决定。OpenBot 不会将它标为完成,也不会在没有决定时再次发送。

只结算供应商确认的用量。未知用量保留未知标记。全部字段见 Chats 参考。

数据升级会保留 Chat ID、Item 和历史 Turn,也会保留旧工具结果。工具列表和读取路由将这些记录返回为 _tag: "Imported"。它们包含 chat、turn、kind 和 endedAt,但不含 position、deadline 或 detached,因为旧记录没有保存这些信息。通过关联执行的 status 和 failure 查看状态。旧的待处理请求和委派会保留在历史中,但不计入活动工作,也不会出现在模型等待结果的工作列表中。

旧工作没有原始 token 的凭据,因此升级不会再次运行它。待处理输入会移出活动收件箱,并保留在加密历史中。旧任务 Chat 仅保留为历史;请通过父 Chat 提交新工作。

如果直接 Chat 的旧 Turn 处于 outcome_unknown,请检查已保存的调用,并通过执行决策路由为每个未知调用提交 stop。这会记录你的决定,但不会撤销外部操作。在该执行结束前,新输入会返回 409 和 ChatConflict。之后,新输入会使用当前调用人的权限启动新 Turn。它不会复用原调用人的权限,也不会运行旧的未完成调用。