You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-03-30 10:57:20 -07:00
fix: Set response schema for function that returns None
PiperOrigin-RevId: 784053725
This commit is contained in:
committed by
Copybara-Service
parent
31fa5d91bd
commit
33ac8380ad
@@ -20,7 +20,6 @@ import typing
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
@@ -329,7 +328,26 @@ def from_function_with_options(
|
||||
return declaration
|
||||
|
||||
return_annotation = inspect.signature(func).return_annotation
|
||||
if return_annotation is inspect._empty:
|
||||
|
||||
# Handle functions with no return annotation or that return None
|
||||
if (
|
||||
return_annotation is inspect._empty
|
||||
or return_annotation is None
|
||||
or return_annotation is type(None)
|
||||
):
|
||||
# Create a response schema for None/null return
|
||||
return_value = inspect.Parameter(
|
||||
'return_value',
|
||||
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||
annotation=None,
|
||||
)
|
||||
declaration.response = (
|
||||
_function_parameter_parse_util._parse_schema_from_parameter(
|
||||
variant,
|
||||
return_value,
|
||||
func.__name__,
|
||||
)
|
||||
)
|
||||
return declaration
|
||||
|
||||
return_value = inspect.Parameter(
|
||||
|
||||
@@ -17,6 +17,8 @@ from typing import List
|
||||
|
||||
from google.adk.tools import _automatic_function_calling_util
|
||||
from google.adk.tools.agent_tool import ToolContext
|
||||
from google.adk.utils.variant_utils import GoogleLLMVariant
|
||||
from google.genai import types
|
||||
# TODO: crewai requires python 3.10 as minimum
|
||||
# from crewai_tools import FileReadTool
|
||||
from pydantic import BaseModel
|
||||
@@ -262,3 +264,118 @@ def test_basemodel_list():
|
||||
# assert function_decl.name == 'directory_read_tool'
|
||||
# assert function_decl.parameters.type == 'OBJECT'
|
||||
# assert function_decl.parameters.properties['file_path'].type == 'STRING'
|
||||
|
||||
|
||||
def test_function_no_return_annotation_gemini_api():
|
||||
"""Test function with no return annotation using GEMINI_API variant."""
|
||||
|
||||
def function_no_return(param: str):
|
||||
"""A function with no return annotation."""
|
||||
return None
|
||||
|
||||
function_decl = _automatic_function_calling_util.build_function_declaration(
|
||||
func=function_no_return, variant=GoogleLLMVariant.GEMINI_API
|
||||
)
|
||||
|
||||
assert function_decl.name == 'function_no_return'
|
||||
assert function_decl.parameters.type == 'OBJECT'
|
||||
assert function_decl.parameters.properties['param'].type == 'STRING'
|
||||
# GEMINI_API should not have response schema
|
||||
assert function_decl.response is None
|
||||
|
||||
|
||||
def test_function_no_return_annotation_vertex_ai():
|
||||
"""Test function with no return annotation using VERTEX_AI variant."""
|
||||
|
||||
def function_no_return(param: str):
|
||||
"""A function with no return annotation."""
|
||||
return None
|
||||
|
||||
function_decl = _automatic_function_calling_util.build_function_declaration(
|
||||
func=function_no_return, variant=GoogleLLMVariant.VERTEX_AI
|
||||
)
|
||||
|
||||
assert function_decl.name == 'function_no_return'
|
||||
assert function_decl.parameters.type == 'OBJECT'
|
||||
assert function_decl.parameters.properties['param'].type == 'STRING'
|
||||
# VERTEX_AI should have response schema for None return
|
||||
assert function_decl.response is not None
|
||||
assert function_decl.response.type == types.Type.NULL
|
||||
|
||||
|
||||
def test_function_explicit_none_return_vertex_ai():
|
||||
"""Test function with explicit None return annotation using VERTEX_AI variant."""
|
||||
|
||||
def function_none_return(param: str) -> None:
|
||||
"""A function that explicitly returns None."""
|
||||
pass
|
||||
|
||||
function_decl = _automatic_function_calling_util.build_function_declaration(
|
||||
func=function_none_return, variant=GoogleLLMVariant.VERTEX_AI
|
||||
)
|
||||
|
||||
assert function_decl.name == 'function_none_return'
|
||||
assert function_decl.parameters.type == 'OBJECT'
|
||||
assert function_decl.parameters.properties['param'].type == 'STRING'
|
||||
# VERTEX_AI should have response schema for explicit None return
|
||||
assert function_decl.response is not None
|
||||
assert function_decl.response.type == types.Type.NULL
|
||||
|
||||
|
||||
def test_function_explicit_none_return_gemini_api():
|
||||
"""Test function with explicit None return annotation using GEMINI_API variant."""
|
||||
|
||||
def function_none_return(param: str) -> None:
|
||||
"""A function that explicitly returns None."""
|
||||
pass
|
||||
|
||||
function_decl = _automatic_function_calling_util.build_function_declaration(
|
||||
func=function_none_return, variant=GoogleLLMVariant.GEMINI_API
|
||||
)
|
||||
|
||||
assert function_decl.name == 'function_none_return'
|
||||
assert function_decl.parameters.type == 'OBJECT'
|
||||
assert function_decl.parameters.properties['param'].type == 'STRING'
|
||||
# GEMINI_API should not have response schema
|
||||
assert function_decl.response is None
|
||||
|
||||
|
||||
def test_function_regular_return_type_vertex_ai():
|
||||
"""Test function with regular return type using VERTEX_AI variant."""
|
||||
|
||||
def function_string_return(param: str) -> str:
|
||||
"""A function that returns a string."""
|
||||
return param
|
||||
|
||||
function_decl = _automatic_function_calling_util.build_function_declaration(
|
||||
func=function_string_return, variant=GoogleLLMVariant.VERTEX_AI
|
||||
)
|
||||
|
||||
assert function_decl.name == 'function_string_return'
|
||||
assert function_decl.parameters.type == 'OBJECT'
|
||||
assert function_decl.parameters.properties['param'].type == 'STRING'
|
||||
# VERTEX_AI should have response schema for string return
|
||||
assert function_decl.response is not None
|
||||
assert function_decl.response.type == types.Type.STRING
|
||||
|
||||
|
||||
def test_transfer_to_agent_like_function():
|
||||
"""Test a function similar to transfer_to_agent that caused the original issue."""
|
||||
|
||||
def transfer_to_agent(agent_name: str, tool_context: ToolContext):
|
||||
"""Transfer the question to another agent."""
|
||||
tool_context.actions.transfer_to_agent = agent_name
|
||||
|
||||
function_decl = _automatic_function_calling_util.build_function_declaration(
|
||||
func=transfer_to_agent,
|
||||
ignore_params=['tool_context'],
|
||||
variant=GoogleLLMVariant.VERTEX_AI,
|
||||
)
|
||||
|
||||
assert function_decl.name == 'transfer_to_agent'
|
||||
assert function_decl.parameters.type == 'OBJECT'
|
||||
assert function_decl.parameters.properties['agent_name'].type == 'STRING'
|
||||
assert 'tool_context' not in function_decl.parameters.properties
|
||||
# This should now have a response schema for VERTEX_AI variant
|
||||
assert function_decl.response is not None
|
||||
assert function_decl.response.type == types.Type.NULL
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from typing import Dict
|
||||
|
||||
from google.adk.tools import _automatic_function_calling_util
|
||||
from google.adk.utils.variant_utils import GoogleLLMVariant
|
||||
from google.genai import types
|
||||
|
||||
|
||||
def test_from_function_with_options_no_return_annotation_gemini():
|
||||
"""Test from_function_with_options with no return annotation for GEMINI_API."""
|
||||
|
||||
def test_function(param: str):
|
||||
"""A test function with no return annotation."""
|
||||
return None
|
||||
|
||||
declaration = _automatic_function_calling_util.from_function_with_options(
|
||||
test_function, GoogleLLMVariant.GEMINI_API
|
||||
)
|
||||
|
||||
assert declaration.name == 'test_function'
|
||||
assert declaration.parameters.type == 'OBJECT'
|
||||
assert declaration.parameters.properties['param'].type == 'STRING'
|
||||
# GEMINI_API should not have response schema
|
||||
assert declaration.response is None
|
||||
|
||||
|
||||
def test_from_function_with_options_no_return_annotation_vertex():
|
||||
"""Test from_function_with_options with no return annotation for VERTEX_AI."""
|
||||
|
||||
def test_function(param: str):
|
||||
"""A test function with no return annotation."""
|
||||
return None
|
||||
|
||||
declaration = _automatic_function_calling_util.from_function_with_options(
|
||||
test_function, GoogleLLMVariant.VERTEX_AI
|
||||
)
|
||||
|
||||
assert declaration.name == 'test_function'
|
||||
assert declaration.parameters.type == 'OBJECT'
|
||||
assert declaration.parameters.properties['param'].type == 'STRING'
|
||||
# VERTEX_AI should have response schema for None return
|
||||
assert declaration.response is not None
|
||||
assert declaration.response.type == types.Type.NULL
|
||||
|
||||
|
||||
def test_from_function_with_options_explicit_none_return_vertex():
|
||||
"""Test from_function_with_options with explicit None return for VERTEX_AI."""
|
||||
|
||||
def test_function(param: str) -> None:
|
||||
"""A test function that explicitly returns None."""
|
||||
pass
|
||||
|
||||
declaration = _automatic_function_calling_util.from_function_with_options(
|
||||
test_function, GoogleLLMVariant.VERTEX_AI
|
||||
)
|
||||
|
||||
assert declaration.name == 'test_function'
|
||||
assert declaration.parameters.type == 'OBJECT'
|
||||
assert declaration.parameters.properties['param'].type == 'STRING'
|
||||
# VERTEX_AI should have response schema for explicit None return
|
||||
assert declaration.response is not None
|
||||
assert declaration.response.type == types.Type.NULL
|
||||
|
||||
|
||||
def test_from_function_with_options_explicit_none_return_gemini():
|
||||
"""Test from_function_with_options with explicit None return for GEMINI_API."""
|
||||
|
||||
def test_function(param: str) -> None:
|
||||
"""A test function that explicitly returns None."""
|
||||
pass
|
||||
|
||||
declaration = _automatic_function_calling_util.from_function_with_options(
|
||||
test_function, GoogleLLMVariant.GEMINI_API
|
||||
)
|
||||
|
||||
assert declaration.name == 'test_function'
|
||||
assert declaration.parameters.type == 'OBJECT'
|
||||
assert declaration.parameters.properties['param'].type == 'STRING'
|
||||
# GEMINI_API should not have response schema
|
||||
assert declaration.response is None
|
||||
|
||||
|
||||
def test_from_function_with_options_string_return_vertex():
|
||||
"""Test from_function_with_options with string return for VERTEX_AI."""
|
||||
|
||||
def test_function(param: str) -> str:
|
||||
"""A test function that returns a string."""
|
||||
return param
|
||||
|
||||
declaration = _automatic_function_calling_util.from_function_with_options(
|
||||
test_function, GoogleLLMVariant.VERTEX_AI
|
||||
)
|
||||
|
||||
assert declaration.name == 'test_function'
|
||||
assert declaration.parameters.type == 'OBJECT'
|
||||
assert declaration.parameters.properties['param'].type == 'STRING'
|
||||
# VERTEX_AI should have response schema for string return
|
||||
assert declaration.response is not None
|
||||
assert declaration.response.type == types.Type.STRING
|
||||
|
||||
|
||||
def test_from_function_with_options_dict_return_vertex():
|
||||
"""Test from_function_with_options with dict return for VERTEX_AI."""
|
||||
|
||||
def test_function(param: str) -> Dict[str, str]:
|
||||
"""A test function that returns a dict."""
|
||||
return {'result': param}
|
||||
|
||||
declaration = _automatic_function_calling_util.from_function_with_options(
|
||||
test_function, GoogleLLMVariant.VERTEX_AI
|
||||
)
|
||||
|
||||
assert declaration.name == 'test_function'
|
||||
assert declaration.parameters.type == 'OBJECT'
|
||||
assert declaration.parameters.properties['param'].type == 'STRING'
|
||||
# VERTEX_AI should have response schema for dict return
|
||||
assert declaration.response is not None
|
||||
assert declaration.response.type == types.Type.OBJECT
|
||||
|
||||
|
||||
def test_from_function_with_options_int_return_vertex():
|
||||
"""Test from_function_with_options with int return for VERTEX_AI."""
|
||||
|
||||
def test_function(param: str) -> int:
|
||||
"""A test function that returns an int."""
|
||||
return 42
|
||||
|
||||
declaration = _automatic_function_calling_util.from_function_with_options(
|
||||
test_function, GoogleLLMVariant.VERTEX_AI
|
||||
)
|
||||
|
||||
assert declaration.name == 'test_function'
|
||||
assert declaration.parameters.type == 'OBJECT'
|
||||
assert declaration.parameters.properties['param'].type == 'STRING'
|
||||
# VERTEX_AI should have response schema for int return
|
||||
assert declaration.response is not None
|
||||
assert declaration.response.type == types.Type.INTEGER
|
||||
|
||||
|
||||
def test_from_function_with_options_no_params():
|
||||
"""Test from_function_with_options with no parameters."""
|
||||
|
||||
def test_function() -> None:
|
||||
"""A test function with no parameters that returns None."""
|
||||
pass
|
||||
|
||||
declaration = _automatic_function_calling_util.from_function_with_options(
|
||||
test_function, GoogleLLMVariant.VERTEX_AI
|
||||
)
|
||||
|
||||
assert declaration.name == 'test_function'
|
||||
# No parameters should result in no parameters field or empty parameters
|
||||
assert (
|
||||
declaration.parameters is None
|
||||
or len(declaration.parameters.properties) == 0
|
||||
)
|
||||
# VERTEX_AI should have response schema for None return
|
||||
assert declaration.response is not None
|
||||
assert declaration.response.type == types.Type.NULL
|
||||
Reference in New Issue
Block a user