SSE-Protokollvertrag
POST /chat/control/stream antwortet mit Server-Sent Events (text/event-stream): einer langlebigen Verbindung, die den Antworttext während seiner Entstehung sowie strukturierte Ereignisse zu Orten, Kartenaktionen und Routen überträgt.
Diese Seite beschreibt den Vertrag für diesen Stream. Bei Verwendung von kaleidr.js liest das Chatfenster den Stream selbst; dann benötigen Sie diese Seite nicht. Lesen Sie sie, wenn Sie einen eigenen Client entwickeln. Funktionsfähige Clients für Node und Python finden Sie unter Stream lesen. Der Anfragekörper ist unter Endpunkte beschrieben.
/chat/summary/stream und /chat/button/stream verwenden dasselbe Frame-Format, jedoch mit weniger Ereignistypen. Sie werden am Ende dieser Seite beschrieben.
Vor dem Beginn des Streams
Eine abgelehnte Anfrage wird nie zu einem Stream. Fehler bei Authentifizierung, Berechtigungen, Kontingent, Ratenbegrenzung, Validierung oder Größe ergeben eine reguläre HTTP-Antwort mit JSON-Körper und 4xx-Status (Fehler). Prüfen Sie zuerst den Status und interpretieren Sie den Körper nur bei 200 als SSE.
Die Anfrage unterliegt Größenbeschränkungen: maximal 400 Nachrichten, 64.000 Zeichen je Nachricht und insgesamt 200.000 Zeichen über alle Nachrichten hinweg. Bei Überschreitung folgt der Status 422 mit einem Code wie conversation_too_long; für einen Anfragekörper über 2 MiB gilt 413. Die vollständige Liste steht unter Endpunkte.
Eine erfolgreiche Antwort hat folgende Header:
HTTP/1.1 200 OK
Content-Type: text/event-stream; charset=utf-8
Cache-Control: no-cache, no-transform
Frame-Format
Ein Frame besteht aus einer oder zwei Zeilen, gefolgt von einer Leerzeile. Jede Zeile endet mit \n.
event: place
data: {"name": "Louvre Museum", "coordinates": {"lat": 48.8606, "lng": 2.3376}}
data: {"text": "The Louvre sits on the right bank"}
: keep-alive
- Ein benanntes Frame enthält eine
event:-Zeile und danach einedata:-Zeile. - Ein Text-Frame enthält nur eine
data:-Zeile und keineevent:-Zeile. - Ein Kommentar beginnt mit einem Doppelpunkt. Kaleidr sendet
: keep-aliveals Lebenszeichen; es enthält keine Daten. data:enthält immer ein vollständiges JSON-Objekt in genau einer Zeile. Der Text ist UTF-8-kodiert.
Netzwerkpakete stimmen nicht notwendigerweise mit Frame-Grenzen überein. Ein Lesevorgang kann ein halbes Frame oder mehrere Frames enthalten. Puffern Sie die empfangenen Daten, trennen Sie sie an Zeilenumbrüchen und verarbeiten Sie ein Frame erst, wenn seine abschließende Leerzeile vorliegt.
Kompatibilitätsregel
Ignorieren Sie unbekannte Ereignisnamen und nicht verwendete Felder. Neue Ereignisse und Felder können ohne Versionswechsel zum Vertrag hinzukommen. Ein Client, der bei einem unbekannten Ereignis abbricht, wird dadurch unbrauchbar.
Stabile Ereignisse
event: | Nutzdaten | Bedeutung |
|---|---|---|
| (unbenannt) | { text } | Nächster Teil des Antworttextes. Hängen Sie ihn an den bestehenden Text an. Kann [[Name]]-Markierungen um Ortsnamen enthalten. |
phase | { type: "phase", phase: "thinking", session_id } | Die Anfrage wurde angenommen und die Verarbeitung hat begonnen. phase ist immer "thinking". |
start | { type: "start", session_id, purpose: "control" } | Der Antworttext wird gleich übertragen. |
place | { name, coordinates?: { lat, lng }, address?, category?, icon?, source?, id?, unverified? } | Ein Ort aus der Antwort. Führen Sie Einträge nach name zusammen; die ersten für einen Namen empfangenen Koordinaten sind endgültig. source: "vendor" kennzeichnet einen Ort aus Organisationsdaten, source: "map" einen Ort, den die veröffentlichte Karte bereits zeigt. |
place_linked | Ortsfelder sowie is_update? | Zusätzliche Details zu einem bereits gesendeten Ort. Führen Sie sie mit dem Datensatz desselben name zusammen; ignorieren Sie sie, wenn der Ort noch nicht bekannt ist. |
early_actions | { type: "early_actions", actions: [ { type, payload } ], places_count, is_partial: true } | Kartenaktionen, die schon vor dem Ende der Antwort anwendbar sind; werden gesendet, sobald mindestens zwei Orte Koordinaten besitzen. |
prompt_options | { type: "prompt_options", schema_version, kind, question, allow_free_text, options: [ { label, send_text } ] } | Auswahlfrage bei einer mehrdeutigen Anfrage. Zeigen Sie die Optionen an und senden Sie den gewählten Wert send_text als nächste Nutzernachricht. |
route_clear | {} | Entfernen Sie die gezeichneten Routen. Neue Routen folgen. |
route | { type: "route", schema_version, id?, profile, origin, destination, primary, alternatives[], comparison[], stops?, color?, label?, transit?, attribution? } | Berechnete Route. Bei Wegbeschreibungen kommt sie vor dem Antworttext, damit die Karte während der Textübertragung zeichnen kann. |
end | { type: "end", full_text, places[], actions[], question_type, response_time, total_tokens?, dropped_places? } | Die Antwort ist vollständig. Dies ist das maßgebliche Ergebnis. |
error | { type: "error", message, code?, partial_text } | Die Antwort ist fehlgeschlagen. Wird statt end gesendet. |
quota | { type: "quota", product, meter, tier, used, limit, reset_at, period } mit Serverschlüssel; { type: "quota", meter, reset_at } mit Sitzungstoken | KI-Guthabenverbrauch der Organisation nach Abrechnung dieses Aufrufs. Letztes Streamereignis nach end oder error. Siehe quota. |
: keep-alive | Kommentarzeile | Lebenszeichen nach zehn Sekunden ohne Ausgabe. Ignorieren Sie den Inhalt, aber behandeln Sie ihn als Aktivität. |
Entfernt: grounding (2026-09-05). Es wird nicht mehr gesendet.
Live-Datenkarten und Ebenen
Eine Frage nach aktuellen oder gemessenen Fakten kann zusätzliche Ereignisse dieser Art auslösen. Eine Karte trifft vor dem Antworttext ein, eine Ebene währenddessen oder danach. Die Ereignisse sind ergänzend: Stellen Sie unterstützte Inhalte dar und ignorieren Sie den Rest. Weitere Felder können später hinzukommen.
event: | Nutzdaten | Zweck |
|---|---|---|
weather_card | Objekt mit lat, lng, name, Temperaturen (temp_c, temp_f), conditions, optional forecast[] | Aktuelles Wetter an einem Ort. |
weather_layer | Objekt mit layer, tile_path_template, tile_size, max_zoom, scope, attribution | Temperaturüberlagerung für eine Region. |
satellite_card | Objekt mit name, norad_id, lat, lng, altitude_km, velocity_kms, observed_at | Position eines benannten Satelliten. |
constellation_layer | Objekt mit group, features[] aus { name, norad_id, lat, lng }, optional track | Satelliten in der Umgebung des Satelliten auf der Karte. |
building_card | Objekt mit lat, lng, optional name, height_m, num_floors | Angaben zu einem Gebäude. |
trees_card | Objekt mit lat, lng, count, radius_m | Erfasste Bäume um einen Punkt. |
heritage_card | Objekt mit lat, lng, count, sites[] | Denkmäler und Kulturstätten um einen Punkt. |
rail_card | Objekt mit lat, lng, lines[], optional nearest_station | Bahnlinien um einen Punkt. |
arrivals_card | Objekt mit lat, lng, stop, departures[], alerts[], attribution | ÖPNV-Abfahrten an einer Haltestelle. |
vehicles_layer | Objekt mit operator, vehicles[], shapes[], attribution | Positionen von ÖPNV-Fahrzeugen an der Haltestelle oder Linie der Karte. |
streetview_card | Objekt mit lat, lng, image_id, image_lat, image_lng | Straßenansichten in der Nähe eines Ortes. |
end wiederholt bei deren Auftreten fünf dieser Elemente unter demselben Namen, damit ein Client sie auch nach einem verpassten Ereignis noch lesen kann: weather_card, satellite_card, constellation_layer, weather_layer und vehicles_layer (beispielsweise end.weather_card). Andere Karten werden in end nicht wiederholt. Live-Veranstaltungslisten stehen nicht über die Plattform-API bereit; siehe Datenlizenzen.
Diagnose und experimentelle Ereignisse (nicht vertraglich)
Diese Ereignisse können im Datenstrom erscheinen. Sie gehören nicht zum Vertrag; verlassen Sie sich nicht darauf.
event: | Nutzdaten | Hinweise |
|---|---|---|
debug | { scope, … } | Interne Diagnose. Bei normalen Anfragen nicht vorhanden. |
place_name_found | { name, is_early } | Früher Hinweis, dass ein Ortsname erwähnt wurde. Verwenden Sie das danach folgende place-Ereignis. |
Ereignisreihenfolge
Ein Stream durchläuft die folgenden Phasen. Als optional markierte Phasen treten nicht in jedem Stream auf.
- Optional.
place-Ereignisse mitsource: "vendor": bis zu drei Orte aus den eigenen Daten Ihrer Organisation, wenn der Schlüssel die Berechtigungvendorbesitzt. Sie werden zuerst gesendet, damit die Karte sie sofort darstellen kann. phase. Immer das erste Ereignis der eigentlichen Antwort.- Optional.
: keep-alive-Kommentare während der Antwortvorbereitung. - Optional. Ereignisse vor dem Text:
prompt_optionsoderroute_clear, gefolgt vonrouteund einer Karte. start.- Text-Frames, durchsetzt mit
place,place_linked,early_actionsund mitunterroute_clear,routesowie Ebenenereignissen. Keepalives überbrücken stille Zeiträume. - Optional. Späte
place-,place_linked-, Ebenen- und Routenereignisse nach Abschluss des Textes. endodererror.- Optional.
quota. - Der Server schließt die Verbindung.
Für Clients hat dies zwei Konsequenzen:
endist nicht das letzte Ereignis. Lesen Sie bis zum Schließen des Antwortkörpers weiter, sonst verpassen Siequota.- Ein Stream kann ohne
endund ohneerrorschließen, zum Beispiel bei einem Netzwerkausfall. Behandeln Sie einen solchen Stream ohne Abschlussereignis als fehlgeschlagen.
Einzelheiten der Nutzdaten
Text und Markierungen
Text-Frames enthalten ausschließlich Text; strukturierte Daten kommen nicht innerhalb des Fließtextes an. Der Text kann Ortsnamen in doppelten eckigen Klammern markieren, beispielsweise near [[Louvre Museum]]. Das Chatfenster wandelt diese Markierungen in Links zu Kartenmarkern um. Entfernen Sie für die Klartextanzeige die Klammern, nicht aber den Namen.
place
| Feld | Typ | Hinweise |
|---|---|---|
| name | string | Schlüssel für die Zusammenführung. |
| coordinates | object { lat, lng } | Dezimalgrad. Kann fehlen. Ein nicht lokalisierbarer Ort wird ohne Koordinaten mit unverified: true gesendet. Zeichnen Sie dann keinen Marker. |
| address | string | Optional. |
| category | string | Optional. |
| icon | string | Optional. Ein Emoji für den Ort. |
| source | string | Optional. "vendor" steht für Orte aus eigenen Organisationsdaten. "map" bezeichnet einen Ort, den die gerade betrachtete veröffentlichte Karte schon zeigt; das Chatfenster fügt deshalb keinen eigenen Marker hinzu. |
| id | string | Optional. Bei Orten aus eigenen Daten vorhanden. |
| unverified | boolean | Optional. true, wenn die Position nicht bestätigt ist. |
Ein Ort kann weitere Felder enthalten. Ignorieren Sie nicht benötigte Felder.
Derselbe Name kann mehrfach eintreffen: erst mit Koordinaten, später mit weiteren Details. Führen Sie pro Namen einen Datensatz, verschieben Sie einen Marker nach Erhalt seiner Koordinaten nicht mehr und ergänzen Sie anhand späterer Ereignisse die übrigen Felder.
Aktionen
early_actions.actions und end.actions sind Listen aus { type, payload }.
| Aktionstyp | Nutzdaten | Verarbeitung |
|---|---|---|
| map.fitBounds | { coordinates: [[lng, lat], …], padding, maxZoom, duration } | Passen Sie die Kartenansicht an die Koordinaten an. |
| places.highlight | { places: [ { name, lat, lng } ], options } | Heben Sie diese Orte hervor. |
| map.showRoute | { id, label, profile, coordinates: [[lng, lat], …], distance_m, duration_s, turn_by_turn[], transit?, attribution? } | Zeichnen Sie die Linie und zeigen Sie attribution an. |
| route.clear | { id? } | Entfernen Sie die Route mit dieser id oder alle Routen, wenn keine id angegeben ist. |
Es können weitere Aktionstypen auftreten. Überspringen Sie Typen, die Sie nicht verarbeiten. Beachten Sie die zwei Koordinatenformate: Ein Ort enthält coordinates: { lat, lng }, eine Linie besteht dagegen aus [lng, lat]-Paaren.
route
primary enthält die zu zeichnende Linie: { coordinates: [[lng, lat], …], distance_m, duration_s, turn_by_turn[] }. Jeder Schritt in turn_by_turn enthält text und gegebenenfalls direction_type, distance_m, duration_s, lat und lng. origin und destination haben jeweils ein label. alternatives[] enthält weitere Linien mit route_id; comparison[] nennt für jede Alternative deren delta_duration_s gegenüber der Hauptroute. Falls vorhanden, enthält stops[] geordnete Punkte { lat, lng, name } einer Route mit mehreren Zwischenstopps.
Eine Straßenroute enthält attribution: { text, url, license }. Zeigen Sie beim Zeichnen der Route text mit einem Link zu url an. Bei einer ÖPNV-Route steht der Hinweis auf den Betreiber dagegen unter transit.
Jedes route-Ereignis besitzt eine id. Wird eine Route mit einer bereits vorhandenen id gezeichnet, ersetzt sie die bisherige Route.
end
| Feld | Typ | Hinweise |
|---|---|---|
| full_text | string | Vollständige Antwort. Verwenden Sie sie bevorzugt statt aneinandergereihter Text-Frames. |
| places | array | Endgültige Ortsliste in Reihenfolge. Führen Sie sie nach Namen mit den gesammelten Orten zusammen. |
| actions | array | Endgültige Kartenaktionen. Wenden Sie sie an; sie ersetzen early_actions. |
| dropped_places | array of strings | Optional. Früher gesendete Namen, die nicht mehr zur finalen Antwort gehören. Entfernen Sie die zugehörigen Marker. |
| question_type | string | Beschreibende Kennzeichnung des Fragetyps. Rein informativ. |
| response_time | number | Antwortdauer in Sekunden. |
| total_tokens | number | Optional. Anzahl verwendeter Tokens, nach der abgerechnet wird. |
error
event: error
data: {"type": "error", "code": "upstream_stall", "message": "The AI service stopped responding. Please try again.", "partial_text": "The Louvre sits"}
message kann den Nutzern direkt angezeigt werden. partial_text ist der Text, der vor dem Fehler entstand. code wird nur in einem Fall übermittelt:
| Fall | code | Vorgehen |
|---|---|---|
| Die Antwort kam nicht weiter oder der Stream erreichte seine maximale Länge | "upstream_stall" | Bieten Sie einen erneuten Versuch an. |
| Sonstiger Fehler während der Antwort | nicht vorhanden | Zeigen Sie message an; ein erneuter Versuch kann erfolgreich sein. |
Nach error folgt kein end; ein quota-Ereignis kann jedoch noch eintreffen.
quota
Die Felder hängen von den Zugangsdaten ab, mit denen der Stream geöffnet wurde.
| Feld | Typ | Serverschlüssel oder älterer Schlüssel | Sitzungstoken |
|---|---|---|---|
| type | Zeichenfolge, "quota" | Ja | Ja |
| meter | Zeichenfolge | Ja | Ja |
| reset_at | Zeichenfolge, ISO 8601 mit Zeitzonenoffset | Ja | Ja |
| product | Zeichenfolge | Ja | Nein |
| period | Zeichenfolge | Ja | Nein |
| tier | Zeichenfolge | Ja | Nein |
| used | Zahl | Ja | Nein |
| limit | Zahl | Ja | Nein |
Ein Sitzungstoken wird von Besuchern einer Webseite verwendet: entweder aus einem öffentlichen Schlüssel ausgetauscht oder aus einer Besuchersitzung für eine veröffentlichte Karte. Sein quota-Ereignis gibt nur den Zeitpunkt der nächsten Periodenrücksetzung an, nicht aber Tarif, Verbrauch oder Limit der Organisation. Browserclients dürfen nicht mit used oder limit rechnen. Dieselbe Regel gilt für den Antwortkörper bei 429 quota_exceeded; siehe Kontingente und Ratenbegrenzungen.
Keepalives und Zeitlimits
| Verhalten | Wert | Maßnahme des Clients |
|---|---|---|
| Lebenszeichen des Chatstreams | Ein : keep-alive-Kommentar nach zehn Sekunden ohne andere Ausgabe | Setzen Sie den Inaktivitätstimer bei jedem empfangenen Byte zurück, auch bei Kommentaren. |
| Inaktive Verbindung | Eine rund eine Minute stille Verbindung kann auf dem Transportweg geschlossen werden | Verwenden Sie einen Leerlauf-/Lesetimeout von 30 bis 60 Sekunden. Setzen Sie kein kurzes Gesamtzeitlimit. |
| Zeit bis zum ersten Text | Bei schwierigen Fragen können mehrere Dutzend Sekunden vergehen | Zeigen Sie ab phase einen Fortschritt an und werten Sie die Wartezeit nicht als Fehler. |
| Maximale Streamdauer | Ungefähr zehn Minuten, danach sendet der Server error mit upstream_stall | Wählen Sie zur Absicherung ein Gesamtzeitlimit von etwas mehr als zehn Minuten. |
Abbruch eines Streams und Abrechnung
Schließen Sie die Verbindung, um einen Stream abzubrechen. Der Server erkennt den Abbruch innerhalb von ungefähr zehn Sekunden und stoppt die Antwort.
- Ein Stream, der vollständig abgeschlossen wird, wird nach den verwendeten Tokens abgerechnet, wie in
end.total_tokensausgewiesen. - Wird ein Stream vorher abgebrochen oder schlägt er fehl, erfolgt eine Schätzung. Mindestens wird die Größe des in der Anfrage gesendeten Gesprächs angesetzt; ansonsten richtet sich der Betrag nach dem bereits übertragenen Text. Daher kann der Betrag höher als die Reservierung sein, die bei Annahme der Anfrage vorgenommen wurde: Das Schließen der Verbindung nach dem ersten Frame macht ein langes Gespräch nicht kostenlos.
- Orte aus Ihren eigenen Daten (
source: "vendor") zählen bei der Berechnung nicht mit.
Ein erneuter Versuch ist eine neue Anfrage und wird erneut abgerechnet. Siehe Kontingente und Ratenbegrenzungen.
Streams für Zusammenfassungen und Schaltflächen
Diese zwei Endpunkte versorgen die Ortskarte in Kaleidr Chat. Sie verwenden dasselbe Frame-Format, dieselben unbenannten Text-Frames und dasselbe abschließende quota-Ereignis. Vom Chatstream unterscheiden sie sich in drei Punkten: kein phase-Ereignis, keine Orts- oder Aktionsereignisse und keine Keepalives. Verwenden Sie ein Inaktivitätslimit von 60 Sekunden.
Beide Endpunkte lehnen überlange Felder vor Streambeginn mit 422 field_too_long ab und ignorieren nicht aufgeführte Felder; siehe Endpunkte. Bei Abbruch oder Fehler beruht die Abrechnung auf einer Schätzung des bereits übertragenen Textes und überschreitet nie den reservierten Betrag.
POST /chat/summary/stream für einen Ort:
- Optional.
addressmit Nutzdaten{ type: "address", address, source } startmit Nutzdaten{ type: "start", poi_type }- Optional, gegebenenfalls mehrfach.
urlmit Nutzdaten{ type: "url", website, source } - Text-Frames mit Nutzdaten
{ text } endmit Nutzdaten{ type: "end", full_text, summary, poi_type, category, city, country, sources[], website?, total_tokens? }odererrormit Nutzdaten{ type: "error", message, partial_text }- Optional.
quota
POST /chat/summary/stream für ein Gebäude (type: "building"):
startmit Nutzdaten{ type: "start", address }- Text-Frames
endmit Nutzdaten{ type: "end", full_text, parsed, total_tokens? }odererror- Optional.
quota
POST /chat/button/stream:
startmit Nutzdaten{ type: "start", query_type }- Text-Frames
endmit Nutzdaten{ type: "end", full_text, reply, query_type, first_token_ms, total_ms, total_tokens? }- Optional.
quota
Der Schaltflächenstream besitzt kein error-Ereignis. Bei einem Fehler nach Beginn schließt die Verbindung ohne end. Ein Fehler vor dem Start wird als HTTP 500 mit {"detail": "Button chat stream error"} beantwortet.
POST /spot/overview/stream sendet die oben beschriebenen Frames einer Ortszusammenfassung, davor ein place-Frame mit { type: "place", name }: dem vollständigen Namen des beschriebenen Orts oder der Region, etwa "Springfield, Ohio, United States of America", zum Beschriften oder Kopieren. Er kommt vor allen anderen Frames und kann fehlen. Er ist nicht das place-Ereignis des Chat-Streams und enthält keine Koordinaten. Kann eine ref nicht beschrieben werden, folgt statt der Zusammenfassung ein einzelnes error-Frame mit { type: "error", message, ref }. Siehe Endpunkte.
Verwandte Seiten
- Stream lesen: vollständige Node- und Python-Clients.
- Endpunkte: Anfragekörper jedes Streams.
- Fehler: Fehler vor und während eines Streams.
- Kontingente und Ratenbegrenzungen:
quota-Ereignis und Abrechnung.