Skip to content

Commit 33f857a

Browse files
authored
Merge branch 'main' into feature_agent_filters
2 parents d292fd4 + 25bda64 commit 33f857a

File tree

38 files changed

+830
-125
lines changed

38 files changed

+830
-125
lines changed

.github/_typos.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extend-exclude = [
1515
"CodeTokenizerTests.cs",
1616
"test_code_tokenizer.py",
1717
"*response.json",
18+
"test_content.txt",
1819
]
1920

2021
[default.extend-words]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
# These are optional elements. Feel free to remove any of them.
3+
status: { accepted }
4+
contact: { rogerbarreto, taochen }
5+
date: { 2024-06-20 }
6+
deciders: { alliscode, moonbox3, eavanvalkenburg }
7+
consulted: {}
8+
informed: {}
9+
---
10+
11+
# Support for Azure Model-as-a-Service in SK
12+
13+
## Context and Problem Statement
14+
15+
There has been a demand from customers for the implementation of Model-as-a-Service (MaaS) in SK. MaaS, which is also referred to as [serverless API](https://learn.microsoft.com/en-us/azure/ai-studio/how-to/model-catalog-overview#model-deployment-managed-compute-and-serverless-api-pay-as-you-go), is available in [Azure AI Studio](https://learn.microsoft.com/en-us/azure/ai-studio/what-is-ai-studio). This mode of consumption operates on a pay-as-you-go basis, typically using tokens for billing purposes. Clients can access the service via the [Azure AI Model Inference API](https://learn.microsoft.com/en-us/azure/ai-studio/reference/reference-model-inference-api?tabs=azure-studio) or client SDKs.
16+
17+
At present, there is no official support for MaaS in SK. The purpose of this ADR is to examine the constraints of the service and explore potential solutions to enable support for the service in SK via the development of a new AI connector.
18+
19+
## Client SDK
20+
21+
The Azure team will be providing a new client library, namely `Azure.AI.Inference` in .Net and `azure-ai-inference` in Python, for effectively interacting with the service. While the service API is OpenAI-compatible, it is not permissible to use the OpenAI and the Azure OpenAI client libraries for interacting with the service as they are not independent with respect to both the models and their providers. This is because Azure AI Studio features a diverse range of open-source models, other than OpenAI models.
22+
23+
### Limitations
24+
25+
The initial release of the client SDK will only support chat completion and text/image embedding generation, with image generation to be added later.
26+
27+
Plans to support for text completion are currently unclear, and it is highly unlikely that the SDK will ever include support for text completion. As a result, the new AI connector will **NOT** support text completions in the initial version until we get more customer signals or the client SDK adds support.
28+
29+
## AI Connector
30+
31+
### Naming options
32+
33+
- Azure
34+
- AzureAI
35+
- AzureAIInference
36+
- AzureAIModelInference
37+
38+
Decision: `AzureAIInference`
39+
40+
### Support for model-specific parameters
41+
42+
Models can possess supplementary parameters that are not part of the default API. The service API and the client SDK enable the provision of model-specific parameters. Users can provide model-specific settings via a dedicated argument along with other settings, such as `temperature` and `top_p`, among others.
43+
44+
In the context of SK, execution parameters are categorized under `PromptExecutionSettings`, which is inherited by all connector-specific setting classes. The settings of the new connector will contain a member of type `dictionary`, which will group together the model-specific parameters.
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
// Copyright (c) Microsoft. All rights reserved.
2-
using Azure.AI.OpenAI.Assistants;
32
using Microsoft.SemanticKernel;
43
using Microsoft.SemanticKernel.Connectors.OpenAI;
54
using Resources;
65

76
namespace Agents;
87

98
/// <summary>
10-
/// Demonstrate uploading and retrieving files with <see cref="OpenAIFileService"/> .
9+
/// Demonstrate using <see cref="OpenAIFileService"/> .
1110
/// </summary>
1211
public class OpenAIAssistant_FileService(ITestOutputHelper output) : BaseTest(output)
1312
{
@@ -19,7 +18,6 @@ public class OpenAIAssistant_FileService(ITestOutputHelper output) : BaseTest(ou
1918
[Fact]
2019
public async Task UploadAndRetrieveFilesAsync()
2120
{
22-
var openAIClient = new AssistantsClient(TestConfiguration.OpenAI.ApiKey);
2321
OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey);
2422

2523
BinaryContent[] files = [
@@ -29,41 +27,40 @@ public async Task UploadAndRetrieveFilesAsync()
2927
new BinaryContent(data: await EmbeddedResource.ReadAllAsync("travelinfo.txt"), mimeType: "text/plain") { InnerContent = "travelinfo.txt" }
3028
];
3129

32-
var fileIds = new Dictionary<string, BinaryContent>();
33-
foreach (var file in files)
30+
var fileContents = new Dictionary<string, BinaryContent>();
31+
foreach (BinaryContent file in files)
3432
{
35-
var result = await openAIClient.UploadFileAsync(new BinaryData(file.Data), Azure.AI.OpenAI.Assistants.OpenAIFilePurpose.FineTune);
36-
fileIds.Add(result.Value.Id, file);
33+
OpenAIFileReference result = await fileService.UploadContentAsync(file, new(file.InnerContent!.ToString()!, OpenAIFilePurpose.FineTune));
34+
fileContents.Add(result.Id, file);
3735
}
3836

39-
foreach (var file in (await openAIClient.GetFilesAsync(Azure.AI.OpenAI.Assistants.OpenAIFilePurpose.FineTune)).Value)
37+
foreach (OpenAIFileReference fileReference in await fileService.GetFilesAsync(OpenAIFilePurpose.FineTune))
4038
{
41-
if (!fileIds.ContainsKey(file.Id))
39+
// Only interested in the files we uploaded
40+
if (!fileContents.ContainsKey(fileReference.Id))
4241
{
4342
continue;
4443
}
4544

46-
var data = (await openAIClient.GetFileContentAsync(file.Id)).Value;
45+
BinaryContent content = await fileService.GetFileContentAsync(fileReference.Id);
4746

48-
var mimeType = fileIds[file.Id].MimeType;
49-
var fileName = fileIds[file.Id].InnerContent!.ToString();
50-
var metadata = new Dictionary<string, object?> { ["id"] = file.Id };
51-
var uri = new Uri($"https://api.openai.com/v1/files/{file.Id}/content");
52-
var content = mimeType switch
47+
string? mimeType = fileContents[fileReference.Id].MimeType;
48+
string? fileName = fileContents[fileReference.Id].InnerContent!.ToString();
49+
ReadOnlyMemory<byte> data = content.Data ?? new();
50+
51+
var typedContent = mimeType switch
5352
{
54-
"image/jpeg" => new ImageContent(data, mimeType) { Uri = uri, InnerContent = fileName, Metadata = metadata },
55-
"audio/wav" => new AudioContent(data, mimeType) { Uri = uri, InnerContent = fileName, Metadata = metadata },
56-
_ => new BinaryContent(data, mimeType) { Uri = uri, InnerContent = fileName, Metadata = metadata }
53+
"image/jpeg" => new ImageContent(data, mimeType) { Uri = content.Uri, InnerContent = fileName, Metadata = content.Metadata },
54+
"audio/wav" => new AudioContent(data, mimeType) { Uri = content.Uri, InnerContent = fileName, Metadata = content.Metadata },
55+
_ => new BinaryContent(data, mimeType) { Uri = content.Uri, InnerContent = fileName, Metadata = content.Metadata }
5756
};
5857

59-
// Display the the file-name and mime-tyupe for each content type.
60-
Console.WriteLine($"File: {fileName} - {mimeType}");
61-
62-
// Display the each content type-name.
63-
Console.WriteLine($"Type: {content}");
58+
Console.WriteLine($"\nFile: {fileName} - {mimeType}");
59+
Console.WriteLine($"Type: {typedContent}");
60+
Console.WriteLine($"Uri: {typedContent.Uri}");
6461

6562
// Delete the test file remotely
66-
await openAIClient.DeleteFileAsync(file.Id);
63+
await fileService.DeleteFileAsync(fileReference.Id);
6764
}
6865
}
6966
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.Reflection;
4+
using Microsoft.SemanticKernel;
5+
6+
namespace Functions;
7+
8+
public class MethodFunctions_Yaml(ITestOutputHelper output) : BaseTest(output)
9+
{
10+
private const string FunctionConfig = """
11+
name: ValidateTaskId
12+
description: Validate a task id.
13+
input_variables:
14+
- name: kernel
15+
description: Kernel instance.
16+
- name: taskId
17+
description: Task identifier.
18+
is_required: true
19+
output_variable:
20+
description: String indicating whether or not the task id is valid.
21+
""";
22+
23+
/// <summary>
24+
/// This example create a plugin and uses a separate configuration file for the function metadata.
25+
/// </summary>
26+
/// <remarks>
27+
/// Some reasons you would want to do this:
28+
/// 1. It's not possible to modify the existing code to add the KernelFunction attribute.
29+
/// 2. You want to keep the function metadata separate from the function implementation.
30+
/// </remarks>
31+
[Fact]
32+
public async Task CreateFunctionFromMethodWithYamlConfigAsync()
33+
{
34+
var kernel = new Kernel();
35+
36+
var config = KernelFunctionYaml.ToPromptTemplateConfig(FunctionConfig);
37+
38+
var target = new ValidatorPlugin();
39+
MethodInfo method = target.GetType().GetMethod(config.Name!)!;
40+
var functions = new List<KernelFunction>();
41+
var functionName = config.Name;
42+
var description = config.Description;
43+
var parameters = config.InputVariables;
44+
functions.Add(KernelFunctionFactory.CreateFromMethod(method, target, new()
45+
{
46+
FunctionName = functionName,
47+
Description = description,
48+
Parameters = parameters.Select(p => new KernelParameterMetadata(p.Name) { Description = p.Description, IsRequired = p.IsRequired }).ToList(),
49+
}));
50+
51+
var plugin = kernel.ImportPluginFromFunctions("ValidatorPlugin", functions);
52+
53+
var function = plugin["ValidateTaskId"];
54+
var result = await kernel.InvokeAsync(function, new() { { "taskId", "1234" } });
55+
Console.WriteLine(result.GetValue<string>());
56+
57+
Console.WriteLine("Function Metadata:");
58+
Console.WriteLine(function.Metadata.Description);
59+
Console.WriteLine(function.Metadata.Parameters[0].Description);
60+
Console.WriteLine(function.Metadata.Parameters[1].Description);
61+
}
62+
63+
/// <summary>
64+
/// Plugin example with no KernelFunction or Description attributes.
65+
/// </summary>
66+
private sealed class ValidatorPlugin
67+
{
68+
public string ValidateTaskId(Kernel kernel, string taskId)
69+
{
70+
return taskId.Equals("1234", StringComparison.Ordinal) ? "Valid task id" : "Invalid task id";
71+
}
72+
}
73+
}

dotnet/samples/Concepts/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Down below you can find the code snippets that demonstrate the usage of many Sem
7878
- [MethodFunctions](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Functions/MethodFunctions.cs)
7979
- [MethodFunctions_Advanced](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Functions/MethodFunctions_Advanced.cs)
8080
- [MethodFunctions_Types](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Functions/MethodFunctions_Types.cs)
81+
- [MethodFunctions_Yaml](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Functions/MethodFunctions_Yaml.cs)
8182
- [PromptFunctions_Inline](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Functions/PromptFunctions_Inline.cs)
8283
- [PromptFunctions_MultipleArguments](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Functions/PromptFunctions_MultipleArguments.cs)
8384

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Function Invocation Approval
2+
3+
This console application shows how to use function invocation filter (`IFunctionInvocationFilter`) to invoke a Kernel Function only if such operation was approved.
4+
If function invocation was rejected, the result will contain the reason why, so the LLM can respond appropriately.
5+
6+
The application uses a sample plugin which builds software by following these development stages: collection of requirements, design, implementation, testing and deployment.
7+
8+
Each step can be approved or rejected. Based on that, the LLM will decide how to proceed.
9+
10+
## Configuring Secrets
11+
12+
The example requires credentials to access OpenAI or Azure OpenAI.
13+
14+
If you have set up those credentials as secrets within Secret Manager or through environment variables for other samples from the solution in which this project is found, they will be re-used.
15+
16+
### To set your secrets with Secret Manager:
17+
18+
```
19+
cd dotnet/samples/Demos/FunctionInvocationApproval
20+
21+
dotnet user-secrets init
22+
23+
dotnet user-secrets set "OpenAI:ChatModelId" "..."
24+
dotnet user-secrets set "OpenAI:ApiKey" "..."
25+
26+
dotnet user-secrets set "AzureOpenAI:ChatDeploymentName" "..."
27+
dotnet user-secrets set "AzureOpenAI:Endpoint" "https://... .openai.azure.com/"
28+
dotnet user-secrets set "AzureOpenAI:ApiKey" "..."
29+
```
30+
31+
### To set your secrets with environment variables
32+
33+
Use these names:
34+
35+
```
36+
# OpenAI
37+
OpenAI__ChatModelId
38+
OpenAI__ApiKey
39+
40+
# Azure OpenAI
41+
AzureOpenAI__ChatDeploymentName
42+
AzureOpenAI__Endpoint
43+
AzureOpenAI__ApiKey
44+
```

dotnet/samples/Demos/HomeAutomation/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ If you have set up those credentials as secrets within Secret Manager or through
1212
### To set your secrets with Secret Manager:
1313

1414
```
15-
cd dotnet/samples/HouseAutomation
15+
cd dotnet/samples/Demos/HouseAutomation
1616
1717
dotnet user-secrets init
1818

dotnet/samples/GettingStartedWithAgents/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Example|Description
2222
[Step1_Agent](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs)|How to create and use an agent.
2323
[Step2_Plugins](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs)|How to associate plug-ins with an agent.
2424
[Step3_Chat](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step3_Chat.cs)|How to create a conversation between agents.
25-
[Step4_KernelFunctionStrategies](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Step4_KernelFunctionStrategies/Step1_Agent.cs)|How to utilize a `KernelFunction` as a _chat strategy_.
25+
[Step4_KernelFunctionStrategies](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step4_KernelFunctionStrategies.cs)|How to utilize a `KernelFunction` as a _chat strategy_.
2626
[Step5_JsonResult](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step5_JsonResult.cs)|How to have an agent produce JSON.
2727
[Step6_DependencyInjection](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs)|How to define dependency injection patterns for agents.
2828
[Step7_OpenAIAssistant](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step7_OpenAIAssistant.cs)|How to create an Open AI Assistant agent.

dotnet/src/Connectors/Connectors.HuggingFace/Core/HuggingFaceMessageApiClient.cs

+5-6
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,8 @@ internal async IAsyncEnumerable<StreamingChatMessageContent> StreamCompleteChatM
8585
var endpoint = this.GetChatGenerationEndpoint();
8686

8787
var huggingFaceExecutionSettings = HuggingFacePromptExecutionSettings.FromExecutionSettings(executionSettings);
88-
huggingFaceExecutionSettings.ModelId ??= this._clientCore.ModelId;
8988

90-
var request = this.CreateChatRequest(chatHistory, huggingFaceExecutionSettings);
89+
var request = this.CreateChatRequest(chatHistory, huggingFaceExecutionSettings, modelId);
9190
request.Stream = true;
9291

9392
using var activity = ModelDiagnostics.StartCompletionActivity(endpoint, modelId, this._clientCore.ModelProvider, chatHistory, huggingFaceExecutionSettings);
@@ -149,8 +148,7 @@ internal async Task<IReadOnlyList<ChatMessageContent>> CompleteChatMessageAsync(
149148
var endpoint = this.GetChatGenerationEndpoint();
150149

151150
var huggingFaceExecutionSettings = HuggingFacePromptExecutionSettings.FromExecutionSettings(executionSettings);
152-
huggingFaceExecutionSettings.ModelId ??= this._clientCore.ModelId;
153-
var request = this.CreateChatRequest(chatHistory, huggingFaceExecutionSettings);
151+
var request = this.CreateChatRequest(chatHistory, huggingFaceExecutionSettings, modelId);
154152

155153
using var activity = ModelDiagnostics.StartCompletionActivity(endpoint, modelId, this._clientCore.ModelProvider, chatHistory, huggingFaceExecutionSettings);
156154
using var httpRequestMessage = this._clientCore.CreatePost(request, endpoint, this._clientCore.ApiKey);
@@ -276,7 +274,8 @@ private async IAsyncEnumerable<StreamingChatMessageContent> ProcessChatResponseS
276274

277275
private ChatCompletionRequest CreateChatRequest(
278276
ChatHistory chatHistory,
279-
HuggingFacePromptExecutionSettings huggingFaceExecutionSettings)
277+
HuggingFacePromptExecutionSettings huggingFaceExecutionSettings,
278+
string modelId)
280279
{
281280
HuggingFaceClient.ValidateMaxTokens(huggingFaceExecutionSettings.MaxTokens);
282281

@@ -287,7 +286,7 @@ private ChatCompletionRequest CreateChatRequest(
287286
JsonSerializer.Serialize(huggingFaceExecutionSettings));
288287
}
289288

290-
var request = ChatCompletionRequest.FromChatHistoryAndExecutionSettings(chatHistory, huggingFaceExecutionSettings);
289+
var request = ChatCompletionRequest.FromChatHistoryAndExecutionSettings(chatHistory, huggingFaceExecutionSettings, modelId);
291290
return request;
292291
}
293292

dotnet/src/Connectors/Connectors.HuggingFace/Core/Models/ChatCompletionRequest.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@ internal sealed class ChatCompletionRequest
102102
/// </summary>
103103
/// <param name="chatHistory">Chat history to be used for the request.</param>
104104
/// <param name="executionSettings">Execution settings to be used for the request.</param>
105-
/// <returns>TexGenerationtRequest object.</returns>
106-
internal static ChatCompletionRequest FromChatHistoryAndExecutionSettings(ChatHistory chatHistory, HuggingFacePromptExecutionSettings executionSettings)
105+
/// <param name="modelId">Model id to use if value in prompt execution settings is not set.</param>
106+
/// <returns>TexGenerationRequest object.</returns>
107+
internal static ChatCompletionRequest FromChatHistoryAndExecutionSettings(ChatHistory chatHistory, HuggingFacePromptExecutionSettings executionSettings, string modelId)
107108
{
108109
return new ChatCompletionRequest
109110
{
@@ -118,7 +119,7 @@ internal static ChatCompletionRequest FromChatHistoryAndExecutionSettings(ChatHi
118119
Temperature = executionSettings.Temperature,
119120
Stop = executionSettings.Stop,
120121
MaxTokens = executionSettings.MaxTokens,
121-
Model = executionSettings.ModelId ?? TextGenerationInferenceDefaultModel,
122+
Model = executionSettings.ModelId ?? modelId ?? TextGenerationInferenceDefaultModel,
122123
TopP = executionSettings.TopP,
123124
TopLogProbs = executionSettings.TopLogProbs
124125
};

0 commit comments

Comments
 (0)