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