跳转到正文
Onevium文档
本页内容

业务会话:续聊、历史、事件与停止重开

用真实 Gateway 路由创建与延续会话,读取历史和 SSE,并以 generation、幂等键和控制记录管理停止与重开。

会话、任务和控制操作#

先按外部接入总览发布服务并启用接入,再从业务后端调用这里的 Gateway API。

对象用途
conversation_id一个长期业务会话;后续消息继续使用它
generation当前上下文代际,从 1 开始;reset 完成后加 1
run_id一次消息产生的任务,用于查结果、观察事件或停止
operation_id一次停止/reset 控制操作,必须另查是否完成

新建会话不会停止旧会话的任务。Reset 保留会话 ID 和业务关联,换一代上下文;它不会撤销已经发生的文件修改或外部操作。

准备示例环境#

以下命令用于 Bash、curl(支持 --fail-with-body)和 jq。示例响应用于说明返回格式,不是真实任务结果;请使用自己请求返回的 ID。

APP_KEY 从凭证存储注入测试终端或后端环境,其他变量按界面填写。不要打印密钥。用下面的固定操作标识完成一次练习;有意开始另一轮练习时再修改 FLOW_ID,网络重试时不要修改。

这里的 GATEWAY_URL 只填网关 origin,不包含末尾 /api/v1 例如界面显示 http://127.0.0.1:48541/api/v1 时,变量填 http://127.0.0.1:48541;下方命令会自行追加 /api/v1,不要重复。

bash
export GATEWAY_URL='http://127.0.0.1:REPLACE_WITH_API_PORT'
export ENDPOINT_ID='YOUR_PUBLISHED_ENDPOINT_ID'
export SUBJECT='docs-test-user'
: "${APP_KEY:?Load APP_KEY from your credential store first}"
FLOW_ID='docs-demo-001'
BUSINESS_KEY="case-$FLOW_ID"
CREATE_KEY="$FLOW_ID-create"

本页全部步骤需要 conversations:readconversations:writeruns:readruns:write。Scope 和业务用户主体的处理见鉴权与权限。本地地址使用 Onevium 显示的 API 地址;远端使用配置好的 HTTPS Gateway 地址。

1. 新建业务会话#

POST /api/v1/conversations,成功返回 201

字段/请求头要求
endpoint_id必填,当前应用下已发布的服务 ID
conversation_key可选业务关联键,1–256 字符且无首尾空白,例如工单编号;不传则不绑定业务键
Idempotency-Key必填,1–128 个可见 ASCII 字符,不含空格;表示这一次创建操作

conversation_key 不是幂等键。在同一应用、subject、服务下,使用另一创建请求标识重复创建同一业务键,会返回 409 BUSY;应保存并继续原会话。

bash
CREATE_BODY=$(jq -nc --arg endpoint "$ENDPOINT_ID" --arg key "$BUSINESS_KEY" \
  '{endpoint_id:$endpoint,conversation_key:$key}')
CREATE_RESPONSE=$(curl --fail-with-body -sS "$GATEWAY_URL/api/v1/conversations" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" \
  -H "Idempotency-Key: $CREATE_KEY" \
  -H 'Content-Type: application/json' --data "$CREATE_BODY")
printf '%s\n' "$CREATE_RESPONSE" | jq .
CONVERSATION_ID=$(printf '%s' "$CREATE_RESPONSE" | jq -er '.conversation_id')
GENERATION=$(printf '%s' "$CREATE_RESPONSE" | jq -er '.generation')

只在请求成功并取得两个值后继续。示例响应:

json
{
  "conversation_id": "conv_0123456789abcdef_0000000000000001",
  "owner_id": "example-owner",
  "app_id": "11111111111111111111111111111111",
  "subject": "docs-test-user",
  "endpoint_id": "22222222222222222222222222222222",
  "binding_version": 1,
  "generation": 1,
  "business_key": "case-docs-demo-001",
  "state": "active",
  "created_at": "2026-09-09T08:00:00.000Z"
}

请求字段 conversation_key 在会话记录中名为 business_keybinding_version 标识会话采用的已发布服务版本;它与 generation 是两个不同字段。

2. 消息字段怎么填#

POST /api/v1/conversations/{conversation_id}/messages 接受以下字段,未知字段会被拒绝。

字段要求与含义
expected_generation必填正整数,必须是当前代际;不要写字符串 "1"
input必填对象,以下三种输入至少一种非空
input.text可选文字,最多 64 KiB UTF-8;缺省按空文字处理
input.data可选普通 JSON 对象,传递结构化业务材料
input.resource_ids可选,最多 5 个不重复的已就绪附件 ID;须属于同一应用和 subject,类型还需服务支持
start_before可选 RFC 3339 时间,必须带时区;消息接口要求晚于现在且不超过接纳时刻后 5 分钟,缺省为 5 分钟后

start_before最晚开始时间,不是执行超时。请求 JSON 总大小上限为 1 MiB。不要在这里传模型、工作目录、工具权限或 busy_policy;模型和能力来自已发布服务,忙碌策略只属于 reset。

3. 发送第一条消息#

bash
MESSAGE_1_KEY="$FLOW_ID-message-1"
MESSAGE_1_BODY=$(jq -nc --argjson generation "$GENERATION" \
  '{expected_generation:$generation,input:{text:"记住演示代号 Orion,只回复收到。",data:{case_id:"DEMO-001"}}}')
ADMISSION=$(curl --fail-with-body -sS \
  "$GATEWAY_URL/api/v1/conversations/$CONVERSATION_ID/messages" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" \
  -H "Idempotency-Key: $MESSAGE_1_KEY" \
  -H 'Content-Type: application/json' --data "$MESSAGE_1_BODY")
printf '%s\n' "$ADMISSION" | jq .
RUN_ID=$(printf '%s' "$ADMISSION" | jq -er '.run_id')

202 示例接纳响应:

json
{
  "conversation_id": "conv_0123456789abcdef_0000000000000001",
  "generation": 1,
  "message_id": "msg_0123456789abcdef_0000000000000003",
  "run_id": "run_0123456789abcdef_0000000000000002",
  "state": "queued",
  "event_cursor": 2
}

保存 run_id,不能把 202 当成模型已回复。event_cursor 是接纳时的会话事件序号。原操作的幂等重放可能仍返回最初这份 queued 响应,当前状态要用 GET 查询。

4. 查询任务状态与结果#

bash
curl --fail-with-body -sS "$GATEWAY_URL/api/v1/runs/$RUN_ID" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" | jq .

成功终态的示例响应:

json
{
  "run_id": "run_0123456789abcdef_0000000000000002",
  "message_id": "msg_0123456789abcdef_0000000000000003",
  "conversation_id": "conv_0123456789abcdef_0000000000000001",
  "generation": 1,
  "state": "succeeded",
  "delivery_state": "device_received",
  "created_at": "2026-09-09T08:00:01.000Z",
  "updated_at": "2026-09-09T08:00:03.000Z",
  "result": { "text": "收到。" }
}
状态客户端应如何处理
queued已排队;结合投递状态和开始期限观察
running正在执行
waiting_approval等待审批,尚未完成
cancelling已请求停止,尚未确认停止
reconciling正在核对执行状态,不把未知结果当成成功
succeeded成功终态,读取 result
failed失败终态,检查 result.error(若有)
cancelledinterruptedexpired均为终态,但不是成功

waiting_approval 需要现有审批流程处理;这些公开业务路由没有“批准任意工具操作”的接口。

delivery_state 只有 waiting_devicedevice_received,与运行状态独立。result 可以为 null,有结果时可包含 textresource_idserror。失败也可能保留部分输出。

5. 继续同一会话#

先确认前一轮结果,再读当前快照取得真实代际:

bash
SNAPSHOT=$(curl --fail-with-body -sS \
  "$GATEWAY_URL/api/v1/conversations/$CONVERSATION_ID" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT")
GENERATION=$(printf '%s' "$SNAPSHOT" | jq -er '.generation')
MESSAGE_2_KEY="$FLOW_ID-message-2"
MESSAGE_2_BODY=$(jq -nc --argjson generation "$GENERATION" \
  '{expected_generation:$generation,input:{text:"之前的演示代号是什么?"}}')
ADMISSION_2=$(curl --fail-with-body -sS \
  "$GATEWAY_URL/api/v1/conversations/$CONVERSATION_ID/messages" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" \
  -H "Idempotency-Key: $MESSAGE_2_KEY" \
  -H 'Content-Type: application/json' --data "$MESSAGE_2_BODY")
printf '%s\n' "$ADMISSION_2" | jq .
RUN_ID=$(printf '%s' "$ADMISSION_2" | jq -er '.run_id')

这次会获得新的 message_idrun_id;保存第二个 Run ID 再查结果。演示中预期回复 Orion,实际仍应核对模型输出。

同会话允许排队。 一轮运行时,下一条消息可以被接纳为 queued,执行按会话串行。当前每会话最多 20 条 queued 任务,超过上限返回 429;会话在 resetting 等不可接纳状态时才会返回 BUSY。应用和设备另有总任务限额。

6. 读取快照和分页历史#

GET /conversations/{id} 返回快照,顶层为 conversation_id、当前 generationwatermarkmessagesruns。它不是创建接口的完整会话记录。快照最多取近期 200 条消息和 100 条任务,并有响应体预算;更早消息用历史接口读取。

快照的每个 run 包含 run_idgenerationstate、布尔值 device_receivedresultresult_available。若结果记录存在但快照受体积预算限制,可能出现 result_available: trueresult: null,此时单独 GET run。watermark 用于下面的 SSE 续传,不用于历史翻页。

bash
HISTORY=$(curl --fail-with-body -sS --get \
  "$GATEWAY_URL/api/v1/conversations/$CONVERSATION_ID/messages" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" \
  --data-urlencode "generation=$GENERATION" --data-urlencode 'limit=50')
printf '%s\n' "$HISTORY" | jq .
查询参数含义
generation可选正整数;不传则包含不同代际的保留消息
limit缺省 50;HTTP 接口接受 1–200,当前存储层每页实际最多 100 条,建议使用不超过 100
before可选正整数,使用上一页最小的 cursor 读取更早消息;不是 message_id,也不是 SSE seq

当前页非空时,可以继续读取前一页:

bash
BEFORE=$(printf '%s' "$HISTORY" | jq -er '.messages | map(.cursor) | min // empty')
curl --fail-with-body -sS --get \
  "$GATEWAY_URL/api/v1/conversations/$CONVERSATION_ID/messages" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" \
  --data-urlencode "generation=$GENERATION" \
  --data-urlencode "before=$BEFORE" --data-urlencode 'limit=50' | jq .

历史响应示例(这里仅有一条消息):

json
{
  "messages": [
    {
      "cursor": 1,
      "message_id": "msg_0123456789abcdef_0000000000000003",
      "conversation_id": "conv_0123456789abcdef_0000000000000001",
      "generation": 1,
      "run_id": "run_0123456789abcdef_0000000000000002",
      "role": "user",
      "created_at": "2026-09-09T08:00:01.000Z",
      "content": { "text": "记住演示代号 Orion,只回复收到。", "data": { "case_id": "DEMO-001" } }
    }
  ]
}

每页按较早到较晚排列,下一页取本页最小 cursor;空数组时结束。快照可能还含 content.partial: true 的未完成助手消息;最终结果仍查对应 run。当前事件保留 7 天,终态消息/结果正文约 30 天,不将 Gateway 当作永久业务档案。

要列出会话,使用 GET /api/v1/conversations?endpoint_id=服务ID&limit=50,可用会话 ID 作为 before。这里的 endpoint_id 用于定位设备,当前返回该设备下同一应用/subject 的会话,不能假定只包含该服务。

bash
curl --fail-with-body -sS --get "$GATEWAY_URL/api/v1/conversations" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" \
  --data-urlencode "endpoint_id=$ENDPOINT_ID" --data-urlencode 'limit=50' | jq .

7. 用 SSE 观察和断线续传#

首次建立业务界面时,先 GET 快照,再从 watermark 订阅,避免把旧内容重复追加:

bash
SNAPSHOT=$(curl --fail-with-body -sS \
  "$GATEWAY_URL/api/v1/conversations/$CONVERSATION_ID" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT")
LAST_SEQ=$(printf '%s' "$SNAPSHOT" | jq -er '.watermark')
curl --fail-with-body -sS -N \
  "$GATEWAY_URL/api/v1/conversations/$CONVERSATION_ID/events" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" \
  -H "Last-Event-ID: $LAST_SEQ" \
  -H 'Accept: text/event-stream'

示例事件,不是实测输出:

text
id: 3
event: run.started
data: {"conversation_id":"conv_0123456789abcdef_0000000000000001","generation":1,"event_id":"evt_0123456789abcdef_0000000000000004","seq":3,"run_id":"run_0123456789abcdef_0000000000000002","message_id":null,"type":"run.started","payload":{"state":"running"}}
  • Last-Event-ID数值 seq,不是 event_id。也可以用 ?after=3;两者同时提供时请求头优先。
  • 保存最后已处理序号,再从该序号重连;服务只返回 seq 更大的事件。先持久化处理结果,再推进客户端游标。
  • GET /api/v1/runs/{run_id}/events 只观察一个 run,仍使用会话级 seq,因此允许跳号;会话 reset 等事件应观察会话流。
  • assistant.delta 是增量,assistant.message 是完整消息;按消息 ID 更新,不把两者都重复追加成最终答案。
  • 410 CURSOR_EXPIRED 时重新取快照,再从新 watermark 订阅。: ping 是保活注释。

看到终态后可主动关闭观察流;SSE 不保证自动关闭。断开 SSE 不取消任务。当前每设备/应用/subject 的并发流上限分别为 32/16/4,应用应复用观察连接。

8. 停止一个 run#

停止接口是 POST /api/v1/runs/{run_id}/cancel,请求体必须是空对象。它不是 /stop,也不接受 expected_generation

对仍需停止的实际 RUN_ID 执行:

bash
CANCEL_KEY="$FLOW_ID-cancel-1"
CONTROL=$(curl --fail-with-body -sS \
  "$GATEWAY_URL/api/v1/runs/$RUN_ID/cancel" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" \
  -H "Idempotency-Key: $CANCEL_KEY" \
  -H 'Content-Type: application/json' --data '{}')
printf '%s\n' "$CONTROL" | jq .
OPERATION_ID=$(printf '%s' "$CONTROL" | jq -er '.operation_id')
curl --fail-with-body -sS "$GATEWAY_URL/api/v1/operations/$OPERATION_ID" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" | jq .

202 控制响应示例:

json
{
  "operation_id": "op_0123456789abcdef_0000000000000005",
  "state": "stopping",
  "ref": { "conversation_id": "conv_0123456789abcdef_0000000000000001", "generation": 1 }
}

pendingwaiting_devicestopping 都未完成;succeeded 表示控制已完成;failed 表示控制未成功。停止/reset 操作通常返回 stoppingsucceeded,客户端仍应识别完整协议枚举。

未开始的 queued run 可以直接取消;已执行的 run 需要确认实际终态。停止已经完成的 run,operation 可以成功,而 run 仍保留原来的 succeeded 等终态。控制完成后再 GET run 核对,不能仅凭 202 或 cancelling 宣称停止成功。

9. 重开上下文:reset#

POST /api/v1/conversations/{id}/reset 支持两个字段:

字段含义
expected_generation必填,当前代际
busy_policy可选,缺省 reject;存在非终态任务则返回 409 BUSY。明确选择 cancel_and_reset 时,会取消本代未完成任务并等待停止,再换代

先读快照取得当前 generation。没有未完成任务时:

bash
GENERATION=$(curl --fail-with-body -sS \
  "$GATEWAY_URL/api/v1/conversations/$CONVERSATION_ID" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" | jq -er '.generation')
RESET_KEY="$FLOW_ID-reset-idle-1"
RESET_BODY=$(jq -nc --argjson generation "$GENERATION" \
  '{expected_generation:$generation,busy_policy:"reject"}')
RESET_RESPONSE=$(curl --fail-with-body -sS \
  "$GATEWAY_URL/api/v1/conversations/$CONVERSATION_ID/reset" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" \
  -H "Idempotency-Key: $RESET_KEY" \
  -H 'Content-Type: application/json' --data "$RESET_BODY")
printf '%s\n' "$RESET_RESPONSE" | jq .
OPERATION_ID=$(printf '%s' "$RESET_RESPONSE" | jq -er '.operation_id')
RESET_STATUS=$(curl --fail-with-body -sS \
  "$GATEWAY_URL/api/v1/operations/$OPERATION_ID" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT")
printf '%s\n' "$RESET_STATUS" | jq .

若你明确要停止未完成工作再重开,应作为另一控制操作,用新键发送:

bash
RESET_KEY="$FLOW_ID-reset-busy-1"
RESET_BODY=$(jq -nc --argjson generation "$GENERATION" \
  '{expected_generation:$generation,busy_policy:"cancel_and_reset"}')
RESET_RESPONSE=$(curl --fail-with-body -sS \
  "$GATEWAY_URL/api/v1/conversations/$CONVERSATION_ID/reset" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT" \
  -H "Idempotency-Key: $RESET_KEY" \
  -H 'Content-Type: application/json' --data "$RESET_BODY")
printf '%s\n' "$RESET_RESPONSE" | jq .
OPERATION_ID=$(printf '%s' "$RESET_RESPONSE" | jq -er '.operation_id')

两种方式择一,不要盲目依次执行。读取其返回的 operation ID 并查询到 succeeded完成响应示例:

json
{
  "operation_id": "op_0123456789abcdef_0000000000000006",
  "state": "succeeded",
  "ref": { "conversation_id": "conv_0123456789abcdef_0000000000000001", "generation": 2 }
}

完成后使用 ref.generation 或最新快照中的代际发送下一条消息,旧代际会得到 409。Reset 还会采用服务当前发布版本;服务重新发布后出现版本冲突时,需要检查是否应重开。历史消息并不因 reset 立即删除,可用 generation=1 查询旧代际的保留记录。

确认 operation 已成功后,再更新本地代际变量:

bash
RESET_STATUS=$(curl --fail-with-body -sS \
  "$GATEWAY_URL/api/v1/operations/$OPERATION_ID" \
  -H "Authorization: Bearer $APP_KEY" \
  -H "X-Onevium-Subject: $SUBJECT")
GENERATION=$(printf '%s' "$RESET_STATUS" | jq -er \
  'select(.state == "succeeded") | .ref.generation')

幂等、重试与并发约定#

每次有意的新建、发消息、停止、reset 使用新键。同一次操作遇到断网或响应丢失时,保留原 应用、subject、服务、目标、请求键和正文,重放后查询资源当前状态。

  • 同键不同内容:409 IDEMPOTENCY_CONFLICT,不要用反复改键掩盖未知执行结果。
  • 代际冲突:先读快照,核对待发送内容是否仍适用;若决定按新上下文重新执行,作为新操作使用新键。
  • 创建时业务键重复:找回已保存的 conversation ID,不把它当成普通重试。
  • 停止/reset 响应丢失:同键重试取回 operation,再继续查询。
  • 429:读取 Retry-After 并等待;队列、存储等条件也需实际恢复。

与直接创建 run 的区别#

总览中的 POST /api/v1/agent-endpoints/{endpoint_id}/runs 适合任务型入口,字段为 input、可选 conversation_key、可选 expected_generation、可选 start_before

不带业务键时每次新操作建立新会话;带业务键时会查找或建立关联会话。传 expected_generation 时必须同时传 conversation_key,且关联会话已经存在。该入口的最晚开始窗口为 24 小时,与消息接口的 5 分钟不同。需要明确控制会话代际的交互流程,优先使用本页的会话接口。

验证清单和错误处理#

本地集成至少检查:新建、第二轮续聊、历史翻页、SSE 重连、取消 queued/运行中任务、两种 reset 策略、旧 generation 拒绝、同键重试和跨 subject 404。

错误下一步
400 UNKNOWN_FIELD / INVALID_REQUEST对照对应接口字段,不混用 message、cancel、reset 请求体
401 / 403核对密钥、scope 和应用/设备状态
404核对真实 ID、应用和 subject;不要因猜到 ID 就认为有权限
409 GENERATION_CONFLICT读快照,检查代际及服务发布变化
409 BUSY区分业务键重复、会话 resetting、reset 存在未完成任务
410 CURSOR_EXPIRED重新加载快照并更新 SSE 游标
503 CAPABILITY_UNAVAILABLE检查接入是否暂停、Gateway 和设备是否可用

下一步#

配置应用密钥与 scope,学习Webhook 和回调,或部署远端网关