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.
Files changed (378) hide show
  1. package/README.md +23 -0
  2. package/dist/archive-artifacts-cli.d.ts +50 -0
  3. package/dist/archive-artifacts-cli.d.ts.map +1 -0
  4. package/dist/artifact-archive.d.ts +48 -0
  5. package/dist/artifact-archive.d.ts.map +1 -0
  6. package/dist/artifact-pull.d.ts +22 -0
  7. package/dist/artifact-pull.d.ts.map +1 -0
  8. package/dist/banner.d.ts +3 -0
  9. package/dist/banner.d.ts.map +1 -0
  10. package/dist/connectors-cli.d.ts +31 -0
  11. package/dist/connectors-cli.d.ts.map +1 -0
  12. package/dist/credentials.d.ts +46 -0
  13. package/dist/credentials.d.ts.map +1 -0
  14. package/dist/csv.d.ts +49 -0
  15. package/dist/csv.d.ts.map +1 -0
  16. package/dist/dataset-download.d.ts +6 -0
  17. package/dist/dataset-download.d.ts.map +1 -0
  18. package/dist/dataset-edit-rows.d.ts +10 -0
  19. package/dist/dataset-edit-rows.d.ts.map +1 -0
  20. package/dist/dataset-upload-snapshot.d.ts +13 -0
  21. package/dist/dataset-upload-snapshot.d.ts.map +1 -0
  22. package/dist/daytona-slice-rehearsal.d.ts +89 -0
  23. package/dist/daytona-slice-rehearsal.d.ts.map +1 -0
  24. package/dist/daytona-slice.d.ts +106 -0
  25. package/dist/daytona-slice.d.ts.map +1 -0
  26. package/dist/deploy-key-merge-core.d.ts +121 -0
  27. package/dist/deploy-key-merge-core.d.ts.map +1 -0
  28. package/dist/diff-comments-cli.d.ts +12 -0
  29. package/dist/diff-comments-cli.d.ts.map +1 -0
  30. package/dist/diff-comments-cli.js +21 -2
  31. package/dist/egress-allowlist-cli.d.ts +23 -0
  32. package/dist/egress-allowlist-cli.d.ts.map +1 -0
  33. package/dist/egress-policy.d.ts +108 -0
  34. package/dist/egress-policy.d.ts.map +1 -0
  35. package/dist/error-response.d.ts +9 -0
  36. package/dist/error-response.d.ts.map +1 -0
  37. package/dist/file-parser.d.ts +5 -0
  38. package/dist/file-parser.d.ts.map +1 -0
  39. package/dist/gepa-engine-dispatch.d.ts +9 -0
  40. package/dist/gepa-engine-dispatch.d.ts.map +1 -0
  41. package/dist/gepa-engine-dispatch.js +307 -0
  42. package/dist/gepa-python-command.d.ts +6 -0
  43. package/dist/gepa-python-command.d.ts.map +1 -0
  44. package/dist/gepa-python-command.js +26 -0
  45. package/dist/gepa-python-paths.d.ts +6 -0
  46. package/dist/gepa-python-paths.d.ts.map +1 -0
  47. package/dist/gepa-python-paths.js +42 -0
  48. package/dist/git-credential.d.ts +69 -0
  49. package/dist/git-credential.d.ts.map +1 -0
  50. package/dist/git-recovery-environment.d.ts +31 -0
  51. package/dist/git-recovery-environment.d.ts.map +1 -0
  52. package/dist/github-setup.d.ts +122 -0
  53. package/dist/github-setup.d.ts.map +1 -0
  54. package/dist/global-flags.d.ts +12 -0
  55. package/dist/global-flags.d.ts.map +1 -0
  56. package/dist/help.d.ts +46 -0
  57. package/dist/help.d.ts.map +1 -0
  58. package/dist/help.js +93 -25
  59. package/dist/hosted-boot.d.ts +273 -0
  60. package/dist/hosted-boot.d.ts.map +1 -0
  61. package/dist/hosted-bootstrap-rehearsal.d.ts +86 -0
  62. package/dist/hosted-bootstrap-rehearsal.d.ts.map +1 -0
  63. package/dist/hosted-bootstrap.d.ts +188 -0
  64. package/dist/hosted-bootstrap.d.ts.map +1 -0
  65. package/dist/hosted-broker-server.d.ts +50 -0
  66. package/dist/hosted-broker-server.d.ts.map +1 -0
  67. package/dist/hosted-git-http-server.d.ts +36 -0
  68. package/dist/hosted-git-http-server.d.ts.map +1 -0
  69. package/dist/hosted-harness-claude.d.ts +195 -0
  70. package/dist/hosted-harness-claude.d.ts.map +1 -0
  71. package/dist/hosted-harness-opencode.d.ts +227 -0
  72. package/dist/hosted-harness-opencode.d.ts.map +1 -0
  73. package/dist/hosted-harness.d.ts +118 -0
  74. package/dist/hosted-harness.d.ts.map +1 -0
  75. package/dist/hosted-harvest.d.ts +68 -0
  76. package/dist/hosted-harvest.d.ts.map +1 -0
  77. package/dist/hosted-headless.d.ts +72 -0
  78. package/dist/hosted-headless.d.ts.map +1 -0
  79. package/dist/hosted-loop-lifecycle.d.ts +206 -0
  80. package/dist/hosted-loop-lifecycle.d.ts.map +1 -0
  81. package/dist/hosted-loop.d.ts +170 -0
  82. package/dist/hosted-loop.d.ts.map +1 -0
  83. package/dist/hosted-run-event-sink.d.ts +222 -0
  84. package/dist/hosted-run-event-sink.d.ts.map +1 -0
  85. package/dist/hosted-runtime-assets.d.ts +186 -0
  86. package/dist/hosted-runtime-assets.d.ts.map +1 -0
  87. package/dist/hosted-session-cli.d.ts +258 -0
  88. package/dist/hosted-session-cli.d.ts.map +1 -0
  89. package/dist/hosted-skill-staging.d.ts +73 -0
  90. package/dist/hosted-skill-staging.d.ts.map +1 -0
  91. package/dist/http.d.ts +8 -0
  92. package/dist/http.d.ts.map +1 -0
  93. package/dist/index.d.ts +22 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +119 -129
  96. package/dist/instruction-set-gepa-launch.d.ts +3 -0
  97. package/dist/instruction-set-gepa-launch.d.ts.map +1 -0
  98. package/dist/instruction-set-gepa-launch.js +15 -0
  99. package/dist/instruction-set-loader.d.ts +6 -0
  100. package/dist/instruction-set-loader.d.ts.map +1 -0
  101. package/dist/instruction-set-loader.js +59 -0
  102. package/dist/instruction-set-manifest.d.ts +14 -0
  103. package/dist/instruction-set-manifest.d.ts.map +1 -0
  104. package/dist/instruction-set-manifest.js +162 -0
  105. package/dist/instruction-sets-cli.d.ts +40 -0
  106. package/dist/instruction-sets-cli.d.ts.map +1 -0
  107. package/dist/instruction-sets-cli.js +361 -0
  108. package/dist/json-response.d.ts +4 -0
  109. package/dist/json-response.d.ts.map +1 -0
  110. package/dist/jsonl-stream.d.ts +8 -0
  111. package/dist/jsonl-stream.d.ts.map +1 -0
  112. package/dist/kill-switch-cli.d.ts +30 -0
  113. package/dist/kill-switch-cli.d.ts.map +1 -0
  114. package/dist/loopback-serve.d.ts +25 -0
  115. package/dist/loopback-serve.d.ts.map +1 -0
  116. package/dist/manifests-cli.d.ts +22 -0
  117. package/dist/manifests-cli.d.ts.map +1 -0
  118. package/dist/markdown-report-input.d.ts +7 -0
  119. package/dist/markdown-report-input.d.ts.map +1 -0
  120. package/dist/markdown-report-input.js +15 -1
  121. package/dist/merge-job.d.ts +155 -0
  122. package/dist/merge-job.d.ts.map +1 -0
  123. package/dist/model-configs-cli.d.ts +19 -0
  124. package/dist/model-configs-cli.d.ts.map +1 -0
  125. package/dist/model-configs-cli.js +104 -0
  126. package/dist/optimization-export-cli.d.ts +19 -0
  127. package/dist/optimization-export-cli.d.ts.map +1 -0
  128. package/dist/optimizations-list-cli.d.ts +20 -0
  129. package/dist/optimizations-list-cli.d.ts.map +1 -0
  130. package/dist/optimizations-promote-cli.d.ts +8 -0
  131. package/dist/optimizations-promote-cli.d.ts.map +1 -0
  132. package/dist/optimizations-promote-cli.js +34 -0
  133. package/dist/option-validation.d.ts +15 -0
  134. package/dist/option-validation.d.ts.map +1 -0
  135. package/dist/preview-runtime.d.ts +63 -0
  136. package/dist/preview-runtime.d.ts.map +1 -0
  137. package/dist/prompt-draft-push.d.ts +60 -0
  138. package/dist/prompt-draft-push.d.ts.map +1 -0
  139. package/dist/prompt-label-cli.d.ts +11 -0
  140. package/dist/prompt-label-cli.d.ts.map +1 -0
  141. package/dist/prompt-label-cli.js +29 -0
  142. package/dist/prompt-length-wire.d.ts +28 -0
  143. package/dist/prompt-length-wire.d.ts.map +1 -0
  144. package/dist/prompt-length-wire.js +2 -2
  145. package/dist/prompt-report-cli.d.ts +10 -0
  146. package/dist/prompt-report-cli.d.ts.map +1 -0
  147. package/dist/prompt-report-cli.js +2 -2
  148. package/dist/prompt-summary-table.d.ts +15 -0
  149. package/dist/prompt-summary-table.d.ts.map +1 -0
  150. package/dist/prompt-summary-table.js +6 -0
  151. package/dist/registration-tag-warning.d.ts +32 -0
  152. package/dist/registration-tag-warning.d.ts.map +1 -0
  153. package/dist/report-comments-cli.d.ts +16 -0
  154. package/dist/report-comments-cli.d.ts.map +1 -0
  155. package/dist/report-markdown-blankness.d.ts +2 -0
  156. package/dist/report-markdown-blankness.d.ts.map +1 -0
  157. package/dist/report-markdown-blankness.js +6 -0
  158. package/dist/report-replacement-warning.d.ts +17 -0
  159. package/dist/report-replacement-warning.d.ts.map +1 -0
  160. package/dist/report-replacement-warning.js +31 -0
  161. package/dist/repository-backup-core.d.ts +225 -0
  162. package/dist/repository-backup-core.d.ts.map +1 -0
  163. package/dist/repository-recovery-manifest.d.ts +83 -0
  164. package/dist/repository-recovery-manifest.d.ts.map +1 -0
  165. package/dist/run-event-digest.d.ts +74 -0
  166. package/dist/run-event-digest.d.ts.map +1 -0
  167. package/dist/runner-dir-verify.d.ts +66 -0
  168. package/dist/runner-dir-verify.d.ts.map +1 -0
  169. package/dist/runner-instruction-set-contract.d.ts +24 -0
  170. package/dist/runner-instruction-set-contract.d.ts.map +1 -0
  171. package/dist/runner-instruction-set-contract.js +50 -0
  172. package/dist/sandbox-hygiene.d.ts +45 -0
  173. package/dist/sandbox-hygiene.d.ts.map +1 -0
  174. package/dist/sandbox-provider.d.ts +193 -0
  175. package/dist/sandbox-provider.d.ts.map +1 -0
  176. package/dist/scorer-draft-push.d.ts +50 -0
  177. package/dist/scorer-draft-push.d.ts.map +1 -0
  178. package/dist/scorers-cli.d.ts +20 -0
  179. package/dist/scorers-cli.d.ts.map +1 -0
  180. package/dist/scorers-cli.js +22 -0
  181. package/dist/scorers-verify-parity-bridge.d.ts +16 -0
  182. package/dist/scorers-verify-parity-bridge.d.ts.map +1 -0
  183. package/dist/scorers-verify-parity-bridge.js +90 -0
  184. package/dist/scorers-verify-parity.d.ts +81 -0
  185. package/dist/scorers-verify-parity.d.ts.map +1 -0
  186. package/dist/scorers-verify-parity.js +502 -0
  187. package/dist/scores-accept-cli.d.ts +21 -0
  188. package/dist/scores-accept-cli.d.ts.map +1 -0
  189. package/dist/secret-redaction.d.ts +47 -0
  190. package/dist/secret-redaction.d.ts.map +1 -0
  191. package/dist/session-timeline.d.ts +54 -0
  192. package/dist/session-timeline.d.ts.map +1 -0
  193. package/dist/setup-prompt.d.ts +5 -0
  194. package/dist/setup-prompt.d.ts.map +1 -0
  195. package/dist/skill-installer.d.ts +68 -0
  196. package/dist/skill-installer.d.ts.map +1 -0
  197. package/dist/skilled-proposer-launch.d.ts +7 -0
  198. package/dist/skilled-proposer-launch.d.ts.map +1 -0
  199. package/dist/skilled-proposer-launch.js +60 -0
  200. package/dist/smoke.d.ts +102 -0
  201. package/dist/smoke.d.ts.map +1 -0
  202. package/dist/start-intent.d.ts +12 -0
  203. package/dist/start-intent.d.ts.map +1 -0
  204. package/dist/task-assignment-manifest.d.ts +15 -0
  205. package/dist/task-assignment-manifest.d.ts.map +1 -0
  206. package/dist/task-create-error.d.ts +18 -0
  207. package/dist/task-create-error.d.ts.map +1 -0
  208. package/dist/team-connectors-cli.d.ts +29 -0
  209. package/dist/team-connectors-cli.d.ts.map +1 -0
  210. package/dist/types.d.ts +39 -0
  211. package/dist/types.d.ts.map +1 -0
  212. package/dist/vercel-sandbox-provider.d.ts +156 -0
  213. package/dist/vercel-sandbox-provider.d.ts.map +1 -0
  214. package/dist/workbench-cli.d.ts +233 -0
  215. package/dist/workbench-cli.d.ts.map +1 -0
  216. package/dist/workspace-sync.d.ts +207 -0
  217. package/dist/workspace-sync.d.ts.map +1 -0
  218. package/dist/workspace.d.ts +92 -0
  219. package/dist/workspace.d.ts.map +1 -0
  220. package/dist/zip-draft-push.d.ts +55 -0
  221. package/dist/zip-draft-push.d.ts.map +1 -0
  222. package/package.json +15 -2
  223. package/requirements/README.md +138 -0
  224. package/requirements/skilled-proposer.in +66 -0
  225. package/requirements/skilled-proposer.lock +265 -0
  226. package/scripts/build-cli-dist.mjs +30 -0
  227. package/scripts/ensure-skilled-proposer-venv.mjs +5 -0
  228. package/scripts/orizu-source-checkout.d.mts +1 -0
  229. package/scripts/orizu-source-checkout.mjs +10 -0
  230. package/scripts/refresh-skilled-proposer-lock.mjs +256 -0
  231. package/scripts/vendor-gepa-python.mjs +187 -4
  232. package/src/diff-comments-cli.ts +41 -3
  233. package/src/gepa-engine-dispatch.ts +340 -0
  234. package/src/gepa-python-command.ts +31 -0
  235. package/src/gepa-python-paths.ts +56 -0
  236. package/src/help.ts +95 -25
  237. package/src/index.ts +131 -166
  238. package/src/instruction-set-gepa-launch.ts +28 -0
  239. package/src/instruction-set-loader.ts +37 -0
  240. package/src/instruction-set-manifest.ts +120 -0
  241. package/src/instruction-sets-cli.ts +306 -0
  242. package/src/markdown-report-input.ts +19 -1
  243. package/src/model-configs-cli.ts +119 -0
  244. package/src/optimizations-promote-cli.ts +41 -0
  245. package/src/prompt-label-cli.ts +55 -0
  246. package/src/prompt-length-wire.ts +1 -1
  247. package/src/prompt-report-cli.ts +2 -2
  248. package/src/prompt-summary-table.ts +12 -0
  249. package/src/report-markdown-blankness.ts +6 -0
  250. package/src/report-replacement-warning.ts +35 -0
  251. package/src/runner-instruction-set-contract.ts +64 -0
  252. package/src/scorers-cli.ts +40 -0
  253. package/src/scorers-verify-parity-bridge.ts +90 -0
  254. package/src/scorers-verify-parity.ts +564 -0
  255. package/src/skilled-proposer-launch.ts +89 -0
  256. package/src/skilled-proposer-venv-manager.mjs +757 -0
  257. package/tsconfig.json +2 -0
  258. package/vendor/gepa-python/LICENSE +7 -0
  259. package/vendor/gepa-python/manifest.json +107 -0
  260. package/vendor/gepa-python/src/gepa/__init__.py +22 -0
  261. package/vendor/gepa-python/src/gepa/adapters/__init__.py +0 -0
  262. package/vendor/gepa-python/src/gepa/adapters/anymaths_adapter/__init__.py +1 -0
  263. package/vendor/gepa-python/src/gepa/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
  264. package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/__init__.py +26 -0
  265. package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/confidence_adapter.py +618 -0
  266. package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/scoring.py +206 -0
  267. package/vendor/gepa-python/src/gepa/adapters/default_adapter/__init__.py +0 -0
  268. package/vendor/gepa-python/src/gepa/adapters/default_adapter/default_adapter.py +206 -0
  269. package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/__init__.py +0 -0
  270. package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/dspy_adapter.py +525 -0
  271. package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/instruction_proposal.py +500 -0
  272. package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/__init__.py +6 -0
  273. package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
  274. package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/full_program_adapter.py +269 -0
  275. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/__init__.py +56 -0
  276. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
  277. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
  278. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
  279. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
  280. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
  281. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
  282. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
  283. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
  284. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
  285. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
  286. package/vendor/gepa-python/src/gepa/adapters/langchain_adapter/__init__.py +11 -0
  287. package/vendor/gepa-python/src/gepa/adapters/langchain_adapter/langchain_adapter.py +261 -0
  288. package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/__init__.py +37 -0
  289. package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/mcp_adapter.py +693 -0
  290. package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/mcp_client.py +364 -0
  291. package/vendor/gepa-python/src/gepa/adapters/optimize_anything_adapter/optimize_anything_adapter.py +879 -0
  292. package/vendor/gepa-python/src/gepa/adapters/terminal_bench_adapter/__init__.py +0 -0
  293. package/vendor/gepa-python/src/gepa/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
  294. package/vendor/gepa-python/src/gepa/api.py +468 -0
  295. package/vendor/gepa-python/src/gepa/core/__init__.py +0 -0
  296. package/vendor/gepa-python/src/gepa/core/adapter.py +227 -0
  297. package/vendor/gepa-python/src/gepa/core/callbacks.py +551 -0
  298. package/vendor/gepa-python/src/gepa/core/data_loader.py +74 -0
  299. package/vendor/gepa-python/src/gepa/core/engine.py +1025 -0
  300. package/vendor/gepa-python/src/gepa/core/result.py +271 -0
  301. package/vendor/gepa-python/src/gepa/core/state.py +706 -0
  302. package/vendor/gepa-python/src/gepa/examples/__init__.py +0 -0
  303. package/vendor/gepa-python/src/gepa/examples/aime.py +24 -0
  304. package/vendor/gepa-python/src/gepa/examples/anymaths-bench/eval_default.py +111 -0
  305. package/vendor/gepa-python/src/gepa/examples/anymaths-bench/train_anymaths.py +177 -0
  306. package/vendor/gepa-python/src/gepa/examples/mcp_adapter/__init__.py +4 -0
  307. package/vendor/gepa-python/src/gepa/examples/mcp_adapter/mcp_optimization_example.py +456 -0
  308. package/vendor/gepa-python/src/gepa/examples/rag_adapter/__init__.py +9 -0
  309. package/vendor/gepa-python/src/gepa/examples/rag_adapter/rag_optimization.py +824 -0
  310. package/vendor/gepa-python/src/gepa/examples/terminal-bench/train_terminus.py +161 -0
  311. package/vendor/gepa-python/src/gepa/gepa_utils.py +117 -0
  312. package/vendor/gepa-python/src/gepa/gskill/__init__.py +1 -0
  313. package/vendor/gepa-python/src/gepa/gskill/gskill/__init__.py +0 -0
  314. package/vendor/gepa-python/src/gepa/gskill/gskill/cost_tracker.py +194 -0
  315. package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/__init__.py +0 -0
  316. package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/claude_code.py +1117 -0
  317. package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/claude_code_skills.py +613 -0
  318. package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/mini_swe_agent.py +440 -0
  319. package/vendor/gepa-python/src/gepa/gskill/gskill/experiment_logger.py +367 -0
  320. package/vendor/gepa-python/src/gepa/gskill/gskill/swe_fitness_fn.py +209 -0
  321. package/vendor/gepa-python/src/gepa/gskill/gskill/swe_harness.py +352 -0
  322. package/vendor/gepa-python/src/gepa/gskill/gskill/train_optimize_anything.py +830 -0
  323. package/vendor/gepa-python/src/gepa/image.py +104 -0
  324. package/vendor/gepa-python/src/gepa/lm.py +261 -0
  325. package/vendor/gepa-python/src/gepa/logging/__init__.py +0 -0
  326. package/vendor/gepa-python/src/gepa/logging/experiment_tracker.py +457 -0
  327. package/vendor/gepa-python/src/gepa/logging/logger.py +75 -0
  328. package/vendor/gepa-python/src/gepa/logging/utils.py +130 -0
  329. package/vendor/gepa-python/src/gepa/optimize_anything.py +1701 -0
  330. package/vendor/gepa-python/src/gepa/proposer/__init__.py +0 -0
  331. package/vendor/gepa-python/src/gepa/proposer/base.py +53 -0
  332. package/vendor/gepa-python/src/gepa/proposer/merge.py +403 -0
  333. package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/__init__.py +0 -0
  334. package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/base.py +64 -0
  335. package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/reflection_lm.py +178 -0
  336. package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/reflective_mutation.py +641 -0
  337. package/vendor/gepa-python/src/gepa/py.typed +0 -0
  338. package/vendor/gepa-python/src/gepa/strategies/__init__.py +0 -0
  339. package/vendor/gepa-python/src/gepa/strategies/acceptance.py +66 -0
  340. package/vendor/gepa-python/src/gepa/strategies/batch_sampler.py +103 -0
  341. package/vendor/gepa-python/src/gepa/strategies/candidate_selector.py +82 -0
  342. package/vendor/gepa-python/src/gepa/strategies/component_selector.py +36 -0
  343. package/vendor/gepa-python/src/gepa/strategies/eval_policy.py +64 -0
  344. package/vendor/gepa-python/src/gepa/strategies/instruction_proposal.py +153 -0
  345. package/vendor/gepa-python/src/gepa/strategies/proposal_sampling.py +128 -0
  346. package/vendor/gepa-python/src/gepa/strategies/proposal_selection.py +84 -0
  347. package/vendor/gepa-python/src/gepa/utils/__init__.py +63 -0
  348. package/vendor/gepa-python/src/gepa/utils/code_execution.py +626 -0
  349. package/vendor/gepa-python/src/gepa/utils/stdio_capture.py +145 -0
  350. package/vendor/gepa-python/src/gepa/utils/stop_condition.py +230 -0
  351. package/vendor/gepa-python/src/gepa/visualization.py +359 -0
  352. package/vendor/orizu-gepa/pyproject.toml +18 -0
  353. package/vendor/orizu-gepa/src/orizu_gepa_connector/__init__.py +9 -0
  354. package/vendor/orizu-gepa/src/orizu_gepa_connector/__main__.py +5 -0
  355. package/vendor/orizu-gepa/src/orizu_gepa_connector/adapter.py +138 -0
  356. package/vendor/orizu-gepa/src/orizu_gepa_connector/callbacks.py +666 -0
  357. package/vendor/orizu-gepa/src/orizu_gepa_connector/cli.py +7 -0
  358. package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +269 -0
  359. package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +52 -0
  360. package/vendor/orizu-gepa/src/orizu_gepa_connector/preflight.py +64 -0
  361. package/vendor/orizu-gepa/src/orizu_gepa_connector/reflection.py +186 -0
  362. package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +721 -0
  363. package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +381 -0
  364. package/vendor/orizu-gepa/src/orizu_gepa_connector/stop_conditions.py +55 -0
  365. package/vendor/orizu-gepa/src/orizu_gepa_connector/translator.py +203 -0
  366. package/vendor/orizu-gepa-python/pyproject.toml +1 -1
  367. package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +1 -0
  368. package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +52 -7
  369. package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +52 -0
  370. package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +10 -5
  371. package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +323 -68
  372. package/vendor/orizu-gepa-python/src/orizu_gepa/runner.py +207 -13
  373. package/vendor/skills/orizu-cli/SKILL.md +3 -1
  374. package/vendor/skills/orizu-cli/references/cli-reference.md +85 -1
  375. package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +303 -0
  376. package/vendor/skills/orizu-cli/references/optimization-reports.md +1 -1
  377. package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +26 -1
  378. package/vendor/skills/orizu-cli/references/prompt-control-plane.md +113 -10
@@ -0,0 +1,879 @@
1
+ """GEPAAdapter implementation for the ``optimize_anything`` API.
2
+
3
+ This adapter bridges the user-facing ``optimize_anything`` API and GEPA's
4
+ internal engine. It handles:
5
+
6
+ - **Evaluation**: calls the user's wrapped evaluator (single or parallel)
7
+ - **Caching**: optional memory or disk cache for ``(candidate, example)`` pairs
8
+ - **Refinement**: when :class:`~gepa.optimize_anything.RefinerConfig` is set,
9
+ iteratively improves candidates via an LLM after each evaluation
10
+ - **Best-evals tracking**: maintains top-K evaluations per example for
11
+ warm-starting via :class:`~gepa.optimize_anything.OptimizationState`
12
+ - **Reflective dataset**: formats evaluation results for the reflection LLM
13
+ """
14
+
15
+ import hashlib
16
+ import inspect
17
+ import json
18
+ import logging
19
+ import pickle
20
+ import threading
21
+ from collections.abc import Callable, Mapping, Sequence
22
+ from concurrent.futures import ThreadPoolExecutor, as_completed
23
+ from pathlib import Path
24
+ from typing import TYPE_CHECKING, Any
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ from gepa.core.adapter import DataInst, EvaluationBatch, GEPAAdapter
29
+ from gepa.proposer.reflective_mutation.base import LanguageModel
30
+
31
+ if TYPE_CHECKING:
32
+ from gepa.optimize_anything import Candidate, OptimizationState, RefinerConfig, SideInfo
33
+
34
+
35
+ REFINER_PROMPT_TEMPLATE = """You are refining a candidate to improve its performance.
36
+
37
+ ## Instructions
38
+ {refiner_prompt}
39
+
40
+ ## Current Candidate (JSON)
41
+ ```json
42
+ {candidate_to_improve}
43
+ ```
44
+
45
+ ## Evaluation History
46
+ The following shows all evaluation attempts so far, including scores and feedback:
47
+ ```json
48
+ {evaluation_feedback}
49
+ ```
50
+
51
+ ## Task
52
+ Analyze the evaluation history and propose an improved version of the candidate.
53
+ Return ONLY a valid JSON object with the improved parameters (no explanation, no markdown fences).
54
+ """
55
+
56
+
57
+ class BatchEvaluatorWrapper:
58
+ """Wraps a user ``batch_evaluator`` while preserving its single-external-call contract.
59
+
60
+ The whole point of ``batch_evaluator`` is that the user receives ALL
61
+ (candidate, example) pairs in ONE call (e.g. to submit a provider batch job
62
+ or fan out over their own cluster), so per-call features of the standard
63
+ evaluator path — ``oa.log()`` capture and stdout/stderr capture — cannot
64
+ apply here: there is no per-evaluation call to scope them to. Diagnostics
65
+ belong in each returned ``side_info``.
66
+
67
+ What IS restored for parity with the standard evaluator path:
68
+
69
+ - ``str_candidate_mode``: candidates are unwrapped to ``str`` before the
70
+ user function sees them (mirror of ``EvaluatorWrapper``).
71
+ - ``opt_states`` injection: if the function's signature accepts an
72
+ ``opt_states`` keyword (or ``**kwargs``), it receives a list of
73
+ :class:`OptimizationState` aligned with ``pairs`` — the batch analogue of
74
+ the per-call ``opt_state`` kwarg (warm-starting, #160).
75
+ - Exception policy: when ``raise_on_exception=False``, a raised batch call
76
+ is converted to per-pair ``(0.0, {"error": ...})`` results instead of
77
+ sinking the iteration.
78
+ - Result normalization: each per-pair result may be ``float``,
79
+ ``(score, side_info)`` (the mirror of the evaluator protocol), or a
80
+ legacy ``(score, output, side_info)`` 3-tuple; all normalize to
81
+ ``(score, side_info)``.
82
+
83
+ Concurrency: grouped evaluations arrive as one call at a time, but when
84
+ ``refiner_config`` is combined with ``parallel=True`` (and no single-pair
85
+ evaluator exists), per-example refinement loops run on a thread pool and
86
+ may call this wrapper concurrently with singleton batches — the wrapped
87
+ function should be thread-safe in that configuration.
88
+ """
89
+
90
+ def __init__(
91
+ self,
92
+ batch_fn: Callable[..., Any],
93
+ *,
94
+ str_candidate_key: str | None = None,
95
+ raise_on_exception: bool = True,
96
+ single_instance_mode: bool = False,
97
+ ):
98
+ self._fn = batch_fn
99
+ self._str_candidate_key = str_candidate_key
100
+ self._raise_on_exception = raise_on_exception
101
+ self._single_instance_mode = single_instance_mode
102
+ sig = inspect.signature(batch_fn)
103
+ has_var_kw = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values())
104
+ self._accepts_opt_states = has_var_kw or "opt_states" in sig.parameters
105
+
106
+ def __call__(
107
+ self,
108
+ pairs: list[tuple["Candidate", Any]],
109
+ opt_states: "list[OptimizationState] | None" = None,
110
+ ) -> list[tuple[float, dict[str, Any]]]:
111
+ call_pairs: list[tuple[Any, Any]] = list(pairs)
112
+ if self._str_candidate_key is not None:
113
+ call_pairs = [(candidate[self._str_candidate_key], example) for candidate, example in call_pairs]
114
+ if self._single_instance_mode:
115
+ # Mirror of EvaluatorWrapper's single-instance handling: the
116
+ # internal placeholder example must never reach user code — the
117
+ # user-visible example is None in this mode.
118
+ call_pairs = [(candidate, None) for candidate, _example in call_pairs]
119
+
120
+ kwargs: dict[str, Any] = {}
121
+ if self._accepts_opt_states and opt_states is not None:
122
+ kwargs["opt_states"] = opt_states
123
+
124
+ try:
125
+ raw_results = list(self._fn(call_pairs, **kwargs))
126
+ except Exception as e:
127
+ if self._raise_on_exception:
128
+ raise
129
+ # _gepa_transient_failure marks these as synthetic whole-batch
130
+ # failure results: the adapter will NOT cache them, so one
131
+ # transient infrastructure error cannot poison the (disk) cache
132
+ # for every pair across runs.
133
+ return [(0.0, {"error": str(e), "_gepa_transient_failure": True}) for _ in pairs]
134
+
135
+ if len(raw_results) != len(pairs):
136
+ raise ValueError(f"batch_evaluator returned {len(raw_results)} results but expected {len(pairs)}")
137
+
138
+ normalized: list[tuple[float, dict[str, Any]]] = []
139
+ for idx, r in enumerate(raw_results):
140
+ if isinstance(r, list | tuple):
141
+ r_seq = list(r)
142
+ if len(r_seq) >= 3:
143
+ # Legacy (score, output, side_info): the output slot is
144
+ # ignored — the adapter packages outputs identically to the
145
+ # sequential path as (score, candidate, side_info).
146
+ si_raw = r_seq[2]
147
+ elif len(r_seq) == 2:
148
+ si_raw = r_seq[1]
149
+ else:
150
+ si_raw = {}
151
+ if si_raw is None:
152
+ si_raw = {}
153
+ if not isinstance(si_raw, dict):
154
+ # Mirror the evaluator path's loudness instead of silently
155
+ # discarding user diagnostics.
156
+ raise TypeError(
157
+ f"batch_evaluator result {idx}: side_info must be a dict or None, "
158
+ f"got {type(si_raw).__name__}"
159
+ )
160
+ # Defensive copy (mirror of EvaluatorWrapper): the user may
161
+ # retain and mutate their dict; the cache, best-evals history,
162
+ # and objective scores must not be corruptible after return.
163
+ normalized.append((float(r_seq[0]), dict(si_raw)))
164
+ else:
165
+ normalized.append((float(r), {}))
166
+ return normalized
167
+
168
+
169
+ class OptimizeAnythingAdapter(GEPAAdapter):
170
+ """Adapter connecting the ``optimize_anything`` API to GEPA's engine.
171
+
172
+ Created automatically by :func:`~gepa.optimize_anything.optimize_anything` —
173
+ users do not instantiate this directly.
174
+ """
175
+
176
+ def __init__(
177
+ self,
178
+ evaluator: Callable[..., tuple[float, Any, dict[str, Any]]] | None = None,
179
+ reflection_lm: LanguageModel | None = None,
180
+ reflection_prompt_template: str | None = None,
181
+ parallel: bool = True,
182
+ max_workers: int | None = None,
183
+ refiner_config: "RefinerConfig | None" = None,
184
+ best_example_evals_k: int = 1,
185
+ objective: str | None = None,
186
+ background: str | None = None,
187
+ cache_mode: str = "memory", # "off", "memory", "disk"
188
+ cache_dir: str | Path | None = None,
189
+ batch_evaluator: Callable[..., Any] | None = None,
190
+ ):
191
+ if evaluator is None and batch_evaluator is None:
192
+ raise ValueError("Provide evaluator=, batch_evaluator=, or both.")
193
+ self.evaluator = evaluator
194
+ if batch_evaluator is not None and not isinstance(batch_evaluator, BatchEvaluatorWrapper):
195
+ batch_evaluator = BatchEvaluatorWrapper(batch_evaluator)
196
+ self._batch_evaluator = batch_evaluator
197
+ if batch_evaluator is not None and parallel:
198
+ logger.info(
199
+ "batch_evaluator provided: grouped evaluations route through it, so the "
200
+ "parallel=True thread pool is not used as their transport. If refiner_config "
201
+ "is set, refinement loops still run on the thread pool and may invoke "
202
+ "batch_evaluator concurrently with singleton batches — ensure it is thread-safe."
203
+ )
204
+ self.parallel = parallel
205
+ self.max_workers = max_workers
206
+ self.refiner_config = refiner_config
207
+ self.best_example_evals_k = best_example_evals_k
208
+ self.objective = objective
209
+ self.background = background
210
+ self.cache_mode = cache_mode
211
+ self.cache_dir = Path(cache_dir) if cache_dir else None
212
+
213
+ # Track top-K best evaluations per example for warm-start support
214
+ self._best_evals_by_example: dict[str, list[dict]] = {} # keyed by _example_hash
215
+ self._best_evals_lock = threading.Lock() # Thread safety for parallel execution
216
+
217
+ # Initialize evaluation cache
218
+ self._eval_cache: dict[tuple[str, str], tuple[float, Any, dict]] = {}
219
+ self._eval_cache_lock = threading.Lock()
220
+
221
+ # Setup disk cache directory and load existing entries
222
+ self._cache_dir_path: Path | None = None
223
+ if self.cache_mode == "disk" and self.cache_dir:
224
+ self._cache_dir_path = self.cache_dir / "fitness_cache"
225
+ self._cache_dir_path.mkdir(parents=True, exist_ok=True)
226
+ self._load_cache()
227
+
228
+ # Refiner uses LiteLLM directly via refiner_config.refiner_lm callable
229
+ # No separate predictor needed - the LM callable is set up in optimize_anything.py
230
+
231
+ def _get_best_example_evals(self, example: object) -> list[dict]:
232
+ """Get sorted top-K best evaluations for this example (thread-safe)."""
233
+ key = self._example_hash(example)
234
+ with self._best_evals_lock:
235
+ # Return a copy to avoid mutation issues
236
+ return list(self._best_evals_by_example.get(key, []))
237
+
238
+ def _update_best_example_evals(self, example: object, score: float, side_info: "SideInfo") -> None:
239
+ """Add evaluation to example's best evals, maintain sorted top-K (thread-safe)."""
240
+ key = self._example_hash(example)
241
+ with self._best_evals_lock:
242
+ if key not in self._best_evals_by_example:
243
+ self._best_evals_by_example[key] = []
244
+
245
+ self._best_evals_by_example[key].append({"score": score, "side_info": side_info})
246
+
247
+ # Sort descending by score, keep top K
248
+ self._best_evals_by_example[key] = sorted(
249
+ self._best_evals_by_example[key],
250
+ key=lambda x: x["score"],
251
+ reverse=True,
252
+ )[: self.best_example_evals_k]
253
+
254
+ # --- Evaluation caching methods ---
255
+
256
+ def _candidate_hash(self, candidate: dict[str, str]) -> str:
257
+ """SHA256 hash of candidate for cache key (first 16 chars)."""
258
+ return hashlib.sha256(json.dumps(sorted(candidate.items())).encode()).hexdigest()[:16]
259
+
260
+ def _example_hash(self, example: Any) -> str:
261
+ """Hash example for cache key (first 16 chars)."""
262
+ if example is None:
263
+ return "none"
264
+ try:
265
+ # Try JSON serialization for dicts, lists, primitives
266
+ return hashlib.sha256(json.dumps(example, sort_keys=True, default=str).encode()).hexdigest()[:16]
267
+ except (TypeError, ValueError):
268
+ # Fallback to id for unhashable objects
269
+ return f"id_{id(example)}"
270
+
271
+ def _cache_key(self, candidate: dict[str, str], example: Any) -> tuple[str, str]:
272
+ """Build cache key tuple."""
273
+ return (self._candidate_hash(candidate), self._example_hash(example))
274
+
275
+ def _cache_filename(self, cache_key: tuple[str, str]) -> str:
276
+ """Build filename from cache key."""
277
+ return f"{cache_key[0]}_{cache_key[1]}.pkl"
278
+
279
+ def _load_cache(self) -> None:
280
+ """Load all cache entries from disk into memory."""
281
+ if self._cache_dir_path is None:
282
+ return
283
+ for cache_file in self._cache_dir_path.glob("*.pkl"):
284
+ try:
285
+ with open(cache_file, "rb") as f:
286
+ data = pickle.load(f)
287
+ # File stores: {"key": (cand_hash, ex_hash), "result": (score, output, side_info)}
288
+ self._eval_cache[data["key"]] = data["result"]
289
+ except Exception as exc:
290
+ logger.warning("Failed to load cache file %s: %s", cache_file, exc)
291
+
292
+ def _save_cache_entry(self, cache_key: tuple[str, str], result: tuple) -> None:
293
+ """Save single cache entry to disk."""
294
+ if self._cache_dir_path is None:
295
+ return
296
+ filename = self._cache_filename(cache_key)
297
+ filepath = self._cache_dir_path / filename
298
+ with open(filepath, "wb") as f:
299
+ pickle.dump({"key": cache_key, "result": result}, f)
300
+
301
+ def _build_opt_state(self, example: Any) -> "OptimizationState":
302
+ """Build an OptimizationState for the given example."""
303
+ from gepa.optimize_anything import OptimizationState
304
+
305
+ return OptimizationState(best_example_evals=self._get_best_example_evals(example))
306
+
307
+ def _resolve_pair(self, candidate: "Candidate", example: Any) -> tuple[float, Any, dict]:
308
+ """Resolve ONE (candidate, example) pair, whichever evaluation transport exists.
309
+
310
+ Uses the single-pair evaluator when provided; otherwise routes a
311
+ singleton batch through ``batch_evaluator``. This is the seam that
312
+ makes the refiner (and any other per-pair resolution) work identically
313
+ with or without a single-pair evaluator — including shared caching,
314
+ since :meth:`_call_evaluator`'s cache wraps this method on both paths.
315
+ """
316
+ if self.evaluator is not None:
317
+ return self.evaluator(
318
+ candidate,
319
+ example=example,
320
+ opt_state=self._build_opt_state(example),
321
+ )
322
+ assert self._batch_evaluator is not None
323
+ ((score, side_info),) = self._batch_evaluator(
324
+ [(candidate, example)], opt_states=[self._build_opt_state(example)]
325
+ )
326
+ # Slot 1 is the "output" placeholder, mirroring EvaluatorWrapper's
327
+ # (score, None, side_info) contract; downstream packaging rebuilds it
328
+ # from the candidate (_assemble_no_refiner_batch / refiner path).
329
+ return score, None, side_info
330
+
331
+ def _call_evaluator(
332
+ self,
333
+ candidate: "Candidate",
334
+ example: Any,
335
+ ) -> tuple[float, Any, dict]:
336
+ """Call evaluator with optional caching."""
337
+ # No caching
338
+ if self.cache_mode == "off":
339
+ return self._resolve_pair(candidate, example)
340
+
341
+ # Build cache key
342
+ cache_key = self._cache_key(candidate, example)
343
+
344
+ # Check cache (thread-safe)
345
+ with self._eval_cache_lock:
346
+ if cache_key in self._eval_cache:
347
+ return self._eval_cache[cache_key]
348
+
349
+ # Cache miss - call evaluator
350
+ result = self._resolve_pair(candidate, example)
351
+
352
+ # Store in cache (thread-safe); synthetic transient-failure results
353
+ # from a batch_evaluator exception are never cached.
354
+ side_info = result[2] if len(result) > 2 and isinstance(result[2], dict) else {}
355
+ if not side_info.get("_gepa_transient_failure"):
356
+ with self._eval_cache_lock:
357
+ self._eval_cache[cache_key] = result
358
+ if self.cache_mode == "disk":
359
+ self._save_cache_entry(cache_key, result)
360
+
361
+ return result
362
+
363
+ def evaluate(
364
+ self,
365
+ batch: list[DataInst],
366
+ candidate: "Candidate",
367
+ capture_traces: bool = False,
368
+ ) -> EvaluationBatch:
369
+ """Evaluate a candidate on a batch of examples, with optional refinement.
370
+
371
+ When ``refiner_config`` is set, each example goes through an
372
+ evaluate→refine→re-evaluate loop before returning the best result.
373
+ Multi-objective scores from ``side_info["scores"]`` are extracted and
374
+ forwarded as ``objective_scores`` in the returned batch.
375
+ """
376
+ # No-refiner path: evaluate each example (optionally in parallel) and
377
+ # package. Factored into ``_assemble_no_refiner_batch`` so that
378
+ # ``batch_evaluate``'s parallel fallback can reuse the exact same
379
+ # packaging (best-evals update, objective-score extraction, counts).
380
+ if self.refiner_config is None:
381
+ if self._batch_evaluator is not None:
382
+ # batch_evaluator is the preferred transport for any multi-pair
383
+ # evaluation (valset evals, merge evals, seed evals) — one
384
+ # external call for the whole batch, cache-aware.
385
+ return self._batch_evaluate_via_user_fn([(candidate, batch)])[0]
386
+ if self.parallel and len(batch) > 1:
387
+ raw_results = self._evaluate_parallel(batch, candidate)
388
+ else:
389
+ raw_results = [self._call_evaluator(candidate, example) for example in batch]
390
+ return self._assemble_no_refiner_batch(candidate, batch, raw_results)
391
+
392
+ # Refiner path: evaluate with refinement. eval_output is a list of
393
+ # (score, output, side_info) where output = (score, best_candidate, side_info).
394
+ eval_output = self._evaluate_with_refinement(batch, candidate)
395
+
396
+ scores = [score for score, _, _ in eval_output]
397
+ side_infos: list[SideInfo] = [info for _, _, info in eval_output]
398
+ # outputs = list of (score, best_candidate, side_info) tuples
399
+ outputs = [out for _, out, _ in eval_output]
400
+ objective_scores = [self._extract_objective_scores(candidate, si) for si in side_infos]
401
+
402
+ # Count actual evaluator invocations from the attempt history recorded
403
+ # in each side_info (the refiner path may call the evaluator multiple
404
+ # times per example).
405
+ num_metric_calls = 0
406
+ for si in side_infos:
407
+ rp_info = si.get("refiner_prompt_specific_info", {})
408
+ attempts = rp_info.get("Attempts", [])
409
+ num_metric_calls += sum(1 for a in attempts if "side_info" in a)
410
+
411
+ return EvaluationBatch(
412
+ outputs=outputs,
413
+ scores=scores,
414
+ trajectories=side_infos,
415
+ objective_scores=objective_scores,
416
+ num_metric_calls=num_metric_calls,
417
+ )
418
+
419
+ def batch_evaluate(
420
+ self,
421
+ items: list[tuple["Candidate", list]],
422
+ ) -> list[EvaluationBatch]:
423
+ """Evaluate multiple (candidate, batch) pairs.
424
+
425
+ When a ``batch_evaluator`` was provided at construction time (and no
426
+ refiner is configured), all (candidate, example) pairs are flattened
427
+ into a single call to the user's batch function.
428
+
429
+ Falls back to sequential ``evaluate()`` calls for the refiner path or a
430
+ single item. Always captures traces. With ``refiner_config`` set,
431
+ evaluation is per-example by construction (each example runs its own
432
+ evaluate->refine->re-evaluate loop), so grouped calls degrade to
433
+ singleton batches through ``_resolve_pair`` — refinement is never
434
+ silently skipped.
435
+ """
436
+ if self._batch_evaluator is not None and self.refiner_config is None:
437
+ return self._batch_evaluate_via_user_fn(items)
438
+ if self.parallel and self.refiner_config is None and len(items) > 1:
439
+ return self._batch_evaluate_parallel_fallback(items)
440
+ return [self.evaluate(batch, candidate, capture_traces=True) for candidate, batch in items]
441
+
442
+ def _batch_evaluate_via_user_fn(
443
+ self,
444
+ items: list[tuple["Candidate", list]],
445
+ ) -> list[EvaluationBatch]:
446
+ """Flatten all (candidate, example) pairs, call batch_evaluator once, repackage.
447
+
448
+ The (wrapped) batch_evaluator normalizes every per-pair result to
449
+ ``(score, side_info)``; packaging then goes through
450
+ :meth:`_assemble_no_refiner_batch` so the batch path produces byte-identical
451
+ EvaluationBatches to the sequential path: ``outputs`` are
452
+ ``(score, candidate, side_info)`` tuples, best-evals history is updated
453
+ (feeding ``OptimizationState.best_example_evals``), objective scores are
454
+ extracted, and metric calls are counted the same way.
455
+ """
456
+ assert self._batch_evaluator is not None
457
+
458
+ pairs: list[tuple[dict[str, str], Any]] = []
459
+ pair_to_item_idx: list[int] = []
460
+ for item_idx, (candidate, batch) in enumerate(items):
461
+ for example in batch:
462
+ pairs.append((candidate, example))
463
+ pair_to_item_idx.append(item_idx)
464
+
465
+ # Cache layer: identical key/value format to _call_evaluator, so
466
+ # entries are shared across the batch path, the sequential path, and
467
+ # the refiner's singleton resolutions. Only misses reach the user's
468
+ # function — still as ONE call.
469
+ results: list[tuple[float, dict[str, Any]] | None] = [None] * len(pairs)
470
+ miss_indices = list(range(len(pairs)))
471
+ if self.cache_mode != "off":
472
+ miss_indices = []
473
+ with self._eval_cache_lock:
474
+ for idx, (cand, example) in enumerate(pairs):
475
+ cached = self._eval_cache.get(self._cache_key(cand, example))
476
+ if cached is not None:
477
+ score, _output, side_info = cached
478
+ results[idx] = (score, side_info)
479
+ else:
480
+ miss_indices.append(idx)
481
+
482
+ if miss_indices:
483
+ # With caching enabled, duplicate (candidate, example) pairs within
484
+ # one grouped call resolve to a single user-fn evaluation (the
485
+ # sequential path gets the same effect through per-pair cache hits).
486
+ if self.cache_mode != "off":
487
+ first_by_key: dict[tuple[str, str], int] = {}
488
+ unique_miss: list[int] = []
489
+ alias_of: dict[int, int] = {}
490
+ for idx in miss_indices:
491
+ key = self._cache_key(*pairs[idx])
492
+ if key in first_by_key:
493
+ alias_of[idx] = first_by_key[key]
494
+ else:
495
+ first_by_key[key] = idx
496
+ unique_miss.append(idx)
497
+ else:
498
+ unique_miss = list(miss_indices)
499
+ alias_of = {}
500
+
501
+ miss_pairs = [pairs[idx] for idx in unique_miss]
502
+ miss_opt_states = [self._build_opt_state(example) for _, example in miss_pairs]
503
+ miss_results = self._batch_evaluator(miss_pairs, opt_states=miss_opt_states)
504
+ for idx, res in zip(unique_miss, miss_results, strict=True):
505
+ results[idx] = res
506
+ score, side_info = res
507
+ # Transient whole-batch failures (see BatchEvaluatorWrapper)
508
+ # are never cached — they must be re-evaluated next time.
509
+ if self.cache_mode != "off" and not side_info.get("_gepa_transient_failure"):
510
+ cache_key = self._cache_key(*pairs[idx])
511
+ with self._eval_cache_lock:
512
+ self._eval_cache[cache_key] = (score, None, side_info)
513
+ if self.cache_mode == "disk":
514
+ self._save_cache_entry(cache_key, (score, None, side_info))
515
+ for idx, src in alias_of.items():
516
+ results[idx] = results[src]
517
+
518
+ raw_by_item: dict[int, list[tuple[float, Any, SideInfo]]] = {}
519
+ for res, item_idx in zip(results, pair_to_item_idx, strict=True):
520
+ assert res is not None, "unresolved evaluation pair (cache/miss bookkeeping bug)"
521
+ score, side_info = res
522
+ raw_by_item.setdefault(item_idx, []).append((score, None, side_info))
523
+
524
+ return [
525
+ self._assemble_no_refiner_batch(items[item_idx][0], items[item_idx][1], raw_by_item.get(item_idx, []))
526
+ for item_idx in range(len(items))
527
+ ]
528
+
529
+ def _evaluate_with_refinement(
530
+ self,
531
+ batch: list[DataInst],
532
+ candidate: "Candidate",
533
+ ) -> list[tuple[float, Any, "SideInfo"]]:
534
+ """Evaluate batch with automatic refinement."""
535
+ if self.parallel and len(batch) > 1:
536
+ return self._evaluate_with_refinement_parallel(batch, candidate)
537
+
538
+ results = []
539
+ for example in batch:
540
+ result = self._evaluate_single_with_refinement(candidate, example)
541
+ results.append(result)
542
+ return results
543
+
544
+ def _evaluate_single_with_refinement(
545
+ self,
546
+ candidate: "Candidate",
547
+ example: object,
548
+ ) -> tuple[float, Any, "SideInfo"]:
549
+ """Evaluate a single example with refinement."""
550
+ assert self.refiner_config is not None
551
+
552
+ # refiner_prompt is always in candidate (auto-injected by optimize_anything)
553
+ refiner_prompt = candidate.get("refiner_prompt", "")
554
+
555
+ # 1. Evaluate original candidate
556
+ original_score, original_output, original_side_info = self._call_evaluator(candidate, example)
557
+
558
+ # Update best evals with original evaluation
559
+ self._update_best_example_evals(example, original_score, original_side_info)
560
+
561
+ # 2. Refine and evaluate
562
+ best_refined_score, best_refined_candidate, best_refined_side_info, all_attempts = self._refine_and_evaluate(
563
+ candidate, example, refiner_prompt, original_score, original_side_info
564
+ )
565
+
566
+ # 3. Score = best of original and refined (refiner is a score booster)
567
+ # Track best_candidate (the actual candidate dict that won)
568
+ if best_refined_score > original_score:
569
+ final_score = best_refined_score
570
+ best_candidate = best_refined_candidate # Refined candidate won
571
+ else:
572
+ final_score = original_score
573
+ best_candidate = candidate # Original candidate won
574
+
575
+ # 4. Side_info = original evaluation (so reflection sees raw candidate quality)
576
+ aggregated_side_info = dict(original_side_info)
577
+
578
+ # 5. Add refiner_prompt_specific_info with attempt history
579
+ # Only include "scores" if the user's side_info has "scores" (for objective frontier)
580
+ refiner_side_info: dict[str, Any] = {"Attempts": all_attempts}
581
+
582
+ if "scores" in original_side_info:
583
+ # Only consider attempts that produced real evaluation results (have "side_info")
584
+ # so that failed attempts (JSON parse errors, exceptions) with placeholder score=0.0
585
+ # don't mask real evaluations when original scores are negative
586
+ evaluated_attempts = [a for a in all_attempts if "side_info" in a]
587
+ best_refined_scores = {}
588
+ if evaluated_attempts:
589
+ best_attempt = max(
590
+ evaluated_attempts,
591
+ key=lambda x: x.get("score", float("-inf")),
592
+ )
593
+ best_attempt_side_info = best_attempt.get("side_info")
594
+ if isinstance(best_attempt_side_info, dict) and "scores" in best_attempt_side_info:
595
+ best_refined_scores = best_attempt_side_info["scores"]
596
+ refiner_side_info["scores"] = best_refined_scores
597
+
598
+ aggregated_side_info["refiner_prompt_specific_info"] = refiner_side_info
599
+
600
+ # Package output as (score, best_candidate, side_info) tuple
601
+ output = (final_score, best_candidate, aggregated_side_info)
602
+ return final_score, output, aggregated_side_info
603
+
604
+ def _run_parallel(
605
+ self,
606
+ batch: list[DataInst],
607
+ candidate: "Candidate",
608
+ eval_fn, # callable(candidate, example) -> result
609
+ ) -> list[tuple[float, Any, "SideInfo"]]:
610
+ """Run evaluation function in parallel across batch."""
611
+ results: list[tuple[int, tuple[float, Any, SideInfo]]] = []
612
+
613
+ with ThreadPoolExecutor(max_workers=self.max_workers or len(batch)) as executor:
614
+ future_to_idx = {executor.submit(eval_fn, candidate, example): idx for idx, example in enumerate(batch)}
615
+ for future in as_completed(future_to_idx):
616
+ idx = future_to_idx[future]
617
+ results.append((idx, future.result()))
618
+
619
+ results.sort(key=lambda x: x[0])
620
+ return [result for _, result in results]
621
+
622
+ def _run_parallel_pairs(
623
+ self,
624
+ pairs: list[tuple["Candidate", Any]],
625
+ ) -> list[tuple[float, Any, "SideInfo"]]:
626
+ """Evaluate ``(candidate, example)`` pairs concurrently, preserving input order.
627
+
628
+ Unlike :meth:`_run_parallel` (one candidate, many examples), this fans
629
+ out over pairs whose candidate may differ, so a single thread pool is
630
+ shared across all candidates in a ``batch_evaluate`` call.
631
+ """
632
+ results: list[tuple[int, tuple[float, Any, SideInfo]]] = []
633
+ with ThreadPoolExecutor(max_workers=self.max_workers or len(pairs)) as executor:
634
+ future_to_idx = {
635
+ executor.submit(self._call_evaluator, candidate, example): idx
636
+ for idx, (candidate, example) in enumerate(pairs)
637
+ }
638
+ for future in as_completed(future_to_idx):
639
+ idx = future_to_idx[future]
640
+ results.append((idx, future.result()))
641
+ results.sort(key=lambda x: x[0])
642
+ return [result for _, result in results]
643
+
644
+ def _batch_evaluate_parallel_fallback(
645
+ self,
646
+ items: list[tuple["Candidate", list]],
647
+ ) -> list[EvaluationBatch]:
648
+ """Fan all ``(candidate, example)`` pairs out across one thread pool, regroup per item.
649
+
650
+ No-refiner path only. Saturates ``max_workers`` across candidates and
651
+ examples, then repackages each item via :meth:`_assemble_no_refiner_batch`
652
+ so results are identical to per-item ``evaluate()`` calls.
653
+ """
654
+ pairs: list[tuple[Candidate, Any]] = []
655
+ pair_to_item_idx: list[int] = []
656
+ for item_idx, (candidate, batch) in enumerate(items):
657
+ for example in batch:
658
+ pairs.append((candidate, example))
659
+ pair_to_item_idx.append(item_idx)
660
+
661
+ raw_by_pair = self._run_parallel_pairs(pairs) if pairs else []
662
+
663
+ raw_per_item: list[list[tuple[float, Any, SideInfo]]] = [[] for _ in items]
664
+ for pair_idx, raw in enumerate(raw_by_pair):
665
+ raw_per_item[pair_to_item_idx[pair_idx]].append(raw)
666
+
667
+ return [
668
+ self._assemble_no_refiner_batch(candidate, batch, raw_per_item[item_idx])
669
+ for item_idx, (candidate, batch) in enumerate(items)
670
+ ]
671
+
672
+ def _extract_objective_scores(self, candidate: "Candidate", side_info: "SideInfo") -> dict[str, float]:
673
+ """Pull per-objective scores out of a side_info dict (top-level + per-param)."""
674
+ objective_score: dict[str, float] = {}
675
+ if "scores" in side_info:
676
+ objective_score.update(side_info["scores"])
677
+ for param_name in candidate.keys():
678
+ specific = side_info.get(param_name + "_specific_info") if isinstance(side_info, dict) else None
679
+ if isinstance(specific, dict) and "scores" in specific:
680
+ objective_score.update({f"{param_name}::{k}": v for k, v in specific["scores"].items()})
681
+ return objective_score
682
+
683
+ def _assemble_no_refiner_batch(
684
+ self,
685
+ candidate: "Candidate",
686
+ batch: list[DataInst],
687
+ raw_results: list[tuple[float, Any, "SideInfo"]],
688
+ ) -> EvaluationBatch:
689
+ """Package raw ``(score, _, side_info)`` evaluator results into an EvaluationBatch.
690
+
691
+ Shared by :meth:`evaluate` (no-refiner path) and
692
+ :meth:`_batch_evaluate_parallel_fallback` so both produce identical
693
+ output, update best-evals history, and count metric calls the same way.
694
+ """
695
+ # Package outputs as (score, candidate, side_info) tuples.
696
+ eval_output = [(score, (score, candidate, side_info), side_info) for score, _, side_info in raw_results]
697
+ for example, (score, _, side_info) in zip(batch, eval_output, strict=True):
698
+ self._update_best_example_evals(example, score, side_info)
699
+
700
+ scores = [score for score, _, _ in eval_output]
701
+ side_infos: list[SideInfo] = [info for _, _, info in eval_output]
702
+ outputs = [out for _, out, _ in eval_output]
703
+ objective_scores = [self._extract_objective_scores(candidate, si) for si in side_infos]
704
+
705
+ return EvaluationBatch(
706
+ outputs=outputs,
707
+ scores=scores,
708
+ trajectories=side_infos,
709
+ objective_scores=objective_scores,
710
+ num_metric_calls=len(batch),
711
+ )
712
+
713
+ def _evaluate_parallel(self, batch, candidate):
714
+ """Evaluate batch in parallel (no refinement)."""
715
+ return self._run_parallel(batch, candidate, self._call_evaluator)
716
+
717
+ def _evaluate_with_refinement_parallel(self, batch, candidate):
718
+ """Evaluate batch in parallel with refinement."""
719
+ return self._run_parallel(batch, candidate, self._evaluate_single_with_refinement)
720
+
721
+ def _refine_and_evaluate(
722
+ self,
723
+ candidate: "Candidate",
724
+ example: object,
725
+ refiner_prompt: str,
726
+ original_score: float,
727
+ original_side_info: "SideInfo",
728
+ ) -> tuple[float, dict[str, str] | None, "SideInfo | None", list[dict]]:
729
+ """
730
+ Refine a candidate using the refiner LLM and evaluate the refined version.
731
+ Refines ALL non-refiner params at once via JSON dict.
732
+
733
+ Returns:
734
+ (best_refined_score, best_refined_candidate, best_refined_side_info, all_attempts)
735
+ best_refined_candidate is the candidate dict that achieved best refined score,
736
+ or None if no refinement improved over original.
737
+ best_refined_side_info is None if no refinement improved over original.
738
+ """
739
+ # This method should only be called when refiner_config is not None
740
+ assert self.refiner_config is not None, "refiner_config must be set to use refinement"
741
+
742
+ # Build params dict: all non-refiner params
743
+ params_dict = {k: v for k, v in candidate.items() if k != "refiner_prompt"}
744
+
745
+ best_score = original_score
746
+ best_candidate: dict[str, str] | None = None
747
+ best_side_info: SideInfo | None = None
748
+
749
+ # Initialize attempts with original evaluation (iteration 0)
750
+ all_attempts: list[dict] = [
751
+ {
752
+ "iteration": 0,
753
+ "candidate": params_dict,
754
+ "score": original_score,
755
+ "side_info": original_side_info,
756
+ }
757
+ ]
758
+
759
+ # Iterative refinement
760
+ refiner_lm = self.refiner_config.refiner_lm
761
+ assert callable(refiner_lm), (
762
+ "refiner_lm must be a callable LanguageModel, not a string or None. "
763
+ "Ensure optimize_anything() has converted it via make_litellm_lm()."
764
+ )
765
+
766
+ current_params = params_dict
767
+ for refinement_iter in range(self.refiner_config.max_refinements):
768
+ # Format ALL attempts so far for the refiner (provides full history)
769
+ current_feedback = self._format_all_attempts_feedback(all_attempts)
770
+ try:
771
+ # Call refiner LLM with formatted prompt
772
+ prompt = REFINER_PROMPT_TEMPLATE.format(
773
+ refiner_prompt=refiner_prompt,
774
+ candidate_to_improve=json.dumps(current_params, indent=2),
775
+ evaluation_feedback=current_feedback,
776
+ )
777
+ raw_output = refiner_lm(prompt).strip()
778
+ # Strip markdown code fences if present
779
+ if raw_output.startswith("```"):
780
+ # Remove first line (```json or ```) and last line (```)
781
+ lines = raw_output.split("\n")
782
+ raw_output = "\n".join(lines[1:-1] if lines[-1].strip() == "```" else lines[1:]).strip()
783
+
784
+ try:
785
+ parsed_refined = json.loads(raw_output)
786
+ if not isinstance(parsed_refined, dict):
787
+ raise ValueError(f"Expected JSON dict, got {type(parsed_refined).__name__}")
788
+ except (json.JSONDecodeError, ValueError) as parse_err:
789
+ # JSON parse failed: record error so refiner can learn, continue to next iteration
790
+ all_attempts.append(
791
+ {
792
+ "iteration": refinement_iter + 1,
793
+ "error": f"JSON parse error: {parse_err}",
794
+ "raw_output": raw_output[:2000],
795
+ "score": -1e9,
796
+ }
797
+ )
798
+ continue
799
+
800
+ # Reconstruct full candidate: refined params + original refiner_prompt
801
+ refined_candidate_dict = {**parsed_refined, "refiner_prompt": candidate.get("refiner_prompt", "")}
802
+ refined_score, refined_output, refined_eval_side_info = self._call_evaluator(
803
+ refined_candidate_dict, example
804
+ )
805
+
806
+ # Update best evals with this refinement evaluation
807
+ self._update_best_example_evals(example, refined_score, refined_eval_side_info)
808
+
809
+ # Track attempt
810
+ all_attempts.append(
811
+ {
812
+ "iteration": refinement_iter + 1,
813
+ "candidate": parsed_refined,
814
+ "score": refined_score,
815
+ "side_info": refined_eval_side_info,
816
+ }
817
+ )
818
+
819
+ # Update best if improved
820
+ if refined_score > best_score:
821
+ best_score = refined_score
822
+ best_candidate = refined_candidate_dict # Track the actual candidate dict
823
+ best_side_info = refined_eval_side_info
824
+ current_params = parsed_refined
825
+ else:
826
+ # Stop when no improvement
827
+ break
828
+
829
+ except Exception as e:
830
+ # Refinement failed, record error and stop
831
+ all_attempts.append(
832
+ {
833
+ "iteration": refinement_iter + 1,
834
+ "error": str(e),
835
+ "score": -1e9,
836
+ }
837
+ )
838
+ break
839
+
840
+ return best_score, best_candidate, best_side_info, all_attempts
841
+
842
+ def _format_all_attempts_feedback(self, all_attempts: list[dict]) -> str:
843
+ """Format all attempts (including original) into readable feedback for the refiner LLM.
844
+
845
+ This provides the refiner with full history of optimization attempts,
846
+ allowing it to understand the progression and avoid repeating failed approaches.
847
+ """
848
+ return json.dumps(all_attempts, indent=2, default=str)
849
+
850
+ def make_reflective_dataset(
851
+ self,
852
+ candidate: dict[str, str],
853
+ eval_batch: EvaluationBatch,
854
+ components_to_update: list[str],
855
+ ) -> Mapping[str, Sequence[Mapping[str, Any]]]:
856
+ """Format evaluation results into per-component reflection datasets.
857
+
858
+ For each component being updated, produces a list of dicts (one per
859
+ example) combining shared SideInfo fields with any
860
+ ``<component>_specific_info`` data. The ``"scores"`` key is renamed
861
+ to ``"Scores (Higher is Better)"`` for clarity in the LLM prompt.
862
+ """
863
+ scores, side_infos = eval_batch.scores, eval_batch.trajectories
864
+ assert side_infos is not None
865
+ ret: dict[str, list[dict[str, Any]]] = {}
866
+ for component_name in components_to_update:
867
+ ret[component_name] = []
868
+ for score, side_info in zip(scores, side_infos, strict=False):
869
+ ret[component_name].append({})
870
+ for k, v in side_info.items():
871
+ if k == "scores":
872
+ ret[component_name][-1]["Scores (Higher is Better)"] = v
873
+ elif not k.endswith("_specific_info"):
874
+ ret[component_name][-1][k] = v
875
+ elif k == f"{component_name}_specific_info":
876
+ ret[component_name][-1].update(v)
877
+ else:
878
+ continue
879
+ return ret