diff --git a/docs/incidents/2026-07-22-miniprogram-voice-retry.md b/docs/incidents/2026-07-22-miniprogram-voice-retry.md new file mode 100644 index 0000000..2980d3c --- /dev/null +++ b/docs/incidents/2026-07-22-miniprogram-voice-retry.md @@ -0,0 +1,127 @@ +# 小程序语音转录失败后无法重试 + +## 状态 + +- 记录日期:2026-07-22 +- 当前状态:待处理 +- 影响范围:小程序反馈生成页的语音转录重试流程 +- 相关会话:`7dc4f0dd-bd32-4d4b-b90a-c36ae83c2ca7` + +## 用户现象 + +1. 用户点击“生成反馈”。 +2. 小程序提示“部分语音转录失败,请重试后再生成反馈”。 +3. 用户尝试点击重试入口,但界面没有可观察到的状态变化。 +4. 再次点击“生成反馈”仍收到相同提示。 + +## 已确认事实 + +### 请求轨迹 + +2026-07-22 14:07:04 至 14:09:42(Asia/Shanghai)期间,同一反馈会话共发起 6 次: + +```text +POST /api/v1/feedback-sessions/7dc4f0dd-bd32-4d4b-b90a-c36ae83c2ca7/generate +``` + +6 次请求均返回 HTTP 400。同期 Loki 中没有以下请求: + +```text +POST /api/v1/audio-segments/{segment_id}/retry +POST /api/v1/feedback-lessons/{lesson_id}/finish +``` + +因此,本次“点击重试无反应”发生在重试请求到达 API 之前,不能归因于 API 重试接口或 FunASR 在重试阶段报错。 + +可用于关联排查的生成请求编号包括: + +- `e0516f03-7c38-4cfc-aa6f-78dfa4188fb5` +- `2eacfd84-449d-4135-a74b-0582a1ab1a4f` +- `14359b2d-a97c-4b3e-a3d1-cc37ad33b8ab` +- `49375fae-b00f-471b-a63b-9aa4e1a13dd8` +- `c30130e9-3e10-4050-a807-720fc90bb39a` +- `45f8188e-921b-45a1-bc06-76b1502949be` + +### 失败片段 + +数据库中存在一个阻止反馈生成的失败片段: + +- 片段 ID:`4dc61129-10b4-4a4c-91ff-b44ac38f5997` +- 时长:2800 ms +- 格式:MP3 +- 转录尝试次数:1 +- 失败时间:2026-07-21 15:18:18(Asia/Shanghai) +- FunASR 响应:HTTP 422,`no clear speech was recognized` + +同一反馈会话的其余 5 个录音片段均为 `ready`。当前生成接口会在发现任意 `failed` 片段时拒绝整个反馈生成请求。 + +## 代码层问题 + +### 主操作没有优先处理失败片段 + +`pages/feedback/index.ts` 的 `syncVoicePresentation` 在存在可生成内容时把主操作设置为“生成反馈”,即使会话同时包含失败片段。`saveFeedback` 也只优先处理 `processing`,没有在调用生成接口前处理 `failedSegmentCount`。 + +结果是用户可以持续点击“生成反馈”,但 API 必然持续返回 HTTP 400。 + +### 重试入口不够明确 + +当前重试流程隐藏在语音状态区域和原生弹窗中: + +```text +点击语音状态 -> 打开语音记录弹窗 -> 点击“重试失败项” +``` + +页面没有独立、明确的“重试转录”主操作,也没有显示将要重试的失败片段数量。 + +### 生成失败后没有刷新会话 + +`generateFeedback` 捕获 API 错误后只显示短暂 Toast,没有重新调用 `loadActiveSession`,也没有把错误信息和 `X-Request-Id` 保存到页面的持久错误区域。 + +这会导致服务端状态和页面状态不一致时,用户无法通过当前页面恢复到正确的重试入口。 + +### 缺少重试操作可观测性 + +API 当前只有通用 HTTP 请求日志。重试请求未到达服务端时,没有前端操作日志;请求到达服务端后,也缺少 `transcription_retry_requested` 等业务事件日志。 + +## 待实现方案 + +1. 当 `failedSegmentCount > 0` 时,主操作优先显示“重试转录”,并直接调用失败片段重试流程。 +2. 禁止在存在失败片段时调用生成接口。 +3. 重试开始后立即显示处理中状态,刷新会话并恢复转录轮询,直到片段进入 `ready` 或再次进入 `failed`。 +4. 生成接口返回失败片段错误时,重新加载活动会话并展示持久错误信息及请求编号。 +5. API 为重试受理、生成拒绝和转录失败增加包含会话 ID、课堂 ID、片段 ID、尝试次数和错误分类的结构化业务日志。 +6. 对“未识别到清晰语音”提供更准确的用户提示,说明可能是录音过短、静音或环境噪声,并允许放弃该失败片段或重新录制。 + +## 验收标准 + +- 存在失败片段时,主按钮不再显示“生成反馈”。 +- 点击“重试转录”后,API 日志中能查询到对应 `/retry` 请求和业务事件。 +- 页面在重试期间显示明确的处理中状态,且不会重复提交生成请求。 +- 重试成功后自动恢复“生成反馈”;再次失败时显示可理解的失败原因。 +- 所有 API 错误都能在页面上获得可复制的请求编号。 +- 自动化测试覆盖失败片段优先级、重试状态流转和生成失败后的会话刷新。 + +## 临时排查查询 + +排除健康检查后查看 API 日志: + +```logql +{application="teaching-feedback", service="api"} +| json +| span_path != "/health" +``` + +查看失败请求: + +```logql +{application="teaching-feedback", service="api"} +| json +| status >= 400 +``` + +查看指定反馈会话: + +```logql +{application="teaching-feedback", service="api"} +|= "7dc4f0dd-bd32-4d4b-b90a-c36ae83c2ca7" +```