mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
Java: add ToolCallBehaviour test (#5268)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [ ] The code builds clean without any errors or warnings - [ ] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [ ] All unit tests pass, and I have added new tests where possible - [ ] I didn't break anyone 😄
This commit is contained in:
+66
-24
@@ -198,8 +198,10 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|
||||
functionResult.getResult(),
|
||||
functionToolCall.getId());
|
||||
|
||||
msgs.add(requestToolMessage);
|
||||
return msgs;
|
||||
ArrayList<ChatRequestMessage> res = new ArrayList<>(
|
||||
msgs);
|
||||
res.add(requestToolMessage);
|
||||
return res;
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -207,8 +209,10 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|
||||
})
|
||||
.flatMap(it -> it)
|
||||
.flatMap(
|
||||
msgs -> internalChatMessageContentsAsync(msgs, kernel, functions,
|
||||
invocationContext, autoInvokeAttempts - 1));
|
||||
msgs -> {
|
||||
return internalChatMessageContentsAsync(msgs, kernel, functions,
|
||||
invocationContext, autoInvokeAttempts - 1);
|
||||
});
|
||||
});
|
||||
|
||||
return result.map(op -> (List<ChatMessageContent<?>>) op);
|
||||
@@ -218,36 +222,50 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|
||||
private Mono<FunctionResult<String>> invokeFunctionTool(
|
||||
Kernel kernel,
|
||||
ChatCompletionsFunctionToolCall toolCall) {
|
||||
|
||||
try {
|
||||
OpenAIFunctionToolCall openAIFunctionToolCall = extractOpenAIFunctionToolCall(toolCall);
|
||||
KernelFunction<?> function = kernel.getFunction(
|
||||
openAIFunctionToolCall.getPluginName(),
|
||||
openAIFunctionToolCall.getFunctionName());
|
||||
|
||||
return function
|
||||
.invokeAsync(kernel)
|
||||
.withArguments(openAIFunctionToolCall.getArguments())
|
||||
.withResultType(ContextVariableTypes.getGlobalVariableTypeForClass(
|
||||
String.class));
|
||||
} catch (JsonProcessingException e) {
|
||||
return Mono.error(new SKException("Failed to parse tool arguments"));
|
||||
}
|
||||
}
|
||||
|
||||
private OpenAIFunctionToolCall extractOpenAIFunctionToolCall(
|
||||
ChatCompletionsFunctionToolCall toolCall) throws JsonProcessingException {
|
||||
|
||||
// Split the full name of a function into plugin and function name
|
||||
String name = toolCall.getFunction().getName();
|
||||
String[] parts = name.split(OpenAIFunction.getNameSeparator());
|
||||
String pluginName = parts.length > 1 ? parts[0] : "";
|
||||
String fnName = parts.length > 1 ? parts[1] : parts[0];
|
||||
|
||||
KernelFunction<?> function = kernel.getFunction(pluginName, fnName);
|
||||
|
||||
KernelFunctionArguments arguments = KernelFunctionArguments.builder().build();
|
||||
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode jsonToolCallArguments = mapper.readTree(toolCall.getFunction().getArguments());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode jsonToolCallArguments = mapper.readTree(toolCall.getFunction().getArguments());
|
||||
|
||||
jsonToolCallArguments.fields().forEachRemaining(
|
||||
entry -> arguments.put(entry.getKey(),
|
||||
ContextVariable.of(entry.getValue().asText())));
|
||||
} catch (JsonProcessingException e) {
|
||||
LOGGER.error("Failed to parse json", e);
|
||||
return Mono.empty();
|
||||
}
|
||||
jsonToolCallArguments.fields().forEachRemaining(
|
||||
entry -> arguments.put(entry.getKey(),
|
||||
ContextVariable.of(entry.getValue().asText())));
|
||||
|
||||
return new OpenAIFunctionToolCall(
|
||||
toolCall.getId(),
|
||||
pluginName,
|
||||
fnName,
|
||||
arguments);
|
||||
|
||||
return function
|
||||
.invokeAsync(kernel)
|
||||
.withArguments(arguments)
|
||||
.withResultType(ContextVariableTypes.getGlobalVariableTypeForClass(
|
||||
String.class));
|
||||
}
|
||||
|
||||
private Mono<List<ChatMessageContent>> getChatMessageContentsAsync(
|
||||
private Mono<List<OpenAIChatMessageContent>> getChatMessageContentsAsync(
|
||||
ChatCompletions completions) {
|
||||
FunctionResultMetadata completionMetadata = FunctionResultMetadata.build(
|
||||
completions.getId(),
|
||||
@@ -262,16 +280,40 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Flux.fromIterable(responseMessages)
|
||||
.map(response -> new ChatMessageContent(
|
||||
.map(response -> new OpenAIChatMessageContent(
|
||||
AuthorRole.ASSISTANT,
|
||||
response.getContent(),
|
||||
this.getModelId(),
|
||||
null,
|
||||
null,
|
||||
completionMetadata))
|
||||
completionMetadata,
|
||||
formOpenAiToolCalls(response)))
|
||||
.collectList();
|
||||
}
|
||||
|
||||
private List<OpenAIFunctionToolCall> formOpenAiToolCalls(ChatResponseMessage response) {
|
||||
if (response.getToolCalls() == null || response.getToolCalls().isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return response
|
||||
.getToolCalls()
|
||||
.stream()
|
||||
.map(call -> {
|
||||
if (call instanceof ChatCompletionsFunctionToolCall) {
|
||||
try {
|
||||
return extractOpenAIFunctionToolCall(
|
||||
(ChatCompletionsFunctionToolCall) call);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new SKException("Failed to parse tool arguments", e);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static ChatCompletionsOptions getCompletionsOptions(
|
||||
ChatCompletionService chatCompletionService,
|
||||
List<ChatRequestMessage> chatRequestMessages,
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.aiservices.openai.chatcompletion;
|
||||
|
||||
import com.microsoft.semantickernel.orchestration.FunctionResultMetadata;
|
||||
import com.microsoft.semantickernel.services.chatcompletion.AuthorRole;
|
||||
import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class OpenAIChatMessageContent<T> extends ChatMessageContent<T> {
|
||||
|
||||
@Nullable
|
||||
private final List<OpenAIFunctionToolCall> toolCall;
|
||||
|
||||
public OpenAIChatMessageContent(AuthorRole authorRole, String content, @Nullable String modelId,
|
||||
@Nullable T innerContent, @Nullable Charset encoding,
|
||||
@Nullable FunctionResultMetadata metadata) {
|
||||
super(authorRole, content, modelId, innerContent, encoding, metadata);
|
||||
toolCall = null;
|
||||
}
|
||||
|
||||
public OpenAIChatMessageContent(
|
||||
AuthorRole authorRole,
|
||||
String content,
|
||||
@Nullable String modelId,
|
||||
@Nullable T innerContent,
|
||||
@Nullable Charset encoding,
|
||||
@Nullable FunctionResultMetadata metadata,
|
||||
@Nullable List<OpenAIFunctionToolCall> toolCall) {
|
||||
super(authorRole, content, modelId, innerContent, encoding, metadata);
|
||||
|
||||
if (toolCall == null) {
|
||||
this.toolCall = null;
|
||||
} else {
|
||||
this.toolCall = Collections.unmodifiableList(toolCall);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<OpenAIFunctionToolCall> getToolCall() {
|
||||
return toolCall;
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.aiservices.openai.chatcompletion;
|
||||
|
||||
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class OpenAIFunctionToolCall {
|
||||
|
||||
/// <summary>Gets the ID of the tool call.</summary>
|
||||
@Nullable
|
||||
private final String id;
|
||||
|
||||
/// <summary>Gets the name of the plugin with which this function is associated, if any.</summary>
|
||||
|
||||
@Nullable
|
||||
private final String pluginName;
|
||||
|
||||
/// <summary>Gets the name of the function.</summary>
|
||||
private final String functionName;
|
||||
|
||||
/// <summary>Gets a name/value collection of the arguments to the function, if any.</summary>
|
||||
@Nullable
|
||||
private final KernelFunctionArguments arguments;
|
||||
|
||||
public OpenAIFunctionToolCall(
|
||||
@Nullable String id,
|
||||
@Nullable String pluginName,
|
||||
String functionName,
|
||||
@Nullable KernelFunctionArguments arguments) {
|
||||
this.id = id;
|
||||
this.pluginName = pluginName;
|
||||
this.functionName = functionName;
|
||||
this.arguments = arguments;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPluginName() {
|
||||
return pluginName;
|
||||
}
|
||||
|
||||
public String getFunctionName() {
|
||||
return functionName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KernelFunctionArguments getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.tests;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.post;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||
|
||||
import com.azure.ai.openai.OpenAIAsyncClient;
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.client.MappingBuilder;
|
||||
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
|
||||
import com.github.tomakehurst.wiremock.matching.ContainsPattern;
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatMessageContent;
|
||||
import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIFunctionToolCall;
|
||||
import com.microsoft.semantickernel.exceptions.SKException;
|
||||
import com.microsoft.semantickernel.orchestration.InvocationContext;
|
||||
import com.microsoft.semantickernel.orchestration.ToolCallBehavior;
|
||||
import com.microsoft.semantickernel.plugin.KernelPlugin;
|
||||
import com.microsoft.semantickernel.semanticfunctions.KernelFunction;
|
||||
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromMethod;
|
||||
import com.microsoft.semantickernel.services.chatcompletion.AuthorRole;
|
||||
import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService;
|
||||
import com.microsoft.semantickernel.services.chatcompletion.ChatHistory;
|
||||
import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@WireMockTest
|
||||
public class ToolCallBehaviourTest {
|
||||
|
||||
private WireMockServer wm;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
wm = new WireMockServer();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() {
|
||||
wm.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toolThatDoesNotExistThrows() {
|
||||
ChatCompletionService chatCompletionService = getChatCompletionService();
|
||||
|
||||
Kernel kernel = Kernel.builder()
|
||||
.withAIService(ChatCompletionService.class, chatCompletionService)
|
||||
.build();
|
||||
|
||||
ChatHistory messages = new ChatHistory();
|
||||
messages.addMessage(
|
||||
new ChatMessageContent<>(
|
||||
AuthorRole.USER,
|
||||
"Call A function"));
|
||||
|
||||
try {
|
||||
chatCompletionService
|
||||
.getChatMessageContentsAsync(
|
||||
messages,
|
||||
kernel,
|
||||
InvocationContext.builder()
|
||||
.withToolCallBehavior(
|
||||
ToolCallBehavior.allowAllKernelFunctions(true))
|
||||
.build())
|
||||
.block();
|
||||
Assertions.fail("Expected an exception to be thrown");
|
||||
} catch (SKException e) {
|
||||
Assertions.assertTrue(e.getMessage()
|
||||
.contains("Failed to find plugin apluginname"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonAutoInvokedIsNotCalled() throws NoSuchMethodException {
|
||||
ChatCompletionService chatCompletionService = getChatCompletionService();
|
||||
|
||||
TestPlugin testPlugin = Mockito.spy(new TestPlugin());
|
||||
KernelFunction<String> method = KernelFunctionFromMethod.<String>builder()
|
||||
.withFunctionName("doIt")
|
||||
.withMethod(TestPlugin.class.getMethod("doIt"))
|
||||
.withTarget(testPlugin)
|
||||
.withPluginName("apluginname")
|
||||
.build();
|
||||
|
||||
Kernel kernel = Kernel.builder()
|
||||
.withAIService(ChatCompletionService.class, chatCompletionService)
|
||||
.withPlugin(
|
||||
new KernelPlugin(
|
||||
"apluginname",
|
||||
"A plugin description",
|
||||
Map.of("doIt", method)))
|
||||
.build();
|
||||
|
||||
ChatHistory messages = new ChatHistory();
|
||||
messages.addMessage(
|
||||
new ChatMessageContent<>(
|
||||
AuthorRole.USER,
|
||||
"Call A function"));
|
||||
|
||||
List<ChatMessageContent<?>> result = chatCompletionService
|
||||
.getChatMessageContentsAsync(
|
||||
messages,
|
||||
kernel,
|
||||
InvocationContext.builder()
|
||||
.withToolCallBehavior(
|
||||
ToolCallBehavior.allowOnlyKernelFunctions(false, method))
|
||||
.build())
|
||||
.block();
|
||||
|
||||
List<OpenAIFunctionToolCall> toolCalls = ((OpenAIChatMessageContent<?>) result.get(0))
|
||||
.getToolCall();
|
||||
|
||||
Assertions.assertNotNull(toolCalls);
|
||||
Assertions.assertEquals(1, toolCalls.size());
|
||||
Assertions.assertEquals("apluginname", toolCalls.get(0).getPluginName());
|
||||
Assertions.assertEquals("doIt", toolCalls.get(0).getFunctionName());
|
||||
Assertions.assertEquals("call_abc123", toolCalls.get(0).getId());
|
||||
|
||||
Mockito.verify(testPlugin, Mockito.times(0)).doIt();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toolIsInvoked() throws NoSuchMethodException {
|
||||
ChatCompletionService chatCompletionService = getChatCompletionService();
|
||||
|
||||
TestPlugin testPlugin = Mockito.spy(new TestPlugin());
|
||||
KernelFunction<String> method = KernelFunctionFromMethod.<String>builder()
|
||||
.withFunctionName("doIt")
|
||||
.withMethod(TestPlugin.class.getMethod("doIt"))
|
||||
.withTarget(testPlugin)
|
||||
.withPluginName("apluginname")
|
||||
.build();
|
||||
|
||||
Kernel kernel = Kernel.builder()
|
||||
.withAIService(ChatCompletionService.class, chatCompletionService)
|
||||
.withPlugin(
|
||||
new KernelPlugin(
|
||||
"apluginname",
|
||||
"A plugin description",
|
||||
Map.of("doIt", method)))
|
||||
.build();
|
||||
|
||||
ChatHistory messages = new ChatHistory();
|
||||
messages.addMessage(
|
||||
new ChatMessageContent<>(
|
||||
AuthorRole.USER,
|
||||
"Call A function"));
|
||||
|
||||
List<ChatMessageContent<?>> result = chatCompletionService
|
||||
.getChatMessageContentsAsync(
|
||||
messages,
|
||||
kernel,
|
||||
InvocationContext.builder()
|
||||
.withToolCallBehavior(
|
||||
ToolCallBehavior.allowAllKernelFunctions(true))
|
||||
.build())
|
||||
.block();
|
||||
|
||||
Assertions.assertTrue(result.get(0).getContent().contains("tool call done"));
|
||||
Mockito.verify(testPlugin, Mockito.times(1)).doIt();
|
||||
|
||||
result = chatCompletionService
|
||||
.getChatMessageContentsAsync(
|
||||
messages,
|
||||
kernel,
|
||||
InvocationContext.builder()
|
||||
.withToolCallBehavior(
|
||||
ToolCallBehavior.allowOnlyKernelFunctions(true, Arrays.asList(method)))
|
||||
.build())
|
||||
.block();
|
||||
|
||||
Assertions.assertTrue(result.get(0).getContent().contains("tool call done"));
|
||||
Mockito.verify(testPlugin, Mockito.times(2)).doIt();
|
||||
|
||||
result = chatCompletionService
|
||||
.getChatMessageContentsAsync(
|
||||
messages,
|
||||
kernel,
|
||||
InvocationContext.builder()
|
||||
.withToolCallBehavior(
|
||||
ToolCallBehavior.allowOnlyKernelFunctions(true, method))
|
||||
.build())
|
||||
.block();
|
||||
|
||||
Assertions.assertTrue(result.get(0).getContent().contains("tool call done"));
|
||||
Mockito.verify(testPlugin, Mockito.times(3)).doIt();
|
||||
}
|
||||
|
||||
private ChatCompletionService getChatCompletionService() {
|
||||
wm
|
||||
.addStubMapping(
|
||||
stubFor(
|
||||
buildResponse("Call A function", """
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": null,
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_abc123",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "apluginname-doIt",
|
||||
"arguments": "{}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"logprobs": null,
|
||||
"finish_reason": "tool_calls"
|
||||
}
|
||||
]
|
||||
""")));
|
||||
|
||||
wm.addStubMapping(
|
||||
stubFor(
|
||||
buildResponse("Tool call performed", """
|
||||
"choices" : [
|
||||
{
|
||||
"finish_reason" : "stop",
|
||||
"index" : 0,
|
||||
"message" : {
|
||||
"content" : "tool call done",
|
||||
"role" : "assistant"
|
||||
}
|
||||
}
|
||||
]
|
||||
""")));
|
||||
|
||||
wm.start();
|
||||
|
||||
final OpenAIAsyncClient client = new OpenAIClientBuilder()
|
||||
.endpoint("http://localhost:" + wm.port() + "/")
|
||||
.buildAsyncClient();
|
||||
|
||||
return ChatCompletionService.builder()
|
||||
.withOpenAIAsyncClient(client)
|
||||
.withModelId("gpt-35-turbo-2")
|
||||
.build();
|
||||
}
|
||||
|
||||
private static MappingBuilder buildResponse(String bodyMatcher, String responseBody) {
|
||||
return post(urlEqualTo(
|
||||
"//openai/deployments/gpt-35-turbo-2/chat/completions?api-version=2023-12-01-preview"))
|
||||
.withRequestBody(new ContainsPattern(bodyMatcher))
|
||||
.willReturn(
|
||||
aResponse()
|
||||
.withStatus(200)
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withBody(formResponse(responseBody)));
|
||||
}
|
||||
|
||||
public static class TestPlugin {
|
||||
|
||||
public String doIt() {
|
||||
return "Tool call performed";
|
||||
}
|
||||
}
|
||||
|
||||
private static String formResponse(String chatChoices) {
|
||||
return """
|
||||
{
|
||||
%s,
|
||||
"created" : 1707253061,
|
||||
"id" : "chatcmpl-xxx",
|
||||
"model" : "gpt-35-turbo",
|
||||
"object" : "chat.completion",
|
||||
"prompt_filter_results" : [
|
||||
{
|
||||
"content_filter_results" : {
|
||||
"hate" : {
|
||||
"filtered" : false,
|
||||
"severity" : "safe"
|
||||
},
|
||||
"self_harm" : {
|
||||
"filtered" : false,
|
||||
"severity" : "safe"
|
||||
},
|
||||
"sexual" : {
|
||||
"filtered" : false,
|
||||
"severity" : "safe"
|
||||
},
|
||||
"violence" : {
|
||||
"filtered" : false,
|
||||
"severity" : "safe"
|
||||
}
|
||||
},
|
||||
"prompt_index" : 0
|
||||
}
|
||||
],
|
||||
"usage" : {
|
||||
"completion_tokens" : 67,
|
||||
"prompt_tokens" : 17,
|
||||
"total_tokens" : 84
|
||||
}
|
||||
}
|
||||
""".formatted(chatChoices);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -225,7 +225,8 @@ public class Example57_KernelHooks {
|
||||
event.getArguments(),
|
||||
new FunctionResult<>(
|
||||
ContextVariable.of(result),
|
||||
event.getResult().getMetadata()));
|
||||
event.getResult().getMetadata(),
|
||||
result));
|
||||
};
|
||||
kernel.getGlobalKernelHooks().addHook(hook);
|
||||
|
||||
|
||||
+3
-2
@@ -2,6 +2,7 @@
|
||||
package com.microsoft.semantickernel;
|
||||
|
||||
import com.microsoft.semantickernel.contextvariables.CaseInsensitiveMap;
|
||||
import com.microsoft.semantickernel.exceptions.SKException;
|
||||
import com.microsoft.semantickernel.plugin.KernelPlugin;
|
||||
import com.microsoft.semantickernel.semanticfunctions.KernelFunction;
|
||||
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionMetadata;
|
||||
@@ -49,12 +50,12 @@ class KernelPluginCollection {
|
||||
KernelFunction<?> getFunction(String pluginName, String functionName) {
|
||||
KernelPlugin plugin = plugins.get(pluginName);
|
||||
if (plugin == null) {
|
||||
throw new IllegalArgumentException("Failed to find plugin " + pluginName);
|
||||
throw new SKException("Failed to find plugin " + pluginName);
|
||||
}
|
||||
KernelFunction<?> function = plugin.get(functionName);
|
||||
|
||||
if (function == null) {
|
||||
throw new IllegalArgumentException(
|
||||
throw new SKException(
|
||||
"Function '" + functionName + "' not found in plugin '" + pluginName + "'");
|
||||
}
|
||||
return function;
|
||||
|
||||
+2
-1
@@ -5,6 +5,7 @@ import static com.microsoft.semantickernel.contextvariables.ContextVariableTypes
|
||||
|
||||
import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter;
|
||||
import com.microsoft.semantickernel.contextvariables.ContextVariableTypes;
|
||||
import com.microsoft.semantickernel.exceptions.SKException;
|
||||
|
||||
/**
|
||||
* A {@link ContextVariableTypeConverter} for {@code java.lang.Character} variables. Use
|
||||
@@ -33,7 +34,7 @@ public class CharacterVariableContextVariableTypeConverter extends
|
||||
*/
|
||||
static Character charFromPromptString(String s) {
|
||||
if (s.length() != 1) {
|
||||
throw new IllegalArgumentException("Cannot convert string to character: " + s);
|
||||
throw new SKException("Cannot convert string to character: " + s);
|
||||
}
|
||||
return s.charAt(0);
|
||||
}
|
||||
|
||||
+1
-1
@@ -174,7 +174,7 @@ public final class CodeBlock extends Block implements CodeRendering {
|
||||
|
||||
// Check if the function has parameters to be set
|
||||
if (functionMetadata.getParameters().isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
throw new SKException(
|
||||
"Function " + fBlock.getPluginName() + "." + fBlock.getFunctionName()
|
||||
+ " does not take any arguments but it is being called in the template with {this._tokens.Count - 1} arguments.");
|
||||
}
|
||||
|
||||
+2
-1
@@ -117,7 +117,8 @@ public class FunctionInvocation<T> extends Mono<FunctionResult<T>> {
|
||||
try {
|
||||
sink.next(new FunctionResult<>(
|
||||
ContextVariable.convert(result.getResult(), variableType),
|
||||
result.getMetadata()));
|
||||
result.getMetadata(),
|
||||
result.getUnconvertedResult()));
|
||||
} catch (Exception e) {
|
||||
sink.error(new SKException(
|
||||
"Failed to convert result to requested type: "
|
||||
|
||||
+13
-3
@@ -17,6 +17,8 @@ public class FunctionResult<T> {
|
||||
|
||||
private final ContextVariable<T> result;
|
||||
private final FunctionResultMetadata metadata;
|
||||
@Nullable
|
||||
private final Object unconvertedResult;
|
||||
|
||||
/**
|
||||
* Create a new instance of FunctionResult.
|
||||
@@ -26,9 +28,11 @@ public class FunctionResult<T> {
|
||||
*/
|
||||
public FunctionResult(
|
||||
ContextVariable<T> result,
|
||||
@Nullable FunctionResultMetadata metadata) {
|
||||
@Nullable FunctionResultMetadata metadata,
|
||||
@Nullable Object unconvertedResult) {
|
||||
this.result = result;
|
||||
this.metadata = metadata == null ? FunctionResultMetadata.empty() : metadata;
|
||||
this.unconvertedResult = unconvertedResult;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,8 +40,9 @@ public class FunctionResult<T> {
|
||||
*
|
||||
* @param of The result of the function invocation.
|
||||
*/
|
||||
public FunctionResult(ContextVariable<T> of) {
|
||||
this(of, FunctionResultMetadata.empty());
|
||||
public FunctionResult(ContextVariable<T> of,
|
||||
@Nullable Object unconvertedResult) {
|
||||
this(of, FunctionResultMetadata.empty(), unconvertedResult);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,4 +76,9 @@ public class FunctionResult<T> {
|
||||
public FunctionResultMetadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Object getUnconvertedResult() {
|
||||
return unconvertedResult;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -1,16 +1,14 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.orchestration;
|
||||
|
||||
import com.microsoft.semantickernel.semanticfunctions.KernelFunction;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.microsoft.semantickernel.semanticfunctions.KernelFunction;
|
||||
|
||||
/**
|
||||
* Defines the behavior of a tool call. Currently, the only tool available is function calling.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -119,7 +119,7 @@ public class KernelFunctionArguments implements Map<String, ContextVariable<?>>
|
||||
return (ContextVariable<T>) value;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
throw new SKException(
|
||||
String.format(
|
||||
"Variable %s is of type %s, but requested type is %s",
|
||||
key, value.getType().getClazz(), clazz));
|
||||
|
||||
+5
-6
@@ -177,7 +177,7 @@ public class KernelFunctionFromMethod<T> extends KernelFunction<T> {
|
||||
mono = invokeAsyncFunction(method, instance, args);
|
||||
}
|
||||
|
||||
Mono<T> r = mono
|
||||
return mono
|
||||
.map(it -> {
|
||||
if (it instanceof Iterable) {
|
||||
// Handle return from things like Mono<List<?>>
|
||||
@@ -186,9 +186,7 @@ public class KernelFunctionFromMethod<T> extends KernelFunction<T> {
|
||||
} else {
|
||||
return (T) it;
|
||||
}
|
||||
});
|
||||
|
||||
return r
|
||||
})
|
||||
.map(it -> {
|
||||
// If given a variable type, use it.
|
||||
// If it's wrong, then it's a programming error on the part of the caller.
|
||||
@@ -200,7 +198,7 @@ public class KernelFunctionFromMethod<T> extends KernelFunction<T> {
|
||||
it.getClass().getName()));
|
||||
}
|
||||
return new FunctionResult<>(
|
||||
new ContextVariable<>(variableType, it));
|
||||
new ContextVariable<>(variableType, it), it);
|
||||
}
|
||||
|
||||
Class<?> returnParameterType = function
|
||||
@@ -221,7 +219,8 @@ public class KernelFunctionFromMethod<T> extends KernelFunction<T> {
|
||||
|
||||
if (contextVariableType != null) {
|
||||
return new FunctionResult<>(
|
||||
new ContextVariable<>(contextVariableType, it));
|
||||
new ContextVariable<>(contextVariableType, it),
|
||||
it);
|
||||
}
|
||||
|
||||
// If we get here, then either the returnParameterType doesn't match T
|
||||
|
||||
+6
-3
@@ -164,7 +164,8 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
|
||||
return Flux.just(
|
||||
new FunctionResult<>(
|
||||
new ContextVariable<>(variableType, value),
|
||||
chatMessageContent.getMetadata()));
|
||||
chatMessageContent.getMetadata(),
|
||||
chatMessageContent));
|
||||
}
|
||||
return Flux.empty();
|
||||
})
|
||||
@@ -175,7 +176,8 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
|
||||
it.getResult() != null
|
||||
? variableType.of(it.getResult()).getValue()
|
||||
: null),
|
||||
it.getMetadata());
|
||||
it.getMetadata(),
|
||||
it.getUnconvertedResult());
|
||||
});
|
||||
} else if (client instanceof TextGenerationService) {
|
||||
result = ((TextGenerationService) client)
|
||||
@@ -200,7 +202,8 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
|
||||
new ContextVariable<>(
|
||||
variableType,
|
||||
value),
|
||||
textContent.getMetadata()));
|
||||
textContent.getMetadata(),
|
||||
textContent));
|
||||
});
|
||||
} else {
|
||||
return Flux.error(new IllegalStateException("Unknown service type"));
|
||||
|
||||
Reference in New Issue
Block a user