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,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base backend abstraction for evaluation execution.
|
|
3
|
+
|
|
4
|
+
This module defines the abstract interface for evaluation backends,
|
|
5
|
+
enabling pluggable execution strategies (thread pool, Temporal, etc.).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import Dict, Any, Optional, Callable, TypeVar, Generic, List
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
T = TypeVar("T")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TaskStatus(Enum):
|
|
19
|
+
"""Status of a backend task."""
|
|
20
|
+
PENDING = "pending"
|
|
21
|
+
RUNNING = "running"
|
|
22
|
+
COMPLETED = "completed"
|
|
23
|
+
FAILED = "failed"
|
|
24
|
+
CANCELLED = "cancelled"
|
|
25
|
+
TIMEOUT = "timeout"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class BackendConfig:
|
|
30
|
+
"""
|
|
31
|
+
Base configuration for backends.
|
|
32
|
+
|
|
33
|
+
Subclasses add backend-specific options.
|
|
34
|
+
"""
|
|
35
|
+
max_workers: int = 4
|
|
36
|
+
timeout_seconds: float = 300.0 # 5 minutes default
|
|
37
|
+
retry_count: int = 0
|
|
38
|
+
retry_delay_seconds: float = 1.0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class TaskHandle(Generic[T]):
|
|
43
|
+
"""
|
|
44
|
+
Handle to a submitted task.
|
|
45
|
+
|
|
46
|
+
Allows checking status and retrieving results.
|
|
47
|
+
"""
|
|
48
|
+
task_id: str
|
|
49
|
+
backend_name: str
|
|
50
|
+
submitted_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
51
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
# Internal state - set by backend
|
|
54
|
+
_status: TaskStatus = TaskStatus.PENDING
|
|
55
|
+
_result: Optional[T] = None
|
|
56
|
+
_error: Optional[str] = None
|
|
57
|
+
_completed_at: Optional[datetime] = None
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def status(self) -> TaskStatus:
|
|
61
|
+
"""Current task status."""
|
|
62
|
+
return self._status
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def is_done(self) -> bool:
|
|
66
|
+
"""Whether task has completed (success or failure)."""
|
|
67
|
+
return self._status in (
|
|
68
|
+
TaskStatus.COMPLETED,
|
|
69
|
+
TaskStatus.FAILED,
|
|
70
|
+
TaskStatus.CANCELLED,
|
|
71
|
+
TaskStatus.TIMEOUT,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def succeeded(self) -> bool:
|
|
76
|
+
"""Whether task completed successfully."""
|
|
77
|
+
return self._status == TaskStatus.COMPLETED
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def result(self) -> Optional[T]:
|
|
81
|
+
"""Task result if completed successfully."""
|
|
82
|
+
return self._result
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def error(self) -> Optional[str]:
|
|
86
|
+
"""Error message if task failed."""
|
|
87
|
+
return self._error
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Backend(ABC):
|
|
91
|
+
"""
|
|
92
|
+
Abstract base class for evaluation backends.
|
|
93
|
+
|
|
94
|
+
Backends handle the actual execution of evaluation tasks,
|
|
95
|
+
whether locally (thread pool), distributed (Temporal, Celery),
|
|
96
|
+
or in a compute cluster (Ray).
|
|
97
|
+
|
|
98
|
+
Example implementation:
|
|
99
|
+
class MyBackend(Backend):
|
|
100
|
+
name = "my_backend"
|
|
101
|
+
|
|
102
|
+
def submit(self, fn, args, kwargs, context):
|
|
103
|
+
# Submit task to execution system
|
|
104
|
+
task_id = my_system.submit(fn, *args, **kwargs)
|
|
105
|
+
return TaskHandle(task_id=task_id, backend_name=self.name)
|
|
106
|
+
|
|
107
|
+
def get_result(self, handle, timeout):
|
|
108
|
+
# Wait for and return result
|
|
109
|
+
return my_system.wait(handle.task_id, timeout)
|
|
110
|
+
|
|
111
|
+
Thread Safety:
|
|
112
|
+
Backends must be thread-safe. Multiple threads may call
|
|
113
|
+
submit() and get_result() concurrently.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
name: str = "base"
|
|
117
|
+
|
|
118
|
+
@abstractmethod
|
|
119
|
+
def submit(
|
|
120
|
+
self,
|
|
121
|
+
fn: Callable[..., T],
|
|
122
|
+
args: tuple = (),
|
|
123
|
+
kwargs: Optional[Dict[str, Any]] = None,
|
|
124
|
+
context: Optional[Dict[str, Any]] = None,
|
|
125
|
+
) -> TaskHandle[T]:
|
|
126
|
+
"""
|
|
127
|
+
Submit a task for execution.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
fn: The function to execute
|
|
131
|
+
args: Positional arguments
|
|
132
|
+
kwargs: Keyword arguments
|
|
133
|
+
context: Trace context for propagation (trace_id, span_id, etc.)
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
TaskHandle to track the task
|
|
137
|
+
"""
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
@abstractmethod
|
|
141
|
+
def get_result(
|
|
142
|
+
self,
|
|
143
|
+
handle: TaskHandle[T],
|
|
144
|
+
timeout: Optional[float] = None,
|
|
145
|
+
) -> T:
|
|
146
|
+
"""
|
|
147
|
+
Get the result of a submitted task.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
handle: The task handle from submit()
|
|
151
|
+
timeout: Maximum seconds to wait (None = wait forever)
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
The task result
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
TimeoutError: If timeout exceeded
|
|
158
|
+
Exception: If task failed
|
|
159
|
+
"""
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
@abstractmethod
|
|
163
|
+
def get_status(self, handle: TaskHandle) -> TaskStatus:
|
|
164
|
+
"""
|
|
165
|
+
Get current status of a task.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
handle: The task handle
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Current TaskStatus
|
|
172
|
+
"""
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
@abstractmethod
|
|
176
|
+
def cancel(self, handle: TaskHandle) -> bool:
|
|
177
|
+
"""
|
|
178
|
+
Attempt to cancel a task.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
handle: The task handle
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
True if cancelled, False if already running/complete
|
|
185
|
+
"""
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
def submit_batch(
|
|
189
|
+
self,
|
|
190
|
+
tasks: List[tuple], # List of (fn, args, kwargs, context)
|
|
191
|
+
) -> List[TaskHandle]:
|
|
192
|
+
"""
|
|
193
|
+
Submit multiple tasks.
|
|
194
|
+
|
|
195
|
+
Default implementation submits sequentially.
|
|
196
|
+
Override for backends with batch submission support.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
tasks: List of (function, args, kwargs, context) tuples
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
List of TaskHandles
|
|
203
|
+
"""
|
|
204
|
+
handles = []
|
|
205
|
+
for fn, args, kwargs, context in tasks:
|
|
206
|
+
handle = self.submit(fn, args, kwargs or {}, context)
|
|
207
|
+
handles.append(handle)
|
|
208
|
+
return handles
|
|
209
|
+
|
|
210
|
+
def shutdown(self, wait: bool = True) -> None:
|
|
211
|
+
"""
|
|
212
|
+
Shutdown the backend.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
wait: Whether to wait for pending tasks to complete
|
|
216
|
+
"""
|
|
217
|
+
pass # Default: no-op
|
|
218
|
+
|
|
219
|
+
def __enter__(self) -> "Backend":
|
|
220
|
+
return self
|
|
221
|
+
|
|
222
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
223
|
+
self.shutdown(wait=True)
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Celery backend for distributed task execution.
|
|
3
|
+
|
|
4
|
+
Provides distributed task execution using Celery with Redis/RabbitMQ.
|
|
5
|
+
Requires: pip install 'celery[redis]'
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import threading
|
|
10
|
+
import uuid
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any, Callable, Dict, List, Optional, TypeVar
|
|
13
|
+
|
|
14
|
+
from .base import Backend, BackendConfig, TaskHandle, TaskStatus
|
|
15
|
+
from ._utils import CELERY
|
|
16
|
+
|
|
17
|
+
T = TypeVar("T")
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class CeleryConfig(BackendConfig):
|
|
23
|
+
"""
|
|
24
|
+
Configuration for Celery backend.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
broker_url: Message broker URL (Redis/RabbitMQ)
|
|
28
|
+
result_backend: Result backend URL
|
|
29
|
+
task_queue: Queue name for tasks
|
|
30
|
+
task_priority: Default task priority (0-9, higher = more priority)
|
|
31
|
+
task_serializer: Serialization format ('json', 'pickle')
|
|
32
|
+
result_serializer: Result serialization format
|
|
33
|
+
task_acks_late: Acknowledge tasks after execution
|
|
34
|
+
task_reject_on_worker_lost: Reject tasks if worker dies
|
|
35
|
+
task_track_started: Track when tasks start executing
|
|
36
|
+
worker_prefetch_multiplier: Number of tasks to prefetch
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
broker_url: str = "redis://localhost:6379/0"
|
|
40
|
+
result_backend: str = "redis://localhost:6379/1"
|
|
41
|
+
task_queue: str = "eval_tasks"
|
|
42
|
+
task_priority: int = 0
|
|
43
|
+
task_serializer: str = "cloudpickle"
|
|
44
|
+
result_serializer: str = "cloudpickle"
|
|
45
|
+
task_acks_late: bool = True
|
|
46
|
+
task_reject_on_worker_lost: bool = True
|
|
47
|
+
task_track_started: bool = True
|
|
48
|
+
worker_prefetch_multiplier: int = 1
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CeleryBackend(Backend):
|
|
52
|
+
"""
|
|
53
|
+
Celery backend for distributed task execution.
|
|
54
|
+
|
|
55
|
+
Uses Celery for distributed task execution across multiple workers.
|
|
56
|
+
Integrates with existing Celery infrastructure (Redis, RabbitMQ).
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
config = CeleryConfig(
|
|
60
|
+
broker_url="redis://localhost:6379/0",
|
|
61
|
+
result_backend="redis://localhost:6379/1",
|
|
62
|
+
task_queue="eval_tasks",
|
|
63
|
+
)
|
|
64
|
+
backend = CeleryBackend(config)
|
|
65
|
+
|
|
66
|
+
handle = backend.submit(my_eval_func, args=(input_data,))
|
|
67
|
+
result = backend.get_result(handle)
|
|
68
|
+
|
|
69
|
+
Note:
|
|
70
|
+
Requires Celery workers to be running to execute tasks.
|
|
71
|
+
Start workers with: celery -A your_app worker -Q eval_tasks
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
name: str = "celery"
|
|
75
|
+
|
|
76
|
+
def __init__(self, config: Optional[CeleryConfig] = None):
|
|
77
|
+
"""
|
|
78
|
+
Initialize Celery backend.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
config: Celery configuration
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
ImportError: If celery is not installed
|
|
85
|
+
"""
|
|
86
|
+
CELERY.require()
|
|
87
|
+
|
|
88
|
+
self.config = config or CeleryConfig()
|
|
89
|
+
self._app: Optional[Any] = None
|
|
90
|
+
self._task: Optional[Any] = None
|
|
91
|
+
self._handles: Dict[str, TaskHandle] = {}
|
|
92
|
+
self._async_results: Dict[str, Any] = {}
|
|
93
|
+
self._lock = threading.Lock()
|
|
94
|
+
|
|
95
|
+
self._setup_celery()
|
|
96
|
+
|
|
97
|
+
def _setup_celery(self) -> None:
|
|
98
|
+
"""Set up Celery application and task from shared worker module."""
|
|
99
|
+
from .celery_worker import app, eval_task
|
|
100
|
+
|
|
101
|
+
# Override broker/backend if config differs from env defaults
|
|
102
|
+
app.conf.update(
|
|
103
|
+
broker_url=self.config.broker_url,
|
|
104
|
+
result_backend=self.config.result_backend,
|
|
105
|
+
task_serializer=self.config.task_serializer,
|
|
106
|
+
result_serializer=self.config.result_serializer,
|
|
107
|
+
task_acks_late=self.config.task_acks_late,
|
|
108
|
+
task_reject_on_worker_lost=self.config.task_reject_on_worker_lost,
|
|
109
|
+
task_track_started=self.config.task_track_started,
|
|
110
|
+
worker_prefetch_multiplier=self.config.worker_prefetch_multiplier,
|
|
111
|
+
task_default_queue=self.config.task_queue,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
self._app = app
|
|
115
|
+
self._task = eval_task
|
|
116
|
+
|
|
117
|
+
def submit(
|
|
118
|
+
self,
|
|
119
|
+
fn: Callable[..., T],
|
|
120
|
+
args: tuple = (),
|
|
121
|
+
kwargs: Optional[Dict[str, Any]] = None,
|
|
122
|
+
context: Optional[Dict[str, Any]] = None,
|
|
123
|
+
) -> TaskHandle[T]:
|
|
124
|
+
"""
|
|
125
|
+
Submit a task to Celery.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
fn: The function to execute
|
|
129
|
+
args: Positional arguments
|
|
130
|
+
kwargs: Keyword arguments
|
|
131
|
+
context: Trace context (stored in task metadata)
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
TaskHandle to track the task
|
|
135
|
+
"""
|
|
136
|
+
kwargs = kwargs or {}
|
|
137
|
+
task_id = str(uuid.uuid4())
|
|
138
|
+
|
|
139
|
+
# Create handle
|
|
140
|
+
handle = TaskHandle(
|
|
141
|
+
task_id=task_id,
|
|
142
|
+
backend_name=self.name,
|
|
143
|
+
metadata={
|
|
144
|
+
"queue": self.config.task_queue,
|
|
145
|
+
"priority": self.config.task_priority,
|
|
146
|
+
"function": fn.__name__ if hasattr(fn, "__name__") else str(fn),
|
|
147
|
+
"context": context,
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
handle._status = TaskStatus.PENDING
|
|
151
|
+
|
|
152
|
+
with self._lock:
|
|
153
|
+
self._handles[task_id] = handle
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
# Submit task to Celery
|
|
157
|
+
async_result = self._task.apply_async(
|
|
158
|
+
args=(fn, args, kwargs),
|
|
159
|
+
task_id=task_id,
|
|
160
|
+
queue=self.config.task_queue,
|
|
161
|
+
priority=self.config.task_priority,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
with self._lock:
|
|
165
|
+
self._async_results[task_id] = async_result
|
|
166
|
+
handle._status = TaskStatus.PENDING
|
|
167
|
+
|
|
168
|
+
logger.debug(f"Submitted task {task_id} to queue {self.config.task_queue}")
|
|
169
|
+
|
|
170
|
+
except Exception as e:
|
|
171
|
+
handle._status = TaskStatus.FAILED
|
|
172
|
+
handle._error = str(e)
|
|
173
|
+
logger.error(f"Failed to submit task {task_id}: {e}")
|
|
174
|
+
|
|
175
|
+
return handle
|
|
176
|
+
|
|
177
|
+
def get_result(
|
|
178
|
+
self,
|
|
179
|
+
handle: TaskHandle[T],
|
|
180
|
+
timeout: Optional[float] = None,
|
|
181
|
+
) -> T:
|
|
182
|
+
"""
|
|
183
|
+
Get the result of a Celery task.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
handle: The task handle from submit()
|
|
187
|
+
timeout: Maximum seconds to wait
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
The task result
|
|
191
|
+
|
|
192
|
+
Raises:
|
|
193
|
+
TimeoutError: If timeout exceeded
|
|
194
|
+
Exception: If task failed
|
|
195
|
+
"""
|
|
196
|
+
timeout = timeout or self.config.timeout_seconds
|
|
197
|
+
|
|
198
|
+
with self._lock:
|
|
199
|
+
async_result = self._async_results.get(handle.task_id)
|
|
200
|
+
|
|
201
|
+
if async_result is None:
|
|
202
|
+
# Try to get result from backend
|
|
203
|
+
from celery.result import AsyncResult
|
|
204
|
+
async_result = AsyncResult(handle.task_id, app=self._app)
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
result = async_result.get(timeout=timeout)
|
|
208
|
+
|
|
209
|
+
with self._lock:
|
|
210
|
+
if handle.task_id in self._handles:
|
|
211
|
+
self._handles[handle.task_id]._status = TaskStatus.COMPLETED
|
|
212
|
+
self._handles[handle.task_id]._result = result
|
|
213
|
+
|
|
214
|
+
return result
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
error_name = type(e).__name__
|
|
218
|
+
if "TimeoutError" in error_name or "TimeLimitExceeded" in error_name:
|
|
219
|
+
with self._lock:
|
|
220
|
+
if handle.task_id in self._handles:
|
|
221
|
+
self._handles[handle.task_id]._status = TaskStatus.TIMEOUT
|
|
222
|
+
raise TimeoutError(f"Task {handle.task_id} timed out after {timeout}s")
|
|
223
|
+
|
|
224
|
+
with self._lock:
|
|
225
|
+
if handle.task_id in self._handles:
|
|
226
|
+
self._handles[handle.task_id]._status = TaskStatus.FAILED
|
|
227
|
+
self._handles[handle.task_id]._error = str(e)
|
|
228
|
+
raise
|
|
229
|
+
|
|
230
|
+
def get_status(self, handle: TaskHandle) -> TaskStatus:
|
|
231
|
+
"""
|
|
232
|
+
Get current status of a Celery task.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
handle: The task handle
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Current TaskStatus
|
|
239
|
+
"""
|
|
240
|
+
with self._lock:
|
|
241
|
+
async_result = self._async_results.get(handle.task_id)
|
|
242
|
+
|
|
243
|
+
if async_result is None:
|
|
244
|
+
from celery.result import AsyncResult
|
|
245
|
+
async_result = AsyncResult(handle.task_id, app=self._app)
|
|
246
|
+
|
|
247
|
+
return self._map_celery_status(async_result.status)
|
|
248
|
+
|
|
249
|
+
def _map_celery_status(self, celery_status: str) -> TaskStatus:
|
|
250
|
+
"""Map Celery task status to TaskStatus."""
|
|
251
|
+
status_map = {
|
|
252
|
+
"PENDING": TaskStatus.PENDING,
|
|
253
|
+
"STARTED": TaskStatus.RUNNING,
|
|
254
|
+
"SUCCESS": TaskStatus.COMPLETED,
|
|
255
|
+
"FAILURE": TaskStatus.FAILED,
|
|
256
|
+
"REVOKED": TaskStatus.CANCELLED,
|
|
257
|
+
"RETRY": TaskStatus.RUNNING,
|
|
258
|
+
}
|
|
259
|
+
return status_map.get(celery_status, TaskStatus.PENDING)
|
|
260
|
+
|
|
261
|
+
def cancel(self, handle: TaskHandle) -> bool:
|
|
262
|
+
"""
|
|
263
|
+
Revoke/cancel a Celery task.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
handle: The task handle
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
True if revoked, False otherwise
|
|
270
|
+
"""
|
|
271
|
+
try:
|
|
272
|
+
with self._lock:
|
|
273
|
+
async_result = self._async_results.get(handle.task_id)
|
|
274
|
+
|
|
275
|
+
if async_result is None:
|
|
276
|
+
from celery.result import AsyncResult
|
|
277
|
+
async_result = AsyncResult(handle.task_id, app=self._app)
|
|
278
|
+
|
|
279
|
+
async_result.revoke(terminate=True)
|
|
280
|
+
|
|
281
|
+
with self._lock:
|
|
282
|
+
if handle.task_id in self._handles:
|
|
283
|
+
self._handles[handle.task_id]._status = TaskStatus.CANCELLED
|
|
284
|
+
|
|
285
|
+
logger.debug(f"Revoked task {handle.task_id}")
|
|
286
|
+
return True
|
|
287
|
+
|
|
288
|
+
except Exception as e:
|
|
289
|
+
logger.warning(f"Failed to revoke task {handle.task_id}: {e}")
|
|
290
|
+
return False
|
|
291
|
+
|
|
292
|
+
def submit_batch(
|
|
293
|
+
self,
|
|
294
|
+
tasks: List[tuple],
|
|
295
|
+
) -> List[TaskHandle]:
|
|
296
|
+
"""
|
|
297
|
+
Submit multiple tasks efficiently using Celery group.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
tasks: List of (function, args, kwargs, context) tuples
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
List of TaskHandles
|
|
304
|
+
"""
|
|
305
|
+
from celery import group
|
|
306
|
+
|
|
307
|
+
handles = []
|
|
308
|
+
signatures = []
|
|
309
|
+
|
|
310
|
+
for fn, args, kwargs, context in tasks:
|
|
311
|
+
kwargs = kwargs or {}
|
|
312
|
+
task_id = str(uuid.uuid4())
|
|
313
|
+
|
|
314
|
+
handle = TaskHandle(
|
|
315
|
+
task_id=task_id,
|
|
316
|
+
backend_name=self.name,
|
|
317
|
+
metadata={
|
|
318
|
+
"queue": self.config.task_queue,
|
|
319
|
+
"function": fn.__name__ if hasattr(fn, "__name__") else str(fn),
|
|
320
|
+
"context": context,
|
|
321
|
+
},
|
|
322
|
+
)
|
|
323
|
+
handle._status = TaskStatus.PENDING
|
|
324
|
+
handles.append(handle)
|
|
325
|
+
|
|
326
|
+
with self._lock:
|
|
327
|
+
self._handles[task_id] = handle
|
|
328
|
+
|
|
329
|
+
sig = self._task.signature(
|
|
330
|
+
args=(fn, args, kwargs),
|
|
331
|
+
task_id=task_id,
|
|
332
|
+
queue=self.config.task_queue,
|
|
333
|
+
)
|
|
334
|
+
signatures.append(sig)
|
|
335
|
+
|
|
336
|
+
# Submit as a group
|
|
337
|
+
try:
|
|
338
|
+
job = group(signatures)
|
|
339
|
+
group_result = job.apply_async()
|
|
340
|
+
|
|
341
|
+
# Store individual results
|
|
342
|
+
for handle, async_result in zip(handles, group_result.results):
|
|
343
|
+
with self._lock:
|
|
344
|
+
self._async_results[handle.task_id] = async_result
|
|
345
|
+
|
|
346
|
+
except Exception as e:
|
|
347
|
+
logger.error(f"Failed to submit batch: {e}")
|
|
348
|
+
for handle in handles:
|
|
349
|
+
handle._status = TaskStatus.FAILED
|
|
350
|
+
handle._error = str(e)
|
|
351
|
+
|
|
352
|
+
return handles
|
|
353
|
+
|
|
354
|
+
def shutdown(self, wait: bool = True) -> None:
|
|
355
|
+
"""
|
|
356
|
+
Shutdown the Celery backend.
|
|
357
|
+
|
|
358
|
+
Revokes only our tracked tasks, not the entire Celery queue.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
wait: Whether to wait for pending tasks
|
|
362
|
+
"""
|
|
363
|
+
if self._app is not None:
|
|
364
|
+
with self._lock:
|
|
365
|
+
task_ids = list(self._async_results.keys())
|
|
366
|
+
|
|
367
|
+
for task_id in task_ids:
|
|
368
|
+
try:
|
|
369
|
+
self._app.control.revoke(task_id, terminate=not wait)
|
|
370
|
+
except Exception:
|
|
371
|
+
pass
|
|
372
|
+
|
|
373
|
+
with self._lock:
|
|
374
|
+
self._handles.clear()
|
|
375
|
+
self._async_results.clear()
|
|
376
|
+
|
|
377
|
+
logger.info(f"Celery backend shut down, revoked {len(task_ids)} tasks")
|
|
378
|
+
|
|
379
|
+
def get_queue_length(self) -> int:
|
|
380
|
+
"""
|
|
381
|
+
Get the number of tasks waiting in the queue.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Number of pending tasks in the queue
|
|
385
|
+
"""
|
|
386
|
+
try:
|
|
387
|
+
with self._app.pool.acquire(block=True) as conn:
|
|
388
|
+
return conn.default_channel.client.llen(self.config.task_queue)
|
|
389
|
+
except Exception:
|
|
390
|
+
return -1
|
|
391
|
+
|
|
392
|
+
def get_stats(self) -> dict:
|
|
393
|
+
"""
|
|
394
|
+
Get Celery backend statistics.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
Dictionary with queue and worker stats
|
|
398
|
+
"""
|
|
399
|
+
stats = {
|
|
400
|
+
"queue": self.config.task_queue,
|
|
401
|
+
"broker": self.config.broker_url,
|
|
402
|
+
"pending_tasks": len([
|
|
403
|
+
h for h in self._handles.values()
|
|
404
|
+
if h._status == TaskStatus.PENDING
|
|
405
|
+
]),
|
|
406
|
+
"running_tasks": len([
|
|
407
|
+
h for h in self._handles.values()
|
|
408
|
+
if h._status == TaskStatus.RUNNING
|
|
409
|
+
]),
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
stats["queue_length"] = self.get_queue_length()
|
|
414
|
+
except Exception:
|
|
415
|
+
stats["queue_length"] = -1
|
|
416
|
+
|
|
417
|
+
return stats
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared Celery app and task definition.
|
|
3
|
+
|
|
4
|
+
This module is importable by both the SDK client (CeleryBackend) and a
|
|
5
|
+
standalone Celery worker process, solving the problem of task registration
|
|
6
|
+
needing to be visible to workers.
|
|
7
|
+
|
|
8
|
+
Run as a worker::
|
|
9
|
+
|
|
10
|
+
celery -A fi.evals.framework.backends.celery_worker worker \\
|
|
11
|
+
-Q eval_tasks --loglevel=info
|
|
12
|
+
|
|
13
|
+
Or configure via environment variables::
|
|
14
|
+
|
|
15
|
+
CELERY_BROKER_URL=redis://redis:6379/0 \\
|
|
16
|
+
CELERY_RESULT_BACKEND=redis://redis:6379/1 \\
|
|
17
|
+
celery -A fi.evals.framework.backends.celery_worker worker \\
|
|
18
|
+
-Q eval_tasks --loglevel=info
|
|
19
|
+
|
|
20
|
+
Requires: pip install 'celery[redis]'
|
|
21
|
+
|
|
22
|
+
Security note:
|
|
23
|
+
Pickle serialization is used here because Celery tasks transport arbitrary
|
|
24
|
+
Python callables (evaluation functions). This is the standard approach for
|
|
25
|
+
Celery task serialization in trusted environments. Never expose the broker
|
|
26
|
+
to untrusted networks.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import os
|
|
30
|
+
|
|
31
|
+
import cloudpickle
|
|
32
|
+
from celery import Celery
|
|
33
|
+
from kombu.serialization import register
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# Register cloudpickle as a Celery serializer.
|
|
37
|
+
# Unlike stdlib pickle, cloudpickle can serialize lambdas, closures, and
|
|
38
|
+
# functions defined in __main__ — exactly what we need for ad-hoc eval fns.
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
register(
|
|
42
|
+
"cloudpickle",
|
|
43
|
+
cloudpickle.dumps,
|
|
44
|
+
cloudpickle.loads,
|
|
45
|
+
content_type="application/x-cloudpickle",
|
|
46
|
+
content_encoding="binary",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# App configuration — shared between SDK client and Docker worker
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
BROKER_URL = os.environ.get("CELERY_BROKER_URL", "redis://localhost:6379/0")
|
|
54
|
+
RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND", "redis://localhost:6379/1")
|
|
55
|
+
TASK_QUEUE = os.environ.get("CELERY_TASK_QUEUE", "eval_tasks")
|
|
56
|
+
|
|
57
|
+
app = Celery(
|
|
58
|
+
"eval_tasks",
|
|
59
|
+
broker=BROKER_URL,
|
|
60
|
+
backend=RESULT_BACKEND,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
app.conf.update(
|
|
64
|
+
task_serializer="cloudpickle",
|
|
65
|
+
result_serializer="cloudpickle",
|
|
66
|
+
accept_content=["cloudpickle", "json"],
|
|
67
|
+
task_acks_late=True,
|
|
68
|
+
task_reject_on_worker_lost=True,
|
|
69
|
+
task_track_started=True,
|
|
70
|
+
worker_prefetch_multiplier=1,
|
|
71
|
+
task_default_queue=TASK_QUEUE,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@app.task(bind=True, name="eval_task")
|
|
76
|
+
def eval_task(self, fn, args, kwargs):
|
|
77
|
+
"""Generic task that executes the provided function."""
|
|
78
|
+
return fn(*args, **kwargs)
|