Files
--/docs/upstream-sse-conversation.md
T
2026-09-18 03:46:58 +08:00

272 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 上游 Conversation SSE 协议说明
Conversation SSE 是上游对话链路的流式返回协议。每条 SSE `data:` 通常是一段 JSON payload,也可能是协议标记或结束标记。客户端需要按顺序消费这些 payload,维护当前会话状态、文本内容、工具调用状态和图片结果指针。
## 基本形态
常见 payload 示例:
```text
"v1"
{"type":"resume_conversation_token",...}
{"p":"","o":"add","v":{...}}
{"v":{...}}
{"p":"/message/content/parts/0","o":"append","v":"..."}
{"type":"server_ste_metadata","metadata":{...}}
[DONE]
```
处理建议:
| payload | 含义 | 处理方式 |
|:--|:--|:--|
| `"v1"` | 协议版本标记 | 可记录,通常不影响业务 |
| `[DONE]` | 当前 SSE 流结束 | 停止继续读取 |
| JSON object | 事件、消息或 patch | 按字段更新会话状态 |
| JSON string | 短文本 patch 或协议标记 | 结合上下文处理 |
| 非 JSON 内容 | 原始内容 | 保留为 raw 事件,避免中断流 |
## 常用字段
| 字段 | 说明 |
|:--|:--|
| `type` | 上游事件类型,如 `resume_conversation_token`、`input_message`、`message_marker`、`title_generation`、`server_ste_metadata` |
| `conversation_id` | 当前会话 ID,可从多个事件中获得 |
| `p` | patch 路径,例如 `/message/content/parts/0` |
| `o` | patch 操作,例如 `add`、`append`、`replace`、`patch` |
| `v` | patch 值,可能是字符串、数组,也可能包含完整 message |
| `c` | 消息序号或游标,常见于 add 类事件 |
| `message.id` | 消息 ID |
| `message.author.role` | 消息角色,常见 `system`、`user`、`assistant`、`tool` |
| `message.content.content_type` | 内容类型,如 `text`、`multimodal_text`、`model_editable_context` |
| `message.content.parts` | 内容片段,可能包含文本、图片指针或多模态对象 |
| `message.status` | 消息状态,如 `in_progress`、`finished_successfully` |
| `message.end_turn` | 是否结束当前轮次 |
| `metadata.tool_invoked` | 本轮是否调用工具 |
| `metadata.turn_use_case` | 本轮用途,如 `text`、`multimodal` |
| `metadata.async_task_type` | 异步工具任务类型,图片生成通常为 `image_gen` |
## 会话启动事件
上游通常会先返回恢复令牌或会话令牌:
```json
{
"type": "resume_conversation_token",
"kind": "topic",
"token": "...",
"conversation_id": "..."
}
```
这个事件主要用于标识会话和恢复上下文。业务层通常只需要保存 `conversation_id`,`token` 不应该暴露给下游用户。
## 消息 add 场景
完整消息可能通过 `add` 或带 `v.message` 的事件出现:
```json
{
"p": "",
"o": "add",
"v": {
"message": {
"author": {"role": "assistant"},
"content": {"content_type": "text", "parts": [""]},
"status": "in_progress"
},
"conversation_id": "..."
},
"c": 3
}
```
此类事件常用于创建一条新消息。若消息角色为 `assistant`,后续文本通常会通过 patch 继续追加。
## 文本增量场景
文本输出通常由多条 patch 组成:
```json
{"p":"/message/content/parts/0","o":"append","v":"Hello"}
{"v":" world"}
{"p":"","o":"patch","v":[
{"p":"/message/content/parts/0","o":"append","v":"!"},
{"p":"/message/status","o":"replace","v":"finished_successfully"},
{"p":"/message/end_turn","o":"replace","v":true}
]}
```
处理要点:
| 形态 | 含义 |
|:--|:--|
| `p == "/message/content/parts/0"` 且 `o == "append"` | 向当前文本追加内容 |
| `o == "replace"` | 用新值替换目标字段 |
| `o == "patch"` 且 `v` 是数组 | 批量 patch,需要按数组顺序处理 |
| 只有 `v` 且 `v` 是字符串 | 可能是省略路径的文本增量,应结合当前文本流处理 |
## 输入消息场景
用户输入会以 `input_message` 或普通 `user` message 出现。图片编辑请求会包含用户上传的参考图:
```json
{
"type": "input_message",
"input_message": {
"author": {"role": "user"},
"content": {
"content_type": "multimodal_text",
"parts": [
{"asset_pointer": "sediment://file_input"},
"编辑提示词"
]
}
},
"conversation_id": "..."
}
```
这类 `sediment://...` 表示输入附件,不是生成结果。即使它可以被下载,也不能当作输出图片返回。
## 图片工具成功场景
图片生成或图片编辑成功时,上游一般会出现工具消息:
```json
{
"v": {
"message": {
"author": {"role": "tool"},
"content": {
"content_type": "multimodal_text",
"parts": [
{"asset_pointer": "file-service://file_result"},
{"asset_pointer": "sediment://file_result"}
]
},
"metadata": {"async_task_type": "image_gen"}
}
},
"conversation_id": "..."
}
```
只有同时满足以下条件的图片指针,才应该视为输出结果:
| 条件 | 说明 |
|:--|:--|
| `message.author.role == "tool"` | 来源是工具消息 |
| `metadata.async_task_type == "image_gen"` | 工具任务是图片生成 |
| `asset_pointer` 为 `file-service://...` 或 `sediment://...` | 指向可解析图片资源 |
## 图片指针类型
| 指针 | 常见来源 | 说明 |
|:--|:--|:--|
| `file-service://file_xxx` | 图片工具输出 | 可通过文件下载接口解析 |
| `sediment://file_xxx` | 输入附件或图片工具输出 | 需要结合消息角色判断来源 |
| `file_upload` | 上传过程占位 | 通常不应作为输出 |
不要只凭字符串里出现 `file_` 或 `sediment://` 就判定为输出图。必须结合消息角色和任务类型。
## 策略拒绝场景
当上游拒绝请求时,通常不会产生图片工具消息,而是返回普通 assistant 文本:
```text
I can't assist with that request. If you have another type of modification...
```
常见伴随事件:
```json
{"type":"title_generation","title":"Request Denied","conversation_id":"..."}
```
```json
{
"type": "server_ste_metadata",
"metadata": {
"tool_invoked": false,
"turn_use_case": "multimodal",
"did_prompt_contain_image": true
},
"conversation_id": "..."
}
```
处理要点:
| 条件 | 行为 |
|:--|:--|
| 有 assistant 拒绝文本 | 应返回文本消息 |
| `tool_invoked == false` | 说明没有实际工具结果 |
| 没有 `role=tool` 且 `async_task_type=image_gen` 的消息 | 不应收集输出图片 |
| 用户输入消息里有图片指针 | 仍然只视为输入附件 |
## moderation 场景
部分请求可能返回 moderation 事件:
```json
{
"type": "moderation",
"moderation_response": {
"blocked": true
},
"conversation_id": "..."
}
```
若 `blocked == true`,应认为本轮被策略拦截。后续如有 assistant 文本,应优先返回该文本;若没有文本,可返回合适的错误信息。
## marker 和 title 事件
上游会返回一些辅助事件:
```json
{"type":"message_marker","marker":"user_visible_token","event":"first"}
{"type":"message_marker","marker":"last_token","event":"last"}
{"type":"title_generation","title":"...","conversation_id":"..."}
```
这些事件通常用于前端展示、标题生成或流式状态标记,不代表实际文本内容或图片结果。
## metadata 事件
`server_ste_metadata` 用于描述本轮调度和工具状态:
```json
{
"type": "server_ste_metadata",
"metadata": {
"tool_invoked": true,
"turn_use_case": "multimodal",
"model_slug": "i-mini-m",
"did_prompt_contain_image": true
}
}
```
常用判断:
| 字段 | 说明 |
|:--|:--|
| `tool_invoked == true` | 上游认为本轮调用过工具 |
| `tool_invoked == false` | 上游未调用工具,常见于拒绝或纯文本响应 |
| `turn_use_case == "text"` | 按文本响应处理 |
| `turn_use_case == "multimodal"` | 多模态请求,不代表一定有图片输出 |
| `did_prompt_contain_image == true` | 输入包含图片,不代表输出包含图片 |
## 结束后的结果判断
SSE 结束后可按以下顺序判断结果:
1. 如果已经收集到图片工具输出指针,解析并下载输出图片。
2. 如果没有输出图片指针,但有 assistant 文本,并且本轮被拦截或未调用工具,返回文本消息。
3. 如果没有输出图片指针,但有 `conversation_id`,可查询完整会话明细,继续寻找图片工具输出。
4. 查询完整会话时,仍然只读取 `role=tool` 且 `async_task_type=image_gen` 的消息。
5. 如果没有图片结果也没有文本,返回上游异常或空结果错误。