diff --git a/src/google/adk/tools/_function_tool_declarations.py b/src/google/adk/tools/_function_tool_declarations.py index a835cd899e..5b7c10947b 100644 --- a/src/google/adk/tools/_function_tool_declarations.py +++ b/src/google/adk/tools/_function_tool_declarations.py @@ -64,8 +64,12 @@ def _get_function_fields( # Get type hints with forward reference resolution try: type_hints = get_type_hints(func) - except TypeError: - # Can happen with mock objects or complex annotations + except (TypeError, NameError, AttributeError): + # TypeError can happen with mock objects or complex annotations. NameError + # / AttributeError happen when an annotation is an unresolvable forward + # reference at runtime, e.g. a type imported only under `if TYPE_CHECKING:` + # (common for the context parameter, which is excluded from the schema + # anyway). Fall back to the raw per-parameter annotations below. type_hints = {} for name, param in sig.parameters.items(): @@ -145,7 +149,10 @@ def _build_response_json_schema( try: type_hints = get_type_hints(func) return_annotation = type_hints.get('return', return_annotation) - except TypeError: + except (TypeError, NameError, AttributeError): + # An unresolvable parameter annotation (e.g. a `TYPE_CHECKING`-only + # import) must not block resolving the return type; fall back to the + # raw return annotation, which pydantic resolves below. pass # Handle AsyncGenerator and Generator return types (streaming tools) diff --git a/tests/unittests/tools/test_function_tool_with_import_annotations.py b/tests/unittests/tools/test_function_tool_with_import_annotations.py index 0d171628d3..71e8d8ea61 100644 --- a/tests/unittests/tools/test_function_tool_with_import_annotations.py +++ b/tests/unittests/tools/test_function_tool_with_import_annotations.py @@ -16,6 +16,7 @@ from typing import Any from typing import Dict +from typing import TYPE_CHECKING from google.adk.tools import _automatic_function_calling_util from google.adk.tools.function_tool import FunctionTool @@ -23,6 +24,9 @@ from google.genai import types import pydantic +if TYPE_CHECKING: + from google.adk.tools.tool_context import ToolContext + def test_string_annotation_none_return_vertex(): """Test function with string annotation 'None' return for VERTEX_AI.""" @@ -229,3 +233,25 @@ def function_with_list(items: list[ItemModel]) -> int: assert processed_args['items'][0].name == 'Burger' assert processed_args['items'][0].quantity == 10 assert processed_args['items'][1].quantity == 5 + + +def test_type_checking_only_context_annotation_builds_declaration(): + """A context parameter annotated with a TYPE_CHECKING-only import must not + break declaration building for the remaining parameters.""" + + def test_function(x: int, tool_context: ToolContext) -> str: + """A tool with a context parameter. + + Args: + x: a number. + """ + return str(x) + + declaration = FunctionTool(test_function)._get_declaration() + + assert declaration.name == 'test_function' + schema = declaration.parameters_json_schema + # The real parameter is described, the context parameter is excluded. + assert 'x' in schema['properties'] + assert 'tool_context' not in schema['properties'] + assert schema['required'] == ['x']