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,185 @@
1
+ """Unit 2 (BBG U2 / ARCH §1.9 G3) — the executable goal/verification binding.
2
+
3
+ This module makes the EXISTING ``ScenarioGoal``/``VerificationSpec`` types
4
+ executable at three rungs (turn / settle / run). It imports ONLY from
5
+ ``models.py`` and stdlib (the engine-side one-way rule). The closed vocabularies
6
+ declared here are the single canonical home; ``contract.py`` and ``trinity.py``
7
+ mirror them, never redeclare them (ARCH §3).
8
+
9
+ Everything is deterministic: no model calls, no wall-clock, sorted iteration.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, Dict, List, Mapping, Optional, Sequence
14
+
15
+ from .models import ScenarioGoal, VerificationSpec
16
+
17
+ # --- canon (ARCH §3 / §2a; R5/A7 STAGED — the v1 5-kind set is frozen) -------
18
+ GOAL_CHECK_KINDS = (
19
+ "state_predicate",
20
+ "world_invariant",
21
+ "world_success_condition",
22
+ "eval_template",
23
+ "keyword_fallback",
24
+ )
25
+ GOAL_PREDICATE_OPS = ("eq", "ne", "gte", "lte", "contains", "exists")
26
+ GOAL_CHECK_RUNGS = ("turn", "settle", "run")
27
+
28
+
29
+ def _resolve_path(state: Mapping[str, Any], path: str) -> tuple[bool, Any]:
30
+ """Dotted-path resolution over a nested mapping. Returns (found, value)."""
31
+ cursor: Any = state
32
+ for part in str(path).split("."):
33
+ if isinstance(cursor, Mapping) and part in cursor:
34
+ cursor = cursor[part]
35
+ else:
36
+ return False, None
37
+ return True, cursor
38
+
39
+
40
+ def _eval_predicate(check: Mapping[str, Any], state: Mapping[str, Any]) -> bool:
41
+ """Evaluate a ``state_predicate`` {path, op, value} over environment_state.
42
+ Missing path ⇒ False except ``exists`` (which reports presence)."""
43
+ path = check.get("path") or check.get("target")
44
+ op = str(check.get("op") or "eq")
45
+ expected = check.get("value")
46
+ found, actual = _resolve_path(state, str(path)) if path is not None else (False, None)
47
+ if op == "exists":
48
+ return bool(found)
49
+ if not found:
50
+ return False
51
+ try:
52
+ if op == "eq":
53
+ return actual == expected
54
+ if op == "ne":
55
+ return actual != expected
56
+ if op == "gte":
57
+ return float(actual) >= float(expected)
58
+ if op == "lte":
59
+ return float(actual) <= float(expected)
60
+ if op == "contains":
61
+ if isinstance(actual, (str, list, tuple, dict)):
62
+ return expected in actual
63
+ return False
64
+ except (TypeError, ValueError):
65
+ return False
66
+ return False
67
+
68
+
69
+ def _world_status_pass(world_status: Mapping[str, Any], name: str, *, condition_type: str) -> Optional[bool]:
70
+ """Bind a world_invariant/world_success_condition check BY NAME against the
71
+ WorldContractEnvironment status the engine already holds in
72
+ ``environment_state['world_contract']`` (invariant_results/success_results).
73
+ Returns the pass bool, or None if no such named condition is present."""
74
+ key = "invariant_results" if condition_type == "world_invariant" else "success_results"
75
+ results = world_status.get(key) or []
76
+ for entry in results:
77
+ if not isinstance(entry, Mapping):
78
+ continue
79
+ if str(entry.get("name") or entry.get("id") or "") == name:
80
+ return bool(entry.get("pass"))
81
+ return None
82
+
83
+
84
+ def _checks_at_rung(verification: Optional[VerificationSpec], rung: str) -> List[Mapping[str, Any]]:
85
+ if verification is None:
86
+ return []
87
+ out: List[Mapping[str, Any]] = []
88
+ for raw in verification.checks:
89
+ if not isinstance(raw, Mapping):
90
+ continue
91
+ if str(raw.get("rung") or "turn") == rung:
92
+ out.append(raw)
93
+ return out
94
+
95
+
96
+ def _evaluate_rung(
97
+ goal: ScenarioGoal,
98
+ verification: Optional[VerificationSpec],
99
+ rung: str,
100
+ *,
101
+ environment_state: Mapping[str, Any],
102
+ world_status: Mapping[str, Any],
103
+ messages: Sequence[Mapping[str, Any]],
104
+ ) -> Dict[str, Any]:
105
+ """Evaluate all checks at ``rung``; resolve state_predicate / world_* checks.
106
+ eval_template / keyword_fallback are rung-run/legacy and skipped here."""
107
+ states_reached: List[str] = []
108
+ check_records: List[Dict[str, Any]] = []
109
+ for raw in sorted(_checks_at_rung(verification, rung), key=lambda c: str(c.get("name") or "")):
110
+ name = str(raw.get("name") or "")
111
+ kind = str(raw.get("kind") or "state_predicate")
112
+ if kind == "state_predicate":
113
+ passed: Optional[bool] = _eval_predicate(raw, environment_state)
114
+ elif kind in ("world_invariant", "world_success_condition"):
115
+ passed = _world_status_pass(world_status, name, condition_type=kind)
116
+ else:
117
+ # eval_template / keyword_fallback are not evaluated at turn/settle.
118
+ continue
119
+ record = {"name": name, "kind": kind, "rung": rung, "passed": bool(passed)}
120
+ check_records.append(record)
121
+ if passed and name and name in (goal.states or []):
122
+ states_reached.append(name)
123
+
124
+ stop: Optional[str] = None
125
+ success_state = goal.success_state
126
+ failure_states = set(goal.failure_states or [])
127
+ passed_names = {r["name"] for r in check_records if r["passed"]}
128
+ if success_state and success_state in passed_names:
129
+ stop = "goal_success"
130
+ elif failure_states & passed_names:
131
+ stop = "goal_failure"
132
+ return {"states_reached": states_reached, "stop": stop, "checks": check_records}
133
+
134
+
135
+ def evaluate_turn(
136
+ goal: ScenarioGoal,
137
+ verification: Optional[VerificationSpec],
138
+ *,
139
+ environment_state: Mapping[str, Any],
140
+ world_status: Optional[Mapping[str, Any]] = None,
141
+ messages: Optional[Sequence[Mapping[str, Any]]] = None,
142
+ ) -> Dict[str, Any]:
143
+ """Evaluate all rung-``turn`` checks after each observe."""
144
+ return _evaluate_rung(
145
+ goal, verification, "turn",
146
+ environment_state=environment_state,
147
+ world_status=world_status or environment_state.get("world_contract") or {},
148
+ messages=messages or [],
149
+ )
150
+
151
+
152
+ def evaluate_settle(
153
+ goal: ScenarioGoal,
154
+ verification: Optional[VerificationSpec],
155
+ *,
156
+ environment_state: Mapping[str, Any],
157
+ world_status: Optional[Mapping[str, Any]] = None,
158
+ messages: Optional[Sequence[Mapping[str, Any]]] = None,
159
+ ) -> Dict[str, Any]:
160
+ """Same shape over rung-``settle`` checks at episode end."""
161
+ return _evaluate_rung(
162
+ goal, verification, "settle",
163
+ environment_state=environment_state,
164
+ world_status=world_status or environment_state.get("world_contract") or {},
165
+ messages=messages or [],
166
+ )
167
+
168
+
169
+ def evaluate_run(
170
+ goal: ScenarioGoal,
171
+ verification: Optional[VerificationSpec],
172
+ *,
173
+ environment_state: Optional[Mapping[str, Any]] = None,
174
+ world_status: Optional[Mapping[str, Any]] = None,
175
+ messages: Optional[Sequence[Mapping[str, Any]]] = None,
176
+ ) -> Dict[str, Any]:
177
+ """Rung-``run`` summary consumed later by loss.py — pure data, no eval
178
+ execution here (eval templates run in the existing evaluation lineage; the
179
+ goal machine only *names* them)."""
180
+ names = sorted(
181
+ str(c.get("name") or "")
182
+ for c in _checks_at_rung(verification, "run")
183
+ if isinstance(c, Mapping)
184
+ )
185
+ return {"rung": "run", "named_checks": names, "success_state": goal.success_state}
@@ -0,0 +1,467 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from types import ModuleType
8
+
9
+ import aiohttp
10
+
11
+ try:
12
+ from livekit.agents import llm as livekit_llm
13
+ from livekit.agents import stt as livekit_stt
14
+ from livekit.agents import tts as livekit_tts
15
+ except ImportError as exc:
16
+ raise ImportError(
17
+ "LiveKit model construction requires the 'livekit' optional dependency"
18
+ ) from exc
19
+
20
+ from fi.simulate.agent.definition import LLMConfig, STTConfig, TTSConfig
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ @dataclass
26
+ class LiveKitModels:
27
+ stt: livekit_stt.STT
28
+ llm: livekit_llm.LLM
29
+ tts: livekit_tts.TTS
30
+ http_session: aiohttp.ClientSession | None = None
31
+
32
+ async def aclose(self) -> None:
33
+ if self.http_session is not None and not self.http_session.closed:
34
+ await self.http_session.close()
35
+
36
+
37
+ STTFactory = Callable[[STTConfig, aiohttp.ClientSession | None], livekit_stt.STT]
38
+ LLMFactory = Callable[[LLMConfig], livekit_llm.LLM]
39
+ TTSFactory = Callable[[TTSConfig, aiohttp.ClientSession | None], livekit_tts.TTS]
40
+
41
+
42
+ def _import_plugin(name: str) -> ModuleType:
43
+ try:
44
+ import importlib
45
+
46
+ return importlib.import_module(f"livekit.plugins.{name}")
47
+ except ImportError:
48
+ raise ImportError(
49
+ f"livekit-plugins-{name} is not installed. "
50
+ f"Install it with: pip install livekit-plugins-{name}"
51
+ ) from None
52
+
53
+
54
+ def _openai_llm(config: LLMConfig) -> livekit_llm.LLM:
55
+ openai = _import_plugin("openai")
56
+ kwargs: dict[str, object] = {
57
+ "model": config.model,
58
+ "temperature": config.temperature,
59
+ }
60
+ api_key = os.environ.get("SIMULATOR_LLM_API_KEY") or os.environ.get(
61
+ "OPENAI_API_KEY"
62
+ )
63
+ base_url = os.environ.get("SIMULATOR_LLM_BASE_URL") or os.environ.get(
64
+ "OPENAI_BASE_URL"
65
+ )
66
+ if api_key:
67
+ kwargs["api_key"] = api_key
68
+ if base_url:
69
+ kwargs["base_url"] = base_url.rstrip("/")
70
+ header_name = os.environ.get("SIMULATOR_LLM_API_KEY_HEADER")
71
+ if api_key and header_name:
72
+ kwargs["extra_headers"] = {header_name: api_key}
73
+ return openai.LLM(**kwargs)
74
+
75
+
76
+ def _openai_stt(
77
+ config: STTConfig,
78
+ _http_session: aiohttp.ClientSession | None,
79
+ ) -> livekit_stt.STT:
80
+ openai = _import_plugin("openai")
81
+ return openai.STT(
82
+ model=config.model,
83
+ language=config.language or "en",
84
+ )
85
+
86
+
87
+ def _elevenlabs_stt(
88
+ config: STTConfig,
89
+ http_session: aiohttp.ClientSession | None,
90
+ ) -> livekit_stt.STT:
91
+ elevenlabs = _import_plugin("elevenlabs")
92
+ return elevenlabs.STT(
93
+ api_key=_required_env("ELEVEN_API_KEY", "ELEVENLABS_API_KEY"),
94
+ http_session=http_session,
95
+ model_id=_provider_model(
96
+ config.model,
97
+ default="gpt-4o-mini-transcribe",
98
+ replacement="scribe_v2_realtime",
99
+ ),
100
+ server_vad={
101
+ "vad_silence_threshold_secs": 0.8,
102
+ "vad_threshold": 0.4,
103
+ "min_speech_duration_ms": 100,
104
+ "min_silence_duration_ms": 500,
105
+ },
106
+ )
107
+
108
+
109
+ def _deepgram_stt(
110
+ config: STTConfig,
111
+ http_session: aiohttp.ClientSession | None,
112
+ ) -> livekit_stt.STT:
113
+ deepgram = _import_plugin("deepgram")
114
+ return deepgram.STT(
115
+ api_key=_required_env("DEEPGRAM_API_KEY"),
116
+ http_session=http_session,
117
+ model=_provider_model(
118
+ config.model,
119
+ default="gpt-4o-mini-transcribe",
120
+ replacement="nova-3",
121
+ ),
122
+ language=config.language or "en-US",
123
+ )
124
+
125
+
126
+ def _cartesia_stt(
127
+ config: STTConfig,
128
+ http_session: aiohttp.ClientSession | None,
129
+ ) -> livekit_stt.STT:
130
+ cartesia = _import_plugin("cartesia")
131
+ return cartesia.STT(
132
+ api_key=_required_env("CARTESIA_API_KEY"),
133
+ http_session=http_session,
134
+ model=_provider_model(
135
+ config.model,
136
+ default="gpt-4o-mini-transcribe",
137
+ replacement="ink-2",
138
+ ),
139
+ language=config.language or "en",
140
+ )
141
+
142
+
143
+ def _openai_tts(
144
+ config: TTSConfig,
145
+ _http_session: aiohttp.ClientSession | None,
146
+ ) -> livekit_tts.TTS:
147
+ openai = _import_plugin("openai")
148
+ return openai.TTS(model=config.model, voice=config.voice)
149
+
150
+
151
+ def _elevenlabs_tts(
152
+ config: TTSConfig,
153
+ http_session: aiohttp.ClientSession | None,
154
+ ) -> livekit_tts.TTS:
155
+ elevenlabs = _import_plugin("elevenlabs")
156
+ return elevenlabs.TTS(
157
+ api_key=_required_env("ELEVEN_API_KEY", "ELEVENLABS_API_KEY"),
158
+ http_session=http_session,
159
+ model=_provider_model(
160
+ config.model,
161
+ default="gpt-4o-mini-tts",
162
+ replacement="eleven_flash_v2_5",
163
+ ),
164
+ voice_id=config.voice,
165
+ )
166
+
167
+
168
+ def _deepgram_tts(
169
+ config: TTSConfig,
170
+ http_session: aiohttp.ClientSession | None,
171
+ ) -> livekit_tts.TTS:
172
+ deepgram = _import_plugin("deepgram")
173
+ return deepgram.TTS(
174
+ api_key=_required_env("DEEPGRAM_API_KEY"),
175
+ http_session=http_session,
176
+ model=_provider_model(
177
+ config.model,
178
+ default="gpt-4o-mini-tts",
179
+ replacement="aura-2-andromeda-en",
180
+ ),
181
+ )
182
+
183
+
184
+ def _cartesia_tts(
185
+ config: TTSConfig,
186
+ http_session: aiohttp.ClientSession | None,
187
+ ) -> livekit_tts.TTS:
188
+ cartesia = _import_plugin("cartesia")
189
+ voice = (
190
+ config.voice
191
+ if config.voice not in {"alloy", ""}
192
+ else "f786b574-daa5-4673-aa0c-cbe3e8534c02"
193
+ )
194
+ # Both ride on Cartesia's ``__experimental_controls``. sonic-3 rejects a wrong emotion name or
195
+ # level with HTTP 400, and rejects the plugin's own TTSVoiceEmotion vocabulary, so the values
196
+ # come from a set validated against the live API rather than from the plugin's types.
197
+ speed = config.speed
198
+ emotion = config.emotion
199
+ # The only record of what the simulator actually sounded like: call_metadata reports a constant
200
+ # speed and voice name whatever it was given.
201
+ logger.info(
202
+ "cartesia_tts voice=%s speed=%s emotion=%s", voice, speed, emotion or None
203
+ )
204
+ return cartesia.TTS(
205
+ api_key=_required_env("CARTESIA_API_KEY"),
206
+ http_session=http_session,
207
+ model=_provider_model(
208
+ config.model,
209
+ default="gpt-4o-mini-tts",
210
+ replacement="sonic-3",
211
+ ),
212
+ voice=voice,
213
+ **({"speed": float(speed)} if isinstance(speed, (int, float)) else {}),
214
+ **({"emotion": list(emotion)} if emotion else {}),
215
+ )
216
+
217
+
218
+ def _google_credentials_kwargs() -> dict[str, object]:
219
+ """Pick Vertex AI vs Gemini API from env — Vertex when possible.
220
+
221
+ Vertex is preferred: it has higher throughput and uses ADC so the
222
+ key never lives in the SDK process. Falls back to the direct Gemini
223
+ API when only ``GEMINI_API_KEY`` (or ``GOOGLE_API_KEY``) is set.
224
+ """
225
+
226
+ project = os.environ.get("GOOGLE_CLOUD_PROJECT") or os.environ.get("VERTEX_PROJECT")
227
+ location = os.environ.get("GOOGLE_CLOUD_LOCATION") or os.environ.get(
228
+ "VERTEX_LOCATION",
229
+ "us-central1",
230
+ )
231
+ credentials = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
232
+ if project and credentials:
233
+ return {"vertexai": True, "project": project, "location": location}
234
+ api_key = os.environ.get("GEMINI_API_KEY") or os.environ.get("GOOGLE_API_KEY")
235
+ if api_key:
236
+ return {"vertexai": False, "api_key": api_key}
237
+ raise ValueError(
238
+ "google_credentials_missing: set GOOGLE_APPLICATION_CREDENTIALS + "
239
+ "GOOGLE_CLOUD_PROJECT for Vertex or GEMINI_API_KEY for Gemini API"
240
+ )
241
+
242
+
243
+ def _google_speech_credentials_kwargs() -> dict[str, object]:
244
+ credentials = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
245
+ if credentials:
246
+ return {"credentials_file": credentials}
247
+ raise ValueError(
248
+ "google_cloud_speech_credentials_missing: set "
249
+ "GOOGLE_APPLICATION_CREDENTIALS for Google STT/TTS"
250
+ )
251
+
252
+
253
+ def _google_llm(config: LLMConfig) -> livekit_llm.LLM:
254
+ google = _import_plugin("google")
255
+ kwargs = _google_credentials_kwargs()
256
+ model = _provider_model(
257
+ config.model,
258
+ default="gpt-4o",
259
+ replacement="gemini-2.5-flash-lite",
260
+ )
261
+ if (
262
+ kwargs.get("vertexai") is True
263
+ and model.startswith("gemini-3")
264
+ and not os.environ.get("GOOGLE_CLOUD_LOCATION")
265
+ and not os.environ.get("VERTEX_LOCATION")
266
+ ):
267
+ kwargs["location"] = "global"
268
+ thinking = _simulator_thinking(model)
269
+ if thinking is not None:
270
+ kwargs["thinking_config"] = thinking
271
+ return google.LLM(model=model, temperature=config.temperature, **kwargs)
272
+
273
+
274
+ # Which control a model accepts for deliberation is a provider fact and cannot be inferred from a
275
+ # version inside its name. Gemini 2.5 and earlier take a token budget and reject a level outright;
276
+ # Gemini 3 takes a level, and the LiveKit plugin answers a budget it cannot use by substituting its
277
+ # own "minimal", which Vertex rejects. A model in neither list is left alone deliberately: running
278
+ # at the provider's own default costs some latency, where sending a control it rejects costs every
279
+ # inference and leaves the caller silent for the whole call with nothing in the transcript to say
280
+ # why. So a model nobody has characterised yet still holds a conversation.
281
+ _THINKING_BY_BUDGET = ("gemini-1.5", "gemini-2.0", "gemini-2.5")
282
+ _THINKING_BY_LEVEL = ("gemini-3",)
283
+ # The least a level-taking model will accept. "minimal" exists in the plugin but Vertex refuses it.
284
+ _LEAST_THINKING_LEVEL = "low"
285
+
286
+
287
+ def _simulator_thinking(model: str) -> dict[str, object] | None:
288
+ """How much the simulated caller deliberates before answering.
289
+
290
+ A person on a phone call answers from what they already know, so thinking buys nothing here and
291
+ is charged twice: once in latency the target hears as an unnatural pause, and again in a call
292
+ whose duration no longer reflects how the conversation actually went. As near off as the model
293
+ allows. `SIMULATOR_LLM_THINKING` takes a thinking level, or a token budget as a number, and an
294
+ explicit request is honoured even on a model this module does not recognise.
295
+ """
296
+ asked = os.environ.get("SIMULATOR_LLM_THINKING", "").strip().lower()
297
+ off = asked in ("", "off", "0", "none", "false")
298
+ if model.startswith(_THINKING_BY_LEVEL):
299
+ return {"thinking_level": _LEAST_THINKING_LEVEL if off or asked.isdigit() else asked}
300
+ if model.startswith(_THINKING_BY_BUDGET):
301
+ if off:
302
+ return {"thinking_budget": 0}
303
+ return {"thinking_budget": int(asked)} if asked.isdigit() else {"thinking_level": asked}
304
+ if off:
305
+ return None
306
+ return {"thinking_budget": int(asked)} if asked.isdigit() else {"thinking_level": asked}
307
+
308
+
309
+ def _google_stt(
310
+ config: STTConfig,
311
+ _http_session: aiohttp.ClientSession | None,
312
+ ) -> livekit_stt.STT:
313
+ google = _import_plugin("google")
314
+ kwargs = _google_speech_credentials_kwargs()
315
+ languages = [
316
+ language.strip()
317
+ for language in (config.language or "en-US").split(",")
318
+ if language.strip()
319
+ ]
320
+ # Non-streaming mode lets AgentSession's VAD-backed StreamAdapter defer
321
+ # the Google request until speech exists. This avoids Google's 400
322
+ # "Long duration elapsed without audio" during agent-first quiet gaps.
323
+ return google.STT(
324
+ languages=languages or ["en-US"],
325
+ use_streaming=False,
326
+ **kwargs,
327
+ )
328
+
329
+
330
+ def _google_tts(
331
+ config: TTSConfig,
332
+ _http_session: aiohttp.ClientSession | None,
333
+ ) -> livekit_tts.TTS:
334
+ from google.cloud import texttospeech
335
+
336
+ google = _import_plugin("google")
337
+ kwargs = _google_speech_credentials_kwargs()
338
+ voice = (
339
+ config.voice if config.voice not in {"alloy", ""} else "en-US-Chirp3-HD-Kore"
340
+ )
341
+ language = "-".join(voice.split("-")[:2]) if "-" in voice else "en-US"
342
+ return google.TTS(
343
+ voice_name=voice,
344
+ language=language,
345
+ audio_encoding=texttospeech.AudioEncoding.LINEAR16,
346
+ **kwargs,
347
+ )
348
+
349
+
350
+ def _gemini_tts(
351
+ config: TTSConfig,
352
+ _http_session: aiohttp.ClientSession | None,
353
+ ) -> livekit_tts.TTS:
354
+ """Streaming Gemini TTS over the genai/Vertex endpoint (multilingual).
355
+
356
+ Cloud TTS (``_google_tts``) needs the Text-to-Speech API the Vertex SA
357
+ lacks; the genai speech endpoint works with the same ADC and streams audio
358
+ for gemini-3.1, keeping time-to-first-byte low.
359
+ """
360
+ from fi.simulate.simulation.gemini_tts_stream import StreamingGeminiTTS
361
+
362
+ kwargs = _google_credentials_kwargs()
363
+ model = _provider_model(
364
+ config.model,
365
+ default="gpt-4o-mini-tts",
366
+ replacement="gemini-3.1-flash-tts-preview",
367
+ )
368
+ if not model.startswith("gemini-"):
369
+ model = "gemini-3.1-flash-tts-preview"
370
+ voice = config.voice if config.voice not in {"alloy", ""} else "Kore"
371
+ if kwargs.get("vertexai") is True:
372
+ location = kwargs.get("location")
373
+ if model.startswith("gemini-3"):
374
+ location = "global"
375
+ return StreamingGeminiTTS(
376
+ model=model,
377
+ voice_name=voice,
378
+ vertexai=True,
379
+ project=kwargs.get("project"),
380
+ location=location,
381
+ )
382
+ return StreamingGeminiTTS(model=model, voice_name=voice, api_key=kwargs["api_key"])
383
+
384
+
385
+ _LLM_FACTORIES: dict[str, LLMFactory] = {
386
+ "openai": _openai_llm,
387
+ "openai_compatible": _openai_llm,
388
+ "google": _google_llm,
389
+ "vertex": _google_llm,
390
+ "gemini": _google_llm,
391
+ }
392
+ _STT_FACTORIES: dict[str, STTFactory] = {
393
+ "openai": _openai_stt,
394
+ "elevenlabs": _elevenlabs_stt,
395
+ "deepgram": _deepgram_stt,
396
+ "cartesia": _cartesia_stt,
397
+ "google": _google_stt,
398
+ "vertex": _google_stt,
399
+ }
400
+ _TTS_FACTORIES: dict[str, TTSFactory] = {
401
+ "openai": _openai_tts,
402
+ "elevenlabs": _elevenlabs_tts,
403
+ "deepgram": _deepgram_tts,
404
+ "cartesia": _cartesia_tts,
405
+ "google": _google_tts,
406
+ "vertex": _google_tts,
407
+ "gemini": _gemini_tts,
408
+ "gemini_tts": _gemini_tts,
409
+ }
410
+ _HTTP_PROVIDERS = {"cartesia", "deepgram", "elevenlabs"}
411
+
412
+
413
+ def build_livekit_llm(config: LLMConfig) -> livekit_llm.LLM:
414
+ provider = config.provider.lower()
415
+ factory = _factory(_LLM_FACTORIES, provider, "LLM")
416
+ return factory(config)
417
+
418
+
419
+ async def build_livekit_models(
420
+ *,
421
+ llm_config: LLMConfig,
422
+ stt_config: STTConfig,
423
+ tts_config: TTSConfig,
424
+ ) -> LiveKitModels:
425
+ stt_provider = stt_config.provider.lower()
426
+ tts_provider = tts_config.provider.lower()
427
+ stt_factory = _factory(_STT_FACTORIES, stt_provider, "STT")
428
+ tts_factory = _factory(_TTS_FACTORIES, tts_provider, "TTS")
429
+ http_session = (
430
+ aiohttp.ClientSession()
431
+ if {stt_provider, tts_provider} & _HTTP_PROVIDERS
432
+ else None
433
+ )
434
+ try:
435
+ return LiveKitModels(
436
+ stt=stt_factory(stt_config, http_session),
437
+ llm=build_livekit_llm(llm_config),
438
+ tts=tts_factory(tts_config, http_session),
439
+ http_session=http_session,
440
+ )
441
+ except Exception:
442
+ if http_session is not None:
443
+ await http_session.close()
444
+ raise
445
+
446
+
447
+ def _factory(factories: dict[str, Callable], provider: str, model_type: str):
448
+ factory = factories.get(provider)
449
+ if factory is None:
450
+ supported = ", ".join(sorted(factories))
451
+ raise ValueError(
452
+ f"Unsupported LiveKit {model_type} provider: {provider!r}. "
453
+ f"Supported: {supported}"
454
+ )
455
+ return factory
456
+
457
+
458
+ def _required_env(*names: str) -> str:
459
+ for name in names:
460
+ value = os.environ.get(name)
461
+ if value:
462
+ return value
463
+ raise ValueError(f"Missing provider credential: {' or '.join(names)}")
464
+
465
+
466
+ def _provider_model(configured: str, *, default: str, replacement: str) -> str:
467
+ return replacement if configured == default else configured