From bb7b49ceaa033986cfbd8f56a3c87467a658436a Mon Sep 17 00:00:00 2001 From: Biao Date: Fri, 3 Jul 2026 12:43:05 +0800 Subject: [PATCH 1/2] =?UTF-8?q?docs(portai):=20=E6=96=B0=E5=A2=9E=20AI=20A?= =?UTF-8?q?gent=20=E5=AF=B9=E8=AF=9D=20API=20=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 conversation.md 描述发起对话接口 POST /v1/babbage/agents/:id/conversations,含阻塞式与 SSE 流式两种响应模式及请求示例 - 新增 continue.md 描述继续对话接口,覆盖 answers_by_tool_call 结构、多次中断续跑及归属校验规则 - conversation.md 提供 conversation_response schema,含 interrupt 中断结构(node_id、tool_call_id、questions)与 error 字段说明 - 新增 portai 与 chat 目录的 _category_.json,配置 sidebar 位置(2.7)、cpu 图标与分组标签 - en / zh-CN / zh-HK 三语言目录同步创建,内容与结构保持一致 --- docs/en/docs/portai/_category_.json | 8 + docs/en/docs/portai/chat/_category_.json | 7 + docs/en/docs/portai/chat/continue.md | 127 ++++++++++++++ docs/en/docs/portai/chat/conversation.md | 184 ++++++++++++++++++++ docs/zh-CN/docs/portai/_category_.json | 8 + docs/zh-CN/docs/portai/chat/_category_.json | 7 + docs/zh-CN/docs/portai/chat/continue.md | 127 ++++++++++++++ docs/zh-CN/docs/portai/chat/conversation.md | 184 ++++++++++++++++++++ docs/zh-HK/docs/portai/_category_.json | 8 + docs/zh-HK/docs/portai/chat/_category_.json | 7 + docs/zh-HK/docs/portai/chat/continue.md | 127 ++++++++++++++ docs/zh-HK/docs/portai/chat/conversation.md | 184 ++++++++++++++++++++ 12 files changed, 978 insertions(+) create mode 100644 docs/en/docs/portai/_category_.json create mode 100644 docs/en/docs/portai/chat/_category_.json create mode 100644 docs/en/docs/portai/chat/continue.md create mode 100644 docs/en/docs/portai/chat/conversation.md create mode 100644 docs/zh-CN/docs/portai/_category_.json create mode 100644 docs/zh-CN/docs/portai/chat/_category_.json create mode 100644 docs/zh-CN/docs/portai/chat/continue.md create mode 100644 docs/zh-CN/docs/portai/chat/conversation.md create mode 100644 docs/zh-HK/docs/portai/_category_.json create mode 100644 docs/zh-HK/docs/portai/chat/_category_.json create mode 100644 docs/zh-HK/docs/portai/chat/continue.md create mode 100644 docs/zh-HK/docs/portai/chat/conversation.md diff --git a/docs/en/docs/portai/_category_.json b/docs/en/docs/portai/_category_.json new file mode 100644 index 00000000..2e103af6 --- /dev/null +++ b/docs/en/docs/portai/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "AI Agent", + "collapsible": true, + "collapsed": false, + "link": null, + "position": 2.7, + "icon": "cpu" +} diff --git a/docs/en/docs/portai/chat/_category_.json b/docs/en/docs/portai/chat/_category_.json new file mode 100644 index 00000000..3c5c2174 --- /dev/null +++ b/docs/en/docs/portai/chat/_category_.json @@ -0,0 +1,7 @@ +{ + "position": 1, + "label": "Conversation", + "collapsible": true, + "collapsed": false, + "link": null +} diff --git a/docs/en/docs/portai/chat/continue.md b/docs/en/docs/portai/chat/continue.md new file mode 100644 index 00000000..333894d7 --- /dev/null +++ b/docs/en/docs/portai/chat/continue.md @@ -0,0 +1,127 @@ +--- +slug: continue +title: Continue Conversation +sidebar_position: 3 +language_tabs: false +toc_footers: [] +includes: [] +search: true +highlight_theme: '' +headingLevel: 2 +--- + +When [Start Conversation](/docs/portai/chat/conversation) returns `status = interrupted`, the Agent is waiting for more information from you. Send your answers via this endpoint and the paused run resumes from the interrupt point, until it succeeds, gets interrupted again, or fails. + +A single round of conversation may be interrupted multiple times: if the run returns `interrupted` again after continuing, call this endpoint again with the new `interrupt`. + +As with Start Conversation, choose blocking / SSE streaming response mode with the `Accept` header. + + + +## Request + + + + + + +
HTTP MethodPOST
HTTP URL/v1/babbage/agents/:id/conversations/:chat_uid/messages/:message_id/continue
+ +### Path Parameters + +| Name | Type | Required | Description | +| ---------- | ------ | -------- | ------------------------------------------------------------------------ | +| id | string | YES | UID of the target Agent | +| chat_uid | string | YES | `chat_uid` of the interrupted conversation, from the Start Conversation response | +| message_id | string | YES | ID of the interrupted message, from the `message_id` in the interrupt response | + +### Request Body + +| Name | Type | Required | Description | +| -------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------- | +| answers_by_tool_call | object | YES | Answers keyed by `tool_call_id`; must not be empty. Each value is a map of question text → answer | + +The structure of `answers_by_tool_call` is "tool call ID → answers for that inquiry", where each answer set is a map of "question → answer": + +- The outer key corresponds to `tool_call_id` in the interrupt structure. +- The inner key is the interrupt question text, and the value is the answer you selected or typed. +- All questions required by the interrupt must be answered, otherwise an error is returned. + +### Request Example + + + + +```bash +curl -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversations/ct_9f2c1a5b/messages/43/continue" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d '{ + "answers_by_tool_call": { + "call_abc123": { + "Which time range would you like to check?": "Past month" + } + } + }' +``` + + + + +```bash +curl -N -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversations/ct_9f2c1a5b/messages/43/continue" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: text/event-stream" \ + -d '{ + "answers_by_tool_call": { + "call_abc123": { + "Which time range would you like to check?": "Past month" + } + } + }' +``` + + + + +## Response + +The response structure is identical to [Start Conversation](/docs/portai/chat/conversation#schemaconversation_response). + +### Response Headers + +- Content-Type: application/json (blocking) +- Content-Type: text/event-stream (streaming) + +### Response Example + +Run succeeded: + +```json +{ + "code": 0, + "message": "success", + "data": { + "chat_uid": "ct_9f2c1a5b", + "message_id": "43", + "status": "succeeded", + "answer": "Over the past month, Tesla (TSLA.US)...", + "references": [], + "elapsed_time": 2.74 + } +} +``` + +### Response Status + +| Status | Description | Schema | +| ------ | ------------------ | ----------------------------------------------------------------------------- | +| 200 | Success | [conversation_response](/docs/portai/chat/conversation#schemaconversation_response) | +| 400 | Invalid parameters | Empty `answers_by_tool_call`, message not interrupted, missing required interrupt answers, or conversation / message ownership mismatch | +| 500 | Internal error | None | + +### Ownership Checks + +To prevent resuming someone else's run, the server verifies that the conversation identified by `chat_uid` belongs to the authenticated member, and the message identified by `message_id` belongs to that conversation. Failing either check returns `400`. diff --git a/docs/en/docs/portai/chat/conversation.md b/docs/en/docs/portai/chat/conversation.md new file mode 100644 index 00000000..b63c6897 --- /dev/null +++ b/docs/en/docs/portai/chat/conversation.md @@ -0,0 +1,184 @@ +--- +slug: conversation +title: Start Conversation +sidebar_position: 2 +language_tabs: false +toc_footers: [] +includes: [] +search: true +highlight_theme: '' +headingLevel: 2 +--- + +Ask a question to the specified Agent. You can start a brand-new conversation, or pass the `chat_uid` of an existing one to continue asking in the same conversation. + +The Agent generates the answer using capabilities such as market data and account access. When the Agent needs more information or confirmation from you, the run is **interrupted** (`status` is `interrupted`) — send your answers via [Continue Conversation](/docs/portai/chat/continue) to resume it. + +Choose the response mode with the `Accept` header: `text/event-stream` streams the run progress and answer via SSE; any other value returns a blocking response with the aggregated final result. + + + +## Request + + + + + + +
HTTP MethodPOST
HTTP URL/v1/babbage/agents/:id/conversations
+ +### Path Parameters + +| Name | Type | Required | Description | +| ---- | ------ | -------- | -------------------------------------------------- | +| id | string | YES | UID of the target Agent; must be a published Agent | + +### Request Body + +| Name | Type | Required | Description | +| -------- | ------ | -------- | ------------------------------------------------------------------------------------------------ | +| query | string | YES | The user question; must not be empty | +| chat_uid | string | NO | Identifier of an existing conversation. Pass it to continue that conversation; omit to start a new one | + +### Request Example + + + + +```bash +# Blocking: get the final result in one response +curl -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversations" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d '{ + "query": "How has Tesla stock performed recently?" + }' +``` + + + + +```bash +# Streaming: receive run progress and the answer in real time +curl -N -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversations" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: text/event-stream" \ + -d '{ + "query": "How has Tesla stock performed recently?" + }' +``` + + + + +## Response + +### Response Headers + +- Content-Type: application/json (blocking) +- Content-Type: text/event-stream (streaming) + +### Response Example + +Run succeeded: + +```json +{ + "code": 0, + "message": "success", + "data": { + "chat_uid": "ct_9f2c1a5b", + "message_id": "42", + "status": "succeeded", + "answer": "Tesla (TSLA.US) recently...", + "references": [ + { "index": 1, "title": "...", "url": "..." } + ], + "elapsed_time": 3.21 + } +} +``` + +Run interrupted (the Agent needs more information from you; the run is paused): + +```json +{ + "code": 0, + "message": "success", + "data": { + "chat_uid": "ct_9f2c1a5b", + "message_id": "43", + "status": "interrupted", + "answer": "", + "references": null, + "elapsed_time": 1.05, + "interrupt": { + "node_id": "n_ask_human", + "tool_call_id": "call_abc123", + "questions": [ + { + "question": "Which time range would you like to check?", + "options": [ + { "description": "Past week" }, + { "description": "Past month" } + ], + "multi_select": false + } + ], + "message_id": 43, + "chat_id": 1001 + } + } +} +``` + +In streaming mode, the server keeps pushing run progress as `event: message` events; the `event` field inside `data` identifies the event type: + +``` +event: message +data: {"event":"chat_started","workflow_run_id":"wr_...","data":{"chat_uid":"ct_9f2c1a5b","message_id":42}} + +event: message +data: {"event":"message","workflow_run_id":"wr_...","data":{"text":"Tesla"}} + +event: message +data: {"event":"workflow_finished","workflow_run_id":"wr_...","data":{"status":"succeeded","elapsed_time":3.21,"outputs":{"answer":"..."}}} +``` + +### Response Status + +| Status | Description | Schema | +| ------ | --------------------------------- | ------------------------------------------------------ | +| 200 | Success | [conversation_response](#schemaconversation_response) | +| 400 | Invalid parameters or risk control | Empty `query`, malformed request body, or blocked by content risk control | +| 500 | Internal error | None | + +## Schemas + +### conversation_response + + + +| Name | Type | Required | Description | +| -------------------- | -------- | -------- | -------------------------------------------------------------- | +| chat_uid | string | true | Conversation identifier, used for follow-up questions and troubleshooting | +| message_id | string | true | Message ID of this round (as a string) | +| status | string | true | Final run status: `succeeded` / `interrupted` / `failed` / `stopped` | +| answer | string | false | Final answer text; valid when `status` is `succeeded` | +| references | object[] | false | Sources referenced by the answer; `null` if none | +| elapsed_time | number | false | Run duration in seconds | +| interrupt | object | false | Present only when `status` is `interrupted` | +| ∟ node_id | string | true | ID of the node that triggered the interrupt | +| ∟ tool_call_id | string | true | Tool call ID of this inquiry; used as the answer key when continuing | +| ∟ questions | object[] | true | Questions you need to answer | +| ∟∟ question | string | true | Question text | +| ∟∟ options | object[] | false | Options; empty means free-form answer | +| ∟∟∟ description | string | false | Option text | +| ∟∟ multi_select | boolean | false | Whether multiple options may be selected | +| ∟ message_id | int64 | false | ID of the paused message | +| ∟ chat_id | int64 | false | ID of the owning conversation | +| error | object | false | Present only when the run failed | +| ∟ code | int32 | false | Error code | +| ∟ message | string | false | Error message | diff --git a/docs/zh-CN/docs/portai/_category_.json b/docs/zh-CN/docs/portai/_category_.json new file mode 100644 index 00000000..fc347d33 --- /dev/null +++ b/docs/zh-CN/docs/portai/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "AI 智能体", + "collapsible": true, + "collapsed": false, + "link": null, + "position": 2.7, + "icon": "cpu" +} diff --git a/docs/zh-CN/docs/portai/chat/_category_.json b/docs/zh-CN/docs/portai/chat/_category_.json new file mode 100644 index 00000000..c36ceaba --- /dev/null +++ b/docs/zh-CN/docs/portai/chat/_category_.json @@ -0,0 +1,7 @@ +{ + "position": 1, + "label": "对话", + "collapsible": true, + "collapsed": false, + "link": null +} diff --git a/docs/zh-CN/docs/portai/chat/continue.md b/docs/zh-CN/docs/portai/chat/continue.md new file mode 100644 index 00000000..3977fc1d --- /dev/null +++ b/docs/zh-CN/docs/portai/chat/continue.md @@ -0,0 +1,127 @@ +--- +slug: continue +title: 继续对话 +sidebar_position: 3 +language_tabs: false +toc_footers: [] +includes: [] +search: true +highlight_theme: '' +headingLevel: 2 +--- + +当 [发起对话](/zh-CN/docs/portai/chat/conversation) 返回 `status = interrupted` 时,Agent 正等待你补充信息。通过本接口回传答案,暂停的运行会从中断处继续执行,直到成功、再次中断或失败。 + +同一轮对话可能发生多次中断:若继续后再次返回 `interrupted`,按新的 `interrupt` 重复调用本接口即可。 + +与发起对话一致,通过请求头 `Accept` 选择阻塞式 / SSE 流式响应。 + + + +## Request + + + + + + +
HTTP MethodPOST
HTTP URL/v1/babbage/agents/:id/conversations/:chat_uid/messages/:message_id/continue
+ +### Path Parameters + +| Name | Type | Required | Description | +| ---------- | ------ | -------- | -------------------------------------------------- | +| id | string | YES | 目标 Agent 的 UID | +| chat_uid | string | YES | 中断所属会话的 `chat_uid`,取自发起对话的响应 | +| message_id | string | YES | 被中断的消息 ID,取自中断响应的 `message_id` | + +### Request Body + +| Name | Type | Required | Description | +| -------------------- | ------ | -------- | -------------------------------------------------------------- | +| answers_by_tool_call | object | YES | 以 `tool_call_id` 为 key 的答案集合,不能为空。value 为「问题文本 → 回答」的键值对 | + +`answers_by_tool_call` 的结构为「工具调用 ID → 该次询问的答案」,其中每个答案又是一组「问题 → 回答」: + +- 外层 key 对应中断结构里的 `tool_call_id`。 +- 内层 key 为中断问题文本,value 为你选择或填写的答案。 +- 必须回答该次中断要求的所有问题,否则返回错误。 + +### Request Example + + + + +```bash +curl -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversations/ct_9f2c1a5b/messages/43/continue" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d '{ + "answers_by_tool_call": { + "call_abc123": { + "你想查询哪个时间区间?": "近一个月" + } + } + }' +``` + + + + +```bash +curl -N -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversations/ct_9f2c1a5b/messages/43/continue" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: text/event-stream" \ + -d '{ + "answers_by_tool_call": { + "call_abc123": { + "你想查询哪个时间区间?": "近一个月" + } + } + }' +``` + + + + +## Response + +响应结构与 [发起对话](/zh-CN/docs/portai/chat/conversation#schemaconversation_response) 完全一致。 + +### Response Headers + +- Content-Type: application/json(阻塞式) +- Content-Type: text/event-stream(流式) + +### Response Example + +运行成功: + +```json +{ + "code": 0, + "message": "success", + "data": { + "chat_uid": "ct_9f2c1a5b", + "message_id": "43", + "status": "succeeded", + "answer": "近一个月特斯拉(TSLA.US)……", + "references": [], + "elapsed_time": 2.74 + } +} +``` + +### Response Status + +| Status | Description | Schema | +| ------ | ----------- | ----------------------------------------------------- | +| 200 | 返回成功 | [conversation_response](/zh-CN/docs/portai/chat/conversation#schemaconversation_response) | +| 400 | 参数非法 | `answers_by_tool_call` 为空、消息未处于中断状态、缺少中断所需答案,或会话 / 消息归属不符 | +| 500 | 内部错误 | None | + +### 归属校验 + +为防止续跑他人的运行,服务端会校验:`chat_uid` 对应的会话必须属于当前认证成员,且 `message_id` 对应的消息必须属于该会话。任一不满足都会返回 `400`。 diff --git a/docs/zh-CN/docs/portai/chat/conversation.md b/docs/zh-CN/docs/portai/chat/conversation.md new file mode 100644 index 00000000..f6907059 --- /dev/null +++ b/docs/zh-CN/docs/portai/chat/conversation.md @@ -0,0 +1,184 @@ +--- +slug: conversation +title: 发起对话 +sidebar_position: 2 +language_tabs: false +toc_footers: [] +includes: [] +search: true +highlight_theme: '' +headingLevel: 2 +--- + +向指定 Agent 提问。可以开启一个全新会话,也可以传入已有会话的 `chat_uid` 在同一会话中追加提问。 + +Agent 会结合行情、账户等能力生成回答。当 Agent 需要你补充信息或确认时,本次运行会**中断**(`status` 为 `interrupted`),此时需通过 [继续对话](/zh-CN/docs/portai/chat/continue) 回传答案后才能继续。 + +通过请求头 `Accept` 选择响应模式:`text/event-stream` 为 SSE 流式,逐步推送运行过程与回答;其他值为阻塞式,一次性返回聚合后的最终结果。 + + + +## Request + + + + + + +
HTTP MethodPOST
HTTP URL/v1/babbage/agents/:id/conversations
+ +### Path Parameters + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ---------------------------------------- | +| id | string | YES | 目标 Agent 的 UID,需为已发布 Agent | + +### Request Body + +| Name | Type | Required | Description | +| -------- | ------ | -------- | -------------------------------------------------- | +| query | string | YES | 用户问题,不能为空 | +| chat_uid | string | NO | 已有会话标识。传入则在该会话中继续提问,不传则新建会话 | + +### Request Example + + + + +```bash +# 阻塞式:一次性拿到最终结果 +curl -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversations" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d '{ + "query": "帮我看看特斯拉最近的股价表现" + }' +``` + + + + +```bash +# 流式:实时接收运行过程与回答 +curl -N -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversations" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: text/event-stream" \ + -d '{ + "query": "帮我看看特斯拉最近的股价表现" + }' +``` + + + + +## Response + +### Response Headers + +- Content-Type: application/json(阻塞式) +- Content-Type: text/event-stream(流式) + +### Response Example + +运行成功: + +```json +{ + "code": 0, + "message": "success", + "data": { + "chat_uid": "ct_9f2c1a5b", + "message_id": "42", + "status": "succeeded", + "answer": "特斯拉(TSLA.US)最近……", + "references": [ + { "index": 1, "title": "...", "url": "..." } + ], + "elapsed_time": 3.21 + } +} +``` + +运行中断(Agent 需要你补充信息,运行暂停): + +```json +{ + "code": 0, + "message": "success", + "data": { + "chat_uid": "ct_9f2c1a5b", + "message_id": "43", + "status": "interrupted", + "answer": "", + "references": null, + "elapsed_time": 1.05, + "interrupt": { + "node_id": "n_ask_human", + "tool_call_id": "call_abc123", + "questions": [ + { + "question": "你想查询哪个时间区间?", + "options": [ + { "description": "近一周" }, + { "description": "近一个月" } + ], + "multi_select": false + } + ], + "message_id": 43, + "chat_id": 1001 + } + } +} +``` + +流式模式下,服务端以 `event: message` 持续推送运行过程,`data` 中的 `event` 字段标识事件类型: + +``` +event: message +data: {"event":"chat_started","workflow_run_id":"wr_...","data":{"chat_uid":"ct_9f2c1a5b","message_id":42}} + +event: message +data: {"event":"message","workflow_run_id":"wr_...","data":{"text":"特斯拉"}} + +event: message +data: {"event":"workflow_finished","workflow_run_id":"wr_...","data":{"status":"succeeded","elapsed_time":3.21,"outputs":{"answer":"..."}}} +``` + +### Response Status + +| Status | Description | Schema | +| ------ | ----------- | ----------------------------------------------- | +| 200 | 返回成功 | [conversation_response](#schemaconversation_response) | +| 400 | 参数非法或命中风控 | `query` 为空、请求体错误,或内容风控拦截 | +| 500 | 内部错误 | None | + +## Schemas + +### conversation_response + + + +| Name | Type | Required | Description | +| -------------------- | -------- | -------- | ------------------------------------------------ | +| chat_uid | string | true | 会话标识,追加提问或排查问题时使用 | +| message_id | string | true | 本轮消息 ID(字符串形式) | +| status | string | true | 运行终态:`succeeded` / `interrupted` / `failed` / `stopped` | +| answer | string | false | 最终回答文本,`status` 为 `succeeded` 时有效 | +| references | object[] | false | 回答引用的资料来源,无引用时为 `null` | +| elapsed_time | number | false | 运行耗时(秒) | +| interrupt | object | false | 仅当 `status` 为 `interrupted` 时出现 | +| ∟ node_id | string | true | 触发中断的节点 ID | +| ∟ tool_call_id | string | true | 本次询问对应的工具调用 ID,继续对话时作为答案的 key | +| ∟ questions | object[] | true | 需要你回答的问题列表 | +| ∟∟ question | string | true | 问题文本 | +| ∟∟ options | object[] | false | 可选项,为空表示自由作答 | +| ∟∟∟ description | string | false | 选项文本 | +| ∟∟ multi_select | boolean | false | 是否允许多选 | +| ∟ message_id | int64 | false | 被暂停的消息 ID | +| ∟ chat_id | int64 | false | 所属会话 ID | +| error | object | false | 仅当运行出错时出现 | +| ∟ code | int32 | false | 错误码 | +| ∟ message | string | false | 错误信息 | diff --git a/docs/zh-HK/docs/portai/_category_.json b/docs/zh-HK/docs/portai/_category_.json new file mode 100644 index 00000000..7490fde3 --- /dev/null +++ b/docs/zh-HK/docs/portai/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "AI 智能體", + "collapsible": true, + "collapsed": false, + "link": null, + "position": 2.7, + "icon": "cpu" +} diff --git a/docs/zh-HK/docs/portai/chat/_category_.json b/docs/zh-HK/docs/portai/chat/_category_.json new file mode 100644 index 00000000..b9440989 --- /dev/null +++ b/docs/zh-HK/docs/portai/chat/_category_.json @@ -0,0 +1,7 @@ +{ + "position": 1, + "label": "對話", + "collapsible": true, + "collapsed": false, + "link": null +} diff --git a/docs/zh-HK/docs/portai/chat/continue.md b/docs/zh-HK/docs/portai/chat/continue.md new file mode 100644 index 00000000..ea866b3b --- /dev/null +++ b/docs/zh-HK/docs/portai/chat/continue.md @@ -0,0 +1,127 @@ +--- +slug: continue +title: 繼續對話 +sidebar_position: 3 +language_tabs: false +toc_footers: [] +includes: [] +search: true +highlight_theme: '' +headingLevel: 2 +--- + +當 [發起對話](/zh-HK/docs/portai/chat/conversation) 返回 `status = interrupted` 時,Agent 正等待你補充信息。通過本接口回傳答案,暫停的運行會從中斷處繼續執行,直到成功、再次中斷或失敗。 + +同一輪對話可能發生多次中斷:若繼續後再次返回 `interrupted`,按新的 `interrupt` 重複調用本接口即可。 + +與發起對話一致,通過請求頭 `Accept` 選擇阻塞式 / SSE 流式響應。 + + + +## Request + + + + + + +
HTTP MethodPOST
HTTP URL/v1/babbage/agents/:id/conversations/:chat_uid/messages/:message_id/continue
+ +### Path Parameters + +| Name | Type | Required | Description | +| ---------- | ------ | -------- | -------------------------------------------------- | +| id | string | YES | 目標 Agent 的 UID | +| chat_uid | string | YES | 中斷所屬會話的 `chat_uid`,取自發起對話的響應 | +| message_id | string | YES | 被中斷的消息 ID,取自中斷響應的 `message_id` | + +### Request Body + +| Name | Type | Required | Description | +| -------------------- | ------ | -------- | -------------------------------------------------------------- | +| answers_by_tool_call | object | YES | 以 `tool_call_id` 為 key 的答案集合,不能為空。value 為「問題文本 → 回答」的鍵值對 | + +`answers_by_tool_call` 的結構為「工具調用 ID → 該次詢問的答案」,其中每個答案又是一組「問題 → 回答」: + +- 外層 key 對應中斷結構裏的 `tool_call_id`。 +- 內層 key 為中斷問題文本,value 為你選擇或填寫的答案。 +- 必須回答該次中斷要求的所有問題,否則返回錯誤。 + +### Request Example + + + + +```bash +curl -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversations/ct_9f2c1a5b/messages/43/continue" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d '{ + "answers_by_tool_call": { + "call_abc123": { + "你想查詢哪個時間區間?": "近一個月" + } + } + }' +``` + + + + +```bash +curl -N -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversations/ct_9f2c1a5b/messages/43/continue" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: text/event-stream" \ + -d '{ + "answers_by_tool_call": { + "call_abc123": { + "你想查詢哪個時間區間?": "近一個月" + } + } + }' +``` + + + + +## Response + +響應結構與 [發起對話](/zh-HK/docs/portai/chat/conversation#schemaconversation_response) 完全一致。 + +### Response Headers + +- Content-Type: application/json(阻塞式) +- Content-Type: text/event-stream(流式) + +### Response Example + +運行成功: + +```json +{ + "code": 0, + "message": "success", + "data": { + "chat_uid": "ct_9f2c1a5b", + "message_id": "43", + "status": "succeeded", + "answer": "近一個月特斯拉(TSLA.US)……", + "references": [], + "elapsed_time": 2.74 + } +} +``` + +### Response Status + +| Status | Description | Schema | +| ------ | ----------- | ----------------------------------------------------- | +| 200 | 返回成功 | [conversation_response](/zh-HK/docs/portai/chat/conversation#schemaconversation_response) | +| 400 | 參數非法 | `answers_by_tool_call` 為空、消息未處於中斷狀態、缺少中斷所需答案,或會話 / 消息歸屬不符 | +| 500 | 內部錯誤 | None | + +### 歸屬校驗 + +為防止續跑他人的運行,服務端會校驗:`chat_uid` 對應的會話必須屬於當前認證成員,且 `message_id` 對應的消息必須屬於該會話。任一不滿足都會返回 `400`。 diff --git a/docs/zh-HK/docs/portai/chat/conversation.md b/docs/zh-HK/docs/portai/chat/conversation.md new file mode 100644 index 00000000..fda6cf2a --- /dev/null +++ b/docs/zh-HK/docs/portai/chat/conversation.md @@ -0,0 +1,184 @@ +--- +slug: conversation +title: 發起對話 +sidebar_position: 2 +language_tabs: false +toc_footers: [] +includes: [] +search: true +highlight_theme: '' +headingLevel: 2 +--- + +向指定 Agent 提問。可以開啟一個全新會話,也可以傳入已有會話的 `chat_uid` 在同一會話中追加提問。 + +Agent 會結合行情、賬戶等能力生成回答。當 Agent 需要你補充信息或確認時,本次運行會**中斷**(`status` 為 `interrupted`),此時需通過 [繼續對話](/zh-HK/docs/portai/chat/continue) 回傳答案後才能繼續。 + +通過請求頭 `Accept` 選擇響應模式:`text/event-stream` 為 SSE 流式,逐步推送運行過程與回答;其他值為阻塞式,一次性返回聚合後的最終結果。 + + + +## Request + + + + + + +
HTTP MethodPOST
HTTP URL/v1/babbage/agents/:id/conversations
+ +### Path Parameters + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ---------------------------------------- | +| id | string | YES | 目標 Agent 的 UID,需為已發佈 Agent | + +### Request Body + +| Name | Type | Required | Description | +| -------- | ------ | -------- | -------------------------------------------------- | +| query | string | YES | 用戶問題,不能為空 | +| chat_uid | string | NO | 已有會話標識。傳入則在該會話中繼續提問,不傳則新建會話 | + +### Request Example + + + + +```bash +# 阻塞式:一次性拿到最終結果 +curl -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversations" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d '{ + "query": "幫我看看特斯拉最近的股價表現" + }' +``` + + + + +```bash +# 流式:實時接收運行過程與回答 +curl -N -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversations" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Accept: text/event-stream" \ + -d '{ + "query": "幫我看看特斯拉最近的股價表現" + }' +``` + + + + +## Response + +### Response Headers + +- Content-Type: application/json(阻塞式) +- Content-Type: text/event-stream(流式) + +### Response Example + +運行成功: + +```json +{ + "code": 0, + "message": "success", + "data": { + "chat_uid": "ct_9f2c1a5b", + "message_id": "42", + "status": "succeeded", + "answer": "特斯拉(TSLA.US)最近……", + "references": [ + { "index": 1, "title": "...", "url": "..." } + ], + "elapsed_time": 3.21 + } +} +``` + +運行中斷(Agent 需要你補充信息,運行暫停): + +```json +{ + "code": 0, + "message": "success", + "data": { + "chat_uid": "ct_9f2c1a5b", + "message_id": "43", + "status": "interrupted", + "answer": "", + "references": null, + "elapsed_time": 1.05, + "interrupt": { + "node_id": "n_ask_human", + "tool_call_id": "call_abc123", + "questions": [ + { + "question": "你想查詢哪個時間區間?", + "options": [ + { "description": "近一週" }, + { "description": "近一個月" } + ], + "multi_select": false + } + ], + "message_id": 43, + "chat_id": 1001 + } + } +} +``` + +流式模式下,服務端以 `event: message` 持續推送運行過程,`data` 中的 `event` 字段標識事件類型: + +``` +event: message +data: {"event":"chat_started","workflow_run_id":"wr_...","data":{"chat_uid":"ct_9f2c1a5b","message_id":42}} + +event: message +data: {"event":"message","workflow_run_id":"wr_...","data":{"text":"特斯拉"}} + +event: message +data: {"event":"workflow_finished","workflow_run_id":"wr_...","data":{"status":"succeeded","elapsed_time":3.21,"outputs":{"answer":"..."}}} +``` + +### Response Status + +| Status | Description | Schema | +| ------ | ----------- | ----------------------------------------------- | +| 200 | 返回成功 | [conversation_response](#schemaconversation_response) | +| 400 | 參數非法或命中風控 | `query` 為空、請求體錯誤,或內容風控攔截 | +| 500 | 內部錯誤 | None | + +## Schemas + +### conversation_response + + + +| Name | Type | Required | Description | +| -------------------- | -------- | -------- | ------------------------------------------------ | +| chat_uid | string | true | 會話標識,追加提問或排查問題時使用 | +| message_id | string | true | 本輪消息 ID(字符串形式) | +| status | string | true | 運行終態:`succeeded` / `interrupted` / `failed` / `stopped` | +| answer | string | false | 最終回答文本,`status` 為 `succeeded` 時有效 | +| references | object[] | false | 回答引用的資料來源,無引用時為 `null` | +| elapsed_time | number | false | 運行耗時(秒) | +| interrupt | object | false | 僅當 `status` 為 `interrupted` 時出現 | +| ∟ node_id | string | true | 觸發中斷的節點 ID | +| ∟ tool_call_id | string | true | 本次詢問對應的工具調用 ID,繼續對話時作為答案的 key | +| ∟ questions | object[] | true | 需要你回答的問題列表 | +| ∟∟ question | string | true | 問題文本 | +| ∟∟ options | object[] | false | 可選項,為空表示自由作答 | +| ∟∟∟ description | string | false | 選項文本 | +| ∟∟ multi_select | boolean | false | 是否允許多選 | +| ∟ message_id | int64 | false | 被暫停的消息 ID | +| ∟ chat_id | int64 | false | 所屬會話 ID | +| error | object | false | 僅當運行出錯時出現 | +| ∟ code | int32 | false | 錯誤碼 | +| ∟ message | string | false | 錯誤信息 | From 7a5902b8f7d3463dd7a6703b310c675bd7473ed9 Mon Sep 17 00:00:00 2001 From: Biao Date: Tue, 21 Jul 2026 10:57:09 +0800 Subject: [PATCH 2/2] =?UTF-8?q?docs(portai):=20=E6=96=B0=E5=A2=9E=20SSE=20?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E5=8F=82=E8=80=83=E9=A1=B5=E5=B9=B6=E8=A1=A5?= =?UTF-8?q?=E5=85=A8=E4=BA=8B=E4=BB=B6=E7=B1=BB=E5=9E=8B=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 docs/{en,zh-CN,zh-HK}/docs/portai/chat/events.md,逐一列出 chat/workflow 生命周期、message、thinking、node_tool_use、subagent、agent_tool、human_interaction_required 及辅助事件的字段表与示例 - 说明帧格式信封结构:SSE event 恒为 message,客户端应按 data.event 分发并忽略未知事件以保持向前兼容 - 给出成功/中断/失败三种典型事件序列,并注明中断运行不发 workflow_finished、恢复时不再发 workflow_started - conversation.md 的流式示例中 workflow_run_id 由占位符 wr_... 修正为真实格式的数字字符串 745910371102313,并添加指向 events 页的链接 - 三语言页面 sidebar_position 均为 4,紧随 continue(3)之后 --- docs/en/docs/portai/chat/conversation.md | 8 +- docs/en/docs/portai/chat/events.md | 404 ++++++++++++++++++++ docs/zh-CN/docs/portai/chat/conversation.md | 8 +- docs/zh-CN/docs/portai/chat/events.md | 404 ++++++++++++++++++++ docs/zh-HK/docs/portai/chat/conversation.md | 8 +- docs/zh-HK/docs/portai/chat/events.md | 404 ++++++++++++++++++++ 6 files changed, 1227 insertions(+), 9 deletions(-) create mode 100644 docs/en/docs/portai/chat/events.md create mode 100644 docs/zh-CN/docs/portai/chat/events.md create mode 100644 docs/zh-HK/docs/portai/chat/events.md diff --git a/docs/en/docs/portai/chat/conversation.md b/docs/en/docs/portai/chat/conversation.md index b63c6897..750f09d8 100644 --- a/docs/en/docs/portai/chat/conversation.md +++ b/docs/en/docs/portai/chat/conversation.md @@ -138,15 +138,17 @@ In streaming mode, the server keeps pushing run progress as `event: message` eve ``` event: message -data: {"event":"chat_started","workflow_run_id":"wr_...","data":{"chat_uid":"ct_9f2c1a5b","message_id":42}} +data: {"event":"chat_started","workflow_run_id":"745910371102313","data":{"chat_uid":"ct_9f2c1a5b","message_id":42}} event: message -data: {"event":"message","workflow_run_id":"wr_...","data":{"text":"Tesla"}} +data: {"event":"message","workflow_run_id":"745910371102313","data":{"text":"Tesla"}} event: message -data: {"event":"workflow_finished","workflow_run_id":"wr_...","data":{"status":"succeeded","elapsed_time":3.21,"outputs":{"answer":"..."}}} +data: {"event":"workflow_finished","workflow_run_id":"745910371102313","data":{"status":"succeeded","elapsed_time":3.21,"outputs":{"answer":"..."}}} ``` +See [SSE Events](/docs/portai/chat/events) for the full list of event types, their payload structures, and parsing guidance. + ### Response Status | Status | Description | Schema | diff --git a/docs/en/docs/portai/chat/events.md b/docs/en/docs/portai/chat/events.md new file mode 100644 index 00000000..1d5f862b --- /dev/null +++ b/docs/en/docs/portai/chat/events.md @@ -0,0 +1,404 @@ +--- +slug: events +title: SSE Events +sidebar_position: 4 +language_tabs: false +toc_footers: [] +includes: [] +search: true +highlight_theme: '' +headingLevel: 2 +--- + +In streaming mode (`Accept: text/event-stream`), [Start Conversation](/docs/portai/chat/conversation) and [Continue Conversation](/docs/portai/chat/continue) push the run progress as a sequence of SSE events. This page lists every event type and how to parse it. + +## Frame Format + +Every SSE frame uses the same envelope: the SSE `event` field is always `message`, and the `data` field is a JSON object: + +``` +event: message +data: {"event":"","workflow_run_id":"745910371102313","data":{...}} +``` + +| Name | Type | Description | +| --------------- | ------ | ------------------------------------------------------------------ | +| event | string | Event type. Dispatch on this field | +| workflow_run_id | string | ID of this run, a numeric ID in string form (e.g. `"745910371102313"`); identical across all events of the same run | +| data | object | Event payload; structure varies by event type (see below) | + +Parsing rules: + +- **Dispatch on `data.event`**, not on the SSE `event` field (which is always `message`). +- **Ignore unknown event types.** New event types may be added at any time; a client that only handles the events it knows stays forward compatible. +- Unless noted otherwise, `started_at` / `finished_at` are Unix timestamps in seconds, and `elapsed_time` is a duration in seconds. + +## Typical Event Sequences + +A run always begins with `chat_started` and ends with `chat_finished`. What happens in between depends on the outcome: + +**Succeeded:** + +``` +chat_started → workflow_started → thinking_started → message (type=think) ... +→ node_tool_use_started / node_tool_use_finished ... +→ thinking_finished → message (type=answer) ... +→ workflow_finished (status=succeeded) → chat_finished +``` + +**Interrupted** (the Agent needs your input; resume via [Continue Conversation](/docs/portai/chat/continue)): + +``` +chat_started → workflow_started → ... → human_interaction_required → chat_finished +``` + +Note: an interrupted run does **not** emit `workflow_finished`. When you continue an interrupted run, the resumed stream does **not** emit `workflow_started` again. + +**Failed:** + +``` +chat_started → workflow_started → ... → workflow_finished (status=failed) → chat_finished +``` + +## Minimal Client + +For a plain question-and-answer integration you only need to handle four events; everything else is optional progress display: + +| Event | What to do | +| ---------------------------- | ------------------------------------------------------------------- | +| `message` (`type=answer`) | Append `data.text` to the answer being displayed | +| `human_interaction_required` | Show the questions and call Continue Conversation with the answers | +| `workflow_finished` | Read the final `status` and `outputs.answer`; show errors if failed | +| `chat_finished` | Close the stream; the run is over | + +## Conversation Lifecycle + +### chat_started + +The first event of every stream. The conversation and message records have been created; save `chat_uid` (for follow-up questions) and `message_id` (for continuing after an interrupt). + +| Name | Type | Description | +| ---------- | ------ | ---------------------------------------------- | +| chat_id | int64 | Internal ID of the conversation | +| chat_uid | string | Conversation identifier, used in follow-up requests | +| message_id | int64 | Message ID of this round | + +```json +{"event":"chat_started","workflow_run_id":"745910371102313","data":{"chat_id":1001,"chat_uid":"ct_9f2c1a5b","message_id":42}} +``` + +### chat_finished + +The last event of every stream. After this the server closes the connection. + +| Name | Type | Description | +| ------------- | ------ | ----------------------------------------------- | +| chat_id | int64 | Internal ID of the conversation | +| chat_uid | string | Conversation identifier | +| message_id | int64 | Message ID of this round | +| error | string | Error detail; empty on success | +| error_message | string | User-facing error message; empty on success | + +## Run Lifecycle + +### workflow_started + +The Agent run has started. Not emitted when resuming an interrupted run via Continue Conversation. + +| Name | Type | Description | +| ----------- | ------ | ------------------------------------------------------ | +| workflow_id | int64 | ID of the workflow backing this Agent | +| started_at | int64 | Start time (Unix seconds) | +| inputs | object | Run inputs | +| hit_cache | bool | `true` if the answer is served from cache; the stream then goes straight to the answer | + +### workflow_finished + +The run has ended — successfully, with a failure, or stopped by the user. Read the final result here. + +| Name | Type | Description | +| ------------- | -------- | ------------------------------------------------------------------------ | +| status | string | `succeeded` / `failed` / `stopped` | +| outputs | object | Run outputs; `outputs.answer` is the complete final answer text | +| elapsed_time | number | Run duration in seconds | +| error | string | Localized error description; only when `status` is `failed` | +| error_code | int32 | Error code; only when `status` is `failed` | +| error_message | string | User-facing error message; only on failure | +| error_args | object | Extra error context (e.g. `workflow_run_id`); may be omitted | +| process_data | object[] | Process stages the run went through; for display only | + +```json +{"event":"workflow_finished","workflow_run_id":"745910371102313","data":{"status":"succeeded","elapsed_time":3.21,"outputs":{"answer":"Tesla (TSLA.US) recently..."}}} +``` + +Notes: + +- `outputs.answer` is the authoritative full answer. If you assembled the answer from `message` events, you can use this to verify or replace it. +- When `status` is `stopped` (run stopped by the user), `outputs.answer` contains the partial answer generated so far. + +## Answer Streaming + +### message + +Incremental text chunks. This is the highest-frequency event; concatenate `text` fragments in arrival order. + +| Name | Type | Description | +| -------------------- | ------ | --------------------------------------------------------------------------- | +| text | string | Incremental text fragment | +| type | string | `answer` — final answer text; `think` — reasoning process; `process` — stage progress description | +| key | string | Identifier of the stream segment the fragment belongs to. Fragments with the same `key` form one continuous block — group by `key` when rendering | +| started_at | int64 | Time this segment started (Unix seconds) | +| stage | string | Stage identifier; only for `type=process` | +| stage_title | string | Stage title while running; only for `type=process` | +| stage_finished_title | string | Stage title after it finishes; only for `type=process` | +| outputs | object | Extra payload attached to the fragment; usually absent | + +```json +{"event":"message","workflow_run_id":"745910371102313","data":{"text":"Tesla","type":"answer","key":"n_llm_1:answer","started_at":1752048000}} +``` + +Parsing: + +- Only `type=answer` fragments belong to the user-visible answer. Concatenating all of them yields the same text as `workflow_finished.outputs.answer`. +- `type=think` fragments are the Agent's intermediate reasoning — show them in a collapsible "thinking" section or ignore them. +- `type=process` fragments describe stage progress and come with `stage` fields for grouping. + +## Thinking Phase + +### thinking_started + +The Agent has entered the reasoning phase (analyzing the question, planning tool calls). Between this and `thinking_finished`, `message` events with `type=think` and tool-call events may arrive. + +| Name | Type | Description | +| ---------- | ----- | ------------------------- | +| started_at | int64 | Start time (Unix seconds) | + +### thinking_finished + +The reasoning phase is over; answer text (`message` with `type=answer`) follows. + +| Name | Type | Description | +| ------------ | ----- | ----------------------------- | +| finished_at | int64 | Finish time (Unix seconds) | +| elapsed_time | int32 | Reasoning duration in seconds | + +## Tool Calls + +The Agent calls tools (market data, account info, web search, …) while generating the answer. Each call is bracketed by a started/finished pair — match them by `tool_use_id`. + +This pair covers **all ordinary tool calls**. Only two special cases are reported with their own event families instead: spawning a subagent (`subagent_*`, see below) and calling another Agent as a tool (`agent_tool_*`, see below). If your Agent uses neither feature, every tool call arrives as `node_tool_use_started` / `node_tool_use_finished`. + +### node_tool_use_started + +A tool call has started. + +| Name | Type | Description | +| -------------- | -------- | ------------------------------------------------------------------------------ | +| tool_use_id | string | Unique ID of this call; use it to match the finished event | +| tool_name | string | Localized display name of the tool (e.g. shown in your UI) | +| tool_func_name | string | Locale-stable tool identifier; use this for logic keyed on the tool kind | +| tool_args | string | Call arguments as a JSON string | +| tips | string | Progress text suitable for direct display (e.g. "Searching the web…") | +| tip_chips | string[] | Short tags accompanying `tips`; may be omitted | +| iteration | int | Round number. Calls in the same round (same `iteration`) run in parallel | +| started_at | int64 | Start time (Unix seconds) | + +### node_tool_use_finished + +The tool call has ended. + +| Name | Type | Description | +| -------------- | -------- | ---------------------------------------------------------------------- | +| tool_use_id | string | Matches the `tool_use_id` of the started event | +| status | string | `succeeded` / `failed` | +| error | string | Error description on failure | +| elapsed_time | number | Call duration in seconds | +| started_at | int64 | Start time (Unix seconds) | +| tool_name | string | Localized display name | +| tool_func_name | string | Locale-stable tool identifier | +| tool_args | string | Call arguments as a JSON string | +| tool_type | string | Tool category | +| tips | string | Progress text | +| tip_chips | string[] | Short tags; may be omitted | +| iteration | int | Round number | +| is_thinking | bool | `true` if the call happened during the thinking phase | +| outputs | object | Filtered call results, see below | + +`outputs` only carries fields that are meant for display: + +| Field | Description | +| ------------------------- | ----------------------------------------------------------------- | +| outputs.references | Sources referenced by the tool result | +| outputs.reference_domains | Domains of the referenced sources | +| outputs.query | The query the tool executed | +| outputs.text | Raw response text of the tool | +| outputs.tool_args | Parsed request arguments | +| outputs.data | Structured result; present only for selected tools | + +```json +{"event":"node_tool_use_finished","workflow_run_id":"745910371102313","data":{"tool_use_id":"call_abc123","status":"succeeded","elapsed_time":1.42,"tool_name":"Web Search","tool_func_name":"web_search","tool_args":"{\"query\":\"TSLA stock news\"}","tool_type":"builtin","tips":"Searched the web","iteration":1,"is_thinking":true,"outputs":{"query":"TSLA stock news","references":[{"index":1,"title":"...","url":"..."}]}}} +``` + +## Subagent Events + +When the Agent spawns a subagent to work on a sub-task, the subagent's lifecycle is reported with a dedicated event family instead of `node_tool_use_*`. + +### subagent_started + +| Name | Type | Description | +| ----------- | ------ | ----------------------------------------------- | +| node_id | string | ID of the node that spawned the subagent | +| tool_use_id | string | Unique ID of this spawn; matches the finished event | +| started_at | int64 | Start time (Unix seconds) | +| goal | string | Goal assigned to the subagent | +| prompt | string | Full task prompt given to the subagent | +| subagent_id | string | Subagent identifier; may be omitted | +| tools | array | Tools granted to the subagent; may be omitted | + +### subagent_progress + +Emitted every time the subagent calls one of its own tools. Use it to render a live timeline inside the subagent card. + +| Name | Type | Description | +| -------------------- | ------ | ------------------------------------------------------- | +| node_id | string | ID of the node that spawned the subagent | +| parent_tool_call_id | string | `tool_use_id` of the owning `subagent_started` event | +| subagent_tool_name | string | Name of the tool the subagent called | +| subagent_tool_args | string | Arguments of that call (JSON string) | +| subagent_status | string | Status of that call: `running` / `succeeded` / `failed` | +| subagent_duration_ms | int64 | Duration of that call in **milliseconds** | +| subagent_iteration | int | The subagent's internal round number | +| started_at | int64 | Start time (Unix seconds) | + +### subagent_finished + +| Name | Type | Description | +| ------------ | ------ | -------------------------------------------------------------------- | +| node_id | string | ID of the node that spawned the subagent | +| tool_use_id | string | Matches the `tool_use_id` of `subagent_started` | +| status | string | `succeeded` / `failed` | +| started_at | int64 | Start time (Unix seconds) | +| elapsed_time | number | Total subagent duration in seconds | +| error | string | Error description on failure | +| outputs | object | Subagent result: typically `goal`, `result`, and `subagent_tools` (the timeline of tool calls it made) | + +## Agent Tool Events + +When the Agent delegates to another Agent as a tool, that inner run is reported with the `agent_tool_*` family. The shape mirrors the subagent events. + +### agent_tool_started + +| Name | Type | Description | +| --------------- | -------- | -------------------------------------------------- | +| node_id | string | ID of the calling node | +| tool_use_id | string | Unique ID of this call; matches the finished event | +| agent_tool_name | string | Identifier of the Agent being called | +| title | string | Display title; may be omitted | +| started_at | int64 | Start time (Unix seconds) | +| tool_args | string | Call arguments (JSON string) | +| tool_name | string | Localized display name | +| tips | string | Progress text; may be omitted | +| tip_chips | string[] | Short tags; may be omitted | +| is_thinking | bool | `true` if called during the thinking phase | + +### agent_tool_progress + +Emitted for each inner tool call the delegated Agent makes. + +| Name | Type | Description | +| ------------------- | ------ | -------------------------------------------------------- | +| node_id | string | ID of the calling node | +| parent_tool_call_id | string | `tool_use_id` of the owning `agent_tool_started` event | +| agent_tool_name | string | Identifier of the Agent being called | +| inner_tool_name | string | Name of the inner tool the delegated Agent called | +| inner_tool_args | string | Arguments of that inner call (JSON string) | +| status | string | Status of the inner call: `running` / `succeeded` / `failed` | +| duration_ms | int64 | Duration of the inner call in **milliseconds** | +| started_at | int64 | Start time (Unix seconds) | +| is_thinking | bool | `true` if during the thinking phase | + +### agent_tool_finished + +| Name | Type | Description | +| --------------- | -------- | ------------------------------------------------- | +| node_id | string | ID of the calling node | +| tool_use_id | string | Matches the `tool_use_id` of `agent_tool_started` | +| agent_tool_name | string | Identifier of the Agent being called | +| status | string | `succeeded` / `failed` | +| started_at | int64 | Start time (Unix seconds) | +| elapsed_time | number | Total duration in seconds | +| error | string | Error description on failure | +| tool_args | string | Call arguments (JSON string) | +| outputs | object | Result of the delegated Agent | +| tool_type | string | Tool category | +| tips | string | Progress text; may be omitted | +| tip_chips | string[] | Short tags; may be omitted | +| is_thinking | bool | `true` if during the thinking phase | + +## Interrupt + +### human_interaction_required + +The run is paused: the Agent needs more information or confirmation from you. Collect answers to `questions` and call [Continue Conversation](/docs/portai/chat/continue) — the answers are keyed by `tool_call_id`. + +| Name | Type | Description | +| --------------- | -------- | ------------------------------------------------------------------ | +| node_id | string | ID of the node that triggered the interrupt | +| tool_call_id | string | ID of this inquiry; the outer key of `answers_by_tool_call` when continuing | +| questions | object[] | Questions you need to answer | +| ∟ question | string | Question text; the inner key of `answers_by_tool_call` | +| ∟ options | object[] | Options; empty means free-form answer | +| ∟∟ description | string | Option text | +| ∟ multi_select | boolean | Whether multiple options may be selected | +| message_id | int64 | ID of the paused message; used in the Continue Conversation URL | +| chat_id | int64 | ID of the owning conversation | + +```json +{"event":"human_interaction_required","workflow_run_id":"745910371102313","data":{"node_id":"n_ask_human","tool_call_id":"call_abc123","questions":[{"question":"Which time range would you like to check?","options":[{"description":"Past week"},{"description":"Past month"}],"multi_select":false}],"message_id":43,"chat_id":1001}} +``` + +After this event the stream ends with `chat_finished`; an interrupted run does not emit `workflow_finished`. + +## Auxiliary Events + +These events are informational; a minimal client can ignore all of them. + +### query_masked + +Sensitive content in the user query was masked before processing. Display `masked_query` instead of the original query. + +| Name | Type | Description | +| ------------ | ------ | ----------------------- | +| raw_query | string | The original user query | +| masked_query | string | The masked query | + +### plan_changed + +The Agent created or updated its task plan. + +| Name | Type | Description | +| ---------- | ------ | ---------------------------------------- | +| node_id | string | ID of the planning node | +| started_at | int64 | Time of the change (Unix seconds) | +| outputs | object | The current plan content | + +This event additionally carries a top-level `tool_name` field (sibling of `data`) identifying the planning tool. + +### context_compress_started / context_compress_finished + +Long conversations trigger context compression; these events bracket the compression. Unlike other events, the timestamps here are RFC 3339 strings. + +`context_compress_started`: + +| Name | Type | Description | +| ---------- | ------ | ------------------------------- | +| started_at | string | Start time (RFC 3339) | +| inputs | object | Compression input summary | + +`context_compress_finished`: + +| Name | Type | Description | +| ---------- | ------ | ------------------------------- | +| created_at | string | Finish time (RFC 3339) | +| inputs | object | Compression input summary | +| outputs | object | Compression result summary | diff --git a/docs/zh-CN/docs/portai/chat/conversation.md b/docs/zh-CN/docs/portai/chat/conversation.md index f6907059..8f7e68f7 100644 --- a/docs/zh-CN/docs/portai/chat/conversation.md +++ b/docs/zh-CN/docs/portai/chat/conversation.md @@ -138,15 +138,17 @@ curl -N -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversati ``` event: message -data: {"event":"chat_started","workflow_run_id":"wr_...","data":{"chat_uid":"ct_9f2c1a5b","message_id":42}} +data: {"event":"chat_started","workflow_run_id":"745910371102313","data":{"chat_uid":"ct_9f2c1a5b","message_id":42}} event: message -data: {"event":"message","workflow_run_id":"wr_...","data":{"text":"特斯拉"}} +data: {"event":"message","workflow_run_id":"745910371102313","data":{"text":"特斯拉"}} event: message -data: {"event":"workflow_finished","workflow_run_id":"wr_...","data":{"status":"succeeded","elapsed_time":3.21,"outputs":{"answer":"..."}}} +data: {"event":"workflow_finished","workflow_run_id":"745910371102313","data":{"status":"succeeded","elapsed_time":3.21,"outputs":{"answer":"..."}}} ``` +完整的事件类型列表、负载结构与解析方式见 [SSE 事件](/zh-CN/docs/portai/chat/events)。 + ### Response Status | Status | Description | Schema | diff --git a/docs/zh-CN/docs/portai/chat/events.md b/docs/zh-CN/docs/portai/chat/events.md new file mode 100644 index 00000000..7c0c0022 --- /dev/null +++ b/docs/zh-CN/docs/portai/chat/events.md @@ -0,0 +1,404 @@ +--- +slug: events +title: SSE 事件 +sidebar_position: 4 +language_tabs: false +toc_footers: [] +includes: [] +search: true +highlight_theme: '' +headingLevel: 2 +--- + +流式模式(`Accept: text/event-stream`)下,[发起对话](/zh-CN/docs/portai/chat/conversation)和[继续对话](/zh-CN/docs/portai/chat/continue)会以一系列 SSE 事件推送运行过程。本页列出所有事件类型及解析方式。 + +## 帧格式 + +每一帧 SSE 都使用同样的信封结构:SSE 的 `event` 字段恒为 `message`,`data` 字段是一个 JSON 对象: + +``` +event: message +data: {"event":"","workflow_run_id":"745910371102313","data":{...}} +``` + +| 名称 | 类型 | 说明 | +| --------------- | ------ | ------------------------------------------------ | +| event | string | 事件类型,按此字段分发处理 | +| workflow_run_id | string | 本次运行的 ID,为数字 ID 的字符串形式(如 `"745910371102313"`),同一次运行的所有事件中该值一致 | +| data | object | 事件负载,结构随事件类型而不同(见下文) | + +解析规则: + +- **按 `data.event` 分发**,而不是 SSE 的 `event` 字段(它恒为 `message`)。 +- **忽略未知事件类型。** 事件类型可能随时新增,只处理已知事件的客户端可保持向前兼容。 +- 除特别说明外,`started_at` / `finished_at` 为 Unix 秒级时间戳,`elapsed_time` 为秒数。 + +## 典型事件序列 + +一次运行始于 `chat_started`、止于 `chat_finished`,中间的事件取决于运行结果: + +**成功:** + +``` +chat_started → workflow_started → thinking_started → message (type=think) ... +→ node_tool_use_started / node_tool_use_finished ... +→ thinking_finished → message (type=answer) ... +→ workflow_finished (status=succeeded) → chat_finished +``` + +**中断**(Agent 需要你补充信息,通过[继续对话](/zh-CN/docs/portai/chat/continue)恢复): + +``` +chat_started → workflow_started → ... → human_interaction_required → chat_finished +``` + +注意:中断的运行**不会**发送 `workflow_finished`;继续被中断的运行时,恢复后的流**不会**再次发送 `workflow_started`。 + +**失败:** + +``` +chat_started → workflow_started → ... → workflow_finished (status=failed) → chat_finished +``` + +## 最简客户端 + +纯问答场景只需处理四种事件,其余事件都是可选的过程展示: + +| 事件 | 处理方式 | +| ---------------------------- | ------------------------------------------------------ | +| `message`(`type=answer`) | 将 `data.text` 追加到正在展示的回答中 | +| `human_interaction_required` | 展示问题,携带答案调用继续对话接口 | +| `workflow_finished` | 读取最终 `status` 和 `outputs.answer`;失败时展示错误 | +| `chat_finished` | 关闭流,本次运行结束 | + +## 会话生命周期 + +### chat_started + +每个流的第一个事件。会话和消息记录已创建;保存 `chat_uid`(用于追问)和 `message_id`(用于中断后继续)。 + +| 名称 | 类型 | 说明 | +| ---------- | ------ | -------------------------- | +| chat_id | int64 | 会话内部 ID | +| chat_uid | string | 会话标识,用于后续请求 | +| message_id | int64 | 本轮消息 ID | + +```json +{"event":"chat_started","workflow_run_id":"745910371102313","data":{"chat_id":1001,"chat_uid":"ct_9f2c1a5b","message_id":42}} +``` + +### chat_finished + +每个流的最后一个事件,之后服务端关闭连接。 + +| 名称 | 类型 | 说明 | +| ------------- | ------ | ------------------------------ | +| chat_id | int64 | 会话内部 ID | +| chat_uid | string | 会话标识 | +| message_id | int64 | 本轮消息 ID | +| error | string | 错误详情,成功时为空 | +| error_message | string | 面向用户的错误信息,成功时为空 | + +## 运行生命周期 + +### workflow_started + +Agent 运行已开始。通过继续对话恢复被中断的运行时不会发送此事件。 + +| 名称 | 类型 | 说明 | +| ----------- | ------ | ------------------------------------------------------ | +| workflow_id | int64 | 该 Agent 底层工作流的 ID | +| started_at | int64 | 开始时间(Unix 秒) | +| inputs | object | 运行输入 | +| hit_cache | bool | 为 `true` 时表示答案命中缓存,流会直接进入回答阶段 | + +### workflow_finished + +运行已结束——成功、失败或被用户停止。在此读取最终结果。 + +| 名称 | 类型 | 说明 | +| ------------- | -------- | ---------------------------------------------------------- | +| status | string | `succeeded` / `failed` / `stopped` | +| outputs | object | 运行输出;`outputs.answer` 为完整的最终回答文本 | +| elapsed_time | number | 运行耗时(秒) | +| error | string | 本地化的错误描述,仅 `status` 为 `failed` 时存在 | +| error_code | int32 | 错误码,仅 `status` 为 `failed` 时存在 | +| error_message | string | 面向用户的错误信息,仅失败时存在 | +| error_args | object | 额外错误上下文(如 `workflow_run_id`),可能省略 | +| process_data | object[] | 运行经过的过程阶段,仅用于展示 | + +```json +{"event":"workflow_finished","workflow_run_id":"745910371102313","data":{"status":"succeeded","elapsed_time":3.21,"outputs":{"answer":"特斯拉(TSLA.US)近期..."}}} +``` + +说明: + +- `outputs.answer` 是权威的完整回答。如果你通过 `message` 事件拼接了回答,可用它校验或替换。 +- `status` 为 `stopped`(用户停止运行)时,`outputs.answer` 为已生成的部分回答。 + +## 回答流式输出 + +### message + +增量文本片段。这是频率最高的事件,按到达顺序拼接 `text` 即可。 + +| 名称 | 类型 | 说明 | +| -------------------- | ------ | ------------------------------------------------------------------------ | +| text | string | 增量文本片段 | +| type | string | `answer` — 最终回答文本;`think` — 推理过程;`process` — 阶段进度描述 | +| key | string | 片段所属流段的标识。相同 `key` 的片段构成一个连续块,渲染时按 `key` 分组 | +| started_at | int64 | 该流段开始时间(Unix 秒) | +| stage | string | 阶段标识,仅 `type=process` 时存在 | +| stage_title | string | 阶段进行中的标题,仅 `type=process` 时存在 | +| stage_finished_title | string | 阶段完成后的标题,仅 `type=process` 时存在 | +| outputs | object | 附加在片段上的额外负载,通常不存在 | + +```json +{"event":"message","workflow_run_id":"745910371102313","data":{"text":"特斯拉","type":"answer","key":"n_llm_1:answer","started_at":1752048000}} +``` + +解析: + +- 只有 `type=answer` 的片段属于用户可见的回答,全部拼接后与 `workflow_finished.outputs.answer` 一致。 +- `type=think` 的片段是 Agent 的中间推理,可放入可折叠的「思考」区域展示,也可忽略。 +- `type=process` 的片段描述阶段进度,附带 `stage` 系列字段用于分组。 + +## 思考阶段 + +### thinking_started + +Agent 进入推理阶段(分析问题、规划工具调用)。在它与 `thinking_finished` 之间可能出现 `type=think` 的 `message` 事件和工具调用事件。 + +| 名称 | 类型 | 说明 | +| ---------- | ----- | -------------------- | +| started_at | int64 | 开始时间(Unix 秒) | + +### thinking_finished + +推理阶段结束,随后是回答文本(`type=answer` 的 `message`)。 + +| 名称 | 类型 | 说明 | +| ------------ | ----- | -------------------- | +| finished_at | int64 | 结束时间(Unix 秒) | +| elapsed_time | int32 | 推理耗时(秒) | + +## 工具调用 + +Agent 在生成回答的过程中会调用工具(行情、账户、联网搜索等)。每次调用都由一对 started/finished 事件包裹——用 `tool_use_id` 配对。 + +这对事件覆盖**所有普通工具调用**。只有两种特殊调用改用各自的事件族上报:派生子智能体(`subagent_*`,见下文)和把另一个 Agent 作为工具调用(`agent_tool_*`,见下文)。如果你的 Agent 没有使用这两项能力,所有工具调用都只会以 `node_tool_use_started` / `node_tool_use_finished` 出现。 + +### node_tool_use_started + +一次工具调用已开始。 + +| 名称 | 类型 | 说明 | +| -------------- | -------- | --------------------------------------------------------------- | +| tool_use_id | string | 本次调用的唯一 ID,用于与 finished 事件配对 | +| tool_name | string | 工具的本地化展示名(用于界面显示) | +| tool_func_name | string | 与语言无关的稳定工具标识,按工具类型处理逻辑时用它 | +| tool_args | string | 调用参数(JSON 字符串) | +| tips | string | 可直接展示的进度文案(如「正在联网搜索…」) | +| tip_chips | string[] | 与 `tips` 配套的短标签,可能省略 | +| iteration | int | 轮次编号。同一轮(相同 `iteration`)的调用是并行执行的 | +| started_at | int64 | 开始时间(Unix 秒) | + +### node_tool_use_finished + +工具调用已结束。 + +| 名称 | 类型 | 说明 | +| -------------- | -------- | ---------------------------------------- | +| tool_use_id | string | 与 started 事件的 `tool_use_id` 一致 | +| status | string | `succeeded` / `failed` | +| error | string | 失败时的错误描述 | +| elapsed_time | number | 调用耗时(秒) | +| started_at | int64 | 开始时间(Unix 秒) | +| tool_name | string | 本地化展示名 | +| tool_func_name | string | 稳定工具标识 | +| tool_args | string | 调用参数(JSON 字符串) | +| tool_type | string | 工具类别 | +| tips | string | 进度文案 | +| tip_chips | string[] | 短标签,可能省略 | +| iteration | int | 轮次编号 | +| is_thinking | bool | 为 `true` 表示调用发生在思考阶段 | +| outputs | object | 过滤后的调用结果,见下 | + +`outputs` 只携带用于展示的字段: + +| 字段 | 说明 | +| ------------------------- | -------------------------------- | +| outputs.references | 工具结果引用的来源 | +| outputs.reference_domains | 引用来源的域名 | +| outputs.query | 工具执行的查询 | +| outputs.text | 工具的原始响应文本 | +| outputs.tool_args | 解析后的请求参数 | +| outputs.data | 结构化结果,仅部分工具存在 | + +```json +{"event":"node_tool_use_finished","workflow_run_id":"745910371102313","data":{"tool_use_id":"call_abc123","status":"succeeded","elapsed_time":1.42,"tool_name":"联网搜索","tool_func_name":"web_search","tool_args":"{\"query\":\"TSLA stock news\"}","tool_type":"builtin","tips":"已联网搜索","iteration":1,"is_thinking":true,"outputs":{"query":"TSLA stock news","references":[{"index":1,"title":"...","url":"..."}]}}} +``` + +## 子智能体事件 + +Agent 派生子智能体处理子任务时,其生命周期使用独立的事件族上报,而不是 `node_tool_use_*`。 + +### subagent_started + +| 名称 | 类型 | 说明 | +| ----------- | ------ | ------------------------------------------ | +| node_id | string | 派生子智能体的节点 ID | +| tool_use_id | string | 本次派生的唯一 ID,与 finished 事件配对 | +| started_at | int64 | 开始时间(Unix 秒) | +| goal | string | 分配给子智能体的目标 | +| prompt | string | 交给子智能体的完整任务提示词 | +| subagent_id | string | 子智能体标识,可能省略 | +| tools | array | 授予子智能体的工具列表,可能省略 | + +### subagent_progress + +子智能体每调用一次自己的工具就发送一次,用于在子智能体卡片内实时渲染时间线。 + +| 名称 | 类型 | 说明 | +| -------------------- | ------ | ------------------------------------------------ | +| node_id | string | 派生子智能体的节点 ID | +| parent_tool_call_id | string | 所属 `subagent_started` 事件的 `tool_use_id` | +| subagent_tool_name | string | 子智能体调用的工具名 | +| subagent_tool_args | string | 该调用的参数(JSON 字符串) | +| subagent_status | string | 该调用的状态:`running` / `succeeded` / `failed` | +| subagent_duration_ms | int64 | 该调用耗时(**毫秒**) | +| subagent_iteration | int | 子智能体内部的轮次编号 | +| started_at | int64 | 开始时间(Unix 秒) | + +### subagent_finished + +| 名称 | 类型 | 说明 | +| ------------ | ------ | ------------------------------------------------------------------------ | +| node_id | string | 派生子智能体的节点 ID | +| tool_use_id | string | 与 `subagent_started` 的 `tool_use_id` 一致 | +| status | string | `succeeded` / `failed` | +| started_at | int64 | 开始时间(Unix 秒) | +| elapsed_time | number | 子智能体总耗时(秒) | +| error | string | 失败时的错误描述 | +| outputs | object | 子智能体结果:通常包含 `goal`、`result` 和 `subagent_tools`(其工具调用时间线) | + +## Agent 工具事件 + +Agent 把另一个 Agent 作为工具调用时,该内部运行使用 `agent_tool_*` 事件族上报,结构与子智能体事件相似。 + +### agent_tool_started + +| 名称 | 类型 | 说明 | +| --------------- | -------- | --------------------------------------- | +| node_id | string | 调用方节点 ID | +| tool_use_id | string | 本次调用的唯一 ID,与 finished 事件配对 | +| agent_tool_name | string | 被调用 Agent 的标识 | +| title | string | 展示标题,可能省略 | +| started_at | int64 | 开始时间(Unix 秒) | +| tool_args | string | 调用参数(JSON 字符串) | +| tool_name | string | 本地化展示名 | +| tips | string | 进度文案,可能省略 | +| tip_chips | string[] | 短标签,可能省略 | +| is_thinking | bool | 为 `true` 表示发生在思考阶段 | + +### agent_tool_progress + +被委托 Agent 每进行一次内部工具调用就发送一次。 + +| 名称 | 类型 | 说明 | +| ------------------- | ------ | ---------------------------------------------------- | +| node_id | string | 调用方节点 ID | +| parent_tool_call_id | string | 所属 `agent_tool_started` 事件的 `tool_use_id` | +| agent_tool_name | string | 被调用 Agent 的标识 | +| inner_tool_name | string | 被委托 Agent 调用的内部工具名 | +| inner_tool_args | string | 该内部调用的参数(JSON 字符串) | +| status | string | 内部调用的状态:`running` / `succeeded` / `failed` | +| duration_ms | int64 | 内部调用耗时(**毫秒**) | +| started_at | int64 | 开始时间(Unix 秒) | +| is_thinking | bool | 为 `true` 表示发生在思考阶段 | + +### agent_tool_finished + +| 名称 | 类型 | 说明 | +| --------------- | -------- | --------------------------------------------- | +| node_id | string | 调用方节点 ID | +| tool_use_id | string | 与 `agent_tool_started` 的 `tool_use_id` 一致 | +| agent_tool_name | string | 被调用 Agent 的标识 | +| status | string | `succeeded` / `failed` | +| started_at | int64 | 开始时间(Unix 秒) | +| elapsed_time | number | 总耗时(秒) | +| error | string | 失败时的错误描述 | +| tool_args | string | 调用参数(JSON 字符串) | +| outputs | object | 被委托 Agent 的结果 | +| tool_type | string | 工具类别 | +| tips | string | 进度文案,可能省略 | +| tip_chips | string[] | 短标签,可能省略 | +| is_thinking | bool | 为 `true` 表示发生在思考阶段 | + +## 中断 + +### human_interaction_required + +运行已暂停:Agent 需要你补充信息或确认。收集 `questions` 的答案后调用[继续对话](/zh-CN/docs/portai/chat/continue)——答案以 `tool_call_id` 为键。 + +| 名称 | 类型 | 说明 | +| -------------- | -------- | ----------------------------------------------------------- | +| node_id | string | 触发中断的节点 ID | +| tool_call_id | string | 本次询问的 ID,继续对话时作为 `answers_by_tool_call` 的外层键 | +| questions | object[] | 需要回答的问题 | +| ∟ question | string | 问题文本,继续对话时作为 `answers_by_tool_call` 的内层键 | +| ∟ options | object[] | 选项,为空表示自由输入 | +| ∟∟ description | string | 选项文本 | +| ∟ multi_select | boolean | 是否可多选 | +| message_id | int64 | 被暂停消息的 ID,用于继续对话接口的 URL | +| chat_id | int64 | 所属会话的 ID | + +```json +{"event":"human_interaction_required","workflow_run_id":"745910371102313","data":{"node_id":"n_ask_human","tool_call_id":"call_abc123","questions":[{"question":"你想查看哪个时间范围?","options":[{"description":"近一周"},{"description":"近一月"}],"multi_select":false}],"message_id":43,"chat_id":1001}} +``` + +此事件之后流以 `chat_finished` 结束;被中断的运行不会发送 `workflow_finished`。 + +## 辅助事件 + +以下事件均为信息性事件,最简客户端可全部忽略。 + +### query_masked + +用户提问中的敏感内容在处理前被脱敏。展示时用 `masked_query` 替换原始提问。 + +| 名称 | 类型 | 说明 | +| ------------ | ------ | -------------- | +| raw_query | string | 原始用户提问 | +| masked_query | string | 脱敏后的提问 | + +### plan_changed + +Agent 创建或更新了任务计划。 + +| 名称 | 类型 | 说明 | +| ---------- | ------ | ------------------------ | +| node_id | string | 规划节点的 ID | +| started_at | int64 | 变更时间(Unix 秒) | +| outputs | object | 当前计划内容 | + +此事件额外携带一个顶层 `tool_name` 字段(与 `data` 同级),标识规划工具。 + +### context_compress_started / context_compress_finished + +长对话会触发上下文压缩,这两个事件包裹压缩过程。与其他事件不同,这里的时间戳是 RFC 3339 字符串。 + +`context_compress_started`: + +| 名称 | 类型 | 说明 | +| ---------- | ------ | -------------------- | +| started_at | string | 开始时间(RFC 3339) | +| inputs | object | 压缩输入摘要 | + +`context_compress_finished`: + +| 名称 | 类型 | 说明 | +| ---------- | ------ | -------------------- | +| created_at | string | 结束时间(RFC 3339) | +| inputs | object | 压缩输入摘要 | +| outputs | object | 压缩结果摘要 | diff --git a/docs/zh-HK/docs/portai/chat/conversation.md b/docs/zh-HK/docs/portai/chat/conversation.md index fda6cf2a..fc5fd494 100644 --- a/docs/zh-HK/docs/portai/chat/conversation.md +++ b/docs/zh-HK/docs/portai/chat/conversation.md @@ -138,15 +138,17 @@ curl -N -X POST "https://openapi.longbridge.com/v1/babbage/agents/123/conversati ``` event: message -data: {"event":"chat_started","workflow_run_id":"wr_...","data":{"chat_uid":"ct_9f2c1a5b","message_id":42}} +data: {"event":"chat_started","workflow_run_id":"745910371102313","data":{"chat_uid":"ct_9f2c1a5b","message_id":42}} event: message -data: {"event":"message","workflow_run_id":"wr_...","data":{"text":"特斯拉"}} +data: {"event":"message","workflow_run_id":"745910371102313","data":{"text":"特斯拉"}} event: message -data: {"event":"workflow_finished","workflow_run_id":"wr_...","data":{"status":"succeeded","elapsed_time":3.21,"outputs":{"answer":"..."}}} +data: {"event":"workflow_finished","workflow_run_id":"745910371102313","data":{"status":"succeeded","elapsed_time":3.21,"outputs":{"answer":"..."}}} ``` +完整的事件類型列表、負載結構與解析方式見 [SSE 事件](/zh-HK/docs/portai/chat/events)。 + ### Response Status | Status | Description | Schema | diff --git a/docs/zh-HK/docs/portai/chat/events.md b/docs/zh-HK/docs/portai/chat/events.md new file mode 100644 index 00000000..1c9ce2bb --- /dev/null +++ b/docs/zh-HK/docs/portai/chat/events.md @@ -0,0 +1,404 @@ +--- +slug: events +title: SSE 事件 +sidebar_position: 4 +language_tabs: false +toc_footers: [] +includes: [] +search: true +highlight_theme: '' +headingLevel: 2 +--- + +流式模式(`Accept: text/event-stream`)下,[發起對話](/zh-HK/docs/portai/chat/conversation)和[繼續對話](/zh-HK/docs/portai/chat/continue)會以一系列 SSE 事件推送運行過程。本頁列出所有事件類型及解析方式。 + +## 幀格式 + +每一幀 SSE 都使用同樣的信封結構:SSE 的 `event` 字段恆為 `message`,`data` 字段是一個 JSON 對象: + +``` +event: message +data: {"event":"","workflow_run_id":"745910371102313","data":{...}} +``` + +| 名稱 | 類型 | 說明 | +| --------------- | ------ | ------------------------------------------------ | +| event | string | 事件類型,按此字段分發處理 | +| workflow_run_id | string | 本次運行的 ID,為數字 ID 的字符串形式(如 `"745910371102313"`),同一次運行的所有事件中該值一致 | +| data | object | 事件負載,結構隨事件類型而不同(見下文) | + +解析規則: + +- **按 `data.event` 分發**,而不是 SSE 的 `event` 字段(它恆為 `message`)。 +- **忽略未知事件類型。** 事件類型可能隨時新增,只處理已知事件的客戶端可保持向前兼容。 +- 除特別說明外,`started_at` / `finished_at` 為 Unix 秒級時間戳,`elapsed_time` 為秒數。 + +## 典型事件序列 + +一次運行始於 `chat_started`、止於 `chat_finished`,中間的事件取決於運行結果: + +**成功:** + +``` +chat_started → workflow_started → thinking_started → message (type=think) ... +→ node_tool_use_started / node_tool_use_finished ... +→ thinking_finished → message (type=answer) ... +→ workflow_finished (status=succeeded) → chat_finished +``` + +**中斷**(Agent 需要你補充信息,通過[繼續對話](/zh-HK/docs/portai/chat/continue)恢復): + +``` +chat_started → workflow_started → ... → human_interaction_required → chat_finished +``` + +注意:中斷的運行**不會**發送 `workflow_finished`;繼續被中斷的運行時,恢復後的流**不會**再次發送 `workflow_started`。 + +**失敗:** + +``` +chat_started → workflow_started → ... → workflow_finished (status=failed) → chat_finished +``` + +## 最簡客戶端 + +純問答場景只需處理四種事件,其餘事件都是可選的過程展示: + +| 事件 | 處理方式 | +| ---------------------------- | ------------------------------------------------------ | +| `message`(`type=answer`) | 將 `data.text` 追加到正在展示的回答中 | +| `human_interaction_required` | 展示問題,攜帶答案調用繼續對話接口 | +| `workflow_finished` | 讀取最終 `status` 和 `outputs.answer`;失敗時展示錯誤 | +| `chat_finished` | 關閉流,本次運行結束 | + +## 會話生命週期 + +### chat_started + +每個流的第一個事件。會話和消息記錄已創建;保存 `chat_uid`(用於追問)和 `message_id`(用於中斷後繼續)。 + +| 名稱 | 類型 | 說明 | +| ---------- | ------ | -------------------------- | +| chat_id | int64 | 會話內部 ID | +| chat_uid | string | 會話標識,用於後續請求 | +| message_id | int64 | 本輪消息 ID | + +```json +{"event":"chat_started","workflow_run_id":"745910371102313","data":{"chat_id":1001,"chat_uid":"ct_9f2c1a5b","message_id":42}} +``` + +### chat_finished + +每個流的最後一個事件,之後服務端關閉連接。 + +| 名稱 | 類型 | 說明 | +| ------------- | ------ | ------------------------------ | +| chat_id | int64 | 會話內部 ID | +| chat_uid | string | 會話標識 | +| message_id | int64 | 本輪消息 ID | +| error | string | 錯誤詳情,成功時為空 | +| error_message | string | 面向用戶的錯誤信息,成功時為空 | + +## 運行生命週期 + +### workflow_started + +Agent 運行已開始。通過繼續對話恢復被中斷的運行時不會發送此事件。 + +| 名稱 | 類型 | 說明 | +| ----------- | ------ | ------------------------------------------------------ | +| workflow_id | int64 | 該 Agent 底層工作流的 ID | +| started_at | int64 | 開始時間(Unix 秒) | +| inputs | object | 運行輸入 | +| hit_cache | bool | 為 `true` 時表示答案命中緩存,流會直接進入回答階段 | + +### workflow_finished + +運行已結束——成功、失敗或被用戶停止。在此讀取最終結果。 + +| 名稱 | 類型 | 說明 | +| ------------- | -------- | ---------------------------------------------------------- | +| status | string | `succeeded` / `failed` / `stopped` | +| outputs | object | 運行輸出;`outputs.answer` 為完整的最終回答文本 | +| elapsed_time | number | 運行耗時(秒) | +| error | string | 本地化的錯誤描述,僅 `status` 為 `failed` 時存在 | +| error_code | int32 | 錯誤碼,僅 `status` 為 `failed` 時存在 | +| error_message | string | 面向用戶的錯誤信息,僅失敗時存在 | +| error_args | object | 額外錯誤上下文(如 `workflow_run_id`),可能省略 | +| process_data | object[] | 運行經過的過程階段,僅用於展示 | + +```json +{"event":"workflow_finished","workflow_run_id":"745910371102313","data":{"status":"succeeded","elapsed_time":3.21,"outputs":{"answer":"特斯拉(TSLA.US)近期..."}}} +``` + +說明: + +- `outputs.answer` 是權威的完整回答。如果你通過 `message` 事件拼接了回答,可用它校驗或替換。 +- `status` 為 `stopped`(用戶停止運行)時,`outputs.answer` 為已生成的部分回答。 + +## 回答流式輸出 + +### message + +增量文本片段。這是頻率最高的事件,按到達順序拼接 `text` 即可。 + +| 名稱 | 類型 | 說明 | +| -------------------- | ------ | ------------------------------------------------------------------------ | +| text | string | 增量文本片段 | +| type | string | `answer` — 最終回答文本;`think` — 推理過程;`process` — 階段進度描述 | +| key | string | 片段所屬流段的標識。相同 `key` 的片段構成一個連續塊,渲染時按 `key` 分組 | +| started_at | int64 | 該流段開始時間(Unix 秒) | +| stage | string | 階段標識,僅 `type=process` 時存在 | +| stage_title | string | 階段進行中的標題,僅 `type=process` 時存在 | +| stage_finished_title | string | 階段完成後的標題,僅 `type=process` 時存在 | +| outputs | object | 附加在片段上的額外負載,通常不存在 | + +```json +{"event":"message","workflow_run_id":"745910371102313","data":{"text":"特斯拉","type":"answer","key":"n_llm_1:answer","started_at":1752048000}} +``` + +解析: + +- 只有 `type=answer` 的片段屬於用戶可見的回答,全部拼接後與 `workflow_finished.outputs.answer` 一致。 +- `type=think` 的片段是 Agent 的中間推理,可放入可摺疊的「思考」區域展示,也可忽略。 +- `type=process` 的片段描述階段進度,附帶 `stage` 系列字段用於分組。 + +## 思考階段 + +### thinking_started + +Agent 進入推理階段(分析問題、規劃工具調用)。在它與 `thinking_finished` 之間可能出現 `type=think` 的 `message` 事件和工具調用事件。 + +| 名稱 | 類型 | 說明 | +| ---------- | ----- | -------------------- | +| started_at | int64 | 開始時間(Unix 秒) | + +### thinking_finished + +推理階段結束,隨後是回答文本(`type=answer` 的 `message`)。 + +| 名稱 | 類型 | 說明 | +| ------------ | ----- | -------------------- | +| finished_at | int64 | 結束時間(Unix 秒) | +| elapsed_time | int32 | 推理耗時(秒) | + +## 工具調用 + +Agent 在生成回答的過程中會調用工具(行情、賬戶、聯網搜索等)。每次調用都由一對 started/finished 事件包裹——用 `tool_use_id` 配對。 + +這對事件覆蓋**所有普通工具調用**。只有兩種特殊調用改用各自的事件族上報:派生子智能體(`subagent_*`,見下文)和把另一個 Agent 作為工具調用(`agent_tool_*`,見下文)。如果你的 Agent 沒有使用這兩項能力,所有工具調用都只會以 `node_tool_use_started` / `node_tool_use_finished` 出現。 + +### node_tool_use_started + +一次工具調用已開始。 + +| 名稱 | 類型 | 說明 | +| -------------- | -------- | --------------------------------------------------------------- | +| tool_use_id | string | 本次調用的唯一 ID,用於與 finished 事件配對 | +| tool_name | string | 工具的本地化展示名(用於界面顯示) | +| tool_func_name | string | 與語言無關的穩定工具標識,按工具類型處理邏輯時用它 | +| tool_args | string | 調用參數(JSON 字符串) | +| tips | string | 可直接展示的進度文案(如「正在聯網搜索…」) | +| tip_chips | string[] | 與 `tips` 配套的短標籤,可能省略 | +| iteration | int | 輪次編號。同一輪(相同 `iteration`)的調用是並行執行的 | +| started_at | int64 | 開始時間(Unix 秒) | + +### node_tool_use_finished + +工具調用已結束。 + +| 名稱 | 類型 | 說明 | +| -------------- | -------- | ---------------------------------------- | +| tool_use_id | string | 與 started 事件的 `tool_use_id` 一致 | +| status | string | `succeeded` / `failed` | +| error | string | 失敗時的錯誤描述 | +| elapsed_time | number | 調用耗時(秒) | +| started_at | int64 | 開始時間(Unix 秒) | +| tool_name | string | 本地化展示名 | +| tool_func_name | string | 穩定工具標識 | +| tool_args | string | 調用參數(JSON 字符串) | +| tool_type | string | 工具類別 | +| tips | string | 進度文案 | +| tip_chips | string[] | 短標籤,可能省略 | +| iteration | int | 輪次編號 | +| is_thinking | bool | 為 `true` 表示調用發生在思考階段 | +| outputs | object | 過濾後的調用結果,見下 | + +`outputs` 只攜帶用於展示的字段: + +| 字段 | 說明 | +| ------------------------- | -------------------------------- | +| outputs.references | 工具結果引用的來源 | +| outputs.reference_domains | 引用來源的域名 | +| outputs.query | 工具執行的查詢 | +| outputs.text | 工具的原始響應文本 | +| outputs.tool_args | 解析後的請求參數 | +| outputs.data | 結構化結果,僅部分工具存在 | + +```json +{"event":"node_tool_use_finished","workflow_run_id":"745910371102313","data":{"tool_use_id":"call_abc123","status":"succeeded","elapsed_time":1.42,"tool_name":"聯網搜索","tool_func_name":"web_search","tool_args":"{\"query\":\"TSLA stock news\"}","tool_type":"builtin","tips":"已聯網搜索","iteration":1,"is_thinking":true,"outputs":{"query":"TSLA stock news","references":[{"index":1,"title":"...","url":"..."}]}}} +``` + +## 子智能體事件 + +Agent 派生子智能體處理子任務時,其生命週期使用獨立的事件族上報,而不是 `node_tool_use_*`。 + +### subagent_started + +| 名稱 | 類型 | 說明 | +| ----------- | ------ | ------------------------------------------ | +| node_id | string | 派生子智能體的節點 ID | +| tool_use_id | string | 本次派生的唯一 ID,與 finished 事件配對 | +| started_at | int64 | 開始時間(Unix 秒) | +| goal | string | 分配給子智能體的目標 | +| prompt | string | 交給子智能體的完整任務提示詞 | +| subagent_id | string | 子智能體標識,可能省略 | +| tools | array | 授予子智能體的工具列表,可能省略 | + +### subagent_progress + +子智能體每調用一次自己的工具就發送一次,用於在子智能體卡片內實時渲染時間線。 + +| 名稱 | 類型 | 說明 | +| -------------------- | ------ | ------------------------------------------------ | +| node_id | string | 派生子智能體的節點 ID | +| parent_tool_call_id | string | 所屬 `subagent_started` 事件的 `tool_use_id` | +| subagent_tool_name | string | 子智能體調用的工具名 | +| subagent_tool_args | string | 該調用的參數(JSON 字符串) | +| subagent_status | string | 該調用的狀態:`running` / `succeeded` / `failed` | +| subagent_duration_ms | int64 | 該調用耗時(**毫秒**) | +| subagent_iteration | int | 子智能體內部的輪次編號 | +| started_at | int64 | 開始時間(Unix 秒) | + +### subagent_finished + +| 名稱 | 類型 | 說明 | +| ------------ | ------ | ------------------------------------------------------------------------ | +| node_id | string | 派生子智能體的節點 ID | +| tool_use_id | string | 與 `subagent_started` 的 `tool_use_id` 一致 | +| status | string | `succeeded` / `failed` | +| started_at | int64 | 開始時間(Unix 秒) | +| elapsed_time | number | 子智能體總耗時(秒) | +| error | string | 失敗時的錯誤描述 | +| outputs | object | 子智能體結果:通常包含 `goal`、`result` 和 `subagent_tools`(其工具調用時間線) | + +## Agent 工具事件 + +Agent 把另一個 Agent 作為工具調用時,該內部運行使用 `agent_tool_*` 事件族上報,結構與子智能體事件相似。 + +### agent_tool_started + +| 名稱 | 類型 | 說明 | +| --------------- | -------- | --------------------------------------- | +| node_id | string | 調用方節點 ID | +| tool_use_id | string | 本次調用的唯一 ID,與 finished 事件配對 | +| agent_tool_name | string | 被調用 Agent 的標識 | +| title | string | 展示標題,可能省略 | +| started_at | int64 | 開始時間(Unix 秒) | +| tool_args | string | 調用參數(JSON 字符串) | +| tool_name | string | 本地化展示名 | +| tips | string | 進度文案,可能省略 | +| tip_chips | string[] | 短標籤,可能省略 | +| is_thinking | bool | 為 `true` 表示發生在思考階段 | + +### agent_tool_progress + +被委託 Agent 每進行一次內部工具調用就發送一次。 + +| 名稱 | 類型 | 說明 | +| ------------------- | ------ | ---------------------------------------------------- | +| node_id | string | 調用方節點 ID | +| parent_tool_call_id | string | 所屬 `agent_tool_started` 事件的 `tool_use_id` | +| agent_tool_name | string | 被調用 Agent 的標識 | +| inner_tool_name | string | 被委託 Agent 調用的內部工具名 | +| inner_tool_args | string | 該內部調用的參數(JSON 字符串) | +| status | string | 內部調用的狀態:`running` / `succeeded` / `failed` | +| duration_ms | int64 | 內部調用耗時(**毫秒**) | +| started_at | int64 | 開始時間(Unix 秒) | +| is_thinking | bool | 為 `true` 表示發生在思考階段 | + +### agent_tool_finished + +| 名稱 | 類型 | 說明 | +| --------------- | -------- | --------------------------------------------- | +| node_id | string | 調用方節點 ID | +| tool_use_id | string | 與 `agent_tool_started` 的 `tool_use_id` 一致 | +| agent_tool_name | string | 被調用 Agent 的標識 | +| status | string | `succeeded` / `failed` | +| started_at | int64 | 開始時間(Unix 秒) | +| elapsed_time | number | 總耗時(秒) | +| error | string | 失敗時的錯誤描述 | +| tool_args | string | 調用參數(JSON 字符串) | +| outputs | object | 被委託 Agent 的結果 | +| tool_type | string | 工具類別 | +| tips | string | 進度文案,可能省略 | +| tip_chips | string[] | 短標籤,可能省略 | +| is_thinking | bool | 為 `true` 表示發生在思考階段 | + +## 中斷 + +### human_interaction_required + +運行已暫停:Agent 需要你補充信息或確認。收集 `questions` 的答案後調用[繼續對話](/zh-HK/docs/portai/chat/continue)——答案以 `tool_call_id` 為鍵。 + +| 名稱 | 類型 | 說明 | +| -------------- | -------- | ----------------------------------------------------------- | +| node_id | string | 觸發中斷的節點 ID | +| tool_call_id | string | 本次詢問的 ID,繼續對話時作為 `answers_by_tool_call` 的外層鍵 | +| questions | object[] | 需要回答的問題 | +| ∟ question | string | 問題文本,繼續對話時作為 `answers_by_tool_call` 的內層鍵 | +| ∟ options | object[] | 選項,為空表示自由輸入 | +| ∟∟ description | string | 選項文本 | +| ∟ multi_select | boolean | 是否可多選 | +| message_id | int64 | 被暫停消息的 ID,用於繼續對話接口的 URL | +| chat_id | int64 | 所屬會話的 ID | + +```json +{"event":"human_interaction_required","workflow_run_id":"745910371102313","data":{"node_id":"n_ask_human","tool_call_id":"call_abc123","questions":[{"question":"你想查看哪個時間範圍?","options":[{"description":"近一週"},{"description":"近一月"}],"multi_select":false}],"message_id":43,"chat_id":1001}} +``` + +此事件之後流以 `chat_finished` 結束;被中斷的運行不會發送 `workflow_finished`。 + +## 輔助事件 + +以下事件均為信息性事件,最簡客戶端可全部忽略。 + +### query_masked + +用戶提問中的敏感內容在處理前被脫敏。展示時用 `masked_query` 替換原始提問。 + +| 名稱 | 類型 | 說明 | +| ------------ | ------ | -------------- | +| raw_query | string | 原始用戶提問 | +| masked_query | string | 脫敏後的提問 | + +### plan_changed + +Agent 創建或更新了任務計劃。 + +| 名稱 | 類型 | 說明 | +| ---------- | ------ | ------------------------ | +| node_id | string | 規劃節點的 ID | +| started_at | int64 | 變更時間(Unix 秒) | +| outputs | object | 當前計劃內容 | + +此事件額外攜帶一個頂層 `tool_name` 字段(與 `data` 同級),標識規劃工具。 + +### context_compress_started / context_compress_finished + +長對話會觸發上下文壓縮,這兩個事件包裹壓縮過程。與其他事件不同,這裡的時間戳是 RFC 3339 字符串。 + +`context_compress_started`: + +| 名稱 | 類型 | 說明 | +| ---------- | ------ | -------------------- | +| started_at | string | 開始時間(RFC 3339) | +| inputs | object | 壓縮輸入摘要 | + +`context_compress_finished`: + +| 名稱 | 類型 | 說明 | +| ---------- | ------ | -------------------- | +| created_at | string | 結束時間(RFC 3339) | +| inputs | object | 壓縮輸入摘要 | +| outputs | object | 壓縮結果摘要 |