agno Environments 训练数据加载器(Trainer Loader):在 SFT JSONL 与训练器之间构建可验证的交接边界
agno Environments 训练数据加载器Trainer Loader在 SFT JSONL 与训练器之间构建可验证的交接边界【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno导读本文围绕 agno 仓库cookbook/environments/_12_trainer_loader/目录下的两个示例basic.py与validate_messages.py及其测试记录讲解如何把强化学习环境Environment中滚动rollout产生的对话数据导出为标准「对话式 SFT JSONL」并在训练器消费边界上完成加载与校验。读完本文你将掌握run_rollouts→learning_zone()→to_sft_jsonl的完整导出链路、可移植 SFT 行的精确格式约束、ExportReport各计数器的语义以及如何用校验脚本在把数据交给任何训练器适配器之前拦截非法行。背景数据从环境滚动到训练系统的必经之路在 agno 的 environments 模块中一次典型的训练数据生产流程由 runner.py 中的run_rollouts(env, *, k8, tasksNone, modelNone, concurrency4)驱动它接受一个Environment由agent、tasks、scorer组成定义于 environment.py对每个任务执行 k 次尝试再交给 scorer 判定通过/失败。但「生成了包含对话的文件」并不等于「可以开始训练」。_12_trainer_loader这一节正是把持住加载器边界loader boundary它只负责读取、验证、加载消息数组绝不启动任何训练作业。这一设计意图在该目录 README.md 中写得非常明确——These examples validate and load message rows only; they do not start a training job.对应的测试记录 TEST_LOG.md 也逐条给出了两个示例的 PASS 状态与实测结果。训练器加载器的定位用在哪、为什么这么用按 README.md 的指引这一组示例的使用时机是前置依赖先运行_11_export_provenance/完成带溯源provenance的导出再进入本节把生成的文件接到独立的训练系统上刻意忽略溯源边车加载器只消费文本 JSONL故意忽略path.meta.json溯源边车sidecar边车文件应归档用于审计而训练器只读取文本数据后续流程如果要做逐轮回归验证在生成下一批数据集前确认环境配置没有漂移继续前往_13_saved_baselines/。两个示例的分工文件职责basic.py导出通过的学习区learning zone尝试然后加载其messages数组validate_messages.py在把行交给任何训练器特定适配器之前强制校验可移植行结构row shape与允许的角色basic.py导出通过样本并加载消息数组bASIC.py 的完整流程如下定义结构化输出与计分器用 pydantic 定义Answer(BaseModel)value: int并用CodeScorer(exact_value)包装一个(run, expected) - bool的精确值比较函数。CodeScorer定义于 code.py接受任意(run, expected) - bool | float | Score形式的可调用对象。构造环境Environment(nametrainer-loader-basic, agentagent, tasks(...), scorerCodeScorer(exact_value))其中两个任务是纯算术推演题大数相乘 数字和 模运算expected分别为20944939与76998482。执行滚动run_rollouts(env, k6)即每个任务滚动 6 次。取出学习区result.learning_zone()是 runner.py 提供的过滤器只保留「既有通过尝试又有失败尝试」的任务——这是 SFT 数据生产的核心素材。导出并加载to_sft_jsonl(zone, output_path)写入 JSONL随后load_message_rows()逐行json.loads(line)[messages]取出消息数组并用断言len(message_rows) report.n_written保证「加载器收到的消息数组数 实际写入行数」最后打印loader received N message arrays并声明停在加载器边界、未发生训练。validate_messages.py可移植行的结构校验validate_messages.py 的核心是validate_sft_rows(path)函数它对每一行一个{messages: [...]}对象执行以下断言顶层键严格等于{messages}set(row) {messages}messages非空至少存在一条role user的消息最后一条消息必须是assistantrow[messages][-1][role] assistant每条消息的键严格等于{role, content}角色必须是system/user/assistant三者之一content必须是非空字符串isinstance(content, str) and content.strip()。该脚本的其余骨架与basic.py一致同样的Answer、CodeScorer、run_rollouts(env, k4)与to_sft_jsonl只是任务换成了product-a与product-c。这种「先断言、后交给适配器」的模式正是 README.md 所说enforce the portable row shape and allowed roles before handing rows to any trainer-specific adapter。可移植 SFT JSONL 格式为什么「少即是多」导出器实现位于 sft.py其文档字符串阐述了格式设计的关键哲学Tinker、Together、Fireworks 与 OpenAI 都接受{messages: [{role, content}]}这个核心结构。它们在两个轴上分叉——工具表示tool representation与损失加权loss weighting——而这个导出器两者都不输出因此文件是通过「省略」而非「翻译」实现可移植的。这意味着禁止添加多余键不要「好心」地加上tools、weight或trainable键。最严格的消费者做的是集合严格相等检查不是丢弃未知键任何一个多余键都会让整个文件被拒收——一个键的失误会让所有下游消费者同时失败。溯源放在边车文件分数与指纹fingerprint无处可放因此写入path.meta.json边车。边车含env_fingerprint、policy_fingerprint、report各跳过计数器、optionsonly_passed与lines逐行task_id/attempt_index/score溯源列表。消息构建规则_conversation_from()决定一条候选对话是否可导出规则如下消息来源是run.messages但剔除from_historyTrue的历史消息保留角色限于{system, user, assistant}且内容为非空字符串system 消息会被保留——它携带诱导模型输出格式的指令assistant 文本取最终一条 assistant 消息的原始Message.content字符串逐字节忠实于模型输出绝不序列化run.content在output_schema下它是 pydantic 模型其str()/model_dump_json()都会产生模型从未生成过的文本对话必须至少包含一条 user 消息否则返回None不可导出。分类与跳过逻辑_classify每条候选按顺序应用跳过检查首个命中即生效且每个计数器只计一次未评分score is None不算候选永远到不了导出器失败only_passedTrue 时计n_skipped_failed工具调用超限tool_call_limit_hit计n_skipped_limit_hit——「在拒绝工具调用之后得出的答案」被视为受胁迫的答案使用了工具run.tools 非空计n_skipped_tool_runs——交集格式没有工具表示只导出最终答案而不导出产生它的工具轨迹会教会模型「不用工具也能答」无可导出文本计n_skipped_no_text。ExportReport的全部字段为n_written、n_skipped_failed、n_skipped_tool_runs、n_skipped_limit_hit、n_skipped_no_text、n_dropped_over_cap。上限与确定性严格消费者的硬性上限定义在 _validate.pyMAX_CONVERSATIONS 320对应消费端BATCH_SIZE(8) * MAX_STEPS(40)MAX_DATASET_BYTES 1024 * 10241 MiBUTF-8 编码后字节数超限行按发射顺序从尾部丢弃并计数绝不静默截断发射顺序为「任务顺序、任务内尝试顺序」在任何并发下都确定写入时newline关闭平台换行翻译——在 Windows 上若被翻译成 CRLF会让恰好贴满字节上限的文件超限破坏 sha256 固定的确定性。官方校验器 validate_sft_jsonl_validate.py 还提供一个validate_sft_jsonl(path) - int函数它严格复刻了最严苛消费端的验收逻辑vendored Tinker acceptance check文件 ≤ 1 MiB、非空、按\n切分splitlines()会额外切分 U2028/U2029/U0085而json.dumps(ensure_asciiFalse)会原样发射这些字符故必须用split(\n)、行数 ≤ 320、每行是合法 JSON、顶层键严格等于{messages}、消息键严格等于{role, content}、角色白名单、内容非空字符串、至少一条 user、末条为 assistant。返回对话数遇到首个违规即抛ValueError。validate_messages.py中的断言集正是这一验收规则的 Python 直译。测试记录解读实测结果与校准过程TEST_LOG.md 记录了 2026-07-20 使用OpenAIResponses(idgpt-5.5, reasoning_effortlow)实测的结果basic.py — PASS行为导出通过的学习区尝试只加载其消息数组在任何训练操作之前停止结果product-a通过 3/60.50product-d通过 6/61.00加载器收到 3 个消息数组未启动任何训练校准记录最初的任务集是product-aproduct-b、k4但两行都在 4/4 上饱和全通过学习区取不到「有通过有失败」的任务导出为空。因此把product-b换成product-d并把 k 提到 6才记录下这次 PASS。这印证了learning_zone()的构造性质——它只保留同时含通过与失败尝试的任务若某任务全通过则无法进入学习区。validate_messages.py — PASS行为校验可移植 SFT 行的顶层键、允许角色、user 与最终 assistant 的存在性、非空文本内容结果product-a通过 1/40.25product-c通过 4/41.00。唯一导出的通过行满足全部加载器形状断言未启动训练。这两条记录还给出了一条实用的任务难度校准经验当单次滚动全部通过时学习区会空转应通过替换任务或调整 k 值让任务集落在「部分通过、部分失败」的区间才能生产出有区分度的 SFT 训练数据。运行方式与前置条件按 README.md运行命令为python cookbook/environments/_12_trainer_loader/basic.py python cookbook/environments/_12_trainer_loader/validate_messages.py需要OPENAI_API_KEY环境变量所有示例均通过OpenAIResponses使用gpt-5.5reasoning_effortlow输出文件写入各自脚本同目录下的data/generated/trainer_input.jsonl与validated.jsonl并附带对应的.meta.json溯源边车。关键结论速查边界意识生成 SFT JSONL ≠ 发起训练加载器示例刻意止步于「加载消息数组」。可移植格式{messages: [{role: system|user|assistant, content: 非空字符串}]}顶层与消息级均严格集合相等禁止任何多余键。跳过语义失败、工具超限、带工具运行、无文本分别计入ExportReport的不同计数器超上限的行从尾部丢弃并计数。溯源分离分数与指纹存于.meta.json边车供审计使用训练器只消费文本 JSONL。学习区特性learning_zone()只保留有通过也有失败的任务任务全通过时导出为空——需要校准任务难度或 k 值。校验闭环validate_messages.py的断言与_validate.py的validate_sft_jsonl完全对齐是交给任何训练器适配器前的最后一道闸门。如需在生成下一批数据集之前做逐轮回归验证可继续参考_13_saved_baselines/的示例。【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考