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,170 @@
1
+ """Matrix runner: sweep a scenario across provider/channel legs.
2
+
3
+ Composes an SDK simulation manifest with per-leg overrides so the same
4
+ scenario can be executed against ``{Vapi, Retell, LiveKit}`` in
5
+ ``{webrtc, sip_outbound, sip_inbound}`` shapes. Legs that cannot run
6
+ against a provider (e.g. Retell over PSTN outbound) are declared with
7
+ ``skip_reason`` and reported as ``UNSUPPORTED`` in the canonical output
8
+ instead of being silently omitted.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import copy
14
+ import logging
15
+ from dataclasses import dataclass, field
16
+ from datetime import datetime, timezone
17
+ from pathlib import Path
18
+ from typing import Any, Literal, Mapping
19
+
20
+ from pydantic import BaseModel, Field
21
+
22
+ from fi.simulate.runtime import (
23
+ FailureStage,
24
+ SimulationFailure,
25
+ TestCaseStatus,
26
+ )
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ Channel = Literal["webrtc", "sip_outbound", "sip_inbound"]
31
+
32
+
33
+ class MatrixLeg(BaseModel):
34
+ """One column of the provider × channel matrix."""
35
+
36
+ provider: str
37
+ channel: Channel
38
+ agent_definition_overrides: dict[str, Any] = Field(default_factory=dict)
39
+ provider_evidence_overrides: dict[str, Any] | None = None
40
+ skip_reason: str | None = None
41
+ max_concurrent_calls: int | None = None
42
+
43
+ @property
44
+ def label(self) -> str:
45
+ return f"{self.provider}:{self.channel}"
46
+
47
+
48
+ @dataclass
49
+ class MatrixLegResult:
50
+ leg: MatrixLeg
51
+ manifest: dict[str, Any]
52
+ started_at: datetime
53
+ ended_at: datetime | None = None
54
+ report: Any = None
55
+ error: str | None = None
56
+ skipped: bool = False
57
+ skipped_cases: list[dict[str, Any]] = field(default_factory=list)
58
+
59
+
60
+ def _leg_manifest(base: Mapping[str, Any], leg: MatrixLeg) -> dict[str, Any]:
61
+ manifest = copy.deepcopy(dict(base))
62
+ agent_definition = dict(manifest.get("agent_definition") or {})
63
+ agent_definition.update(leg.agent_definition_overrides)
64
+ transport = dict(agent_definition.get("transport") or {})
65
+ transport["kind"] = leg.channel
66
+ if leg.channel == "webrtc":
67
+ for legacy in ("sip_trunk_id", "sip_number", "sip_call_to", "dispatch_rule_name"):
68
+ transport.pop(legacy, None)
69
+ agent_definition["transport"] = transport
70
+ if leg.provider_evidence_overrides is not None:
71
+ agent_definition["provider_evidence"] = leg.provider_evidence_overrides
72
+ manifest["agent_definition"] = agent_definition
73
+ simulation = dict(manifest.get("simulation") or {})
74
+ simulation["engine"] = simulation.get("engine", "livekit")
75
+ manifest["simulation"] = simulation
76
+ manifest["name"] = f"{manifest.get('name', 'matrix-run')}:{leg.label}"
77
+ return manifest
78
+
79
+
80
+ def build_skipped_report(
81
+ leg: MatrixLeg,
82
+ base: Mapping[str, Any],
83
+ *,
84
+ now: datetime | None = None,
85
+ ) -> MatrixLegResult:
86
+ manifest = _leg_manifest(base, leg)
87
+ started = now or datetime.now(timezone.utc)
88
+ scenario = manifest.get("scenario") or {}
89
+ dataset = scenario.get("dataset") or []
90
+ skipped_cases = [
91
+ {
92
+ "index": index,
93
+ "persona": (row.get("persona") if isinstance(row, dict) else None),
94
+ "status": TestCaseStatus.UNSUPPORTED.value,
95
+ "failure": SimulationFailure(
96
+ stage=FailureStage.PREPARING,
97
+ code="provider_channel_unsupported",
98
+ message=leg.skip_reason or "provider_channel_unsupported",
99
+ retryable=False,
100
+ provider=leg.provider,
101
+ details={"channel": leg.channel},
102
+ ).model_dump(mode="json", exclude_none=True),
103
+ }
104
+ for index, row in enumerate(dataset)
105
+ ]
106
+ return MatrixLegResult(
107
+ leg=leg,
108
+ manifest=manifest,
109
+ started_at=started,
110
+ ended_at=started,
111
+ skipped=True,
112
+ skipped_cases=skipped_cases,
113
+ )
114
+
115
+
116
+ async def run_matrix(
117
+ base_manifest: Mapping[str, Any],
118
+ legs: list[MatrixLeg],
119
+ *,
120
+ manifest_path: Path,
121
+ max_concurrent_calls: int = 1,
122
+ run_leg,
123
+ ) -> list[MatrixLegResult]:
124
+ """Sweep ``legs`` against ``base_manifest``.
125
+
126
+ ``run_leg`` is an async callable ``(manifest, manifest_path, leg)``
127
+ that executes one leg and returns the SDK report. It is injected
128
+ rather than imported here so this module remains framework-lean and
129
+ testable without a live LiveKit backend.
130
+ """
131
+
132
+ results: list[MatrixLegResult] = []
133
+ for leg in legs:
134
+ if leg.skip_reason:
135
+ skipped = build_skipped_report(leg, base_manifest)
136
+ logger.info(
137
+ "matrix_leg_skipped",
138
+ extra={"leg": leg.label, "reason": leg.skip_reason},
139
+ )
140
+ results.append(skipped)
141
+ continue
142
+ started = datetime.now(timezone.utc)
143
+ manifest = _leg_manifest(base_manifest, leg)
144
+ result = MatrixLegResult(leg=leg, manifest=manifest, started_at=started)
145
+ try:
146
+ result.report = await run_leg(
147
+ manifest=manifest,
148
+ manifest_path=manifest_path,
149
+ leg=leg,
150
+ max_concurrent_calls=leg.max_concurrent_calls or max_concurrent_calls,
151
+ )
152
+ except Exception as exc: # noqa: BLE001
153
+ logger.warning(
154
+ "matrix_leg_error",
155
+ extra={"leg": leg.label, "error": type(exc).__name__},
156
+ )
157
+ result.error = f"{type(exc).__name__}: {exc}"
158
+ finally:
159
+ result.ended_at = datetime.now(timezone.utc)
160
+ results.append(result)
161
+ return results
162
+
163
+
164
+ __all__ = [
165
+ "Channel",
166
+ "MatrixLeg",
167
+ "MatrixLegResult",
168
+ "build_skipped_report",
169
+ "run_matrix",
170
+ ]
@@ -0,0 +1,279 @@
1
+ from typing import List, Dict, Any, Union, Optional, Literal
2
+ from pydantic import BaseModel, Field, validator, model_validator
3
+ import hashlib
4
+ import pandas as pd
5
+ import json
6
+ from fi.simulate.agent.wrapper import SimulationArtifact, SimulationEvent
7
+
8
+ PERSONA_TEMPERAMENT_AXES = ("rajas", "sattva", "tamas")
9
+ # Byte-equal to fi.opt.optimizers.council.GUNA_AXES (council.py:40) — pinned by
10
+ # a cross-equality unit test, never imported (fi.simulate must not depend on
11
+ # fi.opt). Scholarly design device used as deterministic engineering metadata —
12
+ # same framing as council.py:36-38; the four honest limits of Phase-7
13
+ # RESEARCH §3.4 are binding (design metaphor, not a psychometric claim about
14
+ # simulated users; axes ship ONLY with a transcript-observable realization
15
+ # metric — see behavior_policy.py).
16
+
17
+ PERSONA_EVIDENCE_CLASSES = (
18
+ "hand_written", "schema_sampled", "policy_evolved",
19
+ "trace_mined", "cloud_downloaded", "legacy",
20
+ )
21
+ SCENARIO_KINDS = ("task", "adversarial", "regression", "perturbation", "composed")
22
+
23
+
24
+ class PersonaIdentity(BaseModel):
25
+ """Layer 1 — minimal, behavioral-first. Demographics optional, non-default,
26
+ lint-flagged (P7-D4; SCOPE: demographics explain ~1.5% of behavioral
27
+ variance). NO field here ever backs a realism claim."""
28
+ name: Optional[str] = None
29
+ role: Optional[str] = None
30
+ summary: Optional[str] = None
31
+ language: Optional[str] = None # locale for bias-lint re-runs
32
+ demographics: Dict[str, Any] = Field(default_factory=dict) # ALWAYS lint-flagged
33
+ style_notes: List[str] = Field(default_factory=list) # verbatim vendor/platform text
34
+
35
+
36
+ class PersonaTemperament(BaseModel):
37
+ """Layer 2 — continuous axes that COMPILE into layer 3 (behavior_policy.py);
38
+ never prose adjectives in a prompt (PPol / 2604.00026)."""
39
+ rajas: float = Field(0.5, ge=0.0, le=1.0) # activation/urgency dial
40
+ sattva: float = Field(0.5, ge=0.0, le=1.0) # clarity/cooperation dial
41
+ tamas: float = Field(0.5, ge=0.0, le=1.0) # inertia/withdrawal dial
42
+
43
+
44
+ class BehaviorPolicy(BaseModel):
45
+ """Layer 3 — the executable, searchable representation (PPol). The six
46
+ parameters map 1:1 onto the canon axes V1_PERSONA_BEHAVIOR_AXES
47
+ ["patience", "disclosure", "interruption", "escalation", "cooperation",
48
+ "repair"] (ARCH §4), each paired with its transcript-observable
49
+ realization metric (behavior_policy.py); a parameter without one DOES NOT
50
+ SHIP (R§3.4 limit 4). verbosity/tempo dials are POST-v1.x — they exceed
51
+ the closed axis set and ship no realization metric in v1 (ARCH Decision 4)."""
52
+ patience_curve: List[float] = Field(default_factory=lambda: [1.0]) # axis: patience — per-turn patience 0..1
53
+ disclosure_policy: float = Field(0.7, ge=0.0, le=1.0) # axis: disclosure — fraction of known facts volunteered
54
+ interruption_propensity: float = Field(0.1, ge=0.0, le=1.0) # axis: interruption
55
+ escalation_schedule: List[float] = Field(default_factory=lambda: [0.0]) # axis: escalation — per-turn pressure 0..1
56
+ cooperation_bounds: float = Field(0.8, ge=0.0, le=1.0) # axis: cooperation — ceiling on helpfulness (anti-cooperative-bias)
57
+ repair_propensity: float = Field(0.5, ge=0.0, le=1.0) # axis: repair — good-faith repair probability after misunderstanding
58
+
59
+
60
+ class PersonaFact(BaseModel):
61
+ """Layer 4 — retrievable knowledge store (2603.19313: retrieved, not
62
+ prompt-stuffed). Goals are NOT here: the Scenario owns the task
63
+ (2601.15290 separation)."""
64
+ key: str
65
+ value: str
66
+ disclosure: Literal["volunteer", "on_request", "withhold"] = "on_request"
67
+
68
+
69
+ class AttackConditioning(BaseModel):
70
+ """Optional red-team conditioning (PCAP). Values must be members of the
71
+ gate-enforced 10x6 taxonomy — membership is asserted FACADE-side
72
+ (studio.validate_persona) and by the gate, not here (fi.simulate must not
73
+ import fi.alk.trinity)."""
74
+ strategies: List[str] = Field(default_factory=list) # ⊆ V1_REDTEAM_RESEARCH_ATTACK_TYPES
75
+ surfaces: List[str] = Field(default_factory=list) # ⊆ V1_REDTEAM_RESEARCH_SURFACES
76
+ in_character_floor: float = Field(0.6, ge=0.0, le=1.0)
77
+
78
+
79
+ class PersonaProvenance(BaseModel):
80
+ """Layer 5 — how the persona was made + what it is calibrated FOR.
81
+ No class ever claims population representativeness (2602.18462 hard
82
+ limit — stated here in the schema, not just docs)."""
83
+ evidence_class: Literal[
84
+ "hand_written", "schema_sampled", "policy_evolved",
85
+ "trace_mined", "cloud_downloaded", "legacy",
86
+ ] = "legacy"
87
+ calibrated: bool = False
88
+ calibration_ref: Optional[str] = None # content hash of the calibration artifact
89
+ source_format: Optional[str] = None # "vapi" | "retell" | "futureagi" | None
90
+ raw: Optional[str] = None # verbatim vendor/source text (ARCH Decision 8 losslessness)
91
+ pin: Dict[str, Any] = Field(default_factory=dict) # download pin block (studio/_download.py)
92
+ representativeness_claim: Literal["none"] = "none" # frozen; the schema-level hard limit
93
+
94
+
95
+ class Persona(BaseModel):
96
+ """
97
+ A single test case defining a customer persona, situation, and desired outcome.
98
+ """
99
+ persona: Dict[str, Any] = Field(..., description="Characteristics of the simulated customer (e.g., name, age, communication_style).")
100
+ situation: str = Field(..., description="The context or reason for the customer's call.")
101
+ outcome: str = Field(..., description="The desired goal or resolution for the conversation.")
102
+ # ---- Phase 7 typed layers (ALL optional => full back-compat) ----------
103
+ identity: Optional[PersonaIdentity] = None
104
+ temperament: Optional[PersonaTemperament] = None
105
+ behavior_policy: Optional[BehaviorPolicy] = None
106
+ knowledge: List[PersonaFact] = Field(default_factory=list)
107
+ attack: Optional[AttackConditioning] = None
108
+ provenance: Optional[PersonaProvenance] = None
109
+ version: Optional[str] = None # content address, ARCH §2d
110
+
111
+ @property
112
+ def is_typed(self) -> bool:
113
+ """True when the persona carries an executable layer-3 policy —
114
+ the precondition for fidelity measurement (fidelity.py)."""
115
+ return self.behavior_policy is not None
116
+
117
+ def content_hash(self) -> str:
118
+ payload = self.model_dump(exclude={"version"}, exclude_none=True)
119
+ canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"), default=str)
120
+ return "sha256:" + hashlib.sha256(canonical.encode("utf-8")).hexdigest()
121
+
122
+ @model_validator(mode="after")
123
+ def _stamp_version(self) -> "Persona":
124
+ if self.version is None and self.is_typed:
125
+ object.__setattr__(self, "version", self.content_hash())
126
+ return self
127
+
128
+
129
+ class ScenarioGoal(BaseModel):
130
+ """Goal/state progression — the task-state half of the 2601.15290 split."""
131
+ states: List[str] = Field(default_factory=list) # ordered milestone names
132
+ success_state: Optional[str] = None
133
+ failure_states: List[str] = Field(default_factory=list)
134
+
135
+
136
+ class VerificationSpec(BaseModel):
137
+ checks: List[Dict[str, Any]] = Field(default_factory=list) # eval-template refs / predicates
138
+ threshold: float = Field(0.7, ge=0.0, le=1.0)
139
+
140
+
141
+ class CoverageDeclaration(BaseModel):
142
+ """Declared coverage axes (2605.26521 obligations, not counts)."""
143
+ intents: List[str] = Field(default_factory=list)
144
+ personas: List[str] = Field(default_factory=list) # persona version hashes
145
+ perturbations: List[str] = Field(default_factory=list)
146
+ tool_obligations: List[str] = Field(default_factory=list) # "allow:<tool>" / "deny:<tool>"
147
+ delegation_obligations: List[str] = Field(default_factory=list)
148
+
149
+
150
+ class ScenarioConstraints(BaseModel):
151
+ """The tau^2 move: the scenario BOUNDS persona freedom so fidelity is
152
+ checkable — declared tools, observable state, goal machine."""
153
+ declared_tools: List[str] = Field(default_factory=list)
154
+ observable_state: Dict[str, Any] = Field(default_factory=dict)
155
+ max_user_knowledge: List[str] = Field(default_factory=list) # PersonaFact keys usable here
156
+
157
+
158
+ class EscalationStep(BaseModel):
159
+ turn: int = Field(..., ge=1)
160
+ pressure: float = Field(..., ge=0.0, le=1.0)
161
+ tactic: str # free label, e.g. "reframe", "urgency", "authority"
162
+
163
+
164
+ class EscalationArc(BaseModel):
165
+ """Turn-wise in-character escalation (Crescendo finding, R§1 2605.04019)."""
166
+ steps: List[EscalationStep]
167
+ hold_character: bool = True
168
+
169
+
170
+ class Scenario(BaseModel):
171
+ """
172
+ Defines a collection of test cases for a simulation.
173
+ """
174
+ name: str = Field(..., description="A unique name for the scenario.")
175
+ description: Optional[str] = Field(None, description="A brief description of what this scenario tests.")
176
+ dataset: List[Persona] = Field(..., description="A list of personas defining the test cases.")
177
+ # ---- Phase 7 typing (ALL optional; kind=None == legacy untyped — NEVER
178
+ # silently retyped; studio-created scenarios must carry an explicit kind;
179
+ # the gate requires kinds only on studio-library scenarios — ARCH §2a) ----
180
+ kind: Optional[Literal["task", "adversarial", "regression", "perturbation", "composed"]] = None
181
+ goal: Optional[ScenarioGoal] = None
182
+ verification: Optional[VerificationSpec] = None
183
+ coverage: Optional[CoverageDeclaration] = None
184
+ constraints: Optional[ScenarioConstraints] = None
185
+ escalation: Optional[EscalationArc] = None
186
+ attack_type: Optional[str] = None # adversarial kind: required
187
+ attack_surface: Optional[str] = None # adversarial kind: required
188
+ version: Optional[str] = None # content address, ARCH §2d
189
+ parent_version: Optional[str] = None # expansion lineage (studio/_coverage.py)
190
+
191
+ @validator('dataset', pre=True)
192
+ def load_dataset(cls, v: Union[List[Dict], str]) -> List[Dict]:
193
+ if isinstance(v, str):
194
+ if v.endswith('.csv'):
195
+ return pd.read_csv(v).to_dict('records')
196
+ elif v.endswith('.json'):
197
+ with open(v, 'r') as f:
198
+ return json.load(f)
199
+ else:
200
+ raise ValueError("Unsupported file type for dataset. Please use .csv or .json.")
201
+ return v
202
+
203
+ def content_hash(self) -> str: # same canonicalization as Persona
204
+ payload = self.model_dump(exclude={"version"}, exclude_none=True)
205
+ canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"), default=str)
206
+ return "sha256:" + hashlib.sha256(canonical.encode("utf-8")).hexdigest()
207
+
208
+ @model_validator(mode="after")
209
+ def _kind_contract(self) -> "Scenario":
210
+ if self.kind == "adversarial":
211
+ if not (self.attack_type and self.attack_surface and self.escalation):
212
+ raise ValueError(
213
+ "adversarial scenarios must declare attack_type, "
214
+ "attack_surface, and an escalation arc"
215
+ )
216
+ if self.kind is not None and self.version is None:
217
+ object.__setattr__(self, "version", self.content_hash())
218
+ return self
219
+
220
+ class TestCaseResult(BaseModel):
221
+ """
222
+ Represents the result of a single test case.
223
+ """
224
+ persona: Persona = Field(..., description="The original persona that was run.")
225
+ transcript: str = Field(..., description="The full transcript of the conversation.")
226
+ messages: List[Dict[str, Any]] = Field(
227
+ default_factory=list,
228
+ description="Normalized message trajectory including user, assistant, and tool turns.",
229
+ )
230
+ tool_calls: List[Dict[str, Any]] = Field(
231
+ default_factory=list,
232
+ description="Tool calls observed during the run, when wrappers expose them.",
233
+ )
234
+ artifacts: List[SimulationArtifact] = Field(
235
+ default_factory=list,
236
+ description="Multimodal artifacts observed during the run, such as audio, images, screenshots, files, and traces.",
237
+ )
238
+ events: List[SimulationEvent] = Field(
239
+ default_factory=list,
240
+ description="Normalized simulation events, including tools, memory, voice, browser/CUA, and framework spans.",
241
+ )
242
+ metadata: Dict[str, Any] = Field(
243
+ default_factory=dict,
244
+ description="Engine, scenario, timing, stop reason, and other run metadata.",
245
+ )
246
+ evaluation: dict | None = Field(
247
+ default=None,
248
+ description="Optional evaluation results (scores, reasons) keyed by template.",
249
+ )
250
+ audio_input_path: str | None = Field(
251
+ default=None,
252
+ description="Optional path to recorded customer (input) audio for this test.",
253
+ )
254
+ audio_output_path: str | None = Field(
255
+ default=None,
256
+ description="Optional path to recorded agent (output) audio for this test.",
257
+ )
258
+ audio_combined_path: str | None = Field(
259
+ default=None,
260
+ description="Optional path to a single WAV containing the mixed conversation.",
261
+ )
262
+ audio_stereo_path: str | None = Field(
263
+ default=None,
264
+ description="Optional path to a 2-channel WAV: ch0 customer, ch1 assistant.",
265
+ )
266
+
267
+ class TestReport(BaseModel):
268
+ """
269
+ A comprehensive report aggregating the results of all test cases in a scenario.
270
+ """
271
+ results: List[TestCaseResult] = Field(default_factory=list, description="A list of results for each test case.")
272
+
273
+ def admissible_results(self) -> List[TestCaseResult]:
274
+ return [r for r in self.results
275
+ if r.metadata.get("admission", {}).get("admissible", True)]
276
+
277
+ def inconclusive_results(self) -> List[TestCaseResult]:
278
+ return [r for r in self.results
279
+ if r.metadata.get("admission", {}).get("verdict") == "inconclusive"]
@@ -0,0 +1,153 @@
1
+ from typing import Optional, Callable
2
+ import os
3
+
4
+ from fi.simulate.agent.definition import (
5
+ AgentDefinition,
6
+ LiveKitSimulatorRuntime,
7
+ SimulatorAgentDefinition,
8
+ )
9
+ from fi.simulate.simulation.models import Scenario, TestReport
10
+ from fi.simulate.simulation.engines import (
11
+ LiveKitEngine,
12
+ BaseEngine,
13
+ CloudEngine,
14
+ LocalTextEngine,
15
+ )
16
+
17
+
18
+ class TestRunner:
19
+ """
20
+ Main entry point for running agent simulations.
21
+
22
+ Supports three execution modes:
23
+ 1. Local mode (LiveKit): Uses LiveKit to connect to deployed agents
24
+ 2. Cloud mode (Backend API): Uses Future AGI backend for orchestrated testing
25
+ 3. Local text mode: Runs a self-contained text simulator against an agent callback
26
+
27
+ The mode is automatically determined based on the arguments provided.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ api_key: Optional[str] = None,
33
+ secret_key: Optional[str] = None,
34
+ api_url: Optional[str] = None,
35
+ ):
36
+ """
37
+ Initialize the TestRunner.
38
+
39
+ Args:
40
+ api_key: Optional API key for cloud mode. If not provided, will check FI_API_KEY env var.
41
+ secret_key: Optional Secret key for cloud mode. If not provided, will check FI_SECRET_KEY env var.
42
+ api_url: Optional API URL for cloud mode. If not provided, will check FI_BASE_URL env var.
43
+ """
44
+ self.api_key = api_key or os.environ.get("FI_API_KEY")
45
+ self.secret_key = secret_key or os.environ.get("FI_SECRET_KEY")
46
+ self.api_url = api_url or os.environ.get("FI_BASE_URL")
47
+ self._engine: Optional[BaseEngine] = None
48
+
49
+ async def run_test(
50
+ self,
51
+ # --- Local Mode Arguments (LiveKit) ---
52
+ agent_definition: Optional[AgentDefinition] = None,
53
+ livekit_runtime: Optional[LiveKitSimulatorRuntime] = None,
54
+ scenario: Optional[Scenario] = None,
55
+ simulator: Optional[SimulatorAgentDefinition] = None,
56
+ # --- Cloud Mode Arguments (Backend API) ---
57
+ run_id: Optional[str] = None,
58
+ run_test_name: Optional[str] = None,
59
+ agent_callback: Optional[Callable] = None,
60
+ # --- Shared Arguments ---
61
+ num_scenarios: int = 1,
62
+ topic: Optional[str] = None,
63
+ simulation_run_id: Optional[str] = None,
64
+ record_audio: bool = False,
65
+ recorder_sample_rate: int = 8000,
66
+ recorder_join_delay: float = 0.2,
67
+ min_turn_messages: int = 8,
68
+ max_seconds: float = 45.0,
69
+ **kwargs,
70
+ ) -> TestReport:
71
+ """
72
+ Run a test simulation.
73
+
74
+ Mode is determined by arguments:
75
+ - If `run_id` or `run_test_name` is provided → Cloud mode (Backend API)
76
+ - If `agent_definition` is provided → Local mode (LiveKit)
77
+
78
+ Args:
79
+ agent_definition: Target-agent configuration for local mode
80
+ livekit_runtime: FutureAGI LiveKit simulator runtime
81
+ scenario: Test scenario for local mode
82
+ simulator: Simulator configuration for local mode
83
+ run_id: Run ID from platform for cloud mode
84
+ run_test_name: Run test name (alternative to run_id) - will fetch ID from backend
85
+ agent_callback: User's agent function to wrap for cloud mode
86
+ num_scenarios: Number of scenarios to generate (local mode only)
87
+ topic: Topic for scenario generation (local mode only)
88
+ simulation_run_id: Optional stable run ID for local LiveKit mode
89
+ record_audio: Whether to record audio
90
+ recorder_sample_rate: Audio sample rate
91
+ recorder_join_delay: Delay before recorder joins
92
+ min_turn_messages: Minimum turn messages
93
+ max_seconds: Maximum test duration
94
+ **kwargs: Additional arguments passed to engine
95
+
96
+ Returns:
97
+ TestReport with results from all test cases
98
+ """
99
+ # Dispatch to appropriate engine
100
+ if run_id is not None or run_test_name is not None:
101
+ # Cloud mode - Use CloudEngine
102
+ timeout = kwargs.pop("timeout", 120.0) # Default 120s for LLM operations
103
+ engine = CloudEngine(
104
+ self.api_key, self.secret_key, self.api_url, timeout=timeout
105
+ )
106
+ return await engine.run(
107
+ run_id=run_id,
108
+ run_test_name=run_test_name,
109
+ agent_callback=agent_callback,
110
+ **kwargs,
111
+ )
112
+ elif agent_callback is not None:
113
+ # Local text mode - no backend, LiveKit, or model dependency required
114
+ engine = LocalTextEngine()
115
+ return await engine.run(
116
+ scenario=scenario,
117
+ agent_callback=agent_callback,
118
+ num_scenarios=num_scenarios,
119
+ topic=topic,
120
+ run_id=simulation_run_id,
121
+ **kwargs,
122
+ )
123
+
124
+ elif agent_definition is not None:
125
+ # Local mode - use LiveKit engine
126
+ if LiveKitEngine is None:
127
+ raise ImportError(
128
+ "LiveKit mode requires the LiveKit dependency, but it is not available in this environment. "
129
+ "Install it (and compatible plugins) then retry. Cloud mode (run_id/run_test_name) does not "
130
+ "require LiveKit."
131
+ )
132
+ engine = LiveKitEngine()
133
+ return await engine.run(
134
+ agent_definition=agent_definition,
135
+ livekit_runtime=livekit_runtime,
136
+ scenario=scenario,
137
+ simulator=simulator,
138
+ num_scenarios=num_scenarios,
139
+ topic=topic,
140
+ record_audio=record_audio,
141
+ recorder_sample_rate=recorder_sample_rate,
142
+ recorder_join_delay=recorder_join_delay,
143
+ min_turn_messages=min_turn_messages,
144
+ max_seconds=max_seconds,
145
+ run_id=simulation_run_id,
146
+ **kwargs,
147
+ )
148
+ else:
149
+ raise ValueError(
150
+ "Must provide either 'agent_definition' (Local/LiveKit mode) "
151
+ "'agent_callback' with a scenario/topic (Local text mode), "
152
+ "or 'run_id'/'run_test_name' (Cloud/Backend API mode)."
153
+ )