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,212 @@
1
+ """Voice environment plugin (canonical plan §3/§7.2-7.4).
2
+
3
+ This is the registry-facing wrapper that finally routes voice through the same
4
+ ``SimulationRunner`` spine as chat. It does *not* reimplement the voice engine —
5
+ it hydrates the typed inputs from ``spec.environment.config`` and drives the
6
+ existing, working ``run_voice_simulation`` (LiveKit engine), returning the legacy
7
+ ``TestReport`` the runner converts uniformly.
8
+
9
+ The voice config is secret-free by construction: providers are referenced by
10
+ ``api_key_env`` / ``api_secret_env`` (env var *names*), never raw values, so the
11
+ whole config embeds inside the validated ``SimulationSpec`` without tripping
12
+ ``_reject_resolved_secrets``. Secrets reach the child process through the
13
+ environment, resolved by the runner activity.
14
+
15
+ Distinct from :class:`fi.simulate.environment.VoiceEnvironment`, which is the
16
+ deterministic *replay* adapter (a sync ``EnvironmentAdapter`` fixture) — a
17
+ different role, kept under its existing public name.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import inspect
23
+ import logging
24
+
25
+ from fi.simulate.environments.base import EnvironmentManifest
26
+ from fi.simulate.registry import register_environment
27
+ from fi.simulate.runtime.capabilities import EndpointCapabilities
28
+ from fi.simulate.runtime.failures import FailureStage, SimulationFailure
29
+ from fi.simulate.runtime.report import SimulationReport
30
+ from fi.simulate.runtime.run import RunStatus, TestCaseStatus
31
+ from fi.simulate.simulation.models import TestReport
32
+
33
+ _logger = logging.getLogger(__name__)
34
+
35
+ # Kwargs VoiceEnvironmentPlugin.run already binds explicitly when it calls
36
+ # run_voice_simulation (see below) — reserved so a same-named key surviving in
37
+ # hosted voice.params cannot collide with them at the call site.
38
+ _PLUGIN_OWNED = frozenset(
39
+ {
40
+ "agent_definition",
41
+ "livekit_runtime",
42
+ "scenario",
43
+ "simulator",
44
+ "simulation_run_id",
45
+ "on_case_complete",
46
+ "on_case_start",
47
+ }
48
+ )
49
+
50
+
51
+ def _filter_hosted_voice_params(params: dict, run_voice_simulation) -> dict:
52
+ """Drop platform-sent keys run_voice_simulation can't bind: it is
53
+ keyword-only with a closed parameter list, so one stray key would raise
54
+ TypeError at hydration and kill the whole hosted run. (A **kwargs sink, if
55
+ one is ever added, accepts everything, so nothing is dropped.)"""
56
+ parameters = inspect.signature(run_voice_simulation).parameters
57
+ if any(p.kind is inspect.Parameter.VAR_KEYWORD for p in parameters.values()):
58
+ return dict(params)
59
+ accepted = {
60
+ name
61
+ for name, parameter in parameters.items()
62
+ if parameter.kind is not inspect.Parameter.VAR_KEYWORD
63
+ } - _PLUGIN_OWNED
64
+ dropped = set(params) - accepted
65
+ if dropped:
66
+ _logger.warning("hosted_voice_params_ignored", extra={"keys": sorted(dropped)})
67
+ return {key: value for key, value in params.items() if key in accepted}
68
+
69
+
70
+ @register_environment("voice")
71
+ class VoiceEnvironmentPlugin:
72
+ manifest = EnvironmentManifest(
73
+ name="voice",
74
+ world_kinds=["voice_telephony", "voice"],
75
+ capabilities=EndpointCapabilities(
76
+ audio=True,
77
+ streaming=True,
78
+ interruption=True,
79
+ recording=True,
80
+ transcript_events=True,
81
+ web_rtc=True,
82
+ ),
83
+ )
84
+
85
+ async def run(
86
+ self,
87
+ spec,
88
+ *,
89
+ target=None,
90
+ artifacts=None,
91
+ events=None,
92
+ environment=None,
93
+ auto_execute_tools: bool = True,
94
+ stop_when=None,
95
+ agent_wrapper_kwargs=None,
96
+ on_case_complete=None,
97
+ on_case_start=None,
98
+ ) -> TestReport:
99
+ from fi.simulate import voice as voice_api
100
+ from fi.simulate.agent.definition import (
101
+ AgentDefinition,
102
+ LiveKitSimulatorRuntime,
103
+ SimulatorAgentDefinition,
104
+ )
105
+
106
+ config = dict(spec.environment.config or {})
107
+ agent_definition = AgentDefinition.model_validate(config["agent_definition"])
108
+ livekit_runtime = (
109
+ LiveKitSimulatorRuntime.model_validate(config["livekit_runtime"])
110
+ if config.get("livekit_runtime")
111
+ else None
112
+ )
113
+ simulator = (
114
+ SimulatorAgentDefinition.model_validate(config["simulator"])
115
+ if config.get("simulator")
116
+ else None
117
+ )
118
+ params = _filter_hosted_voice_params(
119
+ dict(config.get("params") or {}), voice_api.run_voice_simulation
120
+ )
121
+
122
+ # When streaming, score the case's goal (if any) BEFORE the case is
123
+ # submitted, so the streamed payload carries ``goal_machine`` metadata
124
+ # identically to the post-run report — then forward to the runner's sink
125
+ # callback. Absent streaming, the report-level pass below is authoritative.
126
+ streamed_callback = None
127
+ if on_case_complete is not None:
128
+ scenario = spec.scenario
129
+
130
+ async def streamed_callback(index, case): # noqa: ANN001
131
+ self._attach_goal_to_case(scenario, case)
132
+ await on_case_complete(index, case)
133
+
134
+ report = await voice_api.run_voice_simulation(
135
+ agent_definition=agent_definition,
136
+ livekit_runtime=livekit_runtime,
137
+ scenario=spec.scenario,
138
+ simulator=simulator,
139
+ simulation_run_id=spec.run_id,
140
+ on_case_complete=streamed_callback,
141
+ on_case_start=on_case_start,
142
+ **params,
143
+ )
144
+ self._attach_goal_machine(spec.scenario, report)
145
+ return report
146
+
147
+ @classmethod
148
+ def _attach_goal_machine(cls, scenario, report: TestReport) -> None:
149
+ """Voice world-contract (plan §1.9, settle-only). A declared
150
+ ``scenario.goal`` is scored over each case transcript at episode end and
151
+ attached as metadata — the same idiom chat uses. No declared goal ⇒ no-op
152
+ (byte-identical). Voice has no per-turn hook, so this scores the run; it
153
+ does not (and must not) early-stop a live call or fail the run.
154
+
155
+ Idempotent: overwrites ``goal_machine`` with the same value the streaming
156
+ path already wrote, so streamed and reconciled cases stay identical.
157
+ """
158
+ for case in report.results:
159
+ cls._attach_goal_to_case(scenario, case)
160
+
161
+ @staticmethod
162
+ def _attach_goal_to_case(scenario, case) -> None:
163
+ """Score ``scenario.goal`` over one case's transcript, in place."""
164
+ goal = getattr(scenario, "goal", None)
165
+ if goal is None:
166
+ return
167
+ from fi.simulate.simulation import goal_machine
168
+
169
+ verification = getattr(scenario, "verification", None)
170
+ settle = goal_machine.evaluate_settle(
171
+ goal,
172
+ verification,
173
+ environment_state={},
174
+ world_status={},
175
+ messages=getattr(case, "messages", None) or [],
176
+ )
177
+ case.metadata["goal_machine"] = {
178
+ "states_reached": settle.get("states_reached", []),
179
+ "stop_reason": None,
180
+ "checks": settle.get("checks", []),
181
+ }
182
+
183
+ def finalize_run_status(self, report: SimulationReport) -> SimulationReport:
184
+ """A voice run whose only case(s) failed is a failed job, not COMPLETED.
185
+
186
+ ``from_legacy`` carries per-case status but the overall status is the
187
+ environment's to decide (plan §3: environments enforce terminal
188
+ conditions). Chat keeps COMPLETED; only voice downgrades.
189
+ """
190
+ failed = [
191
+ case
192
+ for case in report.test_cases
193
+ if case.status is not TestCaseStatus.COMPLETED
194
+ ]
195
+ if not report.test_cases or len(failed) != len(report.test_cases):
196
+ return report
197
+ failure = failed[0].failure or SimulationFailure(
198
+ stage=FailureStage.RUNNING,
199
+ code="voice_run_failed",
200
+ message="voice simulation failed",
201
+ retryable=False,
202
+ )
203
+ return SimulationReport.model_validate(
204
+ {
205
+ **report.model_dump(exclude={"report_hash"}),
206
+ "status": RunStatus.FAILED.value,
207
+ "failure": failure.model_dump(),
208
+ }
209
+ )
210
+
211
+
212
+ __all__ = ["VoiceEnvironmentPlugin"]
@@ -0,0 +1,4 @@
1
+ from .ai_eval import evaluate_agent_report, evaluate_report
2
+
3
+ __all__ = ["evaluate_agent_report", "evaluate_report"]
4
+
@@ -0,0 +1,227 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Iterable, Mapping, Sequence
4
+ import os
5
+ import base64
6
+ import json
7
+
8
+ from fi.simulate.simulation.models import TestReport
9
+
10
+
11
+ def evaluate_agent_report(
12
+ report: TestReport,
13
+ *,
14
+ config: Mapping[str, Any] | None = None,
15
+ threshold: float = 0.7,
16
+ attach: bool = True,
17
+ ) -> Any:
18
+ """
19
+ Evaluate a simulation report locally with Agent Learning eval metrics.
20
+
21
+ This is the no-cloud path for the trinity loop:
22
+ Agent Learning simulation captures messages/tool calls/events/artifacts,
23
+ Agent Learning evals score the agent trajectory and pentest signals, and
24
+ Agent Learning optimization can optimize against the attached numeric
25
+ scores.
26
+
27
+ Returns the AgentReportEvaluation object. When `attach=True`,
28
+ each TestCaseResult receives an `evaluation["agent_report"]` payload and
29
+ aggregate summary is copied into `result.metadata["agent_report_summary"]`.
30
+ """
31
+
32
+ try:
33
+ from fi.evals.metrics.agents import evaluate_agent_report as evaluate
34
+ except Exception as e: # pragma: no cover - import error clarity
35
+ raise RuntimeError(
36
+ "Agent Learning Kit eval metrics are required. "
37
+ "Reinstall `agent-learning-kit`."
38
+ ) from e
39
+
40
+ evaluation = evaluate(report, config=dict(config or {}), threshold=threshold)
41
+ if attach:
42
+ _attach_agent_report_evaluation(report, evaluation)
43
+ return evaluation
44
+
45
+
46
+ def evaluate_report(
47
+ report: TestReport,
48
+ *,
49
+ eval_templates: Iterable[str] | None = ("task_completion", "tone", "is_helpful"),
50
+ eval_specs: Sequence[dict] | None = None,
51
+ model_name: str = "turing_flash",
52
+ api_key: str | None = None,
53
+ secret_key: str | None = None,
54
+ extra_inputs: Mapping[str, str] | None = None,
55
+ ) -> TestReport:
56
+ """
57
+ Evaluate each test case transcript using Agent Learning evals.
58
+
59
+ - Templates like "task_completion" will receive input and output fields
60
+ mapped from persona and transcript.
61
+ - "tone" will receive the whole transcript as input.
62
+
63
+ Docs: https://docs.futureagi.com/future-agi/get-started/evaluation/running-your-first-eval#evaluate-using-sdk
64
+ """
65
+
66
+ try:
67
+ from fi.evals import Evaluator
68
+ except Exception as e: # pragma: no cover - import error clarity
69
+ raise RuntimeError(
70
+ "Agent Learning Kit eval engine is required. Reinstall `agent-learning-kit`."
71
+ ) from e
72
+
73
+ evaluator = Evaluator(fi_api_key=api_key, fi_secret_key=secret_key)
74
+
75
+ for result in report.results:
76
+ persona = result.persona
77
+ transcript = result.transcript
78
+
79
+ scores: dict[str, dict] = {}
80
+
81
+ def resolve_source(key: str) -> str | None:
82
+ if key == "transcript":
83
+ return transcript
84
+ if key == "messages":
85
+ return json.dumps(result.messages, default=str)
86
+ if key == "tool_calls":
87
+ return json.dumps(result.tool_calls, default=str)
88
+ if key == "artifacts":
89
+ return json.dumps([_model_to_dict(item) for item in result.artifacts], default=str)
90
+ if key == "events":
91
+ return json.dumps([_model_to_dict(item) for item in result.events], default=str)
92
+ if key == "metadata":
93
+ return json.dumps(result.metadata, default=str)
94
+ if key == "persona":
95
+ return json.dumps(persona.persona, default=str)
96
+ if key == "persona.situation":
97
+ return persona.situation
98
+ if key == "persona.outcome":
99
+ return persona.outcome
100
+ if key == "audio_input_path":
101
+ val = getattr(result, "audio_input_path", None)
102
+ return os.path.abspath(val) if val and os.path.exists(val) else val
103
+ if key == "audio_output_path":
104
+ val = getattr(result, "audio_output_path", None)
105
+ return os.path.abspath(val) if val and os.path.exists(val) else val
106
+ if key == "audio_combined_path":
107
+ val = getattr(result, "audio_combined_path", None)
108
+ return os.path.abspath(val) if val and os.path.exists(val) else val
109
+ return None
110
+
111
+ def _encode_audio_inputs(inputs: dict[str, str]) -> dict[str, str]:
112
+ """Strict encoding: if a value is a local audio file path, replace that value with base64.
113
+
114
+ - Never rename keys or add aliases.
115
+ - Do not add extra fields (no audio_mime or data URI).
116
+ """
117
+ audio_exts = {".wav", ".ogg", ".mp3", ".m4a", ".flac", ".aac"}
118
+ for k, v in list(inputs.items()):
119
+ if isinstance(v, str) and os.path.exists(v):
120
+ _, ext = os.path.splitext(v.lower())
121
+ if ext in audio_exts:
122
+ try:
123
+ with open(v, "rb") as f:
124
+ data = f.read()
125
+ inputs[k] = base64.b64encode(data).decode("ascii")
126
+ except Exception:
127
+ # Leave as-is on read failure
128
+ pass
129
+ return inputs
130
+
131
+ # If eval_specs provided, use explicit mappings per template
132
+ if eval_specs:
133
+ for spec in eval_specs:
134
+ template = spec.get("template")
135
+ mapping: Mapping[str, str] = spec.get("map", {}) # desired_input_key -> source_key
136
+ if not template:
137
+ continue
138
+ inputs: dict[str, str] = {}
139
+ for dest, source in mapping.items():
140
+ val = resolve_source(source)
141
+ if val is not None:
142
+ inputs[dest] = val
143
+ if extra_inputs:
144
+ inputs.update(extra_inputs)
145
+ inputs = _encode_audio_inputs(inputs)
146
+ try:
147
+ ev = evaluator.evaluate(eval_templates=template, inputs=inputs, model_name=model_name)
148
+ item = ev.eval_results[0] if ev and getattr(ev, "eval_results", None) else None
149
+ scores[template] = {
150
+ "output": getattr(item, "output", None),
151
+ "reason": getattr(item, "reason", None),
152
+ "score": getattr(item, "score", None),
153
+ }
154
+ except Exception as e:
155
+ scores[template] = {"error": str(e), "inputs": inputs}
156
+ else:
157
+ # Fallback: simple built-ins by template name
158
+ for template in (eval_templates or []):
159
+ inputs: dict[str, str] = {}
160
+ if template == "tone":
161
+ inputs = {"input": transcript}
162
+ elif template == "task_completion":
163
+ inputs = {"input": persona.situation, "output": transcript}
164
+ elif template == "is_helpful":
165
+ inputs = {"input": transcript}
166
+ else:
167
+ inputs = {"input": transcript}
168
+
169
+ if extra_inputs:
170
+ inputs.update(extra_inputs)
171
+ inputs = _encode_audio_inputs(inputs)
172
+
173
+ try:
174
+ ev = evaluator.evaluate(eval_templates=template, inputs=inputs, model_name=model_name)
175
+ item = ev.eval_results[0] if ev and getattr(ev, "eval_results", None) else None
176
+ scores[template] = {
177
+ "output": getattr(item, "output", None),
178
+ "reason": getattr(item, "reason", None),
179
+ "score": getattr(item, "score", None),
180
+ }
181
+ except Exception as e:
182
+ scores[template] = {"error": str(e)}
183
+
184
+ result.evaluation = scores
185
+
186
+ return report
187
+
188
+
189
+ def _model_to_dict(value):
190
+ if hasattr(value, "model_dump"):
191
+ return value.model_dump()
192
+ if hasattr(value, "dict"):
193
+ return value.dict()
194
+ if isinstance(value, list):
195
+ return [_model_to_dict(item) for item in value]
196
+ if isinstance(value, tuple):
197
+ return [_model_to_dict(item) for item in value]
198
+ if isinstance(value, dict):
199
+ return {key: _model_to_dict(item) for key, item in value.items()}
200
+ if hasattr(value, "__dict__"):
201
+ return {key: _model_to_dict(item) for key, item in vars(value).items()}
202
+ return value
203
+
204
+
205
+ def _attach_agent_report_evaluation(report: TestReport, evaluation: Any) -> None:
206
+ cases = getattr(evaluation, "cases", []) or []
207
+ summary = _model_to_dict(getattr(evaluation, "summary", {}))
208
+ aggregate = {
209
+ "score": getattr(evaluation, "score", None),
210
+ "passed": getattr(evaluation, "passed", None),
211
+ "threshold": getattr(evaluation, "threshold", None),
212
+ "summary": summary,
213
+ }
214
+ for index, result in enumerate(report.results):
215
+ case = cases[index] if index < len(cases) else None
216
+ metrics = getattr(case, "metrics", []) if case is not None else []
217
+ payload = {
218
+ **aggregate,
219
+ "case_score": getattr(case, "score", None) if case is not None else None,
220
+ "case_passed": getattr(case, "passed", None) if case is not None else None,
221
+ "metrics": [_model_to_dict(metric) for metric in metrics],
222
+ "findings": _model_to_dict(getattr(case, "findings", [])) if case is not None else [],
223
+ }
224
+ result.evaluation = dict(result.evaluation or {})
225
+ result.evaluation["agent_report"] = payload
226
+ result.metadata = dict(result.metadata or {})
227
+ result.metadata["agent_report_summary"] = aggregate
@@ -0,0 +1,35 @@
1
+ from .base import (
2
+ AgentEvidenceSource,
3
+ EvidenceCapabilities,
4
+ EvidenceClass,
5
+ EvidenceSourceSpec,
6
+ EvidenceSourceSummary,
7
+ )
8
+ from .caller_observed import CallerObservedEvidenceSource
9
+ from .livekit_instrumentation import LiveKitAgentInstrumentationSource
10
+ from .livekit_room import LiveKitRoomDataEvidenceSource
11
+ from .otel import OpenTelemetryEvidenceSource
12
+ from .providers import (
13
+ EvidenceContext,
14
+ ProviderConfigError,
15
+ ProviderFetchResult,
16
+ RetellEvidenceSource,
17
+ VapiEvidenceSource,
18
+ )
19
+
20
+ __all__ = [
21
+ "AgentEvidenceSource",
22
+ "CallerObservedEvidenceSource",
23
+ "EvidenceCapabilities",
24
+ "EvidenceClass",
25
+ "EvidenceContext",
26
+ "EvidenceSourceSpec",
27
+ "EvidenceSourceSummary",
28
+ "LiveKitAgentInstrumentationSource",
29
+ "LiveKitRoomDataEvidenceSource",
30
+ "OpenTelemetryEvidenceSource",
31
+ "ProviderConfigError",
32
+ "ProviderFetchResult",
33
+ "RetellEvidenceSource",
34
+ "VapiEvidenceSource",
35
+ ]
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from typing import Protocol
5
+
6
+ from pydantic import BaseModel, Field, JsonValue
7
+
8
+
9
+ class EvidenceClass(str, Enum):
10
+ CALLER_OBSERVED = "caller_observed"
11
+ PROVIDER_REPORTED = "provider_reported"
12
+ AGENT_INSTRUMENTED = "agent_instrumented"
13
+ PLATFORM_VERIFIED = "platform_verified"
14
+
15
+
16
+ class EvidenceCapabilities(BaseModel):
17
+ transcript: bool = False
18
+ audio: bool = False
19
+ tool_calls: bool = False
20
+ tool_results: bool = False
21
+ usage: bool = False
22
+ internal_latency: bool = False
23
+ configuration_snapshot: bool = False
24
+
25
+ def supported(self) -> set[str]:
26
+ return {
27
+ name
28
+ for name, enabled in self.model_dump().items()
29
+ if enabled
30
+ }
31
+
32
+
33
+ class EvidenceSourceSpec(BaseModel):
34
+ source_id: str
35
+ adapter: str
36
+ adapter_version: str = "1"
37
+ evidence_class: EvidenceClass
38
+ capabilities: EvidenceCapabilities = Field(default_factory=EvidenceCapabilities)
39
+ config: dict[str, JsonValue] = Field(default_factory=dict)
40
+
41
+
42
+ class EvidenceSourceSummary(BaseModel):
43
+ source_id: str
44
+ adapter: str
45
+ evidence_class: EvidenceClass
46
+ capabilities: EvidenceCapabilities = Field(default_factory=EvidenceCapabilities)
47
+ available: bool = True
48
+ redactions: list[str] = Field(default_factory=list)
49
+ metadata: dict[str, JsonValue] = Field(default_factory=dict)
50
+
51
+
52
+ class AgentEvidenceSource(Protocol):
53
+ capabilities: EvidenceCapabilities
54
+
55
+ async def connect(self, context: object) -> None: ...
56
+
57
+ async def fetch_final(self) -> object: ...
58
+
59
+ async def close(self) -> None: ...
@@ -0,0 +1,50 @@
1
+ """CallerObservedEvidenceSource skeleton (plan §6).
2
+
3
+ Reports whatever the SDK's own recorder + transcript captured for the
4
+ simulator leg (audio, transcript, timing) without consulting provider
5
+ APIs or agent instrumentation. Real per-track summary derivation lands
6
+ alongside the media router in §5.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import uuid
12
+
13
+ from .base import EvidenceCapabilities, EvidenceClass, EvidenceSourceSummary
14
+ from .providers.base import EvidenceContext, ProviderFetchResult
15
+
16
+
17
+ class CallerObservedEvidenceSource:
18
+ capabilities = EvidenceCapabilities(
19
+ transcript=True,
20
+ audio=True,
21
+ tool_calls=False,
22
+ tool_results=False,
23
+ usage=False,
24
+ internal_latency=False,
25
+ configuration_snapshot=False,
26
+ )
27
+
28
+ def __init__(self) -> None:
29
+ self._source_id = f"caller_observed:{uuid.uuid4().hex[:12]}"
30
+ self._context: EvidenceContext | None = None
31
+
32
+ async def connect(self, context: EvidenceContext) -> None:
33
+ self._context = context
34
+
35
+ async def fetch_final(self) -> ProviderFetchResult:
36
+ summary = EvidenceSourceSummary(
37
+ source_id=self._source_id,
38
+ adapter="caller_observed",
39
+ evidence_class=EvidenceClass.CALLER_OBSERVED,
40
+ capabilities=self.capabilities,
41
+ available=False,
42
+ metadata={"reason": "not_implemented"},
43
+ )
44
+ return ProviderFetchResult(summary=summary, artifacts=[])
45
+
46
+ async def close(self) -> None:
47
+ return None
48
+
49
+
50
+ __all__ = ["CallerObservedEvidenceSource"]
@@ -0,0 +1,51 @@
1
+ """LiveKitAgentInstrumentationSource skeleton (plan §6.6).
2
+
3
+ Consumes canonical events emitted by
4
+ ``fi.simulate.instrumentation.livekit.FutureAGIObserver`` and surfaces
5
+ them as agent-instrumented evidence. Bridging the observer's in-process
6
+ event stream into a case-scoped ``EvidenceSourceSummary`` lands with
7
+ Stage 7.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import uuid
13
+
14
+ from .base import EvidenceCapabilities, EvidenceClass, EvidenceSourceSummary
15
+ from .providers.base import EvidenceContext, ProviderFetchResult
16
+
17
+
18
+ class LiveKitAgentInstrumentationSource:
19
+ capabilities = EvidenceCapabilities(
20
+ transcript=True,
21
+ audio=False,
22
+ tool_calls=True,
23
+ tool_results=True,
24
+ usage=True,
25
+ internal_latency=True,
26
+ configuration_snapshot=True,
27
+ )
28
+
29
+ def __init__(self) -> None:
30
+ self._source_id = f"livekit_instrumentation:{uuid.uuid4().hex[:12]}"
31
+ self._context: EvidenceContext | None = None
32
+
33
+ async def connect(self, context: EvidenceContext) -> None:
34
+ self._context = context
35
+
36
+ async def fetch_final(self) -> ProviderFetchResult:
37
+ summary = EvidenceSourceSummary(
38
+ source_id=self._source_id,
39
+ adapter="livekit_instrumentation",
40
+ evidence_class=EvidenceClass.AGENT_INSTRUMENTED,
41
+ capabilities=self.capabilities,
42
+ available=False,
43
+ metadata={"reason": "not_implemented"},
44
+ )
45
+ return ProviderFetchResult(summary=summary, artifacts=[])
46
+
47
+ async def close(self) -> None:
48
+ return None
49
+
50
+
51
+ __all__ = ["LiveKitAgentInstrumentationSource"]
@@ -0,0 +1,50 @@
1
+ """LiveKitRoomDataEvidenceSource skeleton (plan §6.5).
2
+
3
+ Streams participant events, tracks, and data-channel messages via the
4
+ LiveKit room API. This skeleton establishes the seam for Stage 7 and
5
+ declares the capabilities LiveKit-native evidence carries even before
6
+ the collector is wired up.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import uuid
12
+
13
+ from .base import EvidenceCapabilities, EvidenceClass, EvidenceSourceSummary
14
+ from .providers.base import EvidenceContext, ProviderFetchResult
15
+
16
+
17
+ class LiveKitRoomDataEvidenceSource:
18
+ capabilities = EvidenceCapabilities(
19
+ transcript=False,
20
+ audio=True,
21
+ tool_calls=False,
22
+ tool_results=False,
23
+ usage=False,
24
+ internal_latency=False,
25
+ configuration_snapshot=True,
26
+ )
27
+
28
+ def __init__(self) -> None:
29
+ self._source_id = f"livekit_room:{uuid.uuid4().hex[:12]}"
30
+ self._context: EvidenceContext | None = None
31
+
32
+ async def connect(self, context: EvidenceContext) -> None:
33
+ self._context = context
34
+
35
+ async def fetch_final(self) -> ProviderFetchResult:
36
+ summary = EvidenceSourceSummary(
37
+ source_id=self._source_id,
38
+ adapter="livekit_room",
39
+ evidence_class=EvidenceClass.PLATFORM_VERIFIED,
40
+ capabilities=self.capabilities,
41
+ available=False,
42
+ metadata={"reason": "not_implemented"},
43
+ )
44
+ return ProviderFetchResult(summary=summary, artifacts=[])
45
+
46
+ async def close(self) -> None:
47
+ return None
48
+
49
+
50
+ __all__ = ["LiveKitRoomDataEvidenceSource"]