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,147 @@
|
|
|
1
|
+
"""Stage zero: working out which agent you mean.
|
|
2
|
+
|
|
3
|
+
Everything the harness does is about one agent, so something has to establish which one. That
|
|
4
|
+
used to be two flags on a command line, which is the wrong place for it: the whole point is that
|
|
5
|
+
you say what you want and it happens, and "here is my agent, set up a test environment for it"
|
|
6
|
+
is a sentence, not an invocation.
|
|
7
|
+
|
|
8
|
+
So this is a stage like any other. It can look around the filesystem to find what you are
|
|
9
|
+
pointing at, it asks if what you said is ambiguous, and it finishes by naming the agent and where
|
|
10
|
+
it lives. Everything after it, including where artifacts are written, follows from that.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from .backends import SessionSpec, tool, tool_server
|
|
20
|
+
|
|
21
|
+
from .config import (
|
|
22
|
+
artifact_dir,
|
|
23
|
+
chosen_model,
|
|
24
|
+
)
|
|
25
|
+
from .session import Stage
|
|
26
|
+
from .sources import AgentSource, clone_github_repository, resolve, supported
|
|
27
|
+
from .tools import schema
|
|
28
|
+
|
|
29
|
+
RECEPTION_SERVER = "agent"
|
|
30
|
+
TOOL_NAMES = ("point_at_agent",)
|
|
31
|
+
|
|
32
|
+
_INSTRUCTIONS = """
|
|
33
|
+
You are the front desk of a harness that builds test environments for agents.
|
|
34
|
+
|
|
35
|
+
Somebody has arrived with an agent they want tested. Your only job is to work out which agent,
|
|
36
|
+
and where it lives, and then call point_at_agent. Nothing else happens until you do.
|
|
37
|
+
|
|
38
|
+
Usually they will just tell you: a path, a repository, a folder, or a public GitHub URL. Take it.
|
|
39
|
+
For an explicit local path, call point_at_agent immediately: that tool validates the path. Do not
|
|
40
|
+
scan it first. Use Read, Glob or Grep only when the person gave an ambiguous parent directory or
|
|
41
|
+
no path and you genuinely have to locate the agent. For a GitHub URL, call point_at_agent with kind "github" and the URL as
|
|
42
|
+
path. The harness clones it into this session; do not ask them to clone it themselves. A name is a
|
|
43
|
+
label for their artifacts, so lower case and no spaces.
|
|
44
|
+
|
|
45
|
+
The agent is usually somewhere else on disk, not inside the harness. A path they give you is
|
|
46
|
+
relative to where you are looking from, which is a workspace holding many repositories, so try
|
|
47
|
+
it as given before deciding it does not exist.
|
|
48
|
+
|
|
49
|
+
If the path really is not there, say so and say what you did find near it. If they gestured
|
|
50
|
+
vaguely at a directory holding several agents, look, and ask which one with AskUserQuestion.
|
|
51
|
+
|
|
52
|
+
Do not read the agent properly and do not start working anything out about it. That is the next
|
|
53
|
+
stage's job and it has its own instructions. Point at the agent, say in one line what you are
|
|
54
|
+
about to do, and stop.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def point_at(
|
|
59
|
+
name: str,
|
|
60
|
+
path: str,
|
|
61
|
+
kind: str,
|
|
62
|
+
found: dict[str, AgentSource],
|
|
63
|
+
source_dir: Path | None = None,
|
|
64
|
+
) -> dict[str, Any]:
|
|
65
|
+
"""Establish which agent this is, or say why it cannot be.
|
|
66
|
+
|
|
67
|
+
A plain function rather than only a tool body, so what counts as a reachable agent can be
|
|
68
|
+
exercised without standing up a session.
|
|
69
|
+
"""
|
|
70
|
+
name, path, kind = name.strip(), path.strip(), (kind.strip() or "repo")
|
|
71
|
+
if not name:
|
|
72
|
+
return _err("no name: the artifacts have to be filed under something")
|
|
73
|
+
if kind not in supported():
|
|
74
|
+
return _err(f"no such kind {kind!r}; there is {', '.join(supported())}")
|
|
75
|
+
if kind == "repo" and not Path(path).expanduser().exists():
|
|
76
|
+
return _err(
|
|
77
|
+
f"there is nothing at {path!r}. Look again with Glob, and if you cannot find it, "
|
|
78
|
+
"ask where the agent actually lives."
|
|
79
|
+
)
|
|
80
|
+
try:
|
|
81
|
+
if kind == "github":
|
|
82
|
+
root = clone_github_repository(
|
|
83
|
+
path, source_dir or artifact_dir(name) / "source"
|
|
84
|
+
)
|
|
85
|
+
found["source"] = resolve(kind, name=name, root=root, url=path)
|
|
86
|
+
else:
|
|
87
|
+
found["source"] = resolve(kind, name=name, root=Path(path).expanduser())
|
|
88
|
+
except Exception as failed:
|
|
89
|
+
return _err(f"could not reach that agent: {failed}")
|
|
90
|
+
return {
|
|
91
|
+
"content": [{"type": "text", "text": f"Pointed at {name} ({kind}) at {path}."}]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def open_stage(
|
|
96
|
+
*,
|
|
97
|
+
cwd: str | Path | None = None,
|
|
98
|
+
source_dir: str | Path | None = None,
|
|
99
|
+
ask: Callable[..., Any] | None = None,
|
|
100
|
+
max_turns: int = 20,
|
|
101
|
+
) -> tuple[Stage, dict[str, AgentSource]]:
|
|
102
|
+
"""A stage that establishes which agent this conversation is about."""
|
|
103
|
+
found: dict[str, AgentSource] = {}
|
|
104
|
+
|
|
105
|
+
@tool(
|
|
106
|
+
"point_at_agent",
|
|
107
|
+
"Name the agent this conversation is about and say where it is. `kind` is how it is "
|
|
108
|
+
f"supplied, one of: {', '.join(supported())}. For a repository, `path` is its directory; "
|
|
109
|
+
"for github, it is the public HTTPS repository URL and the harness clones it. Call this "
|
|
110
|
+
"once you know what you are pointing at.",
|
|
111
|
+
schema({"name": str, "path": str, "kind": str}, ["name", "path"]),
|
|
112
|
+
)
|
|
113
|
+
async def point_at_agent(args: dict[str, Any]) -> dict[str, Any]:
|
|
114
|
+
return point_at(
|
|
115
|
+
str(args.get("name") or ""),
|
|
116
|
+
str(args.get("path") or ""),
|
|
117
|
+
str(args.get("kind") or "repo"),
|
|
118
|
+
found,
|
|
119
|
+
Path(source_dir) if source_dir else None,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
server = tool_server(
|
|
123
|
+
name=RECEPTION_SERVER, version="0.1.0", tools=[point_at_agent]
|
|
124
|
+
)
|
|
125
|
+
spec = SessionSpec(
|
|
126
|
+
system_prompt=_INSTRUCTIONS.strip(),
|
|
127
|
+
servers={RECEPTION_SERVER: server},
|
|
128
|
+
builtins=("Read", "Glob", "Grep", "AskUserQuestion"),
|
|
129
|
+
cwd=str(cwd or Path.cwd()),
|
|
130
|
+
max_turns=max_turns,
|
|
131
|
+
model=chosen_model(),
|
|
132
|
+
ask=ask,
|
|
133
|
+
thinking=True,
|
|
134
|
+
)
|
|
135
|
+
return Stage(spec, name="reception"), found
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def opening() -> str:
|
|
139
|
+
return (
|
|
140
|
+
"Somebody has just opened the harness and has not said anything yet. Greet them in one "
|
|
141
|
+
"short line and ask which agent they want tested and where it lives. Do not list your "
|
|
142
|
+
"capabilities."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _err(text: str) -> dict[str, Any]:
|
|
147
|
+
return {"content": [{"type": "text", "text": text}], "is_error": True}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"""Native Retell API-chat execution for hosted RL Environment runs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import urllib.error
|
|
8
|
+
import urllib.request
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Any, Mapping
|
|
11
|
+
|
|
12
|
+
from fi.simulate.agent.wrapper import (
|
|
13
|
+
AgentInput,
|
|
14
|
+
AgentResponse,
|
|
15
|
+
AgentWrapper,
|
|
16
|
+
SimulationEvent,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from .call_runner import ArtifactUploader, CallRunnerContext, RETELL_API_KEY_ALIAS
|
|
20
|
+
from .chat_call_runner import (
|
|
21
|
+
_HostedChatTarget,
|
|
22
|
+
_conversation_scenario,
|
|
23
|
+
_drive_conversation,
|
|
24
|
+
_duration_ms,
|
|
25
|
+
_scenario_document,
|
|
26
|
+
_tool_world,
|
|
27
|
+
)
|
|
28
|
+
from .contract import AgentContract
|
|
29
|
+
from .hosted_scheduler import CallAborted, CallOutcome, Scenario, World
|
|
30
|
+
from .outbound import ArtifactKind, format_rfc3339_millis
|
|
31
|
+
from .process_runtime import EnvironmentRuntime
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RetellChatError(RuntimeError):
|
|
35
|
+
"""A bounded, credential-safe Retell Chat API error."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RetellChatEnded(Exception):
|
|
39
|
+
"""Retell reports that the remote chat reached a normal terminal state."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _arguments(value: Any) -> dict[str, Any]:
|
|
43
|
+
if isinstance(value, Mapping):
|
|
44
|
+
return dict(value)
|
|
45
|
+
if isinstance(value, str):
|
|
46
|
+
try:
|
|
47
|
+
parsed = json.loads(value)
|
|
48
|
+
except ValueError:
|
|
49
|
+
return {"_raw": value}
|
|
50
|
+
return dict(parsed) if isinstance(parsed, Mapping) else {"_raw": value}
|
|
51
|
+
return {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _completion_messages(
|
|
55
|
+
messages: Any,
|
|
56
|
+
) -> tuple[str, list[dict[str, Any]], list[dict[str, Any]]]:
|
|
57
|
+
if not isinstance(messages, list):
|
|
58
|
+
raise RetellChatError("retell_chat_completion_messages_invalid")
|
|
59
|
+
answer = ""
|
|
60
|
+
calls: list[dict[str, Any]] = []
|
|
61
|
+
responses: list[dict[str, Any]] = []
|
|
62
|
+
for item in messages:
|
|
63
|
+
if not isinstance(item, Mapping):
|
|
64
|
+
continue
|
|
65
|
+
role = str(item.get("role") or "")
|
|
66
|
+
if role == "agent" and str(item.get("content") or "").strip():
|
|
67
|
+
answer = str(item["content"]).strip()
|
|
68
|
+
elif role == "tool_call_invocation":
|
|
69
|
+
calls.append(
|
|
70
|
+
{
|
|
71
|
+
"id": str(item.get("tool_call_id") or item.get("message_id") or ""),
|
|
72
|
+
"name": str(item.get("name") or ""),
|
|
73
|
+
"arguments": _arguments(item.get("arguments")),
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
elif role == "tool_call_result":
|
|
77
|
+
responses.append(
|
|
78
|
+
{
|
|
79
|
+
"tool_call_id": str(item.get("tool_call_id") or ""),
|
|
80
|
+
"content": item.get("content"),
|
|
81
|
+
"success": not bool(item.get("error")),
|
|
82
|
+
"error": item.get("error"),
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
return answer, calls, responses
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class RetellChatWrapper(AgentWrapper):
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
*,
|
|
92
|
+
api_key: str,
|
|
93
|
+
agent_id: str,
|
|
94
|
+
agent_version: int | None = None,
|
|
95
|
+
dynamic_variables: Mapping[str, Any] | None = None,
|
|
96
|
+
api_base_url: str = "https://api.retellai.com",
|
|
97
|
+
) -> None:
|
|
98
|
+
if not api_key:
|
|
99
|
+
raise RetellChatError("retell_chat_api_key_missing")
|
|
100
|
+
if not agent_id:
|
|
101
|
+
raise RetellChatError("retell_chat_agent_id_missing")
|
|
102
|
+
self._api_key = api_key
|
|
103
|
+
self._agent_id = agent_id
|
|
104
|
+
self._agent_version = agent_version
|
|
105
|
+
self._dynamic_variables = {
|
|
106
|
+
str(key): str(value) for key, value in dict(dynamic_variables or {}).items()
|
|
107
|
+
}
|
|
108
|
+
self._base = api_base_url.rstrip("/")
|
|
109
|
+
self._chat_id: str | None = None
|
|
110
|
+
|
|
111
|
+
def _request(
|
|
112
|
+
self, method: str, path: str, payload: Mapping[str, Any] | None = None
|
|
113
|
+
) -> dict[str, Any]:
|
|
114
|
+
body = None if payload is None else json.dumps(payload).encode("utf-8")
|
|
115
|
+
request = urllib.request.Request(
|
|
116
|
+
self._base + path,
|
|
117
|
+
data=body,
|
|
118
|
+
method=method,
|
|
119
|
+
headers={
|
|
120
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
"Accept": "application/json",
|
|
123
|
+
"User-Agent": "FutureAGI-ALK/1.0",
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
try:
|
|
127
|
+
with urllib.request.urlopen(request, timeout=120) as response: # noqa: S310
|
|
128
|
+
raw = response.read()
|
|
129
|
+
except urllib.error.HTTPError as exc:
|
|
130
|
+
detail = " ".join(exc.read(512).decode("utf-8", errors="replace").split())
|
|
131
|
+
if exc.code == 400 and "chat already ended" in detail.lower():
|
|
132
|
+
raise RetellChatEnded from exc
|
|
133
|
+
raise RetellChatError(
|
|
134
|
+
f"retell_chat_api_error: {method} returned HTTP {exc.code}"
|
|
135
|
+
+ (f": {detail}" if detail else "")
|
|
136
|
+
) from exc
|
|
137
|
+
except (urllib.error.URLError, TimeoutError) as exc:
|
|
138
|
+
raise RetellChatError(
|
|
139
|
+
f"retell_chat_api_unavailable: {type(exc).__name__}"
|
|
140
|
+
) from exc
|
|
141
|
+
if not raw:
|
|
142
|
+
return {}
|
|
143
|
+
try:
|
|
144
|
+
value = json.loads(raw)
|
|
145
|
+
except ValueError as exc:
|
|
146
|
+
raise RetellChatError("retell_chat_api_response_invalid_json") from exc
|
|
147
|
+
if not isinstance(value, dict):
|
|
148
|
+
raise RetellChatError("retell_chat_api_response_must_be_object")
|
|
149
|
+
return value
|
|
150
|
+
|
|
151
|
+
def _is_ended(self) -> bool:
|
|
152
|
+
if self._chat_id is None:
|
|
153
|
+
return False
|
|
154
|
+
result = self._request("GET", f"/get-chat/{self._chat_id}")
|
|
155
|
+
return str(result.get("chat_status") or "").strip().lower() == "ended"
|
|
156
|
+
|
|
157
|
+
def _ended_response(
|
|
158
|
+
self,
|
|
159
|
+
*,
|
|
160
|
+
answer: str = "",
|
|
161
|
+
calls: list[dict[str, Any]] | None = None,
|
|
162
|
+
responses: list[dict[str, Any]] | None = None,
|
|
163
|
+
) -> AgentResponse:
|
|
164
|
+
return AgentResponse(
|
|
165
|
+
content=answer,
|
|
166
|
+
tool_calls=calls or [],
|
|
167
|
+
tool_responses=responses or [],
|
|
168
|
+
events=[
|
|
169
|
+
SimulationEvent(
|
|
170
|
+
type="external_agent",
|
|
171
|
+
name="retell_chat_ended",
|
|
172
|
+
payload={"provider": "retell", "chat_id": self._chat_id},
|
|
173
|
+
)
|
|
174
|
+
],
|
|
175
|
+
metadata={
|
|
176
|
+
"external_agent": {"success": True},
|
|
177
|
+
"retell_chat_id": self._chat_id,
|
|
178
|
+
"conversation_ended": True,
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def _complete(self, content: str) -> AgentResponse:
|
|
183
|
+
if self._chat_id is None:
|
|
184
|
+
create: dict[str, Any] = {
|
|
185
|
+
"agent_id": self._agent_id,
|
|
186
|
+
"metadata": {"source": "futureagi-rl-environment"},
|
|
187
|
+
}
|
|
188
|
+
if self._agent_version is not None:
|
|
189
|
+
create["agent_version"] = self._agent_version
|
|
190
|
+
if self._dynamic_variables:
|
|
191
|
+
create["retell_llm_dynamic_variables"] = self._dynamic_variables
|
|
192
|
+
result = self._request("POST", "/create-chat", create)
|
|
193
|
+
self._chat_id = str(result.get("chat_id") or "").strip()
|
|
194
|
+
if not self._chat_id:
|
|
195
|
+
raise RetellChatError("retell_chat_create_missing_chat_id")
|
|
196
|
+
try:
|
|
197
|
+
result = self._request(
|
|
198
|
+
"POST",
|
|
199
|
+
"/create-chat-completion",
|
|
200
|
+
{"chat_id": self._chat_id, "content": content},
|
|
201
|
+
)
|
|
202
|
+
except RetellChatEnded:
|
|
203
|
+
return self._ended_response()
|
|
204
|
+
answer, calls, responses = _completion_messages(result.get("messages"))
|
|
205
|
+
explicitly_ended = (
|
|
206
|
+
bool(result.get("call_ended"))
|
|
207
|
+
or str(result.get("chat_status") or "").strip().lower() == "ended"
|
|
208
|
+
)
|
|
209
|
+
if not answer:
|
|
210
|
+
if explicitly_ended or self._is_ended():
|
|
211
|
+
return self._ended_response(calls=calls, responses=responses)
|
|
212
|
+
raise RetellChatError("retell_chat_completion_missing_agent_message")
|
|
213
|
+
if explicitly_ended:
|
|
214
|
+
return self._ended_response(answer=answer, calls=calls, responses=responses)
|
|
215
|
+
event = SimulationEvent(
|
|
216
|
+
type="external_agent",
|
|
217
|
+
name="retell_chat_completion",
|
|
218
|
+
payload={
|
|
219
|
+
"provider": "retell",
|
|
220
|
+
"chat_id": self._chat_id,
|
|
221
|
+
"tool_call_count": len(calls),
|
|
222
|
+
},
|
|
223
|
+
)
|
|
224
|
+
return AgentResponse(
|
|
225
|
+
content=answer,
|
|
226
|
+
tool_calls=calls,
|
|
227
|
+
tool_responses=responses,
|
|
228
|
+
events=[event],
|
|
229
|
+
metadata={
|
|
230
|
+
"external_agent": {"success": True},
|
|
231
|
+
"retell_chat_id": self._chat_id,
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
async def call(self, input: AgentInput) -> AgentResponse:
|
|
236
|
+
content = str((input.new_message or {}).get("content") or "").strip()
|
|
237
|
+
if not content:
|
|
238
|
+
raise RetellChatError("retell_chat_user_message_missing")
|
|
239
|
+
return await asyncio.to_thread(self._complete, content)
|
|
240
|
+
|
|
241
|
+
async def aclose(self) -> None:
|
|
242
|
+
if self._chat_id is None:
|
|
243
|
+
return
|
|
244
|
+
chat_id, self._chat_id = self._chat_id, None
|
|
245
|
+
try:
|
|
246
|
+
await asyncio.to_thread(self._request, "PATCH", f"/end-chat/{chat_id}")
|
|
247
|
+
except (RetellChatError, RetellChatEnded):
|
|
248
|
+
# Closing is idempotent. A chat may have ended as part of its final
|
|
249
|
+
# completion, in which case Retell rejects the redundant PATCH with
|
|
250
|
+
# "Chat already ended". Cleanup must never replace a valid call
|
|
251
|
+
# outcome with that harmless terminal response.
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class RetellChatCallRunner:
|
|
256
|
+
"""Drive multi-turn scenarios directly through Retell's native Chat API."""
|
|
257
|
+
|
|
258
|
+
def __init__(self, adapter: ArtifactUploader, context: CallRunnerContext) -> None:
|
|
259
|
+
self._adapter = adapter
|
|
260
|
+
self._context = context
|
|
261
|
+
path = context.bundle_dir / "contract.json"
|
|
262
|
+
self._contract = (
|
|
263
|
+
AgentContract.model_validate_json(path.read_text())
|
|
264
|
+
if path.is_file()
|
|
265
|
+
else None
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
async def run(
|
|
269
|
+
self,
|
|
270
|
+
scenario: Scenario,
|
|
271
|
+
runtime: EnvironmentRuntime,
|
|
272
|
+
*,
|
|
273
|
+
world: World | None = None,
|
|
274
|
+
) -> CallOutcome:
|
|
275
|
+
del world
|
|
276
|
+
if self._contract is None:
|
|
277
|
+
raise CallAborted(
|
|
278
|
+
"chat_contract_unavailable: bundle/contract.json is absent"
|
|
279
|
+
)
|
|
280
|
+
agent_id = str(
|
|
281
|
+
runtime.metadata.get("provider_target_id")
|
|
282
|
+
or self._context.job.agent.config.get("agent_id")
|
|
283
|
+
or ""
|
|
284
|
+
).strip()
|
|
285
|
+
api_key = self._context.target_provider_secret_values.get(
|
|
286
|
+
RETELL_API_KEY_ALIAS, ""
|
|
287
|
+
)
|
|
288
|
+
config = self._context.job.agent.config
|
|
289
|
+
raw_dynamic = config.get("dynamic_variables")
|
|
290
|
+
dynamic_variables = raw_dynamic if isinstance(raw_dynamic, Mapping) else {}
|
|
291
|
+
version_value = config.get("agent_version")
|
|
292
|
+
agent_version = int(version_value) if version_value not in (None, "") else None
|
|
293
|
+
wrapper = RetellChatWrapper(
|
|
294
|
+
api_key=api_key,
|
|
295
|
+
agent_id=agent_id,
|
|
296
|
+
agent_version=agent_version,
|
|
297
|
+
dynamic_variables=dynamic_variables,
|
|
298
|
+
api_base_url=str(
|
|
299
|
+
config.get("provider_api_base_url") or "https://api.retellai.com"
|
|
300
|
+
),
|
|
301
|
+
)
|
|
302
|
+
document = _scenario_document(self._context.bundle_dir, scenario.scenario_key)
|
|
303
|
+
conversation_scenario = _conversation_scenario(document)
|
|
304
|
+
target_world = _tool_world(
|
|
305
|
+
self._context.bundle_dir,
|
|
306
|
+
self._contract,
|
|
307
|
+
runtime,
|
|
308
|
+
self._context.source_directory,
|
|
309
|
+
)
|
|
310
|
+
target = _HostedChatTarget(
|
|
311
|
+
wrapper=wrapper,
|
|
312
|
+
contract=self._contract,
|
|
313
|
+
world=target_world,
|
|
314
|
+
scenario_key=scenario.scenario_key,
|
|
315
|
+
scenario_id=scenario.scenario_id,
|
|
316
|
+
)
|
|
317
|
+
started = datetime.now(timezone.utc)
|
|
318
|
+
try:
|
|
319
|
+
transcript = await _drive_conversation(
|
|
320
|
+
target, conversation_scenario, self._contract, self._context.bundle_dir
|
|
321
|
+
)
|
|
322
|
+
except CallAborted:
|
|
323
|
+
raise
|
|
324
|
+
except Exception as exc:
|
|
325
|
+
raise CallAborted(
|
|
326
|
+
f"retell_chat_target_failed: {type(exc).__name__}: {exc}"
|
|
327
|
+
) from exc
|
|
328
|
+
finally:
|
|
329
|
+
await wrapper.aclose()
|
|
330
|
+
ended = datetime.now(timezone.utc)
|
|
331
|
+
transcript_id = await self._adapter.upload_artifact(
|
|
332
|
+
transcript.artifact(),
|
|
333
|
+
kind=ArtifactKind.TRANSCRIPT,
|
|
334
|
+
scenario_key=scenario.scenario_key,
|
|
335
|
+
)
|
|
336
|
+
calls = tuple(transcript.calls)
|
|
337
|
+
if calls:
|
|
338
|
+
trace = "\n".join(
|
|
339
|
+
json.dumps(
|
|
340
|
+
{
|
|
341
|
+
"name": call.name,
|
|
342
|
+
"arguments": call.arguments,
|
|
343
|
+
"result": call.result,
|
|
344
|
+
"ok": call.ok,
|
|
345
|
+
"error": call.error,
|
|
346
|
+
"refused": call.refused,
|
|
347
|
+
"at": call.at,
|
|
348
|
+
},
|
|
349
|
+
sort_keys=True,
|
|
350
|
+
default=str,
|
|
351
|
+
)
|
|
352
|
+
for call in calls
|
|
353
|
+
).encode()
|
|
354
|
+
await self._adapter.upload_artifact(
|
|
355
|
+
trace, kind=ArtifactKind.TOOL_TRACE, scenario_key=scenario.scenario_key
|
|
356
|
+
)
|
|
357
|
+
return CallOutcome(
|
|
358
|
+
calls=calls,
|
|
359
|
+
turns=len(transcript.exchanges),
|
|
360
|
+
started_at=format_rfc3339_millis(started),
|
|
361
|
+
ended_at=format_rfc3339_millis(ended),
|
|
362
|
+
duration_ms=_duration_ms(started, ended),
|
|
363
|
+
transcript_artifact=transcript_id,
|
|
364
|
+
messages=tuple(transcript.canonical_messages()),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
__all__ = [
|
|
369
|
+
"RetellChatCallRunner",
|
|
370
|
+
"RetellChatEnded",
|
|
371
|
+
"RetellChatError",
|
|
372
|
+
"RetellChatWrapper",
|
|
373
|
+
]
|