feat: 任务断点续跑/暂停中断/LLM 过滤优化与调度容错
调度与状态机: - 修复 PAUSED 任务被拾起后复活执行(点击暂停反而开始任务):next_queued_run 只取 QUEUED,execute_run 以 PAUSED 进入直接返回,暂停必须显式 resume - 重启恢复:启动时 recover_interrupted_runs 把遗留 RUNNING 置 QUEUED(保留产物) - 暂停信号 paused.flag:暂停接口写、继续/重试清除,OCR 逐帧检查秒级中断, 节点内被暂停保持 PAUSED 不误报 FAILED - 调度轮询容错:_loop 异常不杀死线程(曾致任务永久停留 QUEUED) subtitle-ocr 节点级断点: - ocr_partial.jsonl 逐帧存档,重启/暂停后只处理未处理帧,产物与一次跑完一致 - 进度日志携带窗口平均耗时与线程数;取消后抑制进度日志井喷 llm-filter 过滤质量与限流自适应: - 上下文净化:喂给 LLM 的是过滤后的字幕(规则层垃圾从上下文剔除) - 正则确定性过滤:裸网址域名、HTML/水印模式直接删除 - 429/5xx 指数退避重试;worker 限流错误 report_failure 内存临时降最大线程数 并缩容(无错误窗口回升),失败条目降并发后重试一轮 - 保留长文本保护(noise 不删 ≥min_keep_len 文本,LLM 判定不稳的必要兜底) 前端: - 工作流编排页支持选择工作流编辑(加载最新/历史版本)、版本历史面板、 新建/编辑双模式;管理后台编辑跳转 workflow.html?edit=<id> 工作流:ocr-subtitle v7(filter pool_max_workers=20、pool_fast_threshold=1)
This commit is contained in:
@@ -41,6 +41,10 @@ vrsub/
|
||||
`data/storage/runs/<run_id>/steps/<node_id>/` 落盘并登记到 artifacts 表。
|
||||
- **前端**:由 FastAPI 静态挂载 `web/`,节点注册/实例管理页面已移除,
|
||||
仅保留应用中心、任务管理、管理后台(工作流)与工作流编排。
|
||||
- **工作流编排页(web/workflow.html)**:支持新建工作流(空表单预填演示模板),
|
||||
从列表"编辑"加载任一工作流的最新定义(ID 锁定,保存即追加新版本);"版本"
|
||||
查看全部历史版本并可"加载到编辑器"(对比/回滚后另存新版本);管理后台
|
||||
(admin.html)无编辑器,点"编辑"自动跳转 `workflow.html?edit=<id>` 加载。
|
||||
|
||||
## 节点输入/输出协议
|
||||
|
||||
@@ -56,7 +60,7 @@ vrsub/
|
||||
| `vlm-ocr` | `image_uri` | `text`、`text_uri` | 直接调本地 Ollama 多模态模型(glm-ocr)的 `/api/chat` 做视频帧 OCR(流式 + 5s 上限),参数:`model`、`ollama_host`、`prompt`、`timeout_seconds`、`keep_alive`、`num_predict`、`temperature`、`repeat_penalty` |
|
||||
| `frame-extract` | `video_uri` | `frames_manifest`、`frame_count` | 按**帧间隔**抽帧(解析 fps → step=round(间隔秒×fps),ffmpeg select 按帧号精确取帧,帧时间=帧号/fps 无累计偏差)并 crop 裁切字幕区域,参数:`interval_seconds`(默认 0.5)、`crop`([x,y,w,h] 0~1)。**帧文件必须按帧号数值排序读取**(`_sorted_frame_files`):ffmpeg `%04d` 编号超过 9999 帧后扩为 5 位,字典序 `sorted()` 会把 5 位编号排在 4 位之前导致时间与图像错位(真实发生于 run_339ec7ee437f 的 14236 帧任务,回归测试见 `test_frame_files_read_order_matches_frame_number`) |
|
||||
| `subtitle-ocr` | `frames_manifest` | `srt_uri`、`count` | 自适应线程池并发逐帧调 vlm-ocr → 垃圾过滤(无文字帧)→ 相同字幕合并(记录最后可见帧)→ 组装 SRT,消失时间=最后可见帧+采样间隔(间隔从帧清单推导),参数:`min_chars`、`min_alnum_ratio`、`garbage_tokens`、`pool_min_workers`/`pool_max_workers`/`pool_window_seconds`/`pool_fast_threshold`/`pool_slow_threshold` |
|
||||
| `llm-filter` | `srt_uri` | `srt_uri`、`kept`、`removed` | 两级过滤:①**规则层**(不调 LLM)直接删横线装饰/HTML 水印 token/URL/邮箱/单双 ASCII 字符;②**LLM 五类分类**(garbage/overlay/noise 删,repeat/dialogue 留,未识别回退保留)每条连同前后各 `context_size`(默认 10)条纯文本分批判断,**按文本去重**(忽略空白/大小写,相同文本只调一次 LLM,上下文取首次出现)保证判定一致并省调用,**长文本保护**(≥`min_keep_len` 默认 12 时 noise 不构成删除依据)。参数:`context_size`、`min_keep_len`、`overlay_tokens`(JSON 数组)、`dedupe`(默认开)、`model`、`pool_min_workers`/`pool_max_workers`/`pool_window_seconds`/`pool_fast_threshold`/`pool_slow_threshold`。回归数据:testdata/ocr_srt_run_ac7f480a3ccb.srt(真实任务 1666 条 OCR 输出) |
|
||||
| `llm-filter` | `srt_uri` | `srt_uri`、`kept`、`removed` | 两级过滤:①**规则层**(不调 LLM)正则确定性删除——横线装饰、URL/邮箱/**裸网址域名**(含中文夹杂的注册地址)、**HTML/水印模式**(html code/标签/javascript 等)、overlay token(html/marketing 等)、单双 ASCII 字符;②**LLM 五类分类**(garbage/overlay/noise 删,repeat/dialogue 留,未识别回退保留)每条连同前后各 `context_size`(默认 10)条纯文本分批判断——**上下文净化**:喂给 LLM 的是**过滤后的字幕**,规则层确定性垃圾从上下文中剔除(原文不进 LLM),避免覆盖层垃圾污染场景判断误删真实对话(回归:run_011d01f19999 曾 190 条含 ≥4 汉字对话被误删);**长文本保护**:≥`min_keep_len`(默认 12)时 noise 不构成删除依据——LLM 判定不稳定,长度是必要兜底(实测移除保护后新增误删 124 条真实长对话)。**限流自适应**:LLM 调用 429/5xx 指数退避重试(最多 3 次,1s/2s/4s),worker 捕获限流错误时调用线程池 `report_failure()` **内存中临时降低最大线程数并缩容**(连续无错误窗口后逐步回升),失败条目在收紧后的并发下**重试一轮**,二次仍失败才整体失败——20 并发一拥而上触发 429 时自动收敛到配额内而不打挂任务。**按文本去重**(忽略空白/大小写,相同文本只调一次 LLM,上下文取首次出现)保证判定一致并省调用。参数:`context_size`、`min_keep_len`、`overlay_tokens`(JSON 数组)、`dedupe`(默认开)、`model`、`pool_min_workers`/`pool_max_workers`/`pool_window_seconds`/`pool_fast_threshold`/`pool_slow_threshold`。回归数据:testdata/ocr_srt_run_ac7f480a3ccb.srt(真实任务 1666 条 OCR 输出) |
|
||||
| `srt-to-dual-eye-ass` | `cn_srt_uri` | `ass_uri` | 参数:`resolution`,如 `3840x1920` |
|
||||
|
||||
### 模型权重解析(本地优先)
|
||||
@@ -238,14 +242,35 @@ http://127.0.0.1:8000/docs API 文档
|
||||
- **状态机**:`QUEUED / RUNNING / PAUSED / COMPLETED / FAILED`。排队中或运行中的
|
||||
任务可暂停(`POST /api/runs/{run_id}/pause`),PAUSED 可继续
|
||||
(`POST /api/runs/{run_id}/resume` → 恢复 QUEUED)。
|
||||
- **调度器语义**:`next_queued_run` 同时取 QUEUED 与 PAUSED;`execute_run` 在每个
|
||||
节点边界检查状态,被暂停则停下保持 PAUSED(当前节点执行完后才停);
|
||||
继续时从产物表(`restore_run_outputs`,剥去"节点ID."前缀还原输出名)重建已完成
|
||||
节点的输出,**跳过已完成节点断点续跑**,最后补做 final_outputs 收尾。
|
||||
- **调度器语义**:`next_queued_run` **只取 QUEUED**——PAUSED 任务不会被调度器
|
||||
自动拾起(修复回归:此前 PAUSED 被拾起后 `execute_run` 先置 RUNNING 再检查,
|
||||
节点循环读到的是刚改的 RUNNING,"暂停检查"永远不成立 → 任务被复活继续跑,
|
||||
表现为"点击暂停反而开始任务")。`execute_run` 以 PAUSED 进入时直接返回保持暂停,
|
||||
必须用户显式 resume(PAUSED → QUEUED)后才真正执行;运行中被暂停的任务在每个
|
||||
节点边界检查状态停下保持 PAUSED(当前节点执行完后才停);继续时从产物表
|
||||
(`restore_run_outputs`,剥去"节点ID."前缀还原输出名)重建已完成节点的输出,
|
||||
**跳过已完成节点断点续跑**,最后补做 final_outputs 收尾。
|
||||
- **重启恢复**:进程被杀/重启时遗留的 RUNNING 任务在启动时被
|
||||
`recover_interrupted_runs` 恢复为 QUEUED(保留产物),调度器自动断点续跑;
|
||||
PAUSED 任务保持不变,等待显式 resume。
|
||||
- **节点级断点(subtitle-ocr)**:OCR 每帧完成后立即把 `{frame, text}` 追加到
|
||||
`steps/ocr/ocr_partial.jsonl`(多线程下加锁串行化)。invoke 启动时读取存档,
|
||||
只对未处理帧调用 vlm-ocr,存档文本与新增结果合并后组装 SRT——2 小时视频级
|
||||
OCR 任务中断/重启后不重复已处理帧,产物与一次跑完逐字节一致。
|
||||
- **节点内暂停响应(subtitle-ocr)**:暂停接口(`POST /api/runs/{run_id}/pause`)
|
||||
除置 PAUSED 外还向 run 根目录写入 `paused.flag`;OCR 工作线程**逐帧检查**该
|
||||
信号,存在即立即中止(不 OCR、不入存档,恢复时重跑该帧),invoke 返回
|
||||
failed;调度器捕获节点异常时若任务已是 PAUSED 则**保持 PAUSED 不标 FAILED**,
|
||||
resume 时清除信号并从断点存档继续——点击暂停后 OCR 秒级停下,不再等整个
|
||||
节点跑完。继续/重试接口与调度器执行前都会清理残留信号。
|
||||
- **前端**:任务管理页为 QUEUED/RUNNING 提供"暂停"、PAUSED 提供"继续"按钮。
|
||||
- **进度日志(数据处理速度)**:
|
||||
- 调度器:每节点完成打印"任务 X 进度 i/N 节点: Y 耗时 Zs, 运行累计 Ws";
|
||||
- subtitle-ocr:`OCR 进度 X/Y 帧 (Z 帧/s)`;llm-filter:`字幕判定进度 X/Y 条 (Z 条/s)`
|
||||
- subtitle-ocr:`OCR 进度 X/Y 帧 (Z 帧/s, 平均 Ws/帧, 线程 N/M)`;llm-filter:
|
||||
`字幕判定进度 X/Y 条 (Z 条/s, 平均 Ws/条, 线程 N/M)`——`W` 为最近窗口
|
||||
平均单任务耗时(窗口未满时回退累计平均),`N` 为当前目标线程数、`M` 为
|
||||
`pool_max_workers` 上限,用于判断多线程是否因单次处理过慢(窗口平均 ≥
|
||||
`pool_fast_threshold`)而未扩容;
|
||||
(线程池 `on_progress` 回调,每任务完成触发);
|
||||
- whisper:分块转写打印"分块 X/Y 完成 offset=... 耗时 Zs (Nx 实时, 累计 ...s)";
|
||||
- frame-extract:ffmpeg `-progress` 输出解析 `frame=N`,打印"抽帧进度 X/Y 帧 (Z 帧/s)"。
|
||||
@@ -340,7 +365,8 @@ http://127.0.0.1:8000/docs API 文档
|
||||
- 存储、队列、调度器都要通过抽象边界隔离,方便从单机实现替换为分布式实现。
|
||||
- 用户端永远只看到"输入 -> 进度 -> 结果",不暴露工作流细节。
|
||||
- 单体对分布式版的三处降级:无子进程隔离、无空闲 TTL 回收(模型常驻,
|
||||
仅懒加载)、慢任务无法强制中断(由节点自身超时兜底)。
|
||||
仅懒加载)、非 OCR 慢任务无法强制中断(由节点自身超时兜底;OCR 节点支持
|
||||
暂停信号逐帧中断)。
|
||||
|
||||
## 单体化说明
|
||||
|
||||
|
||||
Reference in New Issue
Block a user