From d70bd6b3aeff651214f036435175449128d68bd3 Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com>
Date: Mon, 23 Oct 2023 18:37:48 +0100
Subject: [PATCH 01/24] .Net Remove SKCancelEventArgs unneeded finalizer
(#3249)
### Motivation and Context
Garba Collection will handle this scenario normally
### Description
Removal of unneeded implementation.
### Contribution Checklist
- [x] The code builds clean without any errors or warnings
- [x] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [x] All unit tests pass, and I have added new tests where possible
- [x] I didn't break anyone :smile:
---
.../Events/SKCancelEventArgs.cs | 8 --------
1 file changed, 8 deletions(-)
diff --git a/dotnet/src/SemanticKernel.Abstractions/Events/SKCancelEventArgs.cs b/dotnet/src/SemanticKernel.Abstractions/Events/SKCancelEventArgs.cs
index dfcaae8c14..a3cca0bd5f 100644
--- a/dotnet/src/SemanticKernel.Abstractions/Events/SKCancelEventArgs.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/Events/SKCancelEventArgs.cs
@@ -29,12 +29,4 @@ public abstract class SKCancelEventArgs : SKEventArgs
{
this._cancelTokenSource.Cancel();
}
-
- ///
- /// Dispose resources.
- ///
- ~SKCancelEventArgs()
- {
- this._cancelTokenSource.Dispose();
- }
}
From da2ea9c83824ade0baf3e5cf2c264a824a5ad1eb Mon Sep 17 00:00:00 2001
From: Abby Harrison <54643756+awharrison-28@users.noreply.github.com>
Date: Mon, 23 Oct 2023 11:24:16 -0700
Subject: [PATCH 02/24] Python: Update pyproject.toml to 0.3.14.dev (#3262)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Update version for next pip package release
### Motivation and Context
### Description
### Contribution Checklist
- [ ] The code builds clean without any errors or warnings
- [ ] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [ ] All unit tests pass, and I have added new tests where possible
- [ ] I didn't break anyone :smile:
Co-authored-by: Matthew Bolaños
---
python/pyproject.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/pyproject.toml b/python/pyproject.toml
index 4970cf6746..27a7d942d4 100644
--- a/python/pyproject.toml
+++ b/python/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "semantic-kernel"
-version = "0.3.13.dev"
+version = "0.3.14.dev"
description = ""
authors = ["Microsoft "]
readme = "pip/README.md"
From aef2bddf8661f7204649d994e321b05c66f5c33b Mon Sep 17 00:00:00 2001
From: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>
Date: Mon, 23 Oct 2023 19:37:00 +0100
Subject: [PATCH 03/24] Revert "Add Java to the CodeQL language matrix" (#3263)
Reverts microsoft/semantic-kernel#3257
---
.github/workflows/codeql-analysis.yml | 61 ++++++++++++++-------------
1 file changed, 31 insertions(+), 30 deletions(-)
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index b4708221f5..a3f6d583cd 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -6,9 +6,9 @@ name: "CodeQL"
on:
push:
- branches: ["main", "experimental*", "feature*"]
+ branches: [ "main", "experimental*", "feature*" ]
schedule:
- - cron: "17 11 * * 2"
+ - cron: '17 11 * * 2'
jobs:
analyze:
@@ -22,44 +22,45 @@ jobs:
strategy:
fail-fast: false
matrix:
- language: ["csharp", "python", "java"]
+ language: [ 'csharp', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- - name: Checkout repository
- uses: actions/checkout@v4
+ - name: Checkout repository
+ uses: actions/checkout@v4
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v2
- with:
- languages: ${{ matrix.language }}
- # If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
- # Prefix the list here with "+" to use these queries and those in the config file.
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
- # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
- # queries: security-extended,security-and-quality
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
- # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
- # If this step fails, then you should remove it and run the build manually (see below)
- - name: Autobuild
- uses: github/codeql-action/autobuild@v2
- # ℹ️ Command-line programs to run using the OS shell.
- # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+ # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
- # If the Autobuild fails above, remove it and uncomment the following three lines.
- # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- # - run: |
- # echo "Run, Build Application using script"
- # ./location_of_script_within_repo/buildscript.sh
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
- with:
- category: "/language:${{matrix.language}}"
+ # - run: |
+ # echo "Run, Build Application using script"
+ # ./location_of_script_within_repo/buildscript.sh
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
+ with:
+ category: "/language:${{matrix.language}}"
From 360102243b8ab8d8a7790408e31872f9c8620b77 Mon Sep 17 00:00:00 2001
From: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>
Date: Mon, 23 Oct 2023 23:14:11 +0100
Subject: [PATCH 04/24] .Net: Introduce IAIServiceSelector interface to allow
AI service and request settings to be selected during semantic function
execution (#3227)
### Motivation and Context
Fix following issues for Semantic functions:
1. Allow semantic functions to dynamically retrieve the AI service and
request settings to be used when executing the function
Previously semantic functions execution has the following limitations:
1. The AI service was set on the semantic function when it was created.
This meant that an instance of a semantic function could not be used
with multiple different Kernel instances.
1. The AI request settings were set on the semantic function when it was
created. This meant that different request settings could not be
selected when the function was executed.
### Description
This PR add's a new abstraction called `IAIServiceSelector`. An instance
of this is added to a semantic function when it is created. The default
implementation works as follows:
1. If the semantic function only has a single associated model request
setting instance with no service id then the default AI service and the
model request settings are used. This is consistent with the previous
behaviour.
1. If the semantic function only has a single associated model request
setting instance with a service id then the named AI service and the
model request settings are used. If the named AI service is not
available an exception will be thrown. This is consistent with the
previous behaviour.
1. If the semantic function only has multiple associated model request
setting instances then:
1. The model request setting instances are considered in order
1. The first model request setting instance with no service id is
considered the default
1. For each model request setting instance that has a service id, if the
service exists then it will be used with the associated model request
settings
1. If no matching service can be found and default request settings are
provided the default service will be used
1. If no matching service can be found and no default request settings
are provided an exception will be thrown
### Contribution Checklist
- [x] The code builds clean without any errors or warnings
- [x] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [x] All unit tests pass, and I have added new tests where possible
- [x] I didn't break anyone :smile:
---
.../Prompt/Blocks/CodeBlockTests.cs | 24 +-
.../Prompt/PromptTemplateEngineTests.cs | 8 +-
.../Plugins/SamplePluginsTests.cs | 4 -
.../Action/ActionPlannerTests.cs | 6 +-
...adOnlyFunctionCollectionExtensionsTests.cs | 13 +-
.../Sequential/SequentialPlanParserTests.cs | 12 +-
.../Sequential/SequentialPlannerTests.cs | 9 +-
.../Functions/IAIServiceSelector.cs | 25 ++
.../Functions/ISKFunction.cs | 17 +-
.../Orchestration/SKContext.cs | 10 +
.../KernelSemanticFunctionExtensions.cs | 22 +-
.../Extensions/SKFunctionExtensions.cs | 4 +-
.../Functions/DelegatingAIServiceSelector.cs | 25 ++
.../Functions/InstrumentedSKFunction.cs | 25 +-
.../Functions/NativeFunction.cs | 37 +--
.../Functions/OrderedIAIServiceSelector.cs | 61 +++++
.../Functions/SemanticFunction.cs | 81 ++++--
dotnet/src/SemanticKernel.Core/Kernel.cs | 1 +
.../Planning/InstrumentedPlan.cs | 25 +-
.../src/SemanticKernel.Core/Planning/Plan.cs | 36 +--
.../Functions/FunctionCollectionTests.cs | 6 +-
.../Functions/MultipleModelTests.cs | 168 +++++++++++++
...redIAIServiceConfigurationProviderTests.cs | 232 ++++++++++++++++++
.../Functions/SKContextTests.cs | 14 +-
.../Functions/SKFunctionTests2.cs | 3 +
.../Functions/SemanticFunctionTests.cs | 32 +--
.../Orchestration/FunctionResultTests.cs | 4 +-
.../Orchestration/KernelResultTests.cs | 4 +-
.../Planning/PlanSerializationTests.cs | 36 +--
.../Planning/PlanTests.cs | 89 +++----
30 files changed, 814 insertions(+), 219 deletions(-)
create mode 100644 dotnet/src/SemanticKernel.Abstractions/Functions/IAIServiceSelector.cs
create mode 100644 dotnet/src/SemanticKernel.Core/Functions/DelegatingAIServiceSelector.cs
create mode 100644 dotnet/src/SemanticKernel.Core/Functions/OrderedIAIServiceSelector.cs
create mode 100644 dotnet/src/SemanticKernel.UnitTests/Functions/MultipleModelTests.cs
create mode 100644 dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs
diff --git a/dotnet/src/Extensions/Extensions.UnitTests/TemplateEngine/Prompt/Blocks/CodeBlockTests.cs b/dotnet/src/Extensions/Extensions.UnitTests/TemplateEngine/Prompt/Blocks/CodeBlockTests.cs
index c6a35e87ff..8004cedc61 100644
--- a/dotnet/src/Extensions/Extensions.UnitTests/TemplateEngine/Prompt/Blocks/CodeBlockTests.cs
+++ b/dotnet/src/Extensions/Extensions.UnitTests/TemplateEngine/Prompt/Blocks/CodeBlockTests.cs
@@ -10,6 +10,7 @@ using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AI;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Orchestration;
+using Microsoft.SemanticKernel.Services;
using Microsoft.SemanticKernel.TemplateEngine.Basic.Blocks;
using Moq;
using Xunit;
@@ -21,6 +22,7 @@ public class CodeBlockTests
private readonly Mock _functions;
private readonly ILoggerFactory _logger = NullLoggerFactory.Instance;
private readonly Mock _functionRunner = new();
+ private readonly Mock _serviceProvider = new();
public CodeBlockTests()
{
@@ -32,7 +34,7 @@ public class CodeBlockTests
{
// Arrange
var functionRunner = new Mock();
- var context = new SKContext(this._functionRunner.Object);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object);
var target = new CodeBlock("functionName", this._logger);
this._functionRunner.Setup(r => r.RunAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
@@ -49,7 +51,7 @@ public class CodeBlockTests
public async Task ItThrowsIfAFunctionCallThrowsAsync()
{
// Arrange
- var context = new SKContext(this._functionRunner.Object, functions: this._functions.Object);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object, functions: this._functions.Object);
var function = new Mock();
function
.Setup(x => x.InvokeAsync(It.IsAny(), It.IsAny(), It.IsAny()))
@@ -145,7 +147,7 @@ public class CodeBlockTests
{
// Arrange
var variables = new ContextVariables { ["varName"] = "foo" };
- var context = new SKContext(this._functionRunner.Object, variables, functions: this._functions.Object);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object, variables, functions: this._functions.Object);
// Act
var codeBlock = new CodeBlock("$varName", NullLoggerFactory.Instance);
@@ -160,7 +162,7 @@ public class CodeBlockTests
{
// Arrange
var variables = new ContextVariables { ["varName"] = "bar" };
- var context = new SKContext(this._functionRunner.Object, variables, functions: this._functions.Object);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object, variables, functions: this._functions.Object);
var varBlock = new VarBlock("$varName");
// Act
@@ -175,7 +177,7 @@ public class CodeBlockTests
public async Task ItRendersCodeBlockConsistingOfJustAValBlock1Async()
{
// Arrange
- var context = new SKContext(this._functionRunner.Object);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object);
// Act
var codeBlock = new CodeBlock("'ciao'", NullLoggerFactory.Instance);
@@ -190,7 +192,7 @@ public class CodeBlockTests
{
// Arrange
var kernel = new Mock();
- var context = new SKContext(this._functionRunner.Object);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object);
var valBlock = new ValBlock("'arrivederci'");
// Act
@@ -209,7 +211,7 @@ public class CodeBlockTests
const string Plugin = "pluginName";
var variables = new ContextVariables { ["input"] = "zero", ["var1"] = "uno", ["var2"] = "due" };
- var context = new SKContext(this._functionRunner.Object, variables, functions: this._functions.Object);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object, variables, functions: this._functions.Object);
var funcId = new FunctionIdBlock(Func);
var canary0 = string.Empty;
@@ -257,7 +259,7 @@ public class CodeBlockTests
const string VarValue = "varValue";
var variables = new ContextVariables { [Var] = VarValue };
- var context = new SKContext(this._functionRunner.Object, variables, functions: this._functions.Object);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object, variables, functions: this._functions.Object);
var funcId = new FunctionIdBlock(Func);
var varBlock = new VarBlock($"${Var}");
@@ -290,7 +292,7 @@ public class CodeBlockTests
const string Plugin = "pluginName";
const string Value = "value";
- var context = new SKContext(this._functionRunner.Object, variables: null, functions: this._functions.Object);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object, variables: null, functions: this._functions.Object);
var funcId = new FunctionIdBlock(Func);
var valBlock = new ValBlock($"'{Value}'");
@@ -328,7 +330,7 @@ public class CodeBlockTests
var variables = new ContextVariables();
variables.Set("bob", BobValue);
variables.Set("input", Value);
- var context = new SKContext(this._functionRunner.Object, variables: variables, functions: this._functions.Object);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object, variables: variables, functions: this._functions.Object);
var funcId = new FunctionIdBlock(Func);
var namedArgBlock1 = new NamedArgBlock($"foo='{FooValue}'");
var namedArgBlock2 = new NamedArgBlock("baz=$bob");
@@ -362,7 +364,7 @@ public class CodeBlockTests
this._functionRunner.Setup(r => r.RunAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Returns((pluginName, functionName, variables, cancellationToken) =>
{
- var context = new SKContext(this._functionRunner.Object, variables);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object, variables);
return function.InvokeAsync(context, null, cancellationToken);
});
}
diff --git a/dotnet/src/Extensions/Extensions.UnitTests/TemplateEngine/Prompt/PromptTemplateEngineTests.cs b/dotnet/src/Extensions/Extensions.UnitTests/TemplateEngine/Prompt/PromptTemplateEngineTests.cs
index c222beacd8..83a6be8cd7 100644
--- a/dotnet/src/Extensions/Extensions.UnitTests/TemplateEngine/Prompt/PromptTemplateEngineTests.cs
+++ b/dotnet/src/Extensions/Extensions.UnitTests/TemplateEngine/Prompt/PromptTemplateEngineTests.cs
@@ -11,6 +11,7 @@ using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Orchestration;
+using Microsoft.SemanticKernel.Services;
using Microsoft.SemanticKernel.TemplateEngine.Basic;
using Microsoft.SemanticKernel.TemplateEngine.Basic.Blocks;
using Moq;
@@ -29,6 +30,7 @@ public sealed class PromptTemplateEngineTests
private readonly ITestOutputHelper _logger;
private readonly Mock _kernel;
private readonly Mock _functionRunner;
+ private readonly Mock _serviceProvider;
public PromptTemplateEngineTests(ITestOutputHelper testOutputHelper)
{
@@ -38,6 +40,7 @@ public sealed class PromptTemplateEngineTests
this._functions = new Mock();
this._kernel = new Mock();
this._functionRunner = new Mock();
+ this._serviceProvider = new Mock();
}
[Fact]
@@ -375,7 +378,7 @@ public sealed class PromptTemplateEngineTests
this._functionRunner.Setup(r => r.RunAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Returns((pluginName, functionName, variables, cancellationToken) =>
{
- var context = new SKContext(this._functionRunner.Object, variables);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object, variables);
return function.InvokeAsync(context, null, cancellationToken);
});
}
@@ -385,7 +388,7 @@ public sealed class PromptTemplateEngineTests
this._functionRunner.Setup(r => r.RunAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Returns((pluginName, functionName, variables, cancellationToken) =>
{
- var context = new SKContext(this._functionRunner.Object, variables);
+ var context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object, variables);
var function = functions.First(f => f.PluginName == functionName);
return function.InvokeAsync(context, null, cancellationToken);
@@ -396,6 +399,7 @@ public sealed class PromptTemplateEngineTests
{
return new SKContext(
this._functionRunner.Object,
+ this._serviceProvider.Object,
this._variables,
this._functions.Object);
}
diff --git a/dotnet/src/IntegrationTests/Plugins/SamplePluginsTests.cs b/dotnet/src/IntegrationTests/Plugins/SamplePluginsTests.cs
index d4817e2d61..040290ac3e 100644
--- a/dotnet/src/IntegrationTests/Plugins/SamplePluginsTests.cs
+++ b/dotnet/src/IntegrationTests/Plugins/SamplePluginsTests.cs
@@ -25,8 +25,6 @@ public class SamplePluginsTests
{
var function = kernel.Functions.GetFunction(view.PluginName, view.Name);
Assert.NotNull(function);
- Assert.NotNull(function.RequestSettings);
- Assert.True(function.RequestSettings.ExtensionData.ContainsKey("max_tokens"));
});
}
@@ -49,8 +47,6 @@ public class SamplePluginsTests
{
var function = kernel.Functions.GetFunction(view.PluginName, view.Name);
Assert.NotNull(function);
- Assert.NotNull(function.RequestSettings);
- Assert.True(function.RequestSettings.ExtensionData.ContainsKey("max_tokens"));
});
}
}
diff --git a/dotnet/src/Planners/Planners.Core.UnitTests/Action/ActionPlannerTests.cs b/dotnet/src/Planners/Planners.Core.UnitTests/Action/ActionPlannerTests.cs
index 3c42aab053..8aff332f21 100644
--- a/dotnet/src/Planners/Planners.Core.UnitTests/Action/ActionPlannerTests.cs
+++ b/dotnet/src/Planners/Planners.Core.UnitTests/Action/ActionPlannerTests.cs
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SemanticKernel.AI;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Orchestration;
+using Microsoft.SemanticKernel.Services;
using Moq;
using Xunit;
@@ -163,11 +164,12 @@ This plan uses the `GitHubPlugin.PullsList` function to list the open pull reque
functions.Setup(x => x.GetFunctionViews()).Returns(new List());
}
var functionRunner = new Mock();
+ var serviceProvider = new Mock();
var kernel = new Mock();
- var returnContext = new SKContext(functionRunner.Object, new ContextVariables(testPlanString), functions.Object);
+ var returnContext = new SKContext(functionRunner.Object, serviceProvider.Object, new ContextVariables(testPlanString), functions.Object);
- var context = new SKContext(functionRunner.Object, functions: functions.Object);
+ var context = new SKContext(functionRunner.Object, serviceProvider.Object, functions: functions.Object);
var mockFunctionFlowFunction = new Mock();
mockFunctionFlowFunction.Setup(x => x.InvokeAsync(
diff --git a/dotnet/src/Planners/Planners.Core.UnitTests/Extensions/ReadOnlyFunctionCollectionExtensionsTests.cs b/dotnet/src/Planners/Planners.Core.UnitTests/Extensions/ReadOnlyFunctionCollectionExtensionsTests.cs
index c4079d1b21..edcf0554af 100644
--- a/dotnet/src/Planners/Planners.Core.UnitTests/Extensions/ReadOnlyFunctionCollectionExtensionsTests.cs
+++ b/dotnet/src/Planners/Planners.Core.UnitTests/Extensions/ReadOnlyFunctionCollectionExtensionsTests.cs
@@ -2,6 +2,7 @@
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Orchestration;
+using Microsoft.SemanticKernel.Services;
using Moq;
using Xunit;
@@ -57,9 +58,10 @@ public class ReadOnlyFunctionCollectionExtensionsTests
.Returns(asyncEnumerable);
var functionRunner = new Mock();
+ var serviceProvider = new Mock();
// Arrange GetAvailableFunctionsAsync parameters
- var context = new SKContext(functionRunner.Object, variables);
+ var context = new SKContext(functionRunner.Object, serviceProvider.Object, variables);
var config = InitializeConfig(t);
var semanticQuery = "test";
@@ -136,9 +138,10 @@ public class ReadOnlyFunctionCollectionExtensionsTests
functions.Setup(x => x.GetFunctionViews()).Returns(functionsView);
var functionRunner = new Mock();
+ var serviceProvider = new Mock();
// Arrange GetAvailableFunctionsAsync parameters
- var context = new SKContext(functionRunner.Object, variables, functions.Object);
+ var context = new SKContext(functionRunner.Object, serviceProvider.Object, variables, functions.Object);
var config = InitializeConfig(t);
var semanticQuery = "test";
@@ -206,9 +209,10 @@ public class ReadOnlyFunctionCollectionExtensionsTests
functions.Setup(x => x.GetFunctionViews()).Returns(functionsView);
var functionRunner = new Mock();
+ var serviceProvider = new Mock();
// Arrange GetAvailableFunctionsAsync parameters
- var context = new SKContext(functionRunner.Object, variables, functions.Object);
+ var context = new SKContext(functionRunner.Object, serviceProvider.Object, variables, functions.Object);
var config = InitializeConfig(t);
config.SemanticMemoryConfig = new() { RelevancyThreshold = 0.78, Memory = memory.Object };
var semanticQuery = "test";
@@ -243,6 +247,7 @@ public class ReadOnlyFunctionCollectionExtensionsTests
// Arrange
var kernel = new Mock();
var functionRunner = new Mock();
+ var serviceProvider = new Mock();
var variables = new ContextVariables();
var functions = new FunctionCollection();
@@ -267,7 +272,7 @@ public class ReadOnlyFunctionCollectionExtensionsTests
.Returns(asyncEnumerable);
// Arrange GetAvailableFunctionsAsync parameters
- var context = new SKContext(functionRunner.Object, variables);
+ var context = new SKContext(functionRunner.Object, serviceProvider.Object, variables);
var config = InitializeConfig(t);
config.SemanticMemoryConfig = new() { RelevancyThreshold = 0.78, Memory = memory.Object };
var semanticQuery = "test";
diff --git a/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlanParserTests.cs b/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlanParserTests.cs
index 141ac5f572..263a75a8fc 100644
--- a/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlanParserTests.cs
+++ b/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlanParserTests.cs
@@ -4,6 +4,7 @@ using System.Globalization;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Orchestration;
+using Microsoft.SemanticKernel.Services;
using Microsoft.SemanticKernel.TemplateEngine;
using Moq;
using Xunit;
@@ -38,9 +39,10 @@ public class SequentialPlanParserTests
private SKContext CreateSKContext(
IFunctionRunner functionRunner,
+ IAIServiceProvider serviceProvider,
ContextVariables? variables = null)
{
- return new SKContext(functionRunner, variables);
+ return new SKContext(functionRunner, serviceProvider, variables);
}
private static Mock CreateMockFunction(FunctionView functionView, string result = "")
@@ -59,12 +61,13 @@ public class SequentialPlanParserTests
kernel = kernelMock.Object;
var functionRunnerMock = new Mock();
+ var serviceProviderMock = new Mock();
// For Create
kernelMock.Setup(k => k.CreateNewContext(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Returns((contextVariables, skills, loggerFactory, culture) =>
{
- return this.CreateSKContext(functionRunnerMock.Object, contextVariables);
+ return this.CreateSKContext(functionRunnerMock.Object, serviceProviderMock.Object, contextVariables);
});
var functionsView = new List();
@@ -77,7 +80,7 @@ public class SequentialPlanParserTests
var mockFunction = CreateMockFunction(functionView);
functionsView.Add(functionView);
- var result = this.CreateSKContext(functionRunnerMock.Object);
+ var result = this.CreateSKContext(functionRunnerMock.Object, serviceProviderMock.Object);
result.Variables.Update(resultString);
mockFunction.Setup(x => x.InvokeAsync(It.IsAny(), null, It.IsAny()))
.ReturnsAsync(new FunctionResult(name, pluginName, result));
@@ -88,7 +91,8 @@ public class SequentialPlanParserTests
It.IsAny(),
It.IsAny(),
It.IsAny(),
- It.IsAny()
+ It.IsAny(),
+ It.IsAny()
)).Returns(mockFunction.Object);
}
else
diff --git a/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlannerTests.cs b/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlannerTests.cs
index 785eed2201..200f4088d9 100644
--- a/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlannerTests.cs
+++ b/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlannerTests.cs
@@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.AI;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Orchestration;
+using Microsoft.SemanticKernel.Services;
using Moq;
using Xunit;
@@ -60,6 +61,7 @@ public sealed class SequentialPlannerTests
functions.Setup(x => x.GetFunctionViews()).Returns(functionsView);
var functionRunner = new Mock();
+ var serviceProvider = new Mock();
kernel.Setup(x => x.LoggerFactory).Returns(new Mock().Object);
var expectedFunctions = input.Select(x => x.name).ToList();
@@ -67,10 +69,12 @@ public sealed class SequentialPlannerTests
var context = new SKContext(
functionRunner.Object,
+ serviceProvider.Object,
new ContextVariables());
var returnContext = new SKContext(
functionRunner.Object,
+ serviceProvider.Object,
new ContextVariables());
var planString =
@@ -147,15 +151,16 @@ public sealed class SequentialPlannerTests
{
// Arrange
var functionRunner = new Mock();
+ var serviceProvider = new Mock();
var kernel = new Mock();
var functions = new Mock();
functions.Setup(x => x.GetFunctionViews()).Returns(new List());
var planString = "notvalid<";
- var returnContext = new SKContext(functionRunner.Object, new ContextVariables(planString));
+ var returnContext = new SKContext(functionRunner.Object, serviceProvider.Object, new ContextVariables(planString));
- var context = new SKContext(functionRunner.Object, new ContextVariables());
+ var context = new SKContext(functionRunner.Object, serviceProvider.Object, new ContextVariables());
var mockFunctionFlowFunction = new Mock();
mockFunctionFlowFunction.Setup(x => x.InvokeAsync(
diff --git a/dotnet/src/SemanticKernel.Abstractions/Functions/IAIServiceSelector.cs b/dotnet/src/SemanticKernel.Abstractions/Functions/IAIServiceSelector.cs
new file mode 100644
index 0000000000..3325f21ee0
--- /dev/null
+++ b/dotnet/src/SemanticKernel.Abstractions/Functions/IAIServiceSelector.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft. All rights reserved.
+using System.Collections.Generic;
+using Microsoft.SemanticKernel.AI;
+using Microsoft.SemanticKernel.Services;
+
+#pragma warning disable IDE0130
+// ReSharper disable once CheckNamespace - Using the main namespace
+namespace Microsoft.SemanticKernel;
+#pragma warning restore IDE0130
+
+///
+/// Selector which will return a tuple containing instances of and from the specified provider based on the model settings.
+///
+public interface IAIServiceSelector
+{
+ ///
+ /// Return the AI service and requesting settings from the specified provider based on the model settings.
+ /// The returned value is a tuple containing instances of and
+ ///
+ /// Type of AI service to return
+ /// AI service provider
+ /// Collection of model settings
+ ///
+ (T?, AIRequestSettings?) SelectAIService(IAIServiceProvider serviceProvider, IReadOnlyList? modelSettings) where T : IAIService;
+}
diff --git a/dotnet/src/SemanticKernel.Abstractions/Functions/ISKFunction.cs b/dotnet/src/SemanticKernel.Abstractions/Functions/ISKFunction.cs
index 717639dab7..02565b7672 100644
--- a/dotnet/src/SemanticKernel.Abstractions/Functions/ISKFunction.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/Functions/ISKFunction.cs
@@ -33,11 +33,6 @@ public interface ISKFunction
///
string Description { get; }
- ///
- /// AI service settings
- ///
- AIRequestSettings? RequestSettings { get; }
-
///
/// Returns a description of the function, including parameters.
///
@@ -56,12 +51,21 @@ public interface ISKFunction
AIRequestSettings? requestSettings = null,
CancellationToken cancellationToken = default);
+ #region Obsolete
+
+ ///
+ /// AI service settings
+ ///
+ [Obsolete("Use PromptTemplateConfig.ModelSettings instead. This will be removed in a future release.")]
+ AIRequestSettings? RequestSettings { get; }
+
///
/// Set the AI service used by the semantic function, passing a factory method.
/// The factory allows to lazily instantiate the client and to properly handle its disposal.
///
/// AI service factory
/// Self instance
+ [Obsolete("Use implementation of IAIServiceConfigurationProvider instead. This will be removed in a future release.")]
ISKFunction SetAIService(Func serviceFactory);
///
@@ -69,10 +73,9 @@ public interface ISKFunction
///
/// LLM completion settings
/// Self instance
+ [Obsolete("Use implementation of IAIServiceConfigurationProvider instead. This will be removed in a future release.")]
ISKFunction SetAIConfiguration(AIRequestSettings? requestSettings);
- #region Obsolete
-
///
/// Name of the plugin containing the function. The name is used by the function collection and in prompt templates e.g. {{skillName.functionName}}
///
diff --git a/dotnet/src/SemanticKernel.Abstractions/Orchestration/SKContext.cs b/dotnet/src/SemanticKernel.Abstractions/Orchestration/SKContext.cs
index cb220a3ee6..ac239f2381 100644
--- a/dotnet/src/SemanticKernel.Abstractions/Orchestration/SKContext.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/Orchestration/SKContext.cs
@@ -7,6 +7,7 @@ using System.Globalization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SemanticKernel.Diagnostics;
+using Microsoft.SemanticKernel.Services;
namespace Microsoft.SemanticKernel.Orchestration;
@@ -58,16 +59,23 @@ public sealed class SKContext
///
public IFunctionRunner Runner { get; }
+ ///
+ /// AI service provider
+ ///
+ public IAIServiceProvider ServiceProvider { get; }
+
///
/// Constructor for the context.
///
/// Function runner reference
+ /// AI service provider
/// Context variables to include in context.
/// Functions to include in context.
/// Logger factory to be used in context
/// Culture related to the context
internal SKContext(
IFunctionRunner functionRunner,
+ IAIServiceProvider serviceProvider,
ContextVariables? variables = null,
IReadOnlyFunctionCollection? functions = null,
ILoggerFactory? loggerFactory = null,
@@ -76,6 +84,7 @@ public sealed class SKContext
Verify.NotNull(functionRunner, nameof(functionRunner));
this.Runner = functionRunner;
+ this.ServiceProvider = serviceProvider;
this.Variables = variables ?? new();
this.Functions = functions ?? NullReadOnlyFunctionCollection.Instance;
this.LoggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
@@ -110,6 +119,7 @@ public sealed class SKContext
{
return new SKContext(
this.Runner,
+ this.ServiceProvider,
variables ?? this.Variables.Clone(),
functions ?? this.Functions,
this.LoggerFactory,
diff --git a/dotnet/src/SemanticKernel.Core/Extensions/KernelSemanticFunctionExtensions.cs b/dotnet/src/SemanticKernel.Core/Extensions/KernelSemanticFunctionExtensions.cs
index ef8e7e06e5..d34f80b687 100644
--- a/dotnet/src/SemanticKernel.Core/Extensions/KernelSemanticFunctionExtensions.cs
+++ b/dotnet/src/SemanticKernel.Core/Extensions/KernelSemanticFunctionExtensions.cs
@@ -7,7 +7,6 @@ using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.AI;
-using Microsoft.SemanticKernel.AI.TextCompletion;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.TemplateEngine;
@@ -30,12 +29,14 @@ public static class KernelSemanticFunctionExtensions
/// Name of the semantic function. The name can contain only alphanumeric chars + underscore.
/// Prompt template configuration.
/// Prompt template.
+ /// Optional, AI service selector.
/// A C# function wrapping AI logic, usually defined with natural language
public static ISKFunction RegisterSemanticFunction(
this IKernel kernel,
string functionName,
PromptTemplateConfig promptTemplateConfig,
- IPromptTemplate promptTemplate)
+ IPromptTemplate promptTemplate,
+ IAIServiceSelector? serviceSelector = null)
{
return kernel.RegisterSemanticFunction(FunctionCollection.GlobalFunctionsPluginName, functionName, promptTemplateConfig, promptTemplate);
}
@@ -48,13 +49,15 @@ public static class KernelSemanticFunctionExtensions
/// Name of the semantic function. The name can contain only alphanumeric chars + underscore.
/// Prompt template configuration.
/// Prompt template.
+ /// Optional, AI service selector.
/// A C# function wrapping AI logic, usually defined with natural language
public static ISKFunction RegisterSemanticFunction(
this IKernel kernel,
string pluginName,
string functionName,
PromptTemplateConfig promptTemplateConfig,
- IPromptTemplate promptTemplate)
+ IPromptTemplate promptTemplate,
+ IAIServiceSelector? serviceSelector = null)
{
// Future-proofing the name not to contain special chars
Verify.ValidFunctionName(functionName);
@@ -276,21 +279,16 @@ public static class KernelSemanticFunctionExtensions
string pluginName,
string functionName,
PromptTemplateConfig promptTemplateConfig,
- IPromptTemplate promptTemplate)
+ IPromptTemplate promptTemplate,
+ IAIServiceSelector? serviceSelector = null)
{
- ISKFunction func = SemanticFunction.FromSemanticConfig(
+ return SemanticFunction.FromSemanticConfig(
pluginName,
functionName,
promptTemplateConfig,
promptTemplate,
+ serviceSelector,
kernel.LoggerFactory
);
-
- func.SetAIConfiguration(promptTemplateConfig.GetDefaultRequestSettings());
-
- // Note: the service is instantiated using the kernel configuration state when the function is invoked
- func.SetAIService(() => kernel.GetService(promptTemplateConfig.GetDefaultRequestSettings()?.ServiceId ?? null));
-
- return func;
}
}
diff --git a/dotnet/src/SemanticKernel.Core/Extensions/SKFunctionExtensions.cs b/dotnet/src/SemanticKernel.Core/Extensions/SKFunctionExtensions.cs
index b855aa9b5d..533987024a 100644
--- a/dotnet/src/SemanticKernel.Core/Extensions/SKFunctionExtensions.cs
+++ b/dotnet/src/SemanticKernel.Core/Extensions/SKFunctionExtensions.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
@@ -22,6 +23,7 @@ public static class SKFunctionExtensions
/// Semantic function
/// Request settings
/// Self instance
+ [Obsolete("Use implementation of IAIServiceConfigurationProvider instead. This will be removed in a future release.")]
public static ISKFunction UseCompletionSettings(this ISKFunction skFunction, AIRequestSettings requestSettings)
{
return skFunction.SetAIConfiguration(requestSettings);
@@ -49,7 +51,7 @@ public static class SKFunctionExtensions
CancellationToken cancellationToken = default)
{
var context = kernel.CreateNewContext(variables, functions, loggerFactory, culture);
- return function.InvokeAsync(context, requestSettings ?? function.RequestSettings, cancellationToken);
+ return function.InvokeAsync(context, requestSettings, cancellationToken);
}
///
diff --git a/dotnet/src/SemanticKernel.Core/Functions/DelegatingAIServiceSelector.cs b/dotnet/src/SemanticKernel.Core/Functions/DelegatingAIServiceSelector.cs
new file mode 100644
index 0000000000..47d310f3c3
--- /dev/null
+++ b/dotnet/src/SemanticKernel.Core/Functions/DelegatingAIServiceSelector.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.SemanticKernel.AI;
+using Microsoft.SemanticKernel.AI.TextCompletion;
+using Microsoft.SemanticKernel.Services;
+
+namespace Microsoft.SemanticKernel.Functions;
+
+///
+/// Added for backward compatibility only, this will be removed when ISKFunction.SetAIService and ISKFunction.SetAIConfiguration are removed.
+///
+[Obsolete("Remove this when ISKFunction.SetAIService and ISKFunction.SetAIConfiguration are removed.")]
+internal class DelegatingAIServiceSelector : IAIServiceSelector
+{
+ internal Func? ServiceFactory { get; set; }
+ internal AIRequestSettings? RequestSettings { get; set; }
+
+ ///
+ public (T?, AIRequestSettings?) SelectAIService(IAIServiceProvider serviceProvider, IReadOnlyList? modelSettings) where T : IAIService
+ {
+ return ((T?)this.ServiceFactory?.Invoke() ?? serviceProvider.GetService(null), this.RequestSettings ?? modelSettings?[0]);
+ }
+}
diff --git a/dotnet/src/SemanticKernel.Core/Functions/InstrumentedSKFunction.cs b/dotnet/src/SemanticKernel.Core/Functions/InstrumentedSKFunction.cs
index 302f98aa18..cef67d4718 100644
--- a/dotnet/src/SemanticKernel.Core/Functions/InstrumentedSKFunction.cs
+++ b/dotnet/src/SemanticKernel.Core/Functions/InstrumentedSKFunction.cs
@@ -31,9 +31,6 @@ internal sealed class InstrumentedSKFunction : ISKFunction
///
public string Description => this._function.Description;
- ///
- public AIRequestSettings? RequestSettings => this._function.RequestSettings;
-
///
/// Initialize a new instance of the class.
///
@@ -78,14 +75,6 @@ internal sealed class InstrumentedSKFunction : ISKFunction
this._function.InvokeAsync(context, requestSettings, cancellationToken)).ConfigureAwait(false);
}
- ///
- public ISKFunction SetAIConfiguration(AIRequestSettings? requestSettings) =>
- this._function.SetAIConfiguration(requestSettings);
-
- ///
- public ISKFunction SetAIService(Func serviceFactory) =>
- this._function.SetAIService(serviceFactory);
-
#region private ================================================================================
private readonly ISKFunction _function;
@@ -174,6 +163,20 @@ internal sealed class InstrumentedSKFunction : ISKFunction
#region Obsolete =======================================================================
+ ///
+ [Obsolete("Use ISKFunction.RequestSettingsFactory instead. This will be removed in a future release.")]
+ public AIRequestSettings? RequestSettings => this._function.RequestSettings;
+
+ ///
+ [Obsolete("Use ISKFunction.SetAIRequestSettingsFactory instead. This will be removed in a future release.")]
+ public ISKFunction SetAIConfiguration(AIRequestSettings? requestSettings) =>
+ this._function.SetAIConfiguration(requestSettings);
+
+ ///
+ [Obsolete("Use ISKFunction.SetAIServiceFactory instead. This will be removed in a future release.")]
+ public ISKFunction SetAIService(Func serviceFactory) =>
+ this._function.SetAIService(serviceFactory);
+
///
[Obsolete("Methods, properties and classes which include Skill in the name have been renamed. Use ISKFunction.PluginName instead. This will be removed in a future release.")]
[EditorBrowsable(EditorBrowsableState.Never)]
diff --git a/dotnet/src/SemanticKernel.Core/Functions/NativeFunction.cs b/dotnet/src/SemanticKernel.Core/Functions/NativeFunction.cs
index aa52bd7f81..da7ff333cd 100644
--- a/dotnet/src/SemanticKernel.Core/Functions/NativeFunction.cs
+++ b/dotnet/src/SemanticKernel.Core/Functions/NativeFunction.cs
@@ -44,9 +44,6 @@ internal sealed class NativeFunction : ISKFunction, IDisposable
///
public string Description { get; }
- ///
- public AIRequestSettings? RequestSettings { get; }
-
///
/// List of function parameters
///
@@ -150,20 +147,6 @@ internal sealed class NativeFunction : ISKFunction, IDisposable
}
}
- ///
- public ISKFunction SetAIService(Func serviceFactory)
- {
- this.ThrowNotSemantic();
- return this;
- }
-
- ///
- public ISKFunction SetAIConfiguration(AIRequestSettings? requestSettings)
- {
- this.ThrowNotSemantic();
- return this;
- }
-
///
/// Dispose of resources.
///
@@ -873,6 +856,26 @@ internal sealed class NativeFunction : ISKFunction, IDisposable
#region Obsolete
+ ///
+ [Obsolete("Use ISKFunction.RequestSettingsFactory instead. This will be removed in a future release.")]
+ public AIRequestSettings? RequestSettings { get; }
+
+ ///
+ [Obsolete("Use ISKFunction.SetAIServiceFactory instead. This will be removed in a future release.")]
+ public ISKFunction SetAIService(Func serviceFactory)
+ {
+ this.ThrowNotSemantic();
+ return this;
+ }
+
+ ///
+ [Obsolete("Use ISKFunction.SetAIRequestSettingsFactory instead. This will be removed in a future release.")]
+ public ISKFunction SetAIConfiguration(AIRequestSettings? requestSettings)
+ {
+ this.ThrowNotSemantic();
+ return this;
+ }
+
///
[Obsolete("Methods, properties and classes which include Skill in the name have been renamed. Use ISKFunction.PluginName instead. This will be removed in a future release.")]
[EditorBrowsable(EditorBrowsableState.Never)]
diff --git a/dotnet/src/SemanticKernel.Core/Functions/OrderedIAIServiceSelector.cs b/dotnet/src/SemanticKernel.Core/Functions/OrderedIAIServiceSelector.cs
new file mode 100644
index 0000000000..f1f8bf6432
--- /dev/null
+++ b/dotnet/src/SemanticKernel.Core/Functions/OrderedIAIServiceSelector.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.SemanticKernel.AI;
+using Microsoft.SemanticKernel.Diagnostics;
+using Microsoft.SemanticKernel.Services;
+
+namespace Microsoft.SemanticKernel.Functions;
+
+///
+/// Implementation of that selects the AI service based on the order of the model settings.
+/// Uses the service id to select the preferred service provider and then returns the service and associated model settings.
+///
+internal class OrderedIAIServiceSelector : IAIServiceSelector
+{
+ ///
+ public (T?, AIRequestSettings?) SelectAIService(IAIServiceProvider serviceProvider, IReadOnlyList? modelSettings) where T : IAIService
+ {
+ if (modelSettings is null || modelSettings.Count == 0)
+ {
+ var service = serviceProvider.GetService(null);
+ if (service is not null)
+ {
+ return (service, null);
+ }
+ }
+ else
+ {
+ AIRequestSettings? defaultRequestSettings = null;
+ foreach (var model in modelSettings)
+ {
+ if (!string.IsNullOrEmpty(model.ServiceId))
+ {
+ var service = serviceProvider.GetService(model.ServiceId);
+ if (service is not null)
+ {
+ return (service, model);
+ }
+ }
+ else
+ {
+ // First request settings with empty or null service id is the default
+ defaultRequestSettings ??= model;
+ }
+ }
+
+ if (defaultRequestSettings is not null)
+ {
+ var service = serviceProvider.GetService(null);
+ if (service is not null)
+ {
+ return (service, defaultRequestSettings);
+ }
+ }
+ }
+
+ var names = string.Join("|", modelSettings.Select(model => model.ServiceId).ToArray());
+ throw new SKException($"Service of type {typeof(T)} and name {names ?? ""} not registered.");
+ }
+}
diff --git a/dotnet/src/SemanticKernel.Core/Functions/SemanticFunction.cs b/dotnet/src/SemanticKernel.Core/Functions/SemanticFunction.cs
index bb5add248e..43e2923bc0 100644
--- a/dotnet/src/SemanticKernel.Core/Functions/SemanticFunction.cs
+++ b/dotnet/src/SemanticKernel.Core/Functions/SemanticFunction.cs
@@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SemanticKernel.AI;
using Microsoft.SemanticKernel.AI.TextCompletion;
using Microsoft.SemanticKernel.Diagnostics;
+using Microsoft.SemanticKernel.Functions;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.TemplateEngine;
@@ -38,9 +39,6 @@ internal sealed class SemanticFunction : ISKFunction, IDisposable
///
public string Description { get; }
- ///
- public AIRequestSettings? RequestSettings { get; private set; }
-
///
/// List of function parameters
///
@@ -53,6 +51,7 @@ internal sealed class SemanticFunction : ISKFunction, IDisposable
/// Name of the function to create.
/// Prompt template configuration.
/// Prompt template.
+ /// AI service selector
/// The to use for logging. If null, no logging will be performed.
/// The to monitor for cancellation requests. The default is .
/// SK function instance.
@@ -61,6 +60,7 @@ internal sealed class SemanticFunction : ISKFunction, IDisposable
string functionName,
PromptTemplateConfig promptTemplateConfig,
IPromptTemplate promptTemplate,
+ IAIServiceSelector? serviceSelector = null,
ILoggerFactory? loggerFactory = null,
CancellationToken cancellationToken = default)
{
@@ -72,9 +72,12 @@ internal sealed class SemanticFunction : ISKFunction, IDisposable
description: promptTemplateConfig.Description,
pluginName: pluginName,
functionName: functionName,
+ serviceSelector: serviceSelector,
loggerFactory: loggerFactory
- );
- func.SetAIConfiguration(promptTemplateConfig.GetDefaultRequestSettings());
+ )
+ {
+ _modelSettings = promptTemplateConfig.ModelSettings
+ };
return func;
}
@@ -93,22 +96,8 @@ internal sealed class SemanticFunction : ISKFunction, IDisposable
{
this.AddDefaultValues(context.Variables);
- return await this.RunPromptAsync(this._aiService?.Value, requestSettings ?? this.RequestSettings, context, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- public ISKFunction SetAIService(Func serviceFactory)
- {
- Verify.NotNull(serviceFactory);
- this._aiService = new Lazy(serviceFactory);
- return this;
- }
-
- ///
- public ISKFunction SetAIConfiguration(AIRequestSettings? requestSettings)
- {
- this.RequestSettings = requestSettings;
- return this;
+ (var textCompletion, var defaultRequestSettings) = this._serviceSelector.SelectAIService(context.ServiceProvider, this._modelSettings);
+ return await this.RunPromptAsync(textCompletion, requestSettings ?? defaultRequestSettings, context, cancellationToken).ConfigureAwait(false);
}
///
@@ -116,10 +105,6 @@ internal sealed class SemanticFunction : ISKFunction, IDisposable
///
public void Dispose()
{
- if (this._aiService is { IsValueCreated: true } aiService)
- {
- (aiService.Value as IDisposable)?.Dispose();
- }
}
///
@@ -139,6 +124,7 @@ internal sealed class SemanticFunction : ISKFunction, IDisposable
string pluginName,
string functionName,
string description,
+ IAIServiceSelector? serviceSelector = null,
ILoggerFactory? loggerFactory = null)
{
Verify.NotNull(template);
@@ -154,6 +140,8 @@ internal sealed class SemanticFunction : ISKFunction, IDisposable
this.PluginName = pluginName;
this.Description = description;
+ this._serviceSelector = serviceSelector ?? new OrderedIAIServiceSelector();
+
this._view = new(() => new(functionName, pluginName, description, this.Parameters));
}
@@ -162,7 +150,8 @@ internal sealed class SemanticFunction : ISKFunction, IDisposable
private static readonly JsonSerializerOptions s_toStringStandardSerialization = new();
private static readonly JsonSerializerOptions s_toStringIndentedSerialization = new() { WriteIndented = true };
private readonly ILogger _logger;
- private Lazy? _aiService;
+ private IAIServiceSelector _serviceSelector;
+ public List? _modelSettings;
private readonly Lazy _view;
public IPromptTemplate _promptTemplate { get; }
@@ -225,6 +214,46 @@ internal sealed class SemanticFunction : ISKFunction, IDisposable
#region Obsolete
+ ///
+ [Obsolete("Use ISKFunction.ModelSettings instead. This will be removed in a future release.")]
+ public AIRequestSettings? RequestSettings => this._modelSettings?.FirstOrDefault();
+
+ ///
+ [Obsolete("Use ISKFunction.SetAIServiceFactory instead. This will be removed in a future release.")]
+ public ISKFunction SetAIService(Func serviceFactory)
+ {
+ Verify.NotNull(serviceFactory);
+
+ if (this._serviceSelector is DelegatingAIServiceSelector delegatingProvider)
+ {
+ delegatingProvider.ServiceFactory = serviceFactory;
+ }
+ else
+ {
+ var serviceSelector = new DelegatingAIServiceSelector();
+ serviceSelector.ServiceFactory = serviceFactory;
+ this._serviceSelector = serviceSelector;
+ }
+ return this;
+ }
+
+ ///
+ [Obsolete("Use ISKFunction.SetAIRequestSettingsFactory instead. This will be removed in a future release.")]
+ public ISKFunction SetAIConfiguration(AIRequestSettings? requestSettings)
+ {
+ if (this._serviceSelector is DelegatingAIServiceSelector delegatingProvider)
+ {
+ delegatingProvider.RequestSettings = requestSettings;
+ }
+ else
+ {
+ var configurationProvider = new DelegatingAIServiceSelector();
+ configurationProvider.RequestSettings = requestSettings;
+ this._serviceSelector = configurationProvider;
+ }
+ return this;
+ }
+
///
[Obsolete("Methods, properties and classes which include Skill in the name have been renamed. Use ISKFunction.PluginName instead. This will be removed in a future release.")]
[EditorBrowsable(EditorBrowsableState.Never)]
diff --git a/dotnet/src/SemanticKernel.Core/Kernel.cs b/dotnet/src/SemanticKernel.Core/Kernel.cs
index 92c582e975..d7be7c73a7 100644
--- a/dotnet/src/SemanticKernel.Core/Kernel.cs
+++ b/dotnet/src/SemanticKernel.Core/Kernel.cs
@@ -174,6 +174,7 @@ repeat:
{
return new SKContext(
new FunctionRunner(this),
+ this._aiServiceProvider,
variables,
functions ?? this.Functions,
loggerFactory ?? this.LoggerFactory,
diff --git a/dotnet/src/SemanticKernel.Core/Planning/InstrumentedPlan.cs b/dotnet/src/SemanticKernel.Core/Planning/InstrumentedPlan.cs
index b6808c6130..61e39ca9ee 100644
--- a/dotnet/src/SemanticKernel.Core/Planning/InstrumentedPlan.cs
+++ b/dotnet/src/SemanticKernel.Core/Planning/InstrumentedPlan.cs
@@ -28,9 +28,6 @@ internal sealed class InstrumentedPlan : IPlan
///
public string Description => this._plan.Description;
- ///
- public AIRequestSettings? RequestSettings => this._plan.RequestSettings;
-
///
/// Initialize a new instance of the class.
///
@@ -60,14 +57,6 @@ internal sealed class InstrumentedPlan : IPlan
this._plan.InvokeAsync(context, requestSettings, cancellationToken)).ConfigureAwait(false);
}
- ///
- public ISKFunction SetAIConfiguration(AIRequestSettings? requestSettings) =>
- this._plan.SetAIConfiguration(requestSettings);
-
- ///
- public ISKFunction SetAIService(Func serviceFactory) =>
- this._plan.SetAIService(serviceFactory);
-
#region private ================================================================================
private readonly IPlan _plan;
@@ -155,6 +144,20 @@ internal sealed class InstrumentedPlan : IPlan
#region Obsolete =======================================================================
+ ///
+ [Obsolete("Use ISKFunction.RequestSettingsFactory instead. This will be removed in a future release.")]
+ public AIRequestSettings? RequestSettings => this._plan.RequestSettings;
+
+ ///
+ [Obsolete("Use ISKFunction.SetAIRequestSettingsFactory instead. This will be removed in a future release.")]
+ public ISKFunction SetAIConfiguration(AIRequestSettings? requestSettings) =>
+ this._plan.SetAIConfiguration(requestSettings);
+
+ ///
+ [Obsolete("Use ISKFunction.SetAIServiceFactory instead. This will be removed in a future release.")]
+ public ISKFunction SetAIService(Func serviceFactory) =>
+ this._plan.SetAIService(serviceFactory);
+
///
[Obsolete("Methods, properties and classes which include Skill in the name have been renamed. Use ISKFunction.PluginName instead. This will be removed in a future release.")]
[EditorBrowsable(EditorBrowsableState.Never)]
diff --git a/dotnet/src/SemanticKernel.Core/Planning/Plan.cs b/dotnet/src/SemanticKernel.Core/Planning/Plan.cs
index d68b47a783..46c954ad07 100644
--- a/dotnet/src/SemanticKernel.Core/Planning/Plan.cs
+++ b/dotnet/src/SemanticKernel.Core/Planning/Plan.cs
@@ -77,8 +77,8 @@ public sealed class Plan : IPlan
public string Description { get; set; } = string.Empty;
///
- [JsonIgnore]
- public AIRequestSettings? RequestSettings { get; private set; }
+ [JsonPropertyName("model_settings")]
+ public List? ModelSettings { get; private set; }
#endregion ISKFunction implementation
@@ -359,18 +359,6 @@ public sealed class Plan : IPlan
return result;
}
- ///
- public ISKFunction SetAIService(Func serviceFactory)
- {
- return this.Function is not null ? this.Function.SetAIService(serviceFactory) : this;
- }
-
- ///
- public ISKFunction SetAIConfiguration(AIRequestSettings? requestSettings)
- {
- return this.Function is not null ? this.Function.SetAIConfiguration(requestSettings) : this;
- }
-
#endregion ISKFunction implementation
///
@@ -597,9 +585,9 @@ public sealed class Plan : IPlan
this.Name = function.Name;
this.PluginName = function.PluginName;
this.Description = function.Description;
- this.RequestSettings = function.RequestSettings;
#pragma warning disable CS0618 // Type or member is obsolete
+ this.RequestSettings = function.RequestSettings;
this.IsSemantic = function.IsSemantic;
#pragma warning restore CS0618 // Type or member is obsolete
}
@@ -637,6 +625,24 @@ public sealed class Plan : IPlan
#region Obsolete
+ ///
+ [Obsolete("Use ISKFunction.ModelSettings instead. This will be removed in a future release.")]
+ public AIRequestSettings? RequestSettings { get; private set; }
+
+ ///
+ [Obsolete("Use ISKFunction.SetAIServiceFactory instead. This will be removed in a future release.")]
+ public ISKFunction SetAIService(Func serviceFactory)
+ {
+ return this.Function is not null ? this.Function.SetAIService(serviceFactory) : this;
+ }
+
+ ///
+ [Obsolete("Use ISKFunction.SetAIRequestSettingsFactory instead. This will be removed in a future release.")]
+ public ISKFunction SetAIConfiguration(AIRequestSettings? requestSettings)
+ {
+ return this.Function is not null ? this.Function.SetAIConfiguration(requestSettings) : this;
+ }
+
///
[JsonIgnore]
[Obsolete("Methods, properties and classes which include Skill in the name have been renamed. Use ISKFunction.PluginName instead. This will be removed in a future release.")]
diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionCollectionTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionCollectionTests.cs
index addbedc144..5023318384 100644
--- a/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionCollectionTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionCollectionTests.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.Collections.Generic;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AI;
using Moq;
@@ -13,17 +14,18 @@ public class FunctionCollectionTests
public void ItAllowsToReplaceFunctions()
{
// Arrange
+ List modelSettings = new();
+ modelSettings.Add(new AIRequestSettings());
+
var functionOne = new Mock();
functionOne.SetupGet(x => x.Name).Returns("fName");
functionOne.SetupGet(x => x.PluginName).Returns("sName");
functionOne.SetupGet(x => x.Description).Returns("ONE");
- functionOne.SetupGet(x => x.RequestSettings).Returns(new AIRequestSettings());
var functionTwo = new Mock();
functionTwo.SetupGet(x => x.Name).Returns("fName");
functionTwo.SetupGet(x => x.PluginName).Returns("sName");
functionTwo.SetupGet(x => x.Description).Returns("TWO");
- functionTwo.SetupGet(x => x.RequestSettings).Returns(new AIRequestSettings());
var target = new FunctionCollection();
diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/MultipleModelTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/MultipleModelTests.cs
new file mode 100644
index 0000000000..23fb743e9c
--- /dev/null
+++ b/dotnet/src/SemanticKernel.UnitTests/Functions/MultipleModelTests.cs
@@ -0,0 +1,168 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.AI;
+using Microsoft.SemanticKernel.AI.TextCompletion;
+using Microsoft.SemanticKernel.Diagnostics;
+using Microsoft.SemanticKernel.TemplateEngine;
+using Moq;
+using Xunit;
+
+namespace SemanticKernel.UnitTests.Functions;
+public class MultipleModelTests
+{
+ [Fact]
+ public async Task ItUsesServiceIdWhenProvidedAsync()
+ {
+ // Arrange
+ var mockTextCompletion1 = new Mock();
+ var mockTextCompletion2 = new Mock();
+ var mockCompletionResult = new Mock();
+
+ mockTextCompletion1.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object });
+ mockTextCompletion2.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object });
+ mockCompletionResult.Setup(cr => cr.GetCompletionAsync(It.IsAny())).ReturnsAsync("llmResult");
+
+ var kernel = Kernel.Builder
+ .WithAIService("service1", mockTextCompletion1.Object, false)
+ .WithAIService("service2", mockTextCompletion2.Object, true)
+ .Build();
+
+ var templateConfig = new PromptTemplateConfig();
+ templateConfig.ModelSettings.Add(new AIRequestSettings() { ServiceId = "service1" });
+ var func = kernel.CreateSemanticFunction("template", templateConfig, "functionName", "pluginName");
+
+ // Act
+ await kernel.RunAsync(func);
+
+ // Assert
+ mockTextCompletion1.Verify(a => a.GetCompletionsAsync("template", It.IsAny(), It.IsAny()), Times.Once());
+ mockTextCompletion2.Verify(a => a.GetCompletionsAsync("template", It.IsAny(), It.IsAny()), Times.Never());
+ }
+
+ [Fact]
+ public async Task ItFailsIfInvalidServiceIdIsProvidedAsync()
+ {
+ // Arrange
+ var mockTextCompletion1 = new Mock();
+ var mockTextCompletion2 = new Mock();
+
+ var kernel = Kernel.Builder
+ .WithAIService("service1", mockTextCompletion1.Object, false)
+ .WithAIService("service2", mockTextCompletion2.Object, true)
+ .Build();
+
+ var templateConfig = new PromptTemplateConfig();
+ templateConfig.ModelSettings.Add(new AIRequestSettings() { ServiceId = "service3" });
+ var func = kernel.CreateSemanticFunction("template", templateConfig, "functionName", "pluginName");
+
+ // Act
+ var exception = await Assert.ThrowsAsync(() => kernel.RunAsync(func));
+
+ // Assert
+ Assert.Equal("Service of type Microsoft.SemanticKernel.AI.TextCompletion.ITextCompletion and name service3 not registered.", exception.Message);
+ }
+
+ [Theory]
+ [InlineData(new string[] { "service1" }, 1, new int[] { 1, 0, 0 })]
+ [InlineData(new string[] { "service2" }, 2, new int[] { 0, 1, 0 })]
+ [InlineData(new string[] { "service3" }, 0, new int[] { 0, 0, 1 })]
+ [InlineData(new string[] { "service4", "service1" }, 1, new int[] { 1, 0, 0 })]
+ public async Task ItUsesServiceIdByOrderAsync(string[] serviceIds, int defaultServiceIndex, int[] callCount)
+ {
+ // Arrange
+ var mockTextCompletion1 = new Mock();
+ var mockTextCompletion2 = new Mock();
+ var mockTextCompletion3 = new Mock();
+ var mockCompletionResult = new Mock();
+
+ mockTextCompletion1.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object });
+ mockTextCompletion2.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object });
+ mockTextCompletion3.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object });
+ mockCompletionResult.Setup(cr => cr.GetCompletionAsync(It.IsAny())).ReturnsAsync("llmResult");
+
+ var kernel = Kernel.Builder
+ .WithAIService("service1", mockTextCompletion1.Object, defaultServiceIndex == 0)
+ .WithAIService("service2", mockTextCompletion2.Object, defaultServiceIndex == 1)
+ .WithAIService("service3", mockTextCompletion3.Object, defaultServiceIndex == 2)
+ .Build();
+
+ var templateConfig = new PromptTemplateConfig();
+ foreach (var serviceId in serviceIds)
+ {
+ templateConfig.ModelSettings.Add(new AIRequestSettings() { ServiceId = serviceId });
+ }
+ var func = kernel.CreateSemanticFunction("template", templateConfig, "functionName", "pluginName");
+
+ // Act
+ await kernel.RunAsync(func);
+
+ // Assert
+ mockTextCompletion1.Verify(a => a.GetCompletionsAsync("template", It.Is(settings => settings.ServiceId == "service1"), It.IsAny()), Times.Exactly(callCount[0]));
+ mockTextCompletion2.Verify(a => a.GetCompletionsAsync("template", It.Is(settings => settings.ServiceId == "service2"), It.IsAny()), Times.Exactly(callCount[1]));
+ mockTextCompletion3.Verify(a => a.GetCompletionsAsync("template", It.Is(settings => settings.ServiceId == "service3"), It.IsAny()), Times.Exactly(callCount[2]));
+ }
+
+ [Fact]
+ public async Task ItUsesServiceIdWithJsonPromptTemplateConfigAsync()
+ {
+ // Arrange
+ var mockTextCompletion1 = new Mock();
+ var mockTextCompletion2 = new Mock();
+ var mockTextCompletion3 = new Mock();
+ var mockCompletionResult = new Mock();
+
+ mockTextCompletion1.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object });
+ mockTextCompletion2.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object });
+ mockTextCompletion3.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object });
+ mockCompletionResult.Setup(cr => cr.GetCompletionAsync(It.IsAny())).ReturnsAsync("llmResult");
+
+ var kernel = Kernel.Builder
+ .WithAIService("service1", mockTextCompletion1.Object, true)
+ .WithAIService("service2", mockTextCompletion2.Object, false)
+ .WithAIService("service3", mockTextCompletion3.Object, false)
+ .Build();
+
+ var json = @"{
+ ""schema"": 1,
+ ""description"": ""Semantic function"",
+ ""models"": [
+ {
+ ""service_id"": ""service2"",
+ ""max_tokens"": 100,
+ ""temperature"": 0.2,
+ ""top_p"": 0.0,
+ ""presence_penalty"": 0.0,
+ ""frequency_penalty"": 0.0,
+ ""stop_sequences"": [
+ ""\n""
+ ]
+ },
+ {
+ ""service_id"": ""service3"",
+ ""max_tokens"": 100,
+ ""temperature"": 0.4,
+ ""top_p"": 0.0,
+ ""presence_penalty"": 0.0,
+ ""frequency_penalty"": 0.0,
+ ""stop_sequences"": [
+ ""\n""
+ ]
+ }
+ ]
+}";
+
+ var templateConfig = PromptTemplateConfig.FromJson(json);
+ var func = kernel.CreateSemanticFunction("template", templateConfig, "functionName", "pluginName");
+
+ // Act
+ await kernel.RunAsync(func);
+
+ // Assert
+ mockTextCompletion1.Verify(a => a.GetCompletionsAsync("template", It.Is(settings => settings.ServiceId == "service1"), It.IsAny()), Times.Never());
+ mockTextCompletion2.Verify(a => a.GetCompletionsAsync("template", It.Is(settings => settings.ServiceId == "service2"), It.IsAny()), Times.Once());
+ mockTextCompletion3.Verify(a => a.GetCompletionsAsync("template", It.Is(settings => settings.ServiceId == "service3"), It.IsAny()), Times.Never());
+ }
+}
diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs
new file mode 100644
index 0000000000..5159695c30
--- /dev/null
+++ b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs
@@ -0,0 +1,232 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.SemanticKernel.AI;
+using Microsoft.SemanticKernel.AI.TextCompletion;
+using Microsoft.SemanticKernel.Diagnostics;
+using Microsoft.SemanticKernel.Functions;
+using Microsoft.SemanticKernel.Services;
+using Xunit;
+
+namespace SemanticKernel.UnitTests.Functions;
+public class OrderedIAIServiceConfigurationProviderTests
+{
+ [Fact]
+ public void ItThrowsAnSKExceptionForNoServices()
+ {
+ // Arrange
+ var serviceCollection = new AIServiceCollection();
+ var serviceProvider = serviceCollection.Build();
+ var modelSettings = new List();
+ var configurationProvider = new OrderedIAIServiceSelector();
+
+ // Act
+ // Assert
+ Assert.Throws(() => configurationProvider.SelectAIService(serviceProvider, modelSettings));
+ }
+
+ [Fact]
+ public void ItGetsAIServiceConfigurationForSingleAIService()
+ {
+ // Arrange
+ var serviceCollection = new AIServiceCollection();
+ serviceCollection.SetService(new AIService());
+ var serviceProvider = serviceCollection.Build();
+ var modelSettings = new List();
+ var configurationProvider = new OrderedIAIServiceSelector();
+
+ // Act
+ (var aiService, var defaultRequestSettings) = configurationProvider.SelectAIService(serviceProvider, modelSettings);
+
+ // Assert
+ Assert.NotNull(aiService);
+ Assert.Null(defaultRequestSettings);
+ }
+
+ [Fact]
+ public void ItGetsAIServiceConfigurationForSingleTextCompletion()
+ {
+ // Arrange
+ var serviceCollection = new AIServiceCollection();
+ serviceCollection.SetService(new TextCompletion());
+ var serviceProvider = serviceCollection.Build();
+ var modelSettings = new List();
+ var configurationProvider = new OrderedIAIServiceSelector();
+
+ // Act
+ (var aiService, var defaultRequestSettings) = configurationProvider.SelectAIService(serviceProvider, modelSettings);
+
+ // Assert
+ Assert.NotNull(aiService);
+ Assert.Null(defaultRequestSettings);
+ }
+
+ [Fact]
+ public void ItAIServiceConfigurationForTextCompletionByServiceId()
+ {
+ // Arrange
+ var serviceCollection = new AIServiceCollection();
+ serviceCollection.SetService("service1", new TextCompletion());
+ serviceCollection.SetService("service2", new TextCompletion());
+ var serviceProvider = serviceCollection.Build();
+ var modelSettings = new List();
+ var configurationProvider = new OrderedIAIServiceSelector();
+
+ // Act
+ (var aiService, var defaultRequestSettings) = configurationProvider.SelectAIService(serviceProvider, modelSettings);
+
+ // Assert
+ Assert.NotNull(aiService);
+ Assert.Null(defaultRequestSettings);
+ }
+
+ [Fact]
+ public void ItThrowsAnSKExceptionForNotFoundService()
+ {
+ // Arrange
+ var serviceCollection = new AIServiceCollection();
+ serviceCollection.SetService("service1", new TextCompletion());
+ serviceCollection.SetService("service2", new TextCompletion());
+ var serviceProvider = serviceCollection.Build();
+ var modelSettings = new List
+ {
+ new AIRequestSettings() { ServiceId = "service3" }
+ };
+ var configurationProvider = new OrderedIAIServiceSelector();
+
+ // Act
+ // Assert
+ Assert.Throws(() => configurationProvider.SelectAIService(serviceProvider, modelSettings));
+ }
+
+ [Fact]
+ public void ItUsesDefaultServiceForNullModelSettings()
+ {
+ // Arrange
+ var serviceCollection = new AIServiceCollection();
+ serviceCollection.SetService("service1", new TextCompletion());
+ serviceCollection.SetService("service2", new TextCompletion(), true);
+ var serviceProvider = serviceCollection.Build();
+ var configurationProvider = new OrderedIAIServiceSelector();
+
+ // Act
+ (var aiService, var defaultRequestSettings) = configurationProvider.SelectAIService(serviceProvider, null);
+
+ // Assert
+ Assert.Equal(serviceProvider.GetService("service2"), aiService);
+ Assert.Null(defaultRequestSettings);
+ }
+
+ [Fact]
+ public void ItUsesDefaultServiceForEmptyModelSettings()
+ {
+ // Arrange
+ var serviceCollection = new AIServiceCollection();
+ serviceCollection.SetService("service1", new TextCompletion());
+ serviceCollection.SetService("service2", new TextCompletion(), true);
+ var serviceProvider = serviceCollection.Build();
+ var modelSettings = new List();
+ var configurationProvider = new OrderedIAIServiceSelector();
+
+ // Act
+ (var aiService, var defaultRequestSettings) = configurationProvider.SelectAIService(serviceProvider, modelSettings);
+
+ // Assert
+ Assert.Equal(serviceProvider.GetService("service2"), aiService);
+ Assert.Null(defaultRequestSettings);
+ }
+
+ [Fact]
+ public void ItUsesDefaultServiceAndSettings()
+ {
+ // Arrange
+ var serviceCollection = new AIServiceCollection();
+ serviceCollection.SetService("service1", new TextCompletion());
+ serviceCollection.SetService("service2", new TextCompletion(), true);
+ var serviceProvider = serviceCollection.Build();
+ var modelSettings = new List
+ {
+ new AIRequestSettings()
+ };
+ var configurationProvider = new OrderedIAIServiceSelector();
+
+ // Act
+ (var aiService, var defaultRequestSettings) = configurationProvider.SelectAIService(serviceProvider, modelSettings);
+
+ // Assert
+ Assert.Equal(serviceProvider.GetService("service2"), aiService);
+ Assert.Equal(modelSettings[0], defaultRequestSettings);
+ }
+
+ [Fact]
+ public void ItUsesDefaultServiceAndSettingsEmptyServiceId()
+ {
+ // Arrange
+ var serviceCollection = new AIServiceCollection();
+ serviceCollection.SetService("service1", new TextCompletion());
+ serviceCollection.SetService("service2", new TextCompletion(), true);
+ var serviceProvider = serviceCollection.Build();
+ var modelSettings = new List
+ {
+ new AIRequestSettings() { ServiceId = "" }
+ };
+ var configurationProvider = new OrderedIAIServiceSelector();
+
+ // Act
+ (var aiService, var defaultRequestSettings) = configurationProvider.SelectAIService(serviceProvider, modelSettings);
+
+ // Assert
+ Assert.Equal(serviceProvider.GetService("service2"), aiService);
+ Assert.Equal(modelSettings[0], defaultRequestSettings);
+ }
+
+ [Theory]
+ [InlineData(new string[] { "service1" }, "service1")]
+ [InlineData(new string[] { "service2" }, "service2")]
+ [InlineData(new string[] { "service3" }, "service3")]
+ [InlineData(new string[] { "service4", "service1" }, "service1")]
+ public void ItGetsAIServiceConfigurationByOrder(string[] serviceIds, string expectedServiceId)
+ {
+ // Arrange
+ var serviceCollection = new AIServiceCollection();
+ serviceCollection.SetService("service1", new TextCompletion());
+ serviceCollection.SetService("service2", new TextCompletion());
+ serviceCollection.SetService("service3", new TextCompletion());
+ var serviceProvider = serviceCollection.Build();
+ var modelSettings = new List();
+ foreach (var serviceId in serviceIds)
+ {
+ modelSettings.Add(new AIRequestSettings() { ServiceId = serviceId });
+ }
+ var configurationProvider = new OrderedIAIServiceSelector();
+
+ // Act
+ (var aiService, var defaultRequestSettings) = configurationProvider.SelectAIService(serviceProvider, modelSettings);
+
+ // Assert
+ Assert.Equal(serviceProvider.GetService(expectedServiceId), aiService);
+ Assert.Equal(expectedServiceId, defaultRequestSettings!.ServiceId);
+ }
+
+ #region private
+ private sealed class AIService : IAIService
+ {
+ }
+
+ private sealed class TextCompletion : ITextCompletion
+ {
+ public Task> GetCompletionsAsync(string text, AIRequestSettings? requestSettings = null, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable GetStreamingCompletionsAsync(string text, AIRequestSettings? requestSettings = null, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ #endregion
+}
diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/SKContextTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/SKContextTests.cs
index 2686395430..dfb60d5716 100644
--- a/dotnet/src/SemanticKernel.UnitTests/Functions/SKContextTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/Functions/SKContextTests.cs
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Orchestration;
+using Microsoft.SemanticKernel.Services;
using Moq;
using Xunit;
@@ -22,7 +23,7 @@ public class SKContextTests
{
// Arrange
var variables = new ContextVariables();
- var target = new SKContext(new Mock().Object, variables);
+ var target = new SKContext(new Mock().Object, new Mock().Object, variables);
variables.Set("foo1", "bar1");
// Act
@@ -46,8 +47,8 @@ public class SKContextTests
// Arrange
IDictionary functions = KernelBuilder.Create().ImportFunctions(new Parrot(), "test");
this._functions.Setup(x => x.GetFunction("func")).Returns(functions["say"]);
- var (kernel, functionRunner) = this.SetupKernelMock(this._functions.Object);
- var target = new SKContext(functionRunner.Object, new ContextVariables(), this._functions.Object);
+ var (kernel, functionRunner, serviceProvider) = this.SetupKernelMock(this._functions.Object);
+ var target = new SKContext(functionRunner.Object, serviceProvider.Object, new ContextVariables(), this._functions.Object);
Assert.NotNull(target.Functions);
// Act
@@ -60,21 +61,22 @@ public class SKContextTests
Assert.Equal("ciao", result.GetValue());
}
- private (Mock kernelMock, Mock functionRunnerMock) SetupKernelMock(IReadOnlyFunctionCollection? functions = null)
+ private (Mock kernelMock, Mock functionRunnerMock, Mock serviceProviderMock) SetupKernelMock(IReadOnlyFunctionCollection? functions = null)
{
functions ??= new Mock().Object;
var kernel = new Mock();
var functionRunner = new Mock();
+ var serviceProvider = new Mock();
kernel.SetupGet(x => x.Functions).Returns(functions);
kernel.Setup(k => k.CreateNewContext(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Returns((contextVariables, skills, loggerFactory, culture) =>
{
- return new SKContext(functionRunner.Object, contextVariables);
+ return new SKContext(functionRunner.Object, serviceProvider.Object, contextVariables);
});
- return (kernel, functionRunner);
+ return (kernel, functionRunner, serviceProvider);
}
private sealed class Parrot
diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/SKFunctionTests2.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/SKFunctionTests2.cs
index 8a128a37a5..b717eb398c 100644
--- a/dotnet/src/SemanticKernel.UnitTests/Functions/SKFunctionTests2.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/Functions/SKFunctionTests2.cs
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Orchestration;
+using Microsoft.SemanticKernel.Services;
using Moq;
using Xunit;
@@ -1044,9 +1045,11 @@ public sealed class SKFunctionTests2
private SKContext MockContext(string input)
{
var functionRunner = new Mock();
+ var serviceProvider = new Mock();
return new SKContext(
functionRunner.Object,
+ serviceProvider.Object,
new ContextVariables(input)
);
}
diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/SemanticFunctionTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/SemanticFunctionTests.cs
index b80a68a8f2..4de7067cf1 100644
--- a/dotnet/src/SemanticKernel.UnitTests/Functions/SemanticFunctionTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/Functions/SemanticFunctionTests.cs
@@ -169,11 +169,10 @@ public class SemanticFunctionTests
public async Task RunAsyncHandlesPreInvocationAsync(int pipelineCount)
{
// Arrange
- var sut = Kernel.Builder.Build();
- var semanticFunction = sut.CreateSemanticFunction("Write a simple phrase about UnitTests");
var (mockTextResult, mockTextCompletion) = this.SetupMocks();
+ var sut = Kernel.Builder.WithAIService(null, mockTextCompletion.Object).Build();
+ var semanticFunction = sut.CreateSemanticFunction("Write a simple phrase about UnitTests");
- semanticFunction.SetAIService(() => mockTextCompletion.Object);
var invoked = 0;
sut.FunctionInvoking += (sender, e) =>
{
@@ -219,10 +218,9 @@ public class SemanticFunctionTests
public async Task RunAsyncHandlesPreInvocationCancelationDontRunSubsequentFunctionsInThePipelineAsync()
{
// Arrange
- var sut = Kernel.Builder.Build();
var (mockTextResult, mockTextCompletion) = this.SetupMocks();
+ var sut = Kernel.Builder.WithAIService(null, mockTextCompletion.Object).Build();
var semanticFunction = sut.CreateSemanticFunction("Write a simple phrase about UnitTests");
- semanticFunction.SetAIService(() => mockTextCompletion.Object);
var invoked = 0;
sut.FunctionInvoking += (sender, e) =>
@@ -268,11 +266,10 @@ public class SemanticFunctionTests
public async Task RunAsyncPreInvocationSkipDontTriggerInvokedHandlerAsync()
{
// Arrange
- var sut = Kernel.Builder.Build();
var (mockTextResult, mockTextCompletion) = this.SetupMocks();
+ var sut = Kernel.Builder.WithAIService(null, mockTextCompletion.Object).Build();
var semanticFunction1 = sut.CreateSemanticFunction("Write one phrase about UnitTests", functionName: "SkipMe");
var semanticFunction2 = sut.CreateSemanticFunction("Write two phrases about UnitTests", functionName: "DontSkipMe");
- semanticFunction2.SetAIService(() => mockTextCompletion.Object);
var invoked = 0;
var invoking = 0;
string invokedFunction = string.Empty;
@@ -309,11 +306,10 @@ public class SemanticFunctionTests
public async Task RunAsyncHandlesPostInvocationAsync(int pipelineCount)
{
// Arrange
- var sut = Kernel.Builder.Build();
- var semanticFunction = sut.CreateSemanticFunction("Write a simple phrase about UnitTests");
var (mockTextResult, mockTextCompletion) = this.SetupMocks();
+ var sut = Kernel.Builder.WithAIService(null, mockTextCompletion.Object).Build();
+ var semanticFunction = sut.CreateSemanticFunction("Write a simple phrase about UnitTests");
- semanticFunction.SetAIService(() => mockTextCompletion.Object);
var invoked = 0;
sut.FunctionInvoked += (sender, e) =>
@@ -338,11 +334,10 @@ public class SemanticFunctionTests
[Fact]
public async Task RunAsyncChangeVariableInvokingHandlerAsync()
{
- var sut = Kernel.Builder.Build();
+ var (mockTextResult, mockTextCompletion) = this.SetupMocks();
+ var sut = Kernel.Builder.WithAIService(null, mockTextCompletion.Object).Build();
var prompt = "Write a simple phrase about UnitTests {{$input}}";
var semanticFunction = sut.CreateSemanticFunction(prompt);
- var (mockTextResult, mockTextCompletion) = this.SetupMocks();
- semanticFunction.SetAIService(() => mockTextCompletion.Object);
var originalInput = "Importance";
var newInput = "Problems";
@@ -362,11 +357,10 @@ public class SemanticFunctionTests
[Fact]
public async Task RunAsyncChangeVariableInvokedHandlerAsync()
{
- var sut = Kernel.Builder.Build();
+ var (mockTextResult, mockTextCompletion) = this.SetupMocks();
+ var sut = Kernel.Builder.WithAIService(null, mockTextCompletion.Object).Build();
var prompt = "Write a simple phrase about UnitTests {{$input}}";
var semanticFunction = sut.CreateSemanticFunction(prompt);
- var (mockTextResult, mockTextCompletion) = this.SetupMocks();
- semanticFunction.SetAIService(() => mockTextCompletion.Object);
var originalInput = "Importance";
var newInput = "Problems";
@@ -396,15 +390,13 @@ public class SemanticFunctionTests
const string PluginName = "MyPlugin";
const string Prompt = "Write a simple phrase about UnitTests";
- var kernel = Kernel.Builder.Build();
+ var (mockTextResult, mockTextCompletion) = this.SetupMocks("Result3");
+ var kernel = Kernel.Builder.WithAIService(null, mockTextCompletion.Object).Build();
var function1 = SKFunction.FromNativeMethod(Method(Function1), pluginName: PluginName);
var function2 = SKFunction.FromNativeMethod(Method(Function2), pluginName: PluginName);
var function3 = kernel.CreateSemanticFunction(Prompt, functionName: "Function3", pluginName: PluginName);
- var (mockTextResult, mockTextCompletion) = this.SetupMocks("Result3");
-
- function3.SetAIService(() => mockTextCompletion.Object);
// Act
var kernelResult = await kernel.RunAsync(function1, function2, function3);
diff --git a/dotnet/src/SemanticKernel.UnitTests/Orchestration/FunctionResultTests.cs b/dotnet/src/SemanticKernel.UnitTests/Orchestration/FunctionResultTests.cs
index caf17df58a..62d455fd34 100644
--- a/dotnet/src/SemanticKernel.UnitTests/Orchestration/FunctionResultTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/Orchestration/FunctionResultTests.cs
@@ -2,6 +2,7 @@
using System;
using Microsoft.SemanticKernel.Orchestration;
+using Microsoft.SemanticKernel.Services;
using Moq;
using Xunit;
@@ -13,10 +14,11 @@ namespace SemanticKernel.UnitTests.Orchestration;
public class FunctionResultTests
{
private readonly Mock _functionRunner = new();
+ private readonly Mock _serviceProvider = new();
private SKContext CreateContext()
{
- return new SKContext(this._functionRunner.Object);
+ return new SKContext(this._functionRunner.Object, this._serviceProvider.Object);
}
[Fact]
diff --git a/dotnet/src/SemanticKernel.UnitTests/Orchestration/KernelResultTests.cs b/dotnet/src/SemanticKernel.UnitTests/Orchestration/KernelResultTests.cs
index f37023ab8f..17b68facf2 100644
--- a/dotnet/src/SemanticKernel.UnitTests/Orchestration/KernelResultTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/Orchestration/KernelResultTests.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SemanticKernel.Orchestration;
+using Microsoft.SemanticKernel.Services;
using Moq;
using Xunit;
@@ -15,11 +16,12 @@ namespace SemanticKernel.UnitTests.Orchestration;
public class KernelResultTests
{
private readonly Mock _functionRunner = new();
+ private readonly Mock _serviceProvider = new();
private readonly SKContext _context;
public KernelResultTests()
{
- this._context = new SKContext(this._functionRunner.Object);
+ this._context = new SKContext(this._functionRunner.Object, this._serviceProvider.Object);
}
[Fact]
diff --git a/dotnet/src/SemanticKernel.UnitTests/Planning/PlanSerializationTests.cs b/dotnet/src/SemanticKernel.UnitTests/Planning/PlanSerializationTests.cs
index bfea57b580..5791f6e236 100644
--- a/dotnet/src/SemanticKernel.UnitTests/Planning/PlanSerializationTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/Planning/PlanSerializationTests.cs
@@ -10,6 +10,7 @@ using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Planning;
+using Microsoft.SemanticKernel.Services;
using Moq;
using Xunit;
@@ -18,6 +19,7 @@ namespace SemanticKernel.UnitTests.Planning;
public sealed class PlanSerializationTests
{
private readonly Mock _functionRunner = new();
+ private readonly Mock _serviceProvider = new();
[Fact]
public void CanSerializePlan()
@@ -87,7 +89,7 @@ public sealed class PlanSerializationTests
// Arrange Mocks
var functions = new Mock();
- var returnContext = new SKContext(this._functionRunner.Object, new ContextVariables(stepOutput));
+ var returnContext = new SKContext(this._functionRunner.Object, this._serviceProvider.Object, new ContextVariables(stepOutput));
var mockFunction = new Mock();
mockFunction.Setup(x => x.InvokeAsync(It.IsAny(), null, It.IsAny()))
@@ -120,7 +122,7 @@ public sealed class PlanSerializationTests
var functions = new Mock