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