mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
Complete porting the various Blocks for Java (#1108)
### 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:
co-authored by
Mark Wallace
parent
c312960a9a
commit
5396d70bd5
@@ -203,6 +203,7 @@
|
||||
<licenseHeader>
|
||||
<content>// Copyright (c) Microsoft. All rights reserved.</content>
|
||||
</licenseHeader>
|
||||
<toggleOffOn/>
|
||||
</java>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
@@ -55,7 +55,7 @@ public interface Kernel {
|
||||
* @param pipeline List of functions
|
||||
* @return Result of the function composition
|
||||
*/
|
||||
Mono<SKContext<?>> runAsync(SKFunction... pipeline);
|
||||
Mono<SKContext<?>> runAsync(SKFunction<?, ?>... pipeline);
|
||||
|
||||
/**
|
||||
* Run a pipeline composed of synchronous and asynchronous functions.
|
||||
@@ -64,7 +64,7 @@ public interface Kernel {
|
||||
* @param pipeline List of functions
|
||||
* @return Result of the function composition
|
||||
*/
|
||||
Mono<SKContext<?>> runAsync(String input, SKFunction... pipeline);
|
||||
Mono<SKContext<?>> runAsync(String input, SKFunction<?, ?>... pipeline);
|
||||
|
||||
/**
|
||||
* Run a pipeline composed of synchronous and asynchronous functions.
|
||||
@@ -73,7 +73,7 @@ public interface Kernel {
|
||||
* @param pipeline List of functions
|
||||
* @return Result of the function composition
|
||||
*/
|
||||
Mono<SKContext<?>> runAsync(ContextVariables variables, SKFunction... pipeline);
|
||||
Mono<SKContext<?>> runAsync(ContextVariables variables, SKFunction<?, ?>... pipeline);
|
||||
|
||||
/**
|
||||
* Import a set of skills
|
||||
@@ -128,8 +128,6 @@ public interface Kernel {
|
||||
@Nullable private KernelConfig kernelConfig = null;
|
||||
@Nullable private PromptTemplateEngine promptTemplateEngine = null;
|
||||
|
||||
@Nullable private ReadOnlySkillCollection skillCollection = null;
|
||||
|
||||
public Builder setKernelConfig(KernelConfig kernelConfig) {
|
||||
this.kernelConfig = kernelConfig;
|
||||
return this;
|
||||
@@ -140,11 +138,6 @@ public interface Kernel {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSkillCollection(@Nullable ReadOnlySkillCollection skillCollection) {
|
||||
this.skillCollection = skillCollection;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Kernel build() {
|
||||
if (kernelConfig == null) {
|
||||
throw new IllegalStateException("Must provide a kernel configuration");
|
||||
@@ -152,14 +145,12 @@ public interface Kernel {
|
||||
|
||||
return BuildersSingleton.INST
|
||||
.getKernelBuilder()
|
||||
.build(kernelConfig, promptTemplateEngine, skillCollection);
|
||||
.build(kernelConfig, promptTemplateEngine);
|
||||
}
|
||||
}
|
||||
|
||||
interface InternalBuilder {
|
||||
Kernel build(
|
||||
KernelConfig kernelConfig,
|
||||
@Nullable PromptTemplateEngine promptTemplateEngine,
|
||||
@Nullable ReadOnlySkillCollection skillCollection);
|
||||
KernelConfig kernelConfig, @Nullable PromptTemplateEngine promptTemplateEngine);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ 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.KernelDefaultBuilder";
|
||||
"com.microsoft.semantickernel.DefaultKernelBuilder";
|
||||
private static final String FALLBACK_TEXT_COMPLETION_BUILDER_CLASS =
|
||||
"com.microsoft.semantickernel.connectors.ai.openai.textcompletion.OpenAITextCompletionBuilder";
|
||||
|
||||
|
||||
-14
@@ -6,20 +6,6 @@ public abstract class Block {
|
||||
private final String content;
|
||||
private final BlockTypes type;
|
||||
|
||||
/*
|
||||
|
||||
// internal virtual bool? SynchronousRendering => null;
|
||||
|
||||
/// <summary>
|
||||
/// The block content
|
||||
/// </summary>
|
||||
internal string Content { get; }
|
||||
|
||||
/// <summary>
|
||||
/// App logger
|
||||
/// </summary>
|
||||
protected ILogger Log { get; } = NullLogger.Instance;
|
||||
*/
|
||||
/// <summary>
|
||||
/// Base constructor
|
||||
/// </summary>
|
||||
|
||||
@@ -23,6 +23,11 @@
|
||||
<groupId>com.microsoft.semantickernel</groupId>
|
||||
<artifactId>openai-sdk</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.inject</groupId>
|
||||
<artifactId>jakarta.inject-api</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
+3
@@ -10,6 +10,8 @@ import com.microsoft.semantickernel.connectors.ai.openai.azuresdk.ClientBase;
|
||||
import com.microsoft.semantickernel.textcompletion.CompletionRequestSettings;
|
||||
import com.microsoft.semantickernel.textcompletion.TextCompletion;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -29,6 +31,7 @@ public class OpenAITextCompletion extends ClientBase implements TextCompletion {
|
||||
/// <param name="organization">OpenAI Organization Id (usually optional)</param>
|
||||
/// <param name="handlerFactory">Retry handler factory for HTTP requests.</param>
|
||||
/// <param name="log">Application logger</param>
|
||||
@Inject
|
||||
public OpenAITextCompletion(OpenAIAsyncClient client, String modelId) {
|
||||
super(client, modelId);
|
||||
}
|
||||
|
||||
@@ -90,5 +90,16 @@
|
||||
<artifactId>semantickernel-core-skills</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.semantickernel.extensions</groupId>
|
||||
<artifactId>sequentialplanner-extensions</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.inject</groupId>
|
||||
<artifactId>jakarta.inject-api</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+36
-20
@@ -7,7 +7,11 @@ 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.*;
|
||||
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.semanticfunctions.SemanticFunctionConfig;
|
||||
import com.microsoft.semantickernel.skilldefinition.DefaultSkillCollection;
|
||||
import com.microsoft.semantickernel.skilldefinition.FunctionNotFound;
|
||||
@@ -17,6 +21,8 @@ import com.microsoft.semantickernel.templateengine.PromptTemplateEngine;
|
||||
import com.microsoft.semantickernel.textcompletion.CompletionSKFunction;
|
||||
import com.microsoft.semantickernel.textcompletion.TextCompletion;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -26,32 +32,37 @@ import java.util.function.Function;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class KernelDefault implements Kernel {
|
||||
public class DefaultKernel implements Kernel {
|
||||
|
||||
private final KernelConfig kernelConfig;
|
||||
private DefaultSkillCollection defaultSkillCollection;
|
||||
private final DefaultSkillCollection defaultSkillCollection;
|
||||
private final PromptTemplateEngine promptTemplateEngine;
|
||||
@Nullable private SemanticTextMemory memory; // TODO: make this final
|
||||
|
||||
public KernelDefault(
|
||||
@Inject
|
||||
public DefaultKernel(
|
||||
KernelConfig kernelConfig,
|
||||
PromptTemplateEngine promptTemplateEngine,
|
||||
ReadOnlySkillCollection skillCollection) {
|
||||
@Nullable SemanticTextMemory memory) {
|
||||
if (kernelConfig == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
this.kernelConfig = kernelConfig;
|
||||
this.promptTemplateEngine = promptTemplateEngine;
|
||||
this.defaultSkillCollection = new DefaultSkillCollection(skillCollection);
|
||||
this.defaultSkillCollection = new DefaultSkillCollection();
|
||||
|
||||
if (kernelConfig.getSkills() != null) {
|
||||
kernelConfig.getSkills().forEach(this::registerSemanticFunction);
|
||||
if (memory != null) {
|
||||
this.memory = memory.copy();
|
||||
} else {
|
||||
this.memory = null;
|
||||
}
|
||||
|
||||
kernelConfig.getSkills().forEach(this::registerSemanticFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getService(@Nullable String serviceId, Class<T> clazz) throws KernelException {
|
||||
public <T> T getService(String serviceId, Class<T> clazz) {
|
||||
if (TextCompletion.class.isAssignableFrom(clazz)) {
|
||||
Function<Kernel, TextCompletion> factory =
|
||||
kernelConfig.getTextCompletionServiceOrDefault(serviceId);
|
||||
@@ -84,7 +95,7 @@ public class KernelDefault implements Kernel {
|
||||
throw new RuntimeException("This function does not implement RegistrableSkFunction");
|
||||
}
|
||||
((RegistrableSkFunction) func).registerOnKernel(this);
|
||||
defaultSkillCollection = defaultSkillCollection.addSemanticFunction(func);
|
||||
defaultSkillCollection.addSemanticFunction(func);
|
||||
return func;
|
||||
}
|
||||
|
||||
@@ -136,8 +147,6 @@ public class KernelDefault implements Kernel {
|
||||
Object skillInstance, @Nullable String skillName) {
|
||||
if (skillName == null || skillName.isEmpty()) {
|
||||
skillName = ReadOnlySkillCollection.GlobalSkill;
|
||||
// this._log.LogTrace("Importing skill {0} in the global namespace",
|
||||
// skillInstance.GetType().FullName);
|
||||
}
|
||||
|
||||
// skill = new Dictionary<string, ISKFunction>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -151,7 +160,7 @@ public class KernelDefault implements Kernel {
|
||||
DefaultSkillCollection::addNativeFunction,
|
||||
DefaultSkillCollection::merge);
|
||||
|
||||
this.defaultSkillCollection = this.defaultSkillCollection.merge(newSkills);
|
||||
this.defaultSkillCollection.merge(newSkills);
|
||||
|
||||
return functions;
|
||||
}
|
||||
@@ -250,27 +259,34 @@ public class KernelDefault implements Kernel {
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Mono<SKContext<?>> runAsync(SKFunction... pipeline) {
|
||||
public Mono<SKContext<?>> runAsync(SKFunction<?, ?>... pipeline) {
|
||||
return runAsync(SKBuilders.variables().build(), pipeline);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SKContext<?>> runAsync(String input, SKFunction... pipeline) {
|
||||
public Mono<SKContext<?>> runAsync(String input, SKFunction<?, ?>... pipeline) {
|
||||
return runAsync(SKBuilders.variables().build(input), pipeline);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SKContext<?>> runAsync(ContextVariables variables, SKFunction... pipeline) {
|
||||
DefaultSemanticSKContext context =
|
||||
new DefaultSemanticSKContext(variables, this.memory, this.defaultSkillCollection);
|
||||
public Mono<SKContext<?>> runAsync(ContextVariables variables, SKFunction<?, ?>... pipeline) {
|
||||
if (pipeline == null || pipeline.length == 0) {
|
||||
throw new SKException("No parameters provided to pipeline");
|
||||
}
|
||||
|
||||
Mono<SKContext<?>> pipelineBuilder = Mono.just(context);
|
||||
Mono<SKContext<?>> pipelineBuilder =
|
||||
Mono.just(pipeline[0].buildContext(variables, memory, getSkills()));
|
||||
|
||||
for (SKFunction f : Arrays.asList(pipeline)) {
|
||||
pipelineBuilder =
|
||||
pipelineBuilder.flatMap(
|
||||
newContext -> {
|
||||
return f.invokeAsync(newContext, null);
|
||||
SKContext context =
|
||||
f.buildContext(
|
||||
newContext.getVariables(),
|
||||
newContext.getSemanticMemory(),
|
||||
newContext.getSkills());
|
||||
return f.invokeAsync(context, null);
|
||||
});
|
||||
}
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel;
|
||||
|
||||
import com.microsoft.semantickernel.templateengine.DefaultPromptTemplateEngine;
|
||||
import com.microsoft.semantickernel.templateengine.PromptTemplateEngine;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class DefaultKernelBuilder implements Kernel.InternalBuilder {
|
||||
|
||||
@Override
|
||||
public Kernel build(
|
||||
KernelConfig kernelConfig, @Nullable PromptTemplateEngine promptTemplateEngine) {
|
||||
if (promptTemplateEngine == null) {
|
||||
promptTemplateEngine = new DefaultPromptTemplateEngine();
|
||||
}
|
||||
|
||||
if (kernelConfig == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
return new DefaultKernel(kernelConfig, promptTemplateEngine, null);
|
||||
}
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel;
|
||||
|
||||
import com.microsoft.semantickernel.skilldefinition.DefaultSkillCollection;
|
||||
import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection;
|
||||
import com.microsoft.semantickernel.templateengine.DefaultPromptTemplateEngine;
|
||||
import com.microsoft.semantickernel.templateengine.PromptTemplateEngine;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class KernelDefaultBuilder implements Kernel.InternalBuilder {
|
||||
|
||||
@Override
|
||||
public Kernel build(
|
||||
KernelConfig kernelConfig,
|
||||
@Nullable PromptTemplateEngine promptTemplateEngine,
|
||||
@Nullable ReadOnlySkillCollection skillCollection) {
|
||||
if (promptTemplateEngine == null) {
|
||||
promptTemplateEngine = new DefaultPromptTemplateEngine();
|
||||
}
|
||||
|
||||
if (skillCollection == null) {
|
||||
skillCollection = new DefaultSkillCollection();
|
||||
}
|
||||
|
||||
if (kernelConfig == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
return new KernelDefault(kernelConfig, promptTemplateEngine, skillCollection);
|
||||
}
|
||||
}
|
||||
+11
-25
@@ -5,7 +5,6 @@ package com.microsoft.semantickernel.skilldefinition; // Copyright (c) Microsoft
|
||||
import com.microsoft.semantickernel.orchestration.SKFunction;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -63,27 +62,18 @@ public class DefaultSkillCollection implements ReadOnlySkillCollection {
|
||||
this.skillCollection = new CaseInsensitiveMap<>();
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
public DefaultSkillCollection addSemanticFunction(SKFunction functionInstance) {
|
||||
FunctionCollection existingFunctionCollection;
|
||||
if (!skillCollection.containsKey(functionInstance.getSkillName().toLowerCase())) {
|
||||
existingFunctionCollection =
|
||||
new FunctionCollection(functionInstance.getSkillName().toLowerCase());
|
||||
if (!skillCollection.containsKey(functionInstance.getSkillName())) {
|
||||
existingFunctionCollection = new FunctionCollection(functionInstance.getSkillName());
|
||||
skillCollection.put(functionInstance.getSkillName(), existingFunctionCollection);
|
||||
} else {
|
||||
existingFunctionCollection =
|
||||
skillCollection.get(functionInstance.getSkillName().toLowerCase());
|
||||
existingFunctionCollection = skillCollection.get(functionInstance.getSkillName());
|
||||
}
|
||||
|
||||
existingFunctionCollection =
|
||||
existingFunctionCollection.put(
|
||||
functionInstance.getName().toLowerCase(), functionInstance);
|
||||
existingFunctionCollection.put(functionInstance.getName(), functionInstance);
|
||||
|
||||
HashMap<String, FunctionCollection> clonedSkillCollection = new HashMap<>(skillCollection);
|
||||
|
||||
clonedSkillCollection.put(
|
||||
functionInstance.getSkillName().toLowerCase(), existingFunctionCollection);
|
||||
|
||||
return new DefaultSkillCollection(clonedSkillCollection);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,25 +117,21 @@ public class DefaultSkillCollection implements ReadOnlySkillCollection {
|
||||
}
|
||||
|
||||
public DefaultSkillCollection merge(DefaultSkillCollection in) {
|
||||
HashMap<String, FunctionCollection> clone = new HashMap<>(skillCollection);
|
||||
|
||||
in.asMap()
|
||||
.entrySet()
|
||||
.forEach(
|
||||
entry -> {
|
||||
FunctionCollection existing = clone.get(entry.getKey().toLowerCase());
|
||||
FunctionCollection existing =
|
||||
skillCollection.get(entry.getKey().toLowerCase());
|
||||
if (existing == null) {
|
||||
clone.put(
|
||||
skillCollection.put(
|
||||
entry.getKey().toLowerCase(),
|
||||
new FunctionCollection(entry.getValue()));
|
||||
} else {
|
||||
clone.put(
|
||||
entry.getKey().toLowerCase(),
|
||||
existing.merge(new FunctionCollection(entry.getValue())));
|
||||
existing.merge(new FunctionCollection(entry.getValue()));
|
||||
}
|
||||
});
|
||||
|
||||
return new DefaultSkillCollection(clone);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-11
@@ -102,13 +102,9 @@ public class FunctionCollection implements ReadOnlyFunctionCollection {
|
||||
* @param functionInstance
|
||||
* @return Collection for fluent callse
|
||||
*/
|
||||
@CheckReturnValue
|
||||
public FunctionCollection put(String functionName, SKFunction<?, ?> functionInstance) {
|
||||
HashMap<String, SKFunction<?, ?>> functionCollection =
|
||||
new HashMap<>(this.functionCollection);
|
||||
functionCollection.put(functionName.toLowerCase(), functionInstance);
|
||||
|
||||
return new FunctionCollection(skillName, functionCollection);
|
||||
this.functionCollection.put(functionName.toLowerCase(), functionInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,10 +114,7 @@ public class FunctionCollection implements ReadOnlyFunctionCollection {
|
||||
* @return Collection for fluent calls
|
||||
*/
|
||||
public FunctionCollection merge(FunctionCollection value) {
|
||||
HashMap<String, SKFunction<?, ?>> mutable = new HashMap<>(functionCollection);
|
||||
|
||||
mutable.putAll(value.functionCollection);
|
||||
|
||||
return new FunctionCollection(skillName, mutable);
|
||||
functionCollection.putAll(value.functionCollection);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-8
@@ -169,10 +169,9 @@ public class CodeTokenizer {
|
||||
|
||||
if (currentTokenType == TokenTypes.None) {
|
||||
if (!spaceSeparatorFound) {
|
||||
throw new TemplateException();
|
||||
// TODO error code
|
||||
// throw new TemplateException(TemplateException.ErrorCodes.SyntaxError,
|
||||
// "Tokens must be separated by one space least");
|
||||
throw new TemplateException(
|
||||
TemplateException.ErrorCodes.SyntaxError,
|
||||
"Tokens must be separated by one space least");
|
||||
}
|
||||
|
||||
if (isQuote(currentChar)) {
|
||||
@@ -205,10 +204,9 @@ public class CodeTokenizer {
|
||||
break;
|
||||
|
||||
case None:
|
||||
throw new TemplateException();
|
||||
// TODO error code
|
||||
// throw new TemplateException(TemplateException.ErrorCodes.SyntaxError,
|
||||
// "Tokens must be separated by one space least");
|
||||
throw new TemplateException(
|
||||
TemplateException.ErrorCodes.SyntaxError,
|
||||
"Tokens must be separated by one space least");
|
||||
}
|
||||
|
||||
return blocks;
|
||||
|
||||
+13
-9
@@ -8,6 +8,9 @@ import com.microsoft.semantickernel.templateengine.blocks.CodeBlock;
|
||||
import com.microsoft.semantickernel.templateengine.blocks.CodeRendering;
|
||||
import com.microsoft.semantickernel.templateengine.blocks.TextRendering;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -25,7 +28,7 @@ import java.util.List;
|
||||
// immutable when invoked within the template
|
||||
/// </summary>
|
||||
public class DefaultPromptTemplateEngine implements PromptTemplateEngine {
|
||||
// private readonly ILogger _log;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPromptTemplateEngine.class);
|
||||
|
||||
private final TemplateTokenizer tokenizer;
|
||||
|
||||
@@ -46,9 +49,8 @@ public class DefaultPromptTemplateEngine implements PromptTemplateEngine {
|
||||
if (validate) {
|
||||
blocks.forEach(
|
||||
block -> {
|
||||
// TODO ERROR MESSAGE
|
||||
if (!block.isValid()) {
|
||||
throw new TemplateException();
|
||||
throw new TemplateException(TemplateException.ErrorCodes.SyntaxError);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -76,13 +78,15 @@ public class DefaultPromptTemplateEngine implements PromptTemplateEngine {
|
||||
} else if (block instanceof CodeRendering) {
|
||||
return ((CodeBlock) block).renderCodeAsync(context);
|
||||
} else {
|
||||
// const string error = "Unexpected block type, the block doesn't
|
||||
// have a rendering method";
|
||||
// this._log.LogError(error);
|
||||
// throw new
|
||||
// TemplateException(TemplateException.ErrorCodes.UnexpectedBlockType, error);
|
||||
String message =
|
||||
"Unexpected block type, the block doesn't have a rendering"
|
||||
+ " method";
|
||||
LOGGER.error(message);
|
||||
return Mono.error(
|
||||
new TemplateException(
|
||||
TemplateException.ErrorCodes.UnexpectedBlockType,
|
||||
message));
|
||||
}
|
||||
return Mono.just("");
|
||||
})
|
||||
.collectList()
|
||||
.map(
|
||||
|
||||
+132
-1
@@ -3,4 +3,135 @@ package com.microsoft.semantickernel.templateengine;
|
||||
|
||||
import com.microsoft.semantickernel.SKException;
|
||||
|
||||
public class TemplateException extends SKException {}
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class TemplateException extends SKException {
|
||||
|
||||
@Nonnull private final ErrorCodes errorCode;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the {@code TemplateException} class with a provided error code.
|
||||
*
|
||||
* @param error The error code.
|
||||
*/
|
||||
public TemplateException(@Nonnull ErrorCodes error) {
|
||||
this(error, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the {@code TemplateException} class with a provided error code.
|
||||
*
|
||||
* @param errorCode The error code.
|
||||
* @param message The exception message.
|
||||
*/
|
||||
public TemplateException(@Nonnull ErrorCodes errorCode, @Nullable String message) {
|
||||
this(errorCode, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the {@code TemplateException} class with a provided error code.
|
||||
*
|
||||
* @param errorCode The error code.
|
||||
* @param message The exception message.
|
||||
* @param innerException The exception that is the cause of the current exception.
|
||||
*/
|
||||
public TemplateException(
|
||||
@Nonnull ErrorCodes errorCode,
|
||||
@Nullable String message,
|
||||
@Nullable Throwable innerException) {
|
||||
super(getDefaultMessage(errorCode, message), innerException);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
// spotless:off
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets the error code for this exception.
|
||||
* </p>
|
||||
*/
|
||||
//spotless:on
|
||||
public ErrorCodes getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
// spotless:off
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Translate the error code into a default message.
|
||||
* </p>
|
||||
*/
|
||||
//spotless:on
|
||||
private static String getDefaultMessage(
|
||||
@Nonnull ErrorCodes errorCode, @Nullable String message) {
|
||||
return String.format("%s: %s", errorCode.getMessage(), message);
|
||||
}
|
||||
|
||||
// spotless:off
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Error codes for {@code TemplateException}.
|
||||
* </p>
|
||||
*/
|
||||
//spotless:on
|
||||
public enum ErrorCodes {
|
||||
|
||||
// spotless:off
|
||||
/**
|
||||
* <p>
|
||||
* Unknown error.
|
||||
* </p>
|
||||
*/
|
||||
//spotless:on
|
||||
UnknownError("Unknown error"),
|
||||
|
||||
// spotless:off
|
||||
/**
|
||||
* <p>
|
||||
* Syntax error, the template syntax used is not valid.
|
||||
* </p>
|
||||
*/
|
||||
//spotless:on
|
||||
SyntaxError("Syntax error, the template syntax used is not valid"),
|
||||
|
||||
// spotless:off
|
||||
/**
|
||||
* <p>
|
||||
* The block type produced be the tokenizer was not expected.
|
||||
* </p>
|
||||
*/
|
||||
UnexpectedBlockType("The block type produced be the tokenizer was not expected"),
|
||||
|
||||
//spotless:off
|
||||
/**
|
||||
* <p>
|
||||
* The template requires an unknown function.
|
||||
* </p>
|
||||
*/
|
||||
//spotless:on
|
||||
FunctionNotFound("The template requires an unknown function"),
|
||||
|
||||
// spotless:off
|
||||
/**
|
||||
* <p>
|
||||
* The template execution failed, e.g. a function call threw an exception.
|
||||
* </p>
|
||||
*/
|
||||
//spotless:on
|
||||
RuntimeError("The template execution failed, e.g. a function call threw an exception"),
|
||||
;
|
||||
|
||||
private final String message;
|
||||
|
||||
ErrorCodes(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-25
@@ -69,7 +69,7 @@ public class TemplateTokenizer {
|
||||
return Collections.singletonList(new TextBlock(text));
|
||||
}
|
||||
|
||||
List<Block> blocks = new ArrayList<Block>();
|
||||
List<Block> blocks = new ArrayList<>();
|
||||
|
||||
int endOfLastBlock = 0;
|
||||
|
||||
@@ -148,12 +148,10 @@ public class TemplateTokenizer {
|
||||
switch (codeBlocks.get(0).getType()) {
|
||||
case Variable:
|
||||
if (codeBlocks.size() > 1) {
|
||||
throw new TemplateException();
|
||||
// TODO errorcode
|
||||
// throw new
|
||||
// TemplateException(TemplateException.ErrorCodes.SyntaxError,
|
||||
// $"Invalid token detected after the variable:
|
||||
// {contentWithoutDelimiters}");
|
||||
throw new TemplateException(
|
||||
TemplateException.ErrorCodes.SyntaxError,
|
||||
"Invalid token detected after the variable: "
|
||||
+ contentWithoutDelimiters);
|
||||
}
|
||||
|
||||
blocks.add(codeBlocks.get(0));
|
||||
@@ -161,12 +159,10 @@ public class TemplateTokenizer {
|
||||
|
||||
case Value:
|
||||
if (codeBlocks.size() > 1) {
|
||||
throw new TemplateException();
|
||||
// TODO errorcode
|
||||
// throw new
|
||||
// TemplateException(TemplateException.ErrorCodes.SyntaxError,
|
||||
// $"Invalid token detected after the value:
|
||||
// {contentWithoutDelimiters}");
|
||||
throw new TemplateException(
|
||||
TemplateException.ErrorCodes.SyntaxError,
|
||||
"Invalid token detected after the value: "
|
||||
+ contentWithoutDelimiters);
|
||||
}
|
||||
|
||||
blocks.add(codeBlocks.get(0));
|
||||
@@ -174,12 +170,10 @@ public class TemplateTokenizer {
|
||||
|
||||
case FunctionId:
|
||||
if (codeBlocks.size() > 2) {
|
||||
throw new TemplateException();
|
||||
// TODO errorcode
|
||||
// throw new
|
||||
// TemplateException(TemplateException.ErrorCodes.SyntaxError,
|
||||
// $"Functions support only one parameter:
|
||||
// {contentWithoutDelimiters}");
|
||||
throw new TemplateException(
|
||||
TemplateException.ErrorCodes.SyntaxError,
|
||||
"Functions support only one parameter: "
|
||||
+ contentWithoutDelimiters);
|
||||
}
|
||||
|
||||
blocks.add(new CodeBlock(codeBlocks, contentWithoutDelimiters));
|
||||
@@ -189,12 +183,10 @@ public class TemplateTokenizer {
|
||||
case Text:
|
||||
case Undefined:
|
||||
default:
|
||||
throw new TemplateException();
|
||||
// TODO errorcode
|
||||
// throw new
|
||||
// TemplateException(TemplateException.ErrorCodes.UnexpectedBlockType,
|
||||
// $"Code tokenizer returned an incorrect first token
|
||||
// type {codeBlocks[0].Type:G}");
|
||||
throw new TemplateException(
|
||||
TemplateException.ErrorCodes.UnexpectedBlockType,
|
||||
"Code tokenizer returned an incorrect first token type "
|
||||
+ codeBlocks.get(0).getType());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+34
-10
@@ -7,15 +7,21 @@ import com.microsoft.semantickernel.orchestration.SKContext;
|
||||
import com.microsoft.semantickernel.orchestration.SKFunction;
|
||||
import com.microsoft.semantickernel.skilldefinition.ReadOnlyFunctionCollection;
|
||||
import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection;
|
||||
import com.microsoft.semantickernel.templateengine.TemplateException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.annotation.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
// ReSharper disable TemplateIsNotCompileTimeConstantProblem
|
||||
public class CodeBlock extends Block implements CodeRendering {
|
||||
public final class CodeBlock extends Block implements CodeRendering {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CodeBlock.class);
|
||||
private final List<Block> tokens;
|
||||
|
||||
public CodeBlock(List<Block> tokens, String content) {
|
||||
@@ -30,21 +36,39 @@ public class CodeBlock extends Block implements CodeRendering {
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
// TODO
|
||||
Optional<Block> invalid = tokens.stream().filter(token -> !token.isValid()).findFirst();
|
||||
if (invalid.isPresent()) {
|
||||
LOGGER.error("Invalid block" + invalid.get().getContent());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.tokens.size() > 1) {
|
||||
if (this.tokens.get(0).getType() != BlockTypes.FunctionId) {
|
||||
LOGGER.error("Unexpected second token found: " + this.tokens.get(1).getContent());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.tokens.get(1).getType() != BlockTypes.Value
|
||||
&& this.tokens.get(1).getType() != BlockTypes.Variable) {
|
||||
LOGGER.error("Functions support only one parameter");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.tokens.size() > 2) {
|
||||
LOGGER.error("Unexpected second token found: " + this.tokens.get(1).getContent());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Mono<String> renderCodeAsync(SKContext context) {
|
||||
/* TODO
|
||||
if (!this.isValid()) {
|
||||
throw new TemplateException(TemplateException.ErrorCodes.SyntaxError);
|
||||
}
|
||||
if (!this._validated && !this.IsValid(out var error))
|
||||
{
|
||||
throw new TemplateException(TemplateException.ErrorCodes.SyntaxError, error);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
// this.Log.LogTrace("Rendering code: `{0}`", this.Content);
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import com.microsoft.semantickernel.orchestration.ContextVariables;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class FunctionIdBlock extends Block implements TextRendering {
|
||||
public final class FunctionIdBlock extends Block implements TextRendering {
|
||||
private final String skillName;
|
||||
|
||||
private final String functionName;
|
||||
|
||||
+1
-17
@@ -4,9 +4,7 @@ package com.microsoft.semantickernel.templateengine.blocks; // Copyright (c) Mic
|
||||
|
||||
import com.microsoft.semantickernel.orchestration.ContextVariables;
|
||||
|
||||
public class TextBlock extends Block implements TextRendering {
|
||||
|
||||
// internal override BlockTypes Type => BlockTypes.Text;
|
||||
public final class TextBlock extends Block implements TextRendering {
|
||||
|
||||
public TextBlock(String text) {
|
||||
super(text, BlockTypes.Text);
|
||||
@@ -25,18 +23,4 @@ public class TextBlock extends Block implements TextRendering {
|
||||
public TextBlock(String text, int startIndex, int stopIndex) {
|
||||
super(text.substring(startIndex, stopIndex), BlockTypes.Text);
|
||||
}
|
||||
/*
|
||||
|
||||
public override bool IsValid(out string errorMsg)
|
||||
{
|
||||
errorMsg = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
public string Render(ContextVariables? variables)
|
||||
{
|
||||
return this.Content;
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
+45
-70
@@ -4,88 +4,63 @@ package com.microsoft.semantickernel.templateengine.blocks; // Copyright (c) Mic
|
||||
|
||||
import com.microsoft.semantickernel.orchestration.ContextVariables;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import reactor.util.annotation.Nullable;
|
||||
|
||||
public class ValBlock extends Block implements TextRendering {
|
||||
public ValBlock(String content) {
|
||||
super(content, BlockTypes.Value);
|
||||
}
|
||||
public final class ValBlock extends Block implements TextRendering {
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return false;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ValBlock.class);
|
||||
|
||||
// Cache the first and last char
|
||||
private char first = '\0';
|
||||
private char last = '\0';
|
||||
|
||||
// Content, excluding start/end quote chars
|
||||
private String value = "";
|
||||
|
||||
public ValBlock(String quotedValue) {
|
||||
super(quotedValue.trim(), BlockTypes.Value);
|
||||
|
||||
if (this.getContent().length() < 2) {
|
||||
LOGGER.error("A value must have single quotes or double quotes on both sides");
|
||||
return;
|
||||
}
|
||||
|
||||
this.first = this.getContent().charAt(0);
|
||||
this.last = this.getContent().charAt(getContent().length() - 1);
|
||||
this.value = this.getContent().substring(1, this.getContent().length() - 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String render(ContextVariables variables) {
|
||||
return null;
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
internal override BlockTypes Type => BlockTypes.Value;
|
||||
|
||||
// Cache the first and last char
|
||||
private readonly char _first = '\0';
|
||||
private readonly char _last = '\0';
|
||||
|
||||
// Content, excluding start/end quote chars
|
||||
private readonly string _value = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance
|
||||
/// </summary>
|
||||
/// <param name="quotedValue">Block content, including the delimiting chars</param>
|
||||
/// <param name="log">Optional logger</param>
|
||||
public ValBlock(string? quotedValue, ILogger? log = null)
|
||||
: base(quotedValue?.Trim(), log)
|
||||
{
|
||||
if (this.Content.Length < 2)
|
||||
{
|
||||
this.Log.LogError("A value must have single quotes or double quotes on both sides");
|
||||
return;
|
||||
}
|
||||
|
||||
this._first = this.Content[0];
|
||||
this._last = this.Content[this.Content.Length - 1];
|
||||
this._value = this.Content.Substring(1, this.Content.Length - 2);
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
// Content includes the quotes, so it must be at least 2 chars long
|
||||
if (this.getContent().length() < 2) {
|
||||
LOGGER.error("A value must have single quotes or double quotes on both sides");
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma warning disable CA2254 // error strings are used also internally, not just for logging
|
||||
// ReSharper disable TemplateIsNotCompileTimeConstantProblem
|
||||
public override bool IsValid(out string errorMsg)
|
||||
{
|
||||
errorMsg = string.Empty;
|
||||
|
||||
// Content includes the quotes, so it must be at least 2 chars long
|
||||
if (this.Content.Length < 2)
|
||||
{
|
||||
errorMsg = "A value must have single quotes or double quotes on both sides";
|
||||
this.Log.LogError(errorMsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if delimiting chars are consistent
|
||||
if (this._first != this._last)
|
||||
{
|
||||
errorMsg = "A value must be defined using either single quotes or double quotes, not both";
|
||||
this.Log.LogError(errorMsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#pragma warning restore CA2254
|
||||
|
||||
public string Render(ContextVariables? variables)
|
||||
{
|
||||
return this._value;
|
||||
// Check if delimiting chars are consistent
|
||||
if (first != last) {
|
||||
LOGGER.error(
|
||||
"A value must be defined using either single quotes or double quotes, not"
|
||||
+ " both");
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasValPrefix(string? text)
|
||||
{
|
||||
return !text.IsNullOrEmpty()
|
||||
&& text.Length > 0
|
||||
&& (text[0] is Symbols.DblQuote or Symbols.SglQuote);
|
||||
}*/
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean hasValPrefix(@Nullable String text) {
|
||||
return text != null
|
||||
&& text.length() > 0
|
||||
&& (text.charAt(0) == Symbols.DblQuote || text.charAt(0) == Symbols.SglQuote);
|
||||
}
|
||||
}
|
||||
|
||||
+38
-81
@@ -5,19 +5,22 @@ package com.microsoft.semantickernel.templateengine.blocks; // Copyright (c) Mic
|
||||
import com.microsoft.semantickernel.orchestration.ContextVariables;
|
||||
import com.microsoft.semantickernel.templateengine.TemplateException;
|
||||
|
||||
public class VarBlock extends Block implements TextRendering {
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class VarBlock extends Block implements TextRendering {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(VarBlock.class);
|
||||
private final String name;
|
||||
|
||||
public VarBlock(String content) {
|
||||
super(content, BlockTypes.Variable);
|
||||
|
||||
this.name = content.substring(1);
|
||||
}
|
||||
if (content.length() < 2) {
|
||||
LOGGER.error("The variable name is empty");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return true;
|
||||
this.name = content.substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -27,93 +30,47 @@ public class VarBlock extends Block implements TextRendering {
|
||||
}
|
||||
|
||||
if (name == null || name.isEmpty()) {
|
||||
// TODO
|
||||
// const string errMsg = "Variable rendering failed, the variable name is empty";
|
||||
// this.Log.LogError(errMsg);
|
||||
// throw new TemplateException(TemplateException.ErrorCodes.SyntaxError, errMsg);
|
||||
throw new TemplateException();
|
||||
throw new TemplateException(
|
||||
TemplateException.ErrorCodes.SyntaxError,
|
||||
"Variable rendering failed, the variable name is empty");
|
||||
}
|
||||
|
||||
String value = variables.asMap().get(name);
|
||||
|
||||
if (value == null) {
|
||||
// TODO
|
||||
// this.Log.LogWarning("Variable `{0}{1}` not found", Symbols.VarPrefix, this.Name);
|
||||
|
||||
LOGGER.warn("Variable `{}{}` not found", Symbols.VarPrefix, name);
|
||||
}
|
||||
|
||||
return value != null ? value : "";
|
||||
}
|
||||
/*
|
||||
internal override BlockTypes Type => BlockTypes.Variable;
|
||||
|
||||
internal string Name { get; } = string.Empty;
|
||||
|
||||
public VarBlock(string? content, ILogger? log = null) : base(content?.Trim(), log)
|
||||
{
|
||||
if (this.Content.Length < 2)
|
||||
{
|
||||
this.Log.LogError("The variable name is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
this.Name = this.Content.Substring(1);
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
if (getContent() == null || getContent().isEmpty()) {
|
||||
LOGGER.error(
|
||||
"A variable must start with the symbol {} and have a name", Symbols.VarPrefix);
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma warning disable CA2254 // error strings are used also internally, not just for logging
|
||||
// ReSharper disable TemplateIsNotCompileTimeConstantProblem
|
||||
public override bool IsValid(out string errorMsg)
|
||||
{
|
||||
errorMsg = string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(this.Content))
|
||||
{
|
||||
errorMsg = $"A variable must start with the symbol {Symbols.VarPrefix} and have a name";
|
||||
this.Log.LogError(errorMsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.Content[0] != Symbols.VarPrefix)
|
||||
{
|
||||
errorMsg = $"A variable must start with the symbol {Symbols.VarPrefix}";
|
||||
this.Log.LogError(errorMsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.Content.Length < 2)
|
||||
{
|
||||
errorMsg = "The variable name is empty";
|
||||
this.Log.LogError(errorMsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Regex.IsMatch(this.Name, "^[a-zA-Z0-9_]*$"))
|
||||
{
|
||||
errorMsg = $"The variable name '{this.Name}' contains invalid characters. " +
|
||||
"Only alphanumeric chars and underscore are allowed.";
|
||||
this.Log.LogError(errorMsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#pragma warning restore CA2254
|
||||
|
||||
public string Render(ContextVariables? variables)
|
||||
{
|
||||
if (variables == null) { return string.Empty; }
|
||||
|
||||
if (string.IsNullOrEmpty(this.Name))
|
||||
{
|
||||
const string errMsg = "Variable rendering failed, the variable name is empty";
|
||||
this.Log.LogError(errMsg);
|
||||
throw new TemplateException(TemplateException.ErrorCodes.SyntaxError, errMsg);
|
||||
}
|
||||
|
||||
var exists = variables.Get(this.Name, out string value);
|
||||
if (!exists) { this.Log.LogWarning("Variable `{0}{1}` not found", Symbols.VarPrefix, this.Name); }
|
||||
|
||||
return exists ? value : string.Empty;
|
||||
if (getContent().charAt(0) != Symbols.VarPrefix) {
|
||||
LOGGER.error("A variable must start with the symbol {}", Symbols.VarPrefix);
|
||||
return false;
|
||||
}
|
||||
|
||||
*/
|
||||
if (getContent().length() < 2) {
|
||||
LOGGER.error("The variable name is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!name.matches("^[a-zA-Z0-9_]*$")) {
|
||||
LOGGER.error(
|
||||
"The variable name '{}' contains invalid characters. "
|
||||
+ "Only alphanumeric chars and underscore are allowed.",
|
||||
name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
com.microsoft.semantickernel.KernelDefaultBuilder
|
||||
com.microsoft.semantickernel.DefaultKernelBuilder
|
||||
+35
-26
@@ -5,6 +5,7 @@ import com.azure.ai.openai.models.Choice;
|
||||
import com.azure.ai.openai.models.Completions;
|
||||
import com.azure.ai.openai.models.CompletionsOptions;
|
||||
import com.microsoft.openai.AzureOpenAIClient;
|
||||
import com.microsoft.openai.OpenAIAsyncClient;
|
||||
import com.microsoft.semantickernel.builders.SKBuilders;
|
||||
import com.microsoft.semantickernel.connectors.ai.openai.textcompletion.OpenAITextCompletion;
|
||||
import com.microsoft.semantickernel.extensions.KernelExtensions;
|
||||
@@ -17,6 +18,7 @@ import com.microsoft.semantickernel.textcompletion.TextCompletion;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -27,6 +29,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DefaultKernelTest {
|
||||
|
||||
@@ -34,10 +37,9 @@ public class DefaultKernelTest {
|
||||
void contextVariableTest() {
|
||||
String model = "a-model";
|
||||
|
||||
List<Tuple2<String, String>> responses =
|
||||
Arrays.asList(Tuples.of("A", "a"), Tuples.of("user: Aauser: B", "b"));
|
||||
|
||||
com.azure.ai.openai.OpenAIAsyncClient client = mockCompletionOpenAIAsyncClient(responses);
|
||||
OpenAIAsyncClient client =
|
||||
mockCompletionOpenAIAsyncClient(
|
||||
Tuples.of("A", "a"), Tuples.of("user: Aauser: B", "b"));
|
||||
|
||||
Kernel kernel = buildKernel(model, client);
|
||||
|
||||
@@ -82,8 +84,7 @@ public class DefaultKernelTest {
|
||||
@Test
|
||||
void tellAJoke() {
|
||||
String expectedResponse = "a result joke";
|
||||
com.azure.ai.openai.OpenAIAsyncClient client =
|
||||
mockCompletionOpenAIAsyncClient("WRITE", expectedResponse);
|
||||
OpenAIAsyncClient client = mockCompletionOpenAIAsyncClient("WRITE", expectedResponse);
|
||||
String model = "a-model-name";
|
||||
Kernel kernel = buildKernel(model, client);
|
||||
|
||||
@@ -117,10 +118,7 @@ public class DefaultKernelTest {
|
||||
assertTheResultEquals(result, expectedResponse);
|
||||
}
|
||||
|
||||
public static Kernel buildKernel(
|
||||
String model, com.azure.ai.openai.OpenAIAsyncClient openAIAsyncClient) {
|
||||
|
||||
com.microsoft.openai.OpenAIAsyncClient client = new AzureOpenAIClient(openAIAsyncClient);
|
||||
public static Kernel buildKernel(String model, OpenAIAsyncClient client) {
|
||||
|
||||
TextCompletion textCompletion = new OpenAITextCompletion(client, model);
|
||||
|
||||
@@ -132,12 +130,11 @@ public class DefaultKernelTest {
|
||||
return SKBuilders.kernel().setKernelConfig(kernelConfig).build();
|
||||
}
|
||||
|
||||
private static com.azure.ai.openai.OpenAIAsyncClient mockCompletionOpenAIAsyncClient(
|
||||
String arg, String response) {
|
||||
private static AzureOpenAIClient mockCompletionOpenAIAsyncClient(String arg, String response) {
|
||||
List<Tuple2<String, String>> responses =
|
||||
Collections.singletonList(Tuples.of(arg, response));
|
||||
|
||||
return mockCompletionOpenAIAsyncClient(responses);
|
||||
return mockCompletionOpenAIAsyncClient(responses.toArray(new Tuple2[0]));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -152,12 +149,28 @@ public class DefaultKernelTest {
|
||||
|
||||
This if the client is prompted with "Tell me a joke", the mocked client would respond with "This is a joke"
|
||||
*/
|
||||
public static com.azure.ai.openai.OpenAIAsyncClient mockCompletionOpenAIAsyncClient(
|
||||
List<Tuple2<String, String>> responses) {
|
||||
|
||||
public static AzureOpenAIClient mockCompletionOpenAIAsyncClient(
|
||||
Tuple2<String, String>... responses) {
|
||||
|
||||
List<Tuple2<ArgumentMatcher<String>, String>> matchers =
|
||||
Arrays.stream(responses)
|
||||
.map(
|
||||
response ->
|
||||
Tuples.<ArgumentMatcher<String>, String>of(
|
||||
arg -> arg.contains(response.getT1()),
|
||||
response.getT2()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return mockCompletionOpenAIAsyncClientMatchers(matchers.toArray(new Tuple2[0]));
|
||||
}
|
||||
|
||||
public static AzureOpenAIClient mockCompletionOpenAIAsyncClientMatchers(
|
||||
Tuple2<ArgumentMatcher<String>, String>... responses) {
|
||||
com.azure.ai.openai.OpenAIAsyncClient openAIAsyncClient =
|
||||
Mockito.mock(com.azure.ai.openai.OpenAIAsyncClient.class);
|
||||
|
||||
for (Tuple2<String, String> response : responses) {
|
||||
for (Tuple2<ArgumentMatcher<String>, String> response : responses) {
|
||||
|
||||
Choice choice = Mockito.mock(Choice.class);
|
||||
Mockito.when(choice.getText()).thenReturn(response.getT2());
|
||||
@@ -170,13 +183,11 @@ public class DefaultKernelTest {
|
||||
openAIAsyncClient.getCompletions(
|
||||
Mockito.any(String.class),
|
||||
Mockito.<CompletionsOptions>argThat(
|
||||
it ->
|
||||
it.getPrompt()
|
||||
.get(0)
|
||||
.contains(response.getT1()))))
|
||||
it -> response.getT1().matches(it.getPrompt().get(0)))))
|
||||
.thenReturn(Mono.just(completions));
|
||||
}
|
||||
return openAIAsyncClient;
|
||||
AzureOpenAIClient client = new AzureOpenAIClient(openAIAsyncClient);
|
||||
return Mockito.spy(client);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -185,7 +196,7 @@ public class DefaultKernelTest {
|
||||
String model = "a-model";
|
||||
|
||||
String expectedResponse = "foo";
|
||||
com.azure.ai.openai.OpenAIAsyncClient openAIAsyncClient =
|
||||
OpenAIAsyncClient openAIAsyncClient =
|
||||
mockCompletionOpenAIAsyncClient("block", expectedResponse);
|
||||
|
||||
Kernel kernel = buildKernel(model, openAIAsyncClient);
|
||||
@@ -219,13 +230,11 @@ public class DefaultKernelTest {
|
||||
}
|
||||
|
||||
private static void assertCompletionsWasCalledWithModelAndText(
|
||||
com.azure.ai.openai.OpenAIAsyncClient openAIAsyncClient,
|
||||
String model,
|
||||
String expected) {
|
||||
OpenAIAsyncClient openAIAsyncClient, String model, String expected) {
|
||||
Mockito.verify(openAIAsyncClient, Mockito.times(1))
|
||||
.getCompletions(
|
||||
Mockito.matches(model),
|
||||
Mockito.<CompletionsOptions>argThat(
|
||||
Mockito.argThat(
|
||||
completionsOptions ->
|
||||
completionsOptions.getPrompt().size() == 1
|
||||
&& completionsOptions
|
||||
|
||||
-2
@@ -8,7 +8,6 @@ import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration;
|
||||
import com.microsoft.semantickernel.connectors.ai.openai.textembeddings.OpenAITextEmbeddingGeneration;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@@ -43,7 +42,6 @@ public class TextEmbeddingsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testEmbedding() {
|
||||
testEmbeddingGeneration(mockEmbeddingOpenAIAsyncClient(), 1);
|
||||
}
|
||||
|
||||
+2
-4
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.syntaxexamples;
|
||||
|
||||
import com.microsoft.openai.OpenAIAsyncClient;
|
||||
import com.microsoft.semantickernel.DefaultKernelTest;
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.orchestration.SKContext;
|
||||
@@ -12,13 +13,10 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Example02PipelineTest {
|
||||
@Test
|
||||
public void run() {
|
||||
com.azure.ai.openai.OpenAIAsyncClient client =
|
||||
DefaultKernelTest.mockCompletionOpenAIAsyncClient(new ArrayList<>());
|
||||
OpenAIAsyncClient client = DefaultKernelTest.mockCompletionOpenAIAsyncClient();
|
||||
Kernel kernel = DefaultKernelTest.buildKernel("model", client);
|
||||
|
||||
// Load native skill
|
||||
|
||||
+2
-4
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.syntaxexamples;
|
||||
|
||||
import com.microsoft.openai.OpenAIAsyncClient;
|
||||
import com.microsoft.semantickernel.DefaultKernelTest;
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.builders.SKBuilders;
|
||||
@@ -14,13 +15,10 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Example03VariablesTest {
|
||||
@Test
|
||||
public void run() {
|
||||
com.azure.ai.openai.OpenAIAsyncClient client =
|
||||
DefaultKernelTest.mockCompletionOpenAIAsyncClient(new ArrayList<>());
|
||||
OpenAIAsyncClient client = DefaultKernelTest.mockCompletionOpenAIAsyncClient();
|
||||
Kernel kernel = DefaultKernelTest.buildKernel("model", client);
|
||||
|
||||
// Load native skill
|
||||
|
||||
+10
-31
@@ -2,8 +2,9 @@
|
||||
package com.microsoft.semantickernel.syntaxexamples; // Copyright (c) Microsoft. All rights
|
||||
// reserved.
|
||||
|
||||
import static com.microsoft.semantickernel.DefaultKernelTest.mockCompletionOpenAIAsyncClient;
|
||||
|
||||
import com.microsoft.openai.AzureOpenAIClient;
|
||||
import com.microsoft.semantickernel.DefaultKernelTest;
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.KernelConfig;
|
||||
import com.microsoft.semantickernel.builders.SKBuilders;
|
||||
@@ -16,17 +17,17 @@ import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public class Example04CombineLLMPromptsAndNativeCode {
|
||||
public class Example04CombineLLMPromptsAndNativeCodeTest {
|
||||
|
||||
@Test
|
||||
public void run() {
|
||||
com.microsoft.openai.OpenAIAsyncClient client =
|
||||
new AzureOpenAIClient(
|
||||
DefaultKernelTest.mockCompletionOpenAIAsyncClient(new ArrayList<>()));
|
||||
AzureOpenAIClient client =
|
||||
mockCompletionOpenAIAsyncClient(
|
||||
Tuples.of(
|
||||
"Gran Torre Santiago is the tallest building in South America",
|
||||
"A-SUMMARY"));
|
||||
|
||||
KernelConfig kernelConfig =
|
||||
SKBuilders.kernelConfig()
|
||||
@@ -62,28 +63,6 @@ public class Example04CombineLLMPromptsAndNativeCode {
|
||||
kernel.getSkills().getFunction("Search", null),
|
||||
kernel.getSkill("SummarizeSkill").getFunction("Summarize", null));
|
||||
|
||||
Assertions.assertEquals(
|
||||
"Gran Torre Santiago is the tallest building in South America summary",
|
||||
result.block().getResult());
|
||||
|
||||
/*
|
||||
var result2 = await kernel.RunAsync(
|
||||
ask,
|
||||
search["Search"],
|
||||
sumSkill["Summarize"]
|
||||
);
|
||||
|
||||
var result3 = await kernel.RunAsync(
|
||||
ask,
|
||||
search["Search"],
|
||||
sumSkill["Notegen"]
|
||||
);
|
||||
|
||||
Console.WriteLine(ask + "\n");
|
||||
Console.WriteLine("Bing Answer: " + result1 + "\n");
|
||||
Console.WriteLine("Summary: " + result2 + "\n");
|
||||
Console.WriteLine("Notes: " + result3 + "\n");
|
||||
|
||||
*/
|
||||
Assertions.assertEquals("A-SUMMARY", result.block().getResult());
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.syntaxexamples;
|
||||
|
||||
import static com.microsoft.semantickernel.DefaultKernelTest.mockCompletionOpenAIAsyncClientMatchers;
|
||||
|
||||
import com.microsoft.openai.AzureOpenAIClient;
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.KernelConfig;
|
||||
import com.microsoft.semantickernel.builders.SKBuilders;
|
||||
import com.microsoft.semantickernel.extensions.KernelExtensions;
|
||||
import com.microsoft.semantickernel.planner.sequentialplanner.SequentialPlanner;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
public class Example05UsingThePlannerTest {
|
||||
|
||||
public static SequentialPlanner getPlanner(Kernel kernel) {
|
||||
kernel.importSkill(
|
||||
"SummarizeSkill",
|
||||
KernelExtensions.importSemanticSkillFromDirectory(
|
||||
"../../samples/skills", "SummarizeSkill"));
|
||||
kernel.importSkill(
|
||||
"WriterSkill",
|
||||
KernelExtensions.importSemanticSkillFromDirectory(
|
||||
"../../samples/skills", "WriterSkill"));
|
||||
|
||||
return new SequentialPlanner(kernel, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run() {
|
||||
ArgumentMatcher<String> matcher =
|
||||
prompt -> {
|
||||
return prompt.contains(
|
||||
"Create an XML plan step by step, to satisfy the goal given");
|
||||
};
|
||||
|
||||
AzureOpenAIClient client =
|
||||
mockCompletionOpenAIAsyncClientMatchers(Tuples.of(matcher, "A-PLAN"));
|
||||
|
||||
KernelConfig config =
|
||||
SKBuilders.kernelConfig()
|
||||
.addTextCompletionService(
|
||||
"davinci",
|
||||
kernel ->
|
||||
SKBuilders.textCompletionService()
|
||||
.build(client, "text-davinci-003"))
|
||||
.build();
|
||||
|
||||
Kernel kernel = SKBuilders.kernel().setKernelConfig(config).build();
|
||||
|
||||
SequentialPlanner planner = getPlanner(kernel);
|
||||
System.out.println(
|
||||
planner.createPlanAsync(
|
||||
"Write a poem about John Doe, then translate it into Italian.")
|
||||
.block()
|
||||
.getResult());
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.syntaxexamples; // Copyright (c) Microsoft. All rights
|
||||
// reserved.
|
||||
|
||||
import static com.microsoft.semantickernel.DefaultKernelTest.mockCompletionOpenAIAsyncClient;
|
||||
|
||||
import com.microsoft.openai.AzureOpenAIClient;
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.KernelConfig;
|
||||
import com.microsoft.semantickernel.builders.SKBuilders;
|
||||
import com.microsoft.semantickernel.coreskills.TimeSkill;
|
||||
import com.microsoft.semantickernel.orchestration.SKContext;
|
||||
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
|
||||
import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig;
|
||||
import com.microsoft.semantickernel.templateengine.PromptTemplateEngine;
|
||||
import com.microsoft.semantickernel.textcompletion.CompletionSKFunction;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Example06TemplateLanguageTest {
|
||||
|
||||
@Test
|
||||
public void run() {
|
||||
AzureOpenAIClient client =
|
||||
mockCompletionOpenAIAsyncClient(Tuples.of("Today is", "A-RESULT"));
|
||||
|
||||
KernelConfig kernelConfig =
|
||||
SKBuilders.kernelConfig()
|
||||
.addTextCompletionService(
|
||||
"text-davinci-003",
|
||||
kernel ->
|
||||
SKBuilders.textCompletionService()
|
||||
.build(client, "text-davinci-003"))
|
||||
.addTextEmbeddingsGenerationService(
|
||||
"text-embedding-ada-002",
|
||||
kernel ->
|
||||
SKBuilders.textEmbeddingGenerationService()
|
||||
.build(client, "text-embedding-ada-002"))
|
||||
.build();
|
||||
|
||||
Kernel kernel = SKBuilders.kernel().setKernelConfig(kernelConfig).build();
|
||||
|
||||
// Load native skill into the kernel skill collection, sharing its functions with prompt
|
||||
// templates
|
||||
// Functions loaded here are available as "time.*"
|
||||
kernel.importSkill(new TimeSkill(), "time");
|
||||
|
||||
// Semantic Function invoking time.Date and time.Time native functions
|
||||
String functionDefinition =
|
||||
"\n"
|
||||
+ "Today is: {{time.Date}}\n"
|
||||
+ "Current time is: {{time.Time}}\n"
|
||||
+ "\n"
|
||||
+ "Answer to the following questions using JSON syntax, including the data"
|
||||
+ " used.\n"
|
||||
+ "Is it morning, afternoon, evening, or night"
|
||||
+ " (morning/afternoon/evening/night)?\n"
|
||||
+ "Is it weekend time (weekend/not weekend)?";
|
||||
|
||||
PromptTemplate promptRenderer = SKBuilders.promptTemplate().build(functionDefinition, null);
|
||||
|
||||
SKContext skContext = SKBuilders.context().build(kernel.getSkills());
|
||||
|
||||
PromptTemplateEngine promptTemplateEngine = SKBuilders.promptTemplateEngine().build();
|
||||
|
||||
Mono<String> renderedPrompt = promptRenderer.renderAsync(skContext, promptTemplateEngine);
|
||||
|
||||
// Check that it has been rendered
|
||||
Assertions.assertTrue(!renderedPrompt.block().contains("time.Date"));
|
||||
|
||||
// Run the prompt / semantic function
|
||||
CompletionSKFunction kindOfDay =
|
||||
kernel.getSemanticFunctionBuilder()
|
||||
.createFunction(
|
||||
functionDefinition,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new PromptTemplateConfig.CompletionConfig(
|
||||
0, 0, 0, 0, 256, new ArrayList<>()));
|
||||
|
||||
Assertions.assertEquals("A-RESULT", kindOfDay.invokeAsync("").block().getResult());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user