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,350 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Temporal backend for durable workflow execution.
|
|
3
|
+
|
|
4
|
+
Provides distributed, fault-tolerant task execution using Temporal.io.
|
|
5
|
+
Requires: pip install temporalio
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
import threading
|
|
11
|
+
import uuid
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from datetime import timedelta
|
|
14
|
+
from typing import Any, Callable, Dict, Optional, TypeVar
|
|
15
|
+
|
|
16
|
+
from .base import Backend, BackendConfig, TaskHandle, TaskStatus
|
|
17
|
+
from ._utils import TEMPORAL
|
|
18
|
+
|
|
19
|
+
T = TypeVar("T")
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class TemporalConfig(BackendConfig):
|
|
25
|
+
"""
|
|
26
|
+
Configuration for Temporal backend.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
host: Temporal server address (host:port)
|
|
30
|
+
namespace: Temporal namespace
|
|
31
|
+
task_queue: Task queue name for workers
|
|
32
|
+
workflow_id_prefix: Prefix for generated workflow IDs
|
|
33
|
+
execution_timeout_seconds: Maximum workflow execution time
|
|
34
|
+
task_timeout_seconds: Maximum time for a single task
|
|
35
|
+
retry_policy_max_attempts: Max retry attempts for activities
|
|
36
|
+
retry_policy_initial_interval: Initial retry interval in seconds
|
|
37
|
+
retry_policy_backoff_coefficient: Backoff multiplier
|
|
38
|
+
identity: Worker identity string
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
host: str = "localhost:7233"
|
|
42
|
+
namespace: str = "default"
|
|
43
|
+
task_queue: str = "eval-tasks"
|
|
44
|
+
workflow_id_prefix: str = "eval-"
|
|
45
|
+
execution_timeout_seconds: float = 3600.0 # 1 hour
|
|
46
|
+
task_timeout_seconds: float = 300.0 # 5 minutes
|
|
47
|
+
retry_policy_max_attempts: int = 3
|
|
48
|
+
retry_policy_initial_interval: float = 1.0
|
|
49
|
+
retry_policy_backoff_coefficient: float = 2.0
|
|
50
|
+
identity: Optional[str] = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TemporalBackend(Backend):
|
|
54
|
+
"""
|
|
55
|
+
Temporal backend for durable workflow execution.
|
|
56
|
+
|
|
57
|
+
Uses Temporal.io for distributed, fault-tolerant task execution.
|
|
58
|
+
Workflows survive process restarts and infrastructure failures.
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
config = TemporalConfig(
|
|
62
|
+
host="temporal.example.com:7233",
|
|
63
|
+
namespace="evaluations",
|
|
64
|
+
task_queue="eval-tasks",
|
|
65
|
+
)
|
|
66
|
+
backend = TemporalBackend(config)
|
|
67
|
+
|
|
68
|
+
handle = backend.submit(my_eval_func, args=(input_data,))
|
|
69
|
+
result = backend.get_result(handle)
|
|
70
|
+
|
|
71
|
+
Note:
|
|
72
|
+
Requires a running Temporal server and worker processes.
|
|
73
|
+
This backend submits workflows - workers must be running
|
|
74
|
+
to execute them.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
name: str = "temporal"
|
|
78
|
+
|
|
79
|
+
def __init__(self, config: Optional[TemporalConfig] = None):
|
|
80
|
+
"""
|
|
81
|
+
Initialize Temporal backend.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
config: Temporal configuration
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
ImportError: If temporalio is not installed
|
|
88
|
+
"""
|
|
89
|
+
TEMPORAL.require()
|
|
90
|
+
|
|
91
|
+
self.config = config or TemporalConfig()
|
|
92
|
+
self._client: Optional[Any] = None
|
|
93
|
+
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
|
94
|
+
self._loop_thread: Optional[threading.Thread] = None
|
|
95
|
+
self._handles: Dict[str, TaskHandle] = {}
|
|
96
|
+
self._lock = threading.Lock()
|
|
97
|
+
|
|
98
|
+
def _ensure_client(self) -> Any:
|
|
99
|
+
"""Ensure Temporal client is connected."""
|
|
100
|
+
if self._client is None:
|
|
101
|
+
self._setup_event_loop()
|
|
102
|
+
self._client = self._run_async(self._connect())
|
|
103
|
+
return self._client
|
|
104
|
+
|
|
105
|
+
def _setup_event_loop(self) -> None:
|
|
106
|
+
"""Set up dedicated event loop for async operations."""
|
|
107
|
+
if self._loop is None:
|
|
108
|
+
self._loop = asyncio.new_event_loop()
|
|
109
|
+
self._loop_thread = threading.Thread(
|
|
110
|
+
target=self._loop.run_forever,
|
|
111
|
+
daemon=True,
|
|
112
|
+
name="temporal-event-loop",
|
|
113
|
+
)
|
|
114
|
+
self._loop_thread.start()
|
|
115
|
+
|
|
116
|
+
def _run_async(self, coro) -> Any:
|
|
117
|
+
"""Run async coroutine in the dedicated event loop."""
|
|
118
|
+
if self._loop is None:
|
|
119
|
+
self._setup_event_loop()
|
|
120
|
+
future = asyncio.run_coroutine_threadsafe(coro, self._loop)
|
|
121
|
+
return future.result(timeout=60)
|
|
122
|
+
|
|
123
|
+
async def _connect(self) -> Any:
|
|
124
|
+
"""Connect to Temporal server."""
|
|
125
|
+
from temporalio.client import Client
|
|
126
|
+
|
|
127
|
+
client = await Client.connect(
|
|
128
|
+
self.config.host,
|
|
129
|
+
namespace=self.config.namespace,
|
|
130
|
+
)
|
|
131
|
+
logger.info(f"Connected to Temporal at {self.config.host}")
|
|
132
|
+
return client
|
|
133
|
+
|
|
134
|
+
def submit(
|
|
135
|
+
self,
|
|
136
|
+
fn: Callable[..., T],
|
|
137
|
+
args: tuple = (),
|
|
138
|
+
kwargs: Optional[Dict[str, Any]] = None,
|
|
139
|
+
context: Optional[Dict[str, Any]] = None,
|
|
140
|
+
) -> TaskHandle[T]:
|
|
141
|
+
"""
|
|
142
|
+
Submit a task as a Temporal workflow.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
fn: The function to execute
|
|
146
|
+
args: Positional arguments
|
|
147
|
+
kwargs: Keyword arguments
|
|
148
|
+
context: Trace context for propagation
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
TaskHandle to track the workflow
|
|
152
|
+
"""
|
|
153
|
+
kwargs = kwargs or {}
|
|
154
|
+
client = self._ensure_client()
|
|
155
|
+
|
|
156
|
+
workflow_id = f"{self.config.workflow_id_prefix}{uuid.uuid4().hex[:12]}"
|
|
157
|
+
|
|
158
|
+
# Create handle before starting workflow
|
|
159
|
+
handle = TaskHandle(
|
|
160
|
+
task_id=workflow_id,
|
|
161
|
+
backend_name=self.name,
|
|
162
|
+
metadata={
|
|
163
|
+
"namespace": self.config.namespace,
|
|
164
|
+
"task_queue": self.config.task_queue,
|
|
165
|
+
"function": fn.__name__ if hasattr(fn, "__name__") else str(fn),
|
|
166
|
+
"context": context,
|
|
167
|
+
},
|
|
168
|
+
)
|
|
169
|
+
handle._status = TaskStatus.PENDING
|
|
170
|
+
|
|
171
|
+
with self._lock:
|
|
172
|
+
self._handles[workflow_id] = handle
|
|
173
|
+
|
|
174
|
+
# Start workflow asynchronously
|
|
175
|
+
try:
|
|
176
|
+
workflow_handle = self._run_async(
|
|
177
|
+
self._start_workflow(client, workflow_id, fn, args, kwargs)
|
|
178
|
+
)
|
|
179
|
+
handle._status = TaskStatus.RUNNING
|
|
180
|
+
handle.metadata["workflow_run_id"] = workflow_handle.run_id
|
|
181
|
+
except Exception as e:
|
|
182
|
+
handle._status = TaskStatus.FAILED
|
|
183
|
+
handle._error = str(e)
|
|
184
|
+
logger.error(f"Failed to start workflow {workflow_id}: {e}")
|
|
185
|
+
|
|
186
|
+
return handle
|
|
187
|
+
|
|
188
|
+
async def _start_workflow(
|
|
189
|
+
self,
|
|
190
|
+
client: Any,
|
|
191
|
+
workflow_id: str,
|
|
192
|
+
fn: Callable,
|
|
193
|
+
args: tuple,
|
|
194
|
+
kwargs: dict,
|
|
195
|
+
) -> Any:
|
|
196
|
+
"""Start a Temporal workflow with cloudpickle-serialized payload."""
|
|
197
|
+
from temporalio.client import WorkflowHandle
|
|
198
|
+
|
|
199
|
+
from .temporal_worker import serialize_task
|
|
200
|
+
|
|
201
|
+
payload_b64 = serialize_task(fn, args, kwargs)
|
|
202
|
+
|
|
203
|
+
handle: WorkflowHandle = await client.start_workflow(
|
|
204
|
+
"EvalTaskWorkflow",
|
|
205
|
+
payload_b64,
|
|
206
|
+
id=workflow_id,
|
|
207
|
+
task_queue=self.config.task_queue,
|
|
208
|
+
execution_timeout=timedelta(seconds=self.config.execution_timeout_seconds),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return handle
|
|
212
|
+
|
|
213
|
+
def get_result(
|
|
214
|
+
self,
|
|
215
|
+
handle: TaskHandle[T],
|
|
216
|
+
timeout: Optional[float] = None,
|
|
217
|
+
) -> T:
|
|
218
|
+
"""
|
|
219
|
+
Get the result of a workflow.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
handle: The task handle from submit()
|
|
223
|
+
timeout: Maximum seconds to wait
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
The workflow result
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
TimeoutError: If timeout exceeded
|
|
230
|
+
Exception: If workflow failed
|
|
231
|
+
"""
|
|
232
|
+
client = self._ensure_client()
|
|
233
|
+
timeout = timeout or self.config.timeout_seconds
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
result = self._run_async(
|
|
237
|
+
self._get_workflow_result(client, handle.task_id, timeout)
|
|
238
|
+
)
|
|
239
|
+
with self._lock:
|
|
240
|
+
if handle.task_id in self._handles:
|
|
241
|
+
self._handles[handle.task_id]._status = TaskStatus.COMPLETED
|
|
242
|
+
self._handles[handle.task_id]._result = result
|
|
243
|
+
return result
|
|
244
|
+
except asyncio.TimeoutError:
|
|
245
|
+
with self._lock:
|
|
246
|
+
if handle.task_id in self._handles:
|
|
247
|
+
self._handles[handle.task_id]._status = TaskStatus.TIMEOUT
|
|
248
|
+
raise TimeoutError(f"Workflow {handle.task_id} timed out after {timeout}s")
|
|
249
|
+
except Exception as e:
|
|
250
|
+
with self._lock:
|
|
251
|
+
if handle.task_id in self._handles:
|
|
252
|
+
self._handles[handle.task_id]._status = TaskStatus.FAILED
|
|
253
|
+
self._handles[handle.task_id]._error = str(e)
|
|
254
|
+
raise
|
|
255
|
+
|
|
256
|
+
async def _get_workflow_result(
|
|
257
|
+
self, client: Any, workflow_id: str, timeout: float
|
|
258
|
+
) -> Any:
|
|
259
|
+
"""Get workflow result with timeout, deserializing cloudpickle payload."""
|
|
260
|
+
from .temporal_worker import deserialize_result
|
|
261
|
+
|
|
262
|
+
handle = client.get_workflow_handle(workflow_id)
|
|
263
|
+
result_b64 = await asyncio.wait_for(handle.result(), timeout=timeout)
|
|
264
|
+
return deserialize_result(result_b64)
|
|
265
|
+
|
|
266
|
+
def get_status(self, handle: TaskHandle) -> TaskStatus:
|
|
267
|
+
"""
|
|
268
|
+
Get current status of a workflow.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
handle: The task handle
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Current TaskStatus
|
|
275
|
+
"""
|
|
276
|
+
with self._lock:
|
|
277
|
+
if handle.task_id in self._handles:
|
|
278
|
+
return self._handles[handle.task_id]._status
|
|
279
|
+
|
|
280
|
+
# Query Temporal for actual status
|
|
281
|
+
try:
|
|
282
|
+
client = self._ensure_client()
|
|
283
|
+
description = self._run_async(
|
|
284
|
+
self._describe_workflow(client, handle.task_id)
|
|
285
|
+
)
|
|
286
|
+
return self._map_workflow_status(description.status)
|
|
287
|
+
except Exception:
|
|
288
|
+
return TaskStatus.FAILED
|
|
289
|
+
|
|
290
|
+
async def _describe_workflow(self, client: Any, workflow_id: str) -> Any:
|
|
291
|
+
"""Describe a workflow execution."""
|
|
292
|
+
handle = client.get_workflow_handle(workflow_id)
|
|
293
|
+
return await handle.describe()
|
|
294
|
+
|
|
295
|
+
def _map_workflow_status(self, temporal_status: Any) -> TaskStatus:
|
|
296
|
+
"""Map Temporal workflow status to TaskStatus."""
|
|
297
|
+
from temporalio.client import WorkflowExecutionStatus
|
|
298
|
+
|
|
299
|
+
status_map = {
|
|
300
|
+
WorkflowExecutionStatus.RUNNING: TaskStatus.RUNNING,
|
|
301
|
+
WorkflowExecutionStatus.COMPLETED: TaskStatus.COMPLETED,
|
|
302
|
+
WorkflowExecutionStatus.FAILED: TaskStatus.FAILED,
|
|
303
|
+
WorkflowExecutionStatus.CANCELED: TaskStatus.CANCELLED,
|
|
304
|
+
WorkflowExecutionStatus.TERMINATED: TaskStatus.CANCELLED,
|
|
305
|
+
WorkflowExecutionStatus.TIMED_OUT: TaskStatus.TIMEOUT,
|
|
306
|
+
}
|
|
307
|
+
return status_map.get(temporal_status, TaskStatus.PENDING)
|
|
308
|
+
|
|
309
|
+
def cancel(self, handle: TaskHandle) -> bool:
|
|
310
|
+
"""
|
|
311
|
+
Cancel a workflow.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
handle: The task handle
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
True if cancelled, False otherwise
|
|
318
|
+
"""
|
|
319
|
+
try:
|
|
320
|
+
client = self._ensure_client()
|
|
321
|
+
self._run_async(self._cancel_workflow(client, handle.task_id))
|
|
322
|
+
with self._lock:
|
|
323
|
+
if handle.task_id in self._handles:
|
|
324
|
+
self._handles[handle.task_id]._status = TaskStatus.CANCELLED
|
|
325
|
+
return True
|
|
326
|
+
except Exception as e:
|
|
327
|
+
logger.warning(f"Failed to cancel workflow {handle.task_id}: {e}")
|
|
328
|
+
return False
|
|
329
|
+
|
|
330
|
+
async def _cancel_workflow(self, client: Any, workflow_id: str) -> None:
|
|
331
|
+
"""Cancel a workflow execution."""
|
|
332
|
+
handle = client.get_workflow_handle(workflow_id)
|
|
333
|
+
await handle.cancel()
|
|
334
|
+
|
|
335
|
+
def shutdown(self, wait: bool = True) -> None:
|
|
336
|
+
"""
|
|
337
|
+
Shutdown the Temporal backend.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
wait: Whether to wait for pending workflows (not implemented)
|
|
341
|
+
"""
|
|
342
|
+
if self._loop is not None:
|
|
343
|
+
self._loop.call_soon_threadsafe(self._loop.stop)
|
|
344
|
+
if self._loop_thread is not None:
|
|
345
|
+
self._loop_thread.join(timeout=5)
|
|
346
|
+
self._loop = None
|
|
347
|
+
self._loop_thread = None
|
|
348
|
+
|
|
349
|
+
self._client = None
|
|
350
|
+
logger.info("Temporal backend shut down")
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Temporal worker for executing evaluation tasks.
|
|
3
|
+
|
|
4
|
+
This module defines the real Temporal workflow and activity that the
|
|
5
|
+
TemporalBackend submits work to. It uses cloudpickle for serialization,
|
|
6
|
+
matching the pattern used by the Kubernetes backend (_container.py).
|
|
7
|
+
|
|
8
|
+
Run as a standalone worker process::
|
|
9
|
+
|
|
10
|
+
python -m fi.evals.framework.backends.temporal_worker
|
|
11
|
+
|
|
12
|
+
Or with custom settings::
|
|
13
|
+
|
|
14
|
+
TEMPORAL_HOST=temporal.example.com:7233 \\
|
|
15
|
+
TEMPORAL_NAMESPACE=evaluations \\
|
|
16
|
+
TEMPORAL_TASK_QUEUE=eval-tasks \\
|
|
17
|
+
python -m fi.evals.framework.backends.temporal_worker
|
|
18
|
+
|
|
19
|
+
Requires: pip install temporalio cloudpickle
|
|
20
|
+
|
|
21
|
+
Security note:
|
|
22
|
+
cloudpickle is the industry-standard serializer used by Kubeflow Pipelines,
|
|
23
|
+
Ray, Dask, etc. It is only used here in trusted evaluation environments
|
|
24
|
+
(your own code running on your own infrastructure) — never for untrusted input.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import asyncio
|
|
28
|
+
import base64
|
|
29
|
+
import logging
|
|
30
|
+
import os
|
|
31
|
+
from datetime import timedelta
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
import cloudpickle
|
|
35
|
+
from temporalio import activity, workflow
|
|
36
|
+
from temporalio.client import Client
|
|
37
|
+
from temporalio.common import RetryPolicy
|
|
38
|
+
from temporalio.worker import Worker
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def serialize_task(fn, args: tuple = (), kwargs: dict | None = None) -> str:
|
|
44
|
+
"""Serialize a task into a base64 cloudpickle payload."""
|
|
45
|
+
kwargs = kwargs or {}
|
|
46
|
+
payload = cloudpickle.dumps((fn, args, kwargs))
|
|
47
|
+
return base64.b64encode(payload).decode("utf-8")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def deserialize_task(payload_b64: str) -> tuple:
|
|
51
|
+
"""Deserialize a base64 cloudpickle payload into (fn, args, kwargs)."""
|
|
52
|
+
raw = base64.b64decode(payload_b64)
|
|
53
|
+
return cloudpickle.loads(raw)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def serialize_result(result: Any) -> str:
|
|
57
|
+
"""Serialize a result into a base64 cloudpickle payload."""
|
|
58
|
+
return base64.b64encode(cloudpickle.dumps(result)).decode("utf-8")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def deserialize_result(payload_b64: str) -> Any:
|
|
62
|
+
"""Deserialize a base64 cloudpickle result."""
|
|
63
|
+
return cloudpickle.loads(base64.b64decode(payload_b64))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@activity.defn
|
|
67
|
+
async def execute_eval_task(payload_b64: str) -> str:
|
|
68
|
+
"""
|
|
69
|
+
Temporal activity that executes a serialized evaluation task.
|
|
70
|
+
|
|
71
|
+
Receives a cloudpickle-serialized (fn, args, kwargs) tuple as base64,
|
|
72
|
+
executes the function, and returns the cloudpickle-serialized result
|
|
73
|
+
as base64.
|
|
74
|
+
"""
|
|
75
|
+
fn, args, kwargs = deserialize_task(payload_b64)
|
|
76
|
+
logger.info(f"Executing task: {getattr(fn, '__name__', str(fn))}")
|
|
77
|
+
result = fn(*args, **kwargs)
|
|
78
|
+
return serialize_result(result)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@workflow.defn
|
|
82
|
+
class EvalTaskWorkflow:
|
|
83
|
+
"""Workflow that executes a single evaluation task via activity."""
|
|
84
|
+
|
|
85
|
+
@workflow.run
|
|
86
|
+
async def run(self, payload_b64: str) -> str:
|
|
87
|
+
return await workflow.execute_activity(
|
|
88
|
+
execute_eval_task,
|
|
89
|
+
payload_b64,
|
|
90
|
+
start_to_close_timeout=timedelta(minutes=10),
|
|
91
|
+
retry_policy=RetryPolicy(
|
|
92
|
+
maximum_attempts=3,
|
|
93
|
+
initial_interval=timedelta(seconds=1),
|
|
94
|
+
backoff_coefficient=2.0,
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def run_worker(
|
|
100
|
+
host: str = "localhost:7233",
|
|
101
|
+
namespace: str = "default",
|
|
102
|
+
task_queue: str = "eval-tasks",
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Connect to Temporal and run the worker until interrupted."""
|
|
105
|
+
client = await Client.connect(host, namespace=namespace)
|
|
106
|
+
logger.info(f"Connected to Temporal at {host}, namespace={namespace}")
|
|
107
|
+
|
|
108
|
+
worker = Worker(
|
|
109
|
+
client,
|
|
110
|
+
task_queue=task_queue,
|
|
111
|
+
workflows=[EvalTaskWorkflow],
|
|
112
|
+
activities=[execute_eval_task],
|
|
113
|
+
)
|
|
114
|
+
logger.info(f"Worker listening on task queue: {task_queue}")
|
|
115
|
+
await worker.run()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
logging.basicConfig(level=logging.INFO)
|
|
120
|
+
asyncio.run(
|
|
121
|
+
run_worker(
|
|
122
|
+
host=os.environ.get("TEMPORAL_HOST", "localhost:7233"),
|
|
123
|
+
namespace=os.environ.get("TEMPORAL_NAMESPACE", "default"),
|
|
124
|
+
task_queue=os.environ.get("TEMPORAL_TASK_QUEUE", "eval-tasks"),
|
|
125
|
+
)
|
|
126
|
+
)
|