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).