Skip to content

Commit 969a695

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 969a695

File tree

2 files changed

+79
-1
lines changed

2 files changed

+79
-1
lines changed

src/agents/agent.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
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
@@ -25,7 +26,18 @@
2526
from .models.interface import Model
2627
from .prompts import DynamicPromptFunction, Prompt, PromptUtil
2728
from .run_context import RunContextWrapper, TContext
28-
from .tool import FunctionTool, FunctionToolResult, Tool, function_tool
29+
from .tool import (
30+
CodeInterpreterTool,
31+
ComputerTool,
32+
FileSearchTool,
33+
FunctionTool,
34+
FunctionToolResult,
35+
ImageGenerationTool,
36+
LocalShellTool,
37+
Tool,
38+
WebSearchTool,
39+
function_tool,
40+
)
2941
from .util import _transforms
3042
from .util._types import MaybeAwaitable
3143

@@ -246,6 +258,29 @@ def __post_init__(self):
246258
if not isinstance(self.tools, list):
247259
raise TypeError(f"Agent tools must be a list, got {type(self.tools).__name__}")
248260

261+
# Validate each tool is a valid Tool type
262+
for i, tool in enumerate(self.tools):
263+
if not isinstance(
264+
tool,
265+
(
266+
FunctionTool,
267+
FileSearchTool,
268+
WebSearchTool,
269+
ComputerTool,
270+
LocalShellTool,
271+
ImageGenerationTool,
272+
CodeInterpreterTool,
273+
Handoff,
274+
),
275+
):
276+
raise UserError(
277+
f"tools[{i}] must be a valid Tool object (FunctionTool, FileSearchTool, "
278+
f"WebSearchTool, ComputerTool, LocalShellTool, ImageGenerationTool, "
279+
f"CodeInterpreterTool, or Handoff), got {type(tool).__name__}. "
280+
f"Did you forget to use @function_tool decorator or pass the function itself "
281+
f"instead of a tool?"
282+
)
283+
249284
if not isinstance(self.mcp_servers, list):
250285
raise TypeError(
251286
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)