一、有了评测尺子,为什么还不能直接改 Skill
第五篇把比较规则准备好了:同一份 generated-code-testing Skill 会面对固定任务,train 提供改写信号,validation 导航候选搜索,
holdout 只在最后比较冻结基线与最终候选。现在看起来只差一句命令:把 Skill 交给优化器,让它写出更好的版本。
但优化器面对的不是一张可以随意涂改的白纸。一次 Agent 运行里同时有用户任务、Skill 说明、工具调用顺序、模型输出和评分规则。 如果这些内容都只是临时拼成一个字符串,评测器即使发现结果不好,也回答不了两个最基本的问题:究竟允许改哪一段,改完又怎样装回程序?
继续用同一个例子。旧 Skill 已经写明“修改 schema 后先生成代码,再运行测试”,但 Agent 仍可能漏掉“检查生成文件差异”这一步。 我们希望优化器改的是这份做事说明,而不是改用户提交的 schema,不是改测试命令的 Python 控制流,也不是为了得高分偷偷改 rubric。 因而,开始搜索前必须先把一次运行拆成四个边界:
固定输入输出:任务进来,结果出去
固定执行结构:程序按什么顺序调用模型和工具
允许搜索的参数:这份 Skill 对任务的说明
固定评价方式:同一道题怎样得到分数和失败原因
这四条边界解决的是不同问题。前两条保证候选仍然在做同一件事,第三条指出变化真正发生在哪里,第四条保证新旧版本仍用同一把尺子。 如果第三条没有显式接通,优化器可能只改到外围提示词,Skill 正文却纹丝不动;如果第四条跟着候选变化,分数再高也失去可比性。
这一篇只追一条证据链。 旧 Skill 怎样进入可优化参数,失败现场怎样变成下一版 instruction,候选怎样重新执行并被选择,胜出文字怎样写回文件, 最后又怎样经过约束、holdout 与人工审阅。任何一环只存在于 README、类名或注释里,都不能算已经闭合。
上一篇已经解释了题目、rubric 与三份数据的权限,本篇只在流程需要时引用它们。 下面先认识负责划定“程序与参数”边界的 DSPy,再看 GEPA 怎样利用轨迹和反馈改写参数;最后回到 Hermes Agent Self-Evolution 的最新固定快照,逐行核对目标设计与实际调用链之间还差什么。
二、在理解 GEPA 之前,先看 DSPy 让什么变得可优化
现在我们已经有题目和尺子,却还缺一个关键接口:优化器究竟可以改哪里?如果程序只是把一整段提示词拼成字符串后直接发给模型, 评测器即使知道结果不好,也不知道哪段文字属于任务说明、哪段属于输入,更不知道怎样把改写后的版本重新装回原程序。
DSPy解决的正是这个中间问题。它不是某一种搜索算法,而是一套语言模型编程框架: 先声明输入、输出和任务说明,再把一次或多次模型调用组成模块,最后用统一的样本与指标调用优化器。这样,“写提示词”就变成了“编译一个有参数的程序”。
在看类名之前,先把边界分成四格。前两格定义程序怎样工作,第三格才是优化器可以搜索的内容,最后一格只负责判断结果:
固定输入输出字段:Signature
固定执行结构:Module
可被搜索的内容:显式注册的 instructions / demonstrations
判断候选好坏:Example + metric
DSPy 不会任意改写 Python 控制流,也不会自动把任何字符串都当成可优化参数。只有被程序暴露出来的 instruction、示例等参数会进入搜索; 输入字段、Module 的调用结构和 metric 仍由开发者固定。后面所有源码都可以用“这一行属于哪一格”来读。
2.1 把这次生成代码任务装进 DSPy
继续使用 generated-code-testing Skill。我们希望程序接收 schema 变更与仓库状态,输出经过验证的修复,同时允许优化器替换“生成代码后应该怎样检查”这段说明。
要做到这一点,程序至少要把输入输出、调用方式、评测题和评分规则分开。DSPy 用四个对象承接这四项职责。
先用 Signature 声明这次语言调用的表格:输入栏是 schema 变更与失败现场,输出栏是修复说明和验证结果,
instructions 写“运行真实生成命令、检查生成差异、再执行目标测试”。
如果我们要优化 Skill,这段 Skill 说明就必须被适配成可替换的 instructions,而不能只作为一段普通输入跟代码一起传进去。
再用 Module 决定怎样执行这张表格,例如交给 ChainOfThought 做一次预测。“schema 已变但生成物过期”这道题被装成
Example:仓库状态进入输入栏,评分说明随样本保留。最后,metric 读取模型结果和评分说明,返回分数;
GEPA 这类需要反思的优化器还会拿到文字反馈。四样东西合在一起,优化器才既知道“可以改哪段说明”,又知道“改完怎样比较”。
当前 Skill
-> Signature.instructions
-> 允许替换的任务说明
评测题 -> Example
Module -> 运行一次生成代码修复
metric -> 给出分数与失败原因
compile -> 返回新程序
现在再把四个源码名压缩成表格,就不需要靠表格猜它们之间的顺序了:
| 角色 | 在生成代码例子里是什么 | 接住哪项职责 |
|---|---|---|
Signature | 输入是 schema 变更与失败现场,输出是修复和验证结果,instructions 承载 Skill | 声明输入、输出与可替换说明 |
Module | 用 ChainOfThought 执行这份 Signature | 固定调用顺序与控制流程 |
Example | 一道生成代码故障题及其 rubric | 给编译器可重复的比较对象 |
metric | 检查是否重生成、查看差异并跑对测试 | 告诉优化器什么叫更好 |
其中最重要的边界是“什么会被优化”。DSPy 把任务说明放在 Signature.instructions 上;优化器沿着 Module 的 predictor 树寻找这些参数。
官方源码明确区分了 predictor 这类可发现的 Parameter 与普通 Python 属性:前者会进入优化器视野,后者不会自动进入。这个契约见
Signature instructions
和
Module 参数发现规则。
所以 compile() 不是“让模型自己变聪明”的魔法调用。它接收一个结构已经明确的 student program、训练样本和 metric,返回一份参数经过选择或改写的程序副本;
模型权重可以完全不变。后面审计 Hermes 时,我们也会沿着这条边界追问:Skill 正文到底是 predictor 的 instruction,还是一次普通输入?
2.2 三个优化器改变的不是同一件事
有了这个框架,我们就能按“想从失败中学到什么”选择办法。先看最简单的情况:旧 Skill 的说明基本正确,只是模型缺少可模仿的成功案例。
BootstrapFewShot 会运行训练题,把达到评分要求的执行轨迹留下来,作为 demonstrations 放回 predictor。它主要增加示例,不负责系统地重写整段任务说明。
如果示例和说明都可能有问题,可以再扩大搜索空间。MIPROv2 会提出多组 instruction 和 few-shot 候选,
然后用验证分数寻找更好的组合。它能同时调整“怎么说”和“给哪些示例”,代价是需要评测更多组合。
如果我们不仅知道分数低,还保存了“运行了生成器,却没有检查生成差异”这样的失败现场,就可以让 GEPA 读取 trace 和文字反馈,
先解释为什么失败,再重写 predictor instructions。三种方法都接收 DSPy 程序和评测集,但改变的东西与使用的学习信号不同。
下面的表是刚才三条路径的速查:
| 优化器 | 主要改变什么 | 它从评测中怎样学习 |
|---|---|---|
BootstrapFewShot | 挑选并加入 demonstrations | 保留达到 metric 要求的成功执行轨迹 |
MIPROv2 | 候选 instruction 与 demonstrations 的组合 | 提出多个候选,再用验证分数搜索组合 |
GEPA | predictor 的自然语言 instructions | 读取轨迹和文字反馈,反思失败后生成新候选 |
BootstrapFewShot 的官方实现把合格轨迹装进 predictor 的 demos;MIPROv2 同时提出 instruction 与 few-shot 候选并搜索组合,分别见
BootstrapFewShot
与
MIPROv2 候选生成。
GEPA 也会改 instruction,但它不只根据“哪个候选分高”做选择,而是让语言模型阅读失败现场后写出下一版。现在我们才有足够的铺垫理解它为什么不同。
三、GEPA 的关键不是“遗传”,而是带着失败原因改提示词
现在才轮到 GEPA。论文把目标写得很清楚:模型权重保持不变,搜索的是自然语言提示词。每轮从候选池里选一个父代,拿一小批样本执行,收集轨迹、分数和文字反馈, 再让反思模型解释失败原因并提出新的提示词。新候选经过评测后回到池里,循环继续。
继续用生成代码题,一轮反思不是“让模型自由复盘”,而是下面这条可以重新执行的链:
候选 P1 处理 schema 变更后的测试失败
-> 运行了生成器,却没有检查生成文件差异,得分低
-> 反馈指出:无法证明变更只来自预期 schema,也没有发现意外生成物
-> 反思器把“生成后先检查 diff”写成明确步骤
-> 得到候选 P2
-> P1 与 P2 在同一小批题上重新执行
-> 只有实际改善的候选才回到候选池
因此下面的图应按“执行、诊断、修改、复测”阅读。文字反馈负责提出改动方向,重新执行负责证明这次改动是否真的有效; 少了最后一步,反思只是一段听起来合理的自我解释。
3.1 轨迹让“错了”变成“错在哪里”
只有一个 0.4 分,反思模型不知道问题来自工具选择、顺序、遗漏约束,还是输出格式。执行轨迹把中间决策暴露出来;评估器再补充文字反馈, 例如“调用了正确工具,但在拿到空结果后没有执行备用搜索”。GEPA 的 reflective mutation 正是读取这类上下文后,针对失败模式重写提示词。 论文的算法与反馈接口见 GEPA v2 第 3 节。
在独立 GEPA 实现里,这个失败现场不是一段随意拼接的日志,而是 adapter 必须返回的逐题批次。引擎只要求输出、分数和轨迹一一对齐; 轨迹内部是什么,由接入方解释。这个边界让 GEPA 可以优化 Agent、RAG 或别的文字组件,而不必理解它们各自的运行时。
Candidate = dict[str, str]
@dataclass
class EvaluationBatch:
outputs: list[RolloutOutput]
scores: list[float]
trajectories: list[Trajectory] | None = None
def evaluate(
batch,
candidate: Candidate,
capture_traces: bool = False,
) -> EvaluationBatch: ...
EvaluationBatch
的 scores[i] 回答第 i 道题有多好,trajectories[i] 保存这道题为何得到这个结果。
反思阶段再由 make_reflective_dataset 把轨迹压成可操作反馈。没有这条对应关系,“反思”就会退化成只看总分猜原因。
3.2 为什么不永远选择总分第一
一个候选可能擅长发现生成物过期,另一个擅长识别不该提交的生成文件,第三个能稳定选择正确的测试范围。只保留平均分最高的版本,会过早丢掉这些局部强项。 GEPA 维护按样本表现形成的帕累托候选集合:先抽一道题,再从这道题上的强候选中选择父代。这样,仍有独特优势的候选可以继续繁殖,而不是被一个暂时的总冠军清空。
这也解释了论文名称里的 Pareto。它不是为了让术语更复杂,而是在有限 rollout 预算下保护搜索多样性。 论文消融实验显示,只从“当前最好”变异会弱于帕累托选择;但候选合并也不是永远有利,组件之间存在依赖时可能反而破坏组合。
四、先走完一次进化,再看框架怎样接线
到这里,我们已经分别认识了基线、评测样本、rollout、trace、候选、DSPy 和 GEPA。但零件都认识,不等于流程已经连起来。 先继续用那份生成代码 Skill,把“旧版怎样成为可交付新版”从头到尾走一遍;然后再看三个框架怎样为这条流程传递数据。
4.1 旧版怎样走到可交付候选
- 冻结起点。保存旧 Skill、模型配置、评测题来源和数据切分。后面即使产生很多候选,基线也不能跟着变化。
- 跑出基线。让旧版完成训练集和验证集里的任务,保存每题输出、执行轨迹、分数和文字反馈。现在系统知道旧版平均多好,也知道它具体错在哪里。
- 生成候选。优化器选择一个父候选,抽取一小批失败样本,让反思模型阅读 Skill、轨迹和反馈,再提出一版有明确针对性的改写。
- 重新执行。新版完成同样的题目。结构、长度或安全约束不合格就立即淘汰;合格版本才进入验证比较。
- 循环搜索。验证分数和逐题表现决定候选是否留在池里、下一轮从谁继续改。预算耗尽或提升停滞时,搜索停止。
- 只打开一次留出集。选定候选后,才让它和旧版面对搜索期间从未见过的题。没有稳定提升,就不进入交付。
- 交给人审阅。系统展示 Skill diff、基线与候选指标、失败样本和已知局限。人可以接受、拒绝或继续修改;运行中的 Skill 不会被优化器直接覆盖。
这条路线里有三个不同身份:旧版是比较基准,候选是尚未证明的实验品,交付版是通过留出比较和人工审阅后才获得的身份。 “生成了一份更顺眼的文字”只走到第三步,不等于完成自进化。
4.2 适配层把三种表示翻译给彼此
完整路线清楚后,框架接线就容易理解了。Hermes 手里的是一份 Skill 文件;DSPy 希望把任务包装成可重复调用的 program,并把可优化说明放在 predictor instructions; GEPA 看到的则是一组有名字的文字组件,以及“怎样执行组件、怎样返回逐题分数和轨迹”的接口。三者说的不是同一种数据形状,因此中间需要 adapter 做翻译。
GEPA 的
独立官方实现
把候选交给 adapter 执行,再收集逐题分数、轨迹和反思材料;它本身并不知道 DSPy 的 Signature 或 Hermes 的 SKILL.md。
dspy.GEPA 则负责 DSPy 这一侧的翻译:遍历 student program 的 predictors,把 pred.signature.instructions 组成初始候选,
运行模块并捕获 trace,最后把 GEPA 选出的文字装回新程序。对应实现见
dspy.GEPA.compile
和
GEPA adapter 契约。
DSPy 的翻译可以缩成下面四行。第一行决定搜索空间:只有 predictor 的 signature.instructions 被放进初始候选。
adapter 负责把候选跑成轨迹与分数,GEPA 负责搜索,最后再由 adapter 把胜出文字装回程序。
seed_candidate = {
name: pred.signature.instructions
for name, pred in student.named_predictors()
}
result = optimize(seed_candidate=seed_candidate, adapter=adapter, ...)
new_program = adapter.build_program(result.best_candidate)
这段
dspy.GEPA.compile
也给了我们一把很具体的审计尺:想证明某段文字会进化,就必须能指出它在哪一步进入 seed_candidate,又在哪一步从
best_candidate 被写回。
适配层决定“到底改了什么”。
独立 GEPA 可以通过自定义 adapter 优化任意文字组件;但标准 dspy.GEPA 路径从 DSPy predictor instructions 读取种子候选。
如果 Skill 正文只作为普通输入传进 forward,它不会因为参加了同一次执行就自动成为可改写参数。
4.3 Hermes 入口怎样表达这条目标路线
Hermes Agent Self-Evolution 的 CLI 把目标流程写成十个阶段:找到 Skill、准备数据、检查基线、配置 DSPy、运行 GEPA、取出新版、检查约束、比较留出集、展示结果、保存输出。
主入口在
evolve_skill.py。
为了让同一份 Skill 可以反复做同一道题,仓库用 SkillModule 把它包成 DSPy 模块:每次 forward 把 Skill 正文和任务输入交给
ChainOfThought,再返回模型输出。换句话说,它把“某个 Skill 做某道题”变成一项可以重复运行、评分和比较的函数。实现见
SkillModule。
class TaskWithSkill(dspy.Signature):
skill_instructions: str = dspy.InputField()
task_input: str = dspy.InputField()
output: str = dspy.OutputField()
def __init__(self, skill_text):
self.skill_text = skill_text
self.predictor = dspy.ChainOfThought(self.TaskWithSkill)
def forward(self, task_input):
return self.predictor(
skill_instructions=self.skill_text,
task_input=task_input,
)
这段包装已经完成“可重复执行”,却也暴露出下一节要检查的边界:self.predictor 是 DSPy 能发现的 predictor,
self.skill_text 则是普通 Python 属性;forward 又把它放进 InputField。能参与一次运行,不自动等于会进入刚才的
seed_candidate。
| 理想阶段 | 必须留下的证据 | 失败时应该怎样 |
|---|---|---|
| 冻结基线 | 原始 Skill、源码版本、模型配置 | 不能悄悄换基线继续比较 |
| 准备评测 | 题目来源、rubric、切分和种子 | 题目质量不足就停止优化 |
| 反思变异 | 父候选、样本、轨迹、反馈、新候选 | 可定位哪次变异导致退化 |
| 候选门控 | 结构、长度、测试、基准结果 | 任一硬约束失败就淘汰 |
| 留出比较 | 基线与候选在同一未见集合上的结果 | 无提升就不交付 |
| 人工交付 | diff、指标、已知局限、可回滚版本 | 不直接覆盖运行中 Skill |
这张表把 4.1 的完整路线压缩成源码审计标尺。一个仓库可以同时拥有所有模块文件,却还没有把它们接成这条证据链; 判断“实现了没有”,要看值是否真的从入口流到门控和交付,而不是只看类名是否存在。
五、当前源码走到哪里:原型已经成形,闭环还没有
回到 Hermes 仓库时,要把“文件存在”“进入主流程”和“目标闭环已证明”分开。固定快照里的状态可以先压成三层:
| 状态 | 当前可以确认的内容 |
|---|---|
| 已经进入主流程 | 读取 Skill、选择一种数据来源、构造 DSPy 样本、尝试优化、做留出比较并保存输出 |
| 已有代码但没有接入目标路径 | 丰富的 LLM Judge、测试函数和 PR 配置存在,却没有成为本次候选搜索与交付的实际门 |
| 目标路线尚未证明 | GEPA 真正改写 Skill 正文、每个候选经过硬门控、通过后自动形成可审阅 PR |
先说已经具备的部分:仓库能发现和解析 Skill,能生成或导入评测数据,能构造 DSPy 模块,能调用优化器,能在留出集上比较基线与候选,也能保存指标与前后版本。 这些不是空白路线图,而是一套有单元测试覆盖的数据和编排骨架。
但在固定快照 0a929e3 上,几处关键接线决定了它还不能被称为完整交付闭环:
5.1 被优化的 Skill 文本还没有明确注册为 GEPA 参数
SkillModule 把正文保存在普通 Python 属性 self.skill_text,forward 时再把它作为 skill_instructions 输入字段传给 predictor。
主流程却假设 optimized_module.skill_text 已经装着 GEPA 改写后的正文。当前源码没有展示把这个普通属性注册为 DSPy 可优化提示词参数的桥接,
因此仅凭这条调用链,不能确认 GEPA 会改写 Skill 本身,而不是只优化 predictor 内部提示。
baseline_module = SkillModule(skill["body"])
optimized_module = optimizer.compile(baseline_module, ...)
# 主流程从普通属性取“进化后的正文”
evolved_body = optimized_module.skill_text
这里缺的不是一次赋值,而是双向映射:优化前要把 Skill 正文注册成 predictor instruction,优化后再从胜出 instruction 还原成
SKILL.md 正文。当前
主流程
只展示了包装与读取,没有展示这两次翻译。
5.2 依赖没有锁定上限,GEPA 的构造参数也对不上
仓库声明的依赖是 dspy>=3.0.0,没有锁定上限;主流程却调用
dspy.GEPA(metric=skill_fitness_metric, max_steps=iterations)。在本篇固定的最新 DSPy 快照里,GEPA 没有 max_steps,
而是要求在 auto、max_full_evals、max_metric_calls 中恰好选择一个预算,
并提供 reflection_lm 或自定义 instruction_proposer。
对照
Hermes 依赖声明、
实际调用
和
最新 DSPy 构造接口即可确认。
# Hermes 调用
dspy.GEPA(
metric=skill_fitness_metric,
max_steps=iterations,
)
# 最新 DSPy 快照的关键参数
def __init__(
metric,
*,
auto=None,
max_full_evals=None,
max_metric_calls=None,
reflection_lm=None,
instruction_proposer=None,
): ...
这不是参数改个名字就结束:三个预算参数必须恰好选择一个,反思模型与自定义 proposer 也必须至少提供一个。 当前调用既没有合法预算,也没有告诉 GEPA 由谁阅读失败现场并提出新 instruction。
另外,Hermes 的 metric 只接受三个参数,而最新 DSPy 会在构造时检查五参数接口,随后为单个 predictor 传入
gold、pred、完整 trace、predictor 名称和 predictor 子轨迹五项。
配置中的 optimizer_model 虽然被打印出来,也没有作为 reflection LM 交给 GEPA。
因此按本篇最新依赖快照运行时,这条 GEPA 路径会在真正开始反思之前失败;宽泛异常捕获随后把运行切到 MIPROv2。
5.3 结构校验拿到的是正文,却要求它带 frontmatter
load_skill 已经把 frontmatter 与 body 分开。主流程把 skill["body"] 和 evolved_body 送入 validate_all;
但 _check_skill_structure 又要求输入以 --- 开头,并在前 500 字符里包含 name: 与 description:。
对当前调用来说,这个条件天然失败。相关检查在
validate_all
与
_check_skill_structure。
# 调用方传入 body
validator.validate_all(skill["body"], "skill")
validator.validate_all(evolved_body, "skill", ...)
# 校验方却按完整文件检查
has_frontmatter = text.strip().startswith("---")
has_name = "name:" in text[:500] if has_frontmatter else False
has_description = "description:" in text[:500] if has_frontmatter else False
因而基线和候选即使正文完全合法,也会在同一处失败。修复方向只有两个:要么传入重组后的完整 Skill 文件,要么让“正文约束”和“frontmatter 约束”接收各自的数据,而不是让一个函数猜调用方传来的究竟是哪一层。
5.4 丰富的 LLM Judge 存在,但优化器实际拿到的是关键词重合度
fitness.py 定义了 correctness、procedure following、conciseness 和文字反馈组成的 LLMJudge;这正接近 GEPA 需要的可解释反馈。
可是传入 dspy.GEPA(metric=...) 的 skill_fitness_metric 只计算 expected behavior 与输出的单词重合比例,并返回一个浮点数。
两条路径见
LLMJudge
和
skill_fitness_metric。
expected_words = set(expected.lower().split())
output_words = set(agent_output.lower().split())
overlap = len(expected_words & output_words) / len(expected_words)
score = 0.3 + (0.7 * overlap)
return min(1.0, max(0.0, score))
这几行才是搜索实际看见的目标。关键词代理指标速度快,却会奖励“重复 rubric 里的词”,不一定奖励正确完成任务;同时,丰富的失败原因没有通过这条 metric 返回给优化器。 这不是调权重能解决的小问题,而是当前搜索目标与论文方法之间的接口差异。
5.5 测试门、逐候选约束和 PR 交付仍是断开的
ConstraintValidator 有 run_test_suite,配置也有 run_pytest 与 create_pr;但主流程没有调用测试函数,也没有创建 PR。
约束只在优化完成后检查最终提取出的正文,并没有进入 GEPA 的每个候选评测。成功路径最终只是把文件与 metrics.json 写进 output/。
测试函数可见于
run_test_suite,
输出路径则在
保存阶段。
5.6 异常回退会把“GEPA 不可用”和“GEPA 执行失败”混在一起
主流程用一个宽泛的 except Exception 捕获 GEPA compile 的任何错误,然后回退到 MIPROv2。
这能提高演示时的可运行性,却可能把真正的接线错误伪装成“当前 DSPy 版本没有 GEPA”。严谨的进化运行应该只对已知能力缺失回退,其余异常保留失败现场。
这不是说仓库没有价值。 数据导入、样本结构、Skill 包装、留出比较和约束类都提供了可继续建设的骨架。准确的状态描述应是:Phase 1 原型已出现,端到端的 GEPA Skill 改写、硬门控和人工交付尚未由当前调用链证明。
六、怎样读“提升 39.5%”:先问实验真正改了什么
仓库的 Phase 1 报告记录了一次 arxiv Skill 实验:使用 MiniMax M2.5、7 个合成样本中的 3 个训练样本与 2 个验证样本,优化器是 DSPy BootstrapFewShot,
评分是关键词重合度。第一个验证样本从 0.408 到 0.569,报告为 +39.5%;第二个从 0.374 到 0.374,平均从 0.391 到 0.472,即 +20.7%。
配置、指标和结果写在
generate_report.py。
这次实验不是 GEPA 实验,也没有重写 Skill。BootstrapFewShot 选择表现较好的执行轨迹作为 few-shot demonstrations,给模块增加示例。 因此它能支持的窄结论是:在一个很小的合成集合和代理指标上,DSPy 包装后的模块通过示例增强得到更高分。它不能证明 GEPA 已经接通,更不能证明改写后的 Hermes Skill 在真实任务上稳定提升。
| 证据 | 它实际测试了什么 | 不能外推成什么 |
|---|---|---|
| GEPA 论文 | 六类任务上的提示词进化方法与消融 | Hermes 集成已经获得同样收益 |
| Phase 1 报告 | 一个 Skill、极小合成集、BootstrapFewShot、关键词指标 | GEPA 已跑通或 Skill 正文已被改写 |
| 当前源码 | 数据、编排、比较和约束原型 | 测试、PR 与逐候选门控已经自动闭环 |
GEPA 论文自己的结果更强:在六项任务上,相比 GRPO 平均高约 6%、最高约 20%,并报告最多 35 倍更少的 rollout;相对 MIPROv2 平均也有超过 10% 的优势。 这些数字说明方法值得研究,但评价对象是论文基准,不是这份 Hermes 原型。把两个证据层级分开,反而能看清项目下一步真正需要补什么。
七、要把原型变成可信闭环,还缺哪几步
补齐路线不需要发明更多名词,只要让前面的证据链一环不漏地传下去:
- 固定 artifact。记录源仓库 commit、Skill 哈希、模型与数据配置,保证基线可重放。
- 把 Skill 正文变成真正可优化的提示词参数。用 DSPy 明确支持的 instruction / signature 机制接入,而不是依赖普通属性被隐式改写。
- 让 metric 返回正确目标。优先使用可执行检查;开放任务再用 rubric judge,并把可操作的文字反馈交给 GEPA。
- 每个候选先过硬约束。完整 frontmatter、长度增长、测试和缓存边界都应在候选进入池前检查。
- 把异常留在正确层。GEPA 失败就保留失败,不用另一个优化器掩盖;回退必须是显式运行模式。
- 最后只打开一次留出集。同时报告逐题结果、关键旧任务是否退化、置信区间或重复运行,避免一个样本的百分比制造确定感。
- 检查改进的代价。更高分不能靠不可接受的模型成本、token、工具调用或时延换来;这些预算也要和基线并排报告。
- 交付是 diff,不是覆盖。输出候选、基线、指标、失败案例和 PR,让人决定是否进入 Hermes Agent。
- 回到运行时仍要等下一轮。通过评测的 Skill 在安全边界刷新,不能在当前会话中途改变稳定提示前缀。
未见题上稳定提升
+ 关键旧能力没有退化
+ 成本与时延仍在预算内
+ 数据、配置和候选可以重放
+ 旧版本可以回滚
= 有资格交给人决定是否交付
做完这些,离线搜索部分才真正闭合:运行时从真实任务里发现值得沉淀的经验,离线流程把某个 Skill 当成有版本的 artifact 做实验, 人工批准后,新版本再在未来会话生效;新的失败继续成为下一轮评测线索,而不是当场改写自身。下一篇会继续追问:候选进入人工审阅前, Hermes 自己怎样收集本地验证证据,又怎样避免在没有新鲜通过证据时草率结束任务。
八、结论:自进化不是不断重写,而是不断补强证据
回到开头的问题。Agent 把“先运行生成器”写进 Skill,并不等于它已经变好;只有当旧版和新版面对同一套独立任务,新版在正确指标上更稳定, 又没有破坏结构、测试和运行边界,这次改写才有资格被称为改进。
运行时负责发现经验;
评测集负责固定问题;
轨迹与反馈负责解释失败;
候选池负责保留多种强项;
约束与留出集负责阻止自我说服;
人工审阅负责决定是否交付。
Hermes Agent 给出了第一只时钟:回答之后,经验怎样进入长期状态。GEPA 给出了第二只时钟的方法:在不训练模型权重的前提下,怎样让提示词候选经历执行、反思和选择。 Hermes Agent Self-Evolution 则把两者之间的工程接口摆到了桌面上。它当前最有价值的地方,不是宣告自进化已经完成,而是让我们能沿着源码准确看到下一段路。
参考源码与论文
- NousResearch/hermes-agent-self-evolution 固定源码快照
- 项目 README 与阶段状态
- Phase 1 目标、数据策略与完成门
- EvalExample、数据切分与合成生成
- 会话相关性过滤与外部数据集构建
- SkillModule
- Skill 进化主流程
- LLM Judge 与实际 fitness metric
- 约束与测试函数
- Phase 1 实验配置、结果与安全声明
- DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines
- DSPy 官方实现固定快照
- 最新 DSPy 快照的 GEPA 构造接口
- GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning
- GEPA v2 论文 PDF
- GEPA 官方实现固定快照
- GEPA adapter、EvaluationBatch 与反思数据契约