6868from semantic_kernel .agents .open_ai .function_action_result import FunctionActionResult
6969from semantic_kernel .agents .open_ai .run_polling_options import RunPollingOptions
7070from semantic_kernel .connectors .ai .function_calling_utils import kernel_function_metadata_to_function_call_format
71+ from semantic_kernel .connectors .ai .function_choice_behavior import FunctionChoiceBehavior
72+ from semantic_kernel .connectors .ai .function_choice_type import FunctionChoiceType
7173from semantic_kernel .contents .chat_message_content import ChatMessageContent
7274from semantic_kernel .contents .function_call_content import FunctionCallContent
7375from semantic_kernel .contents .utils .author_role import AuthorRole
@@ -124,6 +126,7 @@ async def invoke(
124126 parallel_tool_calls : bool | None = None ,
125127 metadata : dict [str , str ] | None = None ,
126128 polling_options : RunPollingOptions | None = None ,
129+ function_choice_behavior : FunctionChoiceBehavior | None = None ,
127130 ** kwargs : Any ,
128131 ) -> AsyncIterable [tuple [bool , "ChatMessageContent" ]]:
129132 """Invoke the message in the thread.
@@ -139,7 +142,9 @@ async def invoke(
139142 additional_messages: The additional messages to add to the thread. Only supports messages with
140143 role = User or Assistant.
141144 https://platform.openai.com/docs/api-reference/runs/createRun#runs-createrun-additional_messages
142- tools: The tools.
145+ tools: The SDK-level tools (e.g. CodeInterpreter, FileSearch, AzureAISearch). When provided,
146+ overrides the tools from the agent definition. Does not affect kernel function availability;
147+ use function_choice_behavior for that.
143148 temperature: The temperature.
144149 top_p: The top p.
145150 max_prompt_tokens: The max prompt tokens.
@@ -150,6 +155,9 @@ async def invoke(
150155 metadata: The metadata.
151156 polling_options: The polling options defined at the run-level. These will override the agent-level
152157 polling options.
158+ function_choice_behavior: Controls which kernel functions are allowed to execute during this run.
159+ Use FunctionChoiceBehavior.Auto(filters={"included_functions": [...]}) to restrict to specific
160+ functions. Only Auto is supported; other types will raise an error.
153161 kwargs: Additional keyword arguments.
154162
155163 Returns:
@@ -158,7 +166,11 @@ async def invoke(
158166 arguments = KernelArguments () if arguments is None else KernelArguments (** arguments , ** kwargs )
159167 kernel = kernel or agent .kernel
160168
161- tools = cls ._get_tools (agent = agent , kernel = kernel ) # type: ignore
169+ cls ._validate_function_choice_behavior (function_choice_behavior )
170+
171+ tools = cls ._get_tools (
172+ agent = agent , kernel = kernel , tools_override = tools , function_choice_behavior = function_choice_behavior
173+ ) # type: ignore
162174
163175 base_instructions = await agent .format_instructions (kernel = kernel , arguments = arguments )
164176
@@ -232,7 +244,11 @@ async def invoke(
232244
233245 chat_history = ChatHistory () if kwargs .get ("chat_history" ) is None else kwargs ["chat_history" ]
234246 _ = await cls ._invoke_function_calls (
235- kernel = kernel , fccs = fccs , chat_history = chat_history , arguments = arguments
247+ kernel = kernel ,
248+ fccs = fccs ,
249+ chat_history = chat_history ,
250+ arguments = arguments ,
251+ function_choice_behavior = function_choice_behavior ,
236252 )
237253
238254 tool_outputs = cls ._format_tool_outputs (fccs , chat_history )
@@ -467,6 +483,7 @@ async def invoke_stream(
467483 temperature : float | None = None ,
468484 top_p : float | None = None ,
469485 truncation_strategy : TruncationObject | None = None ,
486+ function_choice_behavior : FunctionChoiceBehavior | None = None ,
470487 ** kwargs : Any ,
471488 ) -> AsyncIterable ["StreamingChatMessageContent" ]:
472489 """Invoke the agent stream and yield ChatMessageContent continuously.
@@ -489,10 +506,15 @@ async def invoke_stream(
489506 formed from the streamed chunks.
490507 parallel_tool_calls: Whether to configure parallel tool calls.
491508 response_format: The response format.
492- tools: The tools.
509+ tools: The SDK-level tools (e.g. CodeInterpreter, FileSearch, AzureAISearch). When provided,
510+ overrides the tools from the agent definition. Does not affect kernel function availability;
511+ use function_choice_behavior for that.
493512 temperature: The temperature.
494513 top_p: The top p.
495514 truncation_strategy: The truncation strategy.
515+ function_choice_behavior: Controls which kernel functions are allowed to execute during this run.
516+ Use FunctionChoiceBehavior.Auto(filters={"included_functions": [...]}) to restrict to specific
517+ functions. Only Auto is supported; other types will raise an error.
496518 kwargs: Additional keyword arguments.
497519
498520 Returns:
@@ -502,7 +524,11 @@ async def invoke_stream(
502524 kernel = kernel or agent .kernel
503525 arguments = agent ._merge_arguments (arguments )
504526
505- tools = cls ._get_tools (agent = agent , kernel = kernel ) # type: ignore
527+ cls ._validate_function_choice_behavior (function_choice_behavior )
528+
529+ tools = cls ._get_tools (
530+ agent = agent , kernel = kernel , tools_override = tools , function_choice_behavior = function_choice_behavior
531+ ) # type: ignore
506532
507533 base_instructions = await agent .format_instructions (kernel = kernel , arguments = arguments )
508534
@@ -549,6 +575,7 @@ async def invoke_stream(
549575 arguments = arguments ,
550576 function_steps = function_steps ,
551577 active_messages = active_messages ,
578+ function_choice_behavior = function_choice_behavior ,
552579 ):
553580 if content :
554581 yield content
@@ -564,6 +591,7 @@ async def _process_stream_events(
564591 function_steps : dict [str , FunctionCallContent ],
565592 active_messages : dict [str , RunStep ],
566593 output_messages : "list[ChatMessageContent] | None" = None ,
594+ function_choice_behavior : FunctionChoiceBehavior | None = None ,
567595 ) -> AsyncIterable ["StreamingChatMessageContent" ]:
568596 """Process events from the main stream and delegate tool output handling as needed."""
569597 thread_msg_id = None
@@ -671,6 +699,7 @@ async def _process_stream_events(
671699 run = run ,
672700 function_steps = function_steps ,
673701 arguments = arguments ,
702+ function_choice_behavior = function_choice_behavior ,
674703 )
675704 if action_result is None :
676705 raise RuntimeError (
@@ -959,12 +988,74 @@ def _deduplicate_tools(existing_tools: list[dict], new_tools: list[dict]) -> lis
959988 }
960989 return [tool for tool in new_tools if tool .get ("function" , {}).get ("name" ) not in existing_names ]
961990
991+ @staticmethod
992+ def _validate_function_choice_behavior (
993+ function_choice_behavior : FunctionChoiceBehavior | None ,
994+ ) -> None :
995+ """Validate the function choice behavior is compatible with agent invocations."""
996+ if function_choice_behavior is None :
997+ return
998+ if function_choice_behavior .type_ != FunctionChoiceType .AUTO :
999+ raise AgentInvokeException (
1000+ f"FunctionChoiceBehavior with type '{ function_choice_behavior .type_ } ' is not supported for agent "
1001+ "invocations. Use FunctionChoiceBehavior.Auto(filters=...) to control which kernel functions "
1002+ "are available."
1003+ )
1004+ if not function_choice_behavior .auto_invoke_kernel_functions :
1005+ raise AgentInvokeException (
1006+ "FunctionChoiceBehavior.Auto(auto_invoke=False) is not supported for agent invocations. "
1007+ "The agent run loop manages tool invocation; disabling auto_invoke is not compatible."
1008+ )
1009+ valid_filter_keys : set [str ] = {
1010+ "excluded_plugins" ,
1011+ "included_plugins" ,
1012+ "excluded_functions" ,
1013+ "included_functions" ,
1014+ }
1015+ if function_choice_behavior .filters is not None :
1016+ if not function_choice_behavior .filters :
1017+ raise AgentInvokeException (
1018+ "FunctionChoiceBehavior filters must not be empty. Provide at least one filter key "
1019+ f"from { sorted (valid_filter_keys )} , or omit filters entirely to include all "
1020+ "kernel functions."
1021+ )
1022+ unknown_keys = {str (k ) for k in function_choice_behavior .filters } - valid_filter_keys
1023+ if unknown_keys :
1024+ raise AgentInvokeException (
1025+ f"Unknown filter key(s): { sorted (unknown_keys )} . "
1026+ f"Valid filter keys are: { sorted (valid_filter_keys )} ."
1027+ )
1028+
9621029 @classmethod
963- def _get_tools (cls : type [_T ], agent : "AzureAIAgent" , kernel : "Kernel" ) -> list [dict [str , Any ] | ToolDefinition ]:
964- """Get the tools for the agent."""
965- tools : list [Any ] = list (agent .definition .tools )
966- funcs = kernel .get_full_list_of_function_metadata ()
967- cls ._validate_function_tools_registered (tools , funcs )
1030+ def _get_tools (
1031+ cls : type [_T ],
1032+ agent : "AzureAIAgent" ,
1033+ kernel : "Kernel" ,
1034+ tools_override : list [ToolDefinition ] | None = None ,
1035+ function_choice_behavior : FunctionChoiceBehavior | None = None ,
1036+ ) -> list [dict [str , Any ] | ToolDefinition ]:
1037+ """Get the tools for the agent.
1038+
1039+ Args:
1040+ agent: The agent instance.
1041+ kernel: The kernel to use for function metadata.
1042+ tools_override: When provided, overrides agent.definition.tools (SDK-level tools only).
1043+ function_choice_behavior: When provided, filters which kernel functions are included.
1044+ """
1045+ tools : list [Any ] = list (tools_override ) if tools_override is not None else list (agent .definition .tools )
1046+
1047+ # Always validate against the full kernel function list to catch truly
1048+ # unregistered functions, regardless of FCB filtering.
1049+ all_funcs = kernel .get_full_list_of_function_metadata ()
1050+ cls ._validate_function_tools_registered (tools , all_funcs )
1051+
1052+ # Determine which kernel functions to advertise based on function_choice_behavior
1053+ if function_choice_behavior is not None and not function_choice_behavior .enable_kernel_functions :
1054+ funcs : list [KernelFunctionMetadata ] = []
1055+ elif function_choice_behavior is not None and function_choice_behavior .filters :
1056+ funcs = kernel .get_list_of_function_metadata (function_choice_behavior .filters )
1057+ else :
1058+ funcs = all_funcs
9681059 dict_defs = [kernel_function_metadata_to_function_call_format (f ) for f in funcs ]
9691060 deduped_defs = cls ._deduplicate_tools (tools , dict_defs )
9701061 tools .extend (deduped_defs )
@@ -1071,6 +1162,7 @@ async def _invoke_function_calls(
10711162 fccs : list ["FunctionCallContent" ],
10721163 chat_history : "ChatHistory" ,
10731164 arguments : KernelArguments ,
1165+ function_choice_behavior : FunctionChoiceBehavior | None = None ,
10741166 ) -> list ["AutoFunctionInvocationContext | None" ]:
10751167 """Invoke the function calls."""
10761168 return await asyncio .gather (
@@ -1079,6 +1171,7 @@ async def _invoke_function_calls(
10791171 function_call = function_call ,
10801172 chat_history = chat_history ,
10811173 arguments = arguments ,
1174+ function_behavior = function_choice_behavior ,
10821175 )
10831176 for function_call in fccs
10841177 ],
@@ -1111,6 +1204,7 @@ async def _handle_streaming_requires_action(
11111204 run : ThreadRun ,
11121205 function_steps : dict [str , "FunctionCallContent" ],
11131206 arguments : KernelArguments ,
1207+ function_choice_behavior : FunctionChoiceBehavior | None = None ,
11141208 ** kwargs : Any ,
11151209 ) -> FunctionActionResult | None :
11161210 """Handle the requires action event for a streaming run."""
@@ -1121,7 +1215,11 @@ async def _handle_streaming_requires_action(
11211215
11221216 chat_history = ChatHistory () if kwargs .get ("chat_history" ) is None else kwargs ["chat_history" ]
11231217 results = await cls ._invoke_function_calls (
1124- kernel = kernel , fccs = fccs , chat_history = chat_history , arguments = arguments
1218+ kernel = kernel ,
1219+ fccs = fccs ,
1220+ chat_history = chat_history ,
1221+ arguments = arguments ,
1222+ function_choice_behavior = function_choice_behavior ,
11251223 )
11261224
11271225 function_result_streaming_content = merge_streaming_function_results (
0 commit comments