From a9aa1932f441742814352a8e2e853f39db7be534 Mon Sep 17 00:00:00 2001 From: Md Armaan Ansari Date: Fri, 6 Mar 2026 19:22:11 +0530 Subject: [PATCH] Fix AttributeError when tool attributes are None in web/file search Add 'is not None' checks for user_location, filters, and ranking_options before accessing __dict__, preventing crash when these optional fields default to None. Fixes #1285 --- .../providers/openai/attributes/response.py | 6 +-- .../openai_core/test_response_attributes.py | 39 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/agentops/instrumentation/providers/openai/attributes/response.py b/agentops/instrumentation/providers/openai/attributes/response.py index 195eb5bdb..9cf8f4b80 100644 --- a/agentops/instrumentation/providers/openai/attributes/response.py +++ b/agentops/instrumentation/providers/openai/attributes/response.py @@ -503,7 +503,7 @@ def get_response_tool_web_search_attributes(tool: "WebSearchTool", index: int) - if hasattr(tool, "search_context_size"): parameters["search_context_size"] = tool.search_context_size - if hasattr(tool, "user_location"): + if hasattr(tool, "user_location") and tool.user_location is not None: parameters["user_location"] = tool.user_location.__dict__ tool_data = tool.__dict__ @@ -521,13 +521,13 @@ def get_response_tool_file_search_attributes(tool: "FileSearchTool", index: int) if hasattr(tool, "vector_store_ids"): parameters["vector_store_ids"] = tool.vector_store_ids - if hasattr(tool, "filters"): + if hasattr(tool, "filters") and tool.filters is not None: parameters["filters"] = tool.filters.__dict__ if hasattr(tool, "max_num_results"): parameters["max_num_results"] = tool.max_num_results - if hasattr(tool, "ranking_options"): + if hasattr(tool, "ranking_options") and tool.ranking_options is not None: parameters["ranking_options"] = tool.ranking_options.__dict__ tool_data = tool.__dict__ diff --git a/tests/unit/instrumentation/openai_core/test_response_attributes.py b/tests/unit/instrumentation/openai_core/test_response_attributes.py index 24809423f..89bff10a0 100644 --- a/tests/unit/instrumentation/openai_core/test_response_attributes.py +++ b/tests/unit/instrumentation/openai_core/test_response_attributes.py @@ -612,6 +612,22 @@ def test_get_response_tool_web_search_attributes(self): assert "search_context_size" in result[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0)] assert "user_location" in result[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0)] + def test_get_response_tool_web_search_attributes_none_user_location(self): + """Test web search tool handles None user_location without crashing""" + web_search_tool = MockWebSearchTool( + {"type": "web_search_preview", "search_context_size": "medium", "user_location": None} + ) + + with patch("agentops.instrumentation.providers.openai.attributes.response.WebSearchTool", MockWebSearchTool): + result = get_response_tool_web_search_attributes(web_search_tool, 0) + + assert isinstance(result, dict) + assert MessageAttributes.TOOL_CALL_NAME.format(i=0) in result + assert result[MessageAttributes.TOOL_CALL_NAME.format(i=0)] == "web_search_preview" + # user_location should not appear in parameters since it's None + args = result.get(MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0), "") + assert "user_location" not in args + def test_get_response_tool_file_search_attributes(self): """Test extraction of attributes from file search tool""" # Create a mock file search tool @@ -644,6 +660,29 @@ def test_get_response_tool_file_search_attributes(self): assert "max_num_results" in result[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0)] assert "ranking_options" in result[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0)] + def test_get_response_tool_file_search_attributes_none_filters_and_ranking(self): + """Test file search tool handles None filters and ranking_options without crashing""" + file_search_tool = MockFileSearchTool( + { + "type": "file_search", + "vector_store_ids": ["store_123"], + "filters": None, + "max_num_results": 5, + "ranking_options": None, + } + ) + + with patch("agentops.instrumentation.providers.openai.attributes.response.FileSearchTool", MockFileSearchTool): + result = get_response_tool_file_search_attributes(file_search_tool, 0) + + assert isinstance(result, dict) + assert MessageAttributes.TOOL_CALL_TYPE.format(i=0) in result + assert result[MessageAttributes.TOOL_CALL_TYPE.format(i=0)] == "file_search" + # filters and ranking_options should not appear in parameters since they're None + args = result.get(MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=0), "") + assert "filters" not in args + assert "ranking_options" not in args + def test_get_response_tool_computer_attributes(self): """Test extraction of attributes from computer tool""" # Create a mock computer tool