任务分解和规划(TodoListMiddleware)
原理解析
在deepagents中,使用create_deep_agent api创建agent,会默认添加一个write_todos工具,可以让agent进行规划,其实现也很简单,也是依赖于langchain的中间件机制,langchain中提供了预构建的TodoListMiddleware
基本的原理在于:
- 系统提示词中需要提示模型使用
write_todos这个工具对复杂任务进行规划 - 以及这个
write_todos工具的描述,告诉模型什么时候用这个工具,什么时候不该用
下面是部分相关的代码,可以看到todo的schema中包含两个字段,任务的状态以及任务的描述
1 | |
该中间件核心的实现:
可以看到该中间件会在每次模型调用之前,添加有关todo的系统提示词,告知模型怎么使用write_todos这个工具。
1 | |
然后在模型调用介绍,会判断是否同时调用多次write_todos工具,如果是,则构建一条工具调用错误的msg,提示模型。
1 | |
当然,这是一种比较简单的任务分解,他将一个复杂的任务分解为多个顺序执行的小的任务。但通常会搭配subagent来一起使用
- 如果你不搭配subagent来使用,基于ReAct这个范式下,通常会出现下面这种情况,首先它把任务进行了拆分,但是并没有分别地完成每个子任务,只是单纯地更新了任务状态,再在最后输出了最终的结果。
1 | |
- 我们接下来搭配一个通用的subagent来使用,如果搭配subagent来使用的话,当我们把任务进行了拆分了之后,它会调用工具把任务分配给subagent,最后进行一个汇总
下面是最终的结果,可以看到agent调用了task这个工具(这是一个通用的subagent),等待subagent完成了之后继续完成接下来的任务
1 | |
局限性和改进
上面的todo list 是单纯地将一个复杂的任务拆分为多个顺序执行的小任务,对于大多数情况来说已经够了,但是任务之间没有明显的依赖关系,例如任务C必须等A和B 完成之后再执行。具体可看Learn Claude Code 的s12。思路差不多,我们需要修改todo item的schema,任务的状态需要添加blockedBy(即被谁阻塞),blocks(当前任务阻塞了哪些任务。)
我对官网预构建的TodoListMiddleware稍微改造了一下,具体的代码在我的GitHub仓库,让任务之间有依赖关系。
生成的task_list大概是这样的,如下所示,可以看到不同的任务之间有依赖关系,这个针对复杂的来说还是很有用的。当然我们还是可以添加更多的工具,让agent可以更加精细地管理task,目前的方式是直接让他更新对应的任务状态。这会需要模型比较强大,可能弱一点的模型对于任务的状态更新就会错乱、或者上下文长了之后模型对任务的更新就会不正确。之后我们可以提供一个任务更新的tool,传入task_id来更新任务。
1 | |
全部的对话记录:
1 | |
SubAgent(SubAgentMiddleware)
上面说了使用TodoListMiddleware 中间件进行任务分解和规划,这里我们使用SubAgentMiddleware来委派任务。采用subagent的形式,可以实现上下文隔离,子agent只需要把一个复杂任务的结果返回给主agent即可,这样可以让主agent的上下文保持干净。
deepagents中的create_deep_agent api可以会默认创建一个通用的subagent,同样我们也可以创建专门针对某个任务的subagent
1 | |

原理
subagent的原理也很简单,本质上就是工具调用,SubAgentMiddleware中间件中会添加一个task工具,
调用工具的时候需要传任务的描述 以及subagent的类型,即上面定义的subagent的name
之后调用将任务描述给到subagent,然后subagent返回最终的结果,最终task工具把subagent的结果作为task工具调用的结果返回给主agent
工具的定义:
1 | |
这是SubAgentMiddleware的wrap_model_callhook,他会将有关子agent调用的提示词添加到系统提示词后面,告诉他什么时候用task工具,什么时候不用task工具,以及告诉他有哪些subagent,方便主agent在工具调用的时候传入对应的subagent的类型
1 | |
举例说明
这是上面TodoListMiddleware结果的一部分,主agent发起了两个task工具调用,这说明subagent是可以并行执行的,现在主流的模型都支持并行工具调用。最后两个任务的结果作为工具调用的结果返回回来。因此这里主agent是阻塞的状态,他需要等待子agent返回结果
1 | |
AsyncSubAgent(AsyncSubAgentMiddleware)
上面说到subagent是阻塞的,因为主agent需要等待task(子agent)的结果,但是多个子agent可以并行执行,这是因为可以并行地进行工具调用(可以同时调用多个工具)。
而AsyncSubAgent 是异步的子agent,他不是阻塞的。因为它不需要等待子agent的结果返回。
基本流程:
1、 主agent会调用start_async_task工具,该工具返回一个task_id
2、主agent可以使用check_async_task工具检查任务状态,如果任务完成了,还会返回任务的结果,这里需要注意的是,主agent通常并不会主动调用这个工具,通常我们可以
- 用户主要跟主agent说检查某个任务的状态,主agent调用
check_async_task工具返回结果 - 如果当前还有子agent的结果没有返回,我们可以隔几分钟/隔几轮对话,我们添加一条提示消息,提示主agent别忘了查询任务的状态
感觉claude code很喜欢这样做
- 用户觉得子agent跑偏了,用户可以添加消息纠正其行为.codex好像就有这个功能,codex可以打断他并纠正其行为
具体的实现的话,打断主agent,纠正他的方向比较简单,我们可以取消当前模型的对话,并将用户的指令添加到messages的末尾即可。
如果需要打断某个子agent,可能需要界面上下点工具,比如要具体让用户选择指挥具体的那个subagent,然后我们得在末尾添加一些提示词稍微提醒一下模型调用
update_async_task这个工具。
AsyncSubAgent 会添加五个tools,分别是:
start_async_task |
启动一个新的后台任务 | 立刻返回task_id |
|---|---|---|
check_async_task |
查询任务当前状态与结果 | 返回任务状态 + 结果(若完成) |
update_async_task |
根据所给的task_id,给运行中的任务追加新指令,将中断AsyncSubAgent的运行,并将新的指令添加到对话末尾,之后AsyncSubAgent重新运行 | 返回更新成功/失败的工具调用消息 |
cancel_async_task |
根据所给的task_id,终止运行中的任务 | 返回取消成功/失败的工具调用消息 |
list_async_tasks |
列出所有任务(含实时状态) | 返回所有的任务总览 |
基本原理和subagent差不多,只不过提供的工具有差别,所以导致了行为的差别,wrap_model_call的hook也是一样的,所添加的系统提示词会稍有区别
1 | |
- 本文作者: leftover
- 版权声明: 本文版权归leftover所有,如需转载清标明来源!