首页
/ DeerFlow Plan Mode 实战指南:通过 is_plan_mode 启用 write_todos 工具实现多步任务跟踪

DeerFlow Plan Mode 实战指南:通过 is_plan_mode 启用 write_todos 工具实现多步任务跟踪

2026-09-04 16:19:33作者:柯茵沙

本文基于 DeerFlow 官方文档 plan_mode_usage.md 并结合当前仓库源码展开,讲解 Plan Mode 的启用方式、is_plan_mode 运行时参数的传递链路、write_todos 工具的默认使用策略,以及 TodoMiddleware 在消息历史截断、提前退出等边界场景下的保护机制。读完本文,你可以在 DeerFlow 的 lead agent 上按请求粒度动态开启计划模式,并理解其中间件在代理组装流程中的实际位置与行为。

Plan Mode 是什么

Plan Mode 是 DeerFlow 2.0 提供的一个按请求粒度开关的任务跟踪能力:开启后,代理会挂载一个 TodoList 中间件,该中间件向模型注入一个 write_todos 工具,帮助代理:

  • 把复杂任务拆解为更小、可管理的步骤;
  • 在工作推进过程中实时跟踪进度;
  • 让用户对"当前正在做什么"有可见性。

按照文档说明,该中间件构建在 LangChain 的 TodoListMiddleware 之上。从源码结构看,DeerFlow 并没有直接使用基类,而是定义了一个子类 TodoMiddleware(见 todo_middleware.py),在继承 LangChain 基础行为之外,额外实现了上下文丢失检测与"未完成待办时禁止提前退出"的守护逻辑,这部分是文档未展开的实现细节,下文会专门解析。

通过运行时配置启用 Plan Mode

Plan Mode 由 RunnableConfig.configurable 中的 is_plan_mode 参数控制,属于运行时配置:可以为每次请求动态开启或关闭,无需任何全局配置项。

from langchain_core.runnables import RunnableConfig
from deerflow.agents.lead_agent.agent import make_lead_agent

# Enable plan mode via runtime configuration
config = RunnableConfig(
    configurable={
        "thread_id": "example-thread",
        "thinking_enabled": True,
        "is_plan_mode": True,  # Enable plan mode
    }
)

# Create agent with plan mode enabled
agent = make_lead_agent(config)

配置项语义(继承自原文档,并与源码核对一致):

  • is_plan_mode(bool):是否启用带 TodoList 中间件的 Plan Mode,默认 False
    • 通过 config.get("configurable", {}).get("is_plan_mode", False) 读取;
    • 可以为每次代理调用动态设置;
    • 不需要全局配置。

参数读取的源码位置

lead_agent/agent.py 中,is_plan_mode 的读取发生在 _assemble_lead_agent()(L890 附近):

cfg = _get_runtime_config(config)
is_plan_mode = cfg.get("is_plan_mode", False)

这里有一个值得注意的实现细节:参数并不是只从 configurable 读取,而是经由 _get_runtime_config()agent.py#L194-L200)先把 config["configurable"] 与 LangGraph 的 config["context"] 合并后再取值。也就是说,is_plan_mode 同时可以通过 configurablecontext 两个通道传入,这为 Gateway 等上层运行时提供了统一的注入口径。

解析出的 is_plan_mode 还会被写入 config["metadata"]agent.py#L961-L973),用于 LangSmith/Langfuse 等追踪系统的 run 标签,便于在 trace 中区分哪些 run 启用了计划模式。

Gateway 与嵌入式客户端如何传入该参数

从源码可以确认两条实际的注入路径:

  1. Gateway HTTP 路径gateway/services.py 中定义了白名单 _CONTEXT_CONFIGURABLE_KEYSis_plan_mode 是其中之一。langgraph-compat 层会把请求 body.context 中该白名单内的键同时写入 configurablecontext,因此通过 Gateway 发起的每次 run 都可以独立指定是否启用 Plan Mode。
  2. 嵌入式 DeerFlowClient 路径client.py_get_runnable_config() 把客户端构造参数 plan_mode 映射为 configurable 中的 is_plan_mode,且每次调用可用 overrides.get("plan_mode", self._plan_mode") 覆盖。更重要的是,is_plan_mode 是代理配置缓存键的组成部分(client.py#L298-L310)——当该值发生变化时,客户端会判定配置键不匹配并重建代理,从而保证开关切换立即生效。

默认行为:何时用、何时不用 TodoList

当 Plan Mode 以默认设置启用后,代理会获得 write_todos 工具,其行为策略由自定义提示词约束(提示词全文见下文"自定义提示词"一节)。原文档归纳的使用策略与源码提示词一致:

应当使用 TodoList 的场景

  1. 复杂的多步骤任务(3 个以上明确步骤);
  2. 需要仔细规划的非平凡任务;
  3. 用户明确要求使用 todo list;
  4. 用户一次给出多个任务(编号或逗号分隔列表)。

不应使用 TodoList 的场景

  1. 单一的、直接的任务;
  2. 琐碎任务(少于 3 步);
  3. 纯对话式或信息性请求。

此外,agent.py 中的系统提示词还补充了一条策略要点:当计划可能需要根据中间结果修订时,也应使用 todo list。

任务状态

  • pending:任务尚未开始;
  • in_progress:正在处理(并行执行时可有多个);
  • completed:任务已成功完成。

提示词对状态管理有明确约束:每完成一步立即标记 completed(禁止批量补标)、同一时间保持恰有一个 in_progress(除非任务可并行)、随工作实时更新列表;并强调只有在任务被完全完成时才允许标记 completed——存在未解决问题、部分完成、遇到阻塞器、缺少依赖或质量不达标时,应保持 in_progress 并新建任务描述待解决项。

使用示例

基本用法

from langchain_core.runnables import RunnableConfig
from deerflow.agents.lead_agent.agent import make_lead_agent

# Create agent with plan mode ENABLED
config_with_plan_mode = RunnableConfig(
    configurable={
        "thread_id": "example-thread",
        "thinking_enabled": True,
        "is_plan_mode": True,  # TodoList middleware will be added
    }
)
agent_with_todos = make_lead_agent(config_with_plan_mode)

# Create agent with plan mode DISABLED (default)
config_without_plan_mode = RunnableConfig(
    configurable={
        "thread_id": "another-thread",
        "thinking_enabled": True,
        "is_plan_mode": False,  # No TodoList middleware
    }
)
agent_without_todos = make_lead_agent(config_without_plan_mode)

按任务复杂度动态切换

可以为不同的会话或任务动态启停 Plan Mode,例如按任务复杂度决定:

from langchain_core.runnables import RunnableConfig
from deerflow.agents.lead_agent.agent import make_lead_agent

def create_agent_for_task(task_complexity: str):
    """Create agent with plan mode based on task complexity."""
    is_complex = task_complexity in ["high", "very_high"]

    config = RunnableConfig(
        configurable={
            "thread_id": f"task-{task_complexity}",
            "thinking_enabled": True,
            "is_plan_mode": is_complex,  # Enable only for complex tasks
        }
    )

    return make_lead_agent(config)

# Simple task - no TodoList needed
simple_agent = create_agent_for_task("low")

# Complex task - TodoList enabled for better tracking
complex_agent = create_agent_for_task("high")

IM 渠道同样受益于此机制:从 test_channels.py 可见,渠道会话支持以 default_session={"context": {"is_plan_mode": True}} 的方式为某个会话固定携带该运行参数,即不同 IM 会话可以有不同的计划模式策略。

工作原理:从 is_plan_mode 到 write_todos 的调用链

原文档给出的工作机制步骤如下,每一步都可以在源码中找到对应实现:

  1. 调用 make_lead_agent(config) 时,从 config.configurable 中提取 is_plan_mode
  2. 该配置被传入 build_middlewares(config)
  3. build_middlewares() 读取 is_plan_mode 并调用 _create_todo_list_middleware(is_plan_mode)
  4. is_plan_mode=True,则创建一个 TodoListMiddleware 实例并加入中间件链;
  5. 中间件自动向代理工具集注入 write_todos 工具;
  6. 代理在执行过程中使用该工具管理任务;
  7. 中间件维护 todo list 状态并向代理提供它。

源码级的调用链(lead_agent/agent.py):

make_lead_agent(config)                     # L749
  └─> assemble_lead_agent / _assemble_lead_agent
        │   is_plan_mode = cfg.get("is_plan_mode", False)   # L890
        └─> build_middlewares(config, ...)   # L457
              │   cfg = _get_runtime_config(config)         # L579
              │   is_plan_mode = cfg.get("is_plan_mode", False)
              │   todo_list_middleware = _create_todo_list_middleware(is_plan_mode)  # L581
              │   if todo_list_middleware: middlewares.append(...)                   # L582-L583
              └─> _create_todo_list_middleware(is_plan_mode)  # L332
                    └─> return TodoMiddleware(system_prompt=..., tool_description=...)  # L444

要点:

  • _create_todo_list_middleware()agent.py#L332-L444)在 is_plan_mode 为假时直接返回 None,即"关闭时零成本";
  • build_middlewares() 是公共入口(agent.py#L457),被 make_lead_agent 与嵌入式 DeerFlowClient 复用,因此两条路径下 Plan Mode 的中间件组装逻辑完全一致;
  • 测试用例 test_create_deerflow_agent.py 直接验证了契约:plan_mode=True 时中间件链中出现 TodoMiddleware,默认 plan_mode=False 时不出现;test_client_e2e.py 则验证了 is_plan_mode 参与客户端配置缓存键。

架构与中间件位置

原文档给出的架构示意如下:

make_lead_agent(config)
  │
  ├─> Extracts: is_plan_mode = config.configurable.get("is_plan_mode", False)
  │
  └─> build_middlewares(config)
        │
        ├─> ThreadDataMiddleware
        ├─> SandboxMiddleware
        ├─> SummarizationMiddleware (if enabled via global config)
        ├─> TodoListMiddleware (if is_plan_mode=True)
        ├─> TitleMiddleware
        └─> ClarificationMiddleware

(示意中的 TodoListMiddleware 在当前实现中即 TodoMiddleware。)

从源码结构看,当前 build_middlewares() 的完整中间件链比该示意更长(还包括技能激活、持久上下文、Token 使用、记忆、图像查看、循环检测、Token 预算、终止响应等中间件),但 Plan Mode 相关的位置关系保持不变:

  • TodoMiddleware 在 SummarizationMiddleware 之后追加(agent.py#L569-L583);
  • 其位置注释明确要求"TodoListMiddleware should be before ClarificationMiddleware to allow todo management during clarification flows"(agent.py#L447-L456),即中间件顺序保证在澄清(clarification)流程中仍可管理 todo;
  • ClarificationMiddleware 恒定位于链尾(agent.py#L692-L693)。

自定义提示词:DeerFlow 风格的 system_prompt 与 tool_description

DeerFlow 为 TodoListMiddleware 定制了 system_prompttool_description,与主系统提示词风格保持一致(定义于 _create_todo_list_middleware()agent.py#L345-L444)。原文档概括的特征及对应源码实现如下。

System Prompt 特征

  • 使用 XML 标签 <todo_list_system> 组织内容,与 DeerFlow 主提示词的标签风格一致;
  • CRITICAL RULES 开头,强调关键规则:
    • 每完成一步立即标记 completed,不得批量补标;
    • 同一时间保持恰好一个任务为 in_progress(可并行的任务除外);
    • 随工作实时更新列表,向用户提供进度可见性;
    • 少于 3 步的简单任务不要用该工具,直接完成即可;
  • 明确的 "When to Use" 与 "When NOT to Use" 对照清单;
  • 聚焦实时更新与即时完成任务,并提示"写 todo 本身消耗时间与 token,只在管理复杂问题时使用"。

Tool Description 特征

  • 详细的使用场景与示例(复杂多步任务、非平凡任务、用户显式要求、多任务列表、计划需随中间结果更新);
  • 强烈强调不要在简单任务上使用(开头即声明 "Only use this tool for complex tasks (3+ steps)");
  • 清晰的任务状态定义(pending / in_progress / completed);
  • 独立的 "Task Completion Requirements" 章节,防止过早标记完成:存在未解决错误、部分完成、遇到阻塞、缺少依赖、未达质量标准时不得标记 completed;若被阻塞,应保持 in_progress 并新建描述待解决问题的任务;
  • 最佳实践章节,包括:写下 todo 列表时立即把首个任务标记为 in_progress;除非全部完成,始终至少保持一个任务处于 in_progress 以体现进度。

源码纵深:TodoMiddleware 的两项增强机制

DeerFlow 的 TodoMiddlewaretodo_middleware.py)在 LangChain TodoListMiddleware 之上解决了两个长任务场景的真实问题。

1. write_todos 上下文丢失检测(before_model)

当消息历史被截断(例如被 SummarizationMiddleware 压缩)后,最初的 write_todos 工具调用及其 ToolMessage 可能已滚出活动上下文窗口,模型会"忘记"当前还有哪些待办。before_model 钩子(todo_middleware.py#L124-L160)的检测逻辑是:

  1. 若 state 中没有 todos,直接返回;
  2. 若消息历史中仍能看到 write_todos 工具调用(_todos_in_messages()),无需处理;
  3. 若已注入过 reminder(_reminder_in_messages()),避免重复注入;
  4. 否则注入一条名为 todo_reminderHumanMessage(带 hide_from_ui: True,对用户不可见),列出当前 todo 状态并要求模型继续通过 write_todos 跟踪。

2. 防止带未完成任务提前退出(after_model)

after_model 钩子(todo_middleware.py#L269-L313)在模型产出"干净的最终回复"(无工具调用意图,由 _has_tool_call_intent_or_error() 判定)而 todo 尚未全部完成时:

  1. 先执行基类的并行 write_todos 检查,保持原有语义;
  2. 所有 todo 已完成或无 todo 时放行退出;
  3. 每个 (thread_id, run_id) 的完成提醒最多 _MAX_COMPLETION_REMINDERS = 2 次(todo_middleware.py#L173),防止代理无法推进时无限循环;
  4. 否则将提醒文本入队(而非持久化为用户可见消息),返回 {"jump_to": "model"} 让流程跳回模型节点继续工作。

提醒文本在下一轮模型请求中经 wrap_model_call / awrap_model_calltodo_middleware.py#L343-L365)注入——这里特意保留了对基类 wrap_model_call 的调用,因为基类正是在该钩子里追加 write_todos 系统提示词;TodoMiddleware 在其上叠加 todo_completion_reminderHumanMessage(同样 hide_from_ui)。提醒账本按 (thread_id, run_id) 分键、带锁与 LRU 式裁剪(上限 4096 键),在 before_agent/after_agent 中清理,避免跨 run 泄漏。

与其他中间件的联动

  • Token 统计token_usage_middleware.pywrite_todos 作为精确 token 统计的特例之一处理;
  • 进度展示tool_progress_middleware.pywrite_todos 列入免展示工具(与 ask_clarificationpresent_filestask 同列),避免纯记账型调用刷屏;
  • 中间件清单agents/middlewares/AGENTS.md 将 TodoListMiddleware 标注为 "optional, if is_plan_mode",与本文的开关语义一致。

关键收益与注意事项

原文档总结的 Plan Mode 优势,结合源码可进一步落实为:

  1. 动态控制:按请求启停,无全局状态——实现上就是 configurable/context 中的一个布尔键,DeerFlowClient 通过配置缓存键保证切换即重建;
  2. 灵活性:不同会话可以有不同的 Plan Mode 策略(HTTP run、嵌入式客户端、IM 渠道会话均可独立指定);
  3. 简单性:无需管理全局配置,关闭时中间件函数返回 None,零开销;
  4. 上下文感知:开关决策可以基于任务复杂度、用户偏好等。

注意事项(继承原文档 Notes 并对照源码确认):

  • Todo 中间件使用 LangChain 内置 TodoListMiddleware 作为基类,但注入的是 DeerFlow 风格自定义提示词
  • Plan Mode 默认关闭is_plan_mode=False),以维持向后兼容;
  • 中间件位于 ClarificationMiddleware 之前,使澄清流程期间仍可管理 todo;
  • 自定义提示词与 DeerFlow 主系统提示词强调相同原则:清晰、行动导向、关键规则(CRITICAL rules)。

综上,Plan Mode 是 DeerFlow lead agent 中一个典型的"运行时配置 → 中间件组装 → 模型行为约束 → 状态守护"完整链路:一个布尔参数即可为长时程任务挂上任务账本,并在上下文压缩与提前退出两类常见失效模式下自动兜底,适合作为研究 DeerFlow 中间件体系与运行时参数设计的入门案例。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384