diff --git a/pyproject.toml b/pyproject.toml index 7fd8ae1a..201fc8b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,20 +23,20 @@ classifiers = [ dependencies = [ "azure-core<2.0.0,>=1.28.0", "azure-core-tracing-opentelemetry~=1.0.0b11", - "azure-monitor-opentelemetry-exporter~=1.0.0b53", - "opentelemetry-sdk==1.40", - "opentelemetry-api==1.40", - "opentelemetry-exporter-otlp-proto-http==1.40", - "opentelemetry-instrumentation==0.61b0", - "opentelemetry-instrumentation-django==0.61b0", - "opentelemetry-instrumentation-fastapi==0.61b0", - "opentelemetry-instrumentation-flask==0.61b0", - "opentelemetry-instrumentation-httpx==0.61b0", - "opentelemetry-instrumentation-psycopg2==0.61b0", - "opentelemetry-instrumentation-requests==0.61b0", - "opentelemetry-instrumentation-urllib==0.61b0", - "opentelemetry-instrumentation-urllib3==0.61b0", - "opentelemetry-instrumentation-logging==0.61b0", + "azure-monitor-opentelemetry-exporter~=1.0.0b54", + "opentelemetry-sdk~=1.43.0", + "opentelemetry-api~=1.43.0", + "opentelemetry-exporter-otlp-proto-http~=1.43.0", + "opentelemetry-instrumentation>=0.64b0,<0.65b0", + "opentelemetry-instrumentation-django>=0.64b0,<0.65b0", + "opentelemetry-instrumentation-fastapi>=0.64b0,<0.65b0", + "opentelemetry-instrumentation-flask>=0.64b0,<0.65b0", + "opentelemetry-instrumentation-httpx>=0.64b0,<0.65b0", + "opentelemetry-instrumentation-psycopg2>=0.64b0,<0.65b0", + "opentelemetry-instrumentation-requests>=0.64b0,<0.65b0", + "opentelemetry-instrumentation-urllib>=0.64b0,<0.65b0", + "opentelemetry-instrumentation-urllib3>=0.64b0,<0.65b0", + "opentelemetry-instrumentation-logging>=0.64b0,<0.65b0", "opentelemetry-instrumentation-openai-agents-v2==0.1.0", "opentelemetry-instrumentation-openai-v2==2.3b0", "opentelemetry-resource-detector-azure<1.0.0,>=0.1.5", diff --git a/src/microsoft/opentelemetry/_genai/main_agent/_processor.py b/src/microsoft/opentelemetry/_genai/main_agent/_processor.py index 5cb098a1..7c24a644 100644 --- a/src/microsoft/opentelemetry/_genai/main_agent/_processor.py +++ b/src/microsoft/opentelemetry/_genai/main_agent/_processor.py @@ -28,6 +28,7 @@ from opentelemetry.sdk.trace import ReadableSpan, Span from opentelemetry.sdk.trace.export import SpanProcessor from opentelemetry.trace import Span as SpanAPI +from opentelemetry.util.types import AttributeValue # Each row: (target attribute on current span, # primary source attribute on parent span, @@ -108,15 +109,16 @@ def on_end(self, span: ReadableSpan) -> None: if mutable is None: return + # Build the attributes to write before touching the (now frozen) span. + updates: dict[str, AttributeValue] = {} + # Self-promotion: top-level invoke_agent spans copy their own # gen_ai.agent.* → microsoft.gen_ai.main_agent.* if attributes.get(GEN_AI_OPERATION_NAME_KEY) == INVOKE_AGENT_OPERATION_NAME: for target, source in _SELF_COPY_TABLE: value = attributes.get(source) if value is not None: - mutable[target] = value - return - + updates[target] = value # Fallback propagation: re-read from the parent span whose attributes # may have been set after this child was created (timing issue). if parent is not None: @@ -126,7 +128,26 @@ def on_end(self, span: ReadableSpan) -> None: if value is None: value = parent_attributes.get(fallback) if value is not None: + updates[target] = value + + if not updates: + return + + # OTel SDK >= 1.43 freezes span attributes (``_immutable = True``) inside + # ``end()`` *before* invoking ``on_end``. Writing then raises ``TypeError``. + # Temporarily lift the freeze for our own synchronous writes and always + # restore it so the exported ``ReadableSpan`` snapshot stays frozen. + was_immutable = getattr(mutable, "_immutable", False) + if was_immutable: + mutable._immutable = False # pylint: disable=protected-access + try: + for target, value in updates.items(): mutable[target] = value + finally: + mutable._immutable = True # pylint: disable=protected-access + else: + for target, value in updates.items(): + mutable[target] = value def shutdown(self) -> None: self._parent_spans.clear()