上文书中说道(浅谈 ReCAP(Recursive Context-Aware Reasoning and Planning)------大模型推理与规划的递归范式),ReCAP(Recursive Context-Aware Planning) 用显式递归 + 任务栈结构,让 LLM 能够处理真正的长链条任务(Long-horizon tasks)。
本文使用 LangGraph + Python 模拟ReCAP框架,通过代码的方式讲解:
- ReCAP 的核心思想是什么?
- 为什么智能体需要"显式递归与栈帧"?
- 如何用 LangGraph 实现一个完整、可运行的 ReCAP 执行器?
- 代码架构如何组织?每部分组件扮演什么角色?
我们以前文中一个简单的任务作为例子展开讲解:
root_task = "做一个牛肉汉堡"
(注:本文的所有代码均为可直接运行的纯模拟版 ,不依赖任何 LLM API。文末附完整项目代码)
1. ReCAP 的核心思想:显式递归 + 上下文修正 + 结构化计划
ReCAP 的核心可以归纳为三句话:
① 任务是可以被递归拆解的(Task → Subtasks)
如果一个任务不可直接执行,它应被拆成子任务,再逐个求解。这就天然形成一棵任务树。
② 每个任务在执行过程中,可以因观察(obs)而动态修正计划(Refine)
真实世界具有不确定性,执行动作后环境会变化,因此需要:
- 根据反馈动态调整剩余子任务
- 保留(或更新)当前任务的思考(Think)
③ 整个执行过程必须由一个显式的"栈"来管理递归(类似函数调用栈)
ReCAP 论文强调:
长任务执行需要"持久结构化记忆",不能仅靠 token stream。
因此我们会显式维护一个 stack = [Frame, Frame, Frame...]。
每一层 Frame 包含:
python
{
"task": 任务名称,
"think": 当前层的思考内容,
"subtasks": 该层拆解出的子任务列表,
"cursor": 当前执行到第几个子任务,
}
可把它理解为一次"函数调用"的运行时结构。
2. 为什么 LangGraph 天然适合实现 ReCAP?
LangGraph 本质是一个控制流程编排引擎,相比传统 chain 或 Agent:
- 它允许你显式控制循环、条件分支、状态流转
- 它接受一个显式的
state,可以持续更新、持久保存 - 允许 streaming 观察每一步状态变化
- 几乎可以把任何复杂执行逻辑都变成一个状态机
ReCAP 恰恰需要:
- 可控循环(直到任务完成)
- 持续更新的状态(stack + obs)
- 在每一步根据状态决定下一步行为
因此:LangGraph 是目前最适合实现 ReCAP 的框架之一。
3. 系统架构概览(Architecture Overview)
下面是本项目的结构图:
┌───────────────────────────────────────────────┐
│ LangGraph State │
│ │
│ root_task ------ 初始目标 │
│ stack ------ ReCAP 递归执行栈 │
│ last_obs ------ 最近的环境观察反馈 │
│ done ------ 是否执行完成 │
└───────────────────────────────────────────────┘
▼ 每一步执行 recap_step
┌───────────────────────────────────────────────┐
│ recap_step(): │
│ │
│ 1. 若栈为空 → 初始化根任务 │
│ 2. 取出栈顶 Frame: │
│ - 若没有子任务 → 弹栈 + refine 父任务 │
│ - 若有子任务: │
│ a) primitive → 执行动作,refine 当前层 │
│ b) 非 primitive → 创建子 Frame 压栈 │
└───────────────────────────────────────────────┘
▼ 条件流转
route_next():
- 若 done = True → END
- 否则 → recap_step(继续循环)
用一句话概括:所有 ReCAP 的逻辑都浓缩在一个节点 recap_step 中,而 LangGraph 则负责控制其循环执行与状态管理。
4. 系统模块解析
首先介绍本项目代码与论文的对应部分(论文链接:https://arxiv.org/pdf/2510.23822)
- 栈帧 Frame → 论文中每个任务的上下文 C
- plan_llm → π ( C )
- refine_llm → ρ ( C )
- 栈结构 → 递归执行与回溯
- LangGraph → 有向图驱动执行主循环
下面按功能拆解代码结构,并解释设计动机。
4.1 Frame & GraphState:ReCAP 的运行时模型
每个 Frame 就是一个任务节点的执行上下文:
python
# ========== 1. 状态与栈帧定义 ==========
class Frame(TypedDict):
"""ReCAP 中的一层任务帧(相当于递归函数调用栈的一帧)"""
task: str # 当前任务描述
think: str # 当前任务层的思考 T(这里用字符串模拟)
subtasks: List[str] # 当前层的子任务列表 S
cursor: int # 当前执行到第几个子任务(下标,从 0 开始)
决定这层任务"下一步要干什么"。
GraphState 是 LangGraph 的整体状态:
python
class GraphState(TypedDict):
"""
LangGraph 的全局状态 = ReCAP 论文里的 Context C + 调用栈
"""
root_task: str # 根任务,例如 "做一个牛肉汉堡"
stack: List[Frame] # 手动维护的递归任务栈
last_obs: Optional[str] # 最近一次执行动作的环境反馈
done: bool # 整体任务是否完成
这使得 LangGraph 能在每一步迭代中携带整个 ReCAP 的上下文。
4.2 提供一个"模拟环境":primitive + action
ReCAP 的环境交互通过 primitive 动作实现:
python
# ========== 2. 环境与 primitive 判定 ==========
VALID_ACTIONS = [
"拿起面包片",
"煎牛肉饼",
"切生菜",
"涂酱",
"组装汉堡",
]
def is_primitive(subtask: str) -> bool:
"""
primitive = 可以直接执行的环境动作(叶子节点)
在这个例子里:
- 精确等于 VALID_ACTIONS 的动作视为 primitive
- 或者也可以约定以 "ACTION:" 开头的也算 primitive
"""
return subtask in VALID_ACTIONS or subtask.startswith("ACTION:")
def execute_action(action: str) -> str:
"""
执行 primitive 动作,返回观察(obs)
这里是一个极简的「模拟环境」。
"""
if action not in VALID_ACTIONS:
return f"动作[{action}]无效:当前环境不支持该操作。"
return f"已执行动作[{action}],环境状态有所进展。"
这样可以专注于逻辑,而忽略实际环境复杂性。
4.3 plan_llm:模拟 LLM 的任务拆解函数 π ( C )
python
# ========== 3. 模拟 plan 函数 π(C) ==========
def plan_llm(task: str) -> tuple[str, List[str]]:
"""
用硬编码规则模拟 ReCAP 的 plan:
给定一个任务,返回 (think, subtasks)
可以根据需要扩展这些规则。
"""
task = task.strip()
# 根任务
if task in ("做一个牛肉汉堡", "做牛肉汉堡"):
think = "先准备食材,再处理牛肉,最后组装汉堡。"
subtasks = ["准备食材", "处理牛肉", "组装汉堡"]
# 子任务:准备食材
elif task == "准备食材":
think = "需要把面包和生菜准备好。"
subtasks = ["拿起面包片", "切生菜"]
# 子任务:处理牛肉
elif task == "处理牛肉":
think = "把牛肉饼煎熟。"
subtasks = ["煎牛肉饼"]
# 子任务:组装汉堡
elif task == "组装汉堡":
think = "先涂酱,再把所有材料组装起来。"
subtasks = ["涂酱", "组装汉堡"]
else:
# 未知任务:当作已经原子化的动作,或者空任务处理
think = f"任务[{task}]没有专门的拆解规则,尝试直接执行或跳过。"
subtasks = []
return think, subtasks
它给出对该任务的一个思考(think)并给出子任务(subtasks)。
在真实系统中这里会替换为 LLM 调用,而本文只是用规则模拟。
4.4 refine_llm:模拟 ReCAP 论文里的观察驱动修正
python
# ========== 4. 模拟 refine 函数 ρ(C) ==========
def refine_llm(
frame: Frame,
obs: Optional[str],
is_child_done: bool = False,
child_task: Optional[str] = None,
) -> Frame:
"""
模拟 ReCAP 的 refine 行为:
- 真实论文里会根据【最新观察 obs】【剩余子任务】动态调整计划。
- 这里简化成:
* 保留「剩余子任务」不变(不新增、不删除)
* 但会重置 cursor,从新的剩余列表的头开始执行
* think 字段简单拼接一下「已收到反馈」,纯显示用
"""
old_think = frame["think"]
cursor = frame["cursor"]
old_subtasks = frame["subtasks"]
# 剩余子任务 = 当前 cursor 之后的所有子任务
if cursor < len(old_subtasks):
remaining = old_subtasks[cursor:]
else:
remaining = []
# 简单处理:把反馈记在 think 里,便于打印观察
extra_info = ""
if is_child_done and child_task:
extra_info += f"[子任务 {child_task} 已完成] "
if obs:
extra_info += f"[最新观察: {obs}]"
new_think = old_think
if extra_info:
new_think = old_think + " " + extra_info
new_frame: Frame = {
"task": frame["task"],
"think": new_think,
"subtasks": remaining,
"cursor": 0, # 从新的剩余列表头开始
}
return new_frame
Refine 做两件事:
- 根据 obs / 子任务完成情况修正当前任务状态
- 重建 subtasks 的剩余部分并重置 cursor
这使得 ReCAP 执行过程具有"反馈驱动"的特点。
4.5 recap_step:ReCAP 的单步执行逻辑(核心)
这是整个系统的核心:
python
# ========== 5. ReCAP 主节点:一次执行一个"栈帧步骤" ==========
def recap_step(state: GraphState) -> GraphState:
"""
这是 LangGraph 中的一个节点:
- 如果栈为空:初始化根任务 Frame(相当于 ReCAP(root) 的第一次调用)
- 如果栈非空:取栈顶 Frame,执行一个「子任务或递归一步」
"""
# 确保 stack 至少是个 list(防御一下)
if "stack" not in state or state["stack"] is None:
state["stack"] = []
stack = state["stack"]
# 1. 如果栈为空 → 初始化根任务 Frame
if not stack:
root_task = state["root_task"]
think, subtasks = plan_llm(root_task)
root_frame: Frame = {
"task": root_task,
"think": think,
"subtasks": subtasks,
"cursor": 0,
}
stack.append(root_frame)
state["stack"] = stack
state["last_obs"] = None
state["done"] = False
return state
# 2. 取出当前栈顶 Frame
frame = stack[-1]
# 2.1 如果当前 Frame 没有子任务了 → 认为当前任务已完成,弹栈,然后回到父任务做 refine
if frame["cursor"] >= len(frame["subtasks"]):
finished_task = frame["task"]
stack.pop() # 当前任务完成
# 没有父任务了 → 整体完成
if not stack:
state["done"] = True
state["last_obs"] = f"任务[{finished_task}]已完成,所有任务结束。"
return state
# 有父任务 → 回到父任务,调用 refine_llm 更新它的剩余子任务
parent = stack[-1]
parent_refined = refine_llm(
frame=parent,
obs=state.get("last_obs"),
is_child_done=True,
child_task=finished_task,
)
# 用最新的 think/subtasks 替换父 Frame
stack[-1] = parent_refined
state["stack"] = stack
return state
# 3. 当前 Frame 还有子任务要执行
subtask = frame["subtasks"][frame["cursor"]]
# 3.1 primitive → 执行动作(叶子节点)
if is_primitive(subtask):
obs = execute_action(subtask)
state["last_obs"] = obs
# 更新当前 Frame 的 cursor(这一项已执行)
frame["cursor"] += 1
# 针对当前 Frame 做一次 refine(类似 Leaf Backtracking)
refined = refine_llm(
frame=frame,
obs=obs,
)
stack[-1] = refined
state["stack"] = stack
return state
# 3.2 非 primitive → 作为新的子任务 Frame 压栈(递归向下)
else:
child_task = subtask
child_think, child_subtasks = plan_llm(child_task)
child_frame: Frame = {
"task": child_task,
"think": child_think,
"subtasks": child_subtasks,
"cursor": 0,
}
# 父 Frame 的 cursor 往后移一位,表示这个位置的任务由子 Frame 负责
frame["cursor"] += 1
stack[-1] = frame
# 压入子 Frame
stack.append(child_frame)
state["stack"] = stack
# last_obs 保留为之前的值,等子任务执行后再更新
return state
recap_step 的行为逻辑:
-
栈为空 → 初始化根任务
-
取出栈顶 Frame
-
若当前任务已经无子任务 → 弹栈 + refine 父任务
-
否则:
- 若子任务是 primitive → 执行动作 + refine
- 若是 composite → 拆成新的 Frame,压栈
最终形成一个可控的"递归展开 → 执行 → 回溯 → 完成"的全过程。
4.6 LangGraph 图构建
python
# ========== 6. 路由函数:继续还是结束 ==========
def route_next(state: GraphState) -> str:
"""根据状态决定下一步是继续 recap_step,还是结束"""
if state.get("done"):
return "end"
return "continue"
# ========== 7. 构建 LangGraph 图 ==========
def build_graph():
builder = StateGraph(GraphState)
builder.add_node("recap_step", recap_step)
# START → recap_step
builder.add_edge(START, "recap_step")
# recap_step → (recap_step 或 END)
builder.add_conditional_edges(
"recap_step",
route_next,
{
"continue": "recap_step",
"end": END,
},
)
graph = builder.compile()
return graph
由此建立 ReCAP 的执行主循环。
5. 整体流程演示(任务:做一个牛肉汉堡)
执行过程大致如下:
Step 0: 初始化 root 任务
task = 做一个牛肉汉堡
subtasks = [准备食材, 处理牛肉, 组装汉堡]
Step 1: 进入 "准备食材"
Step 2: 执行 primitive "拿起面包片"
Step 3: 执行 primitive "切生菜"
→ 上层任务准备食材完成 → refine 父任务
Step 4: 进入 "处理牛肉"
Step 5: 执行 primitive "煎牛肉饼"
Step 6: 进入 "组装汉堡"
Step 7: 执行 "涂酱"
Step 8: 执行 "组装汉堡"
Step 9: root 任务完成 → END
实际的程序运行结果为:
=== ReCAP + LangGraph Demo(纯模拟版): 做一个牛肉汉堡 ===
====== Step 0 ======
当前栈深度: 0
当前栈为空
last_obs: None
====== Step 1 ======
当前栈深度: 1
当前任务: 做一个牛肉汉堡
当前子任务列表: ['准备食材', '处理牛肉', '组装汉堡']
cursor: 0
当前层 think: 先准备食材,再处理牛肉,最后组装汉堡。
last_obs: None
====== Step 2 ======
当前栈深度: 2
当前任务: 准备食材
当前子任务列表: ['拿起面包片', '切生菜']
cursor: 0
当前层 think: 需要把面包和生菜准备好。
last_obs: None
====== Step 3 ======
当前栈深度: 2
当前任务: 准备食材
当前子任务列表: ['切生菜']
cursor: 0
当前层 think: 需要把面包和生菜准备好。 [最新观察: 已执行动作[拿起面包片],环境状态有所进展。]
last_obs: 已执行动作[拿起面包片],环境状态有所进展。
====== Step 4 ======
当前栈深度: 2
当前任务: 准备食材
当前子任务列表: []
cursor: 0
当前层 think: 需要把面包和生菜准备好。 [最新观察: 已执行动作[拿起面包片],环境状态有所进展。] [最新观察: 已执行动作[切生菜],环境状态有所进展。]
last_obs: 已执行动作[切生菜],环境状态有所进展。
====== Step 5 ======
当前栈深度: 1
当前任务: 做一个牛肉汉堡
当前子任务列表: ['处理牛肉', '组装汉堡']
cursor: 0
当前层 think: 先准备食材,再处理牛肉,最后组装汉堡。 [子任务 准备食材 已完成] [最新观察: 已执行动作[切生菜],环境状态有所进展。]
last_obs: 已执行动作[切生菜],环境状态有所进展。
====== Step 6 ======
当前栈深度: 2
当前任务: 处理牛肉
当前子任务列表: ['煎牛肉饼']
cursor: 0
当前层 think: 把牛肉饼煎熟。
last_obs: 已执行动作[切生菜],环境状态有所进展。
====== Step 7 ======
当前栈深度: 2
当前任务: 处理牛肉
当前子任务列表: []
cursor: 0
当前层 think: 把牛肉饼煎熟。 [最新观察: 已执行动作[煎牛肉饼],环境状态有所进展。]
last_obs: 已执行动作[煎牛肉饼],环境状态有所进展。
====== Step 8 ======
当前栈深度: 1
当前任务: 做一个牛肉汉堡
当前子任务列表: ['组装汉堡']
cursor: 0
当前层 think: 先准备食材,再处理牛肉,最后组装汉堡。 [子任务 准备食材 已完成] [最新观察: 已执行动作[切生菜],环境状态有所进展。] [子任务 处理牛肉 已完成] [最新观察: 已执行动作[煎牛肉饼],环境状态有所进展。]
last_obs: 已执行动作[煎牛肉饼],环境状态有所进展。
====== Step 9 ======
当前栈深度: 1
当前任务: 做一个牛肉汉堡
当前子任务列表: []
cursor: 0
当前层 think: 先准备食材,再处理牛肉,最后组装汉堡。 [子任务 准备食材 已完成] [最新观察: 已执行动作[切生菜],环境状态有所进展。] [子任务 处理牛肉 已完成] [最新观察: 已执行动作[煎牛肉饼],环境状态有所进展。] [最新观察: 已执行动作[组装汉堡],环境状态有所进展。]
last_obs: 已执行动作[组装汉堡],环境状态有所进展。
====== Step 10 ======
当前栈深度: 0
当前栈为空
last_obs: 任务[做一个牛肉汉堡]已完成,所有任务结束。
所有任务完成。
这个流程完全符合 ReCAP 的设计:
- 任务被递归拆解
- 每层任务的状态由 Frame 持续追踪
- 环境反馈会驱动 refine 行为
- 完成后逐层回溯
6. 可扩展的方向
这个架构具备极强的扩展能力:
① 将 plan_llm / refine_llm 替换为真实 LLM
可无缝接入 GPT / Qwen / Claude / Gemini。
② 扩展为工具调用系统
primitive 动作可映射为:
- HTTP API Tool
- Python Tool
- Shell Tool
- Browser Action
③ 扩展为多智能体系统
不同子任务可分配给不同 Agent:
- Planner
- Worker
- Critic
- Executor
④ 支持持久化与断点恢复
LangGraph CheckpointSaver 可天然支持大任务的恢复执行。
⑤ 支持实时 UI 可视化(如 AG-UI 协议)
可把每一步 recap_step 的状态推到前端渲染。
7. 总结
智能体系统正在从"黑箱的 ReAct 工程"向"可控制、可调试、可扩展的结构化系统"演进,而 ReCAP 或许会成为其中关键的一环。
本篇示例展示了:
- 如何把 ReCAP 的递归执行模型映射到 LangGraph
- 如何构建任务栈 Frame
- 如何实现 plan / refine 的行为循环
- LangGraph 是如何保证 flow 可控且可重入的
如果你正在构建:
- 深度工具链 AI Agent
- 多智能体协作系统
- 企业级自动化任务执行引擎
- LangGraph 层级任务编排系统
那么笔者希望本文的实现可以为你提供一些思路。
附完整项目代码:
python
"""
ReCAP + LangGraph 示例(纯模拟版,无需接入 LLM)
核心点:
- 用 LangGraph 的 StateGraph 做主循环
- 手动维护一个「任务栈 stack」,模拟 ReCAP 的递归执行栈
- 每一层栈帧 Frame = {task, think, subtasks, cursor}
- plan_llm / refine_llm 都用本地模拟逻辑,不调用任何模型或网络
"""
from __future__ import annotations
from typing import List, TypedDict, Optional
from langgraph.graph import StateGraph, START, END
# ========== 1. 状态与栈帧定义 ==========
class Frame(TypedDict):
"""ReCAP 中的一层任务帧(相当于递归函数调用栈的一帧)"""
task: str # 当前任务描述
think: str # 当前任务层的思考 T(这里用字符串模拟)
subtasks: List[str] # 当前层的子任务列表 S
cursor: int # 当前执行到第几个子任务(下标,从 0 开始)
class GraphState(TypedDict):
"""
LangGraph 的全局状态 = ReCAP 论文里的 Context C + 调用栈
"""
root_task: str # 根任务,例如 "做一个牛肉汉堡"
stack: List[Frame] # 手动维护的递归任务栈
last_obs: Optional[str] # 最近一次执行动作的环境反馈
done: bool # 整体任务是否完成
# ========== 2. 玩具环境与 primitive 判定 ==========
VALID_ACTIONS = [
"拿起面包片",
"煎牛肉饼",
"切生菜",
"涂酱",
"组装汉堡",
]
def is_primitive(subtask: str) -> bool:
"""
primitive = 可以直接执行的环境动作(叶子节点)
在这个玩具例子里:
- 精确等于 VALID_ACTIONS 的动作视为 primitive
- 或者你也可以约定以 "ACTION:" 开头的也算 primitive
"""
return subtask in VALID_ACTIONS or subtask.startswith("ACTION:")
def execute_action(action: str) -> str:
"""
执行 primitive 动作,返回观察(obs)
这里是一个极简的「假环境」。
"""
if action not in VALID_ACTIONS:
return f"动作[{action}]无效:当前环境不支持该操作。"
return f"已执行动作[{action}],环境状态有所进展。"
# ========== 3. 模拟版 plan 函数 π(C) ==========
def plan_llm(task: str) -> tuple[str, List[str]]:
"""
用硬编码规则模拟 ReCAP 的 plan:
给定一个任务,返回 (think, subtasks)
你可以根据需要扩展这些规则。
"""
task = task.strip()
# 根任务
if task in ("做一个牛肉汉堡", "做牛肉汉堡"):
think = "先准备食材,再处理牛肉,最后组装汉堡。"
subtasks = ["准备食材", "处理牛肉", "组装汉堡"]
# 子任务:准备食材
elif task == "准备食材":
think = "需要把面包和生菜准备好。"
subtasks = ["拿起面包片", "切生菜"]
# 子任务:处理牛肉
elif task == "处理牛肉":
think = "把牛肉饼煎熟。"
subtasks = ["煎牛肉饼"]
# 子任务:组装汉堡
elif task == "组装汉堡":
think = "先涂酱,再把所有材料组装起来。"
subtasks = ["涂酱", "组装汉堡"]
else:
# 未知任务:当作已经原子化的动作,或者空任务处理
think = f"任务[{task}]没有专门的拆解规则,尝试直接执行或跳过。"
subtasks = []
return think, subtasks
# ========== 4. 模拟版 refine 函数 ρ(C) ==========
def refine_llm(
frame: Frame,
obs: Optional[str],
is_child_done: bool = False,
child_task: Optional[str] = None,
) -> Frame:
"""
模拟 ReCAP 的 refine 行为:
- 真实论文里会根据【最新观察 obs】【剩余子任务】动态调整计划。
- 这里简化成:
* 保留「剩余子任务」不变(不新增、不删除)
* 但会重置 cursor,从新的剩余列表的头开始执行
* think 字段简单拼接一下「已收到反馈」,纯显示用
"""
old_think = frame["think"]
cursor = frame["cursor"]
old_subtasks = frame["subtasks"]
# 剩余子任务 = 当前 cursor 之后的所有子任务
if cursor < len(old_subtasks):
remaining = old_subtasks[cursor:]
else:
remaining = []
# 简单处理:把反馈记在 think 里,便于打印观察
extra_info = ""
if is_child_done and child_task:
extra_info += f"[子任务 {child_task} 已完成] "
if obs:
extra_info += f"[最新观察: {obs}]"
new_think = old_think
if extra_info:
new_think = old_think + " " + extra_info
new_frame: Frame = {
"task": frame["task"],
"think": new_think,
"subtasks": remaining,
"cursor": 0, # 从新的剩余列表头开始
}
return new_frame
# ========== 5. ReCAP 主节点:一次执行一个"栈帧步骤" ==========
def recap_step(state: GraphState) -> GraphState:
"""
这是 LangGraph 中的一个节点:
- 如果栈为空:初始化根任务 Frame(相当于 ReCAP(root) 的第一次调用)
- 如果栈非空:取栈顶 Frame,执行一个「子任务或递归一步」
"""
# 确保 stack 至少是个 list(防御一下)
if "stack" not in state or state["stack"] is None:
state["stack"] = []
stack = state["stack"]
# 1. 如果栈为空 → 初始化根任务 Frame
if not stack:
root_task = state["root_task"]
think, subtasks = plan_llm(root_task)
root_frame: Frame = {
"task": root_task,
"think": think,
"subtasks": subtasks,
"cursor": 0,
}
stack.append(root_frame)
state["stack"] = stack
state["last_obs"] = None
state["done"] = False
return state
# 2. 取出当前栈顶 Frame
frame = stack[-1]
# 2.1 如果当前 Frame 没有子任务了 → 认为当前任务已完成,弹栈,然后回到父任务做 refine
if frame["cursor"] >= len(frame["subtasks"]):
finished_task = frame["task"]
stack.pop() # 当前任务完成
# 没有父任务了 → 整体完成
if not stack:
state["done"] = True
state["last_obs"] = f"任务[{finished_task}]已完成,所有任务结束。"
return state
# 有父任务 → 回到父任务,调用 refine_llm 更新它的剩余子任务
parent = stack[-1]
parent_refined = refine_llm(
frame=parent,
obs=state.get("last_obs"),
is_child_done=True,
child_task=finished_task,
)
# 用最新的 think/subtasks 替换父 Frame
stack[-1] = parent_refined
state["stack"] = stack
return state
# 3. 当前 Frame 还有子任务要执行
subtask = frame["subtasks"][frame["cursor"]]
# 3.1 primitive → 执行动作(叶子节点)
if is_primitive(subtask):
obs = execute_action(subtask)
state["last_obs"] = obs
# 更新当前 Frame 的 cursor(这一项已执行)
frame["cursor"] += 1
# 针对当前 Frame 做一次 refine(类似 Leaf Backtracking)
refined = refine_llm(
frame=frame,
obs=obs,
)
stack[-1] = refined
state["stack"] = stack
return state
# 3.2 非 primitive → 作为新的子任务 Frame 压栈(递归向下)
else:
child_task = subtask
child_think, child_subtasks = plan_llm(child_task)
child_frame: Frame = {
"task": child_task,
"think": child_think,
"subtasks": child_subtasks,
"cursor": 0,
}
# 父 Frame 的 cursor 往后移一位,表示这个位置的任务由子 Frame 负责
frame["cursor"] += 1
stack[-1] = frame
# 压入子 Frame
stack.append(child_frame)
state["stack"] = stack
# last_obs 保留为之前的值,等子任务执行后再更新
return state
# ========== 6. 路由函数:继续还是结束 ==========
def route_next(state: GraphState) -> str:
"""根据状态决定下一步是继续 recap_step,还是结束"""
if state.get("done"):
return "end"
return "continue"
# ========== 7. 构建 LangGraph 图 ==========
def build_graph():
builder = StateGraph(GraphState)
builder.add_node("recap_step", recap_step)
# START → recap_step
builder.add_edge(START, "recap_step")
# recap_step → (recap_step 或 END)
builder.add_conditional_edges(
"recap_step",
route_next,
{
"continue": "recap_step",
"end": END,
},
)
graph = builder.compile()
return graph
# ========== 8. 示例运行入口 ==========
if __name__ == "__main__":
graph = build_graph()
# 初始状态:只给 root_task,其余让 recap_step 自行初始化
init_state: GraphState = {
"root_task": "做一个牛肉汉堡",
"stack": [],
"last_obs": None,
"done": False,
}
print("=== ReCAP + LangGraph Demo(纯模拟版): 做一个牛肉汉堡 ===")
step = 0
for state in graph.stream(init_state, stream_mode="values"):
print(f"\n====== Step {step} ======")
stack = state.get("stack", [])
print("当前栈深度:", len(stack))
if stack:
top = stack[-1]
print("当前任务:", top.get("task"))
print("当前子任务列表:", top.get("subtasks"))
print("cursor:", top.get("cursor"))
print("当前层 think:", top.get("think"))
else:
print("当前栈为空")
print("last_obs:", state.get("last_obs"))
if state.get("done"):
print("所有任务完成。")
break
step += 1