学习资料:Datawhale《Deep Agents 实战》第四章:任务规划与分解
本章重点:
write_todos、TodoListMiddleware、LangChain Middleware、任务状态管理、上下文压缩。
一、为什么 Agent 需要任务规划?
前面学习了 Deep Agents 的虚拟文件系统,主要解决的是上下文管理问题。这一章进一步讨论:当 Agent 面对复杂任务时,如何知道自己应该做什么、已经完成什么,以及接下来做什么。
对于简单任务,例如查询天气,Agent 通常只需要调用一次工具就能完成。
但对于复杂任务,例如:
调研三个 Agent 框架的技术架构,比较优缺点,并撰写分析报告。
整个过程涉及资料检索、信息整理、对比分析和报告撰写等多个步骤。
如果没有明确的规划,Agent 可能出现:
-
遗漏步骤:还没完成资料收集就直接生成报告。
-
重复执行:反复搜索相同内容,浪费 Token 和工具调用。
-
偏离目标:长时间执行后忘记最初的任务要求。
-
进度不清晰:无法判断哪些任务已经完成。
因此,任务规划的核心思想是:
将复杂任务拆解成若干可执行的子任务,并在执行过程中持续维护任务状态。
这类似于我们做科研时,先制定研究计划,再逐步完成实验、分析结果和撰写论文。
二、write_todos:Agent 的任务清单工具
Deep Agents 使用 write_todos 工具管理任务规划。
需要注意的是,在 Deep Agents v0.7 中,任务规划已经变为按需启用的能力,需要显式添加 TodoListMiddleware。
from deepagents import create_deep_agent
from langchain.agents.middleware import TodoListMiddleware
agent = create_deep_agent(
model=model,
middleware=[TodoListMiddleware()],
)
启用之后,Agent 会获得三个能力:
-
write_todos:创建和更新任务清单。 -
todos:在 Agent State 中保存任务状态。 -
规划提示词:引导模型面对复杂任务时进行拆解。
2.1 任务的数据结构
每个 Todo 通常由两个字段组成:
{
"content": "搜索 LangGraph 官方文档",
"status": "pending"
}
其中,content 表示任务内容,status 表示当前状态。
任务状态分为三种:
| 状态 | 含义 |
|---|---|
pending |
待执行 |
in_progress |
执行中 |
completed |
已完成 |
典型状态流转:
pending → in_progress → completed
但这并不是框架强制执行的有限状态机。模型可以根据任务进展重新规划或修改清单。
2.2 任务规划的执行过程
假设用户要求 Agent 完成技术调研。
第一步:制定计划
[ ] 搜索相关技术文档
[ ] 收集竞品资料
[ ] 对比核心技术差异
[ ] 整理报告大纲
[ ] 撰写最终报告
第二步:执行任务
Agent 调用搜索工具、阅读资料,然后将当前任务状态更新为 completed,继续执行下一项。
第三步:动态调整
如果在检索过程中发现新的关键技术,Agent 可以修改原计划,补充新的调查任务。
因此,write_todos 不只是生成一个静态计划,还支持执行过程中的动态调整。
不过需要注意:任务被标记为 completed,并不代表任务结果一定正确。 最终仍然需要检查输出文件、代码测试或报告内容。
三、任务状态如何保存?
write_todos 维护的任务清单保存在 Agent State 的 todos 字段中,而不是简单地拼接进聊天消息。
这种设计的好处是,任务状态和对话历史可以分开管理。
Agent State
├── messages # 对话与工具调用历史
└── todos # 当前任务清单
即使较早的对话被压缩总结,任务清单也不会因此被直接删除。
但要区分两个概念:
-
状态管理:当前运行能够访问任务清单。
-
状态持久化:不同运行之间能够恢复之前保存的状态。
如果需要跨多次 invoke() 恢复状态,就必须配置 Checkpointer,并保持相同的 thread_id。
例如,InMemorySaver 可以在当前进程中保存检查点,但进程退出后不能依靠它恢复数据。实际部署通常需要持久化存储。
另外,子 Agent 默认也不会自动继承主 Agent 的 Todo 清单及规划中间件。
四、Middleware:Deep Agents 能力的实现机制
本章另一个重要知识点是 LangChain Middleware(中间件)。
可以理解为:
Middleware 是向 Agent 注入额外能力的插件机制。
之前使用的文件系统、上下文压缩、任务规划等功能,都与中间件有关。
4.1 两种 Middleware Hook
LangChain Middleware 提供两类主要 Hook。
| 类型 | 典型 Hook | 作用 |
|---|---|---|
| Node-style | before_agent、before_model、after_model、after_agent |
在 Agent 生命周期的特定位置执行 |
| Wrap-style | wrap_model_call、wrap_tool_call |
包装一次模型或工具调用 |
二者的主要区别在于执行边界。
Node-style Hook
会被编译为 Agent 执行图中的独立节点,适合:
-
状态更新
-
输入输出校验
-
审计记录
-
人工审批与中断
Wrap-style Hook
用于包裹模型或工具调用,适合:
-
自动重试
-
缓存
-
模型降级
-
请求和响应转换
例如,模型调用失败后切换备用模型,就可以通过 Wrap-style Middleware 实现。
对于需要 interrupt() 的人工中断,通常优先考虑 Node-style Hook,因为其暂停和恢复的执行边界更加清晰。
4.2 TodoListMiddleware 的底层原理
write_todos 实际上由 LangChain 的 TodoListMiddleware 提供。
除了使用 create_deep_agent(),也可以直接通过 LangChain 的 create_agent() 添加:
from langchain.agents import create_agent
from langchain.agents.middleware import TodoListMiddleware
from deepagents.middleware import FilesystemMiddleware
agent = create_agent(
model=model,
tools=[],
middleware=[
TodoListMiddleware(),
FilesystemMiddleware(),
],
)
这里的两个中间件分别负责:
-
TodoListMiddleware:任务规划与进度管理。 -
FilesystemMiddleware:提供文件读写等工具。
也就是说,Deep Agents 并不是重新实现所有能力,而是在 LangChain Agent 的基础上组合不同中间件,构建适合复杂任务的 Agent Harness。
五、其他几个重要 Middleware
5.1 SummarizationMiddleware:上下文压缩
长任务可能产生大量对话历史和工具输出,导致上下文窗口逐渐被占满。
SummarizationMiddleware 负责压缩旧消息,降低后续模型调用的上下文开销。
需要注意,LangChain 和 Deep Agents 都有同名的 SummarizationMiddleware,但实现方式存在区别。
| 对比项 | LangChain 版本 | Deep Agents 版本 |
|---|---|---|
| Hook | before_model |
wrap_model_call |
| 摘要处理 | 替换 State 中的旧消息 | 保留原始历史,调整本次模型输入 |
| 文件保存 | 不负责历史卸载 | 可将旧消息保存到 Backend |
Deep Agents 的做法更适合长时间运行的任务,因为旧消息虽然不再全部进入模型上下文,但仍然可以根据保存路径查阅。
在手动配置时,可以设置:
SummarizationMiddleware(
model=model,
backend=backend,
trigger=("tokens", 4000),
keep=("messages", 20),
)
其中:
-
trigger:达到什么条件时触发摘要。 -
keep:保留多少近期消息。
这里的参数只是演示,实际使用时应结合模型上下文窗口配置。
5.2 PatchToolCallsMiddleware:修补工具调用记录
Agent 执行过程中可能发生中断。
例如,模型已经发起搜索调用,但运行意外终止,导致消息历史中只有工具请求,没有对应的工具响应。
PatchToolCallsMiddleware 可以检查这种情况,并补充一条对应的 ToolMessage,说明调用被取消或未能执行。
需要区分:
| 功能 | 对应机制 |
|---|---|
| 补齐缺失的工具响应 | PatchToolCallsMiddleware |
| 工具异常后的自动重试 | ToolRetryMiddleware |
| 保存和恢复 Agent 状态 | Checkpointer |
| 判断任务结果是否正确 | 应用层校验或评测 |
特别注意,PatchToolCallsMiddleware 不会重新执行失败工具,也不保证外部操作已经撤销。
这几个机制职责不同,不能混为一谈。
六、任务规划与上下文管理如何协同?
这一部分是我认为本章最值得关注的地方。
假设 Agent 正在执行包含十个步骤的研究任务,已经完成前五步。
随着搜索结果、文件操作和消息历史不断增加,模型上下文可能接近上限。
这时可以通过三个机制协同处理:
复杂研究任务
|
v
TodoListMiddleware
维护任务计划与执行进度
|
v
FilesystemMiddleware
保存大规模中间结果
|
v
SummarizationMiddleware
压缩旧对话,控制上下文长度
|
v
读取 todos 和必要资料
继续执行剩余任务
这里有一个重要设计思想:
将任务进度和大量原始资料从对话上下文中分离。
任务清单负责记录整体目标与完成情况,文件系统负责保存具体资料,上下文压缩负责控制模型输入长度。
三个机制相互配合,使 Agent 更容易完成长程任务。
不过,规划是否合理、进度更新是否及时,仍然依赖模型自身的能力。
七、代码实战:构建具有规划能力的 Agent
下面给出一个精简示例,让 Agent 自主规划技术调研任务。
import os
from langchain_openai import ChatOpenAI
from langchain.agents.middleware import TodoListMiddleware
from deepagents import create_deep_agent
# 初始化模型
model = ChatOpenAI(
model="zai-org/GLM-5.2",
api_key=os.environ["SILICONFLOW_API_KEY"],
base_url="https://api.siliconflow.cn/v1",
)
# 创建具有规划能力的 Agent
agent = create_deep_agent(
model=model,
middleware=[TodoListMiddleware()],
system_prompt="""
你是一名技术研究助手。
面对复杂任务时:
1. 使用 write_todos 制定计划
2. 逐步执行并更新任务状态
3. 整理已获得的信息
4. 输出完整报告
"""
)
result = agent.invoke({
"messages": [{
"role": "user",
"content": "比较 LangGraph 和 Deep Agents 的架构特点"
}]
})
print(result["messages"][-1].content)
print(result.get("todos", []))
这段代码展示的是规划机制。由于没有额外注册互联网搜索工具,Agent