diff --git a/dotnet/src/Connectors/Connectors.OpenAI/AudioToText/OpenAIAudioToTextExecutionSettings.cs b/dotnet/src/Connectors/Connectors.OpenAI/AudioToText/OpenAIAudioToTextExecutionSettings.cs
index 8f2a744ba79b..319862435b13 100644
--- a/dotnet/src/Connectors/Connectors.OpenAI/AudioToText/OpenAIAudioToTextExecutionSettings.cs
+++ b/dotnet/src/Connectors/Connectors.OpenAI/AudioToText/OpenAIAudioToTextExecutionSettings.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
+using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.SemanticKernel.Text;
@@ -17,25 +18,61 @@ public sealed class OpenAIAudioToTextExecutionSettings : PromptExecutionSettings
/// Should be in format {filename}.{extension}
///
[JsonPropertyName("filename")]
- public string Filename { get; set; }
+ public string Filename
+ {
+ get => this._filename;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._filename = value;
+ }
+ }
///
/// An optional language of the audio data as two-letter ISO-639-1 language code (e.g. 'en' or 'es').
///
[JsonPropertyName("language")]
- public string? Language { get; set; }
+ public string? Language
+ {
+ get => this._language;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._language = value;
+ }
+ }
///
/// An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language.
///
[JsonPropertyName("prompt")]
- public string? Prompt { get; set; }
+ public string? Prompt
+ {
+ get => this._prompt;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._prompt = value;
+ }
+ }
///
/// The format of the transcript output, in one of these options: json, text, srt, verbose_json, or vtt. Default is 'json'.
///
[JsonPropertyName("response_format")]
- public string ResponseFormat { get; set; } = "json";
+ public string ResponseFormat
+ {
+ get => this._responseFormat;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._responseFormat = value;
+ }
+ }
///
/// The sampling temperature, between 0 and 1.
@@ -44,7 +81,16 @@ public sealed class OpenAIAudioToTextExecutionSettings : PromptExecutionSettings
/// Default is 0.
///
[JsonPropertyName("temperature")]
- public float Temperature { get; set; } = 0;
+ public float Temperature
+ {
+ get => this._temperature;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._temperature = value;
+ }
+ }
///
/// Creates an instance of class.
@@ -52,7 +98,21 @@ public sealed class OpenAIAudioToTextExecutionSettings : PromptExecutionSettings
/// Filename or identifier associated with audio data. Should be in format {filename}.{extension}
public OpenAIAudioToTextExecutionSettings(string filename)
{
- this.Filename = filename;
+ this._filename = filename;
+ }
+
+ ///
+ public override PromptExecutionSettings Clone()
+ {
+ return new OpenAIAudioToTextExecutionSettings(this.Filename)
+ {
+ ModelId = this.ModelId,
+ ExtensionData = this.ExtensionData is not null ? new Dictionary(this.ExtensionData) : null,
+ Temperature = this.Temperature,
+ ResponseFormat = this.ResponseFormat,
+ Language = this.Language,
+ Prompt = this.Prompt
+ };
}
///
@@ -83,4 +143,14 @@ public OpenAIAudioToTextExecutionSettings(string filename)
throw new ArgumentException($"Invalid execution settings, cannot convert to {nameof(OpenAIAudioToTextExecutionSettings)}", nameof(executionSettings));
}
+
+ #region private ================================================================================
+
+ private float _temperature = 0;
+ private string _responseFormat = "json";
+ private string _filename;
+ private string? _language;
+ private string? _prompt;
+
+ #endregion
}
diff --git a/dotnet/src/Connectors/Connectors.OpenAI/OpenAIPromptExecutionSettings.cs b/dotnet/src/Connectors/Connectors.OpenAI/OpenAIPromptExecutionSettings.cs
index 907193908d72..06848c2e4c80 100644
--- a/dotnet/src/Connectors/Connectors.OpenAI/OpenAIPromptExecutionSettings.cs
+++ b/dotnet/src/Connectors/Connectors.OpenAI/OpenAIPromptExecutionSettings.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -23,7 +24,16 @@ public sealed class OpenAIPromptExecutionSettings : PromptExecutionSettings
/// Default is 1.0.
///
[JsonPropertyName("temperature")]
- public double Temperature { get; set; } = 1;
+ public double Temperature
+ {
+ get => this._temperature;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._temperature = value;
+ }
+ }
///
/// TopP controls the diversity of the completion.
@@ -31,7 +41,16 @@ public sealed class OpenAIPromptExecutionSettings : PromptExecutionSettings
/// Default is 1.0.
///
[JsonPropertyName("top_p")]
- public double TopP { get; set; } = 1;
+ public double TopP
+ {
+ get => this._topP;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._topP = value;
+ }
+ }
///
/// Number between -2.0 and 2.0. Positive values penalize new tokens
@@ -39,7 +58,16 @@ public sealed class OpenAIPromptExecutionSettings : PromptExecutionSettings
/// model's likelihood to talk about new topics.
///
[JsonPropertyName("presence_penalty")]
- public double PresencePenalty { get; set; }
+ public double PresencePenalty
+ {
+ get => this._presencePenalty;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._presencePenalty = value;
+ }
+ }
///
/// Number between -2.0 and 2.0. Positive values penalize new tokens
@@ -47,19 +75,46 @@ public sealed class OpenAIPromptExecutionSettings : PromptExecutionSettings
/// the model's likelihood to repeat the same line verbatim.
///
[JsonPropertyName("frequency_penalty")]
- public double FrequencyPenalty { get; set; }
+ public double FrequencyPenalty
+ {
+ get => this._frequencyPenalty;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._frequencyPenalty = value;
+ }
+ }
///
/// The maximum number of tokens to generate in the completion.
///
[JsonPropertyName("max_tokens")]
- public int? MaxTokens { get; set; }
+ public int? MaxTokens
+ {
+ get => this._maxTokens;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._maxTokens = value;
+ }
+ }
///
/// Sequences where the completion will stop generating further tokens.
///
[JsonPropertyName("stop_sequences")]
- public IList? StopSequences { get; set; }
+ public IList? StopSequences
+ {
+ get => this._stopSequences;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._stopSequences = value;
+ }
+ }
///
/// How many completions to generate for each prompt. Default is 1.
@@ -67,7 +122,16 @@ public sealed class OpenAIPromptExecutionSettings : PromptExecutionSettings
/// Use carefully and ensure that you have reasonable settings for max_tokens and stop.
///
[JsonPropertyName("results_per_prompt")]
- public int ResultsPerPrompt { get; set; } = 1;
+ public int ResultsPerPrompt
+ {
+ get => this._resultsPerPrompt;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._resultsPerPrompt = value;
+ }
+ }
///
/// If specified, the system will make a best effort to sample deterministically such that repeated requests with the
@@ -75,7 +139,16 @@ public sealed class OpenAIPromptExecutionSettings : PromptExecutionSettings
///
[Experimental("SKEXP0013")]
[JsonPropertyName("seed")]
- public long? Seed { get; set; }
+ public long? Seed
+ {
+ get => this._seed;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._seed = value;
+ }
+ }
///
/// Gets or sets the response format to use for the completion.
@@ -85,7 +158,16 @@ public sealed class OpenAIPromptExecutionSettings : PromptExecutionSettings
///
[Experimental("SKEXP0013")]
[JsonPropertyName("response_format")]
- public object? ResponseFormat { get; set; }
+ public object? ResponseFormat
+ {
+ get => this._responseFormat;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._responseFormat = value;
+ }
+ }
///
/// The system prompt to use when generating text using a chat model.
@@ -95,8 +177,11 @@ public sealed class OpenAIPromptExecutionSettings : PromptExecutionSettings
public string ChatSystemPrompt
{
get => this._chatSystemPrompt;
+
set
{
+ this.ThrowIfFrozen();
+
if (string.IsNullOrWhiteSpace(value))
{
value = DefaultChatSystemPrompt;
@@ -109,7 +194,16 @@ public string ChatSystemPrompt
/// Modify the likelihood of specified tokens appearing in the completion.
///
[JsonPropertyName("token_selection_biases")]
- public IDictionary? TokenSelectionBiases { get; set; }
+ public IDictionary? TokenSelectionBiases
+ {
+ get => this._tokenSelectionBiases;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._tokenSelectionBiases = value;
+ }
+ }
///
/// Gets or sets the behavior for how tool calls are handled.
@@ -141,12 +235,68 @@ public string ChatSystemPrompt
/// the function, and sending back the result. The intermediate messages will be retained in the
/// if an instance was provided.
///
- public ToolCallBehavior? ToolCallBehavior { get; set; }
+ public ToolCallBehavior? ToolCallBehavior
+ {
+ get => this._toolCallBehavior;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._toolCallBehavior = value;
+ }
+ }
///
/// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse
///
- public string? User { get; set; }
+ public string? User
+ {
+ get => this._user;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._user = value;
+ }
+ }
+
+ ///
+ public override void Freeze()
+ {
+ base.Freeze();
+
+ if (this._stopSequences is not null)
+ {
+ this._stopSequences = new ReadOnlyCollection(this._stopSequences);
+ }
+ if (this._tokenSelectionBiases is not null)
+ {
+ this._tokenSelectionBiases = new ReadOnlyDictionary(this._tokenSelectionBiases);
+ }
+ }
+
+ ///
+ public override PromptExecutionSettings Clone()
+ {
+ return new OpenAIPromptExecutionSettings()
+ {
+ ModelId = this.ModelId,
+ ExtensionData = this.ExtensionData is not null ? new Dictionary(this.ExtensionData) : null,
+ Temperature = this.Temperature,
+ TopP = this.TopP,
+ PresencePenalty = this.PresencePenalty,
+ FrequencyPenalty = this.FrequencyPenalty,
+ MaxTokens = this.MaxTokens,
+ StopSequences = this.StopSequences is not null ? new List(this.StopSequences) : null,
+ ResultsPerPrompt = this.ResultsPerPrompt,
+ Seed = this.Seed,
+ ResponseFormat = this.ResponseFormat,
+ TokenSelectionBiases = this.TokenSelectionBiases is not null ? new Dictionary(this.TokenSelectionBiases) : null,
+ ToolCallBehavior = this.ToolCallBehavior,
+ User = this.User,
+ ChatSystemPrompt = this.ChatSystemPrompt
+ };
+ }
///
/// Default value for chat system property.
@@ -212,6 +362,18 @@ public static OpenAIPromptExecutionSettings FromExecutionSettingsWithData(Prompt
#region private ================================================================================
+ private double _temperature = 1;
+ private double _topP = 1;
+ private double _presencePenalty;
+ private double _frequencyPenalty;
+ private int? _maxTokens;
+ private IList? _stopSequences;
+ private int _resultsPerPrompt = 1;
+ private long? _seed;
+ private object? _responseFormat;
+ private IDictionary? _tokenSelectionBiases;
+ private ToolCallBehavior? _toolCallBehavior;
+ private string? _user;
private string _chatSystemPrompt = DefaultChatSystemPrompt;
#endregion
diff --git a/dotnet/src/Connectors/Connectors.OpenAI/TextToAudio/OpenAITextToAudioExecutionSettings.cs b/dotnet/src/Connectors/Connectors.OpenAI/TextToAudio/OpenAITextToAudioExecutionSettings.cs
index 2e4d4350e800..55507c7e175e 100644
--- a/dotnet/src/Connectors/Connectors.OpenAI/TextToAudio/OpenAITextToAudioExecutionSettings.cs
+++ b/dotnet/src/Connectors/Connectors.OpenAI/TextToAudio/OpenAITextToAudioExecutionSettings.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
+using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.SemanticKernel.Text;
@@ -16,19 +17,46 @@ public sealed class OpenAITextToAudioExecutionSettings : PromptExecutionSettings
/// The voice to use when generating the audio. Supported voices are alloy, echo, fable, onyx, nova, and shimmer.
///
[JsonPropertyName("voice")]
- public string Voice { get; set; }
+ public string Voice
+ {
+ get => this._voice;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._voice = value;
+ }
+ }
///
/// The format to audio in. Supported formats are mp3, opus, aac, and flac.
///
[JsonPropertyName("response_format")]
- public string ResponseFormat { get; set; } = "mp3";
+ public string ResponseFormat
+ {
+ get => this._responseFormat;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._responseFormat = value;
+ }
+ }
///
/// The speed of the generated audio. Select a value from 0.25 to 4.0. 1.0 is the default.
///
[JsonPropertyName("speed")]
- public float Speed { get; set; } = 1.0f;
+ public float Speed
+ {
+ get => this._speed;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._speed = value;
+ }
+ }
///
/// Creates an instance of class.
@@ -36,7 +64,19 @@ public sealed class OpenAITextToAudioExecutionSettings : PromptExecutionSettings
/// The voice to use when generating the audio. Supported voices are alloy, echo, fable, onyx, nova, and shimmer.
public OpenAITextToAudioExecutionSettings(string voice)
{
- this.Voice = voice;
+ this._voice = voice;
+ }
+
+ ///
+ public override PromptExecutionSettings Clone()
+ {
+ return new OpenAITextToAudioExecutionSettings(this.Voice)
+ {
+ ModelId = this.ModelId,
+ ExtensionData = this.ExtensionData is not null ? new Dictionary(this.ExtensionData) : null,
+ Speed = this.Speed,
+ ResponseFormat = this.ResponseFormat
+ };
}
///
@@ -67,4 +107,12 @@ public OpenAITextToAudioExecutionSettings(string voice)
throw new ArgumentException($"Invalid execution settings, cannot convert to {nameof(OpenAITextToAudioExecutionSettings)}", nameof(executionSettings));
}
+
+ #region private ================================================================================
+
+ private float _speed = 1.0f;
+ private string _responseFormat = "mp3";
+ private string _voice;
+
+ #endregion
}
diff --git a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/OpenAIPromptExecutionSettingsTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/OpenAIPromptExecutionSettingsTests.cs
index 1cdee512adbc..148c7538d06f 100644
--- a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/OpenAIPromptExecutionSettingsTests.cs
+++ b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/OpenAIPromptExecutionSettingsTests.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.SemanticKernel;
@@ -63,7 +64,7 @@ public void ItCanUseOpenAIExecutionSettings()
// Arrange
PromptExecutionSettings actualSettings = new()
{
- ExtensionData = new() {
+ ExtensionData = new Dictionary() {
{ "max_tokens", 1000 },
{ "temperature", 0 }
}
@@ -170,6 +171,51 @@ public void ItUsesCorrectChatSystemPrompt(string chatSystemPrompt, string expect
Assert.Equal(expectedChatSystemPrompt, settings.ChatSystemPrompt);
}
+ [Fact]
+ public void PromptExecutionSettingsCloneWorksAsExpected()
+ {
+ // Arrange
+ string configPayload = @"{
+ ""max_tokens"": 60,
+ ""temperature"": 0.5,
+ ""top_p"": 0.0,
+ ""presence_penalty"": 0.0,
+ ""frequency_penalty"": 0.0
+ }";
+ var executionSettings = JsonSerializer.Deserialize(configPayload);
+
+ // Act
+ var clone = executionSettings!.Clone();
+
+ // Assert
+ Assert.NotNull(clone);
+ Assert.Equal(executionSettings.ModelId, clone.ModelId);
+ Assert.Equivalent(executionSettings.ExtensionData, clone.ExtensionData);
+ }
+
+ [Fact]
+ public void PromptExecutionSettingsFreezeWorksAsExpected()
+ {
+ // Arrange
+ string configPayload = @"{
+ ""max_tokens"": 60,
+ ""temperature"": 0.5,
+ ""top_p"": 0.0,
+ ""presence_penalty"": 0.0,
+ ""frequency_penalty"": 0.0
+ }";
+ var executionSettings = JsonSerializer.Deserialize(configPayload);
+
+ // Act
+ executionSettings!.Freeze();
+
+ // Assert
+ Assert.True(executionSettings.IsFrozen);
+ Assert.Throws(() => executionSettings.ModelId = "gpt-4");
+ Assert.Throws(() => executionSettings.ResultsPerPrompt = 2);
+ Assert.Throws(() => executionSettings.Temperature = 1);
+ }
+
private static void AssertExecutionSettings(OpenAIPromptExecutionSettings executionSettings)
{
Assert.NotNull(executionSettings);
diff --git a/dotnet/src/Functions/Functions.Yaml/PromptExecutionSettingsNodeDeserializer.cs b/dotnet/src/Functions/Functions.Yaml/PromptExecutionSettingsNodeDeserializer.cs
index 5e8269eae7e0..5bd7b839b068 100644
--- a/dotnet/src/Functions/Functions.Yaml/PromptExecutionSettingsNodeDeserializer.cs
+++ b/dotnet/src/Functions/Functions.Yaml/PromptExecutionSettingsNodeDeserializer.cs
@@ -32,7 +32,7 @@ public bool Deserialize(IParser reader, Type expectedType, Func()).Add(kv.Key, kv.Value);
break;
}
}
diff --git a/dotnet/src/Plugins/Plugins.Core/ConversationSummaryPlugin.cs b/dotnet/src/Plugins/Plugins.Core/ConversationSummaryPlugin.cs
index 0c540e64b915..7240a10c31ec 100644
--- a/dotnet/src/Plugins/Plugins.Core/ConversationSummaryPlugin.cs
+++ b/dotnet/src/Plugins/Plugins.Core/ConversationSummaryPlugin.cs
@@ -28,7 +28,7 @@ public ConversationSummaryPlugin()
{
PromptExecutionSettings settings = new()
{
- ExtensionData = new()
+ ExtensionData = new Dictionary()
{
{ "Temperature", 0.1 },
{ "TopP", 0.5 },
diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/PromptExecutionSettings.cs b/dotnet/src/SemanticKernel.Abstractions/AI/PromptExecutionSettings.cs
index 24d0ba1a57ff..fad813b35c86 100644
--- a/dotnet/src/SemanticKernel.Abstractions/AI/PromptExecutionSettings.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/AI/PromptExecutionSettings.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Text.Json.Serialization;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.TextGeneration;
@@ -30,7 +32,16 @@ public class PromptExecutionSettings
/// This identifies the AI model these settings are configured for e.g., gpt-4, gpt-3.5-turbo
///
[JsonPropertyName("model_id")]
- public string? ModelId { get; set; }
+ public string? ModelId
+ {
+ get => this._modelId;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._modelId = value;
+ }
+ }
///
/// Extra properties that may be included in the serialized execution settings.
@@ -39,5 +50,67 @@ public class PromptExecutionSettings
/// Avoid using this property if possible. Instead, use one of the classes that extends .
///
[JsonExtensionData]
- public Dictionary? ExtensionData { get; set; }
+ public IDictionary? ExtensionData
+ {
+ get => this._extensionData;
+
+ set
+ {
+ this.ThrowIfFrozen();
+ this._extensionData = value;
+ }
+ }
+
+ ///
+ /// Gets a value that indicates whether the are currently modifiable.
+ ///
+ public bool IsFrozen
+ {
+ get => this._isFrozen;
+ }
+
+ ///
+ /// Makes the current unmodifiable and sets its IsFrozen property to true.
+ ///
+ public virtual void Freeze()
+ {
+ this._isFrozen = true;
+
+ if (this._extensionData is not null)
+ {
+ this._extensionData = new ReadOnlyDictionary(this._extensionData);
+ }
+ }
+
+ ///
+ /// Creates a new object that is a copy of the current instance.
+ ///
+ public virtual PromptExecutionSettings Clone()
+ {
+ return new()
+ {
+ ModelId = this.ModelId,
+ ExtensionData = this.ExtensionData is not null ? new Dictionary(this.ExtensionData) : null
+ };
+ }
+
+ ///
+ /// Throws an if the are frozen.
+ ///
+ ///
+ protected void ThrowIfFrozen()
+ {
+ if (this._isFrozen)
+ {
+ throw new InvalidOperationException("PromptExecutionSettings are frozen and cannot be modified.");
+ }
+ }
+
+ #region private ================================================================================
+
+ private string? _modelId;
+ private IDictionary? _extensionData;
+ private bool _isFrozen;
+
+ #endregion
}
diff --git a/dotnet/src/SemanticKernel.Abstractions/CompatibilitySuppressions.xml b/dotnet/src/SemanticKernel.Abstractions/CompatibilitySuppressions.xml
new file mode 100644
index 000000000000..450d615a98e7
--- /dev/null
+++ b/dotnet/src/SemanticKernel.Abstractions/CompatibilitySuppressions.xml
@@ -0,0 +1,8 @@
+
+
+
+ CP0002
+ M:Microsoft.SemanticKernel.PromptExecutionSettings.get_ExtensionData
+ true
+
+
\ No newline at end of file
diff --git a/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs b/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs
index 30cace107c82..01ad6d502f68 100644
--- a/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Metrics;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@@ -73,7 +74,10 @@ public abstract class KernelFunction
///
/// Gets the prompt execution settings.
///
- internal IReadOnlyDictionary? ExecutionSettings { get; }
+ ///
+ /// The instances of are frozen and cannot be modified.
+ ///
+ public IReadOnlyDictionary? ExecutionSettings { get; }
///
/// Initializes a new instance of the class.
@@ -97,7 +101,13 @@ internal KernelFunction(string name, string description, IReadOnlyList entry.Key,
+ entry => { var clone = entry.Value.Clone(); clone.Freeze(); return clone; });
+ }
}
///
diff --git a/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/InputVariable.cs b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/InputVariable.cs
index 9f0d3e19594b..5d57a532655c 100644
--- a/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/InputVariable.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/InputVariable.cs
@@ -15,6 +15,28 @@ public sealed class InputVariable
/// The description of the variable.
private string _description = string.Empty;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public InputVariable()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class from an existing instance.
+ ///
+ ///
+ public InputVariable(InputVariable inputVariable)
+ {
+ Verify.NotNull(inputVariable);
+
+ this.Name = inputVariable.Name;
+ this.Description = inputVariable.Description;
+ this.Default = inputVariable.Default;
+ this.IsRequired = inputVariable.IsRequired;
+ this.JsonSchema = inputVariable.JsonSchema;
+ }
+
///
/// Gets or sets the name of the variable.
///
diff --git a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs
index 4415afea8057..c9b0e7b87456 100644
--- a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs
+++ b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Metrics;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading;
@@ -234,13 +235,13 @@ private KernelFunctionFromPrompt(
this._logger = loggerFactory?.CreateLogger(typeof(KernelFunctionFactory)) ?? NullLogger.Instance;
this._promptTemplate = template;
- this._promptConfig = promptConfig;
+ this._inputVariables = promptConfig.InputVariables.Select(iv => new InputVariable(iv)).ToList();
}
#region private
private readonly ILogger _logger;
- private readonly PromptTemplateConfig _promptConfig;
+ private readonly List _inputVariables;
private readonly IPromptTemplate _promptTemplate;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
@@ -264,7 +265,7 @@ private KernelFunctionFromPrompt(
/// Add default values to the arguments if an argument is not defined
private void AddDefaultValues(KernelArguments arguments)
{
- foreach (var parameter in this._promptConfig.InputVariables)
+ foreach (var parameter in this._inputVariables)
{
if (!arguments.ContainsName(parameter.Name) && parameter.Default != null)
{
diff --git a/dotnet/src/SemanticKernel.UnitTests/AI/PromptExecutionSettingsTests.cs b/dotnet/src/SemanticKernel.UnitTests/AI/PromptExecutionSettingsTests.cs
new file mode 100644
index 000000000000..20807a53ef1a
--- /dev/null
+++ b/dotnet/src/SemanticKernel.UnitTests/AI/PromptExecutionSettingsTests.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Text.Json;
+using Microsoft.SemanticKernel;
+using Xunit;
+
+namespace SemanticKernel.UnitTests.AI;
+public class PromptExecutionSettingsTests
+{
+ [Fact]
+ public void PromptExecutionSettingsCloneWorksAsExpected()
+ {
+ // Arrange
+ string configPayload = @"{
+ ""max_tokens"": 60,
+ ""temperature"": 0.5,
+ ""top_p"": 0.0,
+ ""presence_penalty"": 0.0,
+ ""frequency_penalty"": 0.0
+ }";
+ var executionSettings = JsonSerializer.Deserialize(configPayload);
+
+ // Act
+ var clone = executionSettings!.Clone();
+
+ // Assert
+ Assert.NotNull(clone);
+ Assert.Equal(executionSettings.ModelId, clone.ModelId);
+ Assert.Equivalent(executionSettings.ExtensionData, clone.ExtensionData);
+ }
+
+ [Fact]
+ public void PromptExecutionSettingsFreezeWorksAsExpected()
+ {
+ // Arrange
+ string configPayload = @"{
+ ""max_tokens"": 60,
+ ""temperature"": 0.5,
+ ""top_p"": 0.0,
+ ""presence_penalty"": 0.0,
+ ""frequency_penalty"": 0.0
+ }";
+ var executionSettings = JsonSerializer.Deserialize(configPayload);
+
+ // Act
+ executionSettings!.Freeze();
+
+ // Assert
+ Assert.True(executionSettings.IsFrozen);
+ Assert.Throws(() => executionSettings.ModelId = "gpt-4");
+ Assert.NotNull(executionSettings.ExtensionData);
+ Assert.Throws(() => executionSettings.ExtensionData.Add("results_per_prompt", 2));
+ Assert.Throws(() => executionSettings.ExtensionData["temperature"] = 1);
+ }
+}
diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedAIServiceSelectorTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedAIServiceSelectorTests.cs
index 9ae18b669235..b32eae6d48de 100644
--- a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedAIServiceSelectorTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedAIServiceSelectorTests.cs
@@ -85,7 +85,9 @@ public void ItGetsAIServiceConfigurationForTextGenerationByServiceId()
// Assert
Assert.Equal(kernel.GetRequiredService("service2"), aiService);
- Assert.Equal(executionSettings, defaultExecutionSettings);
+ var expectedExecutionSettings = executionSettings.Clone();
+ expectedExecutionSettings.Freeze();
+ Assert.Equivalent(expectedExecutionSettings, defaultExecutionSettings);
}
[Fact]
@@ -144,7 +146,9 @@ public void ItUsesDefaultServiceAndSettingsForDefaultExecutionSettings()
// Assert
Assert.Equal(kernel.GetRequiredService("service2"), aiService);
- Assert.Equal(executionSettings, defaultExecutionSettings);
+ var expectedExecutionSettings = executionSettings.Clone();
+ expectedExecutionSettings.Freeze();
+ Assert.Equivalent(expectedExecutionSettings, defaultExecutionSettings);
}
[Fact]
@@ -165,7 +169,9 @@ public void ItUsesDefaultServiceAndSettingsForDefaultId()
// Assert
Assert.Equal(kernel.GetRequiredService("service2"), aiService);
- Assert.Equal(executionSettings, defaultExecutionSettings);
+ var expectedExecutionSettings = executionSettings.Clone();
+ expectedExecutionSettings.Freeze();
+ Assert.Equivalent(expectedExecutionSettings, defaultExecutionSettings);
}
[Theory]
@@ -222,7 +228,9 @@ public void ItGetsAIServiceConfigurationForTextGenerationByModelId()
// Assert
Assert.NotNull(aiService);
Assert.Equal("model2", aiService.GetModelId());
- Assert.Equal(executionSettings, defaultExecutionSettings);
+ var expectedExecutionSettings = executionSettings.Clone();
+ expectedExecutionSettings.Freeze();
+ Assert.Equivalent(expectedExecutionSettings, defaultExecutionSettings);
}
#region private