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,269 @@
1
+ import random
2
+ from typing import Any, Callable
3
+
4
+ import dspy
5
+ from dspy.adapters.types import History
6
+ from dspy.evaluate import Evaluate
7
+ from dspy.primitives import Example, Prediction
8
+ from dspy.teleprompt.bootstrap_trace import TraceData
9
+
10
+ from gepa import EvaluationBatch, GEPAAdapter
11
+ from gepa.proposer.reflective_mutation.base import LanguageModel
12
+
13
+
14
+ class DspyAdapter(GEPAAdapter[Example, TraceData, Prediction]):
15
+ def __init__(
16
+ self,
17
+ task_lm: dspy.LM,
18
+ metric_fn: Callable,
19
+ reflection_lm: LanguageModel,
20
+ failure_score=0.0,
21
+ num_threads: int | None = None,
22
+ add_format_failure_as_feedback: bool = False,
23
+ rng: random.Random | None = None,
24
+ ):
25
+ self.task_lm = task_lm
26
+ self.metric_fn = metric_fn
27
+ assert reflection_lm is not None, (
28
+ "DspyAdapter for full-program evolution requires a reflection_lm to be provided"
29
+ )
30
+ self.reflection_lm = reflection_lm
31
+ self.failure_score = failure_score
32
+ self.num_threads = num_threads
33
+ self.add_format_failure_as_feedback = add_format_failure_as_feedback
34
+ self.rng = rng or random.Random(0)
35
+
36
+ def build_program(self, candidate: dict[str, str]) -> tuple[dspy.Module, None] | tuple[None, str]:
37
+ candidate_src = candidate["program"]
38
+ context = {}
39
+ o = self.load_dspy_program_from_code(candidate_src, context)
40
+ return o
41
+
42
+ def load_dspy_program_from_code(
43
+ self,
44
+ candidate_src: str,
45
+ context: dict,
46
+ ):
47
+ try:
48
+ compile(candidate_src, "<string>", "exec")
49
+ except SyntaxError as e:
50
+ # print(f"Syntax Error in original code {e}")
51
+ # return None
52
+ import traceback
53
+
54
+ tb = traceback.format_exc()
55
+ return None, f"Syntax Error in code: {e}\n{tb}"
56
+
57
+ try:
58
+ exec(candidate_src, context) # expose to current namespace
59
+ except Exception as e:
60
+ import traceback
61
+
62
+ tb = traceback.format_exc()
63
+ return None, f"Error in executing code: {e}\n{tb}"
64
+
65
+ dspy_program = context.get("program")
66
+
67
+ if dspy_program is None:
68
+ return (
69
+ None,
70
+ "Your code did not define a `program` object. Please define a `program` object which is an instance of `dspy.Module`, either directly by dspy.Predict or dspy.ChainOfThought, or by instantiating a class that inherits from `dspy.Module`.",
71
+ )
72
+ else:
73
+ if not isinstance(dspy_program, dspy.Module):
74
+ return (
75
+ None,
76
+ f"Your code defined a `program` object, but it is an instance of {type(dspy_program)}, not `dspy.Module`. Please define a `program` object which is an instance of `dspy.Module`, either directly by dspy.Predict or dspy.ChainOfThought, or by instantiating a class that inherits from `dspy.Module`.",
77
+ )
78
+
79
+ dspy_program.set_lm(self.task_lm)
80
+
81
+ return dspy_program, None
82
+
83
+ def evaluate(self, batch, candidate, capture_traces=False):
84
+ program, feedback = self.build_program(candidate)
85
+
86
+ if program is None:
87
+ return EvaluationBatch(
88
+ outputs=[None for _ in batch], scores=[self.failure_score for _ in batch], trajectories=feedback
89
+ )
90
+
91
+ if capture_traces:
92
+ # bootstrap_trace_data-like flow with trace capture
93
+ from dspy.teleprompt.bootstrap_trace import bootstrap_trace_data
94
+
95
+ trajs = bootstrap_trace_data(
96
+ program=program,
97
+ dataset=batch,
98
+ metric=self.metric_fn,
99
+ num_threads=self.num_threads,
100
+ raise_on_error=False,
101
+ capture_failed_parses=True,
102
+ failure_score=self.failure_score,
103
+ format_failure_score=self.failure_score,
104
+ )
105
+ scores = []
106
+ outputs = []
107
+ for t in trajs:
108
+ outputs.append(t["prediction"])
109
+ if hasattr(t["prediction"], "__class__") and t.get("score") is None:
110
+ scores.append(self.failure_score)
111
+ else:
112
+ score = t["score"]
113
+ if hasattr(score, "score"):
114
+ score = score["score"]
115
+ scores.append(score)
116
+ return EvaluationBatch(outputs=outputs, scores=scores, trajectories=trajs)
117
+ else:
118
+ evaluator = Evaluate(
119
+ devset=batch,
120
+ metric=self.metric_fn,
121
+ num_threads=self.num_threads,
122
+ return_all_scores=True,
123
+ failure_score=self.failure_score,
124
+ provide_traceback=True,
125
+ max_errors=len(batch) * 100,
126
+ )
127
+ res = evaluator(program)
128
+ outputs = [r[1] for r in res.results]
129
+ scores = [r[2] for r in res.results]
130
+ scores = [s["score"] if hasattr(s, "score") else s for s in scores]
131
+ return EvaluationBatch(outputs=outputs, scores=scores, trajectories=None)
132
+
133
+ def make_reflective_dataset(self, candidate, eval_batch, components_to_update):
134
+ proposed_program, _ = self.build_program(candidate)
135
+
136
+ assert set(components_to_update) == {"program"}, f"set(components_to_update) = {set(components_to_update)}"
137
+ from dspy.teleprompt.bootstrap_trace import FailedPrediction
138
+
139
+ ret_d: dict[str, list[dict[str, Any]]] = {}
140
+
141
+ if isinstance(eval_batch.trajectories, str):
142
+ feedback = eval_batch.trajectories
143
+ return {"program": {"Feedback": feedback}}
144
+
145
+ ########
146
+ items: list[dict[str, Any]] = []
147
+ for data in eval_batch.trajectories or []:
148
+ example_data = {}
149
+ trace = data["trace"]
150
+ example = data["example"]
151
+ example_data["Program Inputs"] = {**example.inputs()}
152
+ prediction = data["prediction"]
153
+ example_data["Program Outputs"] = {**prediction}
154
+ module_score = data["score"]
155
+
156
+ if hasattr(module_score, "feedback"):
157
+ feedback_text = module_score["feedback"]
158
+ else:
159
+ feedback_text = None
160
+
161
+ if hasattr(module_score, "score"):
162
+ module_score = module_score["score"]
163
+
164
+ trace_instances = trace
165
+
166
+ if len(trace_instances) == 0:
167
+ continue
168
+
169
+ selected = None
170
+ for t in trace_instances:
171
+ if isinstance(t[2], FailedPrediction):
172
+ selected = t
173
+ break
174
+
175
+ if selected is not None:
176
+ trace_instances = [selected]
177
+
178
+ trace_d = []
179
+ example_data["Program Trace"] = trace_d
180
+ for selected in trace_instances:
181
+ inputs = selected[1]
182
+ outputs = selected[2]
183
+
184
+ pred_name = None
185
+ for name, predictor in proposed_program.named_predictors():
186
+ if predictor.signature.equals(selected[0].signature):
187
+ pred_name = name
188
+ break
189
+ assert pred_name is not None, f"Could not find predictor for {selected[0].signature}"
190
+
191
+ new_inputs = {}
192
+ new_outputs = {}
193
+
194
+ contains_history = False
195
+ history_key_name = None
196
+ for input_key, input_val in inputs.items():
197
+ if isinstance(input_val, History):
198
+ contains_history = True
199
+ assert history_key_name is None
200
+ history_key_name = input_key
201
+
202
+ if contains_history:
203
+ s = "```json\n"
204
+ for i, message in enumerate(inputs[history_key_name].messages):
205
+ s += f" {i}: {message}\n"
206
+ s += "```"
207
+ new_inputs["Context"] = s
208
+
209
+ for input_key, input_val in inputs.items():
210
+ if contains_history and input_key == history_key_name:
211
+ continue
212
+ new_inputs[input_key] = str(input_val)
213
+
214
+ if isinstance(outputs, FailedPrediction):
215
+ s = "Couldn't parse the output as per the expected output format. The model's raw response was:\n"
216
+ s += "```\n"
217
+ s += outputs.completion_text + "\n"
218
+ s += "```\n\n"
219
+ new_outputs = s
220
+ else:
221
+ for output_key, output_val in outputs.items():
222
+ new_outputs[output_key] = str(output_val)
223
+
224
+ d = {"Called Module": pred_name, "Inputs": new_inputs, "Generated Outputs": new_outputs}
225
+ # if isinstance(outputs, FailedPrediction):
226
+ # adapter = ChatAdapter()
227
+ # structure_instruction = ""
228
+ # for dd in adapter.format(module.signature, [], {}):
229
+ # structure_instruction += dd["role"] + ": " + dd["content"] + "\n"
230
+ # d["Feedback"] = "Your output failed to parse. Follow this structure:\n" + structure_instruction
231
+ # # d['score'] = self.failure_score
232
+ # else:
233
+ # assert fb["score"] == module_score, f"Currently, GEPA only supports feedback functions that return the same score as the module's score. However, the module-level score is {module_score} and the feedback score is {fb.score}."
234
+ # d['score'] = fb.score
235
+ trace_d.append(d)
236
+
237
+ if feedback_text is not None:
238
+ example_data["Feedback"] = feedback_text
239
+
240
+ items.append(example_data)
241
+
242
+ if len(items) == 0:
243
+ raise Exception("No valid predictions found for program.")
244
+
245
+ ret_d["program"] = items
246
+
247
+ ########
248
+ if len(ret_d) == 0:
249
+ raise Exception("No valid predictions found for any module.")
250
+
251
+ return ret_d
252
+
253
+ def propose_new_texts(
254
+ self,
255
+ candidate: dict[str, str],
256
+ reflective_dataset: dict[str, list[dict[str, Any]]],
257
+ components_to_update: list[str],
258
+ ) -> dict[str, str]:
259
+ from gepa.adapters.dspy_full_program_adapter.dspy_program_proposal_signature import DSPyProgramProposalSignature
260
+
261
+ new_texts: dict[str, str] = {}
262
+ for name in components_to_update:
263
+ base_instruction = candidate[name]
264
+ dataset_with_feedback = reflective_dataset[name]
265
+ new_texts[name] = DSPyProgramProposalSignature.run(
266
+ lm=self.reflection_lm,
267
+ input_dict={"curr_program": base_instruction, "dataset_with_feedback": dataset_with_feedback},
268
+ )["new_program"]
269
+ return new_texts
@@ -0,0 +1,56 @@
1
+ # Copyright (c) 2025 Lakshya A Agrawal and the GEPA contributors
2
+ # https://github.com/gepa-ai/gepa
3
+
4
+ from .evaluation_metrics import RAGEvaluationMetrics
5
+ from .generic_rag_adapter import (
6
+ GenericRAGAdapter,
7
+ RAGDataInst,
8
+ RAGOutput,
9
+ RAGTrajectory,
10
+ )
11
+ from .rag_pipeline import RAGPipeline
12
+ from .vector_store_interface import VectorStoreInterface
13
+ from .vector_stores.chroma_store import ChromaVectorStore
14
+ from .vector_stores.weaviate_store import WeaviateVectorStore
15
+
16
+ # Optional vector stores - import only if dependencies are available
17
+ try:
18
+ from .vector_stores.qdrant_store import QdrantVectorStore
19
+
20
+ _QDRANT_AVAILABLE = True
21
+ except ImportError:
22
+ _QDRANT_AVAILABLE = False
23
+
24
+ try:
25
+ from .vector_stores.milvus_store import MilvusVectorStore
26
+
27
+ _MILVUS_AVAILABLE = True
28
+ except ImportError:
29
+ _MILVUS_AVAILABLE = False
30
+
31
+ try:
32
+ from .vector_stores.lancedb_store import LanceDBVectorStore
33
+
34
+ _LANCEDB_AVAILABLE = True
35
+ except ImportError:
36
+ _LANCEDB_AVAILABLE = False
37
+
38
+ __all__ = [
39
+ "GenericRAGAdapter",
40
+ "RAGDataInst",
41
+ "RAGOutput",
42
+ "RAGTrajectory",
43
+ "VectorStoreInterface",
44
+ "ChromaVectorStore",
45
+ "WeaviateVectorStore",
46
+ "RAGPipeline",
47
+ "RAGEvaluationMetrics",
48
+ ]
49
+
50
+ # Add optional vector stores to __all__ if available
51
+ if _QDRANT_AVAILABLE:
52
+ __all__.append("QdrantVectorStore")
53
+ if _MILVUS_AVAILABLE:
54
+ __all__.append("MilvusVectorStore")
55
+ if _LANCEDB_AVAILABLE:
56
+ __all__.append("LanceDBVectorStore")
@@ -0,0 +1,226 @@
1
+ # Copyright (c) 2025 Lakshya A Agrawal and the GEPA contributors
2
+ # https://github.com/gepa-ai/gepa
3
+
4
+ import re
5
+ from typing import Any
6
+
7
+
8
+ class RAGEvaluationMetrics:
9
+ """
10
+ Evaluation metrics for RAG systems.
11
+
12
+ Provides both retrieval and generation quality metrics
13
+ for comprehensive RAG system evaluation.
14
+ """
15
+
16
+ def evaluate_retrieval(self, retrieved_docs: list[dict[str, Any]], relevant_doc_ids: list[str]) -> dict[str, float]:
17
+ """
18
+ Evaluate retrieval quality metrics.
19
+
20
+ Args:
21
+ retrieved_docs: List of retrieved documents with metadata
22
+ relevant_doc_ids: List of ground truth relevant document IDs
23
+
24
+ Returns:
25
+ Dictionary with retrieval metrics (precision, recall, f1, mrr)
26
+ """
27
+ if not retrieved_docs or not relevant_doc_ids:
28
+ return {"retrieval_precision": 0.0, "retrieval_recall": 0.0, "retrieval_f1": 0.0, "retrieval_mrr": 0.0}
29
+
30
+ # Extract document IDs from retrieved docs
31
+ retrieved_ids = []
32
+ for doc in retrieved_docs:
33
+ doc_id = doc.get("metadata", {}).get("doc_id") or doc.get("metadata", {}).get("id")
34
+ if doc_id:
35
+ retrieved_ids.append(str(doc_id))
36
+
37
+ relevant_set = set(relevant_doc_ids)
38
+ retrieved_set = set(retrieved_ids)
39
+
40
+ # Calculate precision and recall
41
+ if len(retrieved_set) == 0:
42
+ precision = 0.0
43
+ else:
44
+ precision = len(relevant_set.intersection(retrieved_set)) / len(retrieved_set)
45
+
46
+ if len(relevant_set) == 0:
47
+ recall = 0.0
48
+ else:
49
+ recall = len(relevant_set.intersection(retrieved_set)) / len(relevant_set)
50
+
51
+ # Calculate F1
52
+ if precision + recall == 0:
53
+ f1 = 0.0
54
+ else:
55
+ f1 = 2 * (precision * recall) / (precision + recall)
56
+
57
+ # Calculate Mean Reciprocal Rank (MRR)
58
+ mrr = 0.0
59
+ for i, retrieved_id in enumerate(retrieved_ids):
60
+ if retrieved_id in relevant_set:
61
+ mrr = 1.0 / (i + 1)
62
+ break
63
+
64
+ return {"retrieval_precision": precision, "retrieval_recall": recall, "retrieval_f1": f1, "retrieval_mrr": mrr}
65
+
66
+ def evaluate_generation(self, generated_answer: str, ground_truth: str, context: str) -> dict[str, float]:
67
+ """
68
+ Evaluate generation quality metrics.
69
+
70
+ Args:
71
+ generated_answer: Generated answer text
72
+ ground_truth: Ground truth answer
73
+ context: Retrieved context used for generation
74
+
75
+ Returns:
76
+ Dictionary with generation metrics
77
+ """
78
+ # Exact match (case-insensitive)
79
+ exact_match = self._exact_match(generated_answer, ground_truth)
80
+
81
+ # F1 score based on token overlap
82
+ f1_score = self._token_f1(generated_answer, ground_truth)
83
+
84
+ # BLEU-like score
85
+ bleu_score = self._simple_bleu(generated_answer, ground_truth)
86
+
87
+ # Answer relevance (simple keyword overlap with context)
88
+ relevance_score = self._answer_relevance(generated_answer, context)
89
+
90
+ # Faithfulness (how well the answer is supported by context)
91
+ faithfulness_score = self._faithfulness_score(generated_answer, context)
92
+
93
+ return {
94
+ "exact_match": float(exact_match),
95
+ "token_f1": f1_score,
96
+ "bleu_score": bleu_score,
97
+ "answer_relevance": relevance_score,
98
+ "faithfulness": faithfulness_score,
99
+ "answer_confidence": (f1_score + relevance_score + faithfulness_score) / 3.0,
100
+ }
101
+
102
+ def combined_rag_score(
103
+ self,
104
+ retrieval_metrics: dict[str, float],
105
+ generation_metrics: dict[str, float],
106
+ retrieval_weight: float = 0.3,
107
+ generation_weight: float = 0.7,
108
+ ) -> float:
109
+ """
110
+ Combine retrieval and generation metrics into a single score.
111
+
112
+ Args:
113
+ retrieval_metrics: Output from evaluate_retrieval
114
+ generation_metrics: Output from evaluate_generation
115
+ retrieval_weight: Weight for retrieval score
116
+ generation_weight: Weight for generation score
117
+
118
+ Returns:
119
+ Combined score between 0 and 1
120
+ """
121
+ # Primary retrieval metric: F1 score
122
+ retrieval_score = retrieval_metrics.get("retrieval_f1", 0.0)
123
+
124
+ # Primary generation metric: weighted combination
125
+ generation_score = (
126
+ generation_metrics.get("token_f1", 0.0) * 0.4
127
+ + generation_metrics.get("answer_relevance", 0.0) * 0.3
128
+ + generation_metrics.get("faithfulness", 0.0) * 0.3
129
+ )
130
+
131
+ return retrieval_weight * retrieval_score + generation_weight * generation_score
132
+
133
+ def _exact_match(self, prediction: str, ground_truth: str) -> bool:
134
+ """Check if prediction exactly matches ground truth (case-insensitive)."""
135
+ return prediction.strip().lower() == ground_truth.strip().lower()
136
+
137
+ def _token_f1(self, prediction: str, ground_truth: str) -> float:
138
+ """Calculate F1 score based on token overlap."""
139
+ pred_tokens = set(self._normalize_text(prediction).split())
140
+ truth_tokens = set(self._normalize_text(ground_truth).split())
141
+
142
+ if len(pred_tokens) == 0 and len(truth_tokens) == 0:
143
+ return 1.0
144
+ if len(pred_tokens) == 0 or len(truth_tokens) == 0:
145
+ return 0.0
146
+
147
+ intersection = pred_tokens.intersection(truth_tokens)
148
+ precision = len(intersection) / len(pred_tokens)
149
+ recall = len(intersection) / len(truth_tokens)
150
+
151
+ if precision + recall == 0:
152
+ return 0.0
153
+
154
+ return 2 * (precision * recall) / (precision + recall)
155
+
156
+ def _simple_bleu(self, prediction: str, ground_truth: str, n: int = 2) -> float:
157
+ """Simple BLEU-like score for n-gram overlap."""
158
+ pred_words = self._normalize_text(prediction).split()
159
+ truth_words = self._normalize_text(ground_truth).split()
160
+
161
+ if len(pred_words) < n or len(truth_words) < n:
162
+ return self._token_f1(prediction, ground_truth)
163
+
164
+ pred_ngrams = {tuple(pred_words[i : i + n]) for i in range(len(pred_words) - n + 1)}
165
+ truth_ngrams = {tuple(truth_words[i : i + n]) for i in range(len(truth_words) - n + 1)}
166
+
167
+ if len(pred_ngrams) == 0 or len(truth_ngrams) == 0:
168
+ return 0.0
169
+
170
+ intersection = pred_ngrams.intersection(truth_ngrams)
171
+ return len(intersection) / len(pred_ngrams)
172
+
173
+ def _answer_relevance(self, answer: str, context: str) -> float:
174
+ """Measure how well the answer relates to the provided context."""
175
+ answer_words = set(self._normalize_text(answer).split())
176
+ context_words = set(self._normalize_text(context).split())
177
+
178
+ if len(answer_words) == 0:
179
+ return 0.0
180
+
181
+ overlap = answer_words.intersection(context_words)
182
+ return len(overlap) / len(answer_words)
183
+
184
+ def _faithfulness_score(self, answer: str, context: str) -> float:
185
+ """
186
+ Measure how well the answer is supported by the context.
187
+ Simple implementation based on shared key phrases.
188
+ """
189
+ # Extract key phrases (sequences of 2+ words)
190
+ answer_phrases = self._extract_phrases(answer)
191
+ context_phrases = self._extract_phrases(context)
192
+
193
+ if len(answer_phrases) == 0:
194
+ return 1.0 # Empty answer is technically faithful
195
+
196
+ supported_phrases = answer_phrases.intersection(context_phrases)
197
+ return len(supported_phrases) / len(answer_phrases)
198
+
199
+ def _extract_phrases(self, text: str, min_length: int = 2) -> set[str]:
200
+ """Extract meaningful phrases from text."""
201
+ words = self._normalize_text(text).split()
202
+ phrases = set()
203
+
204
+ # Add individual significant words (length > 3)
205
+ for word in words:
206
+ if len(word) > 3:
207
+ phrases.add(word)
208
+
209
+ # Add bi-grams and tri-grams
210
+ for n in range(min_length, min(4, len(words) + 1)):
211
+ for i in range(len(words) - n + 1):
212
+ phrase = " ".join(words[i : i + n])
213
+ if len(phrase) > 5: # Only meaningful phrases
214
+ phrases.add(phrase)
215
+
216
+ return phrases
217
+
218
+ def _normalize_text(self, text: str) -> str:
219
+ """Normalize text for comparison."""
220
+ # Convert to lowercase and remove extra whitespace
221
+ text = text.lower().strip()
222
+ # Remove punctuation and special characters
223
+ text = re.sub(r"[^\w\s]", " ", text)
224
+ # Normalize whitespace
225
+ text = re.sub(r"\s+", " ", text)
226
+ return text