docs: tighten AI stream lifecycle requirements
This commit is contained in:
@@ -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,并能在调用日志中定位。
|
||||
|
||||
Reference in New Issue
Block a user