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,138 @@
1
+ """LiveKit target-agent adapter.
2
+
3
+ Thin adapter around the existing ``LiveKitEngine`` that lets hosted +
4
+ matrix runners see a LiveKit target through the ``AgentEndpoint``
5
+ contract. The actual media/lifecycle work still lives inside the
6
+ engine's per-case runner; this class exposes a stable seam so a full
7
+ ``endpoints/livekit.py`` rewrite can land later without breaking
8
+ callers.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import uuid
14
+ from collections.abc import AsyncIterator
15
+ from datetime import datetime, timezone
16
+
17
+ from fi.simulate.agent.definition import AgentDefinition, TelephonyTransport
18
+ from fi.simulate.realtime.events import RealtimeEvent
19
+ from fi.simulate.realtime.media import AudioFrame
20
+ from fi.simulate.runtime.capabilities import EndpointCapabilities
21
+
22
+ from .base import (
23
+ AgentEndpointManifest,
24
+ DiscoveryRequest,
25
+ DiscoverySnapshot,
26
+ EndpointHandle,
27
+ ReadinessResult,
28
+ ReconciliationResult,
29
+ )
30
+
31
+
32
+ def _capabilities_for(agent_definition: AgentDefinition) -> EndpointCapabilities:
33
+ transport = agent_definition.transport or TelephonyTransport()
34
+ kind = transport.kind
35
+ return EndpointCapabilities(
36
+ audio=True,
37
+ text=True,
38
+ streaming=True,
39
+ interruption=True,
40
+ dtmf=kind in {"sip_inbound", "sip_outbound"},
41
+ transfer=kind in {"sip_inbound", "sip_outbound"},
42
+ transcript_events=True,
43
+ tool_events=False,
44
+ usage_events=True,
45
+ internal_metrics=True,
46
+ recording=True,
47
+ web_rtc=kind == "webrtc",
48
+ sip=kind in {"sip_inbound", "sip_outbound"},
49
+ )
50
+
51
+
52
+ class LiveKitAgentEndpoint:
53
+ """Spec-level LiveKit endpoint. Engine still owns the media path."""
54
+
55
+ def __init__(self, agent_definition: AgentDefinition, *, name: str | None = None) -> None:
56
+ self._agent_definition = agent_definition
57
+ self.manifest = AgentEndpointManifest(
58
+ name=name or agent_definition.name,
59
+ provider="livekit",
60
+ world_kinds=["voice"],
61
+ capabilities=_capabilities_for(agent_definition),
62
+ metadata={
63
+ "url": str(agent_definition.url),
64
+ "room_name_template": agent_definition.room_name,
65
+ "room_mode": agent_definition.room_mode,
66
+ "transport": (
67
+ agent_definition.transport.kind
68
+ if agent_definition.transport
69
+ else "webrtc"
70
+ ),
71
+ },
72
+ )
73
+ self.capabilities = self.manifest.capabilities
74
+
75
+ @property
76
+ def agent_definition(self) -> AgentDefinition:
77
+ return self._agent_definition
78
+
79
+ async def discover(self, request: DiscoveryRequest) -> DiscoverySnapshot:
80
+ supported = self.capabilities.supported()
81
+ missing = [
82
+ cap for cap in request.required_capabilities if cap not in supported
83
+ ]
84
+ return DiscoverySnapshot(
85
+ capabilities=self.capabilities,
86
+ supported=not missing,
87
+ reasons=[f"unsupported:{cap}" for cap in missing],
88
+ )
89
+
90
+ async def prepare(self, plan) -> EndpointHandle: # noqa: ANN001
91
+ return EndpointHandle(
92
+ handle_id=f"lk-{uuid.uuid4().hex[:12]}",
93
+ endpoint_name=self.manifest.name,
94
+ created_at=datetime.now(timezone.utc),
95
+ metadata={
96
+ "plan_id": getattr(plan, "plan_id", None),
97
+ "transport": (
98
+ self._agent_definition.transport.kind
99
+ if self._agent_definition.transport
100
+ else "webrtc"
101
+ ),
102
+ },
103
+ )
104
+
105
+ async def wait_ready(self, handle: EndpointHandle) -> ReadinessResult:
106
+ del handle
107
+ # Real readiness is driven inside LiveKitEngine per case today.
108
+ # This adapter reports ready=True optimistically; hosted callers
109
+ # observe actual readiness through emitted CanonicalEvents.
110
+ return ReadinessResult(ready=True)
111
+
112
+ async def send(
113
+ self, handle: EndpointHandle, event: RealtimeEvent | AudioFrame
114
+ ) -> None:
115
+ raise NotImplementedError(
116
+ "LiveKitAgentEndpoint.send belongs to the future realtime router"
117
+ )
118
+
119
+ async def receive(
120
+ self, handle: EndpointHandle
121
+ ) -> AsyncIterator[RealtimeEvent | AudioFrame]:
122
+ raise NotImplementedError(
123
+ "LiveKitAgentEndpoint.receive belongs to the future realtime router"
124
+ )
125
+ yield # type: ignore[unreachable]
126
+
127
+ async def stop(self, handle: EndpointHandle) -> None:
128
+ del handle
129
+
130
+ async def cleanup(self, handle: EndpointHandle) -> None:
131
+ del handle
132
+
133
+ async def reconcile(self, handle: EndpointHandle) -> ReconciliationResult:
134
+ del handle
135
+ return ReconciliationResult(reconciled=True)
136
+
137
+
138
+ __all__ = ["LiveKitAgentEndpoint"]
@@ -0,0 +1,132 @@
1
+ """Provider-neutral call-origination surface — factory + cleanup helper.
2
+
3
+ Lives outside ``simulation/engines/livekit.py`` on purpose: that module
4
+ raises ``ImportError`` without the optional ``livekit`` extra (which this
5
+ SDK's dev venv lacks), so nothing inside it can be exercised by tests here;
6
+ this module has no such dependency, so it is fully testable (C3/C6).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ from dataclasses import dataclass
13
+ from datetime import datetime, timezone
14
+ from typing import Any, Callable, Protocol
15
+
16
+ from .retell import RetellCallOriginator
17
+ from .vapi import VapiCallOriginator
18
+
19
+
20
+ class CallOriginator(Protocol):
21
+ """The lifecycle every provider originator satisfies (C2/C3)."""
22
+
23
+ async def start(self) -> Any: ...
24
+
25
+ async def stop(self, call_id: str) -> None: ...
26
+
27
+ async def reconcile_and_stop(
28
+ self, *, started_after_ms: int, ended_before_ms: int
29
+ ) -> list[str]: ...
30
+
31
+ async def close(self) -> None: ...
32
+
33
+
34
+ def build_call_originator(transport: Any) -> CallOriginator:
35
+ """Construct the originator named by ``transport.inbound_call_originator``.
36
+
37
+ This is the one place a provider name is compared to select a class;
38
+ construction, start, stop and cleanup afterward stay provider-neutral.
39
+ """
40
+ name = transport.inbound_call_originator
41
+ if name == "vapi":
42
+ return VapiCallOriginator.from_env()
43
+ if name == "retell":
44
+ return RetellCallOriginator.from_env(transport)
45
+ raise ValueError(f"unsupported_inbound_call_originator: {name}")
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class OriginatorFinalizeResult:
50
+ """What cleanup did, so the engine can adapt it without deciding anything."""
51
+
52
+ termination_source: str | None
53
+ reconciled_call_ids: list[str]
54
+ cleanup_errors: list[tuple[str, Exception]]
55
+
56
+
57
+ async def finalize_originator(
58
+ originator: CallOriginator,
59
+ *,
60
+ provider_call_id: str | None,
61
+ originator_name: str,
62
+ case_started_at: datetime,
63
+ cleanup_timeout: float,
64
+ now: Callable[[], datetime] = lambda: datetime.now(timezone.utc),
65
+ ) -> OriginatorFinalizeResult:
66
+ """Best-effort originator cleanup: never raises (C3).
67
+
68
+ ``stop()`` ends the call we know about; with no call id in hand (a
69
+ cancelled ``asyncio.wait_for`` can still leave a live, billed call on
70
+ the provider's side) ``reconcile_and_stop`` hunts for the orphan instead.
71
+ ``close()`` always runs, even when the above raised.
72
+ """
73
+ termination_source: str | None = None
74
+ reconciled_call_ids: list[str] = []
75
+ cleanup_errors: list[tuple[str, Exception]] = []
76
+ try:
77
+ if provider_call_id is not None:
78
+ try:
79
+ await asyncio.wait_for(
80
+ originator.stop(provider_call_id), timeout=cleanup_timeout
81
+ )
82
+ termination_source = "sdk_originator_cleanup"
83
+ except Exception as exc: # noqa: BLE001 - best-effort cleanup, never raises
84
+ cleanup_errors.append((f"{originator_name}_call_stop", exc))
85
+ else:
86
+ # A naive datetime is read as local time by .timestamp(), silently
87
+ # widening the blast-radius window; treat it as UTC instead.
88
+ started = (
89
+ case_started_at
90
+ if case_started_at.tzinfo is not None
91
+ else case_started_at.replace(tzinfo=timezone.utc)
92
+ )
93
+ started_after_ms = int(started.timestamp() * 1000)
94
+ ended_before_ms = int(now().timestamp() * 1000)
95
+ try:
96
+ reconciled_call_ids = await asyncio.wait_for(
97
+ originator.reconcile_and_stop(
98
+ started_after_ms=started_after_ms,
99
+ ended_before_ms=ended_before_ms,
100
+ ),
101
+ timeout=cleanup_timeout,
102
+ )
103
+ if reconciled_call_ids:
104
+ termination_source = "sdk_originator_cleanup"
105
+ except TypeError as exc:
106
+ # A wrong call site (non-int epoch ms) fails loudly and
107
+ # distinguishably from a swallowed HTTP failure.
108
+ cleanup_errors.append(
109
+ (f"{originator_name}_call_reconcile_bad_arguments", exc)
110
+ )
111
+ except Exception as exc: # noqa: BLE001 - best-effort cleanup, never raises
112
+ cleanup_errors.append((f"{originator_name}_call_reconcile", exc))
113
+ finally:
114
+ try:
115
+ # Bounded like every other cleanup step, so a slow client can't
116
+ # stall this case past its cleanup budget.
117
+ await asyncio.wait_for(originator.close(), cleanup_timeout)
118
+ except Exception as exc: # noqa: BLE001 - best-effort cleanup, never raises
119
+ cleanup_errors.append((f"{originator_name}_call_close", exc))
120
+ return OriginatorFinalizeResult(
121
+ termination_source=termination_source,
122
+ reconciled_call_ids=reconciled_call_ids,
123
+ cleanup_errors=cleanup_errors,
124
+ )
125
+
126
+
127
+ __all__ = [
128
+ "CallOriginator",
129
+ "OriginatorFinalizeResult",
130
+ "build_call_originator",
131
+ "finalize_originator",
132
+ ]
@@ -0,0 +1,348 @@
1
+ """Target-endpoint profiles (canonical plan §4.1) — the factory that retires the
2
+ hardcoded ``transport.kind`` branches.
3
+
4
+ A profile is the single declarative record for one target adapter: its capability
5
+ manifest plus the *decisions* the voice engine used to make with ``if
6
+ transport.kind == ...`` chains — whether the leg is SIP, whether it places an
7
+ outbound call, whether it needs a web audio bridge, which provider evidence to
8
+ collect, and which connector to build. The engine keeps its session/audio
9
+ *mechanics*; it just asks the profile instead of branching on strings.
10
+
11
+ Adding a provider = registering one profile here (or via the
12
+ ``fi.simulate.endpoints`` entry-point group) with **zero engine edits**.
13
+
14
+ Import weight: connector / ``livekit.rtc`` imports are deferred into
15
+ ``build_connector`` so the planner can read ``profile.manifest.capabilities``
16
+ without pulling the optional voice stack.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from typing import Any, Callable, Optional
22
+
23
+ from fi.simulate.endpoints.actor_sources import (
24
+ resolve_factory,
25
+ resolve_framework,
26
+ resolve_http,
27
+ resolve_import_object,
28
+ resolve_python_callable,
29
+ resolve_system_prompt,
30
+ )
31
+ from fi.simulate.endpoints.base import AgentEndpointManifest
32
+ from fi.simulate.registry import register_endpoint
33
+ from fi.simulate.runtime.capabilities import EndpointCapabilities
34
+
35
+
36
+ class EndpointProfile:
37
+ """One target adapter's manifest + provider decisions."""
38
+
39
+ def __init__(
40
+ self,
41
+ manifest: AgentEndpointManifest,
42
+ *,
43
+ is_sip: bool = False,
44
+ places_outbound_call: bool = False,
45
+ receives_inbound_call: bool = False,
46
+ uses_web_audio_bridge: bool = False,
47
+ bridge_provider: Optional[str] = None,
48
+ evidence_provider: Optional[str] = None,
49
+ required_env_rule: Optional[Callable[[Any], list[str]]] = None,
50
+ connector_builder: Optional[Callable[..., Any]] = None,
51
+ target_resolver: Optional[Callable[..., Any]] = None,
52
+ runs_caller_code: bool = True,
53
+ ) -> None:
54
+ self.manifest = manifest
55
+ self.is_sip = is_sip
56
+ self.places_outbound_call = places_outbound_call
57
+ self.receives_inbound_call = receives_inbound_call
58
+ self.uses_web_audio_bridge = uses_web_audio_bridge
59
+ self.bridge_provider = bridge_provider
60
+ self.evidence_provider = evidence_provider
61
+ self._required_env_rule = required_env_rule
62
+ self._connector_builder = connector_builder
63
+ self._target_resolver = target_resolver
64
+ # Deny-by-default: a turn-based target executes caller-supplied Python
65
+ # unless it explicitly declares otherwise (http/system_prompt/deployed
66
+ # endpoints). An undeclared third-party kind is treated as unsafe. The
67
+ # hosted gate (resolve_chat_target) reads this, not a module denylist.
68
+ self.runs_caller_code = runs_caller_code
69
+
70
+ @property
71
+ def is_turn_based_target(self) -> bool:
72
+ """This adapter resolves to a turn-based agent (chat) rather than a
73
+ realtime/voice endpoint."""
74
+ return self._target_resolver is not None
75
+
76
+ def resolve_target(
77
+ self, config: Any = None, secret_refs: Any = None, *, hosted: bool = False
78
+ ) -> Any:
79
+ """Resolve this actor-source to a runnable turn-based agent (plan §4.1).
80
+
81
+ Raises for realtime/voice adapters, which are reached through
82
+ ``build_connector`` + the engine instead. ``hosted`` restricts env reads
83
+ to job-provisioned secrets (see resolvers)."""
84
+ if self._target_resolver is None:
85
+ raise ValueError(
86
+ f"target_adapter_not_turn_based: {self.manifest.name!r}"
87
+ )
88
+ return self._target_resolver(config or {}, secret_refs, hosted=hosted)
89
+
90
+ @property
91
+ def joins_as_sip_participant(self) -> bool:
92
+ """The target joins the room as a SIP participant — true for real SIP
93
+ legs and for the web providers reached through the LiveKit audio bridge."""
94
+ return self.is_sip or self.uses_web_audio_bridge
95
+
96
+ @property
97
+ def uses_external_room(self) -> bool:
98
+ """Only plain WebRTC targets run in an external (non-managed) room."""
99
+ return not self.joins_as_sip_participant
100
+
101
+ @property
102
+ def name(self) -> str:
103
+ return self.manifest.name
104
+
105
+ def required_env(self, agent_definition: Any) -> list[str]:
106
+ """Transport-specific env var *names* this adapter needs (the branch that
107
+ used to live in ``voice._voice_required_env``). Base adapters need none."""
108
+ if self._required_env_rule is None:
109
+ return []
110
+ return list(self._required_env_rule(agent_definition))
111
+
112
+ def build_connector(
113
+ self, provider_target: Any, *, conversation_direction: str
114
+ ) -> Any:
115
+ """Provider connector for web-bridged targets; ``None`` for webrtc/sip."""
116
+ if self._connector_builder is None:
117
+ return None
118
+ return self._connector_builder(
119
+ provider_target, conversation_direction=conversation_direction
120
+ )
121
+
122
+
123
+ # --------------------------------------------------------------------------- #
124
+ # required-env rules (parity with the old voice._voice_required_env branches)
125
+ # --------------------------------------------------------------------------- #
126
+ def _vapi_web_required_env(agent_definition: Any) -> list[str]:
127
+ if agent_definition.target is None:
128
+ return ["VAPI_API_KEY", "VAPI_ASSISTANT_ID"]
129
+ return []
130
+
131
+
132
+ def _retell_web_required_env(agent_definition: Any) -> list[str]:
133
+ if agent_definition.target is None:
134
+ return ["RETELL_API_KEY", "RETELL_AGENT_ID"]
135
+ return []
136
+
137
+
138
+ def _sip_inbound_required_env(agent_definition: Any) -> list[str]:
139
+ transport = agent_definition.transport
140
+ target = agent_definition.target
141
+ names: list[str] = []
142
+ if transport is not None and not transport.dispatch_rule_name:
143
+ names.append("LIVEKIT_INBOUND_TRUNK_ID")
144
+ if transport is not None and transport.inbound_call_originator == "vapi":
145
+ # Same conditional-append idiom as the Retell branch below; behaviour
146
+ # identical (the consumer already dropped blanks from the old tuple).
147
+ names.append(
148
+ target.api_key_env
149
+ if target is not None and target.provider == "vapi"
150
+ else "VAPI_API_KEY"
151
+ )
152
+ if target is None or target.provider != "vapi":
153
+ names.append("VAPI_ASSISTANT_ID")
154
+ names.append("VAPI_PHONE_NUMBER_ID")
155
+ names.append("LIVEKIT_INBOUND_DID")
156
+ if transport is not None and transport.inbound_call_originator == "retell":
157
+ # Parity-only rule (CLI/manifest path); the job carries these fields directly.
158
+ names.append("RETELL_API_KEY")
159
+ if not transport.originator_agent_id:
160
+ names.append("RETELL_AGENT_ID")
161
+ if not transport.originator_from_number:
162
+ names.append("RETELL_FROM_NUMBER")
163
+ names.append("LIVEKIT_INBOUND_DID")
164
+ return names
165
+
166
+
167
+ # --------------------------------------------------------------------------- #
168
+ # connector builders (lazy imports keep the voice stack off the planner path)
169
+ # --------------------------------------------------------------------------- #
170
+ def _build_vapi_connector(provider_target: Any, *, conversation_direction: str) -> Any:
171
+ from fi.simulate.agent.definition import VapiTargetConfig
172
+ from fi.simulate.simulation.bridge import VapiWebSocketConnector
173
+
174
+ if isinstance(provider_target, VapiTargetConfig):
175
+ return VapiWebSocketConnector.from_target(
176
+ provider_target,
177
+ first_message_mode=(
178
+ "assistant-waits-for-user"
179
+ if conversation_direction == "simulator_first"
180
+ else "assistant-speaks-first"
181
+ ),
182
+ )
183
+ return VapiWebSocketConnector.from_env()
184
+
185
+
186
+ def _build_retell_connector(
187
+ provider_target: Any, *, conversation_direction: str
188
+ ) -> Any:
189
+ from fi.simulate.agent.definition import RetellTargetConfig
190
+ from fi.simulate.simulation.bridge import RetellWebCallConnector
191
+
192
+ if isinstance(provider_target, RetellTargetConfig):
193
+ return RetellWebCallConnector.from_target(provider_target)
194
+ return RetellWebCallConnector.from_env()
195
+
196
+
197
+ # --------------------------------------------------------------------------- #
198
+ # built-in profiles
199
+ # --------------------------------------------------------------------------- #
200
+ _CHAT_CAPS = EndpointCapabilities(text=True, transcript_events=True, tool_events=True)
201
+ _WEBRTC_CAPS = EndpointCapabilities(
202
+ audio=True,
203
+ streaming=True,
204
+ interruption=True,
205
+ recording=True,
206
+ transcript_events=True,
207
+ web_rtc=True,
208
+ )
209
+ _SIP_CAPS = EndpointCapabilities(
210
+ audio=True,
211
+ streaming=True,
212
+ interruption=True,
213
+ recording=True,
214
+ transcript_events=True,
215
+ sip=True,
216
+ )
217
+
218
+ _PROFILES: list[EndpointProfile] = [
219
+ # chat-era + actor-source target adapters ("drop in any agent", plan §4.1).
220
+ # config keys mirror the manifest agent: block (target/factory/args/kwargs/
221
+ # method/input_mode/system_prompt/...).
222
+ EndpointProfile(
223
+ AgentEndpointManifest(
224
+ name="callable", provider="callable",
225
+ world_kinds=["chat", "text"], capabilities=_CHAT_CAPS,
226
+ ),
227
+ target_resolver=resolve_python_callable,
228
+ ),
229
+ EndpointProfile(
230
+ AgentEndpointManifest(
231
+ name="python_callable", provider="callable",
232
+ world_kinds=["chat", "text"], capabilities=_CHAT_CAPS,
233
+ ),
234
+ target_resolver=resolve_python_callable,
235
+ ),
236
+ EndpointProfile(
237
+ AgentEndpointManifest(
238
+ name="import_object", provider="python",
239
+ world_kinds=["chat", "text"], capabilities=_CHAT_CAPS,
240
+ ),
241
+ target_resolver=resolve_import_object,
242
+ ),
243
+ EndpointProfile(
244
+ AgentEndpointManifest(
245
+ name="factory", provider="python",
246
+ world_kinds=["chat", "text"], capabilities=_CHAT_CAPS,
247
+ ),
248
+ target_resolver=resolve_factory,
249
+ ),
250
+ EndpointProfile(
251
+ AgentEndpointManifest(
252
+ name="framework", provider="framework",
253
+ world_kinds=["chat", "text"], capabilities=_CHAT_CAPS,
254
+ ),
255
+ target_resolver=resolve_framework,
256
+ ),
257
+ EndpointProfile(
258
+ AgentEndpointManifest(
259
+ name="system_prompt", provider="llm",
260
+ world_kinds=["chat", "text"], capabilities=_CHAT_CAPS,
261
+ ),
262
+ target_resolver=resolve_system_prompt,
263
+ runs_caller_code=False,
264
+ ),
265
+ EndpointProfile(
266
+ AgentEndpointManifest(
267
+ name="http", provider="http",
268
+ world_kinds=["chat", "text"], capabilities=_CHAT_CAPS,
269
+ ),
270
+ target_resolver=resolve_http,
271
+ runs_caller_code=False,
272
+ ),
273
+ EndpointProfile(
274
+ AgentEndpointManifest(
275
+ name="websocket", provider="websocket",
276
+ world_kinds=["chat", "text"],
277
+ capabilities=EndpointCapabilities(
278
+ text=True, streaming=True, transcript_events=True, tool_events=True
279
+ ),
280
+ )
281
+ ),
282
+ # voice target adapters (keyed by transport.kind)
283
+ EndpointProfile(
284
+ AgentEndpointManifest(
285
+ name="webrtc", provider="livekit",
286
+ world_kinds=["voice"], capabilities=_WEBRTC_CAPS,
287
+ )
288
+ ),
289
+ EndpointProfile(
290
+ AgentEndpointManifest(
291
+ name="livekit", provider="livekit",
292
+ world_kinds=["voice"], capabilities=_WEBRTC_CAPS,
293
+ )
294
+ ),
295
+ EndpointProfile(
296
+ AgentEndpointManifest(
297
+ name="vapi_websocket", provider="vapi",
298
+ world_kinds=["voice"], capabilities=_WEBRTC_CAPS,
299
+ ),
300
+ uses_web_audio_bridge=True,
301
+ bridge_provider="vapi",
302
+ evidence_provider="vapi",
303
+ required_env_rule=_vapi_web_required_env,
304
+ connector_builder=_build_vapi_connector,
305
+ ),
306
+ EndpointProfile(
307
+ AgentEndpointManifest(
308
+ name="retell_webcall", provider="retell",
309
+ world_kinds=["voice"], capabilities=_WEBRTC_CAPS,
310
+ ),
311
+ uses_web_audio_bridge=True,
312
+ bridge_provider="retell",
313
+ evidence_provider="retell",
314
+ required_env_rule=_retell_web_required_env,
315
+ connector_builder=_build_retell_connector,
316
+ ),
317
+ EndpointProfile(
318
+ AgentEndpointManifest(
319
+ name="sip_outbound", provider="sip",
320
+ world_kinds=["voice"], capabilities=_SIP_CAPS,
321
+ ),
322
+ is_sip=True,
323
+ places_outbound_call=True,
324
+ ),
325
+ EndpointProfile(
326
+ AgentEndpointManifest(
327
+ name="sip_inbound", provider="sip",
328
+ world_kinds=["voice"], capabilities=_SIP_CAPS,
329
+ ),
330
+ is_sip=True,
331
+ receives_inbound_call=True,
332
+ required_env_rule=_sip_inbound_required_env,
333
+ ),
334
+ ]
335
+
336
+ for _profile in _PROFILES:
337
+ register_endpoint(_profile.name, _profile)
338
+
339
+
340
+ def get_profile(name: str) -> Optional[EndpointProfile]:
341
+ """Return the registered profile for a target adapter name, or ``None``."""
342
+ from fi.simulate.registry import endpoint_registry
343
+
344
+ value = endpoint_registry.get_or_none(name)
345
+ return value if isinstance(value, EndpointProfile) else None
346
+
347
+
348
+ __all__ = ["EndpointProfile", "get_profile"]