orizu 0.5.19 → 0.5.21
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/README.md +23 -0
- package/dist/archive-artifacts-cli.d.ts +50 -0
- package/dist/archive-artifacts-cli.d.ts.map +1 -0
- package/dist/artifact-archive.d.ts +48 -0
- package/dist/artifact-archive.d.ts.map +1 -0
- package/dist/artifact-pull.d.ts +22 -0
- package/dist/artifact-pull.d.ts.map +1 -0
- package/dist/banner.d.ts +3 -0
- package/dist/banner.d.ts.map +1 -0
- package/dist/connectors-cli.d.ts +31 -0
- package/dist/connectors-cli.d.ts.map +1 -0
- package/dist/credentials.d.ts +46 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/csv.d.ts +49 -0
- package/dist/csv.d.ts.map +1 -0
- package/dist/dataset-download.d.ts +6 -0
- package/dist/dataset-download.d.ts.map +1 -0
- package/dist/dataset-edit-rows.d.ts +10 -0
- package/dist/dataset-edit-rows.d.ts.map +1 -0
- package/dist/dataset-upload-snapshot.d.ts +13 -0
- package/dist/dataset-upload-snapshot.d.ts.map +1 -0
- package/dist/daytona-slice-rehearsal.d.ts +89 -0
- package/dist/daytona-slice-rehearsal.d.ts.map +1 -0
- package/dist/daytona-slice.d.ts +106 -0
- package/dist/daytona-slice.d.ts.map +1 -0
- package/dist/deploy-key-merge-core.d.ts +121 -0
- package/dist/deploy-key-merge-core.d.ts.map +1 -0
- package/dist/diff-comments-cli.d.ts +12 -0
- package/dist/diff-comments-cli.d.ts.map +1 -0
- package/dist/diff-comments-cli.js +21 -2
- package/dist/egress-allowlist-cli.d.ts +23 -0
- package/dist/egress-allowlist-cli.d.ts.map +1 -0
- package/dist/egress-policy.d.ts +108 -0
- package/dist/egress-policy.d.ts.map +1 -0
- package/dist/error-response.d.ts +9 -0
- package/dist/error-response.d.ts.map +1 -0
- package/dist/file-parser.d.ts +5 -0
- package/dist/file-parser.d.ts.map +1 -0
- package/dist/gepa-engine-dispatch.d.ts +9 -0
- package/dist/gepa-engine-dispatch.d.ts.map +1 -0
- package/dist/gepa-engine-dispatch.js +307 -0
- package/dist/gepa-python-command.d.ts +6 -0
- package/dist/gepa-python-command.d.ts.map +1 -0
- package/dist/gepa-python-command.js +26 -0
- package/dist/gepa-python-paths.d.ts +6 -0
- package/dist/gepa-python-paths.d.ts.map +1 -0
- package/dist/gepa-python-paths.js +42 -0
- package/dist/git-credential.d.ts +69 -0
- package/dist/git-credential.d.ts.map +1 -0
- package/dist/git-recovery-environment.d.ts +31 -0
- package/dist/git-recovery-environment.d.ts.map +1 -0
- package/dist/github-setup.d.ts +122 -0
- package/dist/github-setup.d.ts.map +1 -0
- package/dist/global-flags.d.ts +12 -0
- package/dist/global-flags.d.ts.map +1 -0
- package/dist/help.d.ts +46 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +93 -25
- package/dist/hosted-boot.d.ts +273 -0
- package/dist/hosted-boot.d.ts.map +1 -0
- package/dist/hosted-bootstrap-rehearsal.d.ts +86 -0
- package/dist/hosted-bootstrap-rehearsal.d.ts.map +1 -0
- package/dist/hosted-bootstrap.d.ts +188 -0
- package/dist/hosted-bootstrap.d.ts.map +1 -0
- package/dist/hosted-broker-server.d.ts +50 -0
- package/dist/hosted-broker-server.d.ts.map +1 -0
- package/dist/hosted-git-http-server.d.ts +36 -0
- package/dist/hosted-git-http-server.d.ts.map +1 -0
- package/dist/hosted-harness-claude.d.ts +195 -0
- package/dist/hosted-harness-claude.d.ts.map +1 -0
- package/dist/hosted-harness-opencode.d.ts +227 -0
- package/dist/hosted-harness-opencode.d.ts.map +1 -0
- package/dist/hosted-harness.d.ts +118 -0
- package/dist/hosted-harness.d.ts.map +1 -0
- package/dist/hosted-harvest.d.ts +68 -0
- package/dist/hosted-harvest.d.ts.map +1 -0
- package/dist/hosted-headless.d.ts +72 -0
- package/dist/hosted-headless.d.ts.map +1 -0
- package/dist/hosted-loop-lifecycle.d.ts +206 -0
- package/dist/hosted-loop-lifecycle.d.ts.map +1 -0
- package/dist/hosted-loop.d.ts +170 -0
- package/dist/hosted-loop.d.ts.map +1 -0
- package/dist/hosted-run-event-sink.d.ts +222 -0
- package/dist/hosted-run-event-sink.d.ts.map +1 -0
- package/dist/hosted-runtime-assets.d.ts +186 -0
- package/dist/hosted-runtime-assets.d.ts.map +1 -0
- package/dist/hosted-session-cli.d.ts +258 -0
- package/dist/hosted-session-cli.d.ts.map +1 -0
- package/dist/hosted-skill-staging.d.ts +73 -0
- package/dist/hosted-skill-staging.d.ts.map +1 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +119 -129
- package/dist/instruction-set-gepa-launch.d.ts +3 -0
- package/dist/instruction-set-gepa-launch.d.ts.map +1 -0
- package/dist/instruction-set-gepa-launch.js +15 -0
- package/dist/instruction-set-loader.d.ts +6 -0
- package/dist/instruction-set-loader.d.ts.map +1 -0
- package/dist/instruction-set-loader.js +59 -0
- package/dist/instruction-set-manifest.d.ts +14 -0
- package/dist/instruction-set-manifest.d.ts.map +1 -0
- package/dist/instruction-set-manifest.js +162 -0
- package/dist/instruction-sets-cli.d.ts +40 -0
- package/dist/instruction-sets-cli.d.ts.map +1 -0
- package/dist/instruction-sets-cli.js +361 -0
- package/dist/json-response.d.ts +4 -0
- package/dist/json-response.d.ts.map +1 -0
- package/dist/jsonl-stream.d.ts +8 -0
- package/dist/jsonl-stream.d.ts.map +1 -0
- package/dist/kill-switch-cli.d.ts +30 -0
- package/dist/kill-switch-cli.d.ts.map +1 -0
- package/dist/loopback-serve.d.ts +25 -0
- package/dist/loopback-serve.d.ts.map +1 -0
- package/dist/manifests-cli.d.ts +22 -0
- package/dist/manifests-cli.d.ts.map +1 -0
- package/dist/markdown-report-input.d.ts +7 -0
- package/dist/markdown-report-input.d.ts.map +1 -0
- package/dist/markdown-report-input.js +15 -1
- package/dist/merge-job.d.ts +155 -0
- package/dist/merge-job.d.ts.map +1 -0
- package/dist/model-configs-cli.d.ts +19 -0
- package/dist/model-configs-cli.d.ts.map +1 -0
- package/dist/model-configs-cli.js +104 -0
- package/dist/optimization-export-cli.d.ts +19 -0
- package/dist/optimization-export-cli.d.ts.map +1 -0
- package/dist/optimizations-list-cli.d.ts +20 -0
- package/dist/optimizations-list-cli.d.ts.map +1 -0
- package/dist/optimizations-promote-cli.d.ts +8 -0
- package/dist/optimizations-promote-cli.d.ts.map +1 -0
- package/dist/optimizations-promote-cli.js +34 -0
- package/dist/option-validation.d.ts +15 -0
- package/dist/option-validation.d.ts.map +1 -0
- package/dist/preview-runtime.d.ts +63 -0
- package/dist/preview-runtime.d.ts.map +1 -0
- package/dist/prompt-draft-push.d.ts +60 -0
- package/dist/prompt-draft-push.d.ts.map +1 -0
- package/dist/prompt-label-cli.d.ts +11 -0
- package/dist/prompt-label-cli.d.ts.map +1 -0
- package/dist/prompt-label-cli.js +29 -0
- package/dist/prompt-length-wire.d.ts +28 -0
- package/dist/prompt-length-wire.d.ts.map +1 -0
- package/dist/prompt-length-wire.js +2 -2
- package/dist/prompt-report-cli.d.ts +10 -0
- package/dist/prompt-report-cli.d.ts.map +1 -0
- package/dist/prompt-report-cli.js +2 -2
- package/dist/prompt-summary-table.d.ts +15 -0
- package/dist/prompt-summary-table.d.ts.map +1 -0
- package/dist/prompt-summary-table.js +6 -0
- package/dist/registration-tag-warning.d.ts +32 -0
- package/dist/registration-tag-warning.d.ts.map +1 -0
- package/dist/report-comments-cli.d.ts +16 -0
- package/dist/report-comments-cli.d.ts.map +1 -0
- package/dist/report-markdown-blankness.d.ts +2 -0
- package/dist/report-markdown-blankness.d.ts.map +1 -0
- package/dist/report-markdown-blankness.js +6 -0
- package/dist/report-replacement-warning.d.ts +17 -0
- package/dist/report-replacement-warning.d.ts.map +1 -0
- package/dist/report-replacement-warning.js +31 -0
- package/dist/repository-backup-core.d.ts +225 -0
- package/dist/repository-backup-core.d.ts.map +1 -0
- package/dist/repository-recovery-manifest.d.ts +83 -0
- package/dist/repository-recovery-manifest.d.ts.map +1 -0
- package/dist/run-event-digest.d.ts +74 -0
- package/dist/run-event-digest.d.ts.map +1 -0
- package/dist/runner-dir-verify.d.ts +66 -0
- package/dist/runner-dir-verify.d.ts.map +1 -0
- package/dist/runner-instruction-set-contract.d.ts +24 -0
- package/dist/runner-instruction-set-contract.d.ts.map +1 -0
- package/dist/runner-instruction-set-contract.js +50 -0
- package/dist/sandbox-hygiene.d.ts +45 -0
- package/dist/sandbox-hygiene.d.ts.map +1 -0
- package/dist/sandbox-provider.d.ts +193 -0
- package/dist/sandbox-provider.d.ts.map +1 -0
- package/dist/scorer-draft-push.d.ts +50 -0
- package/dist/scorer-draft-push.d.ts.map +1 -0
- package/dist/scorers-cli.d.ts +20 -0
- package/dist/scorers-cli.d.ts.map +1 -0
- package/dist/scorers-cli.js +22 -0
- package/dist/scorers-verify-parity-bridge.d.ts +16 -0
- package/dist/scorers-verify-parity-bridge.d.ts.map +1 -0
- package/dist/scorers-verify-parity-bridge.js +90 -0
- package/dist/scorers-verify-parity.d.ts +81 -0
- package/dist/scorers-verify-parity.d.ts.map +1 -0
- package/dist/scorers-verify-parity.js +502 -0
- package/dist/scores-accept-cli.d.ts +21 -0
- package/dist/scores-accept-cli.d.ts.map +1 -0
- package/dist/secret-redaction.d.ts +47 -0
- package/dist/secret-redaction.d.ts.map +1 -0
- package/dist/session-timeline.d.ts +54 -0
- package/dist/session-timeline.d.ts.map +1 -0
- package/dist/setup-prompt.d.ts +5 -0
- package/dist/setup-prompt.d.ts.map +1 -0
- package/dist/skill-installer.d.ts +68 -0
- package/dist/skill-installer.d.ts.map +1 -0
- package/dist/skilled-proposer-launch.d.ts +7 -0
- package/dist/skilled-proposer-launch.d.ts.map +1 -0
- package/dist/skilled-proposer-launch.js +60 -0
- package/dist/smoke.d.ts +102 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/start-intent.d.ts +12 -0
- package/dist/start-intent.d.ts.map +1 -0
- package/dist/task-assignment-manifest.d.ts +15 -0
- package/dist/task-assignment-manifest.d.ts.map +1 -0
- package/dist/task-create-error.d.ts +18 -0
- package/dist/task-create-error.d.ts.map +1 -0
- package/dist/team-connectors-cli.d.ts +29 -0
- package/dist/team-connectors-cli.d.ts.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/vercel-sandbox-provider.d.ts +156 -0
- package/dist/vercel-sandbox-provider.d.ts.map +1 -0
- package/dist/workbench-cli.d.ts +233 -0
- package/dist/workbench-cli.d.ts.map +1 -0
- package/dist/workspace-sync.d.ts +207 -0
- package/dist/workspace-sync.d.ts.map +1 -0
- package/dist/workspace.d.ts +92 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/zip-draft-push.d.ts +55 -0
- package/dist/zip-draft-push.d.ts.map +1 -0
- package/package.json +15 -2
- package/requirements/README.md +138 -0
- package/requirements/skilled-proposer.in +66 -0
- package/requirements/skilled-proposer.lock +265 -0
- package/scripts/build-cli-dist.mjs +30 -0
- package/scripts/ensure-skilled-proposer-venv.mjs +5 -0
- package/scripts/orizu-source-checkout.d.mts +1 -0
- package/scripts/orizu-source-checkout.mjs +10 -0
- package/scripts/refresh-skilled-proposer-lock.mjs +256 -0
- package/scripts/vendor-gepa-python.mjs +187 -4
- package/src/diff-comments-cli.ts +41 -3
- package/src/gepa-engine-dispatch.ts +340 -0
- package/src/gepa-python-command.ts +31 -0
- package/src/gepa-python-paths.ts +56 -0
- package/src/help.ts +95 -25
- package/src/index.ts +131 -166
- package/src/instruction-set-gepa-launch.ts +28 -0
- package/src/instruction-set-loader.ts +37 -0
- package/src/instruction-set-manifest.ts +120 -0
- package/src/instruction-sets-cli.ts +306 -0
- package/src/markdown-report-input.ts +19 -1
- package/src/model-configs-cli.ts +119 -0
- package/src/optimizations-promote-cli.ts +41 -0
- package/src/prompt-label-cli.ts +55 -0
- package/src/prompt-length-wire.ts +1 -1
- package/src/prompt-report-cli.ts +2 -2
- package/src/prompt-summary-table.ts +12 -0
- package/src/report-markdown-blankness.ts +6 -0
- package/src/report-replacement-warning.ts +35 -0
- package/src/runner-instruction-set-contract.ts +64 -0
- package/src/scorers-cli.ts +40 -0
- package/src/scorers-verify-parity-bridge.ts +90 -0
- package/src/scorers-verify-parity.ts +564 -0
- package/src/skilled-proposer-launch.ts +89 -0
- package/src/skilled-proposer-venv-manager.mjs +757 -0
- package/tsconfig.json +2 -0
- package/vendor/gepa-python/LICENSE +7 -0
- package/vendor/gepa-python/manifest.json +107 -0
- package/vendor/gepa-python/src/gepa/__init__.py +22 -0
- package/vendor/gepa-python/src/gepa/adapters/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/adapters/anymaths_adapter/__init__.py +1 -0
- package/vendor/gepa-python/src/gepa/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
- package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/__init__.py +26 -0
- package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/confidence_adapter.py +618 -0
- package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/scoring.py +206 -0
- package/vendor/gepa-python/src/gepa/adapters/default_adapter/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/adapters/default_adapter/default_adapter.py +206 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/dspy_adapter.py +525 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/instruction_proposal.py +500 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/__init__.py +6 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/full_program_adapter.py +269 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/__init__.py +56 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
- package/vendor/gepa-python/src/gepa/adapters/langchain_adapter/__init__.py +11 -0
- package/vendor/gepa-python/src/gepa/adapters/langchain_adapter/langchain_adapter.py +261 -0
- package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/__init__.py +37 -0
- package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/mcp_adapter.py +693 -0
- package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/mcp_client.py +364 -0
- package/vendor/gepa-python/src/gepa/adapters/optimize_anything_adapter/optimize_anything_adapter.py +879 -0
- package/vendor/gepa-python/src/gepa/adapters/terminal_bench_adapter/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
- package/vendor/gepa-python/src/gepa/api.py +468 -0
- package/vendor/gepa-python/src/gepa/core/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/core/adapter.py +227 -0
- package/vendor/gepa-python/src/gepa/core/callbacks.py +551 -0
- package/vendor/gepa-python/src/gepa/core/data_loader.py +74 -0
- package/vendor/gepa-python/src/gepa/core/engine.py +1025 -0
- package/vendor/gepa-python/src/gepa/core/result.py +271 -0
- package/vendor/gepa-python/src/gepa/core/state.py +706 -0
- package/vendor/gepa-python/src/gepa/examples/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/examples/aime.py +24 -0
- package/vendor/gepa-python/src/gepa/examples/anymaths-bench/eval_default.py +111 -0
- package/vendor/gepa-python/src/gepa/examples/anymaths-bench/train_anymaths.py +177 -0
- package/vendor/gepa-python/src/gepa/examples/mcp_adapter/__init__.py +4 -0
- package/vendor/gepa-python/src/gepa/examples/mcp_adapter/mcp_optimization_example.py +456 -0
- package/vendor/gepa-python/src/gepa/examples/rag_adapter/__init__.py +9 -0
- package/vendor/gepa-python/src/gepa/examples/rag_adapter/rag_optimization.py +824 -0
- package/vendor/gepa-python/src/gepa/examples/terminal-bench/train_terminus.py +161 -0
- package/vendor/gepa-python/src/gepa/gepa_utils.py +117 -0
- package/vendor/gepa-python/src/gepa/gskill/__init__.py +1 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/cost_tracker.py +194 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/claude_code.py +1117 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/claude_code_skills.py +613 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/mini_swe_agent.py +440 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/experiment_logger.py +367 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/swe_fitness_fn.py +209 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/swe_harness.py +352 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/train_optimize_anything.py +830 -0
- package/vendor/gepa-python/src/gepa/image.py +104 -0
- package/vendor/gepa-python/src/gepa/lm.py +261 -0
- package/vendor/gepa-python/src/gepa/logging/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/logging/experiment_tracker.py +457 -0
- package/vendor/gepa-python/src/gepa/logging/logger.py +75 -0
- package/vendor/gepa-python/src/gepa/logging/utils.py +130 -0
- package/vendor/gepa-python/src/gepa/optimize_anything.py +1701 -0
- package/vendor/gepa-python/src/gepa/proposer/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/proposer/base.py +53 -0
- package/vendor/gepa-python/src/gepa/proposer/merge.py +403 -0
- package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/base.py +64 -0
- package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/reflection_lm.py +178 -0
- package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/reflective_mutation.py +641 -0
- package/vendor/gepa-python/src/gepa/py.typed +0 -0
- package/vendor/gepa-python/src/gepa/strategies/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/strategies/acceptance.py +66 -0
- package/vendor/gepa-python/src/gepa/strategies/batch_sampler.py +103 -0
- package/vendor/gepa-python/src/gepa/strategies/candidate_selector.py +82 -0
- package/vendor/gepa-python/src/gepa/strategies/component_selector.py +36 -0
- package/vendor/gepa-python/src/gepa/strategies/eval_policy.py +64 -0
- package/vendor/gepa-python/src/gepa/strategies/instruction_proposal.py +153 -0
- package/vendor/gepa-python/src/gepa/strategies/proposal_sampling.py +128 -0
- package/vendor/gepa-python/src/gepa/strategies/proposal_selection.py +84 -0
- package/vendor/gepa-python/src/gepa/utils/__init__.py +63 -0
- package/vendor/gepa-python/src/gepa/utils/code_execution.py +626 -0
- package/vendor/gepa-python/src/gepa/utils/stdio_capture.py +145 -0
- package/vendor/gepa-python/src/gepa/utils/stop_condition.py +230 -0
- package/vendor/gepa-python/src/gepa/visualization.py +359 -0
- package/vendor/orizu-gepa/pyproject.toml +18 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/__init__.py +9 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/__main__.py +5 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/adapter.py +138 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/callbacks.py +666 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/cli.py +7 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +269 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +52 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/preflight.py +64 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/reflection.py +186 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +721 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +381 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/stop_conditions.py +55 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/translator.py +203 -0
- package/vendor/orizu-gepa-python/pyproject.toml +1 -1
- package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +1 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +52 -7
- package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +52 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +10 -5
- package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +323 -68
- package/vendor/orizu-gepa-python/src/orizu_gepa/runner.py +207 -13
- package/vendor/skills/orizu-cli/SKILL.md +3 -1
- package/vendor/skills/orizu-cli/references/cli-reference.md +85 -1
- package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +303 -0
- package/vendor/skills/orizu-cli/references/optimization-reports.md +1 -1
- package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +26 -1
- package/vendor/skills/orizu-cli/references/prompt-control-plane.md +113 -10
|
@@ -0,0 +1,1701 @@
|
|
|
1
|
+
"""
|
|
2
|
+
``optimize_anything`` — a universal API for optimizing any text parameter.
|
|
3
|
+
|
|
4
|
+
This is the primary public API for GEPA. You declare **what** to optimize
|
|
5
|
+
(a text-representable artifact — code, prompts, agent architectures,
|
|
6
|
+
configurations, policies, SVG graphics) and **how** to measure it (an
|
|
7
|
+
evaluator); ``optimize_anything`` handles the **how**: prompt construction,
|
|
8
|
+
LLM reflection, candidate selection, and Pareto-efficient search.
|
|
9
|
+
|
|
10
|
+
The key insight is that a wide range of problems can be formulated as
|
|
11
|
+
optimizing a text artifact: speeding up a CUDA kernel, tuning a scheduling
|
|
12
|
+
policy, refining a prompt template, or redesigning an agent architecture.
|
|
13
|
+
If it can be serialized to a string and its quality measured, an LLM can
|
|
14
|
+
reason about it and propose improvements.
|
|
15
|
+
|
|
16
|
+
Core workflow::
|
|
17
|
+
|
|
18
|
+
seed_candidate → evaluate → reflect on ASI → propose → repeat
|
|
19
|
+
↑ |
|
|
20
|
+
└──────────────────────────────────┘
|
|
21
|
+
|
|
22
|
+
Three optimization modes
|
|
23
|
+
------------------------
|
|
24
|
+
``optimize_anything`` unifies three optimization paradigms under one
|
|
25
|
+
declarative API, determined by whether you provide ``dataset`` and ``valset``:
|
|
26
|
+
|
|
27
|
+
1. **Single-Task Search** (``dataset=None, valset=None``):
|
|
28
|
+
Solve one hard problem. The candidate *is* the solution.
|
|
29
|
+
Evaluator is called without ``example``.
|
|
30
|
+
*E.g. circle packing, blackbox mathematical optimization.*
|
|
31
|
+
|
|
32
|
+
2. **Multi-Task Search** (``dataset=<list>, valset=None``):
|
|
33
|
+
Solve a batch of related problems with cross-task transfer. Insights
|
|
34
|
+
from solving one help solve the others. Evaluator is called per-example.
|
|
35
|
+
*E.g. CUDA kernel generation for multiple PyTorch operations,
|
|
36
|
+
multi-aspect SVG optimization.*
|
|
37
|
+
|
|
38
|
+
3. **Generalization** (``dataset=<list>, valset=<list>``):
|
|
39
|
+
Build a skill that transfers to unseen problems. Evaluator is called
|
|
40
|
+
per-example; candidates must generalize to ``valset``.
|
|
41
|
+
*E.g. prompt optimization for AIME math, agent architecture evolution
|
|
42
|
+
for ARC-AGI, cloud scheduling policy discovery.*
|
|
43
|
+
|
|
44
|
+
Seedless mode
|
|
45
|
+
-------------
|
|
46
|
+
When you don't have a starting artifact, pass ``seed_candidate=None`` and
|
|
47
|
+
provide ``objective`` (and optionally ``background``). The reflection LM
|
|
48
|
+
bootstraps the first candidate from the description, then iterates as usual.
|
|
49
|
+
Useful for creative or exploratory tasks where the solution space is large.
|
|
50
|
+
|
|
51
|
+
Quick example::
|
|
52
|
+
|
|
53
|
+
import gepa.optimize_anything as oa
|
|
54
|
+
from gepa.optimize_anything import optimize_anything, GEPAConfig, EngineConfig
|
|
55
|
+
|
|
56
|
+
def evaluate(candidate: str) -> float:
|
|
57
|
+
score, diagnostic = run_candidate(candidate)
|
|
58
|
+
oa.log("Diagnostic:", diagnostic) # captured as ASI
|
|
59
|
+
return score
|
|
60
|
+
|
|
61
|
+
# Start from an existing artifact…
|
|
62
|
+
result = optimize_anything(
|
|
63
|
+
seed_candidate="<initial code>",
|
|
64
|
+
evaluator=evaluate,
|
|
65
|
+
objective="Maximize throughput.",
|
|
66
|
+
config=GEPAConfig(engine=EngineConfig(max_metric_calls=300)),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# … or just describe what you need (seedless mode).
|
|
70
|
+
result = optimize_anything(
|
|
71
|
+
evaluator=evaluate,
|
|
72
|
+
objective="Generate a Python function that reverses a string.",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
print(result.best_candidate)
|
|
76
|
+
|
|
77
|
+
Key concepts:
|
|
78
|
+
- **Candidate**: ``dict[str, str]`` or plain ``str`` of optimizable text
|
|
79
|
+
parameters — prompts, code, configs, agent architectures, etc.
|
|
80
|
+
- **Evaluator**: Your scoring function. Returns ``(score, side_info)`` or
|
|
81
|
+
just ``score``. Higher scores are better.
|
|
82
|
+
- **ASI / SideInfo**: Actionable Side Information — diagnostic feedback
|
|
83
|
+
returned by the evaluator (or captured via ``oa.log()``). ASI is the
|
|
84
|
+
text-optimization analogue of the gradient: where gradients tell a
|
|
85
|
+
numerical optimizer which direction to move, ASI tells the LLM proposer
|
|
86
|
+
*why* a candidate failed and *how* to fix it. Can include error
|
|
87
|
+
messages, profiling traces, rendered images (via :class:`Image`), or
|
|
88
|
+
any structured data that would help an expert diagnose failures.
|
|
89
|
+
- **Pareto-efficient search**: Scores are tracked per-task and per-metric
|
|
90
|
+
individually; any candidate that is the best at *something* survives on
|
|
91
|
+
the frontier, enabling focused improvements that are preserved rather
|
|
92
|
+
than averaged away.
|
|
93
|
+
- **Objective / Background**: Natural-language strings that guide the
|
|
94
|
+
reflection LLM (what to optimize for, domain constraints).
|
|
95
|
+
|
|
96
|
+
Public API:
|
|
97
|
+
- :func:`optimize_anything` — main entry point
|
|
98
|
+
- :data:`SideInfo` — type alias for evaluation diagnostics (ASI)
|
|
99
|
+
- :class:`Evaluator` — evaluator protocol
|
|
100
|
+
- :class:`OptimizationState` — context injected into evaluators
|
|
101
|
+
- :class:`GEPAConfig`, :class:`EngineConfig`, :class:`ReflectionConfig`,
|
|
102
|
+
:class:`RefinerConfig`, :class:`MergeConfig`, :class:`TrackingConfig`
|
|
103
|
+
- :func:`log`, :func:`get_log_context`, :func:`set_log_context` — in-evaluator logging
|
|
104
|
+
- :func:`make_litellm_lm` — convert a model name string to a callable
|
|
105
|
+
- :class:`Image` — wrapper for including images in side_info (VLM reflection)
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
import inspect
|
|
109
|
+
import io
|
|
110
|
+
import os
|
|
111
|
+
import random
|
|
112
|
+
import threading
|
|
113
|
+
import warnings
|
|
114
|
+
from collections.abc import Callable
|
|
115
|
+
from dataclasses import asdict, dataclass, field
|
|
116
|
+
from typing import (
|
|
117
|
+
Any,
|
|
118
|
+
Literal,
|
|
119
|
+
Protocol,
|
|
120
|
+
Sequence,
|
|
121
|
+
TypeAlias,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
from gepa.adapters.optimize_anything_adapter.optimize_anything_adapter import (
|
|
125
|
+
BatchEvaluatorWrapper,
|
|
126
|
+
OptimizeAnythingAdapter,
|
|
127
|
+
)
|
|
128
|
+
from gepa.core.adapter import DataInst, GEPAAdapter, ProposalFn
|
|
129
|
+
from gepa.core.callbacks import GEPACallback
|
|
130
|
+
from gepa.core.data_loader import ensure_loader
|
|
131
|
+
from gepa.core.engine import GEPAEngine
|
|
132
|
+
from gepa.core.result import GEPAResult
|
|
133
|
+
from gepa.core.state import EvaluationCache, FrontierType
|
|
134
|
+
from gepa.image import Image # noqa: F401 — re-exported for user convenience
|
|
135
|
+
from gepa.logging.experiment_tracker import create_experiment_tracker
|
|
136
|
+
from gepa.logging.logger import Logger, LoggerProtocol, StdOutLogger
|
|
137
|
+
from gepa.proposer.merge import MergeProposer
|
|
138
|
+
from gepa.proposer.reflective_mutation.base import CandidateSelector, LanguageModel, ReflectionComponentSelector
|
|
139
|
+
from gepa.proposer.reflective_mutation.reflection_lm import ReflectionLM
|
|
140
|
+
from gepa.proposer.reflective_mutation.reflective_mutation import ReflectiveMutationProposer
|
|
141
|
+
from gepa.strategies.acceptance import AcceptanceCriterion, ImprovementOrEqualAcceptance, StrictImprovementAcceptance
|
|
142
|
+
from gepa.strategies.batch_sampler import BatchSampler, EpochShuffledBatchSampler
|
|
143
|
+
from gepa.strategies.candidate_selector import (
|
|
144
|
+
CurrentBestCandidateSelector,
|
|
145
|
+
EpsilonGreedyCandidateSelector,
|
|
146
|
+
ParetoCandidateSelector,
|
|
147
|
+
TopKParetoCandidateSelector,
|
|
148
|
+
)
|
|
149
|
+
from gepa.strategies.component_selector import (
|
|
150
|
+
AllReflectionComponentSelector,
|
|
151
|
+
RoundRobinReflectionComponentSelector,
|
|
152
|
+
)
|
|
153
|
+
from gepa.strategies.eval_policy import EvaluationPolicy, FullEvaluationPolicy
|
|
154
|
+
from gepa.strategies.proposal_sampling import SamplingStrategy
|
|
155
|
+
from gepa.strategies.proposal_selection import SelectionStrategy
|
|
156
|
+
from gepa.utils import FileStopper, StopperProtocol
|
|
157
|
+
from gepa.utils.stdio_capture import ThreadLocalStreamCapture, stream_manager
|
|
158
|
+
|
|
159
|
+
OptimizableParam = str
|
|
160
|
+
Candidate = dict[str, OptimizableParam]
|
|
161
|
+
|
|
162
|
+
# Cache storage modes for evaluator caching (when cache_evaluation=True)
|
|
163
|
+
CacheEvaluationStorage = Literal["memory", "disk", "auto"]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# Sentinel object for single-instance mode
|
|
167
|
+
class _SingleInstanceSentinel:
|
|
168
|
+
"""Sentinel object used to represent single-instance optimization mode."""
|
|
169
|
+
|
|
170
|
+
def __repr__(self) -> str:
|
|
171
|
+
return "<SingleInstanceSentinel>"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
_SINGLE_INSTANCE_SENTINEL = _SingleInstanceSentinel()
|
|
175
|
+
|
|
176
|
+
# Internal key used to wrap a plain-str seed_candidate into a dict.
|
|
177
|
+
_STR_CANDIDATE_KEY = "current_candidate"
|
|
178
|
+
|
|
179
|
+
SideInfo: TypeAlias = dict[str, Any]
|
|
180
|
+
"""Actionable Side Information (ASI) returned by the evaluator alongside each score.
|
|
181
|
+
|
|
182
|
+
ASI is the text-optimization analogue of the gradient. Where gradients tell
|
|
183
|
+
a numerical optimizer which direction to move, ASI tells an LLM proposer
|
|
184
|
+
*why* a candidate failed and *how* to fix it.
|
|
185
|
+
|
|
186
|
+
Traditional optimizers know *that* a candidate failed but not *why*. SideInfo
|
|
187
|
+
provides the *why* — error messages, expected vs actual output, profiling
|
|
188
|
+
traces, compiler diagnostics, rendered images — enabling the reflection LLM
|
|
189
|
+
to take targeted corrective action rather than random mutation.
|
|
190
|
+
|
|
191
|
+
**More informative SideInfo → better optimization.**
|
|
192
|
+
|
|
193
|
+
You can provide SideInfo in two ways:
|
|
194
|
+
|
|
195
|
+
1. Return ``(score, side_info_dict)`` from your evaluator.
|
|
196
|
+
2. Call ``oa.log(...)`` inside your evaluator (captured under ``"log"`` key).
|
|
197
|
+
|
|
198
|
+
Structure
|
|
199
|
+
---------
|
|
200
|
+
1. **``"scores"`` (optional)** — multi-objective metrics for Pareto tracking.
|
|
201
|
+
All values must follow "higher is better" convention.
|
|
202
|
+
|
|
203
|
+
``{"scores": {"accuracy": 0.85, "latency_inv": 12.5}}``
|
|
204
|
+
|
|
205
|
+
2. **Contextual fields** — any other keys. Common conventions:
|
|
206
|
+
|
|
207
|
+
- ``"Input"`` / ``"Output"`` / ``"Expected"`` — what went in and came out
|
|
208
|
+
- ``"Feedback"`` — qualitative assessment (human or machine)
|
|
209
|
+
- ``"Error"`` — error messages, tracebacks, compilation failures
|
|
210
|
+
- ``"Reasoning"`` — intermediate reasoning traces
|
|
211
|
+
|
|
212
|
+
3. **Parameter-specific info** — ``"<param_name>_specific_info"`` dicts with
|
|
213
|
+
their own ``"scores"`` and contextual fields. During reflection on
|
|
214
|
+
parameter *X*, GEPA merges top-level fields with ``X_specific_info``.
|
|
215
|
+
|
|
216
|
+
4. **Images** — use :class:`~gepa.Image` for visual feedback (rendered
|
|
217
|
+
SVGs, charts, screenshots). Requires a VLM as ``reflection_lm``.
|
|
218
|
+
|
|
219
|
+
Example::
|
|
220
|
+
|
|
221
|
+
{
|
|
222
|
+
"scores": {"accuracy": 0.73, "user_satisfaction": 4.2},
|
|
223
|
+
"Input": "Translate 'Hello world' to French",
|
|
224
|
+
"Output": "Salut monde",
|
|
225
|
+
"Expected": "Bonjour le monde",
|
|
226
|
+
"Feedback": "Translation is too informal for the context",
|
|
227
|
+
"system_prompt_specific_info": {
|
|
228
|
+
"scores": {"tone": 0.3},
|
|
229
|
+
"Analysis": "System prompt led to overly casual translation",
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
Best practices:
|
|
234
|
+
- Include error messages and failure reasons prominently
|
|
235
|
+
- Use consistent field names across evaluations
|
|
236
|
+
- Add context beyond raw numbers (explain *what* went wrong)
|
|
237
|
+
- Use ``"scores"`` only for "higher is better" metrics used in Pareto tracking
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@dataclass
|
|
242
|
+
class OptimizationState:
|
|
243
|
+
"""Accumulated optimization context injected into evaluators that declare an ``opt_state`` parameter.
|
|
244
|
+
|
|
245
|
+
Provides historical evaluation results so your evaluator can warm-start
|
|
246
|
+
from previous best solutions (e.g., pass the best-known circle packing to
|
|
247
|
+
a new optimization attempt).
|
|
248
|
+
|
|
249
|
+
To receive this, simply add ``opt_state: OptimizationState`` to your
|
|
250
|
+
evaluator signature — GEPA injects it automatically.
|
|
251
|
+
|
|
252
|
+
Example::
|
|
253
|
+
|
|
254
|
+
def evaluator(candidate, example, opt_state: OptimizationState):
|
|
255
|
+
prev_best = opt_state.best_example_evals[0]["side_info"] if opt_state.best_example_evals else None
|
|
256
|
+
# ... use prev_best to warm-start ...
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
best_example_evals: list[dict]
|
|
260
|
+
"""Top-K best evaluations for the current example, sorted by score (descending).
|
|
261
|
+
|
|
262
|
+
Each entry: ``{"score": float, "side_info": dict}``. K is controlled by
|
|
263
|
+
``EngineConfig.best_example_evals_k`` (default 30)."""
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
# Evaluation log context — captures diagnostic output without polluting stdout
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class LogContext:
|
|
272
|
+
"""Thread-safe log buffer for a single evaluator invocation.
|
|
273
|
+
|
|
274
|
+
All ``oa.log()`` calls within the same evaluator call write to the same
|
|
275
|
+
buffer, even from child threads (when properly propagated via
|
|
276
|
+
:func:`get_log_context` / :func:`set_log_context`). Writes are
|
|
277
|
+
serialized with a lock so concurrent threads never interleave.
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
def __init__(self) -> None:
|
|
281
|
+
self._buffer = io.StringIO()
|
|
282
|
+
self._lock = threading.Lock()
|
|
283
|
+
|
|
284
|
+
def write(self, text: str) -> None:
|
|
285
|
+
with self._lock:
|
|
286
|
+
self._buffer.write(text)
|
|
287
|
+
|
|
288
|
+
def drain(self) -> str:
|
|
289
|
+
"""Drain and return all accumulated text, leaving the buffer empty."""
|
|
290
|
+
with self._lock:
|
|
291
|
+
old = self._buffer
|
|
292
|
+
text = old.getvalue()
|
|
293
|
+
old.close()
|
|
294
|
+
self._buffer = io.StringIO()
|
|
295
|
+
return text
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
# Thread-local storage for the active LogContext on each thread.
|
|
299
|
+
_log_tls = threading.local()
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _get_log_context() -> "LogContext | None":
|
|
303
|
+
"""Return the active log context for the current thread, or None."""
|
|
304
|
+
return getattr(_log_tls, "context", None)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _set_log_context(ctx: "LogContext | None") -> None:
|
|
308
|
+
"""Set (or clear) the active log context on the current thread."""
|
|
309
|
+
_log_tls.context = ctx
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def get_log_context() -> LogContext:
|
|
313
|
+
"""Return the active log context for the current evaluator call.
|
|
314
|
+
|
|
315
|
+
Use this to propagate ``oa.log()`` capture to child threads spawned
|
|
316
|
+
inside your evaluator::
|
|
317
|
+
|
|
318
|
+
import threading
|
|
319
|
+
import gepa.optimize_anything as oa
|
|
320
|
+
|
|
321
|
+
def my_evaluator(candidate):
|
|
322
|
+
ctx = oa.get_log_context()
|
|
323
|
+
|
|
324
|
+
def worker():
|
|
325
|
+
oa.set_log_context(ctx)
|
|
326
|
+
oa.log("from child thread")
|
|
327
|
+
|
|
328
|
+
t = threading.Thread(target=worker)
|
|
329
|
+
t.start()
|
|
330
|
+
t.join()
|
|
331
|
+
oa.log("from main evaluator thread")
|
|
332
|
+
return score
|
|
333
|
+
|
|
334
|
+
Raises:
|
|
335
|
+
RuntimeError: If called outside an evaluator invocation.
|
|
336
|
+
"""
|
|
337
|
+
ctx = _get_log_context()
|
|
338
|
+
if ctx is None:
|
|
339
|
+
raise RuntimeError(
|
|
340
|
+
"No active log context. get_log_context() must be called inside an evaluator passed to optimize_anything()."
|
|
341
|
+
)
|
|
342
|
+
return ctx
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def set_log_context(ctx: LogContext) -> None:
|
|
346
|
+
"""Set the log context on the current thread.
|
|
347
|
+
|
|
348
|
+
Call this at the start of a child thread to route ``oa.log()`` output
|
|
349
|
+
into the parent evaluator's log buffer. See :func:`get_log_context`
|
|
350
|
+
for a complete usage example.
|
|
351
|
+
"""
|
|
352
|
+
_set_log_context(ctx)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def log(*args: Any, sep: str = " ", end: str = "\n") -> None:
|
|
356
|
+
"""Log diagnostic information during evaluation without printing to stdout.
|
|
357
|
+
|
|
358
|
+
Has the same calling convention as ``print()``. Output is captured
|
|
359
|
+
per-evaluator-call (thread-safe) and automatically included in
|
|
360
|
+
side_info under the ``"log"`` key.
|
|
361
|
+
|
|
362
|
+
Must only be called inside an evaluator function passed to
|
|
363
|
+
``optimize_anything``. Calling it outside that context will emit a
|
|
364
|
+
warning and the output will be silently discarded.
|
|
365
|
+
|
|
366
|
+
For child threads spawned by your evaluator, propagate the log context
|
|
367
|
+
via :func:`get_log_context` / :func:`set_log_context`.
|
|
368
|
+
|
|
369
|
+
Usage::
|
|
370
|
+
|
|
371
|
+
import gepa.optimize_anything as oa
|
|
372
|
+
oa.log("Landing distance:", distance, "meters")
|
|
373
|
+
"""
|
|
374
|
+
ctx = _get_log_context()
|
|
375
|
+
if ctx is None:
|
|
376
|
+
warnings.warn(
|
|
377
|
+
"oa.log() called outside of an evaluator function. "
|
|
378
|
+
"Output will be discarded. Only call oa.log() inside your evaluator, "
|
|
379
|
+
"or propagate the log context to child threads via "
|
|
380
|
+
"oa.get_log_context() / oa.set_log_context().",
|
|
381
|
+
stacklevel=2,
|
|
382
|
+
)
|
|
383
|
+
return
|
|
384
|
+
text = sep.join(str(a) for a in args) + end
|
|
385
|
+
ctx.write(text)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# Thread-safe stdout / stderr capture utilities are in gepa.utils.stdio_capture.
|
|
389
|
+
# The module-level ``stream_manager`` singleton and ``ThreadLocalStreamCapture``
|
|
390
|
+
# class are imported at the top of this file.
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class Evaluator(Protocol):
|
|
394
|
+
def __call__(
|
|
395
|
+
self, candidate: str | Candidate, example: object | None = None, **kwargs: Any
|
|
396
|
+
) -> tuple[float, SideInfo] | float:
|
|
397
|
+
"""Score a candidate, returning a score and diagnostic side information (ASI).
|
|
398
|
+
|
|
399
|
+
This is the function you write. GEPA calls it repeatedly with
|
|
400
|
+
mutated candidates and collects the returned scores and diagnostics
|
|
401
|
+
to drive the optimization loop.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
candidate: The text parameter(s) to evaluate. Type matches
|
|
405
|
+
``seed_candidate``: plain ``str`` if you passed a string,
|
|
406
|
+
or ``dict[str, str]`` if you passed a dict.
|
|
407
|
+
example: One item from ``dataset``. **Not passed** in
|
|
408
|
+
single-task search mode (``dataset=None``).
|
|
409
|
+
opt_state: *(optional)* Declare ``opt_state: OptimizationState``
|
|
410
|
+
in your signature to receive historical best evaluations
|
|
411
|
+
for warm-starting. This is a **reserved parameter name**
|
|
412
|
+
— GEPA injects it automatically.
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
Either ``(score, side_info)`` or just ``score``:
|
|
416
|
+
|
|
417
|
+
- **score** (float): Higher is better.
|
|
418
|
+
- **side_info** (:data:`SideInfo`): Diagnostic dict (ASI)
|
|
419
|
+
powering LLM reflection.
|
|
420
|
+
|
|
421
|
+
If you return score-only, use ``oa.log()`` or
|
|
422
|
+
``capture_stdio=True`` to provide diagnostic context.
|
|
423
|
+
|
|
424
|
+
Evaluator signature per mode:
|
|
425
|
+
|
|
426
|
+
**Single-Task Search** (``dataset=None``) — called without ``example``::
|
|
427
|
+
|
|
428
|
+
import gepa.optimize_anything as oa
|
|
429
|
+
|
|
430
|
+
def evaluate(candidate):
|
|
431
|
+
result = run_code(candidate["code"])
|
|
432
|
+
oa.log(f"Output: {result}") # ASI via oa.log()
|
|
433
|
+
return compute_score(result)
|
|
434
|
+
|
|
435
|
+
**Multi-Task Search / Generalization** (``dataset`` provided) — called per example::
|
|
436
|
+
|
|
437
|
+
def evaluate(candidate, example):
|
|
438
|
+
pred = run(candidate["prompt"], example["input"])
|
|
439
|
+
score = 1.0 if pred == example["expected"] else 0.0
|
|
440
|
+
return score, {"Input": example["input"], "Output": pred}
|
|
441
|
+
|
|
442
|
+
Reserved side_info keys:
|
|
443
|
+
``"log"``, ``"stdout"``, ``"stderr"`` are used by GEPA's
|
|
444
|
+
automatic capture. If you use them, GEPA stores captured
|
|
445
|
+
output under ``"_gepa_log"`` etc. to avoid collisions.
|
|
446
|
+
"""
|
|
447
|
+
...
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# --- Component 1: Engine & Stopping Configuration ---
|
|
451
|
+
@dataclass
|
|
452
|
+
class EngineConfig:
|
|
453
|
+
"""Controls the optimization run loop: budget, parallelism, caching, and stopping.
|
|
454
|
+
|
|
455
|
+
Most users only need to set ``max_metric_calls`` (evaluation budget).
|
|
456
|
+
Parallel evaluation is enabled by default with ``max_workers`` set to
|
|
457
|
+
``os.cpu_count() or 32`` (CPU count when available, otherwise 32).
|
|
458
|
+
|
|
459
|
+
Set ``capture_stdio=True`` to automatically route any ``print()`` output
|
|
460
|
+
inside your evaluator into ASI (under ``"stdout"``/``"stderr"`` keys),
|
|
461
|
+
with no code changes needed. Useful for quick prototyping or wrapping
|
|
462
|
+
existing evaluation scripts that already have print statements.
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
run_dir: str | None = None
|
|
466
|
+
seed: int = 0
|
|
467
|
+
display_progress_bar: bool = False
|
|
468
|
+
raise_on_exception: bool = True
|
|
469
|
+
use_cloudpickle: bool = True
|
|
470
|
+
track_best_outputs: bool = True
|
|
471
|
+
|
|
472
|
+
# Simple stopping conditions
|
|
473
|
+
max_metric_calls: int | None = None
|
|
474
|
+
max_candidate_proposals: int | None = None
|
|
475
|
+
max_reflection_cost: float | None = None
|
|
476
|
+
|
|
477
|
+
# Strategy selection for the engine
|
|
478
|
+
val_evaluation_policy: EvaluationPolicy | Literal["full_eval"] = "full_eval"
|
|
479
|
+
candidate_selection_strategy: CandidateSelector | Literal[
|
|
480
|
+
"pareto", "current_best", "epsilon_greedy", "top_k_pareto"
|
|
481
|
+
] = "pareto"
|
|
482
|
+
frontier_type: FrontierType = "hybrid"
|
|
483
|
+
|
|
484
|
+
# Acceptance criterion for reflective mutation proposals
|
|
485
|
+
acceptance_criterion: AcceptanceCriterion | Literal[
|
|
486
|
+
"strict_improvement", "improvement_or_equal"
|
|
487
|
+
] = "strict_improvement"
|
|
488
|
+
|
|
489
|
+
# Parallelization settings for evaluation
|
|
490
|
+
parallel: bool = True
|
|
491
|
+
max_workers: int | None = field(default_factory=lambda: os.cpu_count() or 32)
|
|
492
|
+
|
|
493
|
+
# Evaluation caching
|
|
494
|
+
cache_evaluation: bool = False
|
|
495
|
+
cache_evaluation_storage: CacheEvaluationStorage = "auto"
|
|
496
|
+
|
|
497
|
+
# Track top-K best evaluations per example, passed to evaluator via OptimizationState
|
|
498
|
+
# Useful for warm-starting optimization from previous best solutions
|
|
499
|
+
best_example_evals_k: int = 30
|
|
500
|
+
|
|
501
|
+
# When True, automatically capture stdout/stderr during evaluation and
|
|
502
|
+
# include it in side_info as {"stdout": "...", "stderr": "..."}.
|
|
503
|
+
# Thread-safe via per-thread sys.stdout/stderr replacement.
|
|
504
|
+
#
|
|
505
|
+
# Captures all Python-level output: print(), sys.stdout.write(), and
|
|
506
|
+
# third-party library output — anything that goes through sys.stdout.
|
|
507
|
+
#
|
|
508
|
+
# Does NOT capture output that bypasses Python's sys.stdout:
|
|
509
|
+
# C extensions writing directly to fd 1/2, or subprocesses spawned
|
|
510
|
+
# internally by libraries. For those, use oa.log() or capture subprocess
|
|
511
|
+
# output manually and pass it to oa.log().
|
|
512
|
+
capture_stdio: bool = False
|
|
513
|
+
|
|
514
|
+
# Proposal strategies (default: 1 parent, 1 mutation per iteration).
|
|
515
|
+
# Swap in a different SamplingStrategy to propose multiple candidates.
|
|
516
|
+
sampling_strategy: SamplingStrategy | None = None
|
|
517
|
+
selection_strategy: SelectionStrategy | None = None
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _build_reflection_prompt_template(objective: str | None = None, background: str | None = None) -> str:
|
|
521
|
+
"""
|
|
522
|
+
Build a reflection prompt template dynamically based on provided objective and background.
|
|
523
|
+
|
|
524
|
+
Only includes sections that have content, ensuring the prompt feels natural
|
|
525
|
+
regardless of which optional parameters are provided.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
objective: High-level goal describing what the optimized component should achieve.
|
|
529
|
+
background: Domain knowledge, constraints, strategies, or implementation requirements.
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
A reflection prompt template string with <curr_param> and <side_info> placeholders.
|
|
533
|
+
"""
|
|
534
|
+
sections = []
|
|
535
|
+
|
|
536
|
+
# System context - always present
|
|
537
|
+
sections.append(
|
|
538
|
+
"You are an expert optimization assistant. Your task is to analyze evaluation "
|
|
539
|
+
"feedback and propose an improved version of a system component."
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
# Objective section
|
|
543
|
+
if objective:
|
|
544
|
+
sections.append(f"""
|
|
545
|
+
## Optimization Goal
|
|
546
|
+
|
|
547
|
+
{objective}""")
|
|
548
|
+
|
|
549
|
+
# Background/context section
|
|
550
|
+
if background:
|
|
551
|
+
sections.append(f"""
|
|
552
|
+
## Domain Context & Constraints
|
|
553
|
+
|
|
554
|
+
{background}""")
|
|
555
|
+
|
|
556
|
+
# Current component and evaluation data - always present
|
|
557
|
+
sections.append("""
|
|
558
|
+
## Current Component
|
|
559
|
+
|
|
560
|
+
The component being optimized:
|
|
561
|
+
|
|
562
|
+
```
|
|
563
|
+
<curr_param>
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
## Evaluation Results
|
|
567
|
+
|
|
568
|
+
Performance data from evaluating the current component across test cases:
|
|
569
|
+
|
|
570
|
+
```
|
|
571
|
+
<side_info>
|
|
572
|
+
```""")
|
|
573
|
+
|
|
574
|
+
# Analysis instructions - tailored based on what context is available
|
|
575
|
+
analysis_points = []
|
|
576
|
+
if objective:
|
|
577
|
+
analysis_points.append(
|
|
578
|
+
"- **Goal alignment**: How well does the current component achieve the stated optimization goal?"
|
|
579
|
+
)
|
|
580
|
+
analysis_points.extend(
|
|
581
|
+
[
|
|
582
|
+
"- **Failure patterns**: What specific errors, edge cases, or failure modes appear in the evaluation data?",
|
|
583
|
+
"- **Success patterns**: What behaviors or approaches worked well and should be preserved?",
|
|
584
|
+
"- **Root causes**: What underlying issues explain the observed failures?",
|
|
585
|
+
]
|
|
586
|
+
)
|
|
587
|
+
if background:
|
|
588
|
+
analysis_points.append(
|
|
589
|
+
"- **Constraint compliance**: Does the component satisfy all requirements from the domain context?"
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
analysis_section = "\n".join(analysis_points)
|
|
593
|
+
constraint_line = "\n4. Adheres to all constraints and requirements from the domain context" if background else ""
|
|
594
|
+
sections.append(f"""
|
|
595
|
+
## Your Task
|
|
596
|
+
|
|
597
|
+
Analyze the evaluation results systematically:
|
|
598
|
+
|
|
599
|
+
{analysis_section}
|
|
600
|
+
|
|
601
|
+
Based on your analysis, propose an improved version that:
|
|
602
|
+
1. Addresses the identified failure patterns and root causes
|
|
603
|
+
2. Preserves successful behaviors from the current version
|
|
604
|
+
3. Makes meaningful improvements rather than superficial changes{constraint_line}""")
|
|
605
|
+
|
|
606
|
+
# Output format - always present
|
|
607
|
+
sections.append("""
|
|
608
|
+
## Output Format
|
|
609
|
+
|
|
610
|
+
Provide ONLY the improved version within ``` blocks. The output must be a complete,
|
|
611
|
+
drop-in replacement for the current component (whether it's a prompt, configuration,
|
|
612
|
+
code, or any other parameter type).
|
|
613
|
+
Do not include explanations, commentary, or markdown outside the ``` blocks.""")
|
|
614
|
+
|
|
615
|
+
return "\n".join(sections)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def _build_seed_generation_prompt(
|
|
619
|
+
objective: str,
|
|
620
|
+
background: str | None = None,
|
|
621
|
+
dataset: list[DataInst] | None = None,
|
|
622
|
+
) -> str:
|
|
623
|
+
"""Build a prompt for the reflection LM to generate an initial seed candidate.
|
|
624
|
+
|
|
625
|
+
Used when ``seed_candidate=None`` — the LLM bootstraps the first candidate
|
|
626
|
+
from the objective, optional background, and optional dataset examples.
|
|
627
|
+
"""
|
|
628
|
+
sections = []
|
|
629
|
+
|
|
630
|
+
sections.append(
|
|
631
|
+
"You are an expert assistant. Your task is to generate an initial candidate "
|
|
632
|
+
"that will be iteratively refined by an optimization system."
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
sections.append(f"\n## Goal\n\n{objective}")
|
|
636
|
+
|
|
637
|
+
if background:
|
|
638
|
+
sections.append(f"\n## Domain Context & Constraints\n\n{background}")
|
|
639
|
+
|
|
640
|
+
if dataset is not None:
|
|
641
|
+
examples = dataset[:3]
|
|
642
|
+
example_lines = [f"- Example {i}: {ex}" for i, ex in enumerate(examples, 1)]
|
|
643
|
+
sections.append(
|
|
644
|
+
"\n## Sample Inputs\n\n"
|
|
645
|
+
"The candidate will be evaluated on inputs like these:\n\n"
|
|
646
|
+
+ "\n".join(example_lines)
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
sections.append(
|
|
650
|
+
"\n## Output Format\n\n"
|
|
651
|
+
"Generate a strong initial candidate based on the goal above.\n"
|
|
652
|
+
"Provide ONLY the candidate within ``` blocks. "
|
|
653
|
+
"Do not include explanations or commentary outside the ``` blocks."
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
return "\n".join(sections)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def _generate_seed_candidate(
|
|
660
|
+
lm: LanguageModel,
|
|
661
|
+
objective: str,
|
|
662
|
+
background: str | None = None,
|
|
663
|
+
dataset: list[DataInst] | None = None,
|
|
664
|
+
logger: LoggerProtocol | None = None,
|
|
665
|
+
) -> Candidate:
|
|
666
|
+
"""Call the reflection LM to generate an initial seed candidate.
|
|
667
|
+
|
|
668
|
+
Returns a single-key candidate dict ``{_STR_CANDIDATE_KEY: generated_text}``.
|
|
669
|
+
"""
|
|
670
|
+
from gepa.strategies.instruction_proposal import InstructionProposalSignature
|
|
671
|
+
|
|
672
|
+
prompt = _build_seed_generation_prompt(
|
|
673
|
+
objective=objective,
|
|
674
|
+
background=background,
|
|
675
|
+
dataset=dataset,
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
if logger:
|
|
679
|
+
logger.log("Generating initial seed candidate via LLM...")
|
|
680
|
+
|
|
681
|
+
lm_output = lm(prompt)
|
|
682
|
+
extracted = InstructionProposalSignature.output_extractor(lm_output)
|
|
683
|
+
generated_text = extracted["new_instruction"]
|
|
684
|
+
|
|
685
|
+
if logger:
|
|
686
|
+
logger.log(f"Generated seed candidate ({len(generated_text)} chars)")
|
|
687
|
+
|
|
688
|
+
return {_STR_CANDIDATE_KEY: generated_text}
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
optimize_anything_reflection_prompt_template: str = """I am optimizing a parameter in my system. The current parameter value is:
|
|
692
|
+
```
|
|
693
|
+
<curr_param>
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
Below is evaluation data showing how this parameter value performed across multiple test cases. The data contains performance metrics, diagnostic information, and other relevant details from the evaluation:
|
|
697
|
+
```
|
|
698
|
+
<side_info>
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
Your task is to propose a new, improved parameter value that can be used as a drop-in replacement for the current one.
|
|
702
|
+
|
|
703
|
+
Carefully analyze all the evaluation data provided above. Look for patterns that indicate what works and what doesn't. Pay special attention to:
|
|
704
|
+
- Performance metrics and how they correlate with parameter behavior
|
|
705
|
+
- Recurring issues, errors, or failure patterns across multiple test cases
|
|
706
|
+
- Successful patterns or behaviors that should be preserved or enhanced
|
|
707
|
+
- Any domain-specific requirements, constraints, or factual information revealed in the evaluation data
|
|
708
|
+
- Specific technical details that are crucial for understanding the parameter's role
|
|
709
|
+
|
|
710
|
+
Based on your analysis, propose a new parameter value that addresses the identified issues while maintaining or improving upon what works well. Your proposal should be directly informed by the patterns and insights from the evaluation data.
|
|
711
|
+
|
|
712
|
+
Provide the new parameter value within ``` blocks."""
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
# --- Component 2: Proposer Configurations ---
|
|
716
|
+
@dataclass
|
|
717
|
+
class ReflectionConfig:
|
|
718
|
+
"""Controls how the LLM proposes improved candidates each iteration.
|
|
719
|
+
|
|
720
|
+
The reflection LM sees evaluation feedback (side_info) for a minibatch of
|
|
721
|
+
examples and proposes an improved candidate. ``reflection_lm`` is the
|
|
722
|
+
model used for this step (defaults to ``openai/gpt-5.1``).
|
|
723
|
+
|
|
724
|
+
``reflection_minibatch_size`` controls how many examples are shown per
|
|
725
|
+
reflection step (default: 1 for single-task, 3 otherwise). Showing a
|
|
726
|
+
small minibatch rather than all examples at once produces focused,
|
|
727
|
+
targeted improvements on that subset. Over iterations, all examples get
|
|
728
|
+
attention, and the Pareto frontier preserves specialized gains across
|
|
729
|
+
iterations rather than averaging them away.
|
|
730
|
+
"""
|
|
731
|
+
|
|
732
|
+
skip_perfect_score: bool = False
|
|
733
|
+
perfect_score: float | None = None
|
|
734
|
+
# Advanced: a ReflectionLM implementation that owns how reflective mutation
|
|
735
|
+
# calls the reflection model (sessions, ComBEE-style aggregation, coding
|
|
736
|
+
# agents). Defaults to the stateless single-call reflector built from
|
|
737
|
+
# ``reflection_lm``. Mirror of gepa.optimize()'s reflection_strategy=.
|
|
738
|
+
reflection_strategy: "ReflectionLM | None" = None
|
|
739
|
+
batch_sampler: BatchSampler | Literal["epoch_shuffled"] = "epoch_shuffled"
|
|
740
|
+
reflection_minibatch_size: int | None = None # Default: 1 for single-instance mode, 3 otherwise
|
|
741
|
+
module_selector: ReflectionComponentSelector | Literal["round_robin", "all"] = "round_robin"
|
|
742
|
+
reflection_lm: LanguageModel | str | None = "openai/gpt-5.1"
|
|
743
|
+
reflection_lm_kwargs: dict[str, Any] | None = None
|
|
744
|
+
"""Extra keyword arguments forwarded to ``litellm.completion`` when
|
|
745
|
+
``reflection_lm`` is a model name string (e.g.
|
|
746
|
+
``{"reasoning_effort": "high", "temperature": 0.7}``).
|
|
747
|
+
Ignored when ``reflection_lm`` is already a callable."""
|
|
748
|
+
reflection_prompt_template: str | dict[str, str] | None = optimize_anything_reflection_prompt_template
|
|
749
|
+
custom_candidate_proposer: ProposalFn | None = None
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
@dataclass
|
|
753
|
+
class MergeConfig:
|
|
754
|
+
"""Enables cross-pollination between candidates on the Pareto frontier.
|
|
755
|
+
|
|
756
|
+
When set, GEPA periodically attempts to merge strengths of two candidates
|
|
757
|
+
that each excel on different subsets of the validation set.
|
|
758
|
+
"""
|
|
759
|
+
|
|
760
|
+
max_merge_invocations: int = 5
|
|
761
|
+
merge_val_overlap_floor: int = 5
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
# --- Refiner Configuration ---
|
|
765
|
+
|
|
766
|
+
DEFAULT_REFINER_PROMPT = """You are a refinement agent improving candidates in an optimization loop.
|
|
767
|
+
|
|
768
|
+
## What We're Optimizing For
|
|
769
|
+
The overall optimization objective is:
|
|
770
|
+
{objective}
|
|
771
|
+
|
|
772
|
+
This tells you what "better" means - use it to guide your improvements.
|
|
773
|
+
|
|
774
|
+
## Domain Knowledge
|
|
775
|
+
{background}
|
|
776
|
+
|
|
777
|
+
## Your Task
|
|
778
|
+
Given a candidate and its evaluation feedback:
|
|
779
|
+
1. Understand why it scored the way it did
|
|
780
|
+
2. Fix any errors (errors = zero score)
|
|
781
|
+
3. Make improvements that move toward the objective
|
|
782
|
+
4. Return the complete improved candidate
|
|
783
|
+
"""
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
@dataclass
|
|
787
|
+
class RefinerConfig:
|
|
788
|
+
"""Automatic per-evaluation candidate refinement via LLM.
|
|
789
|
+
|
|
790
|
+
When enabled, after each evaluation GEPA calls an LLM to propose a refined
|
|
791
|
+
version of the candidate based on the evaluation feedback. The refined
|
|
792
|
+
candidate is re-evaluated, and the better of (original, refined) is kept.
|
|
793
|
+
|
|
794
|
+
A ``refiner_prompt`` parameter is auto-injected into seed candidates and
|
|
795
|
+
co-evolved alongside the other parameters. All non-refiner params are
|
|
796
|
+
refined together as a JSON dict.
|
|
797
|
+
|
|
798
|
+
Set ``config.refiner = None`` to disable refinement.
|
|
799
|
+
"""
|
|
800
|
+
|
|
801
|
+
# Language model for refinement (defaults to reflection_lm if not specified)
|
|
802
|
+
refiner_lm: LanguageModel | str | None = None
|
|
803
|
+
|
|
804
|
+
# Maximum refinement iterations per evaluation
|
|
805
|
+
max_refinements: int = 1
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
# --- Component 3: Experiment Tracking Configuration ---
|
|
809
|
+
@dataclass
|
|
810
|
+
class TrackingConfig:
|
|
811
|
+
"""Experiment tracking and logging (W&B, MLflow, or custom logger)."""
|
|
812
|
+
|
|
813
|
+
logger: LoggerProtocol | None = None
|
|
814
|
+
use_wandb: bool = False
|
|
815
|
+
wandb_api_key: str | None = None
|
|
816
|
+
wandb_init_kwargs: dict[str, Any] | None = None
|
|
817
|
+
wandb_attach_existing: bool = False
|
|
818
|
+
"""Attach to an already-active W&B run without managing its lifecycle.
|
|
819
|
+
|
|
820
|
+
When ``True``, GEPA logs metrics and tables into the run that is already
|
|
821
|
+
active in the process (``wandb.run``) — it will not call ``wandb.init()``
|
|
822
|
+
on entry or ``wandb.finish()`` on exit.
|
|
823
|
+
"""
|
|
824
|
+
wandb_step_metric: str | None = None
|
|
825
|
+
"""Custom x-axis metric name for wandb charts.
|
|
826
|
+
|
|
827
|
+
When set, GEPA uses ``wandb.define_metric`` to declare a custom x-axis
|
|
828
|
+
for all its metrics, decoupling them from wandb's global monotonic step
|
|
829
|
+
counter. The ``step`` value passed to ``log_metrics`` is injected as a
|
|
830
|
+
regular metric (under this name) instead of being passed as ``step=``.
|
|
831
|
+
|
|
832
|
+
**Required when embedding GEPA inside a host training loop** that manages
|
|
833
|
+
its own wandb step counter. Without this, GEPA's ``step=1, 2, 3, ...``
|
|
834
|
+
collides with the host's ``step=100, 101, ...``, causing wandb to drop
|
|
835
|
+
GEPA's data.
|
|
836
|
+
|
|
837
|
+
Example::
|
|
838
|
+
|
|
839
|
+
TrackingConfig(
|
|
840
|
+
use_wandb=True,
|
|
841
|
+
wandb_attach_existing=True,
|
|
842
|
+
wandb_step_metric="gepa/iteration",
|
|
843
|
+
)
|
|
844
|
+
"""
|
|
845
|
+
use_mlflow: bool = False
|
|
846
|
+
mlflow_tracking_uri: str | None = None
|
|
847
|
+
mlflow_experiment_name: str | None = None
|
|
848
|
+
mlflow_attach_existing: bool = False
|
|
849
|
+
"""Attach to an already-active MLflow run without managing its lifecycle.
|
|
850
|
+
|
|
851
|
+
When ``True``, GEPA logs into the run that is already active (via
|
|
852
|
+
``mlflow.active_run()``) — it will not call ``mlflow.start_run()`` on
|
|
853
|
+
entry or ``mlflow.end_run()`` on exit.
|
|
854
|
+
|
|
855
|
+
Use this when embedding GEPA inside a training loop that manages its own
|
|
856
|
+
MLflow run::
|
|
857
|
+
|
|
858
|
+
import mlflow
|
|
859
|
+
with mlflow.start_run(): # caller owns this run
|
|
860
|
+
result = optimize_anything(
|
|
861
|
+
...,
|
|
862
|
+
config=GEPAConfig(
|
|
863
|
+
tracking=TrackingConfig(
|
|
864
|
+
use_mlflow=True,
|
|
865
|
+
mlflow_attach_existing=True,
|
|
866
|
+
)
|
|
867
|
+
),
|
|
868
|
+
)
|
|
869
|
+
mlflow.log_metric("train/loss", 0.1) # still works
|
|
870
|
+
"""
|
|
871
|
+
key_prefix: str = ""
|
|
872
|
+
"""String prepended to every key/name logged to wandb and MLflow.
|
|
873
|
+
|
|
874
|
+
Applies uniformly to metric keys, config keys, summary keys, table names,
|
|
875
|
+
and HTML artifact keys. Useful when running multiple GEPA optimizations in
|
|
876
|
+
the same wandb/MLflow run to keep their data namespaced::
|
|
877
|
+
|
|
878
|
+
TrackingConfig(
|
|
879
|
+
use_wandb=True,
|
|
880
|
+
wandb_attach_existing=True,
|
|
881
|
+
key_prefix="gepa/round2/", # metrics become e.g. gepa/round2/val_score
|
|
882
|
+
)
|
|
883
|
+
"""
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
@dataclass
|
|
887
|
+
class GEPAConfig:
|
|
888
|
+
"""Top-level configuration for :func:`optimize_anything`.
|
|
889
|
+
|
|
890
|
+
Groups all settings into nested component configs. Sensible defaults are
|
|
891
|
+
provided — most users only need to set ``engine.max_metric_calls`` and
|
|
892
|
+
optionally ``reflection.reflection_lm``.
|
|
893
|
+
|
|
894
|
+
Example::
|
|
895
|
+
|
|
896
|
+
config = GEPAConfig(
|
|
897
|
+
engine=EngineConfig(max_metric_calls=200),
|
|
898
|
+
reflection=ReflectionConfig(reflection_lm="openai/gpt-5.1"),
|
|
899
|
+
refiner=RefinerConfig(max_refinements=2),
|
|
900
|
+
)
|
|
901
|
+
"""
|
|
902
|
+
|
|
903
|
+
# Component configurations
|
|
904
|
+
engine: EngineConfig = field(default_factory=EngineConfig)
|
|
905
|
+
reflection: ReflectionConfig = field(default_factory=ReflectionConfig)
|
|
906
|
+
tracking: TrackingConfig = field(default_factory=TrackingConfig)
|
|
907
|
+
|
|
908
|
+
# Use 'None' to disable these optional components
|
|
909
|
+
merge: MergeConfig | None = None
|
|
910
|
+
refiner: RefinerConfig | None = None
|
|
911
|
+
|
|
912
|
+
# Complex callbacks that aren't serializable
|
|
913
|
+
stop_callbacks: StopperProtocol | Sequence[StopperProtocol] | None = None
|
|
914
|
+
callbacks: "list[GEPACallback] | None" = None
|
|
915
|
+
"""Observation callbacks for monitoring optimization progress.
|
|
916
|
+
|
|
917
|
+
Receive events like ``on_optimization_start``, ``on_iteration_end``,
|
|
918
|
+
``on_candidate_accepted``, ``on_proposal_end``, etc. See
|
|
919
|
+
:class:`~gepa.core.callbacks.GEPACallback` for the full protocol.
|
|
920
|
+
|
|
921
|
+
Example::
|
|
922
|
+
|
|
923
|
+
class MyCallback:
|
|
924
|
+
def on_candidate_accepted(self, event):
|
|
925
|
+
print(f"New candidate {event['new_candidate_idx']} accepted")
|
|
926
|
+
|
|
927
|
+
config = GEPAConfig(
|
|
928
|
+
callbacks=[MyCallback()],
|
|
929
|
+
engine=EngineConfig(max_metric_calls=100),
|
|
930
|
+
)
|
|
931
|
+
"""
|
|
932
|
+
|
|
933
|
+
def __post_init__(self):
|
|
934
|
+
"""Handle dicts passed in (e.g., from a JSON/YAML file)."""
|
|
935
|
+
if isinstance(self.engine, dict):
|
|
936
|
+
self.engine = EngineConfig(**self.engine)
|
|
937
|
+
if isinstance(self.reflection, dict):
|
|
938
|
+
self.reflection = ReflectionConfig(**self.reflection)
|
|
939
|
+
if isinstance(self.tracking, dict):
|
|
940
|
+
self.tracking = TrackingConfig(**self.tracking)
|
|
941
|
+
if isinstance(self.merge, dict):
|
|
942
|
+
self.merge = MergeConfig(**self.merge)
|
|
943
|
+
if isinstance(self.refiner, dict):
|
|
944
|
+
self.refiner = RefinerConfig(**self.refiner)
|
|
945
|
+
|
|
946
|
+
def to_dict(self) -> dict[str, Any]:
|
|
947
|
+
"""Convert config to dictionary representation."""
|
|
948
|
+
return asdict(self)
|
|
949
|
+
|
|
950
|
+
@staticmethod
|
|
951
|
+
def from_dict(d: dict[str, Any]) -> "GEPAConfig":
|
|
952
|
+
"""Create config from dictionary representation."""
|
|
953
|
+
return GEPAConfig(**d)
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def make_litellm_lm(model_name: str, **kwargs: Any) -> LanguageModel:
|
|
957
|
+
"""Convert a LiteLLM model name string to a :class:`LanguageModel` callable.
|
|
958
|
+
|
|
959
|
+
The returned callable conforms to the ``LanguageModel`` protocol and
|
|
960
|
+
accepts a plain ``str`` prompt, a ``list[dict]`` chat-messages list, or
|
|
961
|
+
a multimodal messages list (with content arrays containing images).
|
|
962
|
+
|
|
963
|
+
Uses :class:`gepa.lm.LM` which handles reasoning model detection
|
|
964
|
+
(o1/o3/o4/gpt-5), retries with exponential backoff, truncation
|
|
965
|
+
warnings, and ``drop_params=True`` for cross-model compatibility.
|
|
966
|
+
|
|
967
|
+
Args:
|
|
968
|
+
model_name: LiteLLM model identifier (e.g. ``"openai/gpt-5"``).
|
|
969
|
+
**kwargs: Extra keyword arguments forwarded to ``litellm.completion``
|
|
970
|
+
(e.g. ``reasoning_effort="high"``, ``temperature=0.7``).
|
|
971
|
+
"""
|
|
972
|
+
from gepa.lm import LM
|
|
973
|
+
|
|
974
|
+
return LM(model_name, **kwargs)
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
class EvaluatorWrapper:
|
|
978
|
+
"""Internal wrapper that adapts a user's evaluator to GEPA's internal interface.
|
|
979
|
+
|
|
980
|
+
Handles: single-instance mode (omits ``example``), str-candidate unwrapping,
|
|
981
|
+
kwarg filtering (incl. ``opt_state`` injection), ``oa.log()`` capture,
|
|
982
|
+
optional stdout/stderr capture, and normalizing the return value to
|
|
983
|
+
``(score, output, side_info)`` regardless of what the user returns.
|
|
984
|
+
"""
|
|
985
|
+
|
|
986
|
+
def __init__(
|
|
987
|
+
self,
|
|
988
|
+
evaluator_fn: Callable[..., Any],
|
|
989
|
+
single_instance_mode: bool,
|
|
990
|
+
capture_stdio: bool = False,
|
|
991
|
+
str_candidate_mode: bool = False,
|
|
992
|
+
raise_on_exception: bool = True,
|
|
993
|
+
) -> None:
|
|
994
|
+
# Inspect the evaluator's signature once to determine which kwargs it accepts.
|
|
995
|
+
sig = inspect.signature(evaluator_fn)
|
|
996
|
+
has_var_keyword = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values())
|
|
997
|
+
if has_var_keyword:
|
|
998
|
+
accepted_params = None # accept all
|
|
999
|
+
else:
|
|
1000
|
+
accepted_params = set(sig.parameters.keys())
|
|
1001
|
+
|
|
1002
|
+
def _filter_kwargs(kwargs: dict) -> dict:
|
|
1003
|
+
if accepted_params is None:
|
|
1004
|
+
return kwargs
|
|
1005
|
+
return {k: v for k, v in kwargs.items() if k in accepted_params}
|
|
1006
|
+
|
|
1007
|
+
def wrapped_evaluator(
|
|
1008
|
+
candidate: Candidate, example: object | None = None, **kwargs: Any
|
|
1009
|
+
) -> tuple[float, Any, SideInfo]:
|
|
1010
|
+
# Create a fresh, shared log context for this evaluator call.
|
|
1011
|
+
# The same LogContext is accessible from child threads via
|
|
1012
|
+
# oa.get_log_context() / oa.set_log_context().
|
|
1013
|
+
log_ctx = LogContext()
|
|
1014
|
+
_set_log_context(log_ctx)
|
|
1015
|
+
|
|
1016
|
+
# Build full kwargs dict. In single-instance mode, don't forward
|
|
1017
|
+
# example to the evaluator at all.
|
|
1018
|
+
if single_instance_mode:
|
|
1019
|
+
all_kwargs = kwargs
|
|
1020
|
+
else:
|
|
1021
|
+
all_kwargs = {"example": example, **kwargs}
|
|
1022
|
+
|
|
1023
|
+
filtered = _filter_kwargs(all_kwargs)
|
|
1024
|
+
|
|
1025
|
+
# Unwrap candidate for str_candidate_mode
|
|
1026
|
+
eval_candidate: Candidate | str = candidate
|
|
1027
|
+
if str_candidate_mode:
|
|
1028
|
+
eval_candidate = candidate[_STR_CANDIDATE_KEY]
|
|
1029
|
+
|
|
1030
|
+
# Acquire per-thread stream capture from the shared manager per-call.
|
|
1031
|
+
# This scopes the sys.stdout/stderr replacement to only the duration
|
|
1032
|
+
# of evaluator execution, restoring the originals between calls.
|
|
1033
|
+
# Both acquire/start_capture and the evaluator call are inside the
|
|
1034
|
+
# same try/finally so that stream_manager.release() is always called
|
|
1035
|
+
# even if start_capture() raises (e.g. assertion on double-capture).
|
|
1036
|
+
stdout_capturer: ThreadLocalStreamCapture | None = None
|
|
1037
|
+
stderr_capturer: ThreadLocalStreamCapture | None = None
|
|
1038
|
+
try:
|
|
1039
|
+
if capture_stdio:
|
|
1040
|
+
stdout_capturer, stderr_capturer = stream_manager.acquire()
|
|
1041
|
+
stdout_capturer.start_capture()
|
|
1042
|
+
stderr_capturer.start_capture()
|
|
1043
|
+
|
|
1044
|
+
result = evaluator_fn(eval_candidate, **filtered)
|
|
1045
|
+
except Exception as e:
|
|
1046
|
+
result = e # Sentinel; handled below after cleanup
|
|
1047
|
+
finally:
|
|
1048
|
+
captured_stdout = stdout_capturer.stop_capture() if stdout_capturer else ""
|
|
1049
|
+
captured_stderr = stderr_capturer.stop_capture() if stderr_capturer else ""
|
|
1050
|
+
if capture_stdio and stdout_capturer is not None:
|
|
1051
|
+
stream_manager.release()
|
|
1052
|
+
log_output = log_ctx.drain()
|
|
1053
|
+
_set_log_context(None)
|
|
1054
|
+
|
|
1055
|
+
# If evaluator raised, preserve captured diagnostics
|
|
1056
|
+
if isinstance(result, Exception):
|
|
1057
|
+
if raise_on_exception:
|
|
1058
|
+
raise result
|
|
1059
|
+
fail_side_info: SideInfo = {"error": str(result)}
|
|
1060
|
+
if log_output:
|
|
1061
|
+
fail_side_info["log"] = log_output
|
|
1062
|
+
if captured_stdout:
|
|
1063
|
+
fail_side_info["stdout"] = captured_stdout
|
|
1064
|
+
if captured_stderr:
|
|
1065
|
+
fail_side_info["stderr"] = captured_stderr
|
|
1066
|
+
return 0.0, None, fail_side_info
|
|
1067
|
+
|
|
1068
|
+
# Detect return type and normalize to (score, output, side_info)
|
|
1069
|
+
if isinstance(result, tuple):
|
|
1070
|
+
score, side_info = result
|
|
1071
|
+
side_info = dict(side_info) if side_info is not None else {}
|
|
1072
|
+
|
|
1073
|
+
# Inject captured output, renaming on collision with a warning
|
|
1074
|
+
injected: dict[str, str] = {}
|
|
1075
|
+
if log_output:
|
|
1076
|
+
injected["log"] = log_output
|
|
1077
|
+
if captured_stdout:
|
|
1078
|
+
injected["stdout"] = captured_stdout
|
|
1079
|
+
if captured_stderr:
|
|
1080
|
+
injected["stderr"] = captured_stderr
|
|
1081
|
+
|
|
1082
|
+
for key in list(injected):
|
|
1083
|
+
if key in side_info:
|
|
1084
|
+
prefixed = f"_gepa_{key}"
|
|
1085
|
+
warnings.warn(
|
|
1086
|
+
f"Your evaluator returned side_info with key '{key}' that conflicts "
|
|
1087
|
+
f"with GEPA's captured output key. The captured output will be stored "
|
|
1088
|
+
f"under '{prefixed}' instead.",
|
|
1089
|
+
stacklevel=2,
|
|
1090
|
+
)
|
|
1091
|
+
injected[prefixed] = injected.pop(key)
|
|
1092
|
+
|
|
1093
|
+
side_info.update(injected)
|
|
1094
|
+
return score, None, side_info
|
|
1095
|
+
else:
|
|
1096
|
+
score = result
|
|
1097
|
+
auto_side_info: SideInfo = {}
|
|
1098
|
+
if captured_stdout:
|
|
1099
|
+
auto_side_info["stdout"] = captured_stdout
|
|
1100
|
+
if captured_stderr:
|
|
1101
|
+
auto_side_info["stderr"] = captured_stderr
|
|
1102
|
+
if log_output:
|
|
1103
|
+
auto_side_info["log"] = log_output
|
|
1104
|
+
return score, None, auto_side_info
|
|
1105
|
+
|
|
1106
|
+
self._wrapped = wrapped_evaluator
|
|
1107
|
+
|
|
1108
|
+
def __call__(
|
|
1109
|
+
self, candidate: Candidate, example: object | None = None, **kwargs: Any
|
|
1110
|
+
) -> tuple[float, Any, SideInfo]:
|
|
1111
|
+
return self._wrapped(candidate, example=example, **kwargs)
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
def optimize_anything(
|
|
1115
|
+
seed_candidate: str | Candidate | None = None,
|
|
1116
|
+
*,
|
|
1117
|
+
evaluator: Callable[..., Any] | None = None,
|
|
1118
|
+
batch_evaluator: Callable[..., Any] | None = None,
|
|
1119
|
+
dataset: list[DataInst] | None = None,
|
|
1120
|
+
valset: list[DataInst] | None = None,
|
|
1121
|
+
objective: str | None = None,
|
|
1122
|
+
background: str | None = None,
|
|
1123
|
+
config: GEPAConfig | None = None,
|
|
1124
|
+
) -> GEPAResult:
|
|
1125
|
+
"""Optimize any text artifact using LLM-guided search.
|
|
1126
|
+
|
|
1127
|
+
This is the main entry point for GEPA. You declare the **what** — your
|
|
1128
|
+
artifact, your evaluator, and any domain knowledge — and
|
|
1129
|
+
``optimize_anything`` handles the **how**: prompt construction, reflection,
|
|
1130
|
+
candidate selection, and Pareto-efficient search.
|
|
1131
|
+
|
|
1132
|
+
**Three optimization modes** (determined by ``dataset`` / ``valset``):
|
|
1133
|
+
|
|
1134
|
+
1. **Single-Task Search** (``dataset=None, valset=None``):
|
|
1135
|
+
Solve one hard problem. The candidate *is* the solution.
|
|
1136
|
+
Evaluator called without ``example``.
|
|
1137
|
+
*E.g. circle packing, blackbox mathematical optimization.*
|
|
1138
|
+
|
|
1139
|
+
2. **Multi-Task Search** (``dataset=<list>, valset=None``):
|
|
1140
|
+
Solve a batch of related problems with cross-task transfer.
|
|
1141
|
+
Insights from solving one help solve the others.
|
|
1142
|
+
``valset`` defaults to ``dataset``.
|
|
1143
|
+
*E.g. CUDA kernel generation, multi-aspect SVG optimization.*
|
|
1144
|
+
|
|
1145
|
+
3. **Generalization** (``dataset=<list>, valset=<list>``):
|
|
1146
|
+
Build a skill that transfers to unseen problems.
|
|
1147
|
+
*E.g. prompt optimization for AIME math, agent architecture evolution
|
|
1148
|
+
for ARC-AGI, cloud scheduling policy discovery.*
|
|
1149
|
+
|
|
1150
|
+
Args:
|
|
1151
|
+
seed_candidate: Starting point for optimization.
|
|
1152
|
+
|
|
1153
|
+
- ``str`` — single text parameter (evaluator receives ``str``).
|
|
1154
|
+
- ``dict[str, str]`` — named parameters (evaluator receives the dict).
|
|
1155
|
+
- ``None`` — **seedless mode**: the reflection LLM generates the
|
|
1156
|
+
initial candidate from ``objective`` (and optionally ``background``
|
|
1157
|
+
/ ``dataset``). Requires ``objective``. Useful for creative or
|
|
1158
|
+
exploratory tasks where you know *what good looks like* but not
|
|
1159
|
+
where to begin.
|
|
1160
|
+
|
|
1161
|
+
evaluator: Scoring function. Returns ``(score, side_info)`` or ``score``.
|
|
1162
|
+
Optional when ``batch_evaluator`` is provided (at least one is
|
|
1163
|
+
required): without it, every evaluation — including refiner steps,
|
|
1164
|
+
as singleton batches — routes through ``batch_evaluator``.
|
|
1165
|
+
See :class:`Evaluator`. Diagnostic output via ``oa.log()`` is
|
|
1166
|
+
automatically captured as Actionable Side Information (ASI).
|
|
1167
|
+
For richer diagnostics, return a ``(score, dict)`` tuple with
|
|
1168
|
+
structured feedback, error messages, or even rendered images
|
|
1169
|
+
(via :class:`~gepa.Image`).
|
|
1170
|
+
batch_evaluator: Optional low-level batch evaluation hook. When set,
|
|
1171
|
+
GEPA hands you ALL pending ``(candidate, example)`` pairs in ONE
|
|
1172
|
+
call — e.g. to submit a provider batch job or fan out over your
|
|
1173
|
+
own infrastructure — instead of calling ``evaluator`` per pair.
|
|
1174
|
+
Return one result per pair, each ``score`` or
|
|
1175
|
+
``(score, side_info)``. Parity with the standard path: candidates
|
|
1176
|
+
are unwrapped in str-candidate mode; if your function accepts an
|
|
1177
|
+
``opt_states`` keyword it receives per-pair
|
|
1178
|
+
:class:`OptimizationState` objects (warm-starting; note they are
|
|
1179
|
+
snapshotted when the grouped call is issued, so pairs within one
|
|
1180
|
+
call do not see each other's fresh results); a raised call
|
|
1181
|
+
is converted to per-pair ``(0.0, {"error": ...})`` results when
|
|
1182
|
+
``raise_on_exception=False``; best-evals history and output
|
|
1183
|
+
packaging match the sequential path exactly. NOT available on this
|
|
1184
|
+
path (structurally per-call features): ``oa.log()`` capture and
|
|
1185
|
+
stdout/stderr capture — put diagnostics in each returned
|
|
1186
|
+
``side_info`` instead. When both are provided,
|
|
1187
|
+
multi-pair evaluations (minibatch stages, valset/seed/merge evals)
|
|
1188
|
+
prefer ``batch_evaluator`` while single-pair resolutions (refiner
|
|
1189
|
+
steps) use ``evaluator``; with only ``batch_evaluator``, singles
|
|
1190
|
+
route through it as singleton batches. Caching (``cache_evaluation``)
|
|
1191
|
+
applies identically on both paths, with a shared store: a pair
|
|
1192
|
+
cached by either path is a hit for the other. Note: with
|
|
1193
|
+
``refiner_config`` + ``parallel=True`` and no ``evaluator``, your
|
|
1194
|
+
batch function may be called concurrently with singleton batches
|
|
1195
|
+
from the refinement thread pool — make it thread-safe.
|
|
1196
|
+
dataset: Examples for multi-task or generalization modes.
|
|
1197
|
+
``None`` = single-task search mode.
|
|
1198
|
+
valset: Held-out validation set for generalization mode.
|
|
1199
|
+
``None`` = defaults to ``dataset`` (multi-task search).
|
|
1200
|
+
objective: Natural-language goal for the reflection LLM (e.g.
|
|
1201
|
+
``"Generate prompts that solve competition math problems."``).
|
|
1202
|
+
background: Domain knowledge, constraints, or strategies for the
|
|
1203
|
+
reflection LLM.
|
|
1204
|
+
config: Full configuration. See :class:`GEPAConfig`.
|
|
1205
|
+
|
|
1206
|
+
Returns:
|
|
1207
|
+
:class:`~gepa.core.result.GEPAResult` — access ``result.best_candidate``
|
|
1208
|
+
for the optimized parameter(s) and the full optimization history.
|
|
1209
|
+
|
|
1210
|
+
Examples:
|
|
1211
|
+
|
|
1212
|
+
Single-task search (circle packing)::
|
|
1213
|
+
|
|
1214
|
+
import gepa.optimize_anything as oa
|
|
1215
|
+
|
|
1216
|
+
def evaluate(candidate: str) -> float:
|
|
1217
|
+
result = run_code(candidate)
|
|
1218
|
+
oa.log(f"Score: {result.score}, Overlaps: {result.overlaps}")
|
|
1219
|
+
return result.score
|
|
1220
|
+
|
|
1221
|
+
result = optimize_anything(
|
|
1222
|
+
seed_candidate="def pack_circles(): ...",
|
|
1223
|
+
evaluator=evaluate,
|
|
1224
|
+
objective="Maximize the sum of radii for n circles in a unit square.",
|
|
1225
|
+
config=GEPAConfig(engine=EngineConfig(max_metric_calls=500)),
|
|
1226
|
+
)
|
|
1227
|
+
|
|
1228
|
+
Multi-task search (CUDA kernels)::
|
|
1229
|
+
|
|
1230
|
+
result = optimize_anything(
|
|
1231
|
+
seed_candidate={"prompt": "Write an optimized CUDA kernel."},
|
|
1232
|
+
evaluator=kernel_evaluator,
|
|
1233
|
+
dataset=kernel_problems, # batch of related problems
|
|
1234
|
+
objective="Generate prompts that produce fast, correct CUDA kernels.",
|
|
1235
|
+
config=GEPAConfig(engine=EngineConfig(max_metric_calls=300)),
|
|
1236
|
+
)
|
|
1237
|
+
|
|
1238
|
+
Generalization (prompt optimization for math)::
|
|
1239
|
+
|
|
1240
|
+
result = optimize_anything(
|
|
1241
|
+
seed_candidate={"prompt": "Solve this math problem step by step:"},
|
|
1242
|
+
evaluator=math_evaluator,
|
|
1243
|
+
dataset=train_problems, # train on these
|
|
1244
|
+
valset=val_problems, # must generalize to these
|
|
1245
|
+
objective="Generate system prompts that improve math reasoning.",
|
|
1246
|
+
config=GEPAConfig(engine=EngineConfig(max_metric_calls=200)),
|
|
1247
|
+
)
|
|
1248
|
+
|
|
1249
|
+
Seedless mode (no starting artifact)::
|
|
1250
|
+
|
|
1251
|
+
result = optimize_anything(
|
|
1252
|
+
seed_candidate=None, # LLM writes the first draft
|
|
1253
|
+
evaluator=evaluate_3d_render,
|
|
1254
|
+
dataset=visual_aspects,
|
|
1255
|
+
objective="Optimize a Python program to generate a 3D unicorn.",
|
|
1256
|
+
background="Use build123d for CSG geometry, export to STL, render with pyrender.",
|
|
1257
|
+
)
|
|
1258
|
+
"""
|
|
1259
|
+
# Use default config if not provided
|
|
1260
|
+
if config is None:
|
|
1261
|
+
config = GEPAConfig()
|
|
1262
|
+
|
|
1263
|
+
# Detect seed generation mode: when seed_candidate is None, the LLM
|
|
1264
|
+
# will generate the initial candidate from the objective.
|
|
1265
|
+
needs_seed_generation = False
|
|
1266
|
+
if seed_candidate is None:
|
|
1267
|
+
needs_seed_generation = True
|
|
1268
|
+
str_candidate_mode = True
|
|
1269
|
+
if not objective or not objective.strip():
|
|
1270
|
+
raise ValueError(
|
|
1271
|
+
"'objective' is required when seed_candidate is None. "
|
|
1272
|
+
"The reflection LLM needs the objective to generate an initial candidate."
|
|
1273
|
+
)
|
|
1274
|
+
seed_candidate = {_STR_CANDIDATE_KEY: ""} # placeholder until LLM generates it
|
|
1275
|
+
else:
|
|
1276
|
+
# Normalize seed_candidate: str -> {_STR_CANDIDATE_KEY: str}
|
|
1277
|
+
str_candidate_mode = isinstance(seed_candidate, str)
|
|
1278
|
+
if isinstance(seed_candidate, str):
|
|
1279
|
+
seed_candidate = {_STR_CANDIDATE_KEY: seed_candidate}
|
|
1280
|
+
|
|
1281
|
+
# Detect single-instance mode: when both dataset=None and valset=None
|
|
1282
|
+
single_instance_mode = dataset is None and valset is None
|
|
1283
|
+
|
|
1284
|
+
# Set reflection_minibatch_size default based on mode (if not explicitly set)
|
|
1285
|
+
if config.reflection.reflection_minibatch_size is None:
|
|
1286
|
+
config.reflection.reflection_minibatch_size = 1 if single_instance_mode else 3
|
|
1287
|
+
|
|
1288
|
+
# Handle single-instance mode: when both dataset=None and valset=None, create a
|
|
1289
|
+
# dataset with a single sentinel element. The evaluator will be called
|
|
1290
|
+
# without the example parameter.
|
|
1291
|
+
if single_instance_mode:
|
|
1292
|
+
effective_dataset: list[DataInst] = [_SINGLE_INSTANCE_SENTINEL] # type: ignore[list-item]
|
|
1293
|
+
else:
|
|
1294
|
+
effective_dataset = dataset if dataset is not None else [None] # type: ignore[list-item]
|
|
1295
|
+
|
|
1296
|
+
# Wrap the evaluator to handle signature normalization, log/stdout capture, etc.
|
|
1297
|
+
if evaluator is None and batch_evaluator is None:
|
|
1298
|
+
raise ValueError(
|
|
1299
|
+
"Provide evaluator=, batch_evaluator=, or both. "
|
|
1300
|
+
"batch_evaluator alone is sufficient: single-pair resolutions "
|
|
1301
|
+
"(e.g. refiner steps) are routed through it as singleton batches."
|
|
1302
|
+
)
|
|
1303
|
+
if config.engine.capture_stdio and evaluator is None:
|
|
1304
|
+
warnings.warn(
|
|
1305
|
+
"capture_stdio=True has no effect without a single-pair evaluator: the batch_evaluator "
|
|
1306
|
+
"path cannot scope stdio capture to individual evaluations. Return diagnostics in "
|
|
1307
|
+
"side_info instead.",
|
|
1308
|
+
stacklevel=2,
|
|
1309
|
+
)
|
|
1310
|
+
wrapped_evaluator = None
|
|
1311
|
+
if evaluator is not None:
|
|
1312
|
+
wrapped_evaluator = EvaluatorWrapper(
|
|
1313
|
+
evaluator,
|
|
1314
|
+
single_instance_mode,
|
|
1315
|
+
capture_stdio=config.engine.capture_stdio,
|
|
1316
|
+
str_candidate_mode=str_candidate_mode,
|
|
1317
|
+
raise_on_exception=config.engine.raise_on_exception,
|
|
1318
|
+
)
|
|
1319
|
+
|
|
1320
|
+
# Resolve cache mode: cache_evaluation controls on/off, cache_evaluation_storage controls where
|
|
1321
|
+
if not config.engine.cache_evaluation:
|
|
1322
|
+
resolved_cache_mode = "off"
|
|
1323
|
+
if config.engine.cache_evaluation_storage != "auto":
|
|
1324
|
+
warnings.warn(
|
|
1325
|
+
f"cache_evaluation_storage={config.engine.cache_evaluation_storage!r} is set but "
|
|
1326
|
+
f"cache_evaluation=False, so caching is disabled. Set cache_evaluation=True to "
|
|
1327
|
+
f"enable caching with the specified storage mode.",
|
|
1328
|
+
stacklevel=2,
|
|
1329
|
+
)
|
|
1330
|
+
elif config.engine.cache_evaluation_storage == "auto":
|
|
1331
|
+
resolved_cache_mode = "disk" if config.engine.run_dir else "memory"
|
|
1332
|
+
else:
|
|
1333
|
+
resolved_cache_mode = config.engine.cache_evaluation_storage
|
|
1334
|
+
|
|
1335
|
+
# Validate disk mode requires run_dir
|
|
1336
|
+
if resolved_cache_mode == "disk" and not config.engine.run_dir:
|
|
1337
|
+
raise ValueError("cache_evaluation_storage='disk' requires run_dir in EngineConfig")
|
|
1338
|
+
|
|
1339
|
+
# Configure cloudpickle for code execution subprocess serialization
|
|
1340
|
+
from gepa.utils.code_execution import set_use_cloudpickle
|
|
1341
|
+
|
|
1342
|
+
set_use_cloudpickle(config.engine.use_cloudpickle)
|
|
1343
|
+
|
|
1344
|
+
active_adapter: GEPAAdapter = OptimizeAnythingAdapter(
|
|
1345
|
+
evaluator=wrapped_evaluator,
|
|
1346
|
+
parallel=config.engine.parallel,
|
|
1347
|
+
max_workers=config.engine.max_workers,
|
|
1348
|
+
refiner_config=config.refiner,
|
|
1349
|
+
best_example_evals_k=config.engine.best_example_evals_k,
|
|
1350
|
+
objective=objective,
|
|
1351
|
+
background=background,
|
|
1352
|
+
cache_mode=resolved_cache_mode,
|
|
1353
|
+
cache_dir=config.engine.run_dir,
|
|
1354
|
+
batch_evaluator=(
|
|
1355
|
+
BatchEvaluatorWrapper(
|
|
1356
|
+
batch_evaluator,
|
|
1357
|
+
str_candidate_key=_STR_CANDIDATE_KEY if str_candidate_mode else None,
|
|
1358
|
+
raise_on_exception=config.engine.raise_on_exception,
|
|
1359
|
+
single_instance_mode=single_instance_mode,
|
|
1360
|
+
)
|
|
1361
|
+
if batch_evaluator is not None
|
|
1362
|
+
else None
|
|
1363
|
+
),
|
|
1364
|
+
)
|
|
1365
|
+
|
|
1366
|
+
# Normalize datasets to DataLoader instances
|
|
1367
|
+
train_loader = ensure_loader(effective_dataset)
|
|
1368
|
+
val_loader = ensure_loader(valset) if valset is not None else train_loader
|
|
1369
|
+
|
|
1370
|
+
# --- 1. Validate and setup reflection LM ---
|
|
1371
|
+
if needs_seed_generation and config.reflection.reflection_lm is None:
|
|
1372
|
+
raise ValueError(
|
|
1373
|
+
"reflection_lm is required when seed_candidate is None. "
|
|
1374
|
+
"Set config.reflection.reflection_lm to a model name or callable."
|
|
1375
|
+
)
|
|
1376
|
+
if not hasattr(active_adapter, "propose_new_texts"):
|
|
1377
|
+
assert config.reflection.reflection_lm is not None, (
|
|
1378
|
+
f"reflection_lm was not provided. The adapter '{active_adapter!s}' does not provide a propose_new_texts method, "
|
|
1379
|
+
+ "and hence, GEPA will use the default proposer, which requires a reflection_lm to be specified."
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
# Default refiner_lm to reflection_lm name BEFORE converting reflection_lm to callable
|
|
1383
|
+
if config.refiner is not None and config.refiner.refiner_lm is None:
|
|
1384
|
+
config.refiner.refiner_lm = config.reflection.reflection_lm
|
|
1385
|
+
|
|
1386
|
+
# Convert reflection_lm string to callable
|
|
1387
|
+
if isinstance(config.reflection.reflection_lm, str):
|
|
1388
|
+
config.reflection.reflection_lm = make_litellm_lm(
|
|
1389
|
+
config.reflection.reflection_lm, **(config.reflection.reflection_lm_kwargs or {})
|
|
1390
|
+
)
|
|
1391
|
+
elif config.reflection.reflection_lm is not None and not hasattr(config.reflection.reflection_lm, "total_cost"):
|
|
1392
|
+
from gepa.lm import TrackingLM
|
|
1393
|
+
|
|
1394
|
+
config.reflection.reflection_lm = TrackingLM(config.reflection.reflection_lm)
|
|
1395
|
+
|
|
1396
|
+
# --- 2. Build stoppers (all in one place, after LM conversion) ---
|
|
1397
|
+
stop_callbacks_list: list[StopperProtocol] = []
|
|
1398
|
+
|
|
1399
|
+
if config.stop_callbacks is not None:
|
|
1400
|
+
if isinstance(config.stop_callbacks, Sequence):
|
|
1401
|
+
stop_callbacks_list.extend(config.stop_callbacks)
|
|
1402
|
+
else:
|
|
1403
|
+
stop_callbacks_list.append(config.stop_callbacks)
|
|
1404
|
+
|
|
1405
|
+
if config.engine.run_dir is not None:
|
|
1406
|
+
stop_callbacks_list.append(FileStopper(os.path.join(config.engine.run_dir, "gepa.stop")))
|
|
1407
|
+
|
|
1408
|
+
if config.engine.max_metric_calls is not None:
|
|
1409
|
+
from gepa.utils import MaxMetricCallsStopper
|
|
1410
|
+
|
|
1411
|
+
stop_callbacks_list.append(MaxMetricCallsStopper(config.engine.max_metric_calls))
|
|
1412
|
+
|
|
1413
|
+
if config.engine.max_candidate_proposals is not None:
|
|
1414
|
+
from gepa.utils import MaxCandidateProposalsStopper
|
|
1415
|
+
|
|
1416
|
+
stop_callbacks_list.append(MaxCandidateProposalsStopper(config.engine.max_candidate_proposals))
|
|
1417
|
+
|
|
1418
|
+
if config.engine.max_reflection_cost is not None and config.reflection.reflection_strategy is not None:
|
|
1419
|
+
strategy = config.reflection.reflection_strategy
|
|
1420
|
+
if not hasattr(strategy, "total_cost"):
|
|
1421
|
+
raise ValueError(
|
|
1422
|
+
"max_reflection_cost is set but reflection_strategy does not expose total_cost — "
|
|
1423
|
+
"the cost stopper would silently never fire (unbounded reflection spend). Expose a "
|
|
1424
|
+
"total_cost property on the strategy, or remove max_reflection_cost."
|
|
1425
|
+
)
|
|
1426
|
+
if config.engine.max_reflection_cost is not None:
|
|
1427
|
+
from gepa.utils import MaxReflectionCostStopper
|
|
1428
|
+
|
|
1429
|
+
stop_callbacks_list.append(
|
|
1430
|
+
MaxReflectionCostStopper(config.engine.max_reflection_cost, reflection_lm=config.reflection.reflection_lm)
|
|
1431
|
+
)
|
|
1432
|
+
|
|
1433
|
+
if not stop_callbacks_list:
|
|
1434
|
+
raise ValueError(
|
|
1435
|
+
"At least one stopping condition must be provided via config.engine.max_metric_calls or config.stop_callbacks."
|
|
1436
|
+
)
|
|
1437
|
+
|
|
1438
|
+
stop_callback: StopperProtocol
|
|
1439
|
+
if len(stop_callbacks_list) == 1:
|
|
1440
|
+
stop_callback = stop_callbacks_list[0]
|
|
1441
|
+
else:
|
|
1442
|
+
from gepa.utils import CompositeStopper
|
|
1443
|
+
|
|
1444
|
+
stop_callback = CompositeStopper(*stop_callbacks_list)
|
|
1445
|
+
|
|
1446
|
+
# Convert refiner_lm string to LiteLLM callable (if refiner is enabled)
|
|
1447
|
+
if config.refiner is not None:
|
|
1448
|
+
if isinstance(config.refiner.refiner_lm, str):
|
|
1449
|
+
config.refiner.refiner_lm = make_litellm_lm(config.refiner.refiner_lm)
|
|
1450
|
+
|
|
1451
|
+
# Generate seed candidate via LLM if seed_candidate was None
|
|
1452
|
+
if needs_seed_generation:
|
|
1453
|
+
assert config.reflection.reflection_lm is not None and not isinstance(config.reflection.reflection_lm, str)
|
|
1454
|
+
assert objective is not None # validated earlier in needs_seed_generation block
|
|
1455
|
+
seed_candidate = _generate_seed_candidate(
|
|
1456
|
+
lm=config.reflection.reflection_lm,
|
|
1457
|
+
objective=objective,
|
|
1458
|
+
background=background,
|
|
1459
|
+
dataset=dataset,
|
|
1460
|
+
logger=config.tracking.logger or StdOutLogger(),
|
|
1461
|
+
)
|
|
1462
|
+
|
|
1463
|
+
# Auto-inject refiner_prompt into seed_candidate if refiner is enabled
|
|
1464
|
+
if config.refiner is not None:
|
|
1465
|
+
formatted_refiner_prompt = DEFAULT_REFINER_PROMPT.format(
|
|
1466
|
+
objective=objective or "Maximize the score",
|
|
1467
|
+
background=background or "No additional background provided.",
|
|
1468
|
+
)
|
|
1469
|
+
if "refiner_prompt" not in seed_candidate:
|
|
1470
|
+
seed_candidate["refiner_prompt"] = formatted_refiner_prompt
|
|
1471
|
+
# If user provides their own refiner_prompt, use it (allows custom refiner prompts)
|
|
1472
|
+
|
|
1473
|
+
# Setup default logger if not provided
|
|
1474
|
+
if config.tracking.logger is None:
|
|
1475
|
+
if config.engine.run_dir is not None:
|
|
1476
|
+
os.makedirs(config.engine.run_dir, exist_ok=True)
|
|
1477
|
+
config.tracking.logger = Logger(os.path.join(config.engine.run_dir, "run_log.txt"))
|
|
1478
|
+
else:
|
|
1479
|
+
config.tracking.logger = StdOutLogger()
|
|
1480
|
+
|
|
1481
|
+
# --- 3. Setup random number generator ---
|
|
1482
|
+
rng = random.Random(config.engine.seed)
|
|
1483
|
+
|
|
1484
|
+
# --- 4. Build candidate selector from EngineConfig ---
|
|
1485
|
+
candidate_selector: CandidateSelector
|
|
1486
|
+
if isinstance(config.engine.candidate_selection_strategy, str):
|
|
1487
|
+
factories = {
|
|
1488
|
+
"pareto": lambda: ParetoCandidateSelector(rng=rng),
|
|
1489
|
+
"current_best": lambda: CurrentBestCandidateSelector(),
|
|
1490
|
+
"epsilon_greedy": lambda: EpsilonGreedyCandidateSelector(epsilon=0.1, rng=rng),
|
|
1491
|
+
"top_k_pareto": lambda: TopKParetoCandidateSelector(k=5, rng=rng),
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
try:
|
|
1495
|
+
candidate_selector = factories[config.engine.candidate_selection_strategy]()
|
|
1496
|
+
except KeyError as exc:
|
|
1497
|
+
raise ValueError(
|
|
1498
|
+
f"Unknown candidate_selector strategy: {config.engine.candidate_selection_strategy}. "
|
|
1499
|
+
"Supported strategies: 'pareto', 'current_best', 'epsilon_greedy', 'top_k_pareto'"
|
|
1500
|
+
) from exc
|
|
1501
|
+
elif isinstance(config.engine.candidate_selection_strategy, CandidateSelector):
|
|
1502
|
+
candidate_selector = config.engine.candidate_selection_strategy
|
|
1503
|
+
else:
|
|
1504
|
+
raise TypeError(
|
|
1505
|
+
"candidate_selection_strategy must be a supported string strategy or an instance of CandidateSelector."
|
|
1506
|
+
)
|
|
1507
|
+
|
|
1508
|
+
# --- 5. Build evaluation policy from EngineConfig ---
|
|
1509
|
+
if config.engine.val_evaluation_policy is None or config.engine.val_evaluation_policy == "full_eval":
|
|
1510
|
+
config.engine.val_evaluation_policy = FullEvaluationPolicy()
|
|
1511
|
+
elif not isinstance(config.engine.val_evaluation_policy, EvaluationPolicy):
|
|
1512
|
+
raise ValueError(
|
|
1513
|
+
f"val_evaluation_policy should be 'full_eval' or an EvaluationPolicy instance, but got {type(config.engine.val_evaluation_policy)}"
|
|
1514
|
+
)
|
|
1515
|
+
|
|
1516
|
+
# --- 5b. Build acceptance criterion from EngineConfig ---
|
|
1517
|
+
acceptance_criterion_instance: AcceptanceCriterion
|
|
1518
|
+
if isinstance(config.engine.acceptance_criterion, str):
|
|
1519
|
+
acceptance_factories: dict[str, type[AcceptanceCriterion]] = {
|
|
1520
|
+
"strict_improvement": StrictImprovementAcceptance,
|
|
1521
|
+
"improvement_or_equal": ImprovementOrEqualAcceptance,
|
|
1522
|
+
}
|
|
1523
|
+
try:
|
|
1524
|
+
acceptance_criterion_instance = acceptance_factories[config.engine.acceptance_criterion]()
|
|
1525
|
+
except KeyError as exc:
|
|
1526
|
+
raise ValueError(
|
|
1527
|
+
f"Unknown acceptance_criterion: {config.engine.acceptance_criterion}. "
|
|
1528
|
+
"Supported strategies: 'strict_improvement', 'improvement_or_equal'"
|
|
1529
|
+
) from exc
|
|
1530
|
+
elif isinstance(config.engine.acceptance_criterion, AcceptanceCriterion):
|
|
1531
|
+
acceptance_criterion_instance = config.engine.acceptance_criterion
|
|
1532
|
+
else:
|
|
1533
|
+
raise TypeError(
|
|
1534
|
+
"acceptance_criterion must be a supported string strategy or an instance of AcceptanceCriterion."
|
|
1535
|
+
)
|
|
1536
|
+
|
|
1537
|
+
# --- 6. Build module selector from ReflectionConfig ---
|
|
1538
|
+
if isinstance(config.reflection.module_selector, str):
|
|
1539
|
+
module_selector_cls = {
|
|
1540
|
+
"round_robin": RoundRobinReflectionComponentSelector,
|
|
1541
|
+
"all": AllReflectionComponentSelector,
|
|
1542
|
+
}.get(config.reflection.module_selector)
|
|
1543
|
+
|
|
1544
|
+
assert module_selector_cls is not None, (
|
|
1545
|
+
f"Unknown module_selector strategy: {config.reflection.module_selector}. "
|
|
1546
|
+
"Supported strategies: 'round_robin', 'all'"
|
|
1547
|
+
)
|
|
1548
|
+
|
|
1549
|
+
module_selector_instance: ReflectionComponentSelector = module_selector_cls()
|
|
1550
|
+
else:
|
|
1551
|
+
module_selector_instance = config.reflection.module_selector
|
|
1552
|
+
|
|
1553
|
+
# --- 7. Build batch sampler from ReflectionConfig ---
|
|
1554
|
+
if config.reflection.batch_sampler == "epoch_shuffled":
|
|
1555
|
+
config.reflection.batch_sampler = EpochShuffledBatchSampler(
|
|
1556
|
+
minibatch_size=config.reflection.reflection_minibatch_size, rng=rng
|
|
1557
|
+
)
|
|
1558
|
+
|
|
1559
|
+
# --- 8. Build experiment tracker from TrackingConfig ---
|
|
1560
|
+
experiment_tracker = create_experiment_tracker(
|
|
1561
|
+
use_wandb=config.tracking.use_wandb,
|
|
1562
|
+
wandb_api_key=config.tracking.wandb_api_key,
|
|
1563
|
+
wandb_init_kwargs=config.tracking.wandb_init_kwargs,
|
|
1564
|
+
wandb_attach_existing=config.tracking.wandb_attach_existing,
|
|
1565
|
+
wandb_step_metric=config.tracking.wandb_step_metric,
|
|
1566
|
+
use_mlflow=config.tracking.use_mlflow,
|
|
1567
|
+
mlflow_tracking_uri=config.tracking.mlflow_tracking_uri,
|
|
1568
|
+
mlflow_experiment_name=config.tracking.mlflow_experiment_name,
|
|
1569
|
+
mlflow_attach_existing=config.tracking.mlflow_attach_existing,
|
|
1570
|
+
key_prefix=config.tracking.key_prefix,
|
|
1571
|
+
)
|
|
1572
|
+
|
|
1573
|
+
# --- 9. Build reflection prompt template from objective/background if provided ---
|
|
1574
|
+
# Check for conflicting configuration: user cannot provide both objective/background
|
|
1575
|
+
# AND a custom reflection_prompt_template (these are mutually exclusive approaches)
|
|
1576
|
+
user_provided_custom_template = (
|
|
1577
|
+
config.reflection.reflection_prompt_template is not None
|
|
1578
|
+
and config.reflection.reflection_prompt_template != optimize_anything_reflection_prompt_template
|
|
1579
|
+
)
|
|
1580
|
+
# Treat empty strings as "not provided" - only non-empty strings count
|
|
1581
|
+
user_provided_objective_or_background = bool(objective) or bool(background)
|
|
1582
|
+
|
|
1583
|
+
if user_provided_custom_template and user_provided_objective_or_background:
|
|
1584
|
+
raise ValueError(
|
|
1585
|
+
"Cannot specify both 'objective'/'background' parameters and a custom "
|
|
1586
|
+
"'config.reflection.reflection_prompt_template'. These are mutually exclusive options. "
|
|
1587
|
+
"Either use objective/background to auto-generate a reflection prompt, or provide "
|
|
1588
|
+
"your own custom template via config.reflection.reflection_prompt_template."
|
|
1589
|
+
)
|
|
1590
|
+
|
|
1591
|
+
# If objective or background are provided, build a custom reflection prompt template
|
|
1592
|
+
# with those values filled in, creating a template with <curr_param> and <side_info> placeholders
|
|
1593
|
+
if user_provided_objective_or_background:
|
|
1594
|
+
config.reflection.reflection_prompt_template = _build_reflection_prompt_template(
|
|
1595
|
+
objective=objective, background=background
|
|
1596
|
+
)
|
|
1597
|
+
|
|
1598
|
+
# --- 10. Validate reflection prompt template ---
|
|
1599
|
+
if config.reflection.reflection_prompt_template is not None:
|
|
1600
|
+
assert not (active_adapter is not None and getattr(active_adapter, "propose_new_texts", None) is not None), (
|
|
1601
|
+
f"Adapter {active_adapter!s} provides its own propose_new_texts method; "
|
|
1602
|
+
"reflection_prompt_template will be ignored. Set reflection_prompt_template to None."
|
|
1603
|
+
)
|
|
1604
|
+
|
|
1605
|
+
# Validate template(s) - can be a single string or dict of templates
|
|
1606
|
+
from gepa.strategies.instruction_proposal import InstructionProposalSignature
|
|
1607
|
+
|
|
1608
|
+
if isinstance(config.reflection.reflection_prompt_template, dict):
|
|
1609
|
+
for param_name, template in config.reflection.reflection_prompt_template.items():
|
|
1610
|
+
try:
|
|
1611
|
+
InstructionProposalSignature.validate_prompt_template(template)
|
|
1612
|
+
except ValueError as e:
|
|
1613
|
+
raise ValueError(f"Invalid reflection_prompt_template for parameter '{param_name}': {e}") from e
|
|
1614
|
+
else:
|
|
1615
|
+
InstructionProposalSignature.validate_prompt_template(config.reflection.reflection_prompt_template)
|
|
1616
|
+
|
|
1617
|
+
# --- 11. Build reflective proposer from ReflectionConfig ---
|
|
1618
|
+
reflective_proposer = ReflectiveMutationProposer(
|
|
1619
|
+
logger=config.tracking.logger,
|
|
1620
|
+
trainset=train_loader,
|
|
1621
|
+
adapter=active_adapter,
|
|
1622
|
+
candidate_selector=candidate_selector,
|
|
1623
|
+
module_selector=module_selector_instance,
|
|
1624
|
+
batch_sampler=config.reflection.batch_sampler,
|
|
1625
|
+
perfect_score=config.reflection.perfect_score,
|
|
1626
|
+
skip_perfect_score=config.reflection.skip_perfect_score,
|
|
1627
|
+
experiment_tracker=experiment_tracker,
|
|
1628
|
+
reflection_lm=config.reflection.reflection_lm,
|
|
1629
|
+
reflection_prompt_template=config.reflection.reflection_prompt_template,
|
|
1630
|
+
custom_candidate_proposer=config.reflection.custom_candidate_proposer,
|
|
1631
|
+
callbacks=config.callbacks,
|
|
1632
|
+
sampling_strategy=config.engine.sampling_strategy,
|
|
1633
|
+
reflection_strategy=config.reflection.reflection_strategy,
|
|
1634
|
+
)
|
|
1635
|
+
|
|
1636
|
+
# Define evaluator function for merge proposer
|
|
1637
|
+
def merge_evaluator(
|
|
1638
|
+
inputs: list[DataInst], prog: Candidate
|
|
1639
|
+
) -> tuple[list[object], list[float], list[dict[str, float]] | None]:
|
|
1640
|
+
eval_out = active_adapter.evaluate(inputs, prog, capture_traces=False)
|
|
1641
|
+
return eval_out.outputs, eval_out.scores, eval_out.objective_scores
|
|
1642
|
+
|
|
1643
|
+
# --- 12. Build merge proposer from MergeConfig (if provided) ---
|
|
1644
|
+
merge_proposer: MergeProposer | None = None
|
|
1645
|
+
if config.merge is not None:
|
|
1646
|
+
merge_proposer = MergeProposer(
|
|
1647
|
+
logger=config.tracking.logger,
|
|
1648
|
+
valset=val_loader,
|
|
1649
|
+
evaluator=merge_evaluator,
|
|
1650
|
+
use_merge=True,
|
|
1651
|
+
max_merge_invocations=config.merge.max_merge_invocations,
|
|
1652
|
+
rng=rng,
|
|
1653
|
+
val_overlap_floor=config.merge.merge_val_overlap_floor,
|
|
1654
|
+
callbacks=config.callbacks,
|
|
1655
|
+
)
|
|
1656
|
+
|
|
1657
|
+
# --- 13. Create evaluation cache if enabled ---
|
|
1658
|
+
evaluation_cache: EvaluationCache[Any, Any] | None = None
|
|
1659
|
+
if config.engine.cache_evaluation:
|
|
1660
|
+
evaluation_cache = EvaluationCache[Any, Any]()
|
|
1661
|
+
|
|
1662
|
+
# --- 14. Build the main engine from EngineConfig ---
|
|
1663
|
+
engine = GEPAEngine(
|
|
1664
|
+
adapter=active_adapter,
|
|
1665
|
+
run_dir=config.engine.run_dir,
|
|
1666
|
+
valset=val_loader,
|
|
1667
|
+
seed_candidate=seed_candidate,
|
|
1668
|
+
perfect_score=config.reflection.perfect_score,
|
|
1669
|
+
seed=config.engine.seed,
|
|
1670
|
+
reflective_proposer=reflective_proposer,
|
|
1671
|
+
merge_proposer=merge_proposer,
|
|
1672
|
+
frontier_type=config.engine.frontier_type,
|
|
1673
|
+
logger=config.tracking.logger,
|
|
1674
|
+
experiment_tracker=experiment_tracker,
|
|
1675
|
+
callbacks=config.callbacks,
|
|
1676
|
+
track_best_outputs=config.engine.track_best_outputs,
|
|
1677
|
+
display_progress_bar=config.engine.display_progress_bar,
|
|
1678
|
+
raise_on_exception=config.engine.raise_on_exception,
|
|
1679
|
+
stop_callback=stop_callback,
|
|
1680
|
+
val_evaluation_policy=config.engine.val_evaluation_policy,
|
|
1681
|
+
acceptance_criterion=acceptance_criterion_instance,
|
|
1682
|
+
selection_strategy=config.engine.selection_strategy,
|
|
1683
|
+
use_cloudpickle=config.engine.use_cloudpickle,
|
|
1684
|
+
evaluation_cache=evaluation_cache,
|
|
1685
|
+
)
|
|
1686
|
+
|
|
1687
|
+
# --- 16. Run optimization ---
|
|
1688
|
+
logger = config.tracking.logger
|
|
1689
|
+
with experiment_tracker:
|
|
1690
|
+
if isinstance(logger, Logger):
|
|
1691
|
+
with logger:
|
|
1692
|
+
state = engine.run()
|
|
1693
|
+
else:
|
|
1694
|
+
state = engine.run()
|
|
1695
|
+
|
|
1696
|
+
return GEPAResult.from_state(
|
|
1697
|
+
state,
|
|
1698
|
+
run_dir=config.engine.run_dir,
|
|
1699
|
+
seed=config.engine.seed,
|
|
1700
|
+
str_candidate_key=_STR_CANDIDATE_KEY if str_candidate_mode else None,
|
|
1701
|
+
)
|