一次运行由一个 run.yaml 串联四个角色/工具 YAML:
# run.yaml
task: deep_research # 选择 task 插件
exp_name: my-run
wandb_project_name: rl-rag
models:
solver: ${MODEL_PATH} # 支持 ${ENV} 展开
proposer: ${QUERY_MODEL_PATH}
proposer: proposer.yaml # 可以是相对路径,也可以内联成 dict
solver: solver.yaml
judge: judge.yaml
tools: tools.yaml
# 可选:output_dir(默认按 output_hpo_<rl_method>_<stage>_<qstage>_<hybrid> 派生)
# 可选:training: { learning_rate: 1e-6, seed: 7, ... } # 覆盖通用超参# proposer.yaml —— 出题模型如何进化、是否用工具
strategy: hote # none | hote | "包.模块:函数"(自定义)
use_tools: true # true=接入数据源工具(real_tool),false=纯参数化(no_tool)
# rl_method: gspo # 可选:proposer 用自己的 RL 算法;不填则跟随 solver 的 rl_method# solver.yaml —— 作答模型(始终进化,即被训练的模型)
strategy: hote
use_tools: true
rl_method: grpo # grpo | gspo | reinforce_pp | rloo | seeupo | rltr
hybrid: true # 基础 hybrid(每步一半 query 用 no_tool、一半用 use_tools 对应模式)# judge.yaml —— 评分/rubric 如何进化
strategy: hote # hote=实时生成新 rubric + 演化 rubric buffer;none=仅静态 rubric
use_tools: false # 预留配置位(当前 deep_research 下 judge 不实际调用工具)# tools.yaml —— 与数据源交互的工具
backend: mcp # 映射到 --tools
mcp_tool_names: [google_search, snippet_search, browse_webpage]
mcp_parser_name: v20250824
max_tool_calls: 10
# mcp_host: 172.16.10.157 # 可选:你部署好的 MCP server 地址
# mcp_port: 8200 # 不填则用 MCP_TRANSPORT_HOST / MCP_TRANSPORT_PORT 环境变量内置进化方式:
| 角色 | strategy | 含义 |
|---|---|---|
| proposer | none |
不进化:不生成合成问题、不训练出题模型,只用原始数据集 |
| proposer | hote |
独立出题模型在线生成合成问题并被训练;use_tools 控制是否用工具,rl_method 可独立指定 |
| solver | hote |
作答模型被训练,支持全部 RL 算法 + 基础 hybrid;use_tools 控制工具 |
| judge | none |
不进化:仅用静态/预定义 rubric 打分 |
| judge | hote |
实时生成新 rubric + 演化 rubric buffer |
real_tool 与 no_tool 两套配置的唯一区别就是 proposer/solver 的 use_tools
(true→real_tool 阶段,false→no_tool 阶段),其余完全相同。
进化策略是一个函数 fn(ctx: BuildContext, role: RoleConfig) -> None,
通过修改 ctx.args(flag→值 的字典)来设置训练引擎参数:
# my_pkg/my_strategies.py
from open_instruct.evolve_framework import register_evolution_strategy
def proposer_generate_only(ctx, role):
"""出题但不训练出题模型。"""
ctx.args["apply_evolutionary_queries"] = "true"
ctx.args["train_query_generator"] = "false"
stage = "real_tool" if role.use_tools else "no_tool"
ctx.args["query_gen_training_stage"] = stage
ctx.args["query_model_name_or_path"] = ctx.run.models["proposer"]
ctx.args["query_num_learners_per_node"] = ctx.resources.query_num_learners_per_node
ctx.args["query_vllm_num_engines"] = ctx.resources.query_vllm_num_engines
ctx.meta["query_gen_training_stage"] = stage # 供 output_dir 命名
register_evolution_strategy("proposer", "generate_only", proposer_generate_only)BuildContext 可用字段:ctx.run(RunConfig)、ctx.task(当前 Task)、
ctx.args(输出参数字典)、ctx.resources(GPU/引擎数)、ctx.meta(跨策略传值,
其中 training_stage/query_gen_training_stage/rl_method/hybrid_mode 用于派生 output_dir)。
role.use_tools、role.opt("key", default) 读取角色配置。
在 YAML 中引用(推荐 包.模块:函数 形式,会自动 import,无需手动注册):
# proposer.yaml
strategy: my_pkg.my_strategies:proposer_generate_only
use_tools: true新任务 = 写一个 Task 子类,提供三组参数贡献,然后注册。无需改框架或训练引擎。
# open_instruct/tasks/my_task/task.py
from open_instruct.evolve_framework.config import FLAG, ArgDict, ToolConfig
from open_instruct.tasks.base import Task
class MyTask(Task):
name = "my_task"
def base_args(self, run) -> ArgDict:
# 任务常量:数据集、系统提示、通用超参(不要设置角色旋钮/模型路径/资源/output_dir)
return {
"dataset_mixer_list": ["<dataset_path>", "1.0"],
"dataset_mixer_list_splits": "train",
"system_prompt_file": "<prompt.yaml>",
"no_tool_system_prompt_file": "<no_tool_prompt.yaml>",
"learning_rate": "5e-7", "beta": "0.001", "response_length": "16384",
"apply_verifiable_reward": "true", "gradient_checkpointing": FLAG,
# ... 其余通用超参
}
def tool_args(self, tools: ToolConfig) -> ArgDict:
# 把 tools.yaml 翻译成引擎的工具参数
return {"tools": tools.backend or "mcp", ...}
def judge_args(self, evolve: bool, use_tools: bool) -> ArgDict:
# judge:hote -> evolve=True;judge:none -> evolve=False(关闭 rubric 演化)
return {"use_rubric_buffer": "true" if evolve else "false", ...}注册(在 open_instruct/tasks/__init__.py 中):
from .my_task.task import MyTask
register_task(MyTask())之后在 run.yaml 里 task: my_task 即可。建议把该任务的 run/proposer/solver/judge/tools
默认 YAML 放在 open_instruct/tasks/my_task/configs/,参考 deep_research/configs/。
工具分两层:
-
新增一个工具后端(backend):在引擎的工具注册表登记工具类
open_instruct/tool_utils/tool_actor.py的register_tool(name, "包.模块:类"), 然后在tools.yaml用backend: <name>,并在对应 task 的tool_args中输出该后端 需要的参数。 -
给 MCP 后端新增一个子工具(如新的检索/浏览工具):在
open_instruct/search_utils/mcp_tools.py的MCP_TOOL_REGISTRY中登记,然后在tools.yaml的mcp_tool_names中加入它的名字,例如:backend: mcp mcp_tool_names: [google_search, snippet_search, browse_webpage, <your_tool>]
工具是否被各角色调用由各自的 use_tools 控制(true 才会接入数据源)。
A run is described by one run.yaml that wires together four role/tool YAMLs:
# run.yaml
task: deep_research # select the task plugin
exp_name: my-run
wandb_project_name: rl-rag
models:
solver: ${MODEL_PATH} # ${ENV} expansion supported
proposer: ${QUERY_MODEL_PATH}
proposer: proposer.yaml # a relative path, or an inline dict
solver: solver.yaml
judge: judge.yaml
tools: tools.yaml
# optional: output_dir (defaults to output_hpo_<rl_method>_<stage>_<qstage>_<hybrid>)
# optional: training: { learning_rate: 1e-6, seed: 7, ... } # override generic hyperparams# proposer.yaml — how the question model evolves and whether it uses tools
strategy: hote # none | hote | "package.module:function" (custom)
use_tools: true # true = use data-source tools (real_tool), false = parametric only (no_tool)
# rl_method: gspo # optional: give the proposer its own RL algorithm; defaults to the solver's# solver.yaml — the answering model (always evolves; this is the model being trained)
strategy: hote
use_tools: true
rl_method: grpo # grpo | gspo | reinforce_pp | rloo | seeupo | rltr
hybrid: true # basic hybrid (each step: half the queries no_tool, half use_tools mode)# judge.yaml — how scoring / rubrics evolve
strategy: hote # hote = generate new rubrics live + evolving rubric buffer; none = static rubrics only
use_tools: false # reserved config hook (judge does not call tools in deep_research today)# tools.yaml — tools that interact with the data source
backend: mcp # maps to --tools
mcp_tool_names: [google_search, snippet_search, browse_webpage]
mcp_parser_name: v20250824
max_tool_calls: 10
# mcp_host: 172.16.10.157 # optional: address of your deployed MCP server
# mcp_port: 8200 # if omitted, falls back to MCP_TRANSPORT_HOST / MCP_TRANSPORT_PORT env varsBuilt-in evolution strategies:
| Role | strategy | Meaning |
|---|---|---|
| proposer | none |
no evolution: no synthetic questions, no query-model training, original dataset only |
| proposer | hote |
a separate query model generates synthetic questions online and is trained; use_tools controls tools, rl_method can be set independently |
| solver | hote |
the answering model is trained; supports all RL algorithms + basic hybrid; use_tools controls tools |
| judge | none |
no evolution: score against static / predefined rubrics only |
| judge | hote |
generate new rubrics live + evolving rubric buffer |
The only difference between the real_tool and no_tool configs is the use_tools of proposer/solver (true → real_tool stage, false → no_tool stage); everything else is identical.
An evolution strategy is a function fn(ctx: BuildContext, role: RoleConfig) -> None that sets training-engine flags by mutating ctx.args (a flag→value dict):
# my_pkg/my_strategies.py
from open_instruct.evolve_framework import register_evolution_strategy
def proposer_generate_only(ctx, role):
"""Generate questions but do not train the query model."""
ctx.args["apply_evolutionary_queries"] = "true"
ctx.args["train_query_generator"] = "false"
stage = "real_tool" if role.use_tools else "no_tool"
ctx.args["query_gen_training_stage"] = stage
ctx.args["query_model_name_or_path"] = ctx.run.models["proposer"]
ctx.args["query_num_learners_per_node"] = ctx.resources.query_num_learners_per_node
ctx.args["query_vllm_num_engines"] = ctx.resources.query_vllm_num_engines
ctx.meta["query_gen_training_stage"] = stage # used for output_dir naming
register_evolution_strategy("proposer", "generate_only", proposer_generate_only)BuildContext fields: ctx.run (RunConfig), ctx.task (current Task), ctx.args (output flag dict), ctx.resources (GPU/engine counts), ctx.meta (cross-strategy values; training_stage/query_gen_training_stage/rl_method/hybrid_mode are used to derive output_dir). Read role config via role.use_tools and role.opt("key", default).
Reference it from YAML (the package.module:function form is auto-imported, no manual registration needed):
# proposer.yaml
strategy: my_pkg.my_strategies:proposer_generate_only
use_tools: trueA new task = a Task subclass that contributes three groups of parameters, then register it. No changes to the framework or training engine.
# open_instruct/tasks/my_task/task.py
from open_instruct.evolve_framework.config import FLAG, ArgDict, ToolConfig
from open_instruct.tasks.base import Task
class MyTask(Task):
name = "my_task"
def base_args(self, run) -> ArgDict:
# task constants: dataset, system prompts, generic hyperparams
# (do NOT set role knobs / model paths / resources / output_dir)
return {
"dataset_mixer_list": ["<dataset_path>", "1.0"],
"dataset_mixer_list_splits": "train",
"system_prompt_file": "<prompt.yaml>",
"no_tool_system_prompt_file": "<no_tool_prompt.yaml>",
"learning_rate": "5e-7", "beta": "0.001", "response_length": "16384",
"apply_verifiable_reward": "true", "gradient_checkpointing": FLAG,
# ... other generic hyperparams
}
def tool_args(self, tools: ToolConfig) -> ArgDict:
# translate tools.yaml into the engine's tool flags
return {"tools": tools.backend or "mcp", ...}
def judge_args(self, evolve: bool, use_tools: bool) -> ArgDict:
# judge:hote -> evolve=True; judge:none -> evolve=False (disable rubric evolution)
return {"use_rubric_buffer": "true" if evolve else "false", ...}Register it (in open_instruct/tasks/__init__.py):
from .my_task.task import MyTask
register_task(MyTask())Then set task: my_task in run.yaml. Put the task's default run/proposer/solver/judge/tools YAMLs under open_instruct/tasks/my_task/configs/, mirroring deep_research/configs/.
Tools work at two levels:
-
Add a new tool backend: register the tool class in the engine's tool registry via
register_tool(name, "package.module:Class")inopen_instruct/tool_utils/tool_actor.py, then setbackend: <name>intools.yamland emit the backend's flags from the task'stool_args. -
Add a sub-tool to the MCP backend (e.g. a new search/browse tool): register it in
MCP_TOOL_REGISTRYinopen_instruct/search_utils/mcp_tools.py, then add its name tomcp_tool_namesintools.yaml:backend: mcp mcp_tool_names: [google_search, snippet_search, browse_webpage, <your_tool>]
Whether each role actually calls the tools is controlled by its own use_tools (only true connects to the data source).