New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
.Net: Function calling abstraction #6083
base: main
Are you sure you want to change the base?
.Net: Function calling abstraction #6083
Conversation
Co-authored-by: Stephen Toub <[email protected]>
Co-authored-by: Stephen Toub <[email protected]>
…lling.cs Co-authored-by: Stephen Toub <[email protected]>
…Content.cs Co-authored-by: Stephen Toub <[email protected]>
…eyMenshykh/semantic-kernel into function-call-content-types
Co-authored-by: Stephen Toub <[email protected]>
…alifiedFunctionName utility class.
…r one of the KernelArguments constructors.
…eyMenshykh/semantic-kernel into function-call-content-types
…e `FunctionName` utility class.
…/SergeyMenshykh/semantic-kernel into function-calling-abstraction-poc
{ | ||
if (executionSettings.FunctionChoiceBehavior is not null && executionSettings.ToolCallBehavior is not null) | ||
{ | ||
throw new ArgumentException("ToolBehaviors and ToolCallBehavior cannot be used together."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw new ArgumentException("ToolBehaviors and ToolCallBehavior cannot be used together."); | |
throw new ArgumentException($"{nameof(executionSettings.FunctionChoiceBehavior)} and {executionSettings.ToolCallBehavior} cannot be used together."); |
I also think that we should not throw an exception and use configuration based on priority. If both are specified, use FunctionChoiceBehavior
as a new approach, which will overwrite ToolCallBehavior
, we can mention this behavior in XML documentation.
New approach also allows to specify configuration in json/yaml. So, if I have already existing working code with ToolCallBehavior
, and I want to try new configuration in json/yaml, I will receive an exception at first try, because it will force me to remove ToolCallBehavior
from my existing code. Choosing one property based on priority will require minimal changes from developers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think that we should not throw an exception and use configuration based on priority. If both are specified, use FunctionChoiceBehavior as a new approach, which will overwrite ToolCallBehavior, we can mention this behavior in XML documentation.
Both solutions look good to me. The one that throws the exception notifies the caller immediately when the code is invoked, potentially saving time they would spend troubleshooting unexpected behavior. The one that relies on priority is more permissive and allows callers to use both solutions simultaneously, even at the cost of potential confusion or unexpected behavior. I would probably go with the proposed "priority" one taking into account its permissiveness. Let's see what others think.
New approach also allows to specify configuration in json/yaml. So, if I have already existing working code with ToolCallBehavior, and I want to try new configuration in json/yaml, I will receive an exception at first try, because it will force me to remove ToolCallBehavior from my existing code. Choosing one property based on priority will require minimal changes from developers.
Today, as far as I understand, prompt execution settings provided via the code override the ones that come from prompts, so no exception will be thrown in this scenario. To allow execution settings specified in the prompt to be applied, either the settings specified in the code should be removed or a different service selector should be used.
{ | ||
if (requiredFunctions.Count() > 1) | ||
{ | ||
throw new KernelException("Only one required function is allowed."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it a limitation in OpenAI only or the same is applicable in other connectors? It would be nice to see this limitation during compilation, rather than runtime, if possible of course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it a limitation in OpenAI only or the same is applicable in other connectors?
- OpenAI initially supported only one required function, but recently added support for multiple functions - https://community.openai.com/t/new-api-feature-forcing-function-calling-via-tool-choice-required/731488. However, we cannot use it yet because the Azure.AI.OpenAI SDK has not exposed the new tool choice yet.
- Mistral AI seems to support only one - https://docs.mistral.ai/capabilities/function_calling/#tool_choice
- Gemini supports many - https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#tool-config
It would be nice to see this limitation during compilation, rather than runtime, if possible of course.
It's an interesting problem to solve. Without going into the design, I see two options:
- To have a set of choices that can be specified on a connector-specific prompt execution setting, so that the choices' usage can be enforced by the compiler.
- To have a set of choices that can be supplied via the connector constructor and enforced by compiler as well.
Both have their pros and cons and would require another iteration of design.
We can also consider other solutions to address the problem:
- Use the first function from the list - this is not obvious and can be confusing for callers.
- Execute the functions by the connector itself - this will solve the problem but will require a solution for obtaining arguments for the functions.
dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAIFunctionsTests.cs
Outdated
Show resolved
Hide resolved
dotnet/src/SemanticKernel.Abstractions/AI/FunctionChoiceBehaviors/AutoFunctionChoiceBehavior.cs
Outdated
Show resolved
Hide resolved
dotnet/src/SemanticKernel.Abstractions/AI/FunctionChoiceBehaviors/FunctionChoiceBehavior.cs
Outdated
Show resolved
Hide resolved
dotnet/src/SemanticKernel.Abstractions/AI/FunctionChoiceBehaviors/NoneFunctionChoiceBehavior.cs
Outdated
Show resolved
Hide resolved
List<KernelFunction>? requiredFunctions = null; | ||
bool allowAnyRequestedKernelFunction = false; | ||
|
||
// Handle functions provided via constructor as function instances. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logic in this method is very similar to logic in AutoFunctionChoiceBehavior.GetConfiguration
method. Should we move it to the base class and re-use?
* Fix issue in CLientCore when none tool choice specified in first request
...src/SemanticKernel.Abstractions/AI/FunctionChoiceBehaviors/RequiredFunctionChoiceBehavior.cs
Show resolved
Hide resolved
...src/SemanticKernel.Abstractions/AI/FunctionChoiceBehaviors/RequiredFunctionChoiceBehavior.cs
Outdated
Show resolved
Hide resolved
dotnet/src/SemanticKernel.Abstractions/AI/FunctionChoiceBehaviors/NoneFunctionChoiceBehavior.cs
Outdated
Show resolved
Hide resolved
...emanticKernel.Abstractions/AI/FunctionChoiceBehaviors/FunctionChoiceBehaviorConfiguration.cs
Outdated
Show resolved
Hide resolved
...emanticKernel.Abstractions/AI/FunctionChoiceBehaviors/FunctionChoiceBehaviorConfiguration.cs
Show resolved
Hide resolved
dotnet/src/SemanticKernel.Abstractions/AI/FunctionChoiceBehaviors/FunctionChoiceBehavior.cs
Outdated
Show resolved
Hide resolved
…tor. 2. Convert prompt function name in format "plugin.function" to "plugin-function".
2. Use one list to send functions to connectors instead of two ones.
…om the public API serface of the *FunctionChoiceBehavior classes
…/SergeyMenshykh/semantic-kernel into function-calling-abstraction-poc
…/SergeyMenshykh/semantic-kernel into function-calling-abstraction-poc
Motivation and Context
Today, every AI connector in SK that supports function calling has its own implementation of tool call behavior model classes. These classes are used to configure the way connectors advertise and invoke functions. For example, the behavior classes can describe which functions should be advertised to LLM by a connector, whether the functions should be called automatically by the connector, or if the connector caller will invoke them himself manually, etc.
All the tool call behavior classes are the same in terms of describing the desired function call behavior. However, the classes have a mapping functionality to map the function call behavior to the connector-specific model classes, and that's what makes the function calling classes non-reusable between connectors.
Additionally, today, it's not possible to specify function calling behavior declaratively in YAML, MD, and Kernel(config.json) prompts.
Description
This PR introduces a few function choice behavior classes that allow configure function calling behavior in connector agnostic way:
Callers can specify one of the function choice behaviors using the new property, FunctionChoiceBehavior, which has been added to the PromptExecutionSettings class. Here's an example:
The function choice behavior can be configured in JSON and YAML prompts as well:
Closes - #5253
ADR - #6099
Contribution Checklist