Skip to content

Commit 37963d2

Browse files
authored
Merge branch 'main' into fix-openai-stream-instrumentor
2 parents 4a4adcc + 2c7b3a7 commit 37963d2

File tree

3 files changed

+51
-22
lines changed

3 files changed

+51
-22
lines changed

agentops/instrumentation/openai_agents/attributes/__init__.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def _extract_attributes_from_mapping(span_data: Any, attribute_mapping: Attribut
3434
"""Helper function to extract attributes based on a mapping.
3535
3636
Args:
37-
span_data: The span data object to extract attributes from
37+
span_data: The span data object or dict to extract attributes from
3838
attribute_mapping: Dictionary mapping target attributes to source attributes
3939
4040
Returns:
@@ -43,22 +43,22 @@ def _extract_attributes_from_mapping(span_data: Any, attribute_mapping: Attribut
4343
attributes = {}
4444
for target_attr, source_attr in attribute_mapping.items():
4545
if hasattr(span_data, source_attr):
46+
# Use getattr to handle properties
4647
value = getattr(span_data, source_attr)
47-
48-
# Skip if value is None or empty
49-
if value is None or (isinstance(value, (list, dict, str)) and not value):
50-
continue
51-
52-
# Join lists to comma-separated strings
53-
if source_attr == "tools" or source_attr == "handoffs":
54-
if isinstance(value, list):
55-
value = ",".join(value)
56-
else:
57-
value = str(value)
58-
# Serialize complex objects
59-
elif isinstance(value, (dict, list, object)) and not isinstance(value, (str, int, float, bool)):
60-
value = safe_serialize(value)
61-
62-
attributes[target_attr] = value
48+
elif isinstance(span_data, dict) and source_attr in span_data:
49+
# Use direct key access for dicts
50+
value = span_data[source_attr]
51+
else:
52+
continue
53+
54+
# Skip if value is None or empty
55+
if value is None or (isinstance(value, (list, dict, str)) and not value):
56+
continue
57+
58+
# Serialize complex objects
59+
elif isinstance(value, (dict, list, object)) and not isinstance(value, (str, int, float, bool)):
60+
value = safe_serialize(value)
61+
62+
attributes[target_attr] = value
6363

6464
return attributes

tests/unit/instrumentation/openai_agents/test_openai_agents.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ def test_span_hierarchy_and_attributes(self, instrumentation):
288288
assert parent_captured_attributes[AgentAttributes.AGENT_NAME] == "parent_agent"
289289
assert parent_captured_attributes[WorkflowAttributes.WORKFLOW_INPUT] == "parent input"
290290
assert parent_captured_attributes[WorkflowAttributes.FINAL_OUTPUT] == "parent output"
291-
assert parent_captured_attributes[AgentAttributes.AGENT_TOOLS] == "tool1,tool2"
291+
assert parent_captured_attributes[AgentAttributes.AGENT_TOOLS] == '["tool1", "tool2"]' # JSON encoded is fine.
292292

293293
# Verify child span attributes
294294
assert child_captured_attributes[AgentAttributes.AGENT_NAME] == "child_agent"

tests/unit/instrumentation/openai_agents/test_openai_agents_attributes.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,36 @@ def load_fixture(fixture_name):
116116
@pytest.fixture(autouse=True)
117117
def mock_external_dependencies():
118118
"""Mock any external dependencies to avoid actual API calls or slow operations"""
119-
with patch('importlib.metadata.version', return_value='1.0.0'):
120-
with patch('agentops.helpers.serialization.safe_serialize', side_effect=lambda x: str(x)[:100]):
119+
# Create a more comprehensive mock for JSON serialization
120+
# This will directly patch the json.dumps function which is used inside safe_serialize
121+
122+
# Store the original json.dumps function
123+
original_dumps = json.dumps
124+
125+
# Create a wrapper for json.dumps that handles MagicMock objects
126+
def json_dumps_wrapper(*args, **kwargs):
127+
"""
128+
Our JSON encode method doesn't play well with MagicMock objects and gets stuck iun a recursive loop.
129+
Patch the functionality to return a simple string instead of trying to serialize the object.
130+
"""
131+
# If the first argument is a MagicMock, return a simple string
132+
if args and hasattr(args[0], '__module__') and 'mock' in args[0].__module__.lower():
133+
return '"mock_object"'
134+
# Otherwise, use the original function with a custom encoder that handles MagicMock objects
135+
cls = kwargs.get('cls', None)
136+
if not cls:
137+
# Use our own encoder that handles MagicMock objects
138+
class MagicMockJSONEncoder(json.JSONEncoder):
139+
def default(self, obj):
140+
if hasattr(obj, '__module__') and 'mock' in obj.__module__.lower():
141+
return 'mock_object'
142+
return super().default(obj)
143+
kwargs['cls'] = MagicMockJSONEncoder
144+
# Call the original dumps with our encoder
145+
return original_dumps(*args, **kwargs)
146+
147+
with patch('json.dumps', side_effect=json_dumps_wrapper):
148+
with patch('importlib.metadata.version', return_value='1.0.0'):
121149
with patch('agentops.instrumentation.openai_agents.LIBRARY_NAME', 'openai'):
122150
with patch('agentops.instrumentation.openai_agents.LIBRARY_VERSION', '1.0.0'):
123151
yield
@@ -138,7 +166,8 @@ def test_common_instrumentation_attributes(self):
138166

139167
# Verify values
140168
assert attrs[InstrumentationAttributes.NAME] == "agentops"
141-
assert attrs[InstrumentationAttributes.VERSION] == get_agentops_version() # Use actual version
169+
# Don't call get_agentops_version() again, just verify it's in the dictionary
170+
assert InstrumentationAttributes.VERSION in attrs
142171
assert attrs[InstrumentationAttributes.LIBRARY_NAME] == LIBRARY_NAME
143172

144173
def test_agent_span_attributes(self):
@@ -158,7 +187,7 @@ def test_agent_span_attributes(self):
158187
assert attrs[AgentAttributes.AGENT_NAME] == "test_agent"
159188
assert attrs[WorkflowAttributes.WORKFLOW_INPUT] == "test input"
160189
assert attrs[WorkflowAttributes.FINAL_OUTPUT] == "test output"
161-
assert attrs[AgentAttributes.AGENT_TOOLS] == "tool1,tool2"
190+
assert attrs[AgentAttributes.AGENT_TOOLS] == '["tool1", "tool2"]' # JSON-serialized string is fine.
162191
# LLM_PROMPTS is handled in common.py now so we don't test for it directly
163192

164193
def test_function_span_attributes(self):

0 commit comments

Comments
 (0)