Java: sample Example07_BingAndGooglePlugins (#4685)

Add Example07_BingAndGooglePlugins
This commit is contained in:
David Grieve
2024-01-26 09:40:12 -05:00
committed by GitHub
parent 300efbe54e
commit 75a711a5a6
27 changed files with 1132 additions and 139 deletions
@@ -7,6 +7,7 @@ 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.ChatCompletionsToolDefinition;
import com.azure.ai.openai.models.ChatRequestAssistantMessage;
import com.azure.ai.openai.models.ChatRequestMessage;
import com.azure.ai.openai.models.ChatRequestSystemMessage;
@@ -28,6 +29,7 @@ import com.microsoft.semantickernel.exceptions.AIException;
import com.microsoft.semantickernel.orchestration.FunctionResultMetadata;
import com.microsoft.semantickernel.orchestration.KernelFunction;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
import com.microsoft.semantickernel.orchestration.ToolCallBehavior;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes;
@@ -251,26 +253,26 @@ public class OpenAIChatCompletion implements 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;
}
if (promptExecutionSettings.getResultsPerPrompt() < 1
|| promptExecutionSettings.getResultsPerPrompt() > MAX_RESULTS_PER_PROMPT) {
throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, String.format("Results per prompt must be in range between 1 and %d, inclusive.", MAX_RESULTS_PER_PROMPT));
}
List<ChatCompletionsToolDefinition> toolDefinitions =
chatCompletionsToolDefinitions(promptExecutionSettings.getToolCallBehavior(), functions);
if (toolDefinitions != null && !toolDefinitions.isEmpty()) {
options.setTools(toolDefinitions);
// TODO: options.setToolChoices(toolChoices);
}
Map<String, Integer> logit = null;
if (promptExecutionSettings.getTokenSelectionBiases() != null) {
logit = promptExecutionSettings
@@ -302,6 +304,30 @@ public class OpenAIChatCompletion implements ChatCompletionService {
return options;
}
private static List<ChatCompletionsToolDefinition> chatCompletionsToolDefinitions(
ToolCallBehavior toolCallBehavior,
List<FunctionDefinition> functions) {
if (functions == null || functions.isEmpty()) {
return Collections.emptyList();
}
if (toolCallBehavior == null || !(toolCallBehavior.kernelFunctionsEnabled() || toolCallBehavior.autoInvokeEnabled())) {
return Collections.emptyList();
}
return functions.stream()
.filter(function -> {
String[] parts = function.getName().split("-");
String pluginName = parts.length > 0 ? parts[0] : "";
String fnName = parts.length > 1 ? parts[1] : "";
return toolCallBehavior.functionEnabled(pluginName, fnName);
})
.map(ChatCompletionsFunctionToolDefinition::new)
.collect(Collectors.toList());
}
private static List<ChatRequestMessage> getChatRequestMessages(ChatHistory chatHistory) {
List<ChatMessageContent> messages = chatHistory.getMessages();
if (messages == null || messages.isEmpty()) {
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.microsoft.semantic-kernel</groupId>
<artifactId>semantickernel-parent</artifactId>
<version>1.0.0-alpha.1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>semantickernel-connectors-web-bing</artifactId>
<name>Semantic Kernel Bing Connector</name>
<description>Defines Bing connectors to be used with the Semantic Kernel</description>
<dependencies>
<dependency>
<groupId>com.microsoft.semantic-kernel</groupId>
<artifactId>semantickernel-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<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>
</dependencies>
</project>
@@ -1,29 +1,27 @@
package com.microsoft.semantickernel.plugins.web.bing;
package com.microsoft.semantickernel.connectors.web.bing;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
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 com.microsoft.semantickernel.connectors.WebSearchEngineConnector;
import com.microsoft.semantickernel.exceptions.SKException;
import reactor.core.publisher.Mono;
public class BingConnector {
public class BingConnector implements WebSearchEngineConnector {
private static String BING_SEARCH_URL;
static {
@@ -54,7 +52,7 @@ public class BingConnector {
private final WebPage[] value;
@JsonCreator
public WebPages(
@JsonProperty("value") WebPage[] value
@JsonProperty("value") BingWebPage[] value
) {
this.value = value;
}
@@ -64,52 +62,52 @@ public class BingConnector {
}
}
public static class WebPage {
@JsonProperty("name")
private String name;
public static class BingWebPage implements WebPage {
private final String name;
private final String url;
private final String snippet;
@JsonProperty("url")
private String url;
@JsonProperty("snippet")
private String snippet;
public WebPage() {
@JsonCreator
public BingWebPage(
@JsonProperty("name") String name,
@JsonProperty("url") String url,
@JsonProperty("snippet") String snippet
) {
this.name = name;
this.url = url;
this.snippet = snippet;
}
@Override
public String getName() {
return name;
}
@Override
public String getUrl() {
return url;
}
@Override
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) {
public BingConnector(String apiKey) {
this(apiKey, HttpClient.createDefault());
}
@Override
public Mono<List<WebPage>> 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");
@@ -134,24 +132,23 @@ public class BingConnector {
return url;
}
private static Mono<List<String>> handleResponse(HttpResponse response) {
private static Mono<List<WebPage>> handleResponse(HttpResponse response) {
return response.getBodyAsString()
.flatMap(body -> {
if (body == null || body.isEmpty()) return Mono.empty();
.map(body -> {
if (body == null || body.isEmpty()) return null;
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());
BingSearchResponse bingSearchResponse = objectMapper.readValue(body, BingSearchResponse.class);
if (bingSearchResponse.getWebPages() != null && bingSearchResponse.getWebPages().getValue() != null) {
return Arrays.asList(bingSearchResponse.getWebPages().getValue());
}
return Mono.just(urls);
} catch (JsonProcessingException e) {
Mono.error(e);
throw new SKException(e.getMessage(), e);
}
return Mono.empty();
return null;
});
}
}
@@ -0,0 +1,159 @@
package com.microsoft.semantickernel.plugins.web;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import com.microsoft.semantickernel.plugin.annotations.DefineKernelFunction;
import com.microsoft.semantickernel.plugin.annotations.KernelFunctionParameter;
public class SearchUrlPlugin {
/*
* Amazon Search URLs
*/
/// <summary>
/// Get search URL for Amazon
/// </summary>
@DefineKernelFunction(name="AmazonSearchUrl", description="Return URL for Amazon search query")
public String AmazonSearchUrl(@KernelFunctionParameter(description="Text to search for", name="query", required=true, type=String.class) String query)
{
String encoded = encode(query);
return String.format("https://www.amazon.com/s?k=%s", encoded);
}
/*
* Bing Search URLs
*/
/// <summary>
/// Get search URL for Bing
/// </summary>
@DefineKernelFunction(name="BingSearchUrl", description="Return URL for Bing search query.")
public String BingSearchUrl(@KernelFunctionParameter(description="Text to search for", name="query", required=true, type=String.class) String query)
{
String encoded = encode(query);
return String.format("https://www.bing.com/search?q=%s", encoded);
}
/// <summary>
/// Get search URL for Bing Images
/// </summary>
@DefineKernelFunction(name="BingImagesSearchUrl", description="Return URL for Bing Images search query.")
public String BingImagesSearchUrl(@KernelFunctionParameter(description="Text to search for", name="query", required=true, type=String.class) String query)
{
String encoded = encode(query);
return String.format("https://www.bing.com/images/search?q=%s", encoded);
}
/// <summary>
/// Get search URL for Bing Maps
/// </summary>
@DefineKernelFunction(name="BingMapsSearchUrl", description="Return URL for Bing Maps search query.")
public String BingMapsSearchUrl(@KernelFunctionParameter(description="Text to search for", name="query", required=true, type=String.class) String query)
{
String encoded = encode(query);
return String.format("https://www.bing.com/maps?q=%s", encoded);
}
/// <summary>
/// Get search URL for Bing Shopping
/// </summary>
@DefineKernelFunction(name="BingShoppingSearchUrl", description="Return URL for Bing Shopping search query.")
public String BingShoppingSearchUrl(@KernelFunctionParameter(description="Text to search for", name="query", required=true, type=String.class) String query)
{
String encoded = encode(query);
return String.format("https://www.bing.com/shop?q=%s", encoded);
}
/// <summary>
/// Get search URL for Bing News
/// </summary>
@DefineKernelFunction(name="BingNewsSearchUrl", description="Return URL for Bing News search query.")
public String BingNewsSearchUrl(@KernelFunctionParameter(description="Text to search for", name="query", required=true, type=String.class) String query)
{
String encoded = encode(query);
return String.format("https://www.bing.com/news/search?q=%s", encoded);
}
/// <summary>
/// Get search URL for Bing Travel
/// </summary>
@DefineKernelFunction(name="BingTravelSearchUrl", description="Return URL for Bing Travel search query.")
public String BingTravelSearchUrl(@KernelFunctionParameter(description="Text to search for", name="query", required=true, type=String.class) String query)
{
String encoded = encode(query);
return String.format("https://www.bing.com/travel/search?q=%s", encoded);
}
/*
* Facebook Search URLs
*/
/// <summary>
/// Get search URL for Facebook
/// </summary>
@DefineKernelFunction(name="FacebookSearchUrl", description="Return URL for Facebook search query.")
public String FacebookSearchUrl(@KernelFunctionParameter(description="Text to search for", name="query", required=true, type=String.class) String query)
{
String encoded = encode(query);
return String.format("https://www.facebook.com/search/top/?q=%s", encoded);
}
/*
* GitHub Search URLs
*/
/// <summary>
/// Get search URL for GitHub
/// </summary>
@DefineKernelFunction(name="GitHubSearchUrl", description="Return URL for GitHub search query.")
public String GitHubSearchUrl(@KernelFunctionParameter(description="Text to search for", name="query", required=true, type=String.class) String query)
{
String encoded = encode(query);
return String.format("https://github.com/search?q=%s", encoded);
}
/*
* LinkedIn Search URLs
*/
/// <summary>
/// Get search URL for LinkedIn
/// </summary>
@DefineKernelFunction(name="LinkedInSearchUrl", description="Return URL for LinkedIn search query.")
public String LinkedInSearchUrl(@KernelFunctionParameter(description="Text to search for", name="query", required=true, type=String.class) String query)
{
String encoded = encode(query);
return String.format("https://www.linkedin.com/search/results/index/?keywords=%s", encoded);
}
/*
* Twitter Search URLs
*/
/// <summary>
/// Get search URL for Twitter
/// </summary>
@DefineKernelFunction(name="TwitterSearchUrl", description="Return URL for Twitter search query.")
public String TwitterSearchUrl(@KernelFunctionParameter(description="Text to search for", name="query", required=true, type=String.class) String query)
{
String encoded = encode(query);
return String.format("https://twitter.com/search?q=%s", encoded);
}
/*
* Wikipedia Search URLs
*/
/// <summary>
/// Get search URL for Wikipedia
/// </summary>
@DefineKernelFunction(name="WikipediaSearchUrl", description="Return URL for Wikipedia search query.")
public String WikipediaSearchUrl(@KernelFunctionParameter(description="Text to search for", name="query", required=true, type=String.class) String query)
{
String encoded = encode(query);
return String.format("https://wikipedia.org/w/index.php?search=%s", encoded);
}
private static String encode(String query) {
try {
return URLEncoder.encode(query, "UTF-8");
} catch (UnsupportedEncodingException e) {
// should never happen since UTF-8 is always supported
return query;
}
}
}
@@ -0,0 +1,75 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.plugins.web;
import java.util.stream.Collectors;
import com.microsoft.semantickernel.connectors.WebSearchEngineConnector;
import com.microsoft.semantickernel.connectors.WebSearchEngineConnector.WebPage;
import com.microsoft.semantickernel.exceptions.SKException;
import com.microsoft.semantickernel.plugin.annotations.DefineKernelFunction;
import com.microsoft.semantickernel.plugin.annotations.KernelFunctionParameter;
import reactor.core.publisher.Mono;
/// <summary>
/// Web search engine plugin (e.g. Bing).
/// </summary>
public class WebSearchEnginePlugin {
/// <summary>
/// The count parameter name.
/// </summary>
public static final String COUNT_PARAM = "count";
/// <summary>
/// The offset parameter name.
/// </summary>
public static final String OFFSET_PARAM = "offset";
private final WebSearchEngineConnector connector;
/// <summary>
/// Initializes a new instance of the <see cref="WebSearchEnginePlugin"/> class.
/// </summary>
/// <param name="connector">The web search engine connector.</param>
public WebSearchEnginePlugin(WebSearchEngineConnector connector) {
this.connector = connector;
}
/// <summary>
/// Performs a web search using the provided query, count, and offset.
/// </summary>
/// <param name="query">The text to search for.</param>
/// <param name="count">The number of results to return. Default is 1.</param>
/// <param name="offset">The number of results to skip. Default is 0.</param>
/// <param name="cancellationToken">A cancellation token to observe while waiting for the task to complete.</param>
/// <returns>A task that represents the asynchronous operation. The value of the TResult parameter contains the search results as a string.</returns>
/// <remarks>
/// This method is marked as "unsafe." The usage of JavaScriptEncoder.UnsafeRelaxedJsonEscaping may introduce security risks.
/// Only use this method if you are aware of the potential risks and have validated the input to prevent security vulnerabilities.
/// </remarks>
@DefineKernelFunction(name="search", description="Searches the web for the given query")
public Mono<String> searchAsync(
@KernelFunctionParameter(description="The search query", name="query", type=String.class) String query,
@KernelFunctionParameter(description="The number of results to return", name="count", defaultValue = "1", type=int.class) int count,
@KernelFunctionParameter(description="The number of results to skip", name="offset", defaultValue = "0", type=int.class) int offset)
{
return connector.searchAsync(query, count, offset).map(results -> {
if (results == null || results.isEmpty()) {
throw new SKException("Failed to get a response from the web search engine.");
}
return count == 1
? results.get(0).getSnippet()
// TODO: .NET code does `JsonSerializer.Serialize(results, s_jsonOptionsCache)` here
// The joiner results in "[\"result1\",\"result2\"]"
: results.stream()
.limit(count)
.map(WebPage::getSnippet)
.collect(Collectors.joining("\",\"", "[\"", "\"]"));
});
}
}
+6
View File
@@ -64,6 +64,7 @@
</properties>
<modules>
<module>connectors/semantickernel-connectors-web-bing</module>
<!-- <module>connectors/semantickernel-connectors-ai-openai</module>-->
<!-- <module>connectors/semantickernel-connectors-memory-sqlite</module>-->
<!-- <module>connectors/semantickernel-connectors-memory-azurecognitivesearch</module>-->
@@ -150,6 +151,11 @@
<artifactId>semantickernel-actionplanner-extension</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.microsoft.semantic-kernel</groupId>
<artifactId>semantickernel-connectors-web-bing</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
+4
View File
@@ -35,6 +35,10 @@
<groupId>com.microsoft.semantic-kernel</groupId>
<artifactId>semantickernel-plugin-core</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.semantic-kernel</groupId>
<artifactId>semantickernel-connectors-web-bing</artifactId>
</dependency>
<!--
<dependency>
<groupId>com.microsoft.semantic-kernel</groupId>
@@ -0,0 +1,219 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.samples.syntaxexamples;
import com.azure.ai.openai.OpenAIAsyncClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.credential.KeyCredential;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.chatcompletion.ChatCompletionService;
import com.microsoft.semantickernel.connectors.web.bing.BingConnector;
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.plugin.KernelPluginFactory;
import com.microsoft.semantickernel.plugins.web.WebSearchEnginePlugin;
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt;
import com.microsoft.semantickernel.semanticfunctions.KernelPromptTemplateFactory;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig;
/// <summary>
/// The example shows how to use Bing and Google to search for current data
/// you might want to import into your system, e.g. providing AI prompts with
/// recent information, or for AI to generate recent information to display to users.
/// </summary>
public class Example07_BingAndGooglePlugins {
private static final String CLIENT_KEY = System.getenv("CLIENT_KEY");
private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY");
// Only required if AZURE_CLIENT_KEY is set
private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT");
private static final String MODEL_ID = System.getenv()
.getOrDefault("MODEL_ID", "gpt-3.5-turbo");
private static final String BING_API_KEY = System.getenv("BING_API_KEY");
private static final String GOOGLE_API_KEY = System.getenv("GOOGLE_API_KEY");
private static final String GOOGLE_SEARCH_ENGINE_ID = System.getenv("GOOGLE_SEARCH_ENGINE_ID");
public static void main(String[] args) {
OpenAIAsyncClient client;
if (AZURE_CLIENT_KEY != null) {
client = new OpenAIClientBuilder()
.credential(new AzureKeyCredential(AZURE_CLIENT_KEY))
.endpoint(CLIENT_ENDPOINT)
.buildAsyncClient();
} else {
client = new OpenAIClientBuilder()
.credential(new KeyCredential(CLIENT_KEY))
.buildAsyncClient();
}
// Load Bing plugin
var bingConnector = new BingConnector(BING_API_KEY);
var bing = KernelPluginFactory.createFromObject(new WebSearchEnginePlugin(bingConnector), "bing");
var chatCompletionService = ChatCompletionService.builder()
.withOpenAIAsyncClient(client)
.withModelId(MODEL_ID)
.build();
var kernel = Kernel.builder()
.withPlugin(bing)
.withAIService(ChatCompletionService.class, chatCompletionService)
.build();
example1Async(kernel, "bing");
example2Async(kernel);
// Load Google plugin
// WebSearchEngineConnector googleConnector = new GoogleConnector(GOOGLE_API_KEY, GOOGLE_SEARCH_ENGINE_ID);
// WebSearchEnginePlugin bing = new WebSearchEnginePlugin(googleConnector);
// kernel.importPluginFromObject(new WebSearchEnginePlugin(googleConnector), "google");
}
private static void example1Async(Kernel kernel, String searchPluginName)
{
System.out.println("======== Bing and Google Search Plugins ========");
// Run
var question = "What's the largest building in the world?";
var kernelArguments = KernelArguments.builder()
.withVariable("query", question)
.build();
var function = kernel.getPlugins().getFunction(searchPluginName, "search");
var result = kernel.invokeAsync(function, kernelArguments, String.class).block();
System.out.println(question);
System.out.printf("----%s----%n", searchPluginName);
System.out.println(result.getResult());
/* OUTPUT:
What's the largest building in the world?
----
The Aerium near Berlin, Germany is the largest uninterrupted volume in the world, while Boeing's
factory in Everett, Washington, United States is the world's largest building by volume. The AvtoVAZ
main assembly building in Tolyatti, Russia is the largest building in area footprint.
----
The Aerium near Berlin, Germany is the largest uninterrupted volume in the world, while Boeing's
factory in Everett, Washington, United States is the world's ...
*/
}
private static void example2Async(Kernel kernel)
{
System.out.println("======== Use Search Plugin to answer user questions ========");
var semanticFunction = """
Answer questions only when you know the facts or the information is provided.
When you don't have sufficient information you reply with a list of commands to find the information needed.
When answering multiple questions, use a bullet point list.
Note: make sure single and double quotes are escaped using a backslash char.
[COMMANDS AVAILABLE]
- bing.search
[INFORMATION PROVIDED]
{{ $externalInformation }}
[EXAMPLE 1]
Question: what's the biggest lake in Italy?
Answer: Lake Garda, also known as Lago di Garda.
[EXAMPLE 2]
Question: what's the biggest lake in Italy? What's the smallest positive number?
Answer:
* Lake Garda, also known as Lago di Garda.
* The smallest positive number is 1.
[EXAMPLE 3]
Question: what's Ferrari stock price? Who is the current number one female tennis player in the world?
Answer:
{{ '{{' }} bing.search "what's Ferrari stock price?" {{ '}}' }}.
{{ '{{' }} bing.search "Who is the current number one female tennis player in the world?" {{ '}}' }}.
[END OF EXAMPLES]
[TASK]
Question: {{ $question }}.
Answer:
""".stripIndent();
// The prompt function will append the answer here
var question = "Who is the most followed person on TikTok right now? What's the exchange rate EUR:USD?";
System.out.println(question);
var promptExecutionSettings = PromptExecutionSettings.builder()
.withMaxTokens(150)
.withTemperature(0)
.withTopP(1)
.build();
var oracle = KernelFunctionFromPrompt.builder()
.withTemplate(semanticFunction)
.withDefaultExecutionSettings(promptExecutionSettings)
.build();
var kernelArguments = KernelArguments.builder()
.withVariable("question", question)
.withVariable("externalInformation", "")
.build();
var answer = kernel.invokeAsync(oracle, kernelArguments, String.class).block();
var result = answer.getResult();
// If the answer contains commands, execute them using the prompt renderer.
if (result.contains("bing.search"))
{
PromptTemplate promptTemplate = new KernelPromptTemplateFactory()
.tryCreate(new PromptTemplateConfig(result));
System.out.println("---- Fetching information from Bing...");
var information = promptTemplate.renderAsync(kernel, null).block();
System.out.println("Information found:");
System.out.println(information);
kernelArguments = KernelArguments.builder()
.withVariable("question", question)
.withVariable("externalInformation", information)
.build();
// Run the prompt function again, now including information from Bing
answer = kernel.invokeAsync(oracle, kernelArguments, String.class).block();
}
else
{
System.out.println("AI had all the information, no need to query Bing.");
}
System.out.println("---- ANSWER:");
System.out.println(answer.getResult());
/* OUTPUT:
Who is the most followed person on TikTok right now? What's the exchange rate EUR:USD?
---- Fetching information from Bing...
Information found:
Khaby Lame is the most-followed user on TikTok. This list contains the top 50 accounts by number
of followers on the Chinese social media platform TikTok, which was merged with musical.ly in 2018.
[1] The most-followed individual on the platform is Khaby Lame, with over 153 million followers..
EUR – Euro To USD – US Dollar 1.00 Euro = 1.10 37097 US Dollars 1 USD = 0.906035 EUR We use the
mid-market rate for our Converter. This is for informational purposes only. You won’t receive this
rate when sending money. Check send rates Convert Euro to US Dollar Convert US Dollar to Euro..
---- ANSWER:
* The most followed person on TikTok right now is Khaby Lame, with over 153 million followers.
* The exchange rate for EUR to USD is 1.1037097 US Dollars for 1 Euro.
*/
}
}
@@ -0,0 +1,38 @@
package com.microsoft.semantickernel.samples.syntaxexamples;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments;
import com.microsoft.semantickernel.plugin.KernelPluginFactory;
import com.microsoft.semantickernel.plugins.web.SearchUrlPlugin;
public class Example11_WebSearchQueries {
public static void main(String[] args) {
var kernel = Kernel.builder().build();
// Load native plugins
var searchUrlPlugin = KernelPluginFactory.createFromObject(new SearchUrlPlugin(), "SearchUrlPlugin");
var bingSearchFunction = searchUrlPlugin.get("BingSearchUrl");
// Run
var ask = "What's the largest building in Europe?";
var kernelArguments = KernelArguments.builder()
.withVariable("query", ask)
.build();
var result = kernel.invokeAsync(bingSearchFunction, kernelArguments, String.class).block();
System.out.println(ask);
System.out.println(result.getResult());
/* Expected output:
* ======== WebSearchQueries ========
* What's the tallest building in Europe?
*
* https://www.bing.com/search?q=What%27s%20the%20tallest%20building%20in%20Europe%3F
* == DONE ==
*/
}
}
@@ -4,7 +4,7 @@ package plugins.searchplugin;
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 com.microsoft.semantickernel.connectors.web.bing.BingConnector;
import reactor.core.publisher.Mono;
@@ -25,7 +25,9 @@ public class Search {
public Mono<String> searchAsync(
@KernelFunctionParameter(description="The search query", name="query", type=String.class) String query
){
return bingConnector.searchAsync(query, 1, 0).map(results -> results.get(0));
return bingConnector
.searchAsync(query, 1, 0).map(results -> results.get(0))
.map(result -> result.getUrl());
}
}
+1 -1
View File
@@ -18,7 +18,7 @@
<dependencies>
<dependency>
<groupId>com.microsoft.semantic-kernel</groupId>
<artifactId>aiservices-azureopenai</artifactId>
<artifactId>aiservices-openai</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.semantic-kernel</groupId>
+5 -1
View File
@@ -47,12 +47,16 @@
</dependency>
<dependency>
<groupId>com.microsoft.semantic-kernel</groupId>
<artifactId>aiservices-azureopenai</artifactId>
<artifactId>aiservices-openai</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.semantic-kernel</groupId>
<artifactId>semantickernel-plugin-core</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.semantic-kernel</groupId>
<artifactId>semantickernel-connectors-web-bing</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.semantic-kernel</groupId>
<artifactId>aiservices-openai</artifactId>
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.connectors;
import reactor.core.publisher.Mono;
import java.util.List;
public interface WebSearchEngineConnector {
interface WebPage {
String getName();
String getUrl();
String getSnippet();
}
/**
* Execute a web search engine search.
* @param query Query to search.
* @param count Number of results. Defaults to 1. Must be between 1 and 50.
* @param offset Number of results to skip. Defaults to 0.
* @return First snippet returned from search.
*/
Mono<List<WebPage>> searchAsync(String query, int count, int offset);
}
@@ -15,14 +15,14 @@ public class KernelFunctionMetadata {
/// <summary>The function's return parameter.</summary>
private final KernelReturnParameterMetadata returnParameter;
private final KernelReturnParameterMetadata<?> returnParameter;
public KernelFunctionMetadata(
String name,
String description,
List<KernelParameterMetadata> parameters,
KernelReturnParameterMetadata returnParameter) {
KernelReturnParameterMetadata<?> returnParameter) {
this.name = name;
this.description = description;
this.parameters = parameters;
@@ -41,7 +41,7 @@ public class KernelFunctionMetadata {
return description;
}
public KernelReturnParameterMetadata getReturnParameter() {
public KernelReturnParameterMetadata<?> getReturnParameter() {
return returnParameter;
}
}
@@ -1,20 +1,41 @@
package com.microsoft.semantickernel.orchestration;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
public class PromptExecutionSettings {
public static final String DEFAULT_SERVICE_ID = "default";
public static final int DEFAULT_MAX_TOKENS = 256;
public static final double DEFAULT_TEMPERATURE = 1.0;
public static final double DEFAULT_TEMPERATURE= 1.0;
public static final double DEFAULT_TOP_P = 1.0;
public static final double DEFAULT_PRESENCE_PENALTY = 0.0;
public static final double DEFAULT_FREQUENCY_PENALTY = 0.0;
public static final int DEFAULT_BEST_OF = 1;
public static final int DEFAULT_RESULTS_PER_PROMPT = 1;
private static final String SERVICE_ID = "service_id";
private static final String MODEL_ID = "model_id";
private static final String TEMPERATURE = "temperature";
private static final String TOP_P = "top_p";
private static final String PRESENCE_PENALTY = "presence_penalty";
private static final String FREQUENCY_PENALTY = "frequency_penalty";
private static final String MAX_TOKENS = "max_tokens";
private static final String BEST_OF = "best_of";
private static final String USER = "user";
private static final String STOP_SEQUENCES = "stop_sequences";
private static final String RESULTS_PER_PROMPT = "results_per_prompt";
private static final String TOKEN_SELECTION_BIASES = "token_selection_biases";
/// <summary>
/// Service identifier.
/// This identifies a service and is set when the AI service is registered.
@@ -28,7 +49,6 @@ public class PromptExecutionSettings {
private final String modelId;
private final double temperature;
private final double topP;
private final double presencePenalty;
private final double frequencyPenalty;
@@ -37,182 +57,243 @@ public class PromptExecutionSettings {
private final int resultsPerPrompt;
private final String user;
private final List<String> stopSequences;
private final ToolCallBehavior toolCallBehavior;
/// <summary>
/// Modify the likelihood of specified tokens appearing in the completion.
/// </summary>s
public Map<Integer, Integer> tokenSelectionBiases;
@JsonCreator
public PromptExecutionSettings(
@JsonProperty("service_id") String serviceId,
@JsonProperty("model_id") String modelId,
@JsonProperty("temperature") double temperature,
@JsonProperty("top_p") double topP,
@JsonProperty("presence_penalty") double presencePenalty,
@JsonProperty("frequency_penalty") double frequencyPenalty,
@JsonProperty("max_tokens") int maxTokens,
@JsonProperty("results_per_prompt") int resultsPerPrompt,
@JsonProperty("best_of") int bestOf,
@JsonProperty("user") String user,
@JsonProperty(value = "stop_sequences") List<String> stopSequences,
@JsonProperty(value = "token_selection_biases") Map<Integer, Integer> tokenSelectionBiases) {
this.serviceId = serviceId;
this.modelId = modelId;
this.temperature = temperature;
this.topP = topP;
this.presencePenalty = presencePenalty;
this.frequencyPenalty = frequencyPenalty;
this.maxTokens = maxTokens;
this.resultsPerPrompt = resultsPerPrompt;
this.bestOf = bestOf;
this.user = user;
this.stopSequences = stopSequences;
this.tokenSelectionBiases = tokenSelectionBiases;
@JsonProperty(SERVICE_ID) String serviceId,
@JsonProperty(MODEL_ID) String modelId,
@JsonProperty(TEMPERATURE) double temperature,
@JsonProperty(TOP_P) double topP,
@JsonProperty(PRESENCE_PENALTY) double presencePenalty,
@JsonProperty(FREQUENCY_PENALTY) double frequencyPenalty,
@JsonProperty(MAX_TOKENS) int maxTokens,
@JsonProperty(RESULTS_PER_PROMPT) int resultsPerPrompt,
@JsonProperty(BEST_OF) int bestOf,
@JsonProperty(USER) String user,
@JsonProperty(STOP_SEQUENCES) List<String> stopSequences,
@JsonProperty(TOKEN_SELECTION_BIASES) Map<Integer, Integer> tokenSelectionBiases) {
this(
serviceId,
modelId,
temperature,
topP,
presencePenalty,
frequencyPenalty,
maxTokens,
resultsPerPrompt,
bestOf,
user,
stopSequences,
tokenSelectionBiases,
null);
}
public PromptExecutionSettings(
String serviceId,
String modelId,
double temperature,
double topP,
double presencePenalty,
double frequencyPenalty,
int maxTokens,
int resultsPerPrompt,
int bestOf,
String user,
@Nullable List<String> stopSequences,
@Nullable Map<Integer, Integer> tokenSelectionBiases,
@Nullable ToolCallBehavior toolCallBehavior) {
this.serviceId = serviceId;
this.modelId = modelId;
this.temperature = temperature;
this.topP = topP;
this.presencePenalty = presencePenalty;
this.frequencyPenalty = frequencyPenalty;
this.maxTokens = maxTokens;
this.resultsPerPrompt = resultsPerPrompt;
this.bestOf = bestOf;
this.user = user;
this.stopSequences = stopSequences != null ? stopSequences : Collections.emptyList();
this.tokenSelectionBiases = tokenSelectionBiases != null ? tokenSelectionBiases : Collections.emptyMap();
this.toolCallBehavior = toolCallBehavior;
}
@JsonProperty(SERVICE_ID)
public String getServiceId() {
return serviceId;
}
@JsonProperty(MODEL_ID)
public String getModelId() {
return modelId;
}
@JsonProperty(TEMPERATURE)
public double getTemperature() {
return Double.isNaN(temperature) ? DEFAULT_TEMPERATURE : temperature;
}
@JsonProperty(TOP_P)
public double getTopP() {
return topP;
}
@JsonProperty(PRESENCE_PENALTY)
public double getPresencePenalty() {
return presencePenalty;
}
@JsonProperty(FREQUENCY_PENALTY)
public double getFrequencyPenalty() {
return frequencyPenalty;
}
@JsonProperty(MAX_TOKENS)
public int getMaxTokens() {
return maxTokens;
}
@JsonProperty(RESULTS_PER_PROMPT)
public int getResultsPerPrompt() {
return resultsPerPrompt;
}
@JsonProperty(BEST_OF)
public int getBestOf() {
// TODO: not present in com.azure:azure-ai-openai
return bestOf;
}
@JsonProperty(USER)
public String getUser() {
return user;
}
@JsonProperty(STOP_SEQUENCES)
public List<String> getStopSequences() {
return stopSequences;
}
@JsonProperty(TOKEN_SELECTION_BIASES)
public Map<Integer, Integer> getTokenSelectionBiases() {
return tokenSelectionBiases;
}
@JsonIgnore
@Nullable
public ToolCallBehavior getToolCallBehavior() {
return toolCallBehavior;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String serviceId;
private String modelId;
private double temperature = Double.NaN;
private double topP = Double.NaN;
private double presencePenalty = Double.NaN;
private double frequencyPenalty = Double.NaN;
private int maxTokens = Integer.MIN_VALUE;
private int resultsPerPrompt = Integer.MIN_VALUE;
private int bestOf = Integer.MIN_VALUE;
private String user;
private List<String> stopSequences;
public Map<Integer, Integer> tokenSelectionBiases;
Map<String, Object> settings = new HashMap<>();
public Builder withServiceId(String serviceId) {
this.serviceId = serviceId;
settings.put(SERVICE_ID, serviceId);
return this;
}
public Builder withModelId(String modelId) {
this.modelId = modelId;
settings.put(MODEL_ID, modelId);
return this;
}
public Builder withTemperature(double temperature) {
this.temperature = temperature;
if (!Double.isNaN(temperature)) {
settings.put(TEMPERATURE, temperature);
}
return this;
}
public Builder withTopP(double topP) {
this.topP = topP;
if (!Double.isNaN(topP)) {
settings.put(TOP_P, topP);
}
return this;
}
public Builder withPresencePenalty(double presencePenalty) {
this.presencePenalty = presencePenalty;
if (!Double.isNaN(presencePenalty)) {
settings.put(PRESENCE_PENALTY, presencePenalty);
}
return this;
}
public Builder withFrequencyPenalty(double frequencyPenalty) {
this.frequencyPenalty = frequencyPenalty;
if (!Double.isNaN(frequencyPenalty)) {
settings.put(FREQUENCY_PENALTY, frequencyPenalty);
}
return this;
}
public Builder withMaxTokens(int maxTokens) {
this.maxTokens = maxTokens;
settings.put(MAX_TOKENS, maxTokens);
return this;
}
public Builder withResultsPerPrompt(int resultsPerPrompt) {
this.resultsPerPrompt = resultsPerPrompt;
settings.put(RESULTS_PER_PROMPT, resultsPerPrompt);
return this;
}
public Builder withBestOf(int bestOf) {
this.bestOf = bestOf;
settings.put(BEST_OF, bestOf);
return this;
}
public Builder withUser(String user) {
this.user = user;
settings.put(USER, user);
return this;
}
public Builder withToolCallBehavior(ToolCallBehavior toolCallBehavior) {
settings.put("toolCallBehavior", toolCallBehavior);
return this;
}
@SuppressWarnings("unchecked")
public Builder withStopSequences(List<String> stopSequences) {
this.stopSequences = stopSequences;
if (stopSequences != null) {
((List<String>)settings.computeIfAbsent(STOP_SEQUENCES, k -> new ArrayList<>())).addAll(stopSequences);
}
return this;
}
@SuppressWarnings("unchecked")
public Builder withTokenSelectionBiases(Map<Integer, Integer> tokenSelectionBiases) {
this.tokenSelectionBiases = tokenSelectionBiases;
if (tokenSelectionBiases != null) {
((Map<Integer, Integer>)settings.computeIfAbsent(TOKEN_SELECTION_BIASES, k -> new HashMap<>())).putAll(tokenSelectionBiases);
}
return this;
}
@SuppressWarnings("unchecked")
public PromptExecutionSettings build() {
return new PromptExecutionSettings(
serviceId,
modelId,
Double.isNaN(temperature) ? DEFAULT_TEMPERATURE : temperature,
Double.isNaN(topP) ? DEFAULT_TOP_P : topP,
Double.isNaN(presencePenalty) ? DEFAULT_PRESENCE_PENALTY : presencePenalty,
Double.isNaN(frequencyPenalty) ? DEFAULT_FREQUENCY_PENALTY : frequencyPenalty,
maxTokens == Integer.MIN_VALUE ? DEFAULT_MAX_TOKENS : maxTokens,
resultsPerPrompt == Integer.MIN_VALUE ? DEFAULT_RESULTS_PER_PROMPT : resultsPerPrompt,
bestOf == Integer.MIN_VALUE ? DEFAULT_BEST_OF : bestOf,
user,
stopSequences,
tokenSelectionBiases
(String)settings.getOrDefault(SERVICE_ID, ""),
(String)settings.getOrDefault(MODEL_ID, ""),
(double)settings.getOrDefault(TEMPERATURE, DEFAULT_TEMPERATURE),
(double)settings.getOrDefault(TOP_P, DEFAULT_TOP_P),
(double)settings.getOrDefault(PRESENCE_PENALTY, DEFAULT_PRESENCE_PENALTY),
(double)settings.getOrDefault(FREQUENCY_PENALTY, DEFAULT_FREQUENCY_PENALTY),
(int)settings.getOrDefault(MAX_TOKENS, DEFAULT_MAX_TOKENS),
(int)settings.getOrDefault(RESULTS_PER_PROMPT, DEFAULT_RESULTS_PER_PROMPT),
(int)settings.getOrDefault(BEST_OF, DEFAULT_BEST_OF),
(String)settings.getOrDefault(USER, ""),
(List<String>)settings.getOrDefault(STOP_SEQUENCES, Collections.emptyList()),
(Map<Integer, Integer>)settings.getOrDefault(TOKEN_SELECTION_BIASES, Collections.emptyMap()),
(ToolCallBehavior)settings.getOrDefault("toolCallBehavior", new ToolCallBehavior())
);
}
}
@@ -0,0 +1,158 @@
package com.microsoft.semantickernel.orchestration;
import java.util.HashMap;
import java.util.Map;
/**
* Defines the behavior of a tool call. Currently, the only tool available is function calling.
*/
public class ToolCallBehavior {
private static final int DEFAULT_MAXIMUM_AUTO_INVOKE_ATTEMPTS = 5;
private static class FunctionCallBehavior {
private final String key;
private final boolean required;
private final boolean enabled;
FunctionCallBehavior(KernelFunction function, boolean required, boolean enabled) {
this.key = getKey(function);
this.required = required;
this.enabled = enabled;
}
boolean required() {
return required;
}
boolean enabled() {
return enabled;
}
static String getKey(KernelFunction function) {
return getKey(function.getSkillName(), function.getName());
}
static String getKey(String skillName, String functionName) {
return String.format("%s-%s", skillName, functionName);
}
}
private final Map<String, Boolean> flags = new HashMap<>();
private final Map<String, FunctionCallBehavior> functionSettings = new HashMap<>();
private final Map<String, Integer> settings = new HashMap<>();
public ToolCallBehavior() {
}
public ToolCallBehavior kernelFunctions(boolean enable) {
setFlag("kernelFunctions", enable);
return this;
}
public ToolCallBehavior autoInvoke(boolean enable) {
setFlag("autoInvoke", true);
return this;
}
public ToolCallBehavior requireFunction(KernelFunction function, boolean require) {
if (function != null) {
String key = FunctionCallBehavior.getKey(function);
functionSettings.compute(
key,
(k,v) -> {
if (v == null) {
return new FunctionCallBehavior(function, require, false);
} else {
return new FunctionCallBehavior(function, require, v.enabled());
}
}
);
}
return this;
}
public ToolCallBehavior enableFunction(KernelFunction function, boolean enable) {
if (function != null) {
String key = FunctionCallBehavior.getKey(function);
functionSettings.compute(
key,
(k,v) -> {
if (v == null) {
return new FunctionCallBehavior(function, false, enable);
} else {
return new FunctionCallBehavior(function, v.required(), enable);
}
}
);
}
return this;
}
public ToolCallBehavior maximumAutoInvokeAttempts(int maximumAutoInvokeAttempts) {
setSetting("maximumAutoInvokeAttempts", maximumAutoInvokeAttempts);
return this;
}
public boolean kernelFunctionsEnabled() {
return getFlag("kernelFunctions");
}
public boolean autoInvokeEnabled() {
return getFlag("autoInvoke");
}
public boolean functionRequired(KernelFunction function) {
if (function != null) {
return functionRequired(function.getSkillName(), function.getName());
}
return false;
}
public boolean functionRequired(String pluginName, String functionName) {
String key = FunctionCallBehavior.getKey(pluginName, functionName);
if (functionSettings.containsKey(key)) {
return functionSettings.get(key).required();
}
return false;
}
public boolean functionEnabled(KernelFunction function) {
if (function != null) {
return functionEnabled(function.getSkillName(), function.getName());
}
return functionSettings.isEmpty();
}
public boolean functionEnabled(String pluginName, String functionName) {
String key = FunctionCallBehavior.getKey(pluginName, functionName);
if (functionSettings.containsKey(key)) {
return functionSettings.get(key).enabled();
}
return functionSettings.isEmpty();
}
public int maximumAutoInvokeAttempts() {
return getSetting("maximumAutoInvokeAttempts", DEFAULT_MAXIMUM_AUTO_INVOKE_ATTEMPTS);
}
protected void setFlag(String key, boolean value) {
flags.put(key, value);
}
protected void setSetting(String key, int value) {
settings.put(key, value);
}
protected boolean getFlag(String key) {
return flags.getOrDefault(key, false);
}
protected int getSetting(String key, int defaultValue) {
return settings.getOrDefault(key, defaultValue);
}
}
@@ -41,13 +41,14 @@ public class ContextVariable<T> {
}
public ContextVariable<T> cloneVariable() {
return new ContextVariable<T>(type, value);
return new ContextVariable<>(type, value);
}
@SuppressWarnings("unchecked")
public static <T> ContextVariable<T> of(T value) {
ContextVariableType<T> type = ContextVariableTypes.getDefaultVariableTypeForClass(
(Class<T>) value.getClass());
return new ContextVariable<T>(type, value);
return new ContextVariable<>(type, value);
}
public static <T> ContextVariable<T> of(T value, ContextVariableTypeConverter<T> converter) {
@@ -78,7 +78,9 @@ public class ContextVariableTypeConverter<T> {
this.toObjects = toObjects;
}
@Nullable
@SuppressWarnings("unchecked")
public <U> U toObject(Object t, Class<U> clazz) {
Optional<Converter<T, ?>> converter = toObjects
.stream()
@@ -103,14 +105,23 @@ public class ContextVariableTypeConverter<T> {
}
public T fromObject(Object s) {
if (s == null) {
return null;
}
return fromObject.apply(s);
}
public String toPromptString(T t) {
if (t == null) {
return "";
}
return toPromptString.apply(t);
}
public T fromPromptString(String t) {
if (t == null) {
return null;
}
return fromPromptString.apply(t);
}
@@ -64,13 +64,14 @@ public class ContextVariableTypes {
contextVariableTypeConverter.getType()));
}
@SuppressWarnings("unchecked")
public static <T> T convert(Object s, Class<T> clazz) {
if (s instanceof ContextVariable && ((ContextVariable<?>) s).getType().getClazz()
.isAssignableFrom(clazz)) {
return ((ContextVariable<T>) s).getValue();
}
if (s != null && clazz.isAssignableFrom(s.getClass())) {
return (T) s;
return clazz.cast(s);
} else {
return null;
}
@@ -1,9 +1,11 @@
package com.microsoft.semantickernel.orchestration.contextvariables.converters;
import static com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes.convert;
import java.util.function.Function;
import javax.annotation.Nullable;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter;
import java.util.function.Function;
import static com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes.convert;
public class NumberVariableContextVariableTypeConverter<T extends Number> extends
ContextVariableTypeConverter<T> {
@@ -16,4 +18,42 @@ public class NumberVariableContextVariableTypeConverter<T extends Number> extend
Number::toString,
fromPromptString);
}
@Override
@Nullable
public <U> U toObject(Object t, Class<U> clazz) {
// Let the parent class have a crack at it first
// since someone may have installed a special converter.
U obj = super.toObject(t, clazz);
if (obj != null) {
return obj;
}
// If the type is a string, and the object is of the same type as the
// converter, then we can convert with the toPromptString method.
if (clazz == String.class && t.getClass() == super.getType()) {
return clazz.cast(toPromptString(getType().cast(t)));
}
return null;
}
@Override
public T fromObject(Object s) {
T obj = super.fromObject(s);
if (obj != null) {
return obj;
}
if (s instanceof String) {
return fromPromptString((String) s);
}
return null;
}
@Override
public T fromPromptString(String s) {
if (s != null && !s.isEmpty()) {
return super.fromPromptString(s);
}
return null;
}
}
@@ -31,9 +31,10 @@ public class KernelFunctionFactory {
@Nullable String functionName,
@Nullable String description,
List<KernelParameterMetadata> parameters,
KernelReturnParameterMetadata returnParameter) {
KernelReturnParameterMetadata<?> returnParameter) {
return createFromMethod(
method,
null,
functionName,
description,
parameters,
@@ -58,7 +59,7 @@ public class KernelFunctionFactory {
@Nullable String functionName,
@Nullable String description,
@Nullable List<KernelParameterMetadata> parameters,
@Nullable KernelReturnParameterMetadata returnParameter) {
@Nullable KernelReturnParameterMetadata<?> returnParameter) {
return KernelFunctionFromMethod.create(method, target, functionName, description,
parameters, returnParameter);
}
@@ -13,13 +13,13 @@ public class KernelParameterMetadata {
public KernelParameterMetadata(
String name,
String description,
String defaultValue,
boolean isRequired) {
Class<?> parameterType,
String defaultValue, boolean isRequired) {
this.name = name;
this.description = description;
this.defaultValue = defaultValue;
this.isRequired = isRequired;
this.parameterType = String.class; // TODO
this.parameterType = String.class;
}
public String getName() {
@@ -142,7 +142,7 @@ public class KernelPluginFactory {
KernelFunctionParameter.class);
return new KernelParameterMetadata(annotation.name(), annotation.description(),
annotation.defaultValue(), annotation.required());
null, annotation.defaultValue(), annotation.required());
}).collect(Collectors.toList());
}
@@ -19,6 +19,7 @@ import com.microsoft.semantickernel.plugin.KernelParameterMetadata;
import com.microsoft.semantickernel.plugin.KernelReturnParameterMetadata;
import com.microsoft.semantickernel.plugin.annotations.DefineKernelFunction;
import com.microsoft.semantickernel.plugin.annotations.KernelFunctionParameter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
@@ -47,7 +48,7 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
String functionName,
String description,
List<KernelParameterMetadata> parameters,
KernelReturnParameterMetadata returnParameter) {
KernelReturnParameterMetadata<?> returnParameter) {
super(
new KernelFunctionMetadata(
functionName,
@@ -84,7 +85,7 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
String functionName,
String description,
List<KernelParameterMetadata> parameters,
KernelReturnParameterMetadata returnParameter) {
KernelReturnParameterMetadata<?> returnParameter) {
MethodDetails methodDetails = getMethodDetails(functionName, method, target);
@@ -246,7 +247,15 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
KernelFunctionParameter annotation =
parameter.getAnnotation(KernelFunctionParameter.class);
if (annotation != null) {
arg = ContextVariable.of(annotation.defaultValue());
// Convert from the defaultValue, which is a String to the argument type
// Expectation here is that the fromPromptString method will be able to handle a null or empty string
Class<?> type = annotation.type();
ContextVariableType<?> cvType = ContextVariableTypes.getDefaultVariableTypeForClass(type);
if (cvType != null) {
String defaultValue = annotation.defaultValue();
Object value = cvType.getConverter().fromPromptString(defaultValue);
arg = ContextVariable.of(value);
}
if (NO_DEFAULT_VALUE.equals(arg)) {
if (!annotation.required()) {
@@ -490,8 +499,8 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction {
return new KernelParameterMetadata(
name,
description,
defaultValue,
isRequired
null,
defaultValue, isRequired
);
}
}
@@ -116,8 +116,8 @@ public class PromptTemplateConfig {
.map(inputVariable -> new KernelParameterMetadata(
inputVariable.getName(),
inputVariable.getDescription(),
inputVariable.getDefaultValue(),
inputVariable.isRequired()
null,
inputVariable.getDefaultValue(), inputVariable.isRequired()
))
.collect(Collectors.toList());
}
@@ -0,0 +1,91 @@
package com.microsoft.semantickernel.orchestration.contextvariables.converters;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypeConverter;
import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes;
public class NumberVariableContextVariableTypeConverterTest {
/**
* Test of toObject method, of class NumberVariableContextVariableTypeConverter.
*/
@Test
void testConvertIntegerToString() {
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
Object expResult = "1";
Object result = instance.toObject(1, String.class);
assertEquals(expResult, result);
}
/**
* Test of fromObject method, of class NumberVariableContextVariableTypeConverter.
*/
@Test
void testFromStringToInteger() {
Object s = "123";
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
Number expResult = Integer.valueOf((String)s);
Number result = instance.fromObject(s);
assertEquals(expResult, result);
}
@Test
void testFromPromptStringToInteger() {
String s = "123";
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
Number expResult = Integer.valueOf(s);
Number result = instance.fromPromptString(s);
assertEquals(expResult, result);
}
@Test
void testFromPromptStringReturnsNullForEmptyString() {
String s = "";
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
Number expResult = null;
Number result = instance.fromPromptString(s);
assertEquals(expResult, result);
}
@Test
void testFromPromptStringReturnsNullForNullString() {
String s = null;
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
Number expResult = null;
Number result = instance.fromPromptString(s);
assertEquals(expResult, result);
}
@Test
void testToPromptString() {
Integer num = 123;
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
String expResult = "123";
String result = instance.toPromptString(num);
assertEquals(expResult, result);
}
@Test
void testToPromptStringReturnsEmptyStringForNull() {
Integer num = null;
ContextVariableType<Integer> type = ContextVariableTypes.getDefaultVariableTypeForClass(Integer.class);
ContextVariableTypeConverter<Integer> instance = type.getConverter();
String expResult = "";
String result = instance.toPromptString(num);
assertEquals(expResult, result);
}
}
+5
View File
@@ -71,6 +71,11 @@
<artifactId>semantickernel-plugin-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.microsoft.semantic-kernel</groupId>
<artifactId>semantickernel-connectors-web-bing</artifactId>
<version>${project.version}</version>
</dependency>
<!--
<dependency>
<groupId>com.microsoft.semantic-kernel</groupId>