Agent Runtime 与评测流水线
状态:已实现;本文保留初始架构说明,最终实验入口与结果见 文档导航 和 实验报告 对应研究设计:proposal.md 当前范围:ShopSimulator single-turn / personalization、随机轨迹采样、离线 trace 分析、Skill 接入和 checkpoint 评测
1. 结论与现状判断
ShopSimulator/shop_env 已经提供了稳定的环境边界:租约式 session、observation_state、显式 action-limit termination、论文奖励分量和稳定 task split。这个边界应继续作为唯一环境真相,不应在实验代码中复制页面状态机、动作解析或奖励逻辑。
应弃用的是 ShopSimulator/single_eval 的 runner,而不是环境。旧 runner 的主要问题是:
- 模型 API、Agent 状态机、环境客户端、并发、重试和 JSON 落盘耦合在一个文件;
- 用
task_nums + range(...)推导任务,无法正确表达带空洞的稳定 task ID; - 每个 task 只保存最终 conversation,缺少逐步模型元数据、canonical observation、无效动作原因、终止原因和采样 provenance;
- 失败被打印后吞掉,不形成可重试、可统计的失败 artifact;
- prompt、persona 和未来 skill 注入没有独立版本边界;
- checkpoint 评测不能保证使用完全相同的冻结任务样本;
- 随机采样脚本和正式评测各自维护一套 rollout 逻辑,后续容易产生口径漂移。
因此新代码放在根目录独立 shopsimrl 包中,只通过 HTTP 使用 ShopSimulator,不继续修改当前已调好的环境,也不依赖 single_eval。
2. 设计目标与非目标
2.1 设计目标
- 一条 rollout pipeline,多种实验用途。 Teacher 随机采样、base/SFT 诊断、val 评测和 checkpoint 对比只改变配置,不复制 runtime。
- trace 是核心研究数据。 一条 episode artifact 应足够支持重放审计、错误分类、SFT 候选筛选、skill 构造和指标重算。
- 环境、模型、prompt、skill 可替换。 Agent 状态机不感知具体 API 服务商、checkpoint 或 skill 检索算法。
- 默认可复现、可恢复。 任务采样、每轨迹 seed、manifest、版本与完整性判断都有稳定规则;中断后只跳过真正完成的 episode。
- 评测公平。 每个 checkpoint 单独运行;使用相同 split manifest、seed、采样参数和 prompt hash 即可重建完全相同的 job 列表。
- 对未来训练友好。 核心协议保持轻量,后续可以增加 SGLang/slime adapter,而不改变 trace 和评测口径。
2.2 当前非目标
- 不在基础设施阶段冻结 skill taxonomy、检索算法、context 模板或 curriculum schedule;
- 不重写 ShopSimulator 的动作 parser、页面状态、search 或 reward;
- 不把 LLM-as-a-Judge 混入在线 Agent loop;Judge 应是消费冻结 trace 的独立离线阶段;
- 不在当前版本实现多轮 Shopper simulator;
- 不让最终 test 结果驱动 prompt、skill 或 checkpoint 选择。项目 split manifest 已冻结;最终 test 的访问保护仍需在正式实验流程中落实。
3. 总体架构
frozen task split ──> deterministic jobs ──> Evaluator / resume / concurrency
│
▼
AgentRuntime (one episode)
┌─────────┼──────────┐
▼ ▼ ▼
ChatModel SkillProvider ShopEnvironment
│ │ │
└─────────┼──────────┘
▼
versioned episode trace
│
┌───────────────────┼────────────────────┐
▼ ▼ ▼
metric summary trace diagnosis SFT / Skill build
每个 AgentRuntime 只负责一个 episode,并拥有一个 model client 和一个 environment lease。Evaluator 只负责 job 调度、并发、断点续跑和落盘,不参与 Agent 决策。这样可以单测状态机,也可以独立替换模型或环境 adapter。
Bare validation traces 的复用
Cold-start Gate A 与训练 online gate 统一采用 \(R_i-B_i=C_i^\top\beta+\epsilon_i\) 的无截距 OLS。通用 evaluator 在 skills.path: null 时产生的完整 bare val run,可通过 gate 的 bare_run_dir 复用;其 summary 继续报告原始 skill-free validation score,gate 只增加 masked rollouts,差值只用于回归。
普通模型配置新增可选 model.checkpoint_id,进入 provenance/fingerprint 而不发送给 API;训练 online gate 要求显式设置它,以免同一服务地址换权重后误用旧基线。详细配对检查和输出约定见 paired_validation.md。
4. 目录规划
shopsimrl/
schemas.py # EpisodeJob、ModelOutput、Skill 和 schema/version helpers
model.py # ChatModel 协议与 OpenAI-compatible HTTP adapter
environment.py # ShopEnvironment 协议与 ShopSimulator HTTP adapter
prompts.py # persona / skill / observation 的版本化 prompt 组装
skills.py # SkillProvider 协议、NoSkills、最小 JSON SkillBank adapter
runtime.py # 单 episode Agent 状态机和完整 trace 生产
tasks.py # 显式 task ID / legacy range split 加载
store.py # atomic JSON、run manifest、trace store 和 resume
evaluation.py # 固定采样、并发执行、聚合指标
paired_validation.py # cold-start/online 共用的 bare 配对校验与逐任务 delta
config.py # YAML 实验配置与路径解析
cli.py # plan / run / summarize
configs/
prompts/persona_single_turn.txt
qwen35_4b_val.yaml
qwen35_4b_test.yaml
archive/ # SiliconFlow 0830 与未使用模板
docs/
runtime_eval.md
tests/
test_runtime.py
test_evaluation.py
test_tasks_and_skills.py
以下为早期可选目录规划;当前训练与分析已经分别实现在 slime_runtime.py、training_analysis.py、online_validation.py 等平铺模块中,不需要创建这些目录即可运行:
shopsimrl/analysis/ # 规则标签、错误 taxonomy、trace 查询与导出
shopsimrl/judge/ # Judge rubric、批处理、校准和原始输出
shopsimrl/skill_build/ # candidate 提取、去重、泄漏检测、人工审核
shopsimrl/training/ # slime/SGLang rollout adapter、RL sample 转换
这些后续模块只消费已版本化 trace 或实现现有协议,不回写环境。
5. 稳定协议
5.1 EpisodeJob
一个 job 由四项唯一确定:
{
"task_id": 1234,
"sample_id": 0,
"seed": 287413091,
"split": "train"
}
episode_id = task-{task_id}__sample-{sample_id}。sample_id 表示同一任务的第几次独立采样,不能用随机 UUID 替代,否则断点续跑和跨 checkpoint 对齐会变得困难。episode seed 由实验 seed、task ID 和 sample ID 稳定哈希得到。
5.2 ChatModel
输入是标准 OpenAI message 列表、当前页面动态 function tools 和 episode step seed;输出统一为:
- 标准
tool_calls(每轮恰好一个)及解析后的 JSON arguments; - 服务商单独返回的 thinking
reasoning_content; - 可选的最终
content,动作执行不再从文本中解析; finish_reason、token usage、response ID、延迟和少量非敏感元数据。
runtime 根据 observation_state.search_available/actions 动态暴露 search(query) 和 click(value);click 参数使用当前可点击值的 enum。assistant tool call 原样进入 conversation,环境 observation 以匹配 tool_call_id 的 role=tool 消息返回。文本 Thought/Action 不作为兼容路径。
API key 只从配置指定的环境变量读取,永不进入 manifest 或 trace。当前 HTTP adapter 支持 OpenAI、vLLM、SGLang 和 SiliconFlow 风格 endpoint,并允许通过 extra_body 传入 enable_thinking 等服务商参数。远程 API 可保留 trust_env=true 使用系统代理;本地 vLLM/SGLang endpoint 应设为 false,避免 localhost 请求被代理截获。
后续若训练 rollout 不经过 HTTP,只需实现相同 ChatModel.generate() 语义,或增加异步等价协议;Agent loop 不需要改写。
5.3 ShopEnvironment
环境协议只有:
reset(task_id) -> reset payload
step(canonical_action) -> step payload
terminate(reason) -> terminal payload
close() -> release lease
新 adapter 强制检查 canonical observation_state 和 observation version,reset 永远传 include_private_goal=false。动作解析、是否 done 和 reward 完全服从环境返回;runtime 不添加 finish、错误购买惩罚或自定义成功规则。
5.4 SkillProvider
SkillProvider.select(public_episode_context) 返回零个或多个带 ID、版本、正文和 metadata 的 skill。public context 只包含 task instruction、persona、初始 observation state 和 job 信息,不包含私有 goal。
当前 JsonSkillBank 只是用于接口验证和可复现小实验的最小 adapter:支持 global skill 和显式 task-scoped skill。它不是最终 retrieval 设计。最终的 embedding/router/LLM selector 只需实现同一协议,并在 identity() 中记录索引、模型、prompt 和 bank snapshot 哈希。
6. Trace 规范
同一 run 的轨迹逐条追加到 JSONL:
runs/<experiment>/traces.jsonl
shopsimrl-episode-v4 至少包含:
job:task、sample、seed、split;status:completed或failed;provenance:model、sampling、environment/observation version、prompt hash、SkillBank hash、runtime action limit;reset:公开任务、persona、初始 observation/state,不保存 lease,也防御性移除私有 goal;selected_skills:实际注入内容、ID、版本、content hash 和选择 metadata;conversation:模型真实看到的 system/user/assistant/tool 消息和 tool-call ID;steps:每步输入 observation、动态 tool schema、thinking、结构化 tool call、转换后的 canonical action、usage/延迟、完整环境结果和action_feedback;final:reward、reward detail、purchase、goal、termination reason;error:失败 stage、异常类型、信息和 traceback;- UTC 时间与 episode 总耗时。
每个 episode 完成后由调度主线程立即 append、flush 和 fsync。恢复时逐行读取,以同一 episode_id 的最后一条合法记录为准;只有 schema 正确、status=completed 且 final.done=true 的 episode 才会跳过。失败 episode 会在重跑时追加新记录,损坏或中断的尾行不会被当作完成结果。
Trace 保留模型 reasoning 是为了研究诊断;对外发布、Judge 输入或 SFT 转换时必须走显式 exporter,不能默认把 reasoning、终局私有 goal 或 persona 全量外发。
模型调用的重试边界按责任划分:网络异常、限流和可重试 HTTP 服务错误由模型 adapter 透明重试;HTTP 成功但没有工具调用、调用数量错误、工具名或 arguments 格式错误时,不再重采样。runtime 将原始响应、protocol_error 和结构化 protocol_feedback 写入当前 step,该 step 消耗一次 action budget,然后在同一页面让模型纠错。能解析为合法函数参数、但 click.value 不在动态 enum 中的动作仍交给环境判定并返回标准 action_feedback。
协议错误 step 的 action 和 environment 为 null。summary 分开统计 model_steps、protocol_errors 和实际送入环境的 total_actions,避免把模型协议错误混作环境无效动作。
当模型正常返回 finish_reason=length,但尚未产生唯一有效工具调用时,这属于有效的模型策略失败而非基础设施错误。runtime 保留 reasoning 和原始响应,记录 policy_failure.code=generation_length,立即请求环境以 generation_length 终止并生成标准零分 reward。该轨迹进入训练和评测,不重采样,也不追加协议纠错轮次。
7. Run manifest 与 artifact 布局
runs/<experiment>/
manifest.json
summary.json
traces.jsonl
manifest 冻结所有影响语义的内容:具体 job 列表、episode seed、task split 版本、model 与 sampling 参数、environment mode、prompt hash、skill snapshot hash 和 action limit。concurrency、输出位置和 resume 是执行参数,不影响语义 fingerprint,因此可在续跑时调整。
若同一 <experiment> 的语义 fingerprint 变化,程序拒绝覆盖,要求换新 experiment 名。这可以避免“同一结果目录混入两套 model/prompt/temperature/task”的常见问题。
8. 三类主要工作流
8.1 Teacher 随机采样
使用项目冻结 manifest 中的 train、设置 sample_size、repeats、较高 temperature 和 Teacher 模型即可。成功与失败 trace 使用完全相同的 schema,采样阶段不按 reward 丢数据。
python -m shopsimrl.cli plan configs\teacher_sample.yaml
python -m shopsimrl.cli run configs\teacher_sample.yaml
后续 SFT exporter 从这些 trace 中筛选 completed + r_success=1,再做语义质量、重复率和覆盖检查;不能让在线 sampler 同时承担筛选逻辑。
8.2 Trace 分析与 Skill 构造
分析阶段只读取冻结 trace,建议依次产生:
episode_labels.jsonl:规则标签、阶段错误、无效动作、检索/规格/预算诊断;judge_outputs.jsonl:Judge 原始输出、rubric/version、解析结果;skill_candidates.jsonl:候选正文、正反 evidence episode/step refs、生成方法;skillbank.snapshot.json:去重、泄漏检测和人工审核后的不可变快照。
每个 skill 至少应可追溯到 evidence refs,并记录 builder model/prompt、去重簇、审核状态和泄漏检查结果。最终 schema 和 taxonomy 等轨迹分析后再冻结;runtime 已经只依赖最小 Skill 对象,不会阻塞该研究选择。
8.3 单 checkpoint 固定评测
一份 YAML 只描述一个 checkpoint 和一个 split。每得到一个 base、SFT、GRPO 或训练 step checkpoint,就复制配置、修改 experiment.name 与 model 后单独运行。只要 split manifest、seed、sample_size、repeats 和 prompt/skill 配置不变,job 列表便完全一致;跨 checkpoint 对比由后续分析读取各自 summary.json 或 traces.jsonl 完成,不在在线 runner 中生成 comparison artifact。
主要报告:
- coverage 与失败数,防止静默排除 API/环境失败;
reward/r_strict、r_loose、r_success和所有r_*分量;- 平均步数、无效动作率、终止原因;
- token 和 wall-clock 成本;
- reward mean 的 SEM,用于快速判断波动,正式报告再按 task 对齐做 bootstrap/paired test。
最终 test 配置必须 skills.path=null。checkpoint 选择只能使用 train/val 结果;选定 checkpoint 和配置 fingerprint 后再单次运行锁定 test。
9. 配置原则
configs/qwen35_4b_val.yaml 与 configs/qwen35_4b_test.yaml 是当前可直接运行的本地协议配置。0830 SiliconFlow 的 train 采集配置在 configs/archive/qwen35_4b_train.yaml。其他模型或 checkpoint 从最接近用途的文件复制后修改:
- 随机采样:
split=train、sample_size=N、repeats=K、Teacher model、非零 temperature; - 固定评测:
split=val/test、固定 temperature、单个 checkpoint、通常repeats=1。
当前 Qwen3.5-4B 配置统一使用 thinking mode 和标准 function tool calling,只以 max_tokens 限制单轮生成,不设置独立 thinking budget。本地评测为 temperature 0.6、top_p=1.0、无 top_k。prompt 不要求输出可见 Thought 或文本 Action,thinking 与 tool call 分字段进入 trace。
展示用 configs/ 评测配置为 max_tokens=4096。历史 SiliconFlow 0830 配置(configs/archive/)为 2048 / top_p=0.95 / top_k=20,不能与本地 4096 数字混画。
先运行 plan。它只验证 YAML、split、persona 一致性、SkillBank 和确定性抽样,不调用模型或环境 API。确认 episode 数、前几个 task ID、prompt/skill hash 后再运行 run。
10. 与训练框架的边界
评测 runtime 不应直接依赖 slime 的内部 sample 类型。建议增加双向纯转换层:
shopsimrl episode/job <-> slime rollout request/sample
shopsimrl trace <-> SFT/GRPO training record
训练 adapter 应继续复用:
- 相同的 prompt builder 和 skill renderer;
- 相同的 ShopEnvironment 语义;
- 相同 episode/step trace 字段;
- 相同 reward 与 termination 口径。
训练框架特有字段(token IDs、loss mask、old logprob、advantages)放在独立 training namespace,不污染通用评测 trace。这样未来更换 slime 版本不会迫使我们迁移历史轨迹。
11. 迁移方案
- 删除
ShopSimulator/single_eval、multi_eval及其历史输出,不保留兼容入口; - 所有后续采样和评测统一使用 YAML pipeline 与
scripts/run_shopsimrl.py; - 先用 fake runtime 单测,再用环境 smoke endpoint 跑 1–3 个 task;
- 用环境契约测试固定 task input、动作数、reward detail 和终止原因;
- 确认一致后生产 Teacher 轨迹,并冻结首个 trace schema;
- 轨迹分析完成后再实现
analysis/、skill_build/和最终 SkillBank selector; - 最后增加 slime rollout adapter,避免基础评测和训练框架同时快速变化。
12. 当前已实现与下一阶段
本次已经实现:
- 独立的 runtime/model/environment/prompt/skill 协议;
- OpenAI-compatible 模型调用与有限重试;
- canonical ShopSimulator HTTP adapter 和可靠 lease 清理;
- 确定性随机采样、同 task 多轨迹、并发和断点续跑;
- 完整 episode trace、run manifest 和 summary;
- skill-free 与最小 JSON SkillBank 路径;
plan / run / summarizeCLI、示例配置和 fake 端到端单测。
项目数据划分已冻结为 persona_splits.v1.json:train=3726、val=400、test=400,并按商品、指令、persona 和用户标识的精确关联分组,避免关联样本跨 split。
建议下一阶段按以下顺序推进:
- 用本地环境和一个可控模型 endpoint 完成 3-task integration smoke test;
- 在正式实验入口增加 split role/test access guard,避免开发阶段误用最终 test;
- 为现有 Teacher 轨迹写 converter 或直接重新采样少量数据,验证 trace analysis 字段;
- 实现规则型
analysis首版和 SFT exporter; - base/SFT 诊断后再冻结 skill candidate 与 SkillBank schema;
- 接入 slime rollout,并用相同 checkpoint 在离线 eval 与训练侧抽样做口径对齐。