Java: Fix linting issues (#4800)

Fix linting issues
This commit is contained in:
John Oliver
2024-01-31 15:51:34 -05:00
committed by GitHub
parent 447eed55c4
commit 11b2421651
79 changed files with 1315 additions and 601 deletions
@@ -26,6 +26,7 @@ import com.microsoft.semantickernel.chatcompletion.ChatHistory;
import com.microsoft.semantickernel.chatcompletion.ChatMessageContent;
import com.microsoft.semantickernel.chatcompletion.StreamingChatMessageContent;
import com.microsoft.semantickernel.exceptions.AIException;
import com.microsoft.semantickernel.exceptions.SKException;
import com.microsoft.semantickernel.hooks.KernelHooks;
import com.microsoft.semantickernel.hooks.PreChatCompletionEvent;
import com.microsoft.semantickernel.orchestration.FunctionResult;
@@ -56,9 +57,15 @@ public class OpenAIChatCompletion implements ChatCompletionService {
private static final Logger LOGGER = LoggerFactory.getLogger(OpenAIChatCompletion.class);
private final OpenAIAsyncClient client;
private final Map<String, ContextVariable<?>> attributes;
@Nullable
private final String serviceId;
public OpenAIChatCompletion(OpenAIAsyncClient client, String modelId, String serviceId) {
public OpenAIChatCompletion(
OpenAIAsyncClient client,
String modelId,
@Nullable
String serviceId) {
this.serviceId = serviceId;
this.client = client;
this.attributes = new HashMap<>();
@@ -75,6 +82,7 @@ public class OpenAIChatCompletion implements ChatCompletionService {
}
@Override
@Nullable
public String getServiceId() {
return serviceId;
}
@@ -82,8 +90,11 @@ public class OpenAIChatCompletion implements ChatCompletionService {
@Override
public Mono<List<ChatMessageContent>> getChatMessageContentsAsync(
ChatHistory chatHistory,
@Nullable
PromptExecutionSettings promptExecutionSettings,
@Nullable
Kernel kernel,
@Nullable
KernelHooks kernelHooks) {
List<ChatRequestMessage> chatRequestMessages = getChatRequestMessages(chatHistory);
@@ -112,6 +123,7 @@ public class OpenAIChatCompletion implements ChatCompletionService {
@Override
public Mono<List<ChatMessageContent>> getChatMessageContentsAsync(
String prompt,
@Nullable
PromptExecutionSettings promptExecutionSettings,
Kernel kernel,
@Nullable
@@ -138,7 +150,9 @@ public class OpenAIChatCompletion implements ChatCompletionService {
private Mono<List<ChatMessageContent>> internalChatMessageContentsAsync(
List<ChatRequestMessage> chatRequestMessages,
@Nullable
List<FunctionDefinition> functions,
@Nullable
PromptExecutionSettings promptExecutionSettings,
KernelHooks kernelHooks) {
@@ -242,7 +256,8 @@ public class OpenAIChatCompletion implements ChatCompletionService {
* {"type":"function", "function": {"name":"search-search", "parameters": {"query":"Banksy"}}}
* where 'name' is <plugin name '-' function name>.
*/
private Mono<StreamingChatMessageContent> invokeTool(Kernel kernel, String json) {
@SuppressWarnings("UnusedMethod")
private Mono<StreamingChatMessageContent<?>> invokeTool(Kernel kernel, String json) {
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(json);
@@ -254,8 +269,10 @@ public class OpenAIChatCompletion implements ChatCompletionService {
if (result != null) {
return result.map(contextVariable -> {
String content = contextVariable.getResult();
return new StreamingChatMessageContent(AuthorRole.TOOL, content).setModelId(
id);
if (content == null) {
throw new SKException("Function result must not be null");
}
return new StreamingChatMessageContent<>(AuthorRole.TOOL, content, id);
});
}
}
@@ -268,6 +285,7 @@ public class OpenAIChatCompletion implements ChatCompletionService {
/*
* The jsonNode should represent: {"name":"search-search", "parameters": {"query":"Banksy"}}}
*/
@SuppressWarnings("StringSplitter")
private Mono<FunctionResult<String>> invokeFunction(Kernel kernel, JsonNode jsonNode) {
String name = jsonNode.get("name").asText();
String[] parts = name.split("-");
@@ -298,23 +316,28 @@ public class OpenAIChatCompletion implements ChatCompletionService {
private static ChatCompletionsOptions getCompletionsOptions(
ChatCompletionService chatCompletionService,
List<ChatRequestMessage> chatRequestMessages,
@Nullable
List<FunctionDefinition> functions,
@Nullable
PromptExecutionSettings promptExecutionSettings) {
ChatCompletionsOptions options = new ChatCompletionsOptions(chatRequestMessages)
.setModel(chatCompletionService.getModelId());
if (promptExecutionSettings == null) {
return options;
}
if (promptExecutionSettings.getResultsPerPrompt() < 1
|| promptExecutionSettings.getResultsPerPrompt() > MAX_RESULTS_PER_PROMPT) {
throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, String.format("Results per prompt must be in range between 1 and %d, inclusive.", MAX_RESULTS_PER_PROMPT));
|| promptExecutionSettings.getResultsPerPrompt() > MAX_RESULTS_PER_PROMPT) {
throw new AIException(AIException.ErrorCodes.INVALID_REQUEST,
String.format("Results per prompt must be in range between 1 and %d, inclusive.",
MAX_RESULTS_PER_PROMPT));
}
List<ChatCompletionsToolDefinition> toolDefinitions =
chatCompletionsToolDefinitions(promptExecutionSettings.getToolCallBehavior(), functions);
List<ChatCompletionsToolDefinition> toolDefinitions =
chatCompletionsToolDefinitions(promptExecutionSettings.getToolCallBehavior(),
functions);
if (toolDefinitions != null && !toolDefinitions.isEmpty()) {
options.setTools(toolDefinitions);
// TODO: options.setToolChoices(toolChoices);
@@ -351,18 +374,22 @@ public class OpenAIChatCompletion implements ChatCompletionService {
return options;
}
@SuppressWarnings("StringSplitter")
private static List<ChatCompletionsToolDefinition> chatCompletionsToolDefinitions(
@Nullable
ToolCallBehavior toolCallBehavior,
@Nullable
List<FunctionDefinition> functions) {
if (functions == null || functions.isEmpty()) {
return Collections.emptyList();
}
if (toolCallBehavior == null || !(toolCallBehavior.kernelFunctionsEnabled() || toolCallBehavior.autoInvokeEnabled())) {
if (toolCallBehavior == null || !(toolCallBehavior.kernelFunctionsEnabled()
|| toolCallBehavior.autoInvokeEnabled())) {
return Collections.emptyList();
}
return functions.stream()
.filter(function -> {
String[] parts = function.getName().split("-");
@@ -384,6 +411,9 @@ public class OpenAIChatCompletion implements ChatCompletionService {
.map(message -> {
AuthorRole authorRole = message.getAuthorRole();
String content = message.getContent();
if (content == null) {
throw new SKException("ChatMessageContent content must not be null");
}
return getChatRequestMessage(authorRole, content);
})
.collect(Collectors.toList());
@@ -405,7 +435,7 @@ public class OpenAIChatCompletion implements ChatCompletionService {
return new ChatRequestToolMessage(content, null);
default:
LOGGER.debug("Unexpected author role: " + authorRole);
return null;
throw new SKException("Unexpected author role: " + authorRole);
}
}
@@ -459,7 +489,10 @@ public class OpenAIChatCompletion implements ChatCompletionService {
private static class ToolContentBuffer implements
ContentBuffer<ChatCompletionsFunctionToolCall> {
@Nullable
private String id = null;
@Nullable
private String name = null;
private List<String> arguments = new ArrayList<>();
@@ -575,6 +608,17 @@ public class OpenAIChatCompletion implements ChatCompletionService {
@Override
public OpenAIChatCompletion build() {
if (this.client == null) {
throw new AIException(AIException.ErrorCodes.INVALID_REQUEST,
"OpenAI client must be provided");
}
if (this.modelId == null || modelId.isEmpty()) {
throw new AIException(AIException.ErrorCodes.INVALID_REQUEST,
"OpenAI model id must be provided");
}
return new OpenAIChatCompletion(client, modelId, serviceId);
}
}
@@ -2,7 +2,9 @@ package com.microsoft.semantickernel.aiservices.openai.chatcompletion;
import com.azure.ai.openai.models.ChatRequestMessage;
import com.azure.ai.openai.models.FunctionDefinition;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
class ParsedPrompt {
@@ -10,8 +12,12 @@ class ParsedPrompt {
private final List<FunctionDefinition> functions;
protected ParsedPrompt(List<ChatRequestMessage> parsedMessages,
@Nullable
List<FunctionDefinition> parsedFunctions) {
this.chatRequestMessages = parsedMessages;
if (parsedFunctions == null) {
parsedFunctions = new ArrayList<>();
}
this.functions = parsedFunctions;
}
@@ -144,7 +144,9 @@ class XMLPromptParser {
// },
// },
//}
assert functionDefinition != null;
if (functionDefinition == null) {
throw new SKException("Failed to parse function definition");
}
if (!parameters.isEmpty()) {
StringBuilder sb = new StringBuilder(
"{\"type\": \"object\", \"properties\": {");
@@ -207,11 +209,11 @@ class XMLPromptParser {
String role,
String content) {
try {
AuthorRole authorRole = AuthorRole.valueOf(role.toUpperCase());
AuthorRole authorRole = AuthorRole.valueOf(role.toUpperCase(Locale.ROOT));
return OpenAIChatCompletion.getChatRequestMessage(authorRole, content);
} catch (IllegalArgumentException e) {
LOGGER.debug("Unknown author role: " + role);
return null;
throw new SKException("Unknown author role: " + role);
}
}
}
@@ -25,6 +25,7 @@ public class OpenAITextGenerationService implements TextGenerationService {
private final OpenAIAsyncClient client;
private final Map<String, ContextVariable<?>> attributes;
@Nullable
private final String serviceId;
/// <summary>
@@ -38,7 +39,9 @@ public class OpenAITextGenerationService implements TextGenerationService {
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> to use for logging. If null, no logging will be performed.</param>
public OpenAITextGenerationService(
OpenAIAsyncClient client,
String modelId, String serviceId) {
String modelId,
@Nullable
String serviceId) {
this.serviceId = serviceId;
this.client = client;
attributes = new HashMap<>();
@@ -55,13 +58,16 @@ public class OpenAITextGenerationService implements TextGenerationService {
}
@Override
@Nullable
public String getServiceId() {
return serviceId;
}
@Override
public Mono<List<TextContent>> getTextContentsAsync(String prompt,
@Nullable PromptExecutionSettings executionSettings, @Nullable Kernel kernel) {
public Mono<List<TextContent>> getTextContentsAsync(
String prompt,
@Nullable PromptExecutionSettings executionSettings,
@Nullable Kernel kernel) {
return this.internalCompleteTextAsync(prompt, executionSettings);
}
@@ -78,6 +84,7 @@ public class OpenAITextGenerationService implements TextGenerationService {
protected Mono<List<TextContent>> internalCompleteTextAsync(
String text,
@Nullable
PromptExecutionSettings requestSettings) {
CompletionsOptions completionsOptions = getCompletionsOptions(text, requestSettings);
@@ -118,7 +125,9 @@ public class OpenAITextGenerationService implements TextGenerationService {
}
private CompletionsOptions getCompletionsOptions(
String text, PromptExecutionSettings requestSettings) {
String text,
@Nullable
PromptExecutionSettings requestSettings) {
if (requestSettings == null) {
return new CompletionsOptions(Collections.singletonList(text))
.setMaxTokens(PromptExecutionSettings.DEFAULT_MAX_TOKENS);
@@ -127,8 +136,10 @@ public class OpenAITextGenerationService implements TextGenerationService {
throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, "Max tokens must be >0");
}
if (requestSettings.getResultsPerPrompt() < 1
|| requestSettings.getResultsPerPrompt() > MAX_RESULTS_PER_PROMPT) {
throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, String.format("Results per prompt must be in range between 1 and %d, inclusive.", MAX_RESULTS_PER_PROMPT));
|| requestSettings.getResultsPerPrompt() > MAX_RESULTS_PER_PROMPT) {
throw new AIException(AIException.ErrorCodes.INVALID_REQUEST,
String.format("Results per prompt must be in range between 1 and %d, inclusive.",
MAX_RESULTS_PER_PROMPT));
}
CompletionsOptions options =
@@ -143,12 +154,6 @@ public class OpenAITextGenerationService implements TextGenerationService {
.setUser(requestSettings.getUser())
.setBestOf(requestSettings.getBestOf())
.setLogitBias(new HashMap<>());
/*
if (requestSettings instanceof ChatRequestSettings) {
options = options.setStop(requestSettings.getStopSequences());
}
*/
return options;
}
@@ -157,7 +162,18 @@ public class OpenAITextGenerationService implements TextGenerationService {
*/
public static class Builder extends TextGenerationService.Builder {
@Override
public TextGenerationService build() {
if (this.client == null) {
throw new AIException(AIException.ErrorCodes.INVALID_REQUEST,
"OpenAI client must be provided");
}
if (this.modelId == null) {
throw new AIException(AIException.ErrorCodes.INVALID_REQUEST,
"OpenAI model id must be provided");
}
return new OpenAITextGenerationService(
this.client,
this.modelId,
@@ -6,7 +6,7 @@ import com.microsoft.semantickernel.orchestration.FunctionResult;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.plugin.KernelPlugin;
import com.microsoft.semantickernel.plugin.KernelPluginFactory;
import com.microsoft.semantickernel.samples.syntaxexamples.Example03_Arguments.StaticTextSkill;
import com.microsoft.semantickernel.samples.syntaxexamples.Example03_Arguments.StaticTextPlugin;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -22,7 +22,7 @@ public class Example03_ArgumentsTest {
// Load native skill
KernelPlugin functionCollection =
KernelPluginFactory.createFromObject(new StaticTextSkill(), "text");
KernelPluginFactory.createFromObject(new StaticTextPlugin(), "text");
KernelArguments arguments = KernelArguments.builder()
.withInput("Today is: ")
@@ -29,7 +29,7 @@ public class Example05_InlineFunctionDefinitionTest {
.endpoint("http://localhost:" + wmRuntimeInfo.getHttpPort())
.buildAsyncClient();
TextGenerationService textGenerationService = OpenAITextGenerationService.builder()
TextGenerationService textGenerationService = TextGenerationService.builder()
.withOpenAIAsyncClient(client)
.withModelId("text-davinci-003")
.build();
@@ -55,6 +55,7 @@ public class Example05_InlineFunctionDefinitionTest {
""".stripIndent();
var excuseFunction = new KernelFunctionFromPrompt.Builder()
.withName("Excuse")
.withTemplate(promptTemplate)
.withDefaultExecutionSettings(
new PromptExecutionSettings.Builder()
+4
View File
@@ -126,6 +126,10 @@
<target>${maven.compiler.release}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<showWarnings>true</showWarnings>
<compilerArgs>
<arg></arg>
<compilerArg></compilerArg>
</compilerArgs>
</configuration>
</plugin>
+24
View File
@@ -75,6 +75,30 @@
</dependency>
</dependencies>
<profiles>
<profile>
<id>bug-check</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<build>
<plugins>
<!-- Ignore linting on samples -->
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>${maven.spotbugs-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<plugins>
<plugin>
@@ -69,7 +69,8 @@ public class ConversationSummaryPlugin {
/// <param name="kernel">The <see cref="Kernel"/> containing services, plugins, and other state for use throughout the operation.</param>
@DefineKernelFunction(
description = "Given a long conversation transcript, summarize the conversation.",
name = "SummarizeConversation"
name = "SummarizeConversation",
returnType = "java.lang.String"
)
public Mono<String> SummarizeConversationAsync(
@KernelFunctionParameter(
@@ -88,7 +89,8 @@ public class ConversationSummaryPlugin {
/// <param name="kernel">The <see cref="Kernel"/> containing services, plugins, and other state for use throughout the operation.</param>
@DefineKernelFunction(
description = "Given a long conversation transcript, identify action items.",
name = "GetConversationActionItems"
name = "GetConversationActionItems",
returnType = "java.lang.String"
)
public Mono<String> GetConversationActionItemsAsync(
@KernelFunctionParameter(
@@ -107,7 +109,8 @@ public class ConversationSummaryPlugin {
/// <param name="kernel">The <see cref="Kernel"/> containing services, plugins, and other state for use throughout the operation.</param>
@DefineKernelFunction(
description = "Given a long conversation transcript, identify topics worth remembering.",
name = "GetConversationTopics"
name = "GetConversationTopics",
returnType = "java.lang.String"
)
public Mono<String> GetConversationTopicsAsync(
@@ -127,14 +130,15 @@ public class ConversationSummaryPlugin {
return Flux.fromIterable(paragraphs)
.concatMap(paragraph -> {
// The first parameter is the input text.
return func.invokeAsync(kernel,
new DefaultKernelArguments.Builder()
.withInput(paragraph)
.build(),
ContextVariableTypes.getDefaultVariableTypeForClass(String.class));
return func
.invokeAsync(kernel,
new DefaultKernelArguments.Builder()
.withInput(paragraph)
.build(),
ContextVariableTypes.getDefaultVariableTypeForClass(String.class));
})
.reduce("", (acc, next) -> {
return acc + "\n" + next.getResultVariable();
return acc + "\n" + next.getResult();
});
}
}
@@ -17,10 +17,12 @@ import reactor.core.publisher.Mono;
*/
public class Example03_Arguments {
public static class StaticTextSkill {
public static class StaticTextPlugin {
@DefineKernelFunction(
description = "Change all string chars to uppercase.",
name = "Uppercase")
name = "Uppercase",
returnType = "java.lang.String")
public static Mono<String> uppercase(
@KernelFunctionParameter(
description = "Text to uppercase",
@@ -29,7 +31,10 @@ public class Example03_Arguments {
return Mono.just(text.toUpperCase(Locale.ROOT));
}
@DefineKernelFunction(description = "Append the day variable", name = "appendDay")
@DefineKernelFunction(
description = "Append the day variable",
name = "appendDay",
returnType = "java.lang.String")
public Mono<String> appendDay(
@KernelFunctionParameter(description = "Text to append to", name = "input")
String input,
@@ -43,7 +48,7 @@ public class Example03_Arguments {
// Load native skill
KernelPlugin functionCollection =
KernelPluginFactory.createFromObject(new StaticTextSkill(), "text");
KernelPluginFactory.createFromObject(new StaticTextPlugin(), "text");
KernelArguments arguments = KernelArguments.builder()
.withInput("Today is: ")
@@ -57,6 +62,6 @@ public class Example03_Arguments {
String.class)
.block();
System.out.println(resultValue.getResultVariable());
System.out.println(resultValue.getResult());
}
}
@@ -85,7 +85,7 @@ public class Example05_InlineFunctionDefinition {
.build(),
String.class)
.block();
System.out.println(result.getResultVariable());
System.out.println(result.getResult());
result = kernel.invokeAsync(excuseFunction,
KernelArguments.builder()
@@ -93,7 +93,7 @@ public class Example05_InlineFunctionDefinition {
.build(),
String.class)
.block();
System.out.println(result.getResultVariable());
System.out.println(result.getResult());
var fixedFunction = KernelFunctionFactory.createFromPrompt(
"Translate this date " + DateTimeFormatter
@@ -112,7 +112,7 @@ public class Example05_InlineFunctionDefinition {
FunctionResult<String> fixedFunctionResult = kernel
.invokeAsync(fixedFunction, null, String.class)
.block();
System.out.println(fixedFunctionResult.getResultVariable());
System.out.println(fixedFunctionResult.getResult());
}
}
@@ -105,6 +105,6 @@ public class Example06_TemplateLanguage {
// Show the result
System.out.println("--- Prompt Function result");
var result = kernel.invokeAsync(kindOfDay, null, String.class).block();
System.out.println(result.getResultVariable());
System.out.println(result.getResult());
}
}
@@ -8,6 +8,7 @@ import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.credential.KeyCredential;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.aiservices.openai.textcompletion.OpenAITextGenerationService;
import com.microsoft.semantickernel.orchestration.FunctionResult;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter.NoopConverter;
@@ -18,7 +19,8 @@ import com.microsoft.semantickernel.plugin.annotations.DefineKernelFunction;
import com.microsoft.semantickernel.plugin.annotations.KernelFunctionParameter;
import com.microsoft.semantickernel.textcompletion.TextGenerationService;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
@@ -115,10 +117,14 @@ public class Example09_FunctionTypes {
.build();
// Different ways to invoke a function (not limited to these examples)
kernel.invokeAsync(plugin.get("NoInputWithVoidResult"), null, String.class).block();
kernel.invokeAsync(plugin.get("NoInputTaskWithVoidResult"), null, String.class).block();
FunctionResult<?> result = kernel.invokeAsync(plugin.get("NoInputWithVoidResult"), null,
String.class).block();
System.out.println(result.getResult());
result = kernel.invokeAsync(plugin.get("NoInputTaskWithVoidResult"), null, String.class)
.block();
System.out.println(result.getResult());
kernel
result = kernel
.invokeAsync(
plugin.get("InputDateTimeWithStringResult"),
KernelArguments
@@ -132,10 +138,13 @@ public class Example09_FunctionTypes {
.build(),
String.class
).block();
System.out.println(result.getResult());
kernel.invokeAsync(plugin.get("NoInputTaskWithStringResult"), null, String.class).block();
result = kernel.invokeAsync(plugin.get("NoInputTaskWithStringResult"), null, String.class)
.block();
System.out.println(result.getResult());
kernel.invokeAsync(plugin.get("MultipleInputsWithVoidResult"),
result = kernel.invokeAsync(plugin.get("MultipleInputsWithVoidResult"),
KernelArguments
.builder()
.withVariable("x", "x string")
@@ -144,8 +153,9 @@ public class Example09_FunctionTypes {
.build(),
String.class)
.block();
System.out.println(result.getResult());
kernel
result = kernel
.invokeAsync(plugin.get("ComplexInputWithStringResult"),
KernelArguments
.builder()
@@ -164,8 +174,9 @@ public class Example09_FunctionTypes {
.build(),
String.class)
.block();
System.out.println(result.getResult());
kernel
result = kernel
.invokeAsync(plugin.get("InputStringTaskWithStringResult"),
KernelArguments
.builder()
@@ -173,8 +184,9 @@ public class Example09_FunctionTypes {
.build(),
String.class)
.block();
System.out.println(result.getResult());
kernel
result = kernel
.invokeAsync(plugin.get("InputStringTaskWithVoidResult"),
KernelArguments
.builder()
@@ -182,24 +194,80 @@ public class Example09_FunctionTypes {
.build(),
Void.class)
.block();
System.out.println(result.getResult());
kernel
result = kernel
.invokeAsync(plugin.get("noInputComplexReturnTypeAsync"),
null,
Temporal.class)
.block();
System.out.println(result.getResult());
kernel
result = kernel
.invokeAsync(plugin.get("noInputComplexReturnType"),
null,
Temporal.class)
.block();
System.out.println(result.getResult());
kernel
// Possibilities for return type combinations:
// | Method return type | Declared Function Return Type | Invocation Return type |
// |--------------------|-------------------------------|------------------------|
// | T | T | T |
// | T | T | V converted from T |
// | T | U extends T | T |
// | T | U extends T | U |
// | T | U extends T | V converted from U |
result = kernel
.invokeAsync(plugin.get("conversionScenarioA"),
null,
OffsetDateTime.class)
.block();
System.out.println(result.getResult());
result = kernel
.invokeAsync(plugin.get("conversionScenarioA"),
null,
Instant.class)
.block();
System.out.println(result.getResult());
result = kernel
.invokeAsync(plugin.get("conversionScenarioB"),
null,
OffsetDateTime.class)
.block();
System.out.println(result.getResult());
result = kernel
.invokeAsync(plugin.get("conversionScenarioB"),
null,
Temporal.class)
.block();
System.out.println(result.getResult());
result = kernel
.invokeAsync(plugin.get("conversionScenarioB"),
null,
Instant.class)
.block();
System.out.println(result.getResult());
result = kernel
.invokeAsync(plugin.get("noInputComplexReturnType"),
null,
String.class)
.block();
System.out.println(result.getResult());
result = kernel
.invokeAsync(plugin.get("withDefaultValue"),
null,
String.class)
.block();
System.out.println(result.getResult());
/*
TODO: support FunctionResult
@@ -254,7 +322,10 @@ public class Example09_FunctionTypes {
/// <summary>
/// Example of using a void task function with no input
/// </summary>
@DefineKernelFunction(name = "NoInputTaskWithVoidResult")
@DefineKernelFunction(
name = "NoInputTaskWithVoidResult",
returnType = "java.lang.Void"
)
public Mono<Void> NoInputTaskWithVoidResult() {
return Mono.fromRunnable(
() -> System.out.println("Running this.NoInputTaskWithVoidResult) -> No input"));
@@ -263,7 +334,9 @@ public class Example09_FunctionTypes {
/// <summary>
/// Example of using a function with a DateTime input and a string result
/// </summary>
@DefineKernelFunction(name = "InputDateTimeWithStringResult")
@DefineKernelFunction(
name = "InputDateTimeWithStringResult",
returnType = "java.lang.String")
public String InputDateTimeWithStringResult(
@KernelFunctionParameter(
name = "currentDate",
@@ -281,7 +354,9 @@ public class Example09_FunctionTypes {
/// <summary>
/// Example of using a Task function with no input and a string result
/// </summary>
@DefineKernelFunction(name = "NoInputTaskWithStringResult")
@DefineKernelFunction(
name = "NoInputTaskWithStringResult",
returnType = "java.lang.String")
public Mono<String> NoInputTaskWithStringResult() {
return Mono.fromCallable(() -> {
var result = "string result";
@@ -341,7 +416,10 @@ public class Example09_FunctionTypes {
/// <summary>
/// Example using an async task function echoing the input
/// </summary>
@DefineKernelFunction(name = "InputStringTaskWithStringResult")
@DefineKernelFunction(
name = "InputStringTaskWithStringResult",
returnType = "java.lang.String"
)
public Mono<String> InputStringTaskWithStringResult(
@KernelFunctionParameter(
@@ -360,7 +438,10 @@ public class Example09_FunctionTypes {
/// <summary>
/// Example using an async void task with string input
/// </summary>
@DefineKernelFunction(name = "InputStringTaskWithVoidResult")
@DefineKernelFunction(
name = "InputStringTaskWithVoidResult",
returnType = "java.lang.Void"
)
public Mono<Void> InputStringTaskWithVoidResult(
@KernelFunctionParameter(
name = "x"
@@ -377,9 +458,9 @@ public class Example09_FunctionTypes {
name = "noInputComplexReturnTypeAsync",
returnType = "java.time.temporal.Temporal"
)
public Mono<LocalDateTime> noInputComplexReturnTypeAsync() {
public Mono<OffsetDateTime> noInputComplexReturnTypeAsync() {
return Mono.just(
LocalDateTime.now()
OffsetDateTime.now()
);
}
@@ -387,8 +468,8 @@ public class Example09_FunctionTypes {
name = "noInputComplexReturnType",
returnType = "java.time.temporal.Temporal"
)
public LocalDateTime noInputComplexReturnType() {
return LocalDateTime.now();
public OffsetDateTime noInputComplexReturnType() {
return OffsetDateTime.now();
}
@DefineKernelFunction(
@@ -406,6 +487,27 @@ public class Example09_FunctionTypes {
return Integer.toString(x);
}
// Possibilities for return type combinations:
// | Method return type | Declared Function Return Type |
// |--------------------|-------------------------------|
// | T | T |
// | T | U extends T |
@DefineKernelFunction(
name = "conversionScenarioA",
returnType = "java.time.OffsetDateTime"
)
public OffsetDateTime conversionScenarioA() {
return OffsetDateTime.now();
}
@DefineKernelFunction(
name = "conversionScenarioB",
returnType = "java.time.temporal.Temporal"
)
public OffsetDateTime conversionScenarioB() {
return OffsetDateTime.now();
}
/*
/// <summary>
/// Example using a function to return the result of another inner function
@@ -170,7 +170,7 @@ public class Example13_ConversationSummaryPlugin {
);
System.out.println("Generated Action Items:");
System.out.println(summary.block().getResultVariable());
System.out.println(summary.block().getResult());
}
private static void getConversationTopicsAsync() {
@@ -191,7 +191,7 @@ public class Example13_ConversationSummaryPlugin {
);
System.out.println("Generated Topics:");
System.out.println(summary.block().getResultVariable());
System.out.println(summary.block().getResult());
}
@@ -203,7 +203,7 @@ public class Example13_ConversationSummaryPlugin {
KernelPlugin conversationSummaryPlugin = KernelPluginFactory.createFromObject(
new ConversationSummaryPlugin(), null);
Mono<FunctionResult<String>> summary = kernel
FunctionResult<String> summary = kernel
.invokeAsync(
conversationSummaryPlugin
.getFunctions()
@@ -212,10 +212,10 @@ public class Example13_ConversationSummaryPlugin {
.withInput(ChatTranscript)
.build(),
String.class
);
).block();
System.out.println("Generated Summary (This may take some time):");
System.out.println(summary.block().getResultVariable());
System.out.println(summary.getResult());
}
private static Kernel initializeKernel() {
@@ -58,7 +58,7 @@ public class Example27_PromptFunctionsUsingChatGPT {
ContextVariableTypes.getDefaultVariableTypeForClass(String.class)
)
.block();
System.out.println(result.getResultVariable());
System.out.println(result.getResult());
}
}
@@ -85,7 +85,7 @@ public class Example56_TemplateMethodFunctionsWithMultipleArguments {
// Show the result
System.out.println("--- Prompt Function result");
var result = kernel.invokeAsync(haiku, arguments, String.class).block();
System.out.println(result.getResultVariable());
System.out.println(result.getResult());
}
}
@@ -65,7 +65,7 @@ public class Example58_ConfigureExecutionSettings {
String.class
).block();
System.out.println(result.getResultVariable());
System.out.println(result.getResult());
// Option 2:
// Load prompt template configuration including the execution settings from a JSON payload
@@ -92,6 +92,6 @@ public class Example58_ConfigureExecutionSettings {
var func = KernelFunctionFactory.createFromPrompt(promptConfig, null);
result = kernel.invokeAsync(func, null, String.class).block();
System.out.println(result.getResultVariable());
System.out.println(result.getResult());
}
}
@@ -85,7 +85,7 @@ public class Example61_MultipleLLMs {
.build();
var result = kernel.invokeAsync(func, arguments, String.class).block();
System.out.println(result.getResultVariable());
System.out.println(result.getResult());
}
@@ -108,7 +108,7 @@ public class Example61_MultipleLLMs {
String.class
).block();
System.out.println(result.getResultVariable());
System.out.println(result.getResult());
}
@@ -141,7 +141,7 @@ public class Example61_MultipleLLMs {
String.class)
.block();
System.out.println(result.getResultVariable());
System.out.println(result.getResult());
}
}
@@ -78,7 +78,7 @@ public class Example62_CustomAIServiceSelector {
.build();
var result = kernel.invokeAsync(func, arguments, String.class).block();
System.out.println(result.getResultVariable());
System.out.println(result.getResult());
}
// A dumb AIServiceSelector that just returns the first service and execution settings it finds
@@ -55,14 +55,14 @@ public class Example63_ChatCompletionPrompts {
System.out.println("Chat Prompt:");
System.out.println(chatPrompt);
System.out.println("Chat Prompt Result:");
System.out.println(chatPromptResult.getResultVariable());
System.out.println(chatPromptResult.getResult());
CountDownLatch cdl = new CountDownLatch(1);
System.out.println("Chat Prompt Result:");
kernel
.invokeAsync(chatSemanticFunction, null, String.class)
.doFinally(x -> cdl.countDown())
.subscribe(result -> System.out.println(result.getResultVariable()));
.subscribe(result -> System.out.println(result.getResult()));
cdl.await();
}
@@ -82,6 +82,6 @@ public class Example64_MultiplePromptTemplates {
.build();
var result = kernel.invokeAsync(function, arguments, String.class).block();
System.out.println(result.getResultVariable());
System.out.println(result.getResult());
}
}
@@ -25,8 +25,8 @@ public class Example69_MutableKernelPlugin {
System.out.println("======== Example69_MutableKernelPlugin ========");
KernelPlugin plugin = new KernelPlugin("Plugin", "Mutable plugin", null);
plugin.getFunctions().put("dateFunction", KernelFunctionFactory.createFromMethod(
Time.class.getMethod("date"), new Time(), "date", null, null, null));
plugin.addFunction(KernelFunctionFactory.createFromMethod(
Time.class.getMethod("date"), new Time(), "dateFunction", null, null, null));
Kernel kernel = Kernel.builder().build();
kernel.getPlugins().add(plugin);
@@ -0,0 +1,67 @@
package com.microsoft.semantickernel.samples.syntaxexamples;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
/**
* Run all the syntax examples.
* <p>
* Refer to the <a href=
* "https://github.com/microsoft/semantic-kernel/blob/experimental-java/java/samples/sample-code/README.md">
* README</a> for configuring your environment to run the examples.
*/
public class RunAll {
public interface MainMethod {
void run(String[] args) throws Exception;
}
public static void main(String[] args) {
List<MainMethod> mains = Arrays.asList(
Example01_NativeFunctions::main,
Example03_Arguments::main,
Example05_InlineFunctionDefinition::main,
Example06_TemplateLanguage::main,
Example08_RetryHandler::main,
Example09_FunctionTypes::main,
Example10_DescribeAllPluginsAndFunctions::main,
//Example11_WebSearchQueries::main,
Example13_ConversationSummaryPlugin::main,
Example17_ChatGPT::main,
//Example26_AADAuth::main,
Example27_PromptFunctionsUsingChatGPT::main,
Example30_ChatWithPrompts::main,
Example33_Chat::main,
Example41_HttpClientUsage::main,
Example43_GetModelResult::main,
Example44_MultiChatCompletion::main,
Example49_LogitBias::main,
Example55_TextChunker::main,
Example56_TemplateMethodFunctionsWithMultipleArguments::main,
Example57_KernelHooks::main,
Example58_ConfigureExecutionSettings::main,
Example61_MultipleLLMs::main,
Example62_CustomAIServiceSelector::main,
Example63_ChatCompletionPrompts::main,
Example64_MultiplePromptTemplates::main,
Example69_MutableKernelPlugin::main
);
Scanner scanner = new Scanner(System.in);
mains.forEach(mainMethod -> {
try {
System.out.println("========================================");
mainMethod.run(args);
System.out.println("Press any key to continue...");
scanner.nextLine();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
}
@@ -2,62 +2,75 @@
package com.microsoft.semantickernel;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import javax.annotation.Nullable;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Marker interface for AI services
* Marker interface for AI services
*/
public interface AIService {
Map<String, ContextVariable<?>> getAttributes();
/**
* Key used to store the model identifier in the {@code attributes} map.
*/
static final String MODEL_ID_KEY = "ModelId";
*/
String MODEL_ID_KEY = "ModelId";
/**
* Key used to store the endpoint in the {@code attributes} map.
*/
static final String ENDPOINT_KEY = "Endpoint";
*/
String ENDPOINT_KEY = "Endpoint";
/**
* Key used to store the API version in the {@code attributes} map.
*/
static final String API_VERSION_KEY = "ApiVersion";
/**
* Gets the model identifier from the {@code attributes} map.
* @return The model identifier if it was specified in the service's attributes; otherwise, null.
*/
default String getModelId() { return getAttribute(this, MODEL_ID_KEY); }
String API_VERSION_KEY = "ApiVersion";
/**
* Gets the model identifier from the {@code attributes} map.
*
* @return The model identifier if it was specified in the service's attributes; otherwise,
* null.
*/
@Nullable
default String getModelId() {
return getAttribute(this, MODEL_ID_KEY);
}
/**
* Gets the endpoint from the {@code attributes} map.
*
* @return The endpoint if it was specified in the service's attributes; otherwise, null.
*/
default String getEndpoint() { return getAttribute(this, ENDPOINT_KEY); }
@Nullable
default String getEndpoint() {
return getAttribute(this, ENDPOINT_KEY);
}
/**
* Gets the API version from the {@code attributes} map.
*
* @return The API version if it was specified in the service's attributes; otherwise, null.
*/
default String getApiVersion() { return getAttribute(this, API_VERSION_KEY); }
@Nullable
default String getApiVersion() {
return getAttribute(this, API_VERSION_KEY);
}
@Nullable
String getServiceId();
/// <summary>
/// Gets the specified attribute.
/// </summary>
@Nullable
static String getAttribute(AIService service, String key)
{
static String getAttribute(AIService service, String key) {
if (service.getAttributes() != null) {
ContextVariable<?> contextVariable = service.getAttributes().get(key);
if (contextVariable != null) {
return contextVariable.getValue().toString();
return contextVariable.getValue(String.class);
}
}
return null;
@@ -5,11 +5,14 @@ import com.microsoft.semantickernel.builders.Buildable;
import com.microsoft.semantickernel.builders.SemanticKernelBuilder;
import com.microsoft.semantickernel.hooks.KernelHooks;
import com.microsoft.semantickernel.orchestration.FunctionResult;
import com.microsoft.semantickernel.orchestration.FunctionResultMetadata;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter.NoopConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.orchestration.contextvariables.NullContextVariable;
import com.microsoft.semantickernel.plugin.KernelPlugin;
import com.microsoft.semantickernel.plugin.KernelPluginCollection;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
@@ -98,20 +101,41 @@ public class Kernel implements Buildable {
kernelHooks = mergeInGlobalHooks(kernelHooks);
ContextVariableType<T> contextVariable;
ContextVariableType<?> contextVariable = getContextVariableType(function, resultType);
return function
.invokeAsync(this, arguments, kernelHooks, contextVariable)
.map(it -> {
return new FunctionResult<>(
ContextVariable.convert(it.getResult(), resultType),
it.getMetadata()
);
})
.defaultIfEmpty(
new FunctionResult<>(
new NullContextVariable<>(resultType),
new FunctionResultMetadata()));
}
@Nullable
private static <T> ContextVariableType<?> getContextVariableType(
KernelFunction function,
Class<T> resultType) {
Class functionReturnType = function.getMetadata().getReturnParameter().getParameterType();
try {
contextVariable = ContextVariableTypes.getDefaultVariableTypeForClass(resultType);
return ContextVariableTypes.getDefaultVariableTypeForClass(
functionReturnType);
} catch (Exception e) {
if (resultType.isAssignableFrom(
function.getMetadata().getReturnParameter().getParameterType())) {
contextVariable = new ContextVariableType<>(new NoopConverter<>(resultType),
resultType);
if (functionReturnType.isAssignableFrom(resultType)) {
return new ContextVariableType<>(
new NoopConverter<>(functionReturnType),
functionReturnType);
} else {
throw e;
return null;
}
}
return function.invokeAsync(this, arguments, kernelHooks, contextVariable);
}
public <T> Mono<FunctionResult<T>> invokeAsync(
@@ -1,9 +1,6 @@
package com.microsoft.semantickernel;
import com.microsoft.semantickernel.orchestration.FunctionResultMetadata;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
public abstract class KernelContent<T extends KernelContent<T>> {
@@ -11,7 +8,7 @@ public abstract class KernelContent<T extends KernelContent<T>> {
@Nullable
private Object innerContent;
@Nullable
private String modelId;
private final String modelId;
@Nullable
private FunctionResultMetadata metadata;
@@ -30,25 +27,26 @@ public abstract class KernelContent<T extends KernelContent<T>> {
}
}
T setInnerContent(@Nullable Object innerContent) {
KernelContent<T> setInnerContent(@Nullable Object innerContent) {
this.innerContent = innerContent;
return (T) this;
return this;
}
@CheckForNull
@Nullable
public Object getInnerContent() {
return innerContent;
}
@CheckForNull
@Nullable
public String getModelId() {
return modelId;
}
@CheckForNull
@Nullable
public FunctionResultMetadata getMetadata() {
return metadata;
}
@Nullable
public abstract String getContent();
}
@@ -24,7 +24,7 @@ public abstract class StreamingKernelContent<T> {
/// The model ID used to generate the content.
/// </summary>
@Nullable
private final String modelId;
private String modelId;
/// <summary>
/// The metadata associated with the content.
@@ -32,18 +32,14 @@ public abstract class StreamingKernelContent<T> {
@Nullable
private final Map<String, ContextVariable<?>> metadata;
protected StreamingKernelContent(@Nullable T innerContent, int choiceIndex,
@Nullable String modelId, @Nullable Map<String, ContextVariable<?>> metadata) {
protected StreamingKernelContent(
@Nullable T innerContent,
int choiceIndex,
@Nullable String modelId,
@Nullable Map<String, ContextVariable<?>> metadata) {
this.innerContent = innerContent;
this.choiceIndex = choiceIndex;
this.modelId = modelId;
this.metadata = metadata;
}
public abstract byte[] toByteArray();
public Object getInnerContent() {
return innerContent;
}
}
@@ -16,18 +16,9 @@ public enum AuthorRole {
return role;
}
public AuthorRole fromString(String role) {
for (AuthorRole authorRole : AuthorRole.values()) {
if (authorRole.role.equalsIgnoreCase(role)) {
return authorRole;
}
}
return null;
}
private final String role;
private AuthorRole(String role) {
AuthorRole(String role) {
this.role = role;
}
}
@@ -11,6 +11,7 @@ import com.microsoft.semantickernel.builders.ServiceLoadUtil;
import com.microsoft.semantickernel.hooks.KernelHooks;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
import java.util.List;
import javax.annotation.Nullable;
import reactor.core.publisher.Mono;
public interface ChatCompletionService extends Buildable, TextAIService {
@@ -36,8 +37,10 @@ public interface ChatCompletionService extends Buildable, TextAIService {
Mono<List<ChatMessageContent>> getChatMessageContentsAsync(
String prompt,
@Nullable
PromptExecutionSettings promptExecutionSettings,
Kernel kernel,
@Nullable
KernelHooks kernelHooks
);
@@ -49,8 +52,13 @@ public interface ChatCompletionService extends Buildable, TextAIService {
abstract class Builder implements SemanticKernelBuilder<ChatCompletionService> {
@Nullable
protected OpenAIAsyncClient client;
@Nullable
protected String modelId;
@Nullable
protected String serviceId;
@@ -13,6 +13,7 @@ import javax.annotation.Nullable;
public class ChatMessageContent extends KernelContent<ChatMessageContent> {
private AuthorRole authorRole;
@Nullable
private String content;
@Nullable
private List<KernelContent> items;
@@ -52,13 +53,14 @@ public class ChatMessageContent extends KernelContent<ChatMessageContent> {
public ChatMessageContent(
AuthorRole authorRole,
List<KernelContent> items,
List<KernelContent<?>> items,
String modelId,
String innerContent,
Charset encoding,
FunctionResultMetadata metadata
) {
super(innerContent, modelId, metadata);
this.content = null;
this.authorRole = authorRole;
this.encoding = encoding != null ? encoding : StandardCharsets.UTF_8;
this.items = new ArrayList<>(items);
@@ -72,11 +74,13 @@ public class ChatMessageContent extends KernelContent<ChatMessageContent> {
this.authorRole = authorRole;
}
@Nullable
@Override
public String getContent() {
return content;
}
public void setContent(String content) {
public void setContent(@Nullable String content) {
this.content = content;
}
@@ -1,33 +1,38 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.chatcompletion;
import com.microsoft.semantickernel.StreamingKernelContent;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import javax.annotation.Nullable;
import com.microsoft.semantickernel.StreamingKernelContent;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
public class StreamingChatMessageContent extends StreamingKernelContent {
public class StreamingChatMessageContent<T> extends StreamingKernelContent<T> {
private final String content;
private final AuthorRole role;
private Charset encoding;
private String modelId;
public StreamingChatMessageContent(
AuthorRole role,
String content) {
this(role, content, null, 0, null, null, null);
String content,
@Nullable
String modelId) {
this(role, content, null, 0, modelId, null, null);
}
public StreamingChatMessageContent(
AuthorRole role,
String content,
Object innerContent,
@Nullable
T innerContent,
int choiceIndex,
@Nullable
String modelId,
@Nullable
Charset encoding,
@Nullable
Map<String, ContextVariable<?>> metadata) {
super(innerContent, choiceIndex, modelId, metadata);
this.role = role;
@@ -40,11 +45,6 @@ public class StreamingChatMessageContent extends StreamingKernelContent {
return content != null ? content : "";
}
@Override
public byte[] toByteArray() {
return toString().getBytes(encoding);
}
public String getContent() {
return content;
}
@@ -57,18 +57,9 @@ public class StreamingChatMessageContent extends StreamingKernelContent {
return encoding;
}
public StreamingChatMessageContent setEncoding(Charset encoding) {
public StreamingChatMessageContent<T> setEncoding(Charset encoding) {
this.encoding = encoding;
return this;
}
public StreamingChatMessageContent setModelId(String modelId) {
this.modelId = modelId;
return this;
}
public String getModelId() {
return modelId;
}
}
@@ -1,42 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.exceptions;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class SkillsNotFoundException extends SKException {
private final ErrorCodes errorCode;
public SkillsNotFoundException(@Nonnull ErrorCodes errorCodes) {
this(errorCodes, null, null);
}
public SkillsNotFoundException(@Nonnull ErrorCodes errorCodes, @Nullable String message) {
this(errorCodes, message, null);
}
public SkillsNotFoundException(
@Nonnull ErrorCodes errorCodes, @Nullable String message, @Nullable Throwable cause) {
super(formatDefaultMessage(errorCodes.getMessage(), message), cause);
this.errorCode = errorCodes;
}
public ErrorCodes getErrorCode() {
return errorCode;
}
public enum ErrorCodes {
SKILLS_NOT_FOUND("Skills not found");
final String message;
ErrorCodes(String message) {
this.message = message;
}
String getMessage() {
return message;
}
}
}
@@ -3,14 +3,18 @@ package com.microsoft.semantickernel.hooks;
import com.microsoft.semantickernel.orchestration.FunctionResult;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import javax.annotation.Nullable;
public class FunctionInvokedEvent<T> implements KernelHookEvent {
private final KernelFunction function;
@Nullable
private final KernelArguments arguments;
private final FunctionResult<T> result;
public FunctionInvokedEvent(KernelFunction function, KernelArguments arguments,
public FunctionInvokedEvent(
KernelFunction function,
@Nullable KernelArguments arguments,
FunctionResult<T> result) {
this.function = function;
this.arguments = arguments;
@@ -21,6 +25,7 @@ public class FunctionInvokedEvent<T> implements KernelHookEvent {
return function;
}
@Nullable
public KernelArguments getArguments() {
return arguments;
}
@@ -2,13 +2,15 @@ package com.microsoft.semantickernel.hooks;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import javax.annotation.Nullable;
public class FunctionInvokingEvent implements KernelHookEvent {
private final KernelFunction function;
@Nullable
private final KernelArguments arguments;
public FunctionInvokingEvent(KernelFunction function, KernelArguments arguments) {
public FunctionInvokingEvent(KernelFunction function, @Nullable KernelArguments arguments) {
this.function = function;
this.arguments = arguments;
}
@@ -17,6 +19,7 @@ public class FunctionInvokingEvent implements KernelHookEvent {
return function;
}
@Nullable
public KernelArguments getArguments() {
return arguments;
}
@@ -1,5 +1,6 @@
package com.microsoft.semantickernel.orchestration;
import com.microsoft.semantickernel.Todo;
import java.util.Collections;
import java.util.Map;
import javax.annotation.Nullable;
@@ -43,23 +44,19 @@ public abstract class DefaultKernelFunction implements KernelFunction {
}
@Override
public String toFullyQualifiedName() {
return null;
}
@Override
@Nullable
public String getDescription() {
return null;
return metadata.getDescription();
}
@Override
public String toEmbeddingString() {
return null;
throw new Todo();
}
@Override
public String toManualString(boolean includeOutputs) {
return null;
throw new Todo();
}
@Override
@@ -1,22 +1,25 @@
package com.microsoft.semantickernel.orchestration;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import javax.annotation.Nullable;
public class FunctionResult<T> {
private final ContextVariable<T> result;
private final FunctionResultMetadata metadata;
public FunctionResult(ContextVariable<T> result, FunctionResultMetadata metadata) {
public FunctionResult(
ContextVariable<T> result,
@Nullable FunctionResultMetadata metadata) {
this.result = result;
this.metadata = metadata;
this.metadata = metadata == null ? FunctionResultMetadata.empty() : metadata;
}
public FunctionResult(ContextVariable<T> of) {
this(of, FunctionResultMetadata.empty());
}
@Nullable
public T getResult() {
return result.getValue();
}
@@ -4,6 +4,7 @@ import com.azure.ai.openai.models.CompletionsUsage;
import com.microsoft.semantickernel.orchestration.contextvariables.CaseInsensitiveMap;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import java.time.OffsetDateTime;
import javax.annotation.Nullable;
public class FunctionResultMetadata {
@@ -14,6 +15,10 @@ public class FunctionResultMetadata {
private final CaseInsensitiveMap<ContextVariable<?>> metadata;
public FunctionResultMetadata() {
this.metadata = new CaseInsensitiveMap<>();
}
public FunctionResultMetadata(CaseInsensitiveMap<ContextVariable<?>> metadata) {
this.metadata = new CaseInsensitiveMap<>(metadata);
}
@@ -39,15 +44,30 @@ public class FunctionResultMetadata {
return new CaseInsensitiveMap<>(metadata);
}
@Nullable
public String getId() {
return metadata.get(ID).getValue(String.class);
ContextVariable<?> id = metadata.get(ID);
if (id == null) {
return null;
}
return id.getValue(String.class);
}
@Nullable
public CompletionsUsage getUsage() {
return metadata.get(USAGE).getValue(CompletionsUsage.class);
ContextVariable<?> usage = metadata.get(USAGE);
if (usage == null) {
return null;
}
return usage.getValue(CompletionsUsage.class);
}
@Nullable
public OffsetDateTime getCreatedAt() {
return metadata.get(CREATED_AT).getValue(OffsetDateTime.class);
ContextVariable<?> createdAt = metadata.get(CREATED_AT);
if (createdAt == null) {
return null;
}
return createdAt.getValue(OffsetDateTime.class);
}
}
@@ -33,16 +33,11 @@ public interface KernelFunction extends Buildable {
*/
String getName();
/**
* The function to create a fully qualified name for
*
* @return A fully qualified name for a function
*/
String toFullyQualifiedName();
/**
* @return A description of the function
*/
@Nullable
String getDescription();
/**
@@ -80,41 +75,46 @@ public interface KernelFunction extends Buildable {
<T> Mono<FunctionResult<T>> invokeAsync(
Kernel kernel,
@Nullable KernelArguments arguments,
ContextVariableType<T> variableType);
@Nullable ContextVariableType<T> variableType);
<T> Mono<FunctionResult<T>> invokeAsync(
Kernel kernel,
@Nullable KernelArguments arguments,
KernelHooks kernelHooks,
ContextVariableType<T> variableType);
@Nullable ContextVariableType<T> variableType);
@Nullable
Map<String, PromptExecutionSettings> getExecutionSettings();
interface FromPromptBuilder {
FromPromptBuilder withName(String name);
FromPromptBuilder withName(@Nullable String name);
FromPromptBuilder withInputParameters(List<InputVariable> inputVariables);
FromPromptBuilder withInputParameters(
@Nullable List<InputVariable> inputVariables);
FromPromptBuilder withPromptTemplate(PromptTemplate promptTemplate);
FromPromptBuilder withPromptTemplate(
@Nullable PromptTemplate promptTemplate);
FromPromptBuilder withExecutionSettings(
@Nullable
Map<String, PromptExecutionSettings> executionSettings);
FromPromptBuilder withDefaultExecutionSettings(
@Nullable
PromptExecutionSettings executionSettings);
FromPromptBuilder withDescription(String description);
FromPromptBuilder withDescription(@Nullable String description);
FromPromptBuilder withTemplate(String template);
FromPromptBuilder withTemplate(@Nullable String template);
KernelFunction build();
FromPromptBuilder withTemplateFormat(String templateFormat);
FromPromptBuilder withOutputVariable(OutputVariable outputVariable);
FromPromptBuilder withOutputVariable(@Nullable OutputVariable outputVariable);
FromPromptBuilder withPromptTemplateFactory(PromptTemplateFactory promptTemplateFactory);
FromPromptBuilder withPromptTemplateFactory(
@Nullable PromptTemplateFactory promptTemplateFactory);
}
}
@@ -5,15 +5,17 @@ import com.microsoft.semantickernel.plugin.KernelReturnParameterMetadata;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
public class KernelFunctionMetadata {
/// <summary>The name of the function.</summary>
private final String name;
/// <summary>The description of the function.</summary>
@Nullable
private final String description;
/// <summary>The function's parameters.</summary>
private final List<KernelParameterMetadata> parameters;
private final List<KernelParameterMetadata<?>> parameters;
/// <summary>The function's return parameter.</summary>
@@ -22,12 +24,19 @@ public class KernelFunctionMetadata {
public KernelFunctionMetadata(
String name,
@Nullable
String description,
List<KernelParameterMetadata> parameters,
@Nullable
List<KernelParameterMetadata<?>> parameters,
KernelReturnParameterMetadata<?> returnParameter) {
this.name = name;
this.description = description;
this.parameters = new ArrayList<>(parameters);
if (parameters == null) {
this.parameters = new ArrayList<>();
} else {
this.parameters = new ArrayList<>(parameters);
}
this.returnParameter = returnParameter;
}
@@ -35,10 +44,11 @@ public class KernelFunctionMetadata {
return name;
}
public List<KernelParameterMetadata> getParameters() {
public List<KernelParameterMetadata<?>> getParameters() {
return Collections.unmodifiableList(parameters);
}
@Nullable
public String getDescription() {
return description;
}
@@ -6,21 +6,24 @@ import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromMethod.I
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
public class MethodDetails {
private final String name;
@Nullable
private final String description;
private final ImplementationFunc function;
private final List<KernelParameterMetadata> parameters;
private final KernelReturnParameterMetadata returnParameter;
private final List<KernelParameterMetadata<?>> parameters;
private final KernelReturnParameterMetadata<?> returnParameter;
public MethodDetails(
String name,
@Nullable
String description,
ImplementationFunc function,
List<KernelParameterMetadata> parameters,
KernelReturnParameterMetadata returnParameter) {
List<KernelParameterMetadata<?>> parameters,
KernelReturnParameterMetadata<?> returnParameter) {
this.name = name;
this.description = description;
this.function = function;
@@ -32,6 +35,7 @@ public class MethodDetails {
return name;
}
@Nullable
public String getDescription() {
return description;
}
@@ -40,11 +44,11 @@ public class MethodDetails {
return function;
}
public List<KernelParameterMetadata> getParameters() {
public List<KernelParameterMetadata<?>> getParameters() {
return Collections.unmodifiableList(parameters);
}
public KernelReturnParameterMetadata getReturnParameter() {
public KernelReturnParameterMetadata<?> getReturnParameter() {
return returnParameter;
}
}
@@ -1,23 +1,148 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.orchestration.contextvariables;
import com.microsoft.semantickernel.exceptions.SKException;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter.NoopConverter;
import javax.annotation.Nullable;
public class ContextVariable<T> {
private final ContextVariableType<T> type;
@Nullable
private final T value;
public ContextVariable(ContextVariableType<T> type, T value) {
public ContextVariable(ContextVariableType<T> type,
@Nullable T value) {
this.type = type;
this.value = value;
}
public static <T> ContextVariable<T> of(Class<T> type, @Nullable T t) {
if (t == null) {
return new ContextVariable<>(
ContextVariableTypes.getDefaultVariableTypeForClass(type),
null);
}
return of(t);
}
public static ContextVariable<?> untypedOf(
@Nullable Object value,
Class<?> clazz) {
ContextVariableType<?> type = ContextVariableTypes.getDefaultVariableTypeForClass(clazz);
return new ContextVariable(type, value);
}
public static ContextVariable<?> untypedOf(
@Nullable Object value,
ContextVariableTypeConverter<?> converter) {
ContextVariableType<?> type = new ContextVariableType(converter, converter.getType());
return new ContextVariable(type, value);
}
public static <T, U> ContextVariable<T> convert(
@Nullable
U it,
Class<T> requestedResultType) {
return convert(
it,
requestedResultType,
ContextVariableTypes.DEFAULT_TYPES
);
}
public static <T, U> ContextVariable<T> convert(
@Nullable
U it,
Class<T> requestedResultType,
ContextVariableTypes contextVariableTypes) {
if (it == null) {
return new ContextVariable<>(
contextVariableTypes.getVariableTypeForClass(requestedResultType),
null);
}
if (requestedResultType.isAssignableFrom(it.getClass())) {
try {
return ContextVariable.of(requestedResultType, requestedResultType.cast(it));
} catch (Exception e) {
return new ContextVariable<>(
new ContextVariableType<>(
new NoopConverter<>(requestedResultType),
requestedResultType
),
requestedResultType.cast(it)
);
}
}
ContextVariableType<T> requestedResultTypeVariable;
try {
requestedResultTypeVariable = contextVariableTypes.getVariableTypeForClass(
requestedResultType);
} catch (Exception e) {
throw new SKException("Unable to find variable type for " + requestedResultType, e);
}
ContextVariableType<U> typeOfActualReturnedType;
try {
// First try to convert from type ? to T using the converter of ? and see if it can convert it to T.
typeOfActualReturnedType = contextVariableTypes.getVariableTypeForClass(
(Class<U>) it.getClass());
} catch (Exception e) {
typeOfActualReturnedType = contextVariableTypes.getVariableTypeForSuperClass(
(Class<U>) it.getClass());
}
if (typeOfActualReturnedType != null) {
// Try the to object
T converted = typeOfActualReturnedType.getConverter().toObject(it, requestedResultType);
if (converted != null) {
return contextVariableTypes.getVariableTypeForClass(requestedResultType)
.of(converted);
}
if (requestedResultType.isAssignableFrom(String.class)) {
// Try using toPromptString
String str = typeOfActualReturnedType.getConverter()
.toPromptString(typeOfActualReturnedType.getClazz().cast(it));
return requestedResultTypeVariable.of(str);
}
}
// Try getting a converter of type T and see if it can convert ? to T.
if (requestedResultTypeVariable != null) {
// Try using from object
T result = requestedResultTypeVariable.getConverter().fromObject(it);
if (result != null) {
return requestedResultTypeVariable.of(result);
}
if (requestedResultType.equals(String.class)) {
// Try using from prompt string
return requestedResultTypeVariable.of(
requestedResultTypeVariable.getConverter().fromPromptString((String) it));
}
}
throw new SKException("Unable to convert " + it.getClass() + " to " + requestedResultType);
}
@Nullable
public T getValue() {
return value;
}
@Nullable
public <U> U getValue(Class<U> clazz) {
if (clazz.isAssignableFrom(value.getClass())) {
return (U) value;
if (value == null || clazz.isAssignableFrom(value.getClass())) {
return clazz.cast(value);
} else {
throw new RuntimeException("Cannot cast " + value.getClass() + " to " + clazz);
}
@@ -1,5 +1,7 @@
package com.microsoft.semantickernel.orchestration.contextvariables;
import javax.annotation.Nullable;
public class ContextVariableType<T> {
private final ContextVariableTypeConverter<T> contextVariableTypeConverter;
@@ -26,10 +28,15 @@ public class ContextVariableType<T> {
* @param it The object to convert.
* @return A context variable of this type.
*/
public ContextVariable<T> of(Object it) {
if (getClazz().isAssignableFrom(it.getClass())) {
return ContextVariable.of((T) it, getConverter());
public ContextVariable<T> of(@Nullable Object it) {
if (it == null) {
return ContextVariable.of(clazz, clazz.cast(null));
}
return ContextVariable.of(getConverter().fromObject(it));
if (clazz.isAssignableFrom(it.getClass())) {
return ContextVariable.of(clazz.cast(it), getConverter());
}
ContextVariableTypeConverter<T> converter = getConverter();
return ContextVariable.of(converter.getType(), converter.fromObject(it));
}
}
@@ -79,10 +79,14 @@ public class ContextVariableTypeConverter<T> {
this.toObjects = new ArrayList<>(toObjects);
}
@Nullable
@SuppressWarnings("unchecked")
public <U> U toObject(Object t, Class<U> clazz) {
public <U> U toObject(@Nullable Object t, Class<U> clazz) {
if (t == null) {
return null;
}
Optional<Converter<T, ?>> converter = toObjects
.stream()
.filter(c -> c.getTargetType().equals(clazz))
@@ -106,14 +110,14 @@ public class ContextVariableTypeConverter<T> {
}
@Nullable
public T fromObject(Object s) {
public T fromObject(@Nullable Object s) {
if (s == null) {
return null;
}
return fromObject.apply(s);
}
public String toPromptString(T t) {
public String toPromptString(@Nullable T t) {
if (t == null) {
return "";
}
@@ -121,7 +125,7 @@ public class ContextVariableTypeConverter<T> {
}
@Nullable
public T fromPromptString(String t) {
public T fromPromptString(@Nullable String t) {
if (t == null) {
return null;
}
@@ -5,6 +5,7 @@ import com.microsoft.semantickernel.orchestration.contextvariables.converters.Ch
import com.microsoft.semantickernel.orchestration.contextvariables.converters.ChatHistoryVariableContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.converters.CompletionUsageContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.converters.DateTimeContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.converters.InstantContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.converters.NumberVariableContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.converters.StringVariableContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.converters.VoidVariableContextVariableTypeConverter;
@@ -12,6 +13,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
public class ContextVariableTypes {
@@ -24,7 +26,9 @@ public class ContextVariableTypes {
new ChatHistoryVariableContextVariableTypeConverter(),
new StringVariableContextVariableTypeConverter(),
new VoidVariableContextVariableTypeConverter(),
new ContextVariableTypeConverter<>(void.class, s -> null, s -> null, s -> null),
new DateTimeContextVariableTypeConverter(),
new InstantContextVariableTypeConverter(),
new CompletionUsageContextVariableTypeConverter(),
new NumberVariableContextVariableTypeConverter<>(Byte.class, Byte::parseByte),
@@ -64,8 +68,9 @@ public class ContextVariableTypes {
contextVariableTypeConverter.getType()));
}
@Nullable
@SuppressWarnings("unchecked")
public static <T> T convert(Object s, Class<T> clazz) {
public static <T> T convert(@Nullable Object s, Class<T> clazz) {
if (s instanceof ContextVariable && ((ContextVariable<?>) s).getType().getClazz()
.isAssignableFrom(clazz)) {
return ((ContextVariable<T>) s).getValue();
@@ -92,10 +97,29 @@ public class ContextVariableTypes {
return (ContextVariableType<T>) variableTypes
.values()
.stream()
.filter(c -> c.getClazz().isAssignableFrom(aClass))
.filter(c -> {
return c.getClazz().isAssignableFrom(aClass);
})
.findFirst()
.orElseThrow(
() -> new RuntimeException("Unknown context variable type: " + aClass.getName()));
}
public <T> ContextVariableType<T> getVariableTypeForSuperClass(Class<T> aClass) {
ContextVariableType<?> contextVariableType = variableTypes.get(aClass);
if (contextVariableType != null) {
return (ContextVariableType<T>) contextVariableType;
}
return (ContextVariableType<T>) variableTypes
.values()
.stream()
.filter(c -> {
return aClass.isAssignableFrom(c.getClazz());
})
.findFirst()
.orElseThrow(
() -> new RuntimeException("Unknown context variable type: " + aClass.getName()));
}
}
@@ -90,11 +90,6 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr
return setVariable(MAIN_KEY, content);
}
@Override
public KernelArguments update(String content) {
return null;
}
public DefaultKernelArguments update(@NonNull DefaultKernelArguments newData) {
return update(newData, true);
}
@@ -198,6 +193,7 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr
}
@Override
@Nullable
public ContextVariable<?> get(Object key) {
return variables.get(key);
}
@@ -0,0 +1,13 @@
package com.microsoft.semantickernel.orchestration.contextvariables;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter.NoopConverter;
public class NullContextVariable<T> extends ContextVariable<T> {
public NullContextVariable(Class<T> type) {
super(new ContextVariableType<>(
new NoopConverter<>(type),
type
), null);
}
}
@@ -29,8 +29,6 @@ public interface WritableKernelArguments extends KernelArguments {
*/
KernelArguments update(ContextVariable<?> content);
KernelArguments update(String content);
/**
* Updates the variables merging or overwriting in the new values.
*
@@ -1,8 +1,10 @@
package com.microsoft.semantickernel.orchestration.contextvariables.converters;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.Arrays;
public class DateTimeContextVariableTypeConverter extends
ContextVariableTypeConverter<OffsetDateTime> {
@@ -21,6 +23,14 @@ public class DateTimeContextVariableTypeConverter extends
Object::toString,
o -> {
return ZonedDateTime.parse(o).toOffsetDateTime();
});
},
Arrays.asList(
new DefaultConverter<OffsetDateTime, Instant>(OffsetDateTime.class, Instant.class) {
@Override
public Instant toObject(OffsetDateTime offsetDateTime) {
return offsetDateTime.toInstant();
}
}
));
}
}
@@ -0,0 +1,26 @@
package com.microsoft.semantickernel.orchestration.contextvariables.converters;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter;
import java.time.Instant;
import java.time.OffsetDateTime;
public class InstantContextVariableTypeConverter extends
ContextVariableTypeConverter<Instant> {
public InstantContextVariableTypeConverter() {
super(
Instant.class,
s -> {
if (s instanceof String) {
return Instant.parse((String) s);
} else if (s instanceof OffsetDateTime) {
return ((OffsetDateTime) s).toInstant();
}
return null;
},
Object::toString,
o -> {
return Instant.parse(o);
});
}
}
@@ -1,11 +1,10 @@
package com.microsoft.semantickernel.orchestration.contextvariables.converters;
import java.util.function.Function;
import javax.annotation.Nullable;
import static com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes.convert;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter;
import static com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes.convert;
import java.util.function.Function;
import javax.annotation.Nullable;
public class NumberVariableContextVariableTypeConverter<T extends Number> extends
ContextVariableTypeConverter<T> {
@@ -21,7 +20,11 @@ public class NumberVariableContextVariableTypeConverter<T extends Number> extend
@Override
@Nullable
public <U> U toObject(Object t, Class<U> clazz) {
public <U> U toObject(@Nullable Object t, Class<U> clazz) {
if (t == null) {
return null;
}
// Let the parent class have a crack at it first
// since someone may have installed a special converter.
U obj = super.toObject(t, clazz);
@@ -35,12 +38,13 @@ public class NumberVariableContextVariableTypeConverter<T extends Number> extend
}
return null;
}
@Override
public T fromObject(Object s) {
@Nullable
public T fromObject(@Nullable Object s) {
T obj = super.fromObject(s);
if (obj != null) {
return obj;
return obj;
}
if (s instanceof String) {
return fromPromptString((String) s);
@@ -49,11 +53,12 @@ public class NumberVariableContextVariableTypeConverter<T extends Number> extend
}
@Override
public T fromPromptString(String s) {
@Nullable
public T fromPromptString(@Nullable String s) {
if (s != null && !s.isEmpty()) {
return super.fromPromptString(s);
}
return null;
return null;
}
}
@@ -1,8 +1,8 @@
package com.microsoft.semantickernel.plugin;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromMethod;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromMethod;
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt;
import com.microsoft.semantickernel.semanticfunctions.KernelPromptTemplateFactory;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
@@ -16,30 +16,6 @@ import javax.annotation.Nullable;
public class KernelFunctionFactory {
/// <summary>
/// Creates a <see cref="KernelFunction"/> instance for a method, specified via a delegate.
/// </summary>
/// <param name="method">The method to be represented via the created <see cref="KernelFunction"/>.</param>
/// <param name="functionName">Optional function name. If null, it will default to one derived from the method represented by <paramref name="method"/>.</param>
/// <param name="description">Optional description of the method. If null, it will default to one derived from the method represented by <paramref name="method"/>, if possible (e.g. via a <see cref="DescriptionAttribute"/> on the method).</param>
/// <param name="parameters">Optional parameter descriptions. If null, it will default to one derived from the method represented by <paramref name="method"/>.</param>
/// <param name="returnParameter">Optional return parameter description. If null, it will default to one derived from the method represented by <paramref name="method"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> to use for logging. If null, no logging will be performed.</param>
/// <returns>The created <see cref="KernelFunction"/> wrapper for <paramref name="method"/>.</returns>
public static KernelFunction createFromMethod(
Method method,
@Nullable String functionName,
@Nullable String description,
List<KernelParameterMetadata> parameters,
KernelReturnParameterMetadata<?> returnParameter) {
return createFromMethod(
method,
null,
functionName,
description,
parameters,
returnParameter);
}
/// <summary>
/// Creates a <see cref="KernelFunction"/> instance for a method, specified via an <see cref="MethodInfo"/> instance
@@ -55,10 +31,10 @@ public class KernelFunctionFactory {
/// <returns>The created <see cref="KernelFunction"/> wrapper for <paramref name="method"/>.</returns>
public static KernelFunction createFromMethod(
Method method,
@Nullable Object target,
Object target,
@Nullable String functionName,
@Nullable String description,
@Nullable List<KernelParameterMetadata> parameters,
@Nullable List<KernelParameterMetadata<?>> parameters,
@Nullable KernelReturnParameterMetadata<?> returnParameter) {
return KernelFunctionFromMethod.create(method, target, functionName, description,
parameters, returnParameter);
@@ -87,8 +63,8 @@ public class KernelFunctionFactory {
* @param templateFormat The template format of {@code promptTemplate}. This must be
* provided if {@code promptTemplateFactory} is not null.
* @param promptTemplateFactory The {@link PromptTemplateFactory} to use when interpreting the
* {@code promptTemplate} into a {@link IPromptTemplate}. If null,
* a default factory will be used.
* {@code promptTemplate} into a {@link PromptTemplate}. If null, a
* default factory will be used.
* @return The created {@link KernelFunction} for invoking the prompt.
*/
public static KernelFunction createFromPrompt(
@@ -129,9 +105,12 @@ public class KernelFunctionFactory {
}
private static Map<String, PromptExecutionSettings> createSettingsDictionary(
@Nullable
PromptExecutionSettings executionSettings) {
HashMap<String, PromptExecutionSettings> map = new HashMap<>();
map.put("default", executionSettings);
if (executionSettings != null) {
map.put("default", executionSettings);
}
return map;
}
}
@@ -1,20 +1,27 @@
package com.microsoft.semantickernel.plugin;
import javax.annotation.Nullable;
public class KernelParameterMetadata<T> {
/// <summary>The name of the parameter.</summary>
private final String name;
/// <summary>The description of the parameter.</summary>
@Nullable
private final String description;
@Nullable
private final String defaultValue;
private final boolean isRequired;
private final Class<T> parameterType;
public KernelParameterMetadata(
String name,
@Nullable
String description,
Class<T> parameterType,
String defaultValue, boolean isRequired) {
@Nullable
String defaultValue,
boolean isRequired) {
this.name = name;
this.description = description;
this.defaultValue = defaultValue;
@@ -26,10 +33,12 @@ public class KernelParameterMetadata<T> {
return name;
}
@Nullable
public String getDescription() {
return description;
}
@Nullable
public String getDefaultValue() {
return defaultValue;
}
@@ -5,15 +5,20 @@ import com.microsoft.semantickernel.orchestration.contextvariables.CaseInsensiti
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import javax.annotation.Nullable;
public class KernelPlugin implements Iterable<KernelFunction> {
private final String name;
@Nullable
private final String description;
private final CaseInsensitiveMap<KernelFunction> functions;
public KernelPlugin(String name, String description,
public KernelPlugin(
String name,
@Nullable
String description,
Map<String, KernelFunction> plugins) {
this.name = name;
this.description = description;
@@ -23,10 +28,15 @@ public class KernelPlugin implements Iterable<KernelFunction> {
}
}
public void addFunction(KernelFunction function) {
functions.put(function.getName(), function);
}
public Map<String, KernelFunction> getFunctions() {
return Collections.unmodifiableMap(functions);
}
@Nullable
public KernelFunction get(String functionName) {
return functions.get(functionName);
}
@@ -40,6 +50,7 @@ public class KernelPlugin implements Iterable<KernelFunction> {
return name;
}
@Nullable
public String getDescription() {
return description;
}
@@ -6,7 +6,7 @@ import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.contextvariables.CaseInsensitiveMap;
import com.microsoft.semantickernel.plugin.annotations.DefineKernelFunction;
import com.microsoft.semantickernel.plugin.annotations.KernelFunctionParameter;
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt.Builder;
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt;
import com.microsoft.semantickernel.semanticfunctions.KernelPromptTemplateFactory;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig;
@@ -46,13 +46,13 @@ public class KernelPluginFactory {
/// Public methods decorated with <see cref="KernelFunctionAttribute"/> will be included in the plugin.
/// Attributed methods must all have different names; overloads are not supported.
/// </remarks>
public static KernelPlugin createFromObject(Object target, @Nullable String pluginName) {
public static KernelPlugin createFromObject(Object target, String pluginName) {
List<KernelFunction> methods = Arrays.stream(target.getClass().getMethods())
.filter(method -> method.isAnnotationPresent(DefineKernelFunction.class))
.map(method -> {
DefineKernelFunction annotation = method.getAnnotation(DefineKernelFunction.class);
Class<?> returnType = getReturnType(annotation, method);
KernelReturnParameterMetadata kernelReturnParameterMetadata = new KernelReturnParameterMetadata(
KernelReturnParameterMetadata<?> kernelReturnParameterMetadata = new KernelReturnParameterMetadata<>(
annotation.returnDescription(),
returnType);
@@ -75,10 +75,9 @@ public class KernelPluginFactory {
returnType = method.getReturnType();
if (Publisher.class.isAssignableFrom(returnType)) {
LOGGER.warn(
"For method: " + method.getDeclaringClass().getName() + "." + method.getName()
+ ", this is an async method, if a return type is required, please specify it in the annotation. Defaulting to Void return type");
returnType = Void.class;
throw new SKException(
"Method: " + method.getDeclaringClass().getName() + "." + method.getName()
+ ", this is an async method, It is required to add an annotation to specify the return type");
}
} else {
try {
@@ -109,7 +108,8 @@ public class KernelPluginFactory {
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is an invalid plugin name.</exception>
/// <exception cref="ArgumentNullException"><paramref name="functions"/> contains a null function.</exception>
/// <exception cref="ArgumentException"><paramref name="functions"/> contains two functions with the same name.</exception>
public static KernelPlugin createFromFunctions(String pluginName,
public static KernelPlugin createFromFunctions(
String pluginName,
@Nullable List<KernelFunction> functions) {
return createFromFunctions(pluginName, null, functions);
}
@@ -132,15 +132,19 @@ public class KernelPluginFactory {
}
private static List<KernelParameterMetadata> getParameters(Method method) {
private static List<KernelParameterMetadata<?>> getParameters(Method method) {
return Arrays.stream(method.getParameters())
.filter(parameter -> parameter.isAnnotationPresent(KernelFunctionParameter.class))
.map(parameter -> {
KernelFunctionParameter annotation = parameter.getAnnotation(
KernelFunctionParameter.class);
return new KernelParameterMetadata(annotation.name(), annotation.description(),
null, annotation.defaultValue(), annotation.required());
return new KernelParameterMetadata<>(
annotation.name(),
annotation.description(),
annotation.type(),
annotation.defaultValue(),
annotation.required());
}).collect(Collectors.toList());
}
@@ -176,8 +180,8 @@ public class KernelPluginFactory {
// configuration, unable to parse {configPath}");
}
KernelFunction plugin = getKernelFunction(pluginDirectoryName,
promptTemplateFactory, configPath, promptPath);
KernelFunction plugin = getKernelFunction(promptTemplateFactory, configPath,
promptPath);
plugins.put(dir.getName(), plugin);
} catch (IOException e) {
@@ -192,8 +196,10 @@ public class KernelPluginFactory {
);
}
private static KernelFunction getKernelFunction(String pluginDirectoryName,
PromptTemplateFactory promptTemplateFactory, File configPath, File promptPath)
private static KernelFunction getKernelFunction(
PromptTemplateFactory promptTemplateFactory,
File configPath,
File promptPath)
throws IOException {
PromptTemplateConfig config = new ObjectMapper().readValue(configPath,
PromptTemplateConfig.class);
@@ -202,11 +208,13 @@ public class KernelPluginFactory {
String template = new String(Files.readAllBytes(promptPath.toPath()),
Charset.defaultCharset());
return getKernelFunction(pluginDirectoryName, promptTemplateFactory, config, template);
return getKernelFunction(promptTemplateFactory, config, template);
}
private static KernelFunction getKernelFunction(String pluginDirectoryName,
PromptTemplateFactory promptTemplateFactory, PromptTemplateConfig config, String template) {
private static KernelFunction getKernelFunction(
PromptTemplateFactory promptTemplateFactory,
PromptTemplateConfig config,
String template) {
PromptTemplate promptTemplate;
if (promptTemplateFactory != null) {
@@ -215,10 +223,12 @@ public class KernelPluginFactory {
promptTemplate = new KernelPromptTemplateFactory().tryCreate(config);
}
return new Builder().withName(config.getName()).withDescription(config.getDescription())
return new KernelFunctionFromPrompt.Builder()
.withName(config.getName())
.withDescription(config.getDescription())
.withExecutionSettings(config.getExecutionSettings())
.withInputParameters(config.getInputVariables()
).withPromptTemplate(promptTemplate)
.withInputParameters(config.getInputVariables())
.withPromptTemplate(promptTemplate)
.withTemplate(template)
.withTemplateFormat(config.getTemplateFormat())
.withOutputVariable(config.getOutputVariable())
@@ -226,9 +236,13 @@ public class KernelPluginFactory {
.build();
}
public static KernelPlugin importPluginFromResourcesDirectory(String parentDirectory,
String pluginDirectoryName, String functionName,
PromptTemplateFactory promptTemplateFactory, @Nullable Class<?> clazz) {
@Nullable
public static KernelPlugin importPluginFromResourcesDirectory(
String parentDirectory,
String pluginDirectoryName,
String functionName,
PromptTemplateFactory promptTemplateFactory,
Class<?> clazz) {
String template = getTemplatePrompt(parentDirectory, pluginDirectoryName, functionName,
clazz);
@@ -236,7 +250,12 @@ public class KernelPluginFactory {
PromptTemplateConfig promptTemplateConfig = getPromptTemplateConfig(parentDirectory,
pluginDirectoryName, functionName, clazz);
KernelFunction function = getKernelFunction(pluginDirectoryName, promptTemplateFactory,
if (promptTemplateConfig == null) {
LOGGER.warn("Unable to load prompt template config for " + functionName + " in "
+ pluginDirectoryName);
return null;
}
KernelFunction function = getKernelFunction(promptTemplateFactory,
promptTemplateConfig, template);
HashMap<String, KernelFunction> plugins = new HashMap<>();
@@ -250,8 +269,11 @@ public class KernelPluginFactory {
);
}
private static String getTemplatePrompt(String pluginDirectory, String pluginName,
String functionName, @Nullable Class clazz) {
private static String getTemplatePrompt(
String pluginDirectory,
String pluginName,
String functionName,
Class<?> clazz) {
String promptFileName =
pluginDirectory + File.separator + pluginName + File.separator + functionName
+ File.separator + PROMPT_FILE;
@@ -265,14 +287,17 @@ public class KernelPluginFactory {
}
}
private static String getFileContents(String file, @Nullable Class clazz)
private static String getFileContents(String file, Class<?> clazz)
throws FileNotFoundException {
return EmbeddedResourceLoader.readFile(file, clazz, ResourceLocation.CLASSPATH_ROOT,
ResourceLocation.CLASSPATH, ResourceLocation.FILESYSTEM);
}
private static PromptTemplateConfig getPromptTemplateConfig(String pluginDirectory,
String pluginName, String functionName, @Nullable Class clazz) {
@Nullable
private static PromptTemplateConfig getPromptTemplateConfig(
String pluginDirectory,
String pluginName, String functionName,
Class<?> clazz) {
String configFileName =
pluginDirectory + File.separator + pluginName + File.separator + functionName
+ File.separator + CONFIG_FILE;
@@ -1,17 +1,22 @@
package com.microsoft.semantickernel.plugin;
import javax.annotation.Nullable;
public class KernelReturnParameterMetadata<T> {
@Nullable
private final String description;
private final Class<T> parameterType;
public KernelReturnParameterMetadata(
@Nullable
String description,
Class<T> parameterType) {
this.description = description;
this.parameterType = parameterType;
}
@Nullable
public String getDescription() {
return description;
}
@@ -1,21 +1,25 @@
package com.microsoft.semantickernel.semanticfunctions;
import reactor.util.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import reactor.util.annotation.NonNull;
public class AggregatorPromptTemplateFactory implements PromptTemplateFactory {
private final List<PromptTemplateFactory> templateFactories;
public AggregatorPromptTemplateFactory(List<PromptTemplateFactory> templateFactories) {
this.templateFactories = new ArrayList<>(templateFactories);
}
@Override
public PromptTemplate tryCreate(@NonNull PromptTemplateConfig templateConfig) {
for (PromptTemplateFactory factory : templateFactories) {
try {
return factory.tryCreate(templateConfig);
} catch (UnknownTemplateFormatException ignored) { }
} catch (UnknownTemplateFormatException ignored) {
// No-op
}
}
throw new UnknownTemplateFormatException(templateConfig.getTemplateFormat());
@@ -1,17 +1,18 @@
package com.microsoft.semantickernel.semanticfunctions;
import com.microsoft.semantickernel.templateengine.handlebars.HandlebarsPromptTemplate;
import reactor.util.annotation.NonNull;
import java.util.Locale;
import reactor.util.annotation.NonNull;
public class HandlebarsPromptTemplateFactory implements PromptTemplateFactory {
public static final String HANDLEBARS_TEMPLATE_FORMAT = "handlebars";
@Override
public PromptTemplate tryCreate(@NonNull PromptTemplateConfig templateConfig) {
if (templateConfig.getTemplateFormat() != null &&
HANDLEBARS_TEMPLATE_FORMAT.equals(templateConfig.getTemplateFormat().toLowerCase(Locale.ROOT))) {
HANDLEBARS_TEMPLATE_FORMAT.equals(
templateConfig.getTemplateFormat().toLowerCase(Locale.ROOT))) {
return new HandlebarsPromptTemplate(templateConfig);
}
@@ -1,6 +1,8 @@
package com.microsoft.semantickernel.semanticfunctions;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.microsoft.semantickernel.exceptions.SKException;
import javax.annotation.Nullable;
public class InputVariable {
@@ -10,27 +12,31 @@ public class InputVariable {
@JsonProperty("type")
private String type;
@Nullable
@JsonProperty("description")
private String description;
@JsonProperty("default")
@Nullable
private String defaultValue;
@JsonProperty("is_required")
private boolean isRequired;
public InputVariable() {
}
public InputVariable(
String name) {
public InputVariable(String name) {
this.name = name;
this.type = String.class.getName();
this.description = null;
this.defaultValue = null;
this.isRequired = true;
}
public InputVariable(
String name,
String type,
@Nullable
String description,
@Nullable
String defaultValue,
boolean isRequired) {
this.name = name;
@@ -56,6 +62,7 @@ public class InputVariable {
this.type = type;
}
@Nullable
public String getDescription() {
return description;
}
@@ -64,6 +71,7 @@ public class InputVariable {
this.description = description;
}
@Nullable
public String getDefaultValue() {
return defaultValue;
}
@@ -79,4 +87,14 @@ public class InputVariable {
public void setRequired(boolean required) {
isRequired = required;
}
public Class<?> getTypeClass() {
try {
return Thread.currentThread().getContextClassLoader().loadClass(type);
} catch (ClassNotFoundException e) {
throw new SKException(
"Could not load class for type: " + type + " when for input variable " + name +
", note this needs to be a fully qualified class name, i.e 'java.lang.String'.");
}
}
}
@@ -16,20 +16,18 @@ import com.microsoft.semantickernel.orchestration.MethodDetails;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter.NoopConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.plugin.KernelParameterMetadata;
import com.microsoft.semantickernel.plugin.KernelReturnParameterMetadata;
import com.microsoft.semantickernel.plugin.annotations.DefineKernelFunction;
import com.microsoft.semantickernel.plugin.annotations.KernelFunctionParameter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -49,8 +47,10 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
private KernelFunctionFromMethod(
ImplementationFunc implementationFunc,
String functionName,
@Nullable
String description,
List<KernelParameterMetadata> parameters,
@Nullable
List<KernelParameterMetadata<?>> parameters,
KernelReturnParameterMetadata<?> returnParameter) {
super(
new KernelFunctionMetadata(
@@ -68,8 +68,12 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
public <T> Mono<FunctionResult<T>> invokeAsync(
Kernel kernel,
@Nullable KernelArguments arguments,
ContextVariableType<T> variableType) {
return function.invoke(kernel, this, arguments, kernel.getHookService());
@Nullable ContextVariableType<T> variableType) {
return invokeAsync(
kernel,
arguments,
kernel.getHookService(),
variableType);
}
@Override
@@ -77,13 +81,41 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
Kernel kernel,
@Nullable KernelArguments arguments,
@Nullable KernelHooks kernelHooks,
ContextVariableType<T> variableType) {
@Nullable ContextVariableType<T> variableType) {
if (kernelHooks == null) {
kernelHooks = kernel.getHookService();
}
return function.invoke(kernel, this, arguments, kernelHooks);
return function
.invoke(kernel, this, arguments, kernelHooks)
.map(it -> convertVariableToReturnType(variableType, it));
}
private <T> FunctionResult convertVariableToReturnType(
@Nullable ContextVariableType<T> variableType,
FunctionResult<Object> it) {
if (variableType != null) {
return new FunctionResult<>(
variableType.of(it.getResult()),
it.getMetadata());
} else {
Object result = it.getResult();
if (result != null && getMetadata().getReturnParameter().getParameterType()
.isAssignableFrom(result.getClass())) {
return new FunctionResult<>(
new ContextVariable<>(
new ContextVariableType<>(
new NoopConverter(
getMetadata().getReturnParameter().getParameterType()),
getMetadata().getReturnParameter().getParameterType()
),
it.getResult())
);
}
throw new RuntimeException("Unable to convert result to type: "
+ getMetadata().getReturnParameter().getParameterType().getName());
}
}
public interface ImplementationFunc {
@@ -100,9 +132,13 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
public static KernelFunction create(
Method method,
Object target,
@Nullable
String functionName,
@Nullable
String description,
List<KernelParameterMetadata> parameters,
@Nullable
List<KernelParameterMetadata<?>> parameters,
@Nullable
KernelReturnParameterMetadata<?> returnParameter) {
MethodDetails methodDetails = getMethodDetails(functionName, method, target);
@@ -129,6 +165,7 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
private static MethodDetails getMethodDetails(
@Nullable
String functionName,
Method method,
Object target) {
@@ -142,12 +179,19 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
returnDescription = annotation.returnDescription();
}
if (functionName == null) {
functionName = method.getName();
}
return new MethodDetails(
functionName,
description,
getFunction(method, target),
getParameters(method),
new KernelReturnParameterMetadata<>(returnDescription, method.getReturnType())
new KernelReturnParameterMetadata<>(
returnDescription,
method.getReturnType()
)
);
}
@@ -193,11 +237,30 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
});
return r
.map(it -> new FunctionResult<>(ContextVariable.of(it)))
.map(it -> {
return new FunctionResult(
new ContextVariable(
new ContextVariableType(
new NoopConverter(
function
.getMetadata()
.getReturnParameter()
.getParameterType()
),
function
.getMetadata()
.getReturnParameter()
.getParameterType()
),
it));
})
.map(it -> {
FunctionInvokedEvent<T> updatedResult = kernelHooks
.executeHooks(
new FunctionInvokedEvent<>(function, updatedArguments, it));
new FunctionInvokedEvent<>(
function,
updatedArguments,
it));
return updatedResult.getResult();
});
} catch (Exception e) {
@@ -239,6 +302,7 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
.subscribeOn(Schedulers.boundedElastic()));
}
@Nullable
private static Function<Parameter, Object> getParameters(
Method method,
@Nullable
@@ -253,6 +317,7 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
};
}
@Nullable
private static Object getArgumentValue(
Method method,
@Nullable KernelArguments context,
@@ -265,15 +330,16 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
KernelFunctionParameter annotation =
parameter.getAnnotation(KernelFunctionParameter.class);
if (annotation != null) {
// Convert from the defaultValue, which is a String to the argument type
// Expectation here is that the fromPromptString method will be able to handle a null or empty string
Class<?> type = annotation.type();
ContextVariableType<?> cvType = ContextVariableTypes.getDefaultVariableTypeForClass(type);
if (cvType != null) {
String defaultValue = annotation.defaultValue();
Object value = cvType.getConverter().fromPromptString(defaultValue);
arg = ContextVariable.of(value);
}
// Convert from the defaultValue, which is a String to the argument type
// Expectation here is that the fromPromptString method will be able to handle a null or empty string
Class<?> type = annotation.type();
ContextVariableType<?> cvType = ContextVariableTypes.getDefaultVariableTypeForClass(
type);
if (cvType != null) {
String defaultValue = annotation.defaultValue();
Object value = cvType.getConverter().fromPromptString(defaultValue);
arg = ContextVariable.untypedOf(value, cvType.getConverter());
}
if (arg != null && NO_DEFAULT_VALUE.equals(arg.getValue())) {
if (!annotation.required()) {
@@ -331,19 +397,22 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
Object value = arg;
if (parameter.getType().isAssignableFrom(arg.getValue().getClass())) {
return arg.getValue();
}
if (arg != null) {
if (isPrimative(arg.getValue().getClass(), parameter.getType())) {
return arg.getValue();
}
if (parameter.getType().isAssignableFrom(arg.getType().getClazz())) {
return arg.getValue();
}
ContextVariableTypeConverter<?> c = arg.getType().getConverter();
if (isPrimative(arg.getType().getClazz(), parameter.getType())) {
return arg.getValue();
}
Object converted = c.toObject(arg.getValue(), parameter.getType());
if (converted != null) {
return converted;
ContextVariableTypeConverter<?> c = arg.getType().getConverter();
Object converted = c.toObject(arg.getValue(), parameter.getType());
if (converted != null) {
return converted;
}
}
// Well-known types only
@@ -366,6 +435,7 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
return value;
}
@SuppressWarnings("OperatorPrecedence")
private static boolean isPrimative(Class<?> argType, Class<?> param) {
return (argType == Byte.class || argType == byte.class) && (param == Byte.class
|| param == byte.class) ||
@@ -415,81 +485,7 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
+ " was invoked with a required context variable missing and no default value.";
}
private static Set<Parameter> determineInputArgs(Method method) {
// Something is bound to the input if either:
// - it is annotated with @SKFunctionInputAttribute
// - SKFunctionParameters annotation has a name of "input"
// - the arg name is "input"
// - there is only 1 string argument to the function
// Get all parameters annotated with SKFunctionInputAttribute
List<Parameter> annotated =
Arrays.stream(method.getParameters())
.filter(it -> it.isAnnotationPresent(KernelFunctionParameter.class))
.collect(Collectors.toList());
if (annotated.size() > 1) {
LOGGER.warn(
"Multiple arguments of "
+ method.getName()
+ " have the @SKFunctionInputAttribute annotation. This is likely an"
+ " error.");
}
// Get all parameters annotated with SKFunctionParameters with a name of "input"
List<Parameter> annotatedWithName =
Arrays.stream(method.getParameters())
.filter(it -> it.isAnnotationPresent(KernelFunctionParameter.class))
.filter(it -> it.getName().equals("input"))
.collect(Collectors.toList());
if (annotatedWithName.size() > 1) {
LOGGER.warn(
"Multiple arguments of "
+ method.getName()
+ " have the name input. This is likely an error.");
}
// Get all parameters named "input", this will frequently fail as compilers strip out
// argument names
List<Parameter> calledInput =
Arrays.stream(method.getParameters())
.filter(it -> getGetVariableName(it).equals("input"))
.collect(Collectors.toList());
// Get parameter if there is only 1 string, and it has not been annotated with
// SKFunctionParameters
List<Parameter> soloString =
Arrays.stream(method.getParameters())
.filter(it -> it.getType().equals(String.class))
.filter(
it ->
!(it.isAnnotationPresent(KernelFunctionParameter.class)
&& !it.getAnnotation(KernelFunctionParameter.class)
.name()
.isEmpty()))
.collect(Collectors.toList());
if (soloString.size() > 1) {
soloString.clear();
}
Set<Parameter> params = new HashSet<>();
params.addAll(annotated);
params.addAll(annotatedWithName);
params.addAll(calledInput);
params.addAll(soloString);
if (params.size() > 1) {
LOGGER.warn(
"Multiple arguments of "
+ method.getName()
+ " are bound to the input variable. This is likely an error.");
}
return params;
}
private static List<KernelParameterMetadata> getParameters(Method method) {
private static List<KernelParameterMetadata<?>> getParameters(Method method) {
return
Arrays.stream(method
.getParameters())
@@ -497,7 +493,7 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
.collect(Collectors.toList());
}
private static KernelParameterMetadata toKernelParameterMetadata(Parameter parameter) {
private static KernelParameterMetadata<?> toKernelParameterMetadata(Parameter parameter) {
KernelFunctionParameter annotation = parameter.getAnnotation(
KernelFunctionParameter.class);
@@ -505,19 +501,22 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
String description = null;
String defaultValue = null;
boolean isRequired = true;
Class<?> type = parameter.getType();
if (annotation != null) {
name = annotation.name();
description = annotation.description();
defaultValue = annotation.defaultValue();
isRequired = annotation.required();
type = annotation.type();
}
return new KernelParameterMetadata(
return new KernelParameterMetadata<>(
name,
description,
null,
defaultValue, isRequired
type,
defaultValue,
isRequired
);
}
}
@@ -8,6 +8,7 @@ import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.TextAIService;
import com.microsoft.semantickernel.chatcompletion.AuthorRole;
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
import com.microsoft.semantickernel.exceptions.SKException;
import com.microsoft.semantickernel.hooks.FunctionInvokedEvent;
import com.microsoft.semantickernel.hooks.FunctionInvokingEvent;
import com.microsoft.semantickernel.hooks.KernelHooks;
@@ -21,6 +22,7 @@ import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes;
import com.microsoft.semantickernel.orchestration.contextvariables.DefaultKernelArguments;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.services.AIServiceSelection;
import com.microsoft.semantickernel.textcompletion.TextGenerationService;
@@ -28,6 +30,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,7 +50,7 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
Map<String, PromptExecutionSettings> executionSettings) {
super(
new KernelFunctionMetadata(
promptConfig.getName(),
getName(promptConfig),
promptConfig.getDescription(),
promptConfig.getKernelParametersMetadata(),
promptConfig.getKernelReturnParameterMetadata()
@@ -57,6 +60,14 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
this.template = template;
}
private static String getName(PromptTemplateConfig promptConfig) {
if (promptConfig.getName() == null) {
return UUID.randomUUID().toString();
} else {
return promptConfig.getName();
}
}
public static KernelFunction create(
PromptTemplateConfig promptConfig
) {
@@ -91,10 +102,17 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
private <T> Flux<FunctionResult<T>> invokeInternalAsync(
Kernel kernel,
@Nullable KernelArguments arguments,
@Nullable KernelArguments argumentsIn,
KernelHooks kernelHooks,
ContextVariableType<T> variableType) {
KernelArguments arguments;
if (argumentsIn == null) {
arguments = new DefaultKernelArguments();
} else {
arguments = argumentsIn;
}
PromptRenderingEvent preRenderingHookResult = kernelHooks
.executeHooks(new PromptRenderingEvent(this, arguments));
@@ -121,6 +139,11 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
args
);
if (aiServiceSelection == null) {
throw new IllegalStateException(
"Failed to initialise aiService, could not find any TextAIService implementations");
}
AIService client = aiServiceSelection.getService();
if (client == null) {
throw new IllegalStateException(
@@ -162,8 +185,13 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
);
} else if (chatMessageContent.getAuthorRole()
== AuthorRole.TOOL) {
String content = chatMessageContent.getContent();
if (content == null || content.isEmpty()) {
return Flux.error(new IllegalStateException(
"Tool message content is empty"));
}
Mono<FunctionResult<String>> toolResult = invokeTool(kernel,
chatMessageContent.getContent());
content);
return toolResult.flux();
}
return Flux.empty();
@@ -238,8 +266,8 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
if (ex instanceof HttpResponseException
&& ((HttpResponseException) ex).getResponse().getStatusCode()
== 400
&& ex.getMessage()
.contains("parameters are not available" + " on")) {
&& ex.getMessage() != null
&& ex.getMessage().contains("parameters are not available" + " on")) {
LOGGER.warn(
"This error indicates that you have attempted"
+ " to use a chat completion model in a"
@@ -258,7 +286,13 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
Kernel kernel,
@Nullable KernelArguments arguments,
@Nullable KernelHooks kernelHooks,
@Nullable
ContextVariableType<T> variableType) {
if (variableType == null) {
return Mono.error(new SKException(
"Cannot invoke a prompt function without a variable return type. Ensure that there is"
+ " a valid type converter for the return type of the prompt function."));
}
if (kernelHooks == null) {
kernelHooks = kernel.getHookService();
}
@@ -270,7 +304,9 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
@Override
public <T> Mono<FunctionResult<T>> invokeAsync(
Kernel kernel,
@Nullable KernelArguments arguments,
@Nullable
KernelArguments arguments,
@Nullable
ContextVariableType<T> variableType) {
return invokeAsync(kernel, arguments, null, variableType);
}
@@ -300,6 +336,7 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
/*
* The jsonNode should represent: {"name":"search-search", "parameters": {"query":"Banksy"}}}
*/
@SuppressWarnings("StringSplitter")
private Mono<FunctionResult<String>> invokeFunction(Kernel kernel, JsonNode jsonNode) {
String name = jsonNode.get("name").asText();
String[] parts = name.split("-");
@@ -307,22 +344,25 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
String fnName = parts.length > 1 ? parts[1] : "";
JsonNode parameters = jsonNode.get("parameters");
if (parameters != null) {
KernelFunction kernelFunction = kernel.getPlugins().getFunction(pluginName, fnName);
if (kernelFunction == null) {
return Mono.empty();
try {
KernelFunction kernelFunction = kernel.getPlugins().getFunction(pluginName, fnName);
ContextVariableType<String> variableType = ContextVariableTypes.getDefaultVariableTypeForClass(
String.class);
Map<String, ContextVariable<?>> variables = new HashMap<>();
parameters.fields().forEachRemaining(entry -> {
String paramName = entry.getKey();
String paramValue = entry.getValue().asText();
ContextVariable<String> contextVariable = new ContextVariable<>(variableType,
paramValue);
variables.put(paramName, contextVariable);
});
KernelArguments arguments = KernelArguments.builder().withVariables(variables)
.build();
return kernelFunction.invokeAsync(kernel, arguments, variableType);
} catch (Exception e) {
return Mono.error(e);
}
ContextVariableType<String> variableType = ContextVariableTypes.getDefaultVariableTypeForClass(
String.class);
Map<String, ContextVariable<?>> variables = new HashMap<>();
parameters.fields().forEachRemaining(entry -> {
String paramName = entry.getKey();
String paramValue = entry.getValue().asText();
ContextVariable<String> contextVariable = new ContextVariable<>(variableType,
paramValue);
variables.put(paramName, contextVariable);
});
KernelArguments arguments = KernelArguments.builder().withVariables(variables).build();
return kernelFunction.invokeAsync(kernel, arguments, variableType);
}
return Mono.empty();
}
@@ -351,6 +391,14 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
@Nullable String templateFormat,
@Nullable PromptTemplateFactory promptTemplateFactory) {
if (templateFormat == null) {
templateFormat = PromptTemplateConfig.SEMANTIC_KERNEL_TEMPLATE_FORMAT;
}
if (functionName == null) {
functionName = UUID.randomUUID().toString();
}
return new KernelFunctionFromPrompt.Builder()
.withName(functionName)
.withDescription(description)
@@ -367,37 +415,50 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
public static final class Builder implements FromPromptBuilder {
@Nullable
private PromptTemplate promptTemplate;
@Nullable
private String name;
private Map<String, PromptExecutionSettings> executionSettings;
@Nullable
private Map<String, PromptExecutionSettings> executionSettings = null;
@Nullable
private String description;
@Nullable
private List<InputVariable> inputVariables;
@Nullable
private String template;
private String templateFormat = PromptTemplateConfig.SEMANTIC_KERNEL_TEMPLATE_FORMAT;
@Nullable
private OutputVariable outputVariable;
@Nullable
private PromptTemplateFactory promptTemplateFactory;
@Override
public FromPromptBuilder withName(String name) {
public FromPromptBuilder withName(@Nullable String name) {
this.name = name;
return this;
}
@Override
public FromPromptBuilder withInputParameters(List<InputVariable> inputVariables) {
this.inputVariables = new ArrayList<>(inputVariables);
public FromPromptBuilder withInputParameters(@Nullable List<InputVariable> inputVariables) {
if (inputVariables != null) {
this.inputVariables = new ArrayList<>(inputVariables);
} else {
this.inputVariables = null;
}
return this;
}
@Override
public FromPromptBuilder withPromptTemplate(PromptTemplate promptTemplate) {
public FromPromptBuilder withPromptTemplate(@Nullable PromptTemplate promptTemplate) {
this.promptTemplate = promptTemplate;
return this;
}
@Override
public FromPromptBuilder withExecutionSettings(
@Nullable
Map<String, PromptExecutionSettings> executionSettings) {
if (this.executionSettings == null) {
this.executionSettings = new HashMap<>();
@@ -411,7 +472,11 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
@Override
public FromPromptBuilder withDefaultExecutionSettings(
PromptExecutionSettings executionSettings) {
@Nullable PromptExecutionSettings executionSettings) {
if (executionSettings == null) {
return this;
}
if (this.executionSettings == null) {
this.executionSettings = new HashMap<>();
}
@@ -426,13 +491,13 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
}
@Override
public FromPromptBuilder withDescription(String description) {
public FromPromptBuilder withDescription(@Nullable String description) {
this.description = description;
return this;
}
@Override
public FromPromptBuilder withTemplate(String template) {
public FromPromptBuilder withTemplate(@Nullable String template) {
this.template = template;
return this;
}
@@ -445,13 +510,14 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
}
@Override
public FromPromptBuilder withOutputVariable(OutputVariable outputVariable) {
public FromPromptBuilder withOutputVariable(@Nullable OutputVariable outputVariable) {
this.outputVariable = outputVariable;
return this;
}
@Override
public FromPromptBuilder withPromptTemplateFactory(
@Nullable
PromptTemplateFactory promptTemplateFactory) {
this.promptTemplateFactory = promptTemplateFactory;
return this;
@@ -459,6 +525,11 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
@Override
public KernelFunction build() {
if (template == null) {
throw new IllegalStateException("Template must be provided");
}
PromptTemplateConfig config = new PromptTemplateConfig(
name,
template,
@@ -483,6 +554,11 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
}
public KernelFunction build(PromptTemplateConfig functionModel) {
if (promptTemplate == null) {
throw new IllegalStateException("Template must be provided");
}
return new KernelFunctionFromPrompt(
promptTemplate,
functionModel,
@@ -5,11 +5,12 @@ import static com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfi
import com.microsoft.semantickernel.templateengine.handlebars.HandlebarsPromptTemplate;
import com.microsoft.semantickernel.templateengine.semantickernel.DefaultPromptTemplate;
import javax.annotation.Nonnull;
import java.util.Locale;
import javax.annotation.Nonnull;
public class KernelPromptTemplateFactory implements PromptTemplateFactory {
@Override
public PromptTemplate tryCreate(@Nonnull PromptTemplateConfig templateConfig) {
switch (templateConfig.getTemplateFormat().toLowerCase(Locale.ROOT)) {
case SEMANTIC_KERNEL_TEMPLATE_FORMAT:
@@ -23,18 +23,15 @@ public class PromptTemplateConfig {
public static final int CURRENT_SCHEMA = 1;
public static final String DEFAULT_CONFIG_NAME = "default";
@Nullable
private String name;
private String template;
private String templateFormat;
@Nullable
private String description;
private List<InputVariable> inputVariables;
@Nullable
private OutputVariable outputVariable;
private Map<String, PromptExecutionSettings> executionSettings;
public static final String SEMANTIC_KERNEL_TEMPLATE_FORMAT = "semantic-kernel";
@@ -56,6 +53,7 @@ public class PromptTemplateConfig {
public PromptTemplateConfig(
@JsonProperty("schema")
int schema,
@Nullable
@JsonProperty("name")
String name,
@JsonProperty("template")
@@ -65,12 +63,16 @@ public class PromptTemplateConfig {
value = "template_format",
defaultValue = SEMANTIC_KERNEL_TEMPLATE_FORMAT)
String templateFormat,
@Nullable
@JsonProperty("description")
String description,
@Nullable
@JsonProperty("input_variables")
List<InputVariable> inputVariables,
@Nullable
@JsonProperty("output_variable")
OutputVariable outputVariable,
@Nullable
@JsonProperty("execution_settings")
Map<String, PromptExecutionSettings> executionSettings) {
this.name = name;
@@ -104,8 +106,19 @@ public class PromptTemplateConfig {
* @param outputVariable Output variable
* @param executionSettings Execution settings
*/
public PromptTemplateConfig(String name, String template, String templateFormat,
String description, List<InputVariable> inputVariables, OutputVariable outputVariable,
public PromptTemplateConfig(
@Nullable
String name,
String template,
@Nullable
String templateFormat,
@Nullable
String description,
@Nullable
List<InputVariable> inputVariables,
@Nullable
OutputVariable outputVariable,
@Nullable
Map<String, PromptExecutionSettings> executionSettings) {
this(
CURRENT_SCHEMA,
@@ -131,16 +144,16 @@ public class PromptTemplateConfig {
);
}
public List<KernelParameterMetadata> getKernelParametersMetadata() {
public List<KernelParameterMetadata<?>> getKernelParametersMetadata() {
if (inputVariables == null) {
return Collections.emptyList();
}
return inputVariables
.stream()
.map(inputVariable -> new KernelParameterMetadata(
.map(inputVariable -> new KernelParameterMetadata<>(
inputVariable.getName(),
inputVariable.getDescription(),
null,
inputVariable.getTypeClass(),
inputVariable.getDefaultValue(), inputVariable.isRequired()
))
.collect(Collectors.toList());
@@ -161,6 +174,7 @@ public class PromptTemplateConfig {
inputVariables.add(inputVariable);
}
@Nullable
public String getName() {
return name;
}
@@ -186,6 +200,7 @@ public class PromptTemplateConfig {
this.templateFormat = templateFormat;
}
@Nullable
public String getDescription() {
return description;
}
@@ -202,6 +217,7 @@ public class PromptTemplateConfig {
this.inputVariables = new ArrayList<>(inputVariables);
}
@Nullable
public OutputVariable getOutputVariable() {
return outputVariable;
}
@@ -210,8 +226,12 @@ public class PromptTemplateConfig {
this.outputVariable = outputVariable;
}
@Nullable
public Map<String, PromptExecutionSettings> getExecutionSettings() {
return Collections.unmodifiableMap(executionSettings);
if (executionSettings != null) {
return Collections.unmodifiableMap(executionSettings);
}
return null;
}
@@ -1,5 +1,7 @@
package com.microsoft.semantickernel.semanticfunctions;
import javax.annotation.Nullable;
public interface PromptTemplateFactory {
PromptTemplate tryCreate(PromptTemplateConfig templateConfig);
@@ -10,7 +12,7 @@ public interface PromptTemplateFactory {
class UnknownTemplateFormatException extends IllegalArgumentException {
public UnknownTemplateFormatException(String templateFormat) {
public UnknownTemplateFormatException(@Nullable String templateFormat) {
super("Unknown template format: " + templateFormat);
}
}
@@ -3,6 +3,7 @@ package com.microsoft.semantickernel.services;
import com.microsoft.semantickernel.AIService;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
import javax.annotation.Nullable;
/**
* The result of an AI service selection.
@@ -10,6 +11,7 @@ import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
public class AIServiceSelection<T extends AIService> {
private final T service;
@Nullable
private final PromptExecutionSettings settings;
/**
@@ -19,7 +21,7 @@ public class AIServiceSelection<T extends AIService> {
* @param settings The settings associated with the selected service. This may be null even if a
* service is selected..
*/
public AIServiceSelection(T service, PromptExecutionSettings settings) {
public AIServiceSelection(T service, @Nullable PromptExecutionSettings settings) {
this.service = service;
this.settings = settings;
}
@@ -39,6 +41,7 @@ public class AIServiceSelection<T extends AIService> {
* @return The settings associated with the selected service. This may be null even if a service
* is selected.
*/
@Nullable
public PromptExecutionSettings getSettings() {
return settings;
}
@@ -29,6 +29,7 @@ public class OrderedAIServiceSelector extends BaseAIServiceSelector {
}
@Nullable
@Override
public <T extends AIService> AIServiceSelection<T> trySelectAIService(
Class<T> serviceType,
@Nullable
@@ -38,9 +39,7 @@ public class OrderedAIServiceSelector extends BaseAIServiceSelector {
Map<Class<? extends AIService>, AIService> services) {
// Allow the execution settings from the kernel arguments to take precedence
Map<String, PromptExecutionSettings> executionSettings = null;
executionSettings = settingsFromArguments(arguments, executionSettings);
Map<String, PromptExecutionSettings> executionSettings = settingsFromArguments(arguments);
executionSettings = settingsFromFunctionSettings(function, executionSettings);
if (executionSettings == null || executionSettings.isEmpty()) {
@@ -105,12 +104,14 @@ public class OrderedAIServiceSelector extends BaseAIServiceSelector {
}
AIService service = getAnyService(serviceType);
PromptExecutionSettings settings;
PromptExecutionSettings settings = null;
if (executionSettings.containsKey(PromptExecutionSettings.DEFAULT_SERVICE_ID)) {
settings = executionSettings.get(PromptExecutionSettings.DEFAULT_SERVICE_ID);
} else {
settings = executionSettings.values().stream().findFirst().orElseGet(() -> null);
if (executionSettings != null && !executionSettings.isEmpty()) {
if (executionSettings.containsKey(PromptExecutionSettings.DEFAULT_SERVICE_ID)) {
settings = executionSettings.get(PromptExecutionSettings.DEFAULT_SERVICE_ID);
} else {
settings = executionSettings.values().stream().findFirst().orElseGet(() -> null);
}
}
if (service != null) {
@@ -125,6 +126,7 @@ public class OrderedAIServiceSelector extends BaseAIServiceSelector {
private static Map<String, PromptExecutionSettings> settingsFromFunctionSettings(
@Nullable
KernelFunction function,
@Nullable
Map<String, PromptExecutionSettings> executionSettings) {
if (executionSettings == null || executionSettings.isEmpty()) {
if (function != null) {
@@ -136,12 +138,12 @@ public class OrderedAIServiceSelector extends BaseAIServiceSelector {
@Nullable
private static Map<String, PromptExecutionSettings> settingsFromArguments(
@Nullable KernelArguments arguments,
Map<String, PromptExecutionSettings> executionSettings) {
@Nullable
KernelArguments arguments) {
if (arguments != null) {
executionSettings = arguments.getExecutionSettings();
return arguments.getExecutionSettings();
}
return executionSettings;
return null;
}
private AIService getServiceByModelId(String modelId) {
@@ -189,6 +191,7 @@ public class OrderedAIServiceSelector extends BaseAIServiceSelector {
return service;
}
@Nullable
AIService getAnyService(Class<? extends AIService> serviceType) {
List<AIService> services = getServices(serviceType);
if (services.isEmpty()) {
@@ -10,6 +10,7 @@ import com.microsoft.semantickernel.chatcompletion.ChatHistory;
import com.microsoft.semantickernel.chatcompletion.ChatMessageContent;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import com.microsoft.semantickernel.orchestration.contextvariables.DefaultKernelArguments;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.plugin.KernelParameterMetadata;
import com.microsoft.semantickernel.plugin.KernelPlugin;
@@ -42,22 +43,27 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
@Override
public Mono<String> renderAsync(Kernel kernel,
public Mono<String> renderAsync(
Kernel kernel,
@Nullable KernelArguments arguments) {
HandleBarsPromptTemplateHandler handler =
new HandleBarsPromptTemplateHandler(kernel, promptTemplate.getTemplate());
if (arguments == null) {
arguments = new DefaultKernelArguments();
}
return handler.render(arguments);
}
private static class MessageResolver implements ValueResolver {
@Override
@SuppressWarnings("NullAway")
public Object resolve(Object context, String name) {
if (context instanceof ChatMessageContent) {
if ("role".equals(name.toLowerCase())) {
if ("role".equalsIgnoreCase(name)) {
return ((ChatMessageContent) context).getAuthorRole().name();
} else if ("content".equals(name.toLowerCase())) {
} else if ("content".equalsIgnoreCase(name)) {
return ((ChatMessageContent) context).getContent();
}
}
@@ -67,7 +73,8 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
@Override
public Object resolve(Object context) {
if (context instanceof ChatMessageContent) {
return ((ChatMessageContent) context).getContent();
String result = ((ChatMessageContent) context).getContent();
return result != null ? result : UNRESOLVED;
}
return UNRESOLVED;
}
@@ -93,9 +100,9 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
}
if (context instanceof KernelFunction) {
KernelFunction function = (KernelFunction) context;
if ("pluginname".equals(name.toLowerCase())) {
if ("pluginname".equalsIgnoreCase(name)) {
return function.getSkillName();
} else if ("name".equals(name.toLowerCase())) {
} else if ("name".equalsIgnoreCase(name)) {
return function.getName();
}
}
@@ -120,19 +127,26 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
@Override
public Object resolve(Object context, String name) {
Object value = null;
if (context instanceof KernelArguments) {
return ((KernelArguments) context).get(name).getValue();
ContextVariable<?> variable = ((KernelArguments) context).get(name);
value = variable != null ? variable.getValue() : null;
}
if (context instanceof ContextVariable) {
return ((ContextVariable<?>) context).getValue();
value = ((ContextVariable<?>) context).getValue();
}
if (value == null) {
return UNRESOLVED;
} else {
return value;
}
return UNRESOLVED;
}
@Override
public Object resolve(Object context) {
if (context instanceof ContextVariable) {
return ((ContextVariable<?>) context).getValue();
Object result = ((ContextVariable<?>) context).getValue();
return result != null ? result : UNRESOLVED;
}
return UNRESOLVED;
}
@@ -182,7 +196,7 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
String.format(
"<function pluginName=\"%s\" name=\"%s\" description=\"%s\">",
pluginName, functionName, description));
List<KernelParameterMetadata> parameters = function.getMetadata().getParameters();
List<KernelParameterMetadata<?>> parameters = function.getMetadata().getParameters();
parameters.forEach(p -> {
sb.append(String.format(
"<parameter name=\"%s\" description=\"%s\" defaultValue=\"%s\" isRequired=\"%s\" type=\"%s\"/>",
@@ -116,7 +116,7 @@ public class CodeTokenizer {
for (int nextCharCursor = 1; nextCharCursor < text.length(); nextCharCursor++) {
char currentChar = nextChar;
nextChar = text.charAt(nextCharCursor);
if (skipNextChar) {
skipNextChar = false;
continue;
@@ -161,7 +161,7 @@ public class CodeTokenizer {
spaceSeparatorFound = false;
} else if (currentChar == namedArgValuePrefix
&& currentTokenType == TokenTypes.NamedArg) {
blocks.add(new NamedArgBlock(currentTokenContent.toString()));
blocks.add(NamedArgBlock.from(currentTokenContent.toString()));
currentTokenContent = new StringBuilder();
currentTokenType = TokenTypes.None;
spaceSeparatorFound = false;
@@ -195,7 +195,7 @@ public class CodeTokenizer {
} else if (currentTokenType == TokenTypes.NamedArg && namedArgSeparatorFound
&& namedArgValuePrefix != 0) {
blocks.add(
new NamedArgBlock(currentTokenContent.toString()));
NamedArgBlock.from(currentTokenContent.toString()));
currentTokenContent = new StringBuilder();
namedArgSeparatorFound = false;
namedArgValuePrefix = '\0';
@@ -277,7 +277,7 @@ public class CodeTokenizer {
break;
case NamedArg:
blocks.add(new NamedArgBlock(currentTokenContent.toString()));
blocks.add(NamedArgBlock.from(currentTokenContent.toString()));
break;
case None:
@@ -305,6 +305,7 @@ public class CodeTokenizer {
return c == Symbols.DblQuote || c == Symbols.SglQuote || c == Symbols.EscapeChar;
}
@SuppressWarnings("NullAway")
@Nullable
private static NamedArgBlock getNamedArg(String tokenContent) {
@@ -314,7 +315,6 @@ public class CodeTokenizer {
if (Verify.isNullOrEmpty(name) || Verify.isNullOrEmpty(value)) {
return null;
}
NamedArgBlock block = new NamedArgBlock(tokenContent, name, value);
if (block.isValid()) {
return block;
@@ -63,10 +63,8 @@ public class DefaultPromptTemplate implements PromptTemplate {
/// Augments <paramref name="config"/>'s <see cref="PromptTemplateConfig.InputVariables"/> with any variables
/// not already contained there but that are referenced in the prompt template.
/// </summary>
@SuppressWarnings("NullAway")
private void addMissingInputVariables(List<Block> blocks) {
if (promptTemplate == null) {
return;
}
// Add all of the existing input variables to our known set. We'll avoid adding any
// dynamically discovered input variables with the same name.
Set<String> seen = new HashSet<>();
@@ -84,7 +82,8 @@ public class DefaultPromptTemplate implements PromptTemplate {
if (block.getType() == BlockTypes.Variable) {
name = ((VarBlock) block).getName();
} else if (block.getType() == BlockTypes.NamedArg) {
name = ((NamedArgBlock) block).getVarBlock().getName();
VarBlock blockName = ((NamedArgBlock) block).getVarBlock();
name = blockName == null ? null : blockName.getName();
}
if (!Verify.isNullOrEmpty(name) && !seen.contains(name)) {
@@ -7,10 +7,12 @@ import javax.annotation.Nullable;
public class TemplateException extends SKException {
@Nonnull private final ErrorCodes errorCode;
@Nonnull
private final ErrorCodes errorCode;
/**
* Initializes a new instance of the {@code TemplateException} class with a provided error code.
* Initializes a new instance of the {@code TemplateException} class with a provided error
* code.
*
* @param error The error code.
*/
@@ -19,26 +21,28 @@ public class TemplateException extends SKException {
}
/**
* Initializes a new instance of the {@code TemplateException} class with a provided error code.
* Initializes a new instance of the {@code TemplateException} class with a provided error
* code.
*
* @param errorCode The error code.
* @param message The exception message.
* @param message The exception message.
*/
public TemplateException(@Nonnull ErrorCodes errorCode, @Nullable String message) {
this(errorCode, message, null);
}
/**
* Initializes a new instance of the {@code TemplateException} class with a provided error code.
* Initializes a new instance of the {@code TemplateException} class with a provided error
* code.
*
* @param errorCode The error code.
* @param message The exception message.
* @param errorCode The error code.
* @param message The exception message.
* @param innerException The exception that is the cause of the current exception.
*/
public TemplateException(
@Nonnull ErrorCodes errorCode,
@Nullable String message,
@Nullable Throwable innerException) {
@Nonnull ErrorCodes errorCode,
@Nullable String message,
@Nullable Throwable innerException) {
super(formatDefaultMessage(errorCode.getMessage(), message), innerException);
this.errorCode = errorCode;
}
@@ -57,19 +61,6 @@ public class TemplateException extends SKException {
// spotless:off
/**
* <p>
* Translate the error code into a default message.
* </p>
*/
//spotless:on
private static String getDefaultMessage(
@Nonnull ErrorCodes errorCode, @Nullable String message) {
return String.format("%s: %s", errorCode.getMessage(), message);
}
// spotless:off
/**
* <p>
* Error codes for {@code TemplateException}.
@@ -1,8 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.templateengine.semantickernel.blocks;
/** Base class for blocks parsed from a prompt template */
import javax.annotation.Nullable;
/**
* Base class for blocks parsed from a prompt template
*/
public abstract class Block {
private final String content;
private final BlockTypes type;
@@ -10,9 +15,11 @@ public abstract class Block {
* Base constructor
*
* @param content Block content
* @param type Block type
* @param type Block type
*/
public Block(String content, BlockTypes type) {
public Block(
@Nullable String content,
BlockTypes type) {
if (content == null) {
content = "";
}
@@ -3,19 +3,18 @@ package com.microsoft.semantickernel.templateengine.semantickernel.blocks;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.exceptions.SKException;
import com.microsoft.semantickernel.orchestration.FunctionResult;
import com.microsoft.semantickernel.orchestration.KernelFunctionMetadata;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes;
import com.microsoft.semantickernel.orchestration.contextvariables.DefaultKernelArguments;
import com.microsoft.semantickernel.orchestration.FunctionResult;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.templateengine.semantickernel.TemplateException;
import java.util.ArrayList;
import com.microsoft.semantickernel.templateengine.semantickernel.TemplateException.ErrorCodes;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import javax.annotation.Nullable;
import org.slf4j.Logger;
@@ -81,7 +80,7 @@ public final class CodeBlock extends Block implements CodeRendering {
@Override
public Mono<String> renderCodeAsync(Kernel kernel, @Nullable KernelArguments arguments) {
if (!this.isValid()) {
throw new TemplateException(TemplateException.ErrorCodes.SYNTAX_ERROR);
throw new TemplateException(ErrorCodes.SYNTAX_ERROR);
}
// this.Log.LogTrace("Rendering code: `{0}`", this.Content);
@@ -149,7 +148,9 @@ public final class CodeBlock extends Block implements CodeRendering {
/// <param name="arguments">The prompt rendering arguments.</param>
/// <returns>The function arguments.</returns>
/// <exception cref="KernelException">Occurs when any argument other than the first is not a named argument.</exception>
private KernelArguments enrichFunctionArguments(Kernel kernel, FunctionIdBlock fBlock,
private KernelArguments enrichFunctionArguments(
Kernel kernel,
FunctionIdBlock fBlock,
KernelArguments arguments) {
Block firstArg = this.tokens.get(1);
@@ -158,14 +159,14 @@ public final class CodeBlock extends Block implements CodeRendering {
.getFunction(fBlock.getPluginName(), fBlock.getFunctionName()).getMetadata();
// Check if the function has parameters to be set
if (functionMetadata.getParameters().size() == 0) {
if (functionMetadata.getParameters().isEmpty()) {
throw new IllegalArgumentException(
"Function " + fBlock.getPluginName() + "." + fBlock.getFunctionName()
+ " does not take any arguments but it is being called in the template with {this._tokens.Count - 1} arguments.");
}
String firstPositionalParameterName = null;
Object firstPositionalInputValue = null;
Object firstPositionalInputValue;
int namedArgsStartIndex = 1;
if (firstArg.getType() != BlockTypes.NamedArg) {
@@ -175,6 +176,12 @@ public final class CodeBlock extends Block implements CodeRendering {
firstPositionalInputValue = ((TextRendering) tokens.get(1)).render(arguments);
// Type check is avoided and marshalling is done by the function itself
if (firstPositionalInputValue == null) {
throw new SKException(
"Unexpected null value for first positional argument: " + tokens.get(1)
.getContent());
}
// Keep previous trust information when updating the input
arguments.put(firstPositionalParameterName,
ContextVariable.of(firstPositionalInputValue));
@@ -191,8 +198,7 @@ public final class CodeBlock extends Block implements CodeRendering {
// Check if the positional parameter clashes with a named parameter
if (firstPositionalParameterName != null &&
firstPositionalParameterName.toLowerCase(Locale.ROOT)
.equals(arg.getName().toLowerCase(Locale.ROOT))) {
firstPositionalParameterName.equalsIgnoreCase(arg.getName())) {
throw new SKException(
"Ambiguity found as a named parameter '{arg.Name}' cannot be set for the first parameter when there is also a positional value: '{firstPositionalInputValue}' provided. Function: {fBlock.PluginName}.{fBlock.FunctionName}");
}
@@ -31,7 +31,7 @@ public final class FunctionIdBlock extends Block implements TextRendering {
@Override
@Nullable
public String render(KernelArguments variables) {
public String render(@Nullable KernelArguments variables) {
return this.getContent();
}
@@ -12,10 +12,14 @@ public class NamedArgBlock extends Block implements TextRendering {
private static final Logger LOGGER = LoggerFactory.getLogger(NamedArgBlock.class);
@Nullable
private final String name;
@Nullable
private final String value;
private final VarBlock argNameAsVarBlock;
@Nullable
private final VarBlock varBlock;
@Nullable
private final ValBlock valBlock;
public NamedArgBlock(String content, String name, String value) {
@@ -34,20 +38,47 @@ public class NamedArgBlock extends Block implements TextRendering {
}
}
public NamedArgBlock(String content) {
protected NamedArgBlock(
String content,
@Nullable
String name,
@Nullable
String value,
VarBlock argNameAsVarBlock,
@Nullable
VarBlock varBlock,
@Nullable
ValBlock valBlock) {
super(content, NamedArg);
this.name = name;
this.value = value;
this.argNameAsVarBlock = argNameAsVarBlock;
this.varBlock = varBlock;
this.valBlock = valBlock;
}
this.name = tryGetName(content);
this.value = tryGetValue(content);
this.argNameAsVarBlock = new VarBlock(Symbols.VarPrefix + name);
public static NamedArgBlock from(String content) {
String name = tryGetName(content);
String value = tryGetValue(content);
VarBlock argNameAsVarBlock = new VarBlock(Symbols.VarPrefix + name);
if (value == null) {
throw new IllegalArgumentException("Unable to extract value from: " + content);
}
VarBlock varBlock;
ValBlock valBlock;
if (value.startsWith(String.valueOf(Symbols.VarPrefix))) {
this.varBlock = new VarBlock(value);
varBlock = new VarBlock(value);
valBlock = null;
} else {
this.valBlock = new ValBlock(value);
valBlock = new ValBlock(value);
varBlock = null;
}
return new NamedArgBlock(content, name, value, argNameAsVarBlock, varBlock, valBlock);
}
@Override
@@ -72,9 +103,8 @@ public class NamedArgBlock extends Block implements TextRendering {
return this.argNameAsVarBlock.isValid();
}
@Nullable
@Override
public String render(KernelArguments variables) {
public String render(@Nullable KernelArguments variables) {
return getContent();
}
@@ -84,10 +114,12 @@ public class NamedArgBlock extends Block implements TextRendering {
}
@Nullable
public String getName() {
return name;
}
@SuppressWarnings("NullAway")
public String getValue(KernelArguments arguments) {
boolean valueIsValidValBlock = this.valBlock != null && this.valBlock.isValid();
if (valueIsValidValBlock) {
@@ -120,6 +152,7 @@ public class NamedArgBlock extends Block implements TextRendering {
return splitAndGetPart(text, 1);
}
@SuppressWarnings("StringSplitter")
@Nullable
private static String splitAndGetPart(String text, int x) {
if (Verify.isNullOrEmpty(text)) {
@@ -2,6 +2,7 @@
package com.microsoft.semantickernel.templateengine.semantickernel.blocks;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import javax.annotation.Nullable;
public final class TextBlock extends Block implements TextRendering {
@@ -15,7 +16,7 @@ public final class TextBlock extends Block implements TextRendering {
}
@Override
public String render(KernelArguments variables) {
public String render(@Nullable KernelArguments variables) {
return super.getContent();
}
@@ -31,8 +31,7 @@ public final class ValBlock extends Block implements TextRendering {
}
@Override
@Nullable
public String render(KernelArguments variables) {
public String render(@Nullable KernelArguments variables) {
return value;
}
@@ -6,6 +6,7 @@ import com.microsoft.semantickernel.orchestration.contextvariables.KernelArgumen
import com.microsoft.semantickernel.templateengine.semantickernel.TemplateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
public final class VarBlock extends Block implements TextRendering {
@@ -23,7 +24,7 @@ public final class VarBlock extends Block implements TextRendering {
}
@Override
public String render(KernelArguments variables) {
public String render(@Nullable KernelArguments variables) {
if (variables == null) {
return "";
}
@@ -8,8 +8,4 @@ public class StreamingTextContent extends StreamingKernelContent<TextContent> {
super(content, 0, null, null);
}
@Override
public byte[] toByteArray() {
return new byte[0];
}
}
@@ -2,8 +2,6 @@ package com.microsoft.semantickernel.textcompletion;
import com.microsoft.semantickernel.KernelContent;
import com.microsoft.semantickernel.orchestration.FunctionResultMetadata;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import java.util.Map;
public class TextContent extends KernelContent<TextContent> {
@@ -21,6 +19,7 @@ public class TextContent extends KernelContent<TextContent> {
return content;
}
@Override
public String getContent() {
return content;
}
@@ -58,8 +58,11 @@ public interface TextGenerationService extends Buildable, TextAIService {
*/
abstract class Builder implements SemanticKernelBuilder<TextGenerationService> {
@Nullable
protected String modelId;
@Nullable
protected OpenAIAsyncClient client;
@Nullable
protected String serviceId;
public Builder withModelId(String modelId) {
@@ -77,6 +80,7 @@ public interface TextGenerationService extends Buildable, TextAIService {
return this;
}
@Override
public abstract TextGenerationService build();
}