- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
24 KiB
Dify 对话流 API 官方文档
文档来源:https://dify.ireborn.com.cn/app/4bea851a-7f24-47bd-9d0b-1d74f69ba603/develop 导出时间:2025-10-14
工作流编排对话型应用 API
对话应用支持会话持久化,可将之前的聊天记录作为上下文进行回答,可适用于聊天/客服 AI 等。
基础 URL
http://dify.ireborn.com.cn/v1
鉴权
Service API 使用 API-Key 进行鉴权。
强烈建议开发者把 API-Key 放在后端存储,而非分享或者放在客户端存储,以免 API-Key 泄露,导致财产损失。
所有 API 请求都应在 Authorization HTTP Header 中包含您的 API-Key,如下所示:
Authorization: Bearer {API_KEY}
POST /chat-messages - 发送对话消息
创建会话消息。
Request Body
| Name | Type | Description |
|---|---|---|
query |
string | 用户输入/提问内容。 |
inputs |
object | 允许传入 App 定义的各变量值。inputs 参数包含了多组键值对(Key/Value pairs),每组的键对应一个特定变量,每组的值则是该变量的具体值。如果变量是文件类型,请指定一个包含以下 files 中所述键的对象。默认 {} |
response_mode |
string | streaming 流式模式(推荐)。基于 SSE(Server-Sent Events)实现类似打字机输出方式的流式返回。blocking 阻塞模式,等待执行完毕后返回结果。(请求若流程较长可能会被中断)。由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。 |
user |
string | 用户标识,用于定义终端用户的身份,方便检索、统计。由开发者定义规则,需保证用户标识在应用内唯一。服务 API 不会共享 WebApp 创建的对话。 |
conversation_id |
string | (选填)会话 ID,需要基于之前的聊天记录继续对话,必须传之前消息的 conversation_id。 |
files |
array[object] | 文件列表,适用于传入文件结合文本理解并回答问题,仅当模型支持 Vision/Video 能力时可用。 文件类型: - document: TXT, MD, MARKDOWN, MDX, PDF, HTML, XLSX, XLS, VTT, PROPERTIES, DOC, DOCX, CSV, EML, MSG, PPTX, PPT, XML, EPUB- image: JPG, JPEG, PNG, GIF, WEBP, SVG- audio: MP3, M4A, WAV, WEBM, MPGA- video: MP4, MOV, MPEG, WEBM- custom: 其他文件类型transfer_method (string) 传递方式: - remote_url: 文件地址- local_file: 上传文件- url: 文件地址(仅当传递方式为 remote_url 时)- upload_file_id: 上传文件 ID(仅当传递方式为 local_file 时) |
auto_generate_name |
bool | (选填)自动生成标题,默认 true。若设置为 false,则可通过调用会话重命名接口并设置 auto_generate 为 true 实现异步生成标题。 |
workflow_id |
string | (选填)工作流ID,用于指定特定版本,如果不提供则使用默认的已发布版本。 |
trace_id |
string | (选填)链路追踪ID。适用于与业务系统已有的trace组件打通,实现端到端分布式追踪等场景。如果未指定,系统会自动生成trace_id。支持以下三种方式传递,具体优先级依次为: 1. Header:通过 HTTP Header X-Trace-Id 传递,优先级最高。 2. Query 参数:通过 URL 查询参数 trace_id 传递。 3. Request Body:通过请求体字段 trace_id 传递(即本字段)。 |
Response
当 response_mode 为 blocking 时,返回 ChatCompletionResponse object。
当 response_mode 为 streaming 时,返回 ChunkChatCompletionResponse object 流式序列。
ChatCompletionResponse(阻塞模式)
返回完整的 App 结果,Content-Type 为 application/json。
| 字段 | 类型 | 描述 |
|---|---|---|
event |
string | 事件类型,固定为 message |
task_id |
string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
id |
string | 唯一ID |
message_id |
string | 消息唯一 ID |
conversation_id |
string | 会话 ID |
mode |
string | App 模式,固定为 chat |
answer |
string | 完整回复内容 |
metadata |
object | 元数据 |
usage |
Usage | 模型用量信息 |
retriever_resources |
array[RetrieverResource] | 引用和归属分段列表 |
created_at |
int | 消息创建时间戳,如:1705395332 |
ChunkChatCompletionResponse(流式模式)
返回 App 输出的流式块,Content-Type 为 text/event-stream。
每个流式块均为 data: 开头,块之间以 \n\n 即两个换行符分隔,如下所示:
data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n
流式块中根据 event 不同,结构也不同:
event: workflow_started
workflow 开始执行
| 字段 | 类型 | 描述 |
|---|---|---|
task_id |
string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
workflow_run_id |
string | workflow 执行 ID |
event |
string | 固定为 workflow_started |
data |
object | 详细内容 |
data.id |
string | workflow 执行 ID |
data.workflow_id |
string | 关联 Workflow ID |
data.created_at |
timestamp | 开始时间 |
event: node_started
node 开始执行
| 字段 | 类型 | 描述 |
|---|---|---|
task_id |
string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
workflow_run_id |
string | workflow 执行 ID |
event |
string | 固定为 node_started |
data |
object | 详细内容 |
data.id |
string | workflow 执行 ID |
data.node_id |
string | 节点 ID |
data.node_type |
string | 节点类型 |
data.title |
string | 节点名称 |
data.index |
int | 执行序号,用于展示 Tracing Node 顺序 |
data.predecessor_node_id |
string | 前置节点 ID,用于画布展示执行路径 |
data.inputs |
object | 节点中所有使用到的前置节点变量内容 |
data.created_at |
timestamp | 开始时间 |
event: node_finished
node 执行结束,成功失败同一事件中不同状态
| 字段 | 类型 | 描述 |
|---|---|---|
task_id |
string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
workflow_run_id |
string | workflow 执行 ID |
event |
string | 固定为 node_finished |
data |
object | 详细内容 |
data.id |
string | node 执行 ID |
data.node_id |
string | 节点 ID |
data.index |
int | 执行序号,用于展示 Tracing Node 顺序 |
data.predecessor_node_id |
string | optional 前置节点 ID,用于画布展示执行路径 |
data.inputs |
object | 节点中所有使用到的前置节点变量内容 |
data.process_data |
json | Optional 节点过程数据 |
data.outputs |
json | Optional 输出内容 |
data.status |
string | 执行状态 running / succeeded / failed / stopped |
data.error |
string | Optional 错误原因 |
data.elapsed_time |
float | Optional 耗时(s) |
data.execution_metadata |
json | 元数据 |
data.execution_metadata.total_tokens |
int | optional 总使用 tokens |
data.execution_metadata.total_price |
decimal | optional 总费用 |
data.execution_metadata.currency |
string | optional 货币,如 USD / RMB |
data.created_at |
timestamp | 开始时间 |
event: workflow_finished
workflow 执行结束,成功失败同一事件中不同状态
| 字段 | 类型 | 描述 |
|---|---|---|
task_id |
string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
workflow_run_id |
string | workflow 执行 ID |
event |
string | 固定为 workflow_finished |
data |
object | 详细内容 |
data.id |
string | workflow 执行 ID |
data.workflow_id |
string | 关联 Workflow ID |
data.status |
string | 执行状态 running / succeeded / failed / stopped |
data.outputs |
json | Optional 输出内容 |
data.error |
string | Optional 错误原因 |
data.elapsed_time |
float | Optional 耗时(s) |
data.total_tokens |
int | Optional 总使用 tokens |
data.total_steps |
int | 总步数(冗余),默认 0 |
data.created_at |
timestamp | 开始时间 |
data.finished_at |
timestamp | 结束时间 |
event: message
LLM 返回文本块事件,即:完整的文本以分块的方式输出。
| 字段 | 类型 | 描述 |
|---|---|---|
task_id |
string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
message_id |
string | 消息唯一 ID |
conversation_id |
string | 会话 ID |
answer |
string | LLM 返回文本块内容 |
created_at |
int | 创建时间戳,如:1705395332 |
event: message_file
文件事件,表示有新文件需要展示
| 字段 | 类型 | 描述 |
|---|---|---|
id |
string | 文件唯一ID |
type |
string | 文件类型,目前仅为image |
belongs_to |
string | 文件归属,user或assistant,该接口返回仅为 assistant |
url |
string | 文件访问地址 |
conversation_id |
string | 会话ID |
event: message_end
消息结束事件,收到此事件则代表流式返回结束。
| 字段 | 类型 | 描述 |
|---|---|---|
task_id |
string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
message_id |
string | 消息唯一 ID |
conversation_id |
string | 会话 ID |
metadata |
object | 元数据 |
usage |
Usage | 模型用量信息 |
retriever_resources |
array[RetrieverResource] | 引用和归属分段列表 |
event: tts_message
TTS 音频流事件,即:语音合成输出。内容是Mp3格式的音频块,使用 base64 编码后的字符串,播放的时候直接解码即可。(开启自动播放才有此消息)
| 字段 | 类型 | 描述 |
|---|---|---|
task_id |
string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
message_id |
string | 消息唯一 ID |
audio |
string | 语音合成之后的音频块使用 Base64 编码之后的文本内容,播放的时候直接 base64 解码送入播放器即可 |
created_at |
int | 创建时间戳,如:1705395332 |
event: tts_message_end
TTS 音频流结束事件,收到这个事件表示音频流返回结束。
| 字段 | 类型 | 描述 |
|---|---|---|
task_id |
string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
message_id |
string | 消息唯一 ID |
audio |
string | 结束事件是没有音频的,所以这里是空字符串 |
created_at |
int | 创建时间戳,如:1705395332 |
event: message_replace
消息内容替换事件。开启内容审查和审查输出内容时,若命中了审查条件,则会通过此事件替换消息内容为预设回复。
| 字段 | 类型 | 描述 |
|---|---|---|
task_id |
string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
message_id |
string | 消息唯一 ID |
conversation_id |
string | 会话 ID |
answer |
string | 替换内容(直接替换 LLM 所有回复文本) |
created_at |
int | 创建时间戳,如:1705395332 |
event: error
流式输出过程中出现的异常会以 stream event 形式输出,收到异常事件后即结束。
| 字段 | 类型 | 描述 |
|---|---|---|
task_id |
string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
message_id |
string | 消息唯一 ID |
status |
int | HTTP 状态码 |
code |
string | 错误码 |
message |
string | 错误消息 |
event: ping
每 10s 一次的 ping 事件,保持连接存活。
Errors
404- 对话不存在400, invalid_param- 传入参数异常400, app_unavailable- App 配置不可用400, provider_not_initialize- 无可用模型凭据配置400, provider_quota_exceeded- 模型调用额度不足400, model_currently_not_support- 当前模型不可用400, workflow_not_found- 指定的工作流版本未找到400, draft_workflow_error- 无法使用草稿工作流版本400, workflow_id_format_error- 工作流ID格式错误,需要UUID格式400, completion_request_error- 文本生成失败500- 服务内部异常
Request 示例
curl -X POST 'http://dify.ireborn.com.cn/v1/chat-messages' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{
"inputs": {},
"query": "What are the specs of the iPhone 13 Pro Max?",
"response_mode": "streaming",
"conversation_id": "",
"user": "abc-123",
"files": [
{
"type": "image",
"transfer_method": "remote_url",
"url": "https://cloud.dify.ai/logo/logo-site.png"
}
]
}'
Response 示例
阻塞模式
{
"event": "message",
"task_id": "c3800678-a077-43df-a102-53f23ed20b88",
"id": "9da23599-e713-473b-982c-4328d4f5c78a",
"message_id": "9da23599-e713-473b-982c-4328d4f5c78a",
"conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2",
"mode": "chat",
"answer": "iPhone 13 Pro Max specs are listed here:...",
"metadata": {
"usage": {
"prompt_tokens": 1033,
"prompt_unit_price": "0.001",
"prompt_price_unit": "0.001",
"prompt_price": "0.0010330",
"completion_tokens": 128,
"completion_unit_price": "0.002",
"completion_price_unit": "0.001",
"completion_price": "0.0002560",
"total_tokens": 1161,
"total_price": "0.0012890",
"currency": "USD",
"latency": 0.7682376249867957
},
"retriever_resources": [
{
"position": 1,
"dataset_id": "101b4c97-fc2e-463c-90b1-5261a4cdcafb",
"dataset_name": "iPhone",
"document_id": "8dd1ad74-0b5f-4175-b735-7d98bbbb4e00",
"document_name": "iPhone List",
"segment_id": "ed599c7f-2766-4294-9d1d-e5235a61270a",
"score": 0.98457545,
"content": "\"Model\",\"Release Date\",\"Display Size\",\"Resolution\",\"Processor\",\"RAM\",\"Storage\",\"Camera\",\"Battery\",\"Operating System\"\n\"iPhone 13 Pro Max\",\"September 24, 2021\",\"6.7 inch\",\"1284 x 2778\",\"Hexa-core (2x3.23 GHz Avalanche + 4x1.82 GHz Blizzard)\",\"6 GB\",\"128, 256, 512 GB, 1TB\",\"12 MP\",\"4352 mAh\",\"iOS 15\""
}
]
},
"created_at": 1705407629
}
流式模式
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " I", "created_at": 1679586595}
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": "'m", "created_at": 1679586595}
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " glad", "created_at": 1679586595}
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " to", "created_at": 1679586595}
data: {"event": "message", "message_id" : "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " meet", "created_at": 1679586595}
data: {"event": "message", "message_id" : "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " you", "created_at": 1679586595}
data: {"event": "message_end", "id": "5e52ce04-874b-4d27-9045-b3bc80def685", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "metadata": {"usage": {"prompt_tokens": 1033, "prompt_unit_price": "0.001", "prompt_price_unit": "0.001", "prompt_price": "0.0010330", "completion_tokens": 135, "completion_unit_price": "0.002", "completion_price_unit": "0.001", "completion_price": "0.0002700", "total_tokens": 1168, "total_price": "0.0013030", "currency": "USD", "latency": 1.381760165997548}, "retriever_resources": [{"position": 1, "dataset_id": "101b4c97-fc2e-463c-90b1-5261a4cdcafb", "dataset_name": "iPhone", "document_id": "8dd1ad74-0b5f-4175-b735-7d98bbbb4e00", "document_name": "iPhone List", "segment_id": "ed599c7f-2766-4294-9d1d-e5235a61270a", "score": 0.98457545, "content": "\"Model\",\"Release Date\",\"Display Size\",\"Resolution\",\"Processor\",\"RAM\",\"Storage\",\"Camera\",\"Battery\",\"Operating System\"\n\"iPhone 13 Pro Max\",\"September 24, 2021\",\"6.7 inch\",\"1284 x 2778\",\"Hexa-core (2x3.23 GHz Avalanche + 4x1.82 GHz Blizzard)\",\"6 GB\",\"128, 256, 512 GB, 1TB\",\"12 MP\",\"4352 mAh\",\"iOS 15\""}]}}
data: {"event": "tts_message", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"}
data: {"event": "tts_message_end", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": ""}
POST /files/upload - 上传文件
上传文件并在发送消息时使用,可实现图文多模态理解。支持您的应用程序所支持的所有格式。上传的文件仅供当前终端用户使用。
该接口需使用 multipart/form-data 进行请求。
Request Body
| Name | Type | Description |
|---|---|---|
file |
file | 要上传的文件。 |
user |
string | 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 |
Response
成功上传后,服务器会返回文件的 ID 和相关信息。
| 字段 | 类型 | 描述 |
|---|---|---|
id |
uuid | ID |
name |
string | 文件名 |
size |
int | 文件大小(byte) |
extension |
string | 文件后缀 |
mime_type |
string | 文件 mime-type |
created_by |
uuid | 上传人 ID |
created_at |
timestamp | 上传时间 |
Errors
400, no_file_uploaded- 必须提供文件400, too_many_files- 目前只接受一个文件400, unsupported_preview- 该文件不支持预览400, unsupported_estimate- 该文件不支持估算413, file_too_large- 文件太大415, unsupported_file_type- 不支持的扩展名,当前只接受文档类文件503, s3_connection_failed- 无法连接到 S3 服务503, s3_permission_denied- 无权限上传文件到 S3503, s3_file_too_large- 文件超出 S3 大小限制
Request 示例
curl -X POST 'http://dify.ireborn.com.cn/v1/files/upload' \
--header 'Authorization: Bearer {api_key}' \
--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \
--form 'user=abc-123'
Response 示例
{
"id": "72fa9618-8f89-4a37-9b33-7e1178a24a67",
"name": "example.png",
"size": 1024,
"extension": "png",
"mime_type": "image/png",
"created_by": 123,
"created_at": 1577836800
}
POST /chat-messages/:task_id/stop - 停止响应
仅支持流式模式。
Path
| 参数 | 类型 | 描述 |
|---|---|---|
task_id |
string | 任务 ID,可在流式返回 Chunk 中获取 |
Request Body
| Name | Type | Description |
|---|---|---|
user |
string | Required 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。API 无法访问 WebApp 创建的会话。 |
Response
| 字段 | 类型 | 描述 |
|---|---|---|
result |
string | 固定返回 success |
Request 示例
curl -X POST 'http://dify.ireborn.com.cn/v1/chat-messages/:task_id/stop' \
-H 'Authorization: Bearer {api_key}' \
-H 'Content-Type: application/json' \
--data-raw '{
"user": "abc-123"
}'
Response 示例
{
"result": "success"
}
其他接口
文档还包含以下接口(此处仅列出标题,详细内容请查看原始文档):
- POST /messages/:message_id/feedbacks - 消息反馈(点赞)
- GET /app/feedbacks - 获取APP的消息点赞和反馈
- GET /messages/{message_id}/suggested - 获取下一轮建议问题列表
- GET /messages - 获取会话历史消息
- GET /conversations - 获取会话列表
- DELETE /conversations/:conversation_id - 删除会话
- POST /conversations/:conversation_id/name - 会话重命名
- GET /conversations/:conversation_id/variables - 获取对话变量
- PUT /conversations/:conversation_id/variables/:variable_id - 更新对话变量
- POST /audio-to-text - 语音转文字
- POST /text-to-audio - 文字转语音
- GET /info - 获取应用基本信息
- GET /parameters - 获取应用参数
- GET /meta - 获取应用Meta信息
- GET /site - 获取应用 WebApp 设置
- GET /apps/annotations - 获取标注列表
- POST /apps/annotations - 创建标注
- PUT /apps/annotations/{annotation_id} - 更新标注
- DELETE /apps/annotations/{annotation_id} - 删除标注
- POST /apps/annotation-reply/{action} - 标注回复初始设置
- GET /apps/annotation-reply/{action}/status/{job_id} - 查询标注回复初始设置任务状态
关键说明
流式模式事件流程
对于工作流编排的对话应用,典型的事件流程如下:
workflow_started- 工作流开始(包含 conversation_id)node_started- 节点开始执行node_finished- 节点执行完成(可能包含输出数据)workflow_finished- 工作流完成(包含最终输出)message- LLM 文本块(逐字返回,可能有多个)message_end- 消息结束
conversation_id 管理
- 首次对话:不传
conversation_id,系统会在workflow_started事件中返回新的conversation_id - 续接对话:传入之前获取的
conversation_id,保持上下文连续性
重要注意事项
- API Key 必须放在后端,不要暴露在客户端
- 流式模式使用 SSE(Server-Sent Events)协议
- 每个事件块以
data:开头,块之间用\n\n分隔 - Cloudflare 有 100 秒超时限制(阻塞模式)
- 流式模式每 10 秒发送一次 ping 事件保持连接