Plan execute cn
Plan-Execute 节点工厂 (create_plan_execute_node) 是 Jianmu 提供的第二种预设复合节点模式,它将复杂的任务处理分解为 规划 (Plan) → 执行 (Execute) → 审查 (Review) 三个顺序阶段,并在审查不通过时自动回环重试。相较于 ReAct 模式中将推理与工具调用交织在一起的单一循环,Plan-Execute 显式地将"思考如何做"与"动手去做"分离到独立的 LLM 调用中,适用于需要结构化分解的复杂任务场景。
架构全景:三阶段回环与条件退出¶
Plan-Execute 的核心结构是一个 LoopUntilSuccess 装饰器包裹的 Sequence 组合节点。外层循环控制整体重试轮次 (max_rounds),内层序列按固定顺序执行规划、执行、审查(可选)、退出条件检查四个步骤。当退出条件未满足时,LoopUntilSuccess 将序列重置为 INVALID 状态并触发新一轮 tick,形成完整的回环机制。
flowchart TD
LUS["LoopUntilSuccess<br/>(max_iterations = max_rounds)"]
SEQ["Sequence (memory=True)<br/>PlanExecute/Loop"]
PLANNER["AgentLLMNode<br/>PlanExecute/Planner<br/>无工具 · 仅规划"]
EXECUTOR["AgentLLMNode<br/>PlanExecute/Executor<br/>有工具 · 产生最终答案"]
TOOL_EXEC["ToolExecutor<br/>PlanExecute/ToolExecutor<br/>(仅在有工具时添加)"]
REVIEWER["EvaluationNode<br/>PlanExecute/Reviewer<br/>(仅在 enable_review 时)"]
EXIT{"退出条件"}
EXIT_SIMPLE["StateCondition: IsDone"]
EXIT_FULL["Sequence: IsDone ∧ Score ≥ threshold"]
LUS --> SEQ
SEQ --> PLANNER
PLANNER --> EXECUTOR
EXECUTOR --> TOOL_EXEC
TOOL_EXEC --> REVIEWER
REVIEWER --> EXIT
EXIT --> EXIT_SIMPLE
EXIT --> EXIT_FULL
EXIT -- "FAILURE → 回环" --> LUS
EXIT -- "SUCCESS → 完成" --> DONE["Status.SUCCESS"]
树中每个节点的添加顺序和条件是精确控制的:planner 和 executor 始终存在;当 tools 非空时,在 executor 之后插入 ToolExecutor;当 config.enable_review 为 True 时,在工具执行器之后追加 EvaluationNode,并使用复合退出条件 IsDone AND ScoreOK,否则仅使用 IsDone 单一条件。
状态键隔离:规划输出与最终答案的分治¶
Plan-Execute 最精妙的设计在于规划器与执行器使用不同的 StateKeys 配置,将规划产物与用户可见的最终答案存储到不同的状态字段中。这是通过 PlanExecuteConfig 中的 plan_key 和 keys.final_answer 两个参数实现的。
| 节点 | final_answer 写入目标 |
实际写入的状态字段 | 用途 |
|---|---|---|---|
| Planner | config.plan_key |
"plan" (默认) |
内部规划步骤,用户不可见 |
| Executor | keys.final_answer |
"final_answer" (默认) |
用户可见的最终回复 |
| Reviewer | config.score_key |
"score" (默认) |
审查评分,用于退出条件判定 |
这种设计保证了规划器生成的中间计划不会被当作最终答案暴露给用户。在创建 planner_keys 时,只有 final_answer 被替换为 plan_key,其余字段 (messages、actions、done) 与执行器共享同一组键,确保两个阶段在同一个对话历史和终止信号上协同工作。
上下文构建器:三个阶段的三套 Prompt 装配¶
每个阶段拥有独立的 ContextBuilder,注入阶段专属的系统提示词。规划器仅注入静态规划提示词和对话历史,没有工具描述——这确保了规划阶段是纯粹的推理,不受工具可用性的干扰。执行器则额外注入了 ToolsDescProvider,使 LLM 知晓可用工具。
flowchart LR
subgraph PlannerContext["Planner ContextBuilder"]
SPP_P["StaticPromptProvider<br/>(plan_prompt)"]
SHP_P["StateHistoryProvider<br/>(消息历史)"]
end
subgraph ExecutorContext["Executor ContextBuilder"]
SPP_E["StaticPromptProvider<br/>(execute_prompt)"]
TDP["ToolsDescProvider<br/>(工具描述)"]
SHP_E["StateHistoryProvider<br/>(消息历史)"]
end
subgraph ReviewerEval["Reviewer (EvaluationNode)"]
EVAL_CONFIG["AgentLLMConfig<br/>(system_prompt = review_prompt)"]
end
默认的三段提示词定义在 jianmu/config/prompts.py 中,分别将模型角色塑造为"规划者"、"执行者"和"严格审查者"。规划提示词明确禁止执行工具("Do NOT execute tools; just plan"),执行提示词则鼓励使用工具,审查提示词要求输出 0-10 的评分和简短反思。
审查回路:EvaluationNode 的评分与门控¶
当 enable_review=True 时,审查器 (EvaluationNode) 通过结构化工具调用 (evaluate_result) 强制模型输出 JSON 格式的 {score, reflection}。这个评分随后被写入 score_key 字段,并与 review_threshold(默认 8.0)比较,决定是否允许退出循环。
审查器使用独立的模型配置(默认 models.evaluate),与规划器和执行器可以选用不同的模型。其工作原理是构造一个包含输入键(默认为 messages 和 answer)的 prompt,调用 LLM 并强制其调用 evaluate_result 函数,从中提取评分和反思。
退出条件的构建逻辑清晰地体现在代码中:
- 无审查模式: 单一
StateCondition,检查keys.done是否为 truthy - 有审查模式:
Sequence组合两个条件——IsDoneANDScore >= review_threshold,两者必须同时满足
配置模型:PlanExecuteConfig 参数全览¶
PlanExecuteConfig 继承自 Pydantic BaseModel,所有参数均有合理的默认值,多数从全局配置中继承。
| 参数 | 类型 | 默认值来源 | 说明 |
|---|---|---|---|
model |
str |
config.models.plan_execute |
规划器和执行器共用模型 |
temperature |
float |
config.llm.temperature (0.7) |
采样温度 |
max_tokens |
int \| None |
config.llm.max_tokens |
最大输出 token 数 |
top_p |
float \| None |
config.llm.top_p (0.95) |
核采样参数 |
top_k |
int \| None |
config.llm.top_k (40) |
Top-K 采样参数 |
timeout |
float |
config.llm.timeout (60.0) |
每次 LLM 调用超时(秒) |
max_budget_tokens |
int \| None |
config.llm.max_budget_tokens |
全局 token 预算上限 |
stream |
bool |
False |
执行器是否启用流式输出 |
max_rounds |
int |
config.limits.max_rounds (10) |
最大规划-执行-审查轮次 |
enable_review |
bool |
True |
是否启用审查阶段 |
review_threshold |
float |
8.0 |
审查通过的最低评分 |
plan_prompt |
str \| None |
None (使用内置默认) |
规划阶段自定义提示词 |
execute_prompt |
str \| None |
None (使用内置默认) |
执行阶段自定义提示词 |
review_prompt |
str \| None |
None (使用内置默认) |
审查阶段自定义提示词 |
plan_key |
str |
"plan" |
规划输出写入的状态字段名 |
score_key |
str |
"score" |
审查评分写入的状态字段名 |
keys |
StateKeys |
StateKeys() |
共享状态键名配置 |
与 ReAct 模式的对比¶
Plan-Execute 与 ReAct 共享 LoopUntilSuccess 外层结构和 Sequence 内层骨架,但在阶段分离、状态隔离和退出策略上存在根本差异。
| 维度 | ReAct | Plan-Execute |
|---|---|---|
| 阶段数量 | 单阶段(LLM + 工具交替) | 2-3 阶段(规划 → 执行 → 审查) |
| 规划与执行 | 交织在同一 LLM 调用中 | 显式分离为独立 LLM 节点 |
| 规划器工具访问 | N/A | 无工具——纯推理 |
| 状态隔离 | 所有节点共享同一 StateKeys |
规划器 final_answer 重定向到 plan_key |
| 退出条件 | done truthy + 可选 token 预算中止 |
done truthy + 可选评分阈值 |
| Token 预算控制 | abort_condition 实时检查 |
无内置 token 预算中止 |
| 上下文构建 | 单一 ContextBuilder |
规划器和执行器各有一个独立 ContextBuilder |
| 适用场景 | 工具驱动型任务(搜索、计算、代码执行) | 需要结构分解的复杂推理任务 |
使用示例与工具集成¶
在最简配置下,创建一个无工具的纯推理 Plan-Execute Agent:
from jianmu.node.presets import create_plan_execute_node
from jianmu import Agent
root = create_plan_execute_node(
name="MyPlanner",
config=PlanExecuteConfig(enable_review=False, max_rounds=3),
)
agent = Agent(root)
await agent.run(input_data={"messages": [Message(role="user", content="解释量子纠缠")]})
当传入 tools 参数时,ToolExecutor 节点会自动插入到执行器之后,使执行器能够调用工具执行具体操作。工具描述通过 ToolsDescProvider 注入到执行器的上下文中。
内部实现细节:LoopUntilSuccess 的重试机制¶
Plan-Execute 依赖 LoopUntilSuccess 装饰器实现多轮重试。当内层 Sequence 返回 FAILURE(意味着子节点失败或退出条件不满足),LoopUntilSuccess.update() 将已装饰子节点停止为 INVALID 状态,并通过 state_manager.signal() 触发新一轮调度,使 Sequence 从第一个子节点重新开始执行。每次重试时 iteration_count 递增,达到 max_iterations(即 max_rounds)后记录终止元数据并永久失败。
阅读路径建议¶
Plan-Execute 节点工厂建立在多个核心模块之上。建议按以下顺序深入阅读:
- ReAct 节点工厂 — 理解共享的
LoopUntilSuccess循环机制和预设模式的设计范式 - LLM 节点 — 深入了解 Planner 和 Executor 底层使用的
AgentLLMNode上下文构建与模型调用流程 - 工具与技能节点 — 理解 Executor 关联的
ToolExecutor如何调度工具执行 - 上下文构建器 — 掌握
ContextBuilder的 provider-filter 架构,理解三个阶段如何独立装配 prompt - 项目配置体系 — 了解如何通过
jianmu.yaml覆盖默认的 Plan-Execute 提示词和模型选择