Optimize Anything Omni¶
pydantic_gepa.experimental.optimize_anything adapts GEPA Optimize Anything
Omni to the same typed examples, application bindings, evaluation, candidates,
and normalized results used by the standard pydantic-gepa backend.
Use this backend when you need to choose or compose optimization engines. Keep
the standard backend when one direct GEPA run is sufficient. The namespace is
experimental because upstream engine and composition APIs are still evolving;
the common Example, Candidate, Component, evaluation, and result models
remain the package boundary.
Install¶
The full extra includes upstream agent-engine dependencies:
uv add "pydantic-gepa[optimize-anything]"
The lighter integrations extra is enough for standard GEPA and for custom
Optimize Anything engines that do not need the built-in agent engines.
First Engine Run¶
Build the application-facing optimization exactly once. Pydantic Evals stays inside pydantic-gepa:
from pydantic_gepa import Component, DataSplit, Example, Optimization
from pydantic_gepa.experimental.optimize_anything import (
Engine,
OptimizeAnythingConfig,
)
instructions = Component(
name="instructions",
initial_text="Classify the request.",
)
data = DataSplit.from_sets(
train=[Example(name="train-1", inputs="refund order", expected_output="refund")],
validation=[
Example(name="val-1", inputs="money back please", expected_output="refund")
],
test=[Example(name="test-1", inputs="return purchase", expected_output="refund")],
)
optimization = Optimization.from_examples(
data=data,
task=run_application,
score=lambda ctx: float(ctx.output == ctx.expected_output),
components=[instructions],
injections=[instructions_injection],
backend="optimize_anything",
optimization_objective="Maximize routing accuracy.",
background="The candidate controls the application instructions.",
)
result = optimization.optimize(
config=OptimizeAnythingConfig(
engine=Engine.gepa(
gepa_config,
candidate_mode="text",
stop_at_score=1.0,
),
component="instructions",
)
)
data.test is held out from every optimizer engine. Pydantic-gepa evaluates
the seed and final candidate on it after optimization and records those calls
separately in result.scores and result.budget.
The complete executable version is
examples/experimental_optimize_anything.py.
It uses a local Pydantic AI function model and requires no API key.
Typed Engines¶
One Engine describes one bounded optimizer execution. Constructors expose
engine-specific settings directly; unknown settings cannot hide in a loose
dictionary.
GEPA¶
gepa = Engine.gepa(
GEPAConfig(...),
candidate_mode="components",
max_evals=100,
max_token_cost=5.0,
max_concurrency=8,
stop_at_score=0.95,
output_dir="runs/output",
run_dir="runs/gepa-state",
)
GEPA is the only built-in engine that can optimize a complete
dict[str, str] component candidate. Set candidate_mode="text" when it must
share a composition with text-only engines.
AutoResearch¶
autoresearch = Engine.autoresearch(
model="claude-sonnet-4-6",
max_evals=30,
max_token_cost=8.0,
max_concurrency=4,
sandbox=True,
)
AutoResearch is text-only. Its CLI, provider credentials, and operating-system sandbox prerequisites are runtime requirements; installing the Python extra does not configure external executables or credentials.
Meta-Harness¶
meta = Engine.meta_harness(
model="claude-sonnet-4-6",
max_iterations=4,
max_candidates_per_iteration=3,
max_evals=30,
max_token_cost=8.0,
)
Best-of-N¶
sampler = Engine.best_of_n(
model="provider:model",
temperature=0.8,
max_samples=6,
max_evals=30,
max_token_cost=4.0,
)
Custom Engine¶
A custom engine implements the upstream-compatible run and
process_result contract. It can evaluate through the shared server without
knowing Pydantic Evals:
from dataclasses import dataclass
from pathlib import Path
from typing import cast
from gepa.oa.engine import Result
from pydantic_gepa.experimental.optimize_anything import (
Engine,
EngineResult,
EvaluationServer,
OptimizationTask,
)
@dataclass
class CandidateEngine:
name: str
candidate: str
def run(self, task: OptimizationTask, server: EvaluationServer) -> EngineResult:
del task
score, evidence = server.evaluate_examples(self.candidate, split="val")
return cast(
"EngineResult",
Result(
best_candidate=self.candidate,
best_score=score,
metadata={"validation_evidence": evidence},
),
)
def process_result(self, result: EngineResult, output_dir: Path | None) -> None:
del result, output_dir
engine = Engine.custom(
CandidateEngine("candidate-generator", "Use the improved policy."),
candidate_mode="text",
max_evals=10,
fingerprint="candidate-engine-v1",
)
Use split="train", split="val", or split="all". Test examples are
intentionally unavailable through the engine-facing server.
Composition¶
Pass one composition instead of engine. All engines in one composition
must use the same candidate mode.
Sequential¶
from pydantic_gepa.experimental.optimize_anything import Sequential
config = OptimizeAnythingConfig(
composition=Sequential(engines=(first, second, third)),
component="instructions",
)
Engines run in order. The running best candidate seeds the next engine, so a regressing stage does not replace an earlier improvement.
Parallel¶
from pydantic_gepa.experimental.optimize_anything import Parallel
config = OptimizeAnythingConfig(
composition=Parallel(engines=(first, second), max_workers=2),
component="instructions",
)
Every engine receives the same parent. The result preserves all sibling branches and deliberately selects no winner.
Best Of¶
from pydantic_gepa.experimental.optimize_anything import BestOf
config = OptimizeAnythingConfig(
composition=BestOf(engines=(first, second), max_workers=2),
component="instructions",
)
Best-of runs sibling engines and selects the greatest normalized search score.
Vote¶
from pydantic_gepa.experimental.optimize_anything import Vote
config = OptimizeAnythingConfig(
composition=Vote(engines=(first, second), max_workers=2),
component="instructions",
)
Vote performs a fair validation rescore after the engine runs. Search scores
remain in EngineRunSummary.search_score; vote scores remain in
selection_score and SelectionSummary. Selection evaluation calls are not
charged to an individual engine.
Adaptive Sequential¶
from pydantic_gepa.experimental.optimize_anything import AdaptiveSequential
config = OptimizeAnythingConfig(
composition=AdaptiveSequential(
engines=(first, second),
plateau_evals=5,
max_evals=30,
patience=2,
improvement_epsilon=0.01,
cycle=True,
),
component="instructions",
)
Adaptive sequential shares one evaluation budget and records each engine slice, score transition, switch, and stop reason.
Omni Pipeline¶
Omni is a composition recipe, not a special engine. Explore with BestOf,
then continue from the winner with a fresh engine:
from pydantic_gepa.experimental.optimize_anything import BestOf, Pipeline, Single
omni = Pipeline(
steps=(
BestOf(engines=(gepa_explorer, autoresearch, meta_harness)),
Single(engine=gepa_continuation),
)
)
result = optimization.optimize(
config=OptimizeAnythingConfig(
composition=omni,
component="instructions",
)
)
Each step receives the selected output of the preceding step. Parallel
cannot appear directly in a pipeline because it has no selected output; use
BestOf or Vote at that boundary.
Candidate Modes¶
componentspasses the complete component mapping. GEPA supports this mode.textunwraps exactly one selected component and merges the returned text back into the full candidate.- Set
component="name"when a text engine should optimize one component while preserving its siblings. - Mixing text and component engines in one composition fails before any model, agent, subprocess, or evaluator work starts.
There is no implicit JSON, multi-file, or prompt flattening format.
Budgets And Cost¶
Optimize Anything separates two resources:
| Field | Meaning |
|---|---|
max_evals |
evaluator calls available to an engine or shared scheduler |
max_token_cost |
optimizer/proposer spend cap |
evaluation_cost |
optional evaluator-side cost |
final_rescore_calls |
vote selection work outside engine budgets |
heldout_evaluation_calls |
post-run protected test work |
Missing cost remains None; it is not converted to zero. Parallel, sequential,
best-of, and vote engines retain their own budgets. Adaptive sequential owns a
shared evaluation pool while each engine keeps its optimizer-cost cap.
Durable Runs¶
Use the common RunConfig:
from pydantic_gepa import RunConfig
config = OptimizeAnythingConfig(
composition=omni,
component="instructions",
run=RunConfig(
id="support-omni",
directory="runs/support-omni",
resume="if_exists",
),
)
Pydantic-gepa checkpoints completed pipeline steps and stores normalized
results. Resume compatibility includes the candidate, datasets, objective,
background, engine declarations, composition, budgets, package version, and
GEPA version. Use fresh=True to reset only a directory already owned by
pydantic-gepa. A completed checkpoint without its result artifact is treated
as corruption rather than silently rerun.
Events And Progress¶
TrackingConfig observers receive the same typed event stream as the standard
backend, plus composition and engine correlation:
from pydantic_gepa.configuration import TrackingConfig
events = []
config = OptimizeAnythingConfig(
engine=engine,
component="instructions",
tracking=TrackingConfig(observers=(events.append,)),
)
Events identify run, pipeline, step, branch, engine execution, candidate, evaluation, selection, budget, checkpoint, and held-out rescore boundaries. GEPA engines additionally expose iteration, proposal, reflection, merge, and Pareto detail through the package-owned callback bridge.
Results¶
Persist result.stable_dump(), not upstream runtime objects:
payload = result.stable_dump()
best = result.best_candidate
score = result.best_score
budget = result.budget
composition = result.composition
For composed runs, result.composition contains deterministic engine order,
input/output candidate lineage, search and selection scores, branch and step
IDs, adaptive schedules, artifact references, and normalized budgets. Raw GEPA
objects are excluded from stable serialization and checkpoints.
Legacy Migration¶
The old experimental call still works for one deprecation cycle:
result = optimization.optimize(config=GEPAConfig(...))
It emits DeprecationWarning and means exactly
Single(Engine.gepa(config=legacy_config)). Migrate to:
result = optimization.optimize(
config=OptimizeAnythingConfig(
engine=Engine.gepa(legacy_config),
)
)
The standard PydanticGEPAOptimizer and standard GEPAConfig API are not
deprecated.
Failure Checklist¶
- Install
pydantic-gepa[optimize-anything]when a built-in agent engine is missing. - Use canonical split name
"val", not"validation", in custom engines. - Add an explicit
componentfor text engines when the candidate has multiple values. - Give every engine
max_evals,max_token_cost, or both. - Do not put an unselected
Parallelstep insidePipeline. - Use distinct validation data; test data is never available to engines.
- Treat an incompatible resume error as evidence that the run definition changed; do not bypass the fingerprint.