本轮先完成 Agent Rollout Adapters and Harnesses:以 generate() 为书签,沿一份任务进入各组件,读清职责和输入输出后返回主线。第一遍走通正常路径,再补一条失败清理路径;实现细节按问题继续深入。
源码固定在 22a89f96835969a47055a659c588f46151f8b07a。本篇选择 Claude Code Harness / Anthropic Adapter,与已有 4.1 图文一致;Codex/OpenAI 留作完整链读通后的实现对照。本轮已核对:这些核心源码与旧 8c17b676 资料版本没有差异,原有动作讲解可以继续使用。
为什么从 generate 进入
CodeWiki 原图展示组件关系;本篇按任务执行读它们的组装和交接。_AdapterService 在示例的 generate.py 中,负责准备图内的 Adapter/模型连接和 HTTP 服务。它不是单轮请求处理,也不是 Harness 的启动逻辑。先扫 generate 骨架,到 state 初始化处下钻,再回外层继续;上游训练、调度和模型服务部署先视为已提供的前提。
动作 4 的前置阅读包括调用方的 Adapter、任务、Session、环境和题目准备;Launch Agent 本体继续进入 run 内部的用户、配置、命令与启动。两层串起来阅读,职责仍各自保留。
先把整体顺序看清
generate()
prepare adapter + task metadata + session
with agent sandbox:
prepare workspace
await Harness.run()
CLI runs and requests the model
HTTP -> translate -> generate -> reply + record
tools -> another request -> ...
collect diff
evaluate diff in a fresh sandbox
return eval result OR export training samples
finally: drop session
generate() 组织的是整次任务,_run_turn() 组织一次模型请求。外层等待 Harness 时,CLI 在独立环境中工作,Adapter 的 HTTP 服务也在接收请求。 CLI 内的工具调用与模型请求可能重复多轮;等 CLI 整体结束,外层才继续收集 diff。Sandbox 的操作贯穿安装、题目准备、CLI 运行与 diff 收集。
一份用于贯穿阅读的任务
下面只是假设的阅读参数,不是运行记录:规范化任务信息包含 instance_id=demo、image=demo-image、workdir=/workspace/demo,题目是“修复一个函数并通过测试”。样本 index=0、group_index=0,没有已有 Session;代码据此构造 cagent-demo-0-0。假设 Adapter 地址为 http://192.0.2.10:18001,SGLang 地址为 http://192.0.2.20:30000。
CLI 使用 Adapter 地址;Adapter 使用 SGLang 地址。题目、环境、会话、生成结果、diff 与 reward 会在不同层交接,不必一次理解所有字段。
连续阅读顺序
| 步骤 | 主线停在哪里 | 下钻职责 | 看完回到哪里 | 原有图文 |
|---|---|---|---|---|
| 1 | generate 准备任务与 Session | 模型入口、任务元数据、会话注册 | generate 的任务预算/环境入口 | Launch Agent 的参数来源 |
| 2 | 进入 Agent 沙箱上下文 | 创建环境、安装 CLI、准备题目和工作区 | generate 的 Harness.run 调用 | Manage Sandbox |
| 3 | await Harness.run,暂时停在等待 | 用户与配置、CLI 命令/环境、后台进程与退出标记 | 留在 CLI 运行期间,转看请求端 | Launch Agent → Exec Commands |
| 4 | 外层仍等待 CLI | CLI 回连 Adapter、HTTP 路由、Session 身份 | _run_turn 的输入处理 | Make API Calls |
| 5 | _run_turn 处理模型输入 | 协议转换、模板 token、采样预算、SGLang 生成 | _run_turn 接回 TurnRecord | Translate & Forward → Generate Tokens |
| 6 | _run_turn 接回生成记录 | 解码、解析、协议响应、本轮轨迹 | HTTP 结果回 CLI,重复 4–6 或结束 | 3.5 完成一次模型请求 |
| 7 | CLI 结束后回 generate | 收集 diff、退出工作环境、独立评分 | generate 的模式分支 | 7.1 任务组装,先只看收尾 |
| 8 | generate 返回结果并退出 | 评测结果或训练轨迹导出、Session 清理 | generate 结束,完成一次任务 | 会话结束与轨迹导出 |
原图动作编号用于定位职责。沿上述主线读:任务准备 → 5 创建与准备环境 → 4 组织启动(继续使用 5 的环境操作)→ 6 CLI 执行与等待 → 7 回连 → 2/3/3.5 请求循环 → 回到 generate 收尾。步骤 7、8 只补齐 4.1 组件如何接回外层,不在这里展开全部 SWE 数据集与训练框架。
1.准备 Adapter、任务和 Session
从主线进入: generate(examples/coding_agent_rl/generate.py:182) 的第 184 行调用 _AdapterService(examples/coding_agent_rl/generate.py:137);随后 get_metadata(examples/coding_agent_rl/swe.py:77) 从样本中整理任务信息。
_AdapterService 准备 tokenizer 与 Adapter,并通过 run_app_in_thread(slime/agent/aiohttp_threaded.py:46) 在后台线程的事件循环中运行 HTTP 服务,得到 adapter_url。因此后面等待 CLI 时,模型请求仍有处理入口。第一遍定位启动与运行位置即可,线程实现无需逐行展开。
任务信息检查后,_session_id(examples/coding_agent_rl/generate.py:308) 决定 sid,BaseAdapter.open_session(slime/agent/adapters/common.py:210) 注册默认采样参数与上下文预算:
session_id = base_sample.session_id = _session_id(base_sample, instance_id)
state.adapter.open_session(
session_id,
sampling_defaults=sampling_params,
max_context_tokens=state.max_context_len,
)
交接: args/base_sample/sampling_params → 可用 Adapter 地址、规范化 md、sid 与 Session 默认设置。返回主线: generate:202–203,准备进入任务预算和沙箱上下文。
第一遍停止标准:知道模型入口和会话身份从哪来,能说明为什么一次模型请求可以找到这次任务。这里的准备在外层 try 前;不要把它的所有失败都当作已进入后面的统一清理分支。
2.创建执行环境,准备工作区
从主线进入: generate:203 → boot_agent_sandbox(examples/coding_agent_rl/generate.py:96);环境交出后,generate:204 → prepare_workspace(examples/coding_agent_rl/swe.py:177)。
async with boot_agent_sandbox(md["image"], instance_id) as sb:
await swe.prepare_workspace(sb, md["workdir"], md)
agent_exit_code = await HARNESS_CLS().run(
sb,
workdir=md["workdir"],
session_id=session_id,
adapter_url=state.adapter_url,
time_budget_sec=CONFIG.agent_time_budget_sec,
prompt=swe.SWE_PROMPT,
)
diff_text = await swe.git_diff(sb, md["workdir"])
先看 Sandbox(slime/agent/sandbox.py:28) 的命令、文件和上下文操作合同,再看 E2BSandbox.aenter(slime/agent/sandbox.py:281)。构造包装对象尚未创建远端环境;__aenter__ 才执行创建。随后安装所选 Harness 的 CLI。prepare_workspace 准备用户权限、数据集需要的命令和 PROBLEM_STATEMENT.md。
交接: 镜像、目录、题目与 sb → 已有工具链和题目材料的工作区。返回主线: generate:205 的 await HARNESS_CLS().run(...)。
本步第一遍只走创建、安装、文件/命令操作与退出位置;启动重试和 E2B SDK 内部后读。这个 async with 一直包住后面的 Agent 运行和 diff 收集。
3.Harness 配置并启动 CLI,然后等待
从主线进入: generate:205 → BaseHarness.run(slime/agent/harness/common.py:81)。它先确保 Agent 用户,建立 HarnessContext,再委派所选子类的配置与启动。
进入 ClaudeCodeHarness.write_config(slime/agent/harness/claude_code.py:44) 看 CLI 配置,进入 ClaudeCodeHarness.launch_and_wait(slime/agent/harness/claude_code.py:57) 看命令与模型地址/会话身份:
env = {
"ANTHROPIC_BASE_URL": ctx.adapter_url,
"ANTHROPIC_AUTH_TOKEN": ctx.session_id,
"ANTHROPIC_MODEL": ctx.model_label,
**self.static_env,
}
extra_envs = os.environ.get(self.extra_envs_env, "").strip()
if extra_envs:
env.update(json.loads(extra_envs))
return await run_agent(sb, workdir=ctx.workdir, start_cmd=cmd, env=env, time_budget_sec=time_budget_sec)
再进入 run_agent(slime/agent/harness/common.py:107) → exec_and_wait(slime/agent/sandbox.py:82) → _await_done_marker(slime/agent/sandbox.py:65):命令在环境中后台启动,输出写入文件,完成时写退出标记;等待端读取标记得到退出码。模型 token/logprob 的训练轨迹由 Adapter 保存,CLI 输出文件不代替它。
交接: sb/workdir/sid/adapter_url/prompt/budget → CLI 配置、命令、环境变量、进程与完成标记。最终输出是 exit_code。
此时先停在等待处。 Harness.run() 尚未返回,generate() 尚未到 diff。为了理解等待期间发生什么,接下来进入 CLI 发起的模型请求。配套:动作 4、动作 5、动作 6。
4.CLI 的请求进入 Adapter,找回 Session
CLI 用 ANTHROPIC_BASE_URL 回连 Adapter,用会话凭据标识此次任务。Slime 可核对配置与 HTTP 接收端;CLI 怎样完整组织请求、选择工具、执行命令和编辑文件,属于外部实现。
HTTP 请求到 POST /v1/messages,由 AnthropicAdapter._register_routes(slime/agent/adapters/anthropic.py:48) 接到共享处理函数:
def _register_routes(self, app: web.Application) -> None:
app.router.add_post("/v1/messages", self._run_turn)
app.router.add_post("/v1/messages/count_tokens", _count_tokens)
进入 BaseAdapter._run_turn(slime/agent/adapters/common.py:318) 的开头,读 JSON、预处理 body、解析 sid、检查会话与轮数门禁,并把当前 handler 加入 inflight。sid 关联到步骤 1 已注册的 Session。
交接: CLI 的消息历史、工具定义、参数与身份 → handler 持有 body/sid/Session。返回位置: _run_turn:341 的 _translate(body);外层仍停在 generate:205。配套:动作 7。
5.翻译输入,向 SGLang 请求生成
沿同一函数继续:
translated, tools_schema = self._translate(body)
prompt_ids = _render_token_ids(translated, tok, tools=tools_schema, add_generation_prompt=True)
turn = await call_sglang_generate(prompt_ids, s, body, adapter=self, session_id=sid)
进入 AnthropicAdapter._translate(slime/agent/adapters/anthropic.py:58),沿 Anthropic 消息与工具转换各走一次;结果是规范化 chat messages 与 tools schema。回到 _run_turn,进入 _render_token_ids(slime/agent/adapters/common.py:58),由外部 tokenizer 的聊天模板编码得到 prompt_ids。
再进入 call_sglang_generate(slime/agent/adapters/common.py:442):读取 Session/body 的采样与上下文预算,将 input_ids、采样参数、rid、return_logprob=True 交给 SGLang /generate。收到完整 JSON 后提取真实输出 token/logprob 与结束原因,构造 TurnRecord(slime/agent/trajectory.py:29)。
交接: 协议消息/tools → prompt_ids → SGLang 请求 → 含 prompt_ids/output_ids/output_log_probs/finish_reason 的 TurnRecord。返回位置: _run_turn:344 后的解码与解析。
第一遍到“token、logprob 和结束原因从哪里来”就够;tokenizer 与 SGLang 服务内部留给后续。此路径先取得完整生成结果,再包装客户端协议响应。配套:动作 2、动作 3。
6.组织响应,记录这一轮,然后回到 CLI
进入 parse_model_output(slime/agent/parsing.py:25),看输出文本怎样拆成 reasoning、可见 text、tool uses 和格式状态;再看 AnthropicAdapter._build_reply(slime/agent/adapters/anthropic.py:63),将解析结果组织成客户端回复与规范化助手消息。
本轮的实际调用顺序为:
decode -> parse -> build_reply
-> _respond
-> record_turn
-> finally: discard inflight task
-> handler returns response to aiohttp
AnthropicAdapter._respond(slime/agent/adapters/anthropic.py:71) 位于 _run_turn:363,TrajectoryManager.record_turn(slime/agent/trajectory.py:283) 位于调用处 _run_turn:384,:391 的 return 先确定返回值,随后 finally:393 移除 inflight,handler 才将 response 交回 aiohttp。记录输入是 sid、本轮 token 记录、输入消息与助手消息;副作用是更新会话轨迹树。
这里说的是函数调用顺序:非流式分支先构造 JSON response,由 HTTP 框架完成发送;流式分支写协议事件。不能把所有分支都表述成 _respond 返回时客户端已收到全部字节。记录中的 TurnRecord.finish_reason 仍来自 SGLang,不能用协议 Reply 的结束原因替代。
交接: 生成记录 → CLI 协议响应 + 会话轨迹更新。返回位置: CLI 可以执行工具、把工具结果加入下一次请求,重复步骤 4–6;这一轮 HTTP 请求结束并不表示整次任务结束。第一遍在 record_turn 内只追正常追加历史,消息重写、分叉和漂移细节后置。
配套:3.5 完成一次模型请求。等 CLI 最终退出,等待端得到退出码,沿 Harness 返回外层 generate:213。
7.回到 generate,收集 diff 并独立评分
从主线进入: generate:213 → git_diff(examples/coding_agent_rl/swe.py:230),从 Agent 工作区收集代码改动。离开 Agent 沙箱上下文后,generate:215 → run_evaluation(examples/coding_agent_rl/swe.py:239):
diff_text = await swe.git_diff(sb, md["workdir"])
reward, applied_cleanly = await swe.run_evaluation(
md,
diff_text=diff_text,
timeout_sec=CONFIG.eval_timeout_sec,
)
第一遍选 _grade_scaleswe(examples/coding_agent_rl/swe.py:253) 的 eval_cmd 分支:在一个新的评估环境中准备基线、应用 diff,再执行评测命令,得到 reward/applied_cleanly。工作环境与评分环境分别退出各自上下文。
交接: Agent 工作区 → diff;任务元数据 + diff + 评分预算 → 独立评分结果。返回主线: generate:220 的模式分支。
CLI 退出码描述进程状态,reward 描述任务评测结果;非零退出不直接等于评分失败。第一遍只走一条评分路径,把组件接回任务;SWE-bench 与全部数据集准备细节以后按需读。
8.导出结果,再清理 Session
现在沿 generate 分清两个返回:
- evaluation 模式: _eval_result(
examples/coding_agent_rl/generate.py:336) 返回用于结果统计的评测 Sample,不走训练轨迹导出。 - 训练模式:
generate:237调 BaseAdapter.finish_session(slime/agent/adapters/common.py:245);它关闭会话并等待在途请求,调用 TrajectoryManager.get_trajectory(slime/agent/trajectory.py:307) 得到 Sample 列表,再解码训练尾部填充response。
轨迹导出第一遍走一条正常、没有 token 漂移的多轮链:TrajectoryManager._chain_to_samples(slime/agent/trajectory.py:479) → TrajectoryManager._split_chain_into_builders(slime/agent/trajectory.py:456) → _SampleBuilder.append_turn(slime/agent/trajectory.py:193) → _SampleBuilder.to_sample(slime/agent/trajectory.py:234)。解释完整 tokens、response_length、loss_mask、rollout_log_probs 如何对齐即可。后续工具/上下文 token 的 mask 与模型实际生成 token 的 mask 不同;每份输出 Sample 当前赋完整任务 reward。导出的 response 是多轮训练尾部,不一定只是最后一句回答。
最终返回主线: generate 确定返回值后,先执行 finally 的 BaseAdapter.drop_session(slime/agent/adapters/common.py:278)。它等待/取消在途请求,移除会话与轨迹,是 cleanup-only,不再次导出样本。 源码接着执行 await asyncio.sleep(10),完成这段等待后,函数才真正把结果返回调用方;阅读任务耗时时要把它计入。
第一遍停止标准:能区分一次 HTTP 请求结束、CLI 进程结束、Session 导出与清理、整次任务结束。环境退出调用只表示进行了回收尝试,实际远端回收成功还需要运行证据。
再补一条失败路径
用同一书签,假设步骤 2 的 prepare_workspace 在沙箱已交出后抛出 RuntimeError:环境上下文退出 → generate 的 except Exception 生成 ABORTED 结果 → finally 清理 Session。只要把异常怎样返回、谁做环境与会话清理说清,就完成第一条异常链。
随后按问题选读:等待 CLI 超时返回 -1 与环境回收、客户端取消与服务端 abort、上下文预算耗尽、轨迹导出为空。不要假设等待函数返回超时就已经终止 CLI。
什么时候算读通 4.1
拿上面的假设任务,连续回答以下问题:谁准备模型入口和 Session?谁创建环境、配置并启动 CLI?外层在哪等,模型请求怎样在等待期间发生?一轮请求怎样返回并记录?CLI 退出后 diff 和评分从哪里来?评测和训练返回有何不同?失败后环境与 Session 由谁处理?
配套先读 tests/test_agent/test_adapters.py(tests/test_agent/test_adapters.py:1) 中工具调用 → 工具结果 → 再次请求 → 导出的 fixture/断言,及 tests/test_agent/test_trajectory_manager_branching.py(tests/test_agent/test_trajectory_manager_branching.py:1) 中正常多轮链的断言。这里的文件/符号与调用关系是静态核对;本轮没有运行真实 CLI、E2B、SGLang、训练或源码仓库测试。
代码片段来自 THUDM/slime 的固定提交,许可见 Apache 2.0;片段为阅读上下文中的摘录,不是独立可执行脚本。