本文档面向新手开发者,用简单清晰的语言介绍 ReCode 项目的代码结构和工作原理。
ReCode 是一个 LLM Agent 框架,核心思想是通过 递归代码生成 来统一"规划"和"执行"。
把复杂任务拆成代码树,遇到"不会做的函数"就让 LLM 展开,直到全部变成能直接执行的动作。
- 需要多步骤推理的任务(如:机器人导航、网页操作)
- 任务粒度不固定,需要动态调整
- 需要生成可追溯的决策过程
| 传统方法 (ReAct) | ReCode |
|---|---|
| 思考 → 执行 → 观察 → 思考... | 写代码 → 执行 → 遇到占位函数 → 展开 → 继续执行 |
| 每步都要 LLM 决策 | 只在需要时调用 LLM |
| 扁平的动作序列 | 树形的代码结构 |
任务开始
↓
创建根节点: solve(任务)
↓
执行代码 ←────────────────┐
↓ │
成功?─── 是 → 下一个节点 ─┘
│
否
↓
是占位函数?─── 是 → 调用LLM展开 → 创建子节点
│
否
↓
报错,结束
ReCode/
├── run.py # 🚀 程序入口,命令行工具
├── base/ # 📦 基础抽象类
│ ├── agent.py # Agent 接口定义
│ └── environment.py # Environment 接口定义
├── agents/ # 🤖 Agent 实现
│ └── recode/
│ ├── agent.py # ReCode Agent 核心逻辑
│ ├── utils.py # 辅助工具(CodeNode、代码解析)
│ └── resources/ # 提示词和示例
│ ├── prompts/ # LLM 提示词模板
│ └── fewshots/ # 少样本示例
├── envs/ # 🌍 环境实现
│ ├── alfworld/ # ALFWorld 家居任务环境
│ ├── webshop/ # WebShop 购物环境
│ └── sciworld/ # ScienceWorld 科学实验环境
├── utils/ # 🔧 工具模块
│ ├── llm.py # LLM 调用封装
│ ├── executor.py # Python 代码执行器
│ ├── common.py # 通用工具函数
│ ├── logger.py # 日志工具
│ └── errors.py # 自定义异常
└── configs/ # ⚙️ 配置文件
├── profiles.yaml # LLM 配置(API Key 等)
└── prices.json # 模型价格表
class Agent(ABC):
async def act(observations) -> actions # 根据观察返回动作
def reset(config, init_info) # 重置 Agent 状态
def report() -> dict # 返回运行报告作用:定义所有 Agent 必须实现的方法,确保不同 Agent 可以互换使用。
class Env(ABC):
async def run(actions) -> observations # 执行动作,返回观察
def reset(config) -> init_info # 重置环境
def is_done() -> bool # 是否结束
def is_success() -> bool # 是否成功
def report() -> dict # 返回环境报告作用:定义环境的标准接口,让 Agent 可以和任何环境交互。
这是 ReCode 的大脑,负责:
- 初始化代码树:把任务变成根节点
solve(instruction, observation) - 执行循环:执行节点代码,处理结果
- 展开占位函数:调用 LLM 生成子代码
- 管理状态:跟踪当前节点、深度等
关键方法:
| 方法 | 作用 |
|---|---|
act() |
主循环:执行当前节点,处理结果 |
_handle_stub() |
处理需要展开的占位函数 |
_expand() |
调用 LLM 生成展开代码 |
_execute() |
执行代码片段 |
_init_code_tree() |
初始化代码树 |
_build_expand_prompt() |
构建 LLM 提示词 |
CodeNode 类:代码树的节点
@dataclass
class CodeNode:
code: str # 这个节点的代码
parent: CodeNode # 父节点
children: list[CodeNode] # 子节点列表
status: NodeStatus # 状态(等待/完成/需展开/出错)
depth: int # 树的深度
thought: str # LLM 的思考过程
observations: list # 执行过程中的观察辅助函数:
| 函数 | 作用 |
|---|---|
split_blocks() |
把代码字符串拆分成独立的语句 |
validate_blocks() |
验证代码块是否合法 |
get_variables() |
从代码中提取变量信息 |
parse_raw_observation() |
解析环境的初始观察 |
核心职责:
- 执行 Python 代码:用
exec()运行代码 - 管理变量:保存执行过程中的变量
- 调用环境:通过
run("动作")与环境交互 - 识别占位函数:捕获
NameError判断是否需要展开
神奇的机制:
# 当执行这行代码时
obj_ID = find_and_take(obj, locations)
# Python 会报错:NameError: name 'find_and_take' is not defined
# Executor 捕获这个错误,判断它是占位函数调用
# 返回 "NeedExpansion" 信号,而不是当作真正的错误AsyncLLM 类:异步调用大语言模型
llm = AsyncLLM("default") # 创建实例
response, cost = await llm(prompt) # 调用功能:
- 支持多个配置 profile(不同的 API Key、模型)
- 自动重试失败请求
- 计算 API 调用费用
| 函数 | 作用 |
|---|---|
read_json_file() |
读取 JSON 文件 |
write_json_file() |
写入 JSON 文件 |
read_yaml_file() |
读取 YAML 文件 |
parse_code_block() |
从 Markdown 中提取代码块 |
parse_xml_tag() |
从文本中提取 XML 标签内容 |
SimpleLogger 类:封装日志记录功能
logger = SimpleLogger(run_id="my_experiment")
logger.info("开始运行...")
logger.log_stats({"total_tests": 10, "successful_tests": 8})功能:
- 同时输出到文件和控制台
- 自动创建日志目录
logs/<run_id>/running_logs/ - 支持多行日志格式化
用于测试和调试的模拟 LLM,用户可以手动输入响应。
mock_llm = MockLLM()
response = await mock_llm("你好") # 在控制台手动输入响应使用场景:
- 调试 Agent 逻辑时不想花费 API 费用
- 手动控制 Agent 的决策过程
| 异常类 | 说明 |
|---|---|
StepLimitError |
环境执行步数超过最大限制时抛出 |
每个环境文件夹包含:
env.py:环境实现类base_config.yaml:环境配置data/:数据文件(如任务索引)
支持的环境:
| 环境 | 说明 | 任务类型 |
|---|---|---|
| ALFWorld | 家居机器人任务 | put, clean, heat, cool, examine, puttwo |
| WebShop | 网页购物任务 | 搜索、浏览、购买商品 |
| ScienceWorld | 科学实验任务 | 物理、化学、生物实验 |
ALFWorld 是一个基于 TextWorld 的家居交互环境。
可用动作(在 resources/prompts/alfworld/actions.txt 中定义):
go to <location>- 前往某位置(如go to table 1)take <object> from <location>- 拿取物品put <object> in/on <location>- 放置物品open/close <object>- 打开/关闭容器examine <object>- 检查物品或位置use <object>- 使用物品(加热、冷却等)
任务类型说明:
| 类型 | 说明 | 示例任务 |
|---|---|---|
put |
找到物品放到指定位置 | "把苹果放到桌子上" |
clean |
清洁物品后放置 | "清洗杯子放到柜子里" |
heat |
加热物品后放置 | "用微波炉加热土豆放到盘子上" |
cool |
冷却物品后放置 | "用冰箱冷却番茄放到台面上" |
examine |
在灯光下检查物品 | "在台灯下检查书" |
puttwo |
放置两个同类物品 | "把两个苹果放到冰箱里" |
配置不同的 LLM 访问参数,支持多个 profile。
models:
default:
api_key: "sk-your_api_key"
base_url: "https://api.openai.com/v1"
model: "gpt-4o-mini"
temperature: 0.0
gpt-4o:
api_key: "sk-your_api_key"
model: "gpt-4o"
temperature: 0.7使用方法:
# 复制示例配置
cp configs/profiles_example.yaml configs/profiles.yaml
# 编辑填入你的 API Key
# 然后运行时指定 profile
python run.py --profile gpt-4o用于计算 API 调用费用,单位是美元/百万 token。
{
"gpt-4o": {"input": 2.5, "output": 10.0},
"gpt-4o-mini": {"input": 0.15, "output": 0.6}
}功能:
- 解析命令行参数
- 加载配置文件
- 创建 Agent 和 Environment 实例
- 运行多个任务实例(支持并发)
- 收集结果并生成报告
使用示例:
# 单个实例
python run.py -a recode -e alfworld -n 1
# 多实例并发
python run.py -a recode -e webshop -n 10 -c 5
# 使用配置文件
python run.py -C configs/example.yaml┌─────────────────────────────────────────────────────────────────┐
│ run.py │
│ 1. 解析命令行参数 │
│ 2. 创建 Agent 和 Env 实例 │
│ 3. 调用 run_single_instance() │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ run_single_instance() │
│ while not env.is_done(): │
│ actions = await agent.act(observations) │
│ observations = await env.run(actions) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ agent.act() │
│ 1. 如果当前节点需要展开 → 调用 LLM │
│ 2. 执行当前节点代码 │
│ 3. 根据执行结果更新状态 │
│ 4. 移动到下一个节点 │
└─────────────────────────────────────────────────────────────────┘
以 "把苹果放到桌子上" 任务为例:
初始状态:
solve("把苹果放到桌子上", observation) [PENDING]
第 1 次展开后:
solve(...) [STUB → 已展开]
├── apple_id = find_and_take("apple", locations) [PENDING]
└── put_on(apple_id, "table") [PENDING]
第 2 次展开后:
solve(...) [COMPLETED]
├── find_and_take(...) [STUB → 已展开]
│ ├── for loc in locations: [PENDING]
│ │ run("go to ...")
│ │ run("take ...")
└── put_on(...) [PENDING]
执行完成后:
solve(...) [COMPLETED]
├── find_and_take(...) [COMPLETED]
│ └── (for 循环已执行) [COMPLETED]
└── put_on(...) [COMPLETED]
| 状态 | 含义 | 下一步 |
|---|---|---|
PENDING |
等待执行 | 执行代码 |
COMPLETED |
执行成功 | 移动到下一节点 |
STUB |
占位函数,需要展开 | 调用 LLM |
ERROR |
执行出错 | 终止或重试 |
SKIP |
跳过 | 移动到下一节点 |
# executor.py 中的关键代码
try:
exec(code, variables) # 执行代码
except NameError as e:
# 检查是不是函数调用
if "函数名(" in code:
return "NeedExpansion" # 标记为需要展开
else:
return "Error" # 真正的错误def next(self):
# 1. 先看子节点
for child in self.children:
if child.status == PENDING:
return child
# 2. 再看兄弟节点
for sibling in 右边的兄弟:
if sibling.status == PENDING:
return sibling
# 3. 向上回溯
return self.parent.next()- 创建
envs/your_env/env.py:
from base.environment import Env
class YourEnv(Env):
async def _run(self, action: str):
# 实现动作执行逻辑
return observation
def reset(self, config, id=None):
# 初始化环境
return {"observations": [...], "env_name": "your_env", "env": self}
def is_success(self):
return self._success
def report(self):
return {"success": self._success, ...}- 在
run.py中注册别名:
ENV_ALIASES = {
"your_env": "envs.your_env.env.YourEnv",
}- 创建提示词和示例:
agents/recode/resources/prompts/your_env/actions.txtagents/recode/resources/fewshots/your_env/base.txt
可以修改这些部分:
| 文件 | 可修改内容 |
|---|---|
agents/recode/agent.py |
展开逻辑、深度限制、重试策略 |
resources/prompts/default_new.py |
LLM 提示词模板 |
resources/fewshots/ |
少样本示例 |
# 查看帮助
python run.py --help
# ALFWorld 测试
python run.py -a recode -e alfworld -n 5 --profile default
# WebShop 测试
python run.py -a recode -e webshop -n 10 -c 3
# 使用指定配置
python run.py -C configs/example.yaml
# 查看日志
ls logs/文档更新日期:2025-01