Standardize the Java builder pattern class layout (#1179)

### 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 SK Contribution Guidelines
(https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
- [ ] The code follows the .NET coding conventions
(https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions)
verified with `dotnet format`
- [ ] All unit tests pass, and I have added new tests where possible
- [ ] I didn't break anyone 😄

---------

Co-authored-by: Mark Wallace <[email protected]>
This commit is contained in:
John Oliver
2023-05-24 11:23:38 +01:00
committed by GitHub
co-authored by Mark Wallace
parent 2223a8b319
commit 0e7a7db8aa
25 changed files with 102 additions and 199 deletions
@@ -45,7 +45,7 @@ public class TextEmbeddingsTest extends AbstractKernelTest {
@EnabledIf("isAzureTestEnabled")
public void testEmbeddingGeneration() throws IOException {
String model = "text-embedding-ada-002";
EmbeddingGeneration<String, Double> embeddingGeneration =
EmbeddingGeneration<String, Float> embeddingGeneration =
new OpenAITextEmbeddingGeneration(getOpenAIClient(), model);
List<String> data = new ArrayList<>();
@@ -58,7 +58,7 @@ public class TextEmbeddingsTest extends AbstractKernelTest {
@EnabledIf("isAzureTestEnabled")
public void testMemory() throws IOException {
String model = "text-embedding-ada-002";
EmbeddingGeneration<String, Double> embeddingGeneration =
EmbeddingGeneration<String, Float> embeddingGeneration =
new OpenAITextEmbeddingGeneration(getAzureOpenAIClient(), model);
Kernel kernel = buildTextEmbeddingsKernel();
@@ -92,7 +92,7 @@ public class TextEmbeddingsTest extends AbstractKernelTest {
public void testEmbeddingGeneration(OpenAIAsyncClient client, int expectedEmbeddingSize) {
String model = "text-embedding-ada-002";
EmbeddingGeneration<String, Double> embeddingGeneration =
EmbeddingGeneration<String, Float> embeddingGeneration =
new OpenAITextEmbeddingGeneration(client, model);
List<String> data = new ArrayList<>();
@@ -111,7 +111,7 @@ public class TextEmbeddingsTest extends AbstractKernelTest {
private Kernel buildTextEmbeddingsKernel() throws IOException {
String model = "text-embedding-ada-002";
EmbeddingGeneration<String, Double> embeddingGeneration =
EmbeddingGeneration<String, Float> embeddingGeneration =
new OpenAITextEmbeddingGeneration(getOpenAIClient(), model);
KernelConfig kernelConfig =
@@ -15,13 +15,13 @@ public final class KernelConfig {
private static final String DEFAULT_SERVICE_ID = "__SK_DEFAULT";
private final Map<String, Function<Kernel, TextCompletion>> textCompletionServices;
private final Map<String, Function<Kernel, EmbeddingGeneration<String, Double>>>
private final Map<String, Function<Kernel, EmbeddingGeneration<String, Float>>>
textEmbeddingGenerationServices;
private final ArrayList<SKFunction<?, ?>> skills;
public KernelConfig(
Map<String, Function<Kernel, TextCompletion>> textCompletionServices,
Map<String, Function<Kernel, EmbeddingGeneration<String, Double>>>
Map<String, Function<Kernel, EmbeddingGeneration<String, Float>>>
textEmbeddingGenerationServices,
List<SKFunction<?, ?>> skills) {
this.textCompletionServices = new HashMap<>();
@@ -60,7 +60,7 @@ public final class KernelConfig {
private List<SKFunction<?, ?>> skillBuilders = new ArrayList<>();
private Map<String, Function<Kernel, EmbeddingGeneration<String, Double>>>
private Map<String, Function<Kernel, EmbeddingGeneration<String, Float>>>
textEmbeddingGenerationServices = new HashMap<>();
public Builder addSkill(SKFunction<?, ?> functionDefinition) {
@@ -85,7 +85,7 @@ public final class KernelConfig {
public Builder addTextEmbeddingsGenerationService(
String serviceId,
Function<Kernel, EmbeddingGeneration<String, Double>> serviceFactory) {
Function<Kernel, EmbeddingGeneration<String, Float>> serviceFactory) {
if (serviceId == null || serviceId.isEmpty()) {
throw new IllegalArgumentException("Null or empty serviceId");
}
@@ -21,17 +21,17 @@ public enum BuildersSingleton {
private static final String FALLBACK_FUNCTION_BUILDER_CLASS =
"com.microsoft.semantickernel.SkFunctionBuilders";
private static final String FALLBACK_KERNEL_BUILDER_CLASS =
"com.microsoft.semantickernel.DefaultKernelBuilder";
"com.microsoft.semantickernel.DefaultKernel$Builder";
private static final String FALLBACK_TEXT_COMPLETION_BUILDER_CLASS =
"com.microsoft.semantickernel.connectors.ai.openai.textcompletion.OpenAITextCompletionBuilder";
"com.microsoft.semantickernel.connectors.ai.openai.textcompletion.OpenAITextCompletion$Builder";
private static final String FALLBACK_TEXT_EMBEDDING_GENERATION_BUILDER_CLASS =
"com.microsoft.semantickernel.connectors.ai.openai.textembeddings.OpenAITextEmbeddingGenerationBuilder";
"com.microsoft.semantickernel.connectors.ai.openai.textembeddings.OpenAITextEmbeddingGeneration$Builder";
private static final String FALLBACK_SKILL_COLLECTION_BUILDER_CLASS =
"com.microsoft.semantickernel.skilldefinition.DefaultCollection$Builder";
private static final String FALLBACK_PROMPT_TEMPLATE_BUILDER_CLASS =
"com.microsoft.semantickernel.semanticfunctions.DefaultPromptTemplateBuilder";
"com.microsoft.semantickernel.semanticfunctions.DefaultPromptTemplate$Builder";
private static final String FALLBACK_VARIABLE_BUILDER_CLASS =
"com.microsoft.semantickernel.orchestration.DefaultContextVariables$Builder";
@@ -44,7 +44,7 @@ public enum BuildersSingleton {
private final FunctionBuilders functionBuilders;
private final Kernel.InternalBuilder kernelBuilder;
private final TextCompletion.Builder textCompletionBuilder;
private final EmbeddingGeneration.Builder<String, Double> textEmbeddingGenerationBuilder;
private final EmbeddingGeneration.Builder<String, Float> textEmbeddingGenerationBuilder;
private final ReadOnlySkillCollection.Builder readOnlySkillCollection;
private final PromptTemplate.Builder promptTemplate;
private final ContextVariables.Builder variables;
@@ -129,7 +129,7 @@ public enum BuildersSingleton {
return textCompletionBuilder;
}
public EmbeddingGeneration.Builder<String, Double> getTextEmbeddingGenerationBuilder() {
public EmbeddingGeneration.Builder<String, Float> getTextEmbeddingGenerationBuilder() {
return textEmbeddingGenerationBuilder;
}
@@ -13,6 +13,8 @@ import com.microsoft.semantickernel.textcompletion.CompletionSKFunction;
import com.microsoft.semantickernel.textcompletion.TextCompletion;
public class SKBuilders {
// Prevent creating object
private SKBuilders() {}
public static CompletionSKFunction.Builder completionFunctions() {
return FunctionBuilders.getCompletionBuilder();
@@ -22,7 +24,7 @@ public class SKBuilders {
return BuildersSingleton.INST.getTextCompletionBuilder();
}
public static EmbeddingGeneration.Builder<String, Double> textEmbeddingGenerationService() {
public static EmbeddingGeneration.Builder<String, Float> textEmbeddingGenerationService() {
return BuildersSingleton.INST.getTextEmbeddingGenerationBuilder();
}
@@ -13,6 +13,8 @@ import java.util.ServiceLoader;
public class ServiceLoadUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceLoadUtil.class);
private ServiceLoadUtil() {}
public static <T> T findServiceLoader(Class<T> clazz, String alternativeClassName) {
List<T> services = findAllServiceLoaders(clazz);
if (services.size() > 0) {
@@ -21,6 +21,8 @@ import java.util.Map;
public class KernelExtensions {
private static final Logger LOGGER = LoggerFactory.getLogger(KernelExtensions.class);
private KernelExtensions() {}
public static Map<String, SemanticFunctionConfig> importSemanticSkillFromDirectory(
String parentDirectory, String skillDirectoryName) {
@@ -27,7 +27,9 @@ public interface PromptTemplate {
*/
Mono<String> renderAsync(SKContext executionContext, PromptTemplateEngine promptTemplateEngine);
interface Builder {
PromptTemplate build(String promptTemplate, PromptTemplateConfig config);
abstract class Builder {
protected Builder() {}
public abstract PromptTemplate build(String promptTemplate, PromptTemplateConfig config);
}
}
@@ -207,63 +207,4 @@ public class PromptTemplateConfig {
public String getDescription() {
return description;
}
/*
/// <summary>
/// Completion configuration parameters.
/// </summary>
[JsonPropertyName("completion")]
[JsonPropertyOrder(4)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
*/
// private final CompletionConfig completion = null;
/*
/// <summary>
/// Default AI services to use.
/// </summary>
[JsonPropertyName("default_services")]
[JsonPropertyOrder(5)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<string> DefaultServices { get; set; } = new();
/// <summary>
/// Input configuration (that is, list of all input parameters).
/// </summary>
[JsonPropertyName("input")]
[JsonPropertyOrder(6)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public InputConfig Input { get; set; } = new();
/// <summary>
/// Remove some default properties to reduce the JSON complexity.
/// </summary>
/// <returns>Compacted prompt template configuration.</returns>
public PromptTemplateConfig Compact()
{
if (this.Completion.StopSequences.Count == 0)
{
this.Completion.StopSequences = null!;
}
if (this.DefaultServices.Count == 0)
{
this.DefaultServices = null!;
}
return this;
}
/// <summary>
/// Creates a prompt template configuration from JSON.
/// </summary>
/// <param name="json">JSON of the prompt template configuration.</param>
/// <returns>Prompt template configuration.</returns>
public static PromptTemplateConfig FromJson(String json) throws JsonProcessingException {
return new ObjectMapper().readValue(json, PromptTemplateConfig.class);
}
*/
}
@@ -30,7 +30,9 @@ public interface PromptTemplateEngine {
*/
Mono<String> renderAsync(String templateText, SKContext context);
interface Builder {
PromptTemplateEngine build();
abstract class Builder {
protected Builder() {}
public abstract PromptTemplateEngine build();
}
}
@@ -15,28 +15,31 @@ public interface CompletionSKFunction
return BuildersSingleton.INST.getFunctionBuilders().completionBuilders(null);
}
interface Builder {
CompletionSKFunction createFunction(
abstract class Builder {
protected Builder() {}
public abstract CompletionSKFunction createFunction(
String promptTemplate,
PromptTemplateConfig config,
String functionName,
@Nullable String skillName);
CompletionSKFunction createFunction(
public abstract CompletionSKFunction createFunction(
String functionName, SemanticFunctionConfig functionConfig);
CompletionSKFunction createFunction(
public abstract CompletionSKFunction createFunction(
@Nullable String skillNameFinal,
String functionName,
SemanticFunctionConfig functionConfig);
CompletionSKFunction createFunction(
public abstract CompletionSKFunction createFunction(
String promptTemplate,
@Nullable String functionName,
@Nullable String skillName,
@Nullable String description);
CompletionSKFunction createFunction(
public abstract CompletionSKFunction createFunction(
String prompt,
@Nullable String functionName,
@Nullable String skillName,
@@ -20,7 +20,9 @@ public interface TextCompletion {
*/
Mono<List<String>> completeAsync(String text, CompletionRequestSettings requestSettings);
interface Builder {
TextCompletion build(OpenAIAsyncClient client, String modelId);
abstract class Builder {
protected Builder() {}
public abstract TextCompletion build(OpenAIAsyncClient client, String modelId);
}
}
@@ -66,4 +66,12 @@ public class OpenAITextCompletion extends ClientBase implements TextCompletion {
.mapNotNull(Choice::getText)
.collectList();
}
public static final class Builder extends TextCompletion.Builder {
@Override
public TextCompletion build(OpenAIAsyncClient client, String modelId) {
return new OpenAITextCompletion(client, modelId);
}
}
}
@@ -1,12 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.connectors.ai.openai.textcompletion;
import com.microsoft.openai.OpenAIAsyncClient;
import com.microsoft.semantickernel.textcompletion.TextCompletion;
public class OpenAITextCompletionBuilder implements TextCompletion.Builder {
@Override
public TextCompletion build(OpenAIAsyncClient client, String modelId) {
return new OpenAITextCompletion(client, modelId);
}
}
@@ -12,27 +12,40 @@ import com.microsoft.semantickernel.connectors.ai.openai.azuresdk.ClientBase;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.stream.Collectors;
public class OpenAITextEmbeddingGeneration extends ClientBase
implements EmbeddingGeneration<String, Double> {
implements EmbeddingGeneration<String, Float> {
public OpenAITextEmbeddingGeneration(OpenAIAsyncClient client, String modelId) {
super(client, modelId);
}
@Override
public Mono<List<Embedding<Double>>> generateEmbeddingsAsync(List<String> data) {
public Mono<List<Embedding<Float>>> generateEmbeddingsAsync(List<String> data) {
return this.internalGenerateTextEmbeddingsAsync(data);
}
protected Mono<List<Embedding<Double>>> internalGenerateTextEmbeddingsAsync(List<String> data) {
protected Mono<List<Embedding<Float>>> internalGenerateTextEmbeddingsAsync(List<String> data) {
EmbeddingsOptions options = new EmbeddingsOptions(data).setModel(getModelId());
return getClient()
.getEmbeddings(getModelId(), options)
.flatMapIterable(Embeddings::getData)
.mapNotNull(EmbeddingItem::getEmbedding)
.map(
embedding ->
embedding.stream()
.map(Double::floatValue)
.collect(Collectors.toList()))
.mapNotNull(Embedding::new)
.collectList();
}
public static class Builder implements EmbeddingGeneration.Builder<String, Float> {
@Override
public EmbeddingGeneration<String, Float> build(OpenAIAsyncClient client, String modelId) {
return new OpenAITextEmbeddingGeneration(client, modelId);
}
}
}
@@ -1,14 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.connectors.ai.openai.textembeddings;
import com.microsoft.openai.OpenAIAsyncClient;
import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration;
public class OpenAITextEmbeddingGenerationBuilder
implements EmbeddingGeneration.Builder<String, Double> {
@Override
public EmbeddingGeneration<String, Double> build(OpenAIAsyncClient client, String modelId) {
return new OpenAITextEmbeddingGeneration(client, modelId);
}
}
@@ -0,0 +1 @@
com.microsoft.semantickernel.connectors.ai.openai.textembeddings.OpenAITextEmbeddingGeneration$Builder
@@ -1 +1 @@
com.microsoft.semantickernel.connectors.ai.openai.textcompletion.OpenAITextCompletionBuilder
com.microsoft.semantickernel.connectors.ai.openai.textcompletion.OpenAITextCompletion$Builder
@@ -1,22 +1,20 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel;
import com.microsoft.semantickernel.ai.AIException;
import com.microsoft.semantickernel.builders.FunctionBuilders;
import com.microsoft.semantickernel.builders.SKBuilders;
import com.microsoft.semantickernel.coreskills.SkillImporter;
import com.microsoft.semantickernel.exceptions.NotSupportedException;
import com.microsoft.semantickernel.exceptions.SkillsNotFoundException;
import com.microsoft.semantickernel.memory.SemanticTextMemory;
import com.microsoft.semantickernel.orchestration.ContextVariables;
import com.microsoft.semantickernel.orchestration.DefaultCompletionSKFunction;
import com.microsoft.semantickernel.orchestration.RegistrableSkFunction;
import com.microsoft.semantickernel.orchestration.SKContext;
import com.microsoft.semantickernel.orchestration.SKFunction;
import com.microsoft.semantickernel.orchestration.*;
import com.microsoft.semantickernel.semanticfunctions.SemanticFunctionConfig;
import com.microsoft.semantickernel.skilldefinition.DefaultSkillCollection;
import com.microsoft.semantickernel.skilldefinition.FunctionNotFound;
import com.microsoft.semantickernel.skilldefinition.ReadOnlyFunctionCollection;
import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection;
import com.microsoft.semantickernel.templateengine.DefaultPromptTemplateEngine;
import com.microsoft.semantickernel.templateengine.PromptTemplateEngine;
import com.microsoft.semantickernel.textcompletion.CompletionSKFunction;
import com.microsoft.semantickernel.textcompletion.TextCompletion;
@@ -174,33 +172,6 @@ public class DefaultKernel implements Kernel {
public CompletionSKFunction.Builder getSemanticFunctionBuilder() {
return FunctionBuilders.getCompletionBuilder(this);
}
/*
private static FunctionCollection importSkill(Object skillInstance, String skillName) {
skillInstance.getClass().getMethods()
.
MethodInfo[] methods = skillInstance.GetType()
.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod);
log.LogTrace("Methods found {0}", methods.Length);
// Filter out null functions
IEnumerable<ISKFunction> functions = from method in methods select SKFunction.FromNativeMethod(method, skillInstance, skillName, log);
List<SKFunction> result = (from function in functions where function != null select function).ToList();
log.LogTrace("Methods imported {0}", result.Count);
// Fail if two functions have the same name
var uniquenessCheck = new HashSet<string>(from x in result select x.Name, StringComparer.OrdinalIgnoreCase);
if (result.Count > uniquenessCheck.Count)
{
throw new KernelException(
KernelException.ErrorCodes.FunctionOverloadNotSupported,
"Function overloads are not supported, please differentiate function names");
}
return result;
}
*/
@Override
public ReadOnlyFunctionCollection getSkill(String skillName) throws FunctionNotFound {
@@ -216,48 +187,12 @@ public class DefaultKernel implements Kernel {
public PromptTemplateEngine getPromptTemplateEngine() {
return promptTemplateEngine;
}
/*
private SKFunction createSemanticFunction(
String skillName, String functionName, SemanticFunctionConfig functionConfig) {
CompletionSKFunction func =
CompletionSKFunction.fromSemanticConfig(
skillName, functionName, functionConfig, promptTemplateEngine);
// Connect the function to the current kernel skill collection, in case the function
// is invoked manually without a context and without a way to find other functions.
func.setDefaultSkillCollection(this.skillCollection);
func.setAIConfiguration(
CompleteRequestSettings.fromCompletionConfig(
functionConfig.getConfig().getCompletionConfig()));
// Note: the service is instantiated using the kernel configuration state when the function
// is invoked
func.setAIService(() -> this.getService(null, TextCompletion.class));
this.skillCollection = this.skillCollection.addSemanticFunction(func);
return func;
}*/
@Override
public void registerMemory(@Nonnull SemanticTextMemory memory) {
this.memory = memory != null ? memory.copy() : null;
}
/// <inheritdoc/>
/*
public ReadOnlySKContext createNewContext() {
return ReadOnlySKContext.build(
ReadOnlyContextVariables.build(),
null,
() -> skillCollection);
}
*/
@Override
public Mono<SKContext<?>> runAsync(SKFunction<?, ?>... pipeline) {
return runAsync(SKBuilders.variables().build(), pipeline);
@@ -292,4 +227,23 @@ public class DefaultKernel implements Kernel {
return pipelineBuilder;
}
public static class Builder implements Kernel.InternalBuilder {
@Override
public Kernel build(
KernelConfig kernelConfig, @Nullable PromptTemplateEngine promptTemplateEngine) {
if (promptTemplateEngine == null) {
promptTemplateEngine = new DefaultPromptTemplateEngine();
}
if (kernelConfig == null) {
throw new AIException(
AIException.ErrorCodes.InvalidConfiguration,
"It is required to set a kernelConfig to build a kernel");
}
return new DefaultKernel(kernelConfig, promptTemplateEngine, null);
}
}
}
@@ -15,7 +15,7 @@ public class SkFunctionBuilders implements FunctionBuilders {
public static final CompletionSKFunction.Builder COMPLETION_BUILDERS =
new InternalCompletionBuilder(null);
private static class InternalCompletionBuilder implements CompletionSKFunction.Builder {
private static class InternalCompletionBuilder extends CompletionSKFunction.Builder {
private final @Nullable Kernel kernel;
private InternalCompletionBuilder(@Nullable Kernel kernel) {
@@ -127,4 +127,11 @@ public class DefaultPromptTemplate implements PromptTemplate {
}
*/
public static final class Builder extends PromptTemplate.Builder {
@Override
public PromptTemplate build(String promptTemplate, PromptTemplateConfig config) {
return new DefaultPromptTemplate(promptTemplate, config);
}
}
}
@@ -1,10 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.semanticfunctions;
public class DefaultPromptTemplateBuilder implements PromptTemplate.Builder {
@Override
public PromptTemplate build(String promptTemplate, PromptTemplateConfig config) {
return new DefaultPromptTemplate(promptTemplate, config);
}
}
@@ -123,8 +123,7 @@ public class DefaultPromptTemplateEngine implements PromptTemplateEngine {
*/
}
public static class Builder implements PromptTemplateEngine.Builder {
public static final class Builder extends PromptTemplateEngine.Builder {
@Override
public PromptTemplateEngine build() {
return new DefaultPromptTemplateEngine();
@@ -1 +1 @@
com.microsoft.semantickernel.DefaultKernelBuilder
com.microsoft.semantickernel.DefaultKernel$Builder
@@ -0,0 +1 @@
com.microsoft.semantickernel.semanticfunctions.DefaultPromptTemplate$Builder
@@ -48,7 +48,7 @@ public class TextEmbeddingsTest {
public void testEmbeddingGeneration(OpenAIAsyncClient client, int expectedEmbeddingSize) {
String model = "text-embedding-ada-002";
EmbeddingGeneration<String, Double> embeddingGeneration =
EmbeddingGeneration<String, Float> embeddingGeneration =
new OpenAITextEmbeddingGeneration(client, model);
List<String> data = new ArrayList<>();