流式回覆
一條 AI 回覆是一個流:開始後,平臺以機器人的身份給使用者寫入一條“生成中”的 AI 訊息;你按順序寫入增量,平臺實時推給使用者;最後結束或報告失敗。概念和上限見 AI 對話。
使用服務端 SDK 時不需要直接呼叫本頁的介面:SDK 的 StartStream 返回的流物件自動合批、編號、選擇寫入方式和重試,Pipe* 直接把 OpenAI、Anthropic、Gemini 的流轉為回覆。
直接呼叫介面時,一條回覆的完整過程如下。使用者 zhangsan 剛給機器人 assistant 發了一條訊息,ai.message_received 中這條訊息的 seq 為 356:
# 1. 开始回复,用户的客户端立即显示“生成中”
STREAM_ID=$(curl -s -X POST "$IM_API/$ORG/$APP/ai/streams" \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-d '{"bot":"assistant","to":"zhangsan","reply_to_seq":356}' | jq -r .stream.stream_id)
# 2. 把模型生成的内容写入两段,用户看到文字逐段出现
curl -s -X POST "$IM_API/$ORG/$APP/ai/streams/$STREAM_ID/deltas:batch" \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-d '{"deltas":[
{"seq":1,"ops":[{"op":"append","index":0,"type":"text","text":"明天上海"}]},
{"seq":2,"ops":[{"op":"append","index":0,"type":"text","text":"**多云**。"}]}
]}'
# 3. 结束回复,平台保存最终消息并发离线推送
curl -s -X POST "$IM_API/$ORG/$APP/ai/streams/$STREAM_ID/finish" \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-d '{"last_seq":2,"model":"gpt-4.1"}'| 要做什麼 | 介面 |
|---|---|
| 開始一條回覆 | 開始回覆 |
| 寫入模型生成的內容 | 寫入增量:持續寫入或按批寫入 |
| 正常結束 | 結束回覆 |
| 模型出錯時結束 | 報告失敗 |
| 檢視一條回覆的狀態和已寫入的內容 | 查詢流 |
流物件
{
"stream_id": "1841000000000000123",
"bot": "assistant",
"to": "zhangsan",
"conversation_id": "1840098765432101",
"message_id": "1841000000000000124",
"seq": 358,
"client_stream_id": "req-7f3a",
"reply_to_seq": 356,
"status": "streaming",
"last_seq": 21,
"finish_reason": null,
"error": null,
"model": null,
"usage": null,
"content_bytes": 0,
"first_delta_ms": 420,
"started_at": "2026-11-02T08:00:00.000Z",
"finished_at": null
}| 欄位 | 型別 | 說明 |
|---|---|---|
stream_id | String | 流 ID |
bot、to | String | 機器人和接收回復的使用者 |
conversation_id、message_id、seq | — | 這條 AI 訊息所在的會話、訊息 ID 和它在會話中的序號 |
client_stream_id | String | 開始時給出的冪等標識 |
reply_to_seq | Number | 回覆的是會話中的哪條訊息 |
status | String | streaming、completed、stopped、failed |
last_seq | Number | 已接受的最後一段增量的序號;與 seq 無關 |
finish_reason、error | — | 結束原因和失敗資訊,見結束原因 |
model、usage | — | 結束時給出的模型名和用量 |
content_bytes | Number | 結束時儲存的內容大小 |
first_delta_ms | Number | 從開始到第一段增量的毫秒數 |
started_at、finished_at | String | 時間 |
content | Array | 只在查詢流且流還在生成中或結束不到 10 分鐘時返回:當前累積的片段 |
開始回覆
以機器人的身份給使用者寫入一條 status 為 streaming 的 AI 訊息,使用者的客戶端立即顯示“生成中”。沒有單聊會話時建立。
/{org_name}/{app_name}/ai/streams請求體
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
bot | String | 是 | 機器人的使用者名稱 |
to | String | 是 | 接收回復的使用者名稱 |
reply_to_seq | Number | 否 | 回覆的是會話中的哪條訊息,一般為 ai.message_received 中訊息的 seq;訊息中顯示為引用 |
client_stream_id | String | 否 | 冪等標識,1~64 個可見 ASCII 字元。重複時返回已有的流,不論它是否已結束 |
replace | Boolean | 否 | 會話中已有正在生成的回覆時,先把它以 stopped(replaced)結束;預設 false,此時返回 409 stream_in_progress |
請求示例
curl -X POST "$IM_API/$ORG/$APP/ai/streams" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"bot": "assistant",
"to": "zhangsan",
"reply_to_seq": 356,
"client_stream_id": "req-7f3a"
}'響應
新建時返回 201 Created,client_stream_id 重複時返回 200 OK;響應體為 { "stream": 流对象 }。
錯誤
| HTTP 狀態碼 | code | 說明 |
|---|---|---|
| 400 | invalid_argument | bot 不是機器人(details.reason 為 not_bot)、to 是機器人(recipient_is_bot),或引數格式不對 |
| 403 | permission_denied | 機器人已停用(bot_disabled) |
| 403 | user_blocked | 使用者拉黑了機器人;應用開啟了單聊好友校驗而雙方不是好友時為 not_friend |
| 404 | not_found | 機器人或使用者不存在 |
| 409 | stream_in_progress | 會話中已有正在生成的回覆,details.stream_id 為它的 ID |
| 409 | limit_exceeded | 機器人同時生成的回覆達到上限(bot_active_streams) |
寫入增量
回覆的內容按增量寫入。每段增量有一個序號 seq,從 1 開始連續遞增,包含一個或多個操作:
{ "seq": 12, "ops": [ { "op": "append", "index": 1, "type": "text", "text": "最高 23" } ] }op | 欄位 | 說明 |
|---|---|---|
append | index、type(text 或 reasoning)、text | 向第 index 個片段追加文字;index 等於當前片段數時新建一個片段 |
put | index、part | 把第 index 個片段整體設為 part,用於圖片、檔案和工具呼叫的狀態變化(如 running 改為 done);index 等於當前片段數時新建 |
- 片段只增不刪,
index從 0 開始;跳過index、向非文字片段追加時返回400 invalid_argument(invalid_ops)。 - 已接受過的序號再次寫入時直接跳過,不報錯,所以失敗後可以放心重發;序號不連續時返回
409 seq_gap,details.expected_seq為期望的下一個序號。 - 流已結束(使用者停止、超時、被替換等)時寫入返回
409 stream_closed,details.reason為結束後的狀態,details.finish_reason為結束原因。收到後應停止呼叫模型。
有兩種寫入方式,可以交替使用。
持續寫入
一個長請求:請求體是 NDJSON,每行一段增量,邊生成邊寫;平臺逐行處理,不等請求體結束,處理後在響應體中寫一行確認。
/{org_name}/{app_name}/ai/streams/{stream_id}/deltas- 請求頭
Content-Type: application/x-ndjson,請求體用分塊傳輸(HTTP/1.1)或 HTTP/2。 - 平臺收到請求後立即返回
200 OK和響應頭,響應體也是 NDJSON:每處理完一批寫一行{"acked_seq": 21};某一行出錯時寫一行錯誤{"error": {"code": "…", "message": "…", "details": {…}}}後結束,已確認的增量有效。 - 請求體結束時寫最後一行確認並返回。連續
idle_timeout_seconds沒有新的一行時平臺結束這個請求;請求最長與一條回覆的時長相同(30 分鐘),不受普通介面 30 秒超時的限制。 - 一行最大 64 KB,超過時返回錯誤
payload_too_large(line)。 - 反向代理須關閉這個路徑的請求體緩衝(nginx 為
proxy_request_buffering off),否則平臺要等請求結束才能收到內容。
printf '%s\n' \
'{"seq":1,"ops":[{"op":"append","index":0,"type":"text","text":"明天上海"}]}' \
'{"seq":2,"ops":[{"op":"append","index":0,"type":"text","text":"**多云**。"}]}' |
curl -N -X POST "$IM_API/$ORG/$APP/ai/streams/1841000000000000123/deltas" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/x-ndjson" \
--data-binary @-
# {"acked_seq":1}
# {"acked_seq":2}按批寫入
普通的請求,一次寫入一批增量。用於執行環境不支援流式請求體(如部分雲函式),或持續寫入被代理斷開後續寫。服務端 SDK 在持續寫入失敗時自動改用它。
/{org_name}/{app_name}/ai/streams/{stream_id}/deltas:batch| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
deltas | Array | 是 | 增量,按序號排列;一次最多 100 段、64 KB |
成功返回 200 OK 和 { "acked_seq": 21 }。
curl -X POST "$IM_API/$ORG/$APP/ai/streams/1841000000000000123/deltas:batch" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"deltas": [
{ "seq": 3, "ops": [ { "op": "put", "index": 1, "part": { "type": "image", "url": "https://cdn.example.com/a.png", "width": 800, "height": 600 } } ] }
]
}'結束回覆
全部增量確認後結束,狀態為 completed。平臺儲存最終內容、更新訊息、對不在前臺的使用者發離線推送,併傳送 ai.stream_finished。
/{org_name}/{app_name}/ai/streams/{stream_id}/finish| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
last_seq | Number | 是 | 最後一段增量的序號,必須等於平臺已接受的最後一段,否則返回 409 seq_gap,確保結束前的內容全部到達 |
finish_reason | String | 否 | 如 stop、length、tool_calls,預設 stop |
model | String | 否 | 模型名 |
usage | Object | 否 | { "input_tokens": 120, "output_tokens": 380 } |
成功返回 200 OK 和 { "stream": 流对象 }。內容稽核沒有透過或內容超過上限時,返回的流狀態為 failed。流已結束時返回 409 stream_closed。
報告失敗
模型呼叫失敗等情況下結束回覆,狀態為 failed(developer),已生成的內容保留,客戶端顯示“生成中斷”和你給出的說明。
/{org_name}/{app_name}/ai/streams/{stream_id}/fail| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
code | String | 是 | 1~64 個字元,由字母、數字、_、-、. 組成,如 model_error |
message | String | 否 | 顯示給使用者的說明,最多 256 個字元 |
成功返回 200 OK 和 { "stream": 流对象 };流已結束時返回 409 stream_closed。
查詢流
/{org_name}/{app_name}/ai/streams/{stream_id}成功返回 200 OK 和 { "stream": 流对象 }。生成中和結束 10 分鐘內帶 content;之後不帶,以訊息正文為準(用查詢單條訊息的介面讀取)。不存在返回 404 not_found。
