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,766 @@
|
|
|
1
|
+
# Copyright (c) Gateway. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Instrumentor for claude-agent-sdk.
|
|
4
|
+
|
|
5
|
+
This module provides automatic instrumentation for the Claude Agent SDK,
|
|
6
|
+
capturing conversation turns, tool calls, and response metadata.
|
|
7
|
+
|
|
8
|
+
Spans are annotated with semantic attributes following Gateway conventions,
|
|
9
|
+
ensuring proper processing and display in the Gateway dashboard.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import functools
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
from typing import TYPE_CHECKING, Any, AsyncIterator, Optional
|
|
18
|
+
|
|
19
|
+
from opentelemetry import trace
|
|
20
|
+
from opentelemetry.context import Context
|
|
21
|
+
from opentelemetry.trace import Span, SpanKind, Status, StatusCode
|
|
22
|
+
|
|
23
|
+
from ..semconv import ( # Gateway observation attributes (primary namespace, resolved by attributeAliases.ts); OpenTelemetry GenAI attributes (widely supported); OpenInference attributes; GatewaySDK custom attributes; Claude-specific attributes
|
|
24
|
+
CLAUDE_API_ERROR,
|
|
25
|
+
CLAUDE_ERRORS,
|
|
26
|
+
CLAUDE_MESSAGE_UUID,
|
|
27
|
+
CLAUDE_MODEL_USAGE,
|
|
28
|
+
CLAUDE_PARENT_TOOL_USE_ID,
|
|
29
|
+
CLAUDE_RATE_LIMIT_STATUS,
|
|
30
|
+
CLAUDE_RATE_LIMIT_TYPE,
|
|
31
|
+
CLAUDE_STOP_REASON,
|
|
32
|
+
CLAUDE_STRUCTURED_OUTPUT,
|
|
33
|
+
CLAUDE_SYSTEM_DATA,
|
|
34
|
+
CLAUDE_SYSTEM_SUBTYPE,
|
|
35
|
+
CLAUDE_THINKING_SIGNATURE,
|
|
36
|
+
GEN_AI_CONVERSATION_ID,
|
|
37
|
+
GEN_AI_OPERATION_NAME,
|
|
38
|
+
GEN_AI_REQUEST_MODEL,
|
|
39
|
+
GEN_AI_RESPONSE_FINISH_REASONS,
|
|
40
|
+
GEN_AI_RESPONSE_ID,
|
|
41
|
+
GEN_AI_RESPONSE_MODEL,
|
|
42
|
+
GEN_AI_SYSTEM,
|
|
43
|
+
GEN_AI_TOOL_CALL_ARGUMENTS,
|
|
44
|
+
GEN_AI_TOOL_CALL_RESULT,
|
|
45
|
+
GEN_AI_TOOL_NAME,
|
|
46
|
+
GEN_AI_USAGE_COST,
|
|
47
|
+
GEN_AI_USAGE_INPUT_TOKENS,
|
|
48
|
+
GEN_AI_USAGE_OUTPUT_TOKENS,
|
|
49
|
+
INPUT_MIME_TYPE,
|
|
50
|
+
INPUT_VALUE,
|
|
51
|
+
LLM_MODEL_NAME,
|
|
52
|
+
LLM_PROVIDER,
|
|
53
|
+
LLM_SYSTEM,
|
|
54
|
+
LLM_TOKEN_COUNT_COMPLETION,
|
|
55
|
+
LLM_TOKEN_COUNT_PROMPT,
|
|
56
|
+
LLM_TOKEN_COUNT_TOTAL,
|
|
57
|
+
GATEWAY_DURATION_API_MS,
|
|
58
|
+
GATEWAY_DURATION_MS,
|
|
59
|
+
GATEWAY_LLM_COST_USD,
|
|
60
|
+
GATEWAY_LLM_THINKING,
|
|
61
|
+
GATEWAY_NUM_TURNS,
|
|
62
|
+
GATEWAY_TOOL_IS_ERROR,
|
|
63
|
+
MIME_TYPE_JSON,
|
|
64
|
+
MIME_TYPE_TEXT,
|
|
65
|
+
MODEL_NAME,
|
|
66
|
+
OBSERVATION_INPUT,
|
|
67
|
+
OBSERVATION_OUTPUT,
|
|
68
|
+
OPENINFERENCE_SPAN_KIND,
|
|
69
|
+
OUTPUT_MIME_TYPE,
|
|
70
|
+
OUTPUT_VALUE,
|
|
71
|
+
SESSION_ID,
|
|
72
|
+
SPAN_KIND_LLM,
|
|
73
|
+
SPAN_KIND_TOOL,
|
|
74
|
+
TOOL_NAME,
|
|
75
|
+
TRACE_NAME,
|
|
76
|
+
)
|
|
77
|
+
from .registry import BaseInstrumentor, _is_package_available
|
|
78
|
+
|
|
79
|
+
if TYPE_CHECKING:
|
|
80
|
+
from openinference.instrumentation import TraceConfig
|
|
81
|
+
|
|
82
|
+
logger = logging.getLogger(__name__)
|
|
83
|
+
|
|
84
|
+
# Tracer name for claude-agent-sdk spans
|
|
85
|
+
TRACER_NAME = "gatewaysdk.instrumentation.claude_agent_sdk"
|
|
86
|
+
|
|
87
|
+
# Attribute to store span on client instance
|
|
88
|
+
_CLIENT_SPAN_ATTR = "_gatewaysdk_conversation_span"
|
|
89
|
+
|
|
90
|
+
# Session IDs that indicate "no real session was provided" — ResultMessage session_id
|
|
91
|
+
# should only override the span when it is NOT one of these placeholder values.
|
|
92
|
+
# "default" is the instrumented_query() default parameter; placeholder values from
|
|
93
|
+
# test helpers or SDK internals should not clobber a user-provided session_id.
|
|
94
|
+
_SESSION_ID_PLACEHOLDERS = frozenset({"default"})
|
|
95
|
+
|
|
96
|
+
# Max length for auto-generated trace names derived from the prompt.
|
|
97
|
+
_TRACE_NAME_MAX_LEN = 80
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ClaudeAgentSDKInstrumentor(BaseInstrumentor):
|
|
101
|
+
"""Instrumentor for claude-agent-sdk.
|
|
102
|
+
|
|
103
|
+
This instrumentor wraps the ClaudeSDKClient to automatically create
|
|
104
|
+
OpenTelemetry spans for:
|
|
105
|
+
- Conversation turns (query calls)
|
|
106
|
+
- Tool executions
|
|
107
|
+
- Response processing
|
|
108
|
+
|
|
109
|
+
Example::
|
|
110
|
+
|
|
111
|
+
import gatewaysdk.tracing as tracing
|
|
112
|
+
|
|
113
|
+
# Auto-instruments claude-agent-sdk if installed
|
|
114
|
+
tracing.init()
|
|
115
|
+
|
|
116
|
+
# Now all ClaudeSDKClient usage is traced
|
|
117
|
+
from claude_agent_sdk import ClaudeSDKClient
|
|
118
|
+
client = ClaudeSDKClient()
|
|
119
|
+
# ...
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self):
|
|
123
|
+
self._instrumented = False
|
|
124
|
+
self._original_query: Optional[Any] = None
|
|
125
|
+
self._original_receive_messages: Optional[Any] = None
|
|
126
|
+
self._original_receive_response: Optional[Any] = None
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def name(self) -> str:
|
|
130
|
+
return "claude_agent_sdk"
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def package_name(self) -> str:
|
|
134
|
+
return "claude_agent_sdk"
|
|
135
|
+
|
|
136
|
+
def is_available(self) -> bool:
|
|
137
|
+
"""Check if claude-agent-sdk is installed."""
|
|
138
|
+
return _is_package_available("claude_agent_sdk")
|
|
139
|
+
|
|
140
|
+
def instrument(self, trace_config: Optional["TraceConfig"] = None) -> None:
|
|
141
|
+
"""Activate instrumentation for claude-agent-sdk.
|
|
142
|
+
|
|
143
|
+
This wraps ClaudeSDKClient.query() and receive_messages() to
|
|
144
|
+
automatically create spans for conversation turns.
|
|
145
|
+
"""
|
|
146
|
+
if self._instrumented:
|
|
147
|
+
logger.debug("claude_agent_sdk already instrumented")
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
if not self.is_available():
|
|
151
|
+
logger.debug("claude_agent_sdk not available, skipping instrumentation")
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
from claude_agent_sdk import ClaudeSDKClient
|
|
156
|
+
|
|
157
|
+
# Store original methods
|
|
158
|
+
self._original_query = ClaudeSDKClient.query
|
|
159
|
+
self._original_receive_messages = ClaudeSDKClient.receive_messages
|
|
160
|
+
|
|
161
|
+
# Create instrumented versions
|
|
162
|
+
instrumented_query = self._create_instrumented_query(self._original_query)
|
|
163
|
+
instrumented_receive_messages = self._create_instrumented_receive_messages(self._original_receive_messages)
|
|
164
|
+
|
|
165
|
+
# Patch the methods
|
|
166
|
+
ClaudeSDKClient.query = instrumented_query
|
|
167
|
+
ClaudeSDKClient.receive_messages = instrumented_receive_messages
|
|
168
|
+
|
|
169
|
+
# NOTE: do NOT also wrap receive_response(). It is a thin convenience
|
|
170
|
+
# method that internally does `async for m in self.receive_messages()`
|
|
171
|
+
# — i.e. it delegates to the (already patched) receive_messages. Also
|
|
172
|
+
# wrapping it double-instruments every message when a caller uses
|
|
173
|
+
# receive_response(): duplicated TOOL/GENERATION spans and a nested
|
|
174
|
+
# attach()/detach() that raises "Token was created in a different
|
|
175
|
+
# Context". Patching receive_messages alone covers both entry points.
|
|
176
|
+
self._original_receive_response = None
|
|
177
|
+
|
|
178
|
+
self._instrumented = True
|
|
179
|
+
logger.info("Instrumented claude_agent_sdk")
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.warning("Failed to instrument claude_agent_sdk: %s", e)
|
|
183
|
+
raise
|
|
184
|
+
|
|
185
|
+
def uninstrument(self) -> None:
|
|
186
|
+
"""Deactivate instrumentation for claude-agent-sdk."""
|
|
187
|
+
if not self._instrumented:
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
from claude_agent_sdk import ClaudeSDKClient
|
|
192
|
+
|
|
193
|
+
# Restore original methods
|
|
194
|
+
if self._original_query is not None:
|
|
195
|
+
ClaudeSDKClient.query = self._original_query
|
|
196
|
+
if self._original_receive_messages is not None:
|
|
197
|
+
ClaudeSDKClient.receive_messages = self._original_receive_messages
|
|
198
|
+
if self._original_receive_response is not None:
|
|
199
|
+
ClaudeSDKClient.receive_response = self._original_receive_response
|
|
200
|
+
|
|
201
|
+
self._instrumented = False
|
|
202
|
+
self._original_query = None
|
|
203
|
+
self._original_receive_messages = None
|
|
204
|
+
self._original_receive_response = None
|
|
205
|
+
logger.info("Uninstrumented claude_agent_sdk")
|
|
206
|
+
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.warning("Failed to uninstrument claude_agent_sdk: %s", e)
|
|
209
|
+
|
|
210
|
+
def _create_instrumented_query(self, original_query):
|
|
211
|
+
"""Create an instrumented version of ClaudeSDKClient.query().
|
|
212
|
+
|
|
213
|
+
This creates a span and stores it on the client instance so that
|
|
214
|
+
receive_messages() can add output attributes to it later.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
@functools.wraps(original_query)
|
|
218
|
+
async def instrumented_query(
|
|
219
|
+
client_self,
|
|
220
|
+
prompt: str | Any,
|
|
221
|
+
session_id: str = "default",
|
|
222
|
+
) -> None:
|
|
223
|
+
tracer = trace.get_tracer(TRACER_NAME)
|
|
224
|
+
|
|
225
|
+
# Get model from client options
|
|
226
|
+
model = getattr(client_self.options, "model", None) or "claude"
|
|
227
|
+
|
|
228
|
+
# Create span as a NEW TRACE (fresh context = each turn is its own trace)
|
|
229
|
+
span = tracer.start_span(
|
|
230
|
+
"llm.conversation_turn",
|
|
231
|
+
kind=SpanKind.CLIENT,
|
|
232
|
+
context=Context(), # Empty context creates a new root trace
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Record input using multiple conventions for compatibility
|
|
236
|
+
input_value = prompt if isinstance(prompt, str) else str(prompt)
|
|
237
|
+
|
|
238
|
+
# Langfuse attributes (REQUIRED for OtelIngestionProcessor to extract I/O)
|
|
239
|
+
span.set_attribute(OBSERVATION_INPUT, input_value)
|
|
240
|
+
span.set_attribute(MODEL_NAME, model)
|
|
241
|
+
|
|
242
|
+
# OpenInference attributes
|
|
243
|
+
span.set_attribute(OPENINFERENCE_SPAN_KIND, SPAN_KIND_LLM)
|
|
244
|
+
span.set_attribute(INPUT_VALUE, input_value)
|
|
245
|
+
span.set_attribute(INPUT_MIME_TYPE, MIME_TYPE_TEXT)
|
|
246
|
+
span.set_attribute(LLM_MODEL_NAME, model)
|
|
247
|
+
span.set_attribute(LLM_SYSTEM, "anthropic")
|
|
248
|
+
span.set_attribute(LLM_PROVIDER, "anthropic")
|
|
249
|
+
|
|
250
|
+
# GenAI attributes
|
|
251
|
+
span.set_attribute(GEN_AI_REQUEST_MODEL, model)
|
|
252
|
+
span.set_attribute(GEN_AI_SYSTEM, "anthropic")
|
|
253
|
+
span.set_attribute(GEN_AI_OPERATION_NAME, "chat")
|
|
254
|
+
|
|
255
|
+
# Session ID — only set when the caller provided a real value.
|
|
256
|
+
# Placeholders like "default" are skipped to avoid creating ghost
|
|
257
|
+
# sessions in Gateway that group all sessionless traces together.
|
|
258
|
+
if session_id not in _SESSION_ID_PLACEHOLDERS:
|
|
259
|
+
span.set_attribute(SESSION_ID, session_id)
|
|
260
|
+
span.set_attribute(GEN_AI_CONVERSATION_ID, session_id)
|
|
261
|
+
else:
|
|
262
|
+
# Auto-generate a session ID per client instance so conversations
|
|
263
|
+
# are grouped without requiring the user to pass one manually.
|
|
264
|
+
auto_session = getattr(client_self, "_gatewaysdk_auto_session_id", None)
|
|
265
|
+
if auto_session is None:
|
|
266
|
+
import uuid
|
|
267
|
+
|
|
268
|
+
auto_session = f"claude-{uuid.uuid4().hex[:12]}"
|
|
269
|
+
client_self._gatewaysdk_auto_session_id = auto_session
|
|
270
|
+
span.set_attribute(SESSION_ID, auto_session)
|
|
271
|
+
span.set_attribute(GEN_AI_CONVERSATION_ID, auto_session)
|
|
272
|
+
|
|
273
|
+
# Trace name — derive from the prompt so traces are distinguishable
|
|
274
|
+
# in the Gateway UI. Truncate to keep names readable.
|
|
275
|
+
trace_label = input_value.strip().replace("\n", " ")
|
|
276
|
+
if len(trace_label) > _TRACE_NAME_MAX_LEN:
|
|
277
|
+
trace_label = trace_label[:_TRACE_NAME_MAX_LEN] + "…"
|
|
278
|
+
span.set_attribute(TRACE_NAME, trace_label)
|
|
279
|
+
|
|
280
|
+
# Store span on client instance for receive_messages to use
|
|
281
|
+
setattr(client_self, _CLIENT_SPAN_ATTR, span)
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
# Call original method
|
|
285
|
+
result = await original_query(client_self, prompt, session_id)
|
|
286
|
+
return result
|
|
287
|
+
except Exception as e:
|
|
288
|
+
# On error, end the span here
|
|
289
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
290
|
+
span.record_exception(e)
|
|
291
|
+
span.end()
|
|
292
|
+
# Clear the stored span
|
|
293
|
+
if hasattr(client_self, _CLIENT_SPAN_ATTR):
|
|
294
|
+
delattr(client_self, _CLIENT_SPAN_ATTR)
|
|
295
|
+
raise
|
|
296
|
+
|
|
297
|
+
return instrumented_query
|
|
298
|
+
|
|
299
|
+
def _create_instrumented_receive_messages(self, original_receive_messages):
|
|
300
|
+
"""Create an instrumented version of ClaudeSDKClient.receive_messages().
|
|
301
|
+
|
|
302
|
+
This uses the span created by query() to record output attributes,
|
|
303
|
+
then ends the span when ResultMessage is received.
|
|
304
|
+
|
|
305
|
+
The output is structured as a sequence of blocks to preserve the order:
|
|
306
|
+
text → tool_call → text (if that's how the response came)
|
|
307
|
+
|
|
308
|
+
Tool calls appear as child spans under the conversation turn span.
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
@functools.wraps(original_receive_messages)
|
|
312
|
+
async def instrumented_receive_messages(client_self) -> AsyncIterator[Any]:
|
|
313
|
+
tracer = trace.get_tracer(TRACER_NAME)
|
|
314
|
+
|
|
315
|
+
# Import message types
|
|
316
|
+
from claude_agent_sdk import (
|
|
317
|
+
AssistantMessage,
|
|
318
|
+
ResultMessage,
|
|
319
|
+
SystemMessage,
|
|
320
|
+
TextBlock,
|
|
321
|
+
ThinkingBlock,
|
|
322
|
+
ToolResultBlock,
|
|
323
|
+
ToolUseBlock,
|
|
324
|
+
UserMessage,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Import new message types with graceful fallback for older SDK versions
|
|
328
|
+
try:
|
|
329
|
+
from claude_agent_sdk import TaskStartedMessage
|
|
330
|
+
except ImportError:
|
|
331
|
+
TaskStartedMessage = None
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
from claude_agent_sdk import TaskProgressMessage
|
|
335
|
+
except ImportError:
|
|
336
|
+
TaskProgressMessage = None
|
|
337
|
+
|
|
338
|
+
try:
|
|
339
|
+
from claude_agent_sdk import TaskNotificationMessage
|
|
340
|
+
except ImportError:
|
|
341
|
+
TaskNotificationMessage = None
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
from claude_agent_sdk import RateLimitEvent
|
|
345
|
+
except ImportError:
|
|
346
|
+
RateLimitEvent = None
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
from claude_agent_sdk import StreamEvent
|
|
350
|
+
except ImportError:
|
|
351
|
+
StreamEvent = None
|
|
352
|
+
|
|
353
|
+
# Get the span created by query()
|
|
354
|
+
conversation_span: Optional[Span] = getattr(client_self, _CLIENT_SPAN_ATTR, None)
|
|
355
|
+
|
|
356
|
+
# Parent context for the spans this instrumentor creates. Those spans
|
|
357
|
+
# are started with context=parent_ctx explicitly (see below), so we do
|
|
358
|
+
# NOT attach() it for the generator's lifetime. Holding an attached
|
|
359
|
+
# context across the generator's yields makes the teardown detach()
|
|
360
|
+
# run in a different context and log a benign "Failed to detach
|
|
361
|
+
# context"; and since the Claude Agent SDK executes tools in a separate
|
|
362
|
+
# subprocess, an attached parent-process context never reached them
|
|
363
|
+
# anyway. Explicit parent context gives correct nesting with no noise.
|
|
364
|
+
parent_ctx = None
|
|
365
|
+
if conversation_span:
|
|
366
|
+
parent_ctx = trace.set_span_in_context(conversation_span)
|
|
367
|
+
|
|
368
|
+
# Track state for this conversation turn
|
|
369
|
+
output_blocks = [] # Sequence of blocks: text, tool_call, text...
|
|
370
|
+
collected_thinking = []
|
|
371
|
+
thinking_signatures = [] # ThinkingBlock signatures
|
|
372
|
+
tool_spans = {} # Map tool_use_id to span
|
|
373
|
+
current_text_buffer = []
|
|
374
|
+
system_events = [] # SystemMessage events
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
async for message in original_receive_messages(client_self):
|
|
378
|
+
if isinstance(message, AssistantMessage):
|
|
379
|
+
model = getattr(message, "model", None)
|
|
380
|
+
if conversation_span and conversation_span.is_recording():
|
|
381
|
+
# Update model from response (GenAI convention)
|
|
382
|
+
if model:
|
|
383
|
+
conversation_span.set_attribute(GEN_AI_RESPONSE_MODEL, model)
|
|
384
|
+
conversation_span.set_attribute(MODEL_NAME, model)
|
|
385
|
+
conversation_span.set_attribute(LLM_MODEL_NAME, model)
|
|
386
|
+
|
|
387
|
+
# Capture API error if present
|
|
388
|
+
if message.error:
|
|
389
|
+
conversation_span.set_attribute(CLAUDE_API_ERROR, message.error)
|
|
390
|
+
|
|
391
|
+
# Capture parent_tool_use_id for sub-agent tracking
|
|
392
|
+
if message.parent_tool_use_id:
|
|
393
|
+
conversation_span.set_attribute(CLAUDE_PARENT_TOOL_USE_ID, message.parent_tool_use_id)
|
|
394
|
+
|
|
395
|
+
# Message ID for correlation with Anthropic logs
|
|
396
|
+
if hasattr(message, "message_id") and message.message_id:
|
|
397
|
+
conversation_span.set_attribute(GEN_AI_RESPONSE_ID, message.message_id)
|
|
398
|
+
|
|
399
|
+
# Per-turn stop reason
|
|
400
|
+
if hasattr(message, "stop_reason") and message.stop_reason:
|
|
401
|
+
conversation_span.set_attribute(GEN_AI_RESPONSE_FINISH_REASONS, [message.stop_reason])
|
|
402
|
+
|
|
403
|
+
# Per-turn usage (captured as events since there may be multiple turns)
|
|
404
|
+
if hasattr(message, "usage") and message.usage:
|
|
405
|
+
usage_attrs = {}
|
|
406
|
+
input_tokens = message.usage.get("input_tokens")
|
|
407
|
+
output_tokens = message.usage.get("output_tokens")
|
|
408
|
+
if input_tokens is not None:
|
|
409
|
+
usage_attrs[GEN_AI_USAGE_INPUT_TOKENS] = input_tokens
|
|
410
|
+
if output_tokens is not None:
|
|
411
|
+
usage_attrs[GEN_AI_USAGE_OUTPUT_TOKENS] = output_tokens
|
|
412
|
+
if usage_attrs:
|
|
413
|
+
conversation_span.add_event("turn_usage", attributes=usage_attrs)
|
|
414
|
+
|
|
415
|
+
for block in message.content:
|
|
416
|
+
if isinstance(block, TextBlock):
|
|
417
|
+
current_text_buffer.append(block.text)
|
|
418
|
+
|
|
419
|
+
elif isinstance(block, ThinkingBlock):
|
|
420
|
+
collected_thinking.append(block.thinking)
|
|
421
|
+
# Capture thinking signature if present
|
|
422
|
+
if hasattr(block, "signature") and block.signature:
|
|
423
|
+
thinking_signatures.append(block.signature)
|
|
424
|
+
|
|
425
|
+
elif isinstance(block, ToolUseBlock):
|
|
426
|
+
# Flush any buffered text before tool call
|
|
427
|
+
if current_text_buffer:
|
|
428
|
+
output_blocks.append({"type": "text", "content": "\n".join(current_text_buffer)})
|
|
429
|
+
current_text_buffer = []
|
|
430
|
+
|
|
431
|
+
# Add tool call block (will be updated with result)
|
|
432
|
+
output_blocks.append(
|
|
433
|
+
{
|
|
434
|
+
"type": "tool_call",
|
|
435
|
+
"name": block.name,
|
|
436
|
+
"id": block.id,
|
|
437
|
+
"input": block.input,
|
|
438
|
+
"output": None,
|
|
439
|
+
}
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# Create a child span for this tool call
|
|
443
|
+
# Note: Span timing is based on when we receive ToolUseBlock/ToolResultBlock messages,
|
|
444
|
+
# not when the tool actually executes in Claude CLI. This may cause timing inaccuracies.
|
|
445
|
+
tool_span = tracer.start_span(
|
|
446
|
+
f"tool.{block.name}",
|
|
447
|
+
kind=SpanKind.INTERNAL,
|
|
448
|
+
context=parent_ctx,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
# OpenInference attributes
|
|
452
|
+
tool_span.set_attribute(OPENINFERENCE_SPAN_KIND, SPAN_KIND_TOOL)
|
|
453
|
+
tool_span.set_attribute(TOOL_NAME, block.name)
|
|
454
|
+
|
|
455
|
+
# GenAI attributes
|
|
456
|
+
tool_span.set_attribute(GEN_AI_TOOL_NAME, block.name)
|
|
457
|
+
tool_span.set_attribute(GEN_AI_OPERATION_NAME, "tool_call")
|
|
458
|
+
|
|
459
|
+
try:
|
|
460
|
+
input_json = json.dumps(block.input)
|
|
461
|
+
# GenAI
|
|
462
|
+
tool_span.set_attribute(GEN_AI_TOOL_CALL_ARGUMENTS, input_json)
|
|
463
|
+
# Langfuse
|
|
464
|
+
tool_span.set_attribute(OBSERVATION_INPUT, input_json)
|
|
465
|
+
# OpenInference
|
|
466
|
+
tool_span.set_attribute(INPUT_VALUE, input_json)
|
|
467
|
+
tool_span.set_attribute(INPUT_MIME_TYPE, MIME_TYPE_JSON)
|
|
468
|
+
except (TypeError, ValueError):
|
|
469
|
+
input_str = str(block.input)
|
|
470
|
+
tool_span.set_attribute(GEN_AI_TOOL_CALL_ARGUMENTS, input_str)
|
|
471
|
+
tool_span.set_attribute(OBSERVATION_INPUT, input_str)
|
|
472
|
+
tool_span.set_attribute(INPUT_VALUE, input_str)
|
|
473
|
+
tool_span.set_attribute(INPUT_MIME_TYPE, MIME_TYPE_TEXT)
|
|
474
|
+
|
|
475
|
+
tool_spans[block.id] = tool_span
|
|
476
|
+
|
|
477
|
+
elif isinstance(message, UserMessage):
|
|
478
|
+
# Capture UserMessage uuid and parent_tool_use_id if present
|
|
479
|
+
if conversation_span and conversation_span.is_recording():
|
|
480
|
+
if hasattr(message, "uuid") and message.uuid:
|
|
481
|
+
conversation_span.set_attribute(CLAUDE_MESSAGE_UUID, message.uuid)
|
|
482
|
+
if hasattr(message, "parent_tool_use_id") and message.parent_tool_use_id:
|
|
483
|
+
conversation_span.set_attribute(CLAUDE_PARENT_TOOL_USE_ID, message.parent_tool_use_id)
|
|
484
|
+
|
|
485
|
+
# Check for tool results
|
|
486
|
+
if isinstance(message.content, list):
|
|
487
|
+
for block in message.content:
|
|
488
|
+
if isinstance(block, ToolResultBlock):
|
|
489
|
+
tool_span = tool_spans.get(block.tool_use_id)
|
|
490
|
+
content_str = str(block.content) if block.content else ""
|
|
491
|
+
is_error = getattr(block, "is_error", False) or False
|
|
492
|
+
|
|
493
|
+
# Update the tool_call block in output_blocks
|
|
494
|
+
for ob in output_blocks:
|
|
495
|
+
if ob.get("type") == "tool_call" and ob.get("id") == block.tool_use_id:
|
|
496
|
+
ob["output"] = content_str
|
|
497
|
+
ob["is_error"] = is_error
|
|
498
|
+
break
|
|
499
|
+
|
|
500
|
+
if tool_span:
|
|
501
|
+
# GenAI
|
|
502
|
+
tool_span.set_attribute(GEN_AI_TOOL_CALL_RESULT, content_str)
|
|
503
|
+
# Langfuse
|
|
504
|
+
tool_span.set_attribute(OBSERVATION_OUTPUT, content_str)
|
|
505
|
+
# OpenInference
|
|
506
|
+
tool_span.set_attribute(OUTPUT_VALUE, content_str)
|
|
507
|
+
# Determine output mime type based on content (validate JSON)
|
|
508
|
+
output_mime = MIME_TYPE_TEXT
|
|
509
|
+
stripped = content_str.strip()
|
|
510
|
+
if stripped and stripped[0] in ("{", "["):
|
|
511
|
+
try:
|
|
512
|
+
json.loads(content_str)
|
|
513
|
+
output_mime = MIME_TYPE_JSON
|
|
514
|
+
except (json.JSONDecodeError, ValueError):
|
|
515
|
+
pass
|
|
516
|
+
tool_span.set_attribute(OUTPUT_MIME_TYPE, output_mime)
|
|
517
|
+
# GatewaySDK
|
|
518
|
+
tool_span.set_attribute(GATEWAY_TOOL_IS_ERROR, is_error)
|
|
519
|
+
|
|
520
|
+
if is_error:
|
|
521
|
+
tool_span.set_status(Status(StatusCode.ERROR, content_str))
|
|
522
|
+
else:
|
|
523
|
+
tool_span.set_status(Status(StatusCode.OK))
|
|
524
|
+
|
|
525
|
+
tool_span.end()
|
|
526
|
+
del tool_spans[block.tool_use_id]
|
|
527
|
+
|
|
528
|
+
# Task messages MUST be checked before SystemMessage because
|
|
529
|
+
# they are subclasses of SystemMessage — isinstance(TaskStartedMessage(), SystemMessage) is True.
|
|
530
|
+
elif TaskStartedMessage is not None and isinstance(message, TaskStartedMessage):
|
|
531
|
+
if conversation_span and conversation_span.is_recording():
|
|
532
|
+
event_attrs = {}
|
|
533
|
+
if hasattr(message, "task_id") and message.task_id:
|
|
534
|
+
event_attrs["claude.task.id"] = message.task_id
|
|
535
|
+
if hasattr(message, "description") and message.description:
|
|
536
|
+
event_attrs["claude.task.description"] = message.description
|
|
537
|
+
if hasattr(message, "tool_use_id") and message.tool_use_id:
|
|
538
|
+
event_attrs["claude.task.tool_use_id"] = message.tool_use_id
|
|
539
|
+
if hasattr(message, "task_type") and message.task_type:
|
|
540
|
+
event_attrs["claude.task.type"] = message.task_type
|
|
541
|
+
if hasattr(message, "session_id") and message.session_id:
|
|
542
|
+
event_attrs["claude.task.session_id"] = message.session_id
|
|
543
|
+
conversation_span.add_event("task.started", attributes=event_attrs)
|
|
544
|
+
|
|
545
|
+
elif TaskProgressMessage is not None and isinstance(message, TaskProgressMessage):
|
|
546
|
+
if conversation_span and conversation_span.is_recording():
|
|
547
|
+
event_attrs = {}
|
|
548
|
+
if hasattr(message, "task_id") and message.task_id:
|
|
549
|
+
event_attrs["claude.task.id"] = message.task_id
|
|
550
|
+
if hasattr(message, "description") and message.description:
|
|
551
|
+
event_attrs["claude.task.description"] = message.description
|
|
552
|
+
if hasattr(message, "tool_use_id") and message.tool_use_id:
|
|
553
|
+
event_attrs["claude.task.tool_use_id"] = message.tool_use_id
|
|
554
|
+
if hasattr(message, "last_tool_name") and message.last_tool_name:
|
|
555
|
+
event_attrs["claude.task.last_tool"] = message.last_tool_name
|
|
556
|
+
if hasattr(message, "usage") and message.usage:
|
|
557
|
+
try:
|
|
558
|
+
event_attrs["claude.task.usage"] = json.dumps(message.usage)
|
|
559
|
+
except (TypeError, ValueError):
|
|
560
|
+
event_attrs["claude.task.usage"] = str(message.usage)
|
|
561
|
+
conversation_span.add_event("task.progress", attributes=event_attrs)
|
|
562
|
+
|
|
563
|
+
elif TaskNotificationMessage is not None and isinstance(message, TaskNotificationMessage):
|
|
564
|
+
if conversation_span and conversation_span.is_recording():
|
|
565
|
+
event_attrs = {}
|
|
566
|
+
if hasattr(message, "task_id") and message.task_id:
|
|
567
|
+
event_attrs["claude.task.id"] = message.task_id
|
|
568
|
+
if hasattr(message, "status") and message.status:
|
|
569
|
+
event_attrs["claude.task.status"] = message.status
|
|
570
|
+
if hasattr(message, "summary") and message.summary:
|
|
571
|
+
event_attrs["claude.task.summary"] = message.summary
|
|
572
|
+
if hasattr(message, "tool_use_id") and message.tool_use_id:
|
|
573
|
+
event_attrs["claude.task.tool_use_id"] = message.tool_use_id
|
|
574
|
+
if hasattr(message, "output_file") and message.output_file:
|
|
575
|
+
event_attrs["claude.task.output_file"] = message.output_file
|
|
576
|
+
if hasattr(message, "usage") and message.usage:
|
|
577
|
+
try:
|
|
578
|
+
event_attrs["claude.task.usage"] = json.dumps(message.usage)
|
|
579
|
+
except (TypeError, ValueError):
|
|
580
|
+
event_attrs["claude.task.usage"] = str(message.usage)
|
|
581
|
+
conversation_span.add_event("task.notification", attributes=event_attrs)
|
|
582
|
+
|
|
583
|
+
elif isinstance(message, SystemMessage):
|
|
584
|
+
# Generic SystemMessage handler (non-task system events)
|
|
585
|
+
system_events.append({"subtype": message.subtype, "data": message.data})
|
|
586
|
+
|
|
587
|
+
if conversation_span and conversation_span.is_recording():
|
|
588
|
+
event_attrs = {CLAUDE_SYSTEM_SUBTYPE: message.subtype}
|
|
589
|
+
if message.data:
|
|
590
|
+
try:
|
|
591
|
+
event_attrs[CLAUDE_SYSTEM_DATA] = json.dumps(message.data)
|
|
592
|
+
except (TypeError, ValueError):
|
|
593
|
+
event_attrs[CLAUDE_SYSTEM_DATA] = str(message.data)
|
|
594
|
+
conversation_span.add_event(f"system.{message.subtype}", attributes=event_attrs)
|
|
595
|
+
|
|
596
|
+
elif RateLimitEvent is not None and isinstance(message, RateLimitEvent):
|
|
597
|
+
if conversation_span and conversation_span.is_recording():
|
|
598
|
+
event_attrs = {}
|
|
599
|
+
# Real SDK uses rate_limit_info nested object
|
|
600
|
+
rli = getattr(message, "rate_limit_info", None)
|
|
601
|
+
if rli:
|
|
602
|
+
if hasattr(rli, "rate_limit_type") and rli.rate_limit_type:
|
|
603
|
+
event_attrs[CLAUDE_RATE_LIMIT_TYPE] = rli.rate_limit_type
|
|
604
|
+
if hasattr(rli, "status") and rli.status:
|
|
605
|
+
event_attrs[CLAUDE_RATE_LIMIT_STATUS] = rli.status
|
|
606
|
+
conversation_span.add_event("rate_limit", attributes=event_attrs)
|
|
607
|
+
|
|
608
|
+
elif StreamEvent is not None and isinstance(message, StreamEvent):
|
|
609
|
+
if conversation_span and conversation_span.is_recording():
|
|
610
|
+
event_type = getattr(message, "type", "stream")
|
|
611
|
+
conversation_span.add_event(f"stream.{event_type}")
|
|
612
|
+
|
|
613
|
+
elif isinstance(message, ResultMessage):
|
|
614
|
+
# Flush any remaining text buffer
|
|
615
|
+
if current_text_buffer:
|
|
616
|
+
output_blocks.append({"type": "text", "content": "\n".join(current_text_buffer)})
|
|
617
|
+
current_text_buffer = []
|
|
618
|
+
|
|
619
|
+
# Record final metrics on the conversation span
|
|
620
|
+
if conversation_span and conversation_span.is_recording():
|
|
621
|
+
# Output attributes (multiple conventions)
|
|
622
|
+
if output_blocks:
|
|
623
|
+
text_only = all(b["type"] == "text" for b in output_blocks)
|
|
624
|
+
if text_only:
|
|
625
|
+
full_response = "\n".join(b["content"] for b in output_blocks)
|
|
626
|
+
# Langfuse
|
|
627
|
+
conversation_span.set_attribute(OBSERVATION_OUTPUT, full_response)
|
|
628
|
+
# OpenInference
|
|
629
|
+
conversation_span.set_attribute(OUTPUT_VALUE, full_response)
|
|
630
|
+
conversation_span.set_attribute(OUTPUT_MIME_TYPE, MIME_TYPE_TEXT)
|
|
631
|
+
else:
|
|
632
|
+
output_json = json.dumps(output_blocks, indent=2)
|
|
633
|
+
# Langfuse
|
|
634
|
+
conversation_span.set_attribute(OBSERVATION_OUTPUT, output_json)
|
|
635
|
+
# OpenInference
|
|
636
|
+
conversation_span.set_attribute(OUTPUT_VALUE, output_json)
|
|
637
|
+
conversation_span.set_attribute(OUTPUT_MIME_TYPE, MIME_TYPE_JSON)
|
|
638
|
+
|
|
639
|
+
# Thinking content
|
|
640
|
+
if collected_thinking:
|
|
641
|
+
conversation_span.set_attribute(GATEWAY_LLM_THINKING, "\n".join(collected_thinking))
|
|
642
|
+
|
|
643
|
+
# Thinking signatures (Claude-specific)
|
|
644
|
+
if thinking_signatures:
|
|
645
|
+
conversation_span.set_attribute(
|
|
646
|
+
CLAUDE_THINKING_SIGNATURE, "\n".join(thinking_signatures)
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
# Structured output (Claude-specific)
|
|
650
|
+
if hasattr(message, "structured_output") and message.structured_output is not None:
|
|
651
|
+
try:
|
|
652
|
+
conversation_span.set_attribute(
|
|
653
|
+
CLAUDE_STRUCTURED_OUTPUT, json.dumps(message.structured_output)
|
|
654
|
+
)
|
|
655
|
+
except (TypeError, ValueError):
|
|
656
|
+
conversation_span.set_attribute(
|
|
657
|
+
CLAUDE_STRUCTURED_OUTPUT, str(message.structured_output)
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
# Cost (GenAI and GatewaySDK)
|
|
661
|
+
if message.total_cost_usd is not None:
|
|
662
|
+
conversation_span.set_attribute(GEN_AI_USAGE_COST, message.total_cost_usd)
|
|
663
|
+
conversation_span.set_attribute(GATEWAY_LLM_COST_USD, message.total_cost_usd)
|
|
664
|
+
|
|
665
|
+
# Token usage (GenAI and OpenInference)
|
|
666
|
+
if message.usage:
|
|
667
|
+
usage = message.usage
|
|
668
|
+
input_tokens = usage.get("input_tokens")
|
|
669
|
+
output_tokens = usage.get("output_tokens")
|
|
670
|
+
|
|
671
|
+
if input_tokens is not None:
|
|
672
|
+
# GenAI
|
|
673
|
+
conversation_span.set_attribute(GEN_AI_USAGE_INPUT_TOKENS, input_tokens)
|
|
674
|
+
# OpenInference
|
|
675
|
+
conversation_span.set_attribute(LLM_TOKEN_COUNT_PROMPT, input_tokens)
|
|
676
|
+
|
|
677
|
+
if output_tokens is not None:
|
|
678
|
+
# GenAI
|
|
679
|
+
conversation_span.set_attribute(GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens)
|
|
680
|
+
# OpenInference
|
|
681
|
+
conversation_span.set_attribute(LLM_TOKEN_COUNT_COMPLETION, output_tokens)
|
|
682
|
+
|
|
683
|
+
if input_tokens is not None and output_tokens is not None:
|
|
684
|
+
conversation_span.set_attribute(LLM_TOKEN_COUNT_TOTAL, input_tokens + output_tokens)
|
|
685
|
+
|
|
686
|
+
# Duration (GatewaySDK)
|
|
687
|
+
conversation_span.set_attribute(GATEWAY_DURATION_MS, message.duration_ms)
|
|
688
|
+
conversation_span.set_attribute(GATEWAY_DURATION_API_MS, message.duration_api_ms)
|
|
689
|
+
|
|
690
|
+
# Turns (GatewaySDK)
|
|
691
|
+
conversation_span.set_attribute(GATEWAY_NUM_TURNS, message.num_turns)
|
|
692
|
+
|
|
693
|
+
# Session ID: update from ResultMessage only when it provides a
|
|
694
|
+
# real session (not a placeholder). In production the SDK always
|
|
695
|
+
# returns the authoritative session_id here; placeholders only
|
|
696
|
+
# appear in test helpers or when no session was created.
|
|
697
|
+
if (
|
|
698
|
+
hasattr(message, "session_id")
|
|
699
|
+
and message.session_id
|
|
700
|
+
and message.session_id not in _SESSION_ID_PLACEHOLDERS
|
|
701
|
+
):
|
|
702
|
+
conversation_span.set_attribute(SESSION_ID, message.session_id)
|
|
703
|
+
conversation_span.set_attribute(GEN_AI_CONVERSATION_ID, message.session_id)
|
|
704
|
+
|
|
705
|
+
# Stop reason (why the conversation ended)
|
|
706
|
+
if hasattr(message, "stop_reason") and message.stop_reason:
|
|
707
|
+
conversation_span.set_attribute(CLAUDE_STOP_REASON, message.stop_reason)
|
|
708
|
+
conversation_span.set_attribute(GEN_AI_RESPONSE_FINISH_REASONS, [message.stop_reason])
|
|
709
|
+
|
|
710
|
+
# Errors (non-fatal errors during the turn)
|
|
711
|
+
if hasattr(message, "errors") and message.errors:
|
|
712
|
+
try:
|
|
713
|
+
conversation_span.set_attribute(CLAUDE_ERRORS, json.dumps(message.errors))
|
|
714
|
+
except (TypeError, ValueError):
|
|
715
|
+
conversation_span.set_attribute(CLAUDE_ERRORS, str(message.errors))
|
|
716
|
+
|
|
717
|
+
# Per-model usage breakdown
|
|
718
|
+
if hasattr(message, "model_usage") and message.model_usage:
|
|
719
|
+
try:
|
|
720
|
+
conversation_span.set_attribute(CLAUDE_MODEL_USAGE, json.dumps(message.model_usage))
|
|
721
|
+
except (TypeError, ValueError):
|
|
722
|
+
conversation_span.set_attribute(CLAUDE_MODEL_USAGE, str(message.model_usage))
|
|
723
|
+
|
|
724
|
+
# Error status
|
|
725
|
+
if message.is_error:
|
|
726
|
+
conversation_span.set_status(
|
|
727
|
+
Status(StatusCode.ERROR, message.result or "Unknown error")
|
|
728
|
+
)
|
|
729
|
+
else:
|
|
730
|
+
conversation_span.set_status(Status(StatusCode.OK))
|
|
731
|
+
|
|
732
|
+
# End the conversation span
|
|
733
|
+
conversation_span.end()
|
|
734
|
+
|
|
735
|
+
# Clear the stored span
|
|
736
|
+
if hasattr(client_self, _CLIENT_SPAN_ATTR):
|
|
737
|
+
delattr(client_self, _CLIENT_SPAN_ATTR)
|
|
738
|
+
|
|
739
|
+
# Yield the original message unchanged
|
|
740
|
+
yield message
|
|
741
|
+
|
|
742
|
+
finally:
|
|
743
|
+
# Clean up any unclosed tool spans
|
|
744
|
+
for tool_id, tool_span in tool_spans.items():
|
|
745
|
+
tool_span.set_status(Status(StatusCode.ERROR, "Tool span not properly closed"))
|
|
746
|
+
tool_span.end()
|
|
747
|
+
|
|
748
|
+
# Ensure conversation span is ended if not already
|
|
749
|
+
if hasattr(client_self, _CLIENT_SPAN_ATTR):
|
|
750
|
+
remaining_span = getattr(client_self, _CLIENT_SPAN_ATTR)
|
|
751
|
+
if remaining_span and remaining_span.is_recording():
|
|
752
|
+
if current_text_buffer:
|
|
753
|
+
output_blocks.append({"type": "text", "content": "\n".join(current_text_buffer)})
|
|
754
|
+
if output_blocks:
|
|
755
|
+
text_only = all(b["type"] == "text" for b in output_blocks)
|
|
756
|
+
if text_only:
|
|
757
|
+
remaining_span.set_attribute(
|
|
758
|
+
OBSERVATION_OUTPUT, "\n".join(b["content"] for b in output_blocks)
|
|
759
|
+
)
|
|
760
|
+
else:
|
|
761
|
+
remaining_span.set_attribute(OBSERVATION_OUTPUT, json.dumps(output_blocks, indent=2))
|
|
762
|
+
remaining_span.set_status(Status(StatusCode.OK))
|
|
763
|
+
remaining_span.end()
|
|
764
|
+
delattr(client_self, _CLIENT_SPAN_ATTR)
|
|
765
|
+
|
|
766
|
+
return instrumented_receive_messages
|