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,652 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import time
6
+ import urllib.error
7
+ import urllib.parse
8
+ import urllib.request
9
+ from dataclasses import dataclass
10
+ from typing import Any, Literal, Mapping
11
+
12
+ from fi.simulate.agent.definition import AgentDefinition
13
+ from fi.simulate.simulation.models import Scenario
14
+
15
+ from ._download import (
16
+ _config,
17
+ _field,
18
+ _headers,
19
+ _rows,
20
+ ScenarioDownloadError,
21
+ fetch_dataset_rows,
22
+ hydrate_platform_scenario,
23
+ validate_download,
24
+ )
25
+ from ._scan import DownloadRejected, scan_content
26
+
27
+ _AGENT_LIST_PATH = "/simulate/agent-definitions/"
28
+ _AGENT_CREATE_PATH = "/simulate/agent-definitions/create/"
29
+ _AGENT_VERSION_CREATE_PATH = "/simulate/agent-definitions/{agent_id}/versions/create/"
30
+ _SCENARIO_LIST_PATH = "/simulate/scenarios/"
31
+ _SCENARIO_CREATE_PATH = "/simulate/scenarios/create/"
32
+ _SCENARIO_DETAIL_PATH = "/simulate/scenarios/{scenario_id}/"
33
+ _TERMINAL_FAILURE_STATUSES = {"failed", "error", "cancelled"}
34
+
35
+
36
+ class ScenarioGenerationError(RuntimeError):
37
+ def __init__(
38
+ self,
39
+ message: str,
40
+ *,
41
+ scenario_id: str | None = None,
42
+ status: str | None = None,
43
+ retryable: bool = False,
44
+ ) -> None:
45
+ super().__init__(message)
46
+ self.scenario_id = scenario_id
47
+ self.status = status
48
+ self.retryable = retryable
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class PlatformAgentReference:
53
+ agent_definition_id: str
54
+ agent_version_id: str
55
+ configuration_hash: str
56
+ reused: bool
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class PlatformScenarioRequest:
61
+ name: str
62
+ agent_definition: AgentDefinition | None = None
63
+ platform_agent_definition_id: str | None = None
64
+ platform_agent_version_id: str | None = None
65
+ description: str | None = None
66
+ custom_instruction: str | None = None
67
+ kind: Literal["graph"] = "graph"
68
+ no_of_rows: int = 10
69
+ poll_interval_seconds: float = 2.0
70
+ timeout_seconds: float = 900.0
71
+
72
+ def __post_init__(self) -> None:
73
+ if not self.name.strip():
74
+ raise ValueError("scenario name must be non-empty")
75
+ if self.kind != "graph":
76
+ raise ValueError("only graph scenario generation is supported")
77
+ if not 10 <= self.no_of_rows <= 20_000:
78
+ raise ValueError("no_of_rows must be between 10 and 20000")
79
+ if self.poll_interval_seconds <= 0:
80
+ raise ValueError("poll_interval_seconds must be greater than zero")
81
+ if self.timeout_seconds <= 0:
82
+ raise ValueError("timeout_seconds must be greater than zero")
83
+ has_local = self.agent_definition is not None
84
+ has_platform = bool(self.platform_agent_definition_id)
85
+ if has_local == has_platform:
86
+ raise ValueError(
87
+ "provide exactly one of agent_definition or platform_agent_definition_id"
88
+ )
89
+ if self.platform_agent_version_id and not has_platform:
90
+ raise ValueError(
91
+ "platform_agent_version_id requires platform_agent_definition_id"
92
+ )
93
+
94
+
95
+ @dataclass(frozen=True)
96
+ class GeneratedScenario:
97
+ scenario: Scenario
98
+ platform_agent_definition_id: str
99
+ platform_agent_version_id: str | None
100
+ platform_scenario_id: str
101
+ platform_dataset_id: str
102
+ platform_status: str
103
+ polling_duration_seconds: float
104
+ checksum_sha256: str
105
+
106
+
107
+ def _request_json(
108
+ url: str,
109
+ headers: Mapping[str, str],
110
+ *,
111
+ method: str = "GET",
112
+ payload: Mapping[str, Any] | None = None,
113
+ timeout: float = 30.0,
114
+ ) -> Any:
115
+ body = None
116
+ request_headers = dict(headers)
117
+ if payload is not None:
118
+ body = json.dumps(payload).encode("utf-8")
119
+ request_headers["Content-Type"] = "application/json"
120
+ request = urllib.request.Request(
121
+ url,
122
+ data=body,
123
+ headers=request_headers,
124
+ method=method,
125
+ )
126
+ try:
127
+ with urllib.request.urlopen(request, timeout=timeout) as response: # noqa: S310
128
+ return json.loads(response.read().decode("utf-8"))
129
+ except urllib.error.HTTPError as exc:
130
+ raise ScenarioGenerationError(
131
+ f"Future AGI API request failed with HTTP {exc.code}"
132
+ ) from exc
133
+ except urllib.error.URLError as exc:
134
+ raise ScenarioGenerationError(
135
+ "Future AGI API request could not be completed",
136
+ retryable=True,
137
+ ) from exc
138
+
139
+
140
+ def _required_config(config: Any | None) -> Any:
141
+ resolved = _config(config)
142
+ if not resolved.api_key or not resolved.secret_key:
143
+ raise ScenarioGenerationError(
144
+ "Future AGI API and secret keys are required for scenario generation"
145
+ )
146
+ return resolved
147
+
148
+
149
+ def _safe_livekit_url(value: object) -> str:
150
+ parsed = urllib.parse.urlsplit(str(value))
151
+ if parsed.scheme not in {"ws", "wss"} or not parsed.hostname:
152
+ raise ScenarioGenerationError("LiveKit agent URL must use ws:// or wss://")
153
+ if parsed.username or parsed.password or parsed.query or parsed.fragment:
154
+ raise ScenarioGenerationError(
155
+ "LiveKit agent URL must not contain credentials, query parameters, or fragments"
156
+ )
157
+ host = parsed.hostname
158
+ if parsed.port:
159
+ host = f"{host}:{parsed.port}"
160
+ return urllib.parse.urlunsplit((parsed.scheme, host, parsed.path, "", ""))
161
+
162
+
163
+ def _agent_payload(agent_definition: AgentDefinition) -> tuple[dict[str, Any], str]:
164
+ transport = agent_definition.transport
165
+ transport_kind = transport.kind if transport else "webrtc"
166
+ inbound = transport_kind != "sip_inbound"
167
+ description = agent_definition.system_prompt
168
+ scan = scan_content({"description": description})
169
+ if scan["status"] == "flagged":
170
+ raise ScenarioGenerationError(
171
+ "agent description was rejected by the local content and secret scan"
172
+ )
173
+
174
+ safe_configuration: dict[str, Any] = {
175
+ "name": agent_definition.name,
176
+ "description": description,
177
+ "transport": transport_kind,
178
+ "inbound": inbound,
179
+ "model": agent_definition.llm.model,
180
+ "model_provider": agent_definition.llm.provider,
181
+ "language": agent_definition.stt.language,
182
+ }
183
+ identity_configuration: dict[str, Any] = {"transport": transport_kind}
184
+ payload: dict[str, Any] = {
185
+ "agent_type": "voice",
186
+ "commit_message": "Created by Agent Learning Kit for scenario generation",
187
+ "description": description,
188
+ "inbound": inbound,
189
+ "language": agent_definition.stt.language,
190
+ "languages": (
191
+ [agent_definition.stt.language] if agent_definition.stt.language else None
192
+ ),
193
+ "model": agent_definition.llm.model,
194
+ "model_details": {
195
+ "provider": agent_definition.llm.provider,
196
+ "temperature": agent_definition.llm.temperature,
197
+ },
198
+ }
199
+ target = agent_definition.target
200
+ if target is not None:
201
+ target_id = (
202
+ target.assistant_id if target.provider == "vapi" else target.agent_id
203
+ )
204
+ target_url = (
205
+ target.api_base_url if target.provider == "vapi" else target.api_url
206
+ )
207
+ payload.update(
208
+ {
209
+ "provider": target.provider,
210
+ "assistant_id": target_id,
211
+ }
212
+ )
213
+ safe_configuration.update(
214
+ {
215
+ "provider": target.provider,
216
+ "assistant_id": target_id,
217
+ "provider_api_url": str(target_url),
218
+ }
219
+ )
220
+ identity_configuration.update(
221
+ {"provider": target.provider, "assistant_id": target_id}
222
+ )
223
+ elif transport_kind in {"sip_outbound", "sip_inbound"}:
224
+ if transport is None or not transport.sip_call_to:
225
+ raise ScenarioGenerationError(
226
+ "SIP platform agent creation requires a target contact number"
227
+ )
228
+ payload.update(
229
+ {
230
+ "provider": "others",
231
+ "contact_number": transport.sip_call_to,
232
+ }
233
+ )
234
+ safe_configuration["contact_number"] = transport.sip_call_to
235
+ identity_configuration.update(
236
+ {"provider": "others", "contact_number": transport.sip_call_to}
237
+ )
238
+ else:
239
+ if agent_definition.url is None:
240
+ raise ScenarioGenerationError(
241
+ "LiveKit target creation requires an AgentDefinition url"
242
+ )
243
+ livekit_url = _safe_livekit_url(agent_definition.url)
244
+ livekit_agent_name = agent_definition.agent_name or agent_definition.name
245
+ payload.update(
246
+ {
247
+ "provider": "livekit",
248
+ "livekit_url": livekit_url,
249
+ "livekit_agent_name": livekit_agent_name,
250
+ }
251
+ )
252
+ safe_configuration.update(
253
+ {
254
+ "livekit_url": livekit_url,
255
+ "livekit_agent_name": livekit_agent_name,
256
+ }
257
+ )
258
+ identity_configuration.update(
259
+ {
260
+ "provider": "livekit",
261
+ "livekit_url": livekit_url,
262
+ "livekit_agent_name": livekit_agent_name,
263
+ }
264
+ )
265
+
266
+ configuration_hash = hashlib.sha256(
267
+ json.dumps(
268
+ safe_configuration,
269
+ sort_keys=True,
270
+ separators=(",", ":"),
271
+ default=str,
272
+ ).encode("utf-8")
273
+ ).hexdigest()
274
+ identity_hash = hashlib.sha256(
275
+ json.dumps(
276
+ identity_configuration,
277
+ sort_keys=True,
278
+ separators=(",", ":"),
279
+ default=str,
280
+ ).encode("utf-8")
281
+ ).hexdigest()
282
+ base_name = "-".join(agent_definition.name.strip().split()) or "agent"
283
+ payload["agent_name"] = f"{base_name[:220]}-alk-{identity_hash[:12]}"
284
+ payload["commit_message"] = _configuration_commit(configuration_hash)
285
+ return {
286
+ key: value for key, value in payload.items() if value is not None
287
+ }, configuration_hash
288
+
289
+
290
+ def _configuration_commit(configuration_hash: str) -> str:
291
+ return f"Agent Learning Kit configuration {configuration_hash}"
292
+
293
+
294
+ def _active_version(detail: Mapping[str, Any]) -> Mapping[str, Any] | None:
295
+ active = detail.get("active_version")
296
+ if isinstance(active, Mapping) and active.get("id"):
297
+ return active
298
+ versions = detail.get("versions")
299
+ if isinstance(versions, list):
300
+ return next(
301
+ (
302
+ version
303
+ for version in versions
304
+ if isinstance(version, Mapping) and version.get("id")
305
+ ),
306
+ None,
307
+ )
308
+ return None
309
+
310
+
311
+ def _active_version_id(detail: Mapping[str, Any]) -> str | None:
312
+ active = _active_version(detail)
313
+ if active is not None:
314
+ return str(active["id"])
315
+ latest = detail.get("latest_version_id")
316
+ return str(latest) if latest else None
317
+
318
+
319
+ def _logical_agent_match(
320
+ item: Mapping[str, Any],
321
+ payload: Mapping[str, Any],
322
+ ) -> bool:
323
+ if str(_field(item, "agent_name") or "") == str(payload["agent_name"]):
324
+ return True
325
+ provider = str(payload.get("provider") or "")
326
+ assistant_id = payload.get("assistant_id")
327
+ if assistant_id:
328
+ return bool(
329
+ str(_field(item, "provider") or "") == provider
330
+ and str(_field(item, "assistant_id") or "") == str(assistant_id)
331
+ )
332
+ contact_number = payload.get("contact_number")
333
+ return bool(
334
+ contact_number
335
+ and str(_field(item, "provider") or "") == provider
336
+ and str(_field(item, "contact_number") or "") == str(contact_number)
337
+ )
338
+
339
+
340
+ def ensure_platform_agent(
341
+ agent_definition: AgentDefinition,
342
+ *,
343
+ config: Any | None = None,
344
+ ) -> PlatformAgentReference:
345
+ cfg = _required_config(config)
346
+ headers = _headers(cfg)
347
+ base = str(cfg.api_url).rstrip("/")
348
+ payload, configuration_hash = _agent_payload(agent_definition)
349
+ search = payload.get("assistant_id") or agent_definition.name
350
+ query = urllib.parse.urlencode(
351
+ {"search": search, "agent_type": "voice", "limit": 100}
352
+ )
353
+ listing = _request_json(f"{base}{_AGENT_LIST_PATH}?{query}", headers)
354
+ existing = next(
355
+ (
356
+ item
357
+ for item in _rows(listing)
358
+ if isinstance(item, Mapping) and _logical_agent_match(item, payload)
359
+ ),
360
+ None,
361
+ )
362
+ if existing is not None:
363
+ agent_id = str(_field(existing, "id") or "")
364
+ if not agent_id:
365
+ raise ScenarioGenerationError(
366
+ "reused platform Agent Definition has no agent id"
367
+ )
368
+ detail = _request_json(f"{base}{_AGENT_LIST_PATH}{agent_id}/", headers)
369
+ active = _active_version(detail) if isinstance(detail, Mapping) else None
370
+ if active is None:
371
+ raise ScenarioGenerationError(
372
+ "reused platform Agent Definition has no active version"
373
+ )
374
+ version_id = str(active["id"])
375
+ if str(active.get("commit_message") or "") == _configuration_commit(
376
+ configuration_hash
377
+ ):
378
+ return PlatformAgentReference(
379
+ agent_definition_id=agent_id,
380
+ agent_version_id=version_id,
381
+ configuration_hash=configuration_hash,
382
+ reused=True,
383
+ )
384
+ created_version = _request_json(
385
+ f"{base}{_AGENT_VERSION_CREATE_PATH.format(agent_id=agent_id)}",
386
+ headers,
387
+ method="POST",
388
+ payload=payload,
389
+ )
390
+ version = (
391
+ created_version.get("version")
392
+ if isinstance(created_version, Mapping)
393
+ else None
394
+ )
395
+ version_id = (
396
+ str(_field(version, "id") or "")
397
+ if isinstance(version, Mapping)
398
+ else ""
399
+ )
400
+ if not version_id:
401
+ raise ScenarioGenerationError(
402
+ "platform Agent Version response did not include a version id"
403
+ )
404
+ return PlatformAgentReference(
405
+ agent_definition_id=agent_id,
406
+ agent_version_id=version_id,
407
+ configuration_hash=configuration_hash,
408
+ reused=False,
409
+ )
410
+
411
+ created = _request_json(
412
+ f"{base}{_AGENT_CREATE_PATH}",
413
+ headers,
414
+ method="POST",
415
+ payload=payload,
416
+ )
417
+ agent = created.get("agent") if isinstance(created, Mapping) else None
418
+ agent_id = str(_field(agent, "id") or "") if isinstance(agent, Mapping) else ""
419
+ if not agent_id:
420
+ raise ScenarioGenerationError(
421
+ "platform Agent Definition response did not include an agent id"
422
+ )
423
+ detail = _request_json(f"{base}{_AGENT_LIST_PATH}{agent_id}/", headers)
424
+ version_id = _active_version_id(detail) if isinstance(detail, Mapping) else None
425
+ if not version_id:
426
+ raise ScenarioGenerationError(
427
+ "created platform Agent Definition has no active version"
428
+ )
429
+ return PlatformAgentReference(
430
+ agent_definition_id=agent_id,
431
+ agent_version_id=version_id,
432
+ configuration_hash=configuration_hash,
433
+ reused=False,
434
+ )
435
+
436
+
437
+ def _extract_scenario(payload: Any) -> Mapping[str, Any]:
438
+ if not isinstance(payload, Mapping):
439
+ return {}
440
+ scenario = payload.get("scenario")
441
+ if isinstance(scenario, Mapping):
442
+ return scenario
443
+ result = payload.get("result")
444
+ if isinstance(result, Mapping):
445
+ nested = result.get("scenario")
446
+ return nested if isinstance(nested, Mapping) else result
447
+ return payload
448
+
449
+
450
+ def _completed_scenario(
451
+ detail: Mapping[str, Any],
452
+ *,
453
+ agent_definition_id: str,
454
+ agent_version_id: str | None,
455
+ polling_duration_seconds: float,
456
+ base: str,
457
+ headers: Mapping[str, str],
458
+ ) -> GeneratedScenario:
459
+ scenario_id = str(_field(detail, "id") or "")
460
+ dataset_id = str(_field(detail, "dataset_id") or _field(detail, "dataset") or "")
461
+ if not dataset_id:
462
+ raise ScenarioGenerationError(
463
+ "completed platform Scenario has no dataset id",
464
+ scenario_id=scenario_id,
465
+ status=str(_field(detail, "status") or "Completed"),
466
+ )
467
+ try:
468
+ rows = fetch_dataset_rows(base, headers, dataset_id)
469
+ except (
470
+ urllib.error.HTTPError,
471
+ urllib.error.URLError,
472
+ ScenarioDownloadError,
473
+ ) as exc:
474
+ raise ScenarioGenerationError(
475
+ "completed platform Scenario dataset could not be retrieved",
476
+ scenario_id=scenario_id,
477
+ status=str(_field(detail, "status") or "Completed"),
478
+ retryable=True,
479
+ ) from exc
480
+ artifact = {
481
+ "id": scenario_id,
482
+ "updated_at": _field(detail, "updated_at"),
483
+ "scenario": dict(detail),
484
+ "rows": rows,
485
+ }
486
+ try:
487
+ pin = validate_download(
488
+ artifact,
489
+ source=urllib.parse.urlsplit(base).netloc or base,
490
+ )
491
+ except DownloadRejected as exc:
492
+ raise ScenarioGenerationError(
493
+ "generated platform Scenario was rejected by the local content scan",
494
+ scenario_id=scenario_id,
495
+ status=str(_field(detail, "status") or "Completed"),
496
+ ) from exc
497
+ scenario = hydrate_platform_scenario(detail, rows, pin=pin)
498
+ return GeneratedScenario(
499
+ scenario=scenario,
500
+ platform_agent_definition_id=agent_definition_id,
501
+ platform_agent_version_id=agent_version_id,
502
+ platform_scenario_id=scenario_id,
503
+ platform_dataset_id=dataset_id,
504
+ platform_status=str(_field(detail, "status") or "Completed"),
505
+ polling_duration_seconds=polling_duration_seconds,
506
+ checksum_sha256=str(pin["checksum_sha256"]),
507
+ )
508
+
509
+
510
+ def fetch_scenario(
511
+ scenario_id: str,
512
+ *,
513
+ platform_agent_definition_id: str = "",
514
+ platform_agent_version_id: str | None = None,
515
+ poll_interval_seconds: float = 2.0,
516
+ timeout_seconds: float = 900.0,
517
+ config: Any | None = None,
518
+ ) -> GeneratedScenario:
519
+ if not scenario_id.strip():
520
+ raise ValueError("scenario_id must be non-empty")
521
+ if poll_interval_seconds <= 0 or timeout_seconds <= 0:
522
+ raise ValueError("poll interval and timeout must be greater than zero")
523
+ cfg = _required_config(config)
524
+ headers = _headers(cfg)
525
+ base = str(cfg.api_url).rstrip("/")
526
+ started = time.monotonic()
527
+ while True:
528
+ raw = _request_json(
529
+ f"{base}{_SCENARIO_DETAIL_PATH.format(scenario_id=scenario_id)}",
530
+ headers,
531
+ )
532
+ detail = _extract_scenario(raw)
533
+ status = str(_field(detail, "status") or "").strip()
534
+ normalized_status = status.lower()
535
+ elapsed = time.monotonic() - started
536
+ if normalized_status == "completed":
537
+ return _completed_scenario(
538
+ detail,
539
+ agent_definition_id=platform_agent_definition_id,
540
+ agent_version_id=platform_agent_version_id,
541
+ polling_duration_seconds=elapsed,
542
+ base=base,
543
+ headers=headers,
544
+ )
545
+ if normalized_status in _TERMINAL_FAILURE_STATUSES:
546
+ raise ScenarioGenerationError(
547
+ f"platform Scenario ended with status {status}",
548
+ scenario_id=scenario_id,
549
+ status=status,
550
+ )
551
+ if elapsed >= timeout_seconds:
552
+ raise ScenarioGenerationError(
553
+ "platform Scenario generation timed out; resume with fetch_scenario",
554
+ scenario_id=scenario_id,
555
+ status=status or "Processing",
556
+ retryable=True,
557
+ )
558
+ time.sleep(min(poll_interval_seconds, timeout_seconds - elapsed))
559
+
560
+
561
+ def generate_scenario(
562
+ request: PlatformScenarioRequest,
563
+ *,
564
+ config: Any | None = None,
565
+ ) -> GeneratedScenario:
566
+ cfg = _required_config(config)
567
+ if request.agent_definition is not None:
568
+ agent = ensure_platform_agent(request.agent_definition, config=cfg)
569
+ agent_definition_id = agent.agent_definition_id
570
+ agent_version_id: str | None = agent.agent_version_id
571
+ else:
572
+ agent_definition_id = str(request.platform_agent_definition_id)
573
+ agent_version_id = request.platform_agent_version_id
574
+
575
+ payload: dict[str, Any] = {
576
+ "name": request.name.strip(),
577
+ "kind": request.kind,
578
+ "generate_graph": True,
579
+ "agent_definition_id": agent_definition_id,
580
+ "no_of_rows": request.no_of_rows,
581
+ }
582
+ if agent_version_id:
583
+ payload["agent_definition_version_id"] = agent_version_id
584
+ if request.description is not None:
585
+ payload["description"] = request.description
586
+ if request.custom_instruction is not None:
587
+ payload["custom_instruction"] = request.custom_instruction
588
+ if scan_content(payload)["status"] == "flagged":
589
+ raise ScenarioGenerationError(
590
+ "scenario generation request was rejected by the local content and secret scan"
591
+ )
592
+
593
+ headers = _headers(cfg)
594
+ base = str(cfg.api_url).rstrip("/")
595
+ query = urllib.parse.urlencode(
596
+ {
597
+ "search": request.name.strip(),
598
+ "agent_definition_id": agent_definition_id,
599
+ "limit": 100,
600
+ }
601
+ )
602
+ listing = _request_json(f"{base}{_SCENARIO_LIST_PATH}?{query}", headers)
603
+ existing = next(
604
+ (
605
+ item
606
+ for item in _rows(listing)
607
+ if str(_field(item, "name") or "") == request.name.strip()
608
+ ),
609
+ None,
610
+ )
611
+ if existing is not None:
612
+ scenario_id = str(_field(existing, "id") or "")
613
+ if scenario_id:
614
+ return fetch_scenario(
615
+ scenario_id,
616
+ platform_agent_definition_id=agent_definition_id,
617
+ platform_agent_version_id=agent_version_id,
618
+ poll_interval_seconds=request.poll_interval_seconds,
619
+ timeout_seconds=request.timeout_seconds,
620
+ config=cfg,
621
+ )
622
+ created = _request_json(
623
+ f"{base}{_SCENARIO_CREATE_PATH}",
624
+ headers,
625
+ method="POST",
626
+ payload=payload,
627
+ )
628
+ scenario = _extract_scenario(created)
629
+ scenario_id = str(_field(scenario, "id") or "")
630
+ if not scenario_id:
631
+ raise ScenarioGenerationError(
632
+ "platform Scenario response did not include a scenario id"
633
+ )
634
+ return fetch_scenario(
635
+ scenario_id,
636
+ platform_agent_definition_id=agent_definition_id,
637
+ platform_agent_version_id=agent_version_id,
638
+ poll_interval_seconds=request.poll_interval_seconds,
639
+ timeout_seconds=request.timeout_seconds,
640
+ config=cfg,
641
+ )
642
+
643
+
644
+ __all__ = [
645
+ "GeneratedScenario",
646
+ "PlatformAgentReference",
647
+ "PlatformScenarioRequest",
648
+ "ScenarioGenerationError",
649
+ "ensure_platform_agent",
650
+ "fetch_scenario",
651
+ "generate_scenario",
652
+ ]