@tangle-network/agent-eval 0.125.0 → 0.126.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -35
- package/README.md +270 -189
- package/dist/analyst/index.d.ts +15 -145
- package/dist/analyst/index.js +33 -47
- package/dist/analyst/index.js.map +1 -1
- package/dist/benchmarks/index.d.ts +45 -162
- package/dist/benchmarks/index.js +8 -9
- package/dist/campaign/index.d.ts +3674 -5393
- package/dist/campaign/index.js +21 -95
- package/dist/{chunk-R226UZOI.js → chunk-474LBSOX.js} +2 -2
- package/dist/{chunk-HM6V7F3M.js → chunk-FO7HEH76.js} +3 -3
- package/dist/chunk-IILEIWGW.js +635 -0
- package/dist/chunk-IILEIWGW.js.map +1 -0
- package/dist/{chunk-EQUK3RFS.js → chunk-J5SQWP6Y.js} +8 -5
- package/dist/chunk-J5SQWP6Y.js.map +1 -0
- package/dist/{chunk-W5B3ZGP3.js → chunk-KE2VWPZX.js} +8 -6
- package/dist/{chunk-W5B3ZGP3.js.map → chunk-KE2VWPZX.js.map} +1 -1
- package/dist/{chunk-DT7OXY3C.js → chunk-LUNF2SEL.js} +538 -851
- package/dist/chunk-LUNF2SEL.js.map +1 -0
- package/dist/chunk-NGUYT5CI.js +4637 -0
- package/dist/chunk-NGUYT5CI.js.map +1 -0
- package/dist/{chunk-QFQZ3U3X.js → chunk-OCFJACJU.js} +2 -2
- package/dist/{chunk-GID26AN4.js → chunk-P22LJ3Y2.js} +4 -6
- package/dist/{chunk-GID26AN4.js.map → chunk-P22LJ3Y2.js.map} +1 -1
- package/dist/{chunk-SJT4OBVL.js → chunk-SDPM6554.js} +3 -3
- package/dist/{chunk-D5JZ7UDZ.js → chunk-UCLVDLCH.js} +136 -50
- package/dist/chunk-UCLVDLCH.js.map +1 -0
- package/dist/chunk-VMUENW6F.js +7274 -0
- package/dist/chunk-VMUENW6F.js.map +1 -0
- package/dist/{chunk-JKDNAOF5.js → chunk-W4L6C2XT.js} +2 -2
- package/dist/chunk-WGXIEX7P.js +116 -0
- package/dist/chunk-WGXIEX7P.js.map +1 -0
- package/dist/{chunk-GRCDRKII.js → chunk-WS3NZZQQ.js} +58 -20
- package/dist/chunk-WS3NZZQQ.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/contract/index.d.ts +3220 -3094
- package/dist/contract/index.js +173 -42
- package/dist/contract/index.js.map +1 -1
- package/dist/control.js +2 -3
- package/dist/fuzz.d.ts +14 -1
- package/dist/fuzz.js +1 -1
- package/dist/hosted/index.d.ts +8 -1
- package/dist/index.d.ts +71 -687
- package/dist/index.js +178 -497
- package/dist/index.js.map +1 -1
- package/dist/openapi.json +1 -1
- package/dist/rl.d.ts +5 -100
- package/dist/rl.js +4 -5
- package/dist/rl.js.map +1 -1
- package/dist/{run-campaign-I3JXKVAK.js → run-campaign-LVFKZCEU.js} +3 -3
- package/dist/traces.js +2 -3
- package/dist/wire/index.d.ts +14 -1
- package/dist/wire/index.js +3 -3
- package/docs/campaign-proposers.md +363 -168
- package/docs/design/loop-taxonomy.md +142 -190
- package/docs/design.md +1 -1
- package/docs/distributed-driver.md +8 -11
- package/docs/feature-guide.md +20 -19
- package/docs/knowledge-readiness.md +2 -5
- package/docs/multi-shot-optimization.md +35 -27
- package/docs/rollout.md +5 -5
- package/package.json +4 -4
- package/dist/chunk-A62YMFWA.js +0 -9269
- package/dist/chunk-A62YMFWA.js.map +0 -1
- package/dist/chunk-A6GT67HT.js +0 -550
- package/dist/chunk-A6GT67HT.js.map +0 -1
- package/dist/chunk-D5JZ7UDZ.js.map +0 -1
- package/dist/chunk-DT7OXY3C.js.map +0 -1
- package/dist/chunk-EQUK3RFS.js.map +0 -1
- package/dist/chunk-GC4ATIKK.js +0 -317
- package/dist/chunk-GC4ATIKK.js.map +0 -1
- package/dist/chunk-GRCDRKII.js.map +0 -1
- package/dist/chunk-LOW3U7JZ.js +0 -328
- package/dist/chunk-LOW3U7JZ.js.map +0 -1
- package/dist/chunk-PMITBABE.js +0 -3841
- package/dist/chunk-PMITBABE.js.map +0 -1
- /package/dist/{chunk-R226UZOI.js.map → chunk-474LBSOX.js.map} +0 -0
- /package/dist/{chunk-HM6V7F3M.js.map → chunk-FO7HEH76.js.map} +0 -0
- /package/dist/{chunk-QFQZ3U3X.js.map → chunk-OCFJACJU.js.map} +0 -0
- /package/dist/{chunk-SJT4OBVL.js.map → chunk-SDPM6554.js.map} +0 -0
- /package/dist/{chunk-JKDNAOF5.js.map → chunk-W4L6C2XT.js.map} +0 -0
- /package/dist/{run-campaign-I3JXKVAK.js.map → run-campaign-LVFKZCEU.js.map} +0 -0
|
@@ -1,247 +1,442 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Optimization Methods And Candidate Generators
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
It does not run an agent, score output, or choose a winner.
|
|
3
|
+
Agent Eval supports two different extension points.
|
|
5
4
|
|
|
6
|
-
`OptimizationMethod`
|
|
7
|
-
|
|
5
|
+
An `OptimizationMethod` owns a complete search procedure and returns one selected surface.
|
|
6
|
+
Use it for official GEPA, official SkillOpt, or another optimizer with its own search and selection behavior.
|
|
8
7
|
|
|
9
|
-
`
|
|
8
|
+
A `SurfaceProposer` suggests candidates inside Agent Eval's campaign loop.
|
|
9
|
+
Use it for caller-defined logic, an agent-runtime worker, or declared parameter combinations.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
| `runImprovementLoop` | Optimize one surface and apply a release rule on separate scenarios. |
|
|
16
|
-
| `OptimizationMethod` | Adapt one complete optimization procedure for comparison. |
|
|
17
|
-
| `compareOptimizationMethods` | Compare selected surfaces on shared final test data. |
|
|
11
|
+
Do not wrap a complete external optimizer in `SurfaceProposer`.
|
|
12
|
+
That would split its search state from its own selection behavior and make budgets harder to compare.
|
|
13
|
+
|
|
14
|
+
## Adapt A Third-Party Text Optimizer
|
|
18
15
|
|
|
19
|
-
|
|
16
|
+
`externalTextOptimizationMethod()` is the general adapter for a package that already owns text or component search.
|
|
17
|
+
The starting candidate is a string for a text surface or a `Record<string, string>` for named components.
|
|
18
|
+
The returned candidate must keep the same form.
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
It
|
|
20
|
+
The `run` callback receives only `trainSet` and `selectionSet`.
|
|
21
|
+
It does not receive final test cases.
|
|
22
|
+
Pass `context.evaluate` to the optimizer so every candidate is executed and scored by the configured Agent Eval path.
|
|
23
|
+
Unknown case IDs and calls beyond `maxEvaluations` are rejected.
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
Optimizer-owned model or service calls must use `context.cost.runPaidCall()`.
|
|
26
|
+
The example below assumes the upstream package enforces `maxCostUsd` for the complete run and returns aggregate usage.
|
|
27
|
+
If the package exposes a model callback instead, wrap each model call separately with the same cost ledger and phase.
|
|
25
28
|
|
|
26
29
|
```ts
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
import { externalTextOptimizationMethod } from '@tangle-network/agent-eval/campaign'
|
|
31
|
+
import { optimize } from 'your-text-optimizer'
|
|
32
|
+
|
|
33
|
+
interface SupportCase {
|
|
34
|
+
id: string
|
|
35
|
+
kind: 'support'
|
|
36
|
+
question: string
|
|
31
37
|
}
|
|
32
|
-
```
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
interface SupportArtifact {
|
|
40
|
+
answer: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const method = externalTextOptimizationMethod<SupportCase, SupportArtifact>({
|
|
44
|
+
name: 'your-text-optimizer',
|
|
45
|
+
source: {
|
|
46
|
+
kind: 'package',
|
|
47
|
+
package: 'your-text-optimizer',
|
|
48
|
+
version: '2.3.1',
|
|
49
|
+
sourceUrl: 'https://github.com/your-org/your-text-optimizer',
|
|
50
|
+
revision: '4f17c2a',
|
|
51
|
+
},
|
|
52
|
+
objective: 'Improve answer accuracy and citation quality.',
|
|
53
|
+
evaluationId: 'support-quality',
|
|
54
|
+
maxEvaluations: 60,
|
|
55
|
+
maxOptimizerCostUsd: 2,
|
|
56
|
+
resume: 'if-compatible',
|
|
57
|
+
describeScenario: (scenario) => ({ question: scenario.question }),
|
|
58
|
+
describeArtifact: (artifact) => ({ answer: artifact.answer }),
|
|
59
|
+
run: async (context) => {
|
|
60
|
+
const paid = await context.cost.runPaidCall({
|
|
61
|
+
actor: context.name,
|
|
62
|
+
model: 'your-text-optimizer',
|
|
63
|
+
maximumCharge: { externallyEnforcedMaximumUsd: 2 },
|
|
64
|
+
execute: (signal) =>
|
|
65
|
+
optimize({
|
|
66
|
+
initialCandidate: context.seedCandidate,
|
|
67
|
+
train: context.trainSet,
|
|
68
|
+
selection: context.selectionSet,
|
|
69
|
+
evaluate: context.evaluate,
|
|
70
|
+
maxEvaluations: context.maxEvaluations,
|
|
71
|
+
maxCostUsd: 2,
|
|
72
|
+
seed: context.seed,
|
|
73
|
+
stateDir: context.stateDir,
|
|
74
|
+
resume: context.restoreRequested,
|
|
75
|
+
artifactDir: context.artifactDir,
|
|
76
|
+
signal,
|
|
77
|
+
}),
|
|
78
|
+
receipt: (result) => ({
|
|
79
|
+
model: 'your-text-optimizer',
|
|
80
|
+
inputTokens: result.usage.inputTokens,
|
|
81
|
+
outputTokens: result.usage.outputTokens,
|
|
82
|
+
actualCostUsd: result.usage.costUsd,
|
|
83
|
+
}),
|
|
84
|
+
})
|
|
85
|
+
if (!paid.succeeded) throw paid.error
|
|
86
|
+
if (context.restoreRequested && !paid.value.resumed) {
|
|
87
|
+
throw new Error('The optimizer could not restore the requested compatible state.')
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
bestCandidate: paid.value.bestCandidate,
|
|
91
|
+
resumed: context.restoreRequested,
|
|
92
|
+
costAccounting: { kind: 'metered' },
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
```
|
|
35
97
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
| `parameterSweepProposer` | Apply declared changes to a JSON config. | JSON string |
|
|
43
|
-
| `fapoProposer` | Try prompt, parameter, and optional structural changes under one escalation policy. | caller-defined |
|
|
98
|
+
Replace the import and field names with the upstream package API.
|
|
99
|
+
`source` records caller-declared package identity.
|
|
100
|
+
`evaluationId` identifies the execution and scoring behavior; use a commit, content hash, or another stable identifier and change it whenever that behavior changes.
|
|
101
|
+
Agent Eval derives the run ID from the complete compatible input instead of requiring a private schema number.
|
|
102
|
+
The callback writes checkpoints under `stateDir`, restores them only when `restoreRequested` is true, and reports whether restoration occurred.
|
|
103
|
+
Agent Eval adds the run ID, evaluation count, artifact directory, source identity, and optimizer token usage to the method result.
|
|
44
104
|
|
|
45
105
|
## Compare Complete Methods
|
|
46
106
|
|
|
47
|
-
|
|
48
|
-
|
|
107
|
+
`compareOptimizationMethods()` gives every method the same:
|
|
108
|
+
|
|
109
|
+
- starting surface,
|
|
110
|
+
- train cases,
|
|
111
|
+
- selection cases,
|
|
112
|
+
- execution function,
|
|
113
|
+
- judges,
|
|
114
|
+
- seed,
|
|
115
|
+
- campaign defaults.
|
|
116
|
+
|
|
117
|
+
An optimization method never receives the final test cases.
|
|
118
|
+
After every method finishes, Agent Eval scores the selected surfaces on the same final cases and reports paired lift estimates.
|
|
49
119
|
|
|
50
120
|
```ts
|
|
51
121
|
import {
|
|
52
|
-
type BuiltinOptimizationMethodConfig,
|
|
53
122
|
compareOptimizationMethods,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
skillOptMethod,
|
|
123
|
+
gepaOptimizationMethod,
|
|
124
|
+
skillOptOptimizationMethod,
|
|
57
125
|
} from '@tangle-network/agent-eval/campaign'
|
|
58
126
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
127
|
+
const optimizer = {
|
|
128
|
+
model: 'gpt-4.1-mini',
|
|
129
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
130
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
131
|
+
budget: {
|
|
132
|
+
maxCostUsd: 5,
|
|
133
|
+
maxRequests: 100,
|
|
134
|
+
maxRequestBytes: 2_000_000,
|
|
135
|
+
maxResponseBytes: 2_000_000,
|
|
136
|
+
maxOutputTokensPerRequest: 32_768,
|
|
137
|
+
pricing: {
|
|
138
|
+
inputUsdPerMillion: Number(process.env.OPTIMIZER_INPUT_USD_PER_MILLION),
|
|
139
|
+
outputUsdPerMillion: Number(process.env.OPTIMIZER_OUTPUT_USD_PER_MILLION),
|
|
140
|
+
},
|
|
141
|
+
},
|
|
66
142
|
}
|
|
67
143
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
144
|
+
const gepa = gepaOptimizationMethod<MyCase, MyArtifact>({
|
|
145
|
+
name: 'gepa',
|
|
146
|
+
objective: 'Improve the instructions so the agent emits valid JSON.',
|
|
147
|
+
evaluationId: 'json-agent',
|
|
148
|
+
recipe: {
|
|
149
|
+
kind: 'engine',
|
|
150
|
+
run: {
|
|
151
|
+
engine: 'gepa',
|
|
152
|
+
maxEvaluations: 40,
|
|
153
|
+
maxProposerCostUsd: 5,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
optimizer,
|
|
157
|
+
describeScenario: (scenario) => ({ input: scenario.input }),
|
|
158
|
+
describeArtifact: (artifact) => ({ output: artifact.output }),
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const skillopt = skillOptOptimizationMethod<MyCase, MyArtifact>({
|
|
162
|
+
name: 'skillopt',
|
|
163
|
+
objective: 'Improve the instructions so the agent emits valid JSON.',
|
|
164
|
+
evaluationId: 'json-agent',
|
|
165
|
+
trainer: {
|
|
166
|
+
epochs: 2,
|
|
167
|
+
batchSize: 4,
|
|
168
|
+
},
|
|
169
|
+
optimizer,
|
|
170
|
+
maxEvaluations: 80,
|
|
171
|
+
describeScenario: (scenario) => ({ input: scenario.input }),
|
|
172
|
+
describeArtifact: (artifact) => ({ output: artifact.output }),
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
const comparison = await compareOptimizationMethods({
|
|
176
|
+
methods: [gepa, skillopt],
|
|
74
177
|
baselineSurface,
|
|
75
178
|
trainScenarios,
|
|
76
179
|
selectionScenarios,
|
|
77
180
|
testScenarios,
|
|
78
181
|
dispatchWithSurface,
|
|
79
182
|
judges,
|
|
80
|
-
runDir,
|
|
183
|
+
runDir: '.agent-eval/optimizer-comparison',
|
|
81
184
|
optimizationRunOptions: {
|
|
82
|
-
costCeiling: 5,
|
|
83
|
-
dispatchTimeoutMs: 60_000,
|
|
84
185
|
maxConcurrency: 4,
|
|
85
186
|
},
|
|
86
|
-
|
|
87
|
-
costCeiling: 2,
|
|
88
|
-
maxConcurrency: 4,
|
|
187
|
+
costCeiling: 23,
|
|
89
188
|
confidence: 0.95,
|
|
90
189
|
})
|
|
91
190
|
```
|
|
92
191
|
|
|
93
|
-
|
|
192
|
+
`costCeiling` is one limit shared by optimizer-model calls, train and selection evaluations, and final test scoring.
|
|
193
|
+
`comparison.scores` contains the final-case baseline score, selected score, lift, simultaneous interval, cost status, duration, and selected surface for each method.
|
|
194
|
+
Official method scores contain optimizer and bridge package versions, source revisions and source-tree hashes, Python runtime, custom engine module hashes, compatible run ID, exact attempt ID, resume status, evaluation count, artifact directory, and available optimizer token usage.
|
|
195
|
+
`comparison.pairwise` compares the highest-ranked method with every other method.
|
|
196
|
+
Ranking follows estimated lift, so inspect intervals before claiming a difference.
|
|
197
|
+
|
|
198
|
+
The runnable version is in [`examples/compare-optimization-methods`](../examples/compare-optimization-methods/).
|
|
199
|
+
|
|
200
|
+
## Install Official GEPA
|
|
201
|
+
|
|
202
|
+
Install the bridge and the source revision tested by this release:
|
|
94
203
|
|
|
95
|
-
|
|
204
|
+
```sh
|
|
205
|
+
python -m pip install agent-eval-rpc
|
|
206
|
+
python -m pip install "gepa[full] @ git+https://github.com/gepa-ai/gepa.git@f919db0a622e2e9f9204779b81fe00cc1b2d808f"
|
|
207
|
+
```
|
|
96
208
|
|
|
97
|
-
`
|
|
98
|
-
|
|
99
|
-
It does not edit a repository, ingest a knowledge base, or replace a retrieval engine.
|
|
209
|
+
The published `gepa==0.1.4` wheel does not contain the required Optimize Anything API.
|
|
210
|
+
The Agent Eval Python package cannot declare a Git dependency in its PyPI metadata, so the GEPA source install is separate.
|
|
100
211
|
|
|
101
|
-
|
|
212
|
+
From this repository:
|
|
102
213
|
|
|
103
214
|
```sh
|
|
104
|
-
|
|
215
|
+
cd clients/python
|
|
216
|
+
uv sync --frozen --group gepa-source
|
|
105
217
|
```
|
|
106
218
|
|
|
107
|
-
|
|
108
|
-
|
|
219
|
+
## Configure GEPA
|
|
220
|
+
|
|
221
|
+
`gepaOptimizationMethod()` accepts text surfaces and component surfaces.
|
|
222
|
+
A component surface has this shape:
|
|
109
223
|
|
|
110
224
|
```ts
|
|
111
|
-
|
|
225
|
+
const baselineSurface = {
|
|
226
|
+
kind: 'components' as const,
|
|
227
|
+
components: {
|
|
228
|
+
planner: 'Plan the task.',
|
|
229
|
+
executor: 'Execute the plan.',
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
```
|
|
112
233
|
|
|
113
|
-
|
|
234
|
+
The `recipe` maps directly to official GEPA operations:
|
|
235
|
+
|
|
236
|
+
| Recipe | Official behavior |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `engine` | Run one registered GEPA engine. |
|
|
239
|
+
| `sequential` | Run engines in order and retain the best result across stages. |
|
|
240
|
+
| `adaptive-sequential` | Switch engines after a configured period without improvement. |
|
|
241
|
+
| `best-of` | Run independent engines and choose the highest selection score. |
|
|
242
|
+
| `vote` | Run independent engines and use GEPA's vote composition. |
|
|
243
|
+
| `omni` | Run official best-of exploration, then continue from its winner. |
|
|
244
|
+
|
|
245
|
+
Each engine run requires `maxEvaluations` and `maxProposerCostUsd`.
|
|
246
|
+
`engineConfig` carries the JSON-safe subset of configuration for the registered GEPA engine.
|
|
247
|
+
GEPA validates the engine name and those values.
|
|
248
|
+
Python callables, classes, custom loggers, and callbacks cannot be serialized through this TypeScript bridge.
|
|
249
|
+
For a custom engine, set `engineModules` to public dotted Python modules that call GEPA's official `register_engine()` function when imported.
|
|
250
|
+
The optimizer process imports those modules before GEPA resolves the engine name.
|
|
251
|
+
|
|
252
|
+
The standard GEPA engine accepts the official `GEPAConfig` fields.
|
|
253
|
+
Give Agent Eval the model, exact endpoint rates, and provider connection separately:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const method = gepaOptimizationMethod({
|
|
257
|
+
objective: 'Improve the complete system prompt.',
|
|
258
|
+
evaluationId: 'support-agent',
|
|
114
259
|
recipe: {
|
|
115
|
-
kind: '
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
260
|
+
kind: 'engine',
|
|
261
|
+
run: {
|
|
262
|
+
engine: 'gepa',
|
|
263
|
+
maxEvaluations: 60,
|
|
264
|
+
maxProposerCostUsd: 8,
|
|
265
|
+
maxConcurrency: 8,
|
|
266
|
+
},
|
|
122
267
|
},
|
|
123
|
-
|
|
124
|
-
|
|
268
|
+
optimizer: {
|
|
269
|
+
model: 'gpt-4.1-mini',
|
|
270
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
271
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
272
|
+
budget: {
|
|
273
|
+
maxCostUsd: 8,
|
|
274
|
+
maxRequests: 100,
|
|
275
|
+
maxRequestBytes: 2_000_000,
|
|
276
|
+
maxResponseBytes: 2_000_000,
|
|
277
|
+
maxOutputTokensPerRequest: 32_768,
|
|
278
|
+
pricing: {
|
|
279
|
+
inputUsdPerMillion: 0.4,
|
|
280
|
+
outputUsdPerMillion: 1.6,
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
describeScenario: (scenario) => ({ input: scenario.input }),
|
|
285
|
+
describeArtifact: (artifact) => ({ output: artifact.output }),
|
|
125
286
|
})
|
|
287
|
+
```
|
|
126
288
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
289
|
+
Replace the rates with the exact rates charged by your endpoint.
|
|
290
|
+
With `optimizer`, every recipe stage must use the standard `gepa` engine.
|
|
291
|
+
Agent Eval keeps the provider key outside Python, enforces the shared model budget, and records exact provider usage.
|
|
292
|
+
`maxProposerCostUsd` also limits each individual GEPA engine stage.
|
|
293
|
+
|
|
294
|
+
Other official engines can still receive their own settings:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
recipe: {
|
|
298
|
+
kind: 'engine',
|
|
299
|
+
run: {
|
|
300
|
+
engine: 'autoresearch',
|
|
301
|
+
maxEvaluations: 60,
|
|
302
|
+
maxProposerCostUsd: 8,
|
|
303
|
+
engineConfig: {
|
|
304
|
+
command: ['python', 'run_research.py'],
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
}
|
|
137
308
|
```
|
|
138
309
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
310
|
+
Their external model spend remains incomplete unless that engine reports it.
|
|
311
|
+
Keep API keys in environment variables or `runner.env`.
|
|
312
|
+
Do not place credentials in `engineConfig` because run settings are persisted.
|
|
313
|
+
|
|
314
|
+
`describeScenario()` controls the train and selection data sent to GEPA.
|
|
315
|
+
`describeArtifact()` controls the execution evidence returned after a candidate is scored.
|
|
316
|
+
Neither callback can receive a final test case.
|
|
143
317
|
|
|
144
|
-
|
|
145
|
-
Final test cases remain in `compareOptimizationMethods()` and are first scored after GEPA exits.
|
|
146
|
-
By default, GEPA starts in its empty run directory.
|
|
147
|
-
Do not set `runner.cwd` to a location that contains final cases.
|
|
318
|
+
## Install Official SkillOpt
|
|
148
319
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
320
|
+
Install the SkillOpt source revision tested by this release:
|
|
321
|
+
|
|
322
|
+
```sh
|
|
323
|
+
python -m pip install agent-eval-rpc
|
|
324
|
+
python -m pip install \
|
|
325
|
+
"skillopt @ git+https://github.com/microsoft/SkillOpt.git@61735e3922efc2b90c6d6cab561e62e98452ca90"
|
|
326
|
+
```
|
|
153
327
|
|
|
154
|
-
|
|
155
|
-
`engineConfig` is passed directly to that GEPA engine.
|
|
156
|
-
The bridge accepts any trimmed engine string and GEPA validates whether it is installed or registered.
|
|
328
|
+
From this repository:
|
|
157
329
|
|
|
158
|
-
|
|
159
|
-
|
|
330
|
+
```sh
|
|
331
|
+
cd clients/python
|
|
332
|
+
uv sync --frozen --group skillopt-source
|
|
333
|
+
```
|
|
160
334
|
|
|
161
|
-
|
|
335
|
+
The published `skillopt==0.2.0` wheel omits the prompt files required by `ReflACTTrainer`.
|
|
336
|
+
The tested source revision contains all 21 files.
|
|
162
337
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
338
|
+
`skillOptOptimizationMethod()` runs SkillOpt's official `ReflACTTrainer`.
|
|
339
|
+
Agent Eval supplies an environment adapter that sends each candidate and case back to the TypeScript execution and judging path.
|
|
340
|
+
SkillOpt's own test evaluation is disabled.
|
|
341
|
+
This integration uses SkillOpt's OpenAI-compatible optimizer backend so every model call can pass through the metered proxy.
|
|
342
|
+
Use SkillOpt directly when you need one of its CLI or provider-specific backends.
|
|
168
343
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
344
|
+
`maxEvaluations` is a hard callback limit, not a prediction of SkillOpt's internal work.
|
|
345
|
+
The official trainer decides how many rollouts each enabled phase needs.
|
|
346
|
+
The callback rejects the first request beyond the declared limit, including work from slow updates or meta-skill phases.
|
|
172
347
|
|
|
173
|
-
|
|
174
|
-
The
|
|
175
|
-
|
|
176
|
-
When `optimizationConcurrency` is greater than one, the shared runner and judges must support concurrent calls.
|
|
348
|
+
SkillOpt connects to a local proxy rather than receiving the provider key.
|
|
349
|
+
The proxy enforces the declared model limits before each call and records provider token usage at the rates supplied in `optimizer.budget`.
|
|
350
|
+
Missing token usage, an oversized request or response, a wrong model, streaming, and a call beyond budget all fail loudly.
|
|
177
351
|
|
|
178
|
-
##
|
|
352
|
+
## Use Official DSPy Optimizers
|
|
179
353
|
|
|
180
|
-
|
|
181
|
-
`
|
|
182
|
-
Top-level `maxConcurrency` controls scenario calls during final test scoring.
|
|
354
|
+
Do not convert a DSPy program into an `OptimizationMethod`.
|
|
355
|
+
Install `agent-eval-rpc[dspy]`, create `DspyJudgeMetric`, and pass it to official DSPy:
|
|
183
356
|
|
|
184
|
-
|
|
185
|
-
|
|
357
|
+
```python
|
|
358
|
+
import dspy
|
|
186
359
|
|
|
187
|
-
|
|
360
|
+
from agent_eval_rpc import DspyJudgeMetric
|
|
188
361
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
362
|
+
metric = DspyJudgeMetric(rubric_name="answer-quality")
|
|
363
|
+
gepa = dspy.GEPA(
|
|
364
|
+
metric=metric.feedback,
|
|
365
|
+
reflection_lm=dspy.LM("openai/gpt-4.1-mini"),
|
|
366
|
+
max_metric_calls=100,
|
|
367
|
+
)
|
|
368
|
+
mipro = dspy.MIPROv2(metric=metric, auto="light")
|
|
193
369
|
```
|
|
194
370
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
371
|
+
This keeps program compilation, traces, demos, and optimizer state inside DSPy.
|
|
372
|
+
Agent Eval supplies the shared rubric and returns rich feedback for `dspy.GEPA`.
|
|
373
|
+
DSPy 3.2.1 requires GEPA 0.0.27.
|
|
374
|
+
Run it in a separate Python environment from the general GEPA bridge, which uses GEPA 0.1.4.
|
|
198
375
|
|
|
199
|
-
##
|
|
376
|
+
## Resume A Compatible Run
|
|
200
377
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
interval: method.liftCi,
|
|
208
|
-
scenarios: method.scenarioScores,
|
|
209
|
-
optimizationCostUsd: method.optimizationCost.totalCostUsd,
|
|
210
|
-
costComplete: method.optimizationCost.accountingComplete,
|
|
211
|
-
})
|
|
212
|
-
}
|
|
213
|
-
```
|
|
378
|
+
Both official methods default to `resume: 'never'`.
|
|
379
|
+
Use `resume: 'if-compatible'` to restore matching SkillOpt state or a matching direct GEPA engine.
|
|
380
|
+
Use `resume: 'required'` when missing or incompatible state should fail.
|
|
381
|
+
Direct GEPA resume also requires `trustResumeState: true` because upstream checkpoints use Python pickle.
|
|
382
|
+
Set it only for checkpoints created locally in a directory you control.
|
|
383
|
+
Composed GEPA recipes restart and never report that official state was restored.
|
|
214
384
|
|
|
215
|
-
|
|
216
|
-
It does not mean the higher-ranked method is conclusively better.
|
|
217
|
-
Read `liftCi` and `comparison.pairwise[].favored` before making that claim.
|
|
218
|
-
`scenarioScores` contains the paired values used to compute each method's result.
|
|
385
|
+
A match includes:
|
|
219
386
|
|
|
220
|
-
|
|
221
|
-
|
|
387
|
+
- optimizer and bridge package versions, revisions, and source-tree hashes,
|
|
388
|
+
- Python runtime and custom engine module hashes,
|
|
389
|
+
- recipe or trainer settings,
|
|
390
|
+
- starting surface,
|
|
391
|
+
- train and selection descriptions,
|
|
392
|
+
- evaluation ID for execution and scoring behavior,
|
|
393
|
+
- seed,
|
|
394
|
+
- limits that affect the run.
|
|
222
395
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
396
|
+
Use a commit, content hash, or another stable value for `evaluationId`.
|
|
397
|
+
Change it whenever dispatch behavior, judges, model settings, or scoring logic changes.
|
|
398
|
+
Concurrent processes cannot write the same compatible run at the same time.
|
|
226
399
|
|
|
227
|
-
|
|
228
|
-
If you choose a method from this result and later claim its deployed effect, confirm that claim on new data that was not used for this ranking.
|
|
400
|
+
## Write A Custom Candidate Generator
|
|
229
401
|
|
|
230
|
-
|
|
402
|
+
Use `SurfaceProposer` when your code or runtime owns candidate creation.
|
|
403
|
+
The proposer receives the current surface, prior campaign history, findings, generation number, requested population size, and cancellation signal.
|
|
231
404
|
|
|
232
|
-
|
|
233
|
-
|
|
405
|
+
```ts
|
|
406
|
+
import type { SurfaceProposer } from '@tangle-network/agent-eval/campaign'
|
|
407
|
+
|
|
408
|
+
const proposer: SurfaceProposer = {
|
|
409
|
+
kind: 'product-rules',
|
|
410
|
+
async propose({ currentSurface, populationSize }) {
|
|
411
|
+
const prompt = String(currentSurface)
|
|
412
|
+
return [
|
|
413
|
+
{
|
|
414
|
+
surface: `${prompt}\nReturn JSON only.`,
|
|
415
|
+
label: 'json-only',
|
|
416
|
+
rationale: 'Training failures included prose around the JSON object.',
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
surface: `${prompt}\nInclude every required field, using null when unknown.`,
|
|
420
|
+
label: 'required-fields',
|
|
421
|
+
rationale: 'Training failures omitted fields.',
|
|
422
|
+
},
|
|
423
|
+
].slice(0, populationSize)
|
|
424
|
+
},
|
|
425
|
+
}
|
|
426
|
+
```
|
|
234
427
|
|
|
235
|
-
|
|
236
|
-
|
|
428
|
+
Return a label and rationale when they will help later analysis.
|
|
429
|
+
Candidate creation must not read final test results.
|
|
237
430
|
|
|
238
|
-
|
|
431
|
+
## Data And Cost Rules
|
|
239
432
|
|
|
240
|
-
|
|
433
|
+
- Train and selection cases are visible to complete optimization methods.
|
|
434
|
+
- Train and selection cases may influence candidate generation, selection, and stopping.
|
|
435
|
+
- Final test cases may only compare surfaces after every method finishes.
|
|
436
|
+
- The same dispatch and judges score every method.
|
|
437
|
+
- Missing cost remains unknown.
|
|
438
|
+
- A method must declare bounded work before it starts.
|
|
439
|
+
- Credentials belong in process environment variables.
|
|
440
|
+
- Resumed state must match every input that can change the result.
|
|
241
441
|
|
|
242
|
-
|
|
243
|
-
- Do not let a custom `OptimizationMethod` load final test rows from another source.
|
|
244
|
-
- Do not compare methods with different runners, judges, or final test scenarios.
|
|
245
|
-
- Do not read `method.optimizationCost` as total comparison cost.
|
|
246
|
-
- Do not report a dollar total as complete when `accountingComplete` is false.
|
|
247
|
-
- Do not reuse the final test set for repeated method selection and continue calling it untouched.
|
|
442
|
+
These rules make method comparisons inspectable without pretending different optimizers have identical internals.
|