mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
Java - Improve NativeFunction support (#1202)
- Add support for SKFunctionInputAttribute. - Detect if there is a single string and use it as input. - Add tests for the NativeFunction ### 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 😄
This commit is contained in:
+2
-2
@@ -10,7 +10,7 @@ import java.lang.annotation.Target;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface DefineSKFunction {
|
||||
String description();
|
||||
String description() default "";
|
||||
|
||||
String name();
|
||||
String name() default "";
|
||||
}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.skilldefinition.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/** Annotates a parameter binding it to the "input" context variable */
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface SKFunctionInputAttribute {}
|
||||
+7
-4
@@ -10,11 +10,14 @@ import java.lang.annotation.Target;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface SKFunctionParameters {
|
||||
String description();
|
||||
|
||||
String name();
|
||||
String NO_DEFAULT_VALUE = "SKFunctionParameters__NO_INPUT_PROVIDED";
|
||||
|
||||
String defaultValue();
|
||||
String description() default "";
|
||||
|
||||
Class<?> type();
|
||||
String name() default "";
|
||||
|
||||
String defaultValue() default NO_DEFAULT_VALUE;
|
||||
|
||||
Class<?> type() default String.class;
|
||||
}
|
||||
|
||||
+2
-5
@@ -61,14 +61,11 @@ public class TextMemorySkill {
|
||||
name = collectionParam,
|
||||
description =
|
||||
"Memories collection associated with the memory to retrieve",
|
||||
defaultValue = DefaultCollection,
|
||||
type = String.class)
|
||||
defaultValue = DefaultCollection)
|
||||
String collection,
|
||||
@SKFunctionParameters(
|
||||
name = KeyParam,
|
||||
description = "The key associated with the memory to retrieve",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
description = "The key associated with the memory to retrieve")
|
||||
String key) {
|
||||
|
||||
SemanticTextMemory memory = context.getSemanticMemory();
|
||||
|
||||
+4
-15
@@ -2,6 +2,7 @@
|
||||
package com.microsoft.semantickernel.coreskills;
|
||||
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -28,33 +29,21 @@ public class TextSkill {
|
||||
|
||||
@DefineSKFunction(description = "Change all string chars to uppercase.", name = "Uppercase")
|
||||
public Mono<String> uppercase(
|
||||
@SKFunctionParameters(
|
||||
description = "Text to uppercase",
|
||||
name = "input",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
@SKFunctionInputAttribute @SKFunctionParameters(description = "Text to uppercase")
|
||||
String text) {
|
||||
return Mono.just(text.toUpperCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@DefineSKFunction(description = "Remove spaces to the left of a string.", name = "LStrip")
|
||||
public Mono<String> lStrip(
|
||||
@SKFunctionParameters(
|
||||
description = "Text to edit",
|
||||
name = "input",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
@SKFunctionInputAttribute @SKFunctionParameters(description = "Text to edit")
|
||||
String text) {
|
||||
return Mono.just(text.replaceAll("^ +", ""));
|
||||
}
|
||||
|
||||
@DefineSKFunction(description = "Remove spaces to the right of a string.", name = "RStrip")
|
||||
public Mono<String> rStrip(
|
||||
@SKFunctionParameters(
|
||||
description = "Text to edit",
|
||||
name = "input",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
@SKFunctionInputAttribute @SKFunctionParameters(description = "Text to edit")
|
||||
String text) {
|
||||
return Mono.just(text.replaceAll(" +$", ""));
|
||||
}
|
||||
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.kernelextensions;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.microsoft.semantickernel.semanticfunctions.DefaultPromptTemplate;
|
||||
import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig;
|
||||
import com.microsoft.semantickernel.semanticfunctions.SemanticFunctionConfig;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/// <summary>
|
||||
/// Class for extensions methods for importing semantic functions from a directory.
|
||||
/// </summary>
|
||||
public class ImportSemanticSkillFromDirectoryExtension {
|
||||
|
||||
private static final Logger LOGGER =
|
||||
LoggerFactory.getLogger(ImportSemanticSkillFromDirectoryExtension.class);
|
||||
|
||||
public static Map<String, SemanticFunctionConfig> importSemanticSkillFromDirectory(
|
||||
String parentDirectory, String skillDirectoryName) {
|
||||
|
||||
String CONFIG_FILE = "config.json";
|
||||
String PROMPT_FILE = "skprompt.txt";
|
||||
|
||||
// Verify.ValidSkillName(skillDirectoryName);
|
||||
File skillDir = new File(parentDirectory, skillDirectoryName);
|
||||
// Verify.DirectoryExists(skillDir);
|
||||
|
||||
File[] files = skillDir.listFiles();
|
||||
if (files == null) {
|
||||
return Collections.unmodifiableMap(new HashMap<>());
|
||||
}
|
||||
|
||||
HashMap<String, SemanticFunctionConfig> skills = new HashMap<>();
|
||||
|
||||
for (File dir : files) {
|
||||
try {
|
||||
// Continue only if prompt template exists
|
||||
File promptPath = new File(dir, PROMPT_FILE);
|
||||
if (!promptPath.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load prompt configuration. Note: the configuration is
|
||||
// optional.
|
||||
PromptTemplateConfig config = new PromptTemplateConfig("", "", null);
|
||||
|
||||
File configPath = new File(dir, CONFIG_FILE);
|
||||
if (configPath.exists()) {
|
||||
config = new ObjectMapper().readValue(configPath, PromptTemplateConfig.class);
|
||||
|
||||
// Verify.NotNull(config, $"Invalid prompt template
|
||||
// configuration, unable to parse {configPath}");
|
||||
}
|
||||
|
||||
// kernel.Log.LogTrace("Config {0}: {1}", functionName,
|
||||
// config.ToJson());
|
||||
|
||||
// Load prompt template
|
||||
DefaultPromptTemplate template =
|
||||
new DefaultPromptTemplate(
|
||||
new String(
|
||||
Files.readAllBytes(promptPath.toPath()),
|
||||
Charset.defaultCharset()),
|
||||
config);
|
||||
|
||||
skills.put(dir.getName(), new SemanticFunctionConfig(config, template));
|
||||
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to read file", e);
|
||||
}
|
||||
}
|
||||
|
||||
return skills;
|
||||
}
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// A kernel extension that allows to load Semantic Functions, defined by prompt templates stored in the filesystem.
|
||||
/// A skill directory contains a set of subdirectories, one for each semantic function.
|
||||
/// This extension requires the path of the parent directory (e.g. "d:\skills") and the name of the skill directory
|
||||
/// (e.g. "OfficeSkill"), which is used also as the "skill name" in the internal skill collection.
|
||||
///
|
||||
/// Note: skill and function names can contain only alphanumeric chars and underscore.
|
||||
///
|
||||
/// Example:
|
||||
/// D:\skills\ # parentDirectory = "D:\skills"
|
||||
///
|
||||
/// |__ OfficeSkill\ # skillDirectoryName = "SummarizeEmailThread"
|
||||
///
|
||||
/// |__ ScheduleMeeting # semantic function
|
||||
/// |__ skprompt.txt # prompt template
|
||||
/// |__ config.json # settings (optional file)
|
||||
///
|
||||
/// |__ SummarizeEmailThread # semantic function
|
||||
/// |__ skprompt.txt # prompt template
|
||||
/// |__ config.json # settings (optional file)
|
||||
///
|
||||
/// |__ MergeWordAndExcelDocs # semantic function
|
||||
/// |__ skprompt.txt # prompt template
|
||||
/// |__ config.json # settings (optional file)
|
||||
///
|
||||
/// |__ XboxSkill\ # another skill, etc.
|
||||
///
|
||||
/// |__ MessageFriend
|
||||
/// |__ skprompt.txt
|
||||
/// |__ config.json
|
||||
/// |__ LaunchGame
|
||||
/// |__ skprompt.txt
|
||||
/// |__ config.json
|
||||
///
|
||||
/// See https://github.com/microsoft/semantic-kernel/tree/main/samples/skills for some skills in our repo.
|
||||
/// </summary>
|
||||
/// <param name="kernel">Semantic Kernel instance</param>
|
||||
/// <param name="parentDirectory">Directory containing the skill directory, e.g. "d:\myAppSkills"</param>
|
||||
/// <param name="skillDirectoryName">Name of the directory containing the selected skill, e.g. "StrategySkill"</param>
|
||||
/// <returns>A list of all the semantic functions found in the directory, indexed by function name.</returns>
|
||||
public static IDictionary<string, ISKFunction> ImportSemanticSkillFromDirectory(
|
||||
this IKernel kernel, string parentDirectory, string skillDirectoryName) {
|
||||
const string CONFIG_FILE = "config.json";
|
||||
const string PROMPT_FILE = "skprompt.txt";
|
||||
|
||||
Verify.ValidSkillName(skillDirectoryName);
|
||||
var skillDir = Path.Combine(parentDirectory, skillDirectoryName);
|
||||
Verify.DirectoryExists(skillDir);
|
||||
|
||||
var skill = new Dictionary<string, ISKFunction>();
|
||||
|
||||
string[] directories = Directory.GetDirectories(skillDir);
|
||||
foreach(string dir in directories)
|
||||
{
|
||||
var functionName = Path.GetFileName(dir);
|
||||
|
||||
// Continue only if prompt template exists
|
||||
var promptPath = Path.Combine(dir, PROMPT_FILE);
|
||||
if (!File.Exists(promptPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load prompt configuration. Note: the configuration is optional.
|
||||
var config = new PromptTemplateConfig();
|
||||
var configPath = Path.Combine(dir, CONFIG_FILE);
|
||||
if (File.Exists(configPath)) {
|
||||
config = PromptTemplateConfig.FromJson(File.ReadAllText(configPath));
|
||||
Verify.NotNull(config, $"Invalid prompt template configuration, unable to parse {configPath}");
|
||||
}
|
||||
|
||||
kernel.Log.LogTrace("Config {0}: {1}", functionName, config.ToJson());
|
||||
|
||||
// Load prompt template
|
||||
var template = new PromptTemplate(File.ReadAllText(promptPath), config, kernel.PromptTemplateEngine);
|
||||
|
||||
// Prepare lambda wrapping AI logic
|
||||
var functionConfig = new SemanticFunctionConfig(config, template);
|
||||
|
||||
kernel.Log.LogTrace("Registering function {0}.{1} loaded from {2}", skillDirectoryName, functionName, dir);
|
||||
skill[functionName] = kernel.RegisterSemanticFunction(skillDirectoryName, functionName, functionConfig);
|
||||
}
|
||||
|
||||
return skill;
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.kernelextensions;
|
||||
|
||||
public class NamedISKFunction {}
|
||||
+10
-1
@@ -43,7 +43,16 @@ class DefaultContextVariables implements ContextVariables, WritableContextVariab
|
||||
}
|
||||
|
||||
public ContextVariables appendToVariable(@NonNull String key, @NonNull String content) {
|
||||
return setVariable(key, this.variables.get(key) + content);
|
||||
String existing = this.variables.get(key);
|
||||
|
||||
String newVal;
|
||||
if (existing == null) {
|
||||
newVal = content;
|
||||
} else {
|
||||
newVal = existing + content;
|
||||
}
|
||||
|
||||
return setVariable(key, newVal);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+195
-543
@@ -1,21 +1,27 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.orchestration;
|
||||
|
||||
import static com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters.NO_DEFAULT_VALUE;
|
||||
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.ai.AIException;
|
||||
import com.microsoft.semantickernel.memory.SemanticTextMemory;
|
||||
import com.microsoft.semantickernel.skilldefinition.KernelSkillsSupplier;
|
||||
import com.microsoft.semantickernel.skilldefinition.ParameterView;
|
||||
import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -28,6 +34,8 @@ import javax.annotation.Nullable;
|
||||
/// </summary>
|
||||
public class NativeSKFunction extends AbstractSkFunction<Void, SemanticSKContext> {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NativeSKFunction.class);
|
||||
|
||||
private final SKNativeTask<SemanticSKContext> function;
|
||||
|
||||
public NativeSKFunction(
|
||||
@@ -58,40 +66,6 @@ public class NativeSKFunction extends AbstractSkFunction<Void, SemanticSKContext
|
||||
// No actions needed
|
||||
}
|
||||
|
||||
/*
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string SkillName { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Description { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSemantic { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CompleteRequestSettings RequestSettings
|
||||
{
|
||||
get { return this._aiRequestSettings; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of function parameters
|
||||
/// </summary>
|
||||
public IList<ParameterView> Parameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a native function instance, wrapping a native object method
|
||||
/// </summary>
|
||||
/// <param name="methodContainerInstance">Object containing the method to invoke</param>
|
||||
/// <param name="methodSignature">Signature of the method to invoke</param>
|
||||
/// <param name="skillName">SK skill name</param>
|
||||
/// <param name="log">Application logger</param>
|
||||
/// <returns>SK function instance</returns>
|
||||
*/
|
||||
|
||||
private static class MethodDetails {
|
||||
public final boolean hasSkFunctionAttribute;
|
||||
public final AbstractSkFunction.DelegateTypes type;
|
||||
@@ -142,132 +116,6 @@ public class NativeSKFunction extends AbstractSkFunction<Void, SemanticSKContext
|
||||
kernelSkillsSupplier);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FunctionView Describe()
|
||||
{
|
||||
return new FunctionView
|
||||
{
|
||||
IsSemantic = this.IsSemantic,
|
||||
Name = this.Name,
|
||||
SkillName = this.SkillName,
|
||||
Description = this.Description,
|
||||
Parameters = this.Parameters,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<SKContext> InvokeAsync(
|
||||
string input,
|
||||
SKContext? context = null,
|
||||
CompleteRequestSettings? settings = null,
|
||||
ILogger? log = null,
|
||||
CancellationToken? cancel = null)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
var cToken = cancel ?? default;
|
||||
log ??= NullLogger.Instance;
|
||||
context = new SKContext(
|
||||
new ContextVariables(""),
|
||||
NullMemory.Instance,
|
||||
this._skillCollection,
|
||||
log,
|
||||
cToken);
|
||||
}
|
||||
|
||||
context.Variables.Update(input);
|
||||
|
||||
return this.InvokeAsync(context, settings, log, cancel);
|
||||
}
|
||||
*/
|
||||
/// <inheritdoc/>
|
||||
/*
|
||||
/// <inheritdoc/>
|
||||
public ISKFunction SetDefaultSkillCollection(IReadOnlySkillCollection skills)
|
||||
{
|
||||
this._skillCollection = skills;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ISKFunction SetAIService(Func<ITextCompletion> serviceFactory)
|
||||
{
|
||||
Verify.NotNull(serviceFactory, "AI LLM service factory is empty");
|
||||
this.VerifyIsSemantic();
|
||||
this._aiService = serviceFactory.Invoke();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ISKFunction SetAIConfiguration(CompleteRequestSettings settings)
|
||||
{
|
||||
Verify.NotNull(settings, "AI LLM request settings are empty");
|
||||
this.VerifyIsSemantic();
|
||||
this._aiRequestSettings = settings;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizer.
|
||||
/// </summary>
|
||||
~SKFunction()
|
||||
{
|
||||
this.ReleaseUnmanagedResources();
|
||||
}
|
||||
|
||||
#region private
|
||||
|
||||
private readonly DelegateTypes _delegateType;
|
||||
private readonly Delegate _function;
|
||||
private readonly ILogger _log;
|
||||
private IReadOnlySkillCollection? _skillCollection;
|
||||
private CompleteRequestSettings _aiRequestSettings = new();
|
||||
|
||||
private struct MethodDetails
|
||||
{
|
||||
public bool HasSkFunctionAttribute { get; set; }
|
||||
public DelegateTypes Type { get; set; }
|
||||
public Delegate Function { get; set; }
|
||||
public List<ParameterView> Parameters { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
private void ReleaseUnmanagedResources()
|
||||
{
|
||||
if (this._aiService is not IDisposable disposable) { return; }
|
||||
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throw an exception if the function is not semantic, use this method when some logic makes sense only for semantic functions.
|
||||
/// </summary>
|
||||
/// <exception cref="KernelException"></exception>
|
||||
private void VerifyIsSemantic()
|
||||
{
|
||||
if (this.IsSemantic) { return; }
|
||||
|
||||
this._log.LogError("The function is not semantic");
|
||||
throw new KernelException(
|
||||
KernelException.ErrorCodes.InvalidFunctionType,
|
||||
"Invalid operation, the method requires a semantic function");
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public SemanticSKContext buildContext(
|
||||
ContextVariables variables,
|
||||
@@ -281,157 +129,8 @@ public class NativeSKFunction extends AbstractSkFunction<Void, SemanticSKContext
|
||||
protected Mono<SemanticSKContext> invokeAsyncInternal(
|
||||
SemanticSKContext context, @Nullable Void settings) {
|
||||
return this.function.run(context);
|
||||
/*
|
||||
TraceFunctionTypeCall(this._delegateType, this._log);
|
||||
|
||||
this.EnsureContextHasSkills(context);
|
||||
|
||||
switch (this._delegateType)
|
||||
{
|
||||
case DelegateTypes.Void: // 1
|
||||
{
|
||||
var callable = (Action)this._function;
|
||||
callable();
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.OutString: // 2
|
||||
{
|
||||
var callable = (Func<string>)this._function;
|
||||
context.Variables.Update(callable());
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.OutTaskString: // 3
|
||||
{
|
||||
var callable = (Func<Task<string>>)this._function;
|
||||
context.Variables.Update(await callable());
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.InSKContext: // 4
|
||||
{
|
||||
var callable = (Action<SKContext>)this._function;
|
||||
callable(context);
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.InSKContextOutString: // 5
|
||||
{
|
||||
var callable = (Func<SKContext, string>)this._function;
|
||||
context.Variables.Update(callable(context));
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.InSKContextOutTaskString: // 6
|
||||
{
|
||||
var callable = (Func<SKContext, Task<string>>)this._function;
|
||||
context.Variables.Update(await callable(context));
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.ContextSwitchInSKContextOutTaskSKContext: // 7
|
||||
{
|
||||
var callable = (Func<SKContext, Task<SKContext>>)this._function;
|
||||
// Note: Context Switching: allows the function to replace with a new context, e.g. to branch execution path
|
||||
context = await callable(context);
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.InString:
|
||||
{
|
||||
var callable = (Action<string>)this._function; // 8
|
||||
callable(context.Variables.Input);
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.InStringOutString: // 9
|
||||
{
|
||||
var callable = (Func<string, string>)this._function;
|
||||
context.Variables.Update(callable(context.Variables.Input));
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.InStringOutTaskString: // 10
|
||||
{
|
||||
var callable = (Func<string, Task<string>>)this._function;
|
||||
context.Variables.Update(await callable(context.Variables.Input));
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.InStringAndContext: // 11
|
||||
{
|
||||
var callable = (Action<string, SKContext>)this._function;
|
||||
callable(context.Variables.Input, context);
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.InStringAndContextOutString: // 12
|
||||
{
|
||||
var callable = (Func<string, SKContext, string>)this._function;
|
||||
context.Variables.Update(callable(context.Variables.Input, context));
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.InStringAndContextOutTaskString: // 13
|
||||
{
|
||||
var callable = (Func<string, SKContext, Task<string>>)this._function;
|
||||
context.Variables.Update(await callable(context.Variables.Input, context));
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.ContextSwitchInStringAndContextOutTaskContext: // 14
|
||||
{
|
||||
var callable = (Func<string, SKContext, Task<SKContext>>)this._function;
|
||||
// Note: Context Switching: allows the function to replace with a new context, e.g. to branch execution path
|
||||
context = await callable(context.Variables.Input, context);
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.InStringOutTask: // 15
|
||||
{
|
||||
var callable = (Func<string, Task>)this._function;
|
||||
await callable(context.Variables.Input);
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.InContextOutTask: // 16
|
||||
{
|
||||
var callable = (Func<SKContext, Task>)this._function;
|
||||
await callable(context);
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.InStringAndContextOutTask: // 17
|
||||
{
|
||||
var callable = (Func<string, SKContext, Task>)this._function;
|
||||
await callable(context.Variables.Input, context);
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.OutTask: // 18
|
||||
{
|
||||
var callable = (Func<Task>)this._function;
|
||||
await callable();
|
||||
return context;
|
||||
}
|
||||
|
||||
case DelegateTypes.Unknown:
|
||||
default:
|
||||
throw new KernelException(
|
||||
KernelException.ErrorCodes.FunctionTypeNotSupported,
|
||||
"Invalid function type detected, unable to execute.");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
private void EnsureContextHasSkills(SKContext context)
|
||||
{
|
||||
// If the function is invoked manually, the user might have left out the skill collection
|
||||
context.Skills ??= this._skillCollection;
|
||||
}
|
||||
*/
|
||||
private static MethodDetails getMethodDetails(
|
||||
Method methodSignature, Object methodContainerInstance) {
|
||||
// Verify.NotNull(methodSignature, "Method is NULL");
|
||||
@@ -453,51 +152,13 @@ public class NativeSKFunction extends AbstractSkFunction<Void, SemanticSKContext
|
||||
// Arrays.asList(methodSignature.getGenericParameterTypes()).contains(String.class);
|
||||
|
||||
String name = methodSignature.getAnnotation(DefineSKFunction.class).name();
|
||||
|
||||
if (name == null || name.isEmpty()) {
|
||||
name = methodSignature.getName();
|
||||
}
|
||||
|
||||
String description = methodSignature.getAnnotation(DefineSKFunction.class).description();
|
||||
|
||||
/*
|
||||
TODO SUPPORT FUNCTION INPUT!!!:
|
||||
|
||||
// SKFunctionInput attribute
|
||||
SKFunctionInputAttribute? skMainParam = methodSignature
|
||||
.GetCustomAttributes(typeof(SKFunctionInputAttribute), true)
|
||||
.Cast<SKFunctionInputAttribute>()
|
||||
.FirstOrDefault();
|
||||
|
||||
// SKFunctionContextParameter attribute
|
||||
IList<SKFunctionContextParameterAttribute> skContextParams = methodSignature
|
||||
.GetCustomAttributes(typeof(SKFunctionContextParameterAttribute), true)
|
||||
.Cast<SKFunctionContextParameterAttribute>().ToList();
|
||||
|
||||
// Handle main string param description, if available/valid
|
||||
// Note: Using [SKFunctionInput] is optional
|
||||
if (hasStringParam)
|
||||
{
|
||||
result.Parameters.Add(skMainParam != null
|
||||
? skMainParam.ToParameterView() // Use the developer description
|
||||
: new ParameterView { Name = "input", Description = "Input string", DefaultValue = "" }); // Use a default description
|
||||
}
|
||||
else if (skMainParam != null)
|
||||
{
|
||||
// The developer used [SKFunctionInput] on a function that doesn't support a string input
|
||||
throw new KernelException(
|
||||
KernelException.ErrorCodes.InvalidFunctionDescription,
|
||||
"The function doesn't have a string parameter, do not use " + typeof(SKFunctionInputAttribute));
|
||||
}
|
||||
|
||||
// Handle named arg passed via the SKContext object
|
||||
// Note: "input" is added first to the list, before context params
|
||||
// Note: Using [SKFunctionContextParameter] is optional
|
||||
result.Parameters.AddRange(skContextParams.Select(x => x.ToParameterView()));
|
||||
|
||||
// Check for param names conflict
|
||||
// Note: the name "input" is reserved for the main parameter
|
||||
Verify.ParametersUniqueness(result.Parameters);
|
||||
|
||||
result.Description = skFunctionAttribute.Description ?? "";
|
||||
|
||||
log?.LogTrace("Method {0} found", result.Name);
|
||||
*/
|
||||
return new MethodDetails(
|
||||
hasSkFunctionAttribute, type, function, parameters, name, description);
|
||||
}
|
||||
@@ -506,55 +167,190 @@ public class NativeSKFunction extends AbstractSkFunction<Void, SemanticSKContext
|
||||
return (contextInput) -> {
|
||||
SemanticSKContext context = contextInput.copy();
|
||||
|
||||
List<Object> args =
|
||||
Arrays.stream(method.getParameters())
|
||||
.map(
|
||||
parameter -> {
|
||||
if (SemanticSKContext.class.isAssignableFrom(
|
||||
parameter.getType())) {
|
||||
return context; // .copy();
|
||||
} else if (parameter.isAnnotationPresent(
|
||||
SKFunctionParameters.class)) {
|
||||
SKFunctionParameters annotation =
|
||||
parameter.getAnnotation(
|
||||
SKFunctionParameters.class);
|
||||
String arg =
|
||||
context.getVariables().get(annotation.name());
|
||||
if (arg == null) {
|
||||
arg = annotation.defaultValue();
|
||||
}
|
||||
return arg;
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Unknown arg " + parameter.getName());
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
Set<Parameter> inputArgs = determineInputArgs(method);
|
||||
|
||||
Mono mono;
|
||||
if (method.getReturnType().isAssignableFrom(Mono.class)) {
|
||||
try {
|
||||
mono = (Mono) method.invoke(instance, args.toArray());
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
try {
|
||||
|
||||
List<Object> args =
|
||||
Arrays.stream(method.getParameters())
|
||||
.map(
|
||||
parameter -> {
|
||||
if (SKContext.class.isAssignableFrom(
|
||||
parameter.getType())) {
|
||||
return context; // .copy();
|
||||
} else {
|
||||
String value =
|
||||
getArgumentValue(
|
||||
method, context, parameter,
|
||||
inputArgs);
|
||||
if (value != null) {
|
||||
return value;
|
||||
} else {
|
||||
throw new AIException(
|
||||
AIException.ErrorCodes
|
||||
.InvalidConfiguration,
|
||||
"Unknown arg " + parameter.getName());
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Mono mono;
|
||||
if (method.getReturnType().isAssignableFrom(Mono.class)) {
|
||||
try {
|
||||
mono = (Mono) method.invoke(instance, args.toArray());
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
return Mono.error(e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
mono = Mono.just(method.invoke(instance, args.toArray()));
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
return Mono.error(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
mono = Mono.just(method.invoke(instance, args.toArray()));
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
||||
return mono.map(
|
||||
it -> {
|
||||
if (it instanceof SKContext) {
|
||||
return it;
|
||||
} else {
|
||||
return context.update((String) it);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
return Mono.error(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static String getArgumentValue(
|
||||
Method method,
|
||||
SemanticSKContext context,
|
||||
Parameter parameter,
|
||||
Set<Parameter> inputArgs) {
|
||||
String variableName = getGetVariableName(parameter);
|
||||
|
||||
String arg = context.getVariables().get(variableName);
|
||||
if (arg == null) {
|
||||
// If this is bound to input get the input value
|
||||
if (inputArgs.contains(parameter)) {
|
||||
String input = context.getVariables().get(ContextVariables.MAIN_KEY);
|
||||
if (input != null && !input.isEmpty()) {
|
||||
arg = input;
|
||||
}
|
||||
}
|
||||
|
||||
return mono.map(
|
||||
it -> {
|
||||
if (it instanceof SKContext) {
|
||||
return it;
|
||||
} else {
|
||||
return context.update((String) it);
|
||||
}
|
||||
});
|
||||
};
|
||||
if (arg == null || arg.isEmpty()) {
|
||||
SKFunctionParameters annotation =
|
||||
parameter.getAnnotation(SKFunctionParameters.class);
|
||||
if (annotation != null) {
|
||||
arg = annotation.defaultValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((arg == null || arg.isEmpty()) && variableName.matches("arg\\d")) {
|
||||
LOGGER.warn(
|
||||
"For the function "
|
||||
+ method.getDeclaringClass().getName()
|
||||
+ "."
|
||||
+ method.getName()
|
||||
+ ", the parameter argument name was detected as \""
|
||||
+ variableName
|
||||
+ "\" this indicates that the argument name for this function was"
|
||||
+ " removed during compilation. To support this function its arguments"
|
||||
+ " must be annotated with @SKFunctionParameters with the name defined,"
|
||||
+ " or @SKFunctionInputAttribute.");
|
||||
}
|
||||
|
||||
if (NO_DEFAULT_VALUE.equals(arg)) {
|
||||
return null;
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
private static String getGetVariableName(Parameter parameter) {
|
||||
SKFunctionParameters annotation = parameter.getAnnotation(SKFunctionParameters.class);
|
||||
|
||||
if (annotation == null || annotation.name() == null || annotation.name().isEmpty()) {
|
||||
return parameter.getName();
|
||||
}
|
||||
return annotation.name();
|
||||
}
|
||||
|
||||
private static Set<Parameter> determineInputArgs(Method method) {
|
||||
// Something is bound to the input if either:
|
||||
// - it is annotated with @SKFunctionInputAttribute
|
||||
// - SKFunctionParameters annotation has a name of "input"
|
||||
// - the arg name is "input"
|
||||
// - there is only 1 string argument to the function
|
||||
|
||||
// Get all parameters annotated with SKFunctionInputAttribute
|
||||
List<Parameter> annotated =
|
||||
Arrays.stream(method.getParameters())
|
||||
.filter(it -> it.isAnnotationPresent(SKFunctionInputAttribute.class))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (annotated.size() > 1) {
|
||||
LOGGER.warn(
|
||||
"Multiple arguments of "
|
||||
+ method.getName()
|
||||
+ " have the @SKFunctionInputAttribute annotation. This is likely an"
|
||||
+ " error.");
|
||||
}
|
||||
|
||||
// Get all parameters annotated with SKFunctionParameters with a name of "input"
|
||||
List<Parameter> annotatedWithName =
|
||||
Arrays.stream(method.getParameters())
|
||||
.filter(it -> it.isAnnotationPresent(SKFunctionParameters.class))
|
||||
.filter(it -> it.getName().equals("input"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (annotatedWithName.size() > 1) {
|
||||
LOGGER.warn(
|
||||
"Multiple arguments of "
|
||||
+ method.getName()
|
||||
+ " have the name input. This is likely an error.");
|
||||
}
|
||||
|
||||
// Get all parameters named "input", this will frequently fail as compilers strip out
|
||||
// argument names
|
||||
List<Parameter> calledInput =
|
||||
Arrays.stream(method.getParameters())
|
||||
.filter(it -> getGetVariableName(it).equals("input"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Get parameter if there is only 1 string, and it has not been annotated with
|
||||
// SKFunctionParameters
|
||||
List<Parameter> soloString =
|
||||
Arrays.stream(method.getParameters())
|
||||
.filter(it -> it.getType().equals(String.class))
|
||||
.filter(
|
||||
it ->
|
||||
!(it.isAnnotationPresent(SKFunctionParameters.class)
|
||||
&& !it.getAnnotation(SKFunctionParameters.class)
|
||||
.name()
|
||||
.isEmpty()))
|
||||
.collect(Collectors.toList());
|
||||
if (soloString.size() > 1) {
|
||||
soloString.clear();
|
||||
}
|
||||
|
||||
Set<Parameter> params = new HashSet<>();
|
||||
params.addAll(annotated);
|
||||
params.addAll(annotatedWithName);
|
||||
params.addAll(calledInput);
|
||||
params.addAll(soloString);
|
||||
|
||||
if (params.size() > 1) {
|
||||
LOGGER.warn(
|
||||
"Multiple arguments of "
|
||||
+ method.getName()
|
||||
+ " are bound to the input variable. This is likely an error.");
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
// Inspect a method and returns the corresponding delegate and related info
|
||||
@@ -569,149 +365,5 @@ public class NativeSKFunction extends AbstractSkFunction<Void, SemanticSKContext
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unknown function type");
|
||||
/*
|
||||
if (EqualMethods(instance, method, typeof(Action), out Delegate? funcDelegate))
|
||||
{
|
||||
return (DelegateTypes.Void, funcDelegate, false);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<string>), out funcDelegate))
|
||||
{
|
||||
return (DelegateTypes.OutString, funcDelegate, false);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<Task<string>>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.OutTaskString, funcDelegate, false);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Action<SKContext>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.InSKContext, funcDelegate, false);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<SKContext, string>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.InSKContextOutString, funcDelegate, false);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<SKContext, Task<string>>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.InSKContextOutTaskString, funcDelegate, false);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<SKContext, Task<SKContext>>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.ContextSwitchInSKContextOutTaskSKContext, funcDelegate, false);
|
||||
}
|
||||
|
||||
// === string input ==
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Action<string>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.InString, funcDelegate, true);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<string, string>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.InStringOutString, funcDelegate, true);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<string, Task<string>>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.InStringOutTaskString, funcDelegate, true);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Action<string, SKContext>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.InStringAndContext, funcDelegate, true);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<string, SKContext, string>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.InStringAndContextOutString, funcDelegate, true);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<string, SKContext, Task<string>>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.InStringAndContextOutTaskString, funcDelegate, true);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<string, SKContext, Task<SKContext>>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.ContextSwitchInStringAndContextOutTaskContext, funcDelegate, true);
|
||||
}
|
||||
|
||||
// == Tasks without output ==
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<string, Task>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.InStringOutTask, funcDelegate, true);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<SKContext, Task>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.InContextOutTask, funcDelegate, false);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<string, SKContext, Task>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.InStringAndContextOutTask, funcDelegate, true);
|
||||
}
|
||||
|
||||
if (EqualMethods(instance, method, typeof(Func<Task>), out funcDelegate!))
|
||||
{
|
||||
return (DelegateTypes.OutTask, funcDelegate, false);
|
||||
}
|
||||
|
||||
// [SKContext DoSomething(SKContext context)] is not supported, use the async form instead.
|
||||
// If you encounter scenarios that require to interact with the context synchronously, please let us know.
|
||||
if (EqualMethods(instance, method, typeof(Func<SKContext, SKContext>), out _))
|
||||
{
|
||||
throw new KernelException(
|
||||
KernelException.ErrorCodes.FunctionTypeNotSupported,
|
||||
$"Function {method.Name} has an invalid signature 'Func<SKContext, SKContext>'. " +
|
||||
"Please use 'Func<SKContext, Task<SKContext>>' instead.");
|
||||
}
|
||||
|
||||
throw new KernelException(
|
||||
KernelException.ErrorCodes.FunctionTypeNotSupported,
|
||||
$"Function {method.Name} has an invalid signature not supported by the kernel");
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
[SuppressMessage("Maintainability", "CA1508:Avoid dead conditional code", Justification = "Delegate.CreateDelegate result can be null")]
|
||||
private static bool EqualMethods(
|
||||
object? instance,
|
||||
MethodInfo userMethod,
|
||||
Type delegateDefinition,
|
||||
[NotNullWhen(true)] out Delegate? result)
|
||||
{
|
||||
// Instance methods
|
||||
if (instance != null)
|
||||
{
|
||||
result = Delegate.CreateDelegate(delegateDefinition, instance, userMethod, false);
|
||||
if (result != null) { return true; }
|
||||
}
|
||||
|
||||
// Static methods
|
||||
result = Delegate.CreateDelegate(delegateDefinition, userMethod, false);
|
||||
|
||||
return result != null;
|
||||
}
|
||||
|
||||
// Internal event to count (and test) that the correct delegates are invoked
|
||||
private static void TraceFunctionTypeCall(DelegateTypes type, ILogger log)
|
||||
{
|
||||
log.Log(
|
||||
LogLevel.Trace,
|
||||
new EventId((int)type, $"FuncType{type}"),
|
||||
"Executing function type {0}: {1}", (int)type, type.ToString("G"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
-95
@@ -33,101 +33,6 @@ public class DefaultPromptTemplate implements PromptTemplate {
|
||||
return promptTemplateEngine.renderAsync(this.promptTemplate, executionContext);
|
||||
}
|
||||
|
||||
/*
|
||||
private final String _template;
|
||||
private readonly IPromptTemplateEngine _templateEngine;
|
||||
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly ILogger _log = NullLogger.Instance;
|
||||
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly PromptTemplateConfig _promptConfig;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for PromptTemplate.
|
||||
/// </summary>
|
||||
/// <param name="template">Template.</param>
|
||||
/// <param name="promptTemplateConfig">Prompt template configuration.</param>
|
||||
/// <param name="kernel">Kernel in which template is to take effect.</param>
|
||||
public PromptTemplate(string template, PromptTemplateConfig promptTemplateConfig, IKernel kernel)
|
||||
: this(template, promptTemplateConfig, kernel.PromptTemplateEngine, kernel.Log)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for PromptTemplate.
|
||||
/// </summary>
|
||||
/// <param name="template">Template.</param>
|
||||
/// <param name="promptTemplateConfig">Prompt template configuration.</param>
|
||||
/// <param name="promptTemplateEngine">Prompt template engine.</param>
|
||||
/// <param name="log">Optional logger for prompt template.</param>
|
||||
public PromptTemplate(
|
||||
string template,
|
||||
PromptTemplateConfig promptTemplateConfig,
|
||||
IPromptTemplateEngine promptTemplateEngine,
|
||||
ILogger? log = null)
|
||||
{
|
||||
this._template = template;
|
||||
this._templateEngine = promptTemplateEngine;
|
||||
this._promptConfig = promptTemplateConfig;
|
||||
if (log != null) { this._log = log; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the list of parameters used by the function, using JSON settings and template variables.
|
||||
/// TODO: consider caching results - though cache invalidation will add extra complexity
|
||||
/// </summary>
|
||||
/// <returns>List of parameters</returns>
|
||||
public IList<ParameterView> GetParameters()
|
||||
{
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Parameters from config.json
|
||||
List<ParameterView> result = new();
|
||||
foreach (PromptTemplateConfig.InputParameter? p in this._promptConfig.Input.Parameters)
|
||||
{
|
||||
if (p == null) { continue; }
|
||||
|
||||
result.Add(new ParameterView
|
||||
{
|
||||
Name = p.Name,
|
||||
Description = p.Description,
|
||||
DefaultValue = p.DefaultValue
|
||||
});
|
||||
|
||||
seen.Add(p.Name);
|
||||
}
|
||||
|
||||
// Parameters from the template
|
||||
List<VarBlock> listFromTemplate = this._templateEngine.ExtractBlocks(this._template)
|
||||
.Where(x => x.Type == BlockTypes.Variable)
|
||||
.Select(x => (VarBlock)x)
|
||||
.Where(x => x != null)
|
||||
.ToList();
|
||||
|
||||
foreach (VarBlock x in listFromTemplate)
|
||||
{
|
||||
if (seen.Contains(x.Name)) { continue; }
|
||||
|
||||
result.Add(new ParameterView { Name = x.Name });
|
||||
seen.Add(x.Name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render the template using the information in the context
|
||||
/// </summary>
|
||||
/// <param name="executionContext">Kernel execution context helpers</param>
|
||||
/// <returns>Prompt rendered to string</returns>
|
||||
public async Task<string> RenderAsync(SKContext executionContext)
|
||||
{
|
||||
return await this._templateEngine.RenderAsync(this._template, executionContext);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
public static final class Builder extends PromptTemplate.Builder {
|
||||
@Override
|
||||
public PromptTemplate build(String promptTemplate, PromptTemplateConfig config) {
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.orchestration;
|
||||
|
||||
import com.microsoft.semantickernel.ai.AIException;
|
||||
import com.microsoft.semantickernel.builders.SKBuilders;
|
||||
import com.microsoft.semantickernel.coreskills.SkillImporter;
|
||||
import com.microsoft.semantickernel.skilldefinition.DefaultSkillCollection;
|
||||
import com.microsoft.semantickernel.skilldefinition.FunctionCollection;
|
||||
import com.microsoft.semantickernel.skilldefinition.FunctionNotFound;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class NativeSKFunctionTest {
|
||||
|
||||
@Test
|
||||
public void singleStringIsBoundToInput() {
|
||||
class Test {
|
||||
@DefineSKFunction
|
||||
public String doSomething(String anInput) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Test skill = Mockito.spy(new Test());
|
||||
FunctionCollection skills =
|
||||
SkillImporter.importSkill(skill, "test", DefaultSkillCollection::new);
|
||||
Mono<?> ignore = skills.getFunction("doSomething").invokeAsync("foo");
|
||||
Mockito.verify(skill, Mockito.times(1)).doSomething("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void annotatedWithSKFunctionInputAttributeIsBoundToInput() {
|
||||
class With2Inputs {
|
||||
@DefineSKFunction
|
||||
public String doSomething(
|
||||
@SKFunctionInputAttribute String anInput,
|
||||
@SKFunctionParameters(name = "secondInput") String secondInput) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
With2Inputs skill = Mockito.spy(new With2Inputs());
|
||||
FunctionCollection skills =
|
||||
SkillImporter.importSkill(skill, "test", DefaultSkillCollection::new);
|
||||
ContextVariables variables = SKBuilders.variables().build();
|
||||
variables = variables.writableClone().appendToVariable("input", "foo");
|
||||
variables = variables.writableClone().appendToVariable("secondInput", "ignore");
|
||||
skills.getFunction("doSomething").invokeWithCustomInputAsync(variables, null, null).block();
|
||||
Mockito.verify(skill, Mockito.times(1)).doSomething("foo", "ignore");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void annotatedWithNonInputIsBound() {
|
||||
class With2Inputs {
|
||||
@DefineSKFunction
|
||||
public String doSomething(
|
||||
@SKFunctionParameters(name = "anInput") String anInput,
|
||||
@SKFunctionParameters(name = "secondInput") String secondInput) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
With2Inputs skill = Mockito.spy(new With2Inputs());
|
||||
FunctionCollection skills =
|
||||
SkillImporter.importSkill(skill, "test", DefaultSkillCollection::new);
|
||||
ContextVariables variables = SKBuilders.variables().build();
|
||||
variables = variables.writableClone().appendToVariable("anInput", "foo");
|
||||
variables = variables.writableClone().appendToVariable("secondInput", "ignore");
|
||||
skills.getFunction("doSomething").invokeWithCustomInputAsync(variables, null, null).block();
|
||||
Mockito.verify(skill, Mockito.times(1)).doSomething("foo", "ignore");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonExistantVariableCreatesError() {
|
||||
class DoesNotExist {
|
||||
@DefineSKFunction
|
||||
public String doSomething(@SKFunctionParameters(name = "doesNotExist") String anInput) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
DoesNotExist skill = Mockito.spy(new DoesNotExist());
|
||||
FunctionCollection skills =
|
||||
SkillImporter.importSkill(skill, "test", DefaultSkillCollection::new);
|
||||
ContextVariables variables =
|
||||
SKBuilders.variables().build().writableClone().appendToVariable("input", "foo");
|
||||
Assertions.assertThrows(
|
||||
AIException.class,
|
||||
() -> {
|
||||
skills.getFunction("doSomething")
|
||||
.invokeWithCustomInputAsync(variables, null, null)
|
||||
.block();
|
||||
});
|
||||
Mockito.verify(skill, Mockito.never()).doSomething("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noAnnotationCreatesError() {
|
||||
class DoesNotExist {
|
||||
public String doSomething(String anInput) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
DoesNotExist skill = Mockito.spy(new DoesNotExist());
|
||||
Assertions.assertThrows(
|
||||
FunctionNotFound.class,
|
||||
() -> {
|
||||
FunctionCollection skills =
|
||||
SkillImporter.importSkill(skill, "test", DefaultSkillCollection::new);
|
||||
skills.getFunction("doSomething").invokeAsync("foo");
|
||||
});
|
||||
Mockito.verify(skill, Mockito.never()).doSomething("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contextIsBound() {
|
||||
class WithContext {
|
||||
@DefineSKFunction
|
||||
public String doSomething(SKContext context) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
WithContext skill = Mockito.spy(new WithContext());
|
||||
FunctionCollection skills =
|
||||
SkillImporter.importSkill(skill, "test", DefaultSkillCollection::new);
|
||||
Mono<?> ignore = skills.getFunction("doSomething").invokeAsync("foo");
|
||||
Mockito.verify(skill, Mockito.only()).doSomething(Mockito.notNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withMonoReturn() {
|
||||
class WithMono {
|
||||
@DefineSKFunction
|
||||
public Mono<String> doSomething() {
|
||||
return Mono.just("A-RESULT");
|
||||
}
|
||||
}
|
||||
|
||||
WithMono skill = Mockito.spy(new WithMono());
|
||||
FunctionCollection skills =
|
||||
SkillImporter.importSkill(skill, "test", DefaultSkillCollection::new);
|
||||
SKContext<?> result = skills.getFunction("doSomething").invokeAsync("foo").block();
|
||||
Assertions.assertEquals("A-RESULT", result.getResult());
|
||||
}
|
||||
}
|
||||
+2
-5
@@ -2,6 +2,7 @@
|
||||
package com.microsoft.semantickernel.syntaxexamples.skills;
|
||||
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -10,11 +11,7 @@ public class SearchEngineSkill {
|
||||
|
||||
@DefineSKFunction(description = "Append the day variable", name = "search")
|
||||
public Mono<String> search(
|
||||
@SKFunctionParameters(
|
||||
description = "Text to search",
|
||||
name = "input",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
@SKFunctionInputAttribute @SKFunctionParameters(description = "Text to search")
|
||||
String input) {
|
||||
return Mono.just("Gran Torre Santiago is the tallest building in South America");
|
||||
}
|
||||
|
||||
+4
-16
@@ -2,6 +2,7 @@
|
||||
package com.microsoft.semantickernel.syntaxexamples.skills;
|
||||
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -12,29 +13,16 @@ public class StaticTextSkill {
|
||||
|
||||
@DefineSKFunction(description = "Change all string chars to uppercase.", name = "Uppercase")
|
||||
public static Mono<String> uppercase(
|
||||
@SKFunctionParameters(
|
||||
description = "Text to uppercase",
|
||||
name = "input",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
@SKFunctionInputAttribute @SKFunctionParameters(description = "Text to uppercase")
|
||||
String text) {
|
||||
return Mono.just(text.toUpperCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@DefineSKFunction(description = "Append the day variable", name = "appendDay")
|
||||
public Mono<String> appendDay(
|
||||
@SKFunctionParameters(
|
||||
description = "Text to append to",
|
||||
name = "input",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
@SKFunctionInputAttribute @SKFunctionParameters(description = "Text to append to")
|
||||
String input,
|
||||
@SKFunctionParameters(
|
||||
description = "Current day",
|
||||
name = "day",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
String day) {
|
||||
@SKFunctionParameters(description = "Current day", name = "day") String day) {
|
||||
return Mono.just(input + day);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-15
@@ -2,6 +2,7 @@
|
||||
package com.microsoft.semantickernel.syntaxexamples.skills;
|
||||
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -28,33 +29,21 @@ public class TextSkill {
|
||||
|
||||
@DefineSKFunction(description = "Change all string chars to uppercase.", name = "Uppercase")
|
||||
public Mono<String> uppercase(
|
||||
@SKFunctionParameters(
|
||||
description = "Text to uppercase",
|
||||
name = "input",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
@SKFunctionInputAttribute @SKFunctionParameters(description = "Text to uppercase")
|
||||
String text) {
|
||||
return Mono.just(text.toUpperCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@DefineSKFunction(description = "Remove spaces to the left of a string.", name = "LStrip")
|
||||
public Mono<String> lStrip(
|
||||
@SKFunctionParameters(
|
||||
description = "Text to edit",
|
||||
name = "input",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
@SKFunctionInputAttribute @SKFunctionParameters(description = "Text to edit")
|
||||
String text) {
|
||||
return Mono.just(text.replaceAll("^ +", ""));
|
||||
}
|
||||
|
||||
@DefineSKFunction(description = "Remove spaces to the right of a string.", name = "RStrip")
|
||||
public Mono<String> rStrip(
|
||||
@SKFunctionParameters(
|
||||
description = "Text to edit",
|
||||
name = "input",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
@SKFunctionInputAttribute @SKFunctionParameters(description = "Text to edit")
|
||||
String text) {
|
||||
return Mono.just(text.replaceAll(" +$", ""));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user