Skip to content

Commit fbdaa4d

Browse files
Autumnclaude
authored andcommitted
Fix AttributeError when optional fields are None in web_search/file_search tools
Add 'is not None' checks alongside hasattr() guards in get_response_tool_web_search_attributes and get_response_tool_file_search_attributes. The hasattr() check returns True even when optional attributes (user_location, filters, ranking_options) are None, causing AttributeError when accessing .__dict__ on None. Fixes: #1285 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a855a92 commit fbdaa4d

2 files changed

Lines changed: 53 additions & 3 deletions

File tree

agentops/instrumentation/providers/openai/attributes/response.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ def get_response_tool_web_search_attributes(tool: "WebSearchTool", index: int) -
503503
if hasattr(tool, "search_context_size"):
504504
parameters["search_context_size"] = tool.search_context_size
505505

506-
if hasattr(tool, "user_location"):
506+
if hasattr(tool, "user_location") and tool.user_location is not None:
507507
parameters["user_location"] = tool.user_location.__dict__
508508

509509
tool_data = tool.__dict__
@@ -521,13 +521,13 @@ def get_response_tool_file_search_attributes(tool: "FileSearchTool", index: int)
521521
if hasattr(tool, "vector_store_ids"):
522522
parameters["vector_store_ids"] = tool.vector_store_ids
523523

524-
if hasattr(tool, "filters"):
524+
if hasattr(tool, "filters") and tool.filters is not None:
525525
parameters["filters"] = tool.filters.__dict__
526526

527527
if hasattr(tool, "max_num_results"):
528528
parameters["max_num_results"] = tool.max_num_results
529529

530-
if hasattr(tool, "ranking_options"):
530+
if hasattr(tool, "ranking_options") and tool.ranking_options is not None:
531531
parameters["ranking_options"] = tool.ranking_options.__dict__
532532

533533
tool_data = tool.__dict__

tests/unit/instrumentation/openai_core/test_response_attributes.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,56 @@ def test_get_response_tool_file_search_attributes(self):
644644
assert "max_num_results" in result[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0)]
645645
assert "ranking_options" in result[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0)]
646646

647+
def test_get_response_tool_web_search_attributes_with_none_user_location(self):
648+
"""Test extraction of attributes from web search tool when user_location is None"""
649+
# Create a mock web search tool with user_location=None (optional field default)
650+
web_search_tool = MockWebSearchTool(
651+
{"type": "web_search_preview", "search_context_size": "medium", "user_location": None}
652+
)
653+
654+
# Call the function directly - should NOT raise AttributeError
655+
with patch("agentops.instrumentation.providers.openai.attributes.response.WebSearchTool", MockWebSearchTool):
656+
result = get_response_tool_web_search_attributes(web_search_tool, 0)
657+
658+
# Verify attributes - should still work without user_location
659+
assert isinstance(result, dict)
660+
assert MessageAttributes.TOOL_CALL_NAME.format(i=0) in result
661+
assert result[MessageAttributes.TOOL_CALL_NAME.format(i=0)] == "web_search_preview"
662+
assert MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0) in result
663+
# user_location should NOT be in parameters since it was None
664+
assert "user_location" not in result[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0)]
665+
# search_context_size should still be present
666+
assert "search_context_size" in result[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0)]
667+
668+
def test_get_response_tool_file_search_attributes_with_none_filters_and_ranking(self):
669+
"""Test extraction of attributes from file search tool when filters and ranking_options are None"""
670+
# Create a mock file search tool with filters=None and ranking_options=None (optional defaults)
671+
file_search_tool = MockFileSearchTool(
672+
{
673+
"type": "file_search",
674+
"vector_store_ids": ["store_123"],
675+
"filters": None,
676+
"max_num_results": 10,
677+
"ranking_options": None,
678+
}
679+
)
680+
681+
# Call the function directly - should NOT raise AttributeError
682+
with patch("agentops.instrumentation.providers.openai.attributes.response.FileSearchTool", MockFileSearchTool):
683+
result = get_response_tool_file_search_attributes(file_search_tool, 0)
684+
685+
# Verify attributes - should still work without filters/ranking_options
686+
assert isinstance(result, dict)
687+
assert MessageAttributes.TOOL_CALL_TYPE.format(i=0) in result
688+
assert result[MessageAttributes.TOOL_CALL_TYPE.format(i=0)] == "file_search"
689+
assert MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0) in result
690+
# filters and ranking_options should NOT be in parameters since they were None
691+
assert "filters" not in result[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0)]
692+
assert "ranking_options" not in result[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0)]
693+
# vector_store_ids and max_num_results should still be present
694+
assert "vector_store_ids" in result[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0)]
695+
assert "max_num_results" in result[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0)]
696+
647697
def test_get_response_tool_computer_attributes(self):
648698
"""Test extraction of attributes from computer tool"""
649699
# Create a mock computer tool

0 commit comments

Comments
 (0)