任务异步化与取消方案
本文属于“任务计划”文档,描述当前待推进的异步任务改造方案。
背景
当前系统里已经有一套可用的任务基础设施:
TaskManagerGenerationTaskGenerationTaskLink/api/v1/film/tasks/{task_id}/status/api/v1/film/tasks/{task_id}/result
但 script-processing 路由下仍有一批接口是同步直接调用 Agent。其中最典型的是:
/api/v1/script-processing/divide
这类接口一旦耗时变长,会带来几个明显问题:
- 请求长时间阻塞
- 页面刷新后丢失上下文
- 用户无法明确知道当前是否已有任务在运行
- 当前任务体系有
cancelled状态,但缺少真正的取消入口和取消执行机制
这份文档的目标,是给出一套可以渐进落地的方案:
长耗时智能体接口任务化
→ 页面刷新后可恢复
→ 任务状态可见
→ 先支持请求取消与协作式停止
→ 强终止能力后置核心结论
1. divide 应优先改为异步任务接口
建议新增:
POST /api/v1/script-processing/divide-async并保留原同步接口:
POST /api/v1/script-processing/divide这样可以兼顾:
- 前端主流程迁移
- 管理端或脚本调试
- 单测与回归验证
2. 页面恢复应复用现有任务体系
不建议为 divide 单独造一套新的任务模型。
推荐直接复用:
GenerationTaskGenerationTaskLink
并通过下面这组业务关联恢复页面状态:
relation_type = "chapter_division"
relation_entity_id = chapter_id3. 取消能力要分阶段做
当前技术栈下,很多长任务本质上还是:
- 单次同步 Agent 调用
- 不一定暴露可中断句柄
因此第一阶段不要承诺“立即强终止”,而应先做:
- 请求取消
- 页面显示“已请求取消”
- worker 在检查点协作式停止
如果未来要强终止,再升级到:
- 运行句柄注册表
- 独立 worker 进程 / 队列系统
为什么 divide 要优先任务化
当前问题
/api/v1/script-processing/divide 当前是同步 Agent 调用:
请求进入
→ ScriptDividerAgent.divide_script(...)
→ 结果返回这条链一旦慢下来,用户会直接感受到:
- 页面一直 loading
- 刷新页面后不知道任务是否还在跑
- 同一章节可能被重复触发提取
任务化后的目标
应改成:
POST divide-async
→ 创建任务
→ 创建 task_link
→ 立即返回 task_id
→ 后台执行 divide
→ 页面按 chapter_id 恢复任务状态divide-async 设计
路由
POST /api/v1/script-processing/divide-async请求体
建议复用当前同步接口的请求体:
{
"chapter_id": "2a67890b-46a9-41ed-b220-18d18a3d300a",
"script_text": "...",
"write_to_db": true
}返回体
{
"success": true,
"code": 200,
"message": "Task created",
"data": {
"task_id": "uuid",
"status": "pending",
"reused": false,
"relation_type": "chapter_division",
"relation_entity_id": "2a67890b-46a9-41ed-b220-18d18a3d300a"
}
}同章节活跃任务复用
建议增加业务约束:
同一
chapter_id同时只允许一个活跃的chapter_division任务。
活跃状态包括:
pendingrunningstreaming
如果存在活跃任务,则:
- 不重复创建
- 直接返回已有
task_id reused = true
这样可以解决:
- 重复点击
- 页面刷新后再次点击
- 多标签页并发触发
页面刷新后的关联与恢复
业务关联写法
divide 任务创建时写入:
resource_type = "task"
relation_type = "chapter_division"
relation_entity_id = chapter_id页面恢复方式
当用户进入这些页面时:
- 章节页
- 分镜列表页
- 项目工作台
可按 chapter_id 查询最新活跃任务:
chapter_id
→ relation_type=chapter_division
→ latest active task如果存在:
- 显示“正在提取分镜”
- 轮询任务状态
- 任务完成后刷新分镜列表
页面提示建议
统一展示:
正在提取分镜已请求取消提取失败提取完成
取消能力设计
第一阶段:请求取消
新增接口:
POST /api/v1/film/tasks/{task_id}/cancel建议在 generation_tasks 表上新增字段:
cancel_requestedcancel_requested_atcancel_reasoncancelled_at
这阶段先解决的是:
- 用户可以发起取消
- 页面知道取消请求已存在
- 任务状态接口能显示取消请求状态
这不等于任务一定能立刻停下。
第二阶段:协作式取消
在 worker 中增加取消检查点:
- 任务启动前
- 调用下一个 Agent 前
- 多阶段流程的阶段边界
- 写库前
如果检测到:
cancel_requested = true则:
- 停止后续步骤
- 将任务写成
cancelled - 记录
cancelled_at
这种方式的限制
如果当前代码正卡在:
- 单次长时间同步模型调用
- 阻塞式 SDK 调用
那么第一阶段无法做到“立刻中断”,只能等待当前步骤结束后再停。
所以产品与开发文档里都应该明确:
第一阶段的取消能力属于“请求取消 + 协作式取消”,不是强终止。
第三阶段:运行句柄注册表
如果后续要增强取消能力,建议在 TaskManager 上新增运行句柄注册表:
task_id -> asyncio.Task这样对真正的异步后台任务,可以尝试:
task.cancel()
但要注意:
即使有运行句柄,也不代表所有同步 LLM 调用都能立即停住。
第四阶段:强终止能力预留
如果未来业务明确要求“立即终止长任务”,就需要进一步升级架构,例如:
- 独立 worker 进程
- 队列系统
- 执行进程与 Web 进程隔离
到那时,才能更可靠地支持:
- kill worker
- 回收阻塞任务
- 更强的取消语义
当前阶段,不建议直接跳到这一步。
当前落地状态
主线接口:已完成任务化并接入真实页面
/api/v1/script-processing/divide/api/v1/script-processing/extract/api/v1/script-processing/check-consistency/api/v1/script-processing/optimize-script/api/v1/script-processing/simplify-script/api/v1/script-processing/analyze-character-portrait/api/v1/script-processing/analyze-prop-info/api/v1/script-processing/analyze-scene-info/api/v1/script-processing/analyze-costume-info
这些接口当前都已经进入:
异步任务
→ 页面按业务实体恢复任务
→ 可请求取消
→ 协作式取消预备能力:已任务化但当前无真实前端入口
/api/v1/script-processing/merge-entities/api/v1/script-processing/analyze-variants
这两条接口当前不再列为前端主线整改项,处理策略是:
- 保留后端实现、测试与 OpenAPI
- 在路由描述与代码注释中标明“预备能力”
- 等未来出现真实页面入口后,再按同一套任务恢复模型接入
推荐的 relation_type 约定
建议统一使用这批值:
chapter_division
script_extraction
entity_merge
consistency_check
variant_analysis
character_portrait_analysis
prop_info_analysis
scene_info_analysis
costume_info_analysis
script_optimization
script_simplification当前已落地:
chapter_divisionscript_extractionconsistency_checkscript_optimizationscript_simplificationcharacter_portrait_analysisprop_info_analysisscene_info_analysiscostume_info_analysisentity_mergevariant_analysis
实施顺序回顾与后续建议
已完成阶段
generation_tasks增加取消字段TaskStore / TaskManager增加取消请求能力- 新增
script_processing_tasks.py divide-async / extract-async落地- 页面按业务实体恢复
divide / extract任务 check-consistency / optimize-script / simplify-script接入任务体系- 资产分析类接口接入任务体系
- worker 协作式取消检查点补齐第一轮
当前收口重点
- 清理未启用接口的前端接入预期
- 在路由描述、代码注释、开发文档中明确:
- 哪些是主线接口
- 哪些是预备能力
- 哪些同步接口只用于兼容与调试
后续建议
- 继续观察是否会新增真实页面入口
- 如果未来重新启用
merge-entities / analyze-variants,再按同一套任务恢复模型接入 - 如果业务明确要求更强的终止能力,再推进运行句柄注册表或独立 worker
对前端的约束
页面不应只依赖本地缓存 taskId。
正确做法是:
页面打开
→ 根据业务实体查最新活跃任务
→ 恢复轮询
→ 根据任务状态更新 UI也就是说:
- 章节页看
chapter_division - 分镜编辑页看
script_extraction - 不以“页面是否没刷新”为前提
对产品语义的建议
当前阶段建议统一对外说明:
用户可看到
- 任务进行中
- 已请求取消
- 已完成
- 已失败
用户暂时不要期待
- 点击取消后立即硬停止
更准确的提示文案应是:
已请求取消,系统会在当前步骤结束后停止。
最终建议
这轮最值得先做的,不是一步到位上强终止,而是:
divide 异步任务化
→ 页面刷新可恢复
→ 任务状态可见
→ 取消请求可登记
→ worker 协作式停止这样能先解决绝大多数体验问题,同时为后续更强的任务控制能力留好扩展点。