gatewaysdk 0.3.2__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.
- gatewaysdk/__init__.py +540 -0
- gatewaysdk/adapter/__init__.py +15 -0
- gatewaysdk/adapter/base.py +94 -0
- gatewaysdk/adapter/messages.py +270 -0
- gatewaysdk/adapter/triplet.py +1026 -0
- gatewaysdk/algorithm/__init__.py +39 -0
- gatewaysdk/algorithm/apo/__init__.py +5 -0
- gatewaysdk/algorithm/apo/apo.py +898 -0
- gatewaysdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
- gatewaysdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
- gatewaysdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
- gatewaysdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
- gatewaysdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
- gatewaysdk/algorithm/base.py +262 -0
- gatewaysdk/algorithm/decorator.py +264 -0
- gatewaysdk/algorithm/evals/__init__.py +7 -0
- gatewaysdk/algorithm/evals/evals.py +217 -0
- gatewaysdk/algorithm/fast.py +250 -0
- gatewaysdk/algorithm/gepa/__init__.py +61 -0
- gatewaysdk/algorithm/gepa/adapter.py +495 -0
- gatewaysdk/algorithm/gepa/gepa.py +570 -0
- gatewaysdk/algorithm/gepa/lib/__init__.py +18 -0
- gatewaysdk/algorithm/gepa/lib/adapters/README.md +12 -0
- gatewaysdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
- gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
- gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
- gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
- gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +268 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
- gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
- gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
- gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +699 -0
- gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
- gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
- gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
- gatewaysdk/algorithm/gepa/lib/api.py +382 -0
- gatewaysdk/algorithm/gepa/lib/core/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/core/adapter.py +180 -0
- gatewaysdk/algorithm/gepa/lib/core/data_loader.py +74 -0
- gatewaysdk/algorithm/gepa/lib/core/engine.py +379 -0
- gatewaysdk/algorithm/gepa/lib/core/result.py +233 -0
- gatewaysdk/algorithm/gepa/lib/core/state.py +636 -0
- gatewaysdk/algorithm/gepa/lib/examples/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/examples/aime.py +24 -0
- gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
- gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
- gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
- gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
- gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
- gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
- gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
- gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +456 -0
- gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
- gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
- gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +820 -0
- gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
- gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
- gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
- gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
- gatewaysdk/algorithm/gepa/lib/gepa_utils.py +117 -0
- gatewaysdk/algorithm/gepa/lib/logging/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
- gatewaysdk/algorithm/gepa/lib/logging/logger.py +75 -0
- gatewaysdk/algorithm/gepa/lib/logging/utils.py +103 -0
- gatewaysdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/proposer/base.py +31 -0
- gatewaysdk/algorithm/gepa/lib/proposer/merge.py +357 -0
- gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
- gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
- gatewaysdk/algorithm/gepa/lib/py.typed +0 -0
- gatewaysdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
- gatewaysdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
- gatewaysdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
- gatewaysdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
- gatewaysdk/algorithm/gepa/lib/strategies/instruction_proposal.py +126 -0
- gatewaysdk/algorithm/gepa/lib/utils/__init__.py +10 -0
- gatewaysdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
- gatewaysdk/algorithm/gepa/tracing.py +105 -0
- gatewaysdk/algorithm/utils.py +177 -0
- gatewaysdk/algorithm/verl/__init__.py +5 -0
- gatewaysdk/algorithm/verl/interface.py +202 -0
- gatewaysdk/automations.py +111 -0
- gatewaysdk/benchmark_hub/README.md +88 -0
- gatewaysdk/benchmark_hub/__init__.py +119 -0
- gatewaysdk/benchmark_hub/_archive.py +189 -0
- gatewaysdk/benchmark_hub/_tar.py +6 -0
- gatewaysdk/benchmark_hub/client.py +992 -0
- gatewaysdk/benchmark_hub/dispatch_shard.py +34 -0
- gatewaysdk/benchmark_hub/eval_config.py +20 -0
- gatewaysdk/benchmark_hub/evals.py +351 -0
- gatewaysdk/benchmark_hub/harbor_adapter.py +673 -0
- gatewaysdk/benchmark_hub/path_utils.py +81 -0
- gatewaysdk/benchmark_hub/save_utils.py +101 -0
- gatewaysdk/benchmark_hub/verifiers_adapter.py +327 -0
- gatewaysdk/benchmark_hub/versioning.py +72 -0
- gatewaysdk/build.py +515 -0
- gatewaysdk/cli/__init__.py +58 -0
- gatewaysdk/cli/agent_runner.py +132 -0
- gatewaysdk/cli/http_client.py +115 -0
- gatewaysdk/cli/platform.py +4086 -0
- gatewaysdk/cli/prometheus.py +115 -0
- gatewaysdk/cli/release_gate.py +215 -0
- gatewaysdk/cli/store.py +131 -0
- gatewaysdk/cli/vllm.py +29 -0
- gatewaysdk/client.py +406 -0
- gatewaysdk/config.py +348 -0
- gatewaysdk/connectors/__init__.py +25 -0
- gatewaysdk/connectors/client.py +203 -0
- gatewaysdk/connectors/skill.py +25 -0
- gatewaysdk/connectors/template.py +106 -0
- gatewaysdk/context.py +606 -0
- gatewaysdk/emitter/__init__.py +43 -0
- gatewaysdk/emitter/annotation.py +370 -0
- gatewaysdk/emitter/exception.py +54 -0
- gatewaysdk/emitter/message.py +61 -0
- gatewaysdk/emitter/object.py +117 -0
- gatewaysdk/emitter/reward.py +320 -0
- gatewaysdk/env_var.py +156 -0
- gatewaysdk/environment/__init__.py +108 -0
- gatewaysdk/environment/_bundle.py +281 -0
- gatewaysdk/environment/_harbor.py +276 -0
- gatewaysdk/environment/_materialize.py +97 -0
- gatewaysdk/environment/_tar.py +33 -0
- gatewaysdk/environment/_world.py +346 -0
- gatewaysdk/environment/core.py +527 -0
- gatewaysdk/environment/errors.py +58 -0
- gatewaysdk/environment/runtime.py +150 -0
- gatewaysdk/environment/schema/__init__.py +35 -0
- gatewaysdk/environment/schema/__main__.py +225 -0
- gatewaysdk/environment/schema/_slack_fidelity.py +116 -0
- gatewaysdk/environment/schema/_slack_scenario.py +183 -0
- gatewaysdk/environment/schema/api.py +1628 -0
- gatewaysdk/environment/schema/batch.py +755 -0
- gatewaysdk/environment/schema/compiler.py +615 -0
- gatewaysdk/environment/schema/conform.py +428 -0
- gatewaysdk/environment/schema/connector.py +777 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/handlers.py +17 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/parity.json +28 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/provenance.json +233 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/scope.toml +112 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/world.json +1380 -0
- gatewaysdk/environment/schema/connectors/base.json +28 -0
- gatewaysdk/environment/schema/connectors/custom/handlers.py +18 -0
- gatewaysdk/environment/schema/connectors/custom/world.json +123 -0
- gatewaysdk/environment/schema/connectors/github/handlers.py +81 -0
- gatewaysdk/environment/schema/connectors/github/parity.json +37 -0
- gatewaysdk/environment/schema/connectors/github/provenance.json +134 -0
- gatewaysdk/environment/schema/connectors/github/scope.toml +158 -0
- gatewaysdk/environment/schema/connectors/github/world.json +5869 -0
- gatewaysdk/environment/schema/connectors/google-calendar/handlers.py +23 -0
- gatewaysdk/environment/schema/connectors/google-calendar/world.json +209 -0
- gatewaysdk/environment/schema/connectors/google-drive/handlers.py +18 -0
- gatewaysdk/environment/schema/connectors/google-drive/world.json +286 -0
- gatewaysdk/environment/schema/connectors/jamf/handlers.py +34 -0
- gatewaysdk/environment/schema/connectors/jamf/parity.json +49 -0
- gatewaysdk/environment/schema/connectors/jamf/provenance.json +181 -0
- gatewaysdk/environment/schema/connectors/jamf/scope.toml +128 -0
- gatewaysdk/environment/schema/connectors/jamf/world.json +2218 -0
- gatewaysdk/environment/schema/connectors/jira/handlers.py +28 -0
- gatewaysdk/environment/schema/connectors/jira/world.json +389 -0
- gatewaysdk/environment/schema/connectors/kandji/handlers.py +38 -0
- gatewaysdk/environment/schema/connectors/kandji/parity.json +29 -0
- gatewaysdk/environment/schema/connectors/kandji/provenance.json +152 -0
- gatewaysdk/environment/schema/connectors/kandji/scope.toml +120 -0
- gatewaysdk/environment/schema/connectors/kandji/world.json +765 -0
- gatewaysdk/environment/schema/connectors/linear/handlers.py +20 -0
- gatewaysdk/environment/schema/connectors/linear/world.json +363 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/fidelity.json +45 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/handlers.py +1 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/provenance.json +851 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/scope.md +231 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/teams-types.json +1710 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/world.json +858 -0
- gatewaysdk/environment/schema/connectors/netsuite/handlers.py +32 -0
- gatewaysdk/environment/schema/connectors/netsuite/parity.json +50 -0
- gatewaysdk/environment/schema/connectors/netsuite/provenance.json +137 -0
- gatewaysdk/environment/schema/connectors/netsuite/scope.toml +78 -0
- gatewaysdk/environment/schema/connectors/netsuite/world.json +16243 -0
- gatewaysdk/environment/schema/connectors/salesforce/handlers.py +806 -0
- gatewaysdk/environment/schema/connectors/salesforce/parity.json +46 -0
- gatewaysdk/environment/schema/connectors/salesforce/provenance.json +143 -0
- gatewaysdk/environment/schema/connectors/salesforce/scope.toml +174 -0
- gatewaysdk/environment/schema/connectors/salesforce/world.json +4695 -0
- gatewaysdk/environment/schema/connectors/slack/MODEL.md +236 -0
- gatewaysdk/environment/schema/connectors/slack/capabilities.json +1039 -0
- gatewaysdk/environment/schema/connectors/slack/handlers.py +762 -0
- gatewaysdk/environment/schema/connectors/slack/parity.json +65 -0
- gatewaysdk/environment/schema/connectors/slack/provenance.json +294 -0
- gatewaysdk/environment/schema/connectors/slack/scope.toml +44 -0
- gatewaysdk/environment/schema/connectors/slack/world.json +4993 -0
- gatewaysdk/environment/schema/connectors/workday/handlers.py +75 -0
- gatewaysdk/environment/schema/connectors/workday/parity.json +36 -0
- gatewaysdk/environment/schema/connectors/workday/provenance.json +179 -0
- gatewaysdk/environment/schema/connectors/workday/scope.toml +180 -0
- gatewaysdk/environment/schema/connectors/workday/world.json +806 -0
- gatewaysdk/environment/schema/data.py +593 -0
- gatewaysdk/environment/schema/extract.py +263 -0
- gatewaysdk/environment/schema/host.py +2437 -0
- gatewaysdk/environment/schema/host_surfaces.py +275 -0
- gatewaysdk/environment/schema/platform.py +1272 -0
- gatewaysdk/environment/schema/scaffold.py +250 -0
- gatewaysdk/environment/schema/skill.py +118 -0
- gatewaysdk/environment/schema/skills/agents/connector-template-author.md +202 -0
- gatewaysdk/environment/schema/skills/connector-schema-authoring/CONNECTORS.md +331 -0
- gatewaysdk/environment/schema/skills/connector-schema-authoring/CONVENTIONS.md +100 -0
- gatewaysdk/environment/schema/skills/connector-schema-authoring/SKILL.md +313 -0
- gatewaysdk/environment/schema/skills/world-data-ingestion/ROWS.md +223 -0
- gatewaysdk/environment/schema/skills/world-data-ingestion/SKILL.md +293 -0
- gatewaysdk/environment/schema/skills/worlds-getting-started/SKILL.md +390 -0
- gatewaysdk/environment/schema/slack.py +757 -0
- gatewaysdk/environment/schema/snapshot.py +479 -0
- gatewaysdk/environment/schema/store.py +904 -0
- gatewaysdk/environment/schema/tasks.py +525 -0
- gatewaysdk/environment/schema/tools_world.py +322 -0
- gatewaysdk/environment/schema/validation.py +577 -0
- gatewaysdk/environment/schema/worker.py +78 -0
- gatewaysdk/environment/schema/workers.py +500 -0
- gatewaysdk/environment/schema/world_tests.py +336 -0
- gatewaysdk/environments/__init__.py +24 -0
- gatewaysdk/environments/client.py +486 -0
- gatewaysdk/environments/types.py +318 -0
- gatewaysdk/execution/__init__.py +15 -0
- gatewaysdk/execution/base.py +64 -0
- gatewaysdk/execution/client_server.py +443 -0
- gatewaysdk/execution/events.py +69 -0
- gatewaysdk/execution/inter_process.py +16 -0
- gatewaysdk/execution/shared_memory.py +282 -0
- gatewaysdk/experiments/__init__.py +90 -0
- gatewaysdk/experiments/assignment.py +177 -0
- gatewaysdk/experiments/client.py +877 -0
- gatewaysdk/experiments/exposure.py +222 -0
- gatewaysdk/experiments/types.py +81 -0
- gatewaysdk/importers/__init__.py +49 -0
- gatewaysdk/importers/_normalize.py +91 -0
- gatewaysdk/importers/builder.py +173 -0
- gatewaysdk/importers/client.py +95 -0
- gatewaysdk/importers/langsmith.py +196 -0
- gatewaysdk/importers/recipes.py +249 -0
- gatewaysdk/instrumentation/__init__.py +300 -0
- gatewaysdk/instrumentation/agentops.py +314 -0
- gatewaysdk/instrumentation/agentops_langchain.py +45 -0
- gatewaysdk/instrumentation/base.py +119 -0
- gatewaysdk/instrumentation/litellm.py +83 -0
- gatewaysdk/instrumentation/registry.py +273 -0
- gatewaysdk/instrumentation/vllm.py +81 -0
- gatewaysdk/instrumentation/weave.py +500 -0
- gatewaysdk/integrations/__init__.py +15 -0
- gatewaysdk/integrations/gateway/__init__.py +11 -0
- gatewaysdk/integrations/gateway/client.py +171 -0
- gatewaysdk/integrations/tool_access.py +549 -0
- gatewaysdk/litagent/__init__.py +11 -0
- gatewaysdk/litagent/decorator.py +536 -0
- gatewaysdk/litagent/litagent.py +252 -0
- gatewaysdk/llm_proxy.py +1742 -0
- gatewaysdk/logging.py +370 -0
- gatewaysdk/memory.py +278 -0
- gatewaysdk/personas.py +186 -0
- gatewaysdk/platform/__init__.py +17 -0
- gatewaysdk/platform/builder.py +221 -0
- gatewaysdk/platform/compatibility.py +67 -0
- gatewaysdk/platform/manifest.py +185 -0
- gatewaysdk/platform/orchestrator.py +901 -0
- gatewaysdk/platform/registry.py +122 -0
- gatewaysdk/platform/worker.py +864 -0
- gatewaysdk/replay/__init__.py +1032 -0
- gatewaysdk/replay/pytest.py +56 -0
- gatewaysdk/reward.py +7 -0
- gatewaysdk/run.py +1781 -0
- gatewaysdk/runner/__init__.py +11 -0
- gatewaysdk/runner/agent.py +878 -0
- gatewaysdk/runner/base.py +182 -0
- gatewaysdk/runner/legacy.py +309 -0
- gatewaysdk/security.py +700 -0
- gatewaysdk/semconv.py +170 -0
- gatewaysdk/server.py +399 -0
- gatewaysdk/sessions.py +282 -0
- gatewaysdk/store/__init__.py +45 -0
- gatewaysdk/store/base.py +908 -0
- gatewaysdk/store/client_server.py +2093 -0
- gatewaysdk/store/collection/__init__.py +30 -0
- gatewaysdk/store/collection/base.py +587 -0
- gatewaysdk/store/collection/memory.py +970 -0
- gatewaysdk/store/collection/mongo.py +1412 -0
- gatewaysdk/store/collection_based.py +1823 -0
- gatewaysdk/store/gateway.py +983 -0
- gatewaysdk/store/gateway_listener.py +465 -0
- gatewaysdk/store/listener.py +58 -0
- gatewaysdk/store/memory.py +396 -0
- gatewaysdk/store/mongo.py +165 -0
- gatewaysdk/store/redis_stream.py +517 -0
- gatewaysdk/store/sqlite.py +3 -0
- gatewaysdk/store/threading.py +370 -0
- gatewaysdk/store/utils.py +142 -0
- gatewaysdk/tracer/__init__.py +14 -0
- gatewaysdk/tracer/base.py +286 -0
- gatewaysdk/tracer/dummy.py +106 -0
- gatewaysdk/tracer/otel.py +559 -0
- gatewaysdk/tracing/__init__.py +110 -0
- gatewaysdk/tracing/api.py +808 -0
- gatewaysdk/tracing/attributes.py +9 -0
- gatewaysdk/tracing/context.py +272 -0
- gatewaysdk/tracing/exporters/__init__.py +10 -0
- gatewaysdk/tracing/exporters/gateway.py +228 -0
- gatewaysdk/tracing/identity.py +288 -0
- gatewaysdk/tracing/init.py +620 -0
- gatewaysdk/tracing/instrumentors/__init__.py +15 -0
- gatewaysdk/tracing/instrumentors/claude_agent_sdk.py +766 -0
- gatewaysdk/tracing/instrumentors/instrumentation_principles.md +294 -0
- gatewaysdk/tracing/instrumentors/registry.py +352 -0
- gatewaysdk/tracing/mapping.py +729 -0
- gatewaysdk/tracing/processors.py +393 -0
- gatewaysdk/tracing/push.py +617 -0
- gatewaysdk/tracing/push_models.py +247 -0
- gatewaysdk/tracing/semconv.py +294 -0
- gatewaysdk/tracing/span_builder.py +356 -0
- gatewaysdk/trainer/__init__.py +6 -0
- gatewaysdk/trainer/init_utils.py +263 -0
- gatewaysdk/trainer/legacy.py +359 -0
- gatewaysdk/trainer/registry.py +12 -0
- gatewaysdk/trainer/trainer.py +638 -0
- gatewaysdk/types/__init__.py +63 -0
- gatewaysdk/types/core.py +556 -0
- gatewaysdk/types/resources.py +204 -0
- gatewaysdk/types/tracer.py +515 -0
- gatewaysdk/types/tracing.py +162 -0
- gatewaysdk/users.py +251 -0
- gatewaysdk/utils/__init__.py +1 -0
- gatewaysdk/utils/id.py +18 -0
- gatewaysdk/utils/metrics.py +1025 -0
- gatewaysdk/utils/otel.py +550 -0
- gatewaysdk/utils/otlp.py +556 -0
- gatewaysdk/utils/redact.py +22 -0
- gatewaysdk/utils/server_launcher.py +1045 -0
- gatewaysdk/utils/system_snapshot.py +90 -0
- gatewaysdk/verl/__init__.py +8 -0
- gatewaysdk/verl/__main__.py +6 -0
- gatewaysdk/verl/async_server.py +46 -0
- gatewaysdk/verl/config.yaml +27 -0
- gatewaysdk/verl/daemon.py +1154 -0
- gatewaysdk/verl/dataset.py +44 -0
- gatewaysdk/verl/entrypoint.py +248 -0
- gatewaysdk/verl/trainer.py +549 -0
- gatewaysdk/world_browser.py +748 -0
- gatewaysdk/world_data.py +453 -0
- gatewaysdk/world_sessions.py +978 -0
- gatewaysdk/world_tasks.py +285 -0
- gatewaysdk/world_tools.py +115 -0
- gatewaysdk/worlds.py +639 -0
- gatewaysdk-0.3.2.dist-info/METADATA +236 -0
- gatewaysdk-0.3.2.dist-info/RECORD +376 -0
- gatewaysdk-0.3.2.dist-info/WHEEL +4 -0
- gatewaysdk-0.3.2.dist-info/entry_points.txt +5 -0
- gatewaysdk-0.3.2.dist-info/licenses/LICENSE +19 -0
|
@@ -0,0 +1,983 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""GatewayTracker and GatewayLightningStore - Gateway tracking integration.
|
|
4
|
+
|
|
5
|
+
This module provides Gateway tracking integration:
|
|
6
|
+
|
|
7
|
+
1. **GatewayLightningStore** (recommended): Creates a real GatewayRun for experiment
|
|
8
|
+
tracking with automatic session management, heartbeats, and crash detection.
|
|
9
|
+
```python
|
|
10
|
+
from gatewaysdk.store import GatewayLightningStore
|
|
11
|
+
|
|
12
|
+
store = GatewayLightningStore(
|
|
13
|
+
api_key="pk-lf-abc123",
|
|
14
|
+
secret_key="sk-lf-xyz789",
|
|
15
|
+
gateway_url="https://app.gateway.dev",
|
|
16
|
+
project_id="proj-123",
|
|
17
|
+
run_name="gepa-experiment-v1",
|
|
18
|
+
config={"lr": 0.01, "epochs": 100},
|
|
19
|
+
)
|
|
20
|
+
# Store creates a real GatewayRun record in the database
|
|
21
|
+
# Rollouts create real TraceSession records
|
|
22
|
+
# All traces are linked via run_id
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
2. **GatewayTracker** (DEPRECATED): Legacy event-based tracking with virtual job IDs.
|
|
26
|
+
Use GatewayLightningStore instead, which uses real GatewayRun records.
|
|
27
|
+
|
|
28
|
+
Features:
|
|
29
|
+
- Real GatewayRun records with lifecycle management (heartbeats, crash detection)
|
|
30
|
+
- Session creation for rollouts (TraceSession records linked to runs)
|
|
31
|
+
- OTLP trace export support
|
|
32
|
+
- Automatic reward-to-score conversion
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import atexit
|
|
38
|
+
import base64
|
|
39
|
+
import logging
|
|
40
|
+
import os
|
|
41
|
+
import queue
|
|
42
|
+
import threading
|
|
43
|
+
import time
|
|
44
|
+
import uuid
|
|
45
|
+
import warnings
|
|
46
|
+
from dataclasses import dataclass
|
|
47
|
+
from datetime import datetime, timezone
|
|
48
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
49
|
+
|
|
50
|
+
from .gateway_listener import GatewayRunListener
|
|
51
|
+
from .memory import InMemoryLightningStore # noqa: F401
|
|
52
|
+
|
|
53
|
+
if TYPE_CHECKING:
|
|
54
|
+
from gatewaysdk.run import Run
|
|
55
|
+
from .client_server import LightningStoreClient # noqa: F401
|
|
56
|
+
from .redis_stream import RedisStreamLightningStore # noqa: F401
|
|
57
|
+
|
|
58
|
+
import httpx
|
|
59
|
+
|
|
60
|
+
from gatewaysdk.emitter.reward import get_rewards_from_span, is_reward_span
|
|
61
|
+
from gatewaysdk.types import (
|
|
62
|
+
Attempt,
|
|
63
|
+
ResourcesUpdate,
|
|
64
|
+
Rollout,
|
|
65
|
+
Span,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
from .listener import StorageListener
|
|
69
|
+
|
|
70
|
+
logger = logging.getLogger(__name__)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class GatewayEvent:
|
|
75
|
+
"""An event to be sent to the Gateway API."""
|
|
76
|
+
|
|
77
|
+
id: str
|
|
78
|
+
type: str
|
|
79
|
+
timestamp: str # ISO 8601 datetime string
|
|
80
|
+
data: Dict[str, Any]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class GatewayTracker:
|
|
84
|
+
"""DEPRECATED: A StorageListener that streams storage events to Gateway.
|
|
85
|
+
|
|
86
|
+
.. deprecated::
|
|
87
|
+
GatewayTracker is deprecated. Use GatewayLightningStore instead, which
|
|
88
|
+
creates real GatewayRun records with proper lifecycle management, heartbeats,
|
|
89
|
+
and crash detection.
|
|
90
|
+
|
|
91
|
+
This tracker implements the StorageListener protocol and can be attached
|
|
92
|
+
to any LightningStore to enable Gateway tracking. However, it uses virtual
|
|
93
|
+
job IDs that are not persisted to the database.
|
|
94
|
+
|
|
95
|
+
For new code, use GatewayLightningStore which:
|
|
96
|
+
- Creates real GatewayRun records in PostgreSQL
|
|
97
|
+
- Has automatic heartbeats and crash detection
|
|
98
|
+
- Creates real TraceSession records for rollouts
|
|
99
|
+
- Links all traces via run_id
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
api_key: Gateway public API key for authentication.
|
|
103
|
+
secret_key: Gateway secret key for authentication.
|
|
104
|
+
gateway_url: Gateway server URL (e.g., "http://localhost:3000").
|
|
105
|
+
project_id: Project ID to associate events with.
|
|
106
|
+
flush_interval: Seconds between automatic flushes (default: 1.0).
|
|
107
|
+
max_buffer_size: Maximum events before forcing a flush (default: 1000).
|
|
108
|
+
request_timeout: HTTP request timeout in seconds (default: 10.0).
|
|
109
|
+
max_retries: Maximum retry attempts for failed requests (default: 3).
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
*,
|
|
115
|
+
api_key: str,
|
|
116
|
+
secret_key: str,
|
|
117
|
+
gateway_url: str,
|
|
118
|
+
project_id: str,
|
|
119
|
+
flush_interval: float = 1.0,
|
|
120
|
+
max_buffer_size: int = 1000,
|
|
121
|
+
request_timeout: float = 10.0,
|
|
122
|
+
max_retries: int = 3,
|
|
123
|
+
) -> None:
|
|
124
|
+
warnings.warn(
|
|
125
|
+
"GatewayTracker is deprecated. Use GatewayLightningStore instead, which "
|
|
126
|
+
"creates real GatewayRun records with proper lifecycle management.",
|
|
127
|
+
DeprecationWarning,
|
|
128
|
+
stacklevel=2,
|
|
129
|
+
)
|
|
130
|
+
# Store configuration
|
|
131
|
+
self._api_key = api_key
|
|
132
|
+
self._secret_key = secret_key
|
|
133
|
+
self._gateway_url = gateway_url.rstrip("/")
|
|
134
|
+
self._project_id = project_id
|
|
135
|
+
self._flush_interval = flush_interval
|
|
136
|
+
self._max_buffer_size = max_buffer_size
|
|
137
|
+
self._request_timeout = request_timeout
|
|
138
|
+
self._max_retries = max_retries
|
|
139
|
+
|
|
140
|
+
# Generate a unique job ID for this tracker instance
|
|
141
|
+
self._job_id = f"job-{uuid.uuid4().hex[:12]}"
|
|
142
|
+
|
|
143
|
+
# Track if job has been completed to prevent double-complete
|
|
144
|
+
self._completed = False
|
|
145
|
+
|
|
146
|
+
# Event buffer (thread-safe queue)
|
|
147
|
+
self._event_buffer: queue.Queue[GatewayEvent] = queue.Queue()
|
|
148
|
+
|
|
149
|
+
# Background sender thread control
|
|
150
|
+
self._stop_event = threading.Event()
|
|
151
|
+
self._sender_thread: Optional[threading.Thread] = None
|
|
152
|
+
|
|
153
|
+
# Start the background sender thread
|
|
154
|
+
self._start_sender_thread()
|
|
155
|
+
|
|
156
|
+
# Emit job.created event immediately
|
|
157
|
+
self._emit_job_created()
|
|
158
|
+
|
|
159
|
+
# Register cleanup on exit
|
|
160
|
+
atexit.register(self._cleanup)
|
|
161
|
+
|
|
162
|
+
logger.info(
|
|
163
|
+
f"GatewayTracker initialized - streaming to {self._gateway_url} "
|
|
164
|
+
f"(project={self._project_id}, job={self._job_id})"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# ─────────────────────────────────────────────────────────────
|
|
168
|
+
# StorageListener Protocol Implementation
|
|
169
|
+
# ─────────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def capabilities(self) -> Dict[str, bool]:
|
|
173
|
+
"""Return the capabilities of the listener."""
|
|
174
|
+
return {
|
|
175
|
+
"otlp_traces": True, # Enable OTLP trace export to Gateway
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def job_id(self) -> str:
|
|
180
|
+
"""Return the job ID for this tracker instance."""
|
|
181
|
+
return self._job_id
|
|
182
|
+
|
|
183
|
+
def otlp_traces_endpoint(self) -> Optional[str]:
|
|
184
|
+
"""Return the OTLP/HTTP traces endpoint."""
|
|
185
|
+
endpoint = f"{self._gateway_url}/api/public/otel/v1/traces"
|
|
186
|
+
logger.debug(f"OTLP traces endpoint: {endpoint}")
|
|
187
|
+
return endpoint
|
|
188
|
+
|
|
189
|
+
def get_otlp_headers(self) -> Dict[str, str]:
|
|
190
|
+
"""Return the authentication headers for OTLP export.
|
|
191
|
+
|
|
192
|
+
Gateway's OTLP endpoint uses Basic Auth with format: public_key:secret_key
|
|
193
|
+
"""
|
|
194
|
+
credentials = f"{self._api_key}:{self._secret_key}"
|
|
195
|
+
encoded = base64.b64encode(credentials.encode()).decode()
|
|
196
|
+
return {
|
|
197
|
+
"Authorization": f"Basic {encoded}",
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async def on_job_created(self, job_id: str, project_id: Optional[str] = None) -> None:
|
|
201
|
+
"""Called when a job is created. (Usually handled internally)"""
|
|
202
|
+
pass # We emit job.created in __init__
|
|
203
|
+
|
|
204
|
+
async def on_rollout_created(self, rollout: Rollout) -> None:
|
|
205
|
+
"""Called when a rollout is created."""
|
|
206
|
+
self._emit(
|
|
207
|
+
"rollout.created",
|
|
208
|
+
{
|
|
209
|
+
"id": rollout.rollout_id,
|
|
210
|
+
"input": rollout.input,
|
|
211
|
+
"status": rollout.status,
|
|
212
|
+
"resource_id": rollout.resources_id,
|
|
213
|
+
"mode": rollout.mode,
|
|
214
|
+
"start_time": rollout.start_time,
|
|
215
|
+
"config": {
|
|
216
|
+
"max_attempts": rollout.config.max_attempts,
|
|
217
|
+
"retry_condition": rollout.config.retry_condition,
|
|
218
|
+
"timeout_seconds": rollout.config.timeout_seconds,
|
|
219
|
+
"unresponsive_seconds": rollout.config.unresponsive_seconds,
|
|
220
|
+
},
|
|
221
|
+
"metadata": rollout.metadata,
|
|
222
|
+
},
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
async def on_rollout_updated(self, rollout: Rollout) -> None:
|
|
226
|
+
"""Called when a rollout is updated."""
|
|
227
|
+
data: Dict[str, Any] = {
|
|
228
|
+
"id": rollout.rollout_id,
|
|
229
|
+
"status": rollout.status,
|
|
230
|
+
}
|
|
231
|
+
if rollout.end_time is not None:
|
|
232
|
+
data["end_time"] = rollout.end_time
|
|
233
|
+
self._emit("rollout.status_changed", data)
|
|
234
|
+
|
|
235
|
+
async def on_attempt_created(self, attempt: Attempt) -> None:
|
|
236
|
+
"""Called when an attempt is created."""
|
|
237
|
+
self._emit(
|
|
238
|
+
"attempt.created",
|
|
239
|
+
{
|
|
240
|
+
"id": attempt.attempt_id,
|
|
241
|
+
"rollout_id": attempt.rollout_id,
|
|
242
|
+
"sequence_id": attempt.sequence_id,
|
|
243
|
+
"status": attempt.status,
|
|
244
|
+
"start_time": attempt.start_time,
|
|
245
|
+
"worker_id": attempt.worker_id,
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
async def on_attempt_updated(self, attempt: Attempt, rollout_id: str) -> None:
|
|
250
|
+
"""Called when an attempt is updated."""
|
|
251
|
+
data: Dict[str, Any] = {
|
|
252
|
+
"id": attempt.attempt_id,
|
|
253
|
+
"rollout_id": rollout_id,
|
|
254
|
+
"status": attempt.status,
|
|
255
|
+
}
|
|
256
|
+
if attempt.end_time is not None:
|
|
257
|
+
data["end_time"] = attempt.end_time
|
|
258
|
+
self._emit("attempt.status_changed", data)
|
|
259
|
+
|
|
260
|
+
async def on_span_created(self, span: Span) -> None:
|
|
261
|
+
"""Called when a span is added.
|
|
262
|
+
|
|
263
|
+
If the span is a reward span, also sends the reward as an Gateway score.
|
|
264
|
+
"""
|
|
265
|
+
# Emit span event to the event buffer
|
|
266
|
+
self._emit(
|
|
267
|
+
"span.emitted",
|
|
268
|
+
{
|
|
269
|
+
"trace_id": span.trace_id,
|
|
270
|
+
"span_id": span.span_id,
|
|
271
|
+
"parent_id": span.parent_id,
|
|
272
|
+
"attempt_id": span.attempt_id,
|
|
273
|
+
"rollout_id": span.rollout_id,
|
|
274
|
+
"sequence_id": span.sequence_id,
|
|
275
|
+
"name": span.name,
|
|
276
|
+
"status": {
|
|
277
|
+
"status_code": span.status.status_code,
|
|
278
|
+
"description": span.status.description,
|
|
279
|
+
},
|
|
280
|
+
"attributes": span.attributes,
|
|
281
|
+
"start_time": span.start_time,
|
|
282
|
+
"end_time": span.end_time,
|
|
283
|
+
},
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Check if this is a reward span and send as Gateway score
|
|
287
|
+
if is_reward_span(span):
|
|
288
|
+
rewards = get_rewards_from_span(span)
|
|
289
|
+
for reward in rewards:
|
|
290
|
+
self._send_score(
|
|
291
|
+
name=reward.name,
|
|
292
|
+
value=reward.value,
|
|
293
|
+
trace_id=span.trace_id,
|
|
294
|
+
observation_id=span.span_id,
|
|
295
|
+
rollout_id=span.rollout_id,
|
|
296
|
+
attempt_id=span.attempt_id,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
async def on_resource_registered(self, resource: ResourcesUpdate) -> None:
|
|
300
|
+
"""Called when a resource is registered/updated.
|
|
301
|
+
|
|
302
|
+
Sends FULL resource content for complete experiment tracking.
|
|
303
|
+
"""
|
|
304
|
+
# Serialize resources to JSON-compatible dicts
|
|
305
|
+
# Resources are Pydantic models (PromptTemplate, LLM, etc.) which need model_dump()
|
|
306
|
+
serialized_resources: Dict[str, Any] = {}
|
|
307
|
+
for name, res in resource.resources.items():
|
|
308
|
+
if hasattr(res, "model_dump"):
|
|
309
|
+
serialized_resources[name] = res.model_dump()
|
|
310
|
+
elif hasattr(res, "dict"):
|
|
311
|
+
# Fallback for older Pydantic v1 models
|
|
312
|
+
serialized_resources[name] = res.dict()
|
|
313
|
+
else:
|
|
314
|
+
# Already a dict or primitive
|
|
315
|
+
serialized_resources[name] = res
|
|
316
|
+
|
|
317
|
+
self._emit(
|
|
318
|
+
"resource.registered",
|
|
319
|
+
{
|
|
320
|
+
"id": resource.resources_id,
|
|
321
|
+
"version": resource.version,
|
|
322
|
+
"create_time": resource.create_time,
|
|
323
|
+
"update_time": resource.update_time,
|
|
324
|
+
"resources": serialized_resources,
|
|
325
|
+
},
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# ─────────────────────────────────────────────────────────────
|
|
329
|
+
# Event Emission Helpers
|
|
330
|
+
# ─────────────────────────────────────────────────────────────
|
|
331
|
+
|
|
332
|
+
def _emit(self, event_type: str, data: Dict[str, Any]) -> None:
|
|
333
|
+
"""Add an event to the buffer for later sending."""
|
|
334
|
+
# Format: 2026-01-17T19:51:40.123Z (Zod datetime expects 'Z' suffix for UTC)
|
|
335
|
+
now = datetime.now(timezone.utc)
|
|
336
|
+
timestamp = now.strftime("%Y-%m-%dT%H:%M:%S.") + f"{now.microsecond // 1000:03d}Z"
|
|
337
|
+
event = GatewayEvent(
|
|
338
|
+
id=f"evt-{uuid.uuid4().hex[:8]}",
|
|
339
|
+
type=event_type,
|
|
340
|
+
timestamp=timestamp,
|
|
341
|
+
data=data,
|
|
342
|
+
)
|
|
343
|
+
try:
|
|
344
|
+
self._event_buffer.put_nowait(event)
|
|
345
|
+
except queue.Full:
|
|
346
|
+
logger.warning(f"Event buffer full, dropping event: {event_type}")
|
|
347
|
+
return
|
|
348
|
+
|
|
349
|
+
# Force flush if buffer is at capacity
|
|
350
|
+
if self._event_buffer.qsize() >= self._max_buffer_size:
|
|
351
|
+
self._trigger_flush()
|
|
352
|
+
|
|
353
|
+
def _emit_job_created(self) -> None:
|
|
354
|
+
"""Emit the job.created event when the tracker is initialized."""
|
|
355
|
+
self._emit(
|
|
356
|
+
"job.created",
|
|
357
|
+
{
|
|
358
|
+
"project_id": self._project_id,
|
|
359
|
+
"type": "agent",
|
|
360
|
+
},
|
|
361
|
+
)
|
|
362
|
+
# Immediately flush to ensure job is created before other events
|
|
363
|
+
self._flush_events()
|
|
364
|
+
|
|
365
|
+
def _send_score(
|
|
366
|
+
self,
|
|
367
|
+
name: str,
|
|
368
|
+
value: float,
|
|
369
|
+
trace_id: str,
|
|
370
|
+
observation_id: Optional[str] = None,
|
|
371
|
+
rollout_id: Optional[str] = None,
|
|
372
|
+
attempt_id: Optional[str] = None,
|
|
373
|
+
) -> None:
|
|
374
|
+
"""Send a score to Gateway's /api/public/scores endpoint.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
name: Score name (e.g., "primary", "task_completion").
|
|
378
|
+
value: Numeric score value.
|
|
379
|
+
trace_id: OTEL trace ID to link the score to.
|
|
380
|
+
observation_id: Optional span ID to link to specific observation.
|
|
381
|
+
rollout_id: Optional rollout ID for metadata.
|
|
382
|
+
attempt_id: Optional attempt ID for metadata.
|
|
383
|
+
"""
|
|
384
|
+
score_payload: Dict[str, Any] = {
|
|
385
|
+
"name": name,
|
|
386
|
+
"value": value,
|
|
387
|
+
"dataType": "NUMERIC",
|
|
388
|
+
"traceId": trace_id,
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if observation_id:
|
|
392
|
+
score_payload["observationId"] = observation_id
|
|
393
|
+
|
|
394
|
+
# Add rollout/attempt context as metadata
|
|
395
|
+
metadata: Dict[str, Any] = {
|
|
396
|
+
"source": "agent_lightning",
|
|
397
|
+
"job_id": self._job_id,
|
|
398
|
+
}
|
|
399
|
+
if rollout_id:
|
|
400
|
+
metadata["rollout_id"] = rollout_id
|
|
401
|
+
if attempt_id:
|
|
402
|
+
metadata["attempt_id"] = attempt_id
|
|
403
|
+
score_payload["metadata"] = metadata
|
|
404
|
+
|
|
405
|
+
# Create Basic auth header
|
|
406
|
+
auth_string = f"{self._api_key}:{self._secret_key}"
|
|
407
|
+
auth_bytes = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
|
|
408
|
+
|
|
409
|
+
# Send score in a non-blocking way (fire and forget with retries)
|
|
410
|
+
def send_score_async() -> None:
|
|
411
|
+
for attempt in range(self._max_retries):
|
|
412
|
+
try:
|
|
413
|
+
with httpx.Client(timeout=self._request_timeout) as client:
|
|
414
|
+
response = client.post(
|
|
415
|
+
f"{self._gateway_url}/api/public/scores",
|
|
416
|
+
headers={
|
|
417
|
+
"Authorization": f"Basic {auth_bytes}",
|
|
418
|
+
"Content-Type": "application/json",
|
|
419
|
+
},
|
|
420
|
+
json=score_payload,
|
|
421
|
+
)
|
|
422
|
+
response.raise_for_status()
|
|
423
|
+
logger.debug(f"Successfully sent score '{name}={value}' to Gateway")
|
|
424
|
+
return
|
|
425
|
+
except httpx.HTTPStatusError as e:
|
|
426
|
+
if e.response.status_code == 401:
|
|
427
|
+
logger.error("Unauthorized (401) sending score to Gateway. Check API credentials.")
|
|
428
|
+
return
|
|
429
|
+
else:
|
|
430
|
+
try:
|
|
431
|
+
error_body = e.response.json()
|
|
432
|
+
except Exception:
|
|
433
|
+
error_body = e.response.text
|
|
434
|
+
logger.warning(
|
|
435
|
+
f"HTTP error sending score (attempt {attempt + 1}/{self._max_retries}): "
|
|
436
|
+
f"{e.response.status_code} - {error_body}"
|
|
437
|
+
)
|
|
438
|
+
except httpx.HTTPError as e:
|
|
439
|
+
logger.warning(f"Failed to send score (attempt {attempt + 1}/{self._max_retries}): {e}")
|
|
440
|
+
|
|
441
|
+
# Exponential backoff
|
|
442
|
+
if attempt < self._max_retries - 1:
|
|
443
|
+
backoff_time = 2**attempt
|
|
444
|
+
time.sleep(backoff_time)
|
|
445
|
+
|
|
446
|
+
logger.error(f"Failed to send score '{name}' after {self._max_retries} retries")
|
|
447
|
+
|
|
448
|
+
# Run in a thread to avoid blocking
|
|
449
|
+
threading.Thread(target=send_score_async, daemon=True, name="gateway-score-sender").start()
|
|
450
|
+
|
|
451
|
+
# ─────────────────────────────────────────────────────────────
|
|
452
|
+
# Background Sender Thread
|
|
453
|
+
# ─────────────────────────────────────────────────────────────
|
|
454
|
+
|
|
455
|
+
def _start_sender_thread(self) -> None:
|
|
456
|
+
"""Start the background sender thread."""
|
|
457
|
+
|
|
458
|
+
def sender_loop() -> None:
|
|
459
|
+
while not self._stop_event.is_set():
|
|
460
|
+
# Wait for the flush interval or until stopped
|
|
461
|
+
self._stop_event.wait(timeout=self._flush_interval)
|
|
462
|
+
if not self._stop_event.is_set():
|
|
463
|
+
self._flush_events()
|
|
464
|
+
|
|
465
|
+
self._sender_thread = threading.Thread(
|
|
466
|
+
target=sender_loop,
|
|
467
|
+
daemon=True,
|
|
468
|
+
name="gateway-sender",
|
|
469
|
+
)
|
|
470
|
+
self._sender_thread.start()
|
|
471
|
+
|
|
472
|
+
def _trigger_flush(self) -> None:
|
|
473
|
+
"""Trigger an immediate flush by interrupting the wait."""
|
|
474
|
+
# The flush will happen on the next iteration since we're using a timeout
|
|
475
|
+
pass
|
|
476
|
+
|
|
477
|
+
def _flush_events(self) -> None:
|
|
478
|
+
"""Flush all buffered events to the Gateway API."""
|
|
479
|
+
events: List[GatewayEvent] = []
|
|
480
|
+
|
|
481
|
+
# Drain the queue
|
|
482
|
+
while True:
|
|
483
|
+
try:
|
|
484
|
+
event = self._event_buffer.get_nowait()
|
|
485
|
+
events.append(event)
|
|
486
|
+
except queue.Empty:
|
|
487
|
+
break
|
|
488
|
+
|
|
489
|
+
if not events:
|
|
490
|
+
return
|
|
491
|
+
|
|
492
|
+
self._send_events(events)
|
|
493
|
+
|
|
494
|
+
def _send_events(self, events: List[GatewayEvent]) -> None:
|
|
495
|
+
"""Send events to the Gateway API with retry logic."""
|
|
496
|
+
if not events:
|
|
497
|
+
return
|
|
498
|
+
|
|
499
|
+
payload = {
|
|
500
|
+
"job_id": self._job_id,
|
|
501
|
+
# Note: project_id is not sent - it's derived from the API key auth
|
|
502
|
+
"events": [
|
|
503
|
+
{
|
|
504
|
+
"id": e.id,
|
|
505
|
+
"type": e.type,
|
|
506
|
+
"timestamp": e.timestamp,
|
|
507
|
+
"data": e.data,
|
|
508
|
+
}
|
|
509
|
+
for e in events
|
|
510
|
+
],
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
# Create Basic auth header from api_key:secret_key
|
|
514
|
+
auth_string = f"{self._api_key}:{self._secret_key}"
|
|
515
|
+
auth_bytes = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
|
|
516
|
+
|
|
517
|
+
for attempt in range(self._max_retries):
|
|
518
|
+
try:
|
|
519
|
+
with httpx.Client(timeout=self._request_timeout) as client:
|
|
520
|
+
response = client.post(
|
|
521
|
+
f"{self._gateway_url}/api/public/v1/agent/ingest",
|
|
522
|
+
headers={
|
|
523
|
+
"Authorization": f"Basic {auth_bytes}",
|
|
524
|
+
"Content-Type": "application/json",
|
|
525
|
+
},
|
|
526
|
+
json=payload,
|
|
527
|
+
)
|
|
528
|
+
response.raise_for_status()
|
|
529
|
+
logger.debug(f"Successfully sent {len(events)} events to Gateway")
|
|
530
|
+
return
|
|
531
|
+
except httpx.HTTPStatusError as e:
|
|
532
|
+
if e.response.status_code == 401:
|
|
533
|
+
# Unauthorized - bad API key, won't fix itself
|
|
534
|
+
logger.error("Unauthorized (401) sending events to Gateway. Check API credentials.")
|
|
535
|
+
return
|
|
536
|
+
elif e.response.status_code == 429:
|
|
537
|
+
# Rate limited - back off
|
|
538
|
+
logger.warning("Rate limited (429) sending events, retrying with backoff...")
|
|
539
|
+
else:
|
|
540
|
+
# Log the full response for debugging
|
|
541
|
+
try:
|
|
542
|
+
error_body = e.response.json()
|
|
543
|
+
except Exception:
|
|
544
|
+
error_body = e.response.text
|
|
545
|
+
logger.warning(
|
|
546
|
+
f"HTTP error sending events (attempt {attempt + 1}/{self._max_retries}): "
|
|
547
|
+
f"{e.response.status_code} - {error_body}"
|
|
548
|
+
)
|
|
549
|
+
except httpx.HTTPError as e:
|
|
550
|
+
logger.warning(f"Failed to send events (attempt {attempt + 1}/{self._max_retries}): {e}")
|
|
551
|
+
|
|
552
|
+
# Exponential backoff
|
|
553
|
+
if attempt < self._max_retries - 1:
|
|
554
|
+
backoff_time = 2**attempt
|
|
555
|
+
time.sleep(backoff_time)
|
|
556
|
+
|
|
557
|
+
logger.error(f"Failed to send {len(events)} events after {self._max_retries} retries - events dropped")
|
|
558
|
+
|
|
559
|
+
# ─────────────────────────────────────────────────────────────
|
|
560
|
+
# Lifecycle Methods
|
|
561
|
+
# ─────────────────────────────────────────────────────────────
|
|
562
|
+
|
|
563
|
+
def complete(self, summary: Optional[Dict[str, Any]] = None) -> None:
|
|
564
|
+
"""Mark the job as complete and flush all remaining events."""
|
|
565
|
+
if self._completed:
|
|
566
|
+
return
|
|
567
|
+
self._completed = True
|
|
568
|
+
self._emit("job.completed", {"summary": summary or {}})
|
|
569
|
+
self._stop_event.set()
|
|
570
|
+
self._flush_events()
|
|
571
|
+
logger.info(f"GatewayTracker job {self._job_id} completed")
|
|
572
|
+
|
|
573
|
+
def fail(self, error: str) -> None:
|
|
574
|
+
"""Mark the job as failed and flush all remaining events."""
|
|
575
|
+
self._emit("job.failed", {"error": error})
|
|
576
|
+
self._stop_event.set()
|
|
577
|
+
self._flush_events()
|
|
578
|
+
logger.error(f"GatewayTracker job {self._job_id} failed: {error}")
|
|
579
|
+
|
|
580
|
+
def _cleanup(self) -> None:
|
|
581
|
+
"""Cleanup resources on exit."""
|
|
582
|
+
if not self._stop_event.is_set():
|
|
583
|
+
# Flush any remaining events before exiting
|
|
584
|
+
self._flush_events()
|
|
585
|
+
self._stop_event.set()
|
|
586
|
+
|
|
587
|
+
def __enter__(self) -> "GatewayTracker":
|
|
588
|
+
return self
|
|
589
|
+
|
|
590
|
+
def __exit__(
|
|
591
|
+
self,
|
|
592
|
+
exc_type: Optional[type],
|
|
593
|
+
exc_val: Optional[BaseException],
|
|
594
|
+
exc_tb: Optional[Any],
|
|
595
|
+
) -> None:
|
|
596
|
+
if exc_type:
|
|
597
|
+
self.fail(str(exc_val) if exc_val else "Unknown error")
|
|
598
|
+
elif not self._completed:
|
|
599
|
+
self.complete()
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
603
|
+
# GatewayLightningStore - Uses GatewayRunListener for consolidated tracking
|
|
604
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def GatewayLightningStore(
|
|
608
|
+
*,
|
|
609
|
+
api_key: str,
|
|
610
|
+
secret_key: str,
|
|
611
|
+
gateway_url: str,
|
|
612
|
+
project_id: str,
|
|
613
|
+
# Run configuration
|
|
614
|
+
run_name: Optional[str] = None,
|
|
615
|
+
run_id: Optional[str] = None,
|
|
616
|
+
config: Optional[Dict[str, Any]] = None,
|
|
617
|
+
labels: Optional[Dict[str, Any]] = None,
|
|
618
|
+
tags: Optional[List[str]] = None,
|
|
619
|
+
source: str = "gateway_store",
|
|
620
|
+
heartbeat_interval: int = 30,
|
|
621
|
+
# Store configuration
|
|
622
|
+
thread_safe: bool = False,
|
|
623
|
+
init_tracing: bool = False,
|
|
624
|
+
# Legacy parameters (passed to listener)
|
|
625
|
+
flush_interval: float = 1.0,
|
|
626
|
+
max_buffer_size: int = 1000,
|
|
627
|
+
request_timeout: float = 10.0,
|
|
628
|
+
max_retries: int = 3,
|
|
629
|
+
**kwargs: Any,
|
|
630
|
+
) -> "InMemoryLightningStore":
|
|
631
|
+
"""Create an InMemoryLightningStore with Gateway tracking.
|
|
632
|
+
|
|
633
|
+
This function creates a real GatewayRun record in the database for experiment
|
|
634
|
+
tracking. All Gateway communication is consolidated in GatewayRunListener:
|
|
635
|
+
- Run creation and lifecycle management
|
|
636
|
+
- Heartbeats for crash detection
|
|
637
|
+
- Metrics logging
|
|
638
|
+
- Session creation for rollouts
|
|
639
|
+
- Score submission
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
api_key: Gateway public API key for authentication.
|
|
643
|
+
secret_key: Gateway secret key for authentication.
|
|
644
|
+
gateway_url: Gateway server URL (e.g., "http://localhost:3000").
|
|
645
|
+
project_id: Project ID to associate the run with.
|
|
646
|
+
run_name: Optional name for the run (auto-generated if not provided).
|
|
647
|
+
config: Hyperparameters/config dict (logged for reproducibility).
|
|
648
|
+
labels: Arbitrary key-value labels for filtering runs.
|
|
649
|
+
tags: Simple string tags for categorization.
|
|
650
|
+
source: Source identifier (default: "gateway_store").
|
|
651
|
+
heartbeat_interval: Seconds between heartbeats (default: 30).
|
|
652
|
+
thread_safe: Whether the underlying store is thread-safe (default: False).
|
|
653
|
+
init_tracing: If True, automatically initialize gatewaysdk.tracing with:
|
|
654
|
+
- Gateway OTLP exporter configured from the provided credentials
|
|
655
|
+
- TracingContextSpanProcessor for automatic context injection
|
|
656
|
+
- OpenInference instrumentors (litellm, openai, anthropic, etc.)
|
|
657
|
+
|
|
658
|
+
Returns:
|
|
659
|
+
An InMemoryLightningStore with GatewayRunListener attached.
|
|
660
|
+
|
|
661
|
+
Example:
|
|
662
|
+
```python
|
|
663
|
+
# Basic usage
|
|
664
|
+
store = GatewayLightningStore(
|
|
665
|
+
api_key="pk-lf-abc123",
|
|
666
|
+
secret_key="sk-lf-xyz789",
|
|
667
|
+
gateway_url="https://app.gateway.dev",
|
|
668
|
+
project_id="proj-123",
|
|
669
|
+
run_name="gepa-experiment-v1",
|
|
670
|
+
config={"lr": 0.01, "epochs": 100},
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
# Log metrics directly on the store
|
|
674
|
+
store.log({"loss": 0.5, "accuracy": 0.92}, step=100)
|
|
675
|
+
|
|
676
|
+
# With automatic tracing SDK setup (recommended)
|
|
677
|
+
store = GatewayLightningStore(
|
|
678
|
+
api_key="pk-lf-abc123",
|
|
679
|
+
secret_key="sk-lf-xyz789",
|
|
680
|
+
gateway_url="https://app.gateway.dev",
|
|
681
|
+
project_id="proj-123",
|
|
682
|
+
init_tracing=True,
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
trainer = Trainer(algorithm=GEPA(...), store=store)
|
|
686
|
+
# Run is tracked with heartbeats, metrics, sessions, etc.
|
|
687
|
+
|
|
688
|
+
# When done, call complete() or fail()
|
|
689
|
+
store.complete()
|
|
690
|
+
```
|
|
691
|
+
"""
|
|
692
|
+
import os
|
|
693
|
+
|
|
694
|
+
# Set env vars for tracing SDK and other components
|
|
695
|
+
os.environ["GATEWAY_HOST"] = gateway_url
|
|
696
|
+
os.environ["GATEWAY_PUBLIC_KEY"] = api_key
|
|
697
|
+
os.environ["GATEWAY_SECRET_KEY"] = secret_key
|
|
698
|
+
os.environ["GATEWAY_PROJECT_ID"] = project_id
|
|
699
|
+
|
|
700
|
+
logger.debug(
|
|
701
|
+
f"GatewayLightningStore: Set env vars - "
|
|
702
|
+
f"GATEWAY_HOST={gateway_url}, "
|
|
703
|
+
f"GATEWAY_PUBLIC_KEY={api_key[:20]}..., "
|
|
704
|
+
f"GATEWAY_PROJECT_ID={project_id}"
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
# Initialize tracing SDK if requested
|
|
708
|
+
if init_tracing:
|
|
709
|
+
import gatewaysdk.tracing as tracing
|
|
710
|
+
|
|
711
|
+
tracing.init(instrument_default=True)
|
|
712
|
+
|
|
713
|
+
# Create consolidated listener - handles ALL Gateway communication
|
|
714
|
+
listener = GatewayRunListener(
|
|
715
|
+
api_key=api_key,
|
|
716
|
+
secret_key=secret_key,
|
|
717
|
+
gateway_url=gateway_url,
|
|
718
|
+
project_id=project_id,
|
|
719
|
+
run_name=run_name,
|
|
720
|
+
run_id=run_id, # Reuse existing run if provided (e.g., platform runs)
|
|
721
|
+
config=config,
|
|
722
|
+
labels=labels,
|
|
723
|
+
tags=tags,
|
|
724
|
+
source=source,
|
|
725
|
+
heartbeat_interval=heartbeat_interval,
|
|
726
|
+
request_timeout=request_timeout,
|
|
727
|
+
max_retries=max_retries,
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
# Start the run (creates DB record, starts heartbeat, registers handlers)
|
|
731
|
+
listener.start()
|
|
732
|
+
|
|
733
|
+
# Create store with listener attached
|
|
734
|
+
store = InMemoryLightningStore(thread_safe=thread_safe, listeners=[listener], **kwargs)
|
|
735
|
+
|
|
736
|
+
# Attach listener to store for access
|
|
737
|
+
store._gateway_listener = listener # type: ignore[attr-defined]
|
|
738
|
+
|
|
739
|
+
# Expose clean API methods on store instance
|
|
740
|
+
def log(
|
|
741
|
+
metrics,
|
|
742
|
+
value=None,
|
|
743
|
+
*,
|
|
744
|
+
step=None,
|
|
745
|
+
labels=None,
|
|
746
|
+
unit="",
|
|
747
|
+
metric_type="gauge",
|
|
748
|
+
):
|
|
749
|
+
"""Log metrics for this run."""
|
|
750
|
+
listener.log(metrics, value, step=step, labels=labels, unit=unit, metric_type=metric_type)
|
|
751
|
+
|
|
752
|
+
def complete(summary: Optional[Dict[str, Any]] = None) -> None:
|
|
753
|
+
"""Mark the run as completed."""
|
|
754
|
+
listener.complete()
|
|
755
|
+
|
|
756
|
+
def fail(error: Optional[str] = None) -> None:
|
|
757
|
+
"""Mark the run as failed."""
|
|
758
|
+
listener.fail(error)
|
|
759
|
+
|
|
760
|
+
def log_state(state: Dict[str, Any]) -> None:
|
|
761
|
+
"""Log rich structured state for live visualization."""
|
|
762
|
+
listener.log_state(state)
|
|
763
|
+
|
|
764
|
+
def flush() -> None:
|
|
765
|
+
"""Manually flush buffered metrics."""
|
|
766
|
+
listener.flush()
|
|
767
|
+
|
|
768
|
+
# Monkey-patch the API methods onto the store instance
|
|
769
|
+
store.log = log # type: ignore[attr-defined]
|
|
770
|
+
store.complete = complete # type: ignore[attr-defined]
|
|
771
|
+
store.fail = fail # type: ignore[attr-defined]
|
|
772
|
+
store.log_state = log_state # type: ignore[attr-defined]
|
|
773
|
+
store.flush = flush # type: ignore[attr-defined]
|
|
774
|
+
|
|
775
|
+
# Add run_id property for convenience
|
|
776
|
+
store.run_id = listener.run_id # type: ignore[attr-defined]
|
|
777
|
+
store.job_id = listener.run_id # type: ignore[attr-defined] # Backward compat
|
|
778
|
+
|
|
779
|
+
# Deprecated: _run attribute for backward compatibility
|
|
780
|
+
# Creates a simple object that forwards to the listener
|
|
781
|
+
class _RunCompat:
|
|
782
|
+
"""Backward compatibility wrapper for store._run access."""
|
|
783
|
+
|
|
784
|
+
def __init__(self, listener):
|
|
785
|
+
self._listener = listener
|
|
786
|
+
|
|
787
|
+
@property
|
|
788
|
+
def id(self):
|
|
789
|
+
return self._listener.run_id
|
|
790
|
+
|
|
791
|
+
def log(self, *args, **kwargs):
|
|
792
|
+
return self._listener.log(*args, **kwargs)
|
|
793
|
+
|
|
794
|
+
def log_state(self, state):
|
|
795
|
+
return self._listener.log_state(state)
|
|
796
|
+
|
|
797
|
+
def flush(self):
|
|
798
|
+
return self._listener.flush()
|
|
799
|
+
|
|
800
|
+
def finish(self, state="completed"):
|
|
801
|
+
return self._listener.finish(state)
|
|
802
|
+
|
|
803
|
+
store._run = _RunCompat(listener) # type: ignore[attr-defined]
|
|
804
|
+
|
|
805
|
+
logger.info(
|
|
806
|
+
f"GatewayLightningStore created with GatewayRun {listener.run_id} "
|
|
807
|
+
f"(project={project_id}, name={listener.run_name})"
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
return store
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
814
|
+
# RedisStreamGatewayLightningStore - For platform runs with distributed coord
|
|
815
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
def RedisStreamGatewayLightningStore(
|
|
819
|
+
*,
|
|
820
|
+
redis_url: str,
|
|
821
|
+
run_id: str,
|
|
822
|
+
api_key: str,
|
|
823
|
+
secret_key: str,
|
|
824
|
+
gateway_url: str,
|
|
825
|
+
project_id: str,
|
|
826
|
+
run_name: Optional[str] = None,
|
|
827
|
+
config: Optional[Dict[str, Any]] = None,
|
|
828
|
+
labels: Optional[Dict[str, Any]] = None,
|
|
829
|
+
tags: Optional[List[str]] = None,
|
|
830
|
+
source: str = "platform",
|
|
831
|
+
heartbeat_interval: int = 30,
|
|
832
|
+
consumer_group: str = "gateway-runners",
|
|
833
|
+
thread_safe: bool = False,
|
|
834
|
+
init_tracing: bool = False,
|
|
835
|
+
) -> "RedisStreamLightningStore":
|
|
836
|
+
"""Create a RedisStreamLightningStore with GatewayRunListener attached.
|
|
837
|
+
|
|
838
|
+
Used by the Python BullMQ worker for platform-managed runs. Combines
|
|
839
|
+
Redis Streams for distributed rollout coordination with GatewayRunListener
|
|
840
|
+
for run lifecycle management (heartbeats, metrics, sessions).
|
|
841
|
+
|
|
842
|
+
Args:
|
|
843
|
+
redis_url: Redis connection string.
|
|
844
|
+
run_id: Existing GatewayRun ID (created by the platform start endpoint).
|
|
845
|
+
api_key: Gateway public API key.
|
|
846
|
+
secret_key: Gateway secret key.
|
|
847
|
+
gateway_url: Gateway server URL.
|
|
848
|
+
project_id: Project ID.
|
|
849
|
+
run_name: Optional run name.
|
|
850
|
+
config: Run config dict.
|
|
851
|
+
labels: Run labels.
|
|
852
|
+
tags: Run tags.
|
|
853
|
+
source: Run source (default: "platform").
|
|
854
|
+
heartbeat_interval: Heartbeat interval in seconds.
|
|
855
|
+
consumer_group: Redis consumer group name.
|
|
856
|
+
thread_safe: Whether the store is thread-safe.
|
|
857
|
+
init_tracing: If True, auto-initialize gatewaysdk.tracing.
|
|
858
|
+
|
|
859
|
+
Returns:
|
|
860
|
+
RedisStreamLightningStore with GatewayRunListener attached.
|
|
861
|
+
"""
|
|
862
|
+
import os
|
|
863
|
+
|
|
864
|
+
from .redis_stream import RedisStreamLightningStore as RedisStore
|
|
865
|
+
|
|
866
|
+
# Set non-secret env vars for SDK components that read from env
|
|
867
|
+
os.environ["GATEWAY_HOST"] = gateway_url
|
|
868
|
+
os.environ["GATEWAY_PUBLIC_KEY"] = api_key
|
|
869
|
+
os.environ["GATEWAY_PROJECT_ID"] = project_id
|
|
870
|
+
|
|
871
|
+
if init_tracing:
|
|
872
|
+
# Temporarily set secret key for tracing init, then remove (F6)
|
|
873
|
+
os.environ["GATEWAY_SECRET_KEY"] = secret_key
|
|
874
|
+
import gatewaysdk.tracing as tracing
|
|
875
|
+
|
|
876
|
+
tracing.init(instrument_default=True)
|
|
877
|
+
os.environ.pop("GATEWAY_SECRET_KEY", None)
|
|
878
|
+
|
|
879
|
+
listener = GatewayRunListener(
|
|
880
|
+
api_key=api_key,
|
|
881
|
+
secret_key=secret_key,
|
|
882
|
+
gateway_url=gateway_url,
|
|
883
|
+
project_id=project_id,
|
|
884
|
+
run_name=run_name,
|
|
885
|
+
config=config,
|
|
886
|
+
labels=labels,
|
|
887
|
+
tags=tags,
|
|
888
|
+
source=source,
|
|
889
|
+
heartbeat_interval=heartbeat_interval,
|
|
890
|
+
)
|
|
891
|
+
listener.start()
|
|
892
|
+
|
|
893
|
+
store = RedisStore(
|
|
894
|
+
redis_url=redis_url,
|
|
895
|
+
run_id=run_id,
|
|
896
|
+
consumer_group=consumer_group,
|
|
897
|
+
thread_safe=thread_safe,
|
|
898
|
+
listeners=[listener],
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
store.log = listener.log # type: ignore[attr-defined]
|
|
902
|
+
store.complete = listener.complete # type: ignore[attr-defined]
|
|
903
|
+
store.fail = listener.fail # type: ignore[attr-defined]
|
|
904
|
+
store.log_state = listener.log_state # type: ignore[attr-defined]
|
|
905
|
+
store.flush = listener.flush # type: ignore[attr-defined]
|
|
906
|
+
|
|
907
|
+
from gatewaysdk.utils.redact import redact_url
|
|
908
|
+
|
|
909
|
+
logger.info(
|
|
910
|
+
f"RedisStreamGatewayLightningStore created for run {run_id} "
|
|
911
|
+
f"(redis={redact_url(redis_url)}, project={project_id})"
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
return store
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
def create_platform_store_client_from_env() -> "LightningStoreClient":
|
|
918
|
+
"""Create a store client for agent containers running on the Gateway platform.
|
|
919
|
+
|
|
920
|
+
Only requires ``GATEWAY_STORE_URL`` (set by the orchestrator). All Gateway
|
|
921
|
+
integration (OTLP export, sessions, scores, heartbeats) is handled
|
|
922
|
+
server-side by the worker's GatewayRunListener.
|
|
923
|
+
|
|
924
|
+
Returns:
|
|
925
|
+
A :class:`LightningStoreClient` connected to the worker's store server.
|
|
926
|
+
|
|
927
|
+
Raises:
|
|
928
|
+
EnvironmentError: If GATEWAY_STORE_URL is not set.
|
|
929
|
+
"""
|
|
930
|
+
import os
|
|
931
|
+
|
|
932
|
+
from .client_server import LightningStoreClient
|
|
933
|
+
|
|
934
|
+
store_url = os.environ.get("GATEWAY_STORE_URL")
|
|
935
|
+
if not store_url:
|
|
936
|
+
raise EnvironmentError(
|
|
937
|
+
"GATEWAY_STORE_URL is not set. This variable is injected automatically "
|
|
938
|
+
"by the Gateway platform orchestrator when starting agent containers."
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
logger.info(f"Creating store client for {store_url}")
|
|
942
|
+
return LightningStoreClient(server_address=store_url)
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def create_platform_store_from_env() -> "RedisStreamLightningStore":
|
|
946
|
+
"""DEPRECATED: Use create_platform_store_client_from_env() instead.
|
|
947
|
+
|
|
948
|
+
This function creates a RedisStreamLightningStore which has been
|
|
949
|
+
replaced by LightningStoreClient for platform runs.
|
|
950
|
+
"""
|
|
951
|
+
import os
|
|
952
|
+
import warnings
|
|
953
|
+
|
|
954
|
+
warnings.warn(
|
|
955
|
+
"create_platform_store_from_env() is deprecated. "
|
|
956
|
+
"Use create_platform_store_client_from_env() instead, which uses "
|
|
957
|
+
"LightningStoreClient (HTTP) rather than Redis Streams.",
|
|
958
|
+
DeprecationWarning,
|
|
959
|
+
stacklevel=2,
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
required = ["GATEWAY_RUN_ID", "GATEWAY_HOST", "GATEWAY_PUBLIC_KEY", "GATEWAY_SECRET_KEY", "GATEWAY_PROJECT_ID"]
|
|
963
|
+
missing = [k for k in required if not os.environ.get(k)]
|
|
964
|
+
if missing:
|
|
965
|
+
raise EnvironmentError(
|
|
966
|
+
f"Missing required environment variables: {', '.join(missing)}. "
|
|
967
|
+
"These are set automatically by the Gateway platform orchestrator."
|
|
968
|
+
)
|
|
969
|
+
|
|
970
|
+
run_id = os.environ["GATEWAY_RUN_ID"]
|
|
971
|
+
host = os.environ.get("REDIS_HOST", "localhost")
|
|
972
|
+
port = os.environ.get("REDIS_PORT", "6379")
|
|
973
|
+
auth = os.environ.get("REDIS_AUTH", "")
|
|
974
|
+
redis_url = f"redis://:{auth}@{host}:{port}" if auth else f"redis://{host}:{port}"
|
|
975
|
+
|
|
976
|
+
return RedisStreamGatewayLightningStore(
|
|
977
|
+
redis_url=redis_url,
|
|
978
|
+
run_id=run_id,
|
|
979
|
+
api_key=os.environ["GATEWAY_PUBLIC_KEY"],
|
|
980
|
+
secret_key=os.environ["GATEWAY_SECRET_KEY"],
|
|
981
|
+
gateway_url=os.environ["GATEWAY_HOST"],
|
|
982
|
+
project_id=os.environ["GATEWAY_PROJECT_ID"],
|
|
983
|
+
)
|