SSE 传输协议
POST /chat/control/stream 使用 服务器发送事件(SSE)(text/event-stream)响应:这是一种长连接响应,会在文本生成时持续发送内容,同时发送地点、地图操作和路线的结构化事件。
本页定义该数据流的协议。如果使用 kaleidr.js,聊天面板会自动解析数据流,无须阅读本页。自行开发客户端时请参考此文档。Node 和 Python 的可用客户端示例参见读取数据流;请求体参见接口列表。
/chat/summary/stream 和 /chat/button/stream 使用相同的帧格式,但事件种类较少。详见本页末尾。
数据流开始之前
被拒绝的请求不会进入流式响应。身份验证、权限范围、配额、速率限制、参数校验及大小限制等错误,会返回带有 JSON 响应体和 4xx 状态码的普通 HTTP 响应(参见错误)。请先检查状态码,只有状态为 200 时才将响应体按 SSE 解析。
请求大小上限为:最多 400 条消息、单条消息最多 64,000 个字符、所有消息内容合计最多 200,000 个字符。超限请求会返回 422 及 conversation_too_long 等错误码;请求体超过 2 MiB 会返回 413。完整限制参见接口列表。
成功响应包含以下响应头:
HTTP/1.1 200 OK
Content-Type: text/event-stream; charset=utf-8
Cache-Control: no-cache, no-transform
帧格式
一个帧由一行或两行内容组成,后面跟一个空行。每行以 \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
- 具名帧 包含一行
event:,随后是一行data:。 - 文本帧 只有一行
data:,没有event:行。 - 注释 以冒号开头。Kaleidr 使用
: keep-alive作为心跳信号;它不包含数据。 data:始终在单独一行中包含一个完整的 JSON 对象。 文本采用 UTF-8 编码。
网络数据块不一定与帧边界对齐。一次读取可能只得到半帧,也可能得到多个帧。应缓冲收到的数据,按换行符切分,并且只在遇到帧末尾的空行时处理该帧。
兼容性规则
忽略无法识别的事件名称及不需要的字段。 协议可能在不更改版本的情况下新增事件和字段。遇到未知事件就报错的客户端可能因此无法正常工作。
稳定事件
event: | 载荷 | 含义 |
|---|---|---|
| (无名称) | { text } | 下一段回答文字。追加到已有文本中。地点名周围可能有 [[Name]] 标记。 |
phase | { type: "phase", phase: "thinking", session_id } | 请求已接受,处理已开始。phase 始终为 "thinking"。 |
start | { type: "start", session_id, purpose: "control" } | 即将开始发送正文。 |
place | { name, coordinates?: { lat, lng }, address?, category?, icon?, source?, id?, unverified? } | 回答中提到的地点。按 name 合并;首次收到的坐标即为最终坐标。source: "vendor" 表示组织自有数据中的地点;source: "map" 表示当前已发布地图上已显示的地点。 |
place_linked | 地点字段加上 is_update? | 已发送地点的补充信息。按相同的 name 合并;如果此前没见过该地点则忽略。 |
early_actions | { type: "early_actions", actions: [ { type, payload } ], places_count, is_partial: true } | 可在回答完成前应用的地图操作;至少两个地点获得坐标后发送。 |
prompt_options | { type: "prompt_options", schema_version, kind, question, allow_free_text, options: [ { label, send_text } ] } | 请求含糊时发送的选择题。展示选项,并把所选选项的 send_text 作为下一条用户消息发送。 |
route_clear | {} | 清除绘制过的路线;后续会发送新路线。 |
route | { type: "route", schema_version, id?, profile, origin, destination, primary, alternatives[], comparison[], stops?, color?, label?, transit?, attribution? } | 计算得到的路线。导航问题中会在正文前到达,让地图能在文字生成期间 绘制。 |
end | { type: "end", full_text, places[], actions[], question_type, response_time, total_tokens?, dropped_places? } | 回答完成。这是权威的最终结果。 |
error | { type: "error", message, code?, partial_text } | 回答失败。代替 end 发送。 |
quota | 服务器密钥使用 { type: "quota", product, meter, tier, used, limit, reset_at, period };会话令牌使用 { type: "quota", meter, reset_at } | 此调用计费后的组织 AI 积分使用情况。在 end 或 error 之后,作为数据流最后一个事件发送。参见 quota。 |
: keep-alive | 注释行 | 静默 10 秒后发送的心跳信号。忽略其内容,但应视为活动。 |
已移除:grounding(2026-09-05)。不再发送该事件。
实时数据卡片和图层
涉及实时或测量数据的问题可能触发以下事件。卡片会在正文之前到达;图层可能在正文发送期间或之后到达。这些事件是附加内容:支持的就绘制,不支持的就忽略。未来可能增加更多字段。
event: | 载荷 | 用途 |
|---|---|---|
weather_card | 包含 lat、lng、name、温度(temp_c、temp_f)、conditions 和可选 forecast[] 的对象 | 地点当前天气。 |
weather_layer | 包含 layer、tile_path_template、tile_size、max_zoom、scope、attribution 的对象 | 区域温度叠加图层。 |
satellite_card | 包含 name、norad_id、lat、lng、altitude_km、velocity_kms、observed_at 的对象 | 指定卫星的位置。 |
constellation_layer | 包含 group、features[](元素为 { name, norad_id, lat, lng })及可选 track 的对象 | 卡片中的卫星周围的其他卫星。 |
building_card | 包含 lat、lng、可选 name、height_m、num_floors 的对象 | 建筑物相关数据。 |
trees_card | 包含 lat、lng、count、radius_m 的对象 | 某点附近的普查树木。 |
heritage_card | 包含 lat、lng、count、sites[] 的对象 | 某点附近的文化遗产地点。 |
rail_card | 包含 lat、lng、lines[]、可选 nearest_station 的对象 | 某点附近的铁路线路。 |
arrivals_card | 包含 lat、lng、stop、departures[]、alerts[]、attribution 的对象 | 某站点的公共交通发车信息。 |
vehicles_layer | 包含 operator、vehicles[]、shapes[]、attribution 的对象 | 卡片中站点或线路的公共交通车辆位置。 |
streetview_card | 包含 lat、lng、image_id、image_lat、image_lng 的对象 | 地点附近的街景影像。 |
发送过这些事件时,end 会以相同名称再次包含其中五种数据,便于漏收事件的客户端读取:weather_card、satellite_card、constellation_layer、weather_layer 和 vehicles_layer(例如 end.weather_card)。其他卡片不会在 end 中重复。平台 API 不提供实时活动列表,参见数据许可。
诊断和实验性内容(不属于协议)
以下内容可能出现在传输数据中,但不属于正式协议,请勿依赖。
event: | 载荷 | 备注 |
|---|---|---|
debug | { scope, … } | 内部诊断信息。正常请求不会发送。 |
place_name_found | { name, is_early } | 提前提示文本中出现某个地点名。应使用其后到达的 place 事件。 |
事件顺序
数据流会经历以下阶段。标记为可选的阶段不一定每次出现。
- 可选。 带有
source: "vendor"的place事件:如果密钥包含vendor权限范围,最多先发送三个来自贵组织自有数据的地点,以便地图立即绘制。 phase。始终是回答本身的第一个事件。- 可选。 生成答案期间发送
: keep-alive注释。 - 可选。 正文之前的事件:
prompt_options;或route_clear后跟route,以及一张卡片。 start。- 正文文本帧,期间穿插
place、place_linked、early_actions,有时还有route_clear、route和图层事件。空闲时通过保活信号维持连接。 - 可选。 正文结束后,延迟到达的
place、place_linked、图层及路线事件。 end或error。- 可选。
quota。 - 服务器关闭连接。
这对客户端有两点影响:
end不是最后一个事件。 请持续读取,直到响应体关闭,否则会错过quota。- 数据流可能在既没有
end也没有error的情况下关闭,例如网络断开。没有终止事件就关闭的流应视为失败。
载荷说明
文本与标记
文本帧只携带文字,不会在正文中嵌入结构化 数据。正文可能用双中括号标记地点名称,例如 near [[Louvre Museum]];聊天面板会将其转换为地图标记链接。若要显示纯文本,请去掉中括号并保留名称。
place
| 字段 | 类型 | 备注 |
|---|---|---|
| name | string | 用于合并记录的键。 |
| coordinates | object { lat, lng } | 十进制度数。可能缺失。 无法定位的地点会在没有坐标的情况下发送,并标记 unverified: true。不要为其绘制地图标记。 |
| address | string | 可选。 |
| category | string | 可选。 |
| icon | string | 可选。地点的表情符号。 |
| source | string | 可选。"vendor" 表示来自贵组织自有数据的地点。"map" 表示已发布地图中已有的地点;聊天面板不会重复绘制标记,因为地图已经绘制。 |
| id | string | 可选。自有数据中的地点会包含此字段。 |
| unverified | boolean | 可选。如果地点位置尚未确认,则为 true。 |
地点还可能包含其他字段,不需要的字段可以忽略。
同一地点名称可能出现多次:先包含坐标,后续再补充信息。每个名称只保留一条记录;一旦获得坐标就不要移动其地图标记,后续事件可补充其他字段。
操作
early_actions.actions 和 end.actions 都是 { type, payload } 对象列表。
| 操作类型 | 载荷 | 处理方式 |
|---|---|---|
| map.fitBounds | { coordinates: [[lng, lat], …], padding, maxZoom, duration } | 让相机视野包含这些坐标。 |
| places.highlight | { places: [ { name, lat, lng } ], options } | 突出显示这些地点。 |
| map.showRoute | { id, label, profile, coordinates: [[lng, lat], …], distance_m, duration_s, turn_by_turn[], transit?, attribution? } | 绘制路线,并展示 attribution。 |
| route.clear | { id? } | 删除具有该 id 的路线;如未指定 id 则删除所有路线。 |
还可能出现其他操作类型。无法处理的类型应跳过。注意两种坐标格式:地点使用 coordinates: { lat, lng },线条使用由 [lng, lat] 组成的列表。
route
primary 包含要绘制的线条:{ coordinates: [[lng, lat], …], distance_m, duration_s, turn_by_turn[] }。turn_by_turn 的每一步包含 text,还可能包含 direction_type、distance_m、duration_s、lat 和 lng。origin 与 destination 各自包含 label。alternatives[] 是带有 route_id 的其他路线;comparison[] 给出每条备选路线相对于主路线的 delta_duration_s。如有 stops[],它是多站路线中按顺序排列的 { lat, lng, name } 点 位。
道路路线包含 attribution: { text, url, license }。每次绘制路线时,都应显示链接到 url 的 text 署名。公共交通路线的运营方署名则位于 transit 中。
每个 route 事件都有 id。绘制具有相同 id 的路线会替换已有路线。
end
| 字段 | 类型 | 备注 |
|---|---|---|
| full_text | string | 完整答案。优先使用此值,而不是手动拼接的正文帧。 |
| places | array | 按顺序排列的最终地点列表。按名称与已收到的地点合并。 |
| actions | array | 最终地图操作。应以此替换 early_actions 并应用。 |
| dropped_places | array of strings | 可选。此前发送但不属于最终答案的地点名称。请移除它们的地图标记。 |
| question_type | string | 问题类型标签,仅供参考。 |
| response_time | number | 回答耗时,以秒为单位。 |
| total_tokens | number | 可选。回答使用的令牌数,也是计费依据。 |
error
event: error
data: {"type": "error", "code": "upstream_stall", "message": "The AI service stopped responding. Please try again.", "partial_text": "The Louvre sits"}
message 可以直接展示给用户。partial_text 是出错前已经生成的文字。code 只在一种情况下出现:
| 情况 | code | 处理方式 |
|---|---|---|
| 回答停止推进或数据流已达到最大长度 | "upstream_stall" | 提示用户重试。 |
| 回答期间发生的其他错误 | 无 | 显示 message;重试可能成功。 |
error 之后不会再有 end,但仍可能收到 quota 事件。
quota
字段取决于打开数据流时使用的凭证。
| 字段 | 类型 | 服务器密钥或旧版密钥 | 会话令牌 |
|---|---|---|---|
| type | 字符串, "quota" | 是 | 是 |
| meter | 字符串 | 是 | 是 |
| reset_at | 字符串,含时区偏移的 ISO 8601 | 是 | 是 |
| product | 字符串 | 是 | 否 |
| period | 字符串 | 是 | 否 |
| tier | 字符串 | 是 | 否 |
| used | 数字 | 是 | 否 |
| limit | 数字 | 是 | 否 |
会话令牌由网页访客使用,可以从可公开使用的密钥交换取得,也可以是已发布地图的访客会话。其 quota 事件仅说明本计费周期何时重置,不会泄露组织的订阅方案、使用量或限额。浏览器客户端不应依赖 used 或 limit 字段。429 quota_exceeded 响应体也遵循相同规则,参见配额与速率限制。
保活信号与超时
| 行为 | 取值 | 客户端处理方式 |
|---|---|---|
| 聊天流心跳 | 连续 10 秒没有其他输出后发送 : keep-alive 注释 | 收到任何字节时重置空闲计时器,包括注释。 |
| 空闲连接 | 连接静默约一分钟时,传输过程中可能被关闭 | 设置 30 到 60 秒的空闲(读取)超时,不要设置过短的总超时。 |
| 首段正文等待时间 | 复杂问题可能需要几十秒 | 从 phase 开始显示进度,不要将等待视为错误。 |
| 数据流最长持续时间 | 约 10 分钟,此后服务器发送带有 upstream_stall 的 error | 为保险起见,将总超时设置为略长于 10 分钟。 |
中止数据流与计费
关闭连接即可中止数据流。服务器通常会在约十秒内发现断开并停止生成答案。
- 正常完成 的数据流按回答消耗的令牌数计费,数值由
end.total_tokens报告。 - 中止或失败 的数据流在完成前结束时按估算值计费。估算至少涵盖请求中提交的全部对话文本,也可能根据已发送的内容计算。因此,最终费用可能高于预留额度:收到首帧后立即断开,也不会让一段较长的对话免费。
- 自有数据中的地点(
source: "vendor")不计入费用。
重试属于新的请求,会再次计费。参见配额与速率限制。
摘要流和按钮流
这两个接口用于 Kaleidr Chat 的地点卡片。它们使用相同的帧格式、无名称文本帧和末尾的 quota 事件。与主要聊天流相比有三点不同:不发送 phase,不发送地点或操作事件,并且不发送保活信号。应设置 60 秒空闲超时。
两个接口都会在数据流开始前以 422 field_too_long 拒绝过长字段,并忽略未列出的字段,参见接口列表。若流中止或失败,费用基于已发送文本估算,且不会高于预留额度。
POST /chat/summary/stream,描述地点:
- 可选。
address,载荷为{ type: "address", address, source } start,载荷为{ type: "start", poi_type }- 可选,可能重复。
url,载荷为{ type: "url", website, source } - 文本帧,载荷为
{ text } end,载荷为{ type: "end", full_text, summary, poi_type, category, city, country, sources[], website?, total_tokens? };或error,载荷为{ type: "error", message, partial_text }- 可选。
quota
POST /chat/summary/stream,描述建筑物(type: "building"):
start,载荷为{ type: "start", address }- 文本帧
end,载荷为{ type: "end", full_text, parsed, total_tokens? },或error- 可选。
quota
POST /chat/button/stream:
start,载荷为{ type: "start", query_type }- 文本帧
end,载荷为{ type: "end", full_text, reply, query_type, first_token_ms, total_ms, total_tokens? }- 可选。
quota
按钮流没有 error 事件。如果开始后失败,连接会在没有 end 的情况下关闭。开始前失败则返回 HTTP 500,响应体为 {"detail": "Button chat stream error"}。
POST /spot/overview/stream 会发送上述地点摘要的帧,并在此之前发送一个载荷为 { type: "place", name } 的 place 帧:name 是所描述地点或区域的完整名称,例如 "Springfield, Ohio, United States of America",可用于标注或复制。该帧先于其他所有帧发送,也可 能不存在。它不同于聊天数据流中的 place 事件,不含坐标。如果无法描述某个 ref,它会用一帧 error(载荷为 { type: "error", message, ref })代替摘要。参见接口列表。