RAG 任务调度链路设计改动
1. 任务状态设计:PENDING 的语义
submit_task 写入 TaskRecord 时状态设为 PENDING,因为此时任务只是"被登记、进队列",尚未被 worker 消费,RUNNING 只能由真正开始执行的 worker 设置。
- 完整状态生命周期:
PENDING(提交)→ RUNNING(worker 取出执行)→ SUCCESS/FAILED(执行结束)。
- 意义:保证"任务已被接受"和"任务已被执行"是两个可观察的独立状态,前端提交后立刻查询也能拿到明确状态,而不是空白。
2. 读写分离:CQRS 模式
- 架构上属于 CQRS(Command Query Responsibility Segregation):
- 写路径(
submit_task/resume_task)→ 驱动真实计算,需要走队列做背压和 worker 并发限流。
- 读路径(
/status 查 dispatcher、/result 查 LangGraph checkpointer)→ 幂等、无副作用,不占用 worker,直接读。
- 两个数据源分工明确:
status(PENDING/RUNNING/SUCCESS/FAILED)= dispatcher 层"这次调用有没有在跑"。
result(answer/citations/interrupts/next_nodes)= LangGraph 层"业务状态",checkpointer 是唯一权威来源,dispatcher 不镜像。
3. TaskRecord.result 字段清理
- 原因:
run_rag_chat_task 等 handler 始终返回 {},业务结果全部走 checkpointer 获取,_mark_success 写入的 result 字段从未被 get_task_snapshot 读出,属于死代码。
- 处理:删除
result 字段,TaskHandler 返回类型语义随之收窄。
4. Callable 类型标注踩坑
- 错误写法:
Callable[[dict[str, Any], str]](只给了参数列表,漏了返回类型)。
- 后果:
typing.Callable 下标必须是 [参数列表, 返回类型] 两元素,写错会在模块导入时直接抛 TypeError,不是运行到才报错。
- 正确写法:
Callable[[dict[str, Any], str], Awaitable[bool]]。
5. SUCCESS 状态语义陷阱
- LangGraph 的
interrupt() 会让 graph.ainvoke() 正常返回(不抛异常、无特殊返回值差异),所以"图跑完"和"图被中断挂起等待人工确认"在 dispatcher 层看起来是同一回事,都会被标记为 SUCCESS。
- 风险:前端容易把
status=SUCCESS 误解为"已经有最终答案",实际还需要查 /result 里的 next_nodes/interrupts 才能判断真实进度。
6. 引入 interrupted 标记的设计取舍
- 两种方案对比:
- 方案 A:直接扩展
TaskStatus 枚举加 INTERRUPTED——会破坏"dispatcher 不镜像 LangGraph 状态"的原则,且 worker 无法仅凭 ainvoke() 返回值区分中断/完成,需要 handler 额外查 snapshot.next 才能判断。
- 方案 B(采用):保留四态不变,额外挂一个独立的
interrupted: bool 字段。两个维度解耦:status 仍表示"调用是否正常结束",interrupted 表示"结束时是否停在中断点",前端可直接据此判断要不要走审批流程,减少无意义的 /result 轮询。
- 实现关键:handler 在
ainvoke() 后主动 await graph.aget_state(config),用 bool(snapshot.next) 判断是否停在中断点,并把这个轻量控制信号(不是业务数据)透传给 dispatcher。
7. _dispatch 的角色
- 定位:worker 循环中的路由转发层,职责是"按
task_type 查 self._handlers 字典找到对应 handler 并执行",与 _worker_loop(负责取任务/标记状态/超时处理)职责分离。
- 找不到对应 handler 时抛
InvalidRequestError 兜底,避免运行时因 handler is None 直接崩溃。
- 之所以单独拆出,是因为
asyncio.wait_for 需要一个协程整体做超时控制,拆出来也便于未来统一加日志/权限校验等横切逻辑。
- 踩坑记录:一度把
_dispatch 改成 -> None 且遗漏 return,导致返回值丢失、bool | None 类型错误一路传导到 _worker_loop 里的 interrupted 变量。修复:显式 return await handler(payload, session_id) 并标注 -> bool。
posted @
2026-07-03 17:41
asphyxiasea
阅读(
4)
评论()
收藏
举报