Skip to content

Commit 659859f

Browse files
committed
add example for custom type conversion. Fix bug in per-invocation type conversion. Add fluent calls to ChatHistory
1 parent 4c62940 commit 659859f

File tree

7 files changed

+293
-18
lines changed

7 files changed

+293
-18
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# 1.2.1
2+
3+
- Fix bug in `FunctionInvocation` not using per-invocation type conversion when calling `withResultType`.
4+
- Fix bug in Global Hooks not being invoked under certain circumstances.
5+
- Add fluent returns to `ChatHistory` `addXMessage` methods.
6+
- Add user agent opt-out for OpenAI requests by setting the property `semantic-kernel.useragent-disable` to `true`.
7+
- Add several convenience `invokePromptAsync` methods to `Kernel`.
8+
9+
#### Non-API Changes
10+
11+
- Add custom type Conversion example, CustomTypes_Example
12+
113
# 1.2.0
214

315
- Add ability to use image_url as content for a OpenAi chat completion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
package com.microsoft.semantickernel.samples.syntaxexamples.java;
3+
4+
import com.azure.ai.openai.OpenAIAsyncClient;
5+
import com.azure.ai.openai.OpenAIClientBuilder;
6+
import com.azure.core.credential.AzureKeyCredential;
7+
import com.azure.core.credential.KeyCredential;
8+
import com.fasterxml.jackson.annotation.JsonCreator;
9+
import com.fasterxml.jackson.annotation.JsonProperty;
10+
import com.microsoft.semantickernel.Kernel;
11+
import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion;
12+
import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter;
13+
import com.microsoft.semantickernel.contextvariables.ContextVariableTypes;
14+
import com.microsoft.semantickernel.contextvariables.converters.ContextVariableJacksonConverter;
15+
import com.microsoft.semantickernel.exceptions.ConfigurationException;
16+
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments;
17+
import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService;
18+
import java.io.IOException;
19+
import java.util.Arrays;
20+
import java.util.Map;
21+
import java.util.function.Function;
22+
import java.util.stream.Collectors;
23+
24+
public class CustomTypes_Example {
25+
26+
private static final String CLIENT_KEY = System.getenv("CLIENT_KEY");
27+
private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY");
28+
29+
// Only required if AZURE_CLIENT_KEY is set
30+
private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT");
31+
private static final String MODEL_ID = System.getenv()
32+
.getOrDefault("MODEL_ID", "gpt-35-turbo-2");
33+
34+
public static void main(String[] args) throws ConfigurationException, IOException {
35+
36+
OpenAIAsyncClient client;
37+
38+
if (AZURE_CLIENT_KEY != null) {
39+
client = new OpenAIClientBuilder()
40+
.credential(new AzureKeyCredential(AZURE_CLIENT_KEY))
41+
.endpoint(CLIENT_ENDPOINT)
42+
.buildAsyncClient();
43+
} else {
44+
client = new OpenAIClientBuilder()
45+
.credential(new KeyCredential(CLIENT_KEY))
46+
.buildAsyncClient();
47+
}
48+
49+
ChatCompletionService chatCompletionService = OpenAIChatCompletion.builder()
50+
.withOpenAIAsyncClient(client)
51+
.withModelId(MODEL_ID)
52+
.build();
53+
54+
exampleBuildingCustomConverter(chatCompletionService);
55+
exampleUsingJackson(chatCompletionService);
56+
exampleUsingGlobalTypes(chatCompletionService);
57+
}
58+
59+
public record Pet(String name, int age, String species) {
60+
61+
@JsonCreator
62+
public Pet(
63+
@JsonProperty("name") String name,
64+
@JsonProperty("age") int age,
65+
@JsonProperty("species") String species) {
66+
this.name = name;
67+
this.age = age;
68+
this.species = species;
69+
}
70+
71+
@Override
72+
public String toString() {
73+
return name + " " + species + " " + age;
74+
}
75+
}
76+
77+
private static void exampleBuildingCustomConverter(
78+
ChatCompletionService chatCompletionService) {
79+
Pet sandy = new Pet("Sandy", 3, "Dog");
80+
81+
Kernel kernel = Kernel.builder()
82+
.withAIService(ChatCompletionService.class, chatCompletionService)
83+
.build();
84+
85+
// Format:
86+
// name: Sandy
87+
// age: 3
88+
// species: Dog
89+
90+
// Custom serializer
91+
Function<Pet, String> petToString = pet -> "name: " + pet.name() + "\n" +
92+
"age: " + pet.age() + "\n" +
93+
"species: " + pet.species() + "\n";
94+
95+
// Custom deserializer
96+
Function<String, Pet> stringToPet = prompt -> {
97+
Map<String, String> properties = Arrays.stream(prompt.split("\n"))
98+
.collect(Collectors.toMap(
99+
line -> line.split(":")[0].trim(),
100+
line -> line.split(":")[1].trim()));
101+
102+
return new Pet(
103+
properties.get("name"),
104+
Integer.parseInt(properties.get("age")),
105+
properties.get("species"));
106+
};
107+
108+
// create custom converter
109+
ContextVariableTypeConverter<Pet> typeConverter = ContextVariableTypeConverter.builder(
110+
Pet.class)
111+
.toPromptString(petToString)
112+
.fromPromptString(stringToPet)
113+
.build();
114+
115+
Pet updated = kernel.invokePromptAsync(
116+
"Change Sandy's name to Daisy:\n{{$Sandy}}",
117+
KernelFunctionArguments.builder()
118+
.withVariable("Sandy", sandy, typeConverter)
119+
.build())
120+
.withTypeConverter(typeConverter)
121+
.withResultType(Pet.class)
122+
.block()
123+
.getResult();
124+
125+
System.out.println("Sandy's updated record: " + updated);
126+
}
127+
128+
public static void exampleUsingJackson(ChatCompletionService chatCompletionService) {
129+
Pet sandy = new Pet("Sandy", 3, "Dog");
130+
131+
Kernel kernel = Kernel.builder()
132+
.withAIService(ChatCompletionService.class, chatCompletionService)
133+
.build();
134+
135+
// Create a converter that defaults to using jackson for serialization
136+
ContextVariableTypeConverter<Pet> typeConverter = ContextVariableJacksonConverter.create(
137+
Pet.class);
138+
139+
// Invoke the prompt with the custom converter
140+
Pet updated = kernel.invokePromptAsync(
141+
"Increase Sandy's age by a year:\n{{$Sandy}}",
142+
KernelFunctionArguments.builder()
143+
.withVariable("Sandy", sandy, typeConverter)
144+
.build())
145+
.withTypeConverter(typeConverter)
146+
.withResultType(Pet.class)
147+
.block()
148+
.getResult();
149+
150+
System.out.println("Sandy's updated record: " + updated);
151+
}
152+
153+
public static void exampleUsingGlobalTypes(ChatCompletionService chatCompletionService) {
154+
Pet sandy = new Pet("Sandy", 3, "Dog");
155+
156+
Kernel kernel = Kernel.builder()
157+
.withAIService(ChatCompletionService.class, chatCompletionService)
158+
.build();
159+
160+
// Create a converter that defaults to using jackson for serialization
161+
ContextVariableTypeConverter<Pet> typeConverter = ContextVariableJacksonConverter.create(
162+
Pet.class);
163+
164+
// Add converter to global types
165+
ContextVariableTypes.addGlobalConverter(typeConverter);
166+
167+
// No need to explicitly tell the invocation how to convert the type
168+
Pet updated = kernel.invokePromptAsync(
169+
"Sandy's is actually a cat correct this:\n{{$Sandy}}",
170+
KernelFunctionArguments.builder()
171+
.withVariable("Sandy", sandy)
172+
.build())
173+
.withResultType(Pet.class)
174+
.block()
175+
.getResult();
176+
177+
System.out.println("Sandy's updated record: " + updated);
178+
}
179+
180+
}

semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableTypeConverter.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,7 @@ public static class Builder<T> {
309309
@SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
310310
public Builder(Class<T> clazz) {
311311
this.clazz = clazz;
312-
fromObject = x -> {
313-
throw new UnsupportedOperationException("fromObject not implemented");
314-
};
312+
fromObject = x -> ContextVariableTypes.convert(x, clazz);
315313
toPromptString = (a, b) -> {
316314
throw new UnsupportedOperationException("toPromptString not implemented");
317315
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
package com.microsoft.semantickernel.contextvariables.converters;
3+
4+
import com.fasterxml.jackson.core.JsonProcessingException;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter;
7+
import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter.Builder;
8+
import com.microsoft.semantickernel.exceptions.SKException;
9+
10+
/**
11+
* A utility class for creating {@link ContextVariableTypeConverter} instances that use Jackson for
12+
* serialization and deserialization.
13+
*/
14+
public final class ContextVariableJacksonConverter {
15+
16+
/**
17+
* Creates a new {@link ContextVariableTypeConverter} that uses Jackson for serialization and
18+
* deserialization.
19+
*
20+
* @param type the type of the context variable
21+
* @param mapper the {@link ObjectMapper} to use for serialization and deserialization
22+
* @param <T> the type of the context variable
23+
* @return a new {@link ContextVariableTypeConverter}
24+
*/
25+
public static <T> ContextVariableTypeConverter<T> create(Class<T> type, ObjectMapper mapper) {
26+
return builder(type, mapper).build();
27+
}
28+
29+
/**
30+
* Creates a new {@link ContextVariableTypeConverter} that uses Jackson for serialization and
31+
* deserialization.
32+
*
33+
* @param type the type of the context variable
34+
* @param <T> the type of the context variable
35+
* @return a new {@link ContextVariableTypeConverter}
36+
*/
37+
public static <T> ContextVariableTypeConverter<T> create(Class<T> type) {
38+
return create(type, new ObjectMapper());
39+
}
40+
41+
/**
42+
* Creates a new {@link Builder} for a {@link ContextVariableTypeConverter} that uses Jackson
43+
* for serialization and deserialization.
44+
*
45+
* @param type the type of the context variable
46+
* @param <T> the type of the context variable
47+
* @return a new {@link Builder}
48+
*/
49+
public static <T> Builder<T> builder(Class<T> type) {
50+
return builder(type, new ObjectMapper());
51+
}
52+
53+
/**
54+
* Creates a new {@link Builder} for a {@link ContextVariableTypeConverter} that uses Jackson
55+
* for serialization and deserialization.
56+
*
57+
* @param type the type of the context variable
58+
* @param mapper the {@link ObjectMapper} to use for serialization and deserialization
59+
* @param <T> the type of the context variable
60+
* @return a new {@link Builder}
61+
*/
62+
public static <T> Builder<T> builder(Class<T> type, ObjectMapper mapper) {
63+
return ContextVariableTypeConverter.builder(type)
64+
.fromPromptString(str -> {
65+
try {
66+
return mapper.readValue(str, type);
67+
} catch (JsonProcessingException e) {
68+
throw new SKException("Failed to deserialize object", e);
69+
}
70+
})
71+
.toPromptString(obj -> {
72+
try {
73+
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
74+
} catch (JsonProcessingException e) {
75+
throw new SKException("Failed to serialize object", e);
76+
}
77+
});
78+
}
79+
}

semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/DateTimeContextVariableTypeConverter.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@ public DateTimeContextVariableTypeConverter() {
3333
return null;
3434
},
3535
Object::toString,
36-
o -> {
37-
return ZonedDateTime.parse(o).toOffsetDateTime();
38-
},
36+
o -> ZonedDateTime.parse(o).toOffsetDateTime(),
3937
Arrays.asList(
4038
new DefaultConverter<OffsetDateTime, Instant>(OffsetDateTime.class, Instant.class) {
4139
@Override

semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionInvocation.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ private static <T> BiConsumer<FunctionResult<?>, SynchronousSink<FunctionResult<
133133
} catch (Exception e) {
134134
sink.error(new SKException(
135135
"Failed to convert result to requested type: "
136-
+ variableType.getClazz().getName(),
136+
+ variableType.getClazz().getName() + " " + result.getResult(),
137137
e));
138138
}
139139
} else {
@@ -196,7 +196,11 @@ public <U> FunctionInvocation<U> withResultType(ContextVariableType<U> resultTyp
196196
* @return A new {@code FunctionInvocation} for fluent chaining.
197197
*/
198198
public <U> FunctionInvocation<U> withResultType(Class<U> resultType) {
199-
return withResultType(ContextVariableTypes.getGlobalVariableTypeForClass(resultType));
199+
try {
200+
return withResultType(contextVariableTypes.getVariableTypeForSuperClass(resultType));
201+
} catch (SKException e) {
202+
return withResultType(ContextVariableTypes.getGlobalVariableTypeForClass(resultType));
203+
}
200204
}
201205

202206
/**

0 commit comments

Comments
 (0)