diff --git a/src/microsoft/opentelemetry/_genai/_langchain/_tracer.py b/src/microsoft/opentelemetry/_genai/_langchain/_tracer.py index f26074ba..610b05f7 100644 --- a/src/microsoft/opentelemetry/_genai/_langchain/_tracer.py +++ b/src/microsoft/opentelemetry/_genai/_langchain/_tracer.py @@ -201,9 +201,7 @@ def _start_trace(self, run: Run) -> None: if is_nested_agent: ancestor_run = self.run_map.get(str(ancestor_id)) - ancestor_name = ( - self._resolve_agent_name(ancestor_run, use_config=False) if ancestor_run else None - ) + ancestor_name = self._resolve_agent_name(ancestor_run, use_config=False) if ancestor_run else None this_name = self._resolve_agent_name(run, use_config=False) if this_name and ancestor_name and this_name.lower() == ancestor_name.lower(): return diff --git a/src/microsoft/opentelemetry/_otlp/handler.py b/src/microsoft/opentelemetry/_otlp/handler.py index 161b2f37..c1fa6348 100644 --- a/src/microsoft/opentelemetry/_otlp/handler.py +++ b/src/microsoft/opentelemetry/_otlp/handler.py @@ -81,7 +81,6 @@ def create_otlp_components( from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader from opentelemetry.sdk._logs.export import BatchLogRecordProcessor - # from opentelemetry.sdk.trace.export import SpanExporter # from opentelemetry.sdk.metrics.export import MetricExporter # from opentelemetry.sdk._logs.export import LogRecordExporter diff --git a/src/microsoft/opentelemetry/_sdkstats/_constants.py b/src/microsoft/opentelemetry/_sdkstats/_constants.py index 9eebe323..22add8ca 100644 --- a/src/microsoft/opentelemetry/_sdkstats/_constants.py +++ b/src/microsoft/opentelemetry/_sdkstats/_constants.py @@ -5,7 +5,6 @@ # -------------------------------------------------------------------------- - from azure.monitor.opentelemetry.exporter._constants import ( # type: ignore[import-not-found] _REQ_DURATION_NAME, _REQ_EXCEPTION_NAME, diff --git a/src/microsoft/opentelemetry/_utils.py b/src/microsoft/opentelemetry/_utils.py index b4f64f8a..529052da 100644 --- a/src/microsoft/opentelemetry/_utils.py +++ b/src/microsoft/opentelemetry/_utils.py @@ -156,11 +156,7 @@ def _append_azure_monitor_components( def _disable_openai_v2_instrumentation(otel_kwargs: Dict[str, Any]) -> None: options = otel_kwargs.get(INSTRUMENTATION_OPTIONS_ARG) - if ( - isinstance(options, dict) - and isinstance(options.get("openai"), dict) - and "enabled" in options["openai"] - ): + if isinstance(options, dict) and isinstance(options.get("openai"), dict) and "enabled" in options["openai"]: return # User has explicitly set openai instrumentation options; do not override overlapping_present = any( diff --git a/src/microsoft/opentelemetry/a365/core/exporters/agent365_exporter.py b/src/microsoft/opentelemetry/a365/core/exporters/agent365_exporter.py index 4df9d48f..7b877482 100644 --- a/src/microsoft/opentelemetry/a365/core/exporters/agent365_exporter.py +++ b/src/microsoft/opentelemetry/a365/core/exporters/agent365_exporter.py @@ -381,7 +381,7 @@ def _extract_token_identity(headers: dict[str, str | bytes]) -> dict[str, str]: auth = auth.decode("utf-8", errors="replace") if not auth.startswith("Bearer "): return {} - parts = auth[len("Bearer "):].split(".") + parts = auth[len("Bearer ") :].split(".") if len(parts) != 3: return {} payload_b64 = parts[1] + "=" * (4 - len(parts[1]) % 4) diff --git a/src/microsoft/opentelemetry/a365/core/message_utils.py b/src/microsoft/opentelemetry/a365/core/message_utils.py index b2d762fa..21f86565 100644 --- a/src/microsoft/opentelemetry/a365/core/message_utils.py +++ b/src/microsoft/opentelemetry/a365/core/message_utils.py @@ -117,9 +117,7 @@ def serialize_messages( message parts contain non-JSON-serializable values. """ try: - serialized_list = [ - asdict(msg, dict_factory=_message_dict_factory) for msg in wrapper.messages - ] + serialized_list = [asdict(msg, dict_factory=_message_dict_factory) for msg in wrapper.messages] return json.dumps( serialized_list, default=str, diff --git a/tests/a365/integration/agentframework/test_agentframework_trace_processor.py b/tests/a365/integration/agentframework/test_agentframework_trace_processor.py index 15d79c4c..9710d349 100644 --- a/tests/a365/integration/agentframework/test_agentframework_trace_processor.py +++ b/tests/a365/integration/agentframework/test_agentframework_trace_processor.py @@ -43,9 +43,7 @@ def add_numbers(a: float, b: float) -> float: class TestAgentFrameworkTraceProcessorIntegration: """Integration tests for AgentFramework trace processor with real Azure OpenAI.""" - def test_agentframework_trace_processor_integration( - self, distro_exporter, azure_openai_config, agent365_config - ): + def test_agentframework_trace_processor_integration(self, distro_exporter, azure_openai_config, agent365_config): """Test AgentFramework trace processor with real Azure OpenAI call.""" # Create Azure OpenAI ChatClient @@ -149,10 +147,7 @@ def _validate_span_attributes(self, distro_exporter, agent365_config): assert attributes[TENANT_ID_KEY] == agent365_config["tenant_id"] # Check for LLM spans (generation spans) - if ( - GEN_AI_PROVIDER_NAME_KEY in attributes - and attributes[GEN_AI_PROVIDER_NAME_KEY] == "openai" - ): + if GEN_AI_PROVIDER_NAME_KEY in attributes and attributes[GEN_AI_PROVIDER_NAME_KEY] == "openai": if GEN_AI_REQUEST_MODEL_KEY in attributes: llm_spans_found += 1 # Validate LLM span attributes diff --git a/tests/a365/integration/agentframework/test_message_format.py b/tests/a365/integration/agentframework/test_message_format.py index 0272a051..8a27f49f 100644 --- a/tests/a365/integration/agentframework/test_message_format.py +++ b/tests/a365/integration/agentframework/test_message_format.py @@ -69,16 +69,10 @@ def _find_chat_spans(self, distro_exporter) -> list[ReadableSpan]: """ get_tracer_provider().force_flush() time.sleep(0.5) - return [ - s - for s in distro_exporter.spans - if s.attributes and GEN_AI_INPUT_MESSAGES_KEY in s.attributes - ] + return [s for s in distro_exporter.spans if s.attributes and GEN_AI_INPUT_MESSAGES_KEY in s.attributes] @pytest.mark.asyncio - async def test_simple_chat_message_mapping( - self, distro_exporter, chat_client: OpenAIChatClient - ) -> None: + async def test_simple_chat_message_mapping(self, distro_exporter, chat_client: OpenAIChatClient) -> None: """Simple chat: verify exported spans contain structured A365 messages after enrichment (no manual mapper call).""" agent = RawAgent( @@ -92,9 +86,7 @@ async def test_simple_chat_message_mapping( assert len(result.text) > 0 chat_spans = self._find_chat_spans(distro_exporter) - assert len(chat_spans) > 0, ( - f"No chat spans found. All spans: {[s.name for s in distro_exporter.spans]}" - ) + assert len(chat_spans) > 0, f"No chat spans found. All spans: {[s.name for s in distro_exporter.spans]}" attrs = dict(chat_spans[-1].attributes or {}) @@ -132,9 +124,7 @@ async def test_simple_chat_message_mapping( print(f"\n=== Enriched output ===\n{json.dumps(output_data, indent=2)}") @pytest.mark.asyncio - async def test_tool_call_message_mapping( - self, distro_exporter, chat_client: OpenAIChatClient - ) -> None: + async def test_tool_call_message_mapping(self, distro_exporter, chat_client: OpenAIChatClient) -> None: """Tool-calling chat: verify tool_call and tool_call_response parts survive enrichment in exported spans.""" agent = RawAgent( @@ -170,7 +160,5 @@ async def test_tool_call_message_mapping( part_types.add(part.get("type", "")) assert "tool_call" in part_types, f"Expected tool_call in exported parts: {part_types}" - assert "tool_call_response" in part_types, ( - f"Expected tool_call_response in exported parts: {part_types}" - ) + assert "tool_call_response" in part_types, f"Expected tool_call_response in exported parts: {part_types}" print(f"\n Exported part types: {part_types}") diff --git a/tests/a365/integration/agentframework/test_observability_pipeline.py b/tests/a365/integration/agentframework/test_observability_pipeline.py index 07cbeff6..a00e1782 100644 --- a/tests/a365/integration/agentframework/test_observability_pipeline.py +++ b/tests/a365/integration/agentframework/test_observability_pipeline.py @@ -170,22 +170,19 @@ async def test_pipeline_invoke_agent_with_tool_call( # pylint: disable=too-many # --- 1. All spans share the same trace_id --- invoke_spans = _find_spans_by_name_prefix(spans, "invoke_agent") - assert len(invoke_spans) >= 1, ( - f"Expected at least 1 invoke_agent span, got: {[s.name for s in spans]}" - ) + assert len(invoke_spans) >= 1, f"Expected at least 1 invoke_agent span, got: {[s.name for s in spans]}" invoke_span = invoke_spans[0] trace_id = invoke_span.context.trace_id for s in spans: assert s.context.trace_id == trace_id, ( - f"Span '{s.name}' has different trace_id: " - f"{s.context.trace_id:032x} vs {trace_id:032x}" + f"Span '{s.name}' has different trace_id: " f"{s.context.trace_id:032x} vs {trace_id:032x}" ) # --- 2. invoke_agent span is the root (no parent) --- - assert invoke_span.parent is None, ( - f"invoke_agent span should be root but has parent: {invoke_span.parent.span_id:016x}" - ) + assert ( + invoke_span.parent is None + ), f"invoke_agent span should be root but has parent: {invoke_span.parent.span_id:016x}" # --- 3. invoke_agent has correct operation name --- assert _get_span_attr(invoke_span, GEN_AI_OPERATION_NAME_KEY) == INVOKE_AGENT_OPERATION_NAME @@ -200,15 +197,11 @@ async def test_pipeline_invoke_agent_with_tool_call( # pylint: disable=too-many if _get_span_attr(s, GEN_AI_OPERATION_NAME_KEY) == "chat" or (s.name.startswith("chat") and _get_span_attr(s, GEN_AI_REQUEST_MODEL_KEY)) ] - assert len(chat_spans) >= 1, ( - f"Expected at least 1 chat span, got: {[s.name for s in spans]}" - ) + assert len(chat_spans) >= 1, f"Expected at least 1 chat span, got: {[s.name for s in spans]}" invoke_span_id = invoke_span.context.span_id for chat_span in chat_spans: - assert chat_span.parent is not None, ( - f"Chat span '{chat_span.name}' should have a parent" - ) + assert chat_span.parent is not None, f"Chat span '{chat_span.name}' should have a parent" # Chat span should be a child of invoke_agent (directly or transitively) self._assert_ancestor( chat_span, @@ -223,13 +216,9 @@ async def test_pipeline_invoke_agent_with_tool_call( # pylint: disable=too-many # Also check by operation name tool_spans = _find_spans_by_operation(spans, EXECUTE_TOOL_OPERATION_NAME) - assert len(tool_spans) >= 1, ( - f"Expected at least 1 execute_tool span. All spans: {[s.name for s in spans]}" - ) + assert len(tool_spans) >= 1, f"Expected at least 1 execute_tool span. All spans: {[s.name for s in spans]}" for tool_span in tool_spans: - assert tool_span.parent is not None, ( - f"Tool span '{tool_span.name}' should have a parent" - ) + assert tool_span.parent is not None, f"Tool span '{tool_span.name}' should have a parent" self._assert_ancestor( tool_span, invoke_span_id, @@ -259,9 +248,9 @@ async def test_pipeline_invoke_agent_with_tool_call( # pylint: disable=too-many attrs = dict(tool_span.attributes or {}) op = str(attrs.get(GEN_AI_OPERATION_NAME_KEY, "")) if op == EXECUTE_TOOL_OPERATION_NAME or tool_span.name.startswith("execute_tool"): - assert GEN_AI_TOOL_NAME_KEY in attrs or "add_numbers" in tool_span.name, ( - f"Tool span missing tool name attribute: {list(attrs.keys())}" - ) + assert ( + GEN_AI_TOOL_NAME_KEY in attrs or "add_numbers" in tool_span.name + ), f"Tool span missing tool name attribute: {list(attrs.keys())}" print("\nāœ“ All pipeline assertions passed") diff --git a/tests/a365/integration/langchain/test_message_format.py b/tests/a365/integration/langchain/test_message_format.py index 06f5f542..6dd1d693 100644 --- a/tests/a365/integration/langchain/test_message_format.py +++ b/tests/a365/integration/langchain/test_message_format.py @@ -59,11 +59,7 @@ def _find_chat_spans(distro_exporter: SpanCapturingExporter) -> list[ReadableSpa """Find exported spans that have gen_ai.input.messages.""" get_tracer_provider().force_flush() time.sleep(0.5) - return [ - s - for s in distro_exporter.spans - if s.attributes and GEN_AI_INPUT_MESSAGES_KEY in s.attributes - ] + return [s for s in distro_exporter.spans if s.attributes and GEN_AI_INPUT_MESSAGES_KEY in s.attributes] @pytest.mark.asyncio async def test_simple_chat_message_mapping( @@ -82,9 +78,7 @@ async def test_simple_chat_message_mapping( assert len(result.content) > 0 chat_spans = self._find_chat_spans(distro_exporter) - assert len(chat_spans) > 0, ( - f"No chat spans found. All spans: {[s.name for s in distro_exporter.spans]}" - ) + assert len(chat_spans) > 0, f"No chat spans found. All spans: {[s.name for s in distro_exporter.spans]}" print(f"\n=== All exported spans ({len(distro_exporter.spans)}) ===") for s in distro_exporter.spans: @@ -123,9 +117,7 @@ async def test_simple_chat_message_mapping( for part in item.get("parts", []): if isinstance(part, dict) and "content" in part: flat_text += part["content"].lower() - assert "capital" in flat_text, ( - f"Expected 'capital' in input messages content, got: {input_data}" - ) + assert "capital" in flat_text, f"Expected 'capital' in input messages content, got: {input_data}" print("\n → List format (pre-mapper)") # --- Output messages --- diff --git a/tests/a365/integration/langchain/test_observability_pipeline.py b/tests/a365/integration/langchain/test_observability_pipeline.py index 7bed0531..b2412233 100644 --- a/tests/a365/integration/langchain/test_observability_pipeline.py +++ b/tests/a365/integration/langchain/test_observability_pipeline.py @@ -197,23 +197,20 @@ async def test_pipeline_invoke_agent_with_tool_call( # --- 1. Find invoke_agent span --- invoke_spans = _find_spans_by_name_prefix(spans, "invoke_agent") - assert len(invoke_spans) >= 1, ( - f"Expected at least 1 invoke_agent span, got: {[s.name for s in spans]}" - ) + assert len(invoke_spans) >= 1, f"Expected at least 1 invoke_agent span, got: {[s.name for s in spans]}" invoke_span = invoke_spans[0] trace_id = invoke_span.context.trace_id # --- 2. All spans share the same trace_id --- for s in spans: assert s.context.trace_id == trace_id, ( - f"Span '{s.name}' has different trace_id: " - f"{s.context.trace_id:032x} vs {trace_id:032x}" + f"Span '{s.name}' has different trace_id: " f"{s.context.trace_id:032x} vs {trace_id:032x}" ) # --- 3. invoke_agent span is the root --- - assert invoke_span.parent is None, ( - f"invoke_agent should be root but has parent: {invoke_span.parent.span_id:016x}" - ) + assert ( + invoke_span.parent is None + ), f"invoke_agent should be root but has parent: {invoke_span.parent.span_id:016x}" # --- 4. invoke_agent has correct operation name --- assert _get_span_attr(invoke_span, GEN_AI_OPERATION_NAME_KEY) == INVOKE_AGENT_OPERATION_NAME @@ -233,9 +230,7 @@ async def test_pipeline_invoke_agent_with_tool_call( ) and not s.name.startswith("execute_tool") ] - assert len(inference_spans) >= 1, ( - f"Expected at least 1 inference span, got: {[s.name for s in spans]}" - ) + assert len(inference_spans) >= 1, f"Expected at least 1 inference span, got: {[s.name for s in spans]}" invoke_span_id = invoke_span.context.span_id for inf_span in inference_spans: @@ -334,9 +329,7 @@ async def test_pipeline_invoke_agent_simple_inference( # Inference spans are descendants inference_spans = [ - s - for s in spans - if s != invoke_span and _get_span_attr(s, GEN_AI_INPUT_MESSAGES_KEY) is not None + s for s in spans if s != invoke_span and _get_span_attr(s, GEN_AI_INPUT_MESSAGES_KEY) is not None ] assert len(inference_spans) >= 1 diff --git a/tests/a365/integration/openai/test_message_format.py b/tests/a365/integration/openai/test_message_format.py index b7df828e..516185b2 100644 --- a/tests/a365/integration/openai/test_message_format.py +++ b/tests/a365/integration/openai/test_message_format.py @@ -65,16 +65,20 @@ def _span_to_json(span: ReadableSpan) -> dict[str, object]: events_list: list[dict[str, object]] = [] for e in getattr(span, "events", None) or []: - events_list.append({ - "name": e.name, - "attributes": dict(e.attributes) if e.attributes else {}, - }) + events_list.append( + { + "name": e.name, + "attributes": dict(e.attributes) if e.attributes else {}, + } + ) links_list: list[dict[str, object]] = [] for lnk in getattr(span, "links", None) or []: - links_list.append({ - "attributes": dict(lnk.attributes) if lnk.attributes else {}, - }) + links_list.append( + { + "attributes": dict(lnk.attributes) if lnk.attributes else {}, + } + ) result: dict[str, object] = { "name": span.name, @@ -116,11 +120,7 @@ def _find_message_spans(self, distro_exporter) -> list[ReadableSpan]: """Find exported spans that have gen_ai.input.messages.""" get_tracer_provider().force_flush() time.sleep(0.5) - return [ - s - for s in distro_exporter.spans - if s.attributes and GEN_AI_INPUT_MESSAGES_KEY in s.attributes - ] + return [s for s in distro_exporter.spans if s.attributes and GEN_AI_INPUT_MESSAGES_KEY in s.attributes] @pytest.mark.asyncio async def test_simple_chat_message_mapping( @@ -153,9 +153,7 @@ async def test_simple_chat_message_mapping( print(json.dumps(span_json, indent=2, default=str)) message_spans = self._find_message_spans(distro_exporter) - assert len(message_spans) > 0, ( - f"No message spans found. All spans: {[s.name for s in distro_exporter.spans]}" - ) + assert len(message_spans) > 0, f"No message spans found. All spans: {[s.name for s in distro_exporter.spans]}" # Verify at least one span has structured A365 array format found_structured = False diff --git a/tests/a365/integration/openai/test_openai_trace_processor.py b/tests/a365/integration/openai/test_openai_trace_processor.py index b2cdf039..e67d40dd 100644 --- a/tests/a365/integration/openai/test_openai_trace_processor.py +++ b/tests/a365/integration/openai/test_openai_trace_processor.py @@ -64,9 +64,7 @@ def test_openai_trace_processor_integration(self, distro_exporter, azure_openai_ agent = Agent( name="TestAgent", instructions="You are a helpful assistant.", - model=OpenAIChatCompletionsModel( - model=azure_openai_config["deployment"], openai_client=openai_client - ), + model=OpenAIChatCompletionsModel(model=azure_openai_config["deployment"], openai_client=openai_client), ) # Execute a simple prompt using async runner @@ -111,9 +109,7 @@ def test_openai_trace_processor_with_tool_calls(self, distro_exporter, azure_ope agent = Agent( name="MathAgent", instructions="You are a helpful math assistant. Use the add_numbers tool to perform calculations.", - model=OpenAIChatCompletionsModel( - model=azure_openai_config["deployment"], openai_client=openai_client - ), + model=OpenAIChatCompletionsModel(model=azure_openai_config["deployment"], openai_client=openai_client), tools=[add_numbers], ) @@ -162,9 +158,7 @@ def test_invoke_agent_span_required_attributes(self, distro_exporter, azure_open agent = Agent( name="TestAgent", instructions="You are a helpful assistant. Answer briefly.", - model=OpenAIChatCompletionsModel( - model=azure_openai_config["deployment"], openai_client=openai_client - ), + model=OpenAIChatCompletionsModel(model=azure_openai_config["deployment"], openai_client=openai_client), ) import asyncio @@ -195,10 +189,7 @@ async def run_agent(): # 1. The InvokeAgentScope span (parent) - has gen_ai.agent.id/name # 2. The instrumentor span (child) - has gen_ai.input/output.messages # Find the scope span (has gen_ai.agent.id) for attribute validation - invoke_agent_spans = [ - s for s in distro_exporter.spans - if s.name.startswith(INVOKE_AGENT_OPERATION_NAME) - ] + invoke_agent_spans = [s for s in distro_exporter.spans if s.name.startswith(INVOKE_AGENT_OPERATION_NAME)] assert len(invoke_agent_spans) >= 1, "No invoke_agent spans found" # The scope span is the one with gen_ai.agent.id set @@ -252,10 +243,7 @@ def _validate_span_attributes(self, distro_exporter, agent365_config): assert attributes[GEN_AI_AGENT_ID_KEY] == agent365_config["agent_id"] # Check for LLM spans (generation spans) - if ( - GEN_AI_PROVIDER_NAME_KEY in attributes - and attributes[GEN_AI_PROVIDER_NAME_KEY] == "openai" - ): + if GEN_AI_PROVIDER_NAME_KEY in attributes and attributes[GEN_AI_PROVIDER_NAME_KEY] == "openai": if GEN_AI_REQUEST_MODEL_KEY in attributes: llm_spans_found += 1 # Validate LLM span attributes @@ -302,10 +290,7 @@ def _validate_tool_span_attributes(self, distro_exporter, agent365_config): assert attributes[GEN_AI_AGENT_ID_KEY] == agent365_config["agent_id"] # Check for LLM spans (generation spans) - if ( - GEN_AI_PROVIDER_NAME_KEY in attributes - and attributes[GEN_AI_PROVIDER_NAME_KEY] == "openai" - ): + if GEN_AI_PROVIDER_NAME_KEY in attributes and attributes[GEN_AI_PROVIDER_NAME_KEY] == "openai": if GEN_AI_REQUEST_MODEL_KEY in attributes: llm_spans_found += 1 print(f"āœ“ Found LLM span with model: {attributes[GEN_AI_REQUEST_MODEL_KEY]}") diff --git a/tests/a365/test_exporter.py b/tests/a365/test_exporter.py index d77dab60..cd950a4f 100644 --- a/tests/a365/test_exporter.py +++ b/tests/a365/test_exporter.py @@ -546,9 +546,7 @@ def test_403_insufficient_scope_logs_actionable_message(self, _enabled): "x-ms-correlation-id": "test-correlation-id", }, ) - with self.assertLogs( - "microsoft.opentelemetry.a365.core.exporters.agent365_exporter", level="ERROR" - ) as log: + with self.assertLogs("microsoft.opentelemetry.a365.core.exporters.agent365_exporter", level="ERROR") as log: result = exporter._post_with_retries(self.URL, "{}", {}) self.assertFalse(result)