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,635 @@
1
+ from __future__ import annotations
2
+
3
+ import itertools
4
+ import logging
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, Callable, Iterable, List, Mapping, Optional, Sequence
7
+
8
+ from ..base.base_optimizer import BaseOptimizer
9
+ from ..components import (
10
+ ComponentDiagnosis,
11
+ diagnose_agent_report_evaluation,
12
+ relevant_search_paths,
13
+ )
14
+ from ..observability import AgentObservabilityWindow
15
+ from ..targets import AgentCandidate, CandidateEvaluation, OptimizationTarget
16
+ from ..types import EvaluationResult, IterationHistory, OptimizationResult
17
+ from .agent import (
18
+ _dedupe_diagnoses,
19
+ _diagnose_candidate_evaluation,
20
+ _dump_model,
21
+ _history_from_candidate,
22
+ _normalize_candidate_evaluation,
23
+ _normalize_diagnoses,
24
+ )
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ CandidateScorer = Callable[
30
+ [AgentCandidate],
31
+ CandidateEvaluation | EvaluationResult | float,
32
+ ]
33
+
34
+
35
+ @dataclass
36
+ class AgentCurriculumStage:
37
+ """
38
+ One metric-focused optimization stage.
39
+
40
+ Stages let a multi-interaction workflow practice one failure family at a
41
+ time, then promote the best candidate into the next stage. Names and
42
+ inspiration labels are metadata only; numeric evaluator evidence controls
43
+ promotion.
44
+ """
45
+
46
+ name: str
47
+ search_paths: Sequence[str] = field(default_factory=tuple)
48
+ diagnoses: Sequence[ComponentDiagnosis | Mapping[str, Any]] = field(default_factory=tuple)
49
+ metric_weights: Mapping[str, float] = field(default_factory=dict)
50
+ target_score: Optional[float] = None
51
+ max_candidates: Optional[int] = None
52
+ evaluator: Optional[CandidateScorer] = None
53
+ metadata: Mapping[str, Any] = field(default_factory=dict)
54
+
55
+
56
+ class AgentCurriculumOptimizer(BaseOptimizer):
57
+ """
58
+ Deterministic staged optimizer for multi-interaction agents.
59
+
60
+ `AgentOptimizer` is a flat candidate search. `AgentCurriculumOptimizer`
61
+ instead runs a sequence of metric-focused drills: memory first, handoff
62
+ next, policy next, etc. Each stage searches only its relevant config paths,
63
+ scores candidates with the stage's metric weights, promotes the best
64
+ candidate, and carries that config into the next stage.
65
+ """
66
+
67
+ def __init__(
68
+ self,
69
+ target: Optional[OptimizationTarget] = None,
70
+ *,
71
+ evaluate_candidate: Optional[CandidateScorer] = None,
72
+ simulation_evaluator: Any = None,
73
+ stages: Optional[Iterable[AgentCurriculumStage | Mapping[str, Any]]] = None,
74
+ curriculum_history: Optional[AgentObservabilityWindow] = None,
75
+ diagnoses: Optional[Iterable[ComponentDiagnosis | dict[str, Any]]] = None,
76
+ max_candidates_per_stage: int = 32,
77
+ include_seed: bool = True,
78
+ auto_diagnose: bool = True,
79
+ diagnostic_score_threshold: float = 0.85,
80
+ target_score: float = 1.0,
81
+ carry_forward: bool = True,
82
+ ) -> None:
83
+ if max_candidates_per_stage < 1:
84
+ raise ValueError("max_candidates_per_stage must be at least 1.")
85
+ if target_score < 0:
86
+ raise ValueError("target_score must be non-negative.")
87
+
88
+ self.target = target
89
+ self.evaluate_candidate = evaluate_candidate
90
+ self.simulation_evaluator = simulation_evaluator
91
+ self.stages = _normalize_curriculum_stages(stages)
92
+ self.curriculum_history = curriculum_history
93
+ self.diagnoses = _normalize_diagnoses(diagnoses)
94
+ self.max_candidates_per_stage = max_candidates_per_stage
95
+ self.include_seed = include_seed
96
+ self.auto_diagnose = auto_diagnose
97
+ self.diagnostic_score_threshold = diagnostic_score_threshold
98
+ self.target_score = target_score
99
+ self.carry_forward = carry_forward
100
+ super().__init__()
101
+
102
+ def optimize(
103
+ self,
104
+ evaluator: Any = None,
105
+ data_mapper: Any = None,
106
+ dataset: Optional[List[dict[str, Any]]] = None,
107
+ metric: Optional[Callable] = None,
108
+ *,
109
+ target: Optional[OptimizationTarget] = None,
110
+ evaluate_candidate: Optional[CandidateScorer] = None,
111
+ simulation_evaluator: Any = None,
112
+ stages: Optional[Iterable[AgentCurriculumStage | Mapping[str, Any]]] = None,
113
+ curriculum_history: Optional[AgentObservabilityWindow] = None,
114
+ diagnoses: Optional[Iterable[ComponentDiagnosis | dict[str, Any]]] = None,
115
+ max_candidates_per_stage: Optional[int] = None,
116
+ include_seed: Optional[bool] = None,
117
+ auto_diagnose: Optional[bool] = None,
118
+ diagnostic_score_threshold: Optional[float] = None,
119
+ target_score: Optional[float] = None,
120
+ carry_forward: Optional[bool] = None,
121
+ **kwargs: Any,
122
+ ) -> OptimizationResult:
123
+ active_target = target or self.target
124
+ if active_target is None:
125
+ raise ValueError("AgentCurriculumOptimizer requires a target.")
126
+ if not active_target.search_space:
127
+ raise ValueError(
128
+ "AgentCurriculumOptimizer target search space cannot be empty."
129
+ )
130
+
131
+ active_evaluator = (
132
+ evaluate_candidate
133
+ or self.evaluate_candidate
134
+ or getattr(simulation_evaluator, "evaluate_candidate", None)
135
+ or getattr(self.simulation_evaluator, "evaluate_candidate", None)
136
+ or evaluator
137
+ )
138
+ active_stages = (
139
+ _normalize_curriculum_stages(stages)
140
+ if stages is not None
141
+ else list(self.stages)
142
+ )
143
+ active_history = curriculum_history or self.curriculum_history
144
+ active_max_candidates = (
145
+ self.max_candidates_per_stage
146
+ if max_candidates_per_stage is None
147
+ else max_candidates_per_stage
148
+ )
149
+ if active_max_candidates < 1:
150
+ raise ValueError("max_candidates_per_stage must be at least 1.")
151
+ use_include_seed = self.include_seed if include_seed is None else include_seed
152
+ use_auto_diagnose = self.auto_diagnose if auto_diagnose is None else auto_diagnose
153
+ active_diagnostic_threshold = (
154
+ self.diagnostic_score_threshold
155
+ if diagnostic_score_threshold is None
156
+ else diagnostic_score_threshold
157
+ )
158
+ active_target_score = self.target_score if target_score is None else target_score
159
+ use_carry_forward = self.carry_forward if carry_forward is None else carry_forward
160
+ active_diagnoses = _normalize_diagnoses(diagnoses)
161
+ if diagnoses is None:
162
+ active_diagnoses = list(self.diagnoses)
163
+
164
+ if not active_stages:
165
+ active_stages = _stages_from_history(
166
+ active_history,
167
+ target=active_target,
168
+ failing_threshold=active_diagnostic_threshold,
169
+ )
170
+ if not active_stages:
171
+ active_stages = [
172
+ AgentCurriculumStage(
173
+ name="diagnosed",
174
+ metadata={"source": "default_single_stage"},
175
+ )
176
+ ]
177
+
178
+ if active_evaluator is None and not all(stage.evaluator for stage in active_stages):
179
+ raise ValueError(
180
+ "AgentCurriculumOptimizer requires evaluate_candidate, "
181
+ "simulation_evaluator, or evaluators on every stage."
182
+ )
183
+
184
+ anchor = active_target.seed_candidate()
185
+ best: CandidateEvaluation | None = None
186
+ history: List[IterationHistory] = []
187
+ stage_summaries: list[dict[str, Any]] = []
188
+ stage_audit: list[dict[str, Any]] = []
189
+
190
+ if use_include_seed and active_evaluator is not None:
191
+ seed_stage = AgentCurriculumStage(
192
+ name="seed",
193
+ metadata={"source": "deployed_seed"},
194
+ )
195
+ seed_evaluation = self._evaluate(
196
+ anchor,
197
+ active_evaluator,
198
+ seed_stage,
199
+ stage_index=0,
200
+ history=history,
201
+ reason="evaluate_deployed_seed",
202
+ )
203
+ best = seed_evaluation
204
+ if use_auto_diagnose and not active_diagnoses:
205
+ active_diagnoses = _diagnose_candidate_evaluation(
206
+ seed_evaluation,
207
+ failing_threshold=active_diagnostic_threshold,
208
+ )
209
+
210
+ for stage_index, stage in enumerate(active_stages, start=1):
211
+ stage_evaluator = stage.evaluator or active_evaluator
212
+ if stage_evaluator is None:
213
+ raise ValueError(f"Curriculum stage '{stage.name}' has no evaluator.")
214
+
215
+ stage_paths = _stage_search_paths(
216
+ active_target,
217
+ stage,
218
+ active_diagnoses,
219
+ )
220
+ if not stage_paths:
221
+ stage_paths = list(active_target.search_space)
222
+
223
+ stage_best: CandidateEvaluation | None = None
224
+ stage_best_score = float("-inf")
225
+ evaluated_in_stage = 0
226
+ stage_target = (
227
+ stage.target_score
228
+ if stage.target_score is not None
229
+ else active_target_score
230
+ )
231
+
232
+ for candidate in _iter_stage_candidates(
233
+ anchor,
234
+ target=active_target,
235
+ stage=stage,
236
+ stage_index=stage_index,
237
+ search_paths=stage_paths,
238
+ include_seed=True,
239
+ max_candidates=stage.max_candidates or active_max_candidates,
240
+ ):
241
+ evaluation = self._evaluate(
242
+ candidate,
243
+ stage_evaluator,
244
+ stage,
245
+ stage_index=stage_index,
246
+ history=history,
247
+ reason=f"curriculum_stage:{stage.name}",
248
+ )
249
+ evaluated_in_stage += 1
250
+ candidate_stage_score = _stage_score(evaluation, stage)
251
+ stage_audit.append(
252
+ {
253
+ "stage": stage.name,
254
+ "stage_index": stage_index,
255
+ "candidate_id": candidate.id,
256
+ "patch": candidate.patch,
257
+ "score": evaluation.score,
258
+ "stage_score": candidate_stage_score,
259
+ }
260
+ )
261
+ if _is_better_stage_candidate(
262
+ evaluation,
263
+ candidate_stage_score,
264
+ stage_best,
265
+ stage_best_score,
266
+ ):
267
+ stage_best = evaluation
268
+ stage_best_score = candidate_stage_score
269
+ if best is None or evaluation.score > best.score:
270
+ best = evaluation
271
+ logger.info(
272
+ "New best curriculum candidate %s score=%.4f",
273
+ candidate.id,
274
+ evaluation.score,
275
+ )
276
+ if candidate_stage_score >= stage_target:
277
+ break
278
+
279
+ if stage_best is not None and use_carry_forward:
280
+ anchor = stage_best.candidate
281
+ if stage_best is not None and use_auto_diagnose:
282
+ stage_diagnoses = _diagnose_candidate_evaluation(
283
+ stage_best,
284
+ failing_threshold=active_diagnostic_threshold,
285
+ )
286
+ if stage_diagnoses:
287
+ active_diagnoses = _dedupe_diagnoses(
288
+ [*active_diagnoses, *stage_diagnoses]
289
+ )
290
+
291
+ stage_summaries.append(
292
+ {
293
+ "stage": stage.name,
294
+ "stage_index": stage_index,
295
+ "search_paths": list(stage_paths),
296
+ "metric_weights": dict(stage.metric_weights),
297
+ "target_score": stage_target,
298
+ "evaluated": evaluated_in_stage,
299
+ "best_stage_score": None
300
+ if stage_best is None
301
+ else round(stage_best_score, 4),
302
+ "best_candidate_id": None if stage_best is None else stage_best.candidate.id,
303
+ "promoted": bool(stage_best is not None and use_carry_forward),
304
+ "target_met": bool(stage_best is not None and stage_best_score >= stage_target),
305
+ "metadata": dict(stage.metadata),
306
+ }
307
+ )
308
+ if best is not None and best.score >= active_target_score:
309
+ break
310
+
311
+ if best is None:
312
+ raise ValueError("AgentCurriculumOptimizer did not evaluate any candidates.")
313
+
314
+ metadata = {
315
+ "optimizer": "AgentCurriculumOptimizer",
316
+ "strategy": "deliberate_practice_curriculum",
317
+ "strategy_inspiration": (
318
+ "curriculum learning, deliberate practice, metacognitive "
319
+ "stage gates, and dharma-style stewardship; names are metadata only"
320
+ ),
321
+ "roles": ["viveka", "abhyasa", "satsanga", "dharma_steward"],
322
+ "target_name": best.candidate.target_name,
323
+ "best_candidate_id": best.candidate.id,
324
+ "stages": stage_summaries,
325
+ "stage_audit": stage_audit,
326
+ "search_paths": _unique_paths(
327
+ path
328
+ for summary in stage_summaries
329
+ for path in summary["search_paths"]
330
+ ),
331
+ "max_candidates_per_stage": active_max_candidates,
332
+ "carry_forward": use_carry_forward,
333
+ "history_source": active_history.source if active_history else None,
334
+ "history_record_count": len(active_history.records) if active_history else 0,
335
+ }
336
+ if active_diagnoses:
337
+ metadata["diagnostics"] = [_dump_model(item) for item in active_diagnoses]
338
+ metadata["auto_diagnosed"] = use_auto_diagnose
339
+
340
+ return OptimizationResult(
341
+ best_generator=best.candidate,
342
+ best_candidate=best.candidate,
343
+ history=history,
344
+ final_score=best.score,
345
+ total_iterations=len(history),
346
+ total_evaluations=len(history),
347
+ metadata=metadata,
348
+ )
349
+
350
+ def _evaluate(
351
+ self,
352
+ candidate: AgentCandidate,
353
+ evaluator: CandidateScorer,
354
+ stage: AgentCurriculumStage,
355
+ *,
356
+ stage_index: int,
357
+ history: List[IterationHistory],
358
+ reason: str,
359
+ ) -> CandidateEvaluation:
360
+ value = evaluator(candidate)
361
+ evaluation = _normalize_candidate_evaluation(value, candidate)
362
+ stage_score = _stage_score(evaluation, stage)
363
+ evaluation.metadata = {
364
+ **candidate.metadata,
365
+ **evaluation.metadata,
366
+ "optimizer": "AgentCurriculumOptimizer",
367
+ "curriculum_stage": stage.name,
368
+ "curriculum_stage_index": stage_index,
369
+ "curriculum_stage_score": stage_score,
370
+ "curriculum_stage_metric_weights": dict(stage.metric_weights),
371
+ "curriculum_stage_reason": reason,
372
+ "curriculum_stage_metadata": dict(stage.metadata),
373
+ }
374
+ history.append(_history_from_candidate(evaluation))
375
+ return evaluation
376
+
377
+
378
+ def _normalize_curriculum_stages(
379
+ stages: Optional[Iterable[AgentCurriculumStage | Mapping[str, Any]]],
380
+ ) -> list[AgentCurriculumStage]:
381
+ if stages is None:
382
+ return []
383
+ normalized: list[AgentCurriculumStage] = []
384
+ for index, raw in enumerate(stages, start=1):
385
+ if isinstance(raw, AgentCurriculumStage):
386
+ normalized.append(raw)
387
+ continue
388
+ item = dict(raw)
389
+ metrics = item.get("metric_weights") or {}
390
+ if not metrics:
391
+ metric_names = _string_list(item.get("metrics") or item.get("metric"))
392
+ metrics = {name: 1.0 for name in metric_names}
393
+ normalized.append(
394
+ AgentCurriculumStage(
395
+ name=str(item.get("name") or f"stage_{index}"),
396
+ search_paths=tuple(_string_list(item.get("search_paths") or item.get("paths"))),
397
+ diagnoses=tuple(_normalize_diagnoses(item.get("diagnoses") or [])),
398
+ metric_weights={
399
+ str(key): float(value)
400
+ for key, value in dict(metrics).items()
401
+ if float(value) > 0
402
+ },
403
+ target_score=_optional_float(item.get("target_score")),
404
+ max_candidates=_optional_int(item.get("max_candidates")),
405
+ evaluator=item.get("evaluator"),
406
+ metadata=dict(item.get("metadata") or {}),
407
+ )
408
+ )
409
+ return normalized
410
+
411
+
412
+ def _stages_from_history(
413
+ history: Optional[AgentObservabilityWindow],
414
+ *,
415
+ target: OptimizationTarget,
416
+ failing_threshold: float,
417
+ ) -> list[AgentCurriculumStage]:
418
+ if history is None:
419
+ return []
420
+ thresholds = dict(history.required_metrics)
421
+ if not thresholds:
422
+ for record in history.records:
423
+ for metric_name in record.metrics:
424
+ thresholds.setdefault(metric_name, failing_threshold)
425
+ stages: list[AgentCurriculumStage] = []
426
+ for metric_name, threshold in thresholds.items():
427
+ observed = [
428
+ float(record.metrics.get(metric_name, 0.0))
429
+ for record in history.records
430
+ ]
431
+ if observed and min(observed) >= threshold:
432
+ continue
433
+ diagnoses = diagnose_agent_report_evaluation(
434
+ {
435
+ "summary": {"metric_averages": {metric_name: min(observed or [0.0])}},
436
+ "cases": [
437
+ {
438
+ "metrics": [
439
+ {
440
+ "name": metric_name,
441
+ "score": min(observed or [0.0]),
442
+ "reason": f"Curriculum history failed {metric_name}.",
443
+ }
444
+ ]
445
+ }
446
+ ],
447
+ },
448
+ failing_threshold=threshold,
449
+ confidence=0.9,
450
+ )
451
+ paths = relevant_search_paths(target.search_space, diagnoses)
452
+ stages.append(
453
+ AgentCurriculumStage(
454
+ name=metric_name,
455
+ search_paths=tuple(path for path in target.search_space if path in paths),
456
+ diagnoses=tuple(diagnoses),
457
+ metric_weights={metric_name: 1.0},
458
+ target_score=float(threshold),
459
+ metadata={
460
+ "source": "curriculum_history",
461
+ "history_source": history.source,
462
+ "observed_min": min(observed or [0.0]),
463
+ },
464
+ )
465
+ )
466
+ return stages
467
+
468
+
469
+ def _stage_search_paths(
470
+ target: OptimizationTarget,
471
+ stage: AgentCurriculumStage,
472
+ active_diagnoses: Sequence[ComponentDiagnosis],
473
+ ) -> list[str]:
474
+ explicit = [path for path in stage.search_paths if path in target.search_space]
475
+ if explicit:
476
+ return [path for path in target.search_space if path in set(explicit)]
477
+ stage_diagnoses = _normalize_diagnoses(stage.diagnoses)
478
+ diagnoses = stage_diagnoses or list(active_diagnoses)
479
+ if diagnoses:
480
+ allowed = relevant_search_paths(target.search_space, diagnoses)
481
+ return [path for path in target.search_space if path in allowed]
482
+ return list(target.search_space)
483
+
484
+
485
+ def _iter_stage_candidates(
486
+ anchor: AgentCandidate,
487
+ *,
488
+ target: OptimizationTarget,
489
+ stage: AgentCurriculumStage,
490
+ stage_index: int,
491
+ search_paths: Sequence[str],
492
+ include_seed: bool,
493
+ max_candidates: int,
494
+ ) -> Iterable[AgentCandidate]:
495
+ count = 0
496
+ if include_seed:
497
+ yield AgentCandidate.from_config(
498
+ anchor.config,
499
+ target_name=target.name,
500
+ layers=target.layers,
501
+ parent_id=anchor.parent_id,
502
+ patch=anchor.patch,
503
+ metadata={
504
+ **anchor.metadata,
505
+ "kind": "curriculum_anchor",
506
+ "curriculum_stage": stage.name,
507
+ "curriculum_stage_index": stage_index,
508
+ },
509
+ )
510
+ count += 1
511
+ if count >= max_candidates:
512
+ return
513
+
514
+ paths = [path for path in search_paths if path in target.search_space]
515
+ value_lists = [
516
+ _values_with_anchor_first(target.search_space[path], anchor.get_path(path))
517
+ for path in paths
518
+ ]
519
+ for values in itertools.product(*value_lists):
520
+ patch = dict(zip(paths, values))
521
+ if all(anchor.get_path(path) == value for path, value in patch.items()):
522
+ continue
523
+ yield anchor.with_patch(
524
+ patch,
525
+ layers=target.layers,
526
+ metadata={
527
+ "kind": "curriculum_stage_candidate",
528
+ "optimizer": "AgentCurriculumOptimizer",
529
+ "curriculum_stage": stage.name,
530
+ "curriculum_stage_index": stage_index,
531
+ "curriculum_search_paths": list(paths),
532
+ "curriculum_stage_metadata": dict(stage.metadata),
533
+ },
534
+ )
535
+ count += 1
536
+ if count >= max_candidates:
537
+ return
538
+
539
+
540
+ def _stage_score(
541
+ evaluation: CandidateEvaluation,
542
+ stage: AgentCurriculumStage,
543
+ ) -> float:
544
+ weights = {key: float(value) for key, value in stage.metric_weights.items() if value > 0}
545
+ if not weights:
546
+ return float(evaluation.score)
547
+ metrics = _metric_averages(evaluation)
548
+ total_weight = sum(weights.values())
549
+ if total_weight <= 0:
550
+ return float(evaluation.score)
551
+ return sum(float(metrics.get(name, 0.0)) * weight for name, weight in weights.items()) / total_weight
552
+
553
+
554
+ def _metric_averages(evaluation: CandidateEvaluation) -> dict[str, float]:
555
+ metrics: dict[str, float] = {}
556
+ for key in ("metric_averages", "metrics"):
557
+ raw = evaluation.metadata.get(key)
558
+ if isinstance(raw, Mapping):
559
+ for name, value in raw.items():
560
+ if isinstance(value, (int, float)) and not isinstance(value, bool):
561
+ metrics[str(name)] = float(value)
562
+ payload = evaluation.metadata.get("agent_report_evaluation")
563
+ if hasattr(payload, "model_dump"):
564
+ payload = payload.model_dump()
565
+ if isinstance(payload, Mapping):
566
+ summary = payload.get("summary")
567
+ if isinstance(summary, Mapping):
568
+ raw_metrics = summary.get("metric_averages")
569
+ if isinstance(raw_metrics, Mapping):
570
+ for name, value in raw_metrics.items():
571
+ if isinstance(value, (int, float)) and not isinstance(value, bool):
572
+ metrics[str(name)] = float(value)
573
+ return metrics
574
+
575
+
576
+ def _is_better_stage_candidate(
577
+ evaluation: CandidateEvaluation,
578
+ stage_score: float,
579
+ current: Optional[CandidateEvaluation],
580
+ current_stage_score: float,
581
+ ) -> bool:
582
+ if current is None:
583
+ return True
584
+ return (
585
+ stage_score,
586
+ evaluation.score,
587
+ -len(evaluation.candidate.patch),
588
+ evaluation.candidate.id,
589
+ ) > (
590
+ current_stage_score,
591
+ current.score,
592
+ -len(current.candidate.patch),
593
+ current.candidate.id,
594
+ )
595
+
596
+
597
+ def _string_list(value: Any) -> list[str]:
598
+ if value in (None, "", [], ()):
599
+ return []
600
+ if isinstance(value, str):
601
+ return [value]
602
+ if isinstance(value, Iterable) and not isinstance(value, (Mapping, bytes)):
603
+ return [str(item) for item in value if item not in (None, "")]
604
+ return [str(value)]
605
+
606
+
607
+ def _optional_float(value: Any) -> Optional[float]:
608
+ if value in (None, ""):
609
+ return None
610
+ return float(value)
611
+
612
+
613
+ def _optional_int(value: Any) -> Optional[int]:
614
+ if value in (None, ""):
615
+ return None
616
+ return int(value)
617
+
618
+
619
+ def _unique_paths(paths: Iterable[str]) -> list[str]:
620
+ seen: set[str] = set()
621
+ result: list[str] = []
622
+ for path in paths:
623
+ if path in seen:
624
+ continue
625
+ seen.add(path)
626
+ result.append(path)
627
+ return result
628
+
629
+
630
+ def _values_with_anchor_first(values: Sequence[Any], anchor_value: Any) -> list[Any]:
631
+ ordered = list(values)
632
+ for index, value in enumerate(ordered):
633
+ if value == anchor_value:
634
+ return [value, *ordered[:index], *ordered[index + 1:]]
635
+ return ordered