본문으로 건너뛰기

SSE wire contract

AI chat endpoint는 Server-Sent Events(text/event-stream)를 stream합니다. 이 페이지는 POST /chat/control/stream에서 사용하는 control-stream contract를 설명합니다. source of truth는 @kaleidr/inference-ui의 parser + types (packages/inference-ui/src/control/sse.tstypes.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는 무시하며 사라질 수 있습니다.

Stable event

Payload는 control parser가 가정하는 형식입니다. start에는 phase field가 없습니다 (phase event에만 존재).

event:PayloadMeaning
(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"는 org가 upload한 data에서 온 place를 나타냅니다.
place_linkedplace fields + is_update?이미 emit된 place를 enrich.
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 } ] }명확화 chip — tappable option으로 표시하고 option의 send_text를 다음 user message로 전송합니다.
route_clear{}새 route 도착 전에 모든 overlay clear. Stable.
route{ type: "route", schema_version, id?, profile, origin, destination, primary, alternatives[], comparison[], stops?, connectors?, color?, label? }journey 형태 turn — engine-computed route. prose보다 먼저 emit되어 답변 stream 중 map이 그릴 수 있습니다.
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-alivecomment lineProxy keep-alive(~10s). 무시.

제거됨: grounding (2026-09-05). Grounding은 계속 답변에 영향을 주지만 wire에 frame을 보내지 않습니다.

Diagnostic / experimental (non-contract)

이는 emit되는 implementation detail입니다. official client는 무시하므로 의존하지 마세요.

event:PayloadNotes
debug{ scope, … }내부 diagnostic. 변경되거나 사라질 수 있음.
place_name_found{ name, is_early? }Non-contract; official client는 무시.

Token 및 structured data

Unnamed event는 plain prose를 stream합니다. place가 참조되는 곳에 inline [[Place]] marker가 포함될 수 있습니다(예: …cafes near [[Louvre]]…) — clean display를 위해 제거하거나 prose를 완전히 무시하고 structured event를 기반으로 UI를 구동하세요.

Structured data는 event로 도착하며 prose 안에 들어가지 않습니다. Resolved place는 geocode되면서 place(place_linked는 late metadata)로 stream됩니다. map action은 early_actions, route는 route_clear 이후 route로 오며, authoritative final payload는 end event입니다. 일반적인 client는 place event가 도착하는 즉시 pin을 render하고 end를 source of truth로 사용합니다.

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

request body는 Endpoints, failure handling은 Errors를 참조하세요.