SSE wire contract
AI chat endpoint 會串流傳送 Server-Sent Events(text/event-stream)。
本頁記錄 POST /chat/control/stream 使用的 control-stream contract。
source of truth 是 @kaleidr/inference-ui 中的 parser + types
(packages/inference-ui/src/control/sse.ts 與 types.ts)。
如果使用 kaleidr.js,chat bundle 會為您完成解析 — 只有建立 custom client 時
才需要此頁面。
/chat/summary/stream 與 /chat/button/stream 使用不同的 SSE
contract,本頁不涵蓋它們。不要在這些 endpoint 上重複使用 control-stream
parser。
相容性規則
Client 必須忽略未知 event name。stable contract 中可以在不進行 version bump 的情況下加入新 event。下方 "diagnostic / experimental" 表中的 downstream event 不屬於 contract — official client 會忽略它們,而且它們可能消失。
穩定 events
Payload 是 control parser 所假設的格式。start 沒有 phase field
(該欄位只出現在 phase event 中)。
event: | Payload | Meaning |
|---|---|---|
| (unnamed) | { text } | 增量 prose token。可能包含 [[Place]] marker。 |
phase | { type: "phase", phase, session_id } | stream 前 lifecycle。 |
start | { type: "start", session_id, purpose? } | Stream 已開啟。 |
place | { name, coordinates: { lat, lng }, address?, category?, source?, id? } | 已解析 place(merge-by-name → map pin)。source: "vendor" 表示此 place 來自 org 上傳的資料。 |
place_linked | place fields + is_update? | 為已 emit 的 place 補充資訊。 |
early_actions | { type: "early_actions", actions: [ { type, payload } ], places_count?, is_partial? } | 完整 reply 前的 map action(fitBounds / route / highlight)。 |
prompt_options | { type: "prompt_options", schema_version, kind, question, allow_free_text, options: [ { label, send_text } ] } | 消歧選項 — 呈現為可點擊選項,並將對應的 send_text 作為下一則 user message 提交。 |
route_clear | {} | 新 route 到達前清除所有 overlay。Stable. |
route | { type: "route", schema_version, id?, profile, origin, destination, primary, alternatives[], comparison[], stops?, connectors?, color?, label? } | 以 journey 形式表達的 turn — engine-computed route,在 prose 前 emit,讓地圖能在回答 streaming 時繪製。 |
quota | { type: "quota", product, meter, tier, used, limit, reset_at, period } | stream 中 meter — 顯示 remaining budget。 |
end | { type: "end", full_text, places[], actions[], question_type, response_time, total_tokens? } | Terminal envelope — 完整結果。 |
error | { type: "error", message, code?, partial_text? } | Terminal error。 |
: keep-alive | comment line | Proxy keep-alive(~10s)。忽略。 |
已移除:grounding(2026-09-05)。Grounding 仍會影響回答,但
不會在 wire 上傳送 frame。
Diagnostic / experimental(non-contract)
這些是 emit 的 implementation detail。official client 會 忽略它們;不要依賴。
event: | Payload | Notes |
|---|---|---|
debug | { scope, … } | 內部 diagnostic。可能變更或消失。 |
place_name_found | { name, is_early? } | Non-contract;official client 會忽略。 |
Tokens 與 structured data
Unnamed event 會 stream 一般 prose。當引用 place 時,可能包含
inline [[Place]] marker(例如 …cafes near [[Louvre]]…)—
可以移除以取得乾淨顯示,也可以完全忽略 prose,並使用 structured event 驅動
UI。
Structured data 以 event 形式到達,絕不內嵌在 prose 中。 Resolved
place 會在 geocode 過程中透過 place(以及用於 late metadata 的 place_linked)stream;
map action 透過 early_actions 到達;route 會在 route_clear 後透過 route 到達;
authoritative final payload 是 end event。
典型 client 會在 place event 到達時呈現 pin,並將
end 視為 source of truth。
最小 client
const res = await fetch(`${API}/inference-api/b2b/v1/chat/control/stream`, {
method: 'POST',
headers: { 'X-Api-Key': key, 'Content-Type': 'application/json', Accept: 'text/event-stream' },
body: JSON.stringify({ messages: [{ role: 'user', content: 'cafes near the Louvre' }] }),
});
const reader = res.body.getReader();
// …decode chunks, split on \n\n, parse `event:` + `data:` lines
// IMPORTANT: ignore any unknown event name (forward-compat rule above).