Skip to content

Commit 9b821db

Browse files
author
Milder Hernandez Cagua
committed
Add FunctionChoiceBehavior implementation
1 parent d380bb5 commit 9b821db

File tree

13 files changed

+611
-83
lines changed

13 files changed

+611
-83
lines changed

agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,19 +110,16 @@ private Mono<List<ChatMessageContent<?>>> internalInvokeAsync(
110110
? invocationContext.getPromptExecutionSettings()
111111
: kernelArguments.getExecutionSettings().get(chatCompletionService.getServiceId());
112112

113-
ToolCallBehavior toolCallBehavior = invocationContext != null
114-
? invocationContext.getToolCallBehavior()
115-
: ToolCallBehavior.allowAllKernelFunctions(true);
116-
117113
// Build base invocation context
118114
InvocationContext.Builder builder = InvocationContext.builder()
119115
.withPromptExecutionSettings(executionSettings)
120-
.withToolCallBehavior(toolCallBehavior)
121116
.withReturnMode(InvocationReturnMode.NEW_MESSAGES_ONLY);
122117

123118
if (invocationContext != null) {
124119
builder = builder
125120
.withTelemetry(invocationContext.getTelemetry())
121+
.withFunctionChoiceBehavior(invocationContext.getFunctionChoiceBehavior())
122+
.withToolCallBehavior(invocationContext.getToolCallBehavior())
126123
.withContextVariableConverter(invocationContext.getContextVariableTypes())
127124
.withKernelHooks(invocationContext.getKernelHooks());
128125
}

aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java

Lines changed: 174 additions & 67 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.microsoft.semantickernel.aiservices.openai.chatcompletion;
2+
3+
import com.azure.ai.openai.models.ChatCompletionsToolDefinition;
4+
import com.azure.ai.openai.models.ChatCompletionsToolSelection;
5+
import com.microsoft.semantickernel.functionchoice.FunctionChoiceBehaviorOptions;
6+
7+
import javax.annotation.Nullable;
8+
import java.util.List;
9+
10+
public class OpenAIToolCallConfig {
11+
private final List<ChatCompletionsToolDefinition> tools;
12+
private final ChatCompletionsToolSelection toolChoice;
13+
private final boolean autoInvoke;
14+
private final FunctionChoiceBehaviorOptions options;
15+
16+
/**
17+
* Creates a new instance of the {@link OpenAIToolCallConfig} class.
18+
*
19+
* @param tools The list of tools available for the call.
20+
* @param toolChoice The tool selection strategy.
21+
* @param autoInvoke Indicates whether to automatically invoke the tool.
22+
* @param options Additional options for function choice behavior.
23+
*/
24+
public OpenAIToolCallConfig(
25+
List<ChatCompletionsToolDefinition> tools,
26+
ChatCompletionsToolSelection toolChoice,
27+
boolean autoInvoke,
28+
@Nullable FunctionChoiceBehaviorOptions options) {
29+
this.tools = tools;
30+
this.toolChoice = toolChoice;
31+
this.autoInvoke = autoInvoke;
32+
this.options = options;
33+
}
34+
35+
/**
36+
* Gets the list of tools available for the call.
37+
*
38+
* @return The list of tools.
39+
*/
40+
public List<ChatCompletionsToolDefinition> getTools() {
41+
return tools;
42+
}
43+
44+
/**
45+
* Gets the tool selection strategy.
46+
*
47+
* @return The tool selection strategy.
48+
*/
49+
public ChatCompletionsToolSelection getToolChoice() {
50+
return toolChoice;
51+
}
52+
53+
/**
54+
* Indicates whether to automatically invoke the tool.
55+
*
56+
* @return True if auto-invocation is enabled; otherwise, false.
57+
*/
58+
public boolean isAutoInvoke() {
59+
return autoInvoke;
60+
}
61+
62+
/**
63+
* Gets additional options for function choice behavior.
64+
*
65+
* @return The function choice behavior options.
66+
*/
67+
public FunctionChoiceBehaviorOptions getOptions() {
68+
return options;
69+
}
70+
}

samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionAgent.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@
1010
import com.microsoft.semantickernel.agents.chatcompletion.ChatHistoryAgentThread;
1111
import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion;
1212
import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter;
13-
import com.microsoft.semantickernel.contextvariables.ContextVariableTypes;
13+
import com.microsoft.semantickernel.functionchoice.FunctionChoiceBehavior;
1414
import com.microsoft.semantickernel.implementation.templateengine.tokenizer.DefaultPromptTemplate;
1515
import com.microsoft.semantickernel.orchestration.InvocationContext;
1616
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
17-
import com.microsoft.semantickernel.orchestration.ToolCallBehavior;
1817
import com.microsoft.semantickernel.plugin.KernelPluginFactory;
1918
import com.microsoft.semantickernel.samples.plugins.github.GitHubModel;
2019
import com.microsoft.semantickernel.samples.plugins.github.GitHubPlugin;
@@ -68,7 +67,7 @@ public static void main(String[] args) {
6867
.build();
6968

7069
InvocationContext invocationContext = InvocationContext.builder()
71-
.withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true))
70+
.withFunctionChoiceBehavior(FunctionChoiceBehavior.auto(true))
7271
.withContextVariableConverter(new ContextVariableTypeConverter<>(
7372
GitHubModel.Issue.class,
7473
o -> (GitHubModel.Issue) o,

samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example59_OpenAIFunctionCalling.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatMessageContent;
1111
import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIFunctionToolCall;
1212
import com.microsoft.semantickernel.contextvariables.ContextVariableTypes;
13+
import com.microsoft.semantickernel.functionchoice.FunctionChoiceBehavior;
1314
import com.microsoft.semantickernel.implementation.CollectionUtil;
1415
import com.microsoft.semantickernel.orchestration.FunctionResult;
1516
import com.microsoft.semantickernel.orchestration.FunctionResultMetadata;
@@ -38,7 +39,7 @@ public class Example59_OpenAIFunctionCalling {
3839
// Only required if AZURE_CLIENT_KEY is set
3940
private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT");
4041
private static final String MODEL_ID = System.getenv()
41-
.getOrDefault("MODEL_ID", "gpt-35-turbo-2");
42+
.getOrDefault("MODEL_ID", "gpt-4o");
4243

4344
// Define functions that can be called by the model
4445
public static class HelperFunctions {
@@ -118,7 +119,7 @@ public static void main(String[] args) throws NoSuchMethodException {
118119

119120
var result = kernel
120121
.invokeAsync(function)
121-
.withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true))
122+
.withFunctionChoiceBehavior(FunctionChoiceBehavior.auto(true))
122123
.withResultType(ContextVariableTypes.getGlobalVariableTypeForClass(String.class))
123124
.block();
124125
System.out.println(result.getResult());
@@ -134,7 +135,7 @@ public static void main(String[] args) throws NoSuchMethodException {
134135
chatHistory,
135136
kernel,
136137
InvocationContext.builder()
137-
.withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(false))
138+
.withFunctionChoiceBehavior(FunctionChoiceBehavior.auto(false))
138139
.withReturnMode(InvocationReturnMode.FULL_HISTORY)
139140
.build())
140141
.block();
@@ -243,7 +244,7 @@ public static void multiTurnaroundCall() {
243244
chatHistory,
244245
kernel,
245246
InvocationContext.builder()
246-
.withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true))
247+
.withFunctionChoiceBehavior(FunctionChoiceBehavior.auto(true))
247248
.withReturnMode(InvocationReturnMode.FULL_HISTORY)
248249
.build())
249250
.block();
@@ -258,7 +259,7 @@ public static void multiTurnaroundCall() {
258259
chatHistory,
259260
kernel,
260261
InvocationContext.builder()
261-
.withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true))
262+
.withFunctionChoiceBehavior(FunctionChoiceBehavior.auto(true))
262263
.withReturnMode(InvocationReturnMode.FULL_HISTORY)
263264
.build())
264265
.block();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.microsoft.semantickernel.functionchoice;
2+
3+
import com.microsoft.semantickernel.semanticfunctions.KernelFunction;
4+
5+
import javax.annotation.Nullable;
6+
import java.util.List;
7+
8+
/**
9+
* A set of allowed kernel functions. All kernel functions are allowed if allKernelFunctionsAllowed is true.
10+
* Otherwise, only the functions in allowedFunctions are allowed.
11+
* <p>
12+
* If a function is allowed, it may be called. If it is not allowed, it will not be called.
13+
*/
14+
public class AutoFunctionChoiceBehavior extends FunctionChoiceBehavior {
15+
private final boolean autoInvoke;
16+
17+
/**
18+
* Create a new instance of AutoFunctionChoiceBehavior.
19+
*
20+
* @param autoInvoke Whether auto-invocation is enabled.
21+
* @param functions A set of functions to advertise to the model.
22+
* @param options Options for the function choice behavior.
23+
*/
24+
public AutoFunctionChoiceBehavior(boolean autoInvoke,
25+
@Nullable List<KernelFunction<?>> functions,
26+
@Nullable FunctionChoiceBehaviorOptions options) {
27+
super(functions, options);
28+
this.autoInvoke = autoInvoke;
29+
}
30+
31+
/**
32+
* Check whether the given function is allowed.
33+
*
34+
* @return Whether the function is allowed.
35+
*/
36+
public boolean isAutoInvoke() {
37+
return autoInvoke;
38+
}
39+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
package com.microsoft.semantickernel.functionchoice;
3+
4+
import com.microsoft.semantickernel.semanticfunctions.KernelFunction;
5+
6+
import javax.annotation.Nullable;
7+
import java.util.HashSet;
8+
import java.util.List;
9+
import java.util.Objects;
10+
import java.util.Set;
11+
12+
/**
13+
* Defines the behavior of a tool call. Currently, the only tool available is function calling.
14+
*/
15+
public abstract class FunctionChoiceBehavior {
16+
private final Set<String> fullFunctionNames;
17+
18+
protected final List<KernelFunction<?>> functions;
19+
protected final FunctionChoiceBehaviorOptions options;
20+
21+
protected FunctionChoiceBehavior(List<KernelFunction<?>> functions,
22+
@Nullable FunctionChoiceBehaviorOptions options) {
23+
this.functions = functions;
24+
this.fullFunctionNames = new HashSet<>();
25+
26+
if (functions != null) {
27+
functions.stream().filter(Objects::nonNull).forEach(
28+
f -> this.fullFunctionNames
29+
.add(formFullFunctionName(f.getPluginName(), f.getName())));
30+
}
31+
32+
if (options != null) {
33+
this.options = options;
34+
} else {
35+
this.options = FunctionChoiceBehaviorOptions.builder().build();
36+
}
37+
}
38+
39+
/**
40+
* Gets the functions that are allowed.
41+
*
42+
* @return The functions that are allowed.
43+
*/
44+
public List<KernelFunction<?>> getFunctions() {
45+
return functions;
46+
}
47+
48+
/**
49+
* Gets the options for the function choice behavior.
50+
*
51+
* @return The options for the function choice behavior.
52+
*/
53+
public FunctionChoiceBehaviorOptions getOptions() {
54+
return options;
55+
}
56+
57+
/**
58+
* Gets an instance of the FunctionChoiceBehavior that provides all the Kernel's plugins functions to the AI model to call.
59+
*
60+
* @param autoInvoke Indicates whether the functions should be automatically invoked by AI connectors
61+
*
62+
* @return A new ToolCallBehavior instance with all kernel functions allowed.
63+
*/
64+
public static FunctionChoiceBehavior auto(boolean autoInvoke) {
65+
return new AutoFunctionChoiceBehavior(autoInvoke, null, null);
66+
}
67+
68+
/**
69+
* Gets an instance of the FunctionChoiceBehavior that provides either all the Kernel's plugins functions to the AI model to call or specific functions.
70+
*
71+
* @param autoInvoke Enable or disable auto-invocation.
72+
* If auto-invocation is enabled, the model may request that the Semantic Kernel
73+
* invoke the kernel functions and return the value to the model.
74+
* @param functions Functions to provide to the model. If null, all the Kernel's plugins' functions are provided to the model.
75+
* If empty, no functions are provided to the model, which is equivalent to disabling function calling.
76+
* @param options Options for the function choice behavior.
77+
*
78+
* @return A new FunctionChoiceBehavior instance with all kernel functions allowed.
79+
*/
80+
public static FunctionChoiceBehavior auto(boolean autoInvoke,
81+
List<KernelFunction<?>> functions,
82+
@Nullable FunctionChoiceBehaviorOptions options) {
83+
return new AutoFunctionChoiceBehavior(autoInvoke, functions, options);
84+
}
85+
86+
/**
87+
* Gets an instance of the FunctionChoiceBehavior that provides either all the Kernel's plugins functions to the AI model to call or specific functions.
88+
* <p>
89+
* This behavior forces the model to call the provided functions.
90+
* SK connectors will invoke a requested function or multiple requested functions if the model requests multiple ones in one request,
91+
* while handling the first request, and stop advertising the functions for the following requests to prevent the model from repeatedly calling the same function(s).
92+
*
93+
* @param functions Functions to provide to the model. If null, all the Kernel's plugins' functions are provided to the model.
94+
* If empty, no functions are provided to the model, which is equivalent to disabling function calling.
95+
* @return A new FunctionChoiceBehavior instance with the required function.
96+
*/
97+
public static FunctionChoiceBehavior required(boolean autoInvoke,
98+
List<KernelFunction<?>> functions,
99+
@Nullable FunctionChoiceBehaviorOptions options) {
100+
return new RequiredFunctionChoiceBehavior(autoInvoke, functions, options);
101+
}
102+
103+
/**
104+
* Gets an instance of the FunctionChoiceBehavior that provides either all the Kernel's plugins functions to the AI model to call or specific functions.
105+
* <p>
106+
* This behavior is useful if the user should first validate what functions the model will use.
107+
*
108+
* @param functions Functions to provide to the model. If null, all the Kernel's plugins' functions are provided to the model.
109+
* If empty, no functions are provided to the model, which is equivalent to disabling function calling.
110+
*/
111+
public static FunctionChoiceBehavior none(List<KernelFunction<?>> functions,
112+
@Nullable FunctionChoiceBehaviorOptions options) {
113+
return new NoneFunctionChoiceBehavior(functions, options);
114+
}
115+
116+
117+
/**
118+
* The separator between the plugin name and the function name.
119+
*/
120+
public static final String FUNCTION_NAME_SEPARATOR = "-";
121+
122+
/**
123+
* Form the full function name.
124+
*
125+
* @param pluginName The name of the plugin that the function is in.
126+
* @param functionName The name of the function.
127+
* @return The key for the function.
128+
*/
129+
public static String formFullFunctionName(@Nullable String pluginName, String functionName) {
130+
if (pluginName == null) {
131+
pluginName = "";
132+
}
133+
return String.format("%s%s%s", pluginName, FUNCTION_NAME_SEPARATOR, functionName);
134+
}
135+
136+
/**
137+
* Check whether the given function is allowed.
138+
*
139+
* @param function The function to check.
140+
* @return Whether the function is allowed.
141+
*/
142+
public boolean isFunctionAllowed(KernelFunction<?> function) {
143+
return isFunctionAllowed(function.getPluginName(), function.getName());
144+
}
145+
146+
/**
147+
* Check whether the given function is allowed.
148+
*
149+
* @param pluginName The name of the plugin that the function is in.
150+
* @param functionName The name of the function.
151+
* @return Whether the function is allowed.
152+
*/
153+
public boolean isFunctionAllowed(@Nullable String pluginName, String functionName) {
154+
// If no functions are provided, all functions are allowed.
155+
if (functions == null || functions.isEmpty()) {
156+
return true;
157+
}
158+
159+
String key = formFullFunctionName(pluginName, functionName);
160+
return fullFunctionNames.contains(key);
161+
}
162+
}

0 commit comments

Comments
 (0)