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,673 @@
|
|
|
1
|
+
"""Harbor → Gateway evals adapter.
|
|
2
|
+
|
|
3
|
+
A harbor-kind container version is a directory of Harbor task dirs
|
|
4
|
+
(``task.toml`` + ``instruction.md`` + ``tests/``). The first-party ``harbor``
|
|
5
|
+
engine executes them (agent in a sandbox, then the task's own verifier script,
|
|
6
|
+
which writes ``/logs/verifier/reward.json`` / ``reward.txt``) and leaves a jobs
|
|
7
|
+
directory of trials. This module maps that jobs directory into the SAME
|
|
8
|
+
version-pinned evaluation shape the verifiers adapter produces — so the
|
|
9
|
+
Environment page, dispatcher back-link, and sub-reward drill-down are identical
|
|
10
|
+
across env kinds.
|
|
11
|
+
|
|
12
|
+
Trial dir layout (harbor >= 0.15, same shape Pier emits):
|
|
13
|
+
trial_dir/
|
|
14
|
+
result.json # TrialResult: task_name, agent_info, verifier_result.rewards, timing
|
|
15
|
+
verifier/reward.json # named numeric metrics (fallback / sub-rewards)
|
|
16
|
+
agent/** # agent logs
|
|
17
|
+
|
|
18
|
+
Reward precedence mirrors deploy/benchmark-runner/harvest.py:_reward — the
|
|
19
|
+
``result.json`` ``verifier_result.rewards`` dict first, then
|
|
20
|
+
``verifier/reward.json``. The scalar reward is the dict's ``reward`` key (or
|
|
21
|
+
its first numeric value); ALL other numeric keys become named sub-rewards.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import glob
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import re
|
|
30
|
+
import shutil
|
|
31
|
+
import subprocess
|
|
32
|
+
from typing import Any, Dict, List, Optional
|
|
33
|
+
|
|
34
|
+
from .evals import EvalHarvestError, GatewayEvalsClient
|
|
35
|
+
|
|
36
|
+
# --- verifiers v1 detection (kept in sync with the runner, engines/hub_bundle.py).
|
|
37
|
+
# The `verifiers.v1` namespace first shipped in verifiers 0.2.0 (PyPI), so a v1
|
|
38
|
+
# Taskset package declares `verifiers>=0.2` (or `~=0.2` / `==0.2.*` / a future
|
|
39
|
+
# `>=1`) in pyproject and/or imports the `verifiers.v1` namespace / subclasses
|
|
40
|
+
# `Taskset` (with the v1 convention of an `__all__` export) in its package
|
|
41
|
+
# __init__. A bare unversioned `verifiers` dep, a pre-0.2 spec (`==0.1.5`,
|
|
42
|
+
# `>=0.1,<0.2`), or the classic v0 `verifiers<1.0.0` pin never matches.
|
|
43
|
+
# Version alternation: minor >= 2 on the 0.x line (`0.2`, `0.10`, ...) or any
|
|
44
|
+
# major >= 1 — never `0.0`/`0.1[.z]`.
|
|
45
|
+
_V1_DEP_RE = re.compile(
|
|
46
|
+
r"""["']verifiers\s*(?:\[[^\]]*\])?\s*(?:>=|~=|==|>)\s*"""
|
|
47
|
+
r"""(?:0\.(?:[2-9]\d*|[1-9]\d+)|[1-9]\d*)"""
|
|
48
|
+
)
|
|
49
|
+
_V1_IMPORT_RE = re.compile(
|
|
50
|
+
r"^\s*(?:from\s+verifiers\.v1\s+import\b"
|
|
51
|
+
r"|from\s+verifiers\s+import\s+v1\b"
|
|
52
|
+
r"|import\s+verifiers\.v1\b)",
|
|
53
|
+
re.MULTILINE,
|
|
54
|
+
)
|
|
55
|
+
_V1_TASKSET_RE = re.compile(
|
|
56
|
+
r"class\s+\w+\s*\(\s*(?:(?:vf|v1|verifiers(?:\.v1)?)\.)?Taskset\s*[,)]"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _read_text(path: str) -> str:
|
|
61
|
+
try:
|
|
62
|
+
with open(path, encoding="utf-8", errors="replace") as f:
|
|
63
|
+
return f.read()
|
|
64
|
+
except OSError:
|
|
65
|
+
return ""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _is_verifiers_v1(src: str) -> bool:
|
|
69
|
+
pyproject = os.path.join(src, "pyproject.toml")
|
|
70
|
+
if os.path.isfile(pyproject) and _V1_DEP_RE.search(_read_text(pyproject)):
|
|
71
|
+
return True
|
|
72
|
+
inits = glob.glob(os.path.join(src, "*", "__init__.py")) + glob.glob(
|
|
73
|
+
os.path.join(src, "src", "*", "__init__.py")
|
|
74
|
+
)
|
|
75
|
+
for init in inits:
|
|
76
|
+
text = _read_text(init)
|
|
77
|
+
if not text:
|
|
78
|
+
continue
|
|
79
|
+
if _V1_IMPORT_RE.search(text):
|
|
80
|
+
return True
|
|
81
|
+
if "__all__" in text and _V1_TASKSET_RE.search(text):
|
|
82
|
+
return True
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def detect_env_kind(src: str) -> str:
|
|
87
|
+
""""mock-tools" iff a ``gateway-env.toml`` sits at the source root (the
|
|
88
|
+
environment builder's declarative bundle: manifest + ``db/`` + ``tools/`` +
|
|
89
|
+
``verifiers/``); else "harbor" iff the source contains Harbor task dirs
|
|
90
|
+
(``tasks/*/task.toml`` or ``*/task.toml``); else "verifiers-v1" for verifiers
|
|
91
|
+
v1 Taskset packages; else "verifiers" (legacy v0).
|
|
92
|
+
|
|
93
|
+
``mock-tools`` is checked FIRST because a root ``gateway-env.toml`` is
|
|
94
|
+
unambiguous — such a bundle is neither a Python package (no ``load_environment``)
|
|
95
|
+
nor a Harbor task tree, so classifying it as ``verifiers`` would drive the
|
|
96
|
+
runner into ``pip install -e`` and crash.
|
|
97
|
+
|
|
98
|
+
Kept in sync with the runner (engines/hub_bundle.py), the local runtime's
|
|
99
|
+
``gatewaysdk.environment._bundle.detect_kind``, and the web read router
|
|
100
|
+
(deriveEnvKind — where ``mock-tools`` maps to the cosmetic ``verifiers``
|
|
101
|
+
badge, since the badge is binary)."""
|
|
102
|
+
if os.path.isfile(os.path.join(src, "gateway-api-world.toml")):
|
|
103
|
+
return "api"
|
|
104
|
+
if os.path.isfile(os.path.join(src, "gateway-env.toml")):
|
|
105
|
+
return "mock-tools"
|
|
106
|
+
if glob.glob(os.path.join(src, "tasks", "*", "task.toml")) or glob.glob(
|
|
107
|
+
os.path.join(src, "*", "task.toml")
|
|
108
|
+
):
|
|
109
|
+
return "harbor"
|
|
110
|
+
if _is_verifiers_v1(src):
|
|
111
|
+
return "verifiers-v1"
|
|
112
|
+
return "verifiers"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def tasks_root(src: str) -> str:
|
|
116
|
+
"""The dir whose CHILDREN are task dirs: <src>/tasks if present, else <src>."""
|
|
117
|
+
tasks = os.path.join(src, "tasks")
|
|
118
|
+
return tasks if glob.glob(os.path.join(tasks, "*", "task.toml")) else src
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _read_json(path: str) -> Optional[Any]:
|
|
122
|
+
try:
|
|
123
|
+
with open(path) as f:
|
|
124
|
+
return json.load(f)
|
|
125
|
+
except (OSError, json.JSONDecodeError):
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def find_trials(job_dir: str) -> List[str]:
|
|
130
|
+
"""Trial dirs = dirs holding a per-trial ``result.json`` AND a ``verifier/``
|
|
131
|
+
subdir (excludes the job-level result.json at the run root)."""
|
|
132
|
+
out = []
|
|
133
|
+
for p in glob.glob(os.path.join(job_dir, "**", "result.json"), recursive=True):
|
|
134
|
+
d = os.path.dirname(p)
|
|
135
|
+
if os.path.isdir(os.path.join(d, "verifier")):
|
|
136
|
+
out.append(d)
|
|
137
|
+
return sorted(out)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def trial_rewards(trial_dir: str) -> Dict[str, float]:
|
|
141
|
+
"""All numeric reward metrics for a trial (``{}`` when the verifier wrote
|
|
142
|
+
nothing — the trial is then UNGRADED and its reward stays null)."""
|
|
143
|
+
results = _read_json(os.path.join(trial_dir, "result.json")) or {}
|
|
144
|
+
rewards = (results.get("verifier_result") or {}).get("rewards") or _read_json(
|
|
145
|
+
os.path.join(trial_dir, "verifier", "reward.json")
|
|
146
|
+
)
|
|
147
|
+
if isinstance(rewards, (int, float)):
|
|
148
|
+
return {"reward": float(rewards)}
|
|
149
|
+
if isinstance(rewards, dict):
|
|
150
|
+
return {
|
|
151
|
+
k: float(v) for k, v in rewards.items() if isinstance(v, (int, float))
|
|
152
|
+
}
|
|
153
|
+
return {}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def scalar_reward(rewards: Dict[str, float]) -> Optional[float]:
|
|
157
|
+
"""The trial's headline reward, or None when NOTHING was graded.
|
|
158
|
+
|
|
159
|
+
An ungraded trial must stay null. Coercing it to 0.0 made "the grader
|
|
160
|
+
never ran" indistinguishable from "the agent scored zero" — the exact
|
|
161
|
+
confusion behind a prod zero that was actually a RewardFileNotFoundError.
|
|
162
|
+
Null is the platform's own contract (null scores are their own category,
|
|
163
|
+
never coerced), and it is what lets an agent act on "nothing was graded —
|
|
164
|
+
a failure to fix, not a pass".
|
|
165
|
+
"""
|
|
166
|
+
if "reward" in rewards:
|
|
167
|
+
return rewards["reward"]
|
|
168
|
+
return next(iter(rewards.values()), None)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def samples_from_job_dir(job_dir: str) -> List[Dict[str, Any]]:
|
|
172
|
+
"""Map a harbor jobs directory → eval-sample dicts (one per trial).
|
|
173
|
+
|
|
174
|
+
``exampleId`` = the task name; ``rolloutIndex`` = the occurrence of that
|
|
175
|
+
task within the job (n-attempts > 1 produces one trial per attempt).
|
|
176
|
+
Trajectories stay in the jobs dir — samples carry compact info only.
|
|
177
|
+
"""
|
|
178
|
+
seen: Dict[str, int] = {}
|
|
179
|
+
samples: List[Dict[str, Any]] = []
|
|
180
|
+
for trial_dir in find_trials(job_dir):
|
|
181
|
+
results = _read_json(os.path.join(trial_dir, "result.json")) or {}
|
|
182
|
+
task_id = (results.get("task_name") or "").split("/")[-1] or os.path.basename(
|
|
183
|
+
trial_dir.rstrip("/")
|
|
184
|
+
)
|
|
185
|
+
idx = seen.get(task_id, 0)
|
|
186
|
+
seen[task_id] = idx + 1
|
|
187
|
+
|
|
188
|
+
rewards = trial_rewards(trial_dir)
|
|
189
|
+
reward = scalar_reward(rewards)
|
|
190
|
+
agent_info = results.get("agent_info") or {}
|
|
191
|
+
agent_result = results.get("agent_result") or {}
|
|
192
|
+
samples.append(
|
|
193
|
+
{
|
|
194
|
+
"exampleId": task_id,
|
|
195
|
+
# `task` is what run tables and reward breakdowns display —
|
|
196
|
+
# without it every harbor sample rendered as "?".
|
|
197
|
+
"task": task_id,
|
|
198
|
+
"rolloutIndex": idx,
|
|
199
|
+
"reward": reward,
|
|
200
|
+
"score": reward,
|
|
201
|
+
# Ungraded is not incorrect — correctness is unknowable when
|
|
202
|
+
# nothing scored the trial.
|
|
203
|
+
"correct": (reward >= 1.0) if reward is not None else None,
|
|
204
|
+
"rewards": {k: v for k, v in rewards.items() if k != "reward"},
|
|
205
|
+
"info": {
|
|
206
|
+
"trialName": os.path.basename(trial_dir.rstrip("/")),
|
|
207
|
+
"agent": agent_info.get("name"),
|
|
208
|
+
"model": (agent_info.get("model_info") or {}).get("name"),
|
|
209
|
+
"costUsd": agent_result.get("cost_usd"),
|
|
210
|
+
"startedAt": results.get("started_at"),
|
|
211
|
+
"finishedAt": results.get("finished_at"),
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
return samples
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def metrics_from_samples(samples: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
219
|
+
"""Aggregate metrics: average reward + average of each named sub-reward
|
|
220
|
+
(mirrors verifiers_adapter.metrics_from_generate_outputs)."""
|
|
221
|
+
rewards = [s["reward"] for s in samples if isinstance(s.get("reward"), (int, float))]
|
|
222
|
+
by_metric: Dict[str, List[float]] = {}
|
|
223
|
+
for s in samples:
|
|
224
|
+
for name, value in (s.get("rewards") or {}).items():
|
|
225
|
+
if isinstance(value, (int, float)):
|
|
226
|
+
by_metric.setdefault(name, []).append(float(value))
|
|
227
|
+
|
|
228
|
+
def _mean(xs: List[float]) -> Optional[float]:
|
|
229
|
+
return sum(xs) / len(xs) if xs else None
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
"n": len(samples),
|
|
233
|
+
"avg_reward": _mean(rewards),
|
|
234
|
+
"metrics": {name: _mean(vals) for name, vals in by_metric.items()},
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def emit_trial_traces(job_dir: str, evaluation_id: str) -> int:
|
|
239
|
+
"""Emit one rich OTLP trace per trial so platform traces are not shells.
|
|
240
|
+
|
|
241
|
+
Reads the verifier's full-state export (``verifier/state.json``, written by
|
|
242
|
+
task verifiers that support it) and maps it to an AGENT root span with one
|
|
243
|
+
TOOL child span per audited tool call, joined to the platform's harvest
|
|
244
|
+
session (``<evaluationId>:<task>:<rolloutIndex>``). Requires
|
|
245
|
+
``GATEWAY_OTLP_ENDPOINT`` + key envs and the opentelemetry sdk; silently
|
|
246
|
+
returns 0 when either is absent (tracing is additive, never load-bearing).
|
|
247
|
+
"""
|
|
248
|
+
endpoint = os.environ.get("GATEWAY_OTLP_ENDPOINT")
|
|
249
|
+
public_key = os.environ.get("GATEWAY_PUBLIC_KEY")
|
|
250
|
+
secret_key = os.environ.get("GATEWAY_SECRET_KEY")
|
|
251
|
+
if not (endpoint and public_key and secret_key):
|
|
252
|
+
return 0
|
|
253
|
+
try:
|
|
254
|
+
from opentelemetry import trace as otel_trace
|
|
255
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
|
|
256
|
+
OTLPSpanExporter,
|
|
257
|
+
)
|
|
258
|
+
from opentelemetry.sdk.resources import Resource
|
|
259
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
260
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
261
|
+
except ImportError:
|
|
262
|
+
return 0
|
|
263
|
+
import base64
|
|
264
|
+
|
|
265
|
+
auth = base64.b64encode(f"{public_key}:{secret_key}".encode()).decode()
|
|
266
|
+
provider = TracerProvider(
|
|
267
|
+
resource=Resource.create({"service.name": "gateway-bench-harvest"})
|
|
268
|
+
)
|
|
269
|
+
provider.add_span_processor(
|
|
270
|
+
BatchSpanProcessor(
|
|
271
|
+
OTLPSpanExporter(
|
|
272
|
+
endpoint=endpoint, headers={"Authorization": f"Basic {auth}"}
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
tracer = provider.get_tracer("bench-harvest")
|
|
277
|
+
emitted = 0
|
|
278
|
+
seen: Dict[str, int] = {}
|
|
279
|
+
for trial_dir in find_trials(job_dir):
|
|
280
|
+
results = _read_json(os.path.join(trial_dir, "result.json")) or {}
|
|
281
|
+
task_id = (results.get("task_name") or "").split("/")[-1] or os.path.basename(
|
|
282
|
+
trial_dir.rstrip("/")
|
|
283
|
+
)
|
|
284
|
+
idx = seen.get(task_id, 0)
|
|
285
|
+
seen[task_id] = idx + 1
|
|
286
|
+
# No full-state export is NOT "no session": every rollout emits at
|
|
287
|
+
# least its graded outline (instruction context, reward, sub-reward
|
|
288
|
+
# gates, agent/model/cost). state.json, when a world exports it,
|
|
289
|
+
# upgrades the same trace with one TOOL span per audited call.
|
|
290
|
+
state = _read_json(os.path.join(trial_dir, "verifier", "state.json")) or {}
|
|
291
|
+
rewards = trial_rewards(trial_dir)
|
|
292
|
+
report = state.get("report") or {}
|
|
293
|
+
case = state.get("case_state") or {}
|
|
294
|
+
with tracer.start_as_current_span(f"episode {task_id}") as root:
|
|
295
|
+
root.set_attribute("openinference.span.kind", "AGENT")
|
|
296
|
+
root.set_attribute("session.id", f"{evaluation_id}:{task_id}:{idx}")
|
|
297
|
+
root.set_attribute("langfuse.session.id", f"{evaluation_id}:{task_id}:{idx}")
|
|
298
|
+
agent_info = results.get("agent_info") or {}
|
|
299
|
+
root.set_attribute(
|
|
300
|
+
"input.value",
|
|
301
|
+
json.dumps(
|
|
302
|
+
{
|
|
303
|
+
"task": task_id,
|
|
304
|
+
**({"case": case.get("title")} if case else {}),
|
|
305
|
+
**(
|
|
306
|
+
{"agent": agent_info.get("name")}
|
|
307
|
+
if agent_info.get("name")
|
|
308
|
+
else {}
|
|
309
|
+
),
|
|
310
|
+
}
|
|
311
|
+
),
|
|
312
|
+
)
|
|
313
|
+
root.set_attribute(
|
|
314
|
+
"output.value",
|
|
315
|
+
json.dumps(
|
|
316
|
+
{
|
|
317
|
+
"reward": scalar_reward(rewards),
|
|
318
|
+
"report": report,
|
|
319
|
+
"failing_gates": [
|
|
320
|
+
k for k, v in rewards.items() if v not in (1.0, None)
|
|
321
|
+
] or "none",
|
|
322
|
+
},
|
|
323
|
+
default=str,
|
|
324
|
+
)[:4000],
|
|
325
|
+
)
|
|
326
|
+
audit_by_seq = {
|
|
327
|
+
entry.get("seq"): entry for entry in state.get("audit_entries") or []
|
|
328
|
+
}
|
|
329
|
+
call_log = state.get("call_log") or []
|
|
330
|
+
per_call = call_log or [
|
|
331
|
+
{
|
|
332
|
+
"seq": entry.get("seq"),
|
|
333
|
+
"tool": entry.get("tool"),
|
|
334
|
+
"arguments": None,
|
|
335
|
+
"result_preview": None,
|
|
336
|
+
"error": None,
|
|
337
|
+
}
|
|
338
|
+
for entry in state.get("audit_entries") or []
|
|
339
|
+
]
|
|
340
|
+
for call in per_call:
|
|
341
|
+
audit = audit_by_seq.get(call.get("seq"), {})
|
|
342
|
+
with tracer.start_as_current_span(
|
|
343
|
+
f"{call.get('seq', 0):02d} {call.get('tool', '?')}"
|
|
344
|
+
) as span:
|
|
345
|
+
span.set_attribute("openinference.span.kind", "TOOL")
|
|
346
|
+
span.set_attribute(
|
|
347
|
+
"input.value",
|
|
348
|
+
call.get("arguments")
|
|
349
|
+
or json.dumps({"args_hash": audit.get("args_hash")}),
|
|
350
|
+
)
|
|
351
|
+
span.set_attribute(
|
|
352
|
+
"output.value",
|
|
353
|
+
(
|
|
354
|
+
json.dumps({"error": call["error"]})
|
|
355
|
+
if call.get("error")
|
|
356
|
+
else call.get("result_preview")
|
|
357
|
+
)
|
|
358
|
+
or json.dumps({"result_hash": audit.get("result_hash")}),
|
|
359
|
+
)
|
|
360
|
+
if audit.get("args_hash"):
|
|
361
|
+
span.set_attribute("audit.args_hash", audit["args_hash"])
|
|
362
|
+
span.set_attribute(
|
|
363
|
+
"audit.result_hash", audit.get("result_hash") or ""
|
|
364
|
+
)
|
|
365
|
+
span.set_attribute("audit.at", audit.get("timestamp") or "")
|
|
366
|
+
|
|
367
|
+
# THE AGENT'S OWN DIALOGUE, when the trial recorded one. Installed
|
|
368
|
+
# agents (claude-code and kin) never touch state.json, so without
|
|
369
|
+
# this the rollout page showed a graded outline over an empty
|
|
370
|
+
# conversation — a run that traced "nothing" while the agent had
|
|
371
|
+
# written a sixteen-point redline. Capped, never load-bearing.
|
|
372
|
+
transcript = agent_transcript(trial_dir)
|
|
373
|
+
if transcript and transcript.get("kind") == "claude-code":
|
|
374
|
+
messages = transcript["messages"][:80]
|
|
375
|
+
first_user = next(
|
|
376
|
+
(m["text"] for m in messages if m["role"] == "user" and m["text"]),
|
|
377
|
+
"",
|
|
378
|
+
)
|
|
379
|
+
if first_user:
|
|
380
|
+
root.set_attribute(
|
|
381
|
+
"input.value",
|
|
382
|
+
json.dumps(
|
|
383
|
+
{"task": task_id, "instruction": first_user[:3000]}
|
|
384
|
+
),
|
|
385
|
+
)
|
|
386
|
+
for step, message in enumerate(messages):
|
|
387
|
+
if message["role"] == "assistant":
|
|
388
|
+
with tracer.start_as_current_span(
|
|
389
|
+
f"{step:02d} assistant"
|
|
390
|
+
) as span:
|
|
391
|
+
span.set_attribute("openinference.span.kind", "LLM")
|
|
392
|
+
span.set_attribute(
|
|
393
|
+
"output.value",
|
|
394
|
+
json.dumps(
|
|
395
|
+
{
|
|
396
|
+
"text": message["text"],
|
|
397
|
+
**(
|
|
398
|
+
{"tool_calls": message["tool_uses"]}
|
|
399
|
+
if message["tool_uses"]
|
|
400
|
+
else {}
|
|
401
|
+
),
|
|
402
|
+
},
|
|
403
|
+
default=str,
|
|
404
|
+
)[:4000],
|
|
405
|
+
)
|
|
406
|
+
elif message["tool_results"]:
|
|
407
|
+
with tracer.start_as_current_span(
|
|
408
|
+
f"{step:02d} tool result"
|
|
409
|
+
) as span:
|
|
410
|
+
span.set_attribute("openinference.span.kind", "TOOL")
|
|
411
|
+
span.set_attribute(
|
|
412
|
+
"output.value",
|
|
413
|
+
"\n".join(message["tool_results"])[:4000],
|
|
414
|
+
)
|
|
415
|
+
elif transcript and transcript.get("kind") == "trajectory":
|
|
416
|
+
for step, entry in enumerate(transcript["steps"][:80]):
|
|
417
|
+
if not isinstance(entry, dict):
|
|
418
|
+
continue
|
|
419
|
+
with tracer.start_as_current_span(
|
|
420
|
+
f"{step:02d} {entry.get('type') or entry.get('role') or 'step'}"
|
|
421
|
+
) as span:
|
|
422
|
+
span.set_attribute("openinference.span.kind", "LLM")
|
|
423
|
+
span.set_attribute(
|
|
424
|
+
"output.value", json.dumps(entry, default=str)[:4000]
|
|
425
|
+
)
|
|
426
|
+
emitted += 1
|
|
427
|
+
provider.shutdown()
|
|
428
|
+
return emitted
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def agent_transcript(trial_dir: str) -> Optional[Dict[str, Any]]:
|
|
432
|
+
"""The best agent transcript a trial left behind.
|
|
433
|
+
|
|
434
|
+
Installed agents record their dialogue in agent-specific shapes; the
|
|
435
|
+
state.json convention only covers worlds that export audited tool calls.
|
|
436
|
+
Preference order:
|
|
437
|
+
1. claude-code's session JSONL (agent/sessions/projects/*/<id>.jsonl) —
|
|
438
|
+
the full message stream, one JSON object per line;
|
|
439
|
+
2. harbor's normalized agent/trajectory.json, shape-tolerantly.
|
|
440
|
+
Returns None when a trial recorded neither (e.g. mini-swe, whose
|
|
441
|
+
transcript already flows through state.json-style worlds)."""
|
|
442
|
+
import glob as _glob
|
|
443
|
+
|
|
444
|
+
jsonls = sorted(
|
|
445
|
+
_glob.glob(
|
|
446
|
+
os.path.join(trial_dir, "agent", "sessions", "projects", "*", "*.jsonl")
|
|
447
|
+
),
|
|
448
|
+
key=lambda path: os.path.getmtime(path),
|
|
449
|
+
)
|
|
450
|
+
if jsonls:
|
|
451
|
+
messages: List[Dict[str, Any]] = []
|
|
452
|
+
with open(jsonls[-1], errors="replace") as fh:
|
|
453
|
+
for line in fh:
|
|
454
|
+
try:
|
|
455
|
+
event = json.loads(line)
|
|
456
|
+
except (ValueError, TypeError):
|
|
457
|
+
continue
|
|
458
|
+
message = event.get("message") if isinstance(event, dict) else None
|
|
459
|
+
if not isinstance(message, dict):
|
|
460
|
+
continue
|
|
461
|
+
role = message.get("role")
|
|
462
|
+
if role not in ("user", "assistant"):
|
|
463
|
+
continue
|
|
464
|
+
content = message.get("content")
|
|
465
|
+
text = ""
|
|
466
|
+
tool_uses: List[Dict[str, Any]] = []
|
|
467
|
+
tool_results: List[str] = []
|
|
468
|
+
if isinstance(content, str):
|
|
469
|
+
text = content
|
|
470
|
+
elif isinstance(content, list):
|
|
471
|
+
for block in content:
|
|
472
|
+
if not isinstance(block, dict):
|
|
473
|
+
continue
|
|
474
|
+
if block.get("type") == "text":
|
|
475
|
+
text += str(block.get("text") or "")
|
|
476
|
+
elif block.get("type") == "tool_use":
|
|
477
|
+
tool_uses.append(
|
|
478
|
+
{
|
|
479
|
+
"name": block.get("name"),
|
|
480
|
+
"input": json.dumps(
|
|
481
|
+
block.get("input"), default=str
|
|
482
|
+
)[:600],
|
|
483
|
+
}
|
|
484
|
+
)
|
|
485
|
+
elif block.get("type") == "tool_result":
|
|
486
|
+
raw = block.get("content")
|
|
487
|
+
if isinstance(raw, list):
|
|
488
|
+
raw = "".join(
|
|
489
|
+
str(piece.get("text") or "")
|
|
490
|
+
for piece in raw
|
|
491
|
+
if isinstance(piece, dict)
|
|
492
|
+
)
|
|
493
|
+
tool_results.append(str(raw)[:800])
|
|
494
|
+
if text or tool_uses or tool_results:
|
|
495
|
+
messages.append(
|
|
496
|
+
{
|
|
497
|
+
"role": role,
|
|
498
|
+
"text": text[:2000],
|
|
499
|
+
"tool_uses": tool_uses,
|
|
500
|
+
"tool_results": tool_results,
|
|
501
|
+
}
|
|
502
|
+
)
|
|
503
|
+
if messages:
|
|
504
|
+
return {"kind": "claude-code", "messages": messages}
|
|
505
|
+
|
|
506
|
+
trajectory = _read_json(os.path.join(trial_dir, "agent", "trajectory.json"))
|
|
507
|
+
if trajectory:
|
|
508
|
+
steps = (
|
|
509
|
+
trajectory.get("steps") if isinstance(trajectory, dict) else trajectory
|
|
510
|
+
)
|
|
511
|
+
if isinstance(steps, list) and steps:
|
|
512
|
+
return {"kind": "trajectory", "steps": steps}
|
|
513
|
+
return None
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def harvest_job_dir(
|
|
517
|
+
job_dir: str,
|
|
518
|
+
version_id: str,
|
|
519
|
+
evals_client: GatewayEvalsClient,
|
|
520
|
+
*,
|
|
521
|
+
model: str,
|
|
522
|
+
run_id: Optional[str] = None,
|
|
523
|
+
dispatch_shard: Optional[Dict[str, Any]] = None,
|
|
524
|
+
rollouts_per_example: int = 1,
|
|
525
|
+
) -> Dict[str, Any]:
|
|
526
|
+
"""Harvest a completed harbor jobs directory into a version-pinned
|
|
527
|
+
evaluation: create → push samples → finalize with aggregate metrics."""
|
|
528
|
+
samples = samples_from_job_dir(job_dir)
|
|
529
|
+
if not samples:
|
|
530
|
+
raise EvalHarvestError(f"no harbor trials found under {job_dir}")
|
|
531
|
+
metrics = metrics_from_samples(samples)
|
|
532
|
+
|
|
533
|
+
evaluation_id = evals_client.create_evaluation(
|
|
534
|
+
version_id,
|
|
535
|
+
model=model,
|
|
536
|
+
run_id=run_id,
|
|
537
|
+
**({"dispatch_shard": dispatch_shard} if dispatch_shard is not None else {}),
|
|
538
|
+
num_examples=len({s["exampleId"] for s in samples}),
|
|
539
|
+
rollouts_per_example=rollouts_per_example,
|
|
540
|
+
)
|
|
541
|
+
inserted = evals_client.push_samples(evaluation_id, samples)
|
|
542
|
+
evals_client.finalize_evaluation(evaluation_id, metrics=metrics)
|
|
543
|
+
traced = emit_trial_traces(job_dir, evaluation_id)
|
|
544
|
+
return {
|
|
545
|
+
"evaluationId": evaluation_id,
|
|
546
|
+
"samples": inserted,
|
|
547
|
+
"metrics": metrics,
|
|
548
|
+
"richTraces": traced,
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def harbor_bin() -> str:
|
|
553
|
+
"""The harbor CLI: on PATH, or fall back to the Pier tool venv (Pier depends
|
|
554
|
+
on the same harbor package)."""
|
|
555
|
+
found = shutil.which("harbor")
|
|
556
|
+
if found:
|
|
557
|
+
return found
|
|
558
|
+
pier_sibling = os.path.expanduser("~/.local/share/uv/tools/datacurve-pier/bin/harbor")
|
|
559
|
+
if os.path.exists(pier_sibling):
|
|
560
|
+
return pier_sibling
|
|
561
|
+
raise EvalHarvestError(
|
|
562
|
+
"harbor CLI not found — `uv tool install harbor` (or install datacurve-pier)."
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
DEFAULT_HARBOR_AGENT = "mini-swe-agent"
|
|
567
|
+
|
|
568
|
+
# Env vars forwarded into the agent sandbox when present — installed agents
|
|
569
|
+
# authenticate their own model calls (mini-swe-agent via OpenRouter/litellm,
|
|
570
|
+
# claude-code via the Anthropic API, goose via provider envs). OPENAI_HOST
|
|
571
|
+
# lets goose's openai provider point at any OpenAI-compatible endpoint
|
|
572
|
+
# (e.g. OPENAI_API_KEY=$OPENROUTER_API_KEY + OPENAI_HOST=https://openrouter.ai/api).
|
|
573
|
+
AGENT_ENV_PASSTHROUGH = (
|
|
574
|
+
"OPENROUTER_API_KEY",
|
|
575
|
+
"ANTHROPIC_API_KEY",
|
|
576
|
+
"OPENAI_API_KEY",
|
|
577
|
+
"OPENAI_HOST",
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def find_mcp_config(src: str) -> Optional[str]:
|
|
582
|
+
"""An env-bundled Claude-style MCP config: <src>/mcp.json (local source
|
|
583
|
+
dirs) or <src>/tasks/mcp.json (what survives the push archiver)."""
|
|
584
|
+
for candidate in (os.path.join(src, "mcp.json"), os.path.join(src, "tasks", "mcp.json")):
|
|
585
|
+
if os.path.isfile(candidate):
|
|
586
|
+
return candidate
|
|
587
|
+
return None
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def resolve_harbor_model(model: str, agent: str) -> str:
|
|
591
|
+
"""mini-swe-agent speaks litellm ids — default to the OpenRouter provider
|
|
592
|
+
prefix. Other agents (claude-code, codex, …) take their provider's native
|
|
593
|
+
ids verbatim."""
|
|
594
|
+
if agent == DEFAULT_HARBOR_AGENT and not model.startswith("openrouter/"):
|
|
595
|
+
return f"openrouter/{model}"
|
|
596
|
+
return model
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def run_harbor_job(
|
|
600
|
+
tasks_dir: str,
|
|
601
|
+
jobs_dir: str,
|
|
602
|
+
*,
|
|
603
|
+
model: str,
|
|
604
|
+
agent: str = DEFAULT_HARBOR_AGENT,
|
|
605
|
+
sandbox_env: Optional[str] = None,
|
|
606
|
+
num_examples: int = -1,
|
|
607
|
+
rollouts_per_example: int = 1,
|
|
608
|
+
n_concurrent: int = 4,
|
|
609
|
+
extra_env: Optional[Dict[str, str]] = None,
|
|
610
|
+
mcp_config: Optional[str] = None,
|
|
611
|
+
) -> str:
|
|
612
|
+
"""Run the first-party harbor engine over a Harbor task dir. Returns the
|
|
613
|
+
job output dir. Exit-code tolerant: harbor exits non-zero when trials fail;
|
|
614
|
+
the reward files still land and the harvest reflects them honestly."""
|
|
615
|
+
resolved_sandbox = sandbox_env or os.environ.get("HARBOR_SANDBOX_ENV", "docker")
|
|
616
|
+
# Trial concurrency is a HOST-CAPACITY knob, not a task knob: each harbor
|
|
617
|
+
# task composes several docker networks, and a host whose address pools
|
|
618
|
+
# are exhausted fails every subsequent trial in ~1s with reward null
|
|
619
|
+
# (run cmt6a2bzc: 2 shards x -n 4 on one instance -> 42/50 trials never
|
|
620
|
+
# ran). HARBOR_N_CONCURRENT lets the dispatcher size runs to the host
|
|
621
|
+
# without a code change.
|
|
622
|
+
env_concurrency = os.environ.get("HARBOR_N_CONCURRENT", "").strip()
|
|
623
|
+
resolved_concurrency = (
|
|
624
|
+
int(env_concurrency) if env_concurrency.isdigit() else n_concurrent
|
|
625
|
+
)
|
|
626
|
+
if resolved_sandbox == "docker":
|
|
627
|
+
# Leftover networks from crashed prior trials shrink the same address
|
|
628
|
+
# pools; a run should never inherit a previous run's leak. Unused
|
|
629
|
+
# networks only — running trials are untouched.
|
|
630
|
+
subprocess.run(
|
|
631
|
+
["docker", "network", "prune", "-f"],
|
|
632
|
+
check=False,
|
|
633
|
+
capture_output=True,
|
|
634
|
+
)
|
|
635
|
+
cmd = [
|
|
636
|
+
harbor_bin(),
|
|
637
|
+
"run",
|
|
638
|
+
"-p", tasks_dir,
|
|
639
|
+
"-a", agent,
|
|
640
|
+
"-m", model,
|
|
641
|
+
"-e", resolved_sandbox,
|
|
642
|
+
"-o", jobs_dir,
|
|
643
|
+
"-k", str(max(1, rollouts_per_example)),
|
|
644
|
+
"-n", str(max(1, resolved_concurrency)),
|
|
645
|
+
"-y",
|
|
646
|
+
"-q",
|
|
647
|
+
]
|
|
648
|
+
if num_examples and num_examples > 0:
|
|
649
|
+
cmd += ["-l", str(num_examples)]
|
|
650
|
+
if mcp_config:
|
|
651
|
+
cmd += ["--mcp-config", mcp_config]
|
|
652
|
+
|
|
653
|
+
env_pairs: Dict[str, str] = {
|
|
654
|
+
key: os.environ[key]
|
|
655
|
+
for key in AGENT_ENV_PASSTHROUGH
|
|
656
|
+
if os.environ.get(key)
|
|
657
|
+
}
|
|
658
|
+
env_pairs.update(extra_env or {})
|
|
659
|
+
for key, value in env_pairs.items():
|
|
660
|
+
if value:
|
|
661
|
+
cmd += ["--ae", f"{key}={value}"]
|
|
662
|
+
|
|
663
|
+
# Redact env values from the printed command line.
|
|
664
|
+
printable = " ".join(
|
|
665
|
+
"--ae " + arg.split("=", 1)[0] + "=***" if i and cmd[i - 1] == "--ae" else arg
|
|
666
|
+
for i, arg in enumerate(cmd)
|
|
667
|
+
if arg != "--ae"
|
|
668
|
+
)
|
|
669
|
+
print(f"[harbor] {printable}", flush=True)
|
|
670
|
+
proc = subprocess.run(cmd)
|
|
671
|
+
if proc.returncode != 0:
|
|
672
|
+
print(f"[harbor] exited {proc.returncode} (harvesting whatever ran)", flush=True)
|
|
673
|
+
return jobs_dir
|