mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
Java: 03-SimpleRag impl to point of invoking Azure OpenAI API (#4484)
03-SimpleRag impl to point of invoking Azure OpenAI API.
Code unwraps {{function}} handlebars template and creates a
List<FunctionDefinition> and adds it to the ChatCompletionOptions.
---------
Co-authored-by: John Oliver <[email protected]>
This commit is contained in:
co-authored by
John Oliver
parent
283572722d
commit
c6b85add4c
@@ -12,13 +12,27 @@
|
||||
|
||||
<artifactId>aiservices-openai</artifactId>
|
||||
<name>OpenAI Services</name>
|
||||
<description>HuggingFace services for Semantic Kernel</description>
|
||||
<description>OpenAI services for Semantic Kernel</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.semantic-kernel</groupId>
|
||||
<artifactId>semantickernel-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-ai-openai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
+303
-35
@@ -4,16 +4,21 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.stream.XMLEventReader;
|
||||
import javax.xml.stream.XMLInputFactory;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.events.Attribute;
|
||||
import javax.xml.stream.events.StartElement;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -22,7 +27,10 @@ import org.slf4j.LoggerFactory;
|
||||
import com.azure.ai.openai.OpenAIAsyncClient;
|
||||
import com.azure.ai.openai.models.ChatChoice;
|
||||
import com.azure.ai.openai.models.ChatCompletions;
|
||||
import com.azure.ai.openai.models.ChatCompletionsFunctionToolCall;
|
||||
import com.azure.ai.openai.models.ChatCompletionsFunctionToolDefinition;
|
||||
import com.azure.ai.openai.models.ChatCompletionsOptions;
|
||||
import com.azure.ai.openai.models.ChatCompletionsToolCall;
|
||||
import com.azure.ai.openai.models.ChatRequestAssistantMessage;
|
||||
import com.azure.ai.openai.models.ChatRequestMessage;
|
||||
import com.azure.ai.openai.models.ChatRequestSystemMessage;
|
||||
@@ -30,6 +38,12 @@ import com.azure.ai.openai.models.ChatRequestToolMessage;
|
||||
import com.azure.ai.openai.models.ChatRequestUserMessage;
|
||||
import com.azure.ai.openai.models.ChatResponseMessage;
|
||||
import com.azure.ai.openai.models.ChatRole;
|
||||
import com.azure.ai.openai.models.FunctionCall;
|
||||
import com.azure.ai.openai.models.FunctionDefinition;
|
||||
import com.azure.core.credential.TokenCredential;
|
||||
import com.azure.core.util.BinaryData;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.chatcompletion.AuthorRole;
|
||||
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
|
||||
@@ -65,7 +79,8 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|
||||
@Nullable PromptExecutionSettings promptExecutionSettings, @Nullable Kernel kernel) {
|
||||
|
||||
List<ChatRequestMessage> chatRequestMessages = getChatRequestMessages(chatHistory);
|
||||
return internalChatMessageContentsAsync(chatRequestMessages, promptExecutionSettings);
|
||||
List<FunctionDefinition> functions = Collections.emptyList();
|
||||
return internalChatMessageContentsAsync(chatRequestMessages, functions, promptExecutionSettings);
|
||||
|
||||
}
|
||||
|
||||
@@ -74,7 +89,8 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|
||||
PromptExecutionSettings promptExecutionSettings, Kernel kernel)
|
||||
{
|
||||
List<ChatRequestMessage> chatRequestMessages = getChatRequestMessages(chatHistory);
|
||||
return internalStreamingChatMessageContentsAsync(chatRequestMessages, promptExecutionSettings);
|
||||
List<FunctionDefinition> functions = Collections.emptyList();
|
||||
return internalStreamingChatMessageContentsAsync(chatRequestMessages, functions, promptExecutionSettings);
|
||||
|
||||
}
|
||||
|
||||
@@ -82,44 +98,151 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|
||||
public Mono<List<ChatMessageContent>> getChatMessageContentsAsync(String prompt,
|
||||
PromptExecutionSettings promptExecutionSettings, Kernel kernel) {
|
||||
List<ChatRequestMessage> chatRequestMessages = getChatRequestMessages(prompt);
|
||||
return internalChatMessageContentsAsync(chatRequestMessages, promptExecutionSettings);
|
||||
List<FunctionDefinition> functions = getFunctionDefinitions(prompt);
|
||||
return internalChatMessageContentsAsync(chatRequestMessages, functions, promptExecutionSettings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<StreamingChatMessageContent> getStreamingChatMessageContentsAsync(String prompt,
|
||||
PromptExecutionSettings promptExecutionSettings, Kernel kernel) {
|
||||
List<ChatRequestMessage> chatRequestMessages = getChatRequestMessages(prompt);
|
||||
return internalStreamingChatMessageContentsAsync(chatRequestMessages, promptExecutionSettings);
|
||||
List<FunctionDefinition> functions = getFunctionDefinitions(prompt);
|
||||
return internalStreamingChatMessageContentsAsync(chatRequestMessages, functions, promptExecutionSettings);
|
||||
}
|
||||
|
||||
private interface ContentBuffer<T> {
|
||||
void append(T content);
|
||||
StreamingChatMessageContent toStreamingChatMessageContent();
|
||||
}
|
||||
|
||||
private static class AssistantContentBuffer implements ContentBuffer<String> {
|
||||
private final StringBuilder sb = new StringBuilder();
|
||||
|
||||
@Override
|
||||
public void append(String content) {
|
||||
sb.append(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamingChatMessageContent toStreamingChatMessageContent() {
|
||||
return new StreamingChatMessageContent(AuthorRole.ASSISTANT, sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ToolContentBuffer implements ContentBuffer<FunctionCall> {
|
||||
|
||||
private String name = null;
|
||||
private List<String> arguments = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void append(FunctionCall functionCall) {
|
||||
String fnName = functionCall.getName();
|
||||
String fnArguments = functionCall.getArguments();
|
||||
if (this.name == null && fnName != null && !fnName.isEmpty()) {
|
||||
this.name = fnName;
|
||||
} else if (fnArguments != null && !fnArguments.isEmpty()) {
|
||||
this.arguments.add(fnArguments);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamingChatMessageContent toStreamingChatMessageContent() {
|
||||
|
||||
StringBuilder sb = new StringBuilder("{\"type\":\"function\", \"function\": ");
|
||||
sb.append(String.format("{\"name\":\"%s\", \"parameters\": ", name));
|
||||
boolean first = true;
|
||||
// when concatentated, args should be valid json
|
||||
for(String argument : arguments) {
|
||||
sb.append(argument);
|
||||
}
|
||||
// close off function, and type
|
||||
sb.append("}}");
|
||||
assert isBalanced(sb.toString());
|
||||
return new StreamingChatMessageContent(AuthorRole.TOOL, sb.toString());
|
||||
}
|
||||
|
||||
private boolean isBalanced(String str) {
|
||||
int openParens = 0;
|
||||
int closeParens = 0;
|
||||
boolean inString = false;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
if (!inString && c == '(') {
|
||||
openParens++;
|
||||
} else if (!inString && c == ')') {
|
||||
closeParens++;
|
||||
} else if (c == '"') {
|
||||
inString = !inString;
|
||||
}
|
||||
}
|
||||
return openParens == closeParens;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ChatResponseCollector {
|
||||
|
||||
private final Map<AuthorRole,ContentBuffer<?>> roleToContent = new HashMap<>();
|
||||
|
||||
private ContentBuffer<?> collectorFor(AuthorRole role) {
|
||||
return roleToContent.computeIfAbsent(role, k -> {
|
||||
if (k == AuthorRole.TOOL) return new ToolContentBuffer();
|
||||
return new AssistantContentBuffer();
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> void append(AuthorRole role, T content) {
|
||||
ContentBuffer<T> contentBuffer = (ContentBuffer<T>)collectorFor(role);
|
||||
contentBuffer.append(content);
|
||||
}
|
||||
|
||||
private StreamingChatMessageContent toStreamingChatMessageContent() {
|
||||
assert roleToContent.size() == 1;
|
||||
ContentBuffer<?> contentBuffer = roleToContent.values().iterator().next();
|
||||
return contentBuffer.toStreamingChatMessageContent();
|
||||
}
|
||||
}
|
||||
|
||||
private Flux<StreamingChatMessageContent> internalStreamingChatMessageContentsAsync(
|
||||
List<ChatRequestMessage> chatRequestMessages,
|
||||
List<FunctionDefinition> functions,
|
||||
PromptExecutionSettings promptExecutionSettings)
|
||||
{
|
||||
ChatCompletionsOptions options = getCompletionsOptions(this, chatRequestMessages, promptExecutionSettings);
|
||||
ChatCompletionsOptions options = getCompletionsOptions(this, chatRequestMessages, functions, promptExecutionSettings);
|
||||
return client
|
||||
.getChatCompletionsStream(getModelId(), options)
|
||||
.filter(chatCompletion -> chatCompletion != null)
|
||||
.map(ChatCompletions::getChoices)
|
||||
.filter(choices -> choices != null && !choices.isEmpty())
|
||||
.collect(StringBuffer::new, (sb, choices) -> {
|
||||
.reduceWith(ChatResponseCollector::new, (accumulator, choices) -> {
|
||||
choices.stream()
|
||||
.map(choice -> choice.getDelta())
|
||||
.filter(delta -> delta != null && delta.getContent() != null)
|
||||
.forEach(delta -> sb.append(delta.getContent()));
|
||||
.filter(chatResponseMessage -> chatResponseMessage != null)
|
||||
.forEach(chatResponseMessage -> {
|
||||
if (chatResponseMessage.getContent() != null) {
|
||||
accumulator.append(AuthorRole.ASSISTANT, chatResponseMessage.getContent());
|
||||
} else if (chatResponseMessage.getToolCalls() != null) {
|
||||
List<ChatCompletionsToolCall> toolCalls = chatResponseMessage.getToolCalls();
|
||||
toolCalls.forEach(toolCall -> {
|
||||
if (toolCall instanceof ChatCompletionsFunctionToolCall) {
|
||||
FunctionCall functionCall = ((ChatCompletionsFunctionToolCall)toolCall).getFunction();
|
||||
accumulator.append(AuthorRole.TOOL, functionCall);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return accumulator;
|
||||
})
|
||||
.map(sb -> {
|
||||
return sb.toString();
|
||||
})
|
||||
.map(content -> new StreamingChatMessageContent(AuthorRole.ASSISTANT, content))
|
||||
.map(ChatResponseCollector::toStreamingChatMessageContent)
|
||||
.flux();
|
||||
}
|
||||
|
||||
private Mono<List<ChatMessageContent>> internalChatMessageContentsAsync(
|
||||
List<ChatRequestMessage> chatRequestMessages,
|
||||
List<FunctionDefinition> functions,
|
||||
PromptExecutionSettings promptExecutionSettings)
|
||||
{
|
||||
ChatCompletionsOptions options = getCompletionsOptions(this, chatRequestMessages, promptExecutionSettings);
|
||||
ChatCompletionsOptions options = getCompletionsOptions(this, chatRequestMessages, functions, promptExecutionSettings);
|
||||
return client
|
||||
.getChatCompletions(getModelId(), options)
|
||||
.flatMap(chatCompletions -> Mono.just(toChatMessageContents(chatCompletions)));
|
||||
@@ -128,11 +251,21 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|
||||
private static ChatCompletionsOptions getCompletionsOptions(
|
||||
ChatCompletionService chatCompletionService,
|
||||
List<ChatRequestMessage> chatRequestMessages,
|
||||
List<FunctionDefinition> functions,
|
||||
PromptExecutionSettings promptExecutionSettings)
|
||||
{
|
||||
ChatCompletionsOptions options = new ChatCompletionsOptions(chatRequestMessages)
|
||||
.setModel(chatCompletionService.getModelId());
|
||||
|
||||
if (functions != null && !functions.isEmpty()) {
|
||||
// options.setFunctions(functions);
|
||||
options.setTools(
|
||||
functions.stream()
|
||||
.map(ChatCompletionsFunctionToolDefinition::new)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
if (promptExecutionSettings == null) {
|
||||
return options;
|
||||
}
|
||||
@@ -170,6 +303,9 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|
||||
|
||||
private static List<ChatRequestMessage> getChatRequestMessages(String prompt)
|
||||
{
|
||||
// TODO: XML parsing should be done as a chain of XMLEvent handlers.
|
||||
// If one handler does not recognize the element, it should pass it to the next handler.
|
||||
// In this way, we can avoid parsing the whole prompt twice and easily extend the parsing logic.
|
||||
List<ChatRequestMessage> messages = new ArrayList<>();
|
||||
try (InputStream is = new ByteArrayInputStream(prompt.getBytes())) {
|
||||
XMLInputFactory factory = XMLInputFactory.newFactory();
|
||||
@@ -177,11 +313,11 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|
||||
while(reader.hasNext()) {
|
||||
XMLEvent event = reader.nextEvent();
|
||||
if (event.isStartElement()) {
|
||||
String name = event.asStartElement().getName().getLocalPart();
|
||||
String name = getElementName(event);
|
||||
if (name.equals("message")) {
|
||||
String role = event.asStartElement().getAttributeByName(javax.xml.namespace.QName.valueOf("role")).getValue();
|
||||
String role = getAttributeValue(event, "role");
|
||||
String content = reader.getElementText();
|
||||
messages.add(getChatRequestMessage(AuthorRole.valueOf(role.toUpperCase()), content));
|
||||
messages.add(getChatRequestMessage(role, content));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,24 +327,156 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|
||||
return messages;
|
||||
}
|
||||
|
||||
private static List<FunctionDefinition> getFunctionDefinitions(String prompt)
|
||||
{
|
||||
// TODO: XML parsing should be done as a chain of XMLEvent handlers. See previous remark.
|
||||
// <function pluginName=\"%s\" name=\"%s\" description=\"%s\">
|
||||
// <parameter name=\"%s\" description=\"%s\" defaultValue=\"%s\" isRequired=\"%s\" type=\"%s\"/>...
|
||||
// </function>
|
||||
List<FunctionDefinition> functionDefinitions = new ArrayList<>();
|
||||
try (InputStream is = new ByteArrayInputStream(prompt.getBytes())) {
|
||||
XMLInputFactory factory = XMLInputFactory.newFactory();
|
||||
XMLEventReader reader = factory.createXMLEventReader(is);
|
||||
FunctionDefinition functionDefinition = null;
|
||||
Map<String, String> parameters = new HashMap<>();
|
||||
List<String> requiredParmeters = new ArrayList<>();
|
||||
while(reader.hasNext()) {
|
||||
XMLEvent event = reader.nextEvent();
|
||||
if (event.isStartElement()) {
|
||||
String elementName = getElementName(event);
|
||||
if (elementName.equals("function")) {
|
||||
assert functionDefinition == null;
|
||||
assert parameters.isEmpty();
|
||||
assert requiredParmeters.isEmpty();
|
||||
String pluginName = getAttributeValue(event, "pluginName");
|
||||
String name = getAttributeValue(event, "name");
|
||||
String description = getAttributeValue(event, "description");
|
||||
// name has to match '^[a-zA-Z0-9_-]{1,64}$'
|
||||
functionDefinition = new FunctionDefinition(pluginName + "-" +name)
|
||||
.setDescription(description);
|
||||
} else if (elementName.equals("parameter")) {
|
||||
String name = getAttributeValue(event, "name");
|
||||
String type = getAttributeValue(event, "type").toLowerCase(Locale.ROOT);
|
||||
String description = getAttributeValue(event, "description");
|
||||
parameters.put(name, String.format("{\"type\": \"%s\", \"description\": \"%s\"}", "string", description));
|
||||
|
||||
String isRequired = getAttributeValue(event, "isRequired");
|
||||
if (Boolean.parseBoolean(isRequired)) {
|
||||
requiredParmeters.add(name);
|
||||
}
|
||||
}
|
||||
} else if (event.isEndElement()) {
|
||||
String elementName = getElementName(event);
|
||||
if (elementName.equals("function")) {
|
||||
// Example JSON Schema:
|
||||
// {
|
||||
// "type": "function",
|
||||
// "function": {
|
||||
// "name": "get_current_weather",
|
||||
// "description": "Get the current weather in a given location",
|
||||
// "parameters": {
|
||||
// "type": "object",
|
||||
// "properties": {
|
||||
// "location": {
|
||||
// "type": "string",
|
||||
// "description": "The city and state, e.g. San Francisco, CA",
|
||||
// },
|
||||
// "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
|
||||
// },
|
||||
// "required": ["location"],
|
||||
// },
|
||||
// },
|
||||
//}
|
||||
assert functionDefinition != null;
|
||||
if (!parameters.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder("{\"type\": \"object\", \"properties\": {");
|
||||
parameters.forEach((name, value) -> {
|
||||
// make "param": {"type": "string", "description": "desc"},
|
||||
sb.append(String.format("\"%s\": %s,", name, value));
|
||||
});
|
||||
// strip off trailing comma and close the properties object
|
||||
sb.replace(sb.length() - 1, sb.length(), "}");
|
||||
if (!requiredParmeters.isEmpty()) {
|
||||
sb.append(", \"required\": [");
|
||||
requiredParmeters.forEach(name -> {
|
||||
sb.append(String.format("\"%s\",", name));
|
||||
});
|
||||
// strip off trailing comma and close the required array
|
||||
sb.replace(sb.length() - 1, sb.length(), "]");
|
||||
}
|
||||
// close the object
|
||||
sb.append("}");
|
||||
//System.out.println(sb.toString());
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JsonNode jsonNode = objectMapper.readTree(sb.toString());
|
||||
BinaryData binaryData = BinaryData.fromObject(jsonNode);
|
||||
functionDefinition.setParameters(binaryData);
|
||||
}
|
||||
functionDefinitions.add(functionDefinition);
|
||||
functionDefinition = null;
|
||||
parameters.clear();
|
||||
requiredParmeters.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException | XMLStreamException | IllegalArgumentException e) {
|
||||
LOGGER.error("Error parsing prompt", e);
|
||||
}
|
||||
return functionDefinitions;
|
||||
}
|
||||
|
||||
private static String getElementName(XMLEvent xmlEvent) {
|
||||
if (xmlEvent.isStartElement()) {
|
||||
return xmlEvent.asStartElement().getName().getLocalPart();
|
||||
} else if (xmlEvent.isEndElement()) {
|
||||
return xmlEvent.asEndElement().getName().getLocalPart();
|
||||
}
|
||||
// TODO: programmer's error - log at debug
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String getAttributeValue(XMLEvent xmlEvent, String attributeName)
|
||||
{
|
||||
if (xmlEvent.isStartElement()) {
|
||||
StartElement element = xmlEvent.asStartElement();
|
||||
Attribute attribute = element.getAttributeByName(QName.valueOf(attributeName));
|
||||
return attribute != null ? attribute.getValue() : "";
|
||||
}
|
||||
// TODO: programmer's error - log at debug
|
||||
return "";
|
||||
}
|
||||
|
||||
private static ChatRequestMessage getChatRequestMessage(
|
||||
AuthorRole authorRole,
|
||||
String role,
|
||||
String content)
|
||||
{
|
||||
|
||||
if (authorRole != null) {
|
||||
switch(authorRole) {
|
||||
case ASSISTANT:
|
||||
return new ChatRequestAssistantMessage(content);
|
||||
case SYSTEM:
|
||||
return new ChatRequestSystemMessage(content);
|
||||
case USER:
|
||||
return new ChatRequestUserMessage(content);
|
||||
case TOOL:
|
||||
return new ChatRequestToolMessage(content, null);
|
||||
}
|
||||
try {
|
||||
AuthorRole authorRole = AuthorRole.valueOf(role.toUpperCase());
|
||||
return getChatRequestMessage(authorRole, content);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.debug("Unknown author role: " + role);
|
||||
return null;
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown author role: " + authorRole);
|
||||
}
|
||||
|
||||
private static ChatRequestMessage getChatRequestMessage(
|
||||
AuthorRole authorRole,
|
||||
String content) {
|
||||
|
||||
switch(authorRole) {
|
||||
case ASSISTANT:
|
||||
return new ChatRequestAssistantMessage(content);
|
||||
case SYSTEM:
|
||||
return new ChatRequestSystemMessage(content);
|
||||
case USER:
|
||||
return new ChatRequestUserMessage(content);
|
||||
case TOOL:
|
||||
return new ChatRequestToolMessage(content, null);
|
||||
default:
|
||||
LOGGER.debug("Unexpected author role: " + authorRole);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static List<ChatMessageContent> toChatMessageContents(ChatCompletions chatCompletions) {
|
||||
@@ -234,19 +502,19 @@ public class OpenAIChatCompletion implements ChatCompletionService {
|
||||
if(chatRole == ChatRole.ASSISTANT) {
|
||||
return AuthorRole.ASSISTANT;
|
||||
}
|
||||
if(chatRole == ChatRole.ASSISTANT) {
|
||||
if(chatRole == ChatRole.SYSTEM) {
|
||||
return AuthorRole.SYSTEM;
|
||||
}
|
||||
if(chatRole == ChatRole.ASSISTANT) {
|
||||
if(chatRole == ChatRole.USER) {
|
||||
return AuthorRole.USER;
|
||||
}
|
||||
if(chatRole == ChatRole.ASSISTANT) {
|
||||
}
|
||||
if(chatRole == ChatRole.TOOL) {
|
||||
return AuthorRole.TOOL;
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown chat role: " + chatRole);
|
||||
}
|
||||
|
||||
public static class Builder implements ChatCompletionService.Builder<OpenAIChatCompletion> {
|
||||
public static class Builder implements ChatCompletionService.Builder {
|
||||
private OpenAIAsyncClient client;
|
||||
private String modelId;
|
||||
|
||||
|
||||
@@ -19,6 +19,16 @@
|
||||
<groupId>com.microsoft.semantic-kernel</groupId>
|
||||
<artifactId>semantickernel-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
|
||||
package com.microsoft.semantickernel.assistants;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
|
||||
import com.microsoft.semantickernel.plugin.Plugin;
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.aiservices.AIService;
|
||||
import com.microsoft.semantickernel.templateengine.PromptTemplateEngine;
|
||||
import com.microsoft.semantickernel.orchestration.SKFunction;
|
||||
import com.microsoft.semantickernel.textcompletion.CompletionSKFunction;
|
||||
import com.microsoft.semantickernel.semanticfunctions.SemanticFunctionConfig;
|
||||
import com.microsoft.semantickernel.orchestration.ContextVariables;
|
||||
import com.microsoft.semantickernel.orchestration.SKContext;
|
||||
import com.microsoft.semantickernel.orchestration.FunctionResult;
|
||||
import com.microsoft.semantickernel.KernelResult;
|
||||
import com.microsoft.semantickernel.memory.SemanticTextMemory;
|
||||
import com.microsoft.semantickernel.KernelConfig;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class AssistantKernel extends Plugin implements Kernel {
|
||||
|
||||
public static AssistantKernel FromConfiguration(
|
||||
String configurationFile,
|
||||
@Nullable List<AIService> aiServices,
|
||||
@Nullable List<Plugin> plugins,
|
||||
@Nullable List<PromptTemplateEngine> promptTemplateEngines
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public AssistantKernel(String name, SKFunction... functions) {
|
||||
super(name, functions);
|
||||
}
|
||||
|
||||
public SKFunction getFunction(String skill, String function) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public <FunctionType extends SKFunction>FunctionType registerSemanticFunction(FunctionType func) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public CompletionSKFunction registerSemanticFunction(
|
||||
String skillName, String functionName, SemanticFunctionConfig functionConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T extends AIService> T getService(String serviceId, Class<T> clazz) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public CompletionSKFunction.Builder getSemanticFunctionBuilder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Mono<KernelResult> runAsync(boolean b, ContextVariables variables, SKFunction... pipeline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Mono<KernelResult> runAsync(ContextVariables variables, SKFunction... pipeline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Mono<KernelResult> runAsync(String s, SKFunction... pipeline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Mono<KernelResult> runAsync(SKFunction... pipeline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public SemanticTextMemory getMemory() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public PromptTemplateEngine getPromptTemplateEngine() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public KernelConfig getConfig() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<SKFunction> functions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Mono<AssistantThread> createThreadAsync() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.microsoft.semantickernel.assistants;
|
||||
|
||||
import com.microsoft.semantickernel.orchestration.SKFunction;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface AssistantThread extends SKFunction {
|
||||
|
||||
public Mono addUserMessageAsync(String message);
|
||||
|
||||
}
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.coreskills;
|
||||
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.SKBuilders;
|
||||
import com.microsoft.semantickernel.orchestration.SKContext;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute;
|
||||
import com.microsoft.semantickernel.text.TextChunker;
|
||||
import com.microsoft.semantickernel.textcompletion.CompletionKernelFunction;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/** Semantic skill that enables conversations summarization */
|
||||
public class ConversationSummarySkill {
|
||||
// The max tokens to process in a single semantic function call.
|
||||
private static final int MaxTokens = 1024;
|
||||
|
||||
private final CompletionKernelFunction summarizeConversationFunction;
|
||||
private final CompletionKernelFunction conversationActionItemsFunction;
|
||||
private final CompletionKernelFunction conversationTopicsFunction;
|
||||
private final Kernel kernel;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the ConversationSummarySkill class
|
||||
*
|
||||
* @param kernel Kernel instance
|
||||
*/
|
||||
public ConversationSummarySkill(Kernel kernel) {
|
||||
this.kernel = kernel;
|
||||
this.summarizeConversationFunction =
|
||||
SKBuilders.completionFunctions()
|
||||
.withKernel(kernel)
|
||||
.withPromptTemplate(
|
||||
SemanticFunctionConstants.SummarizeConversationDefinition)
|
||||
.withFunctionName("summarize")
|
||||
.withSkillName("ConversationSummarySkill")
|
||||
.withDescription(
|
||||
"Given a section of a conversation transcript, summarize the part"
|
||||
+ " of the conversation.")
|
||||
.withCompletionConfig(
|
||||
SKBuilders.completionConfig()
|
||||
.maxTokens(MaxTokens)
|
||||
.temperature(0.1)
|
||||
.topP(0.5)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
this.conversationActionItemsFunction =
|
||||
SKBuilders.completionFunctions()
|
||||
.withKernel(kernel)
|
||||
.withPromptTemplate(
|
||||
SemanticFunctionConstants.GetConversationActionItemsDefinition)
|
||||
.withFunctionName("ActionItems")
|
||||
.withSkillName("ConversationSummarySkill")
|
||||
.withDescription(
|
||||
"Given a section of a conversation transcript, identify action"
|
||||
+ " items.")
|
||||
.withCompletionConfig(
|
||||
SKBuilders.completionConfig()
|
||||
.maxTokens(MaxTokens)
|
||||
.temperature(0.1)
|
||||
.topP(0.5)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
this.conversationTopicsFunction =
|
||||
SKBuilders.completionFunctions()
|
||||
.withKernel(kernel)
|
||||
.withPromptTemplate(
|
||||
SemanticFunctionConstants.GetConversationTopicsDefinition)
|
||||
.withFunctionName("Topics")
|
||||
.withSkillName("ConversationSummarySkill")
|
||||
.withDescription(
|
||||
"Analyze a conversation transcript and extract key topics worth"
|
||||
+ " remembering.")
|
||||
.withCompletionConfig(
|
||||
SKBuilders.completionConfig()
|
||||
.maxTokens(MaxTokens)
|
||||
.temperature(0.1)
|
||||
.topP(0.5)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a long conversation transcript, summarize the conversation
|
||||
*
|
||||
* @param input A long conversation transcript
|
||||
* @param context The SKContext for function execution
|
||||
* @return A context containing the summarized conversation
|
||||
*/
|
||||
@DefineSKFunction(
|
||||
description = "Given a long conversation transcript, summarize the conversation.",
|
||||
name = "SummarizeConversation")
|
||||
public Mono<SKContext> summarizeConversationAsync(
|
||||
@SKFunctionInputAttribute(description = "A long conversation transcript.") String input,
|
||||
@Nullable SKContext context) {
|
||||
List<String> lines = TextChunker.splitPlainTextLines(input, MaxTokens);
|
||||
List<String> paragraphs = TextChunker.splitPlainTextParagraphs(lines, MaxTokens);
|
||||
|
||||
if (context == null) {
|
||||
context = SKBuilders.context().withSkills(kernel.getSkills()).build();
|
||||
}
|
||||
|
||||
SKContext completionContext = context.copy();
|
||||
|
||||
return this.summarizeConversationFunction.aggregatePartitionedResultsAsync(
|
||||
paragraphs, completionContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a long conversation transcript, identify action items
|
||||
*
|
||||
* @param input A long conversation transcript
|
||||
* @param context The SKContext for function execution
|
||||
* @return A context containing the action items
|
||||
*/
|
||||
@DefineSKFunction(
|
||||
description = "Given a long conversation transcript, identify action items.",
|
||||
name = "GetConversationActionItems")
|
||||
public Mono<SKContext> getConversationActionItemsAsync(
|
||||
@SKFunctionInputAttribute(description = "A long conversation transcript.") String input,
|
||||
SKContext context) {
|
||||
List<String> lines = TextChunker.splitPlainTextLines(input, MaxTokens);
|
||||
List<String> paragraphs = TextChunker.splitPlainTextParagraphs(lines, MaxTokens);
|
||||
|
||||
SKContext completionContext = context.copy();
|
||||
|
||||
return this.conversationActionItemsFunction.aggregatePartitionedResultsAsync(
|
||||
paragraphs, completionContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a long conversation transcript, identify topics
|
||||
*
|
||||
* @param input A long conversation transcript
|
||||
* @param context The SKContext for function execution
|
||||
* @return A context containing the topics
|
||||
*/
|
||||
@DefineSKFunction(
|
||||
description =
|
||||
"Given a long conversation transcript, identify topics worth remembering.",
|
||||
name = "GetConversationTopics")
|
||||
public Mono<SKContext> getConversationTopicsAsync(
|
||||
@SKFunctionInputAttribute(description = "A long conversation transcript.") String input,
|
||||
SKContext context) {
|
||||
List<String> lines = TextChunker.splitPlainTextLines(input, MaxTokens);
|
||||
List<String> paragraphs = TextChunker.splitPlainTextParagraphs(lines, MaxTokens);
|
||||
|
||||
SKContext completionContext = context.copy();
|
||||
|
||||
return this.conversationTopicsFunction.aggregatePartitionedResultsAsync(
|
||||
paragraphs, completionContext);
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
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 java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Read and write from a file.
|
||||
*
|
||||
* <p>Usage: kernel.ImportSkill("file", new FileIOSkill());
|
||||
*
|
||||
* <p>Examples:
|
||||
*
|
||||
* <pre>{@code
|
||||
* file.readAsync($path) => "hello world"
|
||||
* file.writeAsync()
|
||||
* }</pre>
|
||||
*/
|
||||
public class FileIOSkill {
|
||||
|
||||
/**
|
||||
* Read a file.
|
||||
*
|
||||
* <p>Example:
|
||||
*
|
||||
* <pre>{@code
|
||||
* file.readAsync($path) => "hello world"
|
||||
* }</pre>
|
||||
*
|
||||
* @param path Source file.
|
||||
* @return File content.
|
||||
*/
|
||||
@DefineSKFunction(description = "Read a file", name = "readAsync")
|
||||
public String readFileAsync(
|
||||
@SKFunctionInputAttribute(description = "Source file") String path,
|
||||
@SKFunctionParameters(
|
||||
name = "charset",
|
||||
description = "Character set to use to read the file",
|
||||
defaultValue = "UTF-8")
|
||||
String charset)
|
||||
throws IOException {
|
||||
Path filePath = Paths.get(path);
|
||||
byte[] fileBytes;
|
||||
fileBytes = Files.readAllBytes(filePath);
|
||||
String fileContents = new String(fileBytes, Charset.forName(charset));
|
||||
return fileContents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a file.
|
||||
*
|
||||
* <p>Example:
|
||||
*
|
||||
* <pre>{@code
|
||||
* file.writeAsync()
|
||||
* }</pre>
|
||||
*
|
||||
* @param path The destination file.
|
||||
* @param content Contains the 'content' to write
|
||||
*/
|
||||
@DefineSKFunction(description = "Write a file", name = "writeAsync")
|
||||
public void writeFileAsync(
|
||||
@SKFunctionInputAttribute(description = "Destination file") String path,
|
||||
@SKFunctionParameters(
|
||||
name = "content",
|
||||
description = "File content",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
String content,
|
||||
@SKFunctionParameters(
|
||||
name = "charset",
|
||||
description = "Character set to use to read the file",
|
||||
defaultValue = "UTF-8")
|
||||
String charset)
|
||||
throws IOException {
|
||||
Path filePath = Paths.get(path);
|
||||
Files.write(filePath, content.getBytes(charset));
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.coreskills;
|
||||
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.azure.core.http.HttpMethod;
|
||||
import com.azure.core.http.HttpRequest;
|
||||
import com.azure.core.http.HttpResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* A skill that provides HTTP functionality.
|
||||
*
|
||||
* <p>Usage:
|
||||
*
|
||||
* <p>Usage: kernel.importSkill(new HttpSkill(), "http");
|
||||
*
|
||||
* <p>Examples:
|
||||
*
|
||||
* <p>{{http.getAsync $url}}
|
||||
*
|
||||
* <p>{{http.postAsync $url}}
|
||||
*
|
||||
* <p>{{http.putAsync $url}}
|
||||
*
|
||||
* <p>{{http.deleteAsync $url}}
|
||||
*/
|
||||
public class HttpSkill {
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public HttpSkill() {
|
||||
this.httpClient = HttpClient.createDefault();
|
||||
}
|
||||
|
||||
public HttpSkill(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP GET request to the specified URI and returns body as a string.
|
||||
*
|
||||
* @param url The URI to send the request to.
|
||||
* @return The response body as a string.
|
||||
*/
|
||||
public Mono<String> getAsync(String url) {
|
||||
if (url == null || url.isEmpty()) {
|
||||
return Mono.error(new IllegalArgumentException("url cannot be `null` or empty"));
|
||||
}
|
||||
HttpRequest request = new HttpRequest(HttpMethod.GET, url);
|
||||
return httpClient.send(request).flatMap(response -> response.getBodyAsString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP POST request to the specified URI and returns body as a string.
|
||||
*
|
||||
* @param url The URI to send the request to.
|
||||
* @param body The response body as a string.
|
||||
* @return
|
||||
*/
|
||||
public Mono<String> postAsync(String url, String body) {
|
||||
if (url == null || url.isEmpty()) {
|
||||
return Mono.error(new IllegalArgumentException("url cannot be `null` or empty"));
|
||||
}
|
||||
HttpRequest request = new HttpRequest(HttpMethod.POST, url);
|
||||
request.setBody(body);
|
||||
return httpClient.send(request).flatMap(response -> response.getBodyAsString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP PUT request to the specified URI and returns body as a string.
|
||||
*
|
||||
* @param url The URI to send the request to.
|
||||
* @param body The response body as a string.
|
||||
* @return
|
||||
*/
|
||||
public Mono<String> putAsync(String url, String body) {
|
||||
if (url == null || url.isEmpty()) {
|
||||
return Mono.error(new IllegalArgumentException("url cannot be `null` or empty"));
|
||||
}
|
||||
HttpRequest request = new HttpRequest(HttpMethod.PUT, url);
|
||||
request.setBody(body);
|
||||
return httpClient.send(request).flatMap(response -> response.getBodyAsString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP DELETE request to the specified URI and returns body as a string.
|
||||
*
|
||||
* @param url The URI to send the request to.
|
||||
* @return The response body as a string.
|
||||
*/
|
||||
public Mono<HttpResponse> deleteAsync(String url) {
|
||||
if (url == null || url.isEmpty()) {
|
||||
return Mono.error(new IllegalArgumentException("url cannot be `null` or empty"));
|
||||
}
|
||||
HttpRequest request = new HttpRequest(HttpMethod.DELETE, url);
|
||||
return httpClient.send(request).flatMap(response -> Mono.just(response));
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
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 java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* MathSkill provides a set of functions to make Math calculations.
|
||||
*
|
||||
* <ul>
|
||||
* <li><code>input</code> is the first number in the operation.
|
||||
* <li><code>amount</code> is the second number in the operation.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Usage:
|
||||
*
|
||||
* <blockquote>
|
||||
*
|
||||
* <pre>
|
||||
* kernel.ImportSkill("math", new MathSkill());
|
||||
*
|
||||
* {{math.add}} => Returns the sum of input and amount
|
||||
* {{math.subtract}} => Returns the differentce of input from amount
|
||||
* </pre>
|
||||
*
|
||||
* </blockquote>
|
||||
*
|
||||
* <p>This skill uses BigDecimal to perform the number conversion and the calculations.
|
||||
*/
|
||||
public class MathSkill {
|
||||
|
||||
/**
|
||||
* Returns the addition result of input and amount values provided.
|
||||
*
|
||||
* <p>This skill attempts to convert the numbers to BigDecimal and then add them.
|
||||
*
|
||||
* <p>If a conversion error occurs, the skill will throw an exception.
|
||||
*
|
||||
* @param input Initial value as string to add the specified amount
|
||||
* @param amount The amount to be added to input
|
||||
* @return The resulting sum as a String.
|
||||
*/
|
||||
@DefineSKFunction(description = "Adds amount to a value.", name = "add")
|
||||
public String add(
|
||||
@SKFunctionInputAttribute(description = "The value to add to.") String input,
|
||||
@SKFunctionParameters(name = "amount", description = "The amount to be added to value.")
|
||||
String amount) {
|
||||
|
||||
BigDecimal bValue = new BigDecimal(input);
|
||||
|
||||
return bValue.add(new BigDecimal(amount)).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subtraction result of input and amount values provided.
|
||||
*
|
||||
* <p>This skill attempts to convert the numbers to BigDecimal and then subtract them.
|
||||
*
|
||||
* <p>If a conversion error occurs, the skill will throw an exception.
|
||||
*
|
||||
* @param input Initial value as string to subtract the specified amount
|
||||
* @param amount The amount to be subtracted from input
|
||||
* @return The resulting difference as a String.
|
||||
*/
|
||||
@DefineSKFunction(description = "Subtracts amount from value.", name = "Subtract")
|
||||
public String subtract(
|
||||
@SKFunctionInputAttribute(description = "The value to subtract from.") String input,
|
||||
@SKFunctionParameters(
|
||||
name = "amount",
|
||||
description = "The amount to be subtracted from value.")
|
||||
String amount) {
|
||||
|
||||
BigDecimal bValue = new BigDecimal(input);
|
||||
|
||||
return bValue.subtract(new BigDecimal(amount)).toString();
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.coreskills;
|
||||
|
||||
class SemanticFunctionConstants {
|
||||
public static final String SummarizeConversationDefinition =
|
||||
"BEGIN CONTENT TO SUMMARIZE:\n"
|
||||
+ "{{$INPUT}}\n"
|
||||
+ "\n"
|
||||
+ "END CONTENT TO SUMMARIZE.\n"
|
||||
+ "\n"
|
||||
+ "Summarize the conversation in 'CONTENT TO SUMMARIZE', identifying main points of"
|
||||
+ " discussion and any conclusions that were reached.\n"
|
||||
+ "Do not incorporate other general knowledge.\n"
|
||||
+ "Summary is in plain text, in complete sentences, with no markup or tags.\n"
|
||||
+ "\n"
|
||||
+ "BEGIN SUMMARY:";
|
||||
|
||||
public static final String GetConversationActionItemsDefinition =
|
||||
"You are an action item extractor. You will be given chat history and need to make note"
|
||||
+ " of action items mentioned in the chat.\n"
|
||||
+ "Extract action items from the content if there are any. If there are no action,"
|
||||
+ " return nothing. If a single field is missing, use an empty string.\n"
|
||||
+ "Return the action items in json.\n"
|
||||
+ "\n"
|
||||
+ "Possible statuses for action items are: Open, Closed, In Progress.\n"
|
||||
+ "\n"
|
||||
+ "EXAMPLE INPUT WITH ACTION ITEMS:\n"
|
||||
+ "\n"
|
||||
+ "John Doe said: \"I will record a demo for the new feature by Friday\"\n"
|
||||
+ "I said: \"Great, thanks John. We may not use all of it but it's good to get it"
|
||||
+ " out there.\"\n"
|
||||
+ "\n"
|
||||
+ "EXAMPLE OUTPUT:\n"
|
||||
+ "{\n"
|
||||
+ " \"actionItems\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"owner\": \"John Doe\",\n"
|
||||
+ " \"actionItem\": \"Record a demo for the new feature\",\n"
|
||||
+ " \"dueDate\": \"Friday\",\n"
|
||||
+ " \"status\": \"Open\",\n"
|
||||
+ " \"notes\": \"\"\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}\n"
|
||||
+ "\n"
|
||||
+ "EXAMPLE INPUT WITHOUT ACTION ITEMS:\n"
|
||||
+ "\n"
|
||||
+ "John Doe said: \"Hey I'm going to the store, do you need anything?\"\n"
|
||||
+ "I said: \"No thanks, I'm good.\"\n"
|
||||
+ "\n"
|
||||
+ "EXAMPLE OUTPUT:\n"
|
||||
+ "{\n"
|
||||
+ " \"action_items\": []\n"
|
||||
+ "}\n"
|
||||
+ "\n"
|
||||
+ "CONTENT STARTS HERE.\n"
|
||||
+ "\n"
|
||||
+ "{{$INPUT}}\n"
|
||||
+ "\n"
|
||||
+ "CONTENT STOPS HERE.\n"
|
||||
+ "\n"
|
||||
+ "OUTPUT:";
|
||||
|
||||
public static final String GetConversationTopicsDefinition =
|
||||
"Analyze the following extract taken from a conversation transcript and extract key"
|
||||
+ " topics.\n"
|
||||
+ "- Topics only worth remembering.\n"
|
||||
+ "- Be brief. Short phrases.\n"
|
||||
+ "- Can use broken English.\n"
|
||||
+ "- Conciseness is very important.\n"
|
||||
+ "- Topics can include names of memories you want to recall.\n"
|
||||
+ "- NO LONG SENTENCES. SHORT PHRASES.\n"
|
||||
+ "- Return in JSON\n"
|
||||
+ "[Input]\n"
|
||||
+ "My name is Macbeth. I used to be King of Scotland, but I died. My wife's name is"
|
||||
+ " Lady Macbeth and we were married for 15 years. We had no children. Our beloved"
|
||||
+ " dog Toby McDuff was a famous hunter of rats in the forest.\n"
|
||||
+ "My tragic story was immortalized by Shakespeare in a play.\n"
|
||||
+ "[Output]\n"
|
||||
+ "{\n"
|
||||
+ " \"topics\": [\n"
|
||||
+ " \"Macbeth\",\n"
|
||||
+ " \"King of Scotland\",\n"
|
||||
+ " \"Lady Macbeth\",\n"
|
||||
+ " \"Dog\",\n"
|
||||
+ " \"Toby McDuff\",\n"
|
||||
+ " \"Shakespeare\",\n"
|
||||
+ " \"Play\",\n"
|
||||
+ " \"Tragedy\"\n"
|
||||
+ " ]\n"
|
||||
+ "}\n"
|
||||
+ "+++++\n"
|
||||
+ "[Input]\n"
|
||||
+ "{{$INPUT}}\n"
|
||||
+ "[Output]";
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.coreskills;
|
||||
|
||||
import com.microsoft.semantickernel.SKBuilders;
|
||||
import com.microsoft.semantickernel.memory.MemoryException;
|
||||
import com.microsoft.semantickernel.memory.MemoryQueryResult;
|
||||
import com.microsoft.semantickernel.memory.MemoryRecordMetadata;
|
||||
import com.microsoft.semantickernel.memory.SemanticTextMemory;
|
||||
import com.microsoft.semantickernel.orchestration.SKContext;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nonnull;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* TextMemorySkill provides a skill to save or recall information from the long or short term.
|
||||
* Usage: {@code kernel.ImportSkill(new TextMemorySkill(), "memory");} Example: {@code
|
||||
* {{memory.recall "what is the capital of France?" }} => "Paris"}
|
||||
*/
|
||||
public class TextMemorySkill {
|
||||
|
||||
/** Name of the context variable used to specify which memory collection to use. */
|
||||
public static final String COLLECTION_PARAM = "collection";
|
||||
|
||||
/** Name of the context variable used to specify the information to store. */
|
||||
public static final String INFO_PARAM = "info";
|
||||
|
||||
/** Name of the context variable used to specify the input to the skill. */
|
||||
public static final String INPUT_PARAM = "input";
|
||||
|
||||
/** Name of the context variable used to specify memory search relevance score. */
|
||||
public static final String RELEVANCE_PARAM = "relevance";
|
||||
|
||||
/**
|
||||
* Name of the context variable used to specify a unique key associated with stored information.
|
||||
*/
|
||||
public static final String KEY_PARAM = "key";
|
||||
|
||||
/** Name of the context variable used to specify the number of memories to recall */
|
||||
public static final String LIMIT_PARAM = "limit";
|
||||
|
||||
/** Default value for the collection name. */
|
||||
public static final String DEFAULT_COLLECTION = "generic";
|
||||
|
||||
/** Default value for the relevance score. */
|
||||
public static final String DEFAULT_RELEVANCE = "0.75";
|
||||
|
||||
/** Default value for the limit. */
|
||||
public static final String DEFAULT_LIMIT = "1";
|
||||
|
||||
/**
|
||||
* Key-based lookup for a specific memory.
|
||||
*
|
||||
* @param collection The collection containing the memory to retrieve
|
||||
* @param key The key associated with the memory to retrieve
|
||||
* @param context The context containing the semantic memory
|
||||
* @return The memory associated with the key
|
||||
*/
|
||||
@DefineSKFunction(description = "Key-based lookup for a specific memory", name = "Retrieve")
|
||||
public Mono<String> retrieveAsync(
|
||||
@SKFunctionParameters(
|
||||
name = COLLECTION_PARAM,
|
||||
description =
|
||||
"Memories collection associated with the memory to retrieve",
|
||||
defaultValue = DEFAULT_COLLECTION)
|
||||
String collection,
|
||||
@SKFunctionParameters(
|
||||
name = KEY_PARAM,
|
||||
description = "The key associated with the memory to retrieve")
|
||||
String key,
|
||||
SKContext context) {
|
||||
|
||||
SemanticTextMemory memory = context.getSemanticMemory();
|
||||
|
||||
if (memory == null) {
|
||||
return Mono.error(new RuntimeException("Memory not present"));
|
||||
}
|
||||
|
||||
return memory.getAsync(collection, key, false)
|
||||
.map(it -> it.getMetadata().getText())
|
||||
.defaultIfEmpty("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Save information to semantic memory.
|
||||
*
|
||||
* @param info The information to save
|
||||
* @param collection Memories collection associated with the information to save
|
||||
* @param key The key associated with the information to save
|
||||
* @param context The context containing the semantic memory
|
||||
* @return The key associated with the saved information
|
||||
*/
|
||||
@DefineSKFunction(description = "Save information to semantic memory", name = "Save")
|
||||
public Mono<SKContext> saveAsync(
|
||||
@SKFunctionParameters(
|
||||
name = INFO_PARAM,
|
||||
description = "The information to save",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
String info,
|
||||
@SKFunctionParameters(
|
||||
name = COLLECTION_PARAM,
|
||||
description =
|
||||
"Memories collection associated with the information to save",
|
||||
defaultValue = DEFAULT_COLLECTION,
|
||||
type = String.class)
|
||||
String collection,
|
||||
@SKFunctionParameters(
|
||||
name = KEY_PARAM,
|
||||
description = "The key associated with the information to save",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
String key,
|
||||
SKContext context) {
|
||||
|
||||
SemanticTextMemory memory = context.getSemanticMemory();
|
||||
if (memory == null) {
|
||||
return Mono.error(
|
||||
new MemoryException(MemoryException.ErrorCodes.UNKNOWN, "Memory not present"));
|
||||
}
|
||||
|
||||
return memory.saveInformationAsync(collection, info, key, null, null)
|
||||
.map(
|
||||
it -> {
|
||||
context.setVariable(TextMemorySkill.KEY_PARAM, it);
|
||||
return SKBuilders.context()
|
||||
.withVariables(context.getVariables())
|
||||
.withSkills(context.getSkills())
|
||||
.withMemory(context.getSemanticMemory())
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Semantic search and return up to N memories related to the input text
|
||||
*
|
||||
* @param input The input text to find related memories for
|
||||
* @param collection Memories collection to search
|
||||
* @param relevance The relevance score, from 0.0 to 1.0, where 1.0 means perfect match
|
||||
* @param limit The maximum number of relevant memories to recall
|
||||
* @param context Contains the memory to search
|
||||
* @return The list of memories related to the input text
|
||||
*/
|
||||
@DefineSKFunction(
|
||||
description = "Semantic search and return up to N memories related to the input text",
|
||||
name = "Recall")
|
||||
public Mono<List<String>> recallAsync(
|
||||
@SKFunctionParameters(
|
||||
name = INPUT_PARAM,
|
||||
description = "The information to recall",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
@Nonnull
|
||||
String input,
|
||||
@SKFunctionParameters(
|
||||
name = COLLECTION_PARAM,
|
||||
description =
|
||||
"Memories collection associated with the information to recall",
|
||||
defaultValue = DEFAULT_COLLECTION,
|
||||
type = String.class)
|
||||
String collection,
|
||||
@SKFunctionParameters(
|
||||
name = RELEVANCE_PARAM,
|
||||
description =
|
||||
"The relevance score, from 0.0 to 1.0, where 1.0 means perfect"
|
||||
+ " match",
|
||||
defaultValue = DEFAULT_RELEVANCE,
|
||||
type = Double.class)
|
||||
double relevance,
|
||||
@SKFunctionParameters(
|
||||
name = LIMIT_PARAM,
|
||||
description = "The maximum number of relevant memories to recall",
|
||||
defaultValue = DEFAULT_LIMIT,
|
||||
type = Integer.class)
|
||||
int limit,
|
||||
@Nonnull SKContext context) {
|
||||
|
||||
SemanticTextMemory memory = context.getSemanticMemory();
|
||||
if (memory == null) {
|
||||
return Mono.error(
|
||||
new MemoryException(MemoryException.ErrorCodes.UNKNOWN, "Memory not present"));
|
||||
}
|
||||
|
||||
// TODO: 1.0 fix cast
|
||||
// Validate parameters
|
||||
if (collection == null || collection.trim().isEmpty()) {
|
||||
if (context.getVariables().get(TextMemorySkill.COLLECTION_PARAM) == null) {
|
||||
collection = DEFAULT_COLLECTION;
|
||||
} else {
|
||||
collection =
|
||||
context.getVariables().get(TextMemorySkill.COLLECTION_PARAM).toString();
|
||||
}
|
||||
}
|
||||
final String _collection = collection;
|
||||
final float _relevance = (float) Math.min(1.0, Math.max(0.0, relevance));
|
||||
final int _limit = Math.max(1, limit);
|
||||
|
||||
// context.Log.LogTrace("Searching memories in collection '{0}', relevance '{1}'",
|
||||
// collection, relevance);
|
||||
|
||||
// Search memory
|
||||
return memory.searchAsync(_collection, input, _limit, _relevance, false)
|
||||
.flatMap(
|
||||
results -> {
|
||||
List<String> memories =
|
||||
results.stream()
|
||||
.map(MemoryQueryResult::getMetadata)
|
||||
.map(MemoryRecordMetadata::getText)
|
||||
.collect(Collectors.toList());
|
||||
return Mono.just(memories);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove information from semantic memory
|
||||
*
|
||||
* @param collection Memories collection associated with the information to remove
|
||||
* @param key The key associated with the information to remove
|
||||
* @param context Contains the memory to remove
|
||||
* @return The reactive stream
|
||||
*/
|
||||
@DefineSKFunction(description = "Remove information from semantic memory", name = "Remove")
|
||||
public Mono<Void> removeAsync(
|
||||
@SKFunctionParameters(
|
||||
name = COLLECTION_PARAM,
|
||||
description =
|
||||
"Memories collection associated with the information to remove",
|
||||
defaultValue = DEFAULT_COLLECTION,
|
||||
type = String.class)
|
||||
String collection,
|
||||
@SKFunctionParameters(
|
||||
name = KEY_PARAM,
|
||||
description = "The key associated with the information to remove",
|
||||
defaultValue = "",
|
||||
type = String.class)
|
||||
String key,
|
||||
SKContext context) {
|
||||
SemanticTextMemory memory = context.getSemanticMemory();
|
||||
if (memory == null) {
|
||||
return Mono.error(
|
||||
new MemoryException(MemoryException.ErrorCodes.UNKNOWN, "Memory not present"));
|
||||
}
|
||||
|
||||
return memory.removeAsync(collection, key);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.coreskills;
|
||||
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction;
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute;
|
||||
import java.util.Locale;
|
||||
|
||||
/** TextSkill provides a set of functions to manipulate strings. */
|
||||
public class TextSkill {
|
||||
|
||||
@DefineSKFunction(description = "Change all string chars to uppercase.", name = "Uppercase")
|
||||
public String uppercase(
|
||||
@SKFunctionInputAttribute(description = "Text to uppercase") String text) {
|
||||
return text.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
@DefineSKFunction(description = "Remove spaces to the left of a string.", name = "LStrip")
|
||||
public String lStrip(@SKFunctionInputAttribute(description = "Text to edit") String text) {
|
||||
return text.replaceAll("^ +", "");
|
||||
}
|
||||
|
||||
@DefineSKFunction(description = "Remove spaces to the right of a string.", name = "RStrip")
|
||||
public String rStrip(@SKFunctionInputAttribute(description = "Text to edit") String text) {
|
||||
return text.replaceAll(" +$", "");
|
||||
}
|
||||
|
||||
@DefineSKFunction(
|
||||
description = "Remove spaces to the left and right of a string",
|
||||
name = "Strip")
|
||||
public String strip(@SKFunctionInputAttribute(description = "Text to edit") String input) {
|
||||
return input.trim();
|
||||
}
|
||||
|
||||
@DefineSKFunction(description = "Change all string chars to lowercase", name = "lowercase")
|
||||
public String lowercase(
|
||||
@SKFunctionInputAttribute(description = "Text to lowercase") String input) {
|
||||
return input.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
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 com.microsoft.semantickernel.util.LocaleParser;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.TextStyle;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Description: TimeSkill provides a set of functions to get the current time and date.
|
||||
*
|
||||
* <p>Usage: kernel.importSkill(new TimeSkill(), "time");
|
||||
*
|
||||
* <p>Examples:
|
||||
*
|
||||
* <p>{{time.date}} => Sunday, 12 January, 2031
|
||||
*
|
||||
* <p>{{time.today}} => Sunday, 12 January, 2031
|
||||
*
|
||||
* <p>{{time.now}} => Sunday, January 12, 2031 9:15 PM
|
||||
*
|
||||
* <p>{{time.utcNow}} => Sunday, January 13, 2031 5:15 AM
|
||||
*
|
||||
* <p>{{time.time}} => 09:15:07 PM
|
||||
*
|
||||
* <p>{{time.year}} => 2031
|
||||
*
|
||||
* <p>{{time.month}} => January
|
||||
*
|
||||
* <p>{{time.monthNumber}} => 01
|
||||
*
|
||||
* <p>{{time.day}} => 12
|
||||
*
|
||||
* <p>{{time.dayOfWeek}} => Sunday
|
||||
*
|
||||
* <p>{{time.hour}} => 9 PM
|
||||
*
|
||||
* <p>{{time.hourNumber}} => 21
|
||||
*
|
||||
* <p>{{time.days_ago $days}} => Sunday, 7 May, 2023
|
||||
*
|
||||
* <p>{{time.last_matching_day $dayName}} => Sunday, 7 May, 2023
|
||||
*
|
||||
* <p>{{time.minute}} => 15
|
||||
*
|
||||
* <p>{{time.second}} => 7
|
||||
*
|
||||
* <p>{{time.timeZoneOffset}} => -08:00
|
||||
*
|
||||
* <p>{{time.timeZoneName}} => Pacific Time
|
||||
*/
|
||||
public class TimeSkill {
|
||||
|
||||
public static final String DAY_MONTH_DAY_YEAR = "EEEE, MMMM d, yyyy";
|
||||
|
||||
/**
|
||||
* Get the current date and time for the system default timezone.
|
||||
*
|
||||
* @return a ZonedDateTime object with the current date and time.
|
||||
*/
|
||||
public ZonedDateTime now() {
|
||||
return ZonedDateTime.now(ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current date.
|
||||
*
|
||||
* <p>Example: {{time.date}} => Sunday, January 12, 2025
|
||||
*
|
||||
* @return The current date.
|
||||
*/
|
||||
@DefineSKFunction(name = "date", description = "Get the current date")
|
||||
public String date(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
// Example: Sunday, 12 January, 2025
|
||||
return DateTimeFormatter.ofPattern(DAY_MONTH_DAY_YEAR)
|
||||
.withLocale(parseLocale(locale))
|
||||
.format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current time.
|
||||
*
|
||||
* <p>Example: {{time.time}} => 9:15:00 AM
|
||||
*
|
||||
* @return The current time.
|
||||
*/
|
||||
@DefineSKFunction(name = "time", description = "Get the current time")
|
||||
public String time(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
// Example: 09:15:07 PM
|
||||
return DateTimeFormatter.ofPattern("hh:mm:ss a")
|
||||
.withLocale(parseLocale(locale))
|
||||
.format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current UTC date and time.
|
||||
*
|
||||
* <p>Example: {{time.utcNow}} => Sunday, January 13, 2025 5:15 AM
|
||||
*
|
||||
* @return The current UTC date and time.
|
||||
*/
|
||||
@DefineSKFunction(name = "utcNow", description = "Get the current UTC date and time")
|
||||
public String utcNow(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return DateTimeFormatter.ofPattern(DAY_MONTH_DAY_YEAR + " h:mm a")
|
||||
.withLocale(parseLocale(locale))
|
||||
.format(now().withZoneSameInstant(ZoneOffset.UTC));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current date (alias for date() method).
|
||||
*
|
||||
* <p>Example: {{time.today}} => Sunday, January 12, 2025
|
||||
*
|
||||
* @return The current date.
|
||||
*/
|
||||
@DefineSKFunction(name = "today", description = "Get the current date")
|
||||
public String today(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return date(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current date and time in the local time zone.
|
||||
*
|
||||
* <p>Example: {{time.now}} => Sunday, January 12, 2025 9:15 AM
|
||||
*
|
||||
* @return The current date and time in the local time zone.
|
||||
*/
|
||||
@DefineSKFunction(
|
||||
name = "now",
|
||||
description = "Get the current date and time in the local time zone")
|
||||
public String now(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return DateTimeFormatter.ofPattern(DAY_MONTH_DAY_YEAR + " h:mm a")
|
||||
.withLocale(parseLocale(locale))
|
||||
.format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current year.
|
||||
*
|
||||
* <p>Example: {{time.year}} => 2025
|
||||
*
|
||||
* @return The current year.
|
||||
*/
|
||||
@DefineSKFunction(name = "year", description = "Get the current year")
|
||||
public String year(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return DateTimeFormatter.ofPattern("yyyy").withLocale(parseLocale(locale)).format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current month name.
|
||||
*
|
||||
* <p>Example: {{time.month}} => January
|
||||
*
|
||||
* @return The current month name.
|
||||
*/
|
||||
@DefineSKFunction(name = "month", description = "Get the current month name")
|
||||
public String month(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return DateTimeFormatter.ofPattern("MMMM").withLocale(parseLocale(locale)).format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current month number.
|
||||
*
|
||||
* <p>Example: {{time.monthNumber}} => 01
|
||||
*
|
||||
* @return The current month number.
|
||||
*/
|
||||
@DefineSKFunction(name = "monthNumber", description = "Get the current month number")
|
||||
public String monthNumber(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return DateTimeFormatter.ofPattern("MM").withLocale(parseLocale(locale)).format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current day of the month.
|
||||
*
|
||||
* <p>Example: {{time.day}} => 12
|
||||
*
|
||||
* @return The current day of the month.
|
||||
*/
|
||||
@DefineSKFunction(name = "day", description = "Get the current day of the month")
|
||||
public String day(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return DateTimeFormatter.ofPattern("d").withLocale(parseLocale(locale)).format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current day of the week.
|
||||
*
|
||||
* <p>Example: {{time.dayOfWeek}} => Sunday
|
||||
*
|
||||
* @return The current day of the week.
|
||||
*/
|
||||
@DefineSKFunction(name = "dayOfWeek", description = "Get the current day of the week")
|
||||
public String dayOfWeek(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return DateTimeFormatter.ofPattern("EEEE").withLocale(parseLocale(locale)).format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current clock hour.
|
||||
*
|
||||
* <p>Example: {{time.hour}} => 9 AM
|
||||
*
|
||||
* @return The current clock hour.
|
||||
*/
|
||||
@DefineSKFunction(name = "hour", description = "Get the current clock hour")
|
||||
public String hour(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return DateTimeFormatter.ofPattern("h a").withLocale(parseLocale(locale)).format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current clock 24-hour number.
|
||||
*
|
||||
* <p>Example: {{time.hourNumber}} => 09
|
||||
*
|
||||
* @return The current clock 24-hour number.
|
||||
*/
|
||||
@DefineSKFunction(name = "hourNumber", description = "Get the current clock 24-hour number")
|
||||
public String hourNumber(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return DateTimeFormatter.ofPattern("HH").withLocale(parseLocale(locale)).format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date of offset from today by a provided number of days
|
||||
*
|
||||
* <p>Example: SKContext context = SKBuilders.context().build(); context.setVariable("input",
|
||||
* "3"); {{time.daysAgo $input}} => Saturday, January 11, 2031
|
||||
*
|
||||
* @param days Number of days to subtract from the current day
|
||||
* @return The date of offset from today by a provided number of days
|
||||
*/
|
||||
@DefineSKFunction(
|
||||
name = "daysAgo",
|
||||
description = "Get the date of offset from today by a provided number of days")
|
||||
public String daysAgo(
|
||||
@SKFunctionInputAttribute(description = "Number of days to offset from today.")
|
||||
String days,
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
int offsetDays = Integer.parseInt(days);
|
||||
return DateTimeFormatter.ofPattern(DAY_MONTH_DAY_YEAR)
|
||||
.withLocale(parseLocale(locale))
|
||||
.format(now().minusDays(offsetDays));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date of the last day matching the supplied week day name
|
||||
*
|
||||
* <p>Example: {{time.dateMatchingLastDayName "Monday"}} => Monday, January 6, 2031
|
||||
*
|
||||
* @param dayName Name of the day to match
|
||||
* @return The date of the last day matching the supplied week day name
|
||||
*/
|
||||
@DefineSKFunction(
|
||||
name = "dateMatchingLastDayName",
|
||||
description = "Get the date of the last day matching the supplied week day name")
|
||||
public String dateMatchingLastDayName(
|
||||
@SKFunctionInputAttribute(description = "Week name day.") String dayName,
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
ZonedDateTime currentDate = now();
|
||||
for (int i = 1; i <= 7; i++) {
|
||||
currentDate = currentDate.minusDays(1);
|
||||
String currentDayName =
|
||||
currentDate.getDayOfWeek().getDisplayName(TextStyle.FULL, parseLocale(locale));
|
||||
|
||||
if (currentDayName.equalsIgnoreCase(dayName)) {
|
||||
return DateTimeFormatter.ofPattern(DAY_MONTH_DAY_YEAR)
|
||||
.withLocale(parseLocale(locale))
|
||||
.format(currentDate);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("dayName is not recognized");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minutes on the current hour.
|
||||
*
|
||||
* <p>Example: {{time.minute}} => 15
|
||||
*
|
||||
* @return The minutes on the current hour.
|
||||
*/
|
||||
@DefineSKFunction(name = "minute", description = "Get the minutes on the current hour")
|
||||
public String minute(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return DateTimeFormatter.ofPattern("mm").withLocale(parseLocale(locale)).format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the seconds on the current minute.
|
||||
*
|
||||
* <p>Example: {{time.second}} => 00
|
||||
*
|
||||
* @return The seconds on the current minute.
|
||||
*/
|
||||
@DefineSKFunction(name = "second", description = "Get the seconds on the current minute")
|
||||
public String second(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return DateTimeFormatter.ofPattern("ss").withLocale(parseLocale(locale)).format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local time zone offset from UTC.
|
||||
*
|
||||
* <p>Example: {{time.timeZoneOffset}} => +03:00
|
||||
*
|
||||
* @return The local time zone offset from UTC.
|
||||
*/
|
||||
@DefineSKFunction(
|
||||
name = "timeZoneOffset",
|
||||
description = "Get the local time zone offset from UTC")
|
||||
public String timeZoneOffset(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
return DateTimeFormatter.ofPattern("XXX").withLocale(parseLocale(locale)).format(now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local time zone name.
|
||||
*
|
||||
* <p>Example: {{time.timeZoneName}} => Pacific Time
|
||||
*
|
||||
* @return The local time zone name.
|
||||
*/
|
||||
@DefineSKFunction(name = "timeZoneName", description = "Get the local time zone name")
|
||||
public String timeZoneName(
|
||||
@SKFunctionParameters(
|
||||
name = "locale",
|
||||
description = "Locale to use when formatting the date",
|
||||
required = false)
|
||||
String locale) {
|
||||
ZoneId zoneId = ZoneId.systemDefault();
|
||||
return zoneId.getDisplayName(TextStyle.FULL, parseLocale(locale));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the locale string into a Locale object.
|
||||
*
|
||||
* <p>By default, it parses using the LocaleParser utility class.
|
||||
*
|
||||
* @param locale string
|
||||
* @return a locale object
|
||||
*/
|
||||
protected Locale parseLocale(String locale) {
|
||||
return LocaleParser.parseLocale(locale);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.coreskills;
|
||||
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction;
|
||||
import java.time.Duration;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/** WaitSkill provides a set of functions to wait before making the rest of operations. */
|
||||
public class WaitSkill {
|
||||
|
||||
/**
|
||||
* Wait for a certain number of seconds.
|
||||
*
|
||||
* <p>Examples:
|
||||
*
|
||||
* <p>SKContext context = SKBuilders.context().build();
|
||||
*
|
||||
* <p>context.setVariable("input","10");
|
||||
*
|
||||
* <p>{{wait.seconds $input}}
|
||||
*
|
||||
* @param seconds The number of seconds to wait as a string.
|
||||
* @return A Mono that completes after the specified delay.
|
||||
* @throws IllegalArgumentException If the input is not a valid number.
|
||||
*/
|
||||
@DefineSKFunction(name = "seconds", description = "Wait a given amount of seconds")
|
||||
public Mono<Void> wait(String seconds) {
|
||||
try {
|
||||
double sec = Math.max(Double.parseDouble(seconds), 0);
|
||||
long milliseconds = (long) (sec * 1000);
|
||||
return Mono.delay(Duration.ofMillis(milliseconds)).then();
|
||||
} catch (NumberFormatException e) {
|
||||
return Mono.error(new IllegalArgumentException("seconds text must be a number", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.microsoft.semantickernel.plugins.web.bing;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.azure.core.http.HttpClient;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class BingConnector {
|
||||
|
||||
public BingConnector(String apiKey, HttpClient httpClient) {
|
||||
}
|
||||
|
||||
public Mono<List<String>> searchAsync(String query, int count, int offset) {
|
||||
return Mono.error(() -> new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
}
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Split text in chunks, attempting to leave meaning intact. For plain text, split looking at new
|
||||
* lines first, then periods, and so on. For markdown, split looking at punctuation first, and so
|
||||
* on.
|
||||
*/
|
||||
public class TextChunker {
|
||||
private static final String s_spaceChar = " ";
|
||||
private static final List<Pattern> s_plaintextSplitOptions =
|
||||
Stream.of("[\n\r]", "\\.", "[\\?\\!]", ";", ":", ",", "[\\)\\]\\}]", " ", "\\-", null)
|
||||
.map(it -> it == null ? null : Pattern.compile(it, Pattern.MULTILINE))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
private static final List<Pattern> s_markdownSplitOptions =
|
||||
Stream.of("\\.", "[\\?\\!]", ";", ":", ",", "[\\)\\]\\}]", " ", "\\-", "[\n\r]", null)
|
||||
.map(it -> it == null ? null : Pattern.compile(it, Pattern.MULTILINE))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
/**
|
||||
* Split plain text into lines
|
||||
*
|
||||
* @param text Text to split
|
||||
* @param maxTokensPerLine Maximum number of tokens per line
|
||||
* @return List of lines
|
||||
*/
|
||||
public static List<String> splitPlainTextLines(String text, int maxTokensPerLine) {
|
||||
return internalSplitLines(text, maxTokensPerLine, true, s_plaintextSplitOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split markdown text into lines
|
||||
*
|
||||
* @param text Text to split
|
||||
* @param maxTokensPerLine Maximum number of tokens per line
|
||||
* @return List of lines
|
||||
*/
|
||||
public static List<String> splitMarkDownLines(String text, int maxTokensPerLine) {
|
||||
List<String> result = new ArrayList<>();
|
||||
internalSplitLines(text, maxTokensPerLine, true, s_markdownSplitOptions);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split plain text into paragraphs
|
||||
*
|
||||
* @param lines Lines of text
|
||||
* @param maxTokensPerParagraph Maximum number of tokens per paragraph.
|
||||
* @return List of paragraphs
|
||||
*/
|
||||
public static List<String> splitPlainTextParagraphs(
|
||||
List<String> lines, int maxTokensPerParagraph) {
|
||||
return internalSplitTextParagraphs(
|
||||
lines,
|
||||
maxTokensPerParagraph,
|
||||
(text) ->
|
||||
internalSplitLines(
|
||||
text, maxTokensPerParagraph, false, s_plaintextSplitOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Split markdown text into paragraphs
|
||||
*
|
||||
* @param lines Lines of text
|
||||
* @param maxTokensPerParagraph Maximum number of tokens per paragraph
|
||||
* @return List of paragraphs
|
||||
*/
|
||||
public static List<String> splitMarkdownParagraphs(
|
||||
List<String> lines, int maxTokensPerParagraph) {
|
||||
return internalSplitTextParagraphs(
|
||||
lines,
|
||||
maxTokensPerParagraph,
|
||||
(text) ->
|
||||
internalSplitLines(
|
||||
text, maxTokensPerParagraph, false, s_markdownSplitOptions));
|
||||
}
|
||||
|
||||
private static List<String> internalSplitTextParagraphs(
|
||||
List<String> lines,
|
||||
int maxTokensPerParagraph,
|
||||
Function<String, List<String>> longLinesSplitter) {
|
||||
if (lines.size() == 0) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// Split long lines first
|
||||
ArrayList<String> truncatedLines = new ArrayList<>();
|
||||
for (String line : lines) {
|
||||
truncatedLines.addAll(longLinesSplitter.apply(line));
|
||||
}
|
||||
|
||||
lines = truncatedLines;
|
||||
|
||||
// Group lines in paragraphs
|
||||
List<String> paragraphs = new ArrayList<>();
|
||||
StringBuilder currentParagraph = new StringBuilder();
|
||||
|
||||
for (String line : lines) {
|
||||
// "+1" to account for the "new line" added by AppendLine()
|
||||
if (currentParagraph.length() > 0
|
||||
&& TokenCount(currentParagraph.length()) + TokenCount(line.length()) + 1
|
||||
>= maxTokensPerParagraph) {
|
||||
paragraphs.add(currentParagraph.toString().trim());
|
||||
currentParagraph = new StringBuilder();
|
||||
}
|
||||
|
||||
currentParagraph.append(line).append("\n");
|
||||
}
|
||||
|
||||
if (currentParagraph.length() > 0) {
|
||||
paragraphs.add(currentParagraph.toString().trim());
|
||||
}
|
||||
|
||||
// distribute text more evenly in the last paragraphs when the last paragraph is too short.
|
||||
if (paragraphs.size() > 1) {
|
||||
String lastParagraph = paragraphs.get(paragraphs.size() - 1);
|
||||
String secondLastParagraph = paragraphs.get(paragraphs.size() - 2);
|
||||
|
||||
if (TokenCount(lastParagraph.length()) < maxTokensPerParagraph / 4) {
|
||||
List<String> lastParagraphTokens =
|
||||
Arrays.stream(lastParagraph.split(s_spaceChar))
|
||||
.filter(it -> it.length() != 0)
|
||||
.collect(Collectors.toList());
|
||||
List<String> secondLastParagraphTokens =
|
||||
Arrays.stream(secondLastParagraph.split(s_spaceChar))
|
||||
.filter(it -> it.length() != 0)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int lastParagraphTokensCount = lastParagraphTokens.size();
|
||||
int secondLastParagraphTokensCount = secondLastParagraphTokens.size();
|
||||
|
||||
if (lastParagraphTokensCount + secondLastParagraphTokensCount
|
||||
<= maxTokensPerParagraph) {
|
||||
StringBuilder newSecondLastParagraph = new StringBuilder();
|
||||
for (int i = 0; i < secondLastParagraphTokensCount; i++) {
|
||||
if (newSecondLastParagraph.length() != 0) {
|
||||
newSecondLastParagraph.append(' ');
|
||||
}
|
||||
|
||||
newSecondLastParagraph.append(secondLastParagraphTokens.get(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < lastParagraphTokensCount; i++) {
|
||||
if (newSecondLastParagraph.length() != 0) {
|
||||
newSecondLastParagraph.append(' ');
|
||||
}
|
||||
|
||||
newSecondLastParagraph.append(lastParagraphTokens.get(i));
|
||||
}
|
||||
|
||||
paragraphs.set(paragraphs.size() - 2, newSecondLastParagraph.toString().trim());
|
||||
paragraphs.remove(paragraphs.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paragraphs;
|
||||
}
|
||||
|
||||
private static class SplitString {
|
||||
public final boolean inputWasSplit;
|
||||
public final List<String> result;
|
||||
|
||||
private SplitString(boolean inputWasSplit, List<String> result) {
|
||||
this.inputWasSplit = inputWasSplit;
|
||||
this.result = result;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> internalSplitLines(
|
||||
String text, int maxTokensPerLine, boolean trim, List<Pattern> splitOptions) {
|
||||
text = text.replaceAll("\\r?\\n|\\r", "\n");
|
||||
|
||||
SplitString result =
|
||||
split(text, maxTokensPerLine, Collections.singletonList(splitOptions.get(0)), trim);
|
||||
if (result.inputWasSplit) {
|
||||
for (int i = 1; i < splitOptions.size(); i++) {
|
||||
result =
|
||||
split(
|
||||
result.result,
|
||||
maxTokensPerLine,
|
||||
Collections.singletonList(splitOptions.get(i)),
|
||||
trim);
|
||||
|
||||
if (!result.inputWasSplit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.result;
|
||||
}
|
||||
|
||||
private static SplitString split(
|
||||
List<String> input, int maxTokens, List<Pattern> separators, boolean trim) {
|
||||
List<String> result = new ArrayList<>();
|
||||
boolean modified = false;
|
||||
for (String str : input) {
|
||||
SplitString r = split(str, maxTokens, separators, trim);
|
||||
result.addAll(r.result);
|
||||
|
||||
modified |= r.inputWasSplit;
|
||||
}
|
||||
return new SplitString(modified, result);
|
||||
}
|
||||
|
||||
private static int indexOfAny(List<Pattern> separators, String input) {
|
||||
return separators.stream()
|
||||
.map(
|
||||
it -> {
|
||||
Matcher matcher = it.matcher(input);
|
||||
if (matcher.find()) {
|
||||
return matcher.start();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
})
|
||||
.filter(it -> it != -1)
|
||||
.min(Integer::compareTo)
|
||||
.orElse(-1);
|
||||
}
|
||||
|
||||
private static SplitString split(
|
||||
String input, int maxTokens, List<Pattern> separators, boolean trim) {
|
||||
// Debug.Assert(inputString is null || input.SequenceEqual(inputString.AsSpan()));
|
||||
boolean inputWasSplit;
|
||||
if (TokenCount(input.length()) > maxTokens) {
|
||||
|
||||
inputWasSplit = true;
|
||||
|
||||
int half = input.length() / 2;
|
||||
int cutPoint = -1;
|
||||
|
||||
if (separators.size() == 1 && separators.get(0) == null) {
|
||||
cutPoint = half;
|
||||
} else if (input.length() > 2) {
|
||||
int pos = 0;
|
||||
while (true) {
|
||||
int index = indexOfAny(separators, input.substring(pos, input.length() - 1));
|
||||
if (index < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
index += pos;
|
||||
|
||||
if (Math.abs(half - index) < Math.abs(half - cutPoint)) {
|
||||
cutPoint = index + 1;
|
||||
}
|
||||
|
||||
pos = index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> result = Collections.singletonList(input);
|
||||
|
||||
if (cutPoint > 0) {
|
||||
String firstHalf = input.substring(0, cutPoint);
|
||||
String secondHalf = input.substring(cutPoint);
|
||||
if (trim) {
|
||||
firstHalf = firstHalf.trim();
|
||||
secondHalf = secondHalf.trim();
|
||||
}
|
||||
|
||||
// Recursion
|
||||
SplitString first = split(firstHalf, maxTokens, separators, trim);
|
||||
SplitString second = split(secondHalf, maxTokens, separators, trim);
|
||||
|
||||
result =
|
||||
Stream.concat(first.result.stream(), second.result.stream())
|
||||
.collect(Collectors.toList());
|
||||
inputWasSplit = first.inputWasSplit || second.inputWasSplit;
|
||||
}
|
||||
|
||||
return new SplitString(inputWasSplit, result);
|
||||
}
|
||||
|
||||
return new SplitString(false, Collections.singletonList(input));
|
||||
}
|
||||
|
||||
private static int TokenCount(int inputLength) {
|
||||
// TODO: partitioning methods should be configurable to allow for different tokenization
|
||||
// strategies
|
||||
// depending on the model to be called. For now, we use an extremely rough estimate.
|
||||
return inputLength / 4;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.util;
|
||||
|
||||
import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Locale parser to support Java 8 and Java 9+ due to JEP 252.
|
||||
*
|
||||
* <p>Supports parsing strings containing languageCode_countryCode and languageCode-countryCode
|
||||
* styles.
|
||||
*/
|
||||
public final class LocaleParser {
|
||||
|
||||
private LocaleParser() {}
|
||||
|
||||
/**
|
||||
* Parses the locale. If the input is "en-US", it will return a Locale based on the language
|
||||
* tag. If the input is "en_US", it will return a Locale based on the language and country code.
|
||||
* Otherwise, it will return a Locale based on the input.
|
||||
*
|
||||
* <p>'en' 'US' used as example above.
|
||||
*
|
||||
* @param locale the locale in String text.
|
||||
* @return
|
||||
*/
|
||||
public static final Locale parseLocale(String locale) {
|
||||
Locale parsedLocale = null;
|
||||
|
||||
if (locale == null
|
||||
|| "".equals(locale.trim())
|
||||
|| SKFunctionParameters.NO_DEFAULT_VALUE.equals(locale)) {
|
||||
return Locale.getDefault();
|
||||
} else if (locale.indexOf("-") > -1) {
|
||||
parsedLocale = Locale.forLanguageTag(locale);
|
||||
} else if (locale.indexOf("_") > -1) {
|
||||
String[] parts = locale.split("_");
|
||||
parsedLocale = new Locale(parts[0], parts[1]);
|
||||
} else {
|
||||
parsedLocale = new Locale(locale);
|
||||
}
|
||||
|
||||
return parsedLocale;
|
||||
}
|
||||
}
|
||||
+141
-2
@@ -1,18 +1,157 @@
|
||||
package com.microsoft.semantickernel.plugins.web.bing;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.azure.core.http.HttpHeader;
|
||||
import com.azure.core.http.HttpHeaderName;
|
||||
import com.azure.core.http.HttpHeaders;
|
||||
import com.azure.core.http.HttpMethod;
|
||||
import com.azure.core.http.HttpRequest;
|
||||
import com.azure.core.http.HttpResponse;
|
||||
import com.azure.core.implementation.jackson.ObjectMapperShim;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationConfig;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class BingConnector {
|
||||
|
||||
private static String BING_SEARCH_URL;
|
||||
static {
|
||||
String bingSearchUrl = System.getProperty("bing.search.url");
|
||||
if (bingSearchUrl == null || bingSearchUrl.isEmpty()) bingSearchUrl = System.getenv("BING_SEARCH_URL");
|
||||
if (bingSearchUrl == null || bingSearchUrl.isEmpty()) bingSearchUrl = "https://api.bing.microsoft.com/v7.0/search";
|
||||
BING_SEARCH_URL = bingSearchUrl;
|
||||
}
|
||||
|
||||
private static final String OCP_APIM_SUBSCRIPTION_KEY = "Ocp-Apim-Subscription-Key";
|
||||
private static final HttpHeaderName OCP_APIM_SUBSCRIPTION_KEY_HEADER = HttpHeaderName.fromString(OCP_APIM_SUBSCRIPTION_KEY);
|
||||
|
||||
private static class BingSearchResponse {
|
||||
private final WebPages webPages;
|
||||
@JsonCreator
|
||||
public BingSearchResponse(
|
||||
@JsonProperty("webPages") WebPages webPages
|
||||
) {
|
||||
this.webPages = webPages;
|
||||
}
|
||||
|
||||
public WebPages getWebPages() {
|
||||
return webPages;
|
||||
}
|
||||
}
|
||||
|
||||
private static class WebPages {
|
||||
private final WebPage[] value;
|
||||
@JsonCreator
|
||||
public WebPages(
|
||||
@JsonProperty("value") WebPage[] value
|
||||
) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public WebPage[] getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static class WebPage {
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("url")
|
||||
private String url;
|
||||
|
||||
@JsonProperty("snippet")
|
||||
private String snippet;
|
||||
|
||||
public WebPage() {
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getSnippet() {
|
||||
return snippet;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public void setSnippet(String snippet) {
|
||||
this.snippet = snippet;
|
||||
}
|
||||
}
|
||||
|
||||
private final String apiKey; // TODO: secure this
|
||||
private final HttpClient httpClient;
|
||||
public BingConnector(String apiKey, HttpClient httpClient) {
|
||||
this.apiKey = apiKey;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
public Mono<List<String>> searchAsync(String query, int count, int offset) {
|
||||
return Mono.error(() -> new UnsupportedOperationException("Not implemented yet"));
|
||||
public Mono<List<String>> searchAsync(String query, int count, int offset) {
|
||||
|
||||
if (count <= 0 || 50 <= count) throw new IllegalArgumentException("count must be between 1 and 50");
|
||||
if (offset < 0) throw new IllegalArgumentException("offset must be greater than or equal to 0");
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethod.GET, searchUrl(query, count, offset));
|
||||
request.setHeader(OCP_APIM_SUBSCRIPTION_KEY_HEADER, apiKey);
|
||||
|
||||
return httpClient.send(request).flatMap(BingConnector::handleResponse);
|
||||
|
||||
}
|
||||
|
||||
private static String searchUrl(String query, int count, int offset) {
|
||||
|
||||
try {
|
||||
query = URLEncoder.encode(query, StandardCharsets.UTF_8.toString());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// ignored - UTF-8 is always supported
|
||||
}
|
||||
|
||||
String url = String.format("%s?q=%s&count=%s&offset=%s",
|
||||
BING_SEARCH_URL, query, Integer.toString(count), Integer.toString(offset));
|
||||
return url;
|
||||
}
|
||||
|
||||
private static Mono<List<String>> handleResponse(HttpResponse response) {
|
||||
return response.getBodyAsString()
|
||||
.flatMap(body -> {
|
||||
if (body == null || body.isEmpty()) return Mono.empty();
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false);
|
||||
BingSearchResponse bingSearchResponse = objectMapper.readValue(body, BingSearchResponse.class);
|
||||
List<String> urls = new ArrayList<String>();
|
||||
for (WebPage webPage : bingSearchResponse.getWebPages().getValue()) {
|
||||
urls.add(webPage.getUrl());
|
||||
}
|
||||
return Mono.just(urls);
|
||||
} catch (JsonProcessingException e) {
|
||||
Mono.error(e);
|
||||
}
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -20,17 +20,20 @@ output_variable:
|
||||
type: string
|
||||
description: The response from the assistant.
|
||||
execution_settings:
|
||||
- model_id: gpt-3.5-turbo
|
||||
gpt-3.5-turbo:
|
||||
model_id: gpt-3.5-turbo
|
||||
temperature: 0.3
|
||||
top_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
frequency_penalty: 0.0
|
||||
max_tokens: 256
|
||||
stop_sequences: [ ]
|
||||
- model_id: gpt-4
|
||||
gpt-4:
|
||||
model_id: gpt-4
|
||||
temperature: 0.7
|
||||
top_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
frequency_penalty: 0.0
|
||||
max_tokens: 256
|
||||
stop_sequences: [ ]
|
||||
|
||||
|
||||
+6
-3
@@ -16,7 +16,7 @@ input_variables:
|
||||
- name: persona
|
||||
type: string
|
||||
description: The persona of the assistant.
|
||||
default_value: You are a helpful assistant.
|
||||
default: You are a helpful assistant.
|
||||
is_required: false
|
||||
- name: messages
|
||||
type: ChatHistory
|
||||
@@ -26,17 +26,20 @@ output_variable:
|
||||
type: string
|
||||
description: The response from the assistant.
|
||||
execution_settings:
|
||||
- model_id: gpt-3.5-turbo
|
||||
gpt-3.5-turbo:
|
||||
model_id: gpt-3.5-turbo
|
||||
temperature: 0.3
|
||||
top_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
frequency_penalty: 0.0
|
||||
max_tokens: 256
|
||||
stop_sequences: [ ]
|
||||
- model_id: gpt-4
|
||||
gpt-4:
|
||||
model_id: gpt-4
|
||||
temperature: 0.7
|
||||
top_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
frequency_penalty: 0.0
|
||||
max_tokens: 256
|
||||
stop_sequences: [ ]
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import com.azure.ai.openai.OpenAIAsyncClient;
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.core.credential.KeyCredential;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.SKBuilders;
|
||||
import com.microsoft.semantickernel.chatcompletion.AzureOpenAIChatCompletion;
|
||||
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
|
||||
import com.microsoft.semantickernel.chatcompletion.ChatHistory;
|
||||
import com.microsoft.semantickernel.orchestration.KernelFunction;
|
||||
@@ -16,6 +15,7 @@ import com.microsoft.semantickernel.templateengine.handlebars.HandlebarsPromptTe
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import plugins.searchplugin.Search;
|
||||
|
||||
public class Main {
|
||||
@@ -31,7 +31,7 @@ public class Main {
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
OpenAIAsyncClient client = new OpenAIClientBuilder()
|
||||
.credential(new KeyCredential(AZURE_OPENAI_API_KEY))
|
||||
.credential(new AzureKeyCredential(AZURE_OPENAI_API_KEY))
|
||||
.endpoint(AZURE_OPENAI_ENDPOINT)
|
||||
.buildAsyncClient();
|
||||
|
||||
@@ -39,9 +39,9 @@ public class Main {
|
||||
KernelFunction chatFunction = KernelFunctionYaml.fromYaml(
|
||||
Path.of("Plugins/ChatPlugin/GroundedChat.prompt.yaml"));
|
||||
|
||||
ChatCompletionService gpt35Turbo = AzureOpenAIChatCompletion.builder()
|
||||
ChatCompletionService gpt35Turbo = ChatCompletionService.builder()
|
||||
.withOpenAIAsyncClient(client)
|
||||
.withModelId(GPT_35_DEPLOYMENT_NAME != null ? GPT_35_DEPLOYMENT_NAME : "gpt-35-turbo")
|
||||
.withModelId(GPT_35_DEPLOYMENT_NAME != null ? GPT_35_DEPLOYMENT_NAME : "gpt-3.5-turbo")
|
||||
.build();
|
||||
|
||||
// Create the search plugin
|
||||
|
||||
@@ -5,11 +5,12 @@ import com.azure.core.http.HttpClient;
|
||||
import com.microsoft.semantickernel.plugin.annotations.DefineKernelFunction;
|
||||
import com.microsoft.semantickernel.plugin.annotations.KernelFunctionParameter;
|
||||
import com.microsoft.semantickernel.plugins.web.bing.BingConnector;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class Search {
|
||||
|
||||
private final BingConnector _bingConnector;
|
||||
private final BingConnector bingConnector;
|
||||
|
||||
public Search(String apiKey) {
|
||||
this(apiKey, HttpClient.createDefault());
|
||||
@@ -17,14 +18,14 @@ public class Search {
|
||||
|
||||
public Search(String apiKey, HttpClient httpClient)
|
||||
{
|
||||
this._bingConnector = new BingConnector(apiKey, httpClient);
|
||||
this.bingConnector = new BingConnector(apiKey, httpClient);
|
||||
}
|
||||
|
||||
@DefineKernelFunction(description="Searches Bing for the given query")
|
||||
@DefineKernelFunction(name="search", description="Searches Bing for the given query")
|
||||
public Mono<String> searchAsync(
|
||||
@KernelFunctionParameter(description="The search query", name="query", type=String.class) String query
|
||||
){
|
||||
return Mono.empty();
|
||||
return bingConnector.searchAsync(query, 1, 0).map(results -> results.get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-3
@@ -21,7 +21,7 @@ input_variables:
|
||||
- name: persona
|
||||
type: string
|
||||
description: The persona of the assistant.
|
||||
default_value: You are a helpful assistant.
|
||||
default: You are a helpful assistant.
|
||||
is_required: false
|
||||
- name: messages
|
||||
type: ChatHistory
|
||||
@@ -31,14 +31,16 @@ output_variable:
|
||||
type: string
|
||||
description: The response from the assistant.
|
||||
execution_settings:
|
||||
- model_id: gpt-3.5-turbo
|
||||
gpt-3.5-turbo:
|
||||
model_id: gpt-3.5-turbo
|
||||
temperature: 0.3
|
||||
top_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
frequency_penalty: 0.0
|
||||
max_tokens: 256
|
||||
stop_sequences: [ ]
|
||||
- model_id: gpt-4
|
||||
gpt-4:
|
||||
model_id: gpt-4
|
||||
temperature: 0.7
|
||||
top_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
|
||||
+7
-4
@@ -33,7 +33,7 @@ input_variables:
|
||||
- name: persona
|
||||
type: string
|
||||
description: The persona of the assistant.
|
||||
default_value: You are a helpful assistant.
|
||||
default: You are a helpful assistant.
|
||||
is_required: false
|
||||
- name: messages
|
||||
type: ChatHistory
|
||||
@@ -43,7 +43,10 @@ output_variable:
|
||||
type: string
|
||||
description: The response from the assistant.
|
||||
execution_settings:
|
||||
- model_id_pattern: ^gpt-4
|
||||
gpt-3.5-turbo:
|
||||
model_id_pattern: gpt-3\.?5-turbo
|
||||
temperature: 0.3
|
||||
gpt-4:
|
||||
model_id_pattern: ^gpt-4
|
||||
temperature: 0.7
|
||||
- model_id_pattern: ^gpt-3\.?5-turbo
|
||||
temperature: 0.3
|
||||
|
||||
|
||||
+4
-2
@@ -115,5 +115,7 @@ output_variable:
|
||||
type: string
|
||||
description: The action the assistant should take.
|
||||
execution_settings:
|
||||
- model_id_pattern: ^gpt-3\.?5-turbo
|
||||
temperature: 0.3
|
||||
gpt-3.5-turbo:
|
||||
model_id_pattern: ^gpt-3\.?5-turbo
|
||||
temperature: 0.3
|
||||
|
||||
|
||||
+3
-2
@@ -41,5 +41,6 @@ output_variable:
|
||||
type: string
|
||||
description: The summary, inputs, and steps required to solve the problem.
|
||||
execution_settings:
|
||||
- model_id_pattern: ^gpt-3\.?5-turbo
|
||||
temperature: 0.3
|
||||
gpt-3.5-turbo:
|
||||
model_id_pattern: ^gpt-3\.?5-turbo
|
||||
temperature: 0.3
|
||||
|
||||
+6
-4
@@ -19,7 +19,7 @@ input_variables:
|
||||
- name: persona
|
||||
type: string
|
||||
description: The persona of the assistant.
|
||||
default_value: You are a helpful assistant.
|
||||
default: You are a helpful assistant.
|
||||
is_required: false
|
||||
- name: messages
|
||||
type: ChatHistory
|
||||
@@ -29,7 +29,9 @@ output_variable:
|
||||
type: string
|
||||
description: The response from the assistant.
|
||||
execution_settings:
|
||||
- model_id_pattern: ^gpt-4
|
||||
gpt-4:
|
||||
model_id_pattern: ^gpt-4
|
||||
temperature: 0.7
|
||||
- model_id_pattern: ^gpt-3\.?5-turbo
|
||||
temperature: 0.3
|
||||
gpt-3.5-turbo:
|
||||
model_id_pattern: ^gpt-3\.?5-turbo
|
||||
temperature: 0.3
|
||||
|
||||
+4
-2
@@ -14,5 +14,7 @@ output_variable:
|
||||
type: string
|
||||
description: The most likely word for [MASK].
|
||||
execution_settings:
|
||||
- model_id_pattern: ^bert-base-uncased
|
||||
- model_id_pattern: ^gpt-3\.?5-turbo
|
||||
bert:
|
||||
model_id_pattern: ^bert-base-uncased
|
||||
gpt-3.5-turbo:
|
||||
model_id_pattern: ^gpt-3\.?5-turbo
|
||||
|
||||
+2
-1
@@ -15,4 +15,5 @@ output_variable:
|
||||
type: string
|
||||
description: The text description of the image.
|
||||
execution_settings:
|
||||
- model_id_pattern: ^gpt-4-vision-preview
|
||||
gpt-4:
|
||||
model_id_pattern: ^gpt-4-vision-preview
|
||||
|
||||
+4
-2
@@ -14,5 +14,7 @@ output_variable:
|
||||
type: string
|
||||
description: The answer.
|
||||
execution_settings:
|
||||
- model_id_pattern: ^deepset/roberta-base-squad2
|
||||
- model_id_pattern: ^gpt-3\.?5-turbo
|
||||
deepset:
|
||||
model_id_pattern: ^deepset/roberta-base-squad2
|
||||
gpt-3.5-turbo:
|
||||
model_id_pattern: ^gpt-3\.?5-turbo
|
||||
|
||||
+4
-2
@@ -14,5 +14,7 @@ output_variable:
|
||||
type: string
|
||||
description: The summary.
|
||||
execution_settings:
|
||||
- model_id_pattern: ^facebook/bart-large-cnn
|
||||
- model_id_pattern: ^gpt-3\.?5-turbo
|
||||
facebook:
|
||||
model_id_pattern: ^facebook/bart-large-cnn
|
||||
gpt-3.5-turbo:
|
||||
model_id_pattern: ^gpt-3\.?5-turbo
|
||||
|
||||
+2
-1
@@ -10,4 +10,5 @@ output_variable:
|
||||
type: Image
|
||||
description: The generated image.
|
||||
execution_settings:
|
||||
- model_id_pattern: ^runwayml/stable-diffusion-v1-5
|
||||
runwayml:
|
||||
model_id_pattern: ^runwayml/stable-diffusion-v1-5
|
||||
|
||||
@@ -7,4 +7,5 @@ template_format: handlebars
|
||||
description: A function that answers a math problem.
|
||||
input_variables:
|
||||
execution_settings:
|
||||
- model_id_pattern: ^wizard-math
|
||||
wizard-math:
|
||||
model_id_pattern: ^wizard-math
|
||||
|
||||
+3
-2
@@ -35,5 +35,6 @@ output_variable:
|
||||
type: string
|
||||
description: The Bing search to perform on behalf of the user.
|
||||
execution_settings:
|
||||
- model_id_pattern: ^gpt-3\.?5-turbo
|
||||
temperature: 0.3
|
||||
gpt-3.5-turbo:
|
||||
model_id_pattern: ^gpt-3\.?5-turbo
|
||||
temperature: 0.3
|
||||
|
||||
@@ -15,4 +15,5 @@ input_variables:
|
||||
- name: colorPallet
|
||||
description: The color pallet of the image to generate
|
||||
execution_settings:
|
||||
- model_id_pattern: ^gpt-4
|
||||
gpt-4:
|
||||
model_id_pattern: ^gpt-4
|
||||
|
||||
@@ -4,4 +4,5 @@ template: |
|
||||
template_format: handlebars
|
||||
description: An assistant that generates images.
|
||||
execution_settings:
|
||||
- model_id_pattern: ^gpt-4
|
||||
gpt-4:
|
||||
model_id_pattern: ^gpt-4
|
||||
|
||||
@@ -5,5 +5,7 @@ template: |
|
||||
template_format: handlebars
|
||||
description: An assistant that helps manage projects.
|
||||
execution_settings:
|
||||
- model_id_pattern: ^gpt-4
|
||||
- model_id_pattern: ^gpt-3\.?5-turbo
|
||||
gpt-4:
|
||||
model_id_pattern: ^gpt-4
|
||||
gpt-3.5-turbo:
|
||||
model_id_pattern: ^gpt-3\.?5-turbo
|
||||
|
||||
@@ -4,4 +4,5 @@ template: |
|
||||
template_format: handlebars
|
||||
description: An assistant that generates images.
|
||||
execution_settings:
|
||||
- model_id_pattern: ^gpt-4
|
||||
gpt-4:
|
||||
model_id_pattern: ^gpt-4
|
||||
|
||||
@@ -64,7 +64,6 @@
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-ai-openai</artifactId>
|
||||
<version>1.0.0-beta.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
|
||||
+8
-5
@@ -1,5 +1,12 @@
|
||||
package com.microsoft.semantickernel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
|
||||
import com.microsoft.semantickernel.orchestration.KernelFunction;
|
||||
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
|
||||
@@ -10,11 +17,7 @@ import com.microsoft.semantickernel.plugin.KernelPlugin;
|
||||
import com.microsoft.semantickernel.plugin.KernelPluginCollection;
|
||||
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
|
||||
import com.microsoft.semantickernel.textcompletion.TextGenerationService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.microsoft.semantickernel.builders.Buildable;
|
||||
import com.microsoft.semantickernel.builders.SemanticKernelBuilder;
|
||||
import com.microsoft.semantickernel.orchestration.KernelFunction;
|
||||
@@ -10,8 +14,7 @@ import com.microsoft.semantickernel.orchestration.contextvariables.KernelArgumen
|
||||
import com.microsoft.semantickernel.plugin.KernelPlugin;
|
||||
import com.microsoft.semantickernel.plugin.KernelPluginCollection;
|
||||
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
||||
+2
-2
@@ -35,8 +35,8 @@ public enum BuildersSingleton {
|
||||
"com.microsoft.semantickernel.connectors.ai.openai.textembeddings.OpenAITextEmbeddingGeneration$Builder";
|
||||
private static final String FALLBACK_KERNEL_BUILDER_CLASS =
|
||||
"com.microsoft.semantickernel.DefaultKernel$Builder";
|
||||
private static final String FALLBACK_KERNEL_CONFIG_BUILDER_CLASS =
|
||||
"com.microsoft.semantickernel.KernelConfig$Builder";
|
||||
// private static final String FALLBACK_KERNEL_CONFIG_BUILDER_CLASS =
|
||||
// "com.microsoft.semantickernel.KernelConfig$Builder";
|
||||
private static final String FALLBACK_PROMPT_TEMPLATE_BUILDER_CLASS =
|
||||
"com.microsoft.semantickernel.semanticfunctions.DefaultPromptTemplate$Builder";
|
||||
private static final String FALLBACK_PROMPT_TEMPLATE_ENGINE_BUILDER_CLASS =
|
||||
|
||||
+8
-3
@@ -8,6 +8,7 @@ import com.azure.ai.openai.OpenAIClient;
|
||||
import com.microsoft.semantickernel.AIService;
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.builders.Buildable;
|
||||
import com.microsoft.semantickernel.builders.BuildersSingleton;
|
||||
import com.microsoft.semantickernel.builders.SemanticKernelBuilder;
|
||||
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
|
||||
|
||||
@@ -40,21 +41,25 @@ public interface ChatCompletionService extends Buildable, AIService {
|
||||
Kernel kernel
|
||||
);
|
||||
|
||||
interface Builder<T extends ChatCompletionService> extends SemanticKernelBuilder<T> {
|
||||
static ChatCompletionService.Builder builder() {
|
||||
return BuildersSingleton.INST.getInstance(ChatCompletionService.Builder.class);
|
||||
}
|
||||
|
||||
interface Builder extends SemanticKernelBuilder<ChatCompletionService> {
|
||||
|
||||
/**
|
||||
* Sets the model ID to use with the ChatCompletion service.
|
||||
* @param modelId the model ID to use with the ChatCompletion service
|
||||
* @return this builder
|
||||
*/
|
||||
Builder<T> withModelId(String modelId);
|
||||
Builder withModelId(String modelId);
|
||||
|
||||
/**
|
||||
* Sets the {@link OpenAIClient} to use for communication with the ChatCompletion service.
|
||||
* @param openAIClient the {@link OpenAIClient} to use for communication with the ChatCompletion service
|
||||
* @return this builder
|
||||
*/
|
||||
Builder<T> withOpenAIAsyncClient(OpenAIAsyncClient openAIClient);
|
||||
Builder withOpenAIAsyncClient(OpenAIAsyncClient openAIClient);
|
||||
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -1,6 +1,11 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
package com.microsoft.semantickernel.orchestration;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.builders.Buildable;
|
||||
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
|
||||
@@ -9,10 +14,8 @@ import com.microsoft.semantickernel.orchestration.contextvariables.KernelArgumen
|
||||
import com.microsoft.semantickernel.semanticfunctions.InputVariable;
|
||||
import com.microsoft.semantickernel.semanticfunctions.OutputVariable;
|
||||
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import com.microsoft.semantickernel.semanticfunctions.PromptTemplateFactory;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
package com.microsoft.semantickernel.plugin;
|
||||
|
||||
import com.microsoft.semantickernel.orchestration.KernelFunction;
|
||||
import com.microsoft.semantickernel.orchestration.contextvariables.CaseInsensitiveMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
public class DefaultKernelPlugin extends KernelPlugin {
|
||||
|
||||
/// <summary>The collection of functions associated with this plugin.</summary>
|
||||
private final Map<String, KernelFunction> functions;
|
||||
|
||||
public DefaultKernelPlugin(String name, String description,
|
||||
Map<String, KernelFunction> functions) {
|
||||
super(name, description);
|
||||
|
||||
this.functions = new CaseInsensitiveMap<>(functions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<KernelFunction> iterator() {
|
||||
return functions.values().iterator();
|
||||
}
|
||||
|
||||
public Map<String, KernelFunction> getFunctions() {
|
||||
return functions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KernelFunction get(String functionName) {
|
||||
return functions.get(functionName);
|
||||
}
|
||||
}
|
||||
+11
-1
@@ -8,7 +8,7 @@ public class KernelParameterMetadata {
|
||||
private final String description;
|
||||
private final String defaultValue;
|
||||
private final boolean isRequired;
|
||||
private Class parameterType;
|
||||
private final Class<?> parameterType;
|
||||
|
||||
public KernelParameterMetadata(
|
||||
String name,
|
||||
@@ -19,6 +19,7 @@ public class KernelParameterMetadata {
|
||||
this.description = description;
|
||||
this.defaultValue = defaultValue;
|
||||
this.isRequired = isRequired;
|
||||
this.parameterType = String.class; // TODO
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -32,5 +33,14 @@ public class KernelParameterMetadata {
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return isRequired;
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return parameterType;
|
||||
}
|
||||
|
||||
//private KernelJsonSchema schema;
|
||||
}
|
||||
|
||||
+23
-8
@@ -1,23 +1,41 @@
|
||||
package com.microsoft.semantickernel.plugin;
|
||||
|
||||
import com.microsoft.semantickernel.orchestration.KernelFunction;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class KernelPlugin implements Iterable<KernelFunction> {
|
||||
import com.microsoft.semantickernel.orchestration.KernelFunction;
|
||||
import com.microsoft.semantickernel.orchestration.contextvariables.CaseInsensitiveMap;
|
||||
|
||||
public class KernelPlugin implements Iterable<KernelFunction> {
|
||||
|
||||
private final String name;
|
||||
private final String description;
|
||||
|
||||
protected KernelPlugin(String name, String description) {
|
||||
private final CaseInsensitiveMap<KernelFunction> functions;
|
||||
|
||||
public KernelPlugin(String name, String description,
|
||||
Map<String, KernelFunction> plugins) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.functions = new CaseInsensitiveMap<>();
|
||||
if (plugins != null) {
|
||||
plugins.forEach((key,kerneFunction) -> this.functions.put(key, kerneFunction));
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Iterator<KernelFunction> iterator();
|
||||
public Map<String, KernelFunction> getFunctions() {
|
||||
return functions;
|
||||
}
|
||||
|
||||
public abstract Map<String, KernelFunction> getFunctions();
|
||||
public KernelFunction get(String functionName) {
|
||||
return functions.get(functionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<KernelFunction> iterator() {
|
||||
return functions.values().iterator();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
@@ -26,7 +44,4 @@ public abstract class KernelPlugin implements Iterable<KernelFunction> {
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public abstract KernelFunction get(String functionName);
|
||||
|
||||
}
|
||||
|
||||
+5
-3
@@ -1,17 +1,19 @@
|
||||
package com.microsoft.semantickernel.plugin;
|
||||
|
||||
import com.microsoft.semantickernel.orchestration.KernelFunction;
|
||||
import com.microsoft.semantickernel.orchestration.KernelFunctionMetadata;
|
||||
import com.microsoft.semantickernel.orchestration.contextvariables.CaseInsensitiveMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.microsoft.semantickernel.orchestration.KernelFunction;
|
||||
import com.microsoft.semantickernel.orchestration.KernelFunctionMetadata;
|
||||
import com.microsoft.semantickernel.orchestration.contextvariables.CaseInsensitiveMap;
|
||||
|
||||
public class KernelPluginCollection implements Iterable<KernelPlugin> {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(KernelPluginCollection.class);
|
||||
|
||||
+12
-7
@@ -23,6 +23,7 @@ import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -88,17 +89,21 @@ public class KernelPluginFactory {
|
||||
@Nullable List<KernelFunction> functions) {
|
||||
|
||||
if (functions == null) {
|
||||
return new DefaultKernelPlugin(
|
||||
return new KernelPlugin(
|
||||
pluginName,
|
||||
null,
|
||||
new HashMap<>()
|
||||
Collections.emptyMap()
|
||||
);
|
||||
}
|
||||
|
||||
return new DefaultKernelPlugin(
|
||||
Map<String, KernelFunction> functionsMap = new HashMap<>();
|
||||
functions.forEach((kernelFunction) ->
|
||||
functionsMap.put(kernelFunction.getName(), kernelFunction));
|
||||
|
||||
return new KernelPlugin(
|
||||
pluginName,
|
||||
null,
|
||||
functions.stream().collect(Collectors.toMap(KernelFunction::getName, f -> f))
|
||||
functionsMap
|
||||
);
|
||||
}
|
||||
|
||||
@@ -181,7 +186,7 @@ public class KernelPluginFactory {
|
||||
}
|
||||
}
|
||||
|
||||
return new DefaultKernelPlugin(
|
||||
return new KernelPlugin(
|
||||
pluginDirectoryName,
|
||||
null,
|
||||
plugins
|
||||
@@ -253,9 +258,9 @@ public class KernelPluginFactory {
|
||||
|
||||
plugins.put(functionName, function);
|
||||
|
||||
return new DefaultKernelPlugin(
|
||||
return new KernelPlugin(
|
||||
pluginDirectoryName,
|
||||
null,
|
||||
promptTemplateConfig.getDescription(),
|
||||
plugins
|
||||
);
|
||||
}
|
||||
|
||||
+78
-12
@@ -1,8 +1,20 @@
|
||||
package com.microsoft.semantickernel.semanticfunctions;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.azure.core.exception.HttpResponseException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.microsoft.semantickernel.AIService;
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.chatcompletion.AuthorRole;
|
||||
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
|
||||
import com.microsoft.semantickernel.orchestration.DefaultKernelFunction;
|
||||
import com.microsoft.semantickernel.orchestration.KernelFunction;
|
||||
@@ -11,14 +23,10 @@ import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
|
||||
import com.microsoft.semantickernel.orchestration.StreamingContent;
|
||||
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
|
||||
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType;
|
||||
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes;
|
||||
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
|
||||
import com.microsoft.semantickernel.textcompletion.TextGenerationService;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -70,18 +78,30 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
|
||||
|
||||
if (client instanceof ChatCompletionService) {
|
||||
|
||||
prompt = "<messages>" + prompt + "</messages>";
|
||||
prompt = "<prompt>".concat(prompt).concat("</prompt>");
|
||||
result = ((ChatCompletionService) client)
|
||||
.getStreamingChatMessageContentsAsync(
|
||||
prompt,
|
||||
executionSettings,
|
||||
kernel)
|
||||
.concatMap(streamingChatMessageContent -> {
|
||||
T value = variableType
|
||||
.getConverter()
|
||||
.fromPromptString(
|
||||
streamingChatMessageContent.getContent());
|
||||
return Flux.just(new StreamingContent<>(value));
|
||||
if (streamingChatMessageContent.getRole() == AuthorRole.ASSISTANT) {
|
||||
T value = variableType
|
||||
.getConverter()
|
||||
.fromPromptString(
|
||||
streamingChatMessageContent.getContent());
|
||||
return Flux.just(new StreamingContent<>(value));
|
||||
} else if (streamingChatMessageContent.getRole() == AuthorRole.TOOL) {
|
||||
Mono<ContextVariable<String>> toolResult = invokeTool(kernel, streamingChatMessageContent.getContent());
|
||||
return toolResult.flatMapMany(contextVariable -> {
|
||||
T value = variableType
|
||||
.getConverter()
|
||||
.fromPromptString(
|
||||
contextVariable.getValue());
|
||||
return Flux.just(new StreamingContent<>(value));
|
||||
});
|
||||
}
|
||||
return Flux.empty();
|
||||
});
|
||||
return result;
|
||||
|
||||
@@ -150,6 +170,52 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction {
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a json string, invoke the tool specified in the json string.
|
||||
* At this time, the only tool we have is 'function'.
|
||||
* The json string should be of the form:
|
||||
* {"type":"function", "function": {"name":"search-search", "parameters": {"query":"Banksy"}}}
|
||||
* where 'name' is <plugin name '-' function name>.
|
||||
*/
|
||||
private Mono<ContextVariable<String>> invokeTool(Kernel kernel, String json) {
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode jsonNode = mapper.readTree(json);
|
||||
jsonNode = jsonNode.get("function");
|
||||
if (jsonNode != null) {
|
||||
return invokeFunction(kernel, jsonNode);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to parse json", e);
|
||||
}
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
/*
|
||||
* The jsonNode should represent: {"name":"search-search", "parameters": {"query":"Banksy"}}}
|
||||
*/
|
||||
private Mono<ContextVariable<String>> invokeFunction(Kernel kernel, JsonNode jsonNode) {
|
||||
String name = jsonNode.get("name").asText();
|
||||
String[] parts = name.split("-");
|
||||
String pluginName = parts.length > 0 ? parts[0] : "";
|
||||
String fnName = parts.length > 1 ? parts[1] : "";
|
||||
JsonNode parameters = jsonNode.get("parameters");
|
||||
if (parameters != null) {
|
||||
KernelFunction kernelFunction = kernel.getPlugins().getFunction(pluginName, fnName);
|
||||
if (kernelFunction == null) return Mono.empty();
|
||||
ContextVariableType<String> variableType = ContextVariableTypes.getDefaultVariableTypeForClass(String.class);
|
||||
Map<String, ContextVariable<?>> variables = new HashMap<>();
|
||||
parameters.fields().forEachRemaining(entry -> {
|
||||
String paramName = entry.getKey();
|
||||
String paramValue = entry.getValue().asText();
|
||||
ContextVariable<String> contextVariable = new ContextVariable<>(variableType, paramValue);
|
||||
variables.put(paramName, contextVariable);
|
||||
});
|
||||
KernelArguments arguments = KernelArguments.builder().withVariables(variables).build();
|
||||
return kernelFunction.invokeAsync(kernel, arguments, variableType);
|
||||
}
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
public static KernelFunction create(
|
||||
String promptTemplate) {
|
||||
|
||||
+86
-19
@@ -5,6 +5,8 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
@@ -18,8 +20,12 @@ import com.github.jknack.handlebars.ValueResolver;
|
||||
import com.microsoft.semantickernel.Kernel;
|
||||
import com.microsoft.semantickernel.chatcompletion.ChatHistory;
|
||||
import com.microsoft.semantickernel.chatcompletion.ChatMessageContent;
|
||||
import com.microsoft.semantickernel.orchestration.KernelFunction;
|
||||
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
|
||||
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
|
||||
import com.microsoft.semantickernel.plugin.KernelParameterMetadata;
|
||||
import com.microsoft.semantickernel.plugin.KernelPlugin;
|
||||
import com.microsoft.semantickernel.plugin.KernelPluginCollection;
|
||||
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
|
||||
import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig;
|
||||
|
||||
@@ -44,7 +50,7 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
|
||||
public Mono<String> renderAsync(Kernel kernel,
|
||||
@Nullable KernelArguments arguments) {
|
||||
HandleBarsPromptTemplateHandler handler =
|
||||
new HandleBarsPromptTemplateHandler(promptTemplate.getTemplate());
|
||||
new HandleBarsPromptTemplateHandler(kernel, promptTemplate.getTemplate());
|
||||
|
||||
return handler.render(arguments);
|
||||
}
|
||||
@@ -83,6 +89,38 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
|
||||
}
|
||||
}
|
||||
|
||||
private static class KernelPluginResolver implements ValueResolver {
|
||||
|
||||
@Override
|
||||
public Object resolve(Object context, String name) {
|
||||
if (context instanceof KernelPluginCollection) {
|
||||
return context;
|
||||
}
|
||||
if (context instanceof KernelFunction) {
|
||||
KernelFunction function = (KernelFunction) context;
|
||||
if ("pluginname".equals(name.toLowerCase())) {
|
||||
return function.getSkillName();
|
||||
} else if ("name".equals(name.toLowerCase())) {
|
||||
return function.getName();
|
||||
}
|
||||
}
|
||||
return UNRESOLVED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolve(Object context) {
|
||||
if (context instanceof KernelPluginCollection) {
|
||||
return ((KernelPluginCollection) context).iterator();
|
||||
}
|
||||
return UNRESOLVED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, Object>> propertySet(Object context) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ContextVariableResolver implements ValueResolver {
|
||||
|
||||
@Override
|
||||
@@ -93,7 +131,7 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
|
||||
if (context instanceof ContextVariable) {
|
||||
return ((ContextVariable<?>) context).getValue();
|
||||
}
|
||||
return null;
|
||||
return UNRESOLVED;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,10 +159,12 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
|
||||
|
||||
public static class HandleBarsPromptTemplateHandler {
|
||||
|
||||
private final Kernel kernel;
|
||||
private final String template;
|
||||
private final Handlebars handlebars;
|
||||
|
||||
public HandleBarsPromptTemplateHandler(String template) {
|
||||
public HandleBarsPromptTemplateHandler(Kernel kernel, String template) {
|
||||
this.kernel = kernel;
|
||||
this.template = template;
|
||||
this.handlebars = new Handlebars();
|
||||
this.handlebars.registerHelper(
|
||||
@@ -134,12 +174,13 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
|
||||
String content = (String) options.fn(context);
|
||||
|
||||
if (context instanceof Optional) {
|
||||
ChatMessageContent message = ((Optional<ChatMessageContent>) context).orElse(
|
||||
null);
|
||||
if (role == null || role.isEmpty()) {
|
||||
role = message.getAuthorRole().name();
|
||||
ChatMessageContent message = ((Optional<ChatMessageContent>) context).orElse(null);
|
||||
if (message != null) {
|
||||
if (role == null || role.isEmpty()) {
|
||||
role = message.getAuthorRole().name();
|
||||
}
|
||||
content = message.getContent();
|
||||
}
|
||||
content = message.getContent();
|
||||
}
|
||||
|
||||
if (role != null && !role.isEmpty()) {
|
||||
@@ -156,10 +197,24 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
|
||||
"each",
|
||||
(context, options) -> {
|
||||
if (context instanceof ChatHistory) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StringBuilder sb = new StringBuilder("<messages>");
|
||||
for(ChatMessageContent message : (ChatHistory) context) {
|
||||
sb.append(options.fn(message));
|
||||
}
|
||||
sb.append("</messages>");
|
||||
return new Handlebars.SafeString(sb.toString());
|
||||
}
|
||||
if (context instanceof KernelPluginCollection) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Iterator<KernelPlugin> plugins = ((KernelPluginCollection) context).iterator();
|
||||
while (plugins.hasNext()) {
|
||||
KernelPlugin plugin = plugins.next();
|
||||
Iterator<KernelFunction> functions = plugin.iterator();
|
||||
while (functions.hasNext()) {
|
||||
KernelFunction function = functions.next();
|
||||
sb.append(options.fn(function));
|
||||
}
|
||||
}
|
||||
return new Handlebars.SafeString(sb.toString());
|
||||
}
|
||||
return "";
|
||||
@@ -169,23 +224,34 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
|
||||
.registerHelper(
|
||||
"functions",
|
||||
(context, options) -> {
|
||||
StringBuilder functions = new StringBuilder("<functions>");
|
||||
// What to do here?
|
||||
functions.append("</functions>");
|
||||
StringBuilder sb = new StringBuilder("<functions>");
|
||||
KernelPluginCollection plugins = kernel.getPlugins();
|
||||
sb.append(options.fn(plugins));
|
||||
sb.append("</functions>");
|
||||
|
||||
return functions.toString();
|
||||
return new Handlebars.SafeString(sb.toString());
|
||||
}
|
||||
)
|
||||
|
||||
.registerHelper(
|
||||
"function",
|
||||
(context, options) -> {
|
||||
String pluginName = options.hash("pluginName");
|
||||
String functionName = options.hash("name");
|
||||
// What to do here?
|
||||
return String.format(
|
||||
"<function pluginName=\"%s\" name=\"%s\" />",
|
||||
pluginName, functionName);
|
||||
KernelFunction function = (KernelFunction) context;
|
||||
String pluginName = function.getSkillName();
|
||||
String functionName = function.getName();
|
||||
String description = function.getDescription();
|
||||
StringBuilder sb = new StringBuilder(
|
||||
String.format(
|
||||
"<function pluginName=\"%s\" name=\"%s\" description=\"%s\">",
|
||||
pluginName, functionName, description));
|
||||
List<KernelParameterMetadata> parameters = function.getMetadata().getParameters();
|
||||
parameters.forEach(p -> {
|
||||
sb.append(String.format(
|
||||
"<parameter name=\"%s\" description=\"%s\" defaultValue=\"%s\" isRequired=\"%s\" type=\"%s\"/>",
|
||||
p.getName(), p.getDescription(), p.getDefaultValue(), p.isRequired(), p.getType()));
|
||||
});
|
||||
sb.append("</function>");
|
||||
return new Handlebars.SafeString(sb.toString());
|
||||
}
|
||||
);
|
||||
// TODO: 1.0 Add more helpers
|
||||
@@ -196,6 +262,7 @@ public class HandlebarsPromptTemplate implements PromptTemplate {
|
||||
try {
|
||||
ArrayList<ValueResolver> resolvers = new ArrayList<>();
|
||||
resolvers.add(new MessageResolver());
|
||||
resolvers.add(new KernelPluginResolver());
|
||||
resolvers.add(new ContextVariableResolver());
|
||||
|
||||
// resolvers.addAll(ValueResolver.defaultValueResolvers());
|
||||
|
||||
Reference in New Issue
Block a user