agent-learning-kit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_learning_kit-0.1.0.dist-info/METADATA +381 -0
- agent_learning_kit-0.1.0.dist-info/RECORD +642 -0
- agent_learning_kit-0.1.0.dist-info/WHEEL +4 -0
- agent_learning_kit-0.1.0.dist-info/entry_points.txt +5 -0
- agent_learning_kit-0.1.0.dist-info/licenses/LICENSE +173 -0
- agent_learning_kit-0.1.0.dist-info/licenses/NOTICE +7 -0
- fi/__init__.py +5 -0
- fi/alk/__init__.py +57 -0
- fi/alk/_facade.py +31 -0
- fi/alk/_module_alias.py +68 -0
- fi/alk/_paths.py +14 -0
- fi/alk/_schema.py +522 -0
- fi/alk/actions.py +727 -0
- fi/alk/bench/__init__.py +517 -0
- fi/alk/bench/_codeexec.py +213 -0
- fi/alk/bench/_coding.py +215 -0
- fi/alk/bench/_docker.py +237 -0
- fi/alk/bench/_grader.py +286 -0
- fi/alk/bench/_pull.py +212 -0
- fi/alk/bench/_voice.py +147 -0
- fi/alk/capabilities.py +627 -0
- fi/alk/cli.py +6396 -0
- fi/alk/config.py +130 -0
- fi/alk/cua_loop.py +562 -0
- fi/alk/evals.py +2351 -0
- fi/alk/extensions.py +163 -0
- fi/alk/harness/ARCHITECTURE.md +231 -0
- fi/alk/harness/DESIGN.md +246 -0
- fi/alk/harness/ENVIRONMENT_CONFORMANCE.md +127 -0
- fi/alk/harness/HOW-IT-WORKS.md +297 -0
- fi/alk/harness/IMPLEMENTATION_AND_VALIDATION_STATUS.md +229 -0
- fi/alk/harness/README.md +417 -0
- fi/alk/harness/__init__.py +77 -0
- fi/alk/harness/__main__.py +3 -0
- fi/alk/harness/amend.py +312 -0
- fi/alk/harness/artifacts.py +319 -0
- fi/alk/harness/authoring_entrypoint.py +189 -0
- fi/alk/harness/authoring_runtime_validation.py +267 -0
- fi/alk/harness/backends/README.md +43 -0
- fi/alk/harness/backends/__init__.py +122 -0
- fi/alk/harness/backends/base.py +241 -0
- fi/alk/harness/backends/claude.py +211 -0
- fi/alk/harness/backends/files.py +182 -0
- fi/alk/harness/backends/vertex_gemini.py +457 -0
- fi/alk/harness/background_noise.py +95 -0
- fi/alk/harness/build.py +385 -0
- fi/alk/harness/bundle.py +593 -0
- fi/alk/harness/bundle_author_v2.py +1831 -0
- fi/alk/harness/bundle_v2.py +719 -0
- fi/alk/harness/call_runner.py +1440 -0
- fi/alk/harness/callback_http_adapter.py +111 -0
- fi/alk/harness/catalogue.py +287 -0
- fi/alk/harness/chat.py +428 -0
- fi/alk/harness/chat_call_runner.py +506 -0
- fi/alk/harness/checks.py +136 -0
- fi/alk/harness/cli.py +1354 -0
- fi/alk/harness/config.py +338 -0
- fi/alk/harness/contract.py +718 -0
- fi/alk/harness/credentials.py +674 -0
- fi/alk/harness/data/persona_vocabulary.json +111 -0
- fi/alk/harness/environment.py +99 -0
- fi/alk/harness/environment_plan.py +168 -0
- fi/alk/harness/events.py +125 -0
- fi/alk/harness/executor.py +304 -0
- fi/alk/harness/folder.py +234 -0
- fi/alk/harness/generated_runtime.py +815 -0
- fi/alk/harness/github.py +72 -0
- fi/alk/harness/hosted_authoring_entrypoint.py +183 -0
- fi/alk/harness/hosted_entrypoint.py +2402 -0
- fi/alk/harness/hosted_scheduler.py +2218 -0
- fi/alk/harness/job.py +426 -0
- fi/alk/harness/judge.py +184 -0
- fi/alk/harness/livekit_source.py +50 -0
- fi/alk/harness/livekit_tool_trace_bootstrap.py +71 -0
- fi/alk/harness/observability.py +208 -0
- fi/alk/harness/outbound.py +3252 -0
- fi/alk/harness/packaging.py +515 -0
- fi/alk/harness/persona_guides.py +157 -0
- fi/alk/harness/platform.py +692 -0
- fi/alk/harness/process_preflight.py +764 -0
- fi/alk/harness/process_runtime.py +5670 -0
- fi/alk/harness/prove.py +425 -0
- fi/alk/harness/provider_import.py +703 -0
- fi/alk/harness/provider_lifecycle.py +392 -0
- fi/alk/harness/provision.py +2896 -0
- fi/alk/harness/reception.py +147 -0
- fi/alk/harness/retell_chat_call_runner.py +373 -0
- fi/alk/harness/run/__init__.py +296 -0
- fi/alk/harness/run/alk.py +184 -0
- fi/alk/harness/run/call.py +162 -0
- fi/alk/harness/run/conversation.py +264 -0
- fi/alk/harness/run/data/voices_by_language_and_gender.json +693 -0
- fi/alk/harness/run/evidence.py +195 -0
- fi/alk/harness/run/grade.py +598 -0
- fi/alk/harness/run/live.py +297 -0
- fi/alk/harness/run/models.py +56 -0
- fi/alk/harness/run/platform_evals.py +227 -0
- fi/alk/harness/run/sdk_voice.py +130 -0
- fi/alk/harness/run/simulation.py +1209 -0
- fi/alk/harness/run/stage.py +91 -0
- fi/alk/harness/run/targets.py +508 -0
- fi/alk/harness/run/tools.py +601 -0
- fi/alk/harness/run/voice.py +340 -0
- fi/alk/harness/runtime.py +172 -0
- fi/alk/harness/sandbox_server.py +2011 -0
- fi/alk/harness/sandbox_worker.py +44 -0
- fi/alk/harness/scenario.py +1048 -0
- fi/alk/harness/scenario_source.py +879 -0
- fi/alk/harness/scenario_tools.py +1143 -0
- fi/alk/harness/scenarios.py +915 -0
- fi/alk/harness/secrets.py +168 -0
- fi/alk/harness/service_catalog.py +97 -0
- fi/alk/harness/session.py +391 -0
- fi/alk/harness/sessions.py +372 -0
- fi/alk/harness/simulator.py +76 -0
- fi/alk/harness/simulator_voice.py +928 -0
- fi/alk/harness/skills/build-environment/SKILL.md +538 -0
- fi/alk/harness/skills/harness.md +131 -0
- fi/alk/harness/skills/kinds/chat.md +48 -0
- fi/alk/harness/skills/kinds/voice-voicemail.md +63 -0
- fi/alk/harness/skills/kinds/voice.md +59 -0
- fi/alk/harness/skills/plan-suite/SKILL.md +103 -0
- fi/alk/harness/skills/provision-environment/SKILL.md +136 -0
- fi/alk/harness/skills/run-scenarios/SKILL.md +112 -0
- fi/alk/harness/skills/understand-agent/SKILL.md +251 -0
- fi/alk/harness/skills/write-scenarios/SKILL.md +606 -0
- fi/alk/harness/skills/write-scenarios/references/refusals.md +28 -0
- fi/alk/harness/skills/write-scenarios/references/world-api.md +92 -0
- fi/alk/harness/source_data_invariants.py +444 -0
- fi/alk/harness/source_tool_evidence.py +79 -0
- fi/alk/harness/sources.py +253 -0
- fi/alk/harness/spend.py +140 -0
- fi/alk/harness/tool_trace_proxy.py +104 -0
- fi/alk/harness/tools.py +1018 -0
- fi/alk/harness/understand.py +169 -0
- fi/alk/harness/voicemail_audio.py +74 -0
- fi/alk/harness/world/__init__.py +33 -0
- fi/alk/harness/world/errors.py +68 -0
- fi/alk/harness/world/expectations.py +91 -0
- fi/alk/harness/world/handle.py +538 -0
- fi/alk/harness/world/kinds.py +196 -0
- fi/alk/harness/world/mutate.py +186 -0
- fi/alk/harness/world/probe.py +413 -0
- fi/alk/harness/world/provision.py +511 -0
- fi/alk/harness/world/provisioned.py +191 -0
- fi/alk/harness/world/runtime.py +616 -0
- fi/alk/harness/world/snapshot.py +288 -0
- fi/alk/harness/world/stores/__init__.py +305 -0
- fi/alk/harness/world/stores/container.py +215 -0
- fi/alk/harness/world/stores/inprocess.py +346 -0
- fi/alk/harness/world/stores/postgres.py +481 -0
- fi/alk/harness/world/stores/prove.py +202 -0
- fi/alk/harness/world/stores/sqlite.py +245 -0
- fi/alk/harness/world/stores/written.py +182 -0
- fi/alk/harness/world/tools.py +1516 -0
- fi/alk/harness/world/workspace.py +144 -0
- fi/alk/image_loop.py +453 -0
- fi/alk/image_perturb.py +241 -0
- fi/alk/improve.py +274 -0
- fi/alk/live/__init__.py +154 -0
- fi/alk/live/_attribution.py +184 -0
- fi/alk/live/_capture.py +264 -0
- fi/alk/live/_codec.py +391 -0
- fi/alk/live/_contract.py +134 -0
- fi/alk/live/_loopback.py +316 -0
- fi/alk/live/_perturb.py +449 -0
- fi/alk/live/_runner.py +386 -0
- fi/alk/live/_stats.py +561 -0
- fi/alk/live/_transcript.py +240 -0
- fi/alk/live/_workers/__init__.py +9 -0
- fi/alk/live/_workers/a2a_worker.py +316 -0
- fi/alk/live/_workers/langgraph_worker.py +217 -0
- fi/alk/live/_workers/livekit_worker.py +207 -0
- fi/alk/live/_workers/mcp_loopback_server.py +46 -0
- fi/alk/live/_workers/mcp_worker.py +158 -0
- fi/alk/live/_workers/pipecat_worker.py +189 -0
- fi/alk/live/a2a_lane.py +138 -0
- fi/alk/live/langgraph_lane.py +339 -0
- fi/alk/live/livekit_lane.py +376 -0
- fi/alk/live/mcp_lane.py +172 -0
- fi/alk/live/pipecat_lane.py +341 -0
- fi/alk/live/voice_redteam.py +494 -0
- fi/alk/loss.py +306 -0
- fi/alk/optimize.py +36260 -0
- fi/alk/practice/__init__.py +51 -0
- fi/alk/practice/_assess.py +103 -0
- fi/alk/practice/_budget.py +81 -0
- fi/alk/practice/_calibrate.py +69 -0
- fi/alk/practice/_capstone.py +86 -0
- fi/alk/practice/_contract.py +91 -0
- fi/alk/practice/_diagnose.py +79 -0
- fi/alk/practice/_drill.py +196 -0
- fi/alk/practice/_experiment.py +720 -0
- fi/alk/practice/_schedule.py +102 -0
- fi/alk/practice/_store.py +194 -0
- fi/alk/practice/_trainer.py +245 -0
- fi/alk/practice/_update.py +125 -0
- fi/alk/redteam.py +2621 -0
- fi/alk/rewardhack.py +237 -0
- fi/alk/simulate.py +10351 -0
- fi/alk/studio/__init__.py +82 -0
- fi/alk/studio/_bias.py +314 -0
- fi/alk/studio/_calibration.py +522 -0
- fi/alk/studio/_coverage.py +262 -0
- fi/alk/studio/_download.py +665 -0
- fi/alk/studio/_fidelity_attack.py +114 -0
- fi/alk/studio/_generate.py +652 -0
- fi/alk/studio/_library.py +370 -0
- fi/alk/studio/_scan.py +134 -0
- fi/alk/studio/_upgrade.py +42 -0
- fi/alk/studio/_vendor.py +172 -0
- fi/alk/suite.py +4200 -0
- fi/alk/tasks.py +828 -0
- fi/alk/telemetry/__init__.py +149 -0
- fi/alk/telemetry/_contract.py +141 -0
- fi/alk/telemetry/_emit.py +182 -0
- fi/alk/telemetry/_ledger.py +296 -0
- fi/alk/telemetry/_queue.py +127 -0
- fi/alk/telemetry/_row.py +294 -0
- fi/alk/telemetry/_run.py +233 -0
- fi/alk/telemetry/_sync.py +193 -0
- fi/alk/telemetry/_url.py +119 -0
- fi/alk/trinity.py +49397 -0
- fi/alk/voice_loop.py +174 -0
- fi/api/__init__.py +1 -0
- fi/api/auth.py +137 -0
- fi/api/types.py +29 -0
- fi/cli/__init__.py +9 -0
- fi/cli/assertions/__init__.py +25 -0
- fi/cli/assertions/conditions.py +76 -0
- fi/cli/assertions/evaluator.py +286 -0
- fi/cli/assertions/exit_codes.py +20 -0
- fi/cli/assertions/parser.py +131 -0
- fi/cli/assertions/reporter.py +194 -0
- fi/cli/commands/__init__.py +9 -0
- fi/cli/commands/config.py +165 -0
- fi/cli/commands/export.py +208 -0
- fi/cli/commands/init.py +112 -0
- fi/cli/commands/list_cmd.py +213 -0
- fi/cli/commands/run.py +486 -0
- fi/cli/commands/validate.py +173 -0
- fi/cli/commands/view.py +424 -0
- fi/cli/config/__init__.py +6 -0
- fi/cli/config/defaults.py +206 -0
- fi/cli/config/loader.py +155 -0
- fi/cli/config/schema.py +174 -0
- fi/cli/main.py +78 -0
- fi/cli/output/__init__.py +6 -0
- fi/cli/output/formatters.py +106 -0
- fi/cli/output/reporters.py +46 -0
- fi/cli/storage/__init__.py +5 -0
- fi/cli/storage/run_history.py +249 -0
- fi/cli/utils/__init__.py +5 -0
- fi/cli/utils/console.py +44 -0
- fi/evals/__init__.py +131 -0
- fi/evals/autoeval/__init__.py +137 -0
- fi/evals/autoeval/analyzer.py +211 -0
- fi/evals/autoeval/config.py +244 -0
- fi/evals/autoeval/export.py +213 -0
- fi/evals/autoeval/interactive.py +283 -0
- fi/evals/autoeval/pipeline.py +625 -0
- fi/evals/autoeval/prompts.py +139 -0
- fi/evals/autoeval/recommender.py +242 -0
- fi/evals/autoeval/rules.py +589 -0
- fi/evals/autoeval/templates.py +299 -0
- fi/evals/autoeval/types.py +232 -0
- fi/evals/core/__init__.py +16 -0
- fi/evals/core/cloud_registry.py +184 -0
- fi/evals/core/engines.py +368 -0
- fi/evals/core/evaluate.py +319 -0
- fi/evals/core/judge_prompt.py +90 -0
- fi/evals/core/prompt_generator.py +83 -0
- fi/evals/core/registry.py +57 -0
- fi/evals/core/result.py +55 -0
- fi/evals/evaluator.py +721 -0
- fi/evals/execution.py +168 -0
- fi/evals/feedback/__init__.py +32 -0
- fi/evals/feedback/calibrator.py +160 -0
- fi/evals/feedback/collector.py +214 -0
- fi/evals/feedback/hooks.py +81 -0
- fi/evals/feedback/retriever.py +128 -0
- fi/evals/feedback/store.py +272 -0
- fi/evals/feedback/types.py +99 -0
- fi/evals/framework/README.md +79 -0
- fi/evals/framework/__init__.py +267 -0
- fi/evals/framework/backends/Dockerfile.eval-runner +33 -0
- fi/evals/framework/backends/__init__.py +99 -0
- fi/evals/framework/backends/_container.py +141 -0
- fi/evals/framework/backends/_utils.py +145 -0
- fi/evals/framework/backends/base.py +223 -0
- fi/evals/framework/backends/celery_backend.py +417 -0
- fi/evals/framework/backends/celery_worker.py +78 -0
- fi/evals/framework/backends/kubernetes_backend.py +665 -0
- fi/evals/framework/backends/ray_backend.py +521 -0
- fi/evals/framework/backends/temporal.py +350 -0
- fi/evals/framework/backends/temporal_worker.py +126 -0
- fi/evals/framework/backends/thread_pool.py +286 -0
- fi/evals/framework/context.py +258 -0
- fi/evals/framework/enrichment.py +306 -0
- fi/evals/framework/evals/__init__.py +68 -0
- fi/evals/framework/evals/agentic.py +399 -0
- fi/evals/framework/evals/builder.py +609 -0
- fi/evals/framework/evals/semantic.py +142 -0
- fi/evals/framework/evaluator.py +647 -0
- fi/evals/framework/evaluators/__init__.py +22 -0
- fi/evals/framework/evaluators/blocking.py +347 -0
- fi/evals/framework/evaluators/non_blocking.py +577 -0
- fi/evals/framework/propagation.py +421 -0
- fi/evals/framework/protocols.py +385 -0
- fi/evals/framework/registry.py +370 -0
- fi/evals/framework/resilience/__init__.py +150 -0
- fi/evals/framework/resilience/circuit_breaker.py +309 -0
- fi/evals/framework/resilience/degradation.py +355 -0
- fi/evals/framework/resilience/health.py +505 -0
- fi/evals/framework/resilience/rate_limiter.py +228 -0
- fi/evals/framework/resilience/retry.py +274 -0
- fi/evals/framework/resilience/types.py +288 -0
- fi/evals/framework/resilience/wrapper.py +433 -0
- fi/evals/framework/types.py +218 -0
- fi/evals/guardrails/README.md +915 -0
- fi/evals/guardrails/__init__.py +96 -0
- fi/evals/guardrails/backends/__init__.py +43 -0
- fi/evals/guardrails/backends/azure.py +361 -0
- fi/evals/guardrails/backends/base.py +88 -0
- fi/evals/guardrails/backends/generic_llm.py +163 -0
- fi/evals/guardrails/backends/granite.py +216 -0
- fi/evals/guardrails/backends/llamaguard.py +221 -0
- fi/evals/guardrails/backends/local_base.py +479 -0
- fi/evals/guardrails/backends/openai.py +365 -0
- fi/evals/guardrails/backends/qwen.py +170 -0
- fi/evals/guardrails/backends/shieldgemma.py +154 -0
- fi/evals/guardrails/backends/turing.py +235 -0
- fi/evals/guardrails/backends/vllm_client.py +321 -0
- fi/evals/guardrails/backends/wildguard.py +188 -0
- fi/evals/guardrails/base.py +888 -0
- fi/evals/guardrails/config.py +221 -0
- fi/evals/guardrails/discovery.py +243 -0
- fi/evals/guardrails/gateway.py +437 -0
- fi/evals/guardrails/registry.py +231 -0
- fi/evals/guardrails/scanners/__init__.py +127 -0
- fi/evals/guardrails/scanners/base.py +191 -0
- fi/evals/guardrails/scanners/code_injection.py +243 -0
- fi/evals/guardrails/scanners/eval_delegate.py +574 -0
- fi/evals/guardrails/scanners/invisible_chars.py +351 -0
- fi/evals/guardrails/scanners/jailbreak.py +412 -0
- fi/evals/guardrails/scanners/language.py +288 -0
- fi/evals/guardrails/scanners/pipeline.py +260 -0
- fi/evals/guardrails/scanners/regex.py +311 -0
- fi/evals/guardrails/scanners/secrets.py +274 -0
- fi/evals/guardrails/scanners/topics.py +649 -0
- fi/evals/guardrails/scanners/urls.py +341 -0
- fi/evals/guardrails/types.py +96 -0
- fi/evals/llm/__init__.py +3 -0
- fi/evals/llm/base_llm_provider.py +35 -0
- fi/evals/llm/providers/litellm.py +70 -0
- fi/evals/local/__init__.py +90 -0
- fi/evals/local/evaluator.py +690 -0
- fi/evals/local/execution_mode.py +121 -0
- fi/evals/local/llm.py +489 -0
- fi/evals/local/metrics/__init__.py +19 -0
- fi/evals/local/registry.py +360 -0
- fi/evals/manager.py +1018 -0
- fi/evals/manager_types.py +362 -0
- fi/evals/metrics/__init__.py +185 -0
- fi/evals/metrics/agents/__init__.py +74 -0
- fi/evals/metrics/agents/metrics.py +693 -0
- fi/evals/metrics/agents/report.py +36463 -0
- fi/evals/metrics/agents/types.py +160 -0
- fi/evals/metrics/base_llm_metric.py +111 -0
- fi/evals/metrics/base_metric.py +138 -0
- fi/evals/metrics/code_security/__init__.py +305 -0
- fi/evals/metrics/code_security/analyzer.py +985 -0
- fi/evals/metrics/code_security/benchmarks/__init__.py +73 -0
- fi/evals/metrics/code_security/benchmarks/builtin.py +750 -0
- fi/evals/metrics/code_security/benchmarks/loader.py +580 -0
- fi/evals/metrics/code_security/benchmarks/types.py +308 -0
- fi/evals/metrics/code_security/detectors/__init__.py +186 -0
- fi/evals/metrics/code_security/detectors/base.py +394 -0
- fi/evals/metrics/code_security/detectors/cryptography.py +345 -0
- fi/evals/metrics/code_security/detectors/injection.py +744 -0
- fi/evals/metrics/code_security/detectors/secrets.py +287 -0
- fi/evals/metrics/code_security/detectors/serialization.py +192 -0
- fi/evals/metrics/code_security/joint_metrics.py +588 -0
- fi/evals/metrics/code_security/judges/__init__.py +83 -0
- fi/evals/metrics/code_security/judges/base.py +238 -0
- fi/evals/metrics/code_security/judges/dual_judge.py +534 -0
- fi/evals/metrics/code_security/judges/llm_judge.py +301 -0
- fi/evals/metrics/code_security/judges/pattern_judge.py +515 -0
- fi/evals/metrics/code_security/metrics.py +388 -0
- fi/evals/metrics/code_security/modes/__init__.py +63 -0
- fi/evals/metrics/code_security/modes/adversarial.py +284 -0
- fi/evals/metrics/code_security/modes/autocomplete.py +198 -0
- fi/evals/metrics/code_security/modes/base.py +283 -0
- fi/evals/metrics/code_security/modes/instruct.py +253 -0
- fi/evals/metrics/code_security/modes/repair.py +230 -0
- fi/evals/metrics/code_security/reports/__init__.py +57 -0
- fi/evals/metrics/code_security/reports/generator.py +404 -0
- fi/evals/metrics/code_security/reports/leaderboard.py +509 -0
- fi/evals/metrics/code_security/types.py +534 -0
- fi/evals/metrics/function_calling/__init__.py +34 -0
- fi/evals/metrics/function_calling/metrics.py +573 -0
- fi/evals/metrics/function_calling/types.py +87 -0
- fi/evals/metrics/hallucination/__init__.py +54 -0
- fi/evals/metrics/hallucination/detector.py +149 -0
- fi/evals/metrics/hallucination/metrics.py +390 -0
- fi/evals/metrics/hallucination/nli.py +253 -0
- fi/evals/metrics/hallucination/sentinel.py +106 -0
- fi/evals/metrics/hallucination/types.py +132 -0
- fi/evals/metrics/heuristics/aggregation_metrics.py +85 -0
- fi/evals/metrics/heuristics/json_metrics.py +87 -0
- fi/evals/metrics/heuristics/similarity_metrics.py +375 -0
- fi/evals/metrics/heuristics/string_metrics.py +391 -0
- fi/evals/metrics/llm_as_judges/__init__.py +17 -0
- fi/evals/metrics/llm_as_judges/custom_judge/metric.py +112 -0
- fi/evals/metrics/llm_as_judges/custom_judge/prompts.py +26 -0
- fi/evals/metrics/llm_as_judges/types.py +48 -0
- fi/evals/metrics/rag/__init__.py +111 -0
- fi/evals/metrics/rag/advanced/__init__.py +14 -0
- fi/evals/metrics/rag/advanced/multi_hop.py +283 -0
- fi/evals/metrics/rag/advanced/source_attribution.py +344 -0
- fi/evals/metrics/rag/generation/__init__.py +17 -0
- fi/evals/metrics/rag/generation/answer_relevancy.py +176 -0
- fi/evals/metrics/rag/generation/context_utilization.py +245 -0
- fi/evals/metrics/rag/generation/faithfulness.py +241 -0
- fi/evals/metrics/rag/generation/groundedness.py +131 -0
- fi/evals/metrics/rag/rag_score.py +277 -0
- fi/evals/metrics/rag/retrieval/__init__.py +20 -0
- fi/evals/metrics/rag/retrieval/context_entity_recall.py +124 -0
- fi/evals/metrics/rag/retrieval/context_precision.py +158 -0
- fi/evals/metrics/rag/retrieval/context_recall.py +106 -0
- fi/evals/metrics/rag/retrieval/noise_sensitivity.py +163 -0
- fi/evals/metrics/rag/retrieval/ranking.py +261 -0
- fi/evals/metrics/rag/types.py +100 -0
- fi/evals/metrics/rag/utils/__init__.py +62 -0
- fi/evals/metrics/rag/utils/claims.py +189 -0
- fi/evals/metrics/rag/utils/entities.py +244 -0
- fi/evals/metrics/rag/utils/nli.py +92 -0
- fi/evals/metrics/rag/utils/similarity.py +345 -0
- fi/evals/metrics/structured/__init__.py +114 -0
- fi/evals/metrics/structured/field_completeness.py +313 -0
- fi/evals/metrics/structured/hierarchy_score.py +366 -0
- fi/evals/metrics/structured/json_validation.py +190 -0
- fi/evals/metrics/structured/schema_compliance.py +280 -0
- fi/evals/metrics/structured/structured_output_score.py +298 -0
- fi/evals/metrics/structured/types.py +108 -0
- fi/evals/metrics/structured/validators/__init__.py +30 -0
- fi/evals/metrics/structured/validators/base.py +189 -0
- fi/evals/metrics/structured/validators/json_validator.py +196 -0
- fi/evals/metrics/structured/validators/pydantic_validator.py +178 -0
- fi/evals/metrics/structured/validators/yaml_validator.py +248 -0
- fi/evals/otel/__init__.py +266 -0
- fi/evals/otel/config.py +400 -0
- fi/evals/otel/conventions.py +463 -0
- fi/evals/otel/enrichment.py +371 -0
- fi/evals/otel/instrumentors/__init__.py +140 -0
- fi/evals/otel/instrumentors/anthropic.py +517 -0
- fi/evals/otel/instrumentors/base.py +382 -0
- fi/evals/otel/instrumentors/openai.py +673 -0
- fi/evals/otel/processors/__init__.py +36 -0
- fi/evals/otel/processors/base.py +473 -0
- fi/evals/otel/processors/cost.py +445 -0
- fi/evals/otel/processors/evaluation.py +559 -0
- fi/evals/otel/processors/llm.py +462 -0
- fi/evals/otel/tracer.py +506 -0
- fi/evals/otel/types.py +232 -0
- fi/evals/otel_utils.py +23 -0
- fi/evals/protect.py +671 -0
- fi/evals/protect_input_adapter.py +154 -0
- fi/evals/streaming/__init__.py +88 -0
- fi/evals/streaming/buffer.py +213 -0
- fi/evals/streaming/evaluator.py +551 -0
- fi/evals/streaming/policy.py +307 -0
- fi/evals/streaming/scorers.py +368 -0
- fi/evals/streaming/types.py +238 -0
- fi/evals/templates.py +472 -0
- fi/evals/types.py +156 -0
- fi/opt/__init__.py +221 -0
- fi/opt/_objective_scoring.py +85 -0
- fi/opt/base/__init__.py +11 -0
- fi/opt/base/base_generator.py +33 -0
- fi/opt/base/base_mapper.py +26 -0
- fi/opt/base/base_optimizer.py +45 -0
- fi/opt/base/evaluator.py +211 -0
- fi/opt/components.py +3095 -0
- fi/opt/datamappers/__init__.py +3 -0
- fi/opt/datamappers/basic_mapper.py +40 -0
- fi/opt/deployment.py +1021 -0
- fi/opt/evidence.py +4332 -0
- fi/opt/generators/__init__.py +3 -0
- fi/opt/generators/litellm.py +66 -0
- fi/opt/integrations/__init__.py +23 -0
- fi/opt/integrations/generative_suite.py +410 -0
- fi/opt/integrations/simulate.py +1313 -0
- fi/opt/mutations.py +771 -0
- fi/opt/observability.py +4639 -0
- fi/opt/optimizer_trace.py +889 -0
- fi/opt/optimizers/__init__.py +80 -0
- fi/opt/optimizers/agent.py +331 -0
- fi/opt/optimizers/agent_bandit.py +392 -0
- fi/opt/optimizers/agent_curriculum.py +635 -0
- fi/opt/optimizers/agent_evolution.py +894 -0
- fi/opt/optimizers/agent_feedback.py +1863 -0
- fi/opt/optimizers/agent_pareto.py +547 -0
- fi/opt/optimizers/agent_social_memory.py +1113 -0
- fi/opt/optimizers/agent_tpe.py +321 -0
- fi/opt/optimizers/bayesian_search.py +449 -0
- fi/opt/optimizers/council.py +2075 -0
- fi/opt/optimizers/futureagi_replay.py +799 -0
- fi/opt/optimizers/gepa.py +322 -0
- fi/opt/optimizers/metaprompt.py +243 -0
- fi/opt/optimizers/promptwizard.py +417 -0
- fi/opt/optimizers/protegi.py +329 -0
- fi/opt/optimizers/random_search.py +224 -0
- fi/opt/research.py +518 -0
- fi/opt/simulation.py +260 -0
- fi/opt/targets.py +232 -0
- fi/opt/types.py +66 -0
- fi/opt/utils/__init__.py +4 -0
- fi/opt/utils/early_stopping.py +266 -0
- fi/opt/utils/setup_logging.py +82 -0
- fi/simulate/__init__.py +540 -0
- fi/simulate/_hashing.py +35 -0
- fi/simulate/_logging.py +10 -0
- fi/simulate/adapters.py +87 -0
- fi/simulate/agent/__init__.py +120 -0
- fi/simulate/agent/browser.py +658 -0
- fi/simulate/agent/definition.py +587 -0
- fi/simulate/agent/frameworks.py +3528 -0
- fi/simulate/agent/generic.py +8286 -0
- fi/simulate/agent/import_probe.py +227 -0
- fi/simulate/agent/memory.py +905 -0
- fi/simulate/agent/mocks.py +101 -0
- fi/simulate/agent/multi_agent.py +361 -0
- fi/simulate/agent/orchestration.py +903 -0
- fi/simulate/agent/realtime.py +665 -0
- fi/simulate/agent/wrapper.py +99 -0
- fi/simulate/agent/wrappers/__init__.py +18 -0
- fi/simulate/agent/wrappers/anthropic.py +62 -0
- fi/simulate/agent/wrappers/gemini.py +65 -0
- fi/simulate/agent/wrappers/http.py +404 -0
- fi/simulate/agent/wrappers/langchain.py +80 -0
- fi/simulate/agent/wrappers/openai.py +75 -0
- fi/simulate/agent/wrappers/websocket.py +326 -0
- fi/simulate/artifacts/__init__.py +11 -0
- fi/simulate/artifacts/manifest.py +62 -0
- fi/simulate/cli.py +20560 -0
- fi/simulate/endpoints/__init__.py +45 -0
- fi/simulate/endpoints/_http_actor.py +73 -0
- fi/simulate/endpoints/actor_sources.py +243 -0
- fi/simulate/endpoints/base.py +107 -0
- fi/simulate/endpoints/builtins.py +10 -0
- fi/simulate/endpoints/callable.py +95 -0
- fi/simulate/endpoints/http.py +76 -0
- fi/simulate/endpoints/livekit.py +138 -0
- fi/simulate/endpoints/originators.py +132 -0
- fi/simulate/endpoints/profiles.py +348 -0
- fi/simulate/endpoints/retell.py +633 -0
- fi/simulate/endpoints/vapi.py +205 -0
- fi/simulate/endpoints/websocket.py +76 -0
- fi/simulate/environment.py +33026 -0
- fi/simulate/environments/__init__.py +11 -0
- fi/simulate/environments/base.py +73 -0
- fi/simulate/environments/chat.py +697 -0
- fi/simulate/environments/voice.py +212 -0
- fi/simulate/evaluation/__init__.py +4 -0
- fi/simulate/evaluation/ai_eval.py +227 -0
- fi/simulate/evidence/__init__.py +35 -0
- fi/simulate/evidence/base.py +59 -0
- fi/simulate/evidence/caller_observed.py +50 -0
- fi/simulate/evidence/livekit_instrumentation.py +51 -0
- fi/simulate/evidence/livekit_room.py +50 -0
- fi/simulate/evidence/otel.py +49 -0
- fi/simulate/evidence/providers/__init__.py +24 -0
- fi/simulate/evidence/providers/base.py +61 -0
- fi/simulate/evidence/providers/retell.py +376 -0
- fi/simulate/evidence/providers/vapi.py +426 -0
- fi/simulate/hosted/__init__.py +32 -0
- fi/simulate/hosted/child_entrypoint.py +306 -0
- fi/simulate/hosted/job.py +150 -0
- fi/simulate/hosted/targets.py +53 -0
- fi/simulate/instrumentation/__init__.py +5 -0
- fi/simulate/instrumentation/livekit/__init__.py +122 -0
- fi/simulate/manifest.py +1033 -0
- fi/simulate/matrix_cli.py +165 -0
- fi/simulate/realtime/__init__.py +40 -0
- fi/simulate/realtime/events.py +107 -0
- fi/simulate/realtime/media.py +61 -0
- fi/simulate/realtime/session.py +91 -0
- fi/simulate/recording/__init__.py +5 -0
- fi/simulate/recording/room_recorder.py +326 -0
- fi/simulate/registry.py +185 -0
- fi/simulate/results/__init__.py +9 -0
- fi/simulate/results/base.py +18 -0
- fi/simulate/results/filesystem.py +71 -0
- fi/simulate/results/futureagi.py +1340 -0
- fi/simulate/runtime/__init__.py +85 -0
- fi/simulate/runtime/capabilities.py +40 -0
- fi/simulate/runtime/events.py +63 -0
- fi/simulate/runtime/failures.py +25 -0
- fi/simulate/runtime/ids.py +34 -0
- fi/simulate/runtime/plan.py +70 -0
- fi/simulate/runtime/planner.py +102 -0
- fi/simulate/runtime/report.py +174 -0
- fi/simulate/runtime/run.py +75 -0
- fi/simulate/runtime/runner.py +333 -0
- fi/simulate/runtime/spec.py +186 -0
- fi/simulate/simulation/__init__.py +30 -0
- fi/simulate/simulation/behavior_policy.py +425 -0
- fi/simulate/simulation/bridge/__init__.py +9 -0
- fi/simulate/simulation/bridge/audio.py +29 -0
- fi/simulate/simulation/bridge/connector.py +46 -0
- fi/simulate/simulation/bridge/livekit.py +252 -0
- fi/simulate/simulation/bridge/retell.py +188 -0
- fi/simulate/simulation/bridge/vapi.py +177 -0
- fi/simulate/simulation/contract.py +419 -0
- fi/simulate/simulation/engines/__init__.py +12 -0
- fi/simulate/simulation/engines/base.py +21 -0
- fi/simulate/simulation/engines/cloud.py +517 -0
- fi/simulate/simulation/engines/livekit.py +4167 -0
- fi/simulate/simulation/engines/local_text.py +89 -0
- fi/simulate/simulation/fidelity.py +374 -0
- fi/simulate/simulation/gemini_tts_stream.py +110 -0
- fi/simulate/simulation/generator.py +91 -0
- fi/simulate/simulation/goal_machine.py +185 -0
- fi/simulate/simulation/livekit_models.py +467 -0
- fi/simulate/simulation/matrix.py +170 -0
- fi/simulate/simulation/models.py +279 -0
- fi/simulate/simulation/runner.py +153 -0
- fi/simulate/simulation/synthetic.py +880 -0
- fi/simulate/simulation/voice_prompt.py +502 -0
- fi/simulate/simulator/__init__.py +55 -0
- fi/simulate/simulator/builtins.py +53 -0
- fi/simulate/suite.py +1288 -0
- fi/simulate/utils/routes.py +164 -0
- fi/simulate/voice.py +225 -0
- fi/simulate/voice_cli.py +182 -0
- fi/utils/__init__.py +1 -0
- fi/utils/constants.py +14 -0
- fi/utils/errors.py +200 -0
- fi/utils/executor.py +26 -0
- fi/utils/routes.py +119 -0
- fi/utils/utils.py +17 -0
fi/alk/harness/chat.py
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"""One conversation, from pointing at an agent to a world you can test against.
|
|
2
|
+
|
|
3
|
+
You say what you want, it does it, you say the next thing. Stages are not commands you invoke;
|
|
4
|
+
they are what the harness moves through while you keep talking. When one produces its artifact
|
|
5
|
+
the next opens on the same agent, and anything already built stays correctable by saying so.
|
|
6
|
+
|
|
7
|
+
Underneath, each stage is still its own session with its own instructions and its own tools, so
|
|
8
|
+
context stays small and a stage can be re-entered later without redoing the ones before it. That
|
|
9
|
+
is an implementation detail, not something to make somebody manage.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from . import build as build_stage
|
|
21
|
+
from . import reception as reception_stage
|
|
22
|
+
from . import scenarios as scenario_stage
|
|
23
|
+
from . import understand as understand_stage
|
|
24
|
+
from .config import artifact_dir
|
|
25
|
+
from .contract import AgentContract
|
|
26
|
+
from .run import stage as run_stage
|
|
27
|
+
from .session import Stage
|
|
28
|
+
from .sources import AgentSource, resolve
|
|
29
|
+
from .world.snapshot import saved as world_saved
|
|
30
|
+
|
|
31
|
+
RECEPTION = "reception"
|
|
32
|
+
UNDERSTAND = "understand"
|
|
33
|
+
BUILD = "build"
|
|
34
|
+
SCENARIOS = "scenarios"
|
|
35
|
+
RUN = "run"
|
|
36
|
+
DONE = "done"
|
|
37
|
+
|
|
38
|
+
_NEXT = {
|
|
39
|
+
RECEPTION: UNDERSTAND,
|
|
40
|
+
UNDERSTAND: BUILD,
|
|
41
|
+
BUILD: SCENARIOS,
|
|
42
|
+
SCENARIOS: RUN,
|
|
43
|
+
RUN: DONE,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class Conversation:
|
|
49
|
+
"""The whole thing, held open."""
|
|
50
|
+
|
|
51
|
+
# Both unknown until somebody says which agent this is about, which is itself a stage.
|
|
52
|
+
source: AgentSource | None = None
|
|
53
|
+
out: Path | None = None
|
|
54
|
+
ask: Callable[..., Any] | None = None
|
|
55
|
+
wanted: int = 10
|
|
56
|
+
# Where to look for an agent. Almost never inside this repo: the harness lives in one place
|
|
57
|
+
# and the agent being tested lives in another, so looking only at our own root means the
|
|
58
|
+
# first thing anybody types cannot be found.
|
|
59
|
+
workspace: Path | None = None
|
|
60
|
+
stage_name: str = ""
|
|
61
|
+
stage: Stage | None = None
|
|
62
|
+
# Set by the flow tool when the open stage hands a request to the stage that owns it.
|
|
63
|
+
_handoff: dict = field(default_factory=dict)
|
|
64
|
+
spent_usd: float = 0.0
|
|
65
|
+
history: list[str] = field(default_factory=list)
|
|
66
|
+
_found: dict[str, Any] = field(default_factory=dict)
|
|
67
|
+
|
|
68
|
+
def __post_init__(self) -> None:
|
|
69
|
+
# Read off the artifacts rather than defaulting to the first stage. An agent whose world
|
|
70
|
+
# is already built is at the scenarios, and saying otherwise before anything has been
|
|
71
|
+
# opened makes every question about where this conversation is answer wrongly.
|
|
72
|
+
self.stage_name = self.stage_name or self._resume_at()
|
|
73
|
+
|
|
74
|
+
# -- what exists so far ----------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def contract(self) -> AgentContract | None:
|
|
78
|
+
return understand_stage.load(self.out) if self.out else None
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def world_built(self) -> bool:
|
|
82
|
+
# The manifest, not a database file. Every saved world writes one; only some of them have
|
|
83
|
+
# a SQLite file beside it, and an agent whose state lives in services and files has none.
|
|
84
|
+
# Keyed on the database, such a world stays "not built" forever and the conversation can
|
|
85
|
+
# never leave this stage, however well the build actually went.
|
|
86
|
+
return world_saved(self.out)
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def scenarios_written(self) -> bool:
|
|
90
|
+
return bool(self.out) and bool(scenario_stage.load(self.out))
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def anything_run(self) -> bool:
|
|
94
|
+
return bool(self.out) and bool(run_stage.load(self.out))
|
|
95
|
+
|
|
96
|
+
def _artifact_for(self, stage_name: str) -> bool:
|
|
97
|
+
return {
|
|
98
|
+
# A contract already on disk settles which agent this is just as well as being told,
|
|
99
|
+
# so coming back to an agent does not mean pointing at its repository again.
|
|
100
|
+
#
|
|
101
|
+
# ``_found`` is checked too, because within the turn that points at an agent the
|
|
102
|
+
# source is not on the conversation yet — it is read off afterwards. Without it, the
|
|
103
|
+
# stage that has just succeeded is told it has produced nothing.
|
|
104
|
+
RECEPTION: self.source is not None
|
|
105
|
+
or self.contract is not None
|
|
106
|
+
or self._found.get("source") is not None,
|
|
107
|
+
UNDERSTAND: self.contract is not None,
|
|
108
|
+
BUILD: self.world_built,
|
|
109
|
+
SCENARIOS: self.scenarios_written,
|
|
110
|
+
RUN: self.anything_run,
|
|
111
|
+
DONE: True,
|
|
112
|
+
}[stage_name]
|
|
113
|
+
|
|
114
|
+
# -- moving between stages -------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
async def _close(self) -> None:
|
|
117
|
+
if self.stage is not None:
|
|
118
|
+
self.spent_usd += self.stage.spent_usd
|
|
119
|
+
await self.stage.__aexit__(None, None, None)
|
|
120
|
+
self.stage = None
|
|
121
|
+
|
|
122
|
+
async def _open(self, stage_name: str) -> str:
|
|
123
|
+
"""Open a stage and return the message that starts it."""
|
|
124
|
+
await self._close()
|
|
125
|
+
self.stage_name = stage_name
|
|
126
|
+
if stage_name == RECEPTION:
|
|
127
|
+
self.stage, self._found = reception_stage.open_stage(
|
|
128
|
+
cwd=self.workspace,
|
|
129
|
+
ask=self.ask,
|
|
130
|
+
# The UI allocates a session before the agent has a name. Put a GitHub clone in
|
|
131
|
+
# that existing session rather than creating a second artifact directory.
|
|
132
|
+
source_dir=(self.out / "source") if self.out else None,
|
|
133
|
+
)
|
|
134
|
+
self._grant_flow()
|
|
135
|
+
await self.stage.__aenter__()
|
|
136
|
+
return reception_stage.opening()
|
|
137
|
+
|
|
138
|
+
# Deliberately not "is there a source": a contract on disk settles which agent this is,
|
|
139
|
+
# and every stage after the first works from the contract rather than from the source.
|
|
140
|
+
# Only re-reading the agent needs to know where it lives.
|
|
141
|
+
if self.source is None and self.contract is None:
|
|
142
|
+
raise RuntimeError("nobody has said which agent this is about yet")
|
|
143
|
+
if stage_name == UNDERSTAND and self.source is None:
|
|
144
|
+
# This guard has to come before the stage opens: with a contract on disk but no
|
|
145
|
+
# source, reopening understand would otherwise die on source.briefing() instead of
|
|
146
|
+
# saying what is actually missing.
|
|
147
|
+
raise RuntimeError(
|
|
148
|
+
"cannot re-read the agent without knowing where it lives"
|
|
149
|
+
)
|
|
150
|
+
if stage_name == UNDERSTAND:
|
|
151
|
+
self.stage, _ = understand_stage.open_stage(
|
|
152
|
+
self.source, out=self.out, ask=self.ask
|
|
153
|
+
)
|
|
154
|
+
opening = understand_stage.opening(self.source)
|
|
155
|
+
self._grant_flow()
|
|
156
|
+
await self.stage.__aenter__()
|
|
157
|
+
return opening
|
|
158
|
+
|
|
159
|
+
contract = self.contract
|
|
160
|
+
if contract is None:
|
|
161
|
+
raise RuntimeError("cannot go further before there is a contract")
|
|
162
|
+
if stage_name == BUILD:
|
|
163
|
+
source_root = str(getattr(self.source, "root", "") or "")
|
|
164
|
+
build_stage.require_buildable(contract, source_root)
|
|
165
|
+
from .provision import provision_if_present
|
|
166
|
+
|
|
167
|
+
await asyncio.to_thread(
|
|
168
|
+
provision_if_present, source_root, self.out, contract
|
|
169
|
+
)
|
|
170
|
+
self.stage, _ = build_stage.open_stage(
|
|
171
|
+
contract,
|
|
172
|
+
out=self.out,
|
|
173
|
+
ask=self.ask,
|
|
174
|
+
# Where the agent's own code lives, so its tools can be bound to rather
|
|
175
|
+
# than rewritten. Empty for an agent given as a specification.
|
|
176
|
+
source_root=source_root,
|
|
177
|
+
)
|
|
178
|
+
opening = build_stage.opening(contract)
|
|
179
|
+
elif stage_name == RUN:
|
|
180
|
+
if not self.scenarios_written:
|
|
181
|
+
raise RuntimeError("cannot run anything before there are scenarios")
|
|
182
|
+
self.stage, _ = run_stage.open_stage(contract, out=self.out, ask=self.ask)
|
|
183
|
+
opening = run_stage.opening(contract, self.out)
|
|
184
|
+
else:
|
|
185
|
+
if not self.world_built:
|
|
186
|
+
raise RuntimeError("cannot write scenarios before there is a world")
|
|
187
|
+
written = len(scenario_stage.load(self.out))
|
|
188
|
+
wanted = written or self.wanted
|
|
189
|
+
self.stage, _ = scenario_stage.open_stage(
|
|
190
|
+
contract, out=self.out, wanted=wanted, ask=self.ask
|
|
191
|
+
)
|
|
192
|
+
opening = scenario_stage.opening(contract, wanted, written)
|
|
193
|
+
self._grant_flow()
|
|
194
|
+
await self.stage.__aenter__()
|
|
195
|
+
return opening
|
|
196
|
+
|
|
197
|
+
def next_stage(self) -> str | None:
|
|
198
|
+
"""The stage that follows the current one, once this one has produced its artifact."""
|
|
199
|
+
if not self._artifact_for(self.stage_name):
|
|
200
|
+
return None
|
|
201
|
+
following = _NEXT.get(self.stage_name)
|
|
202
|
+
return None if following in (None, DONE) else following
|
|
203
|
+
|
|
204
|
+
def _flow_server(self):
|
|
205
|
+
"""One tool every stage gets: handing a request to the stage that owns it.
|
|
206
|
+
|
|
207
|
+
"Create the world", said while the understand stage is open, used to land in a session
|
|
208
|
+
with no build tools, which could only apologise. The stage is the one that knows the
|
|
209
|
+
request is not its job, so the handoff is a tool it calls; whether moving on is allowed
|
|
210
|
+
is still decided by code, from whether this stage's artifact exists.
|
|
211
|
+
"""
|
|
212
|
+
from .backends import tool, tool_server
|
|
213
|
+
from .tools import schema
|
|
214
|
+
|
|
215
|
+
wanted = self._handoff
|
|
216
|
+
|
|
217
|
+
@tool(
|
|
218
|
+
"hand_to_next_stage",
|
|
219
|
+
"The person asked for something that belongs to the NEXT stage of this harness — "
|
|
220
|
+
"building the environment when the contract is done, writing scenarios when the "
|
|
221
|
+
"environment is built, running them when they are written. Call this with their "
|
|
222
|
+
"request, word for word; the conversation moves forward and their request is "
|
|
223
|
+
"handled there. Never call it to escape work that is this stage's own.",
|
|
224
|
+
schema({"request": str}, []),
|
|
225
|
+
)
|
|
226
|
+
async def hand_to_next_stage(args: dict[str, Any]) -> dict[str, Any]:
|
|
227
|
+
if not self._artifact_for(self.stage_name):
|
|
228
|
+
return {
|
|
229
|
+
"content": [
|
|
230
|
+
{
|
|
231
|
+
"type": "text",
|
|
232
|
+
"text": "This stage has not produced its artifact yet, so there is "
|
|
233
|
+
"nothing to move on from. Finish this stage's work first.",
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
"is_error": True,
|
|
237
|
+
}
|
|
238
|
+
if self.next_stage() is None:
|
|
239
|
+
return {
|
|
240
|
+
"content": [
|
|
241
|
+
{"type": "text", "text": "there is no stage after this one"}
|
|
242
|
+
],
|
|
243
|
+
"is_error": True,
|
|
244
|
+
}
|
|
245
|
+
wanted["request"] = str(args.get("request") or "").strip() or "continue"
|
|
246
|
+
return {
|
|
247
|
+
"content": [
|
|
248
|
+
{
|
|
249
|
+
"type": "text",
|
|
250
|
+
"text": "Handed over. Say one short line that you are moving on, and stop.",
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return tool_server(
|
|
256
|
+
name="flow", version="0.1.0", tools=[hand_to_next_stage]
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# -- talking ---------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
async def start(self, on_event: Callable[..., Any] | None = None) -> None:
|
|
262
|
+
"""Open the stage this agent is up to, and set it going."""
|
|
263
|
+
opening = await self._open(self._resume_at())
|
|
264
|
+
await self.stage.say(opening, on_event=on_event) # type: ignore[union-attr]
|
|
265
|
+
|
|
266
|
+
async def open_quietly(self) -> None:
|
|
267
|
+
"""Open the stage without telling it to start.
|
|
268
|
+
|
|
269
|
+
A stage's opening message is an instruction to do the stage's work. Sending it because
|
|
270
|
+
somebody said hello means a greeting kicks off a build, so it is only sent when the work
|
|
271
|
+
is actually what was asked for.
|
|
272
|
+
"""
|
|
273
|
+
await self._open(self._resume_at())
|
|
274
|
+
|
|
275
|
+
def _resume_at(self) -> str:
|
|
276
|
+
"""Pick up where the artifacts say this agent got to."""
|
|
277
|
+
if self.source is None and self.contract is None:
|
|
278
|
+
return RECEPTION
|
|
279
|
+
if self.contract is None:
|
|
280
|
+
return UNDERSTAND
|
|
281
|
+
if not self.world_built:
|
|
282
|
+
return BUILD
|
|
283
|
+
if not self.scenarios_written:
|
|
284
|
+
return SCENARIOS
|
|
285
|
+
return RUN
|
|
286
|
+
|
|
287
|
+
def _grant_flow(self) -> None:
|
|
288
|
+
if self.stage is not None:
|
|
289
|
+
self.stage.grant(
|
|
290
|
+
"flow", self._flow_server(), ["hand_to_next_stage"], ask=self.ask
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
async def say(
|
|
294
|
+
self, message: str, on_event: Callable[..., Any] | None = None
|
|
295
|
+
) -> None:
|
|
296
|
+
"""Send a message to whichever stage is open."""
|
|
297
|
+
self.history.append(message)
|
|
298
|
+
if self.stage is None:
|
|
299
|
+
await self.open_quietly()
|
|
300
|
+
await self.stage.say(message, on_event=on_event) # type: ignore[union-attr]
|
|
301
|
+
# Before anything acts on this turn, take up what it established. A handoff in the same
|
|
302
|
+
# turn opens the next stage, and every stage is built from ``self.source``; read it off
|
|
303
|
+
# afterwards instead and that hop dies on an agent nobody has named, taking the turn with
|
|
304
|
+
# it and leaving the conversation in reception with no way forward.
|
|
305
|
+
established = self._take_up()
|
|
306
|
+
moved = False
|
|
307
|
+
# A handoff moves the request, not just the conversation: the next stage opens and is
|
|
308
|
+
# given the person's own words. Bounded, because each hop is a model turn.
|
|
309
|
+
for _hop in range(3):
|
|
310
|
+
request = self._handoff.pop("request", None)
|
|
311
|
+
if not request:
|
|
312
|
+
break
|
|
313
|
+
following = self.next_stage()
|
|
314
|
+
if following is None:
|
|
315
|
+
break
|
|
316
|
+
await self._open(following)
|
|
317
|
+
moved = True
|
|
318
|
+
await self.stage.say(request, on_event=on_event) # type: ignore[union-attr]
|
|
319
|
+
if established and not moved:
|
|
320
|
+
# Nothing is left to decide once the agent is known, so it goes on rather than making
|
|
321
|
+
# somebody confirm what they already said. Unless a handoff already moved us, which
|
|
322
|
+
# would make this a second hop over the same request.
|
|
323
|
+
await self.advance(on_event=on_event)
|
|
324
|
+
|
|
325
|
+
def _take_up(self) -> bool:
|
|
326
|
+
"""Take up whatever the turn just established. True if this turn named the agent.
|
|
327
|
+
|
|
328
|
+
Reception is the only stage whose result is not a file, so it is the only one the
|
|
329
|
+
conversation has to read back.
|
|
330
|
+
"""
|
|
331
|
+
settled = self._found.pop("source", None)
|
|
332
|
+
if settled is None:
|
|
333
|
+
return False
|
|
334
|
+
self.source = settled
|
|
335
|
+
self.out = self.out or artifact_dir(settled.name)
|
|
336
|
+
return True
|
|
337
|
+
|
|
338
|
+
def reachable(self) -> dict[str, str]:
|
|
339
|
+
"""Every stage, and why it can or cannot be opened right now.
|
|
340
|
+
|
|
341
|
+
Stages are not a wizard. Coming back to correct a contract after the world is built is
|
|
342
|
+
the ordinary case, not an exception, so any stage whose input exists can be opened at
|
|
343
|
+
any time. What cannot be skipped is the input itself: there is nothing to build a world
|
|
344
|
+
from without a contract, and nothing to write scenarios against without a world.
|
|
345
|
+
"""
|
|
346
|
+
contract = self.contract is not None
|
|
347
|
+
# Every stage after the first works from the contract, so that is the first thing each
|
|
348
|
+
# of them needs; its own input is the second.
|
|
349
|
+
needs_contract = "needs a contract first"
|
|
350
|
+
why = {
|
|
351
|
+
RECEPTION: "",
|
|
352
|
+
UNDERSTAND: ""
|
|
353
|
+
if self.source is not None
|
|
354
|
+
else "cannot re-read the agent without knowing where its source lives",
|
|
355
|
+
BUILD: "" if contract else needs_contract,
|
|
356
|
+
SCENARIOS: ""
|
|
357
|
+
if contract and self.world_built
|
|
358
|
+
else (
|
|
359
|
+
needs_contract if not contract else "needs a built environment first"
|
|
360
|
+
),
|
|
361
|
+
RUN: ""
|
|
362
|
+
if contract and self.scenarios_written
|
|
363
|
+
else (needs_contract if not contract else "needs scenarios first"),
|
|
364
|
+
}
|
|
365
|
+
return why
|
|
366
|
+
|
|
367
|
+
async def go_to(
|
|
368
|
+
self, stage_name: str, on_event: Callable[..., Any] | None = None
|
|
369
|
+
) -> str:
|
|
370
|
+
"""Open one stage by name, whether or not it is the next one.
|
|
371
|
+
|
|
372
|
+
The stage is opened but not set going: its opening message is an instruction to do that
|
|
373
|
+
stage's work, and somebody choosing to look at a stage has not thereby asked for it to
|
|
374
|
+
start spending.
|
|
375
|
+
"""
|
|
376
|
+
if stage_name not in _NEXT and stage_name != DONE:
|
|
377
|
+
raise RuntimeError(f"no stage called {stage_name!r}")
|
|
378
|
+
blocked = self.reachable().get(stage_name, "")
|
|
379
|
+
if blocked:
|
|
380
|
+
raise RuntimeError(f"cannot open the {stage_name} stage: {blocked}")
|
|
381
|
+
return await self._open(stage_name)
|
|
382
|
+
|
|
383
|
+
async def advance(self, on_event: Callable[..., Any] | None = None) -> str | None:
|
|
384
|
+
"""Move to the next stage and start it. Returns the stage entered, or None."""
|
|
385
|
+
following = self.next_stage()
|
|
386
|
+
if following is None:
|
|
387
|
+
return None
|
|
388
|
+
opening = await self._open(following)
|
|
389
|
+
await self.stage.say(opening, on_event=on_event) # type: ignore[union-attr]
|
|
390
|
+
return following
|
|
391
|
+
|
|
392
|
+
async def close(self) -> None:
|
|
393
|
+
await self._close()
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def open_conversation(
|
|
397
|
+
*,
|
|
398
|
+
name: str = "",
|
|
399
|
+
path: str = "",
|
|
400
|
+
kind: str = "repo",
|
|
401
|
+
out: Path | None = None,
|
|
402
|
+
ask: Callable[..., Any] | None = None,
|
|
403
|
+
wanted: int = 10,
|
|
404
|
+
workspace: Path | None = None,
|
|
405
|
+
) -> Conversation:
|
|
406
|
+
"""Open the harness. With nothing, it starts by asking which agent you mean.
|
|
407
|
+
|
|
408
|
+
Naming the agent up front is a shortcut for coming back to one already in progress, not the
|
|
409
|
+
way in. Everything it needs can be said.
|
|
410
|
+
"""
|
|
411
|
+
source = resolve(kind, name=name, root=path) if name and path else None
|
|
412
|
+
return Conversation(
|
|
413
|
+
source=source,
|
|
414
|
+
out=out or (artifact_dir(name) if name else None),
|
|
415
|
+
ask=ask,
|
|
416
|
+
wanted=wanted,
|
|
417
|
+
workspace=workspace,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
async def _demo() -> None: # pragma: no cover - convenience for manual runs
|
|
422
|
+
conversation = open_conversation(name="demo", path=".")
|
|
423
|
+
await conversation.start()
|
|
424
|
+
await conversation.close()
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
if __name__ == "__main__": # pragma: no cover
|
|
428
|
+
asyncio.run(_demo())
|