# APO - GEPA


上一篇中，我们了解了 LLM 评估和 APO 的基本概念，今天我来学习 DSPY 提供 APO 的一种实现 GEPA。

<!-- more -->

## GEPA 整体流程

GEPA 整体分为三大阶段：**准备初始化阶段 → 循环迭代阶段（重复直到预算耗尽） → 终止输出阶段**。

### 文本流程简图（ASCII）

![GEPA 流程图](/images/aigc/evaluate/gepa.png)

```
输入材料（训练集、打分器、baseline提示词）
        ↓
【准备阶段】拆分数据集D_pareto / D_feedback，初始化候选池，设置优化预算
        ↓
【循环开始（预算未耗尽则循环）】
        ├─①根据D_pareto上的表现，选出本轮待优化种子prompt
        ├─②随机选一个模块作为本轮优化对象
        ├─③D_feedback采样minibatch小样本，运行得到完整执行轨迹
        ├─④Teacher大模型反思诊断，生成新版本prompt
        ├─⑤第一层刹车校验：同minibatch对比新旧版本
        │    ├─如果新版本更差 → 直接丢弃，回到循环开头
        │    └─如果新版本更好 → 继续往下执行
        ├─⑥新版本在D_pareto跑分，加入候选池
        └─⑦下一轮根据D_pareto分数再次筛选；回到循环开头判断预算
        ↓（预算耗尽，跳出循环）
【终止输出阶段】候选池选出最优prompt，在隔离验证集评测
```

---

## 第一阶段：准备初始化阶段
### 1. 输入基础材料，数据集拆分，初始化候选池
- 怎么做：
    1. 输入训练数据集、打分机制；准备完全隔离的验证数据集（仅用于最终评测）
    2. 将训练集trainset拆分成2个子集
        - D_pareto：小部分数据（如20%），用于候选池打分和候选挑选
        - D_feedback：大部分数据（如80%），用于循环迭代采样
    3. 初始化候选池`candidates pool`
        - 池内放入初始baseline基线提示词
        - 把baseline在D_pareto跑出来的`pareto_scores`存入候选池
- 为什么这么做：
    1. D_pareto与D_feedback承担不同职责；验证数据集完全隔离，保证评估结果的可靠性
    2. 候选池用来保存不同版本提示词+对应D_pareto分数，作为后续迭代的种子库

### 2. 设置预算驱动（budget‑driven）
- 怎么做：
    1. 预先设置优化预算，预算对应循环迭代总轮次
    2. 预算耗尽即停止优化，无提前终止逻辑
- 为什么这么做：
    1. 将提示词优化转化为成本工程问题，而不是单纯追求算法效果
    2. 在可控成本内拿到该预算条件下最好结果，提升工程实践价值

---

## 第二阶段：Loop循环迭代（只要预算没耗尽，完整循环会反复执行）
> 双重淘汰机制：①minibatch快速刹车（本轮直接丢弃）；②根据D_pareto分数筛选（下一轮不再选中）

### 3. 根据D_pareto上的表现挑选本轮待优化的prompt
- 怎么做：
    1. 淘汰池内各方面都很差的候选：在D_pareto的所有测试用例中，没有任何一项分数是所有候选中最高的。通俗来说，就是没有任何特长
    2. 除了这种各方面都很差的候选，其他候选都保留：无论是综合表现好，还是只在某一项上表现很好、其他项很差
    3. 在保留下来的候选集合中随机选1个，作为本轮要优化的目标prompt
- 为什么这么做：
    1. 不是只选总分最好、平均分最好或者各方面都好的候选，避免在一个方向上越优化越深，却在其他方面表现越来越差
    2. 保留各个候选的局部优点，后续有机会把多个局部优点合并，保障泛化能力

### 4. 随机挑选本轮待优化的DSPy模块
- 怎么做：
    1. 如果AI应用包含多个DSPy模块，本轮随机挑选其中一个模块，本轮**只优化该模块**
    2. 如果是单模块应用，直接使用唯一模块作为优化对象
- 为什么这么做：
    1. 解决多模块优化“按下葫芦起了瓢”现象；防止优化一个模块，把其他模块性能搞坏

### 5. 采样minibatch小样本，运行并保存完整执行信息
- 怎么做：
    1. 从D_feedback大数据集中随机抽取一小部分样本，作为本轮minibatch
    2. 本轮迭代内部minibatch保持不变；下一轮循环会重新随机采样新minibatch
    3. 在minibatch上运行当前待优化prompt
    4. 完整记录：分数、全部输入输出、thinking思考过程等完整执行轨迹
- 为什么这么做：
    1. 成本均衡，跑全量D_feedback速度慢；轮次足够、采样随机可以保障通用性
    2. D_feedback和D_pareto做训练数据隔离，防止模型在单一方向过度学习，其他方向性能退化

### 6. Teacher模型做反思，改写生成新版本prompt
- 怎么做：
    1. 将minibatch全套执行信息（分数、输入输出、思考轨迹）交给高质量teacher模型
    2. teacher模型分析分数高低与输入输出之间关联，定位失败原因，做反思诊断
    3. 根据反思结果改写prompt，生成新版本prompt
- 为什么这么做：
    1. 需要能力强的teacher模型做诊断反思；student小模型反思能力不足，不能充当teacher角色
    2. 将失败案例的经验总结，转化为对student小模型的提示词改进

### 7. 第一层过滤：minibatch快速刹车校验
- 怎么做：
    1. 将刚生成的新版本prompt，跑在**本轮固定不变的minibatch数据集**
    2. 对比新版本与旧版本在同一个minibatch的分数
    3. 分支判断：
        - 分支A：新版本分数变差 → 直接丢弃新版本，结束本轮，回到循环开头
        - 分支B：新版本分数有提升 → 继续向下执行
- 为什么这么做：
    1. teacher模型本身可以看到minibatch样本；如果在极小样本上已经回退，更大数据集大概率同样回退
    2. 快速丢弃无效优化，节省LLM调用成本，实现高效刹车

### 8. 新版本在D_pareto上评估，加入候选池
- 怎么做：
    1. minibatch校验通过的新版本prompt，放到D_pareto数据集完整运行
    2. 获取新版本对应的pareto_scores
    3. 将新版本prompt + pareto_scores存入候选池
- 为什么这么做：
    1. minibatch上有提升，**不等于D_pareto上一定表现更好**
    2. 存入候选池，下一轮再根据D_pareto的各项分数做第二层淘汰

### 9. 第二层过滤：下一轮根据D_pareto分数再次筛选
- 怎么做：
    1. 新版本存入候选池之后，不立刻处理；等待下一轮循环执行步骤3
    2. 如果新版本在D_pareto的每一项上都比其他候选差，下一轮步骤3筛选时会被剔除，永远不会被选为优化种子
    3. 判断预算：预算没有耗尽 → 返回循环开头；预算耗尽 → 跳出循环
- 为什么这么做：
    1. 构成完整双重淘汰机制：minibatch不行直接丢弃；minibatch通过但在D_pareto的每一项上都比其他候选差，虽然保存在池子，但不会被选中，等价淘汰
    2. 持续保证候选池整体质量不断变好

---

## 第三阶段：终止输出阶段
### 10. 预算耗尽，选出最优提示词，隔离验证集做最终评测
- 怎么做：
    1. 停止循环，从候选池中选出综合表现最优的提示词
    2. 在全程没有参与优化流程的隔离验证数据集上运行评估，输出最终指标
- 为什么这么做：
    1. 使用从未见过的数据，检验优化后prompt真实泛化效果

---

## GEPA vs MIPROv2

MIPROv2的局限是第一阶段生成候选Prompt、第二阶段统一打分，打分结果无法反过来继续优化已经生成的候选Prompt。

1. MIPROv2：两阶段分步架构；一次性生成全部候选prompt，之后统一打分；打分得到的信息**无法反馈回去修改已经生成的候选**
2. GEPA：持续循环架构；每一轮都拿打分、执行结果反馈驱动下一轮prompt改写
3. DSPy 的优化器指引认为，MIPROv2适合小样本、依赖few‑shot的任务，GEPA擅长多样化任务
4. 从架构设计上看，MIPROv2能做好的事情，GEPA应该也能做好；反过来则未必。MIPROv2的绝对成本可能更低，需要根据对性能和成本的关注做取舍


## 总结

APO 对于大规模 AI Agent 开发有价值。当应用 Prompt 或 System Prompt 扩展到成千上万的维护数量时，人工逐个优化和迭代的时间与成本都会很高。

APO 的前提仍然是高质量数据集和高质量打分器。MIPROv2、GEPA 等中间算法和策略可以持续演进，但数据集和打分器是不变的基础。

