diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index b3d2c48aa69e..fd4ecc23cf80 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -7,14 +7,16 @@
-
+
-
-
-
+
+
+
+
+
@@ -29,28 +31,30 @@
+
+
-
-
-
-
-
-
+
+
-
+
+
+
-
-
+
+
@@ -62,7 +66,11 @@
+
+
+
+
@@ -94,14 +102,16 @@
-
-
-
-
+
+
+
+
-
+
@@ -145,8 +155,6 @@
-
-
diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln
index eefa32d32d28..1d260a6a33bf 100644
--- a/dotnet/SK-dotnet.sln
+++ b/dotnet/SK-dotnet.sln
@@ -550,6 +550,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Runtime.InProcess.UnitTests
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VectorData.UnitTests", "src\Connectors\VectorData.UnitTests\VectorData.UnitTests.csproj", "{AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Agents.Orchestration", "src\Agents\Orchestration\Agents.Orchestration.csproj", "{D1A02387-FA60-22F8-C2ED-4676568B6CC3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1511,6 +1513,12 @@ Global
{AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84}.Publish|Any CPU.Build.0 = Debug|Any CPU
{AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D1A02387-FA60-22F8-C2ED-4676568B6CC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D1A02387-FA60-22F8-C2ED-4676568B6CC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D1A02387-FA60-22F8-C2ED-4676568B6CC3}.Publish|Any CPU.ActiveCfg = Publish|Any CPU
+ {D1A02387-FA60-22F8-C2ED-4676568B6CC3}.Publish|Any CPU.Build.0 = Publish|Any CPU
+ {D1A02387-FA60-22F8-C2ED-4676568B6CC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D1A02387-FA60-22F8-C2ED-4676568B6CC3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1536,7 +1544,7 @@ Global
{AFA81EB7-F869-467D-8A90-744305D80AAC} = {1B4CBDE0-10C2-4E7D-9CD0-FE7586C96ED1}
{627742DB-1E52-468A-99BD-6FF1A542D25B} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0}
{E3299033-EB81-4C4C-BCD9-E8DC40937969} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0}
- {078F96B4-09E1-4E0E-B214-F71A4F4BF633} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0}
+ {078F96B4-09E1-4E0E-B214-F71A4F4BF633} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974}
{F51017A9-15C8-472D-893C-080046D710A6} = {078F96B4-09E1-4E0E-B214-F71A4F4BF633}
{EC3BB6D1-2FB2-4702-84C6-F791DE533ED4} = {24503383-A8C4-4255-9998-28D70FE8E99A}
{4D226C2F-AE9F-4EFB-AF2D-45C8FE5CB34E} = {24503383-A8C4-4255-9998-28D70FE8E99A}
@@ -1716,6 +1724,7 @@ Global
{CCC909E4-5269-A31E-0BFD-4863B4B29BBB} = {A70ED5A7-F8E1-4A57-9455-3C05989542DA}
{DA6B4ED4-ED0B-D25C-889C-9F940E714891} = {A70ED5A7-F8E1-4A57-9455-3C05989542DA}
{AAC7B5E8-CC4E-49D0-AF6A-2B4F7B43BD84} = {5A7028A7-4DDF-4E4F-84A9-37CE8F8D7E89}
+ {D1A02387-FA60-22F8-C2ED-4676568B6CC3} = {6823CD5E-2ABE-41EB-B865-F86EC13F0CF9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
diff --git a/dotnet/docs/EXPERIMENTS.md b/dotnet/docs/EXPERIMENTS.md
index 1e143695e0f0..eabc3243aa67 100644
--- a/dotnet/docs/EXPERIMENTS.md
+++ b/dotnet/docs/EXPERIMENTS.md
@@ -83,6 +83,7 @@ You can use the following diagnostic IDs to ignore warnings or errors for a part
| SKEXP0070 | Amazon AI connector | | | | | |
| | | | | | | |
| SKEXP0080 | Process Framework |
+| SKEXP0081 | Process Framework - Foundry Process
| | | | | | | |
| SKEXP0101 | Experiment with Assistants |
| SKEXP0101 | Experiment with Flow Orchestration |
diff --git a/dotnet/format.ps1 b/dotnet/format.ps1
index e01d5c7694fb..b91f7df3a445 100644
--- a/dotnet/format.ps1
+++ b/dotnet/format.ps1
@@ -14,12 +14,11 @@ pushd $repoRoot
$targets =
git diff --name-only main..HEAD |
ForEach-Object {
- Write-Host "$_ was changed"
-
$dir = Split-Path (Join-Path $repoRoot $_) -Parent # << absolute
while ($dir -and $dir -ne $repoRoot) {
$proj = Get-ChildItem -LiteralPath $dir -Filter *.csproj -File -ErrorAction Ignore |
Select-Object -First 1
+
if ($proj) {
if ($exclude -notcontains $proj.Name) { $proj.FullName }
break
@@ -29,27 +28,18 @@ $targets =
} |
Sort-Object -Unique
+popd
+
if (-not $targets) {
# $targets = Get-ChildItem $repoRoot -Recurse -Filter *.sln |
# Select-Object -Expand FullName
Write-Host "No code changes found"
}
-if ($PSVersionTable.PSVersion.Major -ge 7) {
- $targets | ForEach-Object -Parallel {
- param($t) ; Write-Host " $t" ; dotnet format --verbosity normal $t
- }
-} else {
- $jobs = foreach ($t in $targets) {
- Start-Job -ScriptBlock {
- param($target)
- Write-Host " $target"
- dotnet format --verbosity normal $target
- } -ArgumentList $t
- }
-
- # wait for all to finish and surface errors
- $jobs | Receive-Job -Wait -AutoRemoveJob
+foreach ($t in $targets) {
+ Write-Host "Formatting $t"
}
-popd
\ No newline at end of file
+foreach ($t in $targets) {
+ dotnet format --no-restore --verbosity diag $t
+}
diff --git a/dotnet/nuget.config b/dotnet/nuget.config
index 7159fcd04c36..143754718558 100644
--- a/dotnet/nuget.config
+++ b/dotnet/nuget.config
@@ -1,6 +1,6 @@
-
+
-
+
@@ -11,5 +11,5 @@
-
+
diff --git a/dotnet/nuget/nuget-package.props b/dotnet/nuget/nuget-package.props
index b8677813183d..ab39a095a11b 100644
--- a/dotnet/nuget/nuget-package.props
+++ b/dotnet/nuget/nuget-package.props
@@ -1,7 +1,7 @@
- 1.49.0
+ 1.51.0
$(VersionPrefix)-$(VersionSuffix)
$(VersionPrefix)
@@ -9,7 +9,7 @@
true
- 1.48.0
+ 1.50.0
$(NoWarn);CP0003
diff --git a/dotnet/samples/Concepts/Caching/SemanticCachingWithFilters.cs b/dotnet/samples/Concepts/Caching/SemanticCachingWithFilters.cs
index 4d9eb15d36a2..b44f13356fdf 100644
--- a/dotnet/samples/Concepts/Caching/SemanticCachingWithFilters.cs
+++ b/dotnet/samples/Concepts/Caching/SemanticCachingWithFilters.cs
@@ -2,10 +2,10 @@
using System.Diagnostics;
using Azure.Identity;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Embeddings;
namespace Caching;
@@ -124,8 +124,8 @@ private Kernel GetKernelWithCache(Action configureVectorStor
TestConfiguration.AzureOpenAI.Endpoint,
TestConfiguration.AzureOpenAI.ApiKey);
- // Add Azure OpenAI text embedding generation service
- builder.AddAzureOpenAITextEmbeddingGeneration(
+ // Add Azure OpenAI embedding generator
+ builder.AddAzureOpenAIEmbeddingGenerator(
TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
TestConfiguration.AzureOpenAI.ApiKey);
@@ -138,8 +138,8 @@ private Kernel GetKernelWithCache(Action configureVectorStor
TestConfiguration.AzureOpenAI.Endpoint,
new AzureCliCredential());
- // Add Azure OpenAI text embedding generation service
- builder.AddAzureOpenAITextEmbeddingGeneration(
+ // Add Azure OpenAI embedding generator
+ builder.AddAzureOpenAIEmbeddingGenerator(
TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
new AzureCliCredential());
@@ -181,7 +181,7 @@ public class CacheBaseFilter
/// Filter which is executed during prompt rendering operation.
///
public sealed class PromptCacheFilter(
- ITextEmbeddingGenerationService textEmbeddingGenerationService,
+ IEmbeddingGenerator> embeddingGenerator,
IVectorStore vectorStore)
: CacheBaseFilter, IPromptRenderFilter
{
@@ -193,7 +193,7 @@ public async Task OnPromptRenderAsync(PromptRenderContext context, Func(CollectionName);
await collection.CreateCollectionIfNotExistsAsync();
@@ -218,11 +218,11 @@ public async Task OnPromptRenderAsync(PromptRenderContext context, Func
public sealed class FunctionCacheFilter(
- ITextEmbeddingGenerationService textEmbeddingGenerationService,
+ IEmbeddingGenerator> embeddingGenerator,
IVectorStore vectorStore)
: CacheBaseFilter, IFunctionInvocationFilter
{
- public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func next)
+ public async Task OnFunctionInvocationAsync(Microsoft.SemanticKernel.FunctionInvocationContext context, Func next)
{
// Trigger function invocation
await next(context);
@@ -237,7 +237,7 @@ public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, F
var recordId = context.Result.Metadata?.GetValueOrDefault(RecordIdKey, Guid.NewGuid().ToString()) as string;
// Generate prompt embedding.
- var promptEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(context.Result.RenderedPrompt);
+ var promptEmbedding = await embeddingGenerator.GenerateAsync(context.Result.RenderedPrompt);
// Cache rendered prompt and LLM result.
var collection = vectorStore.GetCollection(CollectionName);
@@ -248,7 +248,7 @@ public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, F
Id = recordId!,
Prompt = context.Result.RenderedPrompt,
Result = result.ToString(),
- PromptEmbedding = promptEmbedding
+ PromptEmbedding = promptEmbedding.Vector
};
await collection.UpsertAsync(cacheRecord, cancellationToken: context.CancellationToken);
diff --git a/dotnet/samples/Concepts/ChatCompletion/OpenAI_FunctionCallingWithMemoryPlugin.cs b/dotnet/samples/Concepts/ChatCompletion/OpenAI_FunctionCallingWithMemoryPlugin.cs
index 4f80ce4be119..f75e6569d747 100644
--- a/dotnet/samples/Concepts/ChatCompletion/OpenAI_FunctionCallingWithMemoryPlugin.cs
+++ b/dotnet/samples/Concepts/ChatCompletion/OpenAI_FunctionCallingWithMemoryPlugin.cs
@@ -2,10 +2,10 @@
using System.ComponentModel;
using System.Text.Json;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
-using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Plugins.Memory;
@@ -35,7 +35,7 @@ public async Task UseFunctionCallingToRetrieveMemoriesAsync()
kernelBuilder.AddOpenAIChatCompletion(
modelId: TestConfiguration.OpenAI.ChatModelId!,
apiKey: TestConfiguration.OpenAI.ApiKey!);
- kernelBuilder.AddOpenAITextEmbeddingGeneration(
+ kernelBuilder.AddOpenAIEmbeddingGenerator(
modelId: TestConfiguration.OpenAI.EmbeddingModelId!,
apiKey: TestConfiguration.OpenAI.ApiKey!);
kernelBuilder.Services.AddSingleton(this.Output);
@@ -43,9 +43,9 @@ public async Task UseFunctionCallingToRetrieveMemoriesAsync()
Kernel kernel = kernelBuilder.Build();
// Create a text memory store and populate it with sample data
- var embeddingGeneration = kernel.GetRequiredService();
+ var embeddingGenerator = kernel.GetRequiredService>>();
VolatileMemoryStore memoryStore = new();
- SemanticTextMemory textMemory = new(memoryStore, embeddingGeneration);
+ SemanticTextMemory textMemory = new(memoryStore, embeddingGenerator);
string collectionName = "SemanticKernel";
await PopulateMemoryAsync(collectionName, textMemory);
@@ -114,7 +114,7 @@ private sealed class FunctionInvocationFilter(ITestOutputHelper output) : IFunct
private readonly ITestOutputHelper _output = output;
///
- public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func next)
+ public async Task OnFunctionInvocationAsync(Microsoft.SemanticKernel.FunctionInvocationContext context, Func next)
{
this._output.WriteLine($"Function Invocation - {context.Function.Name}");
await next(context);
diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj
index 618bff3e06af..776eb84b5616 100644
--- a/dotnet/samples/Concepts/Concepts.csproj
+++ b/dotnet/samples/Concepts/Concepts.csproj
@@ -45,6 +45,8 @@
+
+
@@ -53,6 +55,7 @@
+
diff --git a/dotnet/samples/Concepts/Memory/AWSBedrock_EmbeddingGeneration.cs b/dotnet/samples/Concepts/Memory/AWSBedrock_EmbeddingGeneration.cs
new file mode 100644
index 000000000000..fb33f0b47754
--- /dev/null
+++ b/dotnet/samples/Concepts/Memory/AWSBedrock_EmbeddingGeneration.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Extensions.AI;
+using Microsoft.SemanticKernel;
+using xRetry;
+
+namespace Memory;
+
+// The following example shows how to use Semantic Kernel with AWS Bedrock API for embedding generation,
+// including the ability to specify custom dimensions.
+public class AWSBedrock_EmbeddingGeneration(ITestOutputHelper output) : BaseTest(output)
+{
+ ///
+ /// This test demonstrates how to use the AWS Bedrock API embedding generation.
+ ///
+ [RetryFact(typeof(HttpOperationException))]
+ public async Task GenerateEmbeddings()
+ {
+ IKernelBuilder kernelBuilder = Kernel.CreateBuilder()
+ .AddBedrockEmbeddingGenerator(modelId: TestConfiguration.Bedrock.EmbeddingModelId! ?? "amazon.titan-embed-text-v1");
+
+ Kernel kernel = kernelBuilder.Build();
+
+ var embeddingGenerator = kernel.GetRequiredService>>();
+
+ // Generate embeddings with the default dimensions for the model
+ var embeddings = await embeddingGenerator.GenerateAsync(
+ ["Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models into your codebase."]);
+
+ Console.WriteLine($"Generated '{embeddings.Count}' embedding(s) with '{embeddings[0].Vector.Length}' dimensions (default for current model) for the provided text");
+ }
+}
diff --git a/dotnet/samples/Concepts/Memory/Google_EmbeddingGeneration.cs b/dotnet/samples/Concepts/Memory/Google_EmbeddingGeneration.cs
index 58b45be5b834..e98f5ef5d2cc 100644
--- a/dotnet/samples/Concepts/Memory/Google_EmbeddingGeneration.cs
+++ b/dotnet/samples/Concepts/Memory/Google_EmbeddingGeneration.cs
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
using Google.Apis.Auth.OAuth2;
+using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Embeddings;
using xRetry;
namespace Memory;
@@ -29,20 +29,20 @@ public async Task GenerateEmbeddingWithDefaultDimensionsUsingVertexAI()
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
- kernelBuilder.AddVertexAIEmbeddingGeneration(
+ kernelBuilder.AddVertexAIEmbeddingGenerator(
modelId: TestConfiguration.VertexAI.EmbeddingModelId!,
bearerTokenProvider: GetBearerToken,
location: TestConfiguration.VertexAI.Location,
projectId: TestConfiguration.VertexAI.ProjectId);
Kernel kernel = kernelBuilder.Build();
- var embeddingGenerator = kernel.GetRequiredService();
+ var embeddingGenerator = kernel.GetRequiredService>>();
// Generate embeddings with the default dimensions for the model
- var embeddings = await embeddingGenerator.GenerateEmbeddingsAsync(
+ var embeddings = await embeddingGenerator.GenerateAsync(
["Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models into your codebase."]);
- Console.WriteLine($"Generated '{embeddings.Count}' embedding(s) with '{embeddings[0].Length}' dimensions (default) for the provided text");
+ Console.WriteLine($"Generated '{embeddings.Count}' embedding(s) with '{embeddings[0].Vector.Length}' dimensions (default) for the provided text");
// Uses Google.Apis.Auth.OAuth2 to get the bearer token
async ValueTask GetBearerToken()
@@ -76,18 +76,18 @@ public async Task GenerateEmbeddingWithDefaultDimensionsUsingGoogleAI()
Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
- kernelBuilder.AddGoogleAIEmbeddingGeneration(
+ kernelBuilder.AddGoogleAIEmbeddingGenerator(
modelId: TestConfiguration.GoogleAI.EmbeddingModelId!,
apiKey: TestConfiguration.GoogleAI.ApiKey);
Kernel kernel = kernelBuilder.Build();
- var embeddingGenerator = kernel.GetRequiredService();
+ var embeddingGenerator = kernel.GetRequiredService>>();
// Generate embeddings with the default dimensions for the model
- var embeddings = await embeddingGenerator.GenerateEmbeddingsAsync(
+ var embeddings = await embeddingGenerator.GenerateAsync(
["Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models into your codebase."]);
- Console.WriteLine($"Generated '{embeddings.Count}' embedding(s) with '{embeddings[0].Length}' dimensions (default) for the provided text");
+ Console.WriteLine($"Generated '{embeddings.Count}' embedding(s) with '{embeddings[0].Vector.Length}' dimensions (default) for the provided text");
}
[RetryFact(typeof(HttpOperationException))]
@@ -100,21 +100,21 @@ public async Task GenerateEmbeddingWithCustomDimensionsUsingGoogleAI()
const int CustomDimensions = 512;
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
- kernelBuilder.AddGoogleAIEmbeddingGeneration(
+ kernelBuilder.AddGoogleAIEmbeddingGenerator(
modelId: TestConfiguration.GoogleAI.EmbeddingModelId!,
apiKey: TestConfiguration.GoogleAI.ApiKey,
dimensions: CustomDimensions);
Kernel kernel = kernelBuilder.Build();
- var embeddingGenerator = kernel.GetRequiredService();
+ var embeddingGenerator = kernel.GetRequiredService>>();
// Generate embeddings with the specified custom dimensions
- var embeddings = await embeddingGenerator.GenerateEmbeddingsAsync(
+ var embeddings = await embeddingGenerator.GenerateAsync(
["Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models into your codebase."]);
- Console.WriteLine($"Generated '{embeddings.Count}' embedding(s) with '{embeddings[0].Length}' dimensions (custom: '{CustomDimensions}') for the provided text");
+ Console.WriteLine($"Generated '{embeddings.Count}' embedding(s) with '{embeddings[0].Vector.Length}' dimensions (custom: '{CustomDimensions}') for the provided text");
// Verify that we received embeddings with our requested dimensions
- Assert.Equal(CustomDimensions, embeddings[0].Length);
+ Assert.Equal(CustomDimensions, embeddings[0].Vector.Length);
}
}
diff --git a/dotnet/samples/Concepts/Memory/HuggingFace_EmbeddingGeneration.cs b/dotnet/samples/Concepts/Memory/HuggingFace_EmbeddingGeneration.cs
index b605cb532bab..b64ada3361f0 100644
--- a/dotnet/samples/Concepts/Memory/HuggingFace_EmbeddingGeneration.cs
+++ b/dotnet/samples/Concepts/Memory/HuggingFace_EmbeddingGeneration.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Embeddings;
using xRetry;
#pragma warning disable format // Format item can be simplified
@@ -18,15 +18,15 @@ public async Task RunInferenceApiEmbeddingAsync()
Console.WriteLine("\n======= Hugging Face Inference API - Embedding Example ========\n");
Kernel kernel = Kernel.CreateBuilder()
- .AddHuggingFaceTextEmbeddingGeneration(
+ .AddHuggingFaceEmbeddingGenerator(
model: TestConfiguration.HuggingFace.EmbeddingModelId,
apiKey: TestConfiguration.HuggingFace.ApiKey)
.Build();
- var embeddingGenerator = kernel.GetRequiredService();
+ var embeddingGenerator = kernel.GetRequiredService>>();
// Generate embeddings for each chunk.
- var embeddings = await embeddingGenerator.GenerateEmbeddingsAsync(["John: Hello, how are you?\nRoger: Hey, I'm Roger!"]);
+ var embeddings = await embeddingGenerator.GenerateAsync(["John: Hello, how are you?\nRoger: Hey, I'm Roger!"]);
Console.WriteLine($"Generated {embeddings.Count} embeddings for the provided text");
}
diff --git a/dotnet/samples/Concepts/Memory/Ollama_EmbeddingGeneration.cs b/dotnet/samples/Concepts/Memory/Ollama_EmbeddingGeneration.cs
index 5ba0a45440b2..953f85b41e07 100644
--- a/dotnet/samples/Concepts/Memory/Ollama_EmbeddingGeneration.cs
+++ b/dotnet/samples/Concepts/Memory/Ollama_EmbeddingGeneration.cs
@@ -1,12 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Embeddings;
using xRetry;
-#pragma warning disable format // Format item can be simplified
-#pragma warning disable CA1861 // Avoid constant arrays as arguments
-
namespace Memory;
// The following example shows how to use Semantic Kernel with Ollama API.
@@ -20,15 +17,15 @@ public async Task RunEmbeddingAsync()
Console.WriteLine("\n======= Ollama - Embedding Example ========\n");
Kernel kernel = Kernel.CreateBuilder()
- .AddOllamaTextEmbeddingGeneration(
+ .AddOllamaEmbeddingGenerator(
endpoint: new Uri(TestConfiguration.Ollama.Endpoint),
modelId: TestConfiguration.Ollama.EmbeddingModelId)
.Build();
- var embeddingGenerator = kernel.GetRequiredService();
+ var embeddingGenerator = kernel.GetRequiredService>>();
// Generate embeddings for each chunk.
- var embeddings = await embeddingGenerator.GenerateEmbeddingsAsync(["John: Hello, how are you?\nRoger: Hey, I'm Roger!"]);
+ var embeddings = await embeddingGenerator.GenerateAsync(["John: Hello, how are you?\nRoger: Hey, I'm Roger!"]);
Console.WriteLine($"Generated {embeddings.Count} embeddings for the provided text");
}
diff --git a/dotnet/samples/Concepts/Memory/Onnx_EmbeddingGeneration.cs b/dotnet/samples/Concepts/Memory/Onnx_EmbeddingGeneration.cs
index 8058349f7b0b..e3d5612807fe 100644
--- a/dotnet/samples/Concepts/Memory/Onnx_EmbeddingGeneration.cs
+++ b/dotnet/samples/Concepts/Memory/Onnx_EmbeddingGeneration.cs
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Embeddings;
namespace Memory;
@@ -34,13 +34,13 @@ public async Task RunEmbeddingAsync()
Console.WriteLine("\n======= Onnx - Embedding Example ========\n");
Kernel kernel = Kernel.CreateBuilder()
- .AddBertOnnxTextEmbeddingGeneration(TestConfiguration.Onnx.EmbeddingModelPath, TestConfiguration.Onnx.EmbeddingVocabPath)
+ .AddBertOnnxEmbeddingGenerator(TestConfiguration.Onnx.EmbeddingModelPath, TestConfiguration.Onnx.EmbeddingVocabPath)
.Build();
- var embeddingGenerator = kernel.GetRequiredService();
+ var embeddingGenerator = kernel.GetRequiredService>>();
// Generate embeddings for each chunk.
- var embeddings = await embeddingGenerator.GenerateEmbeddingsAsync(["John: Hello, how are you?\nRoger: Hey, I'm Roger!"]);
+ var embeddings = await embeddingGenerator.GenerateAsync(["John: Hello, how are you?\nRoger: Hey, I'm Roger!"]);
Console.WriteLine($"Generated {embeddings.Count} embeddings for the provided text");
}
@@ -70,12 +70,12 @@ public async Task RunServiceCollectionEmbeddingAsync()
Console.WriteLine("\n======= Onnx - Embedding Example ========\n");
var services = new ServiceCollection()
- .AddBertOnnxTextEmbeddingGeneration(TestConfiguration.Onnx.EmbeddingModelPath, TestConfiguration.Onnx.EmbeddingVocabPath);
+ .AddBertOnnxEmbeddingGenerator(TestConfiguration.Onnx.EmbeddingModelPath, TestConfiguration.Onnx.EmbeddingVocabPath);
var provider = services.BuildServiceProvider();
- var embeddingGenerator = provider.GetRequiredService();
+ var embeddingGenerator = provider.GetRequiredService>>();
// Generate embeddings for each chunk.
- var embeddings = await embeddingGenerator.GenerateEmbeddingsAsync(["John: Hello, how are you?\nRoger: Hey, I'm Roger!"]);
+ var embeddings = await embeddingGenerator.GenerateAsync(["John: Hello, how are you?\nRoger: Hey, I'm Roger!"]);
Console.WriteLine($"Generated {embeddings.Count} embeddings for the provided text");
}
diff --git a/dotnet/samples/Concepts/Memory/OpenAI_EmbeddingGeneration.cs b/dotnet/samples/Concepts/Memory/OpenAI_EmbeddingGeneration.cs
index 93ddda59d614..96eef9281322 100644
--- a/dotnet/samples/Concepts/Memory/OpenAI_EmbeddingGeneration.cs
+++ b/dotnet/samples/Concepts/Memory/OpenAI_EmbeddingGeneration.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Embeddings;
using xRetry;
#pragma warning disable format // Format item can be simplified
@@ -19,15 +19,15 @@ public async Task RunEmbeddingAsync()
Assert.NotNull(TestConfiguration.OpenAI.ApiKey);
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
- kernelBuilder.AddOpenAITextEmbeddingGeneration(
+ kernelBuilder.AddOpenAIEmbeddingGenerator(
modelId: TestConfiguration.OpenAI.EmbeddingModelId!,
apiKey: TestConfiguration.OpenAI.ApiKey!);
Kernel kernel = kernelBuilder.Build();
- var embeddingGenerator = kernel.GetRequiredService();
+ var embeddingGenerator = kernel.GetRequiredService>>();
// Generate embeddings for the specified text.
- var embeddings = await embeddingGenerator.GenerateEmbeddingsAsync(["Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models into your C#, Python, or Java codebase."]);
+ var embeddings = await embeddingGenerator.GenerateAsync(["Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models into your C#, Python, or Java codebase."]);
Console.WriteLine($"Generated {embeddings.Count} embeddings for the provided text");
}
diff --git a/dotnet/samples/Concepts/Memory/TextChunkingAndEmbedding.cs b/dotnet/samples/Concepts/Memory/TextChunkingAndEmbedding.cs
index fb96579f32a1..14bb887effb6 100644
--- a/dotnet/samples/Concepts/Memory/TextChunkingAndEmbedding.cs
+++ b/dotnet/samples/Concepts/Memory/TextChunkingAndEmbedding.cs
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.ClientModel;
+using Azure.AI.OpenAI;
+using Microsoft.Extensions.AI;
using Microsoft.ML.Tokenizers;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Text;
namespace Memory;
@@ -20,10 +22,9 @@ public async Task RunAsync()
private async Task RunExampleAsync()
{
- var embeddingGenerator = new AzureOpenAITextEmbeddingGenerationService(
- deploymentName: EmbeddingModelName,
- endpoint: TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- apiKey: TestConfiguration.AzureOpenAIEmbeddings.ApiKey);
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new ApiKeyCredential(TestConfiguration.AzureOpenAIEmbeddings.ApiKey))
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// To demonstrate batching we'll create abnormally small partitions.
var lines = TextChunker.SplitPlainTextLines(ChatTranscript, maxTokensPerLine: 10);
@@ -46,7 +47,7 @@ private async Task RunExampleAsync()
for (var i = 0; i < chunks.Count; i++)
{
var chunk = chunks[i];
- var embeddings = await embeddingGenerator.GenerateEmbeddingsAsync(chunk);
+ var embeddings = await embeddingGenerator.GenerateAsync(chunk);
Console.WriteLine($"Generated {embeddings.Count} embeddings from chunk {i + 1}");
}
diff --git a/dotnet/samples/Concepts/Memory/VectorStoreExtensions.cs b/dotnet/samples/Concepts/Memory/VectorStoreExtensions.cs
index 3a2183ba34ee..bf793097e7f8 100644
--- a/dotnet/samples/Concepts/Memory/VectorStoreExtensions.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStoreExtensions.cs
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Data;
-using Microsoft.SemanticKernel.Embeddings;
namespace Memory;
@@ -36,13 +36,13 @@ internal static class VectorStoreExtensions
/// Instance of used to created the collection.
/// The collection name.
/// A list of strings.
- /// A text embedding generation service.
+ /// An embedding generator.
/// A delegate which can create a record with a valid key for each string and it's embedding.
internal static async Task> CreateCollectionFromListAsync(
this IVectorStore vectorStore,
string collectionName,
string[] entries,
- ITextEmbeddingGenerationService embeddingGenerationService,
+ IEmbeddingGenerator> embeddingGenerator,
CreateRecordFromString createRecord)
where TKey : notnull
where TRecord : notnull
@@ -54,7 +54,7 @@ internal static async Task> CreateCo
// Create records and generate embeddings for them.
var tasks = entries.Select(entry => Task.Run(async () =>
{
- var record = createRecord(entry, await embeddingGenerationService.GenerateEmbeddingAsync(entry).ConfigureAwait(false));
+ var record = createRecord(entry, (await embeddingGenerator.GenerateAsync(entry).ConfigureAwait(false)).Vector);
await collection.UpsertAsync(record).ConfigureAwait(false);
}));
await Task.WhenAll(tasks).ConfigureAwait(false);
@@ -72,13 +72,13 @@ internal static async Task> CreateCo
/// Instance of used to created the collection.
/// The collection name.
/// A list of s.
- /// A text embedding generation service.
+ /// An embedding generator service.
/// A delegate which can create a record with a valid key for each string and it's embedding.
internal static async Task> CreateCollectionFromTextSearchResultsAsync(
this IVectorStore vectorStore,
string collectionName,
IList searchResults,
- ITextEmbeddingGenerationService embeddingGenerationService,
+ IEmbeddingGenerator> embeddingGenerator,
CreateRecordFromTextSearchResult createRecord)
where TKey : notnull
where TRecord : notnull
@@ -90,7 +90,7 @@ internal static async Task> CreateCo
// Create records and generate embeddings for them.
var tasks = searchResults.Select(searchResult => Task.Run(async () =>
{
- var record = createRecord(searchResult, await embeddingGenerationService.GenerateEmbeddingAsync(searchResult.Value!).ConfigureAwait(false));
+ var record = createRecord(searchResult, (await embeddingGenerator.GenerateAsync(searchResult.Value!).ConfigureAwait(false)).Vector);
await collection.UpsertAsync(record).ConfigureAwait(false);
}));
await Task.WhenAll(tasks).ConfigureAwait(false);
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_DataIngestion_MultiStore.cs b/dotnet/samples/Concepts/Memory/VectorStore_DataIngestion_MultiStore.cs
index 944013a72a1d..3939ff2d0f5d 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_DataIngestion_MultiStore.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_DataIngestion_MultiStore.cs
@@ -1,15 +1,16 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.ClientModel;
using System.Text.Json;
+using Azure.AI.OpenAI;
using Memory.VectorStoreFixtures;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Connectors.InMemory;
using Microsoft.SemanticKernel.Connectors.Qdrant;
using Microsoft.SemanticKernel.Connectors.Redis;
-using Microsoft.SemanticKernel.Embeddings;
using Qdrant.Client;
using StackExchange.Redis;
@@ -46,7 +47,7 @@ public async Task ExampleWithDIAsync(string databaseType)
.CreateBuilder();
// Register an embedding generation service with the DI container.
- kernelBuilder.AddAzureOpenAITextEmbeddingGeneration(
+ kernelBuilder.AddAzureOpenAIEmbeddingGenerator(
deploymentName: TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
endpoint: TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
apiKey: TestConfiguration.AzureOpenAIEmbeddings.ApiKey);
@@ -99,10 +100,9 @@ public async Task ExampleWithDIAsync(string databaseType)
public async Task ExampleWithoutDIAsync(string databaseType)
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- TestConfiguration.AzureOpenAIEmbeddings.ApiKey);
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new ApiKeyCredential(TestConfiguration.AzureOpenAIEmbeddings.ApiKey))
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Construct the chosen vector store and initialize docker containers via the fixtures where needed.
IVectorStore vectorStore;
@@ -128,7 +128,7 @@ public async Task ExampleWithoutDIAsync(string databaseType)
}
// Create the DataIngestor.
- var dataIngestor = new DataIngestor(vectorStore, textEmbeddingGenerationService);
+ var dataIngestor = new DataIngestor(vectorStore, embeddingGenerator);
// Invoke the data ingestor using an appropriate key generator function for each database type.
// Redis and InMemory supports string keys, while Qdrant supports ulong or Guid keys, so we use a different key generator for each key type.
@@ -160,8 +160,8 @@ private async Task UpsertDataAndReadFromVectorStoreAsync(DataIngestor data
/// Sample class that does ingestion of sample data into a vector store and allows retrieval of data from the vector store.
///
/// The vector store to ingest data into.
- /// Used to generate embeddings for the data being ingested.
- private sealed class DataIngestor(IVectorStore vectorStore, ITextEmbeddingGenerationService textEmbeddingGenerationService)
+ /// Used to generate embeddings for the data being ingested.
+ private sealed class DataIngestor(IVectorStore vectorStore, IEmbeddingGenerator> embeddingGenerator)
{
///
/// Create some glossary entries and upsert them into the vector store.
@@ -179,7 +179,7 @@ public async Task> ImportDataAsync(Func uniqueKeyG
var glossaryEntries = CreateGlossaryEntries(uniqueKeyGenerator).ToList();
var tasks = glossaryEntries.Select(entry => Task.Run(async () =>
{
- entry.DefinitionEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(entry.Definition);
+ entry.DefinitionEmbedding = (await embeddingGenerator.GenerateAsync(entry.Definition)).Vector;
}));
await Task.WhenAll(tasks);
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_DataIngestion_Simple.cs b/dotnet/samples/Concepts/Memory/VectorStore_DataIngestion_Simple.cs
index fd9a20c0cb2b..878fdc1d836a 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_DataIngestion_Simple.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_DataIngestion_Simple.cs
@@ -1,12 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
+using Azure.AI.OpenAI;
using Azure.Identity;
using Memory.VectorStoreFixtures;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Connectors.Qdrant;
-using Microsoft.SemanticKernel.Embeddings;
using Qdrant.Client;
namespace Memory;
@@ -29,10 +29,9 @@ public class VectorStore_DataIngestion_Simple(ITestOutputHelper output, VectorSt
public async Task ExampleAsync()
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Initiate the docker container and construct the vector store.
await qdrantFixture.ManualInitializeAsync();
@@ -46,7 +45,7 @@ public async Task ExampleAsync()
var glossaryEntries = CreateGlossaryEntries().ToList();
var tasks = glossaryEntries.Select(entry => Task.Run(async () =>
{
- entry.DefinitionEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(entry.Definition);
+ entry.DefinitionEmbedding = (await embeddingGenerator.GenerateAsync(entry.Definition)).Vector;
}));
await Task.WhenAll(tasks);
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_DynamicDataModel_Interop.cs b/dotnet/samples/Concepts/Memory/VectorStore_DynamicDataModel_Interop.cs
index d7bb667284f4..7b96129afe79 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_DynamicDataModel_Interop.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_DynamicDataModel_Interop.cs
@@ -1,12 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
+using Azure.AI.OpenAI;
using Azure.Identity;
using Memory.VectorStoreFixtures;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Connectors.Qdrant;
-using Microsoft.SemanticKernel.Embeddings;
using Qdrant.Client;
namespace Memory;
@@ -39,10 +39,9 @@ public class VectorStore_DynamicDataModel_Interop(ITestOutputHelper output, Vect
public async Task UpsertWithDynamicRetrieveWithCustomAsync()
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Initiate the docker container and construct the vector store.
await qdrantFixture.ManualInitializeAsync();
@@ -56,7 +55,7 @@ public async Task UpsertWithDynamicRetrieveWithCustomAsync()
var glossaryEntries = CreateDynamicGlossaryEntries().ToList();
var tasks = glossaryEntries.Select(entry => Task.Run(async () =>
{
- entry["DefinitionEmbedding"] = await textEmbeddingGenerationService.GenerateEmbeddingAsync((string)entry["Definition"]!);
+ entry["DefinitionEmbedding"] = (await embeddingGenerator.GenerateAsync((string)entry["Definition"]!)).Vector;
}));
await Task.WhenAll(tasks);
@@ -79,10 +78,9 @@ public async Task UpsertWithDynamicRetrieveWithCustomAsync()
public async Task UpsertWithCustomRetrieveWithDynamicAsync()
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Initiate the docker container and construct the vector store.
await qdrantFixture.ManualInitializeAsync();
@@ -96,7 +94,7 @@ public async Task UpsertWithCustomRetrieveWithDynamicAsync()
var glossaryEntries = CreateCustomGlossaryEntries().ToList();
var tasks = glossaryEntries.Select(entry => Task.Run(async () =>
{
- entry.DefinitionEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(entry.Definition);
+ entry.DefinitionEmbedding = (await embeddingGenerator.GenerateAsync(entry.Definition)).Vector;
}));
await Task.WhenAll(tasks);
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_HybridSearch_Simple_AzureAISearch.cs b/dotnet/samples/Concepts/Memory/VectorStore_HybridSearch_Simple_AzureAISearch.cs
index 1ce7b2e87be0..128f70bcbdb6 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_HybridSearch_Simple_AzureAISearch.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_HybridSearch_Simple_AzureAISearch.cs
@@ -1,12 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure;
+using Azure.AI.OpenAI;
using Azure.Identity;
using Azure.Search.Documents.Indexes;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Connectors.AzureAISearch;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
-using Microsoft.SemanticKernel.Embeddings;
namespace Memory;
@@ -25,10 +25,9 @@ public class VectorStore_HybridSearch_Simple_AzureAISearch(ITestOutputHelper out
public async Task IngestDataAndUseHybridSearch()
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Construct the AzureAISearch VectorStore.
var searchIndexClient = new SearchIndexClient(
@@ -45,7 +44,7 @@ public async Task IngestDataAndUseHybridSearch()
var glossaryEntries = CreateGlossaryEntries().ToList();
var tasks = glossaryEntries.Select(entry => Task.Run(async () =>
{
- entry.DefinitionEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(entry.Definition);
+ entry.DefinitionEmbedding = (await embeddingGenerator.GenerateAsync(entry.Definition)).Vector;
}));
await Task.WhenAll(tasks);
@@ -55,7 +54,7 @@ public async Task IngestDataAndUseHybridSearch()
// Search the collection using a vector search.
var searchString = "What is an Application Programming Interface";
- var searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ var searchVector = (await embeddingGenerator.GenerateAsync(searchString)).Vector;
var resultRecords = await hybridSearchCollection.HybridSearchAsync(searchVector, ["Application", "Programming", "Interface"], top: 1).ToListAsync();
Console.WriteLine("Search string: " + searchString);
@@ -64,7 +63,7 @@ public async Task IngestDataAndUseHybridSearch()
// Search the collection using a vector search.
searchString = "What is Retrieval Augmented Generation";
- searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ searchVector = (await embeddingGenerator.GenerateAsync(searchString)).Vector;
resultRecords = await hybridSearchCollection.HybridSearchAsync(searchVector, ["Retrieval", "Augmented", "Generation"], top: 1).ToListAsync();
Console.WriteLine("Search string: " + searchString);
@@ -73,7 +72,7 @@ public async Task IngestDataAndUseHybridSearch()
// Search the collection using a vector search with pre-filtering.
searchString = "What is Retrieval Augmented Generation";
- searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ searchVector = (await embeddingGenerator.GenerateAsync(searchString)).Vector;
resultRecords = await hybridSearchCollection.HybridSearchAsync(searchVector, ["Retrieval", "Augmented", "Generation"], top: 3, new() { Filter = g => g.Category == "External Definitions" }).ToListAsync();
Console.WriteLine("Search string: " + searchString);
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_Langchain_Interop.cs b/dotnet/samples/Concepts/Memory/VectorStore_Langchain_Interop.cs
index ca10dbe496ee..608d543fe6fd 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_Langchain_Interop.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_Langchain_Interop.cs
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
+using Azure.AI.OpenAI;
using Azure.Identity;
using Memory.VectorStoreLangchainInterop;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
-using Microsoft.SemanticKernel.Embeddings;
using StackExchange.Redis;
using Sdk = Pinecone;
@@ -56,17 +56,16 @@ public async Task ReadDataFromLangchainRedisAsync()
private async Task ReadDataFromCollectionAsync(IVectorStore vectorStore, string collectionName)
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Get the collection.
var collection = vectorStore.GetCollection>(collectionName);
// Search the data set.
var searchString = "I'm looking for an animal that is loyal and will make a great companion";
- var searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ var searchVector = (await embeddingGenerator.GenerateAsync(searchString)).Vector;
var resultRecords = await collection.SearchEmbeddingAsync(searchVector, top: 1).ToListAsync();
this.Output.WriteLine("Search string: " + searchString);
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_AzureAISearch.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_AzureAISearch.cs
index e635a0837a9a..4116b42e518b 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_AzureAISearch.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_AzureAISearch.cs
@@ -1,12 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure;
+using Azure.AI.OpenAI;
using Azure.Identity;
using Azure.Search.Documents.Indexes;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.AzureAISearch;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
namespace Memory;
@@ -37,7 +38,7 @@ public async Task ExampleWithDIAsync()
.CreateBuilder();
// Register an embedding generation service with the DI container.
- kernelBuilder.AddAzureOpenAITextEmbeddingGeneration(
+ kernelBuilder.AddAzureOpenAIEmbeddingGenerator(
deploymentName: TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
endpoint: TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
credential: new AzureCliCredential());
@@ -67,10 +68,9 @@ public async Task ExampleWithDIAsync()
public async Task ExampleWithoutDIAsync()
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Construct the Azure AI Search VectorStore.
var searchIndexClient = new SearchIndexClient(
@@ -79,7 +79,7 @@ public async Task ExampleWithoutDIAsync()
var vectorStore = new AzureAISearchVectorStore(searchIndexClient);
// Create the common processor that works for any vector store.
- var processor = new VectorStore_VectorSearch_MultiStore_Common(vectorStore, textEmbeddingGenerationService, this.Output);
+ var processor = new VectorStore_VectorSearch_MultiStore_Common(vectorStore, embeddingGenerator, this.Output);
// Run the process and pass a key generator function to it, to generate unique record keys.
// The key generator function is required, since different vector stores may require different key types.
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Common.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Common.cs
index 9f50d8b56b28..65a501538d9d 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Common.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Common.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Embeddings;
namespace Memory;
@@ -10,7 +10,7 @@ namespace Memory;
/// The example shows how to write code that can be used with multiple database types.
/// This class contains the common code.
///
-/// For the entrypoint of the example for each database, see the following classes:
+/// For the entry point of the example for each database, see the following classes:
///
///
///
@@ -18,9 +18,9 @@ namespace Memory;
///
///
/// The vector store to ingest data into.
-/// The service to use for generating embeddings.
-/// A helper to write output to the xunit test output stream.
-public class VectorStore_VectorSearch_MultiStore_Common(IVectorStore vectorStore, ITextEmbeddingGenerationService textEmbeddingGenerationService, ITestOutputHelper output)
+/// The service to use for generating embeddings.
+/// A helper to write output to the xUnit test output stream.
+public class VectorStore_VectorSearch_MultiStore_Common(IVectorStore vectorStore, IEmbeddingGenerator> embeddingGenerator, ITestOutputHelper output)
{
///
/// Ingest data into a collection with the given name, and search over that data.
@@ -40,7 +40,7 @@ public async Task IngestDataAndSearchAsync(string collectionName, Func Task.Run(async () =>
{
- entry.DefinitionEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(entry.Definition);
+ entry.DefinitionEmbedding = (await embeddingGenerator.GenerateAsync(entry.Definition)).Vector;
}));
await Task.WhenAll(tasks);
@@ -50,7 +50,7 @@ public async Task IngestDataAndSearchAsync(string collectionName, Func(string collectionName, Func(string collectionName, Func g.Category == "External Definitions" }).ToListAsync();
output.WriteLine("Search string: " + searchString);
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_InMemory.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_InMemory.cs
index bb992e4fbde9..9d2eb384c749 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_InMemory.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_InMemory.cs
@@ -1,9 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
+using Azure.AI.OpenAI;
using Azure.Identity;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Connectors.InMemory;
namespace Memory;
@@ -30,7 +31,7 @@ public async Task ExampleWithDIAsync()
.CreateBuilder();
// Register an embedding generation service with the DI container.
- kernelBuilder.AddAzureOpenAITextEmbeddingGeneration(
+ kernelBuilder.AddAzureOpenAIEmbeddingGenerator(
deploymentName: TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
endpoint: TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
credential: new AzureCliCredential());
@@ -50,7 +51,7 @@ public async Task ExampleWithDIAsync()
// Run the process and pass a key generator function to it, to generate unique record keys.
// The key generator function is required, since different vector stores may require different key types.
- // E.g. InMemory supports any comparible type, but others may only support string or Guid or ulong, etc.
+ // E.g. InMemory supports any comparable type, but others may only support string or Guid or ulong, etc.
// For this example we'll use int.
var uniqueId = 0;
await processor.IngestDataAndSearchAsync("skglossaryWithDI", () => uniqueId++);
@@ -60,20 +61,19 @@ public async Task ExampleWithDIAsync()
public async Task ExampleWithoutDIAsync()
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Construct the InMemory VectorStore.
var vectorStore = new InMemoryVectorStore();
// Create the common processor that works for any vector store.
- var processor = new VectorStore_VectorSearch_MultiStore_Common(vectorStore, textEmbeddingGenerationService, this.Output);
+ var processor = new VectorStore_VectorSearch_MultiStore_Common(vectorStore, embeddingGenerator, this.Output);
// Run the process and pass a key generator function to it, to generate unique record keys.
// The key generator function is required, since different vector stores may require different key types.
- // E.g. InMemory supports any comparible type, but others may only support string or Guid or ulong, etc.
+ // E.g. InMemory supports any comparable type, but others may only support string or Guid or ulong, etc.
// For this example we'll use int.
var uniqueId = 0;
await processor.IngestDataAndSearchAsync("skglossaryWithoutDI", () => uniqueId++);
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Postgres.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Postgres.cs
index e0d65e3c3a2b..6b6f530208fa 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Postgres.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Postgres.cs
@@ -1,10 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
+using Azure.AI.OpenAI;
using Azure.Identity;
using Memory.VectorStoreFixtures;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Connectors.Postgres;
using Npgsql;
@@ -34,7 +35,7 @@ public async Task ExampleWithDIAsync()
.CreateBuilder();
// Register an embedding generation service with the DI container.
- kernelBuilder.AddAzureOpenAITextEmbeddingGeneration(
+ kernelBuilder.AddAzureOpenAIEmbeddingGenerator(
deploymentName: TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
endpoint: TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
credential: new AzureCliCredential());
@@ -63,10 +64,9 @@ public async Task ExampleWithDIAsync()
public async Task ExampleWithoutDIAsync()
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Initialize the Postgres docker container via the fixtures and construct the Postgres VectorStore.
await PostgresFixture.ManualInitializeAsync();
@@ -76,7 +76,7 @@ public async Task ExampleWithoutDIAsync()
var vectorStore = new PostgresVectorStore(dataSource);
// Create the common processor that works for any vector store.
- var processor = new VectorStore_VectorSearch_MultiStore_Common(vectorStore, textEmbeddingGenerationService, this.Output);
+ var processor = new VectorStore_VectorSearch_MultiStore_Common(vectorStore, embeddingGenerator, this.Output);
// Run the process and pass a key generator function to it, to generate unique record keys.
// The key generator function is required, since different vector stores may require different key types.
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Qdrant.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Qdrant.cs
index b0073684890c..54462538f1f3 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Qdrant.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Qdrant.cs
@@ -1,10 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
+using Azure.AI.OpenAI;
using Azure.Identity;
using Memory.VectorStoreFixtures;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Connectors.Qdrant;
using Qdrant.Client;
@@ -34,7 +35,7 @@ public async Task ExampleWithDIAsync()
.CreateBuilder();
// Register an embedding generation service with the DI container.
- kernelBuilder.AddAzureOpenAITextEmbeddingGeneration(
+ kernelBuilder.AddAzureOpenAIEmbeddingGenerator(
deploymentName: TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
endpoint: TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
credential: new AzureCliCredential());
@@ -63,10 +64,9 @@ public async Task ExampleWithDIAsync()
public async Task ExampleWithoutDIAsync()
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Initialize the Qdrant docker container via the fixtures and construct the Qdrant VectorStore.
await qdrantFixture.ManualInitializeAsync();
@@ -74,7 +74,7 @@ public async Task ExampleWithoutDIAsync()
var vectorStore = new QdrantVectorStore(qdrantClient);
// Create the common processor that works for any vector store.
- var processor = new VectorStore_VectorSearch_MultiStore_Common(vectorStore, textEmbeddingGenerationService, this.Output);
+ var processor = new VectorStore_VectorSearch_MultiStore_Common(vectorStore, embeddingGenerator, this.Output);
// Run the process and pass a key generator function to it, to generate unique record keys.
// The key generator function is required, since different vector stores may require different key types.
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Redis.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Redis.cs
index 69cf792a8cbe..4f43bc532977 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Redis.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Redis.cs
@@ -1,10 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
+using Azure.AI.OpenAI;
using Azure.Identity;
using Memory.VectorStoreFixtures;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Connectors.Redis;
using StackExchange.Redis;
@@ -39,7 +40,7 @@ public async Task ExampleWithDIAsync(RedisStorageType redisStorageType)
.CreateBuilder();
// Register an embedding generation service with the DI container.
- kernelBuilder.AddAzureOpenAITextEmbeddingGeneration(
+ kernelBuilder.AddAzureOpenAIEmbeddingGenerator(
deploymentName: TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
endpoint: TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
credential: new AzureCliCredential());
@@ -72,10 +73,9 @@ public async Task ExampleWithDIAsync(RedisStorageType redisStorageType)
public async Task ExampleWithoutDIAsync(RedisStorageType redisStorageType)
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Initialize the Redis docker container via the fixtures and construct the Redis VectorStore with the preferred storage type.
await redisFixture.ManualInitializeAsync();
@@ -83,7 +83,7 @@ public async Task ExampleWithoutDIAsync(RedisStorageType redisStorageType)
var vectorStore = new RedisVectorStore(database, new() { StorageType = redisStorageType });
// Create the common processor that works for any vector store.
- var processor = new VectorStore_VectorSearch_MultiStore_Common(vectorStore, textEmbeddingGenerationService, this.Output);
+ var processor = new VectorStore_VectorSearch_MultiStore_Common(vectorStore, embeddingGenerator, this.Output);
// Run the process and pass a key generator function to it, to generate unique record keys.
// The key generator function is required, since different vector stores may require different key types.
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiVector.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiVector.cs
index 2cd98b672944..9ddf4976fda3 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiVector.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiVector.cs
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
+using Azure.AI.OpenAI;
using Azure.Identity;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Connectors.InMemory;
-using Microsoft.SemanticKernel.Embeddings;
namespace Memory;
@@ -23,10 +23,9 @@ public class VectorStore_VectorSearch_MultiVector(ITestOutputHelper output) : Ba
public async Task VectorSearchWithMultiVectorRecordAsync()
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Construct an InMemory vector store.
var vectorStore = new InMemoryVectorStore();
@@ -39,11 +38,11 @@ public async Task VectorSearchWithMultiVectorRecordAsync()
var productRecords = CreateProductRecords().ToList();
var tasks = productRecords.Select(entry => Task.Run(async () =>
{
- var descriptionEmbeddingTask = textEmbeddingGenerationService.GenerateEmbeddingAsync(entry.Description);
- var featureListEmbeddingTask = textEmbeddingGenerationService.GenerateEmbeddingAsync(string.Join("\n", entry.FeatureList));
+ var descriptionEmbeddingTask = embeddingGenerator.GenerateAsync(entry.Description);
+ var featureListEmbeddingTask = embeddingGenerator.GenerateAsync(string.Join("\n", entry.FeatureList));
- entry.DescriptionEmbedding = await descriptionEmbeddingTask;
- entry.FeatureListEmbedding = await featureListEmbeddingTask;
+ entry.DescriptionEmbedding = (await descriptionEmbeddingTask).Vector;
+ entry.FeatureListEmbedding = (await featureListEmbeddingTask).Vector;
}));
await Task.WhenAll(tasks);
@@ -53,7 +52,7 @@ public async Task VectorSearchWithMultiVectorRecordAsync()
// Search the store using the description embedding.
var searchString = "I am looking for a reasonably priced coffee maker";
- var searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ var searchVector = (await embeddingGenerator.GenerateAsync(searchString)).Vector;
var resultRecords = await collection.SearchEmbeddingAsync(
searchVector, top: 1, new()
{
@@ -67,7 +66,7 @@ public async Task VectorSearchWithMultiVectorRecordAsync()
// Search the store using the feature list embedding.
searchString = "I am looking for a handheld vacuum cleaner that will remove pet hair";
- searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ searchVector = (await embeddingGenerator.GenerateAsync(searchString)).Vector;
resultRecords = await collection.SearchEmbeddingAsync(
searchVector,
top: 1,
diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_Simple.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_Simple.cs
index 9f2e7f1315db..56def93238ac 100644
--- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_Simple.cs
+++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_Simple.cs
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
+using Azure.AI.OpenAI;
using Azure.Identity;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Connectors.InMemory;
-using Microsoft.SemanticKernel.Embeddings;
namespace Memory;
@@ -23,10 +23,9 @@ public class VectorStore_VectorSearch_Simple(ITestOutputHelper output) : BaseTes
public async Task ExampleAsync()
{
// Create an embedding generation service.
- var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
// Construct an InMemory vector store.
var vectorStore = new InMemoryVectorStore();
@@ -39,7 +38,7 @@ public async Task ExampleAsync()
var glossaryEntries = CreateGlossaryEntries().ToList();
var tasks = glossaryEntries.Select(entry => Task.Run(async () =>
{
- entry.DefinitionEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(entry.Definition);
+ entry.DefinitionEmbedding = (await embeddingGenerator.GenerateAsync(entry.Definition)).Vector;
}));
await Task.WhenAll(tasks);
@@ -49,7 +48,7 @@ public async Task ExampleAsync()
// Search the collection using a vector search.
var searchString = "What is an Application Programming Interface";
- var searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ var searchVector = (await embeddingGenerator.GenerateAsync(searchString)).Vector;
var resultRecords = await collection.SearchEmbeddingAsync(searchVector, top: 1).ToListAsync();
Console.WriteLine("Search string: " + searchString);
@@ -58,7 +57,7 @@ public async Task ExampleAsync()
// Search the collection using a vector search.
searchString = "What is Retrieval Augmented Generation";
- searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ searchVector = (await embeddingGenerator.GenerateAsync(searchString)).Vector;
resultRecords = await collection.SearchEmbeddingAsync(searchVector, top: 1).ToListAsync();
Console.WriteLine("Search string: " + searchString);
@@ -67,7 +66,7 @@ public async Task ExampleAsync()
// Search the collection using a vector search with pre-filtering.
searchString = "What is Retrieval Augmented Generation";
- searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ searchVector = (await embeddingGenerator.GenerateAsync(searchString)).Vector;
resultRecords = await collection.SearchEmbeddingAsync(searchVector, top: 3, new() { Filter = g => g.Category == "External Definitions" }).ToListAsync();
Console.WriteLine("Search string: " + searchString);
diff --git a/dotnet/samples/Concepts/Memory/VolatileVectorStore_LoadData.cs b/dotnet/samples/Concepts/Memory/VolatileVectorStore_LoadData.cs
index e3a2c2dc0e64..a6e6970c7ca1 100644
--- a/dotnet/samples/Concepts/Memory/VolatileVectorStore_LoadData.cs
+++ b/dotnet/samples/Concepts/Memory/VolatileVectorStore_LoadData.cs
@@ -1,11 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.ClientModel;
+using System.ClientModel.Primitives;
using System.Text.Json;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Connectors.InMemory;
-using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Data;
-using Microsoft.SemanticKernel.Embeddings;
using Resources;
namespace Memory;
@@ -24,10 +25,11 @@ public async Task LoadStringListAndSearchAsync()
var httpClient = new HttpClient(handler);
// Create an embedding generation service.
- var embeddingGenerationService = new OpenAITextEmbeddingGenerationService(
- modelId: TestConfiguration.OpenAI.EmbeddingModelId,
- apiKey: TestConfiguration.OpenAI.ApiKey,
- httpClient: httpClient);
+ var embeddingGenerator = new OpenAI.OpenAIClient(
+ new ApiKeyCredential(TestConfiguration.OpenAI.ApiKey),
+ new OpenAI.OpenAIClientOptions() { Transport = new HttpClientPipelineTransport(httpClient) })
+ .GetEmbeddingClient(TestConfiguration.OpenAI.EmbeddingModelId)
+ .AsIEmbeddingGenerator();
// Construct an InMemory vector store.
var vectorStore = new InMemoryVectorStore();
@@ -54,7 +56,7 @@ static DataModel CreateRecord(string text, ReadOnlyMemory embedding)
// Create a record collection from a list of strings using the provided delegate.
var collection = await vectorStore.CreateCollectionFromListAsync(
- collectionName, lines, embeddingGenerationService, CreateRecord);
+ collectionName, lines, embeddingGenerator, CreateRecord);
// Save the record collection to a file stream.
using (FileStream fileStream = new(filePath, FileMode.OpenOrCreate))
@@ -70,7 +72,7 @@ static DataModel CreateRecord(string text, ReadOnlyMemory embedding)
// Search the collection using a vector search.
var searchString = "What is the Semantic Kernel?";
- var searchVector = await embeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ var searchVector = (await embeddingGenerator.GenerateAsync(searchString)).Vector;
var resultRecords = await vectorSearch!.SearchEmbeddingAsync(searchVector, top: 1).ToListAsync();
Console.WriteLine("Search string: " + searchString);
@@ -83,9 +85,9 @@ static DataModel CreateRecord(string text, ReadOnlyMemory embedding)
public async Task LoadTextSearchResultsAndSearchAsync()
{
// Create an embedding generation service.
- var embeddingGenerationService = new OpenAITextEmbeddingGenerationService(
- modelId: TestConfiguration.OpenAI.EmbeddingModelId,
- apiKey: TestConfiguration.OpenAI.ApiKey);
+ var embeddingGenerator = new OpenAI.OpenAIClient(TestConfiguration.OpenAI.ApiKey)
+ .GetEmbeddingClient(TestConfiguration.OpenAI.EmbeddingModelId)
+ .AsIEmbeddingGenerator();
// Construct an InMemory vector store.
var vectorStore = new InMemoryVectorStore();
@@ -110,11 +112,11 @@ static DataModel CreateRecord(TextSearchResult searchResult, ReadOnlyMemory(
- collectionName, searchResults!, embeddingGenerationService, CreateRecord);
+ collectionName, searchResults!, embeddingGenerator, CreateRecord);
// Search the collection using a vector search.
var searchString = "What is the Semantic Kernel?";
- var searchVector = await embeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ var searchVector = (await embeddingGenerator.GenerateAsync(searchString)).Vector;
var resultRecords = await vectorSearch!.SearchEmbeddingAsync(searchVector, top: 1).ToListAsync();
Console.WriteLine("Search string: " + searchString);
diff --git a/dotnet/samples/Concepts/Optimization/FrugalGPTWithFilters.cs b/dotnet/samples/Concepts/Optimization/FrugalGPTWithFilters.cs
index d228c5215ed1..abfe2750680f 100644
--- a/dotnet/samples/Concepts/Optimization/FrugalGPTWithFilters.cs
+++ b/dotnet/samples/Concepts/Optimization/FrugalGPTWithFilters.cs
@@ -1,12 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Runtime.CompilerServices;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.InMemory;
-using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
using Microsoft.SemanticKernel.Services;
@@ -58,7 +58,7 @@ public async Task ReducePromptSizeAsync()
.AddOpenAIChatCompletion(
modelId: "gpt-4",
apiKey: TestConfiguration.OpenAI.ApiKey)
- .AddOpenAITextEmbeddingGeneration(
+ .AddOpenAIEmbeddingGenerator(
modelId: "text-embedding-3-small",
apiKey: TestConfiguration.OpenAI.ApiKey)
.Build();
@@ -99,10 +99,10 @@ public async Task ReducePromptSizeAsync()
// Add few-shot prompt optimization filter.
// The filter uses in-memory store for vector similarity search and text embedding generation service to generate embeddings.
var vectorStore = new InMemoryVectorStore();
- var textEmbeddingGenerationService = kernel.GetRequiredService();
+ var embeddingGenerator = kernel.GetRequiredService>>();
// Register optimization filter.
- kernel.PromptRenderFilters.Add(new FewShotPromptOptimizationFilter(vectorStore, textEmbeddingGenerationService));
+ kernel.PromptRenderFilters.Add(new FewShotPromptOptimizationFilter(vectorStore, embeddingGenerator));
// Get result again and compare the usage.
result = await kernel.InvokeAsync(function, arguments);
@@ -169,7 +169,7 @@ public async Task LLMCascadeAsync()
///
private sealed class FewShotPromptOptimizationFilter(
IVectorStore vectorStore,
- ITextEmbeddingGenerationService textEmbeddingGenerationService) : IPromptRenderFilter
+ IEmbeddingGenerator> embeddingGenerator) : IPromptRenderFilter
{
///
/// Maximum number of examples to use which are similar to original request.
@@ -192,7 +192,7 @@ public async Task OnPromptRenderAsync(PromptRenderContext context, Func();
// Generate embedding for each example.
- var embeddings = await textEmbeddingGenerationService.GenerateEmbeddingsAsync(examples);
+ var embeddings = (await embeddingGenerator.GenerateAsync(examples));
// Create vector store record instances with example text and embedding.
for (var i = 0; i < examples.Count; i++)
@@ -201,7 +201,7 @@ public async Task OnPromptRenderAsync(PromptRenderContext context, Func acceptanceCriteria,
ITestOutputHelper output) : IFunctionInvocationFilter
{
- public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func next)
+ public async Task OnFunctionInvocationAsync(Microsoft.SemanticKernel.FunctionInvocationContext context, Func next)
{
// Get registered chat completion services from kernel.
var registeredServices = context.Kernel
diff --git a/dotnet/samples/Concepts/Optimization/PluginSelectionWithFilters.cs b/dotnet/samples/Concepts/Optimization/PluginSelectionWithFilters.cs
index 73a4c8fd0815..600fe20e56cb 100644
--- a/dotnet/samples/Concepts/Optimization/PluginSelectionWithFilters.cs
+++ b/dotnet/samples/Concepts/Optimization/PluginSelectionWithFilters.cs
@@ -1,13 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
-using Microsoft.SemanticKernel.Embeddings;
namespace Optimization;
@@ -34,7 +34,7 @@ public async Task UsingVectorSearchWithKernelAsync()
var builder = Kernel
.CreateBuilder()
.AddOpenAIChatCompletion("gpt-4", TestConfiguration.OpenAI.ApiKey)
- .AddOpenAITextEmbeddingGeneration("text-embedding-3-small", TestConfiguration.OpenAI.ApiKey);
+ .AddOpenAIEmbeddingGenerator("text-embedding-3-small", TestConfiguration.OpenAI.ApiKey);
// Add logging.
var logger = this.LoggerFactory.CreateLogger();
@@ -108,7 +108,7 @@ public async Task UsingVectorSearchWithChatCompletionAsync()
var builder = Kernel
.CreateBuilder()
.AddOpenAIChatCompletion("gpt-4", TestConfiguration.OpenAI.ApiKey)
- .AddOpenAITextEmbeddingGeneration("text-embedding-3-small", TestConfiguration.OpenAI.ApiKey);
+ .AddOpenAIEmbeddingGenerator("text-embedding-3-small", TestConfiguration.OpenAI.ApiKey);
// Add logging.
var logger = this.LoggerFactory.CreateLogger();
@@ -181,7 +181,7 @@ private sealed class PluginSelectionFilter(
string collectionName,
int numberOfBestFunctions) : IFunctionInvocationFilter
{
- public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func next)
+ public async Task OnFunctionInvocationAsync(Microsoft.SemanticKernel.FunctionInvocationContext context, Func next)
{
var request = GetRequestArgument(context.Arguments);
@@ -280,7 +280,7 @@ public string GetFunctionKey(KernelFunction kernelFunction)
}
public class FunctionProvider(
- ITextEmbeddingGenerationService textEmbeddingGenerationService,
+ IEmbeddingGenerator> embeddingGenerator,
IVectorStore vectorStore,
IFunctionKeyProvider functionKeyProvider) : IFunctionProvider
{
@@ -292,7 +292,7 @@ public async Task> GetBestFunctionsAsync(
CancellationToken cancellationToken = default)
{
// Generate embedding for original request.
- var requestEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(request, cancellationToken: cancellationToken);
+ var requestEmbedding = await embeddingGenerator.GenerateAsync(request, cancellationToken: cancellationToken);
var collection = vectorStore.GetCollection(collectionName);
await collection.CreateCollectionIfNotExistsAsync(cancellationToken);
@@ -309,7 +309,7 @@ public async Task> GetBestFunctionsAsync(
}
public class PluginStore(
- ITextEmbeddingGenerationService textEmbeddingGenerationService,
+ IEmbeddingGenerator> embeddingGenerator,
IVectorStore vectorStore,
IFunctionKeyProvider functionKeyProvider) : IPluginStore
{
@@ -320,8 +320,8 @@ public async Task SaveAsync(string collectionName, KernelPluginCollection plugin
var functionsData = GetFunctionsData(plugins);
// Generate embedding for each function.
- var embeddings = await textEmbeddingGenerationService
- .GenerateEmbeddingsAsync(functionsData.Select(l => l.TextToVectorize).ToArray(), cancellationToken: cancellationToken);
+ var embeddings = await embeddingGenerator
+ .GenerateAsync(functionsData.Select(l => l.TextToVectorize).ToArray(), cancellationToken: cancellationToken);
// Create vector store record instances with function information and embedding.
for (var i = 0; i < functionsData.Count; i++)
@@ -332,7 +332,7 @@ public async Task SaveAsync(string collectionName, KernelPluginCollection plugin
{
Id = functionKeyProvider.GetFunctionKey(function),
FunctionInfo = functionInfo,
- FunctionInfoEmbedding = embeddings[i]
+ FunctionInfoEmbedding = embeddings[i].Vector
});
}
diff --git a/dotnet/samples/Concepts/README.md b/dotnet/samples/Concepts/README.md
index 600aade73c4c..cd36158782c7 100644
--- a/dotnet/samples/Concepts/README.md
+++ b/dotnet/samples/Concepts/README.md
@@ -129,6 +129,7 @@ dotnet test -l "console;verbosity=detailed" --filter "FullyQualifiedName=ChatCom
### Memory - Using AI [`Memory`](https://github.com/microsoft/semantic-kernel/tree/main/dotnet/src/SemanticKernel.Abstractions/Memory) concepts
+- [AWSBedrock_EmbeddingGeneration](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Memory/AWSBedrock_EmbeddingGeneration.cs)
- [OpenAI_EmbeddingGeneration](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Memory/OpenAI_EmbeddingGeneration.cs)
- [Ollama_EmbeddingGeneration](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Memory/Ollama_EmbeddingGeneration.cs)
- [Onnx_EmbeddingGeneration](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Memory/Onnx_EmbeddingGeneration.cs)
diff --git a/dotnet/samples/Concepts/Search/MyAzureAISearchPlugin.cs b/dotnet/samples/Concepts/Search/MyAzureAISearchPlugin.cs
index 3c5010e0f547..0872704f22f2 100644
--- a/dotnet/samples/Concepts/Search/MyAzureAISearchPlugin.cs
+++ b/dotnet/samples/Concepts/Search/MyAzureAISearchPlugin.cs
@@ -6,6 +6,7 @@
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Models;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Embeddings;
@@ -34,7 +35,7 @@ public async Task AzureAISearchPluginAsync()
kernelBuilder.Services.AddSingleton();
// Embedding generation service to convert string query to vector
- kernelBuilder.AddOpenAITextEmbeddingGeneration("text-embedding-ada-002", TestConfiguration.OpenAI.ApiKey);
+ kernelBuilder.AddOpenAIEmbeddingGenerator("text-embedding-ada-002", TestConfiguration.OpenAI.ApiKey);
// Chat completion service to ask questions based on data from Azure AI Search index.
kernelBuilder.AddOpenAIChatCompletion("gpt-4", TestConfiguration.OpenAI.ApiKey);
@@ -160,10 +161,10 @@ private sealed class AzureAISearchService(SearchIndexClient indexClient) : IAzur
/// It uses to perform a request to Azure AI Search.
///
private sealed class MyAzureAISearchPlugin(
- ITextEmbeddingGenerationService textEmbeddingGenerationService,
+ IEmbeddingGenerator> embeddingGenerator,
AzureAISearchPlugin.IAzureAISearchService searchService)
{
- private readonly ITextEmbeddingGenerationService _textEmbeddingGenerationService = textEmbeddingGenerationService;
+ private readonly IEmbeddingGenerator> _embeddingGenerator = embeddingGenerator;
private readonly IAzureAISearchService _searchService = searchService;
[KernelFunction("Search")]
@@ -174,7 +175,7 @@ public async Task SearchAsync(
CancellationToken cancellationToken = default)
{
// Convert string query to vector
- ReadOnlyMemory embedding = await this._textEmbeddingGenerationService.GenerateEmbeddingAsync(query, cancellationToken: cancellationToken);
+ ReadOnlyMemory embedding = (await this._embeddingGenerator.GenerateAsync(query, cancellationToken: cancellationToken)).Vector;
// Perform search
return await this._searchService.SearchAsync(collection, embedding, searchFields, cancellationToken) ?? string.Empty;
diff --git a/dotnet/samples/Demos/AgentFrameworkWithAspire/ChatWithAgent.ApiService/Program.cs b/dotnet/samples/Demos/AgentFrameworkWithAspire/ChatWithAgent.ApiService/Program.cs
index 89c83cb3a48e..ff63c2a3238a 100644
--- a/dotnet/samples/Demos/AgentFrameworkWithAspire/ChatWithAgent.ApiService/Program.cs
+++ b/dotnet/samples/Demos/AgentFrameworkWithAspire/ChatWithAgent.ApiService/Program.cs
@@ -138,12 +138,12 @@ private static void AddAIServices(WebApplicationBuilder builder, HostConfig conf
{
case AzureOpenAIEmbeddingsConfig.ConfigSectionName:
{
- builder.Services.AddAzureOpenAITextEmbeddingGeneration(config.AzureOpenAIEmbeddings.DeploymentName, modelId: config.AzureOpenAIEmbeddings.ModelName);
+ builder.Services.AddAzureOpenAIEmbeddingGenerator(config.AzureOpenAIEmbeddings.DeploymentName, modelId: config.AzureOpenAIEmbeddings.ModelName);
break;
}
case OpenAIEmbeddingsConfig.ConfigSectionName:
{
- builder.Services.AddOpenAITextEmbeddingGeneration(config.OpenAIEmbeddings.ModelName);
+ builder.Services.AddOpenAIEmbeddingGenerator(config.OpenAIEmbeddings.ModelName);
break;
}
default:
diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Extensions/VectorStoreExtensions.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Extensions/VectorStoreExtensions.cs
index 8d06423301e0..c35b037ab052 100644
--- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Extensions/VectorStoreExtensions.cs
+++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Extensions/VectorStoreExtensions.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
-using Microsoft.SemanticKernel.Embeddings;
namespace MCPServer;
@@ -25,14 +25,14 @@ public static class VectorStoreExtensions
/// The instance of used to create the collection.
/// The name of the collection.
/// The list of strings to create records from.
- /// The text embedding generation service.
+ /// The text embedding generation service.
/// The delegate which can create a record for each string and its embedding.
/// The created collection.
public static async Task> CreateCollectionFromListAsync(
this IVectorStore vectorStore,
string collectionName,
string[] entries,
- ITextEmbeddingGenerationService embeddingGenerationService,
+ IEmbeddingGenerator> embeddingGenerator,
CreateRecordFromString createRecord)
where TKey : notnull
where TRecord : notnull
@@ -44,7 +44,7 @@ public static async Task> CreateColl
// Create records and generate embeddings for them.
var tasks = entries.Select(entry => Task.Run(async () =>
{
- var record = createRecord(entry, await embeddingGenerationService.GenerateEmbeddingAsync(entry).ConfigureAwait(false));
+ var record = createRecord(entry, (await embeddingGenerator.GenerateAsync(entry).ConfigureAwait(false)).Vector);
await collection.UpsertAsync(record).ConfigureAwait(false);
}));
await Task.WhenAll(tasks).ConfigureAwait(false);
diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs
index aef6893f40d0..40d651a44c82 100644
--- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs
+++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Program.cs
@@ -10,7 +10,6 @@
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Connectors.InMemory;
-using Microsoft.SemanticKernel.Embeddings;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Server;
@@ -32,7 +31,7 @@
// Register embedding generation service and in-memory vector store
kernelBuilder.Services.AddSingleton();
-kernelBuilder.Services.AddOpenAITextEmbeddingGeneration(embeddingModelId, apiKey);
+kernelBuilder.Services.AddOpenAIEmbeddingGenerator(embeddingModelId, apiKey);
// Register MCP server
builder.Services
@@ -96,7 +95,7 @@ static ResourceTemplateDefinition CreateVectorStoreSearchResourceTemplate(Kernel
RequestContext context,
string collection,
string prompt,
- [FromKernelServices] ITextEmbeddingGenerationService embeddingGenerationService,
+ [FromKernelServices] IEmbeddingGenerator> embeddingGenerator,
[FromKernelServices] IVectorStore vectorStore,
CancellationToken cancellationToken) =>
{
@@ -119,11 +118,11 @@ static TextDataModel CreateRecord(string text, ReadOnlyMemory embedding)
string content = EmbeddedResource.ReadAsString("semantic-kernel-info.txt");
// Create a collection from the lines in the file
- await vectorStore.CreateCollectionFromListAsync(collection, content.Split('\n'), embeddingGenerationService, CreateRecord);
+ await vectorStore.CreateCollectionFromListAsync(collection, content.Split('\n'), embeddingGenerator, CreateRecord);
}
// Generate embedding for the prompt
- ReadOnlyMemory promptEmbedding = await embeddingGenerationService.GenerateEmbeddingAsync(prompt, cancellationToken: cancellationToken);
+ ReadOnlyMemory promptEmbedding = (await embeddingGenerator.GenerateAsync(prompt, cancellationToken: cancellationToken)).Vector;
// Retrieve top three matching records from the vector store
var result = vsCollection.SearchEmbeddingAsync(promptEmbedding, top: 3, cancellationToken: cancellationToken);
diff --git a/dotnet/samples/Demos/OnnxSimpleRAG/Program.cs b/dotnet/samples/Demos/OnnxSimpleRAG/Program.cs
index e500c241febe..4ac4340d9841 100644
--- a/dotnet/samples/Demos/OnnxSimpleRAG/Program.cs
+++ b/dotnet/samples/Demos/OnnxSimpleRAG/Program.cs
@@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Linq;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.VectorData;
using Microsoft.ML.OnnxRuntimeGenAI;
@@ -11,7 +12,6 @@
using Microsoft.SemanticKernel.Connectors.InMemory;
using Microsoft.SemanticKernel.Connectors.Onnx;
using Microsoft.SemanticKernel.Data;
-using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
Console.OutputEncoding = System.Text.Encoding.UTF8;
@@ -36,17 +36,17 @@
// Load the services
var builder = Kernel.CreateBuilder()
.AddOnnxRuntimeGenAIChatCompletion(chatModelId, chatModelPath)
- .AddBertOnnxTextEmbeddingGeneration(embeddingModelPath, embeddingVocabPath);
+ .AddBertOnnxEmbeddingGenerator(embeddingModelPath, embeddingVocabPath);
// Build Kernel
var kernel = builder.Build();
// Get the instances of the services
using var chatService = kernel.GetRequiredService() as OnnxRuntimeGenAIChatCompletionService;
-var embeddingService = kernel.GetRequiredService();
+var embeddingService = kernel.GetRequiredService>>();
// Create a vector store and a collection to store information
-var vectorStore = new InMemoryVectorStore();
+var vectorStore = new InMemoryVectorStore(new() { EmbeddingGenerator = embeddingService });
var collection = vectorStore.GetCollection("ExampleCollection");
await collection.CreateCollectionIfNotExistsAsync();
@@ -58,16 +58,12 @@
await collection.UpsertAsync(new InformationItem()
{
Id = Guid.NewGuid().ToString(),
- Text = factContent,
- Embedding = await embeddingService.GenerateEmbeddingAsync(factContent)
+ Text = factContent
});
}
// Add a plugin to search the database with.
-// TODO: Once OpenAITextEmbeddingGenerationService implements MEAI's IEmbeddingGenerator (#10811), configure it with the InMemoryVectorStore above instead of passing it here.
-#pragma warning disable CS0618 // VectorStoreTextSearch with ITextEmbeddingGenerationService is obsolete
-var vectorStoreTextSearch = new VectorStoreTextSearch(collection, embeddingService);
-#pragma warning restore CS0618
+var vectorStoreTextSearch = new VectorStoreTextSearch(collection);
kernel.Plugins.Add(vectorStoreTextSearch.CreateWithSearch("SearchPlugin"));
// Start the conversation
@@ -139,5 +135,5 @@ internal sealed class InformationItem
public string Text { get; set; } = string.Empty;
[VectorStoreRecordVector(Dimensions: 384)]
- public ReadOnlyMemory Embedding { get; set; }
+ public string Embedding => this.Text;
}
diff --git a/dotnet/samples/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Program.cs b/dotnet/samples/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Program.cs
index d59b135b9bac..d200c9f05cb1 100644
--- a/dotnet/samples/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Program.cs
+++ b/dotnet/samples/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Program.cs
@@ -34,11 +34,11 @@
processBuilder
.OnInputEvent(ProcessEvents.TranslateDocument)
- .SendEventTo(new(translateDocumentStep, TranslateStep.Functions.Translate, parameterName: "textToTranslate"));
+ .SendEventTo(new(translateDocumentStep, TranslateStep.ProcessFunctions.Translate, parameterName: "textToTranslate"));
translateDocumentStep
.OnEvent(ProcessEvents.DocumentTranslated)
- .SendEventTo(new(summarizeDocumentStep, SummarizeStep.Functions.Summarize, parameterName: "textToSummarize"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(summarizeDocumentStep, SummarizeStep.ProcessFunctions.Summarize, parameterName: "textToSummarize"));
summarizeDocumentStep
.OnEvent(ProcessEvents.DocumentSummarized)
diff --git a/dotnet/samples/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Steps/SummarizeStep.cs b/dotnet/samples/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Steps/SummarizeStep.cs
index 0f85f0ff0fd9..bbc7c5e39625 100644
--- a/dotnet/samples/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Steps/SummarizeStep.cs
+++ b/dotnet/samples/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Steps/SummarizeStep.cs
@@ -7,12 +7,12 @@ namespace ProcessFramework.Aspire.ProcessOrchestrator.Steps;
public class SummarizeStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessFunctions
{
public const string Summarize = nameof(Summarize);
}
- [KernelFunction(Functions.Summarize)]
+ [KernelFunction(ProcessFunctions.Summarize)]
public async ValueTask SummarizeAsync(KernelProcessStepContext context, Kernel kernel, string textToSummarize)
{
var summaryAgentHttpClient = kernel.GetRequiredService();
diff --git a/dotnet/samples/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Steps/TranslateStep.cs b/dotnet/samples/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Steps/TranslateStep.cs
index 8c1b46c22746..2e6d52ebdb35 100644
--- a/dotnet/samples/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Steps/TranslateStep.cs
+++ b/dotnet/samples/Demos/ProcessFrameworkWithAspire/ProcessFramework.Aspire/ProcessFramework.Aspire.ProcessOrchestrator/Steps/TranslateStep.cs
@@ -7,12 +7,12 @@ namespace ProcessFramework.Aspire.ProcessOrchestrator.Steps;
public class TranslateStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessFunctions
{
public const string Translate = nameof(Translate);
}
- [KernelFunction(Functions.Translate)]
+ [KernelFunction(ProcessFunctions.Translate)]
public async ValueTask TranslateAsync(KernelProcessStepContext context, Kernel kernel, string textToTranslate)
{
var translatorAgentHttpClient = kernel.GetRequiredService();
diff --git a/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Processes/DocumentGenerationProcess.cs b/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Processes/DocumentGenerationProcess.cs
index d79d2c40d0c1..6f319947c07a 100644
--- a/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Processes/DocumentGenerationProcess.cs
+++ b/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Processes/DocumentGenerationProcess.cs
@@ -61,7 +61,7 @@ public static ProcessBuilder CreateProcessBuilder(string processName = "Document
var docsProofreadStep = processBuilder.AddStepFromType();
var docsPublishStep = processBuilder.AddStepFromType();
- var proxyStep = processBuilder.AddProxyStep([DocGenerationTopics.RequestUserReview, DocGenerationTopics.PublishDocumentation]);
+ var proxyStep = processBuilder.AddProxyStep(id: processName, [DocGenerationTopics.RequestUserReview, DocGenerationTopics.PublishDocumentation]);
// Orchestrate the external input events
processBuilder
@@ -70,7 +70,7 @@ public static ProcessBuilder CreateProcessBuilder(string processName = "Document
processBuilder
.OnInputEvent(DocGenerationEvents.UserRejectedDocument)
- .SendEventTo(new(docsGenerationStep, functionName: GenerateDocumentationStep.Functions.ApplySuggestions));
+ .SendEventTo(new(docsGenerationStep, functionName: GenerateDocumentationStep.ProcessFunctions.ApplySuggestions));
processBuilder
.OnInputEvent(DocGenerationEvents.UserApprovedDocument)
@@ -79,22 +79,22 @@ public static ProcessBuilder CreateProcessBuilder(string processName = "Document
// Hooking up the rest of the process steps
infoGatheringStep
.OnFunctionResult()
- .SendEventTo(new(docsGenerationStep, functionName: GenerateDocumentationStep.Functions.GenerateDocs));
+ .SendEventTo(new ProcessFunctionTargetBuilder(docsGenerationStep, functionName: GenerateDocumentationStep.ProcessFunctions.GenerateDocs));
docsGenerationStep
.OnEvent(GenerateDocumentationStep.OutputEvents.DocumentationGenerated)
- .SendEventTo(new(docsProofreadStep));
+ .SendEventTo(new ProcessFunctionTargetBuilder(docsProofreadStep));
docsProofreadStep
.OnEvent(ProofReadDocumentationStep.OutputEvents.DocumentationRejected)
- .SendEventTo(new(docsGenerationStep, functionName: GenerateDocumentationStep.Functions.ApplySuggestions));
+ .SendEventTo(new ProcessFunctionTargetBuilder(docsGenerationStep, functionName: GenerateDocumentationStep.ProcessFunctions.ApplySuggestions));
// When the proofreader approves the documentation, send it to the 'docs' parameter of the docsPublishStep
// Additionally, the generated document is emitted externally for user approval using the pre-configured proxyStep
docsProofreadStep
.OnEvent(ProofReadDocumentationStep.OutputEvents.DocumentationApproved)
.EmitExternalEvent(proxyStep, DocGenerationTopics.RequestUserReview)
- .SendEventTo(new(docsPublishStep, parameterName: "document"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(docsPublishStep, parameterName: "document"));
// When event is approved by user, it gets published externally too
docsPublishStep
diff --git a/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Processes/Steps/GenerateDocumentationStep.cs b/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Processes/Steps/GenerateDocumentationStep.cs
index abe0c36a33d7..5b9f3b1591b4 100644
--- a/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Processes/Steps/GenerateDocumentationStep.cs
+++ b/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Processes/Steps/GenerateDocumentationStep.cs
@@ -14,7 +14,7 @@ public class GenerateDocumentationStep : KernelProcessStep
/// Function names of the steps, to be refereced when hooking up the step in a SK process
///
- public static class Functions
+ public static class ProcessFunctions
{
///
/// Genereta Doc function name
@@ -63,7 +63,7 @@ public override ValueTask ActivateAsync(KernelProcessStepStateinstance of
/// content to be used for document generation
///
- [KernelFunction(Functions.GenerateDocs)]
+ [KernelFunction(ProcessFunctions.GenerateDocs)]
public async Task GenerateDocumentationAsync(Kernel kernel, KernelProcessStepContext context, ProductInfo productInfo)
{
Console.WriteLine($"[{nameof(GenerateDocumentationStep)}]:\tGenerating documentation for provided productInfo...");
@@ -94,7 +94,7 @@ public async Task GenerateDocumentationAsync(Kernel kernel, KernelProcessStepCon
/// instance of
/// suggestions to be integrated into the document content
///
- [KernelFunction(Functions.ApplySuggestions)]
+ [KernelFunction(ProcessFunctions.ApplySuggestions)]
public async Task ApplySuggestionsAsync(Kernel kernel, KernelProcessStepContext context, string suggestions)
{
Console.WriteLine($"[{nameof(GenerateDocumentationStep)}]:\tRewriting documentation with provided suggestions...");
diff --git a/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step01_AzureAIAgent.cs b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step01_AzureAIAgent.cs
index 8db5be7a7984..1b3afff46713 100644
--- a/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step01_AzureAIAgent.cs
+++ b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step01_AzureAIAgent.cs
@@ -20,6 +20,7 @@ public async Task UseTemplateForAzureAgent()
PromptTemplateConfig templateConfig = KernelFunctionYaml.ToPromptTemplateConfig(generateStoryYaml);
// Instructions, Name and Description properties defined via the PromptTemplateConfig.
Azure.AI.Projects.Agent definition = await this.AgentsClient.CreateAgentAsync(TestConfiguration.AzureAI.ChatModelId, templateConfig.Name, templateConfig.Description, templateConfig.Template);
+
AzureAIAgent agent = new(
definition,
this.AgentsClient,
diff --git a/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step09_AzureAIAgent_BingGrounding.cs b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step09_AzureAIAgent_BingGrounding.cs
new file mode 100644
index 000000000000..71eaae05575a
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step09_AzureAIAgent_BingGrounding.cs
@@ -0,0 +1,129 @@
+// Copyright (c) Microsoft. All rights reserved.
+using Azure.AI.Projects;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.AzureAI;
+using Microsoft.SemanticKernel.ChatCompletion;
+using FoundryAgent = Azure.AI.Projects.Agent;
+
+namespace GettingStarted.AzureAgents;
+
+///
+/// Demonstrate using code-interpreter on .
+///
+public class Step09_AzureAIAgent_BingGrounding(ITestOutputHelper output) : BaseAzureAgentTest(output)
+{
+ [Fact]
+ public async Task UseBingGroundingToolWithAgent()
+ {
+ // Access the BingGrounding connection
+ ConnectionsClient connectionsClient = this.Client.GetConnectionsClient();
+ ConnectionResponse bingConnection = await connectionsClient.GetConnectionAsync(TestConfiguration.AzureAI.BingConnectionId);
+
+ // Define the agent
+ ToolConnectionList toolConnections = new()
+ {
+ ConnectionList = { new ToolConnection(bingConnection.Id) }
+ };
+ FoundryAgent definition = await this.AgentsClient.CreateAgentAsync(
+ TestConfiguration.AzureAI.ChatModelId,
+ tools: [new BingGroundingToolDefinition(toolConnections)]);
+ AzureAIAgent agent = new(definition, this.AgentsClient);
+
+ // Create a thread for the agent conversation.
+ AzureAIAgentThread thread = new(this.AgentsClient, metadata: SampleMetadata);
+
+ // Respond to user input
+ try
+ {
+ //await InvokeAgentAsync("How does wikipedia explain Euler's Identity?");
+ await InvokeAgentAsync("What is the current price of gold?");
+ }
+ finally
+ {
+ await thread.DeleteAsync();
+ await this.AgentsClient.DeleteAgentAsync(agent.Id);
+ }
+
+ // Local function to invoke agent and display the conversation messages.
+ async Task InvokeAgentAsync(string input)
+ {
+ ChatMessageContent message = new(AuthorRole.User, input);
+ this.WriteAgentChatMessage(message);
+
+ await foreach (ChatMessageContent response in agent.InvokeAsync(message, thread))
+ {
+ this.WriteAgentChatMessage(response);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task UseBingGroundingToolWithStreaming()
+ {
+ // Access the BingGrounding connection
+ ConnectionsClient connectionClient = this.Client.GetConnectionsClient();
+ ConnectionResponse bingConnectionResponse = await connectionClient.GetConnectionAsync(TestConfiguration.AzureAI.BingConnectionId);
+
+ // Define the agent
+ ToolConnectionList toolConnections = new()
+ {
+ ConnectionList = { new ToolConnection(bingConnectionResponse.Id) }
+ };
+ FoundryAgent definition = await this.AgentsClient.CreateAgentAsync(
+ TestConfiguration.AzureAI.ChatModelId,
+ tools: [new BingGroundingToolDefinition(toolConnections)]);
+ AzureAIAgent agent = new(definition, this.AgentsClient);
+
+ // Create a thread for the agent conversation.
+ AzureAIAgentThread thread = new(this.AgentsClient, metadata: SampleMetadata);
+
+ // Respond to user input
+ try
+ {
+ await InvokeAgentAsync("What is the current price of gold?");
+
+ // Display chat history
+ Console.WriteLine("\n================================");
+ Console.WriteLine("CHAT HISTORY");
+ Console.WriteLine("================================");
+
+ await foreach (ChatMessageContent message in thread.GetMessagesAsync())
+ {
+ this.WriteAgentChatMessage(message);
+ }
+ }
+ finally
+ {
+ await thread.DeleteAsync();
+ await this.AgentsClient.DeleteAgentAsync(agent.Id);
+ }
+
+ // Local function to invoke agent and display the conversation messages.
+ async Task InvokeAgentAsync(string input)
+ {
+ ChatMessageContent message = new(AuthorRole.User, input);
+ this.WriteAgentChatMessage(message);
+
+ bool isFirst = false;
+ await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(message, thread))
+ {
+ if (!isFirst)
+ {
+ Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}:");
+ isFirst = true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(response.Content))
+ {
+ Console.WriteLine($"\t> streamed: {response.Content}");
+ }
+
+ foreach (StreamingAnnotationContent? annotation in response.Items.OfType())
+ {
+ Console.WriteLine($"\t {annotation.ReferenceId} - {annotation.Title}");
+ }
+ }
+ }
+ }
+}
diff --git a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj
index 90818906f219..555751348dae 100644
--- a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj
+++ b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj
@@ -9,7 +9,7 @@
true
- $(NoWarn);CS8618,IDE0009,IDE1006,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0101,SKEXP0110,OPENAI001
+ $(NoWarn);IDE1006;IDE0009;CS8618;CA1051;CA1050;CA1707;CA1054;CA2007;CA5394;VSTHRD111;CS1591;NU1605;RCS1110;RCS1243;SKEXP0001;SKEXP0010;SKEXP0020;SKEXP0040;SKEXP0050;SKEXP0060;SKEXP0070;SKEXP0101;SKEXP0110;OPENAI001
Library
5ee045b0-aea3-4f08-8d31-32d1a6f8fed0
@@ -46,6 +46,8 @@
+
+
diff --git a/dotnet/samples/GettingStartedWithAgents/Orchestration/Step01_Concurrent.cs b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step01_Concurrent.cs
new file mode 100644
index 000000000000..9bf0afc24aea
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step01_Concurrent.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.Orchestration;
+using Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
+using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
+
+namespace GettingStarted.Orchestration;
+
+///
+/// Demonstrates how to use the
+/// for executing multiple agents on the same task in parallel.
+///
+public class Step01_Concurrent(ITestOutputHelper output) : BaseOrchestrationTest(output)
+{
+ [Fact]
+ public async Task ConcurrentTaskAsync()
+ {
+ // Define the agents
+ ChatCompletionAgent physicist =
+ this.CreateAgent(
+ instructions: "You are an expert in physics. You answer questions from a physics perspective.",
+ description: "An expert in physics");
+ ChatCompletionAgent chemist =
+ this.CreateAgent(
+ instructions: "You are an expert in chemistry. You answer questions from a chemistry perspective.",
+ description: "An expert in chemistry");
+
+ // Define the orchestration
+ OrchestrationMonitor monitor = new();
+ ConcurrentOrchestration orchestration =
+ new(physicist, chemist)
+ {
+ ResponseCallback = monitor.ResponseCallback,
+ LoggerFactory = this.LoggerFactory
+ };
+
+ // Start the runtime
+ InProcessRuntime runtime = new();
+ await runtime.StartAsync();
+
+ // Run the orchestration
+ string input = "What is temperature?";
+ Console.WriteLine($"\n# INPUT: {input}\n");
+ OrchestrationResult result = await orchestration.InvokeAsync(input, runtime);
+
+ string[] output = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
+ Console.WriteLine($"\n# RESULT:\n{string.Join("\n\n", output.Select(text => $"{text}"))}");
+
+ await runtime.RunUntilIdleAsync();
+
+ Console.WriteLine("\n\nORCHESTRATION HISTORY");
+ foreach (ChatMessageContent message in monitor.History)
+ {
+ this.WriteAgentChatMessage(message);
+ }
+ }
+}
diff --git a/dotnet/samples/GettingStartedWithAgents/Orchestration/Step01a_ConcurrentWithStructuredOutput.cs b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step01a_ConcurrentWithStructuredOutput.cs
new file mode 100644
index 000000000000..bcb08bb8a7ff
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step01a_ConcurrentWithStructuredOutput.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.Orchestration;
+using Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
+using Microsoft.SemanticKernel.Agents.Orchestration.Transforms;
+using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
+using Microsoft.SemanticKernel.ChatCompletion;
+using Microsoft.SemanticKernel.Connectors.OpenAI;
+using Resources;
+
+namespace GettingStarted.Orchestration;
+
+///
+/// Demonstrates how to use the with structured output.
+///
+public class Step01a_ConcurrentWithStructuredOutput(ITestOutputHelper output) : BaseOrchestrationTest(output)
+{
+ private static readonly JsonSerializerOptions s_options = new() { WriteIndented = true };
+
+ [Fact]
+ public async Task ConcurrentStructuredOutputAsync()
+ {
+ // Define the agents
+ ChatCompletionAgent agent1 =
+ this.CreateAgent(
+ instructions: "You are an expert in identifying themes in articles. Given an article, identify the main themes.",
+ description: "An expert in identifying themes in articles");
+ ChatCompletionAgent agent2 =
+ this.CreateAgent(
+ instructions: "You are an expert in sentiment analysis. Given an article, identify the sentiment.",
+ description: "An expert in sentiment analysis");
+ ChatCompletionAgent agent3 =
+ this.CreateAgent(
+ instructions: "You are an expert in entity recognition. Given an article, extract the entities.",
+ description: "An expert in entity recognition");
+
+ // Define the orchestration with transform
+ Kernel kernel = this.CreateKernelWithChatCompletion();
+ StructuredOutputTransform outputTransform =
+ new(kernel.GetRequiredService(),
+ new OpenAIPromptExecutionSettings { ResponseFormat = typeof(Analysis) });
+ ConcurrentOrchestration orchestration =
+ new(agent1, agent2, agent3)
+ {
+ LoggerFactory = this.LoggerFactory,
+ ResultTransform = outputTransform.TransformAsync,
+ };
+
+ // Start the runtime
+ InProcessRuntime runtime = new();
+ await runtime.StartAsync();
+
+ // Run the orchestration
+ const string resourceId = "Hamlet_full_play_summary.txt";
+ string input = EmbeddedResource.Read(resourceId);
+ Console.WriteLine($"\n# INPUT: @{resourceId}\n");
+ OrchestrationResult result = await orchestration.InvokeAsync(input, runtime);
+
+ Analysis output = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 2));
+ Console.WriteLine($"\n# RESULT:\n{JsonSerializer.Serialize(output, s_options)}");
+
+ await runtime.RunUntilIdleAsync();
+ }
+
+ private sealed class Analysis
+ {
+ public IList Themes { get; set; } = [];
+ public IList Sentiments { get; set; } = [];
+ public IList Entities { get; set; } = [];
+ }
+}
diff --git a/dotnet/samples/GettingStartedWithAgents/Orchestration/Step02_Sequential.cs b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step02_Sequential.cs
new file mode 100644
index 000000000000..f0e08df86ebb
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step02_Sequential.cs
@@ -0,0 +1,81 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.Orchestration;
+using Microsoft.SemanticKernel.Agents.Orchestration.Sequential;
+using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
+
+namespace GettingStarted.Orchestration;
+
+///
+/// Demonstrates how to use the for
+/// executing multiple agents in sequence, i.e.the output of one agent is
+/// the input to the next agent.
+///
+public class Step02_Sequential(ITestOutputHelper output) : BaseOrchestrationTest(output)
+{
+ [Fact]
+ public async Task SequentialTaskAsync()
+ {
+ // Define the agents
+ ChatCompletionAgent analystAgent =
+ this.CreateAgent(
+ name: "Analyst",
+ instructions:
+ """
+ You are a marketing analyst. Given a product description, identify:
+ - Key features
+ - Target audience
+ - Unique selling points
+ """,
+ description: "A agent that extracts key concepts from a product description.");
+ ChatCompletionAgent writerAgent =
+ this.CreateAgent(
+ name: "copywriter",
+ instructions:
+ """
+ You are a marketing copywriter. Given a block of text describing features, audience, and USPs,
+ compose a compelling marketing copy (like a newsletter section) that highlights these points.
+ Output should be short (around 150 words), output just the copy as a single text block.
+ """,
+ description: "An agent that writes a marketing copy based on the extracted concepts.");
+ ChatCompletionAgent editorAgent =
+ this.CreateAgent(
+ name: "editor",
+ instructions:
+ """
+ You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone,
+ give format and make it polished. Output the final improved copy as a single text block.
+ """,
+ description: "An agent that formats and proofreads the marketing copy.");
+
+ // Define the orchestration
+ OrchestrationMonitor monitor = new();
+ SequentialOrchestration orchestration =
+ new(analystAgent, writerAgent, editorAgent)
+ {
+ ResponseCallback = monitor.ResponseCallback,
+ LoggerFactory = this.LoggerFactory
+ };
+
+ // Start the runtime
+ InProcessRuntime runtime = new();
+ await runtime.StartAsync();
+
+ // Run the orchestration
+ string input = "An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours";
+ Console.WriteLine($"\n# INPUT: {input}\n");
+ OrchestrationResult result = await orchestration.InvokeAsync(input, runtime);
+ string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
+ Console.WriteLine($"\n# RESULT: {text}");
+
+ await runtime.RunUntilIdleAsync();
+
+ Console.WriteLine("\n\nORCHESTRATION HISTORY");
+ foreach (ChatMessageContent message in monitor.History)
+ {
+ this.WriteAgentChatMessage(message);
+ }
+ }
+}
diff --git a/dotnet/samples/GettingStartedWithAgents/Orchestration/Step02a_SequentialCancellation.cs b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step02a_SequentialCancellation.cs
new file mode 100644
index 000000000000..0c55ae7e4299
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step02a_SequentialCancellation.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.Orchestration;
+using Microsoft.SemanticKernel.Agents.Orchestration.Sequential;
+using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
+
+namespace GettingStarted.Orchestration;
+
+///
+/// Demonstrates how to use cancel a while its running.
+///
+public class Step02a_SequentialCancellation(ITestOutputHelper output) : BaseOrchestrationTest(output)
+{
+ [Fact]
+ public async Task SequentialCancelledAsync()
+ {
+ // Define the agents
+ ChatCompletionAgent agent =
+ this.CreateAgent(
+ """
+ If the input message is a number, return the number incremented by one.
+ """,
+ description: "A agent that increments numbers.");
+
+ // Define the orchestration
+ SequentialOrchestration orchestration = new(agent) { LoggerFactory = this.LoggerFactory };
+
+ // Start the runtime
+ InProcessRuntime runtime = new();
+ await runtime.StartAsync();
+
+ // Run the orchestration
+ string input = "42";
+ Console.WriteLine($"\n# INPUT: {input}\n");
+
+ OrchestrationResult result = await orchestration.InvokeAsync(input, runtime);
+
+ result.Cancel();
+ await Task.Delay(TimeSpan.FromSeconds(3));
+
+ try
+ {
+ string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
+ Console.WriteLine($"\n# RESULT: {text}");
+ }
+ catch
+ {
+ Console.WriteLine("\n# CANCELLED");
+ }
+
+ await runtime.RunUntilIdleAsync();
+ }
+}
diff --git a/dotnet/samples/GettingStartedWithAgents/Orchestration/Step03_GroupChat.cs b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step03_GroupChat.cs
new file mode 100644
index 000000000000..da078315b861
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step03_GroupChat.cs
@@ -0,0 +1,84 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.Orchestration;
+using Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
+using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
+
+namespace GettingStarted.Orchestration;
+
+///
+/// Demonstrates how to use the ith a default
+/// round robin manager for controlling the flow of conversation in a round robin fashion.
+///
+///
+/// Think of the group chat manager as a state machine, with the following possible states:
+/// - Request for user message
+/// - Termination, after which the manager will try to filter a result from the conversation
+/// - Continuation, at which the manager will select the next agent to speak.
+///
+public class Step03_GroupChat(ITestOutputHelper output) : BaseOrchestrationTest(output)
+{
+ [Fact]
+ public async Task GroupChatAsync()
+ {
+ // Define the agents
+ ChatCompletionAgent writer =
+ this.CreateAgent(
+ name: "CopyWriter",
+ description: "A copy writer",
+ instructions:
+ """
+ You are a copywriter with ten years of experience and are known for brevity and a dry humor.
+ The goal is to refine and decide on the single best copy as an expert in the field.
+ Only provide a single proposal per response.
+ You're laser focused on the goal at hand.
+ Don't waste time with chit chat.
+ Consider suggestions when refining an idea.
+ """);
+ ChatCompletionAgent editor =
+ this.CreateAgent(
+ name: "Reviewer",
+ description: "An editor.",
+ instructions:
+ """
+ You are an art director who has opinions about copywriting born of a love for David Ogilvy.
+ The goal is to determine if the given copy is acceptable to print.
+ If so, state that it is approved.
+ If not, provide insight on how to refine suggested copy without example.
+ """);
+
+ // Define the orchestration
+ OrchestrationMonitor monitor = new();
+ GroupChatOrchestration orchestration =
+ new(new RoundRobinGroupChatManager()
+ {
+ MaximumInvocationCount = 5
+ },
+ writer,
+ editor)
+ {
+ ResponseCallback = monitor.ResponseCallback,
+ LoggerFactory = this.LoggerFactory,
+ };
+
+ // Start the runtime
+ InProcessRuntime runtime = new();
+ await runtime.StartAsync();
+
+ string input = "Create a slogon for a new eletric SUV that is affordable and fun to drive.";
+ Console.WriteLine($"\n# INPUT: {input}\n");
+ OrchestrationResult result = await orchestration.InvokeAsync(input, runtime);
+ string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
+ Console.WriteLine($"\n# RESULT: {text}");
+
+ await runtime.RunUntilIdleAsync();
+
+ Console.WriteLine("\n\nORCHESTRATION HISTORY");
+ foreach (ChatMessageContent message in monitor.History)
+ {
+ this.WriteAgentChatMessage(message);
+ }
+ }
+}
diff --git a/dotnet/samples/GettingStartedWithAgents/Orchestration/Step03a_GroupChatWithHumanInTheLoop.cs b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step03a_GroupChatWithHumanInTheLoop.cs
new file mode 100644
index 000000000000..a10c5b8a25a7
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step03a_GroupChatWithHumanInTheLoop.cs
@@ -0,0 +1,105 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.Orchestration;
+using Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
+using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
+using Microsoft.SemanticKernel.ChatCompletion;
+
+namespace GettingStarted.Orchestration;
+
+///
+/// Demonstrates how to use the with human in the loop
+///
+public class Step03a_GroupChatWithHumanInTheLoop(ITestOutputHelper output) : BaseOrchestrationTest(output)
+{
+ [Fact]
+ public async Task GroupChatWithHumanAsync()
+ {
+ // Define the agents
+ ChatCompletionAgent writer =
+ this.CreateAgent(
+ name: "CopyWriter",
+ description: "A copy writer",
+ instructions:
+ """
+ You are a copywriter with ten years of experience and are known for brevity and a dry humor.
+ The goal is to refine and decide on the single best copy as an expert in the field.
+ Only provide a single proposal per response.
+ You're laser focused on the goal at hand.
+ Don't waste time with chit chat.
+ Consider suggestions when refining an idea.
+ """);
+ ChatCompletionAgent editor =
+ this.CreateAgent(
+ name: "Reviewer",
+ description: "An editor.",
+ instructions:
+ """
+ You are an art director who has opinions about copywriting born of a love for David Ogilvy.
+ The goal is to determine if the given copy is acceptable to print.
+ If so, state that it is approved.
+ If not, provide insight on how to refine suggested copy without example.
+ """);
+
+ // Define the orchestration
+ GroupChatOrchestration orchestration =
+ new(
+ new CustomRoundRobinGroupChatManager()
+ {
+ MaximumInvocationCount = 5,
+ InteractiveCallback = () =>
+ {
+ ChatMessageContent input = new(AuthorRole.User, "I like it");
+ Console.WriteLine($"\n# INPUT: {input.Content}\n");
+ return ValueTask.FromResult(input);
+ }
+ },
+ writer,
+ editor)
+ {
+ LoggerFactory = this.LoggerFactory
+ };
+
+ // Start the runtime
+ InProcessRuntime runtime = new();
+ await runtime.StartAsync();
+
+ // Run the orchestration
+ string input = "Create a slogon for a new eletric SUV that is affordable and fun to drive.";
+ Console.WriteLine($"\n# INPUT: {input}\n");
+ OrchestrationResult result = await orchestration.InvokeAsync(input, runtime);
+ string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
+ Console.WriteLine($"\n# RESULT: {text}");
+
+ await runtime.RunUntilIdleAsync();
+ }
+
+ ///
+ /// Define a custom group chat manager that enables user input.
+ ///
+ ///
+ /// User input is achieved by overriding the default round robin manager
+ /// to allow user input after the reviewer agent's message.
+ ///
+ private sealed class CustomRoundRobinGroupChatManager : RoundRobinGroupChatManager
+ {
+ public override ValueTask> ShouldRequestUserInput(ChatHistory history, CancellationToken cancellationToken = default)
+ {
+ string? lastAgent = history.LastOrDefault()?.AuthorName;
+
+ if (lastAgent is null)
+ {
+ return ValueTask.FromResult(new GroupChatManagerResult(false) { Reason = "No agents have spoken yet." });
+ }
+
+ if (lastAgent == "Reviewer")
+ {
+ return ValueTask.FromResult(new GroupChatManagerResult(true) { Reason = "User input is needed after the reviewer's message." });
+ }
+
+ return ValueTask.FromResult(new GroupChatManagerResult(false) { Reason = "User input is not needed until the reviewer's message." });
+ }
+ }
+}
diff --git a/dotnet/samples/GettingStartedWithAgents/Orchestration/Step03b_GroupChatWithAIManager.cs b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step03b_GroupChatWithAIManager.cs
new file mode 100644
index 000000000000..f0eef06f53bb
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step03b_GroupChatWithAIManager.cs
@@ -0,0 +1,208 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.Orchestration;
+using Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
+using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
+using Microsoft.SemanticKernel.ChatCompletion;
+using Microsoft.SemanticKernel.Connectors.OpenAI;
+
+namespace GettingStarted.Orchestration;
+
+///
+/// Demonstrates how to use the
+/// with a group chat manager that uses a chat completion service to
+/// control the flow of the conversation.
+///
+public class Step03b_GroupChatWithAIManager(ITestOutputHelper output) : BaseOrchestrationTest(output)
+{
+ [Fact]
+ public async Task GroupChatWithAIManagerAsync()
+ {
+ // Define the agents
+ ChatCompletionAgent farmer =
+ this.CreateAgent(
+ name: "Farmer",
+ description: "A rural farmer from Southeast Asia.",
+ instructions:
+ """
+ You're a farmer from Southeast Asia.
+ Your life is deeply connected to land and family.
+ You value tradition and sustainability.
+ You are in a debate. Feel free to challenge the other participants with respect.
+ """);
+ ChatCompletionAgent developer =
+ this.CreateAgent(
+ name: "Developer",
+ description: "An urban software developer from the United States.",
+ instructions:
+ """
+ You're a software developer from the United States.
+ Your life is fast-paced and technology-driven.
+ You value innovation, freedom, and work-life balance.
+ You are in a debate. Feel free to challenge the other participants with respect.
+ """);
+ ChatCompletionAgent teacher =
+ this.CreateAgent(
+ name: "Teacher",
+ description: "A retired history teacher from Eastern Europe",
+ instructions:
+ """
+ You're a retired history teacher from Eastern Europe.
+ You bring historical and philosophical perspectives to discussions.
+ You value legacy, learning, and cultural continuity.
+ You are in a debate. Feel free to challenge the other participants with respect.
+ """);
+ ChatCompletionAgent activist =
+ this.CreateAgent(
+ name: "Activist",
+ description: "A young activist from South America.",
+ instructions:
+ """
+ You're a young activist from South America.
+ You focus on social justice, environmental rights, and generational change.
+ You are in a debate. Feel free to challenge the other participants with respect.
+ """);
+ ChatCompletionAgent spiritual =
+ this.CreateAgent(
+ name: "SpiritualLeader",
+ description: "A spiritual leader from the Middle East.",
+ instructions:
+ """
+ You're a spiritual leader from the Middle East.
+ You provide insights grounded in religion, morality, and community service.
+ You are in a debate. Feel free to challenge the other participants with respect.
+ """);
+ ChatCompletionAgent artist =
+ this.CreateAgent(
+ name: "Artist",
+ description: "An artist from Africa.",
+ instructions:
+ """
+ You're an artist from Africa.
+ You view life through creative expression, storytelling, and collective memory.
+ You are in a debate. Feel free to challenge the other participants with respect.
+ """);
+ ChatCompletionAgent immigrant =
+ this.CreateAgent(
+ name: "Immigrant",
+ description: "An immigrant entrepreneur from Asia living in Canada.",
+ instructions:
+ """
+ You're an immigrant entrepreneur from Asia living in Canada.
+ You balance trandition with adaption.
+ You focus on family success, risk, and opportunity.
+ You are in a debate. Feel free to challenge the other participants with respect.
+ """);
+ ChatCompletionAgent doctor =
+ this.CreateAgent(
+ name: "Doctor",
+ description: "A doctor from Scandinavia.",
+ instructions:
+ """
+ You're a doctor from Scandinavia.
+ Your perspective is shaped by public health, equity, and structured societal support.
+ You are in a debate. Feel free to challenge the other participants with respect.
+ """);
+
+ // Define the orchestration
+ const string topic = "What does a good life mean to you personally?";
+ Kernel kernel = this.CreateKernelWithChatCompletion();
+ GroupChatOrchestration orchestration =
+ new(
+ new AIGroupChatManager(
+ topic,
+ kernel.GetRequiredService())
+ {
+ MaximumInvocationCount = 5
+ },
+ farmer,
+ developer,
+ teacher,
+ activist,
+ spiritual,
+ artist,
+ immigrant,
+ doctor)
+ {
+ LoggerFactory = this.LoggerFactory
+ };
+
+ // Start the runtime
+ InProcessRuntime runtime = new();
+ await runtime.StartAsync();
+
+ // Run the orchestration
+ Console.WriteLine($"\n# INPUT: {topic}\n");
+ OrchestrationResult result = await orchestration.InvokeAsync(topic, runtime);
+ string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds * 3));
+ Console.WriteLine($"\n# RESULT: {text}");
+
+ await runtime.RunUntilIdleAsync();
+ }
+
+ private sealed class AIGroupChatManager(string topic, IChatCompletionService chatCompletion) : GroupChatManager
+ {
+ private static class Prompts
+ {
+ public static string Termination(string topic) =>
+ $"""
+ You are mediator that guides a discussion on the topic of '{topic}'.
+ You need to determine if the discussion has reached a conclusion.
+ If you would like to end the discussion, please respond with True. Otherwise, respond with False.
+ """;
+
+ public static string Selection(string topic, string participants) =>
+ $"""
+ You are mediator that guides a discussion on the topic of '{topic}'.
+ You need to select the next participant to speak.
+ Here are the names and descriptions of the participants:
+ {participants}\n
+ Please respond with only the name of the participant you would like to select.
+ """;
+
+ public static string Filter(string topic) =>
+ $"""
+ You are mediator that guides a discussion on the topic of '{topic}'.
+ You have just concluded the discussion.
+ Please summarize the discussion and provide a closing statement.
+ """;
+ }
+
+ ///
+ public override ValueTask> FilterResults(ChatHistory history, CancellationToken cancellationToken = default) =>
+ this.GetResponseAsync(history, Prompts.Filter(topic), cancellationToken);
+
+ ///
+ public override ValueTask> SelectNextAgent(ChatHistory history, GroupChatTeam team, CancellationToken cancellationToken = default) =>
+ this.GetResponseAsync(history, Prompts.Selection(topic, team.FormatList()), cancellationToken);
+
+ ///
+ public override ValueTask> ShouldRequestUserInput(ChatHistory history, CancellationToken cancellationToken = default) =>
+ ValueTask.FromResult(new GroupChatManagerResult(false) { Reason = "The AI group chat manager does not request user input." });
+
+ ///
+ public override async ValueTask> ShouldTerminate(ChatHistory history, CancellationToken cancellationToken = default)
+ {
+ GroupChatManagerResult result = await base.ShouldTerminate(history, cancellationToken);
+ if (!result.Value)
+ {
+ result = await this.GetResponseAsync(history, Prompts.Termination(topic), cancellationToken);
+ }
+ return result;
+ }
+
+ private async ValueTask> GetResponseAsync(ChatHistory history, string prompt, CancellationToken cancellationToken = default)
+ {
+ OpenAIPromptExecutionSettings executionSettings = new() { ResponseFormat = typeof(GroupChatManagerResult) };
+ ChatHistory request = [.. history, new ChatMessageContent(AuthorRole.System, prompt)];
+ ChatMessageContent response = await chatCompletion.GetChatMessageContentAsync(request, executionSettings, kernel: null, cancellationToken);
+ string responseText = response.ToString();
+ return
+ JsonSerializer.Deserialize>(responseText) ??
+ throw new InvalidOperationException($"Failed to parse response: {responseText}");
+ }
+ }
+}
diff --git a/dotnet/samples/GettingStartedWithAgents/Orchestration/Step04_Handoff.cs b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step04_Handoff.cs
new file mode 100644
index 000000000000..2aa66ee23905
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step04_Handoff.cs
@@ -0,0 +1,116 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.Orchestration;
+using Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
+using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
+using Microsoft.SemanticKernel.ChatCompletion;
+
+namespace GettingStarted.Orchestration;
+
+///
+/// Demonstrates how to use the that represents
+/// a customer support triage system.The orchestration consists of 4 agents, each specialized
+/// in a different area of customer support: triage, refunds, order status, and order returns.
+///
+public class Step04_Handoff(ITestOutputHelper output) : BaseOrchestrationTest(output)
+{
+ [Fact]
+ public async Task OrderSupportAsync()
+ {
+ // Define the agents & tools
+ ChatCompletionAgent triageAgent =
+ this.CreateAgent(
+ instructions: "A customer support agent that triages issues.",
+ name: "TriageAgent",
+ description: "Handle customer requests.");
+ ChatCompletionAgent statusAgent =
+ this.CreateAgent(
+ name: "OrderStatusAgent",
+ instructions: "Handle order status requests.",
+ description: "A customer support agent that checks order status.");
+ statusAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderStatusPlugin()));
+ ChatCompletionAgent returnAgent =
+ this.CreateAgent(
+ name: "OrderReturnAgent",
+ instructions: "Handle order return requests.",
+ description: "A customer support agent that handles order returns.");
+ returnAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderReturnPlugin()));
+ ChatCompletionAgent refundAgent =
+ this.CreateAgent(
+ name: "OrderRefundAgent",
+ instructions: "Handle order refund requests.",
+ description: "A customer support agent that handles order refund.");
+ refundAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromObject(new OrderRefundPlugin()));
+
+ // Define the orchestration
+ OrchestrationMonitor monitor = new();
+ Queue responses = new();
+ HandoffOrchestration orchestration =
+ new(OrchestrationHandoffs
+ .StartWith(triageAgent)
+ .Add(triageAgent, statusAgent, returnAgent, refundAgent)
+ .Add(statusAgent, triageAgent, "Transfer to this agent if the issue is not status related")
+ .Add(returnAgent, triageAgent, "Transfer to this agent if the issue is not return related")
+ .Add(refundAgent, triageAgent, "Transfer to this agent if the issue is not refund related"),
+ triageAgent,
+ statusAgent,
+ returnAgent,
+ refundAgent)
+ {
+ InteractiveCallback = () =>
+ {
+ string input = responses.Dequeue();
+ Console.WriteLine($"\n# INPUT: {input}\n");
+ return ValueTask.FromResult(new ChatMessageContent(AuthorRole.User, input));
+ },
+ ResponseCallback = monitor.ResponseCallback,
+ LoggerFactory = this.LoggerFactory
+ };
+
+ // Start the runtime
+ InProcessRuntime runtime = new();
+ await runtime.StartAsync();
+
+ // Run the orchestration
+ string task = "I am a customer that needs help with my orders";
+ responses.Enqueue("I'd like to track the status of my order");
+ responses.Enqueue("My order ID is 123");
+ responses.Enqueue("I want to return another order of mine");
+ responses.Enqueue("Order ID 321");
+ responses.Enqueue("Broken item");
+ responses.Enqueue("No, bye");
+ Console.WriteLine($"\n# INPUT:\n{task}\n");
+ OrchestrationResult result = await orchestration.InvokeAsync(task, runtime);
+
+ string text = await result.GetValueAsync(TimeSpan.FromSeconds(300));
+ Console.WriteLine($"\n# RESULT: {text}");
+
+ await runtime.RunUntilIdleAsync();
+
+ Console.WriteLine("\n\nORCHESTRATION HISTORY");
+ foreach (ChatMessageContent message in monitor.History)
+ {
+ this.WriteAgentChatMessage(message);
+ }
+ }
+
+ private sealed class OrderStatusPlugin
+ {
+ [KernelFunction]
+ public string CheckOrderStatus(string orderId) => $"Order {orderId} is shipped and will arrive in 2-3 days.";
+ }
+
+ private sealed class OrderReturnPlugin
+ {
+ [KernelFunction]
+ public string ProcessReturn(string orderId, string reason) => $"Return for order {orderId} has been processed successfully.";
+ }
+
+ private sealed class OrderRefundPlugin
+ {
+ [KernelFunction]
+ public string ProcessReturn(string orderId, string reason) => $"Refund for order {orderId} has been processed successfully.";
+ }
+}
diff --git a/dotnet/samples/GettingStartedWithAgents/Orchestration/Step04a_HandoffWithStructuredInput.cs b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step04a_HandoffWithStructuredInput.cs
new file mode 100644
index 000000000000..596a5431aefc
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/Orchestration/Step04a_HandoffWithStructuredInput.cs
@@ -0,0 +1,119 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.Orchestration;
+using Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
+using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
+
+namespace GettingStarted.Orchestration;
+
+///
+/// Demonstrates how to use the .
+///
+public class Step04a_HandoffWithStructuredInput(ITestOutputHelper output) : BaseOrchestrationTest(output)
+{
+ [Fact]
+ public async Task HandoffStructuredInputAsync()
+ {
+ // Initialize plugin
+ GithubPlugin githubPlugin = new();
+ KernelPlugin plugin = KernelPluginFactory.CreateFromObject(githubPlugin);
+
+ // Define the agents
+ ChatCompletionAgent triageAgent =
+ this.CreateAgent(
+ instructions: "Given a GitHub issue, triage it.",
+ name: "TriageAgent",
+ description: "An agent that triages GitHub issues");
+ ChatCompletionAgent pythonAgent =
+ this.CreateAgent(
+ instructions: "You are an agent that handles Python related GitHub issues.",
+ name: "PythonAgent",
+ description: "An agent that handles Python related issues");
+ pythonAgent.Kernel.Plugins.Add(plugin);
+ ChatCompletionAgent dotnetAgent =
+ this.CreateAgent(
+ instructions: "You are an agent that handles .NET related GitHub issues.",
+ name: "DotNetAgent",
+ description: "An agent that handles .NET related issues");
+ dotnetAgent.Kernel.Plugins.Add(plugin);
+
+ // Define the orchestration
+ HandoffOrchestration orchestration =
+ new(OrchestrationHandoffs
+ .StartWith(triageAgent)
+ .Add(triageAgent, dotnetAgent, pythonAgent),
+ triageAgent,
+ pythonAgent,
+ dotnetAgent)
+ {
+ LoggerFactory = this.LoggerFactory
+ };
+
+ GithubIssue input =
+ new()
+ {
+ Id = "12345",
+ Title = "Bug: SQLite Error 1: 'ambiguous column name:' when including VectorStoreRecordKey in VectorSearchOptions.Filter",
+ Body =
+ """
+ Describe the bug
+ When using column names marked as [VectorStoreRecordData(IsFilterable = true)] in VectorSearchOptions.Filter, the query runs correctly.
+ However, using the column name marked as [VectorStoreRecordKey] in VectorSearchOptions.Filter, the query throws exception 'SQLite Error 1: ambiguous column name: StartUTC'.
+ To Reproduce
+ Add a filter for the column marked [VectorStoreRecordKey]. Since that same column exists in both the vec_TestTable and TestTable, the data for both columns cannot be returned.
+
+ Expected behavior
+ The query should explicitly list the vec_TestTable column names to retrieve and should omit the [VectorStoreRecordKey] column since it will be included in the primary TestTable columns.
+
+ Platform
+ Microsoft.SemanticKernel.Connectors.Sqlite v1.46.0-preview
+
+ Additional context
+ Normal DBContext logging shows only normal context queries. Queries run by VectorizedSearchAsync() don't appear in those logs and I could not find a way to enable logging in semantic search so that I could actually see the exact query that is failing. It would have been very useful to see the failing semantic query.
+ """,
+ Labels = []
+ };
+
+ // Start the runtime
+ InProcessRuntime runtime = new();
+ await runtime.StartAsync();
+
+ // Run the orchestration
+ Console.WriteLine($"\n# INPUT:\n{input.Id}: {input.Title}\n");
+ OrchestrationResult result = await orchestration.InvokeAsync(input, runtime);
+ string text = await result.GetValueAsync(TimeSpan.FromSeconds(ResultTimeoutInSeconds));
+ Console.WriteLine($"\n# RESULT: {text}");
+ Console.WriteLine($"\n# LABELS: {string.Join(",", githubPlugin.Labels["12345"])}\n");
+
+ await runtime.RunUntilIdleAsync();
+ }
+
+ private sealed class GithubIssue
+ {
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ [JsonPropertyName("title")]
+ public string Title { get; set; } = string.Empty;
+
+ [JsonPropertyName("body")]
+ public string Body { get; set; } = string.Empty;
+
+ [JsonPropertyName("labels")]
+ public string[] Labels { get; set; } = [];
+ }
+
+ private sealed class GithubPlugin
+ {
+ public Dictionary Labels { get; } = [];
+
+ [KernelFunction]
+ public void AddLabels(string issueId, params string[] labels)
+ {
+ this.Labels[issueId] = labels;
+ }
+ }
+}
diff --git a/dotnet/samples/GettingStartedWithAgents/README.md b/dotnet/samples/GettingStartedWithAgents/README.md
index abe365fbe665..eb4aa3bbbff3 100644
--- a/dotnet/samples/GettingStartedWithAgents/README.md
+++ b/dotnet/samples/GettingStartedWithAgents/README.md
@@ -62,6 +62,20 @@ Example|Description
[Step05_BedrockAgent_FileSearch](./BedrockAgent/Step05_BedrockAgent_FileSearch.cs)|How to use file search with a Bedrock agent (i.e. Bedrock knowledge base).
[Step06_BedrockAgent_AgentChat](./BedrockAgent/Step06_BedrockAgent_AgentChat.cs)|How to create a conversation between two agents and one of them in a Bedrock agent.
+### Orchestration
+
+Example|Description
+---|---
+[Step01_Concurrent](./Orchestration/Step01_Concurrent.cs)|How to use a concurrent orchestration..
+[Step01a_ConcurrentWithStructuredOutput](./Orchestration/Step01a_ConcurrentWithStructuredOutput.cs)|How to use structured output (with concurrent orchestration).
+[Step02_Sequential](./Orchestration/Step02_Sequential.cs)|How to use sequential orchestration.
+[Step02a_Sequential](./Orchestration/Step02a_Sequential.cs)|How to cancel an orchestration (with sequential orchestration).
+[Step03_GroupChat](./Orchestration/Step03_GroupChat.cs)|How to use group-chat orchestration.
+[Step03a_GroupChatWithHumanInTheLoop](./Orchestration/Step03a_GroupChatWithHumanInTheLoop.cs)|How to use group-chat orchestration with human in the loop.
+[Step03b_GroupChatWithAIManager](./Orchestration/Step03b_GroupChatWithAIManager.cs)|How to use group-chat orchestration with a AI powered group-manager.
+[Step04_Handoff](./Orchestration/Step04_Handoff.cs)|How to use handoff orchestration.
+[Step04b_HandoffWithStructuredInput](./Orchestration/Step04b_HandoffWithStructuredInput.cs)|How to use structured input (with handoff orchestration).
+
## Legacy Agents
Support for the OpenAI Assistant API was originally published in `Microsoft.SemanticKernel.Experimental.Agents` package:
diff --git a/dotnet/samples/GettingStartedWithAgents/Resources/Hamlet_full_play_summary.txt b/dotnet/samples/GettingStartedWithAgents/Resources/Hamlet_full_play_summary.txt
new file mode 100644
index 000000000000..9050a46e660f
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/Resources/Hamlet_full_play_summary.txt
@@ -0,0 +1,13 @@
+On a dark winter night, a ghost walks the ramparts of Elsinore Castle in Denmark. Discovered first by a pair of watchmen, then by the scholar Horatio, the ghost resembles the recently deceased King Hamlet, whose brother Claudius has inherited the throne and married the king’s widow, Queen Gertrude. When Horatio and the watchmen bring Prince Hamlet, the son of Gertrude and the dead king, to see the ghost, it speaks to him, declaring ominously that it is indeed his father’s spirit, and that he was murdered by none other than Claudius. Ordering Hamlet to seek revenge on the man who usurped his throne and married his wife, the ghost disappears with the dawn.
+
+Prince Hamlet devotes himself to avenging his father’s death, but, because he is contemplative and thoughtful by nature, he delays, entering into a deep melancholy and even apparent madness. Claudius and Gertrude worry about the prince’s erratic behavior and attempt to discover its cause. They employ a pair of Hamlet’s friends, Rosencrantz and Guildenstern, to watch him. When Polonius, the pompous Lord Chamberlain, suggests that Hamlet may be mad with love for his daughter, Ophelia, Claudius agrees to spy on Hamlet in conversation with the girl. But though Hamlet certainly seems mad, he does not seem to love Ophelia: he orders her to enter a nunnery and declares that he wishes to ban marriages.
+
+A group of traveling actors comes to Elsinore, and Hamlet seizes upon an idea to test his uncle’s guilt. He will have the players perform a scene closely resembling the sequence by which Hamlet imagines his uncle to have murdered his father, so that if Claudius is guilty, he will surely react. When the moment of the murder arrives in the theater, Claudius leaps up and leaves the room. Hamlet and Horatio agree that this proves his guilt. Hamlet goes to kill Claudius but finds him praying. Since he believes that killing Claudius while in prayer would send Claudius’s soul to heaven, Hamlet considers that it would be an inadequate revenge and decides to wait. Claudius, now frightened of Hamlet’s madness and fearing for his own safety, orders that Hamlet be sent to England at once.
+
+Hamlet goes to confront his mother, in whose bedchamber Polonius has hidden behind a tapestry. Hearing a noise from behind the tapestry, Hamlet believes the king is hiding there. He draws his sword and stabs through the fabric, killing Polonius. For this crime, he is immediately dispatched to England with Rosencrantz and Guildenstern. However, Claudius’s plan for Hamlet includes more than banishment, as he has given Rosencrantz and Guildenstern sealed orders for the King of England demanding that Hamlet be put to death.
+
+In the aftermath of her father’s death, Ophelia goes mad with grief and drowns in the river. Polonius’s son, Laertes, who has been staying in France, returns to Denmark in a rage. Claudius convinces him that Hamlet is to blame for his father’s and sister’s deaths. When Horatio and the king receive letters from Hamlet indicating that the prince has returned to Denmark after pirates attacked his ship en route to England, Claudius concocts a plan to use Laertes’ desire for revenge to secure Hamlet’s death. Laertes will fence with Hamlet in innocent sport, but Claudius will poison Laertes’ blade so that if he draws blood, Hamlet will die. As a backup plan, the king decides to poison a goblet, which he will give Hamlet to drink should Hamlet score the first or second hits of the match. Hamlet returns to the vicinity of Elsinore just as Ophelia’s funeral is taking place. Stricken with grief, he attacks Laertes and declares that he had in fact always loved Ophelia. Back at the castle, he tells Horatio that he believes one must be prepared to die, since death can come at any moment. A foolish courtier named Osric arrives on Claudius’s orders to arrange the fencing match between Hamlet and Laertes.
+
+The sword-fighting begins. Hamlet scores the first hit, but declines to drink from the king’s proffered goblet. Instead, Gertrude takes a drink from it and is swiftly killed by the poison. Laertes succeeds in wounding Hamlet, though Hamlet does not die of the poison immediately. First, Laertes is cut by his own sword’s blade, and, after revealing to Hamlet that Claudius is responsible for the queen’s death, he dies from the blade’s poison. Hamlet then stabs Claudius through with the poisoned sword and forces him to drink down the rest of the poisoned wine. Claudius dies, and Hamlet dies immediately after achieving his revenge.
+
+At this moment, a Norwegian prince named Fortinbras, who has led an army to Denmark and attacked Poland earlier in the play, enters with ambassadors from England, who report that Rosencrantz and Guildenstern are dead. Fortinbras is stunned by the gruesome sight of the entire royal family lying sprawled on the floor dead. He moves to take power of the kingdom. Horatio, fulfilling Hamlet’s last request, tells him Hamlet’s tragic story. Fortinbras orders that Hamlet be carried away in a manner befitting a fallen soldier.
\ No newline at end of file
diff --git a/dotnet/samples/GettingStartedWithProcesses/GettingStartedWithProcesses.csproj b/dotnet/samples/GettingStartedWithProcesses/GettingStartedWithProcesses.csproj
index 1da2089382b7..522a1b1571ab 100644
--- a/dotnet/samples/GettingStartedWithProcesses/GettingStartedWithProcesses.csproj
+++ b/dotnet/samples/GettingStartedWithProcesses/GettingStartedWithProcesses.csproj
@@ -10,8 +10,8 @@
- $(NoWarn);CS8618,IDE0009,IDE1006,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0080,SKEXP0101,SKEXP0110,OPENAI001
-
+ $(NoWarn);CS8618,IDE0009,IDE1006,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0080,SKEXP0081,SKEXP0101,SKEXP0110,OPENAI001
+
Library
5ee045b0-aea3-4f08-8d31-32d1a6f8fed0
@@ -44,7 +44,9 @@
+
+
@@ -59,9 +61,9 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/dotnet/samples/GettingStartedWithProcesses/SharedSteps/DisplayAssistantMessageStep.cs b/dotnet/samples/GettingStartedWithProcesses/SharedSteps/DisplayAssistantMessageStep.cs
index 6a6a0f8ddf1e..2b744f801985 100644
--- a/dotnet/samples/GettingStartedWithProcesses/SharedSteps/DisplayAssistantMessageStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/SharedSteps/DisplayAssistantMessageStep.cs
@@ -11,12 +11,12 @@ namespace SharedSteps;
///
public class DisplayAssistantMessageStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string DisplayAssistantMessage = nameof(DisplayAssistantMessage);
}
- [KernelFunction(Functions.DisplayAssistantMessage)]
+ [KernelFunction(ProcessStepFunctions.DisplayAssistantMessage)]
public async ValueTask DisplayAssistantMessageAsync(KernelProcessStepContext context, string assistantMessage)
{
Console.ForegroundColor = ConsoleColor.Blue;
diff --git a/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs b/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs
index 091ed937d4c7..62d92ccceb67 100644
--- a/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs
@@ -15,7 +15,7 @@ namespace SharedSteps;
///
public class ScriptedUserInputStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string GetUserInput = nameof(GetUserInput);
}
@@ -76,7 +76,7 @@ internal string GetNextUserMessage()
/// An instance of which can be
/// used to emit events from within a KernelFunction.
/// A
- [KernelFunction(Functions.GetUserInput)]
+ [KernelFunction(ProcessStepFunctions.GetUserInput)]
public virtual async ValueTask GetUserInputAsync(KernelProcessStepContext context)
{
var userMessage = this.GetNextUserMessage();
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step01/Step01_Processes.cs b/dotnet/samples/GettingStartedWithProcesses/Step01/Step01_Processes.cs
index fe2fab2edcec..0c6f072ad03c 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step01/Step01_Processes.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step01/Step01_Processes.cs
@@ -131,7 +131,7 @@ public override async ValueTask GetUserInputAsync(KernelProcessStepContext conte
///
private sealed class ChatBotResponseStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessFunctions
{
public const string GetChatResponse = nameof(GetChatResponse);
}
@@ -159,7 +159,7 @@ public override ValueTask ActivateAsync(KernelProcessStepState sta
/// The user message from a previous step.
/// A instance.
///
- [KernelFunction(Functions.GetChatResponse)]
+ [KernelFunction(ProcessFunctions.GetChatResponse)]
public async Task GetChatResponseAsync(KernelProcessStepContext context, string userMessage, Kernel _kernel)
{
_state!.ChatMessages.Add(new(AuthorRole.User, userMessage));
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step02/Processes/NewAccountCreationProcess.cs b/dotnet/samples/GettingStartedWithProcesses/Step02/Processes/NewAccountCreationProcess.cs
index 7e96b9544d28..74095b93d81f 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step02/Processes/NewAccountCreationProcess.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step02/Processes/NewAccountCreationProcess.cs
@@ -27,27 +27,27 @@ public static ProcessBuilder CreateProcess()
process
.OnInputEvent(AccountOpeningEvents.NewCustomerFormCompleted)
// The information gets passed to the core system record creation step
- .SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.Functions.CreateNewAccount, parameterName: "customerDetails"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.ProcessStepFunctions.CreateNewAccount, parameterName: "customerDetails"));
// When the newCustomerForm is completed, the user interaction transcript with the user is passed to the core system record creation step
process
.OnInputEvent(AccountOpeningEvents.CustomerInteractionTranscriptReady)
- .SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.Functions.CreateNewAccount, parameterName: "interactionTranscript"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.ProcessStepFunctions.CreateNewAccount, parameterName: "interactionTranscript"));
// When the fraudDetectionCheck step passes, the information gets to core system record creation step to kickstart this step
process
.OnInputEvent(AccountOpeningEvents.NewAccountVerificationCheckPassed)
- .SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.Functions.CreateNewAccount, parameterName: "previousCheckSucceeded"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.ProcessStepFunctions.CreateNewAccount, parameterName: "previousCheckSucceeded"));
// When the coreSystemRecordCreation step successfully creates a new accountId, it will trigger the creation of a new marketing entry through the marketingRecordCreation step
coreSystemRecordCreationStep
.OnEvent(AccountOpeningEvents.NewMarketingRecordInfoReady)
- .SendEventTo(new ProcessFunctionTargetBuilder(marketingRecordCreationStep, functionName: NewMarketingEntryStep.Functions.CreateNewMarketingEntry, parameterName: "userDetails"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(marketingRecordCreationStep, functionName: NewMarketingEntryStep.ProcessStepFunctions.CreateNewMarketingEntry, parameterName: "userDetails"));
// When the coreSystemRecordCreation step successfully creates a new accountId, it will trigger the creation of a new CRM entry through the crmRecord step
coreSystemRecordCreationStep
.OnEvent(AccountOpeningEvents.CRMRecordInfoReady)
- .SendEventTo(new ProcessFunctionTargetBuilder(crmRecordStep, functionName: CRMRecordCreationStep.Functions.CreateCRMEntry, parameterName: "userInteractionDetails"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(crmRecordStep, functionName: CRMRecordCreationStep.ProcessStepFunctions.CreateCRMEntry, parameterName: "userInteractionDetails"));
// ParameterName is necessary when the step has multiple input arguments like welcomePacketStep.CreateWelcomePacketAsync
// When the coreSystemRecordCreation step successfully creates a new accountId, it will pass the account information details to the welcomePacket step
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step02/Processes/NewAccountVerificationProcess.cs b/dotnet/samples/GettingStartedWithProcesses/Step02/Processes/NewAccountVerificationProcess.cs
index e4184a71bd1e..41490e1a69b7 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step02/Processes/NewAccountVerificationProcess.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step02/Processes/NewAccountVerificationProcess.cs
@@ -25,14 +25,14 @@ public static ProcessBuilder CreateProcess()
process
.OnInputEvent(AccountOpeningEvents.NewCustomerFormCompleted)
// The information gets passed to the core system record creation step
- .SendEventTo(new ProcessFunctionTargetBuilder(customerCreditCheckStep, functionName: CreditScoreCheckStep.Functions.DetermineCreditScore, parameterName: "customerDetails"))
+ .SendEventTo(new ProcessFunctionTargetBuilder(customerCreditCheckStep, functionName: CreditScoreCheckStep.ProcessStepFunctions.DetermineCreditScore, parameterName: "customerDetails"))
// The information gets passed to the fraud detection step for validation
- .SendEventTo(new ProcessFunctionTargetBuilder(fraudDetectionCheckStep, functionName: FraudDetectionStep.Functions.FraudDetectionCheck, parameterName: "customerDetails"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(fraudDetectionCheckStep, functionName: FraudDetectionStep.ProcessStepFunctions.FraudDetectionCheck, parameterName: "customerDetails"));
// When the creditScoreCheck step results in Approval, the information gets to the fraudDetection step to kickstart this step
customerCreditCheckStep
.OnEvent(AccountOpeningEvents.CreditScoreCheckApproved)
- .SendEventTo(new ProcessFunctionTargetBuilder(fraudDetectionCheckStep, functionName: FraudDetectionStep.Functions.FraudDetectionCheck, parameterName: "previousCheckSucceeded"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(fraudDetectionCheckStep, functionName: FraudDetectionStep.ProcessStepFunctions.FraudDetectionCheck, parameterName: "previousCheckSucceeded"));
return process;
}
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step02/Step02a_AccountOpening.cs b/dotnet/samples/GettingStartedWithProcesses/Step02/Step02a_AccountOpening.cs
index 2ee5bb33db1f..e8dc106928b9 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step02/Step02a_AccountOpening.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step02/Step02a_AccountOpening.cs
@@ -34,18 +34,18 @@ private KernelProcess SetupAccountOpeningProcess() where TUserIn
var welcomePacketStep = process.AddStepFromType();
process.OnInputEvent(AccountOpeningEvents.StartProcess)
- .SendEventTo(new ProcessFunctionTargetBuilder(newCustomerFormStep, CompleteNewCustomerFormStep.Functions.NewAccountWelcome));
+ .SendEventTo(new ProcessFunctionTargetBuilder(newCustomerFormStep, CompleteNewCustomerFormStep.ProcessStepFunctions.NewAccountWelcome));
// When the welcome message is generated, send message to displayAssistantMessageStep
newCustomerFormStep
.OnEvent(AccountOpeningEvents.NewCustomerFormWelcomeMessageComplete)
- .SendEventTo(new ProcessFunctionTargetBuilder(displayAssistantMessageStep, DisplayAssistantMessageStep.Functions.DisplayAssistantMessage));
+ .SendEventTo(new ProcessFunctionTargetBuilder(displayAssistantMessageStep, DisplayAssistantMessageStep.ProcessStepFunctions.DisplayAssistantMessage));
// When the userInput step emits a user input event, send it to the newCustomerForm step
// Function names are necessary when the step has multiple public functions like CompleteNewCustomerFormStep: NewAccountWelcome and NewAccountProcessUserInfo
userInputStep
.OnEvent(CommonEvents.UserInputReceived)
- .SendEventTo(new ProcessFunctionTargetBuilder(newCustomerFormStep, CompleteNewCustomerFormStep.Functions.NewAccountProcessUserInfo, "userMessage"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(newCustomerFormStep, CompleteNewCustomerFormStep.ProcessStepFunctions.NewAccountProcessUserInfo, "userMessage"));
userInputStep
.OnEvent(CommonEvents.Exit)
@@ -54,57 +54,57 @@ private KernelProcess SetupAccountOpeningProcess() where TUserIn
// When the newCustomerForm step emits needs more details, send message to displayAssistantMessage step
newCustomerFormStep
.OnEvent(AccountOpeningEvents.NewCustomerFormNeedsMoreDetails)
- .SendEventTo(new ProcessFunctionTargetBuilder(displayAssistantMessageStep, DisplayAssistantMessageStep.Functions.DisplayAssistantMessage));
+ .SendEventTo(new ProcessFunctionTargetBuilder(displayAssistantMessageStep, DisplayAssistantMessageStep.ProcessStepFunctions.DisplayAssistantMessage));
// After any assistant message is displayed, user input is expected to the next step is the userInputStep
displayAssistantMessageStep
.OnEvent(CommonEvents.AssistantResponseGenerated)
- .SendEventTo(new ProcessFunctionTargetBuilder(userInputStep, ScriptedUserInputStep.Functions.GetUserInput));
+ .SendEventTo(new ProcessFunctionTargetBuilder(userInputStep, ScriptedUserInputStep.ProcessStepFunctions.GetUserInput));
// When the newCustomerForm is completed...
newCustomerFormStep
.OnEvent(AccountOpeningEvents.NewCustomerFormCompleted)
// The information gets passed to the core system record creation step
- .SendEventTo(new ProcessFunctionTargetBuilder(customerCreditCheckStep, functionName: CreditScoreCheckStep.Functions.DetermineCreditScore, parameterName: "customerDetails"))
+ .SendEventTo(new ProcessFunctionTargetBuilder(customerCreditCheckStep, functionName: CreditScoreCheckStep.ProcessStepFunctions.DetermineCreditScore, parameterName: "customerDetails"))
// The information gets passed to the fraud detection step for validation
- .SendEventTo(new ProcessFunctionTargetBuilder(fraudDetectionCheckStep, functionName: FraudDetectionStep.Functions.FraudDetectionCheck, parameterName: "customerDetails"))
+ .SendEventTo(new ProcessFunctionTargetBuilder(fraudDetectionCheckStep, functionName: FraudDetectionStep.ProcessStepFunctions.FraudDetectionCheck, parameterName: "customerDetails"))
// The information gets passed to the core system record creation step
- .SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.Functions.CreateNewAccount, parameterName: "customerDetails"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.ProcessStepFunctions.CreateNewAccount, parameterName: "customerDetails"));
// When the newCustomerForm is completed, the user interaction transcript with the user is passed to the core system record creation step
newCustomerFormStep
.OnEvent(AccountOpeningEvents.CustomerInteractionTranscriptReady)
- .SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.Functions.CreateNewAccount, parameterName: "interactionTranscript"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.ProcessStepFunctions.CreateNewAccount, parameterName: "interactionTranscript"));
// When the creditScoreCheck step results in Rejection, the information gets to the mailService step to notify the user about the state of the application and the reasons
customerCreditCheckStep
.OnEvent(AccountOpeningEvents.CreditScoreCheckRejected)
- .SendEventTo(new ProcessFunctionTargetBuilder(mailServiceStep, functionName: MailServiceStep.Functions.SendMailToUserWithDetails, parameterName: "message"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(mailServiceStep, functionName: MailServiceStep.ProcessStepFunctions.SendMailToUserWithDetails, parameterName: "message"));
// When the creditScoreCheck step results in Approval, the information gets to the fraudDetection step to kickstart this step
customerCreditCheckStep
.OnEvent(AccountOpeningEvents.CreditScoreCheckApproved)
- .SendEventTo(new ProcessFunctionTargetBuilder(fraudDetectionCheckStep, functionName: FraudDetectionStep.Functions.FraudDetectionCheck, parameterName: "previousCheckSucceeded"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(fraudDetectionCheckStep, functionName: FraudDetectionStep.ProcessStepFunctions.FraudDetectionCheck, parameterName: "previousCheckSucceeded"));
// When the fraudDetectionCheck step fails, the information gets to the mailService step to notify the user about the state of the application and the reasons
fraudDetectionCheckStep
.OnEvent(AccountOpeningEvents.FraudDetectionCheckFailed)
- .SendEventTo(new ProcessFunctionTargetBuilder(mailServiceStep, functionName: MailServiceStep.Functions.SendMailToUserWithDetails, parameterName: "message"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(mailServiceStep, functionName: MailServiceStep.ProcessStepFunctions.SendMailToUserWithDetails, parameterName: "message"));
// When the fraudDetectionCheck step passes, the information gets to core system record creation step to kickstart this step
fraudDetectionCheckStep
.OnEvent(AccountOpeningEvents.FraudDetectionCheckPassed)
- .SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.Functions.CreateNewAccount, parameterName: "previousCheckSucceeded"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.ProcessStepFunctions.CreateNewAccount, parameterName: "previousCheckSucceeded"));
// When the coreSystemRecordCreation step successfully creates a new accountId, it will trigger the creation of a new marketing entry through the marketingRecordCreation step
coreSystemRecordCreationStep
.OnEvent(AccountOpeningEvents.NewMarketingRecordInfoReady)
- .SendEventTo(new ProcessFunctionTargetBuilder(marketingRecordCreationStep, functionName: NewMarketingEntryStep.Functions.CreateNewMarketingEntry, parameterName: "userDetails"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(marketingRecordCreationStep, functionName: NewMarketingEntryStep.ProcessStepFunctions.CreateNewMarketingEntry, parameterName: "userDetails"));
// When the coreSystemRecordCreation step successfully creates a new accountId, it will trigger the creation of a new CRM entry through the crmRecord step
coreSystemRecordCreationStep
.OnEvent(AccountOpeningEvents.CRMRecordInfoReady)
- .SendEventTo(new ProcessFunctionTargetBuilder(crmRecordStep, functionName: CRMRecordCreationStep.Functions.CreateCRMEntry, parameterName: "userInteractionDetails"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(crmRecordStep, functionName: CRMRecordCreationStep.ProcessStepFunctions.CreateCRMEntry, parameterName: "userInteractionDetails"));
// ParameterName is necessary when the step has multiple input arguments like welcomePacketStep.CreateWelcomePacketAsync
// When the coreSystemRecordCreation step successfully creates a new accountId, it will pass the account information details to the welcomePacket step
@@ -125,7 +125,7 @@ private KernelProcess SetupAccountOpeningProcess() where TUserIn
// After crmRecord and marketing gets created, a welcome packet is created to then send information to the user with the mailService step
welcomePacketStep
.OnEvent(AccountOpeningEvents.WelcomePacketCreated)
- .SendEventTo(new ProcessFunctionTargetBuilder(mailServiceStep, functionName: MailServiceStep.Functions.SendMailToUserWithDetails, parameterName: "message"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(mailServiceStep, functionName: MailServiceStep.ProcessStepFunctions.SendMailToUserWithDetails, parameterName: "message"));
// All possible paths end up with the user being notified about the account creation decision throw the mailServiceStep completion
mailServiceStep
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step02/Step02b_AccountOpening.cs b/dotnet/samples/GettingStartedWithProcesses/Step02/Step02b_AccountOpening.cs
index 581e41cf76d0..15e6fc692701 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step02/Step02b_AccountOpening.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step02/Step02b_AccountOpening.cs
@@ -34,18 +34,18 @@ private KernelProcess SetupAccountOpeningProcess() where TUserIn
process
.OnInputEvent(AccountOpeningEvents.StartProcess)
- .SendEventTo(new ProcessFunctionTargetBuilder(newCustomerFormStep, CompleteNewCustomerFormStep.Functions.NewAccountWelcome));
+ .SendEventTo(new ProcessFunctionTargetBuilder(newCustomerFormStep, CompleteNewCustomerFormStep.ProcessStepFunctions.NewAccountWelcome));
// When the welcome message is generated, send message to displayAssistantMessageStep
newCustomerFormStep
.OnEvent(AccountOpeningEvents.NewCustomerFormWelcomeMessageComplete)
- .SendEventTo(new ProcessFunctionTargetBuilder(displayAssistantMessageStep, DisplayAssistantMessageStep.Functions.DisplayAssistantMessage));
+ .SendEventTo(new ProcessFunctionTargetBuilder(displayAssistantMessageStep, DisplayAssistantMessageStep.ProcessStepFunctions.DisplayAssistantMessage));
// When the userInput step emits a user input event, send it to the newCustomerForm step
// Function names are necessary when the step has multiple public functions like CompleteNewCustomerFormStep: NewAccountWelcome and NewAccountProcessUserInfo
userInputStep
.OnEvent(CommonEvents.UserInputReceived)
- .SendEventTo(new ProcessFunctionTargetBuilder(newCustomerFormStep, CompleteNewCustomerFormStep.Functions.NewAccountProcessUserInfo, "userMessage"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(newCustomerFormStep, CompleteNewCustomerFormStep.ProcessStepFunctions.NewAccountProcessUserInfo, "userMessage"));
userInputStep
.OnEvent(CommonEvents.Exit)
@@ -54,12 +54,12 @@ private KernelProcess SetupAccountOpeningProcess() where TUserIn
// When the newCustomerForm step emits needs more details, send message to displayAssistantMessage step
newCustomerFormStep
.OnEvent(AccountOpeningEvents.NewCustomerFormNeedsMoreDetails)
- .SendEventTo(new ProcessFunctionTargetBuilder(displayAssistantMessageStep, DisplayAssistantMessageStep.Functions.DisplayAssistantMessage));
+ .SendEventTo(new ProcessFunctionTargetBuilder(displayAssistantMessageStep, DisplayAssistantMessageStep.ProcessStepFunctions.DisplayAssistantMessage));
// After any assistant message is displayed, user input is expected to the next step is the userInputStep
displayAssistantMessageStep
.OnEvent(CommonEvents.AssistantResponseGenerated)
- .SendEventTo(new ProcessFunctionTargetBuilder(userInputStep, ScriptedUserInputStep.Functions.GetUserInput));
+ .SendEventTo(new ProcessFunctionTargetBuilder(userInputStep, ScriptedUserInputStep.ProcessStepFunctions.GetUserInput));
// When the newCustomerForm is completed...
newCustomerFormStep
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/CRMRecordCreationStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/CRMRecordCreationStep.cs
index e62e8aae45f4..d19f6506de39 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/CRMRecordCreationStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/CRMRecordCreationStep.cs
@@ -10,12 +10,12 @@ namespace Step02.Steps;
///
public class CRMRecordCreationStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string CreateCRMEntry = nameof(CreateCRMEntry);
}
- [KernelFunction(Functions.CreateCRMEntry)]
+ [KernelFunction(ProcessStepFunctions.CreateCRMEntry)]
public async Task CreateCRMEntryAsync(KernelProcessStepContext context, AccountUserInteractionDetails userInteractionDetails, Kernel _kernel)
{
Console.WriteLine($"[CRM ENTRY CREATION] New Account {userInteractionDetails.AccountId} created");
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/CompleteNewCustomerFormStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/CompleteNewCustomerFormStep.cs
index 25d35872d0e0..5c4cf174b31c 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/CompleteNewCustomerFormStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/CompleteNewCustomerFormStep.cs
@@ -17,7 +17,7 @@ namespace Step02.Steps;
///
public class CompleteNewCustomerFormStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string NewAccountProcessUserInfo = nameof(NewAccountProcessUserInfo);
public const string NewAccountWelcome = nameof(NewAccountWelcome);
@@ -60,7 +60,7 @@ public override ValueTask ActivateAsync(KernelProcessStepState
public class CreditScoreCheckStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string DetermineCreditScore = nameof(DetermineCreditScore);
}
private const int MinCreditScore = 600;
- [KernelFunction(Functions.DetermineCreditScore)]
+ [KernelFunction(ProcessStepFunctions.DetermineCreditScore)]
public async Task DetermineCreditScoreAsync(KernelProcessStepContext context, NewCustomerForm customerDetails, Kernel _kernel)
{
// Placeholder for a call to API to validate credit score with customerDetails
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/FraudDetectionStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/FraudDetectionStep.cs
index 5461f13006d4..763fa7ee5983 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/FraudDetectionStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/FraudDetectionStep.cs
@@ -10,12 +10,12 @@ namespace Step02.Steps;
///
public class FraudDetectionStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string FraudDetectionCheck = nameof(FraudDetectionCheck);
}
- [KernelFunction(Functions.FraudDetectionCheck)]
+ [KernelFunction(ProcessStepFunctions.FraudDetectionCheck)]
public async Task FraudDetectionCheckAsync(KernelProcessStepContext context, bool previousCheckSucceeded, NewCustomerForm customerDetails, Kernel _kernel)
{
// Placeholder for a call to API to validate user details for fraud detection
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/MailServiceStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/MailServiceStep.cs
index b11f782cb201..f240b14305e0 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/MailServiceStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/MailServiceStep.cs
@@ -10,12 +10,12 @@ namespace Step02.Steps;
///
public class MailServiceStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string SendMailToUserWithDetails = nameof(SendMailToUserWithDetails);
}
- [KernelFunction(Functions.SendMailToUserWithDetails)]
+ [KernelFunction(ProcessStepFunctions.SendMailToUserWithDetails)]
public async Task SendMailServiceAsync(KernelProcessStepContext context, string message)
{
Console.WriteLine("======== MAIL SERVICE ======== ");
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/NewAccountStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/NewAccountStep.cs
index 5c79e9b1de76..f4e80ea0a762 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/NewAccountStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/NewAccountStep.cs
@@ -10,12 +10,12 @@ namespace Step02.Steps;
///
public class NewAccountStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string CreateNewAccount = nameof(CreateNewAccount);
}
- [KernelFunction(Functions.CreateNewAccount)]
+ [KernelFunction(ProcessStepFunctions.CreateNewAccount)]
public async Task CreateNewAccountAsync(KernelProcessStepContext context, bool previousCheckSucceeded, NewCustomerForm customerDetails, List interactionTranscript, Kernel _kernel)
{
// Placeholder for a call to API to create new account for user
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/NewMarketingEntryStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/NewMarketingEntryStep.cs
index 96bba3e8f02a..1942d883bfba 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/NewMarketingEntryStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/NewMarketingEntryStep.cs
@@ -10,12 +10,12 @@ namespace Step02.Steps;
///
public class NewMarketingEntryStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string CreateNewMarketingEntry = nameof(CreateNewMarketingEntry);
}
- [KernelFunction(Functions.CreateNewMarketingEntry)]
+ [KernelFunction(ProcessStepFunctions.CreateNewMarketingEntry)]
public async Task CreateNewMarketingEntryAsync(KernelProcessStepContext context, MarketingNewEntryDetails userDetails, Kernel _kernel)
{
Console.WriteLine($"[MARKETING ENTRY CREATION] New Account {userDetails.AccountId} created");
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/WelcomePacketStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/WelcomePacketStep.cs
index 3f9349f5eeb3..09d1c1efa9fe 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/WelcomePacketStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step02/Steps/WelcomePacketStep.cs
@@ -10,12 +10,12 @@ namespace Step02.Steps;
///
public class WelcomePacketStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string CreateWelcomePacket = nameof(CreateWelcomePacket);
}
- [KernelFunction(Functions.CreateWelcomePacket)]
+ [KernelFunction(ProcessStepFunctions.CreateWelcomePacket)]
public async Task CreateWelcomePacketAsync(KernelProcessStepContext context, bool marketingEntryCreated, bool crmRecordCreated, AccountDetails accountDetails, Kernel _kernel)
{
Console.WriteLine($"[WELCOME PACKET] New Account {accountDetails.AccountId} created");
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/FishAndChipsProcess.cs b/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/FishAndChipsProcess.cs
index 7ffb84de6312..67a7fbc863ef 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/FishAndChipsProcess.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/FishAndChipsProcess.cs
@@ -80,7 +80,7 @@ public static ProcessBuilder CreateProcessWithStatefulSteps(string processName =
private sealed class AddFishAndChipsCondimentsStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessFunctions
{
public const string AddCondiments = nameof(AddCondiments);
}
@@ -90,7 +90,7 @@ public static class OutputEvents
public const string CondimentsAdded = nameof(CondimentsAdded);
}
- [KernelFunction(Functions.AddCondiments)]
+ [KernelFunction(ProcessFunctions.AddCondiments)]
public async Task AddCondimentsAsync(KernelProcessStepContext context, List fishActions, List potatoActions)
{
Console.WriteLine($"ADD_CONDIMENTS: Added condiments to Fish & Chips - Fish: {JsonSerializer.Serialize(fishActions)}, Potatoes: {JsonSerializer.Serialize(potatoActions)}");
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/FishSandwichProcess.cs b/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/FishSandwichProcess.cs
index eee2110618fa..9f42f1e72cbe 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/FishSandwichProcess.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/FishSandwichProcess.cs
@@ -104,7 +104,7 @@ public static ProcessBuilder CreateProcessWithStatefulStepsV2(string processName
private sealed class AddBunsStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessFunctions
{
public const string AddBuns = nameof(AddBuns);
}
@@ -114,7 +114,7 @@ public static class OutputEvents
public const string BunsAdded = nameof(BunsAdded);
}
- [KernelFunction(Functions.AddBuns)]
+ [KernelFunction(ProcessFunctions.AddBuns)]
public async Task SliceFoodAsync(KernelProcessStepContext context, List foodActions)
{
Console.WriteLine($"BUNS_ADDED_STEP: Buns added to ingredient {foodActions.First()}");
@@ -125,7 +125,7 @@ public async Task SliceFoodAsync(KernelProcessStepContext context, List
private sealed class AddSpecialSauceStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessFunctions
{
public const string AddSpecialSauce = nameof(AddSpecialSauce);
}
@@ -135,7 +135,7 @@ public static class OutputEvents
public const string SpecialSauceAdded = nameof(SpecialSauceAdded);
}
- [KernelFunction(Functions.AddSpecialSauce)]
+ [KernelFunction(ProcessFunctions.AddSpecialSauce)]
public async Task SliceFoodAsync(KernelProcessStepContext context, List foodActions)
{
Console.WriteLine($"SPECIAL_SAUCE_ADDED: Special sauce added to ingredient {foodActions.First()}");
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/FriedFishProcess.cs b/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/FriedFishProcess.cs
index 076c4f932641..40651be2548b 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/FriedFishProcess.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/FriedFishProcess.cs
@@ -40,7 +40,7 @@ public static ProcessBuilder CreateProcess(string processName = "FriedFishProces
gatherIngredientsStep
.OnEvent(GatherFriedFishIngredientsStep.OutputEvents.IngredientsGathered)
- .SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodStep.Functions.ChopFood));
+ .SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodStep.ProcessStepFunctions.ChopFood));
chopStep
.OnEvent(CutFoodStep.OutputEvents.ChoppingReady)
@@ -68,7 +68,7 @@ public static ProcessBuilder CreateProcessWithStatefulStepsV1(string processName
gatherIngredientsStep
.OnEvent(GatherFriedFishIngredientsWithStockStep.OutputEvents.IngredientsGathered)
- .SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.Functions.ChopFood));
+ .SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.ProcessStepFunctions.ChopFood));
chopStep
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.ChoppingReady)
@@ -92,9 +92,9 @@ public static ProcessBuilder CreateProcessWithStatefulStepsV2(string processName
// It is recommended to specify process version in case this process is used as a step by another process
var processBuilder = new ProcessBuilder(processName) { Version = "FriedFishProcess.v2" };
- var gatherIngredientsStep = processBuilder.AddStepFromType(name: "gatherFishIngredientStep", aliases: ["GatherFriedFishIngredientsWithStockStep"]);
- var chopStep = processBuilder.AddStepFromType(name: "chopFishStep", aliases: ["CutFoodStep"]);
- var fryStep = processBuilder.AddStepFromType(name: "fryFishStep", aliases: ["FryFoodStep"]);
+ var gatherIngredientsStep = processBuilder.AddStepFromType(id: "gatherFishIngredientStep", aliases: ["GatherFriedFishIngredientsWithStockStep"]);
+ var chopStep = processBuilder.AddStepFromType(id: "chopFishStep", aliases: ["CutFoodStep"]);
+ var fryStep = processBuilder.AddStepFromType(id: "fryFishStep", aliases: ["FryFoodStep"]);
processBuilder
.OnInputEvent(ProcessEvents.PrepareFriedFish)
@@ -102,7 +102,7 @@ public static ProcessBuilder CreateProcessWithStatefulStepsV2(string processName
gatherIngredientsStep
.OnEvent(GatherFriedFishIngredientsWithStockStep.OutputEvents.IngredientsGathered)
- .SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.Functions.ChopFood));
+ .SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.ProcessStepFunctions.ChopFood));
gatherIngredientsStep
.OnEvent(GatherFriedFishIngredientsWithStockStep.OutputEvents.IngredientsOutOfStock)
@@ -114,11 +114,11 @@ public static ProcessBuilder CreateProcessWithStatefulStepsV2(string processName
chopStep
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.KnifeNeedsSharpening)
- .SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.Functions.SharpenKnife));
+ .SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.ProcessStepFunctions.SharpenKnife));
chopStep
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.KnifeSharpened)
- .SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.Functions.ChopFood));
+ .SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.ProcessStepFunctions.ChopFood));
fryStep
.OnEvent(FryFoodStep.OutputEvents.FoodRuined)
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/PotatoFriesProcess.cs b/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/PotatoFriesProcess.cs
index bdf9c62e6c97..cd41c80167a9 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/PotatoFriesProcess.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/PotatoFriesProcess.cs
@@ -40,7 +40,7 @@ public static ProcessBuilder CreateProcess(string processName = "PotatoFriesProc
gatherIngredientsStep
.OnEvent(GatherPotatoFriesIngredientsStep.OutputEvents.IngredientsGathered)
- .SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodStep.Functions.SliceFood));
+ .SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodStep.ProcessStepFunctions.SliceFood));
sliceStep
.OnEvent(CutFoodStep.OutputEvents.SlicingReady)
@@ -73,7 +73,7 @@ public static ProcessBuilder CreateProcessWithStatefulSteps(string processName =
gatherIngredientsStep
.OnEvent(GatherPotatoFriesIngredientsWithStockStep.OutputEvents.IngredientsGathered)
- .SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodWithSharpeningStep.Functions.SliceFood));
+ .SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodWithSharpeningStep.ProcessStepFunctions.SliceFood));
gatherIngredientsStep
.OnEvent(GatherPotatoFriesIngredientsWithStockStep.OutputEvents.IngredientsOutOfStock)
@@ -85,11 +85,11 @@ public static ProcessBuilder CreateProcessWithStatefulSteps(string processName =
sliceStep
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.KnifeNeedsSharpening)
- .SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodWithSharpeningStep.Functions.SharpenKnife));
+ .SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodWithSharpeningStep.ProcessStepFunctions.SharpenKnife));
sliceStep
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.KnifeSharpened)
- .SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodWithSharpeningStep.Functions.SliceFood));
+ .SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodWithSharpeningStep.ProcessStepFunctions.SliceFood));
fryStep
.OnEvent(FryFoodStep.OutputEvents.FoodRuined)
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/SingleFoodItemProcess.cs b/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/SingleFoodItemProcess.cs
index 690ebfe791e5..8cd915835f5d 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/SingleFoodItemProcess.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step03/Processes/SingleFoodItemProcess.cs
@@ -76,7 +76,7 @@ public static ProcessBuilder CreateProcess(string processName = "SingleFoodItemP
private sealed class DispatchSingleOrderStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessFunctions
{
public const string PrepareSingleOrder = nameof(PrepareSingleOrder);
}
@@ -89,7 +89,7 @@ public static class OutputEvents
public const string PrepareFishAndChips = nameof(PrepareFishAndChips);
}
- [KernelFunction(Functions.PrepareSingleOrder)]
+ [KernelFunction(ProcessFunctions.PrepareSingleOrder)]
public async Task DispatchSingleOrderAsync(KernelProcessStepContext context, FoodItem foodItem)
{
var foodName = foodItem.ToFriendlyString();
@@ -118,7 +118,7 @@ public async Task DispatchSingleOrderAsync(KernelProcessStepContext context, Foo
private sealed class PackOrderStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessFunctions
{
public const string PackFood = nameof(PackFood);
}
@@ -127,7 +127,7 @@ public static class OutputEvents
public const string FoodPacked = nameof(FoodPacked);
}
- [KernelFunction(Functions.PackFood)]
+ [KernelFunction(ProcessFunctions.PackFood)]
public async Task PackFoodAsync(KernelProcessStepContext context, List foodActions)
{
Console.WriteLine($"PACKING_FOOD: Food {foodActions.First()} Packed! - {JsonSerializer.Serialize(foodActions)}");
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/CutFoodStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/CutFoodStep.cs
index c83ff422955d..37f9aaff8ed6 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/CutFoodStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/CutFoodStep.cs
@@ -12,7 +12,7 @@ namespace Step03.Steps;
[KernelProcessStepMetadata("CutFoodStep.V1")]
public class CutFoodStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string ChopFood = nameof(ChopFood);
public const string SliceFood = nameof(SliceFood);
@@ -24,7 +24,7 @@ public static class OutputEvents
public const string SlicingReady = nameof(SlicingReady);
}
- [KernelFunction(Functions.ChopFood)]
+ [KernelFunction(ProcessStepFunctions.ChopFood)]
public async Task ChopFoodAsync(KernelProcessStepContext context, List foodActions)
{
var foodToBeCut = foodActions.First();
@@ -33,7 +33,7 @@ public async Task ChopFoodAsync(KernelProcessStepContext context, List f
await context.EmitEventAsync(new() { Id = OutputEvents.ChoppingReady, Data = foodActions });
}
- [KernelFunction(Functions.SliceFood)]
+ [KernelFunction(ProcessStepFunctions.SliceFood)]
public async Task SliceFoodAsync(KernelProcessStepContext context, List foodActions)
{
var foodToBeCut = foodActions.First();
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/CutFoodWithSharpeningStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/CutFoodWithSharpeningStep.cs
index 86126928dd4c..604bcea7f3ba 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/CutFoodWithSharpeningStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/CutFoodWithSharpeningStep.cs
@@ -12,7 +12,7 @@ namespace Step03.Steps;
[KernelProcessStepMetadata("CutFoodStep.V2")]
public class CutFoodWithSharpeningStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string ChopFood = nameof(ChopFood);
public const string SliceFood = nameof(SliceFood);
@@ -35,7 +35,7 @@ public override ValueTask ActivateAsync(KernelProcessStepState foodActions)
{
var foodToBeCut = foodActions.First();
@@ -54,7 +54,7 @@ public async Task ChopFoodAsync(KernelProcessStepContext context, List f
await context.EmitEventAsync(new() { Id = OutputEvents.ChoppingReady, Data = foodActions });
}
- [KernelFunction(Functions.SliceFood)]
+ [KernelFunction(ProcessStepFunctions.SliceFood)]
public async Task SliceFoodAsync(KernelProcessStepContext context, List foodActions)
{
var foodToBeCut = foodActions.First();
@@ -73,7 +73,7 @@ public async Task SliceFoodAsync(KernelProcessStepContext context, List
await context.EmitEventAsync(new() { Id = OutputEvents.SlicingReady, Data = foodActions });
}
- [KernelFunction(Functions.SharpenKnife)]
+ [KernelFunction(ProcessStepFunctions.SharpenKnife)]
public async Task SharpenKnifeAsync(KernelProcessStepContext context, List foodActions)
{
this._state!.KnifeSharpness += this._state._sharpeningBoost;
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/FryFoodStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/FryFoodStep.cs
index 0f4a770713ee..bfc2dd50f5ae 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/FryFoodStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/FryFoodStep.cs
@@ -12,7 +12,7 @@ namespace Step03.Steps;
[KernelProcessStepMetadata("FryFoodStep.V1")]
public class FryFoodStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string FryFood = nameof(FryFood);
}
@@ -25,7 +25,7 @@ public static class OutputEvents
private readonly Random _randomSeed = new();
- [KernelFunction(Functions.FryFood)]
+ [KernelFunction(ProcessStepFunctions.FryFood)]
public async Task FryFoodAsync(KernelProcessStepContext context, List foodActions)
{
var foodToFry = foodActions.First();
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/GatherIngredientsStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/GatherIngredientsStep.cs
index 55b8e29a70d6..15d253f022b7 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/GatherIngredientsStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step03/Steps/GatherIngredientsStep.cs
@@ -9,7 +9,7 @@ namespace Step03.Steps;
///
public class GatherIngredientsStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string GatherIngredients = nameof(GatherIngredients);
}
@@ -32,7 +32,7 @@ public GatherIngredientsStep(FoodIngredients ingredient)
/// The context for the current step and process.
/// list of actions taken to the food
///
- [KernelFunction(Functions.GatherIngredients)]
+ [KernelFunction(ProcessStepFunctions.GatherIngredients)]
public virtual async Task GatherIngredientsAsync(KernelProcessStepContext context, List foodActions)
{
var ingredient = this._ingredient.ToFriendlyString();
@@ -55,7 +55,7 @@ public virtual async Task GatherIngredientsAsync(KernelProcessStepContext contex
///
public class GatherIngredientsWithStockStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string GatherIngredients = nameof(GatherIngredients);
}
@@ -87,7 +87,7 @@ public override ValueTask ActivateAsync(KernelProcessStepStateThe context for the current step and process.
/// list of actions taken to the food
///
- [KernelFunction(Functions.GatherIngredients)]
+ [KernelFunction(ProcessStepFunctions.GatherIngredients)]
public virtual async Task GatherIngredientsAsync(KernelProcessStepContext context, List foodActions)
{
var ingredient = this._ingredient.ToFriendlyString(); ;
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step04/SchemaGenerator.cs b/dotnet/samples/GettingStartedWithProcesses/Step04/SchemaGenerator.cs
index 5350e2c53d6e..77632832b023 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step04/SchemaGenerator.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step04/SchemaGenerator.cs
@@ -1,6 +1,4 @@
// Copyright (c) Microsoft. All rights reserved.
-using System.Text.Json;
-using System.Text.Json.Serialization;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
@@ -8,21 +6,21 @@ namespace Step04;
internal static class JsonSchemaGenerator
{
+ private static readonly AIJsonSchemaCreateOptions s_config = new()
+ {
+ TransformOptions = new()
+ {
+ DisallowAdditionalProperties = true,
+ RequireAllProperties = true,
+ MoveDefaultKeywordToDescription = true,
+ }
+ };
+
///
/// Wrapper for generating a JSON schema as string from a .NET type.
///
public static string FromType()
{
- JsonSerializerOptions options = new(JsonSerializerOptions.Default)
- {
- UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
- };
- AIJsonSchemaCreateOptions config = new()
- {
- IncludeSchemaKeyword = false,
- DisallowAdditionalProperties = true,
- };
-
- return KernelJsonSchemaBuilder.Build(typeof(TSchemaType), "Intent Result", config).AsJson();
+ return KernelJsonSchemaBuilder.Build(typeof(TSchemaType), "Intent Result", s_config).AsJson();
}
}
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step04/Step04_AgentOrchestration.cs b/dotnet/samples/GettingStartedWithProcesses/Step04/Step04_AgentOrchestration.cs
index 20e506958bd3..3bcf8758956a 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step04/Step04_AgentOrchestration.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step04/Step04_AgentOrchestration.cs
@@ -1,13 +1,17 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.ClientModel;
+using Azure.Identity;
using Events;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
+using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
+using OpenAI;
using SharedSteps;
using Step04.Plugins;
using Step04.Steps;
@@ -18,8 +22,20 @@ namespace Step04;
/// Demonstrate creation of a that orchestrates an conversation.
/// For visual reference of the process check diagram.
///
-public class Step04_AgentOrchestration(ITestOutputHelper output) : BaseTest(output, redirectSystemConsoleOutput: true)
+public class Step04_AgentOrchestration : BaseTest
{
+ public Step04_AgentOrchestration(ITestOutputHelper output) : base(output, redirectSystemConsoleOutput: true)
+ {
+ this.Client =
+ this.UseOpenAIConfig ?
+ OpenAIAssistantAgent.CreateOpenAIClient(new ApiKeyCredential(this.ApiKey ?? throw new ConfigurationNotFoundException("OpenAI:ApiKey"))) :
+ !string.IsNullOrWhiteSpace(this.ApiKey) ?
+ OpenAIAssistantAgent.CreateAzureOpenAIClient(new ApiKeyCredential(this.ApiKey), new Uri(this.Endpoint!)) :
+ OpenAIAssistantAgent.CreateAzureOpenAIClient(new AzureCliCredential(), new Uri(this.Endpoint!));
+ }
+
+ protected OpenAIClient Client { get; init; }
+
// Target Open AI Services
protected override bool ForceOpenAI => true;
@@ -38,6 +54,34 @@ public async Task DelegatedGroupChatAsync()
await RunProcessAsync(process);
}
+ [Fact]
+ public async Task SingleTeacherStudentAgentCallAsync()
+ {
+ // Define process
+ KernelProcess process = SetupSingleAgentProcess(nameof(SingleTeacherStudentAgentCallAsync));
+
+ // Setup kernel with OpenAI Client
+ Kernel kernel = SetupKernel();
+
+ // Execute process
+ await using LocalKernelProcessContext localProcess =
+ await process.StartAsync(
+ kernel,
+ new KernelProcessEvent()
+ {
+ Id = AgentOrchestrationEvents.StartProcess
+ });
+
+ // Cleaning up created agents
+ var processState = await localProcess.GetStateAsync();
+ var agentState = (KernelProcessStepState)processState.Steps.Where(step => step.State.Id == "Student").FirstOrDefault()!.State;
+ var agentId = agentState?.State?.AgentId;
+ if (agentId != null)
+ {
+ await this.Client.GetAssistantClient().DeleteAssistantAsync(agentId);
+ }
+ }
+
private sealed class BasicAgentChatUserInput : ScriptedUserInputStep
{
public BasicAgentChatUserInput()
@@ -58,6 +102,23 @@ public override void PopulateUserInputs(UserInputState state)
}
}
+ private sealed class BasicTeacherAgentInput : ScriptedUserInputStep
+ {
+ public BasicTeacherAgentInput()
+ {
+ this.SuppressOutput = true;
+ }
+
+ public override void PopulateUserInputs(UserInputState state)
+ {
+ state.UserInputs.Add("What is 2+2");
+ state.UserInputs.Add("What is 2+2");
+ state.UserInputs.Add("What is the name of a shape with 3 consecutive sides");
+ state.UserInputs.Add("What is the internal angle of a square");
+ state.UserInputs.Add("What is a parallellogram");
+ }
+ }
+
private async Task RunProcessAsync(KernelProcess process)
{
// Initialize services
@@ -81,6 +142,48 @@ await process.StartAsync(
}
}
+ private KernelProcess SetupSingleAgentProcess(string processName) where TUserInputStep : ScriptedUserInputStep
+ {
+ ProcessBuilder process = new(processName);
+
+ var userInputStep = process.AddStepFromType();
+ var renderMessageStep = process.AddStepFromType();
+ var agentStep = process.AddStepFromAgent(new()
+ {
+ Name = "Student",
+ // On purpose not assigning AgentId, if not provided a new agent is created
+ Description = "Solves problem given",
+ Instructions = "Solve the problem given, if the question is repeated mention something like I already answered but here is the answer with a bit of humor",
+ Model = new()
+ {
+ Id = "gpt-4o",
+ },
+ Type = OpenAIAssistantAgentFactory.OpenAIAssistantAgentType,
+ });
+
+ // Entry point
+ process.OnInputEvent(AgentOrchestrationEvents.StartProcess)
+ .SendEventTo(new(userInputStep));
+
+ // Pass user input to primary agent
+ userInputStep
+ .OnEvent(CommonEvents.UserInputReceived)
+ .SendEventTo(new ProcessFunctionTargetBuilder(agentStep, parameterName: "message"))
+ .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.ProcessStepFunctions.RenderUserText));
+
+ agentStep
+ .OnFunctionResult()
+ .SendEventTo(new ProcessFunctionTargetBuilder(userInputStep))
+ .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.ProcessStepFunctions.RenderMessage));
+
+ agentStep
+ .OnFunctionError()
+ .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.ProcessStepFunctions.RenderError, "error"))
+ .StopProcess();
+
+ return process.Build();
+ }
+
private KernelProcess SetupAgentProcess(string processName) where TUserInputStep : ScriptedUserInputStep
{
ProcessBuilder process = new(processName);
@@ -92,17 +195,17 @@ private KernelProcess SetupAgentProcess(string processName) wher
AttachErrorStep(
userInputStep,
- ScriptedUserInputStep.Functions.GetUserInput);
+ ScriptedUserInputStep.ProcessStepFunctions.GetUserInput);
AttachErrorStep(
managerAgentStep,
- ManagerAgentStep.Functions.InvokeAgent,
- ManagerAgentStep.Functions.InvokeGroup,
- ManagerAgentStep.Functions.ReceiveResponse);
+ ManagerAgentStep.ProcessStepFunctions.InvokeAgent,
+ ManagerAgentStep.ProcessStepFunctions.InvokeGroup,
+ ManagerAgentStep.ProcessStepFunctions.ReceiveResponse);
AttachErrorStep(
agentGroupStep,
- AgentGroupChatStep.Functions.InvokeAgentGroup);
+ AgentGroupChatStep.ProcessStepFunctions.InvokeAgentGroup);
// Entry point
process.OnInputEvent(AgentOrchestrationEvents.StartProcess)
@@ -111,24 +214,24 @@ private KernelProcess SetupAgentProcess(string processName) wher
// Pass user input to primary agent
userInputStep
.OnEvent(CommonEvents.UserInputReceived)
- .SendEventTo(new ProcessFunctionTargetBuilder(managerAgentStep, ManagerAgentStep.Functions.InvokeAgent))
- .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.Functions.RenderUserText, parameterName: "message"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(managerAgentStep, ManagerAgentStep.ProcessStepFunctions.InvokeAgent))
+ .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.ProcessStepFunctions.RenderUserText, parameterName: "message"));
// Process completed
userInputStep
.OnEvent(CommonEvents.UserInputComplete)
- .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.Functions.RenderDone))
+ .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.ProcessStepFunctions.RenderDone))
.StopProcess();
// Render response from primary agent
managerAgentStep
.OnEvent(AgentOrchestrationEvents.AgentResponse)
- .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.Functions.RenderMessage, parameterName: "message"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.ProcessStepFunctions.RenderMessage, parameterName: "message"));
// Request is complete
managerAgentStep
.OnEvent(CommonEvents.UserInputComplete)
- .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.Functions.RenderDone))
+ .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.ProcessStepFunctions.RenderDone))
.StopProcess();
// Request more user input
@@ -139,7 +242,7 @@ private KernelProcess SetupAgentProcess(string processName) wher
// Delegate to inner agents
managerAgentStep
.OnEvent(AgentOrchestrationEvents.AgentWorking)
- .SendEventTo(new ProcessFunctionTargetBuilder(managerAgentStep, ManagerAgentStep.Functions.InvokeGroup));
+ .SendEventTo(new ProcessFunctionTargetBuilder(managerAgentStep, ManagerAgentStep.ProcessStepFunctions.InvokeGroup));
// Provide input to inner agents
managerAgentStep
@@ -149,12 +252,12 @@ private KernelProcess SetupAgentProcess(string processName) wher
// Render response from inner chat (for visibility)
agentGroupStep
.OnEvent(AgentOrchestrationEvents.GroupMessage)
- .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.Functions.RenderInnerMessage, parameterName: "message"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.ProcessStepFunctions.RenderInnerMessage, parameterName: "message"));
// Provide inner response to primary agent
agentGroupStep
.OnEvent(AgentOrchestrationEvents.GroupCompleted)
- .SendEventTo(new ProcessFunctionTargetBuilder(managerAgentStep, ManagerAgentStep.Functions.ReceiveResponse, parameterName: "response"));
+ .SendEventTo(new ProcessFunctionTargetBuilder(managerAgentStep, ManagerAgentStep.ProcessStepFunctions.ReceiveResponse, parameterName: "response"));
KernelProcess kernelProcess = process.Build();
@@ -166,12 +269,25 @@ void AttachErrorStep(ProcessStepBuilder step, params string[] functionNames)
{
step
.OnFunctionError(functionName)
- .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.Functions.RenderError, "error"))
+ .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.ProcessStepFunctions.RenderError, "error"))
.StopProcess();
}
}
}
+ private Kernel SetupKernel()
+ {
+ IKernelBuilder builder = Kernel.CreateBuilder();
+ // Add Chat Completion to Kernel
+ this.AddChatCompletionToKernel(builder);
+ builder.Services.AddSingleton(this.Client);
+
+ // NOTE: Uncomment to see process logging
+ //builder.Services.AddSingleton(this.LoggerFactory);
+
+ return builder.Build();
+ }
+
private Kernel SetupKernel(ChatHistory history)
{
IKernelBuilder builder = Kernel.CreateBuilder();
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step04/Steps/AgentGroupChatStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step04/Steps/AgentGroupChatStep.cs
index 112576fe37c5..ded57d791461 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step04/Steps/AgentGroupChatStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step04/Steps/AgentGroupChatStep.cs
@@ -14,12 +14,12 @@ public class AgentGroupChatStep : KernelProcessStep
public const string ChatServiceKey = $"{nameof(AgentGroupChatStep)}:{nameof(ChatServiceKey)}";
public const string ReducerServiceKey = $"{nameof(AgentGroupChatStep)}:{nameof(ReducerServiceKey)}";
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string InvokeAgentGroup = nameof(InvokeAgentGroup);
}
- [KernelFunction(Functions.InvokeAgentGroup)]
+ [KernelFunction(ProcessStepFunctions.InvokeAgentGroup)]
public async Task InvokeAgentGroupAsync(KernelProcessStepContext context, Kernel kernel, string input)
{
AgentGroupChat chat = kernel.GetRequiredService();
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step04/Steps/ManagerAgentStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step04/Steps/ManagerAgentStep.cs
index 4efcdcbb5849..0848d984af40 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step04/Steps/ManagerAgentStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step04/Steps/ManagerAgentStep.cs
@@ -19,14 +19,14 @@ public class ManagerAgentStep : KernelProcessStep
public const string AgentServiceKey = $"{nameof(ManagerAgentStep)}:{nameof(AgentServiceKey)}";
public const string ReducerServiceKey = $"{nameof(ManagerAgentStep)}:{nameof(ReducerServiceKey)}";
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string InvokeAgent = nameof(InvokeAgent);
public const string InvokeGroup = nameof(InvokeGroup);
public const string ReceiveResponse = nameof(ReceiveResponse);
}
- [KernelFunction(Functions.InvokeAgent)]
+ [KernelFunction(ProcessStepFunctions.InvokeAgent)]
public async Task InvokeAgentAsync(KernelProcessStepContext context, Kernel kernel, string userInput, ILogger logger)
{
// Get the chat history
@@ -60,7 +60,7 @@ public async Task InvokeAgentAsync(KernelProcessStepContext context, Kernel kern
await context.EmitEventAsync(new() { Id = intentEventId });
}
- [KernelFunction(Functions.InvokeGroup)]
+ [KernelFunction(ProcessStepFunctions.InvokeGroup)]
public async Task InvokeGroupAsync(KernelProcessStepContext context, Kernel kernel)
{
// Get the chat history
@@ -73,7 +73,7 @@ public async Task InvokeGroupAsync(KernelProcessStepContext context, Kernel kern
await context.EmitEventAsync(new() { Id = AgentOrchestrationEvents.GroupInput, Data = summary });
}
- [KernelFunction(Functions.ReceiveResponse)]
+ [KernelFunction(ProcessStepFunctions.ReceiveResponse)]
public async Task ReceiveResponseAsync(KernelProcessStepContext context, Kernel kernel, string response)
{
// Get the chat history
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step04/Steps/RenderMessageStep.cs b/dotnet/samples/GettingStartedWithProcesses/Step04/Steps/RenderMessageStep.cs
index 684bdc29bda9..c095373d5041 100644
--- a/dotnet/samples/GettingStartedWithProcesses/Step04/Steps/RenderMessageStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/Step04/Steps/RenderMessageStep.cs
@@ -14,7 +14,7 @@ namespace Step04.Steps;
///
public class RenderMessageStep : KernelProcessStep
{
- public static class Functions
+ public static class ProcessStepFunctions
{
public const string RenderDone = nameof(RenderMessageStep.RenderDone);
public const string RenderError = nameof(RenderMessageStep.RenderError);
@@ -61,8 +61,14 @@ public void RenderUserText(string message)
/// Render an assistant message from the primary chat
///
[KernelFunction]
- public void RenderMessage(ChatMessageContent message)
+ public void RenderMessage(ChatMessageContent? message)
{
+ if (message is null)
+ {
+ // if the message is empty, we don't want to render it
+ return;
+ }
+
Render(message);
}
diff --git a/dotnet/samples/GettingStartedWithProcesses/Step06/Step06_FoundryAgentProcess.cs b/dotnet/samples/GettingStartedWithProcesses/Step06/Step06_FoundryAgentProcess.cs
new file mode 100644
index 000000000000..a2909f7d0380
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithProcesses/Step06/Step06_FoundryAgentProcess.cs
@@ -0,0 +1,273 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.ClientModel;
+using Azure.Identity;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.AzureAI;
+using Microsoft.SemanticKernel.Agents.OpenAI;
+using OpenAI;
+
+namespace Step06;
+public class Step06_FoundryAgentProcess : BaseTest
+{
+ public Step06_FoundryAgentProcess(ITestOutputHelper output) : base(output, redirectSystemConsoleOutput: true)
+ {
+ this.Client =
+ this.UseOpenAIConfig ?
+ OpenAIAssistantAgent.CreateOpenAIClient(new ApiKeyCredential(this.ApiKey ?? throw new ConfigurationNotFoundException("OpenAI:ApiKey"))) :
+ !string.IsNullOrWhiteSpace(this.ApiKey) ?
+ OpenAIAssistantAgent.CreateAzureOpenAIClient(new ApiKeyCredential(this.ApiKey), new Uri(this.Endpoint!)) :
+ OpenAIAssistantAgent.CreateAzureOpenAIClient(new AzureCliCredential(), new Uri(this.Endpoint!));
+ }
+
+ protected OpenAIClient Client { get; init; }
+
+ // Target Open AI Services
+ protected override bool ForceOpenAI => true;
+
+ [Fact]
+ public async Task ProcessWithExistingFoundryAgentsAndSeparateThreadsAsync()
+ {
+ var foundryAgentDefinition1 = new AgentDefinition { Id = "{AGENT_ID}", Name = "Agent1", Type = AzureAIAgentFactory.AzureAIAgentType };
+ var foundryAgentDefinition2 = new AgentDefinition { Id = "{AGENT_ID}", Name = "Agent2", Type = AzureAIAgentFactory.AzureAIAgentType };
+
+ var processBuilder = new FoundryProcessBuilder("foundry_agents");
+
+ var agent1 = processBuilder.AddStepFromAgent(foundryAgentDefinition1);
+ var agent2 = processBuilder.AddStepFromAgent(foundryAgentDefinition2);
+
+ processBuilder.OnProcessEnter().SendEventTo(agent1);
+ processBuilder.OnResultFromStep(agent1).SendEventTo(agent2);
+ processBuilder.OnResultFromStep(agent2).StopProcess();
+
+ var process = processBuilder.Build();
+
+ var foundryClient = AzureAIAgent.CreateAzureAIClient(TestConfiguration.AzureAI.ConnectionString, new AzureCliCredential());
+ var agentsClient = foundryClient.GetAgentsClient();
+
+ var kernelBuilder = Kernel.CreateBuilder();
+ kernelBuilder.Services.AddSingleton(agentsClient);
+ kernelBuilder.Services.AddSingleton(foundryClient);
+ var kernel = kernelBuilder.Build();
+
+ var context = await process.StartAsync(kernel, new() { Id = "start", Data = "What is the best programming language and why?" });
+ var agent1Result = await context.GetStateAsync();
+
+ Assert.NotNull(context);
+ Assert.NotNull(agent1Result);
+ }
+
+ [Fact]
+ public async Task ProcessWithExistingFoundryAgentsAndSharedThreadAsync()
+ {
+ var foundryAgentDefinition1 = new AgentDefinition { Id = "{AGENT_ID}", Name = "Agent1", Type = AzureAIAgentFactory.AzureAIAgentType };
+ var foundryAgentDefinition2 = new AgentDefinition { Id = "{AGENT_ID}", Name = "Agent2", Type = AzureAIAgentFactory.AzureAIAgentType };
+
+ var processBuilder = new FoundryProcessBuilder("foundry_agents");
+
+ processBuilder.AddThread("shared_thread", KernelProcessThreadLifetime.Scoped);
+
+ var agent1 = processBuilder.AddStepFromAgent(foundryAgentDefinition1, defaultThread: "shared_thread");
+ var agent2 = processBuilder.AddStepFromAgent(foundryAgentDefinition2, defaultThread: "shared_thread");
+
+ processBuilder.OnProcessEnter().SendEventTo(agent1);
+ processBuilder.OnResultFromStep(agent2);
+ processBuilder.OnResultFromStep(agent2).StopProcess();
+
+ var process = processBuilder.Build();
+
+ var foundryClient = AzureAIAgent.CreateAzureAIClient(TestConfiguration.AzureAI.ConnectionString, new AzureCliCredential());
+ var agentsClient = foundryClient.GetAgentsClient();
+
+ var kernelBuilder = Kernel.CreateBuilder();
+ kernelBuilder.Services.AddSingleton(agentsClient);
+ kernelBuilder.Services.AddSingleton(foundryClient);
+ var kernel = kernelBuilder.Build();
+
+ var context = await process.StartAsync(kernel, new() { Id = "start", Data = "Why are frogs green?" });
+ var agent1Result = await context.GetStateAsync();
+
+ Assert.NotNull(context);
+ Assert.NotNull(agent1Result);
+ }
+
+ [Fact]
+ public async Task ProcessWithExistingFoundryAgentsWithProcessStateUpdateAndOnCompleteConditions()
+ {
+ // Define the agents
+ var foundryAgentDefinition1 = new AgentDefinition { Id = "{AGENT_ID}", Name = "Agent1", Type = AzureAIAgentFactory.AzureAIAgentType };
+ var foundryAgentDefinition2 = new AgentDefinition { Id = "{AGENT_ID}", Name = "Agent2", Type = AzureAIAgentFactory.AzureAIAgentType };
+
+ // Define the process with a state type
+ var processBuilder = new FoundryProcessBuilder("foundry_agents");
+ processBuilder.AddThread("shared_thread", KernelProcessThreadLifetime.Scoped);
+
+ var agent1 = processBuilder.AddStepFromAgent(foundryAgentDefinition1, defaultThread: "shared_thread");
+ var agent2 = processBuilder.AddStepFromAgent(foundryAgentDefinition2, defaultThread: "shared_thread");
+
+ processBuilder.OnProcessEnter().SendEventTo(agent1);
+ processBuilder.OnResultFromStep(agent1, condition: "_variables_.Counter < `3`").SendEventTo(agent1);
+ processBuilder.OnResultFromStep(agent1, condition: "_variables_.Counter >= `3`").SendEventTo(agent2);
+ processBuilder.OnResultFromStep(agent2).StopProcess();
+
+ var process = processBuilder.Build();
+ }
+
+ [Fact]
+ public async Task ProcessWithExistingFoundryAgentsWithProcessStateUpdateAndOrchestrationConditions()
+ {
+ // Define the agents
+ var foundryAgentDefinition1 = new AgentDefinition { Id = "{AGENT_ID}", Name = "Agent1", Type = AzureAIAgentFactory.AzureAIAgentType };
+ var foundryAgentDefinition2 = new AgentDefinition { Id = "{AGENT_ID}", Name = "Agent2", Type = AzureAIAgentFactory.AzureAIAgentType };
+
+ // Define the process with a state type
+ var processBuilder = new FoundryProcessBuilder("foundry_agents");
+ processBuilder.AddThread("shared_thread", KernelProcessThreadLifetime.Scoped);
+
+ // Agent1 will increment the Counter state variable every time it runs
+ var agent1 = processBuilder.AddStepFromAgent(foundryAgentDefinition1, defaultThread: "shared_thread");
+
+ var agent2 = processBuilder.AddStepFromAgent(foundryAgentDefinition2, defaultThread: "shared_thread");
+
+ processBuilder.OnProcessEnter().SendEventTo(agent1);
+
+ // Increment the counter every time the step runs
+ processBuilder.OnStepEnter(agent1, condition: "always").UpdateProcessState("Counter", StateUpdateOperations.Increment, 1);
+
+ // Agent1 should run as long as the Counter is less than 3
+ processBuilder.OnResultFromStep(agent1, condition: "_variables_.Counter < `3`").SendEventTo(agent1);
+
+ // When the Counter is 3, Agent1 should stop and Agent2 should start
+ processBuilder.OnResultFromStep(agent1, condition: "_variables_.Counter >= `3`").SendEventTo(agent2);
+
+ // When Agent2 is done, the process should stop
+ processBuilder.OnResultFromStep(agent2).StopProcess();
+
+ var process = processBuilder.Build();
+ }
+
+ [Fact]
+ public async Task ProcessWithExistingFoundryAgentsWithDynamicAgentResolution()
+ {
+ // Define the agents
+ var foundryAgentDefinition1 = new AgentDefinition { Id = "{AGENT_ID}", Name = "Agent1", Type = AzureAIAgentFactory.AzureAIAgentType };
+ var foundryAgentDefinition2 = new AgentDefinition { Id = "_variables_.NextAgentId", Name = "Agent2", Type = AzureAIAgentFactory.AzureAIAgentType };
+
+ // Define the process with a state type
+ var processBuilder = new FoundryProcessBuilder("foundry_agents");
+ processBuilder.AddThread("shared_thread", KernelProcessThreadLifetime.Scoped);
+
+ // Agent1 will increment the Counter state variable every time it runs
+ var agent1 = processBuilder.AddStepFromAgent(foundryAgentDefinition1, defaultThread: "shared_thread");
+
+ var agent2 = processBuilder.AddStepFromAgentProxy(stepId: "dynamicAgent", foundryAgentDefinition2, threadName: "shared_thread");
+
+ processBuilder.OnProcessEnter().SendEventTo(agent1);
+
+ // Increment the counter every time the step runs
+ processBuilder.OnStepEnter(agent1, condition: "always").UpdateProcessState("Counter", StateUpdateOperations.Increment, 1);
+
+ // Agent1 should run as long as the Counter is less than 3
+ processBuilder.OnResultFromStep(agent1, condition: "_variables_.Counter < `3`").SendEventTo(agent1);
+
+ // When the Counter is 3, Agent1 should stop and Agent2 should start
+ processBuilder.OnResultFromStep(agent1, condition: "_variables_.Counter >= `3`").SendEventTo(agent2);
+
+ // When Agent2 is done, the process should stop
+ processBuilder.OnResultFromStep(agent2).StopProcess();
+
+ var process = processBuilder.Build();
+ }
+
+ [Fact]
+ public async Task ProcessWithTwoAgentMathChat()
+ {
+ // Define the agents
+ var studentDefinition = new AgentDefinition { Id = "{AGENT_ID}", Name = "Student", Type = AzureAIAgentFactory.AzureAIAgentType };
+ var teacherDefinition = new AgentDefinition { Id = "{AGENT_ID2}", Name = "Teacher", Type = AzureAIAgentFactory.AzureAIAgentType };
+
+ // Define the process with a state type
+ var processBuilder = new FoundryProcessBuilder("two_agent_math_chat");
+
+ // Create a thread for the student
+ processBuilder.AddThread("student_thread", KernelProcessThreadLifetime.Scoped);
+
+ // Add the student
+ var student = processBuilder.AddStepFromAgent(studentDefinition);
+
+ // Add the teacher
+ var teacher = processBuilder.AddStepFromAgent(teacherDefinition);
+
+ // Orchestrate
+ processBuilder.OnProcessEnter().SendEventTo(
+ student,
+ thread: "_variables_.student_thread",
+ messagesIn: ["_variables_.TeacherMessages"],
+ inputs: new Dictionary
+ {
+ { "InteractionCount", "_variables_.StudentState.InteractionCount" }
+ });
+
+ processBuilder.OnResultFromStep(student)
+ .UpdateProcessState(path: "StudentMessages", operation: StateUpdateOperations.Set, value: "_agent_.messages_out")
+ .UpdateProcessState(path: "UserMessages", operation: StateUpdateOperations.Increment, value: "_agent_.user_messages")
+ .UpdateProcessState(path: "InteractionCount", operation: StateUpdateOperations.Increment, value: "_agent_.student_messages")
+ .UpdateProcessState(path: "StudentState.InteractionCount", operation: StateUpdateOperations.Increment, value: "_agent_.student_messages")
+ .UpdateProcessState(path: "StudentState.Name", operation: StateUpdateOperations.Set, value: "Runhan")
+ .SendEventTo(teacher, messagesIn: ["_variables_.StudentMessages"]);
+
+ processBuilder.OnStepExit(teacher, condition: "contains(to_string(_agent_.messages_out), '[COMPLETE]')")
+ .EmitEvent(
+ eventName: "correct_answer",
+ payload: new Dictionary
+ {
+ { "Question", "_variables_.TeacherMessages" },
+ { "Answer", "_variables_.StudentMessages" }
+ })
+ .UpdateProcessState(path: "_variables_.TeacherMessages", operation: StateUpdateOperations.Set, value: "_agent_.messages_out")
+ .UpdateProcessState(path: "_variables_.InteractionCount", operation: StateUpdateOperations.Increment, value: 1);
+
+ processBuilder.OnStepExit(teacher, condition: "_default_")
+ .SendEventTo(student);
+
+ processBuilder.OnWorkflowEvent("correct_answer")
+ .StopProcess(messagesIn: ["_event_.Answer", "_variable_.StudentMessages"]);
+
+ var process = processBuilder.Build();
+
+ await processBuilder.DeployToFoundryAsync(process, TestConfiguration.AzureAI.WorkflowEndpoint);
+ }
+
+ public class TwoAgentMathState
+ {
+ public List UserMessages { get; set; }
+
+ public List StudentMessages { get; set; }
+
+ public List TeacherMessages { get; set; }
+
+ public StudentState StudentState { get; set; } = new();
+
+ public int InteractionCount { get; set; }
+ }
+
+ public class StudentState
+ {
+ public int InteractionCount { get; set; }
+
+ public string Name { get; set; }
+ }
+
+ public class DynamicAgentState
+ {
+ public string NextAgentId { get; set; } = "{AGENT_ID}";
+ public int Counter { get; set; }
+ }
+
+ public class ProcessStateWithCounter
+ {
+ public int Counter { get; set; }
+ }
+}
diff --git a/dotnet/samples/GettingStartedWithTextSearch/GettingStartedWithTextSearch.csproj b/dotnet/samples/GettingStartedWithTextSearch/GettingStartedWithTextSearch.csproj
index 03a522206317..d5312077c373 100644
--- a/dotnet/samples/GettingStartedWithTextSearch/GettingStartedWithTextSearch.csproj
+++ b/dotnet/samples/GettingStartedWithTextSearch/GettingStartedWithTextSearch.csproj
@@ -29,6 +29,7 @@
+
diff --git a/dotnet/samples/GettingStartedWithTextSearch/InMemoryVectorStoreFixture.cs b/dotnet/samples/GettingStartedWithTextSearch/InMemoryVectorStoreFixture.cs
index 23da3fff00ea..96b86ba8d766 100644
--- a/dotnet/samples/GettingStartedWithTextSearch/InMemoryVectorStoreFixture.cs
+++ b/dotnet/samples/GettingStartedWithTextSearch/InMemoryVectorStoreFixture.cs
@@ -1,12 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Reflection;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Connectors.InMemory;
-using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Data;
-using Microsoft.SemanticKernel.Embeddings;
+using OpenAI;
namespace GettingStartedWithTextSearch;
@@ -15,7 +15,7 @@ namespace GettingStartedWithTextSearch;
///
public class InMemoryVectorStoreFixture : IAsyncLifetime
{
- public ITextEmbeddingGenerationService TextEmbeddingGenerationService { get; private set; }
+ public IEmbeddingGenerator> EmbeddingGenerator { get; private set; }
public InMemoryVectorStore InMemoryVectorStore { get; private set; }
@@ -35,13 +35,13 @@ public InMemoryVectorStoreFixture()
.Build();
TestConfiguration.Initialize(configRoot);
- // Create an InMemory vector store.
- this.InMemoryVectorStore = new InMemoryVectorStore();
-
// Create an embedding generation service.
- this.TextEmbeddingGenerationService = new OpenAITextEmbeddingGenerationService(
- TestConfiguration.OpenAI.EmbeddingModelId,
- TestConfiguration.OpenAI.ApiKey);
+ this.EmbeddingGenerator = new OpenAIClient(TestConfiguration.OpenAI.ApiKey)
+ .GetEmbeddingClient(TestConfiguration.OpenAI.EmbeddingModelId)
+ .AsIEmbeddingGenerator();
+
+ // Create an InMemory vector store.
+ this.InMemoryVectorStore = new InMemoryVectorStore(new() { EmbeddingGenerator = this.EmbeddingGenerator });
}
///
@@ -72,7 +72,6 @@ static DataModel CreateRecord(int index, string text, ReadOnlyMemory embe
Text = text,
Link = $"guid://{guid}",
Tag = index % 2 == 0 ? "Even" : "Odd",
- Embedding = embedding
};
}
@@ -122,7 +121,7 @@ private async Task> CreateCollection
// Create records and generate embeddings for them.
var tasks = entries.Select((entry, i) => Task.Run(async () =>
{
- var record = createRecord(i, entry, await this.TextEmbeddingGenerationService.GenerateEmbeddingAsync(entry).ConfigureAwait(false));
+ var record = createRecord(i, entry, (await this.EmbeddingGenerator.GenerateAsync(entry).ConfigureAwait(false)).Vector);
await collection.UpsertAsync(record).ConfigureAwait(false);
}));
await Task.WhenAll(tasks).ConfigureAwait(false);
@@ -155,7 +154,7 @@ public sealed class DataModel
public required string Tag { get; init; }
[VectorStoreRecordVector(1536)]
- public ReadOnlyMemory Embedding { get; init; }
+ public string Embedding => Text;
}
#endregion
}
diff --git a/dotnet/samples/GettingStartedWithTextSearch/Step4_Search_With_VectorStore.cs b/dotnet/samples/GettingStartedWithTextSearch/Step4_Search_With_VectorStore.cs
index a2f950fb3804..f01f6fb5a3b6 100644
--- a/dotnet/samples/GettingStartedWithTextSearch/Step4_Search_With_VectorStore.cs
+++ b/dotnet/samples/GettingStartedWithTextSearch/Step4_Search_With_VectorStore.cs
@@ -23,14 +23,10 @@ public class Step4_Search_With_VectorStore(ITestOutputHelper output, InMemoryVec
public async Task UsingInMemoryVectorStoreRecordTextSearchAsync()
{
// Use embedding generation service and record collection for the fixture.
- var textEmbeddingGeneration = fixture.TextEmbeddingGenerationService;
var collection = fixture.VectorStoreRecordCollection;
// Create a text search instance using the InMemory vector store.
- // TODO: Once OpenAITextEmbeddingGenerationService implements MEAI's IEmbeddingGenerator (#10811), configure it with the collection
-#pragma warning disable CS0618 // VectorStoreTextSearch with ITextEmbeddingGenerationService is obsolete
- var textSearch = new VectorStoreTextSearch(collection, textEmbeddingGeneration);
-#pragma warning restore CS0618
+ var textSearch = new VectorStoreTextSearch(collection);
// Search and return results as TextSearchResult items
var query = "What is the Semantic Kernel?";
@@ -59,14 +55,11 @@ public async Task RagWithInMemoryVectorStoreTextSearchAsync()
Kernel kernel = kernelBuilder.Build();
// Use embedding generation service and record collection for the fixture.
- var textEmbeddingGeneration = fixture.TextEmbeddingGenerationService;
+ var embeddingGenerator = fixture.EmbeddingGenerator;
var collection = fixture.VectorStoreRecordCollection;
// Create a text search instance using the InMemory vector store.
- // TODO: Once OpenAITextEmbeddingGenerationService implements MEAI's IEmbeddingGenerator (#10811), configure it with the collection
-#pragma warning disable CS0618 // VectorStoreTextSearch with ITextEmbeddingGenerationService is obsolete
- var textSearch = new VectorStoreTextSearch(collection, textEmbeddingGeneration);
-#pragma warning restore CS0618
+ var textSearch = new VectorStoreTextSearch(collection);
// Build a text search plugin with vector store search and add to the kernel
var searchPlugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin");
@@ -113,14 +106,11 @@ public async Task FunctionCallingWithInMemoryVectorStoreTextSearchAsync()
Kernel kernel = kernelBuilder.Build();
// Use embedding generation service and record collection for the fixture.
- var textEmbeddingGeneration = fixture.TextEmbeddingGenerationService;
+ var embeddingGenerator = fixture.EmbeddingGenerator;
var collection = fixture.VectorStoreRecordCollection;
// Create a text search instance using the InMemory vector store.
- // TODO: Once OpenAITextEmbeddingGenerationService implements MEAI's IEmbeddingGenerator (#10811), configure it with the collection
-#pragma warning disable CS0618 // VectorStoreTextSearch with ITextEmbeddingGenerationService is obsolete
- var textSearch = new VectorStoreTextSearch(collection, textEmbeddingGeneration);
-#pragma warning restore CS0618
+ var textSearch = new VectorStoreTextSearch(collection);
// Build a text search plugin with vector store search and add to the kernel
var searchPlugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin");
diff --git a/dotnet/samples/GettingStartedWithVectorStores/GettingStartedWithVectorStores.csproj b/dotnet/samples/GettingStartedWithVectorStores/GettingStartedWithVectorStores.csproj
index dec156215f6d..d3532d99c5b0 100644
--- a/dotnet/samples/GettingStartedWithVectorStores/GettingStartedWithVectorStores.csproj
+++ b/dotnet/samples/GettingStartedWithVectorStores/GettingStartedWithVectorStores.csproj
@@ -29,6 +29,7 @@
+
diff --git a/dotnet/samples/GettingStartedWithVectorStores/Step1_Ingest_Data.cs b/dotnet/samples/GettingStartedWithVectorStores/Step1_Ingest_Data.cs
index 40b6ed9640b3..6ab4a293307f 100644
--- a/dotnet/samples/GettingStartedWithVectorStores/Step1_Ingest_Data.cs
+++ b/dotnet/samples/GettingStartedWithVectorStores/Step1_Ingest_Data.cs
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Connectors.InMemory;
-using Microsoft.SemanticKernel.Embeddings;
namespace GettingStartedWithVectorStores;
@@ -22,7 +22,7 @@ public async Task IngestDataIntoInMemoryVectorStoreAsync()
var collection = vectorStore.GetCollection("skglossary");
// Ingest data into the collection.
- await IngestDataIntoVectorStoreAsync(collection, fixture.TextEmbeddingGenerationService);
+ await IngestDataIntoVectorStoreAsync(collection, fixture.EmbeddingGenerator);
// Retrieve an item from the collection and write it to the console.
var record = await collection.GetAsync("4");
@@ -33,11 +33,11 @@ public async Task IngestDataIntoInMemoryVectorStoreAsync()
/// Ingest data into the given collection.
///
/// The collection to ingest data into.
- /// The service to use for generating embeddings.
+ /// The service to use for generating embeddings.
/// The keys of the upserted records.
internal static async Task> IngestDataIntoVectorStoreAsync(
IVectorStoreRecordCollection collection,
- ITextEmbeddingGenerationService textEmbeddingGenerationService)
+ IEmbeddingGenerator> embeddingGenerator)
{
// Create the collection if it doesn't exist.
await collection.CreateCollectionIfNotExistsAsync();
@@ -46,7 +46,7 @@ internal static async Task> IngestDataIntoVectorStoreAsync(
var glossaryEntries = CreateGlossaryEntries().ToList();
var tasks = glossaryEntries.Select(entry => Task.Run(async () =>
{
- entry.DefinitionEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(entry.Definition);
+ entry.DefinitionEmbedding = (await embeddingGenerator.GenerateAsync(entry.Definition)).Vector;
}));
await Task.WhenAll(tasks);
@@ -106,7 +106,7 @@ private static IEnumerable CreateGlossaryEntries()
Key = "6",
Category = "AI",
Term = "LLM",
- Definition = "Large language model. A type of artificial ingelligence algorithm that is designed to understand and generate human language."
+ Definition = "Large language model. A type of artificial intelligence algorithm that is designed to understand and generate human language."
};
}
}
diff --git a/dotnet/samples/GettingStartedWithVectorStores/Step2_Vector_Search.cs b/dotnet/samples/GettingStartedWithVectorStores/Step2_Vector_Search.cs
index 195d638573f7..c4593bb8e433 100644
--- a/dotnet/samples/GettingStartedWithVectorStores/Step2_Vector_Search.cs
+++ b/dotnet/samples/GettingStartedWithVectorStores/Step2_Vector_Search.cs
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Connectors.InMemory;
-using Microsoft.SemanticKernel.Embeddings;
namespace GettingStartedWithVectorStores;
@@ -23,7 +23,7 @@ public async Task SearchAnInMemoryVectorStoreAsync()
var searchResultItem = await SearchVectorStoreAsync(
collection,
"What is an Application Programming Interface?",
- fixture.TextEmbeddingGenerationService);
+ fixture.EmbeddingGenerator);
// Write the search result with its score to the console.
Console.WriteLine(searchResultItem.Record.Definition);
@@ -35,12 +35,12 @@ public async Task SearchAnInMemoryVectorStoreAsync()
///
/// The collection to search.
/// The string to search matches for.
- /// The service to generate embeddings with.
+ /// The service to generate embeddings with.
/// The top search result.
- internal static async Task> SearchVectorStoreAsync(IVectorStoreRecordCollection collection, string searchString, ITextEmbeddingGenerationService textEmbeddingGenerationService)
+ internal static async Task> SearchVectorStoreAsync(IVectorStoreRecordCollection collection, string searchString, IEmbeddingGenerator> embeddingGenerator)
{
// Generate an embedding from the search string.
- var searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ var searchVector = (await embeddingGenerator.GenerateAsync(searchString)).Vector;
// Search the store and get the single most relevant result.
var searchResultItems = await collection.SearchEmbeddingAsync(
@@ -59,7 +59,7 @@ public async Task SearchAnInMemoryVectorStoreWithFilteringAsync()
// Generate an embedding from the search string.
var searchString = "How do I provide additional context to an LLM?";
- var searchVector = await fixture.TextEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ var searchVector = (await fixture.EmbeddingGenerator.GenerateAsync(searchString)).Vector;
// Search the store with a filter and get the single most relevant result.
var searchResultItems = await collection.SearchEmbeddingAsync(
@@ -82,7 +82,7 @@ private async Task> GetVectorStor
var collection = vectorStore.GetCollection("skglossary");
// Ingest data into the collection using the code from step 1.
- await Step1_Ingest_Data.IngestDataIntoVectorStoreAsync(collection, fixture.TextEmbeddingGenerationService);
+ await Step1_Ingest_Data.IngestDataIntoVectorStoreAsync(collection, fixture.EmbeddingGenerator);
return collection;
}
diff --git a/dotnet/samples/GettingStartedWithVectorStores/Step3_Switch_VectorStore.cs b/dotnet/samples/GettingStartedWithVectorStores/Step3_Switch_VectorStore.cs
index cc6c7443968c..da035fe5b5b2 100644
--- a/dotnet/samples/GettingStartedWithVectorStores/Step3_Switch_VectorStore.cs
+++ b/dotnet/samples/GettingStartedWithVectorStores/Step3_Switch_VectorStore.cs
@@ -29,13 +29,13 @@ public async Task UseAnAzureAISearchVectorStoreAsync()
var collection = vectorStore.GetCollection("skglossary");
// Ingest data into the collection using the same code as we used in Step1 with the InMemory Vector Store.
- await Step1_Ingest_Data.IngestDataIntoVectorStoreAsync(collection, fixture.TextEmbeddingGenerationService);
+ await Step1_Ingest_Data.IngestDataIntoVectorStoreAsync(collection, fixture.EmbeddingGenerator);
// Search the vector store using the same code as we used in Step2 with the InMemory Vector Store.
var searchResultItem = await Step2_Vector_Search.SearchVectorStoreAsync(
collection,
"What is an Application Programming Interface?",
- fixture.TextEmbeddingGenerationService);
+ fixture.EmbeddingGenerator);
// Write the search result with its score to the console.
Console.WriteLine(searchResultItem.Record.Definition);
@@ -57,13 +57,13 @@ public async Task UseARedisVectorStoreAsync()
var collection = vectorStore.GetCollection("skglossary");
// Ingest data into the collection using the same code as we used in Step1 with the InMemory Vector Store.
- await Step1_Ingest_Data.IngestDataIntoVectorStoreAsync(collection, fixture.TextEmbeddingGenerationService);
+ await Step1_Ingest_Data.IngestDataIntoVectorStoreAsync(collection, fixture.EmbeddingGenerator);
// Search the vector store using the same code as we used in Step2 with the InMemory Vector Store.
var searchResultItem = await Step2_Vector_Search.SearchVectorStoreAsync(
collection,
"What is an Application Programming Interface?",
- fixture.TextEmbeddingGenerationService);
+ fixture.EmbeddingGenerator);
// Write the search result with its score to the console.
Console.WriteLine(searchResultItem.Record.Definition);
diff --git a/dotnet/samples/GettingStartedWithVectorStores/Step4_Use_DynamicDataModel.cs b/dotnet/samples/GettingStartedWithVectorStores/Step4_Use_DynamicDataModel.cs
index 63ed0ef1d34f..69b67ab9f2ff 100644
--- a/dotnet/samples/GettingStartedWithVectorStores/Step4_Use_DynamicDataModel.cs
+++ b/dotnet/samples/GettingStartedWithVectorStores/Step4_Use_DynamicDataModel.cs
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Connectors.Redis;
-using Microsoft.SemanticKernel.Embeddings;
using StackExchange.Redis;
namespace GettingStartedWithVectorStores;
@@ -30,7 +30,7 @@ public async Task SearchAVectorStoreWithDynamicMappingAsync()
// the data into the database previously.
var collection = vectorStore.GetCollection("skglossary");
var customDataModelCollection = vectorStore.GetCollection("skglossary");
- await Step1_Ingest_Data.IngestDataIntoVectorStoreAsync(customDataModelCollection, fixture.TextEmbeddingGenerationService);
+ await Step1_Ingest_Data.IngestDataIntoVectorStoreAsync(customDataModelCollection, fixture.EmbeddingGenerator);
// To use dynamic data modeling, we still have to describe the storage schema to the vector store
// using a record definition. The benefit over a custom data model is that this definition
@@ -53,7 +53,7 @@ public async Task SearchAVectorStoreWithDynamicMappingAsync()
// Generate an embedding from the search string.
var searchString = "How do I provide additional context to an LLM?";
- var searchVector = await fixture.TextEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
+ var searchVector = (await fixture.EmbeddingGenerator.GenerateAsync(searchString)).Vector;
// Search the generic data model collection and get the single most relevant result.
var searchResultItems = await dynamicDataModelCollection.SearchEmbeddingAsync(
diff --git a/dotnet/samples/GettingStartedWithVectorStores/VectorStoresFixture.cs b/dotnet/samples/GettingStartedWithVectorStores/VectorStoresFixture.cs
index 39ab2c2234a4..55a43c5b1f29 100644
--- a/dotnet/samples/GettingStartedWithVectorStores/VectorStoresFixture.cs
+++ b/dotnet/samples/GettingStartedWithVectorStores/VectorStoresFixture.cs
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Reflection;
+using Azure.AI.OpenAI;
using Azure.Identity;
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
-using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
-using Microsoft.SemanticKernel.Embeddings;
namespace GettingStartedWithVectorStores;
@@ -25,14 +25,13 @@ public VectorStoresFixture()
.Build();
TestConfiguration.Initialize(configRoot);
- this.TextEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
- TestConfiguration.AzureOpenAIEmbeddings.DeploymentName,
- TestConfiguration.AzureOpenAIEmbeddings.Endpoint,
- new AzureCliCredential());
+ this.EmbeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential())
+ .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName)
+ .AsIEmbeddingGenerator();
}
///
/// Gets the text embedding generation service
///
- public ITextEmbeddingGenerationService TextEmbeddingGenerationService { get; }
+ public IEmbeddingGenerator> EmbeddingGenerator { get; }
}
diff --git a/dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs b/dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs
index 9eb6495b68f8..69eb66f482f7 100644
--- a/dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs
+++ b/dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs
@@ -274,7 +274,7 @@ await functionProcessor.InvokeFunctionCallsAsync(
if (message is not null)
{
- ChatMessageContent content = GenerateMessageContent(agent.GetName(), message, completedStep);
+ ChatMessageContent content = GenerateMessageContent(agent.GetName(), message, completedStep, logger);
if (content.Items.Count > 0)
{
@@ -415,7 +415,7 @@ public static async IAsyncEnumerable InvokeStreamin
switch (contentUpdate.UpdateKind)
{
case StreamingUpdateReason.MessageUpdated:
- yield return GenerateStreamingMessageContent(agent.GetName(), contentUpdate);
+ yield return GenerateStreamingMessageContent(agent.GetName(), run!, contentUpdate, logger);
break;
}
}
@@ -524,7 +524,7 @@ await RetrieveMessageAsync(
if (message != null)
{
- ChatMessageContent content = GenerateMessageContent(agent.GetName(), message, step);
+ ChatMessageContent content = GenerateMessageContent(agent.GetName(), message, step, logger);
messages?.Add(content);
}
}
@@ -555,7 +555,7 @@ await RetrieveMessageAsync(
logger.LogAzureAIAgentCompletedRun(nameof(InvokeAsync), run?.Id ?? "Failed", threadId);
}
- private static ChatMessageContent GenerateMessageContent(string? assistantName, ThreadMessage message, RunStep? completedStep = null)
+ private static ChatMessageContent GenerateMessageContent(string? assistantName, ThreadMessage message, RunStep? completedStep = null, ILogger? logger = null)
{
AuthorRole role = new(message.Role.ToString());
@@ -591,7 +591,15 @@ private static ChatMessageContent GenerateMessageContent(string? assistantName,
foreach (MessageTextAnnotation annotation in textContent.Annotations)
{
- content.Items.Add(GenerateAnnotationContent(annotation));
+ AnnotationContent? annotationItem = GenerateAnnotationContent(annotation);
+ if (annotationItem != null)
+ {
+ content.Items.Add(annotationItem);
+ }
+ else
+ {
+ logger?.LogAzureAIAgentUnknownAnnotation(nameof(GenerateMessageContent), message.RunId, message.ThreadId, annotation.GetType());
+ }
}
}
// Process image content
@@ -604,7 +612,7 @@ private static ChatMessageContent GenerateMessageContent(string? assistantName,
return content;
}
- private static StreamingChatMessageContent GenerateStreamingMessageContent(string? assistantName, MessageContentUpdate update)
+ private static StreamingChatMessageContent GenerateStreamingMessageContent(string? assistantName, ThreadRun run, MessageContentUpdate update, ILogger? logger)
{
StreamingChatMessageContent content =
new(AuthorRole.Assistant, content: null)
@@ -625,7 +633,15 @@ private static StreamingChatMessageContent GenerateStreamingMessageContent(strin
// Process annotations
else if (update.TextAnnotation != null)
{
- content.Items.Add(GenerateStreamingAnnotationContent(update.TextAnnotation));
+ StreamingAnnotationContent? annotationItem = GenerateStreamingAnnotationContent(update.TextAnnotation);
+ if (annotationItem != null)
+ {
+ content.Items.Add(annotationItem);
+ }
+ else
+ {
+ logger?.LogAzureAIAgentUnknownAnnotation(nameof(GenerateStreamingMessageContent), run.Id, run.ThreadId, update.TextAnnotation.GetType());
+ }
}
if (update.Role.HasValue && update.Role.Value != MessageRole.User)
@@ -665,46 +681,85 @@ private static StreamingChatMessageContent GenerateStreamingMessageContent(strin
return content.Items.Count > 0 ? content : null;
}
- private static AnnotationContent GenerateAnnotationContent(MessageTextAnnotation annotation)
+ private static AnnotationContent? GenerateAnnotationContent(MessageTextAnnotation annotation)
{
- string? fileId = null;
-
if (annotation is MessageTextFileCitationAnnotation fileCitationAnnotation)
{
- fileId = fileCitationAnnotation.FileId;
+ return
+ new AnnotationContent(
+ kind: AnnotationKind.FileCitation,
+ label: annotation.Text,
+ referenceId: fileCitationAnnotation.FileId)
+ {
+ InnerContent = annotation,
+ StartIndex = fileCitationAnnotation.StartIndex,
+ EndIndex = fileCitationAnnotation.EndIndex,
+ };
+ }
+ if (annotation is MessageTextUrlCitationAnnotation urlCitationAnnotation)
+ {
+ return
+ new AnnotationContent(
+ kind: AnnotationKind.UrlCitation,
+ label: annotation.Text,
+ referenceId: urlCitationAnnotation.UrlCitation.Url)
+ {
+ InnerContent = annotation,
+ Title = urlCitationAnnotation.UrlCitation.Title,
+ StartIndex = urlCitationAnnotation.StartIndex,
+ EndIndex = urlCitationAnnotation.EndIndex,
+ };
}
else if (annotation is MessageTextFilePathAnnotation filePathAnnotation)
{
- fileId = filePathAnnotation.FileId;
+ return
+ new AnnotationContent(
+ label: annotation.Text,
+ kind: AnnotationKind.TextCitation,
+ referenceId: filePathAnnotation.FileId)
+ {
+ InnerContent = annotation,
+ StartIndex = filePathAnnotation.StartIndex,
+ EndIndex = filePathAnnotation.EndIndex,
+ };
}
- return
- new(annotation.Text)
- {
- Quote = annotation.Text,
- FileId = fileId,
- };
+ return null;
}
- private static StreamingAnnotationContent GenerateStreamingAnnotationContent(TextAnnotationUpdate annotation)
+ private static StreamingAnnotationContent? GenerateStreamingAnnotationContent(TextAnnotationUpdate annotation)
{
- string? fileId = null;
+ string? referenceId = null;
+ AnnotationKind kind;
if (!string.IsNullOrEmpty(annotation.OutputFileId))
{
- fileId = annotation.OutputFileId;
+ referenceId = annotation.OutputFileId;
+ kind = AnnotationKind.TextCitation;
}
else if (!string.IsNullOrEmpty(annotation.InputFileId))
{
- fileId = annotation.InputFileId;
+ referenceId = annotation.InputFileId;
+ kind = AnnotationKind.FileCitation;
+ }
+ else if (!string.IsNullOrEmpty(annotation.Url))
+ {
+ referenceId = annotation.Url;
+ kind = AnnotationKind.UrlCitation;
+ }
+ else
+ {
+ return null;
}
return
- new(annotation.TextToReplace)
+ new StreamingAnnotationContent(kind, referenceId)
{
- StartIndex = annotation.StartIndex ?? 0,
- EndIndex = annotation.EndIndex ?? 0,
- FileId = fileId,
+ Label = annotation.TextToReplace,
+ InnerContent = annotation,
+ Title = annotation.Title,
+ StartIndex = annotation.StartIndex,
+ EndIndex = annotation.EndIndex,
};
}
diff --git a/dotnet/src/Agents/AzureAI/Logging/AgentThreadActionsLogMessages.cs b/dotnet/src/Agents/AzureAI/Logging/AgentThreadActionsLogMessages.cs
index 974af70205eb..27b38bbb7456 100644
--- a/dotnet/src/Agents/AzureAI/Logging/AgentThreadActionsLogMessages.cs
+++ b/dotnet/src/Agents/AzureAI/Logging/AgentThreadActionsLogMessages.cs
@@ -1,4 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.Diagnostics.CodeAnalysis;
using Azure.AI.Projects;
using Microsoft.Extensions.Logging;
@@ -136,4 +137,18 @@ public static partial void LogAzureAIAgentPolledRunStatus(
RunStatus runStatus,
string runId,
string threadId);
+
+ ///
+ /// Logs polled run status (complete).
+ ///
+ [LoggerMessage(
+ EventId = 0,
+ Level = LogLevel.Warning,
+ Message = "[{MethodName}] Unknown annotation '{Type}': {RunId}/{ThreadId}.")]
+ public static partial void LogAzureAIAgentUnknownAnnotation(
+ this ILogger logger,
+ string methodName,
+ string runId,
+ string threadId,
+ Type type);
}
diff --git a/dotnet/src/Agents/Core/ChatHistoryAgentThread.cs b/dotnet/src/Agents/Core/ChatHistoryAgentThread.cs
index e78dd578af76..5966c68748ae 100644
--- a/dotnet/src/Agents/Core/ChatHistoryAgentThread.cs
+++ b/dotnet/src/Agents/Core/ChatHistoryAgentThread.cs
@@ -11,7 +11,7 @@
namespace Microsoft.SemanticKernel.Agents;
///
-/// Represents a conversation thread based on an instance of that is maanged inside this class.
+/// Represents a conversation thread based on an instance of that is managed inside this class.
///
public sealed class ChatHistoryAgentThread : AgentThread
{
diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs
index e1cb991b643e..f0a844f274dd 100644
--- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs
+++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs
@@ -401,7 +401,7 @@ public static async IAsyncEnumerable InvokeStreamin
switch (contentUpdate.UpdateKind)
{
case StreamingUpdateReason.MessageUpdated:
- yield return GenerateStreamingMessageContent(agent.GetName(), contentUpdate);
+ yield return GenerateStreamingMessageContent(agent.GetName(), run!, contentUpdate, logger);
break;
}
}
@@ -540,7 +540,7 @@ await RetrieveMessageAsync(
logger.LogOpenAIAssistantCompletedRun(nameof(InvokeAsync), run?.Id ?? "Failed", threadId);
}
- private static ChatMessageContent GenerateMessageContent(string? assistantName, ThreadMessage message, RunStep? completedStep = null)
+ private static ChatMessageContent GenerateMessageContent(string? assistantName, ThreadMessage message, RunStep? completedStep = null, ILogger? logger = null)
{
AuthorRole role = new(message.Role.ToString());
@@ -564,6 +564,7 @@ private static ChatMessageContent GenerateMessageContent(string? assistantName,
new(role, content: null)
{
AuthorName = assistantName,
+ InnerContent = message,
Metadata = metadata,
};
@@ -576,7 +577,15 @@ private static ChatMessageContent GenerateMessageContent(string? assistantName,
foreach (TextAnnotation annotation in itemContent.TextAnnotations)
{
- content.Items.Add(GenerateAnnotationContent(annotation));
+ AnnotationContent? annotationItem = GenerateAnnotationContent(annotation);
+ if (annotationItem is not null)
+ {
+ content.Items.Add(annotationItem);
+ }
+ else
+ {
+ logger?.LogOpenAIAssistantUnknownAnnotation(nameof(GenerateMessageContent), message.RunId, message.ThreadId, annotation.GetType());
+ }
}
}
// Process image content
@@ -590,12 +599,13 @@ private static ChatMessageContent GenerateMessageContent(string? assistantName,
}
[ExcludeFromCodeCoverage]
- private static StreamingChatMessageContent GenerateStreamingMessageContent(string? assistantName, MessageContentUpdate update)
+ private static StreamingChatMessageContent GenerateStreamingMessageContent(string? assistantName, ThreadRun run, MessageContentUpdate update, ILogger? logger)
{
StreamingChatMessageContent content =
new(AuthorRole.Assistant, content: null)
{
AuthorName = assistantName,
+ InnerContent = update,
};
// Process text content
@@ -611,7 +621,15 @@ private static StreamingChatMessageContent GenerateStreamingMessageContent(strin
// Process annotations
else if (update.TextAnnotation != null)
{
- content.Items.Add(GenerateStreamingAnnotationContent(update.TextAnnotation));
+ StreamingAnnotationContent? annotationItem = GenerateStreamingAnnotationContent(update.TextAnnotation);
+ if (annotationItem is not null)
+ {
+ content.Items.Add(annotationItem);
+ }
+ else
+ {
+ logger?.LogOpenAIAssistantUnknownAnnotation(nameof(GenerateMessageContent), run.Id, run.ThreadId, update.TextAnnotation.GetType());
+ }
}
if (update.Role.HasValue && update.Role.Value != MessageRole.User)
@@ -652,49 +670,63 @@ private static StreamingChatMessageContent GenerateStreamingMessageContent(strin
return content.Items.Count > 0 ? content : null;
}
- private static AnnotationContent GenerateAnnotationContent(TextAnnotation annotation)
+ private static AnnotationContent? GenerateAnnotationContent(TextAnnotation annotation)
{
- string? fileId = null;
+ string referenceId;
+ AnnotationKind kind;
if (!string.IsNullOrEmpty(annotation.OutputFileId))
{
- fileId = annotation.OutputFileId;
+ referenceId = annotation.OutputFileId;
+ kind = AnnotationKind.TextCitation;
}
else if (!string.IsNullOrEmpty(annotation.InputFileId))
{
- fileId = annotation.InputFileId;
+ referenceId = annotation.InputFileId;
+ kind = AnnotationKind.FileCitation;
+ }
+ else
+ {
+ return null;
}
return
- new(annotation.TextToReplace)
+ new(kind, label: annotation.TextToReplace, referenceId)
{
- Quote = annotation.TextToReplace,
+ InnerContent = annotation,
StartIndex = annotation.StartIndex,
EndIndex = annotation.EndIndex,
- FileId = fileId,
};
}
[ExcludeFromCodeCoverage]
- private static StreamingAnnotationContent GenerateStreamingAnnotationContent(TextAnnotationUpdate annotation)
+ private static StreamingAnnotationContent? GenerateStreamingAnnotationContent(TextAnnotationUpdate annotation)
{
- string? fileId = null;
+ string referenceId;
+ AnnotationKind kind;
if (!string.IsNullOrEmpty(annotation.OutputFileId))
{
- fileId = annotation.OutputFileId;
+ referenceId = annotation.OutputFileId;
+ kind = AnnotationKind.TextCitation;
}
else if (!string.IsNullOrEmpty(annotation.InputFileId))
{
- fileId = annotation.InputFileId;
+ referenceId = annotation.InputFileId;
+ kind = AnnotationKind.FileCitation;
+ }
+ else
+ {
+ return null;
}
return
- new(annotation.TextToReplace)
+ new(kind, referenceId)
{
- StartIndex = annotation.StartIndex ?? 0,
- EndIndex = annotation.EndIndex ?? 0,
- FileId = fileId,
+ Label = annotation.TextToReplace,
+ InnerContent = annotation,
+ StartIndex = annotation.StartIndex,
+ EndIndex = annotation.EndIndex,
};
}
diff --git a/dotnet/src/Agents/OpenAI/Logging/AssistantThreadActionsLogMessages.cs b/dotnet/src/Agents/OpenAI/Logging/AssistantThreadActionsLogMessages.cs
index 3a39c314c5c3..e4f6ada3c4bd 100644
--- a/dotnet/src/Agents/OpenAI/Logging/AssistantThreadActionsLogMessages.cs
+++ b/dotnet/src/Agents/OpenAI/Logging/AssistantThreadActionsLogMessages.cs
@@ -1,4 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.Agents.OpenAI.Internal;
@@ -136,4 +137,18 @@ public static partial void LogOpenAIAssistantPolledRunStatus(
RunStatus runStatus,
string runId,
string threadId);
+
+ ///
+ /// Logs polled run status (complete).
+ ///
+ [LoggerMessage(
+ EventId = 0,
+ Level = LogLevel.Warning,
+ Message = "[{MethodName}] Unknown annotation '{Type}': {RunId}/{ThreadId}.")]
+ public static partial void LogOpenAIAssistantUnknownAnnotation(
+ this ILogger logger,
+ string methodName,
+ string runId,
+ string threadId,
+ Type type);
}
diff --git a/dotnet/src/Agents/Orchestration/AgentActor.cs b/dotnet/src/Agents/Orchestration/AgentActor.cs
new file mode 100644
index 000000000000..a67be5f056d1
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/AgentActor.cs
@@ -0,0 +1,152 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Agents.Runtime;
+using Microsoft.SemanticKernel.ChatCompletion;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration;
+
+///
+/// An actor that represents an .
+///
+public abstract class AgentActor : OrchestrationActor
+{
+ private AgentInvokeOptions? _options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The unique identifier of the agent.
+ /// The runtime associated with the agent.
+ /// The orchestration context.
+ /// An .
+ /// The logger to use for the actor
+ protected AgentActor(AgentId id, IAgentRuntime runtime, OrchestrationContext context, Agent agent, ILogger? logger = null)
+ : base(
+ id,
+ runtime,
+ context,
+ VerifyDescription(agent),
+ logger)
+ {
+ this.Agent = agent;
+ }
+
+ ///
+ /// Gets the associated agent.
+ ///
+ protected Agent Agent { get; }
+
+ ///
+ /// Gets or sets the current conversation thread used during agent communication.
+ ///
+ protected AgentThread? Thread { get; set; }
+
+ ///
+ /// Optionally overridden to create custom invocation options for the agent.
+ ///
+ protected virtual AgentInvokeOptions? CreateInvokeOptions()
+ {
+ return null;
+ }
+
+ ///
+ /// Optionally overridden to introduce customer filtering logic for the response callback.
+ ///
+ /// The agent response
+ /// true if the response should be filtered (hidden)
+ protected virtual bool ResponseCallbackFilter(ChatMessageContent response) => false;
+
+ ///
+ /// Deletes the agent thread.
+ ///
+ /// A cancellation token that can be used to cancel the operation.
+ protected async ValueTask DeleteThreadAsync(CancellationToken cancellationToken)
+ {
+ if (this.Thread != null)
+ {
+ await this.Thread.DeleteAsync(cancellationToken).ConfigureAwait(false);
+ this.Thread = null;
+ }
+ }
+
+ ///
+ /// Invokes the agent with a single chat message.
+ /// This method sets the message role to and delegates to the overload accepting multiple messages.
+ ///
+ /// The chat message content to send.
+ /// A cancellation token that can be used to cancel the operation.
+ /// A task that returns the response .
+ protected ValueTask InvokeAsync(ChatMessageContent input, CancellationToken cancellationToken)
+ {
+ return this.InvokeAsync([input], cancellationToken);
+ }
+
+ ///
+ /// Invokes the agent with multiple chat messages.
+ /// Processes the response items and consolidates the messages into a single .
+ ///
+ /// The list of chat messages to send.
+ /// A cancellation token that can be used to cancel the operation.
+ /// A task that returns the response .
+ protected async ValueTask InvokeAsync(IList input, CancellationToken cancellationToken)
+ {
+ this.Context.Cancellation.ThrowIfCancellationRequested();
+
+ AgentResponseItem[] responses =
+ await this.Agent.InvokeAsync(
+ input,
+ this.Thread,
+ this.GetInvokeOptions(),
+ cancellationToken).ToArrayAsync(cancellationToken).ConfigureAwait(false);
+
+ AgentResponseItem? firstResponse = responses.FirstOrDefault();
+ this.Thread ??= firstResponse?.Thread;
+
+ // The vast majority of responses will be a single message. Responses with multiple messages will have their content merged.
+ ChatMessageContent response = new(firstResponse?.Message.Role ?? AuthorRole.Assistant, string.Join("\n\n", responses.Select(response => response.Message)))
+ {
+ AuthorName = firstResponse?.Message.AuthorName,
+ };
+
+ if (this.Context.ResponseCallback is not null && !this.ResponseCallbackFilter(response))
+ {
+ await this.Context.ResponseCallback.Invoke(response).ConfigureAwait(false);
+ }
+
+ return response;
+ }
+
+ ///
+ /// Invokes the agent and streams chat message responses asynchronously.
+ /// Yields each streaming message as it becomes available.
+ ///
+ /// The chat message content to send.
+ /// A cancellation token that can be used to cancel the stream.
+ /// An asynchronous stream of responses.
+ protected async IAsyncEnumerable InvokeStreamingAsync(ChatMessageContent input, [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ this.Context.Cancellation.ThrowIfCancellationRequested();
+
+ var responseStream = this.Agent.InvokeStreamingAsync([input], this.Thread, this.GetInvokeOptions(), cancellationToken);
+
+ await foreach (AgentResponseItem response in responseStream.ConfigureAwait(false))
+ {
+ this.Thread ??= response.Thread;
+ yield return response.Message;
+ }
+ }
+
+ private AgentInvokeOptions? GetInvokeOptions() => this._options ??= this.CreateInvokeOptions();
+
+ private static string VerifyDescription(Agent agent)
+ {
+ return agent.Description ?? throw new ArgumentException($"Missing agent description: {agent.Name ?? agent.Id}", nameof(agent));
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/AgentOrchestration.RequestActor.cs b/dotnet/src/Agents/Orchestration/AgentOrchestration.RequestActor.cs
new file mode 100644
index 000000000000..d1a65b1b3b0b
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/AgentOrchestration.RequestActor.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Agents.Orchestration.Transforms;
+using Microsoft.SemanticKernel.Agents.Runtime;
+using Microsoft.SemanticKernel.Agents.Runtime.Core;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration;
+
+public abstract partial class AgentOrchestration
+{
+ ///
+ /// Actor responsible for receiving final message and transforming it into the output type.
+ ///
+ private sealed class RequestActor : OrchestrationActor, IHandle
+ {
+ private readonly OrchestrationInputTransform _transform;
+ private readonly Func, ValueTask> _action;
+ private readonly TaskCompletionSource _completionSource;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The unique identifier of the agent.
+ /// The runtime associated with the agent.
+ /// The orchestration context.
+ /// A function that transforms an input of type TInput into a source type TSource.
+ /// Optional TaskCompletionSource to signal orchestration completion.
+ /// An asynchronous function that processes the resulting source.
+ /// The logger to use for the actor
+ public RequestActor(
+ AgentId id,
+ IAgentRuntime runtime,
+ OrchestrationContext context,
+ OrchestrationInputTransform transform,
+ TaskCompletionSource completionSource,
+ Func, ValueTask> action,
+ ILogger? logger = null)
+ : base(id, runtime, context, $"{id.Type}_Actor", logger)
+ {
+ this._transform = transform;
+ this._action = action;
+ this._completionSource = completionSource;
+ }
+
+ ///
+ /// Handles the incoming message by transforming the input and executing the corresponding action asynchronously.
+ ///
+ /// The input message of type TInput.
+ /// The context of the message, providing additional details.
+ /// A ValueTask representing the asynchronous operation.
+ public async ValueTask HandleAsync(TInput item, MessageContext messageContext)
+ {
+ this.Logger.LogOrchestrationRequestInvoke(this.Context.Orchestration, this.Id);
+ try
+ {
+ IEnumerable input = await this._transform.Invoke(item).ConfigureAwait(false);
+ Task task = this._action.Invoke(input).AsTask();
+ this.Logger.LogOrchestrationStart(this.Context.Orchestration, this.Id);
+ await task.ConfigureAwait(false);
+ }
+ catch (Exception exception) when (!exception.IsCriticalException())
+ {
+ // Log exception details and allow orchestration to fail
+ this.Logger.LogOrchestrationRequestFailure(this.Context.Orchestration, this.Id, exception);
+ this._completionSource.SetException(exception);
+ throw;
+ }
+ }
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/AgentOrchestration.ResultActor.cs b/dotnet/src/Agents/Orchestration/AgentOrchestration.ResultActor.cs
new file mode 100644
index 000000000000..2d7e8bbf37a8
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/AgentOrchestration.ResultActor.cs
@@ -0,0 +1,79 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Agents.Orchestration.Transforms;
+using Microsoft.SemanticKernel.Agents.Runtime;
+using Microsoft.SemanticKernel.Agents.Runtime.Core;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration;
+
+public abstract partial class AgentOrchestration
+{
+ ///
+ /// Actor responsible for receiving the resultant message, transforming it, and handling further orchestration.
+ ///
+ private sealed class ResultActor : OrchestrationActor, IHandle
+ {
+ private readonly TaskCompletionSource _completionSource;
+ private readonly OrchestrationResultTransform _transformResult;
+ private readonly OrchestrationOutputTransform _transform;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The unique identifier of the agent.
+ /// The runtime associated with the agent.
+ /// The orchestration context.
+ /// A delegate that transforms a TResult instance into a ChatMessageContent.
+ /// A delegate that transforms a ChatMessageContent into a TOutput instance.
+ /// Optional TaskCompletionSource to signal orchestration completion.
+ /// The logger to use for the actor
+ public ResultActor(
+ AgentId id,
+ IAgentRuntime runtime,
+ OrchestrationContext context,
+ OrchestrationResultTransform transformResult,
+ OrchestrationOutputTransform transformOutput,
+ TaskCompletionSource completionSource,
+ ILogger>? logger = null)
+ : base(id, runtime, context, $"{id.Type}_Actor", logger)
+ {
+ this._completionSource = completionSource;
+ this._transformResult = transformResult;
+ this._transform = transformOutput;
+ }
+
+ ///
+ /// Processes the received TResult message by transforming it into a TOutput message.
+ /// If a CompletionTarget is defined, it sends the transformed message to the corresponding agent.
+ /// Additionally, it signals completion via the provided TaskCompletionSource if available.
+ ///
+ /// The result item to process.
+ /// The context associated with the message.
+ /// A ValueTask representing asynchronous operation.
+ public async ValueTask HandleAsync(TResult item, MessageContext messageContext)
+ {
+ this.Logger.LogOrchestrationResultInvoke(this.Context.Orchestration, this.Id);
+
+ try
+ {
+ if (!this._completionSource.Task.IsCompleted)
+ {
+ IList result = this._transformResult.Invoke(item);
+ TOutput output = await this._transform.Invoke(result).ConfigureAwait(false);
+ this._completionSource.TrySetResult(output);
+ }
+ }
+ catch (Exception exception)
+ {
+ // Log exception details and fail orchestration as per design.
+ this.Logger.LogOrchestrationResultFailure(this.Context.Orchestration, this.Id, exception);
+ this._completionSource.SetException(exception);
+ throw;
+ }
+ }
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/AgentOrchestration.cs b/dotnet/src/Agents/Orchestration/AgentOrchestration.cs
new file mode 100644
index 000000000000..994b611a59ac
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/AgentOrchestration.cs
@@ -0,0 +1,234 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.SemanticKernel.Agents.Orchestration.Extensions;
+using Microsoft.SemanticKernel.Agents.Orchestration.Transforms;
+using Microsoft.SemanticKernel.Agents.Runtime;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration;
+
+///
+/// Called for every response is produced by any agent.
+///
+/// The agent response
+public delegate ValueTask OrchestrationResponseCallback(ChatMessageContent response);
+
+///
+/// Called when human interaction is requested.
+///
+public delegate ValueTask OrchestrationInteractiveCallback();
+
+///
+/// Base class for multi-agent agent orchestration patterns.
+///
+/// The type of the input to the orchestration.
+/// The type of the result output by the orchestration.
+public abstract partial class AgentOrchestration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Specifies the member agents or orchestrations participating in this orchestration.
+ protected AgentOrchestration(params Agent[] members)
+ {
+ // Capture orchestration root name without generic parameters for use in
+ // agent type and topic formatting as well as logging.
+ this.OrchestrationLabel = this.GetType().Name.Split('`').First();
+
+ this.Members = members;
+ }
+
+ ///
+ /// Gets the description of the orchestration.
+ ///
+ public string Description { get; init; } = string.Empty;
+
+ ///
+ /// Gets the name of the orchestration.
+ ///
+ public string Name { get; init; } = string.Empty;
+
+ ///
+ /// Gets the associated logger.
+ ///
+ public ILoggerFactory LoggerFactory { get; init; } = NullLoggerFactory.Instance;
+
+ ///
+ /// Transforms the orchestration input into a source input suitable for processing.
+ ///
+ public OrchestrationInputTransform InputTransform { get; init; } = DefaultTransforms.FromInput;
+
+ ///
+ /// Transforms the processed result into the final output form.
+ ///
+ public OrchestrationOutputTransform ResultTransform { get; init; } = DefaultTransforms.ToOutput;
+
+ ///
+ /// Optional callback that is invoked for every agent response.
+ ///
+ public OrchestrationResponseCallback? ResponseCallback { get; init; }
+
+ ///
+ /// Gets the list of member targets involved in the orchestration.
+ ///
+ protected IReadOnlyList Members { get; }
+
+ ///
+ /// Orchestration identifier without generic parameters for use in
+ /// agent type and topic formatting as well as logging.
+ ///
+ protected string OrchestrationLabel { get; }
+
+ ///
+ /// Initiates processing of the orchestration.
+ ///
+ /// The input message.
+ /// The runtime associated with the orchestration.
+ /// A cancellation token that can be used to cancel the operation.
+ public async ValueTask> InvokeAsync(
+ TInput input,
+ IAgentRuntime runtime,
+ CancellationToken cancellationToken = default)
+ {
+ Verify.NotNull(input, nameof(input));
+
+ TopicId topic = new($"{this.OrchestrationLabel}_{Guid.NewGuid().ToString().Replace("-", string.Empty)}");
+
+ CancellationTokenSource orchestrationCancelSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+
+ OrchestrationContext context = new(this.OrchestrationLabel, topic, this.ResponseCallback, this.LoggerFactory, cancellationToken);
+
+ ILogger logger = this.LoggerFactory.CreateLogger(this.GetType());
+
+ TaskCompletionSource completion = new();
+
+ AgentType orchestrationType = await this.RegisterAsync(runtime, context, completion, handoff: null).ConfigureAwait(false);
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ logger.LogOrchestrationInvoke(this.OrchestrationLabel, topic);
+
+ Task task = runtime.SendMessageAsync(input, orchestrationType, cancellationToken).AsTask();
+
+ logger.LogOrchestrationYield(this.OrchestrationLabel, topic);
+
+ return new OrchestrationResult(context, completion, orchestrationCancelSource, logger);
+ }
+
+ ///
+ /// Initiates processing according to the orchestration pattern.
+ ///
+ /// The runtime associated with the orchestration.
+ /// The unique identifier for the orchestration session.
+ /// The input to be transformed and processed.
+ /// The initial agent type used for starting the orchestration.
+ protected abstract ValueTask StartAsync(IAgentRuntime runtime, TopicId topic, IEnumerable input, AgentType? entryAgent);
+
+ ///
+ /// Orchestration specific registration, including members and returns an optional entry agent.
+ ///
+ /// The runtime targeted for registration.
+ /// The orchestration context.
+ /// A registration context.
+ /// The logger to use during registration
+ /// The entry AgentType for the orchestration, if any.
+ protected abstract ValueTask RegisterOrchestrationAsync(IAgentRuntime runtime, OrchestrationContext context, RegistrationContext registrar, ILogger logger);
+
+ ///
+ /// Formats and returns a unique AgentType based on the provided topic and suffix.
+ ///
+ /// The topic identifier used in formatting the agent type.
+ /// A suffix to differentiate the agent type.
+ /// A formatted AgentType object.
+ protected AgentType FormatAgentType(TopicId topic, string suffix) => new($"{topic.Type}_{suffix}");
+
+ ///
+ /// Registers the orchestration's root and boot agents, setting up completion and target routing.
+ ///
+ /// The runtime targeted for registration.
+ /// The orchestration context.
+ /// A TaskCompletionSource for the orchestration.
+ /// The actor type used for handoff. Only defined for nested orchestrations.
+ /// The AgentType representing the orchestration entry point.
+ private async ValueTask RegisterAsync(IAgentRuntime runtime, OrchestrationContext context, TaskCompletionSource completion, AgentType? handoff)
+ {
+ // Create a logger for the orchestration registration.
+ ILogger logger = context.LoggerFactory.CreateLogger(this.GetType());
+ logger.LogOrchestrationRegistrationStart(context.Orchestration, context.Topic);
+
+ // Register orchestration
+ RegistrationContext registrar = new(this.FormatAgentType(context.Topic, "Root"), runtime, context, completion, this.ResultTransform);
+ AgentType? entryAgent = await this.RegisterOrchestrationAsync(runtime, context, registrar, logger).ConfigureAwait(false);
+
+ // Register actor for orchestration entry-point
+ AgentType orchestrationEntry =
+ await runtime.RegisterAgentFactoryAsync(
+ this.FormatAgentType(context.Topic, "Boot"),
+ (agentId, runtime) =>
+ {
+ RequestActor actor =
+ new(agentId,
+ runtime,
+ context,
+ this.InputTransform,
+ completion,
+ StartAsync,
+ context.LoggerFactory.CreateLogger());
+#if !NETCOREAPP
+ return actor.AsValueTask();
+#else
+ return ValueTask.FromResult(actor);
+#endif
+ }).ConfigureAwait(false);
+
+ logger.LogOrchestrationRegistrationDone(context.Orchestration, context.Topic);
+
+ return orchestrationEntry;
+
+ ValueTask StartAsync(IEnumerable input) => this.StartAsync(runtime, context.Topic, input, entryAgent);
+ }
+
+ ///
+ /// A context used during registration ().
+ ///
+ public sealed class RegistrationContext(
+ AgentType agentType,
+ IAgentRuntime runtime,
+ OrchestrationContext context,
+ TaskCompletionSource completion,
+ OrchestrationOutputTransform outputTransform)
+ {
+ ///
+ /// Register the final result type.
+ ///
+ public async ValueTask RegisterResultTypeAsync(OrchestrationResultTransform resultTransform)
+ {
+ // Register actor for final result
+ return
+ await runtime.RegisterAgentFactoryAsync(
+ agentType,
+ (agentId, runtime) =>
+ {
+ ResultActor actor =
+ new(agentId,
+ runtime,
+ context,
+ resultTransform,
+ outputTransform,
+ completion,
+ context.LoggerFactory.CreateLogger>());
+#if !NETCOREAPP
+ return actor.AsValueTask();
+#else
+ return ValueTask.FromResult(actor);
+#endif
+ }).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/Agents.Orchestration.csproj b/dotnet/src/Agents/Orchestration/Agents.Orchestration.csproj
new file mode 100644
index 000000000000..d748b3d8457a
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/Agents.Orchestration.csproj
@@ -0,0 +1,39 @@
+
+
+
+
+ Microsoft.SemanticKernel.Agents.Orchestration
+ Microsoft.SemanticKernel.Agents.Orchestration
+ net8.0;netstandard2.0
+ $(NoWarn);SKEXP0110;SKEXP0001
+ false
+ preview
+
+
+
+
+
+
+ Semantic Kernel Agents - Orchestration
+ Defines Agent orchestration patterns.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentActor.cs b/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentActor.cs
new file mode 100644
index 000000000000..fc57ecf236e4
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentActor.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Agents.Runtime;
+using Microsoft.SemanticKernel.Agents.Runtime.Core;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
+
+///
+/// An used with the .
+///
+internal sealed class ConcurrentActor : AgentActor, IHandle
+{
+ private readonly AgentType _handoffActor;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The unique identifier of the agent.
+ /// The runtime associated with the agent.
+ /// The orchestration context.
+ /// An .
+ /// Identifies the actor collecting results.
+ /// The logger to use for the actor
+ public ConcurrentActor(AgentId id, IAgentRuntime runtime, OrchestrationContext context, Agent agent, AgentType resultActor, ILogger? logger = null)
+ : base(id, runtime, context, agent, logger)
+ {
+ this._handoffActor = resultActor;
+ }
+
+ ///
+ public async ValueTask HandleAsync(ConcurrentMessages.Request item, MessageContext messageContext)
+ {
+ this.Logger.LogConcurrentAgentInvoke(this.Id);
+
+ ChatMessageContent response = await this.InvokeAsync(item.Messages, messageContext.CancellationToken).ConfigureAwait(false);
+
+ this.Logger.LogConcurrentAgentResult(this.Id, response.Content);
+
+ await this.SendMessageAsync(response.AsResultMessage(), this._handoffActor, messageContext.CancellationToken).ConfigureAwait(false);
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentMessages.cs b/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentMessages.cs
new file mode 100644
index 000000000000..29088f053ce6
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentMessages.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using Microsoft.SemanticKernel.ChatCompletion;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
+
+///
+/// Common messages used by the .
+///
+internal static class ConcurrentMessages
+{
+ ///
+ /// An empty message instance as a default.
+ ///
+ public static readonly ChatMessageContent Empty = new();
+
+ ///
+ /// The input task for a .
+ ///
+ public sealed class Request
+ {
+ ///
+ /// The request input.
+ ///
+ public IList Messages { get; init; } = [];
+ }
+
+ ///
+ /// A result from a .
+ ///
+ public sealed class Result
+ {
+ ///
+ /// The result message.
+ ///
+ public ChatMessageContent Message { get; init; } = Empty;
+ }
+
+ ///
+ /// Extension method to convert a to a .
+ ///
+ public static Result AsResultMessage(this string text, AuthorRole? role = null) => new() { Message = new ChatMessageContent(role ?? AuthorRole.Assistant, text) };
+
+ ///
+ /// Extension method to convert a to a .
+ ///
+ public static Result AsResultMessage(this ChatMessageContent message) => new() { Message = message };
+
+ ///
+ /// Extension method to convert a collection of to a .
+ ///
+ public static Request AsInputMessage(this IEnumerable messages) => new() { Messages = [.. messages] };
+}
diff --git a/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentOrchestration.String.cs b/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentOrchestration.String.cs
new file mode 100644
index 000000000000..bfd3214fc105
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentOrchestration.String.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Linq;
+
+#if NETCOREAPP
+using System.Threading.Tasks;
+#endif
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
+
+///
+/// An orchestration that broadcasts the input message to each agent.
+///
+public sealed class ConcurrentOrchestration : ConcurrentOrchestration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The agents to be orchestrated.
+ public ConcurrentOrchestration(params Agent[] members)
+ : base(members)
+ {
+ this.ResultTransform =
+ (response, cancellationToken) =>
+ {
+ string[] result = [.. response.Select(r => r.Content ?? string.Empty)];
+#if !NETCOREAPP
+ return result.AsValueTask();
+#else
+ return ValueTask.FromResult(result);
+#endif
+ };
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentOrchestration.cs b/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentOrchestration.cs
new file mode 100644
index 000000000000..fead042e1a14
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentOrchestration.cs
@@ -0,0 +1,82 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Agents.Orchestration.Extensions;
+using Microsoft.SemanticKernel.Agents.Runtime;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
+
+///
+/// An orchestration that broadcasts the input message to each agent.
+///
+///
+/// TOutput must be an array type for .
+///
+public class ConcurrentOrchestration
+ : AgentOrchestration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The agents participating in the orchestration.
+ public ConcurrentOrchestration(params Agent[] agents)
+ : base(agents)
+ {
+ }
+
+ ///
+ protected override ValueTask StartAsync(IAgentRuntime runtime, TopicId topic, IEnumerable input, AgentType? entryAgent)
+ {
+ return runtime.PublishMessageAsync(input.AsInputMessage(), topic);
+ }
+
+ ///
+ protected override async ValueTask RegisterOrchestrationAsync(IAgentRuntime runtime, OrchestrationContext context, RegistrationContext registrar, ILogger logger)
+ {
+ AgentType outputType = await registrar.RegisterResultTypeAsync(response => [.. response.Select(r => r.Message)]).ConfigureAwait(false);
+
+ // Register result actor
+ AgentType resultType = this.FormatAgentType(context.Topic, "Results");
+ await runtime.RegisterAgentFactoryAsync(
+ resultType,
+ (agentId, runtime) =>
+ {
+ ConcurrentResultActor actor = new(agentId, runtime, context, outputType, this.Members.Count, context.LoggerFactory.CreateLogger());
+#if !NETCOREAPP
+ return actor.AsValueTask();
+#else
+ return ValueTask.FromResult(actor);
+#endif
+ }).ConfigureAwait(false);
+ logger.LogRegisterActor(this.OrchestrationLabel, resultType, "RESULTS");
+
+ // Register member actors - All agents respond to the same message.
+ int agentCount = 0;
+ foreach (Agent agent in this.Members)
+ {
+ ++agentCount;
+
+ AgentType agentType =
+ await runtime.RegisterAgentFactoryAsync(
+ this.FormatAgentType(context.Topic, $"Agent_{agentCount}"),
+ (agentId, runtime) =>
+ {
+ ConcurrentActor actor = new(agentId, runtime, context, agent, resultType, context.LoggerFactory.CreateLogger());
+#if !NETCOREAPP
+ return actor.AsValueTask();
+#else
+ return ValueTask.FromResult(actor);
+#endif
+ }).ConfigureAwait(false);
+
+ logger.LogRegisterActor(this.OrchestrationLabel, agentType, "MEMBER", agentCount);
+
+ await runtime.SubscribeAsync(agentType, context.Topic).ConfigureAwait(false);
+ }
+
+ return null;
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentResultActor.cs b/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentResultActor.cs
new file mode 100644
index 000000000000..eb4e1f2994fe
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/Concurrent/ConcurrentResultActor.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Concurrent;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Agents.Runtime;
+using Microsoft.SemanticKernel.Agents.Runtime.Core;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
+
+///
+/// Actor for capturing each message.
+///
+internal sealed class ConcurrentResultActor :
+ OrchestrationActor,
+ IHandle
+{
+ private readonly ConcurrentQueue _results;
+ private readonly AgentType _orchestrationType;
+ private readonly int _expectedCount;
+ private int _resultCount;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The unique identifier of the agent.
+ /// The runtime associated with the agent.
+ /// The orchestration context.
+ /// Identifies the orchestration agent.
+ /// The expected number of messages to be received.
+ /// The logger to use for the actor
+ public ConcurrentResultActor(
+ AgentId id,
+ IAgentRuntime runtime,
+ OrchestrationContext context,
+ AgentType orchestrationType,
+ int expectedCount,
+ ILogger logger)
+ : base(id, runtime, context, "Captures the results of the ConcurrentOrchestration", logger)
+ {
+ this._orchestrationType = orchestrationType;
+ this._expectedCount = expectedCount;
+ this._results = [];
+ }
+
+ ///
+ public async ValueTask HandleAsync(ConcurrentMessages.Result item, MessageContext messageContext)
+ {
+ this.Logger.LogConcurrentResultCapture(this.Id, this._resultCount + 1, this._expectedCount);
+
+ this._results.Enqueue(item);
+
+ if (Interlocked.Increment(ref this._resultCount) == this._expectedCount)
+ {
+ await this.SendMessageAsync(this._results.ToArray(), this._orchestrationType, messageContext.CancellationToken).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/Extensions/RuntimeExtensions.cs b/dotnet/src/Agents/Orchestration/Extensions/RuntimeExtensions.cs
new file mode 100644
index 000000000000..033dd1e1059c
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/Extensions/RuntimeExtensions.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.SemanticKernel.Agents.Runtime;
+using Microsoft.SemanticKernel.Agents.Runtime.Core;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.Extensions;
+
+///
+/// Extension methods for .
+///
+public static class RuntimeExtensions
+{
+ ///
+ /// Sends a message to the specified agent.
+ ///
+ internal static async ValueTask SendMessageAsync(this IAgentRuntime runtime, object message, AgentType agentType, CancellationToken cancellationToken = default)
+ {
+ AgentId? agentId = await runtime.GetAgentAsync(agentType, lazy: false).ConfigureAwait(false);
+ if (agentId.HasValue)
+ {
+ await runtime.SendMessageAsync(message, agentId.Value, sender: null, messageId: null, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Subscribes the specified agent type to the provided topics.
+ ///
+ /// The runtime for managing the subscription.
+ /// The agent type to subscribe.
+ /// A variable list of topics for subscription.
+ public static async Task SubscribeAsync(this IAgentRuntime runtime, string agentType, params TopicId[] topics)
+ {
+ for (int index = 0; index < topics.Length; ++index)
+ {
+ await runtime.AddSubscriptionAsync(new TypeSubscription(topics[index].Type, agentType)).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/GroupChat/GroupChatAgentActor.cs b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatAgentActor.cs
new file mode 100644
index 000000000000..207702d82b4c
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatAgentActor.cs
@@ -0,0 +1,66 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Agents.Runtime;
+using Microsoft.SemanticKernel.Agents.Runtime.Core;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
+
+///
+/// An used with the .
+///
+internal sealed class GroupChatAgentActor :
+ AgentActor,
+ IHandle,
+ IHandle,
+ IHandle
+{
+ private readonly List _cache;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The unique identifier of the agent.
+ /// The runtime associated with the agent.
+ /// The orchestration context.
+ /// An .
+ /// The logger to use for the actor
+ public GroupChatAgentActor(AgentId id, IAgentRuntime runtime, OrchestrationContext context, Agent agent, ILogger? logger = null)
+ : base(id, runtime, context, agent, logger)
+ {
+ this._cache = [];
+ }
+
+ ///
+ public ValueTask HandleAsync(GroupChatMessages.Group item, MessageContext messageContext)
+ {
+ this._cache.AddRange(item.Messages);
+
+#if !NETCOREAPP
+ return Task.CompletedTask.AsValueTask();
+#else
+ return ValueTask.CompletedTask;
+#endif
+ }
+
+ ///
+ public async ValueTask HandleAsync(GroupChatMessages.Reset item, MessageContext messageContext)
+ {
+ await this.DeleteThreadAsync(messageContext.CancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ public async ValueTask HandleAsync(GroupChatMessages.Speak item, MessageContext messageContext)
+ {
+ this.Logger.LogChatAgentInvoke(this.Id);
+
+ ChatMessageContent response = await this.InvokeAsync(this._cache, messageContext.CancellationToken).ConfigureAwait(false);
+
+ this.Logger.LogChatAgentResult(this.Id, response.Content);
+
+ this._cache.Clear();
+ await this.PublishMessageAsync(response.AsGroupMessage(), this.Context.Topic).ConfigureAwait(false);
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/GroupChat/GroupChatManager.cs b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatManager.cs
new file mode 100644
index 000000000000..b65f05f48d61
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatManager.cs
@@ -0,0 +1,105 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.SemanticKernel.ChatCompletion;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
+
+///
+/// Represents the result of a group chat manager operation, including a value and a reason.
+///
+/// The type of the value returned by the operation.
+/// The value returned by the operation.
+public sealed class GroupChatManagerResult(TValue value)
+{
+ ///
+ /// The reason for the result, providing additional context or explanation.
+ ///
+ public string Reason { get; init; } = string.Empty;
+
+ ///
+ /// The value returned by the group chat manager operation.
+ ///
+ public TValue Value { get; } = value;
+}
+
+///
+/// A manager that manages the flow of a group chat.
+///
+public abstract class GroupChatManager
+{
+ private int _invocationCount;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected GroupChatManager() { }
+
+ ///
+ /// Gets the number of times the group chat manager has been invoked.
+ ///
+ public int InvocationCount => this._invocationCount;
+
+ ///
+ /// Gets or sets the maximum number of invocations allowed for the group chat manager.
+ ///
+ public int MaximumInvocationCount { get; init; } = int.MaxValue;
+
+ ///
+ /// Gets or sets the callback to be invoked for interactive input.
+ ///
+ public OrchestrationInteractiveCallback? InteractiveCallback { get; init; }
+
+ ///
+ /// Filters the results of the group chat based on the provided chat history.
+ ///
+ /// The chat history to filter.
+ /// A cancellation token that can be used to cancel the operation.
+ /// A containing the filtered result as a string.
+ public abstract ValueTask> FilterResults(ChatHistory history, CancellationToken cancellationToken = default);
+
+ ///
+ /// Selects the next agent to participate in the group chat based on the provided chat history and team.
+ ///
+ /// The chat history to consider.
+ /// The group of agents participating in the chat.
+ /// A cancellation token that can be used to cancel the operation.
+ /// A containing the identifier of the next agent as a string.
+ public abstract ValueTask> SelectNextAgent(ChatHistory history, GroupChatTeam team, CancellationToken cancellationToken = default);
+
+ ///
+ /// Determines whether user input should be requested based on the provided chat history.
+ ///
+ /// The chat history to consider.
+ /// A cancellation token that can be used to cancel the operation.
+ /// A indicating whether user input should be requested.
+ public abstract ValueTask> ShouldRequestUserInput(ChatHistory history, CancellationToken cancellationToken = default);
+
+ ///
+ /// Determines whether the group chat should be terminated based on the provided chat history and invocation count.
+ ///
+ /// The chat history to consider.
+ /// A cancellation token that can be used to cancel the operation.
+ /// A indicating whether the chat should be terminated.
+ public virtual ValueTask> ShouldTerminate(ChatHistory history, CancellationToken cancellationToken = default)
+ {
+ Interlocked.Increment(ref this._invocationCount);
+
+ bool resultValue = false;
+ string reason = "Maximum number of invocations has not been reached.";
+ if (this.InvocationCount > this.MaximumInvocationCount)
+ {
+ resultValue = true;
+ reason = "Maximum number of invocations reached.";
+ }
+
+ GroupChatManagerResult result = new(resultValue) { Reason = reason };
+
+#if !NETCOREAPP
+ return result.AsValueTask();
+#else
+ return ValueTask.FromResult(result);
+#endif
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/GroupChat/GroupChatManagerActor.cs b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatManagerActor.cs
new file mode 100644
index 000000000000..cff379ded649
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatManagerActor.cs
@@ -0,0 +1,100 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Agents.Runtime;
+using Microsoft.SemanticKernel.Agents.Runtime.Core;
+using Microsoft.SemanticKernel.ChatCompletion;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
+
+///
+/// An used to manage a .
+///
+internal sealed class GroupChatManagerActor :
+ OrchestrationActor,
+ IHandle,
+ IHandle
+{
+ ///
+ /// A common description for the manager.
+ ///
+ public const string DefaultDescription = "Orchestrates a team of agents to accomplish a defined task.";
+
+ private readonly AgentType _orchestrationType;
+ private readonly GroupChatManager _manager;
+ private readonly ChatHistory _chat;
+ private readonly GroupChatTeam _team;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The unique identifier of the agent.
+ /// The runtime associated with the agent.
+ /// The orchestration context.
+ /// The manages the flow of the group-chat.
+ /// The team of agents being orchestrated
+ /// Identifies the orchestration agent.
+ /// The logger to use for the actor
+ public GroupChatManagerActor(AgentId id, IAgentRuntime runtime, OrchestrationContext context, GroupChatManager manager, GroupChatTeam team, AgentType orchestrationType, ILogger? logger = null)
+ : base(id, runtime, context, DefaultDescription, logger)
+ {
+ this._chat = [];
+ this._manager = manager;
+ this._orchestrationType = orchestrationType;
+ this._team = team;
+ }
+
+ ///
+ public async ValueTask HandleAsync(GroupChatMessages.InputTask item, MessageContext messageContext)
+ {
+ this.Logger.LogChatManagerInit(this.Id);
+
+ this._chat.AddRange(item.Messages);
+
+ await this.PublishMessageAsync(item.Messages.AsGroupMessage(), this.Context.Topic).ConfigureAwait(false);
+
+ await this.ManageAsync(messageContext).ConfigureAwait(false);
+ }
+
+ ///
+ public async ValueTask HandleAsync(GroupChatMessages.Group item, MessageContext messageContext)
+ {
+ this.Logger.LogChatManagerInvoke(this.Id);
+
+ this._chat.AddRange(item.Messages);
+
+ await this.ManageAsync(messageContext).ConfigureAwait(false);
+ }
+
+ private async ValueTask ManageAsync(MessageContext messageContext)
+ {
+ if (this._manager.InteractiveCallback != null)
+ {
+ GroupChatManagerResult inputResult = await this._manager.ShouldRequestUserInput(this._chat, messageContext.CancellationToken).ConfigureAwait(false);
+ this.Logger.LogChatManagerInput(this.Id, inputResult.Value, inputResult.Reason);
+ if (inputResult.Value)
+ {
+ ChatMessageContent input = await this._manager.InteractiveCallback.Invoke().ConfigureAwait(false);
+ this.Logger.LogChatManagerUserInput(this.Id, input.Content);
+ this._chat.Add(input);
+ await this.PublishMessageAsync(input.AsGroupMessage(), this.Context.Topic).ConfigureAwait(false);
+ }
+ }
+
+ GroupChatManagerResult terminateResult = await this._manager.ShouldTerminate(this._chat, messageContext.CancellationToken).ConfigureAwait(false);
+ this.Logger.LogChatManagerTerminate(this.Id, terminateResult.Value, terminateResult.Reason);
+ if (terminateResult.Value)
+ {
+ GroupChatManagerResult filterResult = await this._manager.FilterResults(this._chat, messageContext.CancellationToken).ConfigureAwait(false);
+ this.Logger.LogChatManagerResult(this.Id, filterResult.Value, filterResult.Reason);
+ await this.SendMessageAsync(filterResult.Value.AsResultMessage(), this._orchestrationType, messageContext.CancellationToken).ConfigureAwait(false);
+ return;
+ }
+
+ GroupChatManagerResult selectionResult = await this._manager.SelectNextAgent(this._chat, this._team, messageContext.CancellationToken).ConfigureAwait(false);
+ AgentType selectionType = this._team[selectionResult.Value].Type;
+ this.Logger.LogChatManagerSelect(this.Id, selectionType);
+ await this.SendMessageAsync(new GroupChatMessages.Speak(), selectionType, messageContext.CancellationToken).ConfigureAwait(false);
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/GroupChat/GroupChatMessages.cs b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatMessages.cs
new file mode 100644
index 000000000000..aaf084b700c9
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatMessages.cs
@@ -0,0 +1,85 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using Microsoft.SemanticKernel.ChatCompletion;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
+
+///
+/// Common messages used for agent chat patterns.
+///
+public static class GroupChatMessages
+{
+ ///
+ /// An empty message instance as a default.
+ ///
+ internal static readonly ChatMessageContent Empty = new();
+
+ ///
+ /// Broadcast a message to all .
+ ///
+ public sealed class Group
+ {
+ ///
+ /// The chat message being broadcast.
+ ///
+ public IEnumerable Messages { get; init; } = [];
+ }
+
+ ///
+ /// Reset/clear the conversation history for all .
+ ///
+ public sealed class Reset;
+
+ ///
+ /// The final result.
+ ///
+ public sealed class Result
+ {
+ ///
+ /// The chat response message.
+ ///
+ public ChatMessageContent Message { get; init; } = Empty;
+ }
+
+ ///
+ /// Signal a to respond.
+ ///
+ public sealed class Speak;
+
+ ///
+ /// The input task.
+ ///
+ public sealed class InputTask
+ {
+ ///
+ /// A task that does not require any action.
+ ///
+ public static readonly InputTask None = new();
+
+ ///
+ /// The input that defines the task goal.
+ ///
+ public IEnumerable Messages { get; init; } = [];
+ }
+
+ ///
+ /// Extension method to convert a to a .
+ ///
+ public static Group AsGroupMessage(this ChatMessageContent message) => new() { Messages = [message] };
+
+ ///
+ /// Extension method to convert a to a .
+ ///
+ public static Group AsGroupMessage(this IEnumerable messages) => new() { Messages = messages };
+
+ ///
+ /// Extension method to convert a to a .
+ ///
+ public static InputTask AsInputTaskMessage(this IEnumerable messages) => new() { Messages = messages };
+
+ ///
+ /// Extension method to convert a to a .
+ ///
+ public static Result AsResultMessage(this string text) => new() { Message = new(AuthorRole.Assistant, text) };
+}
diff --git a/dotnet/src/Agents/Orchestration/GroupChat/GroupChatOrchestration.String.cs b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatOrchestration.String.cs
new file mode 100644
index 000000000000..ca7dc7c9ff90
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatOrchestration.String.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
+
+///
+/// An orchestration that broadcasts the input message to each agent.
+///
+public sealed class GroupChatOrchestration : GroupChatOrchestration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The manages the flow of the group-chat.
+ /// The agents to be orchestrated.
+ public GroupChatOrchestration(GroupChatManager manager, params Agent[] members)
+ : base(manager, members)
+ {
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/GroupChat/GroupChatOrchestration.cs b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatOrchestration.cs
new file mode 100644
index 000000000000..d2ed007d62d1
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatOrchestration.cs
@@ -0,0 +1,97 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Agents.Orchestration.Extensions;
+using Microsoft.SemanticKernel.Agents.Runtime;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
+
+///
+/// An orchestration that coordinates a group-chat.
+///
+public class GroupChatOrchestration :
+ AgentOrchestration
+{
+ internal const string DefaultAgentDescription = "A helpful agent.";
+
+ private readonly GroupChatManager _manager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The manages the flow of the group-chat.
+ /// The agents participating in the orchestration.
+ public GroupChatOrchestration(GroupChatManager manager, params Agent[] agents)
+ : base(agents)
+ {
+ Verify.NotNull(manager, nameof(manager));
+
+ this._manager = manager;
+ }
+
+ ///
+ protected override ValueTask StartAsync(IAgentRuntime runtime, TopicId topic, IEnumerable input, AgentType? entryAgent)
+ {
+ if (!entryAgent.HasValue)
+ {
+ throw new ArgumentException("Entry agent is not defined.", nameof(entryAgent));
+ }
+ return runtime.SendMessageAsync(input.AsInputTaskMessage(), entryAgent.Value);
+ }
+
+ ///
+ protected override async ValueTask RegisterOrchestrationAsync(IAgentRuntime runtime, OrchestrationContext context, RegistrationContext registrar, ILogger logger)
+ {
+ AgentType outputType = await registrar.RegisterResultTypeAsync(response => [response.Message]).ConfigureAwait(false);
+
+ int agentCount = 0;
+ GroupChatTeam team = [];
+ foreach (Agent agent in this.Members)
+ {
+ ++agentCount;
+ AgentType agentType = await RegisterAgentAsync(agent, agentCount).ConfigureAwait(false);
+ string name = agent.Name ?? agent.Id ?? agentType;
+ string? description = agent.Description;
+
+ team[name] = (agentType, description ?? DefaultAgentDescription);
+
+ logger.LogRegisterActor(this.OrchestrationLabel, agentType, "MEMBER", agentCount);
+
+ await runtime.SubscribeAsync(agentType, context.Topic).ConfigureAwait(false);
+ }
+
+ AgentType managerType =
+ await runtime.RegisterAgentFactoryAsync(
+ this.FormatAgentType(context.Topic, "Manager"),
+ (agentId, runtime) =>
+ {
+ GroupChatManagerActor actor = new(agentId, runtime, context, this._manager, team, outputType, context.LoggerFactory.CreateLogger());
+#if !NETCOREAPP
+ return actor.AsValueTask();
+#else
+ return ValueTask.FromResult(actor);
+#endif
+ }).ConfigureAwait(false);
+ logger.LogRegisterActor(this.OrchestrationLabel, managerType, "MANAGER");
+
+ await runtime.SubscribeAsync(managerType, context.Topic).ConfigureAwait(false);
+
+ return managerType;
+
+ ValueTask RegisterAgentAsync(Agent agent, int agentCount) =>
+ runtime.RegisterAgentFactoryAsync(
+ this.FormatAgentType(context.Topic, $"Agent_{agentCount}"),
+ (agentId, runtime) =>
+ {
+ GroupChatAgentActor actor = new(agentId, runtime, context, agent, context.LoggerFactory.CreateLogger());
+#if !NETCOREAPP
+ return actor.AsValueTask();
+#else
+ return ValueTask.FromResult(actor);
+#endif
+ });
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/GroupChat/GroupChatTeam.cs b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatTeam.cs
new file mode 100644
index 000000000000..1870d68ce489
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/GroupChat/GroupChatTeam.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
+
+///
+/// Describes a team of agents participating in a group chat.
+///
+public class GroupChatTeam : Dictionary;
+
+///
+/// Extensions for .
+///
+public static class ChatGroupExtensions
+{
+ ///
+ /// Format the names of the agents in the team as a comma delimimted list.
+ ///
+ /// The agent team
+ /// A comma delimimted list of agent name.
+ public static string FormatNames(this GroupChatTeam team) => string.Join(",", team.Select(t => t.Key));
+
+ ///
+ /// Format the names and descriptions of the agents in the team as a markdown list.
+ ///
+ /// The agent team
+ /// A markdown list of agent names and descriptions.
+ public static string FormatList(this GroupChatTeam team) => string.Join("\n", team.Select(t => $"- {t.Key}: {t.Value.Description}"));
+}
diff --git a/dotnet/src/Agents/Orchestration/GroupChat/RoundRobinGroupChatManager.cs b/dotnet/src/Agents/Orchestration/GroupChat/RoundRobinGroupChatManager.cs
new file mode 100644
index 000000000000..bfd92a858449
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/GroupChat/RoundRobinGroupChatManager.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.SemanticKernel.ChatCompletion;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.GroupChat;
+
+///
+/// A that selects agents in a round-robin fashion.
+///
+///
+/// Subclass this class to customize filter and user interaction behavior.
+///
+public class RoundRobinGroupChatManager : GroupChatManager
+{
+ private int _currentAgentIndex;
+
+ ///
+ public override ValueTask> FilterResults(ChatHistory history, CancellationToken cancellationToken = default)
+ {
+ GroupChatManagerResult result = new(history.LastOrDefault()?.Content ?? string.Empty) { Reason = "Default result filter provides the final chat message." };
+#if !NETCOREAPP
+ return result.AsValueTask();
+#else
+ return ValueTask.FromResult(result);
+#endif
+ }
+
+ ///
+ public override ValueTask> SelectNextAgent(ChatHistory history, GroupChatTeam team, CancellationToken cancellationToken = default)
+ {
+ string nextAgent = team.Skip(this._currentAgentIndex).First().Key;
+ this._currentAgentIndex = (this._currentAgentIndex + 1) % team.Count;
+ GroupChatManagerResult result = new(nextAgent) { Reason = $"Selected agent at index: {this._currentAgentIndex}" };
+#if !NETCOREAPP
+ return result.AsValueTask();
+#else
+ return ValueTask.FromResult(result);
+#endif
+ }
+
+ ///
+ public override ValueTask> ShouldRequestUserInput(ChatHistory history, CancellationToken cancellationToken = default)
+ {
+ GroupChatManagerResult result = new(false) { Reason = "The default round-robin group chat manager does not request user input." };
+#if !NETCOREAPP
+ return result.AsValueTask();
+#else
+ return ValueTask.FromResult(result);
+#endif
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/Handoff/HandoffActor.cs b/dotnet/src/Agents/Orchestration/Handoff/HandoffActor.cs
new file mode 100644
index 000000000000..5fd9e1ff268b
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/Handoff/HandoffActor.cs
@@ -0,0 +1,187 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Agents.Runtime;
+using Microsoft.SemanticKernel.Agents.Runtime.Core;
+using Microsoft.SemanticKernel.ChatCompletion;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
+
+///
+/// An actor used with the .
+///
+internal sealed class HandoffActor :
+ AgentActor,
+ IHandle,
+ IHandle,
+ IHandle
+{
+ private readonly HandoffLookup _handoffs;
+ private readonly AgentType _resultHandoff;
+ private readonly List _cache;
+
+ private string? _handoffAgent;
+ private string? _taskSummary;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The unique identifier of the agent.
+ /// The runtime associated with the agent.
+ /// The orchestration context.
+ /// An .
+ /// The handoffs available to this agent
+ /// The handoff agent for capturing the result.
+ /// The logger to use for the actor
+ public HandoffActor(AgentId id, IAgentRuntime runtime, OrchestrationContext context, Agent agent, HandoffLookup handoffs, AgentType resultHandoff, ILogger? logger = null)
+ : base(id, runtime, context, agent, logger)
+ {
+ if (handoffs.ContainsKey(agent.Name ?? agent.Id))
+ {
+ throw new ArgumentException($"The agent {agent.Name ?? agent.Id} cannot have a handoff to itself.", nameof(handoffs));
+ }
+
+ this._cache = [];
+ this._handoffs = handoffs;
+ this._resultHandoff = resultHandoff;
+ }
+
+ ///
+ /// Gets or sets the callback to be invoked for interactive input.
+ ///
+ public OrchestrationInteractiveCallback? InteractiveCallback { get; init; }
+
+ ///
+ protected override bool ResponseCallbackFilter(ChatMessageContent response) => response.Role == AuthorRole.Tool;
+
+ ///
+ protected override AgentInvokeOptions? CreateInvokeOptions()
+ {
+ // Clone kernel to avoid modifying the original
+ Kernel kernel = this.Agent.Kernel.Clone();
+ kernel.AutoFunctionInvocationFilters.Add(new HandoffInvocationFilter());
+ kernel.Plugins.Add(this.CreateHandoffPlugin());
+
+ // Create invocation options that use auto-function invocation and our modified kernel.
+ AgentInvokeOptions options =
+ new()
+ {
+ Kernel = kernel,
+ KernelArguments = new(new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() })
+ };
+
+ return options;
+ }
+
+ ///
+ public ValueTask HandleAsync(HandoffMessages.InputTask item, MessageContext messageContext)
+ {
+ this._taskSummary = null;
+ this._cache.AddRange(item.Messages);
+
+#if !NETCOREAPP
+ return Task.CompletedTask.AsValueTask();
+#else
+ return ValueTask.CompletedTask;
+#endif
+ }
+
+ ///
+ public ValueTask HandleAsync(HandoffMessages.Response item, MessageContext messageContext)
+ {
+ this._cache.Add(item.Message);
+
+#if !NETCOREAPP
+ return Task.CompletedTask.AsValueTask();
+#else
+ return ValueTask.CompletedTask;
+#endif
+ }
+
+ ///
+ public async ValueTask HandleAsync(HandoffMessages.Request item, MessageContext messageContext)
+ {
+ this.Logger.LogHandoffAgentInvoke(this.Id);
+
+ while (this._taskSummary == null)
+ {
+ ChatMessageContent response = await this.InvokeAsync(this._cache, messageContext.CancellationToken).ConfigureAwait(false);
+ this._cache.Clear();
+
+ this.Logger.LogHandoffAgentResult(this.Id, response.Content);
+
+ // The response can potentially be a TOOL message from the Handoff plugin due to the filter
+ // which will terminate the conversation when a function from the handoff plugin is called.
+ // Since we don't want to publish that message, so we only publish if the response is an ASSISTANT message.
+ if (response.Role == AuthorRole.Assistant)
+ {
+ await this.PublishMessageAsync(new HandoffMessages.Response { Message = response }, this.Context.Topic, messageId: null, messageContext.CancellationToken).ConfigureAwait(false);
+ }
+
+ if (this._handoffAgent != null)
+ {
+ AgentType handoffType = this._handoffs[this._handoffAgent].AgentType;
+ await this.SendMessageAsync(new HandoffMessages.Request(), handoffType, messageContext.CancellationToken).ConfigureAwait(false);
+
+ this._handoffAgent = null;
+ break;
+ }
+
+ if (this.InteractiveCallback != null && this._taskSummary == null)
+ {
+ ChatMessageContent input = await this.InteractiveCallback().ConfigureAwait(false);
+ this._cache.Add(input);
+ continue;
+ }
+
+ await this.EndAsync(response.Content ?? "No handoff or human response function requested. Ending task.", messageContext.CancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private KernelPlugin CreateHandoffPlugin()
+ {
+ return KernelPluginFactory.CreateFromFunctions(HandoffInvocationFilter.HandoffPlugin, CreateHandoffFunctions());
+
+ IEnumerable CreateHandoffFunctions()
+ {
+ yield return KernelFunctionFactory.CreateFromMethod(
+ this.EndAsync,
+ functionName: "end_task_with_summary",
+ description: "End the task with a summary when there is no further action to take.");
+
+ foreach (KeyValuePair handoff in this._handoffs)
+ {
+ KernelFunction kernelFunction =
+ KernelFunctionFactory.CreateFromMethod(
+ (CancellationToken cancellationToken) => this.HandoffAsync(handoff.Key, cancellationToken),
+ functionName: $"transfer_to_{handoff.Key}",
+ description: handoff.Value.Description);
+
+ yield return kernelFunction;
+ }
+ }
+ }
+
+ private ValueTask HandoffAsync(string agentName, CancellationToken cancellationToken = default)
+ {
+ this.Logger.LogHandoffFunctionCall(this.Id, agentName);
+ this._handoffAgent = agentName;
+
+#if !NETCOREAPP
+ return Task.CompletedTask.AsValueTask();
+#else
+ return ValueTask.CompletedTask;
+#endif
+ }
+
+ private async ValueTask EndAsync(string summary, CancellationToken cancellationToken)
+ {
+ this.Logger.LogHandoffSummary(this.Id, summary);
+ this._taskSummary = summary;
+ await this.SendMessageAsync(new HandoffMessages.Result { Message = new ChatMessageContent(AuthorRole.Assistant, summary) }, this._resultHandoff, cancellationToken).ConfigureAwait(false);
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/Handoff/HandoffInvocationFilter.cs b/dotnet/src/Agents/Orchestration/Handoff/HandoffInvocationFilter.cs
new file mode 100644
index 000000000000..7c67f637bda2
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/Handoff/HandoffInvocationFilter.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Threading.Tasks;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
+
+internal sealed class HandoffInvocationFilter() : IAutoFunctionInvocationFilter
+{
+ public const string HandoffPlugin = nameof(HandoffPlugin);
+
+ public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func next)
+ {
+ // Execution the function
+ await next(context).ConfigureAwait(false);
+
+ // Signal termination if the function is part of the handoff plugin
+ if (context.Function.PluginName == HandoffPlugin)
+ {
+ context.Terminate = true;
+ }
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/Handoff/HandoffMessages.cs b/dotnet/src/Agents/Orchestration/Handoff/HandoffMessages.cs
new file mode 100644
index 000000000000..805bfcd08dac
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/Handoff/HandoffMessages.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
+
+///
+/// A message that describes the input task and captures results for a .
+///
+internal static class HandoffMessages
+{
+ ///
+ /// An empty message instance as a default.
+ ///
+ internal static readonly ChatMessageContent Empty = new();
+
+ ///
+ /// The input message.
+ ///
+ public sealed class InputTask
+ {
+ ///
+ /// The orchestration input messages.
+ ///
+ public IList Messages { get; init; } = [];
+ }
+
+ ///
+ /// The final result.
+ ///
+ public sealed class Result
+ {
+ ///
+ /// The orchestration result message.
+ ///
+ public ChatMessageContent Message { get; init; } = Empty;
+ }
+
+ ///
+ /// Signals the handoff to another agent.
+ ///
+ public sealed class Request;
+
+ ///
+ /// Broadcast an agent response to all actors in the orchestration.
+ ///
+ public sealed class Response
+ {
+ ///
+ /// The chat response message.
+ ///
+ public ChatMessageContent Message { get; init; } = Empty;
+ }
+
+ ///
+ /// Extension method to convert a to a .
+ ///
+ public static InputTask AsInputTaskMessage(this IEnumerable messages) => new() { Messages = [.. messages] };
+
+ ///
+ /// Extension method to convert a to a .
+ ///
+ public static Result AsResultMessage(this ChatMessageContent message) => new() { Message = message };
+}
diff --git a/dotnet/src/Agents/Orchestration/Handoff/HandoffOrchestration.String.cs b/dotnet/src/Agents/Orchestration/Handoff/HandoffOrchestration.String.cs
new file mode 100644
index 000000000000..21f5fb3c5eca
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/Handoff/HandoffOrchestration.String.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
+
+///
+/// An orchestration that passes the input message to the first agent, and
+/// then the subsequent result to the next agent, etc...
+///
+public sealed class HandoffOrchestration : HandoffOrchestration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Defines the handoff connections for each agent.
+ /// The agents to be orchestrated.
+ public HandoffOrchestration(OrchestrationHandoffs handoffs, params Agent[] members)
+ : base(handoffs, members)
+ {
+ }
+}
diff --git a/dotnet/src/Agents/Orchestration/Handoff/HandoffOrchestration.cs b/dotnet/src/Agents/Orchestration/Handoff/HandoffOrchestration.cs
new file mode 100644
index 000000000000..04d75cef719a
--- /dev/null
+++ b/dotnet/src/Agents/Orchestration/Handoff/HandoffOrchestration.cs
@@ -0,0 +1,112 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Agents.Orchestration.Extensions;
+using Microsoft.SemanticKernel.Agents.Runtime;
+
+namespace Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
+
+///
+/// An orchestration that provides the input message to the first agent
+/// and Handoffly passes each agent result to the next agent.
+///
+public class HandoffOrchestration : AgentOrchestration
+{
+ private readonly OrchestrationHandoffs _handoffs;
+
+ ///