开源自托管 Agent 项目 Hermes Agent 怎么把运行轨迹变成训练与评测数据

2026-07-30

本文基于 hermes-agent 仓库 commit 2d40494(2026-07-29)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/NousResearch/hermes-agent 最新代码与文档为准。

**这条支线真正给你的东西不是”多了一个跑批脚本”,而是把 Agent 的每一次运行统一压成一种可存档、可再加工的结构:一条对话轨迹加一份工具调用统计。**先说清是哪个 Hermes——本文讲的是 NousResearch 开源的自托管 Agent 项目 hermes-agent(MIT 许可证,LICENSE 署名 Nous Research),不是同名的开源模型系列,也不是任何同名商标或库。它会常驻在你自己的机器上跑,仓库里那 14 个分类目录、70 份 SKILL.md 的技能层和 18 个顶层插件目录都是给这个常驻进程用的;而跑批与轨迹处理这条支线,是同一套代码的另一种用法:不为了陪你干活,而为了产出数据。

站内已经有几篇讲评测的文章,分工要说在前面:Agent 评测方法评测集怎么构建 讲的是通用方法论,ECC 的评测题设计 讲的是另一个项目的做法;本篇不重复方法论,只讲这个具体仓库里的这几个文件怎么把机制落到实处,以及它落到哪一步就停了。

一、它先解决的是”运行记录能不能当数据用”

Agent 跑完一次,你手上通常有一堆日志:请求、响应、工具输出、报错。日志是给人看的,格式随手写,捞不出结构。要把它变成数据,得先回答三个问题:一条运行记录的边界在哪、里面的角色怎么标、工具调用怎么表达。

这个仓库给的答案很直接。轨迹是一个列表,每项只有两个键:fromvalue。角色只有四种取值——systemhumangpttool。系统那一条不是你日常用的系统提示词,而是一段固定的函数调用说明,里面用 <tools> 包住当次可用工具的定义;模型的每一轮写成 gpt,里面用 <tool_call> 包 JSON 形式的调用,工具结果单独成一条 tool,用 <tool_response> 包住。

有两个细节体现了取向。一是思考内容被强制留位:转换函数里,如果模型有原生 reasoning 就包进 <think>,如果没有原生思考、模型用 <REASONING_SCRATCHPAD> 这种 XML 方式自己写推理,就换成 <think> 标签;哪怕两者都没有,也会补一个空的 <think> 块,让每一轮 gpt 的结构一致。二是轨迹坚持纯文本:转换前先把带图的工具结果规范化,换成其中的文本摘要字段,注释里写明理由是不想把大块 base64 塞进每条轨迹。

看懂这两点,你就知道这套格式是冲着什么去的:它要的是形状齐整、体积可控、能直接喂进训练管子的样本,而不是尽可能完整地保真一次运行。想追求保真回放的话,那是另一套需求,别指望这个格式替你做。

二、批量入口:一行 prompt 到一份合并文件

batch_runner.py 是这条支线的主入口。它接一个 JSONL 数据集,每行至少要有 prompt 字段——没有这个字段的行会打一条告警然后跳过,整个文件一条有效项都没有就直接报错退出。

流程是这样的:数据集按 batch_size 切成批,进程池按 num_workers 并行跑批,每一批内部的 prompt 顺序执行。也就是说并行度是批级的,不是单条级的。每条 prompt 单独构造一个 Agent 实例,并带上形如 [B0:P3] 的日志前缀,方便你在混在一起的输出里认出是哪批哪条。任务标识按 task_{索引} 生成,传进去让每个任务拿到自己隔离的执行环境,避免几十条任务在同一个工作目录里互相踩。

工具不是全开。跑批时按”分布”抽样:toolset_distributions.py 里定义了若干命名分布,每个分布是”工具集名 → 被选中的百分比”,比如全开的那个分布把 web、vision、image_gen、terminal、file、browser 都设成 100,偏图像生成和偏研究的分布则各自压低无关工具集的概率。命令行有一个只列分布不跑任务的开关,先看清有哪些分布再选,比翻代码快。

跑完每条会做两件加工。一是抽工具调用统计:从消息历史里按 tool_call_id 把调用和结果配对,数出每个工具的调用次数、成功数、失败数。判成功的口径值得看一眼——能解析成 JSON 的结果,看 error 字段是否非空、看 success 是否为假;解析不了的字符串,只在内容为空或以 error: 开头时算失败。注释里专门写了终端工具的例外:非零退出码不算失败,因为模型可以自己纠正。这个口径不激进,用它做横向对比之前你得知道它宽在哪。

二是抽推理覆盖统计:数有多少轮助手消息带思考。这个统计不只是拿来看的——一条轨迹如果所有轮都没有任何思考痕迹,会被直接丢弃,同时仍然记进”已完成”,不会在恢复时重跑。

落盘分三层:每批先追加写自己的 batch_{批号}.jsonl;全跑完再把目录下所有批文件合并成一份 trajectories.jsonl,合并时用工具名白名单过滤——白名单直接取工具映射表的键集合,出现表里没有的工具名(模型幻觉出来的名字)整条丢掉,JSON 解析失败的行也丢掉;同时写一份 statistics.json 汇总工具统计与推理覆盖,一份 checkpoint.json 记进度。

恢复机制有个容易踩的设计:--resume 不只信索引。它会扫目录下所有批文件,把每条轨迹里第一条 human 消息的文本收进集合,再拿数据集里每行的 prompt 文本去比对,命中就跳过。好处是索引错位也能续;代价是你改一个字的 prompt 文案,它就当成新任务重跑。断点续跑本身的取舍,检查点与长跑任务 那篇讲得更细,这里只强调它匹配的是内容不是编号。

三、几个零件各管什么

组成部分它负责什么仓库位置你什么时候会碰到它
批量跑批入口切批、进程池并行、抽样工具集、统计、检查点、合并输出batch_runner.py想批量产轨迹时的第一站
轨迹格式转换把内部消息历史转成 from/value 列表,补 <think>、包 <tool_call>agent/agent_runtime_helpers.py需要改格式或对接自己的训练管子
轨迹落盘与标签工具追加写 JSONL、scratchpad 标签换成 <think>、检测未闭合标签agent/trajectory.py单跑模式下想知道文件写到哪了
工具集分布定义命名分布与抽样逻辑,控制每条任务开哪些工具toolset_distributions.py想让产出的数据分布偏向某类工具
终端向单任务跑法只给一个终端工具、在指定环境里执行、输出同一种轨迹格式mini_swe_runner.py只关心命令行类任务,不想开全套工具
轨迹压缩保头保尾、只压中间、超长时用模型生成摘要替换trajectory_compressor.py产出的轨迹太长、装不进目标预算
执行环境后端本地、Docker、Modal、Singularity、Daytona、SSH 等多种执行后端tools/environments/决定模型生成的命令到底在哪执行
数据生成示例用注释和示例说明一次数据生成怎么组织datagen-config-examples/照着改一份自己的跑批脚本

四、单任务跑法与压缩:两头各收一刀

mini_swe_runner.py 是同一格式的另一个生产口,取向明显更窄:它只挂一个终端工具,工具描述里写清了环境隔离、文件系统在同一任务内跨调用保留、命令要非交互。环境用工厂函数按类型创建,支持本地、Docker、Modal 三种;本地那种就是在你自己的机器上直接执行。

它的完成判定用了一个哨兵字符串:系统提示词要求模型任务做完时回显 MINI_SWE_AGENT_FINAL_OUTPUT 加一段总结,运行时在命令输出里检测到这串就置完成并停。这个做法便宜、可控,但也意味着”完成”是流程信号,不是”任务做对了”。模型不喊这句,就一路跑到最大轮次上限。

另一头是压缩。trajectory_compressor.py 的策略写在文件开头的注释里:保护开头几轮(系统、首条 human、首条 gpt、首条 tool),保护末尾若干轮,只压中间,从第二条工具结果开始,压到刚好装进目标预算就停,被压掉的那段用一条 human 摘要消息替代,后面的工具调用保持原样让模型继续。示例配置里能看到配套的旋钮:分词器选哪个、目标上限与摘要目标长度、末尾保护多少轮、摘要用哪个模型、跳过本来就没超的轨迹、装不进去时是否照样保存、每条轨迹的超时。具体数值以仓库文件为准,别照抄本文。

这里有个成本项要摆明:压缩要额外调用一次模型来生成摘要。也就是说你的数据加工阶段又引入了一次模型调用,费用和不确定性都在。各家模型服务的计费与限制规则不同且会调整,以官方最新说明为准;机制上你只需要记住摘要是一次真实的对外请求,不是本地截断。

五、边界与代价:它明确不管的那些事

不管对不对。 轨迹里的 completed 只表示流程走到了终点,工具统计的”成功”只是结果里没有明显错误标记。任务本身有没有做对、做得好不好,这套东西不判。你要正确性判分,得自己接一层。

示例配置不等于命令行契约。 datagen-config-examples/web_research.yaml 用注释写了一次网页研究向数据生成该怎么组织:开哪些工具集、几个并行、每批多少条、跑批阶段临时用的系统提示词(注明不写进轨迹)、输出目录、压缩开关,以及每隔多少条跑一次评测、每次留出多少道题。这份文件读起来像蓝图,但批量入口的命令行参数里并没有接收配置文件的那一项,示例目录里那个可直接运行的脚本也是纯用命令行标志拼出来的。把示例配置当成设计意图的说明书读,别当成可直接投喂的输入。

跑批刻意脱掉了常驻态。 构造 Agent 时明确关掉了上下文文件加载与持久记忆,注释写的理由是不想让 SOUL.md、AGENTS.md 那类文件污染轨迹。对数据纯度是好事,对你排查是坑:跑批环境和你平时那个常驻环境不是同一个人格。

图像与富媒体过不去。 轨迹是文本的,带图的工具结果会被换成文本摘要。要做多模态数据,这条路径不够。

它是真的在执行命令。 常驻进程、开终端、往磁盘写文件、访问外部服务,跑批只是把这件事乘以任务条数。本地环境没有隔离层,模型生成的命令就是在你的机器上跑。这一点没有粉饰的空间:要么用容器或远端后端,要么接受风险。

数据治理它也不管。 轨迹里会原样带着 prompt 文本、命令输出、抓到的页面内容。你拿它做数据集之前,脱敏、许可、来源合规都得自己过一遍。

六、上手与避坑清单

别拿示例 yaml 直接开跑。 会踩是因为文件顶部的用法注释看着就像可以配置文件驱动,而实际入口不吃这个参数。避法:以示例目录里那个 shell 脚本为模板,把参数改成你自己的数据集与分布,再逐条加需要的开关。

别用产出条数当成功率。 会踩是因为全程没有思考痕迹的样本会被静默丢弃,且照样计进已完成,恢复时也不会重跑。避法:跑完先看统计文件里的推理覆盖与丢弃计数,再算比例。

新工具记得登进映射表。 会踩是因为合并阶段的白名单直接取工具映射表的键集合,表里没有的工具名会让整条轨迹被过滤。避法:加完自定义工具,先小批量跑几条,确认合并后条数没有莫名减少。

改 prompt 文案等于放弃续跑。 会踩是因为恢复靠首条 human 文本精确匹配。避法:整理数据集时先把文案定稿,续跑期间只增行不改行。

容器路径的检查会静默跳过。 会踩是因为数据集行里给了镜像时,只有环境类型是 docker 才会先探测再拉取;找不到 docker 命令那一支是直接跳过检查的。避法:换后端之前,在同一台机器上手动跑一次镜像拉取和一条命令,确认真的落在你以为的那个环境里。

调并行先调批大小。 会踩是因为工作进程数只决定同时跑几个批,批内是顺序执行的。避法:想提高并发,把批切小、批数变多,而不是只把工作进程数往上加。并发编排本身的坑见 Agent 并发编排

用终端向那个跑法时先确认哨兵。 会踩是因为完成判定依赖模型回显那串固定标记。避法:改系统提示词时不要动掉这句要求,否则每条任务都会跑到轮次上限才停。

先在一位数条目上跑通再放量。 批量入口有只跑前 N 条的参数,用它验证格式、目录结构、统计文件都对了,再上全量。

收个尾

把这条支线放在一起看,它的取向是清楚的:格式尽量齐整、体积尽量可控、流程尽量能续,而正确性判断留给使用者。这不是缺陷,是分工——一个工程系统同时服务训练和评测时,唯一能共用的部分就是数据的形状,判分标准从来是随场景变的。

接着读哪个文件,看你的目的。想改格式,去看轨迹转换那个函数,从系统消息模板和 <think> 补位那几行开始;想控制数据分布,去读分布定义文件,先跑一次只列分布的命令;想压缩,先读压缩器文件开头那段策略注释,再对着示例配置理解每个旋钮;想只做命令行类任务,直接读终端向那个单任务跑法,它短得多。顺手做一件事:跑三五条,把产出的一条轨迹展开逐项看一遍——from 取值是不是只有四种、<think> 是不是每轮都在、工具统计里的失败是不是你认可的口径。这三项对上了,再谈放量。

本篇属于一个把开源常驻自托管 Agent 项目 Hermes Agent逐层拆开讲的系列,整体地图见 开源自托管 Agent 项目 Hermes Agent 是什么;沿着这条线往下,还可以看 开源自托管项目 Hermes Agent 的 MoA 多模型合议开源自托管 Agent 项目 Hermes Agent 的插件系统

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。