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.
- agent_learning_kit-0.1.0.dist-info/METADATA +381 -0
- agent_learning_kit-0.1.0.dist-info/RECORD +642 -0
- agent_learning_kit-0.1.0.dist-info/WHEEL +4 -0
- agent_learning_kit-0.1.0.dist-info/entry_points.txt +5 -0
- agent_learning_kit-0.1.0.dist-info/licenses/LICENSE +173 -0
- agent_learning_kit-0.1.0.dist-info/licenses/NOTICE +7 -0
- fi/__init__.py +5 -0
- fi/alk/__init__.py +57 -0
- fi/alk/_facade.py +31 -0
- fi/alk/_module_alias.py +68 -0
- fi/alk/_paths.py +14 -0
- fi/alk/_schema.py +522 -0
- fi/alk/actions.py +727 -0
- fi/alk/bench/__init__.py +517 -0
- fi/alk/bench/_codeexec.py +213 -0
- fi/alk/bench/_coding.py +215 -0
- fi/alk/bench/_docker.py +237 -0
- fi/alk/bench/_grader.py +286 -0
- fi/alk/bench/_pull.py +212 -0
- fi/alk/bench/_voice.py +147 -0
- fi/alk/capabilities.py +627 -0
- fi/alk/cli.py +6396 -0
- fi/alk/config.py +130 -0
- fi/alk/cua_loop.py +562 -0
- fi/alk/evals.py +2351 -0
- fi/alk/extensions.py +163 -0
- fi/alk/harness/ARCHITECTURE.md +231 -0
- fi/alk/harness/DESIGN.md +246 -0
- fi/alk/harness/ENVIRONMENT_CONFORMANCE.md +127 -0
- fi/alk/harness/HOW-IT-WORKS.md +297 -0
- fi/alk/harness/IMPLEMENTATION_AND_VALIDATION_STATUS.md +229 -0
- fi/alk/harness/README.md +417 -0
- fi/alk/harness/__init__.py +77 -0
- fi/alk/harness/__main__.py +3 -0
- fi/alk/harness/amend.py +312 -0
- fi/alk/harness/artifacts.py +319 -0
- fi/alk/harness/authoring_entrypoint.py +189 -0
- fi/alk/harness/authoring_runtime_validation.py +267 -0
- fi/alk/harness/backends/README.md +43 -0
- fi/alk/harness/backends/__init__.py +122 -0
- fi/alk/harness/backends/base.py +241 -0
- fi/alk/harness/backends/claude.py +211 -0
- fi/alk/harness/backends/files.py +182 -0
- fi/alk/harness/backends/vertex_gemini.py +457 -0
- fi/alk/harness/background_noise.py +95 -0
- fi/alk/harness/build.py +385 -0
- fi/alk/harness/bundle.py +593 -0
- fi/alk/harness/bundle_author_v2.py +1831 -0
- fi/alk/harness/bundle_v2.py +719 -0
- fi/alk/harness/call_runner.py +1440 -0
- fi/alk/harness/callback_http_adapter.py +111 -0
- fi/alk/harness/catalogue.py +287 -0
- fi/alk/harness/chat.py +428 -0
- fi/alk/harness/chat_call_runner.py +506 -0
- fi/alk/harness/checks.py +136 -0
- fi/alk/harness/cli.py +1354 -0
- fi/alk/harness/config.py +338 -0
- fi/alk/harness/contract.py +718 -0
- fi/alk/harness/credentials.py +674 -0
- fi/alk/harness/data/persona_vocabulary.json +111 -0
- fi/alk/harness/environment.py +99 -0
- fi/alk/harness/environment_plan.py +168 -0
- fi/alk/harness/events.py +125 -0
- fi/alk/harness/executor.py +304 -0
- fi/alk/harness/folder.py +234 -0
- fi/alk/harness/generated_runtime.py +815 -0
- fi/alk/harness/github.py +72 -0
- fi/alk/harness/hosted_authoring_entrypoint.py +183 -0
- fi/alk/harness/hosted_entrypoint.py +2402 -0
- fi/alk/harness/hosted_scheduler.py +2218 -0
- fi/alk/harness/job.py +426 -0
- fi/alk/harness/judge.py +184 -0
- fi/alk/harness/livekit_source.py +50 -0
- fi/alk/harness/livekit_tool_trace_bootstrap.py +71 -0
- fi/alk/harness/observability.py +208 -0
- fi/alk/harness/outbound.py +3252 -0
- fi/alk/harness/packaging.py +515 -0
- fi/alk/harness/persona_guides.py +157 -0
- fi/alk/harness/platform.py +692 -0
- fi/alk/harness/process_preflight.py +764 -0
- fi/alk/harness/process_runtime.py +5670 -0
- fi/alk/harness/prove.py +425 -0
- fi/alk/harness/provider_import.py +703 -0
- fi/alk/harness/provider_lifecycle.py +392 -0
- fi/alk/harness/provision.py +2896 -0
- fi/alk/harness/reception.py +147 -0
- fi/alk/harness/retell_chat_call_runner.py +373 -0
- fi/alk/harness/run/__init__.py +296 -0
- fi/alk/harness/run/alk.py +184 -0
- fi/alk/harness/run/call.py +162 -0
- fi/alk/harness/run/conversation.py +264 -0
- fi/alk/harness/run/data/voices_by_language_and_gender.json +693 -0
- fi/alk/harness/run/evidence.py +195 -0
- fi/alk/harness/run/grade.py +598 -0
- fi/alk/harness/run/live.py +297 -0
- fi/alk/harness/run/models.py +56 -0
- fi/alk/harness/run/platform_evals.py +227 -0
- fi/alk/harness/run/sdk_voice.py +130 -0
- fi/alk/harness/run/simulation.py +1209 -0
- fi/alk/harness/run/stage.py +91 -0
- fi/alk/harness/run/targets.py +508 -0
- fi/alk/harness/run/tools.py +601 -0
- fi/alk/harness/run/voice.py +340 -0
- fi/alk/harness/runtime.py +172 -0
- fi/alk/harness/sandbox_server.py +2011 -0
- fi/alk/harness/sandbox_worker.py +44 -0
- fi/alk/harness/scenario.py +1048 -0
- fi/alk/harness/scenario_source.py +879 -0
- fi/alk/harness/scenario_tools.py +1143 -0
- fi/alk/harness/scenarios.py +915 -0
- fi/alk/harness/secrets.py +168 -0
- fi/alk/harness/service_catalog.py +97 -0
- fi/alk/harness/session.py +391 -0
- fi/alk/harness/sessions.py +372 -0
- fi/alk/harness/simulator.py +76 -0
- fi/alk/harness/simulator_voice.py +928 -0
- fi/alk/harness/skills/build-environment/SKILL.md +538 -0
- fi/alk/harness/skills/harness.md +131 -0
- fi/alk/harness/skills/kinds/chat.md +48 -0
- fi/alk/harness/skills/kinds/voice-voicemail.md +63 -0
- fi/alk/harness/skills/kinds/voice.md +59 -0
- fi/alk/harness/skills/plan-suite/SKILL.md +103 -0
- fi/alk/harness/skills/provision-environment/SKILL.md +136 -0
- fi/alk/harness/skills/run-scenarios/SKILL.md +112 -0
- fi/alk/harness/skills/understand-agent/SKILL.md +251 -0
- fi/alk/harness/skills/write-scenarios/SKILL.md +606 -0
- fi/alk/harness/skills/write-scenarios/references/refusals.md +28 -0
- fi/alk/harness/skills/write-scenarios/references/world-api.md +92 -0
- fi/alk/harness/source_data_invariants.py +444 -0
- fi/alk/harness/source_tool_evidence.py +79 -0
- fi/alk/harness/sources.py +253 -0
- fi/alk/harness/spend.py +140 -0
- fi/alk/harness/tool_trace_proxy.py +104 -0
- fi/alk/harness/tools.py +1018 -0
- fi/alk/harness/understand.py +169 -0
- fi/alk/harness/voicemail_audio.py +74 -0
- fi/alk/harness/world/__init__.py +33 -0
- fi/alk/harness/world/errors.py +68 -0
- fi/alk/harness/world/expectations.py +91 -0
- fi/alk/harness/world/handle.py +538 -0
- fi/alk/harness/world/kinds.py +196 -0
- fi/alk/harness/world/mutate.py +186 -0
- fi/alk/harness/world/probe.py +413 -0
- fi/alk/harness/world/provision.py +511 -0
- fi/alk/harness/world/provisioned.py +191 -0
- fi/alk/harness/world/runtime.py +616 -0
- fi/alk/harness/world/snapshot.py +288 -0
- fi/alk/harness/world/stores/__init__.py +305 -0
- fi/alk/harness/world/stores/container.py +215 -0
- fi/alk/harness/world/stores/inprocess.py +346 -0
- fi/alk/harness/world/stores/postgres.py +481 -0
- fi/alk/harness/world/stores/prove.py +202 -0
- fi/alk/harness/world/stores/sqlite.py +245 -0
- fi/alk/harness/world/stores/written.py +182 -0
- fi/alk/harness/world/tools.py +1516 -0
- fi/alk/harness/world/workspace.py +144 -0
- fi/alk/image_loop.py +453 -0
- fi/alk/image_perturb.py +241 -0
- fi/alk/improve.py +274 -0
- fi/alk/live/__init__.py +154 -0
- fi/alk/live/_attribution.py +184 -0
- fi/alk/live/_capture.py +264 -0
- fi/alk/live/_codec.py +391 -0
- fi/alk/live/_contract.py +134 -0
- fi/alk/live/_loopback.py +316 -0
- fi/alk/live/_perturb.py +449 -0
- fi/alk/live/_runner.py +386 -0
- fi/alk/live/_stats.py +561 -0
- fi/alk/live/_transcript.py +240 -0
- fi/alk/live/_workers/__init__.py +9 -0
- fi/alk/live/_workers/a2a_worker.py +316 -0
- fi/alk/live/_workers/langgraph_worker.py +217 -0
- fi/alk/live/_workers/livekit_worker.py +207 -0
- fi/alk/live/_workers/mcp_loopback_server.py +46 -0
- fi/alk/live/_workers/mcp_worker.py +158 -0
- fi/alk/live/_workers/pipecat_worker.py +189 -0
- fi/alk/live/a2a_lane.py +138 -0
- fi/alk/live/langgraph_lane.py +339 -0
- fi/alk/live/livekit_lane.py +376 -0
- fi/alk/live/mcp_lane.py +172 -0
- fi/alk/live/pipecat_lane.py +341 -0
- fi/alk/live/voice_redteam.py +494 -0
- fi/alk/loss.py +306 -0
- fi/alk/optimize.py +36260 -0
- fi/alk/practice/__init__.py +51 -0
- fi/alk/practice/_assess.py +103 -0
- fi/alk/practice/_budget.py +81 -0
- fi/alk/practice/_calibrate.py +69 -0
- fi/alk/practice/_capstone.py +86 -0
- fi/alk/practice/_contract.py +91 -0
- fi/alk/practice/_diagnose.py +79 -0
- fi/alk/practice/_drill.py +196 -0
- fi/alk/practice/_experiment.py +720 -0
- fi/alk/practice/_schedule.py +102 -0
- fi/alk/practice/_store.py +194 -0
- fi/alk/practice/_trainer.py +245 -0
- fi/alk/practice/_update.py +125 -0
- fi/alk/redteam.py +2621 -0
- fi/alk/rewardhack.py +237 -0
- fi/alk/simulate.py +10351 -0
- fi/alk/studio/__init__.py +82 -0
- fi/alk/studio/_bias.py +314 -0
- fi/alk/studio/_calibration.py +522 -0
- fi/alk/studio/_coverage.py +262 -0
- fi/alk/studio/_download.py +665 -0
- fi/alk/studio/_fidelity_attack.py +114 -0
- fi/alk/studio/_generate.py +652 -0
- fi/alk/studio/_library.py +370 -0
- fi/alk/studio/_scan.py +134 -0
- fi/alk/studio/_upgrade.py +42 -0
- fi/alk/studio/_vendor.py +172 -0
- fi/alk/suite.py +4200 -0
- fi/alk/tasks.py +828 -0
- fi/alk/telemetry/__init__.py +149 -0
- fi/alk/telemetry/_contract.py +141 -0
- fi/alk/telemetry/_emit.py +182 -0
- fi/alk/telemetry/_ledger.py +296 -0
- fi/alk/telemetry/_queue.py +127 -0
- fi/alk/telemetry/_row.py +294 -0
- fi/alk/telemetry/_run.py +233 -0
- fi/alk/telemetry/_sync.py +193 -0
- fi/alk/telemetry/_url.py +119 -0
- fi/alk/trinity.py +49397 -0
- fi/alk/voice_loop.py +174 -0
- fi/api/__init__.py +1 -0
- fi/api/auth.py +137 -0
- fi/api/types.py +29 -0
- fi/cli/__init__.py +9 -0
- fi/cli/assertions/__init__.py +25 -0
- fi/cli/assertions/conditions.py +76 -0
- fi/cli/assertions/evaluator.py +286 -0
- fi/cli/assertions/exit_codes.py +20 -0
- fi/cli/assertions/parser.py +131 -0
- fi/cli/assertions/reporter.py +194 -0
- fi/cli/commands/__init__.py +9 -0
- fi/cli/commands/config.py +165 -0
- fi/cli/commands/export.py +208 -0
- fi/cli/commands/init.py +112 -0
- fi/cli/commands/list_cmd.py +213 -0
- fi/cli/commands/run.py +486 -0
- fi/cli/commands/validate.py +173 -0
- fi/cli/commands/view.py +424 -0
- fi/cli/config/__init__.py +6 -0
- fi/cli/config/defaults.py +206 -0
- fi/cli/config/loader.py +155 -0
- fi/cli/config/schema.py +174 -0
- fi/cli/main.py +78 -0
- fi/cli/output/__init__.py +6 -0
- fi/cli/output/formatters.py +106 -0
- fi/cli/output/reporters.py +46 -0
- fi/cli/storage/__init__.py +5 -0
- fi/cli/storage/run_history.py +249 -0
- fi/cli/utils/__init__.py +5 -0
- fi/cli/utils/console.py +44 -0
- fi/evals/__init__.py +131 -0
- fi/evals/autoeval/__init__.py +137 -0
- fi/evals/autoeval/analyzer.py +211 -0
- fi/evals/autoeval/config.py +244 -0
- fi/evals/autoeval/export.py +213 -0
- fi/evals/autoeval/interactive.py +283 -0
- fi/evals/autoeval/pipeline.py +625 -0
- fi/evals/autoeval/prompts.py +139 -0
- fi/evals/autoeval/recommender.py +242 -0
- fi/evals/autoeval/rules.py +589 -0
- fi/evals/autoeval/templates.py +299 -0
- fi/evals/autoeval/types.py +232 -0
- fi/evals/core/__init__.py +16 -0
- fi/evals/core/cloud_registry.py +184 -0
- fi/evals/core/engines.py +368 -0
- fi/evals/core/evaluate.py +319 -0
- fi/evals/core/judge_prompt.py +90 -0
- fi/evals/core/prompt_generator.py +83 -0
- fi/evals/core/registry.py +57 -0
- fi/evals/core/result.py +55 -0
- fi/evals/evaluator.py +721 -0
- fi/evals/execution.py +168 -0
- fi/evals/feedback/__init__.py +32 -0
- fi/evals/feedback/calibrator.py +160 -0
- fi/evals/feedback/collector.py +214 -0
- fi/evals/feedback/hooks.py +81 -0
- fi/evals/feedback/retriever.py +128 -0
- fi/evals/feedback/store.py +272 -0
- fi/evals/feedback/types.py +99 -0
- fi/evals/framework/README.md +79 -0
- fi/evals/framework/__init__.py +267 -0
- fi/evals/framework/backends/Dockerfile.eval-runner +33 -0
- fi/evals/framework/backends/__init__.py +99 -0
- fi/evals/framework/backends/_container.py +141 -0
- fi/evals/framework/backends/_utils.py +145 -0
- fi/evals/framework/backends/base.py +223 -0
- fi/evals/framework/backends/celery_backend.py +417 -0
- fi/evals/framework/backends/celery_worker.py +78 -0
- fi/evals/framework/backends/kubernetes_backend.py +665 -0
- fi/evals/framework/backends/ray_backend.py +521 -0
- fi/evals/framework/backends/temporal.py +350 -0
- fi/evals/framework/backends/temporal_worker.py +126 -0
- fi/evals/framework/backends/thread_pool.py +286 -0
- fi/evals/framework/context.py +258 -0
- fi/evals/framework/enrichment.py +306 -0
- fi/evals/framework/evals/__init__.py +68 -0
- fi/evals/framework/evals/agentic.py +399 -0
- fi/evals/framework/evals/builder.py +609 -0
- fi/evals/framework/evals/semantic.py +142 -0
- fi/evals/framework/evaluator.py +647 -0
- fi/evals/framework/evaluators/__init__.py +22 -0
- fi/evals/framework/evaluators/blocking.py +347 -0
- fi/evals/framework/evaluators/non_blocking.py +577 -0
- fi/evals/framework/propagation.py +421 -0
- fi/evals/framework/protocols.py +385 -0
- fi/evals/framework/registry.py +370 -0
- fi/evals/framework/resilience/__init__.py +150 -0
- fi/evals/framework/resilience/circuit_breaker.py +309 -0
- fi/evals/framework/resilience/degradation.py +355 -0
- fi/evals/framework/resilience/health.py +505 -0
- fi/evals/framework/resilience/rate_limiter.py +228 -0
- fi/evals/framework/resilience/retry.py +274 -0
- fi/evals/framework/resilience/types.py +288 -0
- fi/evals/framework/resilience/wrapper.py +433 -0
- fi/evals/framework/types.py +218 -0
- fi/evals/guardrails/README.md +915 -0
- fi/evals/guardrails/__init__.py +96 -0
- fi/evals/guardrails/backends/__init__.py +43 -0
- fi/evals/guardrails/backends/azure.py +361 -0
- fi/evals/guardrails/backends/base.py +88 -0
- fi/evals/guardrails/backends/generic_llm.py +163 -0
- fi/evals/guardrails/backends/granite.py +216 -0
- fi/evals/guardrails/backends/llamaguard.py +221 -0
- fi/evals/guardrails/backends/local_base.py +479 -0
- fi/evals/guardrails/backends/openai.py +365 -0
- fi/evals/guardrails/backends/qwen.py +170 -0
- fi/evals/guardrails/backends/shieldgemma.py +154 -0
- fi/evals/guardrails/backends/turing.py +235 -0
- fi/evals/guardrails/backends/vllm_client.py +321 -0
- fi/evals/guardrails/backends/wildguard.py +188 -0
- fi/evals/guardrails/base.py +888 -0
- fi/evals/guardrails/config.py +221 -0
- fi/evals/guardrails/discovery.py +243 -0
- fi/evals/guardrails/gateway.py +437 -0
- fi/evals/guardrails/registry.py +231 -0
- fi/evals/guardrails/scanners/__init__.py +127 -0
- fi/evals/guardrails/scanners/base.py +191 -0
- fi/evals/guardrails/scanners/code_injection.py +243 -0
- fi/evals/guardrails/scanners/eval_delegate.py +574 -0
- fi/evals/guardrails/scanners/invisible_chars.py +351 -0
- fi/evals/guardrails/scanners/jailbreak.py +412 -0
- fi/evals/guardrails/scanners/language.py +288 -0
- fi/evals/guardrails/scanners/pipeline.py +260 -0
- fi/evals/guardrails/scanners/regex.py +311 -0
- fi/evals/guardrails/scanners/secrets.py +274 -0
- fi/evals/guardrails/scanners/topics.py +649 -0
- fi/evals/guardrails/scanners/urls.py +341 -0
- fi/evals/guardrails/types.py +96 -0
- fi/evals/llm/__init__.py +3 -0
- fi/evals/llm/base_llm_provider.py +35 -0
- fi/evals/llm/providers/litellm.py +70 -0
- fi/evals/local/__init__.py +90 -0
- fi/evals/local/evaluator.py +690 -0
- fi/evals/local/execution_mode.py +121 -0
- fi/evals/local/llm.py +489 -0
- fi/evals/local/metrics/__init__.py +19 -0
- fi/evals/local/registry.py +360 -0
- fi/evals/manager.py +1018 -0
- fi/evals/manager_types.py +362 -0
- fi/evals/metrics/__init__.py +185 -0
- fi/evals/metrics/agents/__init__.py +74 -0
- fi/evals/metrics/agents/metrics.py +693 -0
- fi/evals/metrics/agents/report.py +36463 -0
- fi/evals/metrics/agents/types.py +160 -0
- fi/evals/metrics/base_llm_metric.py +111 -0
- fi/evals/metrics/base_metric.py +138 -0
- fi/evals/metrics/code_security/__init__.py +305 -0
- fi/evals/metrics/code_security/analyzer.py +985 -0
- fi/evals/metrics/code_security/benchmarks/__init__.py +73 -0
- fi/evals/metrics/code_security/benchmarks/builtin.py +750 -0
- fi/evals/metrics/code_security/benchmarks/loader.py +580 -0
- fi/evals/metrics/code_security/benchmarks/types.py +308 -0
- fi/evals/metrics/code_security/detectors/__init__.py +186 -0
- fi/evals/metrics/code_security/detectors/base.py +394 -0
- fi/evals/metrics/code_security/detectors/cryptography.py +345 -0
- fi/evals/metrics/code_security/detectors/injection.py +744 -0
- fi/evals/metrics/code_security/detectors/secrets.py +287 -0
- fi/evals/metrics/code_security/detectors/serialization.py +192 -0
- fi/evals/metrics/code_security/joint_metrics.py +588 -0
- fi/evals/metrics/code_security/judges/__init__.py +83 -0
- fi/evals/metrics/code_security/judges/base.py +238 -0
- fi/evals/metrics/code_security/judges/dual_judge.py +534 -0
- fi/evals/metrics/code_security/judges/llm_judge.py +301 -0
- fi/evals/metrics/code_security/judges/pattern_judge.py +515 -0
- fi/evals/metrics/code_security/metrics.py +388 -0
- fi/evals/metrics/code_security/modes/__init__.py +63 -0
- fi/evals/metrics/code_security/modes/adversarial.py +284 -0
- fi/evals/metrics/code_security/modes/autocomplete.py +198 -0
- fi/evals/metrics/code_security/modes/base.py +283 -0
- fi/evals/metrics/code_security/modes/instruct.py +253 -0
- fi/evals/metrics/code_security/modes/repair.py +230 -0
- fi/evals/metrics/code_security/reports/__init__.py +57 -0
- fi/evals/metrics/code_security/reports/generator.py +404 -0
- fi/evals/metrics/code_security/reports/leaderboard.py +509 -0
- fi/evals/metrics/code_security/types.py +534 -0
- fi/evals/metrics/function_calling/__init__.py +34 -0
- fi/evals/metrics/function_calling/metrics.py +573 -0
- fi/evals/metrics/function_calling/types.py +87 -0
- fi/evals/metrics/hallucination/__init__.py +54 -0
- fi/evals/metrics/hallucination/detector.py +149 -0
- fi/evals/metrics/hallucination/metrics.py +390 -0
- fi/evals/metrics/hallucination/nli.py +253 -0
- fi/evals/metrics/hallucination/sentinel.py +106 -0
- fi/evals/metrics/hallucination/types.py +132 -0
- fi/evals/metrics/heuristics/aggregation_metrics.py +85 -0
- fi/evals/metrics/heuristics/json_metrics.py +87 -0
- fi/evals/metrics/heuristics/similarity_metrics.py +375 -0
- fi/evals/metrics/heuristics/string_metrics.py +391 -0
- fi/evals/metrics/llm_as_judges/__init__.py +17 -0
- fi/evals/metrics/llm_as_judges/custom_judge/metric.py +112 -0
- fi/evals/metrics/llm_as_judges/custom_judge/prompts.py +26 -0
- fi/evals/metrics/llm_as_judges/types.py +48 -0
- fi/evals/metrics/rag/__init__.py +111 -0
- fi/evals/metrics/rag/advanced/__init__.py +14 -0
- fi/evals/metrics/rag/advanced/multi_hop.py +283 -0
- fi/evals/metrics/rag/advanced/source_attribution.py +344 -0
- fi/evals/metrics/rag/generation/__init__.py +17 -0
- fi/evals/metrics/rag/generation/answer_relevancy.py +176 -0
- fi/evals/metrics/rag/generation/context_utilization.py +245 -0
- fi/evals/metrics/rag/generation/faithfulness.py +241 -0
- fi/evals/metrics/rag/generation/groundedness.py +131 -0
- fi/evals/metrics/rag/rag_score.py +277 -0
- fi/evals/metrics/rag/retrieval/__init__.py +20 -0
- fi/evals/metrics/rag/retrieval/context_entity_recall.py +124 -0
- fi/evals/metrics/rag/retrieval/context_precision.py +158 -0
- fi/evals/metrics/rag/retrieval/context_recall.py +106 -0
- fi/evals/metrics/rag/retrieval/noise_sensitivity.py +163 -0
- fi/evals/metrics/rag/retrieval/ranking.py +261 -0
- fi/evals/metrics/rag/types.py +100 -0
- fi/evals/metrics/rag/utils/__init__.py +62 -0
- fi/evals/metrics/rag/utils/claims.py +189 -0
- fi/evals/metrics/rag/utils/entities.py +244 -0
- fi/evals/metrics/rag/utils/nli.py +92 -0
- fi/evals/metrics/rag/utils/similarity.py +345 -0
- fi/evals/metrics/structured/__init__.py +114 -0
- fi/evals/metrics/structured/field_completeness.py +313 -0
- fi/evals/metrics/structured/hierarchy_score.py +366 -0
- fi/evals/metrics/structured/json_validation.py +190 -0
- fi/evals/metrics/structured/schema_compliance.py +280 -0
- fi/evals/metrics/structured/structured_output_score.py +298 -0
- fi/evals/metrics/structured/types.py +108 -0
- fi/evals/metrics/structured/validators/__init__.py +30 -0
- fi/evals/metrics/structured/validators/base.py +189 -0
- fi/evals/metrics/structured/validators/json_validator.py +196 -0
- fi/evals/metrics/structured/validators/pydantic_validator.py +178 -0
- fi/evals/metrics/structured/validators/yaml_validator.py +248 -0
- fi/evals/otel/__init__.py +266 -0
- fi/evals/otel/config.py +400 -0
- fi/evals/otel/conventions.py +463 -0
- fi/evals/otel/enrichment.py +371 -0
- fi/evals/otel/instrumentors/__init__.py +140 -0
- fi/evals/otel/instrumentors/anthropic.py +517 -0
- fi/evals/otel/instrumentors/base.py +382 -0
- fi/evals/otel/instrumentors/openai.py +673 -0
- fi/evals/otel/processors/__init__.py +36 -0
- fi/evals/otel/processors/base.py +473 -0
- fi/evals/otel/processors/cost.py +445 -0
- fi/evals/otel/processors/evaluation.py +559 -0
- fi/evals/otel/processors/llm.py +462 -0
- fi/evals/otel/tracer.py +506 -0
- fi/evals/otel/types.py +232 -0
- fi/evals/otel_utils.py +23 -0
- fi/evals/protect.py +671 -0
- fi/evals/protect_input_adapter.py +154 -0
- fi/evals/streaming/__init__.py +88 -0
- fi/evals/streaming/buffer.py +213 -0
- fi/evals/streaming/evaluator.py +551 -0
- fi/evals/streaming/policy.py +307 -0
- fi/evals/streaming/scorers.py +368 -0
- fi/evals/streaming/types.py +238 -0
- fi/evals/templates.py +472 -0
- fi/evals/types.py +156 -0
- fi/opt/__init__.py +221 -0
- fi/opt/_objective_scoring.py +85 -0
- fi/opt/base/__init__.py +11 -0
- fi/opt/base/base_generator.py +33 -0
- fi/opt/base/base_mapper.py +26 -0
- fi/opt/base/base_optimizer.py +45 -0
- fi/opt/base/evaluator.py +211 -0
- fi/opt/components.py +3095 -0
- fi/opt/datamappers/__init__.py +3 -0
- fi/opt/datamappers/basic_mapper.py +40 -0
- fi/opt/deployment.py +1021 -0
- fi/opt/evidence.py +4332 -0
- fi/opt/generators/__init__.py +3 -0
- fi/opt/generators/litellm.py +66 -0
- fi/opt/integrations/__init__.py +23 -0
- fi/opt/integrations/generative_suite.py +410 -0
- fi/opt/integrations/simulate.py +1313 -0
- fi/opt/mutations.py +771 -0
- fi/opt/observability.py +4639 -0
- fi/opt/optimizer_trace.py +889 -0
- fi/opt/optimizers/__init__.py +80 -0
- fi/opt/optimizers/agent.py +331 -0
- fi/opt/optimizers/agent_bandit.py +392 -0
- fi/opt/optimizers/agent_curriculum.py +635 -0
- fi/opt/optimizers/agent_evolution.py +894 -0
- fi/opt/optimizers/agent_feedback.py +1863 -0
- fi/opt/optimizers/agent_pareto.py +547 -0
- fi/opt/optimizers/agent_social_memory.py +1113 -0
- fi/opt/optimizers/agent_tpe.py +321 -0
- fi/opt/optimizers/bayesian_search.py +449 -0
- fi/opt/optimizers/council.py +2075 -0
- fi/opt/optimizers/futureagi_replay.py +799 -0
- fi/opt/optimizers/gepa.py +322 -0
- fi/opt/optimizers/metaprompt.py +243 -0
- fi/opt/optimizers/promptwizard.py +417 -0
- fi/opt/optimizers/protegi.py +329 -0
- fi/opt/optimizers/random_search.py +224 -0
- fi/opt/research.py +518 -0
- fi/opt/simulation.py +260 -0
- fi/opt/targets.py +232 -0
- fi/opt/types.py +66 -0
- fi/opt/utils/__init__.py +4 -0
- fi/opt/utils/early_stopping.py +266 -0
- fi/opt/utils/setup_logging.py +82 -0
- fi/simulate/__init__.py +540 -0
- fi/simulate/_hashing.py +35 -0
- fi/simulate/_logging.py +10 -0
- fi/simulate/adapters.py +87 -0
- fi/simulate/agent/__init__.py +120 -0
- fi/simulate/agent/browser.py +658 -0
- fi/simulate/agent/definition.py +587 -0
- fi/simulate/agent/frameworks.py +3528 -0
- fi/simulate/agent/generic.py +8286 -0
- fi/simulate/agent/import_probe.py +227 -0
- fi/simulate/agent/memory.py +905 -0
- fi/simulate/agent/mocks.py +101 -0
- fi/simulate/agent/multi_agent.py +361 -0
- fi/simulate/agent/orchestration.py +903 -0
- fi/simulate/agent/realtime.py +665 -0
- fi/simulate/agent/wrapper.py +99 -0
- fi/simulate/agent/wrappers/__init__.py +18 -0
- fi/simulate/agent/wrappers/anthropic.py +62 -0
- fi/simulate/agent/wrappers/gemini.py +65 -0
- fi/simulate/agent/wrappers/http.py +404 -0
- fi/simulate/agent/wrappers/langchain.py +80 -0
- fi/simulate/agent/wrappers/openai.py +75 -0
- fi/simulate/agent/wrappers/websocket.py +326 -0
- fi/simulate/artifacts/__init__.py +11 -0
- fi/simulate/artifacts/manifest.py +62 -0
- fi/simulate/cli.py +20560 -0
- fi/simulate/endpoints/__init__.py +45 -0
- fi/simulate/endpoints/_http_actor.py +73 -0
- fi/simulate/endpoints/actor_sources.py +243 -0
- fi/simulate/endpoints/base.py +107 -0
- fi/simulate/endpoints/builtins.py +10 -0
- fi/simulate/endpoints/callable.py +95 -0
- fi/simulate/endpoints/http.py +76 -0
- fi/simulate/endpoints/livekit.py +138 -0
- fi/simulate/endpoints/originators.py +132 -0
- fi/simulate/endpoints/profiles.py +348 -0
- fi/simulate/endpoints/retell.py +633 -0
- fi/simulate/endpoints/vapi.py +205 -0
- fi/simulate/endpoints/websocket.py +76 -0
- fi/simulate/environment.py +33026 -0
- fi/simulate/environments/__init__.py +11 -0
- fi/simulate/environments/base.py +73 -0
- fi/simulate/environments/chat.py +697 -0
- fi/simulate/environments/voice.py +212 -0
- fi/simulate/evaluation/__init__.py +4 -0
- fi/simulate/evaluation/ai_eval.py +227 -0
- fi/simulate/evidence/__init__.py +35 -0
- fi/simulate/evidence/base.py +59 -0
- fi/simulate/evidence/caller_observed.py +50 -0
- fi/simulate/evidence/livekit_instrumentation.py +51 -0
- fi/simulate/evidence/livekit_room.py +50 -0
- fi/simulate/evidence/otel.py +49 -0
- fi/simulate/evidence/providers/__init__.py +24 -0
- fi/simulate/evidence/providers/base.py +61 -0
- fi/simulate/evidence/providers/retell.py +376 -0
- fi/simulate/evidence/providers/vapi.py +426 -0
- fi/simulate/hosted/__init__.py +32 -0
- fi/simulate/hosted/child_entrypoint.py +306 -0
- fi/simulate/hosted/job.py +150 -0
- fi/simulate/hosted/targets.py +53 -0
- fi/simulate/instrumentation/__init__.py +5 -0
- fi/simulate/instrumentation/livekit/__init__.py +122 -0
- fi/simulate/manifest.py +1033 -0
- fi/simulate/matrix_cli.py +165 -0
- fi/simulate/realtime/__init__.py +40 -0
- fi/simulate/realtime/events.py +107 -0
- fi/simulate/realtime/media.py +61 -0
- fi/simulate/realtime/session.py +91 -0
- fi/simulate/recording/__init__.py +5 -0
- fi/simulate/recording/room_recorder.py +326 -0
- fi/simulate/registry.py +185 -0
- fi/simulate/results/__init__.py +9 -0
- fi/simulate/results/base.py +18 -0
- fi/simulate/results/filesystem.py +71 -0
- fi/simulate/results/futureagi.py +1340 -0
- fi/simulate/runtime/__init__.py +85 -0
- fi/simulate/runtime/capabilities.py +40 -0
- fi/simulate/runtime/events.py +63 -0
- fi/simulate/runtime/failures.py +25 -0
- fi/simulate/runtime/ids.py +34 -0
- fi/simulate/runtime/plan.py +70 -0
- fi/simulate/runtime/planner.py +102 -0
- fi/simulate/runtime/report.py +174 -0
- fi/simulate/runtime/run.py +75 -0
- fi/simulate/runtime/runner.py +333 -0
- fi/simulate/runtime/spec.py +186 -0
- fi/simulate/simulation/__init__.py +30 -0
- fi/simulate/simulation/behavior_policy.py +425 -0
- fi/simulate/simulation/bridge/__init__.py +9 -0
- fi/simulate/simulation/bridge/audio.py +29 -0
- fi/simulate/simulation/bridge/connector.py +46 -0
- fi/simulate/simulation/bridge/livekit.py +252 -0
- fi/simulate/simulation/bridge/retell.py +188 -0
- fi/simulate/simulation/bridge/vapi.py +177 -0
- fi/simulate/simulation/contract.py +419 -0
- fi/simulate/simulation/engines/__init__.py +12 -0
- fi/simulate/simulation/engines/base.py +21 -0
- fi/simulate/simulation/engines/cloud.py +517 -0
- fi/simulate/simulation/engines/livekit.py +4167 -0
- fi/simulate/simulation/engines/local_text.py +89 -0
- fi/simulate/simulation/fidelity.py +374 -0
- fi/simulate/simulation/gemini_tts_stream.py +110 -0
- fi/simulate/simulation/generator.py +91 -0
- fi/simulate/simulation/goal_machine.py +185 -0
- fi/simulate/simulation/livekit_models.py +467 -0
- fi/simulate/simulation/matrix.py +170 -0
- fi/simulate/simulation/models.py +279 -0
- fi/simulate/simulation/runner.py +153 -0
- fi/simulate/simulation/synthetic.py +880 -0
- fi/simulate/simulation/voice_prompt.py +502 -0
- fi/simulate/simulator/__init__.py +55 -0
- fi/simulate/simulator/builtins.py +53 -0
- fi/simulate/suite.py +1288 -0
- fi/simulate/utils/routes.py +164 -0
- fi/simulate/voice.py +225 -0
- fi/simulate/voice_cli.py +182 -0
- fi/utils/__init__.py +1 -0
- fi/utils/constants.py +14 -0
- fi/utils/errors.py +200 -0
- fi/utils/executor.py +26 -0
- fi/utils/routes.py +119 -0
- fi/utils/utils.py +17 -0
fi/alk/live/_perturb.py
ADDED
|
@@ -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
|