From e9642b20cdeb1e9ea7002afe1106b9cbab97e5bc Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Tue, 17 Feb 2026 13:26:53 +0100 Subject: [PATCH 01/10] feat: Zero-agent OTEL - remove all observability code from weather agent Remove all OpenTelemetry code, auto-instrumentation, and tracing middleware from the weather agent. All observability is now handled externally by the AuthBridge ext_proc sidecar which: - Creates root spans from A2A request/response - Creates nested child spans from SSE stream events - Sets all MLflow/OpenInference/GenAI attributes Removed: - observability.py (TracerProvider, middleware, span helpers) - OTEL dependencies (opentelemetry-*, openinference-*) - Tracing middleware from Starlette app - All span creation/enrichment in agent code The agent is now a plain A2A agent with zero observability overhead. Refs kagenti/kagenti#667 Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ladislav Smola --- a2a/weather_service/pyproject.toml | 7 - .../src/weather_service/__init__.py | 10 +- .../src/weather_service/agent.py | 25 - .../src/weather_service/observability.py | 551 ------------------ 4 files changed, 5 insertions(+), 588 deletions(-) delete mode 100644 a2a/weather_service/src/weather_service/observability.py diff --git a/a2a/weather_service/pyproject.toml b/a2a/weather_service/pyproject.toml index cf23d96a..16dbe1d9 100644 --- a/a2a/weather_service/pyproject.toml +++ b/a2a/weather_service/pyproject.toml @@ -17,13 +17,6 @@ dependencies = [ "pydantic-settings>=2.8.1", "langchain-mcp-adapters>=0.1.0", "python-keycloak>=5.5.1", - "opentelemetry-exporter-otlp", - # OpenTelemetry GenAI semantic convention instrumentation - # Emits spans with gen_ai.* attributes for MLflow compatibility - "opentelemetry-instrumentation-openai>=0.34b0", - # OpenInference for LangChain instrumentation and AGENT span semantics - "openinference-semantic-conventions>=0.1.12", - "openinference-instrumentation-langchain>=0.1.27", ] [project.scripts] diff --git a/a2a/weather_service/src/weather_service/__init__.py b/a2a/weather_service/src/weather_service/__init__.py index 235755a7..53f7b30c 100644 --- a/a2a/weather_service/src/weather_service/__init__.py +++ b/a2a/weather_service/src/weather_service/__init__.py @@ -1,6 +1,6 @@ -"""Weather Service - OpenTelemetry Observability Setup""" +"""Weather Service - A2A weather agent with zero observability code. -from weather_service.observability import setup_observability - -# Initialize observability before importing agent -setup_observability() +All tracing and observability is handled externally by the AuthBridge +ext_proc sidecar which creates root spans and nested child spans from +the A2A SSE event stream. No OTEL dependencies needed in the agent. +""" diff --git a/a2a/weather_service/src/weather_service/agent.py b/a2a/weather_service/src/weather_service/agent.py index 5cc445a2..d3d39c1a 100644 --- a/a2a/weather_service/src/weather_service/agent.py +++ b/a2a/weather_service/src/weather_service/agent.py @@ -13,10 +13,7 @@ from a2a.utils import new_agent_text_message, new_task from langchain_core.messages import HumanMessage -from starlette.middleware.base import BaseHTTPMiddleware - from weather_service.graph import get_graph, get_mcpclient -from weather_service.observability import create_tracing_middleware, set_span_output, get_root_span logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) @@ -112,8 +109,6 @@ async def execute(self, context: RequestContext, event_queue: EventQueue): input = {"messages": messages} logger.info(f'Processing messages: {input}') - # Note: Root span with MLflow attributes is created by tracing middleware - # Here we just run the agent logic - spans from LangChain are auto-captured output = None # Test MCP connection first @@ -143,15 +138,6 @@ async def execute(self, context: RequestContext, event_queue: EventQueue): logger.info(f'event: {event}') output = output.get("assistant", {}).get("final_answer") - # Set span output BEFORE emitting final event (for streaming response capture) - # This populates mlflow.spanOutputs, output.value, gen_ai.completion - # Use get_root_span() to get the middleware-created root span, not the - # current A2A span (trace.get_current_span() would return wrong span) - if output: - root_span = get_root_span() - if root_span and root_span.is_recording(): - set_span_output(root_span, str(output)) - await event_emitter.emit_event(str(output), final=True) async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None: @@ -187,15 +173,4 @@ def run(): name='agent_card_new', )) - # Add tracing middleware - creates root span with MLflow/GenAI attributes - app.add_middleware(BaseHTTPMiddleware, dispatch=create_tracing_middleware()) - - # Add logging middleware - @app.middleware("http") - async def log_authorization_header(request, call_next): - auth_header = request.headers.get("authorization", "No Authorization header") - logger.info(f"πŸ” Incoming request to {request.url.path} with Authorization: {auth_header[:80] + '...' if len(auth_header) > 80 else auth_header}") - response = await call_next(request) - return response - uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/a2a/weather_service/src/weather_service/observability.py b/a2a/weather_service/src/weather_service/observability.py deleted file mode 100644 index e713de94..00000000 --- a/a2a/weather_service/src/weather_service/observability.py +++ /dev/null @@ -1,551 +0,0 @@ -""" -OpenTelemetry observability setup for Weather Agent. - -Key Features: -- Tracing middleware for root span with MLflow attributes -- Auto-instrumentation of LangChain with OpenInference -- Resource attributes for static agent metadata -- W3C Trace Context propagation for distributed tracing -""" - -import json -import logging -import os -from contextvars import ContextVar -from typing import Dict, Any, Optional -from contextlib import contextmanager -from opentelemetry import trace, context -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor -from opentelemetry.sdk.resources import Resource, SERVICE_NAME, SERVICE_VERSION -from opentelemetry.trace import Status, StatusCode, SpanKind -from opentelemetry.propagate import set_global_textmap, extract -from opentelemetry.propagators.composite import CompositePropagator -from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator -from opentelemetry.baggage.propagation import W3CBaggagePropagator - -logger = logging.getLogger(__name__) - -# Agent metadata (static, used in Resource and spans) -AGENT_NAME = "weather-assistant" -AGENT_VERSION = "1.0.0" -AGENT_FRAMEWORK = "langchain" - -# ContextVar to pass root span from middleware to agent code -# This allows execute() to access the middleware-created root span -# even though trace.get_current_span() would return a child span -_root_span_var: ContextVar = ContextVar('root_span', default=None) - - -def get_root_span(): - """Get the root span created by tracing middleware. - - Use this instead of trace.get_current_span() when you need to set - attributes on the root span (e.g., mlflow.spanOutputs for streaming). - - Returns: - The root span, or None if not in a traced request context. - """ - return _root_span_var.get() - -# OpenInference semantic conventions -try: - from openinference.semconv.trace import SpanAttributes, OpenInferenceSpanKindValues - OPENINFERENCE_AVAILABLE = True -except ImportError: - OPENINFERENCE_AVAILABLE = False - logger.warning("openinference-semantic-conventions not available") - - -def _get_otlp_exporter(endpoint: str): - """Get HTTP OTLP exporter.""" - from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter - if not endpoint.endswith("/v1/traces"): - endpoint = endpoint.rstrip("/") + "/v1/traces" - return OTLPSpanExporter(endpoint=endpoint) - - -def setup_observability() -> None: - """ - Set up OpenTelemetry tracing with OpenInference instrumentation. - - Call this ONCE at agent startup, before importing agent code. - """ - service_name = os.getenv("OTEL_SERVICE_NAME", "weather-service") - namespace = os.getenv("K8S_NAMESPACE_NAME", "team1") - otlp_endpoint = os.getenv( - "OTEL_EXPORTER_OTLP_ENDPOINT", - "http://otel-collector.kagenti-system.svc.cluster.local:8335" - ) - - logger.info("=" * 60) - logger.info("Setting up OpenTelemetry observability") - logger.info(f" Service: {service_name}") - logger.info(f" Namespace: {namespace}") - logger.info(f" OTLP Endpoint: {otlp_endpoint}") - logger.info("=" * 60) - - # Create resource with service and MLflow attributes - # Resource attributes are STATIC and apply to ALL spans/traces - # See: https://mlflow.org/docs/latest/genai/tracing/opentelemetry/ - resource = Resource(attributes={ - # Standard OTEL service attributes - SERVICE_NAME: service_name, - SERVICE_VERSION: AGENT_VERSION, - "service.namespace": namespace, - "k8s.namespace.name": namespace, - # MLflow static metadata (applies to all traces) - # These appear in MLflow trace list columns - "mlflow.traceName": AGENT_NAME, - "mlflow.source": service_name, - # GenAI static attributes - "gen_ai.agent.name": AGENT_NAME, - "gen_ai.agent.version": AGENT_VERSION, - "gen_ai.system": AGENT_FRAMEWORK, - }) - - # Create and configure tracer provider - tracer_provider = TracerProvider(resource=resource) - tracer_provider.add_span_processor( - BatchSpanProcessor(_get_otlp_exporter(otlp_endpoint)) - ) - trace.set_tracer_provider(tracer_provider) - - # Auto-instrument LangChain with OpenInference - try: - from openinference.instrumentation.langchain import LangChainInstrumentor - LangChainInstrumentor().instrument() - logger.info("LangChain instrumented with OpenInference") - except ImportError: - logger.warning("openinference-instrumentation-langchain not available") - - # Configure W3C Trace Context propagation - set_global_textmap(CompositePropagator([ - TraceContextTextMapPropagator(), - W3CBaggagePropagator(), - ])) - - # Instrument OpenAI for GenAI semantic conventions - try: - from opentelemetry.instrumentation.openai import OpenAIInstrumentor - OpenAIInstrumentor().instrument() - logger.info("OpenAI instrumented with GenAI semantic conventions") - except ImportError: - logger.warning("opentelemetry-instrumentation-openai not available") - - -# Tracer for manual spans - use OpenInference-compatible name -_tracer: Optional[trace.Tracer] = None -TRACER_NAME = "openinference.instrumentation.agent" - - -def get_tracer() -> trace.Tracer: - """Get tracer for creating manual spans.""" - global _tracer - if _tracer is None: - _tracer = trace.get_tracer(TRACER_NAME) - return _tracer - - -def _set_genai_mlflow_attributes( - span, - context_id: Optional[str] = None, - task_id: Optional[str] = None, - user_id: Optional[str] = None, - input_text: Optional[str] = None, -): - """Set GenAI and MLflow attributes on a span.""" - # === GenAI Semantic Conventions === - if context_id: - span.set_attribute("gen_ai.conversation.id", context_id) - if input_text: - span.set_attribute("gen_ai.prompt", input_text[:1000]) - span.set_attribute("input.value", input_text[:1000]) - span.set_attribute("gen_ai.agent.name", "weather-assistant") - span.set_attribute("gen_ai.system", "langchain") - - # OpenInference span kind - if OPENINFERENCE_AVAILABLE: - span.set_attribute( - SpanAttributes.OPENINFERENCE_SPAN_KIND, - OpenInferenceSpanKindValues.AGENT.value - ) - - # === MLflow-specific Attributes === - # TODO: Could be handled by OTEL Collector transform/genai_to_mlflow - if input_text: - span.set_attribute("mlflow.spanInputs", input_text[:1000]) - span.set_attribute("mlflow.spanType", "AGENT") - span.set_attribute("mlflow.traceName", "weather-assistant") - span.set_attribute("mlflow.source", "weather-service") - if context_id: - span.set_attribute("mlflow.trace.session", context_id) - if user_id: - span.set_attribute("mlflow.user", user_id) - span.set_attribute("enduser.id", user_id) - - # Custom attributes - if task_id: - span.set_attribute("a2a.task_id", task_id) - if user_id: - span.set_attribute("user.id", user_id) - - -@contextmanager -def enrich_current_span( - context_id: Optional[str] = None, - task_id: Optional[str] = None, - user_id: Optional[str] = None, - input_text: Optional[str] = None, -): - """ - Enrich the current span (e.g., A2A root span) with GenAI and MLflow attributes. - - If there's no recording span in the current context, creates a new one named - 'gen_ai.agent.invoke' to ensure traces are captured. - - Args: - context_id: A2A context_id (becomes gen_ai.conversation.id) - task_id: A2A task_id - user_id: User identifier - input_text: User input message - - Yields: - The span (either enriched existing or newly created) - - Note: - TODO: The following could be handled by OTEL Collector transform instead: - - mlflow.spanInputs/spanOutputs (currently set explicitly for reliability) - - mlflow.spanType classification (could use span name pattern matching) - - mlflow.user/source (could be derived from resource attributes) - """ - current_span = trace.get_current_span() - - # Check if we have a recording span to enrich - # get_current_span() returns INVALID_SPAN if none exists - if current_span.is_recording(): - # Enrich the existing span - _set_genai_mlflow_attributes(current_span, context_id, task_id, user_id, input_text) - try: - yield current_span - except Exception as e: - current_span.set_status(Status(StatusCode.ERROR, str(e))) - current_span.record_exception(e) - raise - else: - # No recording span - create one - # This ensures our GenAI attributes are captured even if A2A doesn't trace - logger.info("No current recording span - creating gen_ai.agent.invoke span") - tracer = get_tracer() - with tracer.start_as_current_span("gen_ai.agent.invoke") as new_span: - _set_genai_mlflow_attributes(new_span, context_id, task_id, user_id, input_text) - try: - yield new_span - new_span.set_status(Status(StatusCode.OK)) - except Exception as e: - new_span.set_status(Status(StatusCode.ERROR, str(e))) - new_span.record_exception(e) - raise - - -def set_span_output(span, output: str): - """ - Set output attributes on a span after work completes. - - Call this after getting a response to populate: - - gen_ai.completion (standard GenAI attribute) - - output.value (OpenInference attribute) - - mlflow.spanOutputs (MLflow Response column) - - Args: - span: The span to update - output: The output/response text - """ - if output: - truncated = str(output)[:1000] - span.set_attribute("gen_ai.completion", truncated) - span.set_attribute("output.value", truncated) - span.set_attribute("mlflow.spanOutputs", truncated) - - -def set_token_usage(span, input_tokens: int = 0, output_tokens: int = 0): - """ - Set token usage attributes on a span. - - Args: - span: The span to update - input_tokens: Number of input tokens - output_tokens: Number of output tokens - - Note: - TODO: This could be handled by OTEL Collector transform copying - gen_ai.usage.* to mlflow.span.chat_usage.* attributes. - """ - if input_tokens: - span.set_attribute("gen_ai.usage.input_tokens", input_tokens) - span.set_attribute("mlflow.span.chat_usage.input_tokens", input_tokens) - if output_tokens: - span.set_attribute("gen_ai.usage.output_tokens", output_tokens) - span.set_attribute("mlflow.span.chat_usage.output_tokens", output_tokens) - - -@contextmanager -def create_agent_span( - name: str = "gen_ai.agent.invoke", - context_id: Optional[str] = None, - task_id: Optional[str] = None, - user_id: Optional[str] = None, - input_text: Optional[str] = None, - break_parent_chain: bool = False, -): - """ - Create a NEW AGENT span with GenAI semantic conventions. - - Use `enrich_current_span` instead if you want to add attributes to - the existing A2A span rather than creating a new child span. - - Args: - name: Span name (use gen_ai.agent.* for MLflow AGENT type detection) - context_id: A2A context_id (becomes gen_ai.conversation.id) - task_id: A2A task_id - user_id: User identifier - input_text: User input message - break_parent_chain: If True, breaks the trace chain (creates isolated root). - Default False to preserve distributed trace visibility. - - Yields: - The span object (set output.value on it before exiting) - """ - tracer = get_tracer() - - # Build attributes - attributes = {} - - # GenAI semantic conventions (for OTEL Collector transforms) - if context_id: - attributes["gen_ai.conversation.id"] = context_id - if input_text: - attributes["gen_ai.prompt"] = input_text[:1000] - attributes["input.value"] = input_text[:1000] - attributes["gen_ai.agent.name"] = "weather-assistant" - attributes["gen_ai.system"] = "langchain" - - # OpenInference span kind - marks this as an AGENT span - if OPENINFERENCE_AVAILABLE: - attributes[SpanAttributes.OPENINFERENCE_SPAN_KIND] = OpenInferenceSpanKindValues.AGENT.value - - # Custom attributes for debugging - if task_id: - attributes["a2a.task_id"] = task_id - if user_id: - attributes["user.id"] = user_id - - # Optional: break the parent chain for isolated traces - detach_token = None - if break_parent_chain: - empty_ctx = context.Context() - detach_token = context.attach(empty_ctx) - - # Start the span - becomes child of current context (A2A span) by default - with tracer.start_as_current_span(name, attributes=attributes) as span: - try: - yield span - span.set_status(Status(StatusCode.OK)) - except Exception as e: - span.set_status(Status(StatusCode.ERROR, str(e))) - span.record_exception(e) - raise - finally: - if detach_token: - context.detach(detach_token) - - -@contextmanager -def trace_context_from_headers(headers: Dict[str, str]): - """ - Activate trace context from HTTP headers. - - Use this to connect to incoming distributed trace. - """ - ctx = extract(headers) - token = context.attach(ctx) - try: - yield ctx - finally: - context.detach(token) - - -def create_tracing_middleware(): - """ - Create Starlette middleware that wraps all requests in a root tracing span. - - This middleware: - 1. Creates a root span BEFORE A2A handlers run - 2. Sets MLflow/GenAI attributes on the root span - 3. Parses A2A JSON-RPC request to extract user input - 4. Captures response to set output attributes - - Usage in agent.py: - from weather_service.observability import create_tracing_middleware - app = server.build() - app.add_middleware(BaseHTTPMiddleware, dispatch=create_tracing_middleware()) - """ - from starlette.requests import Request - from starlette.responses import Response, StreamingResponse - import io - - async def tracing_middleware(request: Request, call_next): - # Skip non-API paths (health checks, agent card, etc.) - if request.url.path in ["/health", "/ready", "/.well-known/agent-card.json"]: - return await call_next(request) - - tracer = get_tracer() - - # Parse request body to extract user input and context - user_input = None - context_id = None - message_id = None - - try: - body = await request.body() - if body: - data = json.loads(body) - # A2A JSON-RPC format: params.message.parts[0].text - params = data.get("params", {}) - message = params.get("message", {}) - parts = message.get("parts", []) - if parts and isinstance(parts, list): - user_input = parts[0].get("text", "") - context_id = params.get("contextId") or message.get("contextId") - message_id = message.get("messageId") - except Exception as e: - logger.debug(f"Could not parse request body: {e}") - - # Break parent chain to make this a true root span - # Without this, the span would inherit parent from W3C Trace Context headers - empty_ctx = context.Context() - detach_token = context.attach(empty_ctx) - - try: - # Create root span with correct GenAI naming convention - # Per https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/ - # Span name: "invoke_agent {gen_ai.agent.name}" when name is available - span_name = f"invoke_agent {AGENT_NAME}" - - with tracer.start_as_current_span( - span_name, - kind=SpanKind.INTERNAL, # In-process agent (not remote service) - ) as span: - # Store span in ContextVar so agent code can access it - # This is needed because trace.get_current_span() in execute() - # returns the innermost span (A2A span), not our root span - span_token = _root_span_var.set(span) - - # === GenAI Semantic Conventions (Required) === - # Per https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/ - span.set_attribute("gen_ai.operation.name", "invoke_agent") - span.set_attribute("gen_ai.provider.name", AGENT_FRAMEWORK) - span.set_attribute("gen_ai.agent.name", AGENT_NAME) - span.set_attribute("gen_ai.agent.version", AGENT_VERSION) - - # Set input attributes (Prompt column in MLflow) - if user_input: - span.set_attribute("gen_ai.prompt", user_input[:1000]) - span.set_attribute("input.value", user_input[:1000]) - span.set_attribute("mlflow.spanInputs", user_input[:1000]) - - # Session tracking - use context_id or message_id as fallback - session_id = context_id or message_id - - if session_id: - span.set_attribute("gen_ai.conversation.id", session_id) - span.set_attribute("mlflow.trace.session", session_id) - span.set_attribute("session.id", session_id) - - # MLflow trace metadata (appears in trace list columns) - span.set_attribute("mlflow.spanType", "AGENT") - span.set_attribute("mlflow.traceName", AGENT_NAME) - span.set_attribute("mlflow.runName", f"{AGENT_NAME}-invoke") - span.set_attribute("mlflow.source", "weather-service") - span.set_attribute("mlflow.version", AGENT_VERSION) - - # User tracking - extract from auth header if available - auth_header = request.headers.get("authorization", "") - if auth_header: - # For Bearer tokens, we could decode JWT to get user - # For now, just indicate authenticated request - span.set_attribute("mlflow.user", "authenticated") - span.set_attribute("enduser.id", "authenticated") - else: - span.set_attribute("mlflow.user", "anonymous") - span.set_attribute("enduser.id", "anonymous") - - # OpenInference span kind (for Phoenix) - if OPENINFERENCE_AVAILABLE: - span.set_attribute( - SpanAttributes.OPENINFERENCE_SPAN_KIND, - OpenInferenceSpanKindValues.AGENT.value, - ) - - try: - # Call the next handler (A2A) - response = await call_next(request) - - # Try to capture response for output attributes - # Note: This only works for non-streaming responses - if isinstance(response, Response) and not isinstance( - response, StreamingResponse - ): - # Read response body - we MUST recreate response after this - response_body = b"" - async for chunk in response.body_iterator: - response_body += chunk - - # Try to parse and extract output for MLflow - try: - if response_body: - resp_data = json.loads(response_body) - result = resp_data.get("result", {}) - artifacts = result.get("artifacts", []) - if artifacts: - parts = artifacts[0].get("parts", []) - if parts: - output_text = parts[0].get("text", "") - if output_text: - span.set_attribute( - "gen_ai.completion", output_text[:1000] - ) - span.set_attribute( - "output.value", output_text[:1000] - ) - span.set_attribute( - "mlflow.spanOutputs", output_text[:1000] - ) - except Exception as e: - logger.debug(f"Could not parse response body: {e}") - - # Always recreate response since we consumed the iterator - span.set_status(Status(StatusCode.OK)) - return Response( - content=response_body, - status_code=response.status_code, - headers=dict(response.headers), - media_type=response.media_type, - ) - - # For streaming responses, just return as-is - span.set_status(Status(StatusCode.OK)) - return response - - except Exception as e: - span.set_status(Status(StatusCode.ERROR, str(e))) - span.record_exception(e) - raise - finally: - # Reset the ContextVar to avoid leaking span reference - _root_span_var.reset(span_token) - finally: - # Always detach the context to restore parent chain for other requests - context.detach(detach_token) - - return tracing_middleware From c4a0bb2ca8c88253b9df34d8b321c668b33c43d6 Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Tue, 17 Feb 2026 14:10:59 +0100 Subject: [PATCH 02/10] chore: Regenerate uv.lock after removing OTEL dependencies Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ladislav Smola --- a2a/weather_service/uv.lock | 313 ------------------------------------ 1 file changed, 313 deletions(-) diff --git a/a2a/weather_service/uv.lock b/a2a/weather_service/uv.lock index 26ca752a..dcc093b2 100644 --- a/a2a/weather_service/uv.lock +++ b/a2a/weather_service/uv.lock @@ -528,44 +528,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/e6/f9d759788518a6248684e3afeb3691f3ab0276d769b6217a1533362298c8/greenlet-3.2.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d6668caf15f181c1b82fb6406f3911696975cc4c37d782e19cb7ba499e556189", size = 269897, upload-time = "2025-04-22T14:27:14.044Z" }, ] -[[package]] -name = "grpcio" -version = "1.74.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/b4/35feb8f7cab7239c5b94bd2db71abb3d6adb5f335ad8f131abb6060840b6/grpcio-1.74.0.tar.gz", hash = "sha256:80d1f4fbb35b0742d3e3d3bb654b7381cd5f015f8497279a1e9c21ba623e01b1", size = 12756048, upload-time = "2025-07-24T18:54:23.039Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/77/b2f06db9f240a5abeddd23a0e49eae2b6ac54d85f0e5267784ce02269c3b/grpcio-1.74.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:69e1a8180868a2576f02356565f16635b99088da7df3d45aaa7e24e73a054e31", size = 5487368, upload-time = "2025-07-24T18:53:03.548Z" }, - { url = "https://files.pythonhosted.org/packages/48/99/0ac8678a819c28d9a370a663007581744a9f2a844e32f0fa95e1ddda5b9e/grpcio-1.74.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8efe72fde5500f47aca1ef59495cb59c885afe04ac89dd11d810f2de87d935d4", size = 10999804, upload-time = "2025-07-24T18:53:05.095Z" }, - { url = "https://files.pythonhosted.org/packages/45/c6/a2d586300d9e14ad72e8dc211c7aecb45fe9846a51e558c5bca0c9102c7f/grpcio-1.74.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a8f0302f9ac4e9923f98d8e243939a6fb627cd048f5cd38595c97e38020dffce", size = 5987667, upload-time = "2025-07-24T18:53:07.157Z" }, - { url = "https://files.pythonhosted.org/packages/c9/57/5f338bf56a7f22584e68d669632e521f0de460bb3749d54533fc3d0fca4f/grpcio-1.74.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f609a39f62a6f6f05c7512746798282546358a37ea93c1fcbadf8b2fed162e3", size = 6655612, upload-time = "2025-07-24T18:53:09.244Z" }, - { url = "https://files.pythonhosted.org/packages/82/ea/a4820c4c44c8b35b1903a6c72a5bdccec92d0840cf5c858c498c66786ba5/grpcio-1.74.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98e0b7434a7fa4e3e63f250456eaef52499fba5ae661c58cc5b5477d11e7182", size = 6219544, upload-time = "2025-07-24T18:53:11.221Z" }, - { url = "https://files.pythonhosted.org/packages/a4/17/0537630a921365928f5abb6d14c79ba4dcb3e662e0dbeede8af4138d9dcf/grpcio-1.74.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:662456c4513e298db6d7bd9c3b8df6f75f8752f0ba01fb653e252ed4a59b5a5d", size = 6334863, upload-time = "2025-07-24T18:53:12.925Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a6/85ca6cb9af3f13e1320d0a806658dca432ff88149d5972df1f7b51e87127/grpcio-1.74.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3d14e3c4d65e19d8430a4e28ceb71ace4728776fd6c3ce34016947474479683f", size = 7019320, upload-time = "2025-07-24T18:53:15.002Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a7/fe2beab970a1e25d2eff108b3cf4f7d9a53c185106377a3d1989216eba45/grpcio-1.74.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bf949792cee20d2078323a9b02bacbbae002b9e3b9e2433f2741c15bdeba1c4", size = 6514228, upload-time = "2025-07-24T18:53:16.999Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c2/2f9c945c8a248cebc3ccda1b7a1bf1775b9d7d59e444dbb18c0014e23da6/grpcio-1.74.0-cp311-cp311-win32.whl", hash = "sha256:55b453812fa7c7ce2f5c88be3018fb4a490519b6ce80788d5913f3f9d7da8c7b", size = 3817216, upload-time = "2025-07-24T18:53:20.564Z" }, - { url = "https://files.pythonhosted.org/packages/ff/d1/a9cf9c94b55becda2199299a12b9feef0c79946b0d9d34c989de6d12d05d/grpcio-1.74.0-cp311-cp311-win_amd64.whl", hash = "sha256:86ad489db097141a907c559988c29718719aa3e13370d40e20506f11b4de0d11", size = 4495380, upload-time = "2025-07-24T18:53:22.058Z" }, - { url = "https://files.pythonhosted.org/packages/4c/5d/e504d5d5c4469823504f65687d6c8fb97b7f7bf0b34873b7598f1df24630/grpcio-1.74.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8533e6e9c5bd630ca98062e3a1326249e6ada07d05acf191a77bc33f8948f3d8", size = 5445551, upload-time = "2025-07-24T18:53:23.641Z" }, - { url = "https://files.pythonhosted.org/packages/43/01/730e37056f96f2f6ce9f17999af1556df62ee8dab7fa48bceeaab5fd3008/grpcio-1.74.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2918948864fec2a11721d91568effffbe0a02b23ecd57f281391d986847982f6", size = 10979810, upload-time = "2025-07-24T18:53:25.349Z" }, - { url = "https://files.pythonhosted.org/packages/79/3d/09fd100473ea5c47083889ca47ffd356576173ec134312f6aa0e13111dee/grpcio-1.74.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:60d2d48b0580e70d2e1954d0d19fa3c2e60dd7cbed826aca104fff518310d1c5", size = 5941946, upload-time = "2025-07-24T18:53:27.387Z" }, - { url = "https://files.pythonhosted.org/packages/8a/99/12d2cca0a63c874c6d3d195629dcd85cdf5d6f98a30d8db44271f8a97b93/grpcio-1.74.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3601274bc0523f6dc07666c0e01682c94472402ac2fd1226fd96e079863bfa49", size = 6621763, upload-time = "2025-07-24T18:53:29.193Z" }, - { url = "https://files.pythonhosted.org/packages/9d/2c/930b0e7a2f1029bbc193443c7bc4dc2a46fedb0203c8793dcd97081f1520/grpcio-1.74.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:176d60a5168d7948539def20b2a3adcce67d72454d9ae05969a2e73f3a0feee7", size = 6180664, upload-time = "2025-07-24T18:53:30.823Z" }, - { url = "https://files.pythonhosted.org/packages/db/d5/ff8a2442180ad0867717e670f5ec42bfd8d38b92158ad6bcd864e6d4b1ed/grpcio-1.74.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e759f9e8bc908aaae0412642afe5416c9f983a80499448fcc7fab8692ae044c3", size = 6301083, upload-time = "2025-07-24T18:53:32.454Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ba/b361d390451a37ca118e4ec7dccec690422e05bc85fba2ec72b06cefec9f/grpcio-1.74.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e7c4389771855a92934b2846bd807fc25a3dfa820fd912fe6bd8136026b2707", size = 6994132, upload-time = "2025-07-24T18:53:34.506Z" }, - { url = "https://files.pythonhosted.org/packages/3b/0c/3a5fa47d2437a44ced74141795ac0251bbddeae74bf81df3447edd767d27/grpcio-1.74.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cce634b10aeab37010449124814b05a62fb5f18928ca878f1bf4750d1f0c815b", size = 6489616, upload-time = "2025-07-24T18:53:36.217Z" }, - { url = "https://files.pythonhosted.org/packages/ae/95/ab64703b436d99dc5217228babc76047d60e9ad14df129e307b5fec81fd0/grpcio-1.74.0-cp312-cp312-win32.whl", hash = "sha256:885912559974df35d92219e2dc98f51a16a48395f37b92865ad45186f294096c", size = 3807083, upload-time = "2025-07-24T18:53:37.911Z" }, - { url = "https://files.pythonhosted.org/packages/84/59/900aa2445891fc47a33f7d2f76e00ca5d6ae6584b20d19af9c06fa09bf9a/grpcio-1.74.0-cp312-cp312-win_amd64.whl", hash = "sha256:42f8fee287427b94be63d916c90399ed310ed10aadbf9e2e5538b3e497d269bc", size = 4490123, upload-time = "2025-07-24T18:53:39.528Z" }, - { url = "https://files.pythonhosted.org/packages/d4/d8/1004a5f468715221450e66b051c839c2ce9a985aa3ee427422061fcbb6aa/grpcio-1.74.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:2bc2d7d8d184e2362b53905cb1708c84cb16354771c04b490485fa07ce3a1d89", size = 5449488, upload-time = "2025-07-24T18:53:41.174Z" }, - { url = "https://files.pythonhosted.org/packages/94/0e/33731a03f63740d7743dced423846c831d8e6da808fcd02821a4416df7fa/grpcio-1.74.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c14e803037e572c177ba54a3e090d6eb12efd795d49327c5ee2b3bddb836bf01", size = 10974059, upload-time = "2025-07-24T18:53:43.066Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c6/3d2c14d87771a421205bdca991467cfe473ee4c6a1231c1ede5248c62ab8/grpcio-1.74.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f6ec94f0e50eb8fa1744a731088b966427575e40c2944a980049798b127a687e", size = 5945647, upload-time = "2025-07-24T18:53:45.269Z" }, - { url = "https://files.pythonhosted.org/packages/c5/83/5a354c8aaff58594eef7fffebae41a0f8995a6258bbc6809b800c33d4c13/grpcio-1.74.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:566b9395b90cc3d0d0c6404bc8572c7c18786ede549cdb540ae27b58afe0fb91", size = 6626101, upload-time = "2025-07-24T18:53:47.015Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ca/4fdc7bf59bf6994aa45cbd4ef1055cd65e2884de6113dbd49f75498ddb08/grpcio-1.74.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1ea6176d7dfd5b941ea01c2ec34de9531ba494d541fe2057c904e601879f249", size = 6182562, upload-time = "2025-07-24T18:53:48.967Z" }, - { url = "https://files.pythonhosted.org/packages/fd/48/2869e5b2c1922583686f7ae674937986807c2f676d08be70d0a541316270/grpcio-1.74.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:64229c1e9cea079420527fa8ac45d80fc1e8d3f94deaa35643c381fa8d98f362", size = 6303425, upload-time = "2025-07-24T18:53:50.847Z" }, - { url = "https://files.pythonhosted.org/packages/a6/0e/bac93147b9a164f759497bc6913e74af1cb632c733c7af62c0336782bd38/grpcio-1.74.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:0f87bddd6e27fc776aacf7ebfec367b6d49cad0455123951e4488ea99d9b9b8f", size = 6996533, upload-time = "2025-07-24T18:53:52.747Z" }, - { url = "https://files.pythonhosted.org/packages/84/35/9f6b2503c1fd86d068b46818bbd7329db26a87cdd8c01e0d1a9abea1104c/grpcio-1.74.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3b03d8f2a07f0fea8c8f74deb59f8352b770e3900d143b3d1475effcb08eec20", size = 6491489, upload-time = "2025-07-24T18:53:55.06Z" }, - { url = "https://files.pythonhosted.org/packages/75/33/a04e99be2a82c4cbc4039eb3a76f6c3632932b9d5d295221389d10ac9ca7/grpcio-1.74.0-cp313-cp313-win32.whl", hash = "sha256:b6a73b2ba83e663b2480a90b82fdae6a7aa6427f62bf43b29912c0cfd1aa2bfa", size = 3805811, upload-time = "2025-07-24T18:53:56.798Z" }, - { url = "https://files.pythonhosted.org/packages/34/80/de3eb55eb581815342d097214bed4c59e806b05f1b3110df03b2280d6dfd/grpcio-1.74.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd3c71aeee838299c5887230b8a1822795325ddfea635edd82954c1eaa831e24", size = 4489214, upload-time = "2025-07-24T18:53:59.771Z" }, -] - [[package]] name = "h11" version = "0.16.0" @@ -621,18 +583,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/08/c1395a292bb23fd03bdf572a1357c5a733d3eecbab877641ceacab23db6e/importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580", size = 55767, upload-time = "2025-01-20T22:21:30.429Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971, upload-time = "2025-01-20T22:21:29.177Z" }, -] - [[package]] name = "jiter" version = "0.9.0" @@ -1127,199 +1077,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/58/37ae3ca75936b824a0a5ca30491c968192007857319d6836764b548b9d9b/openai-1.77.0-py3-none-any.whl", hash = "sha256:07706e91eb71631234996989a8ea991d5ee56f0744ef694c961e0824d4f39218", size = 662031, upload-time = "2025-05-02T19:17:26.151Z" }, ] -[[package]] -name = "openinference-instrumentation" -version = "0.1.44" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "openinference-semantic-conventions" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-sdk" }, - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/41/d9/c0d3040c0b5dc2b97ad20c35fb3fc1e3f2006bb4b08741ff325efcf3a96a/openinference_instrumentation-0.1.44.tar.gz", hash = "sha256:141953d2da33d54d428dfba2bfebb27ce0517dc43d52e1449a09db72ec7d318e", size = 23959, upload-time = "2026-02-01T01:45:55.88Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/6d/6a19587b26ffa273eb27ba7dd2482013afe3b47c8d9f1f39295216975f9f/openinference_instrumentation-0.1.44-py3-none-any.whl", hash = "sha256:86b2a8931e0f39ecfb739901f8987c654961da03baf3cfa5d5b4f45a96897b2d", size = 30093, upload-time = "2026-02-01T01:45:54.932Z" }, -] - -[[package]] -name = "openinference-instrumentation-langchain" -version = "0.1.58" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "openinference-instrumentation" }, - { name = "openinference-semantic-conventions" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7b/f7/ed82c3d146ca6f1b62dabb2e01fbee782a75245d694b23bc90232366dac7/openinference_instrumentation_langchain-0.1.58.tar.gz", hash = "sha256:36a1b1ad162c4e356bd28257173ee3171ad7788a96089553512c6288fa9a0f1c", size = 75239, upload-time = "2026-01-06T23:50:16.243Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/10/df4805c99e9b17fdd4496b080788340dd09ebc436dd5073e54a1c2633a04/openinference_instrumentation_langchain-0.1.58-py3-none-any.whl", hash = "sha256:9dd2e0b201131e53d9e520624ef4eea6268c08faab1dc10d64b52c60b5169d91", size = 24396, upload-time = "2026-01-06T23:50:14.022Z" }, -] - -[[package]] -name = "openinference-semantic-conventions" -version = "0.1.26" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/91/f67c1971deaf5b75dea84731393bca2042ff4a46acae9a727dfe267dd568/openinference_semantic_conventions-0.1.26.tar.gz", hash = "sha256:34dae06b40743fb7b846a36fd402810a554b2ec4ee96b9dd8b820663aee4a1f1", size = 12782, upload-time = "2026-02-01T01:09:46.095Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/ca/bb4b9cbd96f72600abec5280cf8ed67bcd849ed19b8bec919aec97adb61c/openinference_semantic_conventions-0.1.26-py3-none-any.whl", hash = "sha256:35b4f487d18ac7d016125c428c0d950dd290e18dafb99787880a9b2e05745f42", size = 10401, upload-time = "2026-02-01T01:09:44.781Z" }, -] - -[[package]] -name = "opentelemetry-api" -version = "1.36.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "importlib-metadata" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/27/d2/c782c88b8afbf961d6972428821c302bd1e9e7bc361352172f0ca31296e2/opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0", size = 64780, upload-time = "2025-07-29T15:12:06.02Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/ee/6b08dde0a022c463b88f55ae81149584b125a42183407dc1045c486cc870/opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c", size = 65564, upload-time = "2025-07-29T15:11:47.998Z" }, -] - -[[package]] -name = "opentelemetry-exporter-otlp" -version = "1.36.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-exporter-otlp-proto-grpc" }, - { name = "opentelemetry-exporter-otlp-proto-http" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/7f/d31294ac28d567a14aefd855756bab79fed69c5a75df712f228f10c47e04/opentelemetry_exporter_otlp-1.36.0.tar.gz", hash = "sha256:72f166ea5a8923ac42889337f903e93af57db8893de200369b07401e98e4e06b", size = 6144, upload-time = "2025-07-29T15:12:07.153Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/a2/8966111a285124f3d6156a663ddf2aeddd52843c1a3d6b56cbd9b6c3fd0e/opentelemetry_exporter_otlp-1.36.0-py3-none-any.whl", hash = "sha256:de93b7c45bcc78296998775d52add7c63729e83ef2cd6560730a6b336d7f6494", size = 7018, upload-time = "2025-07-29T15:11:50.498Z" }, -] - -[[package]] -name = "opentelemetry-exporter-otlp-proto-common" -version = "1.36.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-proto" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/da/7747e57eb341c59886052d733072bc878424bf20f1d8cf203d508bbece5b/opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf", size = 20302, upload-time = "2025-07-29T15:12:07.71Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/ed/22290dca7db78eb32e0101738366b5bbda00d0407f00feffb9bf8c3fdf87/opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840", size = 18349, upload-time = "2025-07-29T15:11:51.327Z" }, -] - -[[package]] -name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.36.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "googleapis-common-protos" }, - { name = "grpcio" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-proto-common" }, - { name = "opentelemetry-proto" }, - { name = "opentelemetry-sdk" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/72/6f/6c1b0bdd0446e5532294d1d41bf11fbaea39c8a2423a4cdfe4fe6b708127/opentelemetry_exporter_otlp_proto_grpc-1.36.0.tar.gz", hash = "sha256:b281afbf7036b325b3588b5b6c8bb175069e3978d1bd24071f4a59d04c1e5bbf", size = 23822, upload-time = "2025-07-29T15:12:08.292Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/67/5f6bd188d66d0fd8e81e681bbf5822e53eb150034e2611dd2b935d3ab61a/opentelemetry_exporter_otlp_proto_grpc-1.36.0-py3-none-any.whl", hash = "sha256:734e841fc6a5d6f30e7be4d8053adb703c70ca80c562ae24e8083a28fadef211", size = 18828, upload-time = "2025-07-29T15:11:52.235Z" }, -] - -[[package]] -name = "opentelemetry-exporter-otlp-proto-http" -version = "1.36.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "googleapis-common-protos" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-proto-common" }, - { name = "opentelemetry-proto" }, - { name = "opentelemetry-sdk" }, - { name = "requests" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/25/85/6632e7e5700ba1ce5b8a065315f92c1e6d787ccc4fb2bdab15139eaefc82/opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e", size = 16213, upload-time = "2025-07-29T15:12:08.932Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/41/a680d38b34f8f5ddbd78ed9f0042e1cc712d58ec7531924d71cb1e6c629d/opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902", size = 18752, upload-time = "2025-07-29T15:11:53.164Z" }, -] - -[[package]] -name = "opentelemetry-instrumentation" -version = "0.57b0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "packaging" }, - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/12/37/cf17cf28f945a3aca5a038cfbb45ee01317d4f7f3a0e5209920883fe9b08/opentelemetry_instrumentation-0.57b0.tar.gz", hash = "sha256:f2a30135ba77cdea2b0e1df272f4163c154e978f57214795d72f40befd4fcf05", size = 30807, upload-time = "2025-07-29T15:42:44.746Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/6f/f20cd1542959f43fb26a5bf9bb18cd81a1ea0700e8870c8f369bd07f5c65/opentelemetry_instrumentation-0.57b0-py3-none-any.whl", hash = "sha256:9109280f44882e07cec2850db28210b90600ae9110b42824d196de357cbddf7e", size = 32460, upload-time = "2025-07-29T15:41:40.883Z" }, -] - -[[package]] -name = "opentelemetry-instrumentation-openai" -version = "0.48.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "opentelemetry-semantic-conventions-ai" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/2e/9d32127aad2e48f7b05de10acedb96e8da77a7d3f7e4af50096f092cc65b/opentelemetry_instrumentation_openai-0.48.1.tar.gz", hash = "sha256:e081eeb6e5aa59f286abd70ce100212b93885ac23061cd4af304217aefc5347a", size = 26660, upload-time = "2025-11-17T15:26:46.197Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/77/c547fec1f0e6b6cebba850f6638c6d5ff8e9c3eb5266f71fda9badc1f2a2/opentelemetry_instrumentation_openai-0.48.1-py3-none-any.whl", hash = "sha256:9caaee00a60e0d03655aa80c378aa81fa1317f856e09575d23d3af5fb957b68a", size = 36656, upload-time = "2025-11-17T15:26:16.334Z" }, -] - -[[package]] -name = "opentelemetry-proto" -version = "1.36.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fd/02/f6556142301d136e3b7e95ab8ea6a5d9dc28d879a99f3dd673b5f97dca06/opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f", size = 46152, upload-time = "2025-07-29T15:12:15.717Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/57/3361e06136225be8180e879199caea520f38026f8071366241ac458beb8d/opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e", size = 72537, upload-time = "2025-07-29T15:12:02.243Z" }, -] - -[[package]] -name = "opentelemetry-sdk" -version = "1.36.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/85/8567a966b85a2d3f971c4d42f781c305b2b91c043724fa08fd37d158e9dc/opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581", size = 162557, upload-time = "2025-07-29T15:12:16.76Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/59/7bed362ad1137ba5886dac8439e84cd2df6d087be7c09574ece47ae9b22c/opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb", size = 119995, upload-time = "2025-07-29T15:12:03.181Z" }, -] - -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.57b0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7e/31/67dfa252ee88476a29200b0255bda8dfc2cf07b56ad66dc9a6221f7dc787/opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32", size = 124225, upload-time = "2025-07-29T15:12:17.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/75/7d591371c6c39c73de5ce5da5a2cc7b72d1d1cd3f8f4638f553c01c37b11/opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78", size = 201627, upload-time = "2025-07-29T15:12:04.174Z" }, -] - -[[package]] -name = "opentelemetry-semantic-conventions-ai" -version = "0.4.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/e6/40b59eda51ac47009fb47afcdf37c6938594a0bd7f3b9fadcbc6058248e3/opentelemetry_semantic_conventions_ai-0.4.13.tar.gz", hash = "sha256:94efa9fb4ffac18c45f54a3a338ffeb7eedb7e1bb4d147786e77202e159f0036", size = 5368, upload-time = "2025-08-22T10:14:17.387Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/b5/cf25da2218910f0d6cdf7f876a06bed118c4969eacaf60a887cbaef44f44/opentelemetry_semantic_conventions_ai-0.4.13-py3-none-any.whl", hash = "sha256:883a30a6bb5deaec0d646912b5f9f6dcbb9f6f72557b73d0f2560bf25d13e2d5", size = 6080, upload-time = "2025-08-22T10:14:16.477Z" }, -] - [[package]] name = "orjson" version = "3.10.18" @@ -2123,10 +1880,6 @@ dependencies = [ { name = "langchain-ollama" }, { name = "langchain-openai" }, { name = "langgraph" }, - { name = "openinference-instrumentation-langchain" }, - { name = "openinference-semantic-conventions" }, - { name = "opentelemetry-exporter-otlp" }, - { name = "opentelemetry-instrumentation-openai" }, { name = "pydantic-settings" }, { name = "python-keycloak" }, ] @@ -2139,67 +1892,10 @@ requires-dist = [ { name = "langchain-ollama", specifier = ">=0.2.1" }, { name = "langchain-openai", specifier = ">=0.3.7" }, { name = "langgraph", specifier = ">=0.2.55" }, - { name = "openinference-instrumentation-langchain", specifier = ">=0.1.27" }, - { name = "openinference-semantic-conventions", specifier = ">=0.1.12" }, - { name = "opentelemetry-exporter-otlp" }, - { name = "opentelemetry-instrumentation-openai", specifier = ">=0.34b0" }, { name = "pydantic-settings", specifier = ">=2.8.1" }, { name = "python-keycloak", specifier = ">=5.5.1" }, ] -[[package]] -name = "wrapt" -version = "1.17.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/f7/a2aab2cbc7a665efab072344a8949a71081eed1d2f451f7f7d2b966594a2/wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58", size = 53308, upload-time = "2025-01-14T10:33:33.992Z" }, - { url = "https://files.pythonhosted.org/packages/50/ff/149aba8365fdacef52b31a258c4dc1c57c79759c335eff0b3316a2664a64/wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda", size = 38488, upload-time = "2025-01-14T10:33:35.264Z" }, - { url = "https://files.pythonhosted.org/packages/65/46/5a917ce85b5c3b490d35c02bf71aedaa9f2f63f2d15d9949cc4ba56e8ba9/wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438", size = 38776, upload-time = "2025-01-14T10:33:38.28Z" }, - { url = "https://files.pythonhosted.org/packages/ca/74/336c918d2915a4943501c77566db41d1bd6e9f4dbc317f356b9a244dfe83/wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a", size = 83776, upload-time = "2025-01-14T10:33:40.678Z" }, - { url = "https://files.pythonhosted.org/packages/09/99/c0c844a5ccde0fe5761d4305485297f91d67cf2a1a824c5f282e661ec7ff/wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000", size = 75420, upload-time = "2025-01-14T10:33:41.868Z" }, - { url = "https://files.pythonhosted.org/packages/b4/b0/9fc566b0fe08b282c850063591a756057c3247b2362b9286429ec5bf1721/wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6", size = 83199, upload-time = "2025-01-14T10:33:43.598Z" }, - { url = "https://files.pythonhosted.org/packages/9d/4b/71996e62d543b0a0bd95dda485219856def3347e3e9380cc0d6cf10cfb2f/wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b", size = 82307, upload-time = "2025-01-14T10:33:48.499Z" }, - { url = "https://files.pythonhosted.org/packages/39/35/0282c0d8789c0dc9bcc738911776c762a701f95cfe113fb8f0b40e45c2b9/wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662", size = 75025, upload-time = "2025-01-14T10:33:51.191Z" }, - { url = "https://files.pythonhosted.org/packages/4f/6d/90c9fd2c3c6fee181feecb620d95105370198b6b98a0770cba090441a828/wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72", size = 81879, upload-time = "2025-01-14T10:33:52.328Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fa/9fb6e594f2ce03ef03eddbdb5f4f90acb1452221a5351116c7c4708ac865/wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317", size = 36419, upload-time = "2025-01-14T10:33:53.551Z" }, - { url = "https://files.pythonhosted.org/packages/47/f8/fb1773491a253cbc123c5d5dc15c86041f746ed30416535f2a8df1f4a392/wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3", size = 38773, upload-time = "2025-01-14T10:33:56.323Z" }, - { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799, upload-time = "2025-01-14T10:33:57.4Z" }, - { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821, upload-time = "2025-01-14T10:33:59.334Z" }, - { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919, upload-time = "2025-01-14T10:34:04.093Z" }, - { url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721, upload-time = "2025-01-14T10:34:07.163Z" }, - { url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899, upload-time = "2025-01-14T10:34:09.82Z" }, - { url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222, upload-time = "2025-01-14T10:34:11.258Z" }, - { url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707, upload-time = "2025-01-14T10:34:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685, upload-time = "2025-01-14T10:34:15.043Z" }, - { url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567, upload-time = "2025-01-14T10:34:16.563Z" }, - { url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672, upload-time = "2025-01-14T10:34:17.727Z" }, - { url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865, upload-time = "2025-01-14T10:34:19.577Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800, upload-time = "2025-01-14T10:34:21.571Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824, upload-time = "2025-01-14T10:34:22.999Z" }, - { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920, upload-time = "2025-01-14T10:34:25.386Z" }, - { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690, upload-time = "2025-01-14T10:34:28.058Z" }, - { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861, upload-time = "2025-01-14T10:34:29.167Z" }, - { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174, upload-time = "2025-01-14T10:34:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721, upload-time = "2025-01-14T10:34:32.91Z" }, - { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763, upload-time = "2025-01-14T10:34:34.903Z" }, - { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585, upload-time = "2025-01-14T10:34:36.13Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676, upload-time = "2025-01-14T10:34:37.962Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871, upload-time = "2025-01-14T10:34:39.13Z" }, - { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312, upload-time = "2025-01-14T10:34:40.604Z" }, - { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062, upload-time = "2025-01-14T10:34:45.011Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155, upload-time = "2025-01-14T10:34:47.25Z" }, - { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471, upload-time = "2025-01-14T10:34:50.934Z" }, - { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208, upload-time = "2025-01-14T10:34:52.297Z" }, - { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339, upload-time = "2025-01-14T10:34:53.489Z" }, - { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232, upload-time = "2025-01-14T10:34:55.327Z" }, - { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476, upload-time = "2025-01-14T10:34:58.055Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377, upload-time = "2025-01-14T10:34:59.3Z" }, - { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986, upload-time = "2025-01-14T10:35:00.498Z" }, - { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750, upload-time = "2025-01-14T10:35:03.378Z" }, - { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" }, -] - [[package]] name = "xxhash" version = "3.5.0" @@ -2335,15 +2031,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ea/1f/70c57b3d7278e94ed22d85e09685d3f0a38ebdd8c5c73b65ba4c0d0fe002/yarl-1.20.0-py3-none-any.whl", hash = "sha256:5d0fe6af927a47a230f31e6004621fd0959eaa915fc62acfafa67ff7229a3124", size = 46124, upload-time = "2025-04-17T00:45:12.199Z" }, ] -[[package]] -name = "zipp" -version = "3.21.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545, upload-time = "2024-11-10T15:05:20.202Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630, upload-time = "2024-11-10T15:05:19.275Z" }, -] - [[package]] name = "zstandard" version = "0.23.0" From d2abda73c08307e07818270ff6c78f531bb97db0 Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Tue, 17 Feb 2026 16:46:09 +0100 Subject: [PATCH 03/10] feat: Shield agent execution from SSE client disconnects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap execute() with asyncio.shield() so LangGraph continues running even when the SSE client disconnects. Event emission errors from the closed connection are caught silently β€” the agent completes the task and stores the result in the task store regardless. This enables the ext_proc to recover the full trace via tasks/resubscribe after the original client disconnects. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ladislav Smola --- .../src/weather_service/agent.py | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/a2a/weather_service/src/weather_service/agent.py b/a2a/weather_service/src/weather_service/agent.py index d3d39c1a..f46bee01 100644 --- a/a2a/weather_service/src/weather_service/agent.py +++ b/a2a/weather_service/src/weather_service/agent.py @@ -1,3 +1,4 @@ +import asyncio import logging import os import uvicorn @@ -89,6 +90,20 @@ class WeatherExecutor(AgentExecutor): A class to handle weather assistant execution for A2A Agent. """ async def execute(self, context: RequestContext, event_queue: EventQueue): + """ + Shield the agent execution from SSE client disconnects. + + The A2A SDK cancels execute() when the SSE connection drops. By + shielding the actual work, the LangGraph execution runs to completion + and the result is stored in the task store regardless of whether + anyone is listening to the stream. + """ + try: + await asyncio.shield(self._do_execute(context, event_queue)) + except asyncio.CancelledError: + logger.info("Client disconnected, but agent execution continues in background") + + async def _do_execute(self, context: RequestContext, event_queue: EventQueue): """ The agent allows to retrieve weather info through a natural language conversational interface """ @@ -122,23 +137,33 @@ async def execute(self, context: RequestContext, event_queue: EventQueue): logger.info(f'Successfully connected to MCP server. Available tools: {[tool.name for tool in tools]}') except Exception as tool_error: logger.error(f'Failed to connect to MCP server: {tool_error}') - await event_emitter.emit_event(f"Error: Cannot connect to MCP weather service at {os.getenv('MCP_URL', 'http://localhost:8000/sse')}. Please ensure the weather MCP server is running. Error: {tool_error}", failed=True) + try: + await event_emitter.emit_event(f"Error: Cannot connect to MCP weather service at {os.getenv('MCP_URL', 'http://localhost:8000/sse')}. Please ensure the weather MCP server is running. Error: {tool_error}", failed=True) + except Exception: + pass return graph = await get_graph(mcpclient) async for event in graph.astream(input, stream_mode="updates"): - await event_emitter.emit_event( - "\n".join( - f"πŸšΆβ€β™‚οΈ{key}: {str(value)[:256] + '...' if len(str(value)) > 256 else str(value)}" - for key, value in event.items() + try: + await event_emitter.emit_event( + "\n".join( + f"πŸšΆβ€β™‚οΈ{key}: {str(value)[:256] + '...' if len(str(value)) > 256 else str(value)}" + for key, value in event.items() + ) + + "\n" ) - + "\n" - ) + except Exception: + # SSE connection dropped β€” continue processing, skip event emission + logger.debug("Event emission failed (client likely disconnected), continuing execution") output = event logger.info(f'event: {event}') output = output.get("assistant", {}).get("final_answer") - await event_emitter.emit_event(str(output), final=True) + try: + await event_emitter.emit_event(str(output), final=True) + except Exception: + logger.info(f"Final event emission failed (client disconnected), output: {str(output)[:100]}") async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None: """ From 8600eccb49e3016ce271744362442b831d7fd83c Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Tue, 17 Feb 2026 16:47:13 +0100 Subject: [PATCH 04/10] fix: Support explicit tasks/cancel while shielding SSE disconnects Add _cancelled flag to distinguish between SSE disconnect (continue execution) and explicit tasks/cancel (propagate cancel to task). Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ladislav Smola --- .../src/weather_service/agent.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/a2a/weather_service/src/weather_service/agent.py b/a2a/weather_service/src/weather_service/agent.py index f46bee01..94f68180 100644 --- a/a2a/weather_service/src/weather_service/agent.py +++ b/a2a/weather_service/src/weather_service/agent.py @@ -89,6 +89,9 @@ class WeatherExecutor(AgentExecutor): """ A class to handle weather assistant execution for A2A Agent. """ + def __init__(self): + self._cancelled = False + async def execute(self, context: RequestContext, event_queue: EventQueue): """ Shield the agent execution from SSE client disconnects. @@ -97,11 +100,22 @@ async def execute(self, context: RequestContext, event_queue: EventQueue): shielding the actual work, the LangGraph execution runs to completion and the result is stored in the task store regardless of whether anyone is listening to the stream. + + Explicit cancellation via tasks/cancel still works β€” it sets the + _cancelled flag which the shielded execution checks. """ + self._cancelled = False + task = asyncio.ensure_future(self._do_execute(context, event_queue)) try: - await asyncio.shield(self._do_execute(context, event_queue)) + await asyncio.shield(task) except asyncio.CancelledError: - logger.info("Client disconnected, but agent execution continues in background") + if self._cancelled: + # Explicit cancel via tasks/cancel β€” propagate to the task + task.cancel() + logger.info("Agent execution cancelled via tasks/cancel") + else: + # SSE disconnect β€” let the task continue in the background + logger.info("Client disconnected, agent execution continues in background") async def _do_execute(self, context: RequestContext, event_queue: EventQueue): """ @@ -166,10 +180,9 @@ async def _do_execute(self, context: RequestContext, event_queue: EventQueue): logger.info(f"Final event emission failed (client disconnected), output: {str(output)[:100]}") async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None: - """ - Not implemented - """ - raise Exception("cancel not supported") + """Cancel the agent execution.""" + self._cancelled = True + logger.info("Cancel requested for agent execution") def run(): """ From d5d7e08fa071aad4f731371cb7473daee7a1b935 Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Tue, 17 Feb 2026 17:09:54 +0100 Subject: [PATCH 05/10] feat: Update task store directly on SSE disconnect for trace recovery When the final event emission fails due to client disconnect, save the completed task with artifacts directly to the InMemoryTaskStore. This allows the ext_proc's tasks/resubscribe to find the completed result and capture the output for the trace. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ladislav Smola --- .../src/weather_service/agent.py | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/a2a/weather_service/src/weather_service/agent.py b/a2a/weather_service/src/weather_service/agent.py index 94f68180..5cf03cbc 100644 --- a/a2a/weather_service/src/weather_service/agent.py +++ b/a2a/weather_service/src/weather_service/agent.py @@ -89,8 +89,9 @@ class WeatherExecutor(AgentExecutor): """ A class to handle weather assistant execution for A2A Agent. """ - def __init__(self): + def __init__(self, task_store=None): self._cancelled = False + self._task_store = task_store async def execute(self, context: RequestContext, event_queue: EventQueue): """ @@ -177,7 +178,18 @@ async def _do_execute(self, context: RequestContext, event_queue: EventQueue): try: await event_emitter.emit_event(str(output), final=True) except Exception: - logger.info(f"Final event emission failed (client disconnected), output: {str(output)[:100]}") + logger.info(f"Final event emission failed (client disconnected), updating task store directly") + # Update task store directly so tasks/get and tasks/resubscribe + # can return the completed result even after SSE disconnect + if self._task_store and task: + try: + from a2a.types import TaskStatus, TaskState as TS, Artifact, TextPart as TP + task.status = TaskStatus(state=TS.completed) + task.artifacts = [Artifact(parts=[TP(text=str(output))])] + await self._task_store.save(task) + logger.info(f"Task {task.id} saved to store with output ({len(str(output))} chars)") + except Exception as e: + logger.error(f"Failed to save task to store: {e}") async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None: """Cancel the agent execution.""" @@ -190,9 +202,10 @@ def run(): """ agent_card = get_agent_card(host="0.0.0.0", port=8000) + task_store = InMemoryTaskStore() request_handler = DefaultRequestHandler( - agent_executor=WeatherExecutor(), - task_store=InMemoryTaskStore(), + agent_executor=WeatherExecutor(task_store=task_store), + task_store=task_store, ) server = A2AStarletteApplication( From 5ef5ef50e44053d693f5afc3f73b0af49b1f685a Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Tue, 17 Feb 2026 18:22:57 +0100 Subject: [PATCH 06/10] feat: Emit LangGraph events as valid JSON for ext_proc parsing Serialize LangChain messages via model_dump() and json.dumps() instead of Python str(). This produces valid JSON that the ext_proc can parse to extract GenAI semantic convention attributes (token counts, model name, tool names) without regex. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ladislav Smola --- .../src/weather_service/agent.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/a2a/weather_service/src/weather_service/agent.py b/a2a/weather_service/src/weather_service/agent.py index 5cf03cbc..c9b0ba13 100644 --- a/a2a/weather_service/src/weather_service/agent.py +++ b/a2a/weather_service/src/weather_service/agent.py @@ -1,4 +1,5 @@ import asyncio +import json import logging import os import uvicorn @@ -20,6 +21,23 @@ logger = logging.getLogger(__name__) +def _serialize_event(value): + """Serialize a LangGraph event value to JSON-compatible dict. + + Converts LangChain message objects to dicts via model_dump() so the + ext_proc can parse structured GenAI attributes (token counts, model, etc). + """ + if isinstance(value, dict) and "messages" in value: + msgs = [] + for msg in value["messages"]: + if hasattr(msg, "model_dump"): + msgs.append(msg.model_dump()) + else: + msgs.append(str(msg)) + return {"messages": msgs} + return value + + def get_agent_card(host: str, port: int): """Returns the Agent Card for the AG2 Agent.""" capabilities = AgentCapabilities(streaming=True) @@ -163,7 +181,7 @@ async def _do_execute(self, context: RequestContext, event_queue: EventQueue): try: await event_emitter.emit_event( "\n".join( - f"πŸšΆβ€β™‚οΈ{key}: {str(value)[:256] + '...' if len(str(value)) > 256 else str(value)}" + f"πŸšΆβ€β™‚οΈ{key}: {json.dumps(_serialize_event(value), default=str)}" for key, value in event.items() ) + "\n" From bc4ca247a4cc63202d8a30d72358d71fbc5c83c2 Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Tue, 17 Feb 2026 20:33:40 +0100 Subject: [PATCH 07/10] fix: Always save completed task to store for trace recovery The emit_event(final=True) enqueues to EventQueue successfully even after SSE disconnect, but the consumer is gone so the task store never gets the artifact. Save the completed task with artifact directly to the store after every execution, not just on failure. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ladislav Smola --- .../src/weather_service/agent.py | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/a2a/weather_service/src/weather_service/agent.py b/a2a/weather_service/src/weather_service/agent.py index c9b0ba13..e9e67b6d 100644 --- a/a2a/weather_service/src/weather_service/agent.py +++ b/a2a/weather_service/src/weather_service/agent.py @@ -196,18 +196,21 @@ async def _do_execute(self, context: RequestContext, event_queue: EventQueue): try: await event_emitter.emit_event(str(output), final=True) except Exception: - logger.info(f"Final event emission failed (client disconnected), updating task store directly") - # Update task store directly so tasks/get and tasks/resubscribe - # can return the completed result even after SSE disconnect - if self._task_store and task: - try: - from a2a.types import TaskStatus, TaskState as TS, Artifact, TextPart as TP - task.status = TaskStatus(state=TS.completed) - task.artifacts = [Artifact(parts=[TP(text=str(output))])] - await self._task_store.save(task) - logger.info(f"Task {task.id} saved to store with output ({len(str(output))} chars)") - except Exception as e: - logger.error(f"Failed to save task to store: {e}") + logger.info(f"Final event emission failed (client disconnected)") + + # Always save completed task with artifact to the store. + # The emit_event may succeed (enqueue) but the SSE consumer may be + # gone, so the task store never gets the artifact via the normal path. + # This ensures tasks/get can return the result for trace recovery. + if self._task_store and task and output: + try: + from a2a.types import TaskStatus, TaskState as TS, Artifact, TextPart as TP + task.status = TaskStatus(state=TS.completed) + task.artifacts = [Artifact(parts=[TP(text=str(output))])] + await self._task_store.save(task) + logger.info(f"Task {task.id} saved to store with output ({len(str(output))} chars)") + except Exception as e: + logger.error(f"Failed to save task to store: {e}") async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None: """Cancel the agent execution.""" From 0f34c8247c3e24a4fedd5ddf60277cd35bd8c37d Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Tue, 17 Feb 2026 20:40:55 +0100 Subject: [PATCH 08/10] fix: Add required artifactId when saving task to store Artifact constructor requires artifactId field. Generate a UUID. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ladislav Smola --- a2a/weather_service/src/weather_service/agent.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/a2a/weather_service/src/weather_service/agent.py b/a2a/weather_service/src/weather_service/agent.py index e9e67b6d..a2717e02 100644 --- a/a2a/weather_service/src/weather_service/agent.py +++ b/a2a/weather_service/src/weather_service/agent.py @@ -204,9 +204,10 @@ async def _do_execute(self, context: RequestContext, event_queue: EventQueue): # This ensures tasks/get can return the result for trace recovery. if self._task_store and task and output: try: + import uuid from a2a.types import TaskStatus, TaskState as TS, Artifact, TextPart as TP task.status = TaskStatus(state=TS.completed) - task.artifacts = [Artifact(parts=[TP(text=str(output))])] + task.artifacts = [Artifact(artifactId=str(uuid.uuid4()), parts=[TP(text=str(output))])] await self._task_store.save(task) logger.info(f"Task {task.id} saved to store with output ({len(str(output))} chars)") except Exception as e: From ffeb09319755b9962853633a0343bdaa18236ce3 Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Wed, 18 Feb 2026 09:08:50 +0100 Subject: [PATCH 09/10] feat: Add OTEL_INSTRUMENT env var for auto-instrumentation modes Support three instrumentation modes via OTEL_INSTRUMENT env var: - none: no agent-side instrumentation (ext_proc sidecar only) - openinference: LangChainInstrumentor (OpenInference conventions) - openai: OpenAIInstrumentor (gen_ai.* conventions) Auto-instrumented spans become children of the ext_proc root span via W3C traceparent propagation. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ladislav Smola --- a2a/weather_service/pyproject.toml | 4 + .../src/weather_service/agent.py | 4 + .../src/weather_service/observability.py | 81 ++++++ a2a/weather_service/uv.lock | 250 ++++++++++++++++++ 4 files changed, 339 insertions(+) create mode 100644 a2a/weather_service/src/weather_service/observability.py diff --git a/a2a/weather_service/pyproject.toml b/a2a/weather_service/pyproject.toml index 16dbe1d9..63c90e42 100644 --- a/a2a/weather_service/pyproject.toml +++ b/a2a/weather_service/pyproject.toml @@ -17,6 +17,10 @@ dependencies = [ "pydantic-settings>=2.8.1", "langchain-mcp-adapters>=0.1.0", "python-keycloak>=5.5.1", + "opentelemetry-sdk>=1.25.0", + "opentelemetry-exporter-otlp-proto-http>=1.25.0", + "openinference-instrumentation-langchain>=0.1.36", + "opentelemetry-instrumentation-openai>=0.42.0", ] [project.scripts] diff --git a/a2a/weather_service/src/weather_service/agent.py b/a2a/weather_service/src/weather_service/agent.py index a2717e02..cdc480e8 100644 --- a/a2a/weather_service/src/weather_service/agent.py +++ b/a2a/weather_service/src/weather_service/agent.py @@ -5,6 +5,10 @@ import uvicorn from textwrap import dedent +# Initialize OTEL before importing instrumented libraries +from weather_service.observability import setup_observability +setup_observability() + from a2a.server.agent_execution import AgentExecutor, RequestContext from a2a.server.apps import A2AStarletteApplication from a2a.server.events.event_queue import EventQueue diff --git a/a2a/weather_service/src/weather_service/observability.py b/a2a/weather_service/src/weather_service/observability.py new file mode 100644 index 00000000..d79df2c6 --- /dev/null +++ b/a2a/weather_service/src/weather_service/observability.py @@ -0,0 +1,81 @@ +""" +Optional OTEL auto-instrumentation for Weather Agent. + +Controlled by OTEL_INSTRUMENT env var: + - "none" β€” no agent-side instrumentation (ext_proc sidecar only) + - "openinference" β€” LangChainInstrumentor (OpenInference conventions) + - "openai" β€” OpenAIInstrumentor (gen_ai.* conventions) + +The ext_proc sidecar creates the root span and injects traceparent. +Auto-instrumentation here creates child spans under that root. +""" + +import logging +import os + +logger = logging.getLogger(__name__) + +OTEL_INSTRUMENT = os.getenv("OTEL_INSTRUMENT", "none").lower() + + +def setup_observability(): + """Initialize OTEL tracing and auto-instrumentation based on OTEL_INSTRUMENT env var.""" + if OTEL_INSTRUMENT == "none": + logger.info("[OTEL] Instrumentation disabled (OTEL_INSTRUMENT=none)") + return + + # Set up TracerProvider with OTLP exporter + from opentelemetry import trace + from opentelemetry.sdk.resources import Resource, SERVICE_NAME, SERVICE_VERSION + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import BatchSpanProcessor + from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter + from opentelemetry.propagate import set_global_textmap + from opentelemetry.propagators.composite import CompositePropagator + from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator + from opentelemetry.baggage.propagation import W3CBaggagePropagator + + service_name = os.getenv("OTEL_SERVICE_NAME", "weather-service") + otlp_endpoint = os.getenv( + "OTEL_EXPORTER_OTLP_ENDPOINT", + "http://otel-collector.kagenti-system.svc.cluster.local:8335", + ) + if not otlp_endpoint.endswith("/v1/traces"): + otlp_endpoint = otlp_endpoint.rstrip("/") + "/v1/traces" + + resource = Resource({ + SERVICE_NAME: service_name, + SERVICE_VERSION: "1.0.0", + }) + provider = TracerProvider(resource=resource) + provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=otlp_endpoint))) + trace.set_tracer_provider(provider) + + # W3C Trace Context propagation so auto-instrumented spans + # become children of the ext_proc root span (via traceparent header) + set_global_textmap(CompositePropagator([ + TraceContextTextMapPropagator(), + W3CBaggagePropagator(), + ])) + + logger.info(f"[OTEL] TracerProvider initialized: service={service_name} endpoint={otlp_endpoint}") + + # Enable auto-instrumentation based on mode + if OTEL_INSTRUMENT == "openinference": + try: + from openinference.instrumentation.langchain import LangChainInstrumentor + LangChainInstrumentor().instrument() + logger.info("[OTEL] LangChain instrumented with OpenInference") + except ImportError: + logger.warning("[OTEL] openinference-instrumentation-langchain not installed") + + elif OTEL_INSTRUMENT == "openai": + try: + from opentelemetry.instrumentation.openai import OpenAIInstrumentor + OpenAIInstrumentor().instrument() + logger.info("[OTEL] OpenAI instrumented with GenAI semantic conventions") + except ImportError: + logger.warning("[OTEL] opentelemetry-instrumentation-openai not installed") + + else: + logger.warning(f"[OTEL] Unknown OTEL_INSTRUMENT value: {OTEL_INSTRUMENT}") diff --git a/a2a/weather_service/uv.lock b/a2a/weather_service/uv.lock index dcc093b2..cbf94766 100644 --- a/a2a/weather_service/uv.lock +++ b/a2a/weather_service/uv.lock @@ -583,6 +583,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, +] + [[package]] name = "jiter" version = "0.9.0" @@ -1077,6 +1089,168 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/58/37ae3ca75936b824a0a5ca30491c968192007857319d6836764b548b9d9b/openai-1.77.0-py3-none-any.whl", hash = "sha256:07706e91eb71631234996989a8ea991d5ee56f0744ef694c961e0824d4f39218", size = 662031, upload-time = "2025-05-02T19:17:26.151Z" }, ] +[[package]] +name = "openinference-instrumentation" +version = "0.1.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openinference-semantic-conventions" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/d9/c0d3040c0b5dc2b97ad20c35fb3fc1e3f2006bb4b08741ff325efcf3a96a/openinference_instrumentation-0.1.44.tar.gz", hash = "sha256:141953d2da33d54d428dfba2bfebb27ce0517dc43d52e1449a09db72ec7d318e", size = 23959, upload-time = "2026-02-01T01:45:55.88Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/6d/6a19587b26ffa273eb27ba7dd2482013afe3b47c8d9f1f39295216975f9f/openinference_instrumentation-0.1.44-py3-none-any.whl", hash = "sha256:86b2a8931e0f39ecfb739901f8987c654961da03baf3cfa5d5b4f45a96897b2d", size = 30093, upload-time = "2026-02-01T01:45:54.932Z" }, +] + +[[package]] +name = "openinference-instrumentation-langchain" +version = "0.1.59" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openinference-instrumentation" }, + { name = "openinference-semantic-conventions" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/48/f36d8bb49e7aad0edc1cc7985369f8322fff11c1628d56256a11013cc8ab/openinference_instrumentation_langchain-0.1.59.tar.gz", hash = "sha256:5a49d6c833e2462a1dc8f5b6dc79fb6c86164d57feeb2cc66ada404751acf48d", size = 75140, upload-time = "2026-02-12T17:31:43.557Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/90/ed6e921ebf4c5821727f20ea5d4e751b1634e72d0dbfd039662450d6d2be/openinference_instrumentation_langchain-0.1.59-py3-none-any.whl", hash = "sha256:7432234b3cf84949ea447ab03912184073d63bfa07a1f10ca59c7d1fb2d2326f", size = 24393, upload-time = "2026-02-12T17:31:42.693Z" }, +] + +[[package]] +name = "openinference-semantic-conventions" +version = "0.1.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/91/f67c1971deaf5b75dea84731393bca2042ff4a46acae9a727dfe267dd568/openinference_semantic_conventions-0.1.26.tar.gz", hash = "sha256:34dae06b40743fb7b846a36fd402810a554b2ec4ee96b9dd8b820663aee4a1f1", size = 12782, upload-time = "2026-02-01T01:09:46.095Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/ca/bb4b9cbd96f72600abec5280cf8ed67bcd849ed19b8bec919aec97adb61c/openinference_semantic_conventions-0.1.26-py3-none-any.whl", hash = "sha256:35b4f487d18ac7d016125c428c0d950dd290e18dafb99787880a9b2e05745f42", size = 10401, upload-time = "2026-02-01T01:09:44.781Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde", size = 18366, upload-time = "2025-12-11T13:32:20.2Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size = 17288, upload-time = "2025-12-11T13:32:42.029Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985", size = 19641, upload-time = "2025-12-11T13:32:22.248Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-openai" +version = "0.52.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-semantic-conventions-ai" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/0c/01ed8d35ffc9a2279ed983a46283520904d47577a895c3072dad7dde932d/opentelemetry_instrumentation_openai-0.52.3.tar.gz", hash = "sha256:6f88c48538b0829b8cb62d4a80aa0f3f73e22bfab59c98f67ef251a1e5bd1d32", size = 6978364, upload-time = "2026-02-10T14:55:13.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/3f/af4f73cd29b9fdf27dac9f0a76c5e037308f7f37ba3af24b2fdadd76cb30/opentelemetry_instrumentation_openai-0.52.3-py3-none-any.whl", hash = "sha256:7cd786fc2d6663c0a02bc0b9fdc54e54492107f50436a0bef3a610ecaf55afbc", size = 43082, upload-time = "2026-02-10T14:54:28.563Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions-ai" +version = "0.4.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e6/40b59eda51ac47009fb47afcdf37c6938594a0bd7f3b9fadcbc6058248e3/opentelemetry_semantic_conventions_ai-0.4.13.tar.gz", hash = "sha256:94efa9fb4ffac18c45f54a3a338ffeb7eedb7e1bb4d147786e77202e159f0036", size = 5368, upload-time = "2025-08-22T10:14:17.387Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/b5/cf25da2218910f0d6cdf7f876a06bed118c4969eacaf60a887cbaef44f44/opentelemetry_semantic_conventions_ai-0.4.13-py3-none-any.whl", hash = "sha256:883a30a6bb5deaec0d646912b5f9f6dcbb9f6f72557b73d0f2560bf25d13e2d5", size = 6080, upload-time = "2025-08-22T10:14:16.477Z" }, +] + [[package]] name = "orjson" version = "3.10.18" @@ -1880,6 +2054,10 @@ dependencies = [ { name = "langchain-ollama" }, { name = "langchain-openai" }, { name = "langgraph" }, + { name = "openinference-instrumentation-langchain" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation-openai" }, + { name = "opentelemetry-sdk" }, { name = "pydantic-settings" }, { name = "python-keycloak" }, ] @@ -1892,10 +2070,73 @@ requires-dist = [ { name = "langchain-ollama", specifier = ">=0.2.1" }, { name = "langchain-openai", specifier = ">=0.3.7" }, { name = "langgraph", specifier = ">=0.2.55" }, + { name = "openinference-instrumentation-langchain", specifier = ">=0.1.36" }, + { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.25.0" }, + { name = "opentelemetry-instrumentation-openai", specifier = ">=0.42.0" }, + { name = "opentelemetry-sdk", specifier = ">=1.25.0" }, { name = "pydantic-settings", specifier = ">=2.8.1" }, { name = "python-keycloak", specifier = ">=5.5.1" }, ] +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" }, + { url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" }, + { url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" }, + { url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" }, + { url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + [[package]] name = "xxhash" version = "3.5.0" @@ -2031,6 +2272,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ea/1f/70c57b3d7278e94ed22d85e09685d3f0a38ebdd8c5c73b65ba4c0d0fe002/yarl-1.20.0-py3-none-any.whl", hash = "sha256:5d0fe6af927a47a230f31e6004621fd0959eaa915fc62acfafa67ff7229a3124", size = 46124, upload-time = "2025-04-17T00:45:12.199Z" }, ] +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] + [[package]] name = "zstandard" version = "0.23.0" From 58760aac75595dfb86b87d76dd983ac8142d95e4 Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Wed, 18 Feb 2026 09:26:33 +0100 Subject: [PATCH 10/10] fix: Add ASGI middleware to extract traceparent from ext_proc Without ASGI middleware, the LangChain/OpenAI auto-instrumentors create orphaned traces (separate from the ext_proc root span). Add OpenTelemetryMiddleware that extracts the traceparent header injected by the ext_proc sidecar and sets it as the active trace context. Auto-instrumented spans now become proper children of the ext_proc root span. The ASGI middleware is only enabled when OTEL_INSTRUMENT != none. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Ladislav Smola --- a2a/weather_service/pyproject.toml | 1 + .../src/weather_service/agent.py | 5 ++- .../src/weather_service/observability.py | 37 +++++++++++++++++-- a2a/weather_service/uv.lock | 36 ++++++++++++++++++ 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/a2a/weather_service/pyproject.toml b/a2a/weather_service/pyproject.toml index 63c90e42..f486969b 100644 --- a/a2a/weather_service/pyproject.toml +++ b/a2a/weather_service/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "opentelemetry-exporter-otlp-proto-http>=1.25.0", "openinference-instrumentation-langchain>=0.1.36", "opentelemetry-instrumentation-openai>=0.42.0", + "opentelemetry-instrumentation-asgi>=0.46b0", ] [project.scripts] diff --git a/a2a/weather_service/src/weather_service/agent.py b/a2a/weather_service/src/weather_service/agent.py index cdc480e8..9347e5ef 100644 --- a/a2a/weather_service/src/weather_service/agent.py +++ b/a2a/weather_service/src/weather_service/agent.py @@ -6,7 +6,7 @@ from textwrap import dedent # Initialize OTEL before importing instrumented libraries -from weather_service.observability import setup_observability +from weather_service.observability import setup_observability, wrap_asgi_app setup_observability() from a2a.server.agent_execution import AgentExecutor, RequestContext @@ -250,4 +250,7 @@ def run(): name='agent_card_new', )) + # Wrap with OTEL ASGI middleware to extract traceparent from ext_proc + app = wrap_asgi_app(app) + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/a2a/weather_service/src/weather_service/observability.py b/a2a/weather_service/src/weather_service/observability.py index d79df2c6..2682f7b9 100644 --- a/a2a/weather_service/src/weather_service/observability.py +++ b/a2a/weather_service/src/weather_service/observability.py @@ -7,7 +7,8 @@ - "openai" β€” OpenAIInstrumentor (gen_ai.* conventions) The ext_proc sidecar creates the root span and injects traceparent. -Auto-instrumentation here creates child spans under that root. +ASGI middleware here extracts that traceparent so auto-instrumented +spans become children of the ext_proc root span. """ import logging @@ -17,9 +18,14 @@ OTEL_INSTRUMENT = os.getenv("OTEL_INSTRUMENT", "none").lower() +# Stash the provider so wrap_asgi_app can use it +_tracer_provider = None + def setup_observability(): """Initialize OTEL tracing and auto-instrumentation based on OTEL_INSTRUMENT env var.""" + global _tracer_provider + if OTEL_INSTRUMENT == "none": logger.info("[OTEL] Instrumentation disabled (OTEL_INSTRUMENT=none)") return @@ -47,9 +53,9 @@ def setup_observability(): SERVICE_NAME: service_name, SERVICE_VERSION: "1.0.0", }) - provider = TracerProvider(resource=resource) - provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=otlp_endpoint))) - trace.set_tracer_provider(provider) + _tracer_provider = TracerProvider(resource=resource) + _tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=otlp_endpoint))) + trace.set_tracer_provider(_tracer_provider) # W3C Trace Context propagation so auto-instrumented spans # become children of the ext_proc root span (via traceparent header) @@ -79,3 +85,26 @@ def setup_observability(): else: logger.warning(f"[OTEL] Unknown OTEL_INSTRUMENT value: {OTEL_INSTRUMENT}") + + +def wrap_asgi_app(app): + """Wrap a Starlette/ASGI app with OpenTelemetry ASGI middleware. + + This extracts the traceparent header from incoming requests (injected + by the ext_proc sidecar) and sets it as the active trace context. + All auto-instrumented spans (LangChain, OpenAI) then become children + of the ext_proc root span. + + Call this after app = server.build() and before uvicorn.run(app). + """ + if OTEL_INSTRUMENT == "none" or _tracer_provider is None: + return app + + try: + from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware + wrapped = OpenTelemetryMiddleware(app, tracer_provider=_tracer_provider) + logger.info("[OTEL] ASGI middleware installed (traceparent extraction enabled)") + return wrapped + except ImportError: + logger.warning("[OTEL] opentelemetry-instrumentation-asgi not installed, traceparent will not be extracted") + return app diff --git a/a2a/weather_service/uv.lock b/a2a/weather_service/uv.lock index cbf94766..901d6384 100644 --- a/a2a/weather_service/uv.lock +++ b/a2a/weather_service/uv.lock @@ -141,6 +141,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, ] +[[package]] +name = "asgiref" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" }, +] + [[package]] name = "async-property" version = "0.2.2" @@ -1188,6 +1197,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, ] +[[package]] +name = "opentelemetry-instrumentation-asgi" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/db/851fa88db7441da82d50bd80f2de5ee55213782e25dc858e04d0c9961d60/opentelemetry_instrumentation_asgi-0.60b1.tar.gz", hash = "sha256:16bfbe595cd24cda309a957456d0fc2523f41bc7b076d1f2d7e98a1ad9876d6f", size = 26107, upload-time = "2025-12-11T13:36:47.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/76/1fb94367cef64420d2171157a6b9509582873bd09a6afe08a78a8d1f59d9/opentelemetry_instrumentation_asgi-0.60b1-py3-none-any.whl", hash = "sha256:d48def2dbed10294c99cfcf41ebbd0c414d390a11773a41f472d20000fcddc25", size = 16933, upload-time = "2025-12-11T13:35:40.462Z" }, +] + [[package]] name = "opentelemetry-instrumentation-openai" version = "0.52.3" @@ -1251,6 +1276,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/35/b5/cf25da2218910f0d6cdf7f876a06bed118c4969eacaf60a887cbaef44f44/opentelemetry_semantic_conventions_ai-0.4.13-py3-none-any.whl", hash = "sha256:883a30a6bb5deaec0d646912b5f9f6dcbb9f6f72557b73d0f2560bf25d13e2d5", size = 6080, upload-time = "2025-08-22T10:14:16.477Z" }, ] +[[package]] +name = "opentelemetry-util-http" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/fc/c47bb04a1d8a941a4061307e1eddfa331ed4d0ab13d8a9781e6db256940a/opentelemetry_util_http-0.60b1.tar.gz", hash = "sha256:0d97152ca8c8a41ced7172d29d3622a219317f74ae6bb3027cfbdcf22c3cc0d6", size = 11053, upload-time = "2025-12-11T13:37:25.115Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/5c/d3f1733665f7cd582ef0842fb1d2ed0bc1fba10875160593342d22bba375/opentelemetry_util_http-0.60b1-py3-none-any.whl", hash = "sha256:66381ba28550c91bee14dcba8979ace443444af1ed609226634596b4b0faf199", size = 8947, upload-time = "2025-12-11T13:36:37.151Z" }, +] + [[package]] name = "orjson" version = "3.10.18" @@ -2056,6 +2090,7 @@ dependencies = [ { name = "langgraph" }, { name = "openinference-instrumentation-langchain" }, { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation-asgi" }, { name = "opentelemetry-instrumentation-openai" }, { name = "opentelemetry-sdk" }, { name = "pydantic-settings" }, @@ -2072,6 +2107,7 @@ requires-dist = [ { name = "langgraph", specifier = ">=0.2.55" }, { name = "openinference-instrumentation-langchain", specifier = ">=0.1.36" }, { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.25.0" }, + { name = "opentelemetry-instrumentation-asgi", specifier = ">=0.46b0" }, { name = "opentelemetry-instrumentation-openai", specifier = ">=0.42.0" }, { name = "opentelemetry-sdk", specifier = ">=1.25.0" }, { name = "pydantic-settings", specifier = ">=2.8.1" },