agent-learning-kit 0.1.0__py3-none-any.whl

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 (642) hide show
  1. agent_learning_kit-0.1.0.dist-info/METADATA +381 -0
  2. agent_learning_kit-0.1.0.dist-info/RECORD +642 -0
  3. agent_learning_kit-0.1.0.dist-info/WHEEL +4 -0
  4. agent_learning_kit-0.1.0.dist-info/entry_points.txt +5 -0
  5. agent_learning_kit-0.1.0.dist-info/licenses/LICENSE +173 -0
  6. agent_learning_kit-0.1.0.dist-info/licenses/NOTICE +7 -0
  7. fi/__init__.py +5 -0
  8. fi/alk/__init__.py +57 -0
  9. fi/alk/_facade.py +31 -0
  10. fi/alk/_module_alias.py +68 -0
  11. fi/alk/_paths.py +14 -0
  12. fi/alk/_schema.py +522 -0
  13. fi/alk/actions.py +727 -0
  14. fi/alk/bench/__init__.py +517 -0
  15. fi/alk/bench/_codeexec.py +213 -0
  16. fi/alk/bench/_coding.py +215 -0
  17. fi/alk/bench/_docker.py +237 -0
  18. fi/alk/bench/_grader.py +286 -0
  19. fi/alk/bench/_pull.py +212 -0
  20. fi/alk/bench/_voice.py +147 -0
  21. fi/alk/capabilities.py +627 -0
  22. fi/alk/cli.py +6396 -0
  23. fi/alk/config.py +130 -0
  24. fi/alk/cua_loop.py +562 -0
  25. fi/alk/evals.py +2351 -0
  26. fi/alk/extensions.py +163 -0
  27. fi/alk/harness/ARCHITECTURE.md +231 -0
  28. fi/alk/harness/DESIGN.md +246 -0
  29. fi/alk/harness/ENVIRONMENT_CONFORMANCE.md +127 -0
  30. fi/alk/harness/HOW-IT-WORKS.md +297 -0
  31. fi/alk/harness/IMPLEMENTATION_AND_VALIDATION_STATUS.md +229 -0
  32. fi/alk/harness/README.md +417 -0
  33. fi/alk/harness/__init__.py +77 -0
  34. fi/alk/harness/__main__.py +3 -0
  35. fi/alk/harness/amend.py +312 -0
  36. fi/alk/harness/artifacts.py +319 -0
  37. fi/alk/harness/authoring_entrypoint.py +189 -0
  38. fi/alk/harness/authoring_runtime_validation.py +267 -0
  39. fi/alk/harness/backends/README.md +43 -0
  40. fi/alk/harness/backends/__init__.py +122 -0
  41. fi/alk/harness/backends/base.py +241 -0
  42. fi/alk/harness/backends/claude.py +211 -0
  43. fi/alk/harness/backends/files.py +182 -0
  44. fi/alk/harness/backends/vertex_gemini.py +457 -0
  45. fi/alk/harness/background_noise.py +95 -0
  46. fi/alk/harness/build.py +385 -0
  47. fi/alk/harness/bundle.py +593 -0
  48. fi/alk/harness/bundle_author_v2.py +1831 -0
  49. fi/alk/harness/bundle_v2.py +719 -0
  50. fi/alk/harness/call_runner.py +1440 -0
  51. fi/alk/harness/callback_http_adapter.py +111 -0
  52. fi/alk/harness/catalogue.py +287 -0
  53. fi/alk/harness/chat.py +428 -0
  54. fi/alk/harness/chat_call_runner.py +506 -0
  55. fi/alk/harness/checks.py +136 -0
  56. fi/alk/harness/cli.py +1354 -0
  57. fi/alk/harness/config.py +338 -0
  58. fi/alk/harness/contract.py +718 -0
  59. fi/alk/harness/credentials.py +674 -0
  60. fi/alk/harness/data/persona_vocabulary.json +111 -0
  61. fi/alk/harness/environment.py +99 -0
  62. fi/alk/harness/environment_plan.py +168 -0
  63. fi/alk/harness/events.py +125 -0
  64. fi/alk/harness/executor.py +304 -0
  65. fi/alk/harness/folder.py +234 -0
  66. fi/alk/harness/generated_runtime.py +815 -0
  67. fi/alk/harness/github.py +72 -0
  68. fi/alk/harness/hosted_authoring_entrypoint.py +183 -0
  69. fi/alk/harness/hosted_entrypoint.py +2402 -0
  70. fi/alk/harness/hosted_scheduler.py +2218 -0
  71. fi/alk/harness/job.py +426 -0
  72. fi/alk/harness/judge.py +184 -0
  73. fi/alk/harness/livekit_source.py +50 -0
  74. fi/alk/harness/livekit_tool_trace_bootstrap.py +71 -0
  75. fi/alk/harness/observability.py +208 -0
  76. fi/alk/harness/outbound.py +3252 -0
  77. fi/alk/harness/packaging.py +515 -0
  78. fi/alk/harness/persona_guides.py +157 -0
  79. fi/alk/harness/platform.py +692 -0
  80. fi/alk/harness/process_preflight.py +764 -0
  81. fi/alk/harness/process_runtime.py +5670 -0
  82. fi/alk/harness/prove.py +425 -0
  83. fi/alk/harness/provider_import.py +703 -0
  84. fi/alk/harness/provider_lifecycle.py +392 -0
  85. fi/alk/harness/provision.py +2896 -0
  86. fi/alk/harness/reception.py +147 -0
  87. fi/alk/harness/retell_chat_call_runner.py +373 -0
  88. fi/alk/harness/run/__init__.py +296 -0
  89. fi/alk/harness/run/alk.py +184 -0
  90. fi/alk/harness/run/call.py +162 -0
  91. fi/alk/harness/run/conversation.py +264 -0
  92. fi/alk/harness/run/data/voices_by_language_and_gender.json +693 -0
  93. fi/alk/harness/run/evidence.py +195 -0
  94. fi/alk/harness/run/grade.py +598 -0
  95. fi/alk/harness/run/live.py +297 -0
  96. fi/alk/harness/run/models.py +56 -0
  97. fi/alk/harness/run/platform_evals.py +227 -0
  98. fi/alk/harness/run/sdk_voice.py +130 -0
  99. fi/alk/harness/run/simulation.py +1209 -0
  100. fi/alk/harness/run/stage.py +91 -0
  101. fi/alk/harness/run/targets.py +508 -0
  102. fi/alk/harness/run/tools.py +601 -0
  103. fi/alk/harness/run/voice.py +340 -0
  104. fi/alk/harness/runtime.py +172 -0
  105. fi/alk/harness/sandbox_server.py +2011 -0
  106. fi/alk/harness/sandbox_worker.py +44 -0
  107. fi/alk/harness/scenario.py +1048 -0
  108. fi/alk/harness/scenario_source.py +879 -0
  109. fi/alk/harness/scenario_tools.py +1143 -0
  110. fi/alk/harness/scenarios.py +915 -0
  111. fi/alk/harness/secrets.py +168 -0
  112. fi/alk/harness/service_catalog.py +97 -0
  113. fi/alk/harness/session.py +391 -0
  114. fi/alk/harness/sessions.py +372 -0
  115. fi/alk/harness/simulator.py +76 -0
  116. fi/alk/harness/simulator_voice.py +928 -0
  117. fi/alk/harness/skills/build-environment/SKILL.md +538 -0
  118. fi/alk/harness/skills/harness.md +131 -0
  119. fi/alk/harness/skills/kinds/chat.md +48 -0
  120. fi/alk/harness/skills/kinds/voice-voicemail.md +63 -0
  121. fi/alk/harness/skills/kinds/voice.md +59 -0
  122. fi/alk/harness/skills/plan-suite/SKILL.md +103 -0
  123. fi/alk/harness/skills/provision-environment/SKILL.md +136 -0
  124. fi/alk/harness/skills/run-scenarios/SKILL.md +112 -0
  125. fi/alk/harness/skills/understand-agent/SKILL.md +251 -0
  126. fi/alk/harness/skills/write-scenarios/SKILL.md +606 -0
  127. fi/alk/harness/skills/write-scenarios/references/refusals.md +28 -0
  128. fi/alk/harness/skills/write-scenarios/references/world-api.md +92 -0
  129. fi/alk/harness/source_data_invariants.py +444 -0
  130. fi/alk/harness/source_tool_evidence.py +79 -0
  131. fi/alk/harness/sources.py +253 -0
  132. fi/alk/harness/spend.py +140 -0
  133. fi/alk/harness/tool_trace_proxy.py +104 -0
  134. fi/alk/harness/tools.py +1018 -0
  135. fi/alk/harness/understand.py +169 -0
  136. fi/alk/harness/voicemail_audio.py +74 -0
  137. fi/alk/harness/world/__init__.py +33 -0
  138. fi/alk/harness/world/errors.py +68 -0
  139. fi/alk/harness/world/expectations.py +91 -0
  140. fi/alk/harness/world/handle.py +538 -0
  141. fi/alk/harness/world/kinds.py +196 -0
  142. fi/alk/harness/world/mutate.py +186 -0
  143. fi/alk/harness/world/probe.py +413 -0
  144. fi/alk/harness/world/provision.py +511 -0
  145. fi/alk/harness/world/provisioned.py +191 -0
  146. fi/alk/harness/world/runtime.py +616 -0
  147. fi/alk/harness/world/snapshot.py +288 -0
  148. fi/alk/harness/world/stores/__init__.py +305 -0
  149. fi/alk/harness/world/stores/container.py +215 -0
  150. fi/alk/harness/world/stores/inprocess.py +346 -0
  151. fi/alk/harness/world/stores/postgres.py +481 -0
  152. fi/alk/harness/world/stores/prove.py +202 -0
  153. fi/alk/harness/world/stores/sqlite.py +245 -0
  154. fi/alk/harness/world/stores/written.py +182 -0
  155. fi/alk/harness/world/tools.py +1516 -0
  156. fi/alk/harness/world/workspace.py +144 -0
  157. fi/alk/image_loop.py +453 -0
  158. fi/alk/image_perturb.py +241 -0
  159. fi/alk/improve.py +274 -0
  160. fi/alk/live/__init__.py +154 -0
  161. fi/alk/live/_attribution.py +184 -0
  162. fi/alk/live/_capture.py +264 -0
  163. fi/alk/live/_codec.py +391 -0
  164. fi/alk/live/_contract.py +134 -0
  165. fi/alk/live/_loopback.py +316 -0
  166. fi/alk/live/_perturb.py +449 -0
  167. fi/alk/live/_runner.py +386 -0
  168. fi/alk/live/_stats.py +561 -0
  169. fi/alk/live/_transcript.py +240 -0
  170. fi/alk/live/_workers/__init__.py +9 -0
  171. fi/alk/live/_workers/a2a_worker.py +316 -0
  172. fi/alk/live/_workers/langgraph_worker.py +217 -0
  173. fi/alk/live/_workers/livekit_worker.py +207 -0
  174. fi/alk/live/_workers/mcp_loopback_server.py +46 -0
  175. fi/alk/live/_workers/mcp_worker.py +158 -0
  176. fi/alk/live/_workers/pipecat_worker.py +189 -0
  177. fi/alk/live/a2a_lane.py +138 -0
  178. fi/alk/live/langgraph_lane.py +339 -0
  179. fi/alk/live/livekit_lane.py +376 -0
  180. fi/alk/live/mcp_lane.py +172 -0
  181. fi/alk/live/pipecat_lane.py +341 -0
  182. fi/alk/live/voice_redteam.py +494 -0
  183. fi/alk/loss.py +306 -0
  184. fi/alk/optimize.py +36260 -0
  185. fi/alk/practice/__init__.py +51 -0
  186. fi/alk/practice/_assess.py +103 -0
  187. fi/alk/practice/_budget.py +81 -0
  188. fi/alk/practice/_calibrate.py +69 -0
  189. fi/alk/practice/_capstone.py +86 -0
  190. fi/alk/practice/_contract.py +91 -0
  191. fi/alk/practice/_diagnose.py +79 -0
  192. fi/alk/practice/_drill.py +196 -0
  193. fi/alk/practice/_experiment.py +720 -0
  194. fi/alk/practice/_schedule.py +102 -0
  195. fi/alk/practice/_store.py +194 -0
  196. fi/alk/practice/_trainer.py +245 -0
  197. fi/alk/practice/_update.py +125 -0
  198. fi/alk/redteam.py +2621 -0
  199. fi/alk/rewardhack.py +237 -0
  200. fi/alk/simulate.py +10351 -0
  201. fi/alk/studio/__init__.py +82 -0
  202. fi/alk/studio/_bias.py +314 -0
  203. fi/alk/studio/_calibration.py +522 -0
  204. fi/alk/studio/_coverage.py +262 -0
  205. fi/alk/studio/_download.py +665 -0
  206. fi/alk/studio/_fidelity_attack.py +114 -0
  207. fi/alk/studio/_generate.py +652 -0
  208. fi/alk/studio/_library.py +370 -0
  209. fi/alk/studio/_scan.py +134 -0
  210. fi/alk/studio/_upgrade.py +42 -0
  211. fi/alk/studio/_vendor.py +172 -0
  212. fi/alk/suite.py +4200 -0
  213. fi/alk/tasks.py +828 -0
  214. fi/alk/telemetry/__init__.py +149 -0
  215. fi/alk/telemetry/_contract.py +141 -0
  216. fi/alk/telemetry/_emit.py +182 -0
  217. fi/alk/telemetry/_ledger.py +296 -0
  218. fi/alk/telemetry/_queue.py +127 -0
  219. fi/alk/telemetry/_row.py +294 -0
  220. fi/alk/telemetry/_run.py +233 -0
  221. fi/alk/telemetry/_sync.py +193 -0
  222. fi/alk/telemetry/_url.py +119 -0
  223. fi/alk/trinity.py +49397 -0
  224. fi/alk/voice_loop.py +174 -0
  225. fi/api/__init__.py +1 -0
  226. fi/api/auth.py +137 -0
  227. fi/api/types.py +29 -0
  228. fi/cli/__init__.py +9 -0
  229. fi/cli/assertions/__init__.py +25 -0
  230. fi/cli/assertions/conditions.py +76 -0
  231. fi/cli/assertions/evaluator.py +286 -0
  232. fi/cli/assertions/exit_codes.py +20 -0
  233. fi/cli/assertions/parser.py +131 -0
  234. fi/cli/assertions/reporter.py +194 -0
  235. fi/cli/commands/__init__.py +9 -0
  236. fi/cli/commands/config.py +165 -0
  237. fi/cli/commands/export.py +208 -0
  238. fi/cli/commands/init.py +112 -0
  239. fi/cli/commands/list_cmd.py +213 -0
  240. fi/cli/commands/run.py +486 -0
  241. fi/cli/commands/validate.py +173 -0
  242. fi/cli/commands/view.py +424 -0
  243. fi/cli/config/__init__.py +6 -0
  244. fi/cli/config/defaults.py +206 -0
  245. fi/cli/config/loader.py +155 -0
  246. fi/cli/config/schema.py +174 -0
  247. fi/cli/main.py +78 -0
  248. fi/cli/output/__init__.py +6 -0
  249. fi/cli/output/formatters.py +106 -0
  250. fi/cli/output/reporters.py +46 -0
  251. fi/cli/storage/__init__.py +5 -0
  252. fi/cli/storage/run_history.py +249 -0
  253. fi/cli/utils/__init__.py +5 -0
  254. fi/cli/utils/console.py +44 -0
  255. fi/evals/__init__.py +131 -0
  256. fi/evals/autoeval/__init__.py +137 -0
  257. fi/evals/autoeval/analyzer.py +211 -0
  258. fi/evals/autoeval/config.py +244 -0
  259. fi/evals/autoeval/export.py +213 -0
  260. fi/evals/autoeval/interactive.py +283 -0
  261. fi/evals/autoeval/pipeline.py +625 -0
  262. fi/evals/autoeval/prompts.py +139 -0
  263. fi/evals/autoeval/recommender.py +242 -0
  264. fi/evals/autoeval/rules.py +589 -0
  265. fi/evals/autoeval/templates.py +299 -0
  266. fi/evals/autoeval/types.py +232 -0
  267. fi/evals/core/__init__.py +16 -0
  268. fi/evals/core/cloud_registry.py +184 -0
  269. fi/evals/core/engines.py +368 -0
  270. fi/evals/core/evaluate.py +319 -0
  271. fi/evals/core/judge_prompt.py +90 -0
  272. fi/evals/core/prompt_generator.py +83 -0
  273. fi/evals/core/registry.py +57 -0
  274. fi/evals/core/result.py +55 -0
  275. fi/evals/evaluator.py +721 -0
  276. fi/evals/execution.py +168 -0
  277. fi/evals/feedback/__init__.py +32 -0
  278. fi/evals/feedback/calibrator.py +160 -0
  279. fi/evals/feedback/collector.py +214 -0
  280. fi/evals/feedback/hooks.py +81 -0
  281. fi/evals/feedback/retriever.py +128 -0
  282. fi/evals/feedback/store.py +272 -0
  283. fi/evals/feedback/types.py +99 -0
  284. fi/evals/framework/README.md +79 -0
  285. fi/evals/framework/__init__.py +267 -0
  286. fi/evals/framework/backends/Dockerfile.eval-runner +33 -0
  287. fi/evals/framework/backends/__init__.py +99 -0
  288. fi/evals/framework/backends/_container.py +141 -0
  289. fi/evals/framework/backends/_utils.py +145 -0
  290. fi/evals/framework/backends/base.py +223 -0
  291. fi/evals/framework/backends/celery_backend.py +417 -0
  292. fi/evals/framework/backends/celery_worker.py +78 -0
  293. fi/evals/framework/backends/kubernetes_backend.py +665 -0
  294. fi/evals/framework/backends/ray_backend.py +521 -0
  295. fi/evals/framework/backends/temporal.py +350 -0
  296. fi/evals/framework/backends/temporal_worker.py +126 -0
  297. fi/evals/framework/backends/thread_pool.py +286 -0
  298. fi/evals/framework/context.py +258 -0
  299. fi/evals/framework/enrichment.py +306 -0
  300. fi/evals/framework/evals/__init__.py +68 -0
  301. fi/evals/framework/evals/agentic.py +399 -0
  302. fi/evals/framework/evals/builder.py +609 -0
  303. fi/evals/framework/evals/semantic.py +142 -0
  304. fi/evals/framework/evaluator.py +647 -0
  305. fi/evals/framework/evaluators/__init__.py +22 -0
  306. fi/evals/framework/evaluators/blocking.py +347 -0
  307. fi/evals/framework/evaluators/non_blocking.py +577 -0
  308. fi/evals/framework/propagation.py +421 -0
  309. fi/evals/framework/protocols.py +385 -0
  310. fi/evals/framework/registry.py +370 -0
  311. fi/evals/framework/resilience/__init__.py +150 -0
  312. fi/evals/framework/resilience/circuit_breaker.py +309 -0
  313. fi/evals/framework/resilience/degradation.py +355 -0
  314. fi/evals/framework/resilience/health.py +505 -0
  315. fi/evals/framework/resilience/rate_limiter.py +228 -0
  316. fi/evals/framework/resilience/retry.py +274 -0
  317. fi/evals/framework/resilience/types.py +288 -0
  318. fi/evals/framework/resilience/wrapper.py +433 -0
  319. fi/evals/framework/types.py +218 -0
  320. fi/evals/guardrails/README.md +915 -0
  321. fi/evals/guardrails/__init__.py +96 -0
  322. fi/evals/guardrails/backends/__init__.py +43 -0
  323. fi/evals/guardrails/backends/azure.py +361 -0
  324. fi/evals/guardrails/backends/base.py +88 -0
  325. fi/evals/guardrails/backends/generic_llm.py +163 -0
  326. fi/evals/guardrails/backends/granite.py +216 -0
  327. fi/evals/guardrails/backends/llamaguard.py +221 -0
  328. fi/evals/guardrails/backends/local_base.py +479 -0
  329. fi/evals/guardrails/backends/openai.py +365 -0
  330. fi/evals/guardrails/backends/qwen.py +170 -0
  331. fi/evals/guardrails/backends/shieldgemma.py +154 -0
  332. fi/evals/guardrails/backends/turing.py +235 -0
  333. fi/evals/guardrails/backends/vllm_client.py +321 -0
  334. fi/evals/guardrails/backends/wildguard.py +188 -0
  335. fi/evals/guardrails/base.py +888 -0
  336. fi/evals/guardrails/config.py +221 -0
  337. fi/evals/guardrails/discovery.py +243 -0
  338. fi/evals/guardrails/gateway.py +437 -0
  339. fi/evals/guardrails/registry.py +231 -0
  340. fi/evals/guardrails/scanners/__init__.py +127 -0
  341. fi/evals/guardrails/scanners/base.py +191 -0
  342. fi/evals/guardrails/scanners/code_injection.py +243 -0
  343. fi/evals/guardrails/scanners/eval_delegate.py +574 -0
  344. fi/evals/guardrails/scanners/invisible_chars.py +351 -0
  345. fi/evals/guardrails/scanners/jailbreak.py +412 -0
  346. fi/evals/guardrails/scanners/language.py +288 -0
  347. fi/evals/guardrails/scanners/pipeline.py +260 -0
  348. fi/evals/guardrails/scanners/regex.py +311 -0
  349. fi/evals/guardrails/scanners/secrets.py +274 -0
  350. fi/evals/guardrails/scanners/topics.py +649 -0
  351. fi/evals/guardrails/scanners/urls.py +341 -0
  352. fi/evals/guardrails/types.py +96 -0
  353. fi/evals/llm/__init__.py +3 -0
  354. fi/evals/llm/base_llm_provider.py +35 -0
  355. fi/evals/llm/providers/litellm.py +70 -0
  356. fi/evals/local/__init__.py +90 -0
  357. fi/evals/local/evaluator.py +690 -0
  358. fi/evals/local/execution_mode.py +121 -0
  359. fi/evals/local/llm.py +489 -0
  360. fi/evals/local/metrics/__init__.py +19 -0
  361. fi/evals/local/registry.py +360 -0
  362. fi/evals/manager.py +1018 -0
  363. fi/evals/manager_types.py +362 -0
  364. fi/evals/metrics/__init__.py +185 -0
  365. fi/evals/metrics/agents/__init__.py +74 -0
  366. fi/evals/metrics/agents/metrics.py +693 -0
  367. fi/evals/metrics/agents/report.py +36463 -0
  368. fi/evals/metrics/agents/types.py +160 -0
  369. fi/evals/metrics/base_llm_metric.py +111 -0
  370. fi/evals/metrics/base_metric.py +138 -0
  371. fi/evals/metrics/code_security/__init__.py +305 -0
  372. fi/evals/metrics/code_security/analyzer.py +985 -0
  373. fi/evals/metrics/code_security/benchmarks/__init__.py +73 -0
  374. fi/evals/metrics/code_security/benchmarks/builtin.py +750 -0
  375. fi/evals/metrics/code_security/benchmarks/loader.py +580 -0
  376. fi/evals/metrics/code_security/benchmarks/types.py +308 -0
  377. fi/evals/metrics/code_security/detectors/__init__.py +186 -0
  378. fi/evals/metrics/code_security/detectors/base.py +394 -0
  379. fi/evals/metrics/code_security/detectors/cryptography.py +345 -0
  380. fi/evals/metrics/code_security/detectors/injection.py +744 -0
  381. fi/evals/metrics/code_security/detectors/secrets.py +287 -0
  382. fi/evals/metrics/code_security/detectors/serialization.py +192 -0
  383. fi/evals/metrics/code_security/joint_metrics.py +588 -0
  384. fi/evals/metrics/code_security/judges/__init__.py +83 -0
  385. fi/evals/metrics/code_security/judges/base.py +238 -0
  386. fi/evals/metrics/code_security/judges/dual_judge.py +534 -0
  387. fi/evals/metrics/code_security/judges/llm_judge.py +301 -0
  388. fi/evals/metrics/code_security/judges/pattern_judge.py +515 -0
  389. fi/evals/metrics/code_security/metrics.py +388 -0
  390. fi/evals/metrics/code_security/modes/__init__.py +63 -0
  391. fi/evals/metrics/code_security/modes/adversarial.py +284 -0
  392. fi/evals/metrics/code_security/modes/autocomplete.py +198 -0
  393. fi/evals/metrics/code_security/modes/base.py +283 -0
  394. fi/evals/metrics/code_security/modes/instruct.py +253 -0
  395. fi/evals/metrics/code_security/modes/repair.py +230 -0
  396. fi/evals/metrics/code_security/reports/__init__.py +57 -0
  397. fi/evals/metrics/code_security/reports/generator.py +404 -0
  398. fi/evals/metrics/code_security/reports/leaderboard.py +509 -0
  399. fi/evals/metrics/code_security/types.py +534 -0
  400. fi/evals/metrics/function_calling/__init__.py +34 -0
  401. fi/evals/metrics/function_calling/metrics.py +573 -0
  402. fi/evals/metrics/function_calling/types.py +87 -0
  403. fi/evals/metrics/hallucination/__init__.py +54 -0
  404. fi/evals/metrics/hallucination/detector.py +149 -0
  405. fi/evals/metrics/hallucination/metrics.py +390 -0
  406. fi/evals/metrics/hallucination/nli.py +253 -0
  407. fi/evals/metrics/hallucination/sentinel.py +106 -0
  408. fi/evals/metrics/hallucination/types.py +132 -0
  409. fi/evals/metrics/heuristics/aggregation_metrics.py +85 -0
  410. fi/evals/metrics/heuristics/json_metrics.py +87 -0
  411. fi/evals/metrics/heuristics/similarity_metrics.py +375 -0
  412. fi/evals/metrics/heuristics/string_metrics.py +391 -0
  413. fi/evals/metrics/llm_as_judges/__init__.py +17 -0
  414. fi/evals/metrics/llm_as_judges/custom_judge/metric.py +112 -0
  415. fi/evals/metrics/llm_as_judges/custom_judge/prompts.py +26 -0
  416. fi/evals/metrics/llm_as_judges/types.py +48 -0
  417. fi/evals/metrics/rag/__init__.py +111 -0
  418. fi/evals/metrics/rag/advanced/__init__.py +14 -0
  419. fi/evals/metrics/rag/advanced/multi_hop.py +283 -0
  420. fi/evals/metrics/rag/advanced/source_attribution.py +344 -0
  421. fi/evals/metrics/rag/generation/__init__.py +17 -0
  422. fi/evals/metrics/rag/generation/answer_relevancy.py +176 -0
  423. fi/evals/metrics/rag/generation/context_utilization.py +245 -0
  424. fi/evals/metrics/rag/generation/faithfulness.py +241 -0
  425. fi/evals/metrics/rag/generation/groundedness.py +131 -0
  426. fi/evals/metrics/rag/rag_score.py +277 -0
  427. fi/evals/metrics/rag/retrieval/__init__.py +20 -0
  428. fi/evals/metrics/rag/retrieval/context_entity_recall.py +124 -0
  429. fi/evals/metrics/rag/retrieval/context_precision.py +158 -0
  430. fi/evals/metrics/rag/retrieval/context_recall.py +106 -0
  431. fi/evals/metrics/rag/retrieval/noise_sensitivity.py +163 -0
  432. fi/evals/metrics/rag/retrieval/ranking.py +261 -0
  433. fi/evals/metrics/rag/types.py +100 -0
  434. fi/evals/metrics/rag/utils/__init__.py +62 -0
  435. fi/evals/metrics/rag/utils/claims.py +189 -0
  436. fi/evals/metrics/rag/utils/entities.py +244 -0
  437. fi/evals/metrics/rag/utils/nli.py +92 -0
  438. fi/evals/metrics/rag/utils/similarity.py +345 -0
  439. fi/evals/metrics/structured/__init__.py +114 -0
  440. fi/evals/metrics/structured/field_completeness.py +313 -0
  441. fi/evals/metrics/structured/hierarchy_score.py +366 -0
  442. fi/evals/metrics/structured/json_validation.py +190 -0
  443. fi/evals/metrics/structured/schema_compliance.py +280 -0
  444. fi/evals/metrics/structured/structured_output_score.py +298 -0
  445. fi/evals/metrics/structured/types.py +108 -0
  446. fi/evals/metrics/structured/validators/__init__.py +30 -0
  447. fi/evals/metrics/structured/validators/base.py +189 -0
  448. fi/evals/metrics/structured/validators/json_validator.py +196 -0
  449. fi/evals/metrics/structured/validators/pydantic_validator.py +178 -0
  450. fi/evals/metrics/structured/validators/yaml_validator.py +248 -0
  451. fi/evals/otel/__init__.py +266 -0
  452. fi/evals/otel/config.py +400 -0
  453. fi/evals/otel/conventions.py +463 -0
  454. fi/evals/otel/enrichment.py +371 -0
  455. fi/evals/otel/instrumentors/__init__.py +140 -0
  456. fi/evals/otel/instrumentors/anthropic.py +517 -0
  457. fi/evals/otel/instrumentors/base.py +382 -0
  458. fi/evals/otel/instrumentors/openai.py +673 -0
  459. fi/evals/otel/processors/__init__.py +36 -0
  460. fi/evals/otel/processors/base.py +473 -0
  461. fi/evals/otel/processors/cost.py +445 -0
  462. fi/evals/otel/processors/evaluation.py +559 -0
  463. fi/evals/otel/processors/llm.py +462 -0
  464. fi/evals/otel/tracer.py +506 -0
  465. fi/evals/otel/types.py +232 -0
  466. fi/evals/otel_utils.py +23 -0
  467. fi/evals/protect.py +671 -0
  468. fi/evals/protect_input_adapter.py +154 -0
  469. fi/evals/streaming/__init__.py +88 -0
  470. fi/evals/streaming/buffer.py +213 -0
  471. fi/evals/streaming/evaluator.py +551 -0
  472. fi/evals/streaming/policy.py +307 -0
  473. fi/evals/streaming/scorers.py +368 -0
  474. fi/evals/streaming/types.py +238 -0
  475. fi/evals/templates.py +472 -0
  476. fi/evals/types.py +156 -0
  477. fi/opt/__init__.py +221 -0
  478. fi/opt/_objective_scoring.py +85 -0
  479. fi/opt/base/__init__.py +11 -0
  480. fi/opt/base/base_generator.py +33 -0
  481. fi/opt/base/base_mapper.py +26 -0
  482. fi/opt/base/base_optimizer.py +45 -0
  483. fi/opt/base/evaluator.py +211 -0
  484. fi/opt/components.py +3095 -0
  485. fi/opt/datamappers/__init__.py +3 -0
  486. fi/opt/datamappers/basic_mapper.py +40 -0
  487. fi/opt/deployment.py +1021 -0
  488. fi/opt/evidence.py +4332 -0
  489. fi/opt/generators/__init__.py +3 -0
  490. fi/opt/generators/litellm.py +66 -0
  491. fi/opt/integrations/__init__.py +23 -0
  492. fi/opt/integrations/generative_suite.py +410 -0
  493. fi/opt/integrations/simulate.py +1313 -0
  494. fi/opt/mutations.py +771 -0
  495. fi/opt/observability.py +4639 -0
  496. fi/opt/optimizer_trace.py +889 -0
  497. fi/opt/optimizers/__init__.py +80 -0
  498. fi/opt/optimizers/agent.py +331 -0
  499. fi/opt/optimizers/agent_bandit.py +392 -0
  500. fi/opt/optimizers/agent_curriculum.py +635 -0
  501. fi/opt/optimizers/agent_evolution.py +894 -0
  502. fi/opt/optimizers/agent_feedback.py +1863 -0
  503. fi/opt/optimizers/agent_pareto.py +547 -0
  504. fi/opt/optimizers/agent_social_memory.py +1113 -0
  505. fi/opt/optimizers/agent_tpe.py +321 -0
  506. fi/opt/optimizers/bayesian_search.py +449 -0
  507. fi/opt/optimizers/council.py +2075 -0
  508. fi/opt/optimizers/futureagi_replay.py +799 -0
  509. fi/opt/optimizers/gepa.py +322 -0
  510. fi/opt/optimizers/metaprompt.py +243 -0
  511. fi/opt/optimizers/promptwizard.py +417 -0
  512. fi/opt/optimizers/protegi.py +329 -0
  513. fi/opt/optimizers/random_search.py +224 -0
  514. fi/opt/research.py +518 -0
  515. fi/opt/simulation.py +260 -0
  516. fi/opt/targets.py +232 -0
  517. fi/opt/types.py +66 -0
  518. fi/opt/utils/__init__.py +4 -0
  519. fi/opt/utils/early_stopping.py +266 -0
  520. fi/opt/utils/setup_logging.py +82 -0
  521. fi/simulate/__init__.py +540 -0
  522. fi/simulate/_hashing.py +35 -0
  523. fi/simulate/_logging.py +10 -0
  524. fi/simulate/adapters.py +87 -0
  525. fi/simulate/agent/__init__.py +120 -0
  526. fi/simulate/agent/browser.py +658 -0
  527. fi/simulate/agent/definition.py +587 -0
  528. fi/simulate/agent/frameworks.py +3528 -0
  529. fi/simulate/agent/generic.py +8286 -0
  530. fi/simulate/agent/import_probe.py +227 -0
  531. fi/simulate/agent/memory.py +905 -0
  532. fi/simulate/agent/mocks.py +101 -0
  533. fi/simulate/agent/multi_agent.py +361 -0
  534. fi/simulate/agent/orchestration.py +903 -0
  535. fi/simulate/agent/realtime.py +665 -0
  536. fi/simulate/agent/wrapper.py +99 -0
  537. fi/simulate/agent/wrappers/__init__.py +18 -0
  538. fi/simulate/agent/wrappers/anthropic.py +62 -0
  539. fi/simulate/agent/wrappers/gemini.py +65 -0
  540. fi/simulate/agent/wrappers/http.py +404 -0
  541. fi/simulate/agent/wrappers/langchain.py +80 -0
  542. fi/simulate/agent/wrappers/openai.py +75 -0
  543. fi/simulate/agent/wrappers/websocket.py +326 -0
  544. fi/simulate/artifacts/__init__.py +11 -0
  545. fi/simulate/artifacts/manifest.py +62 -0
  546. fi/simulate/cli.py +20560 -0
  547. fi/simulate/endpoints/__init__.py +45 -0
  548. fi/simulate/endpoints/_http_actor.py +73 -0
  549. fi/simulate/endpoints/actor_sources.py +243 -0
  550. fi/simulate/endpoints/base.py +107 -0
  551. fi/simulate/endpoints/builtins.py +10 -0
  552. fi/simulate/endpoints/callable.py +95 -0
  553. fi/simulate/endpoints/http.py +76 -0
  554. fi/simulate/endpoints/livekit.py +138 -0
  555. fi/simulate/endpoints/originators.py +132 -0
  556. fi/simulate/endpoints/profiles.py +348 -0
  557. fi/simulate/endpoints/retell.py +633 -0
  558. fi/simulate/endpoints/vapi.py +205 -0
  559. fi/simulate/endpoints/websocket.py +76 -0
  560. fi/simulate/environment.py +33026 -0
  561. fi/simulate/environments/__init__.py +11 -0
  562. fi/simulate/environments/base.py +73 -0
  563. fi/simulate/environments/chat.py +697 -0
  564. fi/simulate/environments/voice.py +212 -0
  565. fi/simulate/evaluation/__init__.py +4 -0
  566. fi/simulate/evaluation/ai_eval.py +227 -0
  567. fi/simulate/evidence/__init__.py +35 -0
  568. fi/simulate/evidence/base.py +59 -0
  569. fi/simulate/evidence/caller_observed.py +50 -0
  570. fi/simulate/evidence/livekit_instrumentation.py +51 -0
  571. fi/simulate/evidence/livekit_room.py +50 -0
  572. fi/simulate/evidence/otel.py +49 -0
  573. fi/simulate/evidence/providers/__init__.py +24 -0
  574. fi/simulate/evidence/providers/base.py +61 -0
  575. fi/simulate/evidence/providers/retell.py +376 -0
  576. fi/simulate/evidence/providers/vapi.py +426 -0
  577. fi/simulate/hosted/__init__.py +32 -0
  578. fi/simulate/hosted/child_entrypoint.py +306 -0
  579. fi/simulate/hosted/job.py +150 -0
  580. fi/simulate/hosted/targets.py +53 -0
  581. fi/simulate/instrumentation/__init__.py +5 -0
  582. fi/simulate/instrumentation/livekit/__init__.py +122 -0
  583. fi/simulate/manifest.py +1033 -0
  584. fi/simulate/matrix_cli.py +165 -0
  585. fi/simulate/realtime/__init__.py +40 -0
  586. fi/simulate/realtime/events.py +107 -0
  587. fi/simulate/realtime/media.py +61 -0
  588. fi/simulate/realtime/session.py +91 -0
  589. fi/simulate/recording/__init__.py +5 -0
  590. fi/simulate/recording/room_recorder.py +326 -0
  591. fi/simulate/registry.py +185 -0
  592. fi/simulate/results/__init__.py +9 -0
  593. fi/simulate/results/base.py +18 -0
  594. fi/simulate/results/filesystem.py +71 -0
  595. fi/simulate/results/futureagi.py +1340 -0
  596. fi/simulate/runtime/__init__.py +85 -0
  597. fi/simulate/runtime/capabilities.py +40 -0
  598. fi/simulate/runtime/events.py +63 -0
  599. fi/simulate/runtime/failures.py +25 -0
  600. fi/simulate/runtime/ids.py +34 -0
  601. fi/simulate/runtime/plan.py +70 -0
  602. fi/simulate/runtime/planner.py +102 -0
  603. fi/simulate/runtime/report.py +174 -0
  604. fi/simulate/runtime/run.py +75 -0
  605. fi/simulate/runtime/runner.py +333 -0
  606. fi/simulate/runtime/spec.py +186 -0
  607. fi/simulate/simulation/__init__.py +30 -0
  608. fi/simulate/simulation/behavior_policy.py +425 -0
  609. fi/simulate/simulation/bridge/__init__.py +9 -0
  610. fi/simulate/simulation/bridge/audio.py +29 -0
  611. fi/simulate/simulation/bridge/connector.py +46 -0
  612. fi/simulate/simulation/bridge/livekit.py +252 -0
  613. fi/simulate/simulation/bridge/retell.py +188 -0
  614. fi/simulate/simulation/bridge/vapi.py +177 -0
  615. fi/simulate/simulation/contract.py +419 -0
  616. fi/simulate/simulation/engines/__init__.py +12 -0
  617. fi/simulate/simulation/engines/base.py +21 -0
  618. fi/simulate/simulation/engines/cloud.py +517 -0
  619. fi/simulate/simulation/engines/livekit.py +4167 -0
  620. fi/simulate/simulation/engines/local_text.py +89 -0
  621. fi/simulate/simulation/fidelity.py +374 -0
  622. fi/simulate/simulation/gemini_tts_stream.py +110 -0
  623. fi/simulate/simulation/generator.py +91 -0
  624. fi/simulate/simulation/goal_machine.py +185 -0
  625. fi/simulate/simulation/livekit_models.py +467 -0
  626. fi/simulate/simulation/matrix.py +170 -0
  627. fi/simulate/simulation/models.py +279 -0
  628. fi/simulate/simulation/runner.py +153 -0
  629. fi/simulate/simulation/synthetic.py +880 -0
  630. fi/simulate/simulation/voice_prompt.py +502 -0
  631. fi/simulate/simulator/__init__.py +55 -0
  632. fi/simulate/simulator/builtins.py +53 -0
  633. fi/simulate/suite.py +1288 -0
  634. fi/simulate/utils/routes.py +164 -0
  635. fi/simulate/voice.py +225 -0
  636. fi/simulate/voice_cli.py +182 -0
  637. fi/utils/__init__.py +1 -0
  638. fi/utils/constants.py +14 -0
  639. fi/utils/errors.py +200 -0
  640. fi/utils/executor.py +26 -0
  641. fi/utils/routes.py +119 -0
  642. fi/utils/utils.py +17 -0
@@ -0,0 +1,329 @@
1
+ import json
2
+ import logging
3
+ import random
4
+ import re
5
+ from typing import Any, Dict, List, Optional, Set
6
+
7
+ from pydantic import BaseModel, Field, ValidationError
8
+
9
+ from ..base.base_optimizer import BaseOptimizer
10
+ from ..datamappers.basic_mapper import BasicDataMapper
11
+ from ..base.evaluator import Evaluator
12
+ from ..generators.litellm import LiteLLMGenerator
13
+ from ..types import IterationHistory, OptimizationResult
14
+ from ..utils.early_stopping import EarlyStoppingConfig, EarlyStoppingChecker
15
+
16
+ GET_GRADIENTS_PROMPT = """
17
+ You are an expert in prompt engineering. I'm trying to write a zero-shot classifier prompt.
18
+ My current prompt is:
19
+ ---
20
+ {prompt}
21
+ ---
22
+ This prompt performed poorly on the following examples:
23
+ ---
24
+ {error_examples}
25
+ ---
26
+ Provide {num_feedbacks} distinct reasons why the prompt could have failed. Each reason should be a concise critique.
27
+ Return ONLY a valid JSON object with a single key "variations" containing a list of strings (the critiques).
28
+ """
29
+
30
+ APPLY_GRADIENT_PROMPT = """
31
+ You are an expert in prompt engineering. I'm trying to improve a zero-shot classifier prompt.
32
+ My current prompt is:
33
+ ---
34
+ {prompt}
35
+ ---
36
+ It performed poorly on these examples:
37
+ ---
38
+ {error_examples}
39
+ ---
40
+ A key reason for the failure is the following critique: "{feedback}"
41
+ Based on this critique, generate {num_new_prompts} different, improved versions of the prompt.
42
+ Return ONLY a valid JSON object with a single key "variations" containing a list of strings (the new prompts).
43
+ """
44
+
45
+ PARAPHRASE_PROMPT = """
46
+ Generate {num_variations} semantic paraphrases of the following prompt. The meaning should be identical, but the wording should be different.
47
+ ---
48
+ {prompt}
49
+ ---
50
+ Return ONLY a valid JSON object with a single key "variations" containing a list of strings (the paraphrased prompts).
51
+ """
52
+
53
+
54
+ class GradientVariations(BaseModel):
55
+ variations: List[str] = Field(description="A list of generated text strings.")
56
+
57
+
58
+ class ProTeGi(BaseOptimizer):
59
+ """
60
+ A corrected and robust implementation of the ProTeGi optimizer.
61
+ """
62
+
63
+ def __init__(
64
+ self,
65
+ teacher_generator: LiteLLMGenerator,
66
+ num_gradients: int = 4,
67
+ errors_per_gradient: int = 4,
68
+ prompts_per_gradient: int = 1,
69
+ beam_size: int = 4,
70
+ task_model: Optional[str] = None,
71
+ ):
72
+ self.teacher = teacher_generator
73
+ self.task_model = task_model
74
+ self.num_gradients = num_gradients
75
+ self.errors_per_gradient = errors_per_gradient
76
+ self.prompts_per_gradient = prompts_per_gradient
77
+ self.beam_size = beam_size
78
+ logging.info("--- ProTeGi Optimizer Initialized ---")
79
+
80
+ def optimize(
81
+ self,
82
+ evaluator: Evaluator,
83
+ data_mapper: BasicDataMapper,
84
+ dataset: List[Dict[str, Any]],
85
+ initial_prompts: List[str],
86
+ early_stopping: Optional[EarlyStoppingConfig] = None,
87
+ **kwargs: Any,
88
+ ) -> OptimizationResult:
89
+ num_rounds = kwargs.get("num_rounds", 3)
90
+ eval_subset_size = kwargs.get("eval_subset_size", 32)
91
+
92
+ # Initialize early stopping checker
93
+ checker = None
94
+ if early_stopping and early_stopping.is_enabled():
95
+ checker = EarlyStoppingChecker(early_stopping)
96
+ logging.info(f"Early stopping enabled: {early_stopping}")
97
+
98
+ beam = set(initial_prompts)
99
+ best_overall_score = -1.0
100
+ best_overall_prompt = initial_prompts[0] if initial_prompts else ""
101
+ history: List[IterationHistory] = []
102
+
103
+ for round_num in range(num_rounds):
104
+ logging.info(
105
+ f"\n--- Starting Optimization Round {round_num + 1}/{num_rounds} ---"
106
+ )
107
+
108
+ # 1. EXPANSION: Generate new candidates from the current beam
109
+ current_prompts = list(beam)
110
+ logging.info(
111
+ f"Expanding {len(current_prompts)} prompts into new candidates..."
112
+ )
113
+ expanded_prompts = self._expand_candidates(
114
+ current_prompts, evaluator, data_mapper, dataset
115
+ )
116
+
117
+ # The candidate pool for this round is the union of the old beam and new prompts
118
+ candidate_pool = beam.union(expanded_prompts)
119
+ logging.info(
120
+ f"Candidate pool for this round has {len(candidate_pool)} unique prompts."
121
+ )
122
+
123
+ # 2. SELECTION: Score all candidates in the pool
124
+ eval_subset = random.sample(dataset, min(len(dataset), eval_subset_size))
125
+ iteration_history = self._score_candidates(
126
+ list(candidate_pool), evaluator, data_mapper, eval_subset
127
+ )
128
+ history.extend(iteration_history)
129
+
130
+ # 3. BEAM UPDATE: Select the top N prompts for the next round
131
+ sorted_history = sorted(
132
+ iteration_history, key=lambda x: x.average_score, reverse=True
133
+ )
134
+ if not sorted_history:
135
+ logging.warning("No successful evaluations in this round. Halting.")
136
+ break
137
+
138
+ beam = {item.prompt for item in sorted_history[: self.beam_size]}
139
+ best_round_score = sorted_history[0].average_score
140
+ best_round_prompt = sorted_history[0].prompt
141
+
142
+ logging.info(f"Best score in round {round_num + 1}: {best_round_score:.4f}")
143
+ logging.info(f"New beam selected with {len(beam)} prompts.")
144
+
145
+ if best_round_score > best_overall_score:
146
+ best_overall_score = best_round_score
147
+ best_overall_prompt = best_round_prompt
148
+
149
+ # Check early stopping
150
+ if checker:
151
+ num_evals = len(candidate_pool) * len(eval_subset)
152
+ if checker.should_stop(best_round_score, num_evals):
153
+ logging.info(
154
+ f"Early stopping triggered: {checker.get_state()['stop_reason']}"
155
+ )
156
+ break
157
+
158
+ final_best_generator = LiteLLMGenerator(
159
+ self.teacher.model_name, best_overall_prompt
160
+ )
161
+
162
+ # Build result with early stopping metadata
163
+ return OptimizationResult(
164
+ best_generator=final_best_generator,
165
+ history=history,
166
+ final_score=best_overall_score,
167
+ early_stopped=checker.get_state()["stopped"] if checker else False,
168
+ stop_reason=checker.get_state()["stop_reason"] if checker else None,
169
+ total_iterations=len(history),
170
+ total_evaluations=(
171
+ checker.get_state()["total_evaluations"]
172
+ if checker
173
+ else sum(len(h.individual_results) for h in history)
174
+ ),
175
+ )
176
+
177
+ def _expand_candidates(
178
+ self,
179
+ prompts: List[str],
180
+ evaluator: Evaluator,
181
+ data_mapper: BasicDataMapper,
182
+ dataset: List[Dict[str, Any]],
183
+ ) -> Set[str]:
184
+ new_prompts = set()
185
+ for i, prompt in enumerate(prompts):
186
+ logging.debug(f"--> Expanding prompt {i + 1}/{len(prompts)}...")
187
+ errors = self._get_errors(prompt, evaluator, data_mapper, dataset)
188
+ if not errors:
189
+ logging.debug("Prompt produced no errors. No expansion.")
190
+ continue
191
+
192
+ critiques = self._get_gradients(prompt, errors)
193
+ logging.debug(f"Generated {len(critiques)} critiques (gradients).")
194
+
195
+ for feedback in critiques:
196
+ generated = self._apply_gradient(prompt, errors, feedback)
197
+ if generated:
198
+ logging.debug(
199
+ f"Generated {len(generated)} new prompts from critique: '{feedback[:50]}...'"
200
+ )
201
+ new_prompts.update(generated)
202
+ return new_prompts
203
+
204
+ def _get_errors(
205
+ self,
206
+ prompt: str,
207
+ evaluator: Evaluator,
208
+ data_mapper: BasicDataMapper,
209
+ dataset: List[Dict[str, Any]],
210
+ sample_size: int = 32,
211
+ ) -> List[Dict[str, Any]]:
212
+ subset = random.sample(dataset, min(len(dataset), sample_size))
213
+ temp_generator = LiteLLMGenerator(
214
+ self.task_model or self.teacher.model_name, prompt
215
+ )
216
+
217
+ generated_outputs = [temp_generator.generate(example) for example in subset]
218
+ eval_inputs = [
219
+ data_mapper.map(gen_out, ex)
220
+ for gen_out, ex in zip(generated_outputs, subset)
221
+ ]
222
+ results = evaluator.evaluate(eval_inputs)
223
+
224
+ errors = [subset[i] for i, res in enumerate(results) if res.score < 0.5]
225
+ logging.debug(
226
+ f"Found {len(errors)} errors with score < 0.5 from a subset of {len(subset)}."
227
+ )
228
+ return errors
229
+
230
+ def _get_gradients(self, prompt: str, errors: List[Dict[str, Any]]) -> List[str]:
231
+ error_sample = random.sample(errors, min(len(errors), self.errors_per_gradient))
232
+ critique_prompt = GET_GRADIENTS_PROMPT.format(
233
+ prompt=prompt,
234
+ error_examples=json.dumps(error_sample, indent=2, ensure_ascii=False),
235
+ num_feedbacks=self.num_gradients,
236
+ )
237
+ response_text = self.teacher.generate(
238
+ prompt_vars={"prompt": critique_prompt},
239
+ response_format={"type": "json_object"},
240
+ )
241
+ return self._parse_variations_from_json(response_text)
242
+
243
+ def _apply_gradient(
244
+ self, prompt: str, errors: List[Dict[str, Any]], feedback: str
245
+ ) -> List[str]:
246
+ error_sample = random.sample(errors, min(len(errors), self.errors_per_gradient))
247
+ rewrite_prompt = APPLY_GRADIENT_PROMPT.format(
248
+ prompt=prompt,
249
+ error_examples=json.dumps(error_sample, indent=2, ensure_ascii=False),
250
+ feedback=feedback,
251
+ num_new_prompts=self.prompts_per_gradient,
252
+ )
253
+ response_text = self.teacher.generate(
254
+ prompt_vars={"prompt": rewrite_prompt},
255
+ response_format={"type": "json_object"},
256
+ )
257
+ return self._parse_variations_from_json(response_text)
258
+
259
+ def _score_candidates(
260
+ self,
261
+ prompts: List[str],
262
+ evaluator: Evaluator,
263
+ data_mapper: BasicDataMapper,
264
+ dataset: List[Dict[str, Any]],
265
+ ) -> List[IterationHistory]:
266
+ histories = []
267
+ for i, prompt in enumerate(prompts):
268
+ logging.info(
269
+ f"--> Scoring prompt {i + 1}/{len(prompts)}: '{prompt[:100]}...'"
270
+ )
271
+ temp_generator = LiteLLMGenerator(
272
+ self.task_model or self.teacher.model_name, prompt
273
+ )
274
+ generated_outputs = [
275
+ temp_generator.generate(example) for example in dataset
276
+ ]
277
+ eval_inputs = [
278
+ data_mapper.map(gen_out, ex)
279
+ for gen_out, ex in zip(generated_outputs, dataset)
280
+ ]
281
+ results = evaluator.evaluate(eval_inputs)
282
+ avg_score = (
283
+ sum(res.score for res in results) / len(results) if results else 0.0
284
+ )
285
+ logging.info(f" Average score: {avg_score:.4f}")
286
+ histories.append(
287
+ IterationHistory(
288
+ prompt=prompt, average_score=avg_score, individual_results=results
289
+ )
290
+ )
291
+ return histories
292
+
293
+ @staticmethod
294
+ def _parse_variations_from_json(text: str) -> List[str]:
295
+ text = text.strip()
296
+
297
+ try:
298
+ data = json.loads(text)
299
+ return GradientVariations.model_validate(data).variations
300
+ except (json.JSONDecodeError, ValidationError):
301
+ pass
302
+
303
+ try:
304
+ match = re.search(r"```json\s*(\{.*?\})\s*```", text, re.DOTALL)
305
+ if match:
306
+ json_str = match.group(1)
307
+ data = json.loads(json_str)
308
+ return GradientVariations.model_validate(data).variations
309
+ except (json.JSONDecodeError, ValidationError):
310
+ pass
311
+
312
+ try:
313
+ start_index = text.find("{")
314
+ end_index = text.rfind("}")
315
+ if start_index != -1 and end_index != -1 and end_index > start_index:
316
+ json_str = text[start_index : end_index + 1]
317
+ data = json.loads(json_str)
318
+ return GradientVariations.model_validate(data).variations
319
+ except (json.JSONDecodeError, ValidationError) as e:
320
+ logging.error(
321
+ f"Failed to parse teacher model JSON response after all fallbacks: {e}"
322
+ )
323
+ logging.debug(f"Raw problematic output that failed parsing:\n{text}")
324
+ return []
325
+
326
+ # If no JSON object is found at all
327
+ logging.warning("Could not find any JSON in the teacher's response.")
328
+ logging.debug(f"Raw response with no JSON:\n{text}")
329
+ return []
@@ -0,0 +1,224 @@
1
+ import litellm
2
+ import logging
3
+ import time
4
+ import json
5
+ from pydantic import BaseModel, Field, ValidationError
6
+ from typing import List, Dict, Any, Optional
7
+
8
+ from ..base.evaluator import Evaluator
9
+ from ..base.base_optimizer import BaseOptimizer
10
+ from ..base.base_generator import BaseGenerator
11
+
12
+ from ..datamappers import BasicDataMapper
13
+ from ..types import IterationHistory, OptimizationResult
14
+ from ..utils.early_stopping import EarlyStoppingConfig, EarlyStoppingChecker
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class PromptVariations(BaseModel):
20
+ """Output model for prompt variations generated by the teacher model."""
21
+
22
+ variations: List[str] = Field(
23
+ description="A list of prompt variations.",
24
+ )
25
+
26
+
27
+ class RandomSearchOptimizer(BaseOptimizer):
28
+ """
29
+ A simple optimization strategy that tries a number of random prompt variations
30
+ generated by a powerful "teacher" model.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ generator: BaseGenerator,
36
+ teacher_model: str = "gpt-5",
37
+ num_variations: int = 5,
38
+ teacher_model_kwargs: Dict[str, Any] = {},
39
+ eval_template: str = "summary_quality",
40
+ eval_model_name: str = "turing_flash",
41
+ ):
42
+ """
43
+ Initializes the Random Search Optimizer.
44
+
45
+ Args:
46
+ generator: The generator to be optimized.
47
+ teacher_model: A powerful LLM to generate prompt variations.
48
+ num_variations: The number of random variations to try.
49
+ teacher_model_kwargs: A dictionary of kwargs to pass to the teacher model.
50
+ eval_template: The evaluation template to use from the ai-evaluation library.
51
+ eval_model_name: The model to use for evaluation.
52
+ """
53
+ self.generator = generator
54
+ self.teacher_model = teacher_model
55
+ self.num_variations = num_variations
56
+
57
+ if teacher_model_kwargs is None and "gpt" in teacher_model:
58
+ self.teacher_model_kwargs = {"temperature": 1.0, "max_tokens": 16000}
59
+ else:
60
+ self.teacher_model_kwargs = teacher_model_kwargs or {}
61
+
62
+ super().__init__()
63
+
64
+ def optimize(
65
+ self,
66
+ evaluator: Evaluator,
67
+ data_mapper: BasicDataMapper,
68
+ dataset: List[Dict[str, Any]],
69
+ early_stopping: Optional[EarlyStoppingConfig] = None,
70
+ **kwargs: Any,
71
+ ) -> OptimizationResult:
72
+ logger.info("--- Starting Random Search Optimization ---")
73
+ optimization_start_time = time.time()
74
+
75
+ # Initialize early stopping checker
76
+ checker = None
77
+ if early_stopping and early_stopping.is_enabled():
78
+ checker = EarlyStoppingChecker(early_stopping)
79
+ logger.info(f"Early stopping enabled: {early_stopping}")
80
+
81
+ initial_prompt = self.generator.get_prompt_template()
82
+ best_prompt = initial_prompt
83
+ best_score = -1.0
84
+ history = []
85
+
86
+ variations = self._generate_variations(initial_prompt)
87
+
88
+ for i, variation in enumerate(variations):
89
+ iteration_start_time = time.time()
90
+ logger.info(f"--- Testing Variation {i + 1}/{len(variations)} ---")
91
+ logger.info(f"Prompt: {variation}")
92
+ self.generator.set_prompt_template(variation)
93
+
94
+ # Time generation
95
+ generation_start_time = time.time()
96
+ generated_outputs = [
97
+ self.generator.generate(example) for example in dataset
98
+ ]
99
+ generation_end_time = time.time()
100
+ logger.info(
101
+ f"Generation for {len(dataset)} examples took {generation_end_time - generation_start_time:.2f} seconds."
102
+ )
103
+
104
+ eval_inputs = [
105
+ data_mapper.map(gen_output, example)
106
+ for gen_output, example in zip(generated_outputs, dataset)
107
+ ]
108
+
109
+ # Time evaluation
110
+ evaluation_start_time = time.time()
111
+ evaluation_results = evaluator.evaluate(eval_inputs)
112
+ evaluation_end_time = time.time()
113
+ logger.info(
114
+ f"Evaluation for {len(dataset)} examples took {evaluation_end_time - evaluation_start_time:.2f} seconds."
115
+ )
116
+
117
+ if not evaluation_results:
118
+ logger.warning(
119
+ "No evaluation results were returned for this variation."
120
+ )
121
+ continue
122
+
123
+ # Calculate average score for decision-making
124
+ avg_score = sum(res.score for res in evaluation_results) / len(
125
+ evaluation_results
126
+ )
127
+ logger.info(f"Average Score for Variation {i + 1}: {avg_score:.4f}")
128
+
129
+ history.append(
130
+ IterationHistory(
131
+ prompt=variation,
132
+ average_score=avg_score,
133
+ individual_results=evaluation_results,
134
+ )
135
+ )
136
+
137
+ # Check early stopping
138
+ if checker:
139
+ num_evals = len(dataset)
140
+ if checker.should_stop(avg_score, num_evals):
141
+ logger.info(
142
+ f"Early stopping triggered: {checker.get_state()['stop_reason']}"
143
+ )
144
+ break
145
+
146
+ if avg_score > best_score:
147
+ best_score = avg_score
148
+ best_prompt = variation
149
+ logger.info("--- New Best Prompt Found! ---")
150
+
151
+ iteration_end_time = time.time()
152
+ logger.info(
153
+ f"--- Variation {i + 1} finished in {iteration_end_time - iteration_start_time:.2f} seconds ---"
154
+ )
155
+
156
+ self.generator.set_prompt_template(best_prompt)
157
+
158
+ optimization_end_time = time.time()
159
+ logger.info(
160
+ f"--- Random Search Optimization finished in {optimization_end_time - optimization_start_time:.2f} seconds ---"
161
+ )
162
+
163
+ # Build result with early stopping metadata
164
+ return OptimizationResult(
165
+ best_generator=self.generator,
166
+ history=history,
167
+ final_score=best_score,
168
+ early_stopped=checker.get_state()["stopped"] if checker else False,
169
+ stop_reason=checker.get_state()["stop_reason"] if checker else None,
170
+ total_iterations=len(history),
171
+ total_evaluations=(
172
+ checker.get_state()["total_evaluations"]
173
+ if checker
174
+ else len(history) * len(dataset)
175
+ ),
176
+ )
177
+
178
+ def _generate_variations(self, initial_prompt: str) -> List[str]:
179
+ # this entire thing should ideally be sent to the generator itself.
180
+ logger.info(
181
+ f"Generating {self.num_variations} prompt variations with teacher model: {self.teacher_model}..."
182
+ )
183
+
184
+ instruction = f"""
185
+ You are an expert in prompt engineering. Your task is to generate {self.num_variations} variations of the following prompt.
186
+ The variations should be diverse and explore different styles.
187
+ Return ONLY a JSON object with a key "variations" containing a list of strings. For example:
188
+ {{"variations": ["prompt 1", "prompt 2", ...]}}
189
+
190
+ Initial Prompt:
191
+ ---
192
+ {initial_prompt}
193
+ ---
194
+ """
195
+ messages = [{"role": "user", "content": instruction}]
196
+ response_content = ""
197
+
198
+ try:
199
+ teacher_model_kwargs = self.teacher_model_kwargs.copy()
200
+ teacher_model_kwargs["response_format"] = {"type": "json_object"}
201
+
202
+ response = litellm.completion(
203
+ model=self.teacher_model,
204
+ messages=messages,
205
+ **teacher_model_kwargs,
206
+ )
207
+ response_content = response.choices[0].message.content
208
+ # logger.info(f"Teacher model response:\n{response_content}")
209
+
210
+ # Clean the response content to ensure it is valid JSON
211
+ # Sometimes models return JSON wrapped in ```json ... ```
212
+ if response_content.strip().startswith("```json"):
213
+ response_content = response_content.strip()[7:-3].strip()
214
+
215
+ prompt_variations = PromptVariations.model_validate_json(response_content)
216
+ return prompt_variations.variations
217
+
218
+ except (ValidationError, json.JSONDecodeError) as e:
219
+ logger.error(f"Failed to parse teacher model response: {e}")
220
+ except Exception as e:
221
+ logger.error(f"An error occurred with the teacher model: {e}")
222
+
223
+ logger.error(f"Problematic response content was: {response_content}")
224
+ return [initial_prompt + " Be creative."]