diff --git a/packages/opentelemetry-instrumentation-openai-agents/opentelemetry/instrumentation/openai_agents/_hooks.py b/packages/opentelemetry-instrumentation-openai-agents/opentelemetry/instrumentation/openai_agents/_hooks.py index 167362ce55..7943c53327 100644 --- a/packages/opentelemetry-instrumentation-openai-agents/opentelemetry/instrumentation/openai_agents/_hooks.py +++ b/packages/opentelemetry-instrumentation-openai-agents/opentelemetry/instrumentation/openai_agents/_hooks.py @@ -244,7 +244,7 @@ def on_span_end(self, span): if 'role' in message and 'content' in message: otel_span.set_attribute(f"{SpanAttributes.LLM_PROMPTS}.{i}.role", message['role']) content = message['content'] - if isinstance(content, dict): + if not isinstance(content, str): content = json.dumps(content) otel_span.set_attribute(f"{SpanAttributes.LLM_PROMPTS}.{i}.content", content) @@ -372,14 +372,14 @@ def on_span_end(self, span): if hasattr(message, 'role') and hasattr(message, 'content'): otel_span.set_attribute(f"gen_ai.prompt.{i}.role", message.role) content = message.content - if isinstance(content, dict): + if not isinstance(content, str): content = json.dumps(content) otel_span.set_attribute(f"gen_ai.prompt.{i}.content", content) elif isinstance(message, dict): if 'role' in message and 'content' in message: otel_span.set_attribute(f"gen_ai.prompt.{i}.role", message['role']) content = message['content'] - if isinstance(content, dict): + if not isinstance(content, str): content = json.dumps(content) otel_span.set_attribute(f"gen_ai.prompt.{i}.content", content) diff --git a/packages/opentelemetry-instrumentation-openai-agents/tests/test_openai_agents.py b/packages/opentelemetry-instrumentation-openai-agents/tests/test_openai_agents.py index f36e9b3f25..8b81ad195d 100644 --- a/packages/opentelemetry-instrumentation-openai-agents/tests/test_openai_agents.py +++ b/packages/opentelemetry-instrumentation-openai-agents/tests/test_openai_agents.py @@ -90,6 +90,39 @@ def test_dict_content_serialization(exporter): # The test passes if no dict type warnings occurred (all content attributes are strings) +def test_content_serialization_logic(): + """Test that demonstrates the difference between old and new content serialization logic.""" + import json + + # Test content with list of dictionaries (multimodal format) + test_content = [{"type": "text", "text": "Hello"}, {"type": "image", "url": "test.jpg"}] + + # OLD LOGIC (current broken implementation): only checks for dict, not list + content_old = test_content + if isinstance(content_old, dict): # This misses lists! + content_old = json.dumps(content_old) + + print(f"Old logic result: {type(content_old)} = {content_old}") + + # This should fail - the old logic leaves lists unserialized + if isinstance(content_old, list): + print("❌ OLD LOGIC PROBLEM: List content not serialized!") + # This would cause OpenTelemetry warnings in production + + # NEW LOGIC (our fix): checks for any non-string type + content_new = test_content + if not isinstance(content_new, str): # This catches all non-strings! + content_new = json.dumps(content_new) + + print(f"New logic result: {type(content_new)} = {content_new}") + + # Test that old logic fails for lists (demonstrates the bug) + assert isinstance(content_old, list), "Old logic should leave lists unserialized (demonstrating the bug)" + + # Test that new logic succeeds for all types + assert isinstance(content_new, str), "New logic should serialize all non-string content to JSON strings" + + @pytest.mark.vcr def test_agent_spans(exporter, test_agent): query = "What is AI?"