docs: tighten AI stream lifecycle requirements

This commit is contained in:
2026-05-22 22:51:17 +08:00
parent f12a49d4ae
commit 0968f9418f
@@ -173,6 +173,9 @@ INDEX(environment, is_enabled)
| `timeout_ms` | 接口级超时时间 |
| `retry_count` | 接口级重试次数 |
| `retry_delay_ms` | 接口级重试延迟 |
| `first_token_timeout_ms` | 首个文本片段超时时间 |
| `stream_idle_timeout_ms` | 流式空闲超时时间 |
| `max_output_chars` | 单次输出最大字符数 |
| `last_test_status` | 最近一次测试状态 |
| `last_test_time` | 最近一次测试时间 |
| `last_test_trace_id` | 最近一次测试链路 ID |
@@ -263,6 +266,7 @@ INDEX(scene_category, is_enabled)
| `stream_chunk_count` | 流式片段数量 |
| `first_chunk_duration_ms` | 首个片段返回耗时 |
| `completed_at` | 流式完成时间 |
| `output_preview` | 输出预览,保存前后截断后的文本 |
| `duration_ms` | 耗时 |
| `status` | `success``failed` |
| `error_code` | 错误码 |
@@ -278,7 +282,7 @@ INDEX(endpoint_config_id, create_time)
INDEX(status, create_time)
```
日志中 `request_headers` 必须脱敏 `Authorization``api-key``x-api-key``client_secret` 等敏感字段。
日志中 `request_headers` 必须脱敏 `Authorization``api-key``x-api-key``client_secret` 等敏感字段。流式响应内容可能很长,`response_body``parsed_result` 不保存无限完整文本;默认保存元数据、错误上下文、`output_preview``stream_chunk_count` 和首包耗时。业务表需要完整结果时,由具体业务在 `done` 后保存最终文本。
## 初始场景编码
@@ -378,7 +382,7 @@ Coze 默认请求模板:
}
```
Coze 响应解析先沿用现有 `AiChatServiceImpl` 中的 SSE 解析逻辑,但运行时必须边解析边输出 `AiStreamEvent.delta`。如果 Coze 在 End 节点返回 `content.output`适配器至少要在收到 End 前持续转发可用文本片段或阶段事件,不能让用户端一直空白等待;迁移完成后该逻辑归属 `CozeProviderAdapter`
Coze 响应解析先沿用现有 `AiChatServiceImpl` 中的 SSE 解析逻辑,但运行时必须边解析边输出 `AiStreamEvent.delta`。如果某个 Coze workflow 只能在 End 节点一次性返回 `content.output`该 workflow 不能作为用户场景 endpoint 启用,需要先调整 Coze 工作流或更换 provider,使其能产生可展示文本片段。`message` 阶段事件可以辅助展示进度,但不能替代文本 `delta`;迁移完成后该逻辑归属 `CozeProviderAdapter`
## 请求模板与响应解析
@@ -470,6 +474,14 @@ Coze 响应解析先沿用现有 `AiChatServiceImpl` 中的 SSE 解析逻辑,
后端可以在内部累积完整文本,但不能因为落库、解析或审核而阻塞 `delta` 下发。业务落库发生在 `done` 之后;如果中途失败,保存已输出片段和失败原因,便于排查。
流式完成判定:
1. 成功调用必须至少发送一个 `start`、一个非空文本 `delta`、一个 `done`
2. 如果服务商正常结束但没有任何非空 `delta`,调用按失败处理,错误码为 `AI_STREAM_NO_DELTA`
3. 如果首个 `delta` 超过 endpoint 的首包超时阈值,发送 `error` 并记录 `AI_STREAM_TIMEOUT`
4. 如果服务商中途断流,发送 `error`,保留已输出文本,记录 `AI_STREAM_INTERRUPTED`
5. 如果用户端主动断开,后端取消上游请求或停止读取,并记录 `AI_STREAM_CLIENT_DISCONNECTED`
统一错误码:
| 错误码 | 场景 |
@@ -485,6 +497,11 @@ Coze 响应解析先沿用现有 `AiChatServiceImpl` 中的 SSE 解析逻辑,
| `AI_TEMPLATE_INVALID_JSON` | 请求模板渲染后不是合法 JSON |
| `AI_PROVIDER_REQUEST_FAILED` | 外部服务调用失败 |
| `AI_RESPONSE_PARSE_ERROR` | 响应解析失败 |
| `AI_STREAM_NO_DELTA` | 服务商连接成功但没有产生可展示文本片段 |
| `AI_STREAM_TIMEOUT` | 流式首包或整体输出超时 |
| `AI_STREAM_CLIENT_DISCONNECTED` | 用户端连接中断 |
| `AI_STREAM_INTERRUPTED` | 服务商流式输出中断 |
| `AI_STREAM_OUTPUT_LIMIT` | 输出超过 endpoint 配置的最大字符数 |
| `AI_FALLBACK_FAILED` | 主配置和兜底配置都失败 |
业务层收到错误时,只处理统一错误码和用户可见提示,不直接解析 Dify 或 Coze 的原始错误结构。
@@ -553,6 +570,14 @@ POST /ai/scenes/test
- 错误事件。
- traceId 和调用日志入口。
后台测试通过条件:
1. 必须收到 `start`
2. 必须在 `first_token_timeout_ms` 内收到至少一个非空 `delta`
3. 必须收到 `done`,或在失败时收到 `error` 并能展示统一错误码。
4. `chunkCount` 必须大于 0。
5. 调用日志必须能通过 traceId 查到 provider、endpoint、scene、耗时、chunk 数量和错误上下文。
所有返回 AI 生成内容的业务接口都必须改造为流式接口,或通过现有 WebSocket 通道输出统一 `AiStreamEvent`。旧的一次性响应接口在迁移期只能作为内部兼容入口,不能继续作为用户端调用入口;如果某个页面仍调用一次性 AI 接口,该页面不算完成迁移。
## 后台页面
@@ -680,6 +705,22 @@ POST /ai/scenes/test
任何新 AI 功能在上线前必须提供用户端流式展示验证;如果只能一次性显示完整文本,则不能进入验收。
### 用户端场景迁移清单
每个 AI 场景迁移时都要同时完成后端路由、业务接口和用户端渲染,不能只改后台配置:
| sceneCode | 用户端入口 | 输出要求 |
| --- | --- | --- |
| `chat` | 对话页 / WebSocket 对话 | 用户发送后立即进入生成中,逐段追加回复 |
| `script_generate` | 剧本生成页 | 章节、片段或正文逐段显示,最终文本在 `done` 后保存 |
| `short_story_generate` | 短篇小说生成页 | 正文逐段显示,失败时保留已输出内容 |
| `diary_summary` | 日记总结 / AI 评论入口 | 总结文本逐段显示,不等待完整总结 |
| `emotion_summary` | 情绪总结入口 | 总结文本逐段显示 |
| `emotion_analysis` | 情绪分析入口 | 分析文本逐段显示,结构化结果在 `done` 后落库 |
| `life_healing` | 人生事件疗愈入口 | 回复文本逐段显示 |
迁移完成的判定是用户端页面实际消费 `AiStreamEvent`,并能在弱网或长文本生成时持续更新。如果只是在后端拿到流式内容再拼成完整文本返回给页面,不算完成迁移。
## 运行时行为
### 立即生效
@@ -711,6 +752,17 @@ scene_code + environment
运行环境来源固定为后端当前环境配置,例如 Spring Profile 或项目已有环境变量。业务代码不从前端请求参数读取 environment,避免用户侧伪造环境导致误路由。
### 流式连接控制
流式调用需要显式处理连接生命周期:
1. 后端为每次调用创建 `traceId`,并在 `start` 事件中返回。
2. 后端向 Dify/Coze 发起请求后,不能在当前线程同步等待完整响应;必须边读取边转发。
3. 用户端断开 SSE/WebSocket 时,后端应停止继续向用户端写入,并尽量取消上游请求。
4. 超过 `stream_idle_timeout_ms` 没有任何新事件时,后端发送 `error` 并结束连接。
5. 超过 `max_output_chars` 时,后端发送 `error` 并记录 `AI_STREAM_OUTPUT_LIMIT`,避免无限输出拖垮页面和日志。
6. 同一用户同时发起多个 AI 场景请求时,前端按 `traceId` 区分输出容器,不能串流到错误页面区域。
### 配置生效与切换保护
立即生效只针对已启用配置。新增或编辑 provider、endpoint、scene 时,保存动作只落库;是否进入运行时由 `is_enabled` 控制。
@@ -809,6 +861,14 @@ Dify 初始化:
8. 将对话、日记总结、情绪分析、疗愈等旧 Coze 场景逐步迁到 `sceneCode`
9. 保留旧 `AiConfig` 页面或提供只读迁移视图,确认稳定后再下线。
每迁移一个场景,都必须同时完成以下交付:
1. 后台存在该 `sceneCode` 的启用绑定。
2. endpoint 支持流式并通过后台流式测试。
3. 业务接口改为 `invokeStream` 或 WebSocket 流式转发。
4. 用户端页面消费 `AiStreamEvent` 并逐段渲染。
5. 调用日志能查到该场景的 traceId、chunk 数、首包耗时和最终状态。
每一步交付都应保持系统可启动、已有 Coze 对话不回归。
## 测试计划
@@ -821,6 +881,7 @@ Dify 初始化:
- `CozeProviderAdapterTest`:验证 Coze 请求体生成和 SSE 解析。
- `AiRuntimeStreamTest`:验证 `invokeStream` 会按 `start``delta``done` 顺序输出事件,且不会等待完整响应才返回。
- `AiStreamGatewayTest`:验证 SSE/WebSocket 转发格式、错误事件、断连处理和 traceId 透传。
- `AiStreamLifecycleTest`:验证首包超时、空闲超时、用户端断连、无 delta、输出过长等异常都会产生统一错误码和调用日志。
- `AiTemplateRendererTest`:验证占位符替换、对象注入、缺失变量报错和 JSON 校验。
- 迁移脚本测试:验证旧 `t_ai_config` 能拆成 provider、endpoint、scene。
@@ -832,6 +893,7 @@ Dify 初始化:
- 禁用配置后不能被正常调用。
- 测试请求不在浏览器暴露明文 Token。
- 用户端和后台测试页都能逐段显示 AI 输出,不出现等待接口完成后一次性渲染的行为。
- 用户端断网、返回、重复发起请求时,不会把不同 traceId 的输出串到同一个展示区域。
集成验收:
@@ -841,6 +903,7 @@ Dify 初始化:
- 将剧本生成从 Dify 切回 Coze 后,不发版,下一次调用立即生效。
- 主配置失败且配置了兜底时,调用日志记录 fallback。
- 用户端对话、剧本生成、短篇小说生成、总结分析等 AI 场景都能看到流式文本逐段输出。
- 任一场景出现服务商无文本片段、超时或断流时,用户端能看到错误状态,后台能查到统一错误码和 traceId。
## 风险与缓解
@@ -853,6 +916,9 @@ Dify 初始化:
| Token 暴露风险 | 管理后台可编辑但日志和普通详情默认脱敏 |
| 请求模板配置错误导致线上失败 | 保存时校验 JSON,测试通过后再允许启用场景绑定 |
| 场景切换误操作影响生产 | 环境隔离、操作审计、场景测试和明确启用开关 |
| 服务商连接成功但没有输出文本 | 以 `AI_STREAM_NO_DELTA` 判定失败,禁止作为用户场景验收通过 |
| 长文本流式输出拖垮页面或日志 | 设置 `max_output_chars`,日志保存预览和统计,不保存无限完整流 |
| 用户端断连导致后台继续消耗服务商资源 | 检测 SSE/WebSocket 断开并取消上游请求,记录 `AI_STREAM_CLIENT_DISCONNECTED` |
## 验收标准
@@ -869,3 +935,5 @@ Dify 初始化:
11. 对话、剧本生成、短篇小说生成、总结分析、疗愈等所有 AI 用户端入口都能逐段显示输出,不能一次性等完整结果后再渲染。
12. 非流式 endpoint 不能启用到用户场景,启用时必须返回 `AI_ENDPOINT_STREAM_REQUIRED`
13. 每个已迁移 AI 场景都必须满足“有 start、有 delta、有 done 或 error、有调用日志、有用户端可见输出”,不能出现接口成功但页面无输出的情况。
14. 后台流式测试必须校验首包耗时、chunk 数、最终状态和 traceId 日志入口。
15. 用户端断连、服务商断流、无 delta、首包超时等异常都必须结束 loading,并能在调用日志中定位。