Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
# 1.2.1

- Fix bug in `FunctionInvocation` not using per-invocation type conversion when calling `withResultType`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should start using jreleaser to keep the changelog up to date.

- Fix bug in Global Hooks not being invoked under certain circumstances.
- Add fluent returns to `ChatHistory` `addXMessage` methods.
- Add user agent opt-out for OpenAI requests by setting the property `semantic-kernel.useragent-disable` to `true`.
- Add several convenience `invokePromptAsync` methods to `Kernel`.

#### Non-API Changes

- Add custom type Conversion example, CustomTypes_Example

# 1.2.0

- Add ability to use image_url as content for a OpenAi chat completion
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.samples.syntaxexamples.java;

import com.azure.ai.openai.OpenAIAsyncClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.credential.KeyCredential;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion;
import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter;
import com.microsoft.semantickernel.contextvariables.ContextVariableTypes;
import com.microsoft.semantickernel.contextvariables.converters.ContextVariableJacksonConverter;
import com.microsoft.semantickernel.exceptions.ConfigurationException;
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments;
import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class CustomTypes_Example {

private static final String CLIENT_KEY = System.getenv("CLIENT_KEY");
private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY");

// Only required if AZURE_CLIENT_KEY is set
private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT");
private static final String MODEL_ID = System.getenv()
.getOrDefault("MODEL_ID", "gpt-35-turbo-2");

public static void main(String[] args) throws ConfigurationException, IOException {

OpenAIAsyncClient client;

if (AZURE_CLIENT_KEY != null) {
client = new OpenAIClientBuilder()
.credential(new AzureKeyCredential(AZURE_CLIENT_KEY))
.endpoint(CLIENT_ENDPOINT)
.buildAsyncClient();
} else {
client = new OpenAIClientBuilder()
.credential(new KeyCredential(CLIENT_KEY))
.buildAsyncClient();
}

ChatCompletionService chatCompletionService = OpenAIChatCompletion.builder()
.withOpenAIAsyncClient(client)
.withModelId(MODEL_ID)
.build();

exampleBuildingCustomConverter(chatCompletionService);
exampleUsingJackson(chatCompletionService);
exampleUsingGlobalTypes(chatCompletionService);
}

public record Pet(String name, int age, String species) {

@JsonCreator
public Pet(
@JsonProperty("name") String name,
@JsonProperty("age") int age,
@JsonProperty("species") String species) {
this.name = name;
this.age = age;
this.species = species;
}

@Override
public String toString() {
return name + " " + species + " " + age;
}
}

private static void exampleBuildingCustomConverter(
ChatCompletionService chatCompletionService) {
Pet sandy = new Pet("Sandy", 3, "Dog");

Kernel kernel = Kernel.builder()
.withAIService(ChatCompletionService.class, chatCompletionService)
.build();

// Format:
// name: Sandy
// age: 3
// species: Dog

// Custom serializer
Function<Pet, String> petToString = pet -> "name: " + pet.name() + "\n" +
"age: " + pet.age() + "\n" +
"species: " + pet.species() + "\n";

// Custom deserializer
Function<String, Pet> stringToPet = prompt -> {
Map<String, String> properties = Arrays.stream(prompt.split("\n"))
.collect(Collectors.toMap(
line -> line.split(":")[0].trim(),
line -> line.split(":")[1].trim()));

return new Pet(
properties.get("name"),
Integer.parseInt(properties.get("age")),
properties.get("species"));
};

// create custom converter
ContextVariableTypeConverter<Pet> typeConverter = ContextVariableTypeConverter.builder(
Pet.class)
.toPromptString(petToString)
.fromPromptString(stringToPet)
.build();

Pet updated = kernel.invokePromptAsync(
"Change Sandy's name to Daisy:\n{{$Sandy}}",
KernelFunctionArguments.builder()
.withVariable("Sandy", sandy, typeConverter)
.build())
.withTypeConverter(typeConverter)
.withResultType(Pet.class)
.block()
.getResult();

System.out.println("Sandy's updated record: " + updated);
}

public static void exampleUsingJackson(ChatCompletionService chatCompletionService) {
Pet sandy = new Pet("Sandy", 3, "Dog");

Kernel kernel = Kernel.builder()
.withAIService(ChatCompletionService.class, chatCompletionService)
.build();

// Create a converter that defaults to using jackson for serialization
ContextVariableTypeConverter<Pet> typeConverter = ContextVariableJacksonConverter.create(
Pet.class);

// Invoke the prompt with the custom converter
Pet updated = kernel.invokePromptAsync(
"Increase Sandy's age by a year:\n{{$Sandy}}",
KernelFunctionArguments.builder()
.withVariable("Sandy", sandy, typeConverter)
.build())
.withTypeConverter(typeConverter)
.withResultType(Pet.class)
.block()
.getResult();

System.out.println("Sandy's updated record: " + updated);
}

public static void exampleUsingGlobalTypes(ChatCompletionService chatCompletionService) {
Pet sandy = new Pet("Sandy", 3, "Dog");

Kernel kernel = Kernel.builder()
.withAIService(ChatCompletionService.class, chatCompletionService)
.build();

// Create a converter that defaults to using jackson for serialization
ContextVariableTypeConverter<Pet> typeConverter = ContextVariableJacksonConverter.create(
Pet.class);

// Add converter to global types
ContextVariableTypes.addGlobalConverter(typeConverter);

// No need to explicitly tell the invocation how to convert the type
Pet updated = kernel.invokePromptAsync(
"Sandy's is actually a cat correct this:\n{{$Sandy}}",
KernelFunctionArguments.builder()
.withVariable("Sandy", sandy)
.build())
.withResultType(Pet.class)
.block()
.getResult();

System.out.println("Sandy's updated record: " + updated);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,7 @@ public static class Builder<T> {
@SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
public Builder(Class<T> clazz) {
this.clazz = clazz;
fromObject = x -> {
throw new UnsupportedOperationException("fromObject not implemented");
};
fromObject = x -> ContextVariableTypes.convert(x, clazz);
toPromptString = (a, b) -> {
throw new UnsupportedOperationException("toPromptString not implemented");
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.contextvariables.converters;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter;
import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter.Builder;
import com.microsoft.semantickernel.exceptions.SKException;

/**
* A utility class for creating {@link ContextVariableTypeConverter} instances that use Jackson for
* serialization and deserialization.
*/
public final class ContextVariableJacksonConverter {

/**
* Creates a new {@link ContextVariableTypeConverter} that uses Jackson for serialization and
* deserialization.
*
* @param type the type of the context variable
* @param mapper the {@link ObjectMapper} to use for serialization and deserialization
* @param <T> the type of the context variable
* @return a new {@link ContextVariableTypeConverter}
*/
public static <T> ContextVariableTypeConverter<T> create(Class<T> type, ObjectMapper mapper) {
return builder(type, mapper).build();
}

/**
* Creates a new {@link ContextVariableTypeConverter} that uses Jackson for serialization and
* deserialization.
*
* @param type the type of the context variable
* @param <T> the type of the context variable
* @return a new {@link ContextVariableTypeConverter}
*/
public static <T> ContextVariableTypeConverter<T> create(Class<T> type) {
return create(type, new ObjectMapper());
}

/**
* Creates a new {@link Builder} for a {@link ContextVariableTypeConverter} that uses Jackson
* for serialization and deserialization.
*
* @param type the type of the context variable
* @param <T> the type of the context variable
* @return a new {@link Builder}
*/
public static <T> Builder<T> builder(Class<T> type) {
return builder(type, new ObjectMapper());
}

/**
* Creates a new {@link Builder} for a {@link ContextVariableTypeConverter} that uses Jackson
* for serialization and deserialization.
*
* @param type the type of the context variable
* @param mapper the {@link ObjectMapper} to use for serialization and deserialization
* @param <T> the type of the context variable
* @return a new {@link Builder}
*/
public static <T> Builder<T> builder(Class<T> type, ObjectMapper mapper) {
return ContextVariableTypeConverter.builder(type)
.fromPromptString(str -> {
try {
return mapper.readValue(str, type);
} catch (JsonProcessingException e) {
throw new SKException("Failed to deserialize object", e);
}
})
.toPromptString(obj -> {
try {
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new SKException("Failed to serialize object", e);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ public DateTimeContextVariableTypeConverter() {
return null;
},
Object::toString,
o -> {
return ZonedDateTime.parse(o).toOffsetDateTime();
},
o -> ZonedDateTime.parse(o).toOffsetDateTime(),
Arrays.asList(
new DefaultConverter<OffsetDateTime, Instant>(OffsetDateTime.class, Instant.class) {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ private static <T> BiConsumer<FunctionResult<?>, SynchronousSink<FunctionResult<
} catch (Exception e) {
sink.error(new SKException(
"Failed to convert result to requested type: "
+ variableType.getClazz().getName(),
+ variableType.getClazz().getName() + " " + result.getResult(),
e));
}
} else {
Expand Down Expand Up @@ -196,7 +196,11 @@ public <U> FunctionInvocation<U> withResultType(ContextVariableType<U> resultTyp
* @return A new {@code FunctionInvocation} for fluent chaining.
*/
public <U> FunctionInvocation<U> withResultType(Class<U> resultType) {
return withResultType(ContextVariableTypes.getGlobalVariableTypeForClass(resultType));
try {
return withResultType(contextVariableTypes.getVariableTypeForSuperClass(resultType));
} catch (SKException e) {
return withResultType(ContextVariableTypes.getGlobalVariableTypeForClass(resultType));
}
}

/**
Expand Down
Loading