Skip to content

LLM processor 空输入时 ThreadPoolExecutor(max_workers=0) 崩溃:极短音频(12s)转录必失败且重提交无法恢复 #179

Description

@zj1123581321

结论

三处 LLM processor 用 max_workers = min(len(输入列表), 配置上限) 算线程池大小。
输入列表为空时算出 max_workers=0,ThreadPoolExecutor(max_workers=0) 直接抛
ValueError: max_workers must be greater than 0,任务在 LLM 阶段被判 failed。

这是确定性缺陷,与网络/服务状态/负载无关:同一个任务重新提交多少次都会以同一行错误失败。
生产上已经这么失败过两轮(2026-08-12 首次、2026-10-06 重提交复现)。

证据(生产实测,2026-10-06,n305 video-transcript-api 容器)

VTA 任务 task_ac8c5370bae3462ba9cd2be6ba018003,来源 live-recorder 任务
9a4d28020d5348f788bfe33932ea226e(音频时长 12 秒,标题「183冯律」):

[perf] task_ac8c5370bae3462ba9cd2be6ba018003 | llm_processing: 2770ms (FAILED: max_workers must be greater than 0)
ERROR llm_ops._handle_llm_task:918 - LLM任务处理异常: task_ac8c5370..., 错误: max_workers must be greater than 0
cache_manager.update_task_status:2470 - 任务状态更新: task_ac8c5370... -> failed
terminal_status.finalize_terminal_status_and_notify:101 - terminal CAS won: task_ac8c5370... -> failed
[perf-summary] task_ac8c5370... | total: 7839ms

ASR 阶段本身是成功的(日志有 转录完成,生成文件: [...]),死在 LLM 阶段。
12 秒音频转出的文本为空 → 零分段 → min(0, 10) = 0。

该任务在 live-recorder 侧的表现:2026-10-06 重新提交后仍失败,vta_status 回到
confirm_timeout,页面显示「转录失败」。录音文件本身完好(0.4 MB,12 秒)。

缺陷点位(origin/main @ d968df8,行号实测)

文件 行 表达式 空输入可达性
llm/processors/plain_text_processor.py 420 min(len(segments), self.config.concurrent_workers) 已实测复现
llm/processors/speaker_aware_processor.py 970 min(len(chunks), self.config.calibration_concurrent_limit) 代码路径等价可达:normalized_dialogs 为空 → DialogSegmenter.segment([]) → chunks=[](speaker_aware_processor.py:252),未实测
llm/processors/notes_processor.py 551 min(len(mapping.slices), self.config.notes_concurrency) 未确认,取决于章节映射能否为空

三处都在 ThreadPoolExecutor(max_workers=max_workers) 之前没有空列表守卫。

影响面

触发条件是「转录文本为空」。真实来源不是罕见的格式损坏,而是极短录制:
live-recorder 用户中途停止 / 临场切 mode 会产出几十秒碎片,其侧有
vta_min_duration_s(默认 120s)门槛,但本仓 API 侧没有对应门槛,直接
/api/transcribe 提交或 recorder 门槛被调低都会把空文本送进 LLM 阶段。

后果:任务永久失败且无法通过重提交恢复——用户能拿到的只有一段没有文字的录音。

建议修法

空输入时在该函数内直接返回与空输入同构的空结果(例 _calibrate_segments 返回
([], [])),不建池、不 submit、不打 LLM 请求;非空路径的并发行为、分段顺序、
contextvars.copy_context().run 传播与日志一律不变。

不建议改写成 max(1, ...):那只是让空输入白建一个空线程池,语义仍然错。

附:另一批「转录文件失败」历史样本今日重提交已全部恢复

live-recorder 生产库有 23 个 vta_status=confirm_timeout 的历史转录失败样本,
其中 4 个的错误是 转录文件失败: data/temp/task_xxx/xxx.mp4(VTA 侧 ASR 阶段失败)。
2026-10-06 抽样重提交 60ffbf1bb460487094768504e0a6aa6d(8815.69 秒音频):

transcription_deadline duration=8815.69 value=35382.8
capswriter_start attempt=1/5 media=.../强哥本人号.mp4 duration=8815.688667 deadline_total=35382.754668
capswriter_done ... processed=4520.1s ...
→ 任务完成

即当前版本能拿到时长、给出 4 倍实时预算,这批历史失败在今天的链路上不复现。
与 #170 描述的「recorder:// 路径时长预算恒失效」相关,但本次实测拿到的是
duration=8815.69(非 unknown),说明该路径当前已能取到时长——修 #170 前建议先确认
线上镜像是否已含相关改动,避免按过期机制排查。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working优先级:P1影响开发/验收/交付效率,或阻塞上游流程落点:本仓修复代码在本仓 git 跟踪路径内

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions