153 lines
8.1 KiB
Markdown
153 lines
8.1 KiB
Markdown
# 小程序语音转录失败后无法重试
|
||
|
||
## 状态
|
||
|
||
- 记录日期: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
|
||
点击语音状态 -> 打开语音记录弹窗 -> 点击“重试失败项”
|
||
```
|
||
|
||
页面没有独立、明确的“重试转录”主操作,也没有显示将要重试的失败片段数量。
|
||
|
||
### 原生弹窗确认文案超限
|
||
|
||
语音记录弹窗把 `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"
|
||
```
|