diff --git a/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java index 1db5bd38..8256bb00 100644 --- a/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java +++ b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java @@ -38,6 +38,7 @@ import com.microsoft.semantickernel.aiservices.openai.OpenAiService; import com.microsoft.semantickernel.aiservices.openai.chatcompletion.responseformat.ChatCompletionsJsonSchemaResponseFormat; import com.microsoft.semantickernel.aiservices.openai.implementation.OpenAIRequestSettings; +import com.microsoft.semantickernel.contents.FunctionCallContent; import com.microsoft.semantickernel.contextvariables.ContextVariable; import com.microsoft.semantickernel.contextvariables.ContextVariableTypes; import com.microsoft.semantickernel.exceptions.AIException; @@ -468,7 +469,6 @@ private Mono internalChatMessageContentsAsync( .doOnTerminate(span::close); }) .flatMap(completions -> { - List responseMessages = completions .getChoices() .stream() @@ -488,6 +488,7 @@ private Mono internalChatMessageContentsAsync( completions); return Mono.just(messages.addChatMessage(chatMessageContents)); } + // Or if there are no tool calls to be done ChatResponseMessage response = responseMessages.get(0); List toolCalls = response.getToolCalls(); @@ -633,8 +634,8 @@ private Mono> invokeFunctionTool( ContextVariableTypes contextVariableTypes) { try { - OpenAIFunctionToolCall openAIFunctionToolCall = extractOpenAIFunctionToolCall(toolCall); - String pluginName = openAIFunctionToolCall.getPluginName(); + FunctionCallContent functionCallContent = extractFunctionCallContent(toolCall); + String pluginName = functionCallContent.getPluginName(); if (pluginName == null || pluginName.isEmpty()) { return Mono.error( new SKException("Plugin name is required for function tool call")); @@ -642,12 +643,12 @@ private Mono> invokeFunctionTool( KernelFunction function = kernel.getFunction( pluginName, - openAIFunctionToolCall.getFunctionName()); + functionCallContent.getFunctionName()); PreToolCallEvent hookResult = executeHook(invocationContext, kernel, new PreToolCallEvent( - openAIFunctionToolCall.getFunctionName(), - openAIFunctionToolCall.getArguments(), + functionCallContent.getFunctionName(), + functionCallContent.getArguments(), function, contextVariableTypes)); @@ -686,7 +687,7 @@ private static T executeHook( } @SuppressWarnings("StringSplitter") - private OpenAIFunctionToolCall extractOpenAIFunctionToolCall( + private FunctionCallContent extractFunctionCallContent( ChatCompletionsFunctionToolCall toolCall) throws JsonProcessingException { @@ -712,10 +713,10 @@ private OpenAIFunctionToolCall extractOpenAIFunctionToolCall( } }); - return new OpenAIFunctionToolCall( - toolCall.getId(), - pluginName, + return new FunctionCallContent( fnName, + pluginName, + toolCall.getId(), arguments); } @@ -744,7 +745,7 @@ private List> getChatMessageContentsAsync( null, null, completionMetadata, - formOpenAiToolCalls(response)); + formFunctionCallContents(response)); } catch (SKCheckedException e) { LOGGER.warn("Failed to form chat message content", e); return null; @@ -784,7 +785,7 @@ private List> toOpenAIChatMessageContent( null); } else if (message instanceof ChatRequestAssistantMessage) { try { - List calls = getToolCalls( + List calls = getFunctionCallContents( ((ChatRequestAssistantMessage) message).getToolCalls()); return new OpenAIChatMessageContent<>( AuthorRole.ASSISTANT, @@ -823,7 +824,7 @@ private List> toOpenAIChatMessageContent( } @Nullable - private List getToolCalls( + private List getFunctionCallContents( @Nullable List toolCalls) throws SKCheckedException { if (toolCalls == null || toolCalls.isEmpty()) { return null; @@ -835,7 +836,7 @@ private List getToolCalls( .map(call -> { if (call instanceof ChatCompletionsFunctionToolCall) { try { - return extractOpenAIFunctionToolCall( + return extractFunctionCallContent( (ChatCompletionsFunctionToolCall) call); } catch (JsonProcessingException e) { throw SKException.build("Failed to parse tool arguments", e); @@ -852,7 +853,7 @@ private List getToolCalls( } @Nullable - private List formOpenAiToolCalls( + private List formFunctionCallContents( ChatResponseMessage response) throws SKCheckedException { if (response.getToolCalls() == null || response.getToolCalls().isEmpty()) { return null; @@ -864,7 +865,7 @@ private List formOpenAiToolCalls( .map(call -> { if (call instanceof ChatCompletionsFunctionToolCall) { try { - return extractOpenAIFunctionToolCall( + return extractFunctionCallContent( (ChatCompletionsFunctionToolCall) call); } catch (JsonProcessingException e) { throw SKException.build("Failed to parse tool arguments", e); @@ -1251,10 +1252,7 @@ private static ChatRequestAssistantMessage formAssistantMessage( // TODO: handle tools other than function calls ChatRequestAssistantMessage asstMessage = new ChatRequestAssistantMessage(content); - List toolCalls = null; - if (message instanceof OpenAIChatMessageContent) { - toolCalls = ((OpenAIChatMessageContent) message).getToolCall(); - } + List toolCalls = FunctionCallContent.getFunctionCalls(message); if (toolCalls != null) { asstMessage.setToolCalls( diff --git a/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatMessageContent.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatMessageContent.java index 89f45014..ed1e2832 100644 --- a/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatMessageContent.java +++ b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatMessageContent.java @@ -1,12 +1,15 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.aiservices.openai.chatcompletion; +import com.microsoft.semantickernel.contents.FunctionCallContent; import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; +import com.microsoft.semantickernel.services.KernelContent; 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 java.util.stream.Collectors; import javax.annotation.Nullable; /** @@ -16,6 +19,7 @@ */ public class OpenAIChatMessageContent extends ChatMessageContent { + @Deprecated @Nullable private final List toolCall; @@ -28,7 +32,7 @@ public class OpenAIChatMessageContent extends ChatMessageContent { * @param innerContent The inner content. * @param encoding The encoding. * @param metadata The metadata. - * @param toolCall The tool call. + * @param functionCalls The tool call. */ public OpenAIChatMessageContent( AuthorRole authorRole, @@ -37,13 +41,25 @@ public OpenAIChatMessageContent( @Nullable T innerContent, @Nullable Charset encoding, @Nullable FunctionResultMetadata metadata, - @Nullable List toolCall) { - super(authorRole, content, modelId, innerContent, encoding, metadata); + @Nullable List functionCalls) { + super(authorRole, content, (List>) functionCalls, modelId, + innerContent, encoding, metadata); - if (toolCall == null) { + if (functionCalls == null) { this.toolCall = null; } else { - this.toolCall = Collections.unmodifiableList(toolCall); + // Keep OpenAIFunctionToolCall list for legacy + this.toolCall = Collections.unmodifiableList(functionCalls.stream().map(t -> { + if (t instanceof OpenAIFunctionToolCall) { + return (OpenAIFunctionToolCall) t; + } else { + return new OpenAIFunctionToolCall( + t.getId(), + t.getPluginName(), + t.getFunctionName(), + t.getArguments()); + } + }).collect(Collectors.toList())); } } @@ -51,7 +67,10 @@ public OpenAIChatMessageContent( * Gets any tool calls requested. * * @return The tool call. + * + * @deprecated Use {@link FunctionCallContent#getFunctionCalls(ChatMessageContent)} instead. */ + @Deprecated @Nullable public List getToolCall() { return toolCall; diff --git a/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIFunctionToolCall.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIFunctionToolCall.java index b7999cf0..c1def337 100644 --- a/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIFunctionToolCall.java +++ b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIFunctionToolCall.java @@ -1,29 +1,17 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.aiservices.openai.chatcompletion; +import com.microsoft.semantickernel.contents.FunctionCallContent; import com.microsoft.semantickernel.semanticfunctions.KernelArguments; import javax.annotation.Nullable; /** * Represents a call to a function in the OpenAI tool. + * + * @deprecated Use {@link FunctionCallContent} instead. */ -public class OpenAIFunctionToolCall { - - /// Gets the ID of the tool call. - @Nullable - private final String id; - - /// Gets the name of the plugin with which this function is associated, if any. - - @Nullable - private final String pluginName; - - /// Gets the name of the function. - private final String functionName; - - /// Gets a name/value collection of the arguments to the function, if any. - @Nullable - private final KernelArguments arguments; +@Deprecated +public class OpenAIFunctionToolCall extends FunctionCallContent { /** * Creates a new instance of the {@link OpenAIFunctionToolCall} class. @@ -38,55 +26,6 @@ public OpenAIFunctionToolCall( @Nullable String pluginName, String functionName, @Nullable KernelArguments arguments) { - this.id = id; - this.pluginName = pluginName; - this.functionName = functionName; - if (arguments == null) { - this.arguments = null; - } else { - this.arguments = arguments.copy(); - } - } - - /** - * Gets the ID of the tool call. - * - * @return The ID of the tool call. - */ - @Nullable - public String getId() { - return id; - } - - /** - * Gets the name of the plugin with which this function is associated, if any. - * - * @return The name of the plugin with which this function is associated, if any. - */ - @Nullable - public String getPluginName() { - return pluginName; - } - - /** - * Gets the name of the function. - * - * @return The name of the function. - */ - public String getFunctionName() { - return functionName; - } - - /** - * Gets a name/value collection of the arguments to the function, if any. - * - * @return A name/value collection of the arguments to the function, if any. - */ - @Nullable - public KernelArguments getArguments() { - if (arguments == null) { - return null; - } - return arguments.copy(); + super(functionName, pluginName, id, arguments); } } diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/contents/FunctionCallContent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contents/FunctionCallContent.java new file mode 100644 index 00000000..8c973ab0 --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contents/FunctionCallContent.java @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.contents; + +import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; +import com.microsoft.semantickernel.semanticfunctions.KernelArguments; +import com.microsoft.semantickernel.services.KernelContentImpl; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Represents the content of a function call. + *

+ * This class is used to represent a function call in the context of a chat message. + */ +public class FunctionCallContent extends KernelContentImpl { + + @Nullable + private final String id; + @Nullable + private final String pluginName; + private final String functionName; + @Nullable + private final KernelArguments arguments; + + /** + * Creates a new instance of the {@link FunctionCallContent} class. + * + * @param functionName The name of the function. + * @param pluginName The name of the plugin with which this function is associated, if any. + * @param id The ID of the tool call. + * @param arguments A name/value collection of the arguments to the function, if any. + */ + public FunctionCallContent( + String functionName, + @Nullable String pluginName, + @Nullable String id, + @Nullable KernelArguments arguments) { + this.functionName = functionName; + this.pluginName = pluginName; + this.id = id; + if (arguments == null) { + this.arguments = null; + } else { + this.arguments = arguments.copy(); + } + } + + /** + * Gets the ID of the tool call. + * + * @return The ID of the tool call. + */ + @Nullable + public String getId() { + return id; + } + + /** + * Gets the name of the plugin with which this function is associated, if any. + * + * @return The name of the plugin with which this function is associated, if any. + */ + @Nullable + public String getPluginName() { + return pluginName; + } + + /** + * Gets the name of the function. + * + * @return The name of the function. + */ + public String getFunctionName() { + return functionName; + } + + /** + * Gets a name/value collection of the arguments to the function, if any. + * + * @return A name/value collection of the arguments to the function, if any. + */ + @Nullable + public KernelArguments getArguments() { + if (arguments == null) { + return null; + } + return arguments.copy(); + } + + /** + * Gets list of function calls from the message content. + * + * @param messageContent The message content. + * @return The function calls. + */ + public static List getFunctionCalls(ChatMessageContent messageContent) { + if (messageContent.getItems() == null) { + return null; + } + + return messageContent.getItems().stream().filter( + item -> item instanceof FunctionCallContent) + .map(item -> (FunctionCallContent) item) + .collect(Collectors.toList()); + } + + /** + * Gets the content returned by the AI service. + * + * @return The content. + */ + @Nullable + @Override + public String getContent() { + return null; + } +} diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/functionchoice/FunctionChoiceBehavior.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/functionchoice/FunctionChoiceBehavior.java index f0024952..d74a77a7 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/functionchoice/FunctionChoiceBehavior.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/functionchoice/FunctionChoiceBehavior.java @@ -78,7 +78,7 @@ public static FunctionChoiceBehavior auto(boolean autoInvoke) { * @return A new FunctionChoiceBehavior instance with all kernel functions allowed. */ public static FunctionChoiceBehavior auto(boolean autoInvoke, - @Nullable List> functions) { + @Nullable List> functions) { return new AutoFunctionChoiceBehavior(autoInvoke, functions, null); } @@ -95,8 +95,8 @@ public static FunctionChoiceBehavior auto(boolean autoInvoke, * @return A new FunctionChoiceBehavior instance with all kernel functions allowed. */ public static FunctionChoiceBehavior auto(boolean autoInvoke, - @Nullable List> functions, - @Nullable FunctionChoiceBehaviorOptions options) { + @Nullable List> functions, + @Nullable FunctionChoiceBehaviorOptions options) { return new AutoFunctionChoiceBehavior(autoInvoke, functions, options); } @@ -110,7 +110,7 @@ public static FunctionChoiceBehavior auto(boolean autoInvoke, * @return A new FunctionChoiceBehavior instance with the required function. */ public static FunctionChoiceBehavior required(boolean autoInvoke, - @Nullable List> functions) { + @Nullable List> functions) { return new RequiredFunctionChoiceBehavior(autoInvoke, functions, null); } @@ -126,8 +126,8 @@ public static FunctionChoiceBehavior required(boolean autoInvoke, * @return A new FunctionChoiceBehavior instance with the required function. */ public static FunctionChoiceBehavior required(boolean autoInvoke, - @Nullable List> functions, - @Nullable FunctionChoiceBehaviorOptions options) { + @Nullable List> functions, + @Nullable FunctionChoiceBehaviorOptions options) { return new RequiredFunctionChoiceBehavior(autoInvoke, functions, options); } diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/functionchoice/NoneFunctionChoiceBehavior.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/functionchoice/NoneFunctionChoiceBehavior.java index f0d247d3..1842ba5f 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/functionchoice/NoneFunctionChoiceBehavior.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/functionchoice/NoneFunctionChoiceBehavior.java @@ -12,7 +12,7 @@ public class NoneFunctionChoiceBehavior extends FunctionChoiceBehavior { * Create a new instance of NoneFunctionChoiceBehavior. */ public NoneFunctionChoiceBehavior(@Nullable List> functions, - @Nullable FunctionChoiceBehaviorOptions options) { + @Nullable FunctionChoiceBehaviorOptions options) { super(functions, options); } } diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/functionchoice/RequiredFunctionChoiceBehavior.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/functionchoice/RequiredFunctionChoiceBehavior.java index ac1ae2bf..8bfee535 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/functionchoice/RequiredFunctionChoiceBehavior.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/functionchoice/RequiredFunctionChoiceBehavior.java @@ -16,8 +16,8 @@ public class RequiredFunctionChoiceBehavior extends AutoFunctionChoiceBehavior { * @param options Options for the function choice behavior. */ public RequiredFunctionChoiceBehavior(boolean autoInvoke, - @Nullable List> functions, - @Nullable FunctionChoiceBehaviorOptions options) { + @Nullable List> functions, + @Nullable FunctionChoiceBehaviorOptions options) { super(autoInvoke, functions, options); } } diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/KernelContentImpl.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/KernelContentImpl.java index 48bc6d77..e83e909d 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/KernelContentImpl.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/KernelContentImpl.java @@ -47,6 +47,13 @@ public KernelContentImpl( this.metadata = metadata; } + /** + * Initializes a new instance of the {@link KernelContentImpl} class. + */ + public KernelContentImpl() { + this(null, null, null); + } + /** * Gets the inner content representation. * diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java index e06472d1..2d4ce491 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java @@ -135,7 +135,7 @@ public ChatMessageContent( */ public ChatMessageContent( AuthorRole authorRole, - @Nullable List> items, + @Nullable List> items, String modelId, T innerContent, Charset encoding, @@ -153,6 +153,36 @@ public ChatMessageContent( this.contentType = contentType; } + /** + * Creates a new instance of the {@link ChatMessageContent} class. + * + * @param authorRole the author role that generated the content + * @param items the items + * @param modelId the model id + * @param innerContent the inner content + * @param encoding the encoding + * @param metadata the metadata + */ + public ChatMessageContent( + AuthorRole authorRole, + @Nullable String content, + @Nullable List> items, + String modelId, + T innerContent, + Charset encoding, + FunctionResultMetadata metadata) { + super(innerContent, modelId, metadata); + this.content = content; + this.authorRole = authorRole; + this.encoding = encoding != null ? encoding : StandardCharsets.UTF_8; + if (items == null) { + this.items = null; + } else { + this.items = new ArrayList<>(items); + } + this.contentType = ChatMessageContentType.TEXT; + } + /** * Gets the author role that generated the content *