Skip to content

Commit 16169e1

Browse files
committed
fix: validate tool types in Agent.tools list to prevent runtime AttributeError (fixes #1443)
This fix addresses issue #1443 where passing invalid tool types (e.g., raw functions instead of FunctionTool objects) in the tools list would pass initialization but fail at runtime with: AttributeError: 'function' object has no attribute 'name' (in src/agents/run.py line 584) Changes: - Added validation in Agent.__post_init__ to check each tool in the list - Invalid tools now raise UserError at initialization with helpful message - Suggests using @function_tool decorator when raw functions are detected This provides better developer experience by: 1. Catching errors early (at init vs runtime) 2. Providing clear error messages 3. Suggesting the correct fix Test Coverage: - Added test_tools_content_validation_issue_1443 with 3 test cases - Tests raw functions, strings, and mixed valid/invalid tools - Verifies correct error index is reported
1 parent 03dca68 commit 16169e1

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

src/agents/agent.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
import inspect
66
from collections.abc import Awaitable
77
from dataclasses import dataclass, field
8-
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, cast
8+
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, cast, get_args
99

1010
from openai.types.responses.response_prompt_param import ResponsePromptParam
1111
from typing_extensions import NotRequired, TypeAlias, TypedDict
1212

1313
from .agent_output import AgentOutputSchemaBase
14+
from .exceptions import UserError
1415
from .guardrail import InputGuardrail, OutputGuardrail
1516
from .handoffs import Handoff
1617
from .items import ItemHelpers
@@ -246,6 +247,21 @@ def __post_init__(self):
246247
if not isinstance(self.tools, list):
247248
raise TypeError(f"Agent tools must be a list, got {type(self.tools).__name__}")
248249

250+
# Validate each tool is a valid Tool type
251+
# Tool is a Union type, so we need to get the valid types from it
252+
valid_tool_types = get_args(Tool) + (Handoff,)
253+
254+
for i, tool in enumerate(self.tools):
255+
if not isinstance(tool, valid_tool_types):
256+
# Generate a friendly list of valid types for the error message
257+
type_names = ", ".join(t.__name__ for t in valid_tool_types)
258+
raise UserError(
259+
f"tools[{i}] must be a valid Tool object ({type_names}), "
260+
f"got {type(tool).__name__}. "
261+
f"Did you forget to use @function_tool decorator or pass the function itself "
262+
f"instead of a tool?"
263+
)
264+
249265
if not isinstance(self.mcp_servers, list):
250266
raise TypeError(
251267
f"Agent mcp_servers must be a list, got {type(self.mcp_servers).__name__}"

tests/test_agent_config.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,49 @@ def test_list_field_validation(self):
214214
with pytest.raises(TypeError, match="Agent handoffs must be a list"):
215215
Agent(name="test", handoffs="not_a_list") # type: ignore
216216

217+
def test_tools_content_validation_issue_1443(self):
218+
"""Test that tools list validates each element is a valid Tool object (Issue #1443)
219+
220+
This test addresses the issue where passing invalid tool types (e.g., raw functions)
221+
in a list would pass __post_init__ validation but fail later at runtime with:
222+
AttributeError: 'function' object has no attribute 'name'
223+
224+
The fix validates each tool in the list during initialization.
225+
"""
226+
from agents.exceptions import UserError
227+
228+
def raw_function():
229+
"""A raw function, not decorated with @function_tool"""
230+
return "test"
231+
232+
# Case 1: Raw function in tools list should raise UserError at init
233+
with pytest.raises(
234+
UserError,
235+
match=r"tools\[0\] must be a valid Tool object.*got function.*@function_tool",
236+
):
237+
Agent(name="test", tools=[raw_function]) # type: ignore
238+
239+
# Case 2: String in tools list should raise UserError at init
240+
with pytest.raises(
241+
UserError,
242+
match=r"tools\[0\] must be a valid Tool object.*got str",
243+
):
244+
Agent(name="test", tools=["invalid_string"]) # type: ignore
245+
246+
# Case 3: Mixed valid and invalid tools - should catch invalid at correct index
247+
from agents import function_tool
248+
249+
@function_tool
250+
def valid_tool() -> str:
251+
"""A valid tool"""
252+
return "ok"
253+
254+
with pytest.raises(
255+
UserError,
256+
match=r"tools\[1\] must be a valid Tool object.*got str",
257+
):
258+
Agent(name="test", tools=[valid_tool, "invalid"]) # type: ignore
259+
217260
def test_model_settings_validation(self):
218261
"""Test model_settings validation - prevents runtime errors"""
219262
# Valid case

0 commit comments

Comments
 (0)