Fixes from review and add type converters to the InvocationContext

This commit is contained in:
John Oliver
2024-02-07 20:28:10 +00:00
parent 4187b30a31
commit 5c10869c76
26 changed files with 421 additions and 269 deletions
@@ -20,7 +20,6 @@ import com.azure.core.util.BinaryData;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.util.JSONPObject;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.chatcompletion.AuthorRole;
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
@@ -42,7 +41,6 @@ import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariab
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes;
import com.microsoft.semantickernel.plugin.KernelParameterMetadata;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
@@ -99,7 +97,8 @@ public class OpenAIChatCompletion implements ChatCompletionService {
@Nullable InvocationContext invocationContext) {
List<ChatRequestMessage> chatRequestMessages = getChatRequestMessages(chatHistory);
List<FunctionDefinition> functions = kernel != null ? getFunctions(kernel) : Collections.emptyList();
List<FunctionDefinition> functions =
kernel != null ? getFunctions(kernel) : Collections.emptyList();
return internalChatMessageContentsAsync(
chatRequestMessages,
@@ -127,7 +126,8 @@ public class OpenAIChatCompletion implements ChatCompletionService {
List<FunctionDefinition> functions,
@Nullable InvocationContext invocationContext) {
ChatCompletionsOptions options = getCompletionsOptions(this, chatRequestMessages, functions, invocationContext);
ChatCompletionsOptions options = getCompletionsOptions(this, chatRequestMessages, functions,
invocationContext);
Mono<List<ChatMessageContent>> results =
internalChatMessageContentsAsync(options, invocationContext);
@@ -155,7 +155,7 @@ public class OpenAIChatCompletion implements ChatCompletionService {
ChatCompletionsOptions options,
@Nullable InvocationContext invocationContext) {
KernelHooks kernelHooks =
KernelHooks kernelHooks =
invocationContext != null && invocationContext.getKernelHooks() != null
? invocationContext.getKernelHooks()
: new KernelHooks();
@@ -283,7 +283,7 @@ public class OpenAIChatCompletion implements ChatCompletionService {
});
arguments = KernelFunctionArguments.builder().withVariables(variables).build();
}
ContextVariableType<String> variableType = ContextVariableTypes.getDefaultVariableTypeForClass(
ContextVariableType<String> variableType = ContextVariableTypes.getGlobalVariableTypeForClass(
String.class);
return kernelFunction.invokeAsync(kernel, arguments, variableType);
}
@@ -314,12 +314,12 @@ public class OpenAIChatCompletion implements ChatCompletionService {
MAX_RESULTS_PER_PROMPT));
}
ToolCallBehavior toolCallBehavior = invocationContext != null
ToolCallBehavior toolCallBehavior = invocationContext != null
? invocationContext.getToolCallBehavior()
: null;
List<ChatCompletionsToolDefinition> toolDefinitions =
chatCompletionsToolDefinitions(toolCallBehavior,functions);
chatCompletionsToolDefinitions(toolCallBehavior, functions);
if (toolDefinitions != null && !toolDefinitions.isEmpty()) {
options.setTools(toolDefinitions);
// TODO: options.setToolChoices(toolChoices);
@@ -432,7 +432,7 @@ public class OpenAIChatCompletion implements ChatCompletionService {
});
});
return functions;
}
}
private static FunctionDefinition toFunctionDefinition(KernelFunction function) {
String name = String.format("%s-%s", function.getSkillName(), function.getName());
@@ -464,7 +464,8 @@ public class OpenAIChatCompletion implements ChatCompletionService {
"{\"type\": \"object\", \"properties\": {");
parameters.forEach(parameter -> {
// make "param": {"type": "string", "description": "desc"},
sb.append(String.format("\"%s\": %s,", parameter.getName(), parameter.getDescription()));
sb.append(
String.format("\"%s\": %s,", parameter.getName(), parameter.getDescription()));
if (parameter.isRequired()) {
requiredParmeters.add(parameter.getName());
}
@@ -136,7 +136,7 @@ public class ConversationSummaryPlugin {
new KernelFunctionArguments.Builder()
.withInput(paragraph)
.build(),
ContextVariableTypes.getDefaultVariableTypeForClass(String.class));
ContextVariableTypes.getGlobalVariableTypeForClass(String.class));
})
.reduce("", (acc, next) -> {
return acc + "\n" + next.getResult();
@@ -88,7 +88,7 @@ public class Example06_TemplateLanguage {
var promptTemplate = new KernelPromptTemplateFactory()
.tryCreate(new PromptTemplateConfig(functionDefinition));
var renderedPrompt = promptTemplate.renderAsync(kernel, null).block();
var renderedPrompt = promptTemplate.renderAsync(kernel, null, null).block();
System.out.println(renderedPrompt);
var kindOfDay = KernelFunctionFactory
@@ -177,7 +177,7 @@ public class Example07_BingAndGooglePlugins {
.tryCreate(new PromptTemplateConfig(result));
System.out.println("---- Fetching information from Bing...");
var information = promptTemplate.renderAsync(kernel, null).block();
var information = promptTemplate.renderAsync(kernel, null, null).block();
System.out.println("Information found:");
System.out.println(information);
@@ -8,7 +8,6 @@ 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.KernelFunctionArguments;
import com.microsoft.semantickernel.orchestration.FunctionResult;
import com.microsoft.semantickernel.orchestration.KernelFunctionArguments;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
@@ -29,6 +28,7 @@ import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import reactor.core.publisher.Mono;
public class Example09_FunctionTypes {
@@ -78,7 +78,7 @@ public class Example09_FunctionTypes {
}
}
public static void main(String[] args) {
public static void main(String[] args) throws InterruptedException {
System.out.println("======== Method Function types ========");
@@ -130,6 +130,18 @@ public class Example09_FunctionTypes {
FunctionResult<?> result = kernel.invokeAsync(plugin.get("NoInputWithVoidResult"))
.block();
assert result == null;
CountDownLatch cdl = new CountDownLatch(1);
kernel
.invokeAsync(plugin.get("NoInputWithVoidResult"))
.doFinally(ignore -> cdl.countDown())
.subscribe(ignore -> {
throw new RuntimeException("No return expected");
});
cdl.await();
System.out.println(result != null ? result.getResult() : "null");
result = kernel.invokeAsync(plugin.get("NoInputTaskWithVoidResult"))
.block();
@@ -55,7 +55,7 @@ public class Example27_PromptFunctionsUsingChatGPT {
KernelFunctionArguments.builder()
.withVariable("input", "Jupiter")
.build(),
ContextVariableTypes.getDefaultVariableTypeForClass(String.class)
ContextVariableTypes.getGlobalVariableTypeForClass(String.class)
)
.block();
System.out.println(result.getResult());
@@ -90,7 +90,7 @@ public class Example30_ChatWithPrompts {
// This contains the context, ie a piece of a wikipedia page selected by the user.
String systemMessage = PromptTemplateFactory
.build(new PromptTemplateConfig(systemPromptTemplate))
.renderAsync(kernel, arguments)
.renderAsync(kernel, arguments, null)
.block();
System.out.println("------------------------------------\n" + systemMessage);
@@ -99,7 +99,7 @@ public class Example30_ChatWithPrompts {
// This contains the user request, ie "extract locations as a bullet point list"
String userMessage = PromptTemplateFactory
.build(new PromptTemplateConfig(userPromptTemplate))
.renderAsync(kernel, arguments)
.renderAsync(kernel, arguments, null)
.block();
System.out.println("------------------------------------\n" + userMessage);
@@ -68,7 +68,7 @@ public class Example56_TemplateMethodFunctionsWithMultipleArguments {
var promptTemplateFactory = new KernelPromptTemplateFactory();
var promptTemplate = promptTemplateFactory.tryCreate(
new PromptTemplateConfig(functionDefinition));
var renderedPrompt = promptTemplate.renderAsync(kernel, arguments).block();
var renderedPrompt = promptTemplate.renderAsync(kernel, arguments, null).block();
System.out.println(renderedPrompt);
// Run the prompt / prompt function
@@ -30,7 +30,7 @@ public class Example60_AdvancedMethodFunctions {
s -> null
);
ContextVariableTypes.DEFAULT_TYPES.putConverter(type);
ContextVariableTypes.addGlobalConverter(type);
var result = kernel
.invokeAsync(FunctionsChainingPlugin.PluginName, "Function1")
@@ -38,7 +38,7 @@ public class Example60_AdvancedMethodFunctions {
KernelFunctionArguments
.builder()
.build())
.withResultType(ContextVariableTypes.getDefaultVariableTypeForClass(MyCustomType.class))
.withResultType(ContextVariableTypes.getGlobalVariableTypeForClass(MyCustomType.class))
.block();
System.out.println("CustomType.Number: " + result.getResult().number); // 2
@@ -85,7 +85,7 @@ public class Example60_AdvancedMethodFunctions {
return kernel
.invokeAsync(PluginName, "Function2")
.withArguments(KernelFunctionArguments.builder().build())
.withResultType(ContextVariableTypes.getDefaultVariableTypeForClass(
.withResultType(ContextVariableTypes.getGlobalVariableTypeForClass(
Example60_AdvancedMethodFunctions.MyCustomType.class))
.flatMap(value -> {
// Process the result
@@ -12,6 +12,7 @@ import com.microsoft.semantickernel.orchestration.ToolCallBehavior;
import com.microsoft.semantickernel.orchestration.ToolCallBehavior.UnmodifiableToolCallBehavior;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes;
import java.util.function.BiConsumer;
import javax.annotation.Nullable;
import org.slf4j.Logger;
@@ -27,15 +28,17 @@ public class FunctionInvocation<T> extends Mono<FunctionResult<T>> {
private final KernelFunction<?> function;
private final Kernel kernel;
@Nullable
private KernelFunctionArguments arguments;
private final ContextVariableType<T> resultType;
@Nullable
private ContextVariableType<T> resultType;
private KernelFunctionArguments arguments;
@Nullable
private UnmodifiableKernelHooks hooks;
@Nullable
private PromptExecutionSettings promptExecutionSettings;
@Nullable
private UnmodifiableToolCallBehavior toolCallBehavior;
private final ContextVariableTypes contextVariableTypes = new ContextVariableTypes();
public FunctionInvocation(
@@ -43,6 +46,7 @@ public class FunctionInvocation<T> extends Mono<FunctionResult<T>> {
KernelFunction<T> function) {
this.function = function;
this.kernel = kernel;
this.resultType = null;
}
public FunctionInvocation(
@@ -53,6 +57,9 @@ public class FunctionInvocation<T> extends Mono<FunctionResult<T>> {
this.function = function;
this.kernel = kernel;
this.resultType = resultType;
if (resultType != null) {
contextVariableTypes.putConverter(resultType.getConverter());
}
}
public FunctionInvocation<T> withArguments(
@@ -96,6 +103,27 @@ public class FunctionInvocation<T> extends Mono<FunctionResult<T>> {
return this;
}
public FunctionInvocation<T> withTypeConverter(ContextVariableType<?> typeConverter) {
contextVariableTypes.putConverter(typeConverter.getConverter());
return this;
}
@Override
public void subscribe(CoreSubscriber<? super FunctionResult<T>> coreSubscriber) {
performSubscribe(
coreSubscriber,
kernel,
function,
arguments,
resultType,
new InvocationContext(
hooks,
promptExecutionSettings,
toolCallBehavior,
contextVariableTypes
));
}
@Nullable
private static UnmodifiableToolCallBehavior unmodifiableClone(
@Nullable
@@ -121,20 +149,6 @@ public class FunctionInvocation<T> extends Mono<FunctionResult<T>> {
}
}
@Override
public void subscribe(CoreSubscriber<? super FunctionResult<T>> coreSubscriber) {
performSubscribe(
coreSubscriber,
kernel,
function,
arguments,
resultType,
new InvocationContext(
hooks,
promptExecutionSettings,
toolCallBehavior
));
}
// Extracted to static to ensure mutable state is not used
private static <T> void performSubscribe(
@@ -148,6 +162,7 @@ public class FunctionInvocation<T> extends Mono<FunctionResult<T>> {
@Nullable
InvocationContext context
) {
if (variableType == null) {
LOGGER.debug(
"No variable type explicitly specified by calling 'withResultType' for function invocation: "
@@ -155,11 +170,12 @@ public class FunctionInvocation<T> extends Mono<FunctionResult<T>> {
+ " This may cause a runtime error (probably a ClassCastException) if the result type is not compatible with the expected type.");
}
function.invokeAsync(
function
.invokeAsync(
kernel,
arguments,
new KernelFunctionArguments(arguments),
null,
context)
new InvocationContext(context))
.handle(FunctionInvocation.convertToType(variableType))
.subscribe(coreSubscriber);
}
@@ -13,14 +13,6 @@ public class Verify {
return s == null || s.isEmpty();
}
/*
public static boolean isNullOrEmpty(@Nullable ContextVariable<?> s) {
return s == null
|| s.getValue() == null
|| s.getType().equals(String.class) && ((ContextVariable<String>) s).getValue()
.isEmpty();
}
*/
public static boolean isNullOrWhiteSpace(String s) {
return s == null || s.matches("^\\w+$");
}
@@ -5,6 +5,8 @@ import com.microsoft.semantickernel.builders.SemanticKernelBuilder;
import com.microsoft.semantickernel.hooks.KernelHooks;
import com.microsoft.semantickernel.hooks.KernelHooks.UnmodifiableKernelHooks;
import com.microsoft.semantickernel.orchestration.ToolCallBehavior.UnmodifiableToolCallBehavior;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import javax.annotation.Nullable;
@@ -19,20 +21,42 @@ public class InvocationContext implements Buildable {
private final PromptExecutionSettings promptExecutionSettings;
@Nullable
private final UnmodifiableToolCallBehavior toolCallBehavior;
private final ContextVariableTypes contextVariableTypes;
public InvocationContext(
@Nullable KernelHooks hooks,
@Nullable PromptExecutionSettings promptExecutionSettings,
@Nullable ToolCallBehavior toolCallBehavior) {
@Nullable ToolCallBehavior toolCallBehavior,
@Nullable ContextVariableTypes contextVariableTypes) {
this.hooks = unmodifiableClone(hooks);
this.promptExecutionSettings = promptExecutionSettings;
this.toolCallBehavior = unmodifiableClone(toolCallBehavior);
if (contextVariableTypes == null) {
this.contextVariableTypes = new ContextVariableTypes();
} else {
this.contextVariableTypes = new ContextVariableTypes(contextVariableTypes);
}
}
public InvocationContext(InvocationContext context) {
this.hooks = context.hooks;
this.promptExecutionSettings = context.promptExecutionSettings;
this.toolCallBehavior = context.toolCallBehavior;
public InvocationContext() {
this.hooks = null;
this.promptExecutionSettings = null;
this.toolCallBehavior = null;
this.contextVariableTypes = new ContextVariableTypes();
}
public InvocationContext(@Nullable InvocationContext context) {
if (context == null) {
this.hooks = null;
this.promptExecutionSettings = null;
this.toolCallBehavior = null;
this.contextVariableTypes = new ContextVariableTypes();
} else {
this.hooks = context.hooks;
this.promptExecutionSettings = context.promptExecutionSettings;
this.toolCallBehavior = context.toolCallBehavior;
this.contextVariableTypes = context.contextVariableTypes;
}
}
@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "returns UnmodifiableKernelHooks")
@@ -52,6 +76,10 @@ public class InvocationContext implements Buildable {
return toolCallBehavior;
}
public ContextVariableTypes getContextVariableTypes() {
return new ContextVariableTypes(contextVariableTypes);
}
public static Builder builder() {
return new Builder();
}
@@ -93,6 +121,8 @@ public class InvocationContext implements Buildable {
@Nullable
private UnmodifiableToolCallBehavior toolCallBehavior;
private final ContextVariableTypes contextVariableTypes = new ContextVariableTypes();
public Builder withKernelHooks(
@Nullable KernelHooks hooks) {
this.hooks = unmodifiableClone(hooks);
@@ -113,9 +143,17 @@ public class InvocationContext implements Buildable {
return this;
}
public Builder withContextVariableConverter(ContextVariableTypeConverter<?> converter) {
this.contextVariableTypes.putConverter(
converter
);
return this;
}
@Override
public InvocationContext build() {
return new InvocationContext(hooks, promptExecutionSettings, toolCallBehavior);
return new InvocationContext(hooks, promptExecutionSettings, toolCallBehavior,
contextVariableTypes);
}
}
@@ -172,7 +172,7 @@ public abstract class KernelFunction<T> implements Buildable {
@Nullable ContextVariableType<T> variableType,
@Nullable InvocationContext invocationContext);
public static interface FromPromptBuilder<T> {
public interface FromPromptBuilder<T> {
FromPromptBuilder<T> withName(@Nullable String name);
@@ -6,15 +6,14 @@ import com.microsoft.semantickernel.builders.SemanticKernelBuilder;
import com.microsoft.semantickernel.exceptions.SKException;
import com.microsoft.semantickernel.orchestration.contextvariables.CaseInsensitiveMap;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import reactor.util.annotation.NonNull;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import reactor.util.annotation.NonNull;
/**
* Context Variables is a data structure that holds temporary data while a task is being performed.
@@ -30,8 +29,13 @@ public class KernelFunctionArguments implements Buildable, Map<String, ContextVa
private final CaseInsensitiveMap<ContextVariable<?>> variables;
public KernelFunctionArguments(
@Nullable
Map<String, ContextVariable<?>> variables) {
this.variables = new CaseInsensitiveMap<>(variables);
if (variables == null) {
this.variables = new CaseInsensitiveMap<>();
} else {
this.variables = new CaseInsensitiveMap<>(variables);
}
}
public KernelFunctionArguments(@NonNull ContextVariable<?> content) {
@@ -169,7 +173,7 @@ public class KernelFunctionArguments implements Buildable, Map<String, ContextVa
public Set<Entry<String, ContextVariable<?>>> entrySet() {
return variables.entrySet();
}
public static Builder builder() {
return new KernelFunctionArguments.Builder();
}
@@ -179,7 +183,7 @@ public class KernelFunctionArguments implements Buildable, Map<String, ContextVa
*/
public static class Builder implements SemanticKernelBuilder<KernelFunctionArguments> {
private final Map<String,ContextVariable<?>> variables;
private final Map<String, ContextVariable<?>> variables;
public Builder() {
variables = new HashMap<>();
@@ -204,7 +208,25 @@ public class KernelFunctionArguments implements Buildable, Map<String, ContextVa
* @throws SKException if the content cannot be converted to a ContextVariable
*/
public Builder withInput(Object content) {
return withInput(ContextVariable.of(content));
return withInput(ContextVariable.ofGlobalType(content));
}
/**
* Builds an instance with the given content in the default main key
*
* @param content Entry to place in the "input" slot
* @param typeConverter Type converter for the content
* @return {$code this} Builder for fluent coding
* @throws SKException if the content cannot be converted to a ContextVariable
*/
public <T> Builder withInput(T content, ContextVariableTypeConverter<T> typeConverter) {
return withInput(new ContextVariable<>(
new ContextVariableType<>(
typeConverter,
typeConverter.getType()
),
content)
);
}
/**
@@ -231,7 +253,7 @@ public class KernelFunctionArguments implements Buildable, Map<String, ContextVa
}
/**
* Set variable
* Set variable, uses the default type converters
*
* @param key variable name
* @param value variable value
@@ -239,7 +261,27 @@ public class KernelFunctionArguments implements Buildable, Map<String, ContextVa
* @throws SKException if the value cannot be converted to a ContextVariable
*/
public Builder withVariable(String key, Object value) {
return withVariable(key, ContextVariable.of(value));
return withVariable(key, ContextVariable.ofGlobalType(value));
}
/**
* Set variable
*
* @param key variable name
* @param value variable value
* @param typeConverter Type converter for the value
* @return {$code this} Builder for fluent coding
* @throws SKException if the value cannot be converted to a ContextVariable
*/
public <T> Builder withVariable(String key, T value,
ContextVariableTypeConverter<T> typeConverter) {
return withVariable(key, new ContextVariable<>(
new ContextVariableType<>(
typeConverter,
typeConverter.getType()
),
value
));
}
@Override
@@ -1,13 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.orchestration.contextvariables;
import java.util.Objects;
import javax.annotation.Nullable;
import com.azure.ai.openai.models.CompletionsUsage;
import com.microsoft.semantickernel.exceptions.SKException;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter.NoopConverter;
import java.util.Arrays;
import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.Objects;
import javax.annotation.Nullable;
@@ -18,67 +16,21 @@ public class ContextVariable<T> {
@Nullable
private final T value;
public ContextVariable(ContextVariableType<T> type,
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);
}
@SuppressWarnings("rawtypes")
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,
ContextVariableType<T> requestedResultType) {
try {
return convert(
it,
requestedResultType.getClazz(),
new ContextVariableTypes(
Arrays.asList(requestedResultType.getConverter()))
);
} catch (Exception e) {
return convert(
it,
requestedResultType.getClazz(),
ContextVariableTypes.DEFAULT_TYPES
);
}
}
public static <T, U> ContextVariable<T> convert(
@Nullable
U it,
Class<T> requestedResultType) {
return convert(
it,
requestedResultType,
ContextVariableTypes.DEFAULT_TYPES
requestedResultType.getClazz(),
new ContextVariableTypes(Collections.singletonList(requestedResultType.getConverter()))
);
}
@@ -86,7 +38,12 @@ public class ContextVariable<T> {
@Nullable
U it,
Class<T> requestedResultType,
@Nullable
ContextVariableTypes contextVariableTypes) {
if (contextVariableTypes == null) {
contextVariableTypes = new ContextVariableTypes();
}
if (it == null) {
return new ContextVariable<>(
contextVariableTypes.getVariableTypeForClass(requestedResultType),
@@ -95,7 +52,9 @@ public class ContextVariable<T> {
if (requestedResultType.isAssignableFrom(it.getClass())) {
try {
return ContextVariable.of(requestedResultType, requestedResultType.cast(it));
return contextVariableTypes
.getVariableTypeForClass(requestedResultType)
.of(requestedResultType.cast(it));
} catch (Exception e) {
return new ContextVariable<>(
new ContextVariableType<>(
@@ -167,6 +126,31 @@ public class ContextVariable<T> {
throw new SKException("Unable to convert " + it.getClass() + " to " + requestedResultType);
}
public static ContextVariable<?> untypedOf(
@Nullable Object value,
Class<?> clazz,
ContextVariableTypes types) {
ContextVariableType<?> type = types.getVariableTypeForClass(clazz);
return new ContextVariable(type, value);
}
public static ContextVariable<CompletionsUsage> of(CompletionsUsage x) {
return ofValue(x);
}
public static ContextVariable<OffsetDateTime> of(OffsetDateTime x) {
return ofValue(x);
}
public static ContextVariable<String> of(String value) {
return ofValue(value);
}
public static ContextVariable<Object> ofGlobalType(Object x) {
return ofValue(x);
}
@Nullable
public T getValue() {
return value;
@@ -182,7 +166,6 @@ public class ContextVariable<T> {
}
}
public ContextVariableType<T> getType() {
return type;
}
@@ -199,19 +182,15 @@ public class ContextVariable<T> {
return value == null || value.toString().isEmpty();
}
public ContextVariable<T> cloneVariable() {
return new ContextVariable<>(type, value);
}
@SuppressWarnings("unchecked")
public static <T> ContextVariable<T> of(T value) {
private static <T> ContextVariable<T> ofValue(T value) {
Objects.requireNonNull(value, "value cannot be null");
if (value instanceof ContextVariable) {
return (ContextVariable<T>) value;
}
ContextVariableType<T> type = ContextVariableTypes.getDefaultVariableTypeForClass(
ContextVariableType<T> type = ContextVariableTypes.getGlobalVariableTypeForClass(
(Class<T>) value.getClass());
return new ContextVariable<>(type, value);
}
@@ -30,14 +30,21 @@ public class ContextVariableType<T> {
*/
public ContextVariable<T> of(@Nullable Object it) {
if (it == null) {
return ContextVariable.of(clazz, clazz.cast(null));
return new ContextVariable<>(
this,
clazz.cast(null)
);
}
if (clazz.isAssignableFrom(it.getClass())) {
return ContextVariable.of(clazz.cast(it), getConverter());
}
ContextVariableTypeConverter<T> converter = getConverter();
return ContextVariable.of(converter.getType(), converter.fromObject(it));
return new ContextVariable<>(
this,
converter.fromObject(it)
);
}
}
@@ -1,15 +1,6 @@
package com.microsoft.semantickernel.orchestration.contextvariables;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import com.microsoft.semantickernel.exceptions.SKException;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter.NoopConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.converters.BooleanVariableContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.converters.CharacterVariableContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.converters.ChatHistoryVariableContextVariableTypeConverter;
@@ -19,10 +10,15 @@ import com.microsoft.semantickernel.orchestration.contextvariables.converters.In
import com.microsoft.semantickernel.orchestration.contextvariables.converters.NumberVariableContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.converters.StringVariableContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.converters.VoidVariableContextVariableTypeConverter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
public class ContextVariableTypes {
public static final ContextVariableTypes DEFAULT_TYPES;
private static final ContextVariableTypes DEFAULT_TYPES;
static {
List<ContextVariableTypeConverter<?>> types = Arrays.asList(
@@ -60,12 +56,30 @@ public class ContextVariableTypes {
}
private final Map<Class<?>, ContextVariableType<?>> variableTypes = new HashMap<>();
private final Map<Class<?>, ContextVariableType<?>> variableTypes;
public ContextVariableTypes(List<ContextVariableTypeConverter<?>> converters) {
variableTypes = new HashMap<>();
converters.forEach(this::putConverter);
}
public ContextVariableTypes() {
variableTypes = new HashMap<>();
}
public ContextVariableTypes(ContextVariableTypes contextVariableTypes) {
this.variableTypes = new HashMap<>(contextVariableTypes.variableTypes);
}
public static void addGlobalConverter(
ContextVariableTypeConverter<?> type) {
DEFAULT_TYPES.putConverter(type);
}
public static <T> ContextVariableType<T> getGlobalVariableTypeForClass(Class<T> aClass) {
return DEFAULT_TYPES.getVariableTypeForClass(aClass);
}
public <T> void putConverter(
ContextVariableTypeConverter<T> contextVariableTypeConverter) {
variableTypes.put(contextVariableTypeConverter.getType(),
@@ -87,12 +101,28 @@ public class ContextVariableTypes {
}
}
public static <T> ContextVariableType<T> getDefaultVariableTypeForClass(Class<T> aClass) {
return DEFAULT_TYPES.getVariableTypeForClass(aClass);
}
@SuppressWarnings("unchecked")
public <T> ContextVariableType<T> getVariableTypeForClass(Class<T> aClass) {
try {
return this.getVariableTypeForClassInternal(aClass);
} catch (Exception e) {
return DEFAULT_TYPES.getVariableTypeForClassInternal(aClass);
}
}
@SuppressWarnings("unchecked")
public <T> ContextVariableType<T> getVariableTypeForSuperClass(Class<T> aClass) {
try {
return this.getVariableTypeForSuperClassInternal(aClass);
} catch (Exception e) {
return DEFAULT_TYPES.getVariableTypeForSuperClassInternal(aClass);
}
}
@SuppressWarnings("unchecked")
private <T> ContextVariableType<T> getVariableTypeForClassInternal(Class<T> aClass) {
ContextVariableType<?> contextVariableType = variableTypes.get(aClass);
if (contextVariableType != null) {
@@ -111,7 +141,7 @@ public class ContextVariableTypes {
}
@SuppressWarnings("unchecked")
public <T> ContextVariableType<T> getVariableTypeForSuperClass(Class<T> aClass) {
private <T> ContextVariableType<T> getVariableTypeForSuperClassInternal(Class<T> aClass) {
ContextVariableType<?> contextVariableType = variableTypes.get(aClass);
if (contextVariableType != null) {
@@ -128,4 +158,5 @@ public class ContextVariableTypes {
.orElseThrow(
() -> new SKException("Unknown context variable type: " + aClass.getName()));
}
}
@@ -1,13 +0,0 @@
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);
}
}
@@ -19,7 +19,6 @@ import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariab
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.plugin.KernelParameterMetadata;
import com.microsoft.semantickernel.plugin.KernelReturnParameterMetadata;
import com.microsoft.semantickernel.plugin.annotations.DefineKernelFunction;
@@ -168,13 +167,16 @@ public class KernelFunctionFromMethod<T> extends KernelFunction<T> {
@SuppressWarnings("unchecked")
private static <T> ImplementationFunc<T> getFunction(Method method, Object instance) {
return (kernel, function, arguments, variableType, invocationContext) -> {
InvocationContext context;
if (invocationContext == null) {
invocationContext = InvocationContext.builder().build();
context = InvocationContext.builder().build();
} else {
context = invocationContext;
}
// kernelHooks must be effectively final for lambda
KernelHooks kernelHooks = invocationContext.getKernelHooks() != null
? invocationContext.getKernelHooks()
KernelHooks kernelHooks = context.getKernelHooks() != null
? context.getKernelHooks()
: kernel.getGlobalKernelHooks();
assert kernelHooks != null : "getGlobalKernelHooks() should never return null!";
@@ -187,7 +189,7 @@ public class KernelFunctionFromMethod<T> extends KernelFunction<T> {
try {
List<Object> args =
Arrays.stream(method.getParameters())
.map(getParameters(method, updatedArguments, kernel))
.map(getParameters(method, updatedArguments, kernel, context))
.collect(Collectors.toList());
Mono<?> mono;
@@ -231,6 +233,7 @@ public class KernelFunctionFromMethod<T> extends KernelFunction<T> {
// If the function has a return type that has a ContextVariableType<T>, use it.
ContextVariableType<T> contextVariableType = getContextVariableType(
invocationContext,
returnParameterType);
if (contextVariableType == null) {
// If getting the context variable type from the function fails, default to
@@ -267,14 +270,15 @@ public class KernelFunctionFromMethod<T> extends KernelFunction<T> {
@Nullable
@SuppressWarnings("unchecked")
private static <T> ContextVariableType<T> getContextVariableType(Class<?> clazz) {
private static <T> ContextVariableType<T> getContextVariableType(
InvocationContext invocationContext, Class<?> clazz) {
if (clazz != null) {
try {
// unchecked cast
Class<T> tClazz = (Class<T>) clazz;
ContextVariableType<T> type = ContextVariableTypes.getDefaultVariableTypeForClass(
tClazz);
ContextVariableType<T> type = invocationContext.getContextVariableTypes()
.getVariableTypeForClass(tClazz);
return type;
} catch (ClassCastException | SKException e) {
// SKException is thrown from ContextVariableTypes.getDefaultVariableTypeForClass
@@ -341,12 +345,13 @@ public class KernelFunctionFromMethod<T> extends KernelFunction<T> {
Method method,
@Nullable
KernelFunctionArguments context,
Kernel kernel) {
Kernel kernel,
InvocationContext invocationContext) {
return parameter -> {
if (KernelFunctionArguments.class.isAssignableFrom(parameter.getType())) {
return context;
} else {
return getArgumentValue(method, context, parameter, kernel);
return getArgumentValue(method, context, parameter, kernel, invocationContext);
}
};
}
@@ -356,7 +361,8 @@ public class KernelFunctionFromMethod<T> extends KernelFunction<T> {
Method method,
@Nullable KernelFunctionArguments context,
Parameter parameter,
Kernel kernel) {
Kernel kernel,
InvocationContext invocationContext) {
String variableName = getGetVariableName(parameter);
ContextVariable<?> arg = context == null ? null : context.get(variableName);
@@ -367,12 +373,17 @@ public class KernelFunctionFromMethod<T> extends KernelFunction<T> {
// 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);
ContextVariableType<?> cvType = invocationContext
.getContextVariableTypes()
.getVariableTypeForClass(type);
if (cvType != null) {
String defaultValue = annotation.defaultValue();
Object value = cvType.getConverter().fromPromptString(defaultValue);
arg = ContextVariable.untypedOf(value, cvType.getConverter());
arg = ContextVariable.convert(value, type,
invocationContext.getContextVariableTypes());
}
if (arg != null && NO_DEFAULT_VALUE.equals(arg.getValue())) {
@@ -450,8 +461,8 @@ public class KernelFunctionFromMethod<T> extends KernelFunction<T> {
}
// Well-known types only
ContextVariableType<?> converter = ContextVariableTypes.getDefaultVariableTypeForClass(
type);
ContextVariableType<?> converter = invocationContext.getContextVariableTypes()
.getVariableTypeForClass(type);
if (converter != null) {
try {
value = converter.getConverter().fromObject(arg);
@@ -1,17 +1,5 @@
package com.microsoft.semantickernel.semanticfunctions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.azure.core.exception.HttpResponseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
@@ -35,11 +23,18 @@ import com.microsoft.semantickernel.orchestration.KernelFunctionMetadata;
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.plugin.KernelParameterMetadata;
import com.microsoft.semantickernel.services.AIServiceSelection;
import com.microsoft.semantickernel.textcompletion.TextGenerationService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -112,11 +107,12 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
@Nullable ContextVariableType<T> contextVariableType,
@Nullable InvocationContext invocationContext) {
InvocationContext context = invocationContext != null ? invocationContext : InvocationContext.builder().build();
InvocationContext context =
invocationContext != null ? invocationContext : InvocationContext.builder().build();
// must be effectively final for lambda
KernelHooks kernelHooks = context.getKernelHooks() != null
? context.getKernelHooks()
KernelHooks kernelHooks = context.getKernelHooks() != null
? context.getKernelHooks()
: kernel.getGlobalKernelHooks();
assert kernelHooks != null : "getGlobalKernelHooks() should never return null";
@@ -126,13 +122,13 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
// TOOD: put in method, add catch for classcastexception, fallback to noopconverter
ContextVariableType<T> variableType = contextVariableType != null
? contextVariableType
: ContextVariableTypes.<T>getDefaultVariableTypeForClass(
: context.getContextVariableTypes().getVariableTypeForClass(
(Class<T>) this.getMetadata().getReturnParameter().getParameterType()
);
return this
.template
.renderAsync(kernel, preRenderingHookResult.getArguments())
.renderAsync(kernel, preRenderingHookResult.getArguments(), context)
.flatMapMany(prompt -> {
PromptRenderedEvent promptHookResult = kernelHooks
.executeHooks(new PromptRenderedEvent(this, arguments, prompt));
@@ -153,7 +149,8 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
args
);
AIService client = aiServiceSelection != null ? aiServiceSelection.getService() : null;
AIService client =
aiServiceSelection != null ? aiServiceSelection.getService() : null;
if (aiServiceSelection == null) {
throw new IllegalStateException(
"Failed to initialise aiService, could not find any TextAIService implementations");
@@ -199,7 +196,8 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
return Flux.error(new IllegalStateException(
"Tool message content is empty"));
}
Mono<FunctionResult<T>> toolResult = invokeTool(kernel,content);
Mono<FunctionResult<T>> toolResult = invokeTool(kernel, content,
context);
return toolResult.flux();
}
return Flux.empty();
@@ -307,13 +305,16 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
* {"type":"function", "function": {"name":"search-search", "parameters": {"query":"Banksy"}}}
* where 'name' is <plugin name '-' function name>.
*/
private Mono<FunctionResult<T>> invokeTool(Kernel kernel, String json) {
private Mono<FunctionResult<T>> invokeTool(
Kernel kernel,
String json,
InvocationContext invocationContext) {
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(json);
jsonNode = jsonNode.get("function");
if (jsonNode != null) {
return invokeFunction(kernel, jsonNode);
return invokeFunction(kernel, jsonNode, invocationContext);
}
} catch (JsonProcessingException e) {
LOGGER.error("Failed to parse json", e);
@@ -327,7 +328,11 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
* The jsonNode should represent: {"name":"search-search", "parameters": {"query":"Banksy"}}}
*/
@SuppressWarnings({"StringSplitter", "unchecked"})
private Mono<FunctionResult<T>> invokeFunction(Kernel kernel, JsonNode jsonNode) {
private Mono<FunctionResult<T>> invokeFunction(
Kernel kernel,
JsonNode jsonNode,
InvocationContext invocationContext
) {
String name = jsonNode.get("name").asText();
String[] parts = name.split("-");
String pluginName = parts.length > 0 ? parts[0] : "";
@@ -338,13 +343,15 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
KernelFunction<T> kernelFunction = null;
try {
// unchecked cast
kernelFunction = (KernelFunction<T>)kernel.getPlugins().getFunction(pluginName, fnName);
kernelFunction = (KernelFunction<T>) kernel.getPlugins()
.getFunction(pluginName, fnName);
} catch (IllegalArgumentException | ClassCastException e) {
return Mono.error(new SKException(e.getMessage(), e));
}
List<KernelParameterMetadata<?>> params = kernelFunction.getMetadata().getParameters();
Map<String, KernelParameterMetadata<?>> parameterMetaaData =
List<KernelParameterMetadata<?>> params = kernelFunction.getMetadata()
.getParameters();
Map<String, KernelParameterMetadata<?>> parameterMetaaData =
params.stream()
.collect(Collectors.toMap(KernelParameterMetadata::getName, it -> it));
@@ -353,7 +360,8 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
String paramName = entry.getKey();
String paramValue = entry.getValue().asText();
KernelParameterMetadata<?> parameterMetadata = parameterMetaaData.get(paramName);
KernelParameterMetadata<?> parameterMetadata = parameterMetaaData.get(
paramName);
if (parameterMetadata == null) {
// parameter in json not found in function metadata
// TODO: this shouldn't happen, so log it.
@@ -361,10 +369,15 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
}
Class<?> parameterType = parameterMetadata.getType();
ContextVariable<?> contextVariable = ContextVariable.untypedOf(paramValue, parameterType);
ContextVariable<?> contextVariable = ContextVariable.untypedOf(
paramValue,
parameterType,
invocationContext.getContextVariableTypes()
);
variables.put(paramName, contextVariable);
});
KernelFunctionArguments arguments = KernelFunctionArguments.builder().withVariables(variables)
KernelFunctionArguments arguments = KernelFunctionArguments.builder()
.withVariables(variables)
.build();
return kernelFunction.invokeAsync(kernel, arguments);
} catch (Exception e) {
@@ -443,13 +456,14 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
@Override
public FromPromptBuilder<T> withName(@Nullable String name) {
public FromPromptBuilder<T> withName(@Nullable String name) {
this.name = name;
return this;
}
@Override
public FromPromptBuilder<T> withInputParameters(@Nullable List<InputVariable> inputVariables) {
public FromPromptBuilder<T> withInputParameters(
@Nullable List<InputVariable> inputVariables) {
if (inputVariables != null) {
this.inputVariables = new ArrayList<>(inputVariables);
} else {
@@ -459,13 +473,13 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
}
@Override
public FromPromptBuilder<T> withPromptTemplate(@Nullable PromptTemplate promptTemplate) {
public FromPromptBuilder<T> withPromptTemplate(@Nullable PromptTemplate promptTemplate) {
this.promptTemplate = promptTemplate;
return this;
}
@Override
public FromPromptBuilder<T> withExecutionSettings(
public FromPromptBuilder<T> withExecutionSettings(
@Nullable
Map<String, PromptExecutionSettings> executionSettings) {
if (this.executionSettings == null) {
@@ -479,7 +493,7 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
}
@Override
public FromPromptBuilder<T> withDefaultExecutionSettings(
public FromPromptBuilder<T> withDefaultExecutionSettings(
@Nullable PromptExecutionSettings executionSettings) {
if (executionSettings == null) {
return this;
@@ -499,37 +513,37 @@ public class KernelFunctionFromPrompt<T> extends KernelFunction<T> {
}
@Override
public FromPromptBuilder<T> withDescription(@Nullable String description) {
public FromPromptBuilder<T> withDescription(@Nullable String description) {
this.description = description;
return this;
}
@Override
public FromPromptBuilder<T> withTemplate(@Nullable String template) {
public FromPromptBuilder<T> withTemplate(@Nullable String template) {
this.template = template;
return this;
}
@Override
public FromPromptBuilder<T> withTemplateFormat(String templateFormat) {
public FromPromptBuilder<T> withTemplateFormat(String templateFormat) {
this.templateFormat = templateFormat;
return this;
}
@Override
public FromPromptBuilder<T> withOutputVariable(@Nullable OutputVariable outputVariable) {
public FromPromptBuilder<T> withOutputVariable(@Nullable OutputVariable outputVariable) {
this.outputVariable = outputVariable;
return this;
}
@Override
public FromPromptBuilder<T> withOutputVariable(@Nullable String description, String type) {
public FromPromptBuilder<T> withOutputVariable(@Nullable String description, String type) {
return this.withOutputVariable(new OutputVariable(description, type));
}
@Override
public FromPromptBuilder<T> withPromptTemplateFactory(
public FromPromptBuilder<T> withPromptTemplateFactory(
@Nullable
PromptTemplateFactory promptTemplateFactory) {
this.promptTemplateFactory = promptTemplateFactory;
@@ -3,9 +3,8 @@ package com.microsoft.semantickernel.semanticfunctions;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.builders.Buildable;
import com.microsoft.semantickernel.builders.SemanticKernelBuilder;
import com.microsoft.semantickernel.orchestration.InvocationContext;
import com.microsoft.semantickernel.orchestration.KernelFunctionArguments;
import javax.annotation.Nullable;
import reactor.core.publisher.Mono;
@@ -21,7 +20,9 @@ public interface PromptTemplate extends Buildable {
/// <param name="arguments">The arguments.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>Prompt rendered to string</returns>
Mono<String> renderAsync(Kernel kernel,
@Nullable KernelFunctionArguments arguments);
Mono<String> renderAsync(
Kernel kernel,
@Nullable KernelFunctionArguments arguments,
@Nullable InvocationContext context);
}
@@ -1,6 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.templateengine.handlebars;
import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Options;
import com.github.jknack.handlebars.ValueResolver;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.chatcompletion.ChatHistory;
import com.microsoft.semantickernel.chatcompletion.ChatMessageContent;
import com.microsoft.semantickernel.orchestration.InvocationContext;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.KernelFunctionArguments;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import com.microsoft.semantickernel.plugin.KernelParameterMetadata;
import com.microsoft.semantickernel.plugin.KernelPlugin;
import com.microsoft.semantickernel.plugin.KernelPluginCollection;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -11,27 +28,8 @@ import java.util.Locale;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Options;
import com.github.jknack.handlebars.ValueResolver;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.chatcompletion.ChatHistory;
import com.microsoft.semantickernel.chatcompletion.ChatMessageContent;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.KernelFunctionArguments;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import com.microsoft.semantickernel.plugin.KernelParameterMetadata;
import com.microsoft.semantickernel.plugin.KernelPlugin;
import com.microsoft.semantickernel.plugin.KernelPluginCollection;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import reactor.core.publisher.Mono;
public class HandlebarsPromptTemplate implements PromptTemplate {
@@ -47,7 +45,8 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
@Override
public Mono<String> renderAsync(
Kernel kernel,
@Nullable KernelFunctionArguments arguments) {
@Nullable KernelFunctionArguments arguments,
@Nullable InvocationContext context) {
HandleBarsPromptTemplateHandler handler =
new HandleBarsPromptTemplateHandler(kernel, promptTemplate.getTemplate());
@@ -3,6 +3,7 @@ package com.microsoft.semantickernel.templateengine.semantickernel;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.Verify;
import com.microsoft.semantickernel.orchestration.InvocationContext;
import com.microsoft.semantickernel.orchestration.KernelFunctionArguments;
import com.microsoft.semantickernel.semanticfunctions.InputVariable;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
@@ -96,7 +97,8 @@ public class DefaultPromptTemplate implements PromptTemplate {
@Override
public Mono<String> renderAsync(
Kernel kernel,
@Nullable KernelFunctionArguments arguments) {
@Nullable KernelFunctionArguments arguments,
@Nullable InvocationContext context) {
List<Block> blocks = this.extractBlocks();
addMissingInputVariables(blocks);
@@ -109,7 +111,7 @@ public class DefaultPromptTemplate implements PromptTemplate {
((TextRendering) block).render(arguments)
);
} else if (block instanceof CodeRendering) {
return ((CodeRendering) block).renderCodeAsync(kernel, arguments);
return ((CodeRendering) block).renderCodeAsync(kernel, arguments, context);
} else {
return Mono.error(new TemplateException(ErrorCodes.UNEXPECTED_BLOCK_TYPE));
}
@@ -1,18 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.templateengine.semantickernel.blocks;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.exceptions.SKException;
import com.microsoft.semantickernel.orchestration.FunctionResult;
import com.microsoft.semantickernel.orchestration.InvocationContext;
import com.microsoft.semantickernel.orchestration.KernelFunctionArguments;
import com.microsoft.semantickernel.orchestration.KernelFunctionMetadata;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
@@ -20,6 +12,12 @@ import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariab
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes;
import com.microsoft.semantickernel.templateengine.semantickernel.TemplateException;
import com.microsoft.semantickernel.templateengine.semantickernel.TemplateException.ErrorCodes;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
public final class CodeBlock extends Block implements CodeRendering {
@@ -79,11 +77,18 @@ public final class CodeBlock extends Block implements CodeRendering {
}
@Override
public Mono<String> renderCodeAsync(Kernel kernel, @Nullable KernelFunctionArguments arguments) {
public Mono<String> renderCodeAsync(
Kernel kernel,
@Nullable KernelFunctionArguments arguments,
@Nullable InvocationContext context) {
if (!this.isValid()) {
throw new TemplateException(ErrorCodes.SYNTAX_ERROR);
}
if (context == null) {
context = new InvocationContext();
}
// this.Log.LogTrace("Rendering code: `{0}`", this.Content);
switch (this.tokens.get(0).getType()) {
@@ -98,7 +103,8 @@ public final class CodeBlock extends Block implements CodeRendering {
(FunctionIdBlock) this.tokens.get(0),
kernel,
arguments,
ContextVariableTypes.getDefaultVariableTypeForClass(String.class))
context,
ContextVariableTypes.getGlobalVariableTypeForClass(String.class))
.map(it -> {
return it.getValue();
});
@@ -115,6 +121,7 @@ public final class CodeBlock extends Block implements CodeRendering {
FunctionIdBlock fBlock,
Kernel kernel,
@Nullable KernelFunctionArguments arguments,
InvocationContext context,
ContextVariableType<T> resultType) {
// If the code syntax is {{functionName $varName}} use $varName instead of $input
@@ -122,9 +129,10 @@ public final class CodeBlock extends Block implements CodeRendering {
if (this.tokens.size() > 1) {
//Cloning the original arguments to avoid side effects - arguments added to the original arguments collection as a result of rendering template variables.
arguments = this.enrichFunctionArguments(kernel, fBlock,
arguments == null
arguments == null
? new KernelFunctionArguments()
: new KernelFunctionArguments(arguments));
: new KernelFunctionArguments(arguments),
context);
}
return kernel
@@ -150,7 +158,9 @@ public final class CodeBlock extends Block implements CodeRendering {
private KernelFunctionArguments enrichFunctionArguments(
Kernel kernel,
FunctionIdBlock fBlock,
KernelFunctionArguments arguments) {
KernelFunctionArguments arguments,
@Nullable
InvocationContext context) {
Block firstArg = this.tokens.get(1);
// Get the function metadata
@@ -182,8 +192,15 @@ public final class CodeBlock extends Block implements CodeRendering {
}
// Keep previous trust information when updating the input
arguments.put(firstPositionalParameterName,
ContextVariable.of(firstPositionalInputValue));
arguments.put(
firstPositionalParameterName,
ContextVariable
.convert(
firstPositionalInputValue,
functionMetadata.getParameters().get(0).getType(),
context == null ? null : context.getContextVariableTypes()
)
);
namedArgsStartIndex++;
}
@@ -2,8 +2,8 @@
package com.microsoft.semantickernel.templateengine.semantickernel.blocks;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.orchestration.InvocationContext;
import com.microsoft.semantickernel.orchestration.KernelFunctionArguments;
import javax.annotation.Nullable;
import reactor.core.publisher.Mono;
@@ -19,5 +19,8 @@ public interface CodeRendering {
* @param arguments Optional arguments used to render the block
* @return Rendered content
*/
Mono<String> renderCodeAsync(Kernel kernel, @Nullable KernelFunctionArguments arguments);
Mono<String> renderCodeAsync(
Kernel kernel,
@Nullable KernelFunctionArguments arguments,
@Nullable InvocationContext context);
}
@@ -18,7 +18,7 @@ public class NumberVariableContextVariableTypeConverterTest {
*/
@Test
void testConvertIntegerToString() {
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableType<Integer> type = ContextVariableTypes.getGlobalVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
Object expResult = "1";
Object result = instance.toObject(1, String.class);
@@ -31,7 +31,7 @@ public class NumberVariableContextVariableTypeConverterTest {
@Test
void testFromStringToInteger() {
Object s = "123";
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableType<Integer> type = ContextVariableTypes.getGlobalVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
Number expResult = Integer.valueOf((String)s);
Number result = instance.fromObject(s);
@@ -41,7 +41,7 @@ public class NumberVariableContextVariableTypeConverterTest {
@Test
void testFromPromptStringToInteger() {
String s = "123";
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableType<Integer> type = ContextVariableTypes.getGlobalVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
Number expResult = Integer.valueOf(s);
Number result = instance.fromPromptString(s);
@@ -51,7 +51,7 @@ public class NumberVariableContextVariableTypeConverterTest {
@Test
void testFromPromptStringReturnsNullForEmptyString() {
String s = "";
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableType<Integer> type = ContextVariableTypes.getGlobalVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
Number expResult = null;
Number result = instance.fromPromptString(s);
@@ -61,7 +61,7 @@ public class NumberVariableContextVariableTypeConverterTest {
@Test
void testFromPromptStringReturnsNullForNullString() {
String s = null;
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableType<Integer> type = ContextVariableTypes.getGlobalVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
Number expResult = null;
Number result = instance.fromPromptString(s);
@@ -71,7 +71,7 @@ public class NumberVariableContextVariableTypeConverterTest {
@Test
void testToPromptString() {
Integer num = 123;
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableType<Integer> type = ContextVariableTypes.getGlobalVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
String expResult = "123";
String result = instance.toPromptString(num);
@@ -81,7 +81,7 @@ public class NumberVariableContextVariableTypeConverterTest {
@Test
void testToPromptStringReturnsEmptyStringForNull() {
Integer num = null;
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableType<Integer> type = ContextVariableTypes.getGlobalVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
String expResult = "";
String result = instance.toPromptString(num);