Java: Implement Example61_MultipleLLMs (#4655)

### Motivation and Context

<!-- Thank you for your contribution to the semantic-kernel repo!
Please help reviewers and future users, providing the following
information:
  1. Why is this change required?
  2. What problem does it solve?
  3. What scenario does it contribute to?
  4. If it fixes an open issue, please link to the issue here.
-->

### Description

<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->

### Contribution Checklist

<!-- Before submitting this PR, please make sure: -->

- [ ] The code builds clean without any errors or warnings
- [ ] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [ ] All unit tests pass, and I have added new tests where possible
- [ ] I didn't break anyone 😄
This commit is contained in:
John Oliver
2024-01-19 14:44:52 +00:00
committed by GitHub
parent ff39ae85c2
commit 2e49a47324
54 changed files with 974 additions and 239 deletions
@@ -52,8 +52,10 @@ public class OpenAIChatCompletion implements ChatCompletionService {
private static final Logger LOGGER = LoggerFactory.getLogger(OpenAIChatCompletion.class);
private final OpenAIAsyncClient client;
private final Map<String, ContextVariable<?>> attributes;
private final String serviceId;
public OpenAIChatCompletion(OpenAIAsyncClient client, String modelId) {
public OpenAIChatCompletion(OpenAIAsyncClient client, String modelId, String serviceId) {
this.serviceId = serviceId;
this.client = client;
this.attributes = new HashMap<>();
attributes.put(MODEL_ID_KEY, ContextVariable.of(modelId));
@@ -68,6 +70,11 @@ public class OpenAIChatCompletion implements ChatCompletionService {
return attributes;
}
@Override
public String getServiceId() {
return serviceId;
}
@Override
public Mono<List<ChatMessageContent>> getChatMessageContentsAsync(ChatHistory chatHistory,
@Nullable PromptExecutionSettings promptExecutionSettings, @Nullable Kernel kernel) {
@@ -322,6 +329,18 @@ public class OpenAIChatCompletion implements ChatCompletionService {
return options;
}
Map<String, Integer> logit = null;
if (promptExecutionSettings.getTokenSelectionBiases() != null) {
logit = promptExecutionSettings
.getTokenSelectionBiases()
.entrySet()
.stream()
.collect(Collectors.toMap(
entry -> entry.getKey().toString(),
entry -> entry.getValue())
);
}
options
.setTemperature(promptExecutionSettings.getTemperature())
.setTopP(promptExecutionSettings.getTopP())
@@ -335,7 +354,7 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|| promptExecutionSettings.getStopSequences().isEmpty() ? null
: promptExecutionSettings.getStopSequences())
.setUser(promptExecutionSettings.getUser())
.setLogitBias(new HashMap<>());
.setLogitBias(logit);
return options;
}
@@ -550,7 +569,7 @@ public class OpenAIChatCompletion implements ChatCompletionService {
@Override
public OpenAIChatCompletion build() {
return new OpenAIChatCompletion(client, modelId);
return new OpenAIChatCompletion(client, modelId, serviceId);
}
}
}
@@ -15,6 +15,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -23,6 +24,7 @@ public class OpenAITextGenerationService implements TextGenerationService {
private final OpenAIAsyncClient client;
private final Map<String, ContextVariable<?>> attributes;
private final String serviceId;
/// <summary>
/// Creates a new <see cref="OpenAITextGenerationService"/> client instance supporting AAD auth
@@ -35,7 +37,8 @@ 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 modelId, String serviceId) {
this.serviceId = serviceId;
this.client = client;
attributes = new HashMap<>();
attributes.put(MODEL_ID_KEY, ContextVariable.of(modelId));
@@ -50,10 +53,22 @@ public class OpenAITextGenerationService implements TextGenerationService {
return attributes;
}
@Override
public String getServiceId() {
return serviceId;
}
@Override
public Mono<List<TextContent>> getTextContentsAsync(String prompt,
@Nullable PromptExecutionSettings executionSettings, @Nullable Kernel kernel) {
return null;
return this
.internalCompleteTextAsync(prompt, executionSettings)
.map(it -> {
return it.stream().map(
TextContent::new
)
.collect(Collectors.toList());
});
}
@Override
@@ -121,7 +136,8 @@ public class OpenAITextGenerationService implements TextGenerationService {
public TextGenerationService build() {
return new OpenAITextGenerationService(
this.client,
this.modelId
this.modelId,
this.serviceId
);
}
}
@@ -101,7 +101,7 @@ public class InlineFunctionExample {
SKBuilders.textCompletion().withOpenAIAsyncClient(client).withModelId(MODEL).build();
String prompt = "{{$input}}\n" + "Summarize the content above.";
Kernel kernel = SKBuilders.kernel().withDefaultAIService(textCompletion).build();
Kernel kernel = SKBuilders.kernel().withAIService(textCompletion).build();
CompletionKernelFunction summarize =
kernel.getSemanticFunctionBuilder()
@@ -102,7 +102,7 @@ public class InlineFunctionWithPreBuiltSkillExample {
SKBuilders.textCompletion().withOpenAIAsyncClient(client).withModelId(MODEL).build();
String prompt = "{{$input}}\nSummarize the content above.";
Kernel kernel = SKBuilders.kernel().withDefaultAIService(textCompletion).build();
Kernel kernel = SKBuilders.kernel().withAIService(textCompletion).build();
CompletionKernelFunction summarize =
SKBuilders.completionFunctions()
@@ -27,8 +27,8 @@ public class AbstractKernelTest {
TextCompletion textCompletion = new OpenAITextCompletion(openAIClient, model);
return SKBuilders.kernel()
.withDefaultAIService(textCompletion)
.withDefaultAIService(
.withAIService(textCompletion)
.withAIService(
SKBuilders.textEmbeddingGeneration()
.withOpenAIClient(openAIClient)
.withModelId(model)
@@ -36,7 +36,7 @@ public class Example05_InlineFunctionDefinitionTest {
.build();
Kernel kernel = new DefaultKernel.Builder()
.withDefaultAIService(TextGenerationService.class, textGenerationService)
.withAIService(TextGenerationService.class, textGenerationService)
.build();
System.out.println("======== Inline Function Definition ========");
@@ -150,7 +150,7 @@ class AzureCognitiveSearchMemoryTests {
.withMemoryStorage(
new AzureCognitiveSearchMemoryStore(
searchIndexAsyncClient, memoryRecordMapper))
.withDefaultAIService(embeddingGeneration)
.withAIService(embeddingGeneration)
.build();
}
@@ -49,7 +49,7 @@ public class Example05_InlineFunctionDefinition {
.build();
Kernel kernel = Kernel.builder()
.withDefaultAIService(TextGenerationService.class, textGenerationService)
.withAIService(TextGenerationService.class, textGenerationService)
.build();
System.out.println("======== Inline Function Definition ========");
@@ -62,7 +62,7 @@ public class Example06_TemplateLanguage {
.build();
Kernel kernel = Kernel.builder()
.withDefaultAIService(TextGenerationService.class, textGenerationService)
.withAIService(TextGenerationService.class, textGenerationService)
.build();
// Load native plugin into the kernel function collection, sharing its functions with prompt templates
@@ -35,7 +35,7 @@ public class Example08_RetryHandler {
.build();
Kernel kernel = Kernel.builder()
.withDefaultAIService(TextGenerationService.class, textGenerationService)
.withAIService(TextGenerationService.class, textGenerationService)
.build();
String question = "How popular is the Polly library?";
@@ -108,7 +108,7 @@ public class Example09_FunctionTypes {
);
Kernel kernel = Kernel.builder()
.withDefaultAIService(TextGenerationService.class, textGenerationService)
.withAIService(TextGenerationService.class, textGenerationService)
.withPlugin(plugin)
.withPlugin(summarize)
.withPlugin(examplePlugin)
@@ -91,7 +91,7 @@ public class Example10_DescribeAllPluginsAndFunctions {
.build();
Kernel kernel = Kernel.builder()
.withDefaultAIService(TextGenerationService.class, textGenerationService)
.withAIService(TextGenerationService.class, textGenerationService)
.build();
kernel.getPlugins().add(
@@ -239,7 +239,7 @@ public class Example13_ConversationSummaryPlugin {
.build();
return Kernel.builder()
.withDefaultAIService(TextGenerationService.class, textGenerationService)
.withAIService(TextGenerationService.class, textGenerationService)
.build();
}
}
@@ -31,7 +31,7 @@ public class Example26_AADAuth {
.build();
Kernel kernel = Kernel.builder()
.withDefaultAIService(ChatCompletionService.class, chatService)
.withAIService(ChatCompletionService.class, chatService)
.build();
var chatHistory = new ChatHistory();
@@ -44,7 +44,7 @@ public class Example27_PromptFunctionsUsingChatGPT {
.build();
Kernel kernel = Kernel.builder()
.withDefaultAIService(ChatCompletionService.class, openAIChatCompletion)
.withAIService(ChatCompletionService.class, openAIChatCompletion)
.build();
var func = KernelFunctionFactory.createFromPrompt(
@@ -0,0 +1,57 @@
package com.microsoft.semantickernel.samples.syntaxexamples;
import com.azure.ai.openai.OpenAIAsyncClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.http.HttpClient;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
import com.microsoft.semantickernel.textcompletion.TextGenerationService;
public class Example41_HttpClientUsage {
public static void main(String[] args) {
useDefaultHttpClient();
useCustomHttpClient();
}
/// <summary>
/// Demonstrates the usage of the default HttpClient provided by the SK SDK.
/// </summary>
private static void useDefaultHttpClient() {
OpenAIAsyncClient client = new OpenAIClientBuilder()
.credential(new AzureKeyCredential("a-key"))
.endpoint("an-endpoint")
.buildAsyncClient();
var kernel = Kernel.builder()
.withAIService(ChatCompletionService.class,
ChatCompletionService.builder()
.withOpenAIAsyncClient(client)
.withModelId("gpt-35-turbo-2")
.build())
.build();
}
/// <summary>
/// Demonstrates the usage of a custom HttpClient.
/// </summary>
private static void useCustomHttpClient() {
HttpClient customHttpClient = HttpClient.createDefault();
OpenAIAsyncClient client = new OpenAIClientBuilder()
.httpClient(customHttpClient)
.endpoint("https://localhost:5000")
.credential(new AzureKeyCredential("BAD KEY"))
.buildAsyncClient();
TextGenerationService textGenerationService = TextGenerationService.builder()
.withOpenAIAsyncClient(client)
.withModelId("text-davinci-003")
.build();
Kernel kernel = Kernel.builder()
.withAIService(TextGenerationService.class, textGenerationService)
.build();
}
}
@@ -6,12 +6,12 @@ import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.credential.KeyCredential;
import com.azure.core.http.HttpClient;
import com.microsoft.semantickernel.DefaultKernel;
import com.microsoft.semantickernel.DefaultKernel.DefaultServiceProvider;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
import com.microsoft.semantickernel.plugin.KernelPluginCollection;
import com.microsoft.semantickernel.plugin.KernelPluginFactory;
import com.microsoft.semantickernel.samples.plugins.ConversationSummaryPlugin;
import com.microsoft.semantickernel.services.OrderedAIServiceSelector;
import com.microsoft.semantickernel.textcompletion.TextGenerationService;
import java.util.HashMap;
@@ -28,7 +28,7 @@ public class Example42_KernelBuilder {
.buildAsyncClient();
Kernel kernel1 = Kernel.builder()
.withDefaultAIService(ChatCompletionService.class,
.withAIService(ChatCompletionService.class,
ChatCompletionService.builder()
.withOpenAIAsyncClient(client)
.withModelId("gpt-35-turbo-2")
@@ -51,7 +51,7 @@ public class Example42_KernelBuilder {
.build();
Kernel kernel2 = Kernel.builder()
.withDefaultAIService(TextGenerationService.class, textGenerationService)
.withAIService(TextGenerationService.class, textGenerationService)
.build();
/////////////////////////////////////////////////////////
@@ -68,9 +68,7 @@ public class Example42_KernelBuilder {
// wrapper, ultimately constructing a Kernel
// using the public constructor that's available for anyone to use directly if desired.
Kernel kernel = new DefaultKernel(
new DefaultServiceProvider(
new HashMap<>()
),
new OrderedAIServiceSelector(),
new KernelPluginCollection()
);
}
@@ -0,0 +1,111 @@
package com.microsoft.semantickernel.samples.syntaxexamples;
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.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
import com.microsoft.semantickernel.chatcompletion.ChatHistory;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Logit_bias is an optional parameter that modifies the likelihood of specified tokens appearing in
* a Completion. When using the Token Selection Biases parameter, the bias is added to the logits
* generated by the model prior to sampling.
*/
public class Example49_LogitBias {
private static final boolean USE_AZURE_CLIENT = Boolean.parseBoolean(
System.getenv("USE_AZURE_CLIENT"));
private static final String CLIENT_KEY = System.getenv("CLIENT_KEY");
// Only required if USE_AZURE_CLIENT is true
private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT");
public static void main(String[] args) {
OpenAIAsyncClient client;
if (USE_AZURE_CLIENT) {
client = new OpenAIClientBuilder()
.credential(new AzureKeyCredential(CLIENT_KEY))
.endpoint(CLIENT_ENDPOINT)
.buildAsyncClient();
} else {
client = new OpenAIClientBuilder()
.credential(new KeyCredential(CLIENT_KEY))
.buildAsyncClient();
}
ChatCompletionService openAIChatCompletion = ChatCompletionService.builder()
.withOpenAIAsyncClient(client)
.withModelId("gpt-35-turbo-2")
.build();
Kernel kernel = Kernel.builder()
.withAIService(ChatCompletionService.class, openAIChatCompletion)
.build();
// To use Logit Bias you will need to know the token ids of the words you want to use.
// Getting the token ids using the GPT Tokenizer: https://platform.openai.com/tokenizer
// The following text is the tokenized version of the book related tokens
// "novel literature reading author library story chapter paperback hardcover ebook publishing fiction nonfiction manuscript textbook bestseller bookstore reading list bookworm"
var keys = new int[]{39142, 301, 17649, 5403, 3229, 6875, 3446, 12735, 86831, 2653, 3773,
35097, 23763, 17422, 2536, 58162, 47913, 56185, 1888, 35199, 79761, 5403, 1160, 2363,
56741};
/*
// If using GPT-3 (not GPT-3.5 or GPT-4)
var keys = new int[]{3919, 626, 9285, 3555, 1772, 5888, 1621, 6843, 46771, 1327, 9631,
47179, 12407, 10165, 1729, 24046, 17116, 28979, 1266, 32932, 44346, 3555, 1351, 1492,
25323};
*/
// This will make the model try its best to avoid any of the above related words.
//-100 to potentially ban all the tokens from the list.
Map<Integer, Integer> biases = new HashMap<>();
Arrays.stream(keys)
.asLongStream()
.forEach(key -> biases.put((int) key, -100));
var settings = PromptExecutionSettings.builder()
.withTokenSelectionBiases(biases)
.build();
System.out.println("Chat content:");
System.out.println("------------------------");
var chatHistory = new ChatHistory("You are a librarian expert");
// First user message
chatHistory.addUserMessage("Hi, I'm looking some suggestions");
messageOutputAsync(chatHistory);
var replyMessage = openAIChatCompletion.getChatMessageContentsAsync(chatHistory,
settings, kernel).block();
replyMessage.forEach(message -> chatHistory.addAssistantMessage(message.getContent()));
messageOutputAsync(chatHistory);
chatHistory.addUserMessage(
"I love history and philosophy, I'd like to learn something new about Greece, any suggestion");
messageOutputAsync(chatHistory);
replyMessage = openAIChatCompletion.getChatMessageContentsAsync(chatHistory,
settings, kernel).block();
replyMessage.forEach(message -> chatHistory.addAssistantMessage(message.getContent()));
messageOutputAsync(chatHistory);
}
private static void messageOutputAsync(ChatHistory chatHistory) {
var message = chatHistory.getLastMessage();
System.out.println(message.get().getAuthorRole() + ": " + message.get().getContent());
System.out.println("------------------------");
}
}
@@ -47,7 +47,7 @@ public class Example56_TemplateMethodFunctionsWithMultipleArguments {
.build();
Kernel kernel = Kernel.builder()
.withDefaultAIService(ChatCompletionService.class, openAIChatCompletion)
.withAIService(ChatCompletionService.class, openAIChatCompletion)
.build();
System.out.println("======== TemplateMethodFunctionsWithMultipleArguments ========");
@@ -44,10 +44,8 @@ public class Example58_ConfigureExecutionSettings {
.withOpenAIAsyncClient(client)
.build();
System.out.println("======== Using Chat GPT model for text generation ========");
var kernel = Kernel.builder()
.withDefaultAIService(ChatCompletionService.class, openAIChatCompletion)
.withAIService(ChatCompletionService.class, openAIChatCompletion)
.build();
var prompt = "Hello AI, what can you do for me?";
@@ -0,0 +1,147 @@
package com.microsoft.semantickernel.samples.syntaxexamples;
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.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig;
import com.microsoft.semantickernel.textcompletion.TextGenerationService;
import java.util.Arrays;
import java.util.HashMap;
public class Example61_MultipleLLMs {
private static final boolean USE_AZURE_CLIENT = Boolean.parseBoolean(
System.getenv("USE_AZURE_CLIENT"));
private static final String CLIENT_KEY = System.getenv("CLIENT_KEY");
// Only required if USE_AZURE_CLIENT is true
private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT");
public static void main(String[] args) {
System.out.println("======== Example61_MultipleLLMs ========");
OpenAIAsyncClient client;
if (USE_AZURE_CLIENT) {
client = new OpenAIClientBuilder()
.credential(new AzureKeyCredential(CLIENT_KEY))
.endpoint(CLIENT_ENDPOINT)
.buildAsyncClient();
} else {
client = new OpenAIClientBuilder()
.credential(new KeyCredential(CLIENT_KEY))
.buildAsyncClient();
}
System.out.println("======== Using Chat GPT model for text generation ========");
var openAIChatCompletion = ChatCompletionService.builder()
.withOpenAIAsyncClient(client)
.withServiceId("AzureOpenAIChat")
.withModelId("gpt-35-turbo-2")
.build();
var textGenerationService = TextGenerationService.builder()
.withOpenAIAsyncClient(client)
.withServiceId("AzureOpenAIText")
.withModelId("text-davinci-003")
.build();
var kernel = Kernel.builder()
.withAIService(ChatCompletionService.class, openAIChatCompletion)
.withAIService(TextGenerationService.class, textGenerationService)
.build();
runByServiceIdAsync(kernel, "AzureOpenAIText");
runByServiceIdAsync(kernel, "AzureOpenAIChat");
runByModelIdAsync(kernel, "gpt-35-turbo-2");
runByFirstModelIdAsync(kernel, "text-davinci-003", "gpt-35-turbo-2");
}
public static void runByServiceIdAsync(Kernel kernel, String serviceId) {
System.out.println("======== Service Id: " + serviceId + " ========");
var prompt = "Hello AI, what can you do for me?";
KernelArguments arguments = KernelArguments.builder().build();
KernelFunction func = KernelFunctionFromPrompt
.builder()
.withTemplate(prompt)
.withDefaultExecutionSettings(
PromptExecutionSettings.builder()
.withServiceId(serviceId)
.build()
)
.build();
var result = kernel.invokeAsync(func, arguments, String.class).block();
System.out.println(result.getValue());
}
public static void runByModelIdAsync(Kernel kernel, String modelId) {
System.out.println("======== Model Id: " + modelId + " ========");
var prompt = "Hello AI, what can you do for me?";
var result = kernel.invokeAsync(
KernelFunctionFromPrompt
.builder()
.withTemplate(prompt)
.withDefaultExecutionSettings(
PromptExecutionSettings.builder()
.withModelId(modelId)
.build()
)
.build(),
KernelArguments.builder().build(),
String.class
).block();
System.out.println(result.getValue());
}
public static void runByFirstModelIdAsync(Kernel kernel, String... modelIds) {
System.out.println("======== Model Ids: " + String.join(",", modelIds) + " ========");
var prompt = "Hello AI, what can you do for me?";
var modelSettings = new HashMap<String, PromptExecutionSettings>();
Arrays.stream(modelIds).forEach(
modelId -> {
modelSettings.put(
modelId,
PromptExecutionSettings.builder()
.withModelId(modelId)
.build());
}
);
var promptConfig = new PromptTemplateConfig(prompt);
promptConfig.setName("HelloAI");
promptConfig.setExecutionSettings(modelSettings);
var function = KernelFunctionFromPrompt.create(promptConfig);
var result = kernel.invokeAsync(
function,
KernelArguments.builder().build(),
String.class)
.block();
System.out.println(result.getValue());
}
}
@@ -0,0 +1,110 @@
package com.microsoft.semantickernel.samples.syntaxexamples;
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.microsoft.semantickernel.AIService;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt;
import com.microsoft.semantickernel.services.AIServiceSelection;
import com.microsoft.semantickernel.services.BaseAIServiceSelector;
import com.microsoft.semantickernel.textcompletion.TextGenerationService;
import java.util.Map;
import javax.annotation.Nullable;
public class Example62_CustomAIServiceSelector {
private static final boolean USE_AZURE_CLIENT = Boolean.parseBoolean(
System.getenv("USE_AZURE_CLIENT"));
private static final String CLIENT_KEY = System.getenv("CLIENT_KEY");
// Only required if USE_AZURE_CLIENT is true
private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT");
public static void main(String[] args) {
System.out.println("======== Example62_CustomAIServiceSelector ========");
OpenAIAsyncClient client;
if (USE_AZURE_CLIENT) {
client = new OpenAIClientBuilder()
.credential(new AzureKeyCredential(CLIENT_KEY))
.endpoint(CLIENT_ENDPOINT)
.buildAsyncClient();
} else {
client = new OpenAIClientBuilder()
.credential(new KeyCredential(CLIENT_KEY))
.buildAsyncClient();
}
// Build a kernel with multiple chat completion services
var openAIChatCompletion = ChatCompletionService.builder()
.withOpenAIAsyncClient(client)
.withServiceId("AzureOpenAIChat")
.withModelId("gpt-35-turbo-2")
.build();
var textGenerationService = TextGenerationService.builder()
.withOpenAIAsyncClient(client)
.withServiceId("AzureOpenAIText")
.withModelId("text-davinci-003")
.build();
var kernel = Kernel.builder()
.withAIService(ChatCompletionService.class, openAIChatCompletion)
.withAIService(TextGenerationService.class, textGenerationService)
.withServiceSelector(CustomAIServiceSelector::new)
.build();
var prompt = "Hello AI, what can you do for me?";
KernelArguments arguments = KernelArguments.builder().build();
KernelFunction func = KernelFunctionFromPrompt
.builder()
.withTemplate(prompt)
.withDefaultExecutionSettings(
PromptExecutionSettings.builder()
.withTopP(1.0)
.build()
)
.build();
var result = kernel.invokeAsync(func, arguments, String.class).block();
System.out.println(result.getValue());
}
// A dumb AIServiceSelector that just returns the first service and execution settings it finds
static class CustomAIServiceSelector extends BaseAIServiceSelector {
public CustomAIServiceSelector(Map<Class<? extends AIService>, AIService> services) {
super(services);
}
@Nullable
@Override
public AIServiceSelection trySelectAIService(
Class<? extends AIService> serviceType,
KernelFunction function,
KernelArguments arguments,
Map<Class<? extends AIService>, AIService> services) {
// Just get the first one
PromptExecutionSettings executionSettings = function.getExecutionSettings()
.values().stream().findFirst().get();
AIService service = services.values().stream().findFirst().get();
return new AIServiceSelection(
service,
executionSettings
);
}
}
}
@@ -50,7 +50,7 @@ public class Example64_MultiplePromptTemplates {
.build();
var kernel = new DefaultKernel.Builder()
.withDefaultAIService(ChatCompletionService.class, openAIChatCompletion)
.withAIService(ChatCompletionService.class, openAIChatCompletion)
.build();
var templateFactory = new AggregatorPromptTemplateFactory(
@@ -42,7 +42,7 @@ public class Main {
*/
Kernel kernel = Kernel.builder()
.withDefaultAIService(ChatCompletionService.class, gpt35Turbo)
.withAIService(ChatCompletionService.class, gpt35Turbo)
.withPromptTemplate(new HandlebarsPromptTemplate())
.build();
@@ -61,7 +61,7 @@ public class SimpleChatTest {
private static void execute(ChatCompletionService gpt35Turbo, List<Message> messages)
throws IOException {
Kernel kernel = Kernel.builder()
.withDefaultAIService(ChatCompletionService.class, gpt35Turbo)
.withAIService(ChatCompletionService.class, gpt35Turbo)
.withPromptTemplate(new HandlebarsPromptTemplate())
.build();
@@ -40,7 +40,7 @@ public class Main {
.build();
Kernel kernel = Kernel.builder()
.withDefaultAIService(ChatCompletionService.class, gpt35Turbo)
.withAIService(ChatCompletionService.class, gpt35Turbo)
.withPromptTemplate(new HandlebarsPromptTemplate())
.build();
@@ -55,7 +55,7 @@ public class PersonaChatTest {
ChatCompletionService gpt35Turbo = mockService(messages);
Kernel kernel = Kernel.builder()
.withDefaultAIService(ChatCompletionService.class, gpt35Turbo)
.withAIService(ChatCompletionService.class, gpt35Turbo)
.withPromptTemplate(new HandlebarsPromptTemplate())
.build();
@@ -48,7 +48,7 @@ public class Main {
);
Kernel kernel = Kernel.builder()
.withDefaultAIService(ChatCompletionService.class, gpt35Turbo)
.withAIService(ChatCompletionService.class, gpt35Turbo)
.withPromptTemplate(new HandlebarsPromptTemplate())
.withPlugin(searchPlugin)
.build();
@@ -68,7 +68,7 @@ public class SimpleRagTest {
);
Kernel kernel = Kernel.builder()
.withDefaultAIService(ChatCompletionService.class, gpt35Turbo)
.withAIService(ChatCompletionService.class, gpt35Turbo)
.withPromptTemplate(new HandlebarsPromptTemplate())
.withPlugin(searchPlugin)
.build();
@@ -140,7 +140,7 @@ public class AIServiceCollection {
Map<Class<? extends AIService>, String> defaultsClone = new HashMap<>(defaultIds);
return new DefaultAIServiceProvider(servicesClone, defaultsClone);
return new OrderedAIServiceSelector(servicesClone, defaultsClone);
}
/**
@@ -1,34 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.services;
import com.microsoft.semantickernel.AIService;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.KernelArguments;
import com.microsoft.semantickernel.KernelFunction;
import com.microsoft.semantickernel.PromptExecutionSettings;
/**
* Represents a selector which will return an {@link AIServiceSelection} containing instances
* of {@link AIService} and {@link PromptExecutionSettings} from the specified provider based
* on the model settings.
*/
public interface AIServiceSelector {
/**
* Resolves an {@link IAIService} and associated and {@link PromptExecutionSettings} from
* the specified {@link com.microsoft.semantickernel.Kernel} based on the associated
* {@link com.microsoft.semantickernel.KernelFunction} and {@link com.microsoft.semantickernel.KernelArguments}.
* @param serviceType The type of service to select. This must be the same type
* with which the service was registered in the {@link AIServiceSelectionf}
* @param kernel
* @param function
* @param arguments
* @return
*/
AIServiceSelection trySelectAIService(
Class<? extends AIService> serviceType,
Kernel kernel,
KernelFunction function,
KernelArguments arguments
);
}
@@ -1,20 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.services;
import java.util.Map;
import java.util.function.Supplier;
import com.microsoft.semantickernel.AIService;
/**
* Default implementation of {@link AIServiceProvider}
* @deprecated Use {@code com.microsoft.semantickernel.aiservices.DefaultAIServiceProvider} instead
*/
public class DefaultAIServiceProvider extends DefaultNamedServiceProvider<AIService>
implements AIServiceProvider {
public DefaultAIServiceProvider(
Map<Class<? extends AIService>, Map<String, Supplier<? extends AIService>>> services,
Map<Class<? extends AIService>, String> defaultIds) {
super(services, defaultIds);
}
}
@@ -1,19 +0,0 @@
package com.microsoft.semantickernel.services;
import com.microsoft.semantickernel.AIService;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.KernelArguments;
import com.microsoft.semantickernel.KernelFunction;
public enum OrderedAIServiceSelector implements AIServiceSelector {
INST;
@Override
public AIServiceSelection trySelectAIService(Class<? extends AIService> serviceType, Kernel kernel,
KernelFunction function, KernelArguments arguments) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Not implemented");
}
}
@@ -45,6 +45,8 @@ public interface AIService {
*/
default String getApiVersion() { return getAttribute(this, API_VERSION_KEY); }
String getServiceId();
/// <summary>
/// Gets the specified attribute.
/// </summary>
@@ -1,16 +1,5 @@
package com.microsoft.semantickernel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType;
@@ -20,8 +9,16 @@ import com.microsoft.semantickernel.orchestration.contextvariables.KernelArgumen
import com.microsoft.semantickernel.plugin.KernelPlugin;
import com.microsoft.semantickernel.plugin.KernelPluginCollection;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
import com.microsoft.semantickernel.textcompletion.TextGenerationService;
import com.microsoft.semantickernel.services.AIServiceSelector;
import com.microsoft.semantickernel.services.OrderedAIServiceSelector;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -29,13 +26,13 @@ public class DefaultKernel implements Kernel {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultKernel.class);
private final ServiceProvider serviceProvider;
private final AIServiceSelector serviceSelector;
public KernelPluginCollection plugins;
public DefaultKernel(
ServiceProvider serviceProvider,
AIServiceSelector serviceSelector,
@Nullable KernelPluginCollection plugins) {
this.serviceProvider = serviceProvider;
this.serviceSelector = serviceSelector;
if (plugins != null) {
this.plugins = plugins;
} else {
@@ -104,8 +101,8 @@ public class DefaultKernel implements Kernel {
}
@Override
public ServiceProvider getServiceSelector() {
return serviceProvider;
public AIServiceSelector getServiceSelector() {
return serviceSelector;
}
@Override
@@ -113,51 +110,15 @@ public class DefaultKernel implements Kernel {
return plugins;
}
public static class DefaultServiceProvider implements ServiceProvider {
private final Map<Class<?>, AIService> services;
public DefaultServiceProvider(Map<Class<?>, AIService> services) {
this.services = services;
}
@Nullable
@Override
public <T> T getService(Class<T> clazz) {
T service = (T) services.get(clazz);
if (service == null) {
service = (T) services
.entrySet()
.stream()
.filter(it -> clazz.isAssignableFrom(it.getKey()))
.map(it -> it.getValue())
.findFirst()
.orElseGet(() -> null);
}
if (service == null &&
(clazz.equals(TextGenerationService.class) ||
clazz.equals(ChatCompletionService.class))) {
LOGGER.warn(
"Requested a non-existent service type of {}. Consider requesting a TextAIService instead.",
clazz.getName());
}
return service;
}
}
public static class Builder implements Kernel.Builder {
private AIService defaultAIService;
private final Map<Class<?>, AIService> services = new HashMap<>();
private final Map<Class<? extends AIService>, AIService> services = new HashMap<>();
private final List<KernelPlugin> plugins = new ArrayList<>();
private Function<Map<Class<? extends AIService>, AIService>, AIServiceSelector> serviceSelectorProvider;
@Override
public <T extends AIService> Builder withDefaultAIService(Class<T> clazz, T aiService) {
this.defaultAIService = aiService;
public <T extends AIService> Builder withAIService(Class<T> clazz, T aiService) {
services.put(clazz, aiService);
return this;
}
@@ -173,10 +134,25 @@ public class DefaultKernel implements Kernel {
return this;
}
@Override
public Kernel.Builder withServiceSelector(
Function<Map<Class<? extends AIService>, AIService>, AIServiceSelector> serviceSelector) {
this.serviceSelectorProvider = serviceSelector;
return this;
}
@Override
public Kernel build() {
AIServiceSelector serviceSelector;
if (serviceSelectorProvider == null) {
serviceSelector = new OrderedAIServiceSelector(services);
} else {
serviceSelector = serviceSelectorProvider.apply(services);
}
return new DefaultKernel(
new DefaultServiceProvider(services),
serviceSelector,
new KernelPluginCollection(plugins));
}
}
@@ -10,7 +10,11 @@ import com.microsoft.semantickernel.orchestration.contextvariables.KernelArgumen
import com.microsoft.semantickernel.plugin.KernelPlugin;
import com.microsoft.semantickernel.plugin.KernelPluginCollection;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
import com.microsoft.semantickernel.services.AIServiceSelector;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import javax.annotation.Nullable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -57,7 +61,7 @@ public interface Kernel extends Buildable {
List<KernelFunction> getFunctions();
ServiceProvider getServiceSelector();
AIServiceSelector getServiceSelector();
KernelPluginCollection getPlugins();
@@ -65,12 +69,16 @@ public interface Kernel extends Buildable {
return new DefaultKernel.Builder();
}
interface Builder extends SemanticKernelBuilder<Kernel> {
<T extends AIService> Builder withDefaultAIService(Class<T> clazz, T aiService);
<T extends AIService> Builder withAIService(Class<T> clazz, T aiService);
Builder withPromptTemplate(PromptTemplate promptTemplate);
Builder withPlugin(KernelPlugin searchPlugin);
Builder withServiceSelector(
Function<Map<Class<? extends AIService>, AIService>, AIServiceSelector> serviceSelector);
}
}
@@ -1,23 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel;
import javax.annotation.Nullable;
/**
* Provides access to services.
*/
public interface ServiceProvider {
// TODO: IServiceProvider is a .NET API for DI. Do we need to support this?
// And how does NamedServiceProvider interface fit in?
/**
* Gets the service of the specified type and name, or null if not found.
*
* @param <U> The specific type of the service
* @param clazz The type of the service.
* @return The service instance, or null if not found.
*/
@Nullable
public <U> U getService(Class<U> clazz);
}
@@ -10,6 +10,7 @@ import com.microsoft.semantickernel.builders.SemanticKernelBuilder;
import com.microsoft.semantickernel.builders.ServiceLoadUtil;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
import java.util.List;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings.Builder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -49,6 +50,7 @@ public interface ChatCompletionService extends Buildable, TextAIService {
protected OpenAIAsyncClient client;
protected String modelId;
protected String serviceId;
/**
@@ -74,6 +76,10 @@ public interface ChatCompletionService extends Buildable, TextAIService {
return this;
}
public Builder withServiceId(String serviceId) {
this.serviceId = serviceId;
return this;
}
}
}
@@ -60,4 +60,10 @@ public abstract class DefaultKernelFunction implements KernelFunction {
public String toManualString(boolean includeOutputs) {
return null;
}
@Override
@Nullable
public Map<String, PromptExecutionSettings> getExecutionSettings() {
return executionSettings;
}
}
@@ -101,6 +101,9 @@ public interface KernelFunction extends Buildable {
@Nullable KernelArguments arguments,
ContextVariableType<T> variableType);
@Nullable
Map<String, PromptExecutionSettings> getExecutionSettings();
interface FromPromptBuilder {
FromPromptBuilder withName(String name);
@@ -1,20 +1,18 @@
package com.microsoft.semantickernel.orchestration;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.microsoft.semantickernel.DefaultKernel;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.Kernel.Builder;
import java.util.List;
import java.util.Map;
public class PromptExecutionSettings {
public static final String DEFAULT_SERVICE_ID = "default";
public static final int DEFAULT_MAX_TOKENS = 256;
public static final double DEFAULT_TEMPERATURE = 1.0;
public static final double DEFAULT_TOP_P = 1.0;
public static final double DEFAULT_PRESENCE_PENALTY = 0.0;
public static final double DEFAULT_FREQUENCY_PENALTY = 0.0;
public static final int DEFAULT_BEST_OF = 1;
public static final int DEFAULT_BEST_OF = 1;
/// <summary>
/// Service identifier.
@@ -38,6 +36,11 @@ public class PromptExecutionSettings {
private final String user;
private final List<String> stopSequences;
/// <summary>
/// Modify the likelihood of specified tokens appearing in the completion.
/// </summary>s
public Map<Integer, Integer> tokenSelectionBiases;
public PromptExecutionSettings(
@JsonProperty("service_id") String serviceId,
@JsonProperty("model_id") String modelId,
@@ -48,7 +51,8 @@ public class PromptExecutionSettings {
@JsonProperty("max_tokens") int maxTokens,
@JsonProperty("best_of") int bestOf,
@JsonProperty("user") String user,
@JsonProperty(value = "stop_sequences") List<String> stopSequences) {
@JsonProperty(value = "stop_sequences") List<String> stopSequences,
@JsonProperty(value = "token_selection_biases") Map<Integer, Integer> tokenSelectionBiases) {
this.serviceId = serviceId;
this.modelId = modelId;
this.temperature = temperature;
@@ -59,6 +63,7 @@ public class PromptExecutionSettings {
this.bestOf = bestOf;
this.user = user;
this.stopSequences = stopSequences;
this.tokenSelectionBiases = tokenSelectionBiases;
}
public String getServiceId() {
@@ -102,6 +107,10 @@ public class PromptExecutionSettings {
return stopSequences;
}
public Map<Integer, Integer> getTokenSelectionBiases() {
return tokenSelectionBiases;
}
public static Builder builder() {
return new Builder();
@@ -119,6 +128,7 @@ public class PromptExecutionSettings {
private int bestOf = Integer.MIN_VALUE;
private String user;
private List<String> stopSequences;
public Map<Integer, Integer> tokenSelectionBiases;
public Builder withServiceId(String serviceId) {
this.serviceId = serviceId;
@@ -170,18 +180,24 @@ public class PromptExecutionSettings {
return this;
}
public Builder withTokenSelectionBiases(Map<Integer, Integer> tokenSelectionBiases) {
this.tokenSelectionBiases = tokenSelectionBiases;
return this;
}
public PromptExecutionSettings build() {
return new PromptExecutionSettings(
serviceId,
modelId,
modelId,
Double.isNaN(temperature) ? DEFAULT_TEMPERATURE : temperature,
Double.isNaN(topP) ? DEFAULT_TOP_P : topP,
Double.isNaN(presencePenalty) ? DEFAULT_PRESENCE_PENALTY : presencePenalty,
Double.isNaN(frequencyPenalty) ? DEFAULT_FREQUENCY_PENALTY : frequencyPenalty,
maxTokens == Integer.MIN_VALUE ? DEFAULT_MAX_TOKENS : maxTokens,
bestOf == Integer.MIN_VALUE ? DEFAULT_BEST_OF : bestOf,
user,
stopSequences
user,
stopSequences,
tokenSelectionBiases
);
}
}
@@ -3,6 +3,7 @@ package com.microsoft.semantickernel.orchestration.contextvariables;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
@@ -15,6 +16,7 @@ import reactor.util.annotation.NonNull;
public class DefaultKernelArguments implements KernelArguments, WritableKernelArguments {
private final CaseInsensitiveMap<ContextVariable<?>> variables;
private final Map<String, PromptExecutionSettings> executionSettings;
/// <summary>
/// In the simplest scenario, the data is an input string, stored here.
@@ -25,17 +27,45 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr
/// Constructor for context variables.
/// </summary>
/// <param name="content">Optional value for the main variable of the context.</param>
DefaultKernelArguments(@NonNull ContextVariable<?> content) {
public DefaultKernelArguments(@NonNull ContextVariable<?> content) {
this.variables = new CaseInsensitiveMap<>();
this.variables.put(MAIN_KEY, content);
this.executionSettings = new HashMap<>();
}
public DefaultKernelArguments(Map<String, ContextVariable<?>> variables) {
this.variables = new CaseInsensitiveMap<>(variables);
public DefaultKernelArguments(
Map<String, ContextVariable<?>> variables,
Map<String, PromptExecutionSettings> executionSettings) {
if (variables == null) {
this.variables = new CaseInsensitiveMap<>();
} else {
this.variables = new CaseInsensitiveMap<>(variables);
}
if (executionSettings == null) {
this.executionSettings = new HashMap<>();
} else {
this.executionSettings = new HashMap<>(executionSettings);
}
}
public DefaultKernelArguments(
Map<String, ContextVariable<?>> variables,
@Nullable
PromptExecutionSettings executionSettings) {
this.variables = new CaseInsensitiveMap<>(variables);
this.executionSettings = new HashMap<>();
this.executionSettings.put(PromptExecutionSettings.DEFAULT_SERVICE_ID, executionSettings);
}
public DefaultKernelArguments() {
this.variables = new CaseInsensitiveMap<>();
this.executionSettings = new HashMap<>();
}
public DefaultKernelArguments(KernelArguments arguments) {
this(arguments, new HashMap<>());
}
@Override
@@ -93,7 +123,7 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr
@Override
public WritableKernelArguments writableClone() {
return new DefaultKernelArguments(variables);
return new DefaultKernelArguments(variables, executionSettings);
}
@Override
@@ -134,8 +164,8 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr
@Nullable
@Override
public PromptExecutionSettings getExecutionSettings() {
return null;
public Map<String, PromptExecutionSettings> getExecutionSettings() {
return executionSettings;
}
@Override
@@ -209,21 +239,17 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr
return variables.entrySet();
}
public static class WritableBuilder implements WritableKernelArguments.Builder {
@Override
public WritableKernelArguments build(Map<String, ContextVariable<?>> map) {
return new DefaultKernelArguments(map);
}
}
public static class Builder implements KernelArguments.Builder {
private final DefaultKernelArguments variables;
private final Map<String, PromptExecutionSettings> executionSettings;
public Builder() {
variables = new DefaultKernelArguments();
this.variables.put(MAIN_KEY, ContextVariable.of(""));
executionSettings = new HashMap<>();
}
@Override
@@ -257,7 +283,7 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr
@Override
public KernelArguments build() {
return new DefaultKernelArguments(variables);
return new DefaultKernelArguments(variables, executionSettings);
}
}
}
@@ -61,7 +61,7 @@ public interface KernelArguments extends Buildable, Map<String, ContextVariable<
<T> ContextVariable<T> get(String key, Class<T> clazz);
@Nullable
PromptExecutionSettings getExecutionSettings();
Map<String, PromptExecutionSettings> getExecutionSettings();
static Builder builder() {
return new DefaultKernelArguments.Builder();
@@ -123,7 +123,8 @@ public class KernelFunctionFactory {
PromptTemplateConfig promptConfig) {
return new KernelFunctionFromPrompt(
promptTemplate,
promptConfig);
promptConfig,
null);
}
private static Map<String, PromptExecutionSettings> createSettingsDictionary(
@@ -22,6 +22,7 @@ import com.microsoft.semantickernel.orchestration.StreamingContent;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.services.AIServiceSelection;
import com.microsoft.semantickernel.textcompletion.TextGenerationService;
import reactor.core.publisher.Flux;
@@ -33,7 +34,11 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
private final PromptTemplate template;
public KernelFunctionFromPrompt(PromptTemplate template, PromptTemplateConfig promptConfig) {
public KernelFunctionFromPrompt(
PromptTemplate template,
PromptTemplateConfig promptConfig,
@Nullable
Map<String, PromptExecutionSettings> executionSettings) {
super(
new KernelFunctionMetadata(
promptConfig.getName(),
@@ -41,11 +46,44 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
promptConfig.getKernelParametersMetadata(),
promptConfig.getKernelReturnParameterMetadata()
),
promptConfig.getExecutionSettings()
executionSettings != null ? executionSettings : promptConfig.getExecutionSettings()
);
this.template = template;
}
public static KernelFunction create(
PromptTemplateConfig promptConfig
) {
return create(
promptConfig,
null
);
}
public static KernelFunction create(
PromptTemplateConfig promptConfig,
@Nullable
PromptTemplateFactory promptTemplateFactory
) {
if (promptTemplateFactory == null) {
promptTemplateFactory = new KernelPromptTemplateFactory();
}
return create(promptTemplateFactory.tryCreate(promptConfig), promptConfig);
}
public static KernelFunction create(
PromptTemplate promptTemplate,
PromptTemplateConfig promptConfig) {
return new KernelFunctionFromPrompt(
promptTemplate,
promptConfig,
null
);
}
@Override
public <T> Flux<StreamingContent<T>> invokeStreamingAsync(
Kernel kernel,
@@ -57,9 +95,15 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
.flatMapMany(prompt -> {
LOGGER.info("RENDERED PROMPT: \n{}", prompt);
AIService client = kernel
AIServiceSelection aiServiceSelection = kernel
.getServiceSelector()
.getService(TextAIService.class);
.trySelectAIService(
TextAIService.class,
this,
arguments
);
AIService client = aiServiceSelection.getService();
if (client == null) {
throw new IllegalStateException(
@@ -68,12 +112,7 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
Flux<StreamingContent<T>> result;
PromptExecutionSettings executionSettings;
if (arguments != null) {
executionSettings = arguments.getExecutionSettings();
} else {
executionSettings = null;
}
PromptExecutionSettings executionSettings = aiServiceSelection.getSettings();
if (client instanceof ChatCompletionService) {
result = ((ChatCompletionService) client)
@@ -234,7 +273,10 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
@Override
public FromPromptBuilder withExecutionSettings(
Map<String, PromptExecutionSettings> executionSettings) {
this.executionSettings = executionSettings;
if (this.executionSettings == null) {
this.executionSettings = new HashMap<>();
}
this.executionSettings.putAll(executionSettings);
return this;
}
@@ -244,7 +286,12 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
if (this.executionSettings == null) {
this.executionSettings = new HashMap<>();
}
this.executionSettings.put("default", executionSettings);
if (executionSettings.getServiceId() != null) {
this.executionSettings.put(executionSettings.getServiceId(), executionSettings);
}
return this;
}
@@ -301,14 +348,15 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
temp = new KernelPromptTemplateFactory().tryCreate(config);
}
return new KernelFunctionFromPrompt(temp, config);
return new KernelFunctionFromPrompt(temp, config, executionSettings);
}
public KernelFunction build(PromptTemplateConfig functionModel) {
return new KernelFunctionFromPrompt(
promptTemplate,
functionModel
functionModel,
executionSettings
);
}
}
@@ -29,7 +29,8 @@ public class OutputVariable {
try {
return this.getClass().getClassLoader().loadClass(type);
} catch (ClassNotFoundException e) {
throw new SKException("Could not find return type " + type);
throw new SKException("Requested output type could not be found: " + type
+ ", note this needs to be a fully qualified class name, i.e 'java.lang.String'.");
}
}
}
@@ -45,7 +45,7 @@ public class PromptTemplateConfig {
SEMANTIC_KERNEL_TEMPLATE_FORMAT,
"",
Collections.emptyList(),
new OutputVariable("out", "string"),
new OutputVariable("out", String.class.getName()),
Collections.emptyMap()
);
}
@@ -122,12 +122,12 @@ public class PromptTemplateConfig {
.collect(Collectors.toList());
}
public KernelReturnParameterMetadata getKernelReturnParameterMetadata() {
public KernelReturnParameterMetadata<?> getKernelReturnParameterMetadata() {
if (outputVariable == null) {
return new KernelReturnParameterMetadata("", String.class);
return new KernelReturnParameterMetadata<>("", String.class);
}
return new KernelReturnParameterMetadata(
return new KernelReturnParameterMetadata<>(
outputVariable.getDescription(),
outputVariable.getType()
);
@@ -2,7 +2,7 @@
package com.microsoft.semantickernel.services;
import com.microsoft.semantickernel.AIService;
import com.microsoft.semantickernel.PromptExecutionSettings;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
/**
* The result of an AI service selection.
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.services;
import com.microsoft.semantickernel.AIService;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import javax.annotation.Nullable;
/**
* Represents a selector which will return an {@link AIServiceSelection} containing instances of
* {@link AIService} and {@link com.microsoft.semantickernel.orchestration.PromptExecutionSettings}
* from the specified provider based on the model settings.
*/
public interface AIServiceSelector {
/**
* Resolves an {@link AIService} and associated and
* {@link com.microsoft.semantickernel.orchestration.PromptExecutionSettings} from the specified
* {@link com.microsoft.semantickernel.Kernel} based on the associated {@link KernelFunction}
* and {@link KernelArguments}.
*
* @param serviceType The type of service to select. This must be the same type with which the
* service was registered in the {@link AIServiceSelection}
* @param function
* @param arguments
* @return
*/
@Nullable
AIServiceSelection trySelectAIService(
Class<? extends AIService> serviceType,
KernelFunction function,
KernelArguments arguments
);
}
@@ -0,0 +1,36 @@
package com.microsoft.semantickernel.services;
import com.microsoft.semantickernel.AIService;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import java.util.Map;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class BaseAIServiceSelector implements AIServiceSelector {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseAIServiceSelector.class);
protected final Map<Class<? extends AIService>, AIService> services;
protected BaseAIServiceSelector(Map<Class<? extends AIService>, AIService> services) {
this.services = services;
}
@Override
@Nullable
public AIServiceSelection trySelectAIService(
Class<? extends AIService> serviceType,
KernelFunction function,
KernelArguments arguments) {
return trySelectAIService(serviceType, function, arguments, services);
}
@Nullable
public abstract AIServiceSelection trySelectAIService(
Class<? extends AIService> serviceType,
KernelFunction function,
KernelArguments arguments,
Map<Class<? extends AIService>, AIService> services);
}
@@ -0,0 +1,204 @@
package com.microsoft.semantickernel.services;
import com.microsoft.semantickernel.AIService;
import com.microsoft.semantickernel.Verify;
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.textcompletion.TextGenerationService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OrderedAIServiceSelector extends BaseAIServiceSelector {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderedAIServiceSelector.class);
public OrderedAIServiceSelector() {
super(new HashMap<>());
}
public OrderedAIServiceSelector(Map<Class<? extends AIService>, AIService> services) {
super(services);
}
@Nullable
public AIServiceSelection trySelectAIService(
Class<? extends AIService> serviceType,
KernelFunction function,
KernelArguments arguments,
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);
executionSettings = settingsFromFunctionSettings(function, executionSettings);
if (executionSettings == null || executionSettings.isEmpty()) {
AIService service = getAnyService(serviceType);
if (service != null) {
return new AIServiceSelection(service, null);
}
} else {
AIServiceSelection selection = executionSettings
.entrySet()
.stream()
.map(keyValue -> {
PromptExecutionSettings settings = keyValue.getValue();
String serviceId = keyValue.getKey();
if (!Verify.isNullOrEmpty(serviceId)) {
AIService service = getService(serviceId);
if (service != null) {
return new AIServiceSelection(service, settings);
}
}
return null;
})
.filter(Objects::nonNull)
.findFirst()
.orElseGet(() -> null);
if (selection != null) {
return selection;
}
selection = executionSettings
.entrySet()
.stream()
.map(keyValue -> {
PromptExecutionSettings settings = keyValue.getValue();
if (!Verify.isNullOrEmpty(settings.getModelId())) {
AIService service = getServiceByModelId(settings.getModelId());
if (service != null) {
return new AIServiceSelection(service, settings);
}
}
return null;
})
.filter(Objects::nonNull)
.findFirst()
.orElseGet(() -> null);
if (selection != null) {
return selection;
}
}
AIService defaultService = getService(PromptExecutionSettings.DEFAULT_SERVICE_ID);
if (defaultService != null && serviceType.isAssignableFrom(defaultService.getClass())) {
return new AIServiceSelection(defaultService, null);
}
AIService service = getAnyService(serviceType);
PromptExecutionSettings settings;
if (executionSettings.containsKey(PromptExecutionSettings.DEFAULT_SERVICE_ID)) {
executionSettings.get(PromptExecutionSettings.DEFAULT_SERVICE_ID);
settings = executionSettings.get(PromptExecutionSettings.DEFAULT_SERVICE_ID);
} else {
settings = executionSettings.values().stream().findFirst().orElseGet(null);
}
if (service != null) {
return new AIServiceSelection(service, settings);
}
LOGGER.warn("No service found meeting requirements");
return null;
}
@Nullable
private static Map<String, PromptExecutionSettings> settingsFromFunctionSettings(
KernelFunction function, Map<String, PromptExecutionSettings> executionSettings) {
if (executionSettings == null || executionSettings.isEmpty()) {
executionSettings = function.getExecutionSettings();
}
return executionSettings;
}
@Nullable
private static Map<String, PromptExecutionSettings> settingsFromArguments(
KernelArguments arguments, Map<String, PromptExecutionSettings> executionSettings) {
if (arguments != null) {
executionSettings = arguments.getExecutionSettings();
}
return executionSettings;
}
private AIService getServiceByModelId(String modelId) {
return services
.values()
.stream()
.filter(s -> modelId.equalsIgnoreCase(s.getModelId()))
.findFirst()
.orElseGet(() -> null);
}
@Nullable
public AIService getService(String serviceId) {
return services
.values()
.stream()
.filter(s -> serviceId.equalsIgnoreCase(s.getServiceId()))
.findFirst()
.orElseGet(() -> null);
}
@Nullable
public <T> T getService(Class<T> clazz) {
T service = (T) services.get(clazz);
if (service == null) {
service = (T) services
.entrySet()
.stream()
.filter(it -> clazz.isAssignableFrom(it.getKey()))
.map(it -> it.getValue())
.findFirst()
.orElseGet(() -> null);
}
if (service == null &&
(clazz.equals(TextGenerationService.class) ||
clazz.equals(ChatCompletionService.class))) {
LOGGER.warn(
"Requested a non-existent service type of {}. Consider requesting a TextAIService instead.",
clazz.getName());
}
return service;
}
AIService getAnyService(Class<? extends AIService> serviceType) {
List<AIService> services = getServices(serviceType);
if (services.isEmpty()) {
return null;
}
return services.get(0);
}
private List<AIService> getServices(Class<? extends AIService> serviceType) {
return
services
.entrySet()
.stream()
.filter(it -> serviceType.isAssignableFrom(it.getKey()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}
}
@@ -11,6 +11,7 @@ import com.microsoft.semantickernel.orchestration.contextvariables.DefaultKernel
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.templateengine.semantickernel.TemplateException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
@@ -60,6 +60,7 @@ public interface TextGenerationService extends Buildable, TextAIService {
protected String modelId;
protected OpenAIAsyncClient client;
protected String serviceId;
public Builder withModelId(String modelId) {
this.modelId = modelId;
@@ -71,6 +72,12 @@ public interface TextGenerationService extends Buildable, TextAIService {
return this;
}
public Builder withServiceId(String serviceId) {
this.serviceId = serviceId;
return this;
}
public abstract TextGenerationService build();
}
}