Add settings retrieval through properties file and env vars (#1361)

### Motivation and Context

Add settings retrieval through properties file and env vars

### Description
<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->

Adds util.Settings to load settings values from a properties file or
from the environment.

Also, the ability to have different client settings in the properties
file and load them by their specific schema Id:
```yaml
client.azureopenai.key: "AZURE_OPEN_AI_KEY"
client.azureopenai.endpoint: "AZURE_OPEN_AI_ENDPOINT"
client.azureopenai.deploymentname: "AZURE_OPEN_AI_DEPLOYMENT_NAME"

client.myazureopenai.key: "AZURE_OPEN_AI_KEY"
client.myazureopenai.endpoint: "AZURE_OPEN_AI_ENDPOINT"
client.myazureopenai.deploymentname: "AZURE_OPEN_AI_DEPLOYMENT_NAME"
```

By calling `Settings.getAzureOpenAISettingsFromFile(CONF_PROPERTIES)`
the user would be able to load the Azure settings using the default
schema id, as shown in the block at the top.

By calling `Settings.getAzureOpenAISettingsFromFile(CONF_PROPERTIES,
"myazureopenai")` the user would be able to load a different Azure
configuration by just adding the second block to the properties file.

### Contribution Checklist
<!-- Before submitting this PR, please make sure: -->
- [ ] The code builds clean without any errors or warnings
- [ ] The PR follows SK Contribution Guidelines
(https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
- [ ] The code follows the .NET coding conventions
(https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions)
verified with `dotnet format`
- [ ] All unit tests pass, and I have added new tests where possible
- [ ] I didn't break anyone 😄

---------

Co-authored-by: Luigi Montoya <[email protected]>
Co-authored-by: joe-braley <[email protected]>
Co-authored-by: Luigi96 <[email protected]>
Co-authored-by: Mark Wallace <[email protected]>
Co-authored-by: John Oliver <[email protected]>
Co-authored-by: David Grieve <[email protected]>
This commit is contained in:
Milder Hernandez
2023-06-07 18:54:36 +01:00
committed by GitHub
co-authored by Luigi Montoya joe-braley Luigi96 Mark Wallace John Oliver David Grieve
parent a720809a89
commit 1714c25ebe
17 changed files with 300 additions and 97 deletions
@@ -1,2 +0,0 @@
token: "Your Azure OpenAI Key"
endpoint: "Azure OpenAI endpoint"
@@ -1 +0,0 @@
token: "Your OpenAI Key"
+6
View File
@@ -0,0 +1,6 @@
client.openai.key: "OPEN_AI_KEY"
client.openai.organizationid: "OPEN_AI_ORGANIZATION_ID"
client.azureopenai.key: "AZURE_OPEN_AI_KEY"
client.azureopenai.endpoint: "AZURE_OPEN_AI_ENDPOINT"
client.azureopenai.deploymentname: "AZURE_OPEN_AI_DEPLOYMENT_NAME"
@@ -4,59 +4,46 @@ import com.azure.core.credential.AzureKeyCredential;
import com.microsoft.openai.AzureOpenAIClient;
import com.microsoft.openai.OpenAIAsyncClient;
import com.microsoft.openai.OpenAIClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.microsoft.semantickernel.util.AzureOpenAISettings;
import com.microsoft.semantickernel.util.AIProviderSettings;
import com.microsoft.semantickernel.util.OpenAISettings;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class Config {
private static final Logger LOGGER = LoggerFactory.getLogger(Config.class);
public static final String CONF_PROPERTIES = "java/samples/conf.properties";
public static final String OPEN_AI_CONF_PROPERTIES = "samples/java/semantickernel-samples/conf.openai.properties";
public static final String AZURE_CONF_PROPERTIES = "samples/java/semantickernel-samples/conf.azure.properties";
public enum ClientType {
OPEN_AI {
@Override
public OpenAIAsyncClient getClient() throws IOException {
OpenAISettings settings = AIProviderSettings.getOpenAISettingsFromFile(CONF_PROPERTIES);
return new OpenAIClientBuilder()
.setApiKey(settings.getKey())
.build();
}
},
AZURE_OPEN_AI {
@Override
public OpenAIAsyncClient getClient()
throws IOException {
AzureOpenAISettings settings = AIProviderSettings.getAzureOpenAISettingsFromFile(CONF_PROPERTIES);
public static String getOpenAIKey(String conf) {
return getConfigValue(conf, "token");
}
return new AzureOpenAIClient(
new com.azure.ai.openai.OpenAIClientBuilder()
.endpoint(settings.getEndpoint())
.credential(new AzureKeyCredential(settings.getKey()))
.buildAsyncClient());
}
};
public static String getAzureOpenAIEndpoint(String conf) {
return getConfigValue(conf, "endpoint");
}
private static String getConfigValue(String configFile, String propertyName) {
File config = new File(configFile);
try (FileInputStream fis = new FileInputStream(config.getAbsolutePath())) {
Properties props = new Properties();
props.load(fis);
return props.getProperty(propertyName);
} catch (IOException e) {
LOGGER.error("Unable to load config value " + propertyName + " from file" + configFile, e);
throw new RuntimeException(configFile + " not configured properly");
}
}
/**
* Returns the client that will handle AzureOpenAI or OpenAI requests.
*
* @param useAzureOpenAI whether to use AzureOpenAI or OpenAI.
* @return client to be used by the kernel.
*/
public static OpenAIAsyncClient getClient(boolean useAzureOpenAI) {
if (useAzureOpenAI) {
return new AzureOpenAIClient(
new com.azure.ai.openai.OpenAIClientBuilder()
.endpoint(Config.getAzureOpenAIEndpoint(AZURE_CONF_PROPERTIES))
.credential(new AzureKeyCredential(Config.getOpenAIKey(AZURE_CONF_PROPERTIES)))
.buildAsyncClient());
}
return new OpenAIClientBuilder()
.setApiKey(Config.getOpenAIKey(OPEN_AI_CONF_PROPERTIES))
.build();
/**
* Returns the client that will handle AzureOpenAI or OpenAI requests.
*
* @return client to be used by the kernel.
*/
public abstract OpenAIAsyncClient getClient() throws IOException;
}
}
@@ -1,7 +1,6 @@
package com.microsoft.semantickernel;
import com.microsoft.openai.OpenAIAsyncClient;
import com.microsoft.openai.OpenAIClientBuilder;
import com.microsoft.semantickernel.builders.SKBuilders;
import com.microsoft.semantickernel.extensions.KernelExtensions;
import com.microsoft.semantickernel.skilldefinition.ReadOnlyFunctionCollection;
@@ -9,17 +8,17 @@ import com.microsoft.semantickernel.textcompletion.CompletionSKContext;
import com.microsoft.semantickernel.textcompletion.CompletionSKFunction;
import reactor.core.publisher.Mono;
import java.io.IOException;
/**
* Getting started
*
* First create a configuration file based on the examples files at the root of this module:
* conf.azure.properties if using Azure OpenAI
* conf.openai.properties if using OpenAI
* Create a conf.properties file based on the examples files at the root of this module.
*
* <a href="https://learn.microsoft.com/en-us/azure/cognitive-services/openai/quickstart">Get started with Azure OpenAI</a>
* <a href="https://openai.com/product">Get started with OpenAI</a>
*/
public class Example00GettingStarted {
public class Example00_GettingStarted {
/**
* Returns a Semantic Kernel with Text Completion.
@@ -60,14 +59,13 @@ public class Example00GettingStarted {
}
}
public static void run (boolean useAzureOpenAI) {
OpenAIAsyncClient client = Config.getClient(useAzureOpenAI);
Kernel kernel = getKernel(client);
public static void run (Config.ClientType clientType) throws IOException {
Kernel kernel = getKernel(clientType.getClient());
joke(kernel);
}
public static void main (String args[]) {
// Send whether AzureOpenAI will be used. If false, OpenAI will be used.
run(false);
public static void main (String args[]) throws IOException {
// Send one of Config.ClientType.OPEN_AI or Config.ClientType.AZURE_OPEN_AI
run(Config.ClientType.OPEN_AI);
}
}
@@ -14,7 +14,7 @@ import java.io.IOException;
*
* The repository includes some examples under the <a href="https://github.com/microsoft/semantic-kernel/tree/main/samples">samples</a> folder.
*/
public class Example02RunnningPromptsFromFile {
public class Example02_RunnningPromptsFromFile {
/**
* Imports skill 'FunSkill' stored in the samples folder and then returns the semantic function 'Joke' within it.
@@ -30,15 +30,15 @@ public class Example02RunnningPromptsFromFile {
return skill.getFunction("Joke", CompletionSKFunction.class);
}
public static void run (boolean useAzureOpenAI) {
OpenAIAsyncClient client = Config.getClient(useAzureOpenAI);
Kernel kernel = Example00GettingStarted.getKernel(client);
public static void run (Config.ClientType clientType) throws IOException {
Kernel kernel = Example00_GettingStarted.getKernel(clientType.getClient());
CompletionSKFunction jokeFunction = getJokeFunction(kernel);
System.out.println(jokeFunction.invokeAsync("time travel to dinosaur age").block().getResult());
}
public static void main (String args[]) throws IOException {
run(false);
// Send one of Config.ClientType.OPEN_AI or Config.ClientType.AZURE_OPEN_AI
run(Config.ClientType.OPEN_AI);
}
}
@@ -12,7 +12,7 @@ import java.util.ArrayList;
/**
* Define a Semantic Function inline with Java code.
*/
public class Example03SemanticFunctionInline {
public class Example03_SemanticFunctionInline {
/**
* Defines and runs an inline Semantic Function
@@ -115,15 +115,15 @@ public class Example03SemanticFunctionInline {
inlineFunction(kernel, propmt, "tldr", text);
}
public static void run(boolean useAzureOpenAI) {
OpenAIAsyncClient client = Config.getClient(useAzureOpenAI);
Kernel kernel = Example00GettingStarted.getKernel(client);
public static void run (Config.ClientType clientType) throws IOException {
Kernel kernel = Example00_GettingStarted.getKernel(clientType.getClient());
summarize(kernel);
TLDR(kernel);
}
public static void main(String args[]) throws IOException {
run(false);
// Send one of Config.ClientType.OPEN_AI or Config.ClientType.AZURE_OPEN_AI
run(Config.ClientType.OPEN_AI);
}
}
@@ -6,6 +6,7 @@ import com.microsoft.semantickernel.textcompletion.CompletionSKContext;
import com.microsoft.semantickernel.textcompletion.CompletionSKFunction;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
@@ -17,7 +18,7 @@ import java.util.function.Function;
* Context Variables object which in this demo functions similarly as a key-value store that you can use when running the kernel.
* The context is local (i.e. in your computer's RAM) and not persisted anywhere beyond the life of the JVM execution.
*/
public class Example04ContextVariablesChat {
public class Example04_ContextVariablesChat {
public static void startChat (Kernel kernel)
throws ExecutionException, InterruptedException, TimeoutException {
String prompt ="""
@@ -98,16 +99,15 @@ public class Example04ContextVariablesChat {
};
}
public static void run (boolean useAzureOpenAI)
throws ExecutionException, InterruptedException, TimeoutException {
OpenAIAsyncClient client = Config.getClient(useAzureOpenAI);
Kernel kernel = Example00GettingStarted.getKernel(client);
public static void run (Config.ClientType clientType) throws IOException, ExecutionException, InterruptedException, TimeoutException {
Kernel kernel = Example00_GettingStarted.getKernel(clientType.getClient());
startChat(kernel);
}
public static void main (String[] args)
throws ExecutionException, InterruptedException, TimeoutException {
run(false);
throws ExecutionException, InterruptedException, TimeoutException, IOException {
// Send one of Config.ClientType.OPEN_AI or Config.ClientType.AZURE_OPEN_AI
run(Config.ClientType.OPEN_AI);
}
}
@@ -4,7 +4,9 @@ import com.microsoft.openai.OpenAIAsyncClient;
import com.microsoft.semantickernel.extensions.KernelExtensions;
import com.microsoft.semantickernel.planner.sequentialplanner.SequentialPlanner;
public class Example05UsingThePlanner {
import java.io.IOException;
public class Example05_UsingThePlanner {
public static SequentialPlanner getPlanner(Kernel kernel) {
kernel.importSkill("SummarizeSkill", KernelExtensions.importSemanticSkillFromDirectory(
@@ -15,9 +17,8 @@ public class Example05UsingThePlanner {
return new SequentialPlanner(kernel, null, null);
}
public static void run(boolean useAzureOpenAI) {
OpenAIAsyncClient client = Config.getClient(useAzureOpenAI);
Kernel kernel = Example00GettingStarted.getKernel(client);
public static void run (Config.ClientType clientType) throws IOException {
Kernel kernel = Example00_GettingStarted.getKernel(clientType.getClient());
SequentialPlanner planner = getPlanner(kernel);
System.out.println(planner.createPlanAsync(
@@ -27,8 +28,9 @@ public class Example05UsingThePlanner {
// TODO: execute the plan
}
public static void main(String[] args) {
run(true);
public static void main(String[] args) throws IOException {
// Send one of Config.ClientType.OPEN_AI or Config.ClientType.AZURE_OPEN_AI
run(Config.ClientType.OPEN_AI);
}
}
@@ -3,7 +3,9 @@ package com.microsoft.semantickernel;
import com.microsoft.openai.OpenAIAsyncClient;
import com.microsoft.semantickernel.builders.SKBuilders;
public class Example06MemoryAndEmbeddings {
import java.io.IOException;
public class Example06_MemoryAndEmbeddings {
public static Kernel getKernel(OpenAIAsyncClient client) {
KernelConfig config = SKBuilders.kernelConfig()
@@ -22,12 +24,12 @@ public class Example06MemoryAndEmbeddings {
return kernel;
}
public static void run (boolean useAzureOpenAI) {
OpenAIAsyncClient client = Config.getClient(useAzureOpenAI);
Kernel kernel = getKernel(client);
public static void run (Config.ClientType clientType) throws IOException {
Kernel kernel = getKernel(clientType.getClient());
}
public static void main(String[] args) {
run(false);
public static void main(String[] args) throws IOException {
// Send one of Config.ClientType.OPEN_AI or Config.ClientType.AZURE_OPEN_AI
run(Config.ClientType.OPEN_AI);
}
}
@@ -11,6 +11,7 @@ import com.microsoft.semantickernel.orchestration.SKContext;
import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig;
import com.microsoft.semantickernel.templateengine.PromptTemplateEngine;
import java.io.IOException;
import java.util.ArrayList;
public class Example06_TemplateLanguage {
@@ -19,11 +20,11 @@ public class Example06_TemplateLanguage {
/// from a Semantic Function written in natural language
/// </summary>
public static void main(String[] args) {
public static void main(String[] args) throws IOException {
System.out.println("======== TemplateLanguage ========");
OpenAIAsyncClient client = Config.getClient(true);
OpenAIAsyncClient client = Config.ClientType.AZURE_OPEN_AI.getClient();
KernelConfig kernelConfig = SKBuilders.kernelConfig()
.addTextCompletionService(
@@ -6,7 +6,6 @@ import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.http.policy.ExponentialBackoffOptions;
import com.azure.core.http.policy.RetryOptions;
import com.microsoft.openai.AzureOpenAIClient;
import com.microsoft.semantickernel.Config;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.KernelConfig;
import com.microsoft.semantickernel.builders.SKBuilders;
@@ -14,26 +13,30 @@ import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig;
import com.microsoft.semantickernel.textcompletion.CompletionSKContext;
import com.microsoft.semantickernel.textcompletion.CompletionSKFunction;
import com.microsoft.semantickernel.textcompletion.TextCompletion;
import com.microsoft.semantickernel.util.AzureOpenAISettings;
import com.microsoft.semantickernel.util.AIProviderSettings;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import static com.microsoft.semantickernel.Config.AZURE_CONF_PROPERTIES;
import static com.microsoft.semantickernel.Config.CONF_PROPERTIES;
public class Example08_RetryHandler {
public static void main(String[] args) {
public static void main(String[] args) throws IOException {
RetryOptions retryOptions = new RetryOptions(new ExponentialBackoffOptions()
.setMaxDelay(Duration.ofSeconds(180))
.setBaseDelay(Duration.ofSeconds(60))
.setMaxRetries(3)
);
AzureOpenAISettings settings = AIProviderSettings.getAzureOpenAISettingsFromFile(CONF_PROPERTIES);
AzureOpenAIClient client = new AzureOpenAIClient(
new OpenAIClientBuilder()
.retryOptions(retryOptions)
.endpoint(Config.getAzureOpenAIEndpoint(AZURE_CONF_PROPERTIES))
.credential(new AzureKeyCredential(Config.getOpenAIKey(AZURE_CONF_PROPERTIES)))
.endpoint(settings.getEndpoint())
.credential(new AzureKeyCredential(settings.getKey()))
.buildAsyncClient());
@@ -11,13 +11,15 @@ import com.microsoft.semantickernel.chatcompletion.ChatCompletion;
import com.microsoft.semantickernel.chatcompletion.ChatHistory;
import com.microsoft.semantickernel.connectors.ai.openai.chatcompletion.OpenAIChatHistory;
import java.io.IOException;
/**
* The following example shows how to use Semantic Kernel with OpenAI ChatGPT API
*/
public class Example17_ChatGPT {
public static void main(String[] args) {
OpenAIAsyncClient client = Config.getClient(true);
public static void main(String[] args) throws IOException {
OpenAIAsyncClient client = Config.ClientType.OPEN_AI.getClient();
KernelConfig kernelConfig = SKBuilders.kernelConfig()
.addChatCompletionService(
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.semantickernel.util;
import java.io.IOException;
public class AIProviderSettings {
/**
* Returns an instance of OpenAISettings with key and organizationId from the environment
*
* @return OpenAISettings
*/
public static OpenAISettings getOpenAISettingsFromEnv() {
return new OpenAISettings().fromEnv();
}
/**
* Returns an instance of AzureOpenAISettings with key, endpoint and deploymentName from the
* environment
*
* @return AzureOpenAISettings
*/
public static AzureOpenAISettings getAzureOpenAISettingsFromEnv() {
return new AzureOpenAISettings().fromEnv();
}
/**
* Returns an instance of OpenAISettings with key and organizationId from the properties file
*
* @param path Path to the properties file
* @return OpenAISettings
*/
public static OpenAISettings getOpenAISettingsFromFile(String path) throws IOException {
return new OpenAISettings().fromFile(path);
}
/**
* Returns an instance of OpenAISettings with key and organizationId from the properties file
*
* @param path Path to the properties file
* @param clientSettingsId ID of the client settings in the properties file schema
* @return OpenAISettings
*/
public static OpenAISettings getOpenAISettingsFromFile(String path, String clientSettingsId)
throws IOException {
return new OpenAISettings().fromFile(path, clientSettingsId);
}
/**
* Returns an instance of AzureOpenAISettings with key, endpoint and deploymentName from the
* properties file
*
* @param path Path to the properties file
* @return AzureOpenAISettings
*/
public static AzureOpenAISettings getAzureOpenAISettingsFromFile(String path)
throws IOException {
return new AzureOpenAISettings().fromFile(path);
}
/**
* Returns an instance of AzureOpenAISettings with key, endpoint and deploymentName from the
* properties file
*
* @param path Path to the properties file
* @param clientSettingsId ID of the client settings in the properties file schema
* @return AzureOpenAISettings
*/
public static AzureOpenAISettings getAzureOpenAISettingsFromFile(
String path, String clientSettingsId) throws IOException {
return new AzureOpenAISettings().fromFile(path, clientSettingsId);
}
}
@@ -0,0 +1,56 @@
package com.microsoft.semantickernel.util;
import java.io.IOException;
public class AzureOpenAISettings extends ClientSettings<AzureOpenAISettings> {
private static final String DEFAULT_CLIENT_ID = "azureopenai";
private String key;
private String endpoint;
private String deploymentName;
public enum Property {
AZURE_OPEN_AI_KEY("key"),
AZURE_OPEN_AI_ENDPOINT("endpoint"),
AZURE_OPEN_AI_DEPLOYMENT_NAME("deploymentname");
private final String label;
Property (String label) {
this.label = label;
}
public String label() {
return this.label;
}
}
public String getKey() {
return key;
}
public String getEndpoint() {
return endpoint;
}
public String getDeploymentName() {
return deploymentName;
}
@Override
public AzureOpenAISettings fromEnv() {
this.key = getSettingsValueFromEnv(Property.AZURE_OPEN_AI_KEY.name());
this.endpoint = getSettingsValueFromEnv(Property.AZURE_OPEN_AI_ENDPOINT.name());
this.deploymentName = getSettingsValueFromEnv(Property.AZURE_OPEN_AI_DEPLOYMENT_NAME.name());
return this;
}
@Override
public AzureOpenAISettings fromFile(String path) throws IOException {
return fromFile(path, DEFAULT_CLIENT_ID);
}
@Override
public AzureOpenAISettings fromFile(String path, String clientSettingsId) throws IOException {
this.key = getSettingsValueFromFile(path, Property.AZURE_OPEN_AI_KEY.label(), clientSettingsId);
this.endpoint = getSettingsValueFromFile(path, Property.AZURE_OPEN_AI_ENDPOINT.label(), clientSettingsId);
this.deploymentName = getSettingsValueFromFile(path, Property.AZURE_OPEN_AI_DEPLOYMENT_NAME.label(), clientSettingsId);
return this;
}
}
@@ -0,0 +1,31 @@
package com.microsoft.semantickernel.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public abstract class ClientSettings<T extends ClientSettings<T>> {
public abstract T fromEnv();
public abstract T fromFile(String path) throws IOException;
public abstract T fromFile(String path, String clientSettingsId) throws IOException;
private static String getPropertyNameForClientId(String clientSettingsId, String propertyName) {
return "client." + clientSettingsId + "." + propertyName;
}
protected String getSettingsValueFromEnv(String property) {
return System.getenv(property);
}
protected String getSettingsValueFromFile(
String settingsFile, String property, String clientSettingsId) throws IOException {
File Settings = new File(settingsFile);
try (FileInputStream fis = new FileInputStream(Settings.getAbsolutePath())) {
Properties props = new Properties();
props.load(fis);
return props.getProperty(getPropertyNameForClientId(clientSettingsId, property));
} catch (IOException e) {
throw new IOException(settingsFile + " not configured properly");
}
}
}
@@ -0,0 +1,46 @@
package com.microsoft.semantickernel.util;
import java.io.IOException;
public class OpenAISettings extends ClientSettings<OpenAISettings> {
private String key;
private String organizationId;
private static final String DEFAULT_CLIENT_ID = "openai";
private enum Property {
OPEN_AI_KEY("key"),
OPEN_AI_ORGANIZATION_ID("organizationid");
private final String label;
Property (String label) {
this.label = label;
}
public String label() {
return this.label;
}
}
public String getKey() {
return key;
}
public String getOrganizationId() {
return organizationId;
}
@Override
public OpenAISettings fromEnv() {
this.key = getSettingsValueFromEnv(Property.OPEN_AI_KEY.name());
this.organizationId = getSettingsValueFromEnv(Property.OPEN_AI_ORGANIZATION_ID.name());
return this;
}
@Override
public OpenAISettings fromFile(String path) throws IOException {
return fromFile(path, DEFAULT_CLIENT_ID);
}
@Override
public OpenAISettings fromFile(String path, String clientSettingsId) throws IOException {
this.key = getSettingsValueFromFile(path, Property.OPEN_AI_KEY.label(), clientSettingsId);
this.organizationId = getSettingsValueFromFile(path, Property.OPEN_AI_ORGANIZATION_ID.label(), clientSettingsId);
return this;
}
}