diff --git a/python/semantic_kernel/agents/bedrock/action_group_utils.py b/python/semantic_kernel/agents/bedrock/action_group_utils.py index 29e391e0f0f2..9b8248a38b81 100644 --- a/python/semantic_kernel/agents/bedrock/action_group_utils.py +++ b/python/semantic_kernel/agents/bedrock/action_group_utils.py @@ -29,6 +29,7 @@ def kernel_function_metadata_to_bedrock_function_schema(function_metadata: Kerne "parameters": { parameter.name: kernel_function_parameter_to_bedrock_function_parameter(parameter) for parameter in function_metadata.parameters + if parameter.include_in_function_choices }, # This field controls whether user confirmation is required to invoke the function. # If this is set to "ENABLED", the user will be prompted to confirm the function invocation. diff --git a/python/semantic_kernel/connectors/ai/anthropic/services/utils.py b/python/semantic_kernel/connectors/ai/anthropic/services/utils.py index e41905e1cc91..f8b5a711481c 100644 --- a/python/semantic_kernel/connectors/ai/anthropic/services/utils.py +++ b/python/semantic_kernel/connectors/ai/anthropic/services/utils.py @@ -151,7 +151,7 @@ def kernel_function_metadata_to_function_call_format(metadata: KernelFunctionMet "description": metadata.description or "", "input_schema": { "type": "object", - "properties": {p.name: p.schema_data for p in metadata.parameters}, - "required": [p.name for p in metadata.parameters if p.is_required], + "properties": {p.name: p.schema_data for p in metadata.parameters if p.include_in_function_choices}, + "required": [p.name for p in metadata.parameters if p.is_required and p.include_in_function_choices], }, } diff --git a/python/semantic_kernel/connectors/ai/bedrock/services/model_provider/utils.py b/python/semantic_kernel/connectors/ai/bedrock/services/model_provider/utils.py index d4384ed75da7..e234bd781494 100644 --- a/python/semantic_kernel/connectors/ai/bedrock/services/model_provider/utils.py +++ b/python/semantic_kernel/connectors/ai/bedrock/services/model_provider/utils.py @@ -196,8 +196,14 @@ def update_settings_from_function_choice_configuration( "inputSchema": { "json": { "type": "object", - "properties": {param.name: param.schema_data for param in function.parameters}, - "required": [p.name for p in function.parameters if p.is_required], + "properties": { + param.name: param.schema_data + for param in function.parameters + if param.include_in_function_choices + }, + "required": [ + p.name for p in function.parameters if p.is_required and p.include_in_function_choices + ], } }, } diff --git a/python/semantic_kernel/connectors/ai/google/google_ai/services/utils.py b/python/semantic_kernel/connectors/ai/google/google_ai/services/utils.py index 0fd460b32433..3c8f9df31225 100644 --- a/python/semantic_kernel/connectors/ai/google/google_ai/services/utils.py +++ b/python/semantic_kernel/connectors/ai/google/google_ai/services/utils.py @@ -149,9 +149,10 @@ def format_tool_message(message: ChatMessageContent) -> list[Part]: def kernel_function_metadata_to_google_ai_function_call_format(metadata: KernelFunctionMetadata) -> dict[str, Any]: """Convert the kernel function metadata to function calling format.""" parameters: dict[str, Any] | None = None - if metadata.parameters: + visible_parameters = [p for p in metadata.parameters if p.include_in_function_choices] + if visible_parameters: properties = {} - for param in metadata.parameters: + for param in visible_parameters: if param.name is None: continue prop_schema = sanitize_schema_for_google_ai(param.schema_data) if param.schema_data else param.schema_data @@ -159,7 +160,7 @@ def kernel_function_metadata_to_google_ai_function_call_format(metadata: KernelF parameters = { "type": "object", "properties": properties, - "required": [p.name for p in metadata.parameters if p.is_required and p.name is not None], + "required": [p.name for p in visible_parameters if p.is_required and p.name is not None], } return { "name": metadata.custom_fully_qualified_name(GEMINI_FUNCTION_NAME_SEPARATOR), diff --git a/python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py b/python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py index 832899417ef9..f027880e1faf 100644 --- a/python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py +++ b/python/semantic_kernel/connectors/ai/google/vertex_ai/services/utils.py @@ -139,8 +139,9 @@ def format_tool_message(message: ChatMessageContent) -> list[Part]: def kernel_function_metadata_to_vertex_ai_function_call_format(metadata: KernelFunctionMetadata) -> FunctionDeclaration: """Convert the kernel function metadata to function calling format.""" properties: dict[str, Any] = {} - if metadata.parameters: - for param in metadata.parameters: + visible_parameters = [p for p in metadata.parameters if p.include_in_function_choices] + if visible_parameters: + for param in visible_parameters: if param.name is None: continue prop_schema = sanitize_schema_for_google_ai(param.schema_data) if param.schema_data else param.schema_data @@ -151,7 +152,7 @@ def kernel_function_metadata_to_vertex_ai_function_call_format(metadata: KernelF parameters={ "type": "object", "properties": properties, - "required": [p.name for p in metadata.parameters if p.is_required and p.name is not None], + "required": [p.name for p in visible_parameters if p.is_required and p.name is not None], }, ) diff --git a/python/tests/unit/agents/bedrock_agent/test_action_group_utils.py b/python/tests/unit/agents/bedrock_agent/test_action_group_utils.py index 9898e457c40e..f18576ae166f 100644 --- a/python/tests/unit/agents/bedrock_agent/test_action_group_utils.py +++ b/python/tests/unit/agents/bedrock_agent/test_action_group_utils.py @@ -4,6 +4,7 @@ from semantic_kernel.agents.bedrock.action_group_utils import ( BEDROCK_FUNCTION_ALLOWED_PARAMETER_TYPES, + kernel_function_metadata_to_bedrock_function_schema, kernel_function_parameter_type_to_bedrock_function_parameter_type, kernel_function_to_bedrock_function_schema, parse_function_result_contents, @@ -11,6 +12,8 @@ ) from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior from semantic_kernel.contents.function_result_content import FunctionResultContent +from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata +from semantic_kernel.functions.kernel_parameter_metadata import KernelParameterMetadata from semantic_kernel.kernel import Kernel @@ -35,6 +38,27 @@ def test_kernel_function_to_bedrock_function_schema(kernel_with_function: Kernel } +def test_kernel_function_metadata_to_bedrock_function_schema_skips_params_excluded_from_function_choices(): + metadata = KernelFunctionMetadata( + name="lookup", + description="Look up a user", + is_prompt=False, + parameters=[ + KernelParameterMetadata(name="query", schema_data={"type": "string"}, is_required=True), + KernelParameterMetadata( + name="token", + schema_data={"type": "string"}, + is_required=True, + include_in_function_choices=False, + ), + ], + ) + + result = kernel_function_metadata_to_bedrock_function_schema(metadata) + + assert list(result["parameters"]) == ["query"] + + def test_kernel_function_parameter_type_to_bedrock_function_parameter_type(): # Test the conversion of kernel function parameter type to bedrock function parameter type schema_data = {"type": "string"} diff --git a/python/tests/unit/connectors/ai/anthropic/services/test_anthropic_utils.py b/python/tests/unit/connectors/ai/anthropic/services/test_anthropic_utils.py new file mode 100644 index 000000000000..4fb5487b12ab --- /dev/null +++ b/python/tests/unit/connectors/ai/anthropic/services/test_anthropic_utils.py @@ -0,0 +1,30 @@ +# Copyright (c) Microsoft. All rights reserved. + +from semantic_kernel.connectors.ai.anthropic.services.utils import kernel_function_metadata_to_function_call_format +from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata +from semantic_kernel.functions.kernel_parameter_metadata import KernelParameterMetadata + + +def test_function_call_format_skips_params_excluded_from_function_choices() -> None: + metadata = KernelFunctionMetadata( + name="lookup", + description="Look up a user", + is_prompt=False, + parameters=[ + KernelParameterMetadata( + name="query", description="What to look up", schema_data={"type": "string"}, is_required=True + ), + KernelParameterMetadata( + name="token", + description="Internal", + schema_data={"type": "string"}, + is_required=True, + include_in_function_choices=False, + ), + ], + ) + + result = kernel_function_metadata_to_function_call_format(metadata) + + assert list(result["input_schema"]["properties"]) == ["query"] + assert result["input_schema"]["required"] == ["query"] diff --git a/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_model_provider_utils.py b/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_model_provider_utils.py index ef279db2521d..cbce3e60cadd 100644 --- a/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_model_provider_utils.py +++ b/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_model_provider_utils.py @@ -25,6 +25,8 @@ from semantic_kernel.contents.utils.author_role import AuthorRole from semantic_kernel.contents.utils.finish_reason import FinishReason from semantic_kernel.exceptions.service_exceptions import ServiceInvalidRequestError +from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata +from semantic_kernel.functions.kernel_parameter_metadata import KernelParameterMetadata from semantic_kernel.kernel import Kernel @@ -441,3 +443,32 @@ def test_update_settings_from_function_choice_configuration_required_one(mock_be assert mock_bedrock_settings.tool_choice == {"tool": {"name": "plugin-function"}} assert len(mock_bedrock_settings.tools) == 1 assert mock_bedrock_settings.tools[0]["toolSpec"]["name"] == "plugin-function" + + +def test_update_settings_from_function_choice_configuration_skips_params_excluded_from_function_choices() -> None: + metadata = KernelFunctionMetadata( + name="lookup", + description="Look up a user", + is_prompt=False, + parameters=[ + KernelParameterMetadata( + name="query", description="What to look up", schema_data={"type": "string"}, is_required=True + ), + KernelParameterMetadata( + name="token", + description="Internal", + schema_data={"type": "string"}, + is_required=True, + include_in_function_choices=False, + ), + ], + ) + config = FunctionCallChoiceConfiguration() + config.available_functions = [metadata] + settings = BedrockChatPromptExecutionSettings() + + update_settings_from_function_choice_configuration(config, settings, FunctionChoiceType.AUTO) + + schema = settings.tools[0]["toolSpec"]["inputSchema"]["json"] + assert list(schema["properties"]) == ["query"] + assert schema["required"] == ["query"] diff --git a/python/tests/unit/connectors/ai/google/google_ai/services/test_google_ai_utils.py b/python/tests/unit/connectors/ai/google/google_ai/services/test_google_ai_utils.py index 181d9f5d470e..dd47d839dbd9 100644 --- a/python/tests/unit/connectors/ai/google/google_ai/services/test_google_ai_utils.py +++ b/python/tests/unit/connectors/ai/google/google_ai/services/test_google_ai_utils.py @@ -199,3 +199,39 @@ def test_google_ai_function_call_format_empty_parameters() -> None: ) result = kernel_function_metadata_to_google_ai_function_call_format(metadata) assert result["parameters"] is None + + +def test_google_ai_function_call_format_skips_params_excluded_from_function_choices() -> None: + metadata = KernelFunctionMetadata( + name="lookup", + description="Look up a user", + is_prompt=False, + parameters=[ + KernelParameterMetadata( + name="query", description="What to look up", schema_data={"type": "string"}, is_required=True + ), + KernelParameterMetadata( + name="token", + description="Internal", + schema_data={"type": "string"}, + is_required=True, + include_in_function_choices=False, + ), + ], + ) + result = kernel_function_metadata_to_google_ai_function_call_format(metadata) + assert list(result["parameters"]["properties"]) == ["query"] + assert result["parameters"]["required"] == ["query"] + + +def test_google_ai_function_call_format_all_params_excluded_gives_no_parameters() -> None: + metadata = KernelFunctionMetadata( + name="hidden_only", + description="Only hidden parameters", + is_prompt=False, + parameters=[ + KernelParameterMetadata(name="token", schema_data={"type": "string"}, include_in_function_choices=False), + ], + ) + result = kernel_function_metadata_to_google_ai_function_call_format(metadata) + assert result["parameters"] is None diff --git a/python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_utils.py b/python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_utils.py index 882fec8915e6..f05ec0d11187 100644 --- a/python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_utils.py +++ b/python/tests/unit/connectors/ai/google/vertex_ai/services/test_vertex_ai_utils.py @@ -197,3 +197,27 @@ def test_vertex_ai_function_call_format_empty_parameters() -> None: ) result = kernel_function_metadata_to_vertex_ai_function_call_format(metadata) assert isinstance(result, FunctionDeclaration) + + +def test_vertex_ai_function_call_format_skips_params_excluded_from_function_choices() -> None: + metadata = KernelFunctionMetadata( + name="lookup", + description="Look up a user", + is_prompt=False, + parameters=[ + KernelParameterMetadata( + name="query", description="What to look up", schema_data={"type": "string"}, is_required=True + ), + KernelParameterMetadata( + name="token", + description="Internal", + schema_data={"type": "string"}, + is_required=True, + include_in_function_choices=False, + ), + ], + ) + result = kernel_function_metadata_to_vertex_ai_function_call_format(metadata) + schema = type(result).to_dict(result)["parameters"] + assert list(schema["properties"]) == ["query"] + assert schema["required"] == ["query"]