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,449 @@
1
+ """Kit-native perturbation operators for the ``live_stressed`` sub-lane
2
+ (guide §3.6 / PRD §4.2). Imports: stdlib + numpy only.
3
+
4
+ Operators are deterministic under a recorded seed so stressed runs replay.
5
+ Flipping ANY operator stamps the run ``evidence_class="live_stressed"`` and
6
+ records the operator list in the ``live_lane.perturbations`` stanza; the run
7
+ links its clean twin (``paired_clean_run``).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import random
13
+ from typing import Any, Mapping, Sequence
14
+
15
+ import numpy as np
16
+
17
+ PERTURBATION_OPERATORS = (
18
+ "noise",
19
+ "interference",
20
+ "asr_error",
21
+ "accent",
22
+ "homophone",
23
+ "code_switch",
24
+ "near_dup",
25
+ "reverb_blend",
26
+ )
27
+ # Operators applicable to text-rung input (rung 1: TranscriptionFrames /
28
+ # scripted user text). Acoustic operators need a real audio channel (rung 2+).
29
+ TEXT_RUNG_OPERATORS = ("asr_error", "homophone", "code_switch", "near_dup")
30
+ # Acoustic operators applied to the rung-2 loopback PCM channel (Phase-12 12C
31
+ # rung-2 / ARCH §2c). They activate ONLY when the lane runs at rung-2 and hands
32
+ # ``_perturb`` a real PCM ``np.ndarray``; at text-rung they raise exactly as the
33
+ # pre-existing ``noise``/``interference`` did. ``reverb_blend`` is the operator
34
+ # the Phase-12 BBG deferred (the AudioHijack reverberation-hiding insight, used
35
+ # DEFENSIVELY as a test payload against an agent the user is authorized to test).
36
+ ACOUSTIC_RUNG_OPERATORS = ("noise", "interference", "reverb_blend")
37
+
38
+ _VOWELS = "aeiou"
39
+
40
+ # Spoken-form pairs whose transcripts diverge (rung-1 stand-in for the
41
+ # homophone-divergence surface; defensive test payloads, PRD §2 boundary).
42
+ HOMOPHONE_TABLE = {
43
+ "to": "two", "two": "to", "for": "four", "four": "for",
44
+ "right": "write", "write": "right", "buy": "by", "by": "buy",
45
+ "cell": "sell", "sell": "cell", "here": "hear", "hear": "here",
46
+ "new": "knew", "knew": "new", "wait": "weight", "weight": "wait",
47
+ "aloud": "allowed", "allowed": "aloud", "cents": "sense", "sense": "cents",
48
+ }
49
+
50
+ # Phonologically plausible code-switch / pseudo-word substitutions around
51
+ # safety-adjacent terms (SpeechJBB 2606.06037 lineage — shipped as TEST
52
+ # payloads against the user's own agent, never as evasion guidance).
53
+ CODE_SWITCH_TABLE = {
54
+ "password": "passwort", "account": "akaunt", "transfer": "transfèr",
55
+ "delete": "dilit", "confirm": "konfirm", "security": "sekurité",
56
+ "verify": "verefai", "balance": "balans", "cancel": "kansel",
57
+ }
58
+
59
+
60
+ def apply_asr_error(text: str, *, rate: float = 0.08, seed: int = 0) -> str:
61
+ """Confusion-matrix style token corruption at a configured rate —
62
+ deterministic under the seed. Mimics common ASR failure modes: dropped
63
+ characters, adjacent transpositions, vowel confusions, duplications."""
64
+
65
+ if not text or rate <= 0:
66
+ return text
67
+ rng = random.Random(f"{seed}:{text}")
68
+ tokens = text.split(" ")
69
+ corrupted: list[str] = []
70
+ for token in tokens:
71
+ if len(token) < 2 or rng.random() >= rate:
72
+ corrupted.append(token)
73
+ continue
74
+ mode = rng.randrange(4)
75
+ position = rng.randrange(len(token) - 1)
76
+ if mode == 0: # drop a character
77
+ corrupted.append(token[:position] + token[position + 1 :])
78
+ elif mode == 1: # transpose adjacent characters
79
+ corrupted.append(
80
+ token[:position]
81
+ + token[position + 1]
82
+ + token[position]
83
+ + token[position + 2 :]
84
+ )
85
+ elif mode == 2: # vowel confusion
86
+ replaced = False
87
+ chars = list(token)
88
+ for index, char in enumerate(chars):
89
+ if char.lower() in _VOWELS:
90
+ replacement = rng.choice(_VOWELS)
91
+ chars[index] = (
92
+ replacement.upper() if char.isupper() else replacement
93
+ )
94
+ replaced = True
95
+ break
96
+ corrupted.append("".join(chars) if replaced else token)
97
+ else: # duplicate a character
98
+ corrupted.append(token[: position + 1] + token[position:])
99
+ return " ".join(corrupted)
100
+
101
+
102
+ def _rewrap_token(original: str, replacement: str) -> str:
103
+ """Re-wrap a stripped-form replacement in the original token's
104
+ punctuation, preserving the case of the first character."""
105
+
106
+ start = 0
107
+ end = len(original)
108
+ while start < end and not original[start].isalnum():
109
+ start += 1
110
+ while end > start and not original[end - 1].isalnum():
111
+ end -= 1
112
+ core = original[start:end]
113
+ if core and core[0].isupper():
114
+ replacement = replacement[:1].upper() + replacement[1:]
115
+ return original[:start] + replacement + original[end:]
116
+
117
+
118
+ def apply_homophone_swap(text: str, *, rate: float = 0.15, seed: int = 0) -> str:
119
+ """Swap table-listed tokens for their transcript-divergent twin at the
120
+ configured rate — deterministic under the seed. Case of the first
121
+ character is preserved; punctuation-adjacent tokens are matched on
122
+ their stripped lowercase form and re-wrapped."""
123
+
124
+ if not text or rate <= 0:
125
+ return text
126
+ rng = random.Random(f"{seed}:{text}")
127
+ swapped: list[str] = []
128
+ for token in text.split(" "):
129
+ stripped = token.strip("".join(
130
+ char for char in token if not char.isalnum()
131
+ )) if token else token
132
+ key = stripped.lower()
133
+ if key not in HOMOPHONE_TABLE or rng.random() >= rate:
134
+ swapped.append(token)
135
+ continue
136
+ swapped.append(_rewrap_token(token, HOMOPHONE_TABLE[key]))
137
+ return " ".join(swapped)
138
+
139
+
140
+ def apply_code_switch(text: str, *, rate: float = 0.2, seed: int = 0) -> str:
141
+ """Substitute safety-adjacent tokens with their code-switched /
142
+ pseudo-word form (``CODE_SWITCH_TABLE``) at the configured rate —
143
+ deterministic under the seed."""
144
+
145
+ if not text or rate <= 0:
146
+ return text
147
+ rng = random.Random(f"{seed}:{text}")
148
+ switched: list[str] = []
149
+ for token in text.split(" "):
150
+ stripped = token.strip("".join(
151
+ char for char in token if not char.isalnum()
152
+ )) if token else token
153
+ key = stripped.lower()
154
+ if key not in CODE_SWITCH_TABLE or rng.random() >= rate:
155
+ switched.append(token)
156
+ continue
157
+ switched.append(_rewrap_token(token, CODE_SWITCH_TABLE[key]))
158
+ return " ".join(switched)
159
+
160
+
161
+ def apply_near_dup(text: str, *, rate: float = 0.1, seed: int = 0) -> str:
162
+ """Streaming-ASR doubled-hypothesis artifact: duplicate a token as an
163
+ adjacent edit-distance-1 variant ("send" -> "send sent") at the
164
+ configured rate; the variant reuses the ``apply_asr_error`` single-token
165
+ corruption modes on the duplicate. Deterministic under the seed."""
166
+
167
+ if not text or rate <= 0:
168
+ return text
169
+ rng = random.Random(f"{seed}:{text}")
170
+ duplicated: list[str] = []
171
+ for token in text.split(" "):
172
+ duplicated.append(token)
173
+ if len(token) < 2 or rng.random() >= rate:
174
+ continue
175
+ mode = rng.randrange(4)
176
+ position = rng.randrange(len(token) - 1)
177
+ if mode == 0: # drop a character
178
+ variant = token[:position] + token[position + 1 :]
179
+ elif mode == 1: # transpose adjacent characters
180
+ variant = (
181
+ token[:position]
182
+ + token[position + 1]
183
+ + token[position]
184
+ + token[position + 2 :]
185
+ )
186
+ elif mode == 2: # vowel confusion
187
+ chars = list(token)
188
+ variant = token
189
+ for index, char in enumerate(chars):
190
+ if char.lower() in _VOWELS:
191
+ replacement = rng.choice(_VOWELS)
192
+ chars[index] = (
193
+ replacement.upper() if char.isupper() else replacement
194
+ )
195
+ variant = "".join(chars)
196
+ break
197
+ else: # duplicate a character
198
+ variant = token[: position + 1] + token[position:]
199
+ duplicated.append(variant)
200
+ return " ".join(duplicated)
201
+
202
+
203
+ def apply_text_perturbations(
204
+ turns: Sequence[Mapping[str, Any]],
205
+ operators: Sequence[str],
206
+ *,
207
+ seed: int = 0,
208
+ asr_error_rate: float = 0.08,
209
+ homophone_rate: float = 0.15,
210
+ code_switch_rate: float = 0.2,
211
+ near_dup_rate: float = 0.1,
212
+ ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
213
+ """Apply text-rung operators to a user turn script. Returns the
214
+ perturbed turns plus the applied-operator records for the
215
+ ``live_lane.perturbations`` stanza. Acoustic operators raise — the rung
216
+ is the gate between voice timing and voice audio evidence."""
217
+
218
+ applied: list[dict[str, Any]] = []
219
+ for operator in operators:
220
+ if operator not in PERTURBATION_OPERATORS:
221
+ raise ValueError(
222
+ f"unknown perturbation operator {operator!r}; "
223
+ f"expected one of {PERTURBATION_OPERATORS}"
224
+ )
225
+ if operator not in TEXT_RUNG_OPERATORS:
226
+ raise ValueError(
227
+ f"perturbation operator {operator!r} needs a real audio "
228
+ "channel (rung 2 loopback transport or above); only "
229
+ f"{TEXT_RUNG_OPERATORS} apply to text-rung input"
230
+ )
231
+ perturbed: list[dict[str, Any]] = []
232
+ for index, turn in enumerate(turns):
233
+ row = dict(turn)
234
+ if "asr_error" in operators and isinstance(row.get("user"), str):
235
+ row["user"] = apply_asr_error(
236
+ row["user"], rate=asr_error_rate, seed=seed + index
237
+ )
238
+ if "homophone" in operators and isinstance(row.get("user"), str):
239
+ row["user"] = apply_homophone_swap(
240
+ row["user"], rate=homophone_rate, seed=seed + index
241
+ )
242
+ if "code_switch" in operators and isinstance(row.get("user"), str):
243
+ row["user"] = apply_code_switch(
244
+ row["user"], rate=code_switch_rate, seed=seed + index
245
+ )
246
+ if "near_dup" in operators and isinstance(row.get("user"), str):
247
+ row["user"] = apply_near_dup(
248
+ row["user"], rate=near_dup_rate, seed=seed + index
249
+ )
250
+ perturbed.append(row)
251
+ if "asr_error" in operators:
252
+ applied.append(
253
+ {"operator": "asr_error", "rate": asr_error_rate, "seed": seed}
254
+ )
255
+ if "homophone" in operators:
256
+ applied.append(
257
+ {"operator": "homophone", "rate": homophone_rate, "seed": seed}
258
+ )
259
+ if "code_switch" in operators:
260
+ applied.append(
261
+ {"operator": "code_switch", "rate": code_switch_rate, "seed": seed}
262
+ )
263
+ if "near_dup" in operators:
264
+ applied.append(
265
+ {"operator": "near_dup", "rate": near_dup_rate, "seed": seed}
266
+ )
267
+ return perturbed, applied
268
+
269
+
270
+ def perturbations_stanza(
271
+ applied: Sequence[Mapping[str, Any]],
272
+ *,
273
+ seed: int,
274
+ paired_clean_run: str | None = None,
275
+ ) -> dict[str, Any]:
276
+ """The ``live_lane.perturbations`` stanza (guide §3.6): operator list,
277
+ recorded seed, and the clean-twin link (deltas render upstream)."""
278
+
279
+ return {
280
+ "operators": [dict(record) for record in applied],
281
+ "seed": seed,
282
+ "paired_clean_run": paired_clean_run,
283
+ }
284
+
285
+
286
+ # --- acoustic operators (rung 2+ — applied to the user PCM channel before
287
+ # the framework hears it) -----------------------------------------------------
288
+
289
+
290
+ def _require_pcm_acoustic(pcm: Any, *, where: str) -> np.ndarray:
291
+ """Type-guard the input as numpy PCM; a text/str/bytes input raises the
292
+ rung-wall ValueError (the same discipline ``_codec._require_pcm`` enforces —
293
+ an acoustic operator over a transcript is a contract error)."""
294
+
295
+ if isinstance(pcm, (str, bytes)):
296
+ raise ValueError(
297
+ f"{where} needs a real audio channel (rung 2 loopback transport or "
298
+ "above); a text/transcript input is a contract error"
299
+ )
300
+ arr = np.asarray(pcm, dtype=float)
301
+ if arr.ndim != 1:
302
+ arr = arr.reshape(-1)
303
+ return arr
304
+
305
+
306
+ def mix_noise(
307
+ pcm: np.ndarray, *, snr_db: float = 20.0, seed: int = 0
308
+ ) -> np.ndarray:
309
+ """Mix seeded gaussian noise into a PCM stream at the given SNR (dB)."""
310
+
311
+ samples = _require_pcm_acoustic(pcm, where="mix_noise")
312
+ if samples.size == 0:
313
+ return samples
314
+ signal_power = float((samples**2).mean())
315
+ if signal_power == 0:
316
+ return samples
317
+ noise_power = signal_power / (10.0 ** (snr_db / 10.0))
318
+ rng = np.random.default_rng(seed)
319
+ noise = rng.normal(0.0, np.sqrt(noise_power), size=samples.shape)
320
+ return samples + noise
321
+
322
+
323
+ def mix_interference(
324
+ pcm: np.ndarray,
325
+ interference: np.ndarray,
326
+ *,
327
+ level_db: float = -10.0,
328
+ ) -> np.ndarray:
329
+ """Overlay a competing-speaker waveform at the given relative level."""
330
+
331
+ samples = _require_pcm_acoustic(pcm, where="mix_interference")
332
+ competing = _require_pcm_acoustic(interference, where="mix_interference")
333
+ if samples.size == 0 or competing.size == 0:
334
+ return samples
335
+ if competing.size < samples.size:
336
+ repeat_count = int(np.ceil(samples.size / competing.size))
337
+ competing = np.tile(competing, repeat_count)
338
+ competing = competing[: samples.size]
339
+ signal_rms = float(np.sqrt((samples**2).mean()))
340
+ competing_rms = float(np.sqrt((competing**2).mean()))
341
+ if competing_rms == 0 or signal_rms == 0:
342
+ return samples
343
+ target_rms = signal_rms * (10.0 ** (level_db / 20.0))
344
+ return samples + competing * (target_rms / competing_rms)
345
+
346
+
347
+ def apply_reverb_blend(
348
+ pcm: np.ndarray,
349
+ *,
350
+ decay: float = 0.4,
351
+ delay_ms: float = 60.0,
352
+ taps: int = 4,
353
+ sample_rate: int = 24000,
354
+ seed: int = 0,
355
+ ) -> np.ndarray:
356
+ """Reverberation-blended payload operator (Phase-12 12C rung-2 deferred,
357
+ ARCH §2c — the AudioHijack reverberation-hiding insight, used DEFENSIVELY as
358
+ a test payload). Convolves the PCM with a seeded multi-tap exponential-decay
359
+ impulse response (a synthetic room reverb), then mixes the wet signal back at
360
+ ``decay`` so the original waveform stays present. Deterministic under the
361
+ seed (``np.random.default_rng(seed)`` jitters the tap gains reproducibly);
362
+ raises at text-rung exactly like ``mix_noise``/``mix_interference``."""
363
+
364
+ samples = _require_pcm_acoustic(pcm, where="apply_reverb_blend")
365
+ if samples.size == 0 or decay <= 0 or taps < 1:
366
+ return samples.astype(np.float32, copy=False)
367
+ rng = np.random.default_rng(seed)
368
+ delay_samples = max(int(sample_rate * delay_ms / 1000.0), 1)
369
+ ir_len = delay_samples * int(taps) + 1
370
+ impulse = np.zeros(ir_len, dtype=float)
371
+ impulse[0] = 1.0 # the dry direct path
372
+ for tap in range(1, int(taps) + 1):
373
+ position = min(tap * delay_samples, ir_len - 1)
374
+ # exponential decay per tap, jittered reproducibly by the seed
375
+ gain = float(decay**tap) * (0.85 + 0.3 * float(rng.random()))
376
+ impulse[position] += gain
377
+ wet = np.convolve(samples, impulse, mode="full")[: samples.size]
378
+ return wet.astype(np.float32, copy=False)
379
+
380
+
381
+ def apply_acoustic_perturbations(
382
+ pcm: np.ndarray,
383
+ operators: Sequence[str],
384
+ *,
385
+ seed: int = 0,
386
+ interference: np.ndarray | None = None,
387
+ snr_db: float = 20.0,
388
+ interference_level_db: float = -10.0,
389
+ reverb_decay: float = 0.4,
390
+ sample_rate: int = 24000,
391
+ ) -> tuple[np.ndarray, list[dict[str, Any]]]:
392
+ """Apply rung-2 acoustic operators to a real PCM channel (Phase-12 12C
393
+ rung-2 / ARCH §2c). The sibling of ``apply_text_perturbations`` for the audio
394
+ rung: it walks the operator list, applies each acoustic operator to the PCM
395
+ in registry order, and returns the perturbed PCM plus the applied-operator
396
+ records for the ``live_lane.perturbations`` stanza (the paired-clean
397
+ discipline is identical to the text rung). Text-rung operators raise here —
398
+ the rung wall runs in BOTH directions (a homophone swap over a waveform is a
399
+ contract error just as ``mix_noise`` over a transcript is).
400
+
401
+ Deterministic under ``seed``: every stochastic element keys on
402
+ ``np.random.default_rng(seed)`` so a re-run produces a BYTE-IDENTICAL PCM and
403
+ the same records — the determinism the rung-2 gate re-asserts over the
404
+ loopback."""
405
+
406
+ samples = _require_pcm_acoustic(pcm, where="apply_acoustic_perturbations")
407
+ for operator in operators:
408
+ if operator not in PERTURBATION_OPERATORS:
409
+ raise ValueError(
410
+ f"unknown perturbation operator {operator!r}; "
411
+ f"expected one of {PERTURBATION_OPERATORS}"
412
+ )
413
+ if operator not in ACOUSTIC_RUNG_OPERATORS:
414
+ raise ValueError(
415
+ f"perturbation operator {operator!r} is a text-rung operator; "
416
+ f"only {ACOUSTIC_RUNG_OPERATORS} apply to the rung-2 PCM channel"
417
+ )
418
+ applied: list[dict[str, Any]] = []
419
+ out = samples
420
+ if "noise" in operators:
421
+ out = mix_noise(out, snr_db=snr_db, seed=seed)
422
+ applied.append({"operator": "noise", "snr_db": snr_db, "seed": seed})
423
+ if "interference" in operators:
424
+ # a seeded synthetic competing speaker when the caller supplies none, so
425
+ # the operator is self-contained and reproducible on the loopback.
426
+ competing = interference
427
+ if competing is None:
428
+ rng = np.random.default_rng(seed + 104729)
429
+ t = np.arange(max(out.size, 1), dtype=float) / float(sample_rate)
430
+ competing = (
431
+ 0.5 * np.sin(2.0 * np.pi * 180.0 * t)
432
+ + 0.05 * rng.standard_normal(max(out.size, 1))
433
+ )
434
+ out = mix_interference(out, competing, level_db=interference_level_db)
435
+ applied.append(
436
+ {
437
+ "operator": "interference",
438
+ "level_db": interference_level_db,
439
+ "seed": seed,
440
+ }
441
+ )
442
+ if "reverb_blend" in operators:
443
+ out = apply_reverb_blend(
444
+ out, decay=reverb_decay, sample_rate=sample_rate, seed=seed
445
+ )
446
+ applied.append(
447
+ {"operator": "reverb_blend", "decay": reverb_decay, "seed": seed}
448
+ )
449
+ return out.astype(np.float32, copy=False), applied