news 2026/9/10 12:10:59

agno Environments 学习区 SFT 数据导出实战:从 Rollout 到可消费的对话式 JSONL 数据集

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
agno Environments 学习区 SFT 数据导出实战:从 Rollout 到可消费的对话式 JSONL 数据集

agno Environments 学习区 SFT 数据导出实战:从 Rollout 到可消费的对话式 JSONL 数据集

【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno

导读

在 agno 的环境(Environments)体系中,_10_export_sft位于"学习区(learning zone)筛选"与"数据导出"之间的关键环节:它把已经跑完多轮 rollout、被评分器标记为通过/失败的对话尝试,筛选出处于中间难度带(learning zone)的任务,并只把其中通过的纯文本对话导出为一行一个 JSON 对象的对话式 SFT JSONL 文件。本文以 cookbook/environments/_10_export_sft/README.md 及其三个示例脚本为骨架,结合 agno 源码深入讲解run_rolloutslearning_zone()to_sft_jsonl的底层实现、导出格式的严格约束、空学习区的防御性处理,以及它们在整个"数据集生成而非模型训练"工作流中的定位。

前置知识:Export SFT 在 Environments 流水线中的位置

从 cookbook/environments/_10_export_sft/README.md 可知,导出 SFT 数据应放在_06_learning_zone/之后使用:

  • _06_learning_zone/先展示哪些任务处于真实的中间带通过率;
  • _10_export_sft再把处于学习区任务中的通过尝试导出为数据集;
  • 导出的 JSONL 旁会生成一个<path>.meta.jsonsidecar 文件,记录分数与指纹信息,下一章_11_export_provenance/专门分析这个 sidecar。

需要特别强调的是:导出只生成数据集,不会训练任何模型。另外,带有工具调用(tool-bearing)的对话在这个纯文本格式中无法被忠实表示,因此会被跳过,工具相关的验证请参考_17_tool_reliability/

核心 API 全景:Environment、run_rollouts、learning_zone、to_sft_jsonl

三个示例脚本都遵循同一个四步工作流,涉及的 API 均来自agno.environmentsagno.scorer

步骤API作用
1Agent+OpenAIResponses定义执行任务的策略模型
2Environment+Task+CodeScorer定义环境:任务清单、期望输出与评分函数
3run_rollouts(env, k=N)每个任务重复执行 N 次尝试,返回EnvironmentRunResult
4result.learning_zone()to_sft_jsonl(zone, path)筛选学习区任务并导出通过的对话为 JSONL

run_rollouts:同步入口与并发语义

run_rollouts定义在 libs/agno/agno/environments/runner.py,签名如下:

def run_rollouts( env: Environment, *, k: int = 8, tasks: Optional[Sequence[Task]] = None, model: Optional[Model] = None, concurrency: int = 4, ) -> EnvironmentRunResult:
  • k是每个任务的尝试次数,默认为 8;示例脚本中分别使用了k=4k=6
  • concurrency控制并发尝试数,默认 4;
  • 它是arun_rollouts的同步门面,内部用asyncio.run执行;
  • 超时语义沿用异步实现:尝试协程会在env.timeout_seconds到达时被取消;
  • 特殊限制:run_rollouts不能在已有运行事件循环的环境中调用(会抛出RuntimeError),此时应改用await arun_rollouts

EnvironmentRunResult提供summary()(返回带固定键的 CI 契约字典,包含envkn_attemptspass_rateenv_fingerprintpolicy_fingerprint等)和errors()(按任务 id 分组的错误信息)等能力。

learning_zone:按任务而不是按尝试筛选

learning_zone()的实现(libs/agno/agno/environments/runner.py)返回一个只保留学习区任务的EnvironmentRunResult副本:

def learning_zone(self) -> "EnvironmentRunResult": return replace( self, task_results=tuple(task_result for task_result in self.task_results if task_result.in_learning_zone), )

而"学习区"的判定标准非常明确(libs/agno/agno/environments/runner.py):

@property def in_learning_zone(self) -> bool: return 0 < self.n_passed < self.n_scored

即:该任务有通过的评分尝试,也有失败的评分尝试——任务既没有饱和(全部通过)也没有完全无望(全部失败),因此每一次失败都能找到一次通过尝试作为对照。未评分尝试(如超时)不参与统计,也绝不会被当作 0 分强算进通过率。由于保留的是同一个结果对象的副本(指纹一致),summary()、网格视图和导出器都能直接作用于学习区子集。

to_sft_jsonl:把通过的对话写成可消费的 JSONL

to_sft_jsonl定义在 libs/agno/agno/environments/exporters/sft.py:

def to_sft_jsonl( result: EnvironmentRunResult, path: Union[str, Path], *, only_passed: bool = True, ) -> ExportReport:

关键设计点:

  1. only_passed=True是默认值。因为learning_zone()选中的任务天然"既有通过又有失败",如果不按尝试过滤,就会把错误答案写进监督数据集。源码注释说得非常直白:"learning_zone()selects tasks;only_passedselects attempts within them: SFT wants both."——两者缺一不可。
  2. 输出格式为对话式 JSONL:每行一个 JSON 对象{"messages": [{"role", "content"}, ...]},角色仅允许system/user/assistantcontent必须是非空字符串,且至少包含一条 user 消息、最后一条必须是 assistant 消息(见 libs/agno/agno/environments/exporters/_validate.py)。
  3. 容量上限严格对齐最严格的消费端:最多 320 条对话、单文件 1 MiB(utf-8 编码后)——上限来自消费端的BATCH_SIZE (8) * MAX_STEPS (40)。超限的行按发射顺序从尾部丢弃并计入n_dropped_over_cap绝不静默截断
  4. 系统消息会被保留:它承载了诱导出该输出的格式指令;assistant 文本则取模型实际生成的Message.content原文(逐字节一致),绝不是对run.content的序列化——在output_schema场景下 pydantic 模型的str()/model_dump_json()会产生模型从未生成过的文本。
  5. 发射顺序确定:按任务顺序、任务内按尝试顺序输出,并发场景下也保持确定性。
  6. 文件写入使用newline="":禁用平台换行符转换,避免 Windows 上 CRLF 转换悄悄把一个刚好达到字节上限的文件推过 1 MiB 上限,破坏基于 sha256 的确定性。

导出器会同步写出 sidecar 文件<path>.meta.json,其中包含env_fingerprintpolicy_fingerprintreport(各计数)、options(如only_passed)以及lines(每条已写对话的task_idattempt_indexscore)——这就是后续_11_export_provenance/要分析的溯源数据。

ExportReport:每个候选去向都有计数

to_sft_jsonl返回的ExportReport(libs/agno/agno/environments/exporters/sft.py)包含六个计数,且各计数之和等于候选总数(仅指已评分尝试;未评分尝试在任一only_passed取值下都不算候选):

字段含义
n_written实际写入 JSONL 的对话数
n_skipped_failedonly_passed=True被跳过的失败尝试
n_skipped_tool_runs因尝试使用了工具被跳过的次数
n_skipped_limit_hit因触发工具调用上限被跳过的次数
n_skipped_no_text因无法提取可导出文本被跳过的次数
n_dropped_over_cap因超过 320 条 / 1 MiB 上限被从尾部丢弃的次数

_classify(libs/agno/agno/environments/exporters/sft.py)按固定顺序执行跳过检查,命中即计数并跳过——这些规则在构造上就有重叠(例如触发工具上限的尝试同时也是工具型运行),不固定顺序就会重复计数。值得注意的两条规则:

  • 工具调用上限命中(tool_call_limit_hit)会被跳过:"拒绝工具调用之后才给出的答案,是迫于压力下的答案";
  • 工具型运行(run.tools非空)会被跳过:交集格式没有工具表示法,只输出最终答案而不输出产生它的工具轨迹,会训练模型"不用它实际使用的工具也能作答"。

三个示例脚本逐一遍历

basic.py:最小完整闭环

basic.py 演示了完整的"跑 → 筛 → 导出"链路:

  • 两个任务product-aproduct-b均为需要先做大数乘法再按素数(65521 / 65537)取模计算的复合算术题,输出通过Answervalue: int)结构强制为整数;
  • 评分器使用CodeScorer(exact_value),其中exact_value(run, expected)检查run.content.value == expected
  • run_rollouts(env, k=4)跑 4 轮,result.learning_zone()取学习区;
  • 若学习区为空则打印提示并中止;否则to_sft_jsonl(zone, output_path)导出到data/generated/train.jsonl,并打印n_written(写出的通过对话数)与n_skipped_failed(跳过的失败尝试数)。

passed_only.py:验证失败尝试被排除

passed_only.py 专门验证"学习区选中任务之后,默认导出器会把任务内失败的尝试剔除":

  • 使用更难的任务组合(product-aproduct-d,模数提升到 524287 / 99991),k=6
  • 导出后直接读取生成的 JSONL 文件,逐行json.loads解析,并断言report.n_written == n_passed == len(rows)三者相等——即写出的行数、学习区内的通过尝试数、文件中实际行数完全一致;
  • 同时打印n_skipped_failed展示被剔除的失败数。

empty_zone_guard.py:空学习区的防御

empty_zone_guard.py 处理一个真实且常见的场景:空选择是正常结果,必须在下游把空文件误认为"数据集生成完成"之前检查它。

脚本的防御策略:

  1. 首先删除可能存在的陈旧产物(output_path与 sidecar 路径上的.meta.json),保证"无操作导出绝不在对外承诺的路径上留下上次运行的数据集";
  2. 任务组合中刻意加入easy-anchor(17 × 23 = 391 的简单整数题)作为"非学习区锚点",与两道难题混跑;
  3. 真实运行后若学习区非空则正常导出;若为空则断言output_path与 sidecar均不存在,防止空文件残留被误读为有效数据;
  4. 最后用dataclasses.replace(result, task_results=())构造一个合成空选择来演练分支,并明确"导出器在此有意不被调用"。

测试日志解读:验证结果与校准过程

TEST_LOG.md 记录了 2026-07-20 使用OpenAIResponses(id="gpt-5.5", reasoning_effort="low")完成的三组验证:

  • basic.py(PASS):两个算术任务各跑 4 次,学习区两行均被选中,只导出通过的文本对话。product-a通过 1/4(0.25),product-b通过 3/4(0.75),导出器写出 4 条对话、跳过 4 次失败尝试。
  • passed_only.py(PASS):默认导出器在学习区任务内只保留通过尝试。product-a通过 4/6(0.67),product-d通过 5/6(0.83),JSONL 恰好包含 9 条通过尝试,3 条失败尝试被排除。日志还记录了校准过程:最初的任务组合(product-aproduct-c、k=4)在两行上双双饱和到 4/4,于是用更难的product-d替换product-c并把 k 提到 6,才记录到这次 PASS——这正是学习区机制要求的"中间带难度"校准。
  • empty_zone_guard.py(PASS):清理陈旧产物后导出真实学习区选择,再演练显式空选择。最终实跑:easy-anchor通过 4/4(1.00,饱和非学习区)、product-a通过 1/4(0.25,学习区)、product-b通过 4/4(1.00,饱和非学习区)。真实选择写出 1 行,合成空选择被检测并跳过;并断言未来真实运行为空时,data/generated/guarded.jsonl及其.meta.json不会残留。

运行与前置条件

按 README 的指引,三个脚本依次执行即可:

python cookbook/environments/_10_export_sft/basic.py python cookbook/environments/_10_export_sft/passed_only.py python cookbook/environments/_10_export_sft/empty_zone_guard.py

前置条件:

  • 需要OPENAI_API_KEY环境变量;所有示例都通过OpenAIResponses使用gpt-5.5,并在示例中采用reasoning_effort="low"以控制推理成本;
  • 导出目录(data/generated/)由导出器自动创建(output_path.parent.mkdir(parents=True, exist_ok=True));
  • 若在已有事件循环的异步环境中运行,请改用agno.environments提供的ato_sft_jsonlarun_rollouts异步版本。

数据流总结与注意事项

从构建环境到拿到可消费数据集,完整数据流为:

Environment(Task×N, scorer) --run_rollouts(k)--> EnvironmentRunResult --learning_zone()--> 学习区任务子集 --to_sft_jsonl(only_passed=True)--> train.jsonl + train.jsonl.meta.json

几个必须记住的工程要点:

  1. 导出≠训练:本模块只产出数据集文件,不触发任何训练流程;
  2. 学习区选任务、only_passed选尝试:两者组合才是干净的监督数据;
  3. 严格 schema:顶层只能有messages,消息只能有rolecontent,多一个未知键会导致最严格的消费端整文件拒绝(严格集合相等,不会丢弃多余键)——所以不要"好心"添加toolsweighttrainable之类的键
  4. 工具型与触发工具上限的尝试一律跳过,文本缺失、超限的尝试各有独立计数;
  5. 空学习区是正常结果:在把 JSONL 当作"已生成数据集"交给下游之前,必须检查学习区是否为空,并防止陈旧产物残留误导后续步骤。

【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 12:10:57

MyBatis-Plus与Spring依赖注入整合实践指南

1. MyBatis-Plus与Spring依赖注入的深度整合实践在企业级Java开发中&#xff0c;MyBatis-Plus作为MyBatis的增强工具&#xff0c;与Spring框架的依赖注入机制结合使用&#xff0c;能够显著提升开发效率和代码质量。这种组合已经成为现代Java后端开发的标配方案之一。1.1 技术栈…

作者头像 李华
网站建设 2026/9/10 12:10:47

Serenity 的 slugify:文本转 slug 转换工具及其底层实现解析

Serenity 的 slugify&#xff1a;文本转 slug 转换工具及其底层实现解析 【免费下载链接】serenity The Serenity Operating System &#x1f41e; 项目地址: https://gitcode.com/GitHub_Trending/se/serenity slugify 是 Serenity OS 提供的一个命令行文本转 slug&…

作者头像 李华
网站建设 2026/9/10 12:09:59

CANN/GE图引擎Tensor描述符API

aclTensorDesc 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFlow …

作者头像 李华
网站建设 2026/9/10 12:09:49

深入解析Java多线程编程与性能优化实践

1. 线程基础概念与核心价值线程作为操作系统调度的最小执行单元&#xff0c;是现代编程中实现并发的基础设施。我第一次真正理解线程的重要性是在开发一个电商秒杀系统时——当单线程处理能力遇到每秒数万次的请求冲击&#xff0c;系统瞬间崩溃的场景让我深刻认识到多线程编程的…

作者头像 李华
网站建设 2026/9/10 12:09:18

20 分钟跑通 ESP32-P4 MIPI-CSI 摄像头:一份完整实战教程

20 分钟跑通 ESP32-P4 MIPI-CSI 摄像头&#xff1a;一份完整实战教程 【免费下载链接】esp-idf Espressif IoT Development Framework. Official development framework for Espressif SoCs. 项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf 在 ESP-IDF 仓库…

作者头像 李华