Metadata-Version: 2.4
Name: synth-optimizers
Version: 0.1.0
Summary: Apache-2.0 prompt optimization package with GEPA and MIPRO adapters.
Author: Synth
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/synth-laboratories/optimizers
Project-URL: Repository, https://github.com/synth-laboratories/optimizers
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic<3,>=2
Dynamic: license-file

# synth-optimizers

Apache-2.0 licensed Python-only prompt optimization package that provides a local offline mirror of the public Synth GEPA/MIPRO SDK surfaces under `prompt_opt.sdk.optimization.*`.

Repo name: `optimizers`

PyPI distribution name: `synth-optimizers`

Python import package: `prompt_opt`

## What is included

- `prompt_opt.sdk.optimization.policy.v1.PolicyOptimizationOfflineJob`
  - Local drop-in offline job surface for `gepa_offline` and `mipro_offline`.
- `prompt_opt.sdk.optimization.internal.prompt_learning.PromptLearningJob`
  - High-level local prompt-learning wrapper with candidate/state accessors.
- `prompt_opt.sdk.optimization.internal.configs.prompt_learning.PromptLearningConfig`
  - Canonical prompt-learning config models with local `proxied -> retrieved` normalization.
- `prompt_opt.dspy.MIPROv2`
  - DSPy-compatible local MIPRO wrapper routed through the mirrored offline SDK.
- `prompt_opt.dspy.gepa`
  - GEPA slot-in wrapper routed through the same local offline runtime.
- `prompt_opt.adapters.synth_container`
  - Container request/response helpers for local rollout integration.
- `src/gepa/__init__.py`
  - Import compatibility shim so `import gepa` works against this package.

## Install (editable, local)

```bash
cd optimizers
pip install -e .
```

## Quick usage

```python
from prompt_opt.sdk.optimization.policy.v1 import PolicyOptimizationOfflineJob

def task_model(prompt: str) -> str:
    if "Return exactly one of" in prompt:
        return "paris"
    return "rome"

job = PolicyOptimizationOfflineJob.create(
    kind="mipro_offline",
    system_name="cities-local",
    backend_url="local://prompt-opt",
    api_key="local",
    config={
        "prompt_learning": {
            "algorithm": "mipro",
            "execution_mode": "retrieved",
            "task_data": {
                "train_examples": [{"input": "Capital of France?", "answer": "paris"}],
                "validation_examples": [{"input": "Capital of France?", "answer": "paris"}],
            },
            "mipro": {
                "initial_candidate": {
                    "stages": [
                        {
                            "id": "main",
                            "name": "main",
                            "messages": [
                                {"role": "system", "pattern": "Answer the question.", "order": 0},
                                {"role": "user", "pattern": "{input}", "order": 1},
                            ],
                        }
                    ]
                },
                "num_candidates": 4,
                "max_iterations": 3,
            },
            "local_runtime": {"task_model": task_model},
        }
    },
)

result = job.stream_until_complete(timeout=30.0, interval=0.05)
best_candidate = job.get_state_envelope()["state"]["candidates"][result["best_candidate_id"]]
print(best_candidate["candidate_content"])
```

## GEPA shim

```python
from gepa import optimize
from prompt_opt.adapters.synth_offline import LocalEvaluator, SynthOfflineLearningAdapter

def score_fn(example, candidate):
    expected = str(example.get("answer", "")).strip().lower()
    prompt = " ".join(candidate.values()).lower()
    return 1.0 if expected and expected in prompt else 0.0

adapter = SynthOfflineLearningAdapter(LocalEvaluator(score_fn=score_fn))
result = optimize(
    seed_candidate={"system_prompt": "Answer briefly."},
    trainset=[{"input": "Capital of France?", "answer": "paris"}],
    adapter=adapter,
    max_metric_calls=8,
)

print(result.best_candidate)
print(result.val_aggregate_scores[result.best_idx])
```

## Notes

- This package is local-only and does not call Synth backend APIs.
- Runtime execution is retrieval-based only. Hosted configs that specify `execution_mode="proxied"` are accepted and normalized to `retrieved` locally.
- The local runtime mirrors candidate/state/result payloads and offline job methods; `backend_url` and `api_key` are kept for signature parity but are inert in local mode.
- Multi-stage candidates are first-class in both local GEPA and local MIPRO.

## Examples

- Local DSPy MIPRO:
  - `examples/mipro_local_example.py`
- DSPy GEPA slot-in:
  - `examples/dspy_gepa_slot_example.py`
- Local offline Banking77 via Synth `InProcessContainer`:
  - `examples/banking77_container_example.py`
- Local offline GEPA for simple JSONL task files:
  - `examples/gepa_taskfile_container_eval.py`

Run the Banking77 local regression harness with:

```bash
PYTHONPATH=/Users/joshpurtell/Documents/GitHub/prompt-opt/src:/Users/joshpurtell/Documents/GitHub/synth-ai \
uv run --active /Users/joshpurtell/Documents/GitHub/prompt-opt/examples/banking77_container_example.py \
  --algorithms gepa mipro \
  --train-per-label 4 \
  --held-out-per-label 2 \
  --num-generations 2 \
  --children-per-generation 8 \
  --num-candidates 8 \
  --max-iterations 6
```

The script prints per-algorithm JSON with `train_*` and `held_out_*` metrics and raises if held-out improvement does not exceed `--min-held-out-delta`.

Find simple eval task files and run local taskfile optimization (`gepa` or `mipro`) with:

```bash
PYTHONPATH=/Users/joshpurtell/Documents/GitHub/prompt-opt/src:/Users/joshpurtell/Documents/GitHub/synth-ai \
uv run --active /Users/joshpurtell/Documents/GitHub/prompt-opt/examples/gepa_taskfile_container_eval.py \
  --list-simple-task-files
```

```bash
PYTHONPATH=/Users/joshpurtell/Documents/GitHub/prompt-opt/src:/Users/joshpurtell/Documents/GitHub/synth-ai \
uv run --active /Users/joshpurtell/Documents/GitHub/prompt-opt/examples/gepa_taskfile_container_eval.py \
  --algorithm gepa \
  --task-file /Users/joshpurtell/Documents/GitHub/evals/mipro/tasks/multimodal_smoke.jsonl \
  --train-size 8 \
  --num-generations 2 \
  --children-per-generation 8 \
  --total-rollouts 64
```

```bash
PYTHONPATH=/Users/joshpurtell/Documents/GitHub/prompt-opt/src:/Users/joshpurtell/Documents/GitHub/synth-ai \
uv run --active /Users/joshpurtell/Documents/GitHub/prompt-opt/examples/gepa_taskfile_container_eval.py \
  --algorithm gepa \
  --task-preset medec_smoke \
  --train-size 8 \
  --num-generations 2 \
  --children-per-generation 8 \
  --total-rollouts 64
```

```bash
PYTHONPATH=/Users/joshpurtell/Documents/GitHub/prompt-opt/src:/Users/joshpurtell/Documents/GitHub/synth-ai \
uv run --active /Users/joshpurtell/Documents/GitHub/prompt-opt/examples/gepa_taskfile_container_eval.py \
  --algorithm mipro \
  --task-preset drugprot_train_public \
  --train-size 8 \
  --num-candidates 24 \
  --max-iterations 16 \
  --children-per-generation 16 \
  --total-rollouts 256
```

```bash
PYTHONPATH=/Users/joshpurtell/Documents/GitHub/prompt-opt/src:/Users/joshpurtell/Documents/GitHub/synth-ai \
uv run --active /Users/joshpurtell/Documents/GitHub/prompt-opt/examples/gepa_taskfile_container_eval.py \
  --algorithm mipro \
  --task-preset langprobe_banking77 \
  --train-size 32 \
  --num-candidates 12 \
  --max-iterations 8 \
  --children-per-generation 8 \
  --total-rollouts 96
```

```bash
PYTHONPATH=/Users/joshpurtell/Documents/GitHub/prompt-opt/src:/Users/joshpurtell/Documents/GitHub/synth-ai \
uv run --active /Users/joshpurtell/Documents/GitHub/prompt-opt/examples/gepa_taskfile_container_eval.py \
  --algorithm mipro \
  --task-preset langprobe_hotpotqa \
  --train-size 8 \
  --num-candidates 8 \
  --max-iterations 6 \
  --children-per-generation 8 \
  --total-rollouts 64
```

```bash
PYTHONPATH=/Users/joshpurtell/Documents/GitHub/prompt-opt/src:/Users/joshpurtell/Documents/GitHub/synth-ai \
uv run --active /Users/joshpurtell/Documents/GitHub/prompt-opt/examples/gepa_taskfile_container_eval.py \
  --algorithm mipro \
  --task-preset langprobe_iris \
  --train-size 60 \
  --num-candidates 10 \
  --max-iterations 8 \
  --children-per-generation 8 \
  --total-rollouts 96
```

```bash
PYTHONPATH=/Users/joshpurtell/Documents/GitHub/prompt-opt/src:/Users/joshpurtell/Documents/GitHub/synth-ai \
uv run --active /Users/joshpurtell/Documents/GitHub/prompt-opt/examples/gepa_taskfile_container_eval.py \
  --algorithm mipro \
  --task-preset langprobe_hover \
  --train-size 60 \
  --num-candidates 10 \
  --max-iterations 8 \
  --children-per-generation 8 \
  --total-rollouts 96
```
