Files
teaching-feedback-assistant/docs/incidents/2026-07-22-miniprogram-voice-retry.md
2026-07-22 19:13:55 +08:00

151 lines
7.7 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.

# 小程序语音转录失败后无法重试
## 状态
- 记录日期2026-07-22
- 当前状态:已修复,待部署验证
- 影响范围:小程序反馈生成页的语音转录重试流程
- 相关会话:`7dc4f0dd-bd32-4d4b-b90a-c36ae83c2ca7`
## 用户现象
1. 用户点击“生成反馈”。
2. 小程序提示“部分语音转录失败,请重试后再生成反馈”。
3. 用户尝试点击重试入口,但界面没有可观察到的状态变化。
4. 再次点击“生成反馈”仍收到相同提示。
## 已确认事实
### 请求轨迹
2026-07-22 14:07:04 至 14:09:42Asia/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:18Asia/Shanghai
- FunASR 响应HTTP 422`no clear speech was recognized`
同一反馈会话的其余 5 个录音片段均为 `ready`。当前生成接口会在发现任意 `failed` 片段时拒绝整个反馈生成请求。
## 代码层问题
### 主操作没有优先处理失败片段
`pages/feedback/index.ts``syncVoicePresentation` 在存在可生成内容时把主操作设置为“生成反馈”,即使会话同时包含失败片段。`saveFeedback` 也只优先处理 `processing`,没有在调用生成接口前处理 `failedSegmentCount`
结果是用户可以持续点击“生成反馈”,但 API 必然持续返回 HTTP 400。
### 重试入口不够明确
当前重试流程隐藏在语音状态区域和原生弹窗中:
```text
点击语音状态 -> 打开语音记录弹窗 -> 点击“重试失败项”
```
页面没有独立、明确的“重试转录”主操作,也没有显示将要重试的失败片段数量。
### 原生弹窗确认文案超限
语音记录弹窗把 `confirmText` 设置为“重试失败项”,共 5 个字符。项目使用的微信小程序 API 类型定义明确限制 `showModal.confirmText` 最多 4 个字符,因此该弹窗可能直接调用失败;原实现也没有设置 `fail` 回调,界面上不会留下可观察错误。
### 生成失败后没有刷新会话
`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. 对“未识别到清晰语音”提供更准确的用户提示,说明可能是录音过短、静音或环境噪声,并允许放弃该失败片段或重新录制。
## 实施结果
- 主操作按 `failed -> processing -> save` 排序;总结生成改为语音记录区内的独立操作,存在失败片段时仍优先显示“重试转录(数量)”。
- 原生弹窗确认文案缩短为“重试转录”,符合最多 4 个字符的约束。
- 重试开始后立即锁定操作并显示处理中状态;请求结束后无论成功或失败都刷新活动会话并恢复轮询。
- 生成请求结束后始终刷新活动会话API 错误消息和 `X-Request-Id` 保存在页面错误区域,可复制查询。
- 页面增加失败原因说明和“放弃失败录音”;放弃操作不会自动开始录音,后端删除失败片段时会重算课堂状态,空课堂会一并删除。
- 语音记录区分“已加入输入框”“待生成反馈”和“已生成反馈”,避免把待汇总的长录音笼统显示为“已就绪”。
- 语音记录进一步调整为可复用素材列表:每节可查看完整转写、展开或收起、按需加入或重新加入输入框;短录音转写完成后不再自动修改正文。
- 反馈输入框支持确认后清空,语音记录仍保留;单节内容加入后若超过 2000 字会整体拒绝并提示,不再截断后写入。
- API 增加 `transcription_retry_requested``feedback_generation_rejected``failed_transcription_discarded` 业务日志;转录 worker 将无清晰语音分类为 `no_clear_speech`
- 前端自动化测试覆盖失败优先级、重试状态流转、无清晰语音提示和 API 失败后的会话刷新;后端测试覆盖错误分类和 OpenAPI 路由完整性。
- 已就绪录音默认全部参与总结,同时支持全选、取消全选和逐节选择;失败或处理中的录音不会进入可选集合,也不会阻止其他已就绪录音生成总结。
- 总结改为持久化异步任务,页面轮询展示 `queued``processing``ready``failed``applied` 状态;生成完成先预览,确认后才替换输入框。
- AI 模式内部按片段、单次录音、最终正文分层压缩,最终输出是一份不按录音次数或固定栏目分栏的连贯反馈;未配置 AI 时使用本地抽取式兜底。
- 原始转录可远超 2000 字并持续保留;仅最终总结和反馈输入框限制为 2000 字。折叠箭头已由字体字符改为 CSS 矢量形状。
## 验收标准
- 存在失败片段时,主按钮不再显示“生成反馈”。
- 点击“重试转录”后API 日志中能查询到对应 `/retry` 请求和业务事件。
- 页面在重试期间显示明确的处理中状态,且不会重复提交生成请求。
- 重试成功后,该节录音自动进入可选总结集合;再次失败时显示可理解的失败原因。
- 所有已就绪录音默认被选中,可逐节取消或一键全选;总结完成后必须先显示预览,不能自动覆盖输入框。
- 两万字级原始转录不写入输入框;配置 AI 后由分层总结压缩为不超过 2000 字的一份正文。
- 所有 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"
```