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