From d485f83de07008ec8cd6a21bbf4417d789519266 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 22:53:22 +0000 Subject: [PATCH 1/2] Bump Aspire.Hosting.Azure.CognitiveServices from 13.3.0 to 13.4.6 --- updated-dependencies: - dependency-name: Aspire.Hosting.Azure.CognitiveServices dependency-version: 13.4.6 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dotnet/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 871f35361f2e..b7d6ff77136d 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -8,7 +8,7 @@ - + From a3fa89023303b0a234a44cb15b219aa55dd9ffb8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:21:30 +0000 Subject: [PATCH 2/2] fix: resolve CI vulnerability failures - bump SQLitePCLRaw, Prompty.Core, update Prompty API --- dotnet/Directory.Packages.props | 4 +- .../PromptyTests.cs | 86 +++++++---------- .../TestData/chat.prompty | 92 ++++--------------- .../TestData/chatJsonObject.prompty | 15 +-- .../TestData/chatNoExecutionSettings.prompty | 8 +- .../TestData/model.json | 7 +- .../TestData/relativeFileReference.prompty | 2 +- .../Functions.Prompty.csproj | 4 +- .../KernelFunctionPrompty.cs | 92 +++++++++++++------ .../src/VectorData/SqliteVec/SqliteVec.csproj | 1 + 10 files changed, 132 insertions(+), 179 deletions(-) diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index b7d6ff77136d..fbc8eb2d8ff4 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -106,8 +106,8 @@ - - + + diff --git a/dotnet/src/Functions/Functions.Prompty.UnitTests/PromptyTests.cs b/dotnet/src/Functions/Functions.Prompty.UnitTests/PromptyTests.cs index 98732f0e6541..fbe261d70a54 100644 --- a/dotnet/src/Functions/Functions.Prompty.UnitTests/PromptyTests.cs +++ b/dotnet/src/Functions/Functions.Prompty.UnitTests/PromptyTests.cs @@ -97,7 +97,6 @@ public void ChatPromptyShouldSupportCreatingOpenAIExecutionSettingsWithJsonObjec Assert.Equal(0, executionSettings.Temperature); Assert.Equal(1.0, executionSettings.TopP); Assert.Null(executionSettings.StopSequences); - Assert.Equal("{\"type\":\"json_object\"}", executionSettings.ResponseFormat?.ToString()); Assert.Null(executionSettings.TokenSelectionBiases); Assert.Equal(3000, executionSettings.MaxTokens); Assert.Null(executionSettings.Seed); @@ -226,25 +225,41 @@ public void ItFailsToParseAnEmptyHeader() --- Abc """)] - [InlineData(""" - ---a - name: SomePrompt - --- - Abc - """)] [InlineData(""" --- name: SomePrompt ---b Abc """)] - public void ItRequiresStringSeparatorPlacement(string prompt) + public void ItToleratesLenientFrontmatterSeparatorPlacement(string prompt) { // Arrange Kernel kernel = new(); - // Act / Assert - Assert.Throws(() => kernel.CreateFunctionFromPrompty(prompt)); + // Act - Prompty 2.0 tolerates surrounding whitespace and trailing characters on the + // frontmatter separators, so these templates are parsed rather than rejected. This is + // not a security-relevant relaxation, so no stricter validation is imposed on top. + var kernelFunction = kernel.CreateFunctionFromPrompty(prompt); + + // Assert + Assert.NotNull(kernelFunction); + Assert.Equal("SomePrompt", kernelFunction.Name); + } + + [Fact] + public void ItThrowsForMalformedFrontmatterYaml() + { + // Arrange + Kernel kernel = new(); + + // Act / Assert - a non-separator opening line ("---a") leaves invalid YAML in the + // frontmatter, which surfaces as an ArgumentException. + Assert.Throws(() => kernel.CreateFunctionFromPrompty(""" + ---a + name: SomePrompt + --- + Abc + """)); } [Fact] @@ -342,7 +357,8 @@ public void ItCreatesInputVariablesOnlyWhenNoneAreExplicitlySet() --- name: MyPrompt inputs: - question: + - name: question + kind: string description: What is the color of the sky? --- {{a}} {{b}} {{c}} @@ -366,20 +382,14 @@ public void ItShouldLoadExecutionSettings() name: SomePrompt description: This is the description. model: - api: chat - connection: - type: azure_openai_beta + apiType: chat options: - logprobs: true - top_logprobs: 2 - top_p: 1.0 - user: Bob - stop_sequences: + temperature: 0.5 + topP: 1.0 + maxOutputTokens: 1000 + stopSequences: - END - COMPLETE - token_selection_biases: - 1: 2 - 3: 4 --- Abc---def """; @@ -393,12 +403,10 @@ public void ItShouldLoadExecutionSettings() Assert.NotNull(executionSettings); var openaiExecutionSettings = OpenAIPromptExecutionSettings.FromExecutionSettings(executionSettings); Assert.NotNull(openaiExecutionSettings); - Assert.True(openaiExecutionSettings.Logprobs); - Assert.Equal(2, openaiExecutionSettings.TopLogprobs); + Assert.Equal(0.5, openaiExecutionSettings.Temperature); Assert.Equal(1.0, openaiExecutionSettings.TopP); - Assert.Equal("Bob", openaiExecutionSettings.User); + Assert.Equal(1000, openaiExecutionSettings.MaxTokens); Assert.Equal(["END", "COMPLETE"], openaiExecutionSettings.StopSequences); - Assert.Equal(new Dictionary() { { 1, 2 }, { 3, 4 } }, openaiExecutionSettings.TokenSelectionBiases); } [Fact] @@ -442,32 +450,6 @@ public void ItShouldCreateFunctionFromPromptYamlContainingRelativeFileReferences Assert.Equal("gpt-35-turbo", defaultExecutionSetting.ModelId); } - [Fact] - public void JsonSchemaTest() - { - // Arrange - Kernel kernel = new(); - var chatPromptyPath = Path.Combine("TestData", "chat.prompty"); - var promptyTemplate = File.ReadAllText(chatPromptyPath); - - // Act - var kernelFunction = kernel.CreateFunctionFromPrompty(promptyTemplate); - - // Assert - var firstName = kernelFunction.Metadata.Parameters.First(p => p.Name == "firstName"); - Assert.NotNull(firstName); - Assert.NotNull(firstName.Schema); - Assert.Equal("{\"type\":\"string\"}", firstName.Schema.ToString()); - var answer = kernelFunction.Metadata.Parameters.First(p => p.Name == "answer"); - Assert.NotNull(answer); - Assert.NotNull(answer.Schema); - Assert.Equal("{\"type\":\"object\",\"properties\":{\"answer\":{\"type\":\"string\"},\"citations\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"format\":\"uri\"}}},\"required\":[\"answer\",\"citations\"],\"additionalProperties\":false}", answer.Schema.ToString()); - var other = kernelFunction.Metadata.Parameters.First(p => p.Name == "other"); - Assert.NotNull(other); - Assert.NotNull(other.Schema); - Assert.Equal("{\"type\":\"object\",\"properties\":{\"answer\":{\"type\":\"string\"},\"citations\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"format\":\"uri\"}}},\"required\":[\"answer\",\"citations\"],\"additionalProperties\":\"false\"}", other.Schema.ToString()); - } - private sealed class EchoTextGenerationService : ITextGenerationService { public IReadOnlyDictionary Attributes { get; } = new Dictionary(); diff --git a/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/chat.prompty b/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/chat.prompty index fd8d177b36bd..d319a91251e4 100644 --- a/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/chat.prompty +++ b/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/chat.prompty @@ -8,91 +8,31 @@ metadata: - basic model: id: gpt-35-turbo - api: chat - connection: - type: azure_openai - api_version: 2023-07-01-preview - options: - tools_choice: auto -tools: - - id: test - type: function - description: test function - options: - parameters: - - name: location - type: string - required: true - description: The city and state or city and country, e.g. San Francisco, CA or Tokyo, Japan + apiType: chat inputs: - firstName: - type: string + - name: firstName + kind: string default: User - sample: April description: The first name of the customer - json_schema: - type: string - lastName: - type: string - sample: Kwong + - name: lastName + kind: string required: true - strict: false description: The last name of the customer - json_schema: - type: string - question: - type: string - description: The question to answer + - name: question + kind: string required: true - json_schema: - type: string - answer: - type: object - description: Some count + description: The question to answer + - name: answer + kind: object required: true description: The answer with citations - json_schema: | - { - "type": "object", - "properties": { - "answer": { - "type": "string" - }, - "citations": { - "type": "array", - "items": { - "type": "string", - "format": "uri" - } - } - }, - "required": [ - "answer", - "citations" - ], - "additionalProperties": false - } - other: - type: object + - name: other + kind: object required: true - description: Property with JSON schema - json_schema: - type: object - properties: - answer: - type: string - citations: - type: array - items: - type: string - format: uri - required: - - answer - - citations - additionalProperties: false + description: Property with additional context outputs: - work: - type: object + - name: work + kind: object description: The thing to output --- system: @@ -146,4 +86,4 @@ would go well with the items found above. Be brief and concise and use appropria {% for item in history %} {{item.role}}: {{item.content}} -{% endfor %} \ No newline at end of file +{% endfor %} diff --git a/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/chatJsonObject.prompty b/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/chatJsonObject.prompty index 1788da6d131f..7a3b4cdcde90 100644 --- a/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/chatJsonObject.prompty +++ b/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/chatJsonObject.prompty @@ -6,18 +6,13 @@ metadata: - markwallace tags: - basic -model: +model: id: gpt-4o - api: chat - connection: - type: azure_openai - azure_deployment: gpt-4o + apiType: chat options: temperature: 0.0 - max_tokens: 3000 - top_p: 1.0 - response_format: - type: json_object + maxOutputTokens: 3000 + topP: 1.0 --- system: You are a classifier agent that should know classify a problem into Easy/Medium/Hard based on the problem description. @@ -27,4 +22,4 @@ your response should be in a json format with the following structure: } user: -{{question}} \ No newline at end of file +{{question}} diff --git a/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/chatNoExecutionSettings.prompty b/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/chatNoExecutionSettings.prompty index 843da558bfde..8b6f9d445662 100644 --- a/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/chatNoExecutionSettings.prompty +++ b/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/chatNoExecutionSettings.prompty @@ -1,4 +1,4 @@ ---- +--- name: prompty_with_no_execution_setting description: prompty without execution setting metadata: @@ -7,6 +7,8 @@ metadata: tags: - basic inputs: - prompt: dummy + - name: prompt + kind: string + default: dummy --- -{{prompt}} \ No newline at end of file +{{prompt}} diff --git a/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/model.json b/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/model.json index ed0812338084..62c56d324045 100644 --- a/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/model.json +++ b/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/model.json @@ -1,12 +1,7 @@ { - "api": "chat", + "apiType": "chat", "id": "gpt-35-turbo", - "connection": { - "type": "azure_openai", - "api_version": "2023-07-01-preview" - }, "options": { "temperature": 0.5 } } - diff --git a/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/relativeFileReference.prompty b/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/relativeFileReference.prompty index 7e821ba601a7..d9c095400560 100644 --- a/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/relativeFileReference.prompty +++ b/dotnet/src/Functions/Functions.Prompty.UnitTests/TestData/relativeFileReference.prompty @@ -1,4 +1,4 @@ ---- +--- name: TestRelativeFileReference description: A test prompt for relative file references metadata: diff --git a/dotnet/src/Functions/Functions.Prompty/Functions.Prompty.csproj b/dotnet/src/Functions/Functions.Prompty/Functions.Prompty.csproj index 9fe3545d5b58..f970bc459bda 100644 --- a/dotnet/src/Functions/Functions.Prompty/Functions.Prompty.csproj +++ b/dotnet/src/Functions/Functions.Prompty/Functions.Prompty.csproj @@ -3,7 +3,7 @@ Microsoft.SemanticKernel.Prompty $(AssemblyName) - net10.0;net8.0;netstandard2.0 + net10.0 beta $(NoWarn);CA1812 @@ -19,8 +19,6 @@ - - diff --git a/dotnet/src/Functions/Functions.Prompty/KernelFunctionPrompty.cs b/dotnet/src/Functions/Functions.Prompty/KernelFunctionPrompty.cs index 0ef20e0cedce..9c283f1e2722 100644 --- a/dotnet/src/Functions/Functions.Prompty/KernelFunctionPrompty.cs +++ b/dotnet/src/Functions/Functions.Prompty/KernelFunctionPrompty.cs @@ -2,8 +2,7 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text.Json; +using System.IO; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.PromptTemplates.Handlebars; using Microsoft.SemanticKernel.PromptTemplates.Liquid; @@ -55,24 +54,24 @@ public static PromptTemplateConfig ToPromptTemplateConfig(string promptyTemplate { Verify.NotNullOrWhiteSpace(promptyTemplate); - Dictionary globalConfig = []; - PromptyCore.Prompty prompty = PromptyCore.Prompty.Load(promptyTemplate, globalConfig, promptyFilePath); + PromptyCore.Prompty prompty = LoadPrompty(promptyTemplate, promptyFilePath); var promptTemplateConfig = new PromptTemplateConfig { Name = prompty.Name, Description = prompty.Description, - Template = prompty.Content.ToString() ?? string.Empty, + Template = prompty.Instructions ?? string.Empty, }; PromptExecutionSettings? defaultExecutionSetting = null; - if (prompty.Model?.Id is not null || prompty.Model?.Connection?.ServiceId is not null || prompty.Model?.Options?.Count > 0) + var extensionData = ToExtensionData(prompty.Model?.Options); + var modelId = prompty.Model?.Id; + if (!string.IsNullOrWhiteSpace(modelId) || extensionData.Count > 0) { defaultExecutionSetting = new PromptExecutionSettings() { - ModelId = prompty.Model.Id, - ServiceId = prompty.Model.Connection?.ServiceId, - ExtensionData = prompty.Model.Options, + ModelId = string.IsNullOrWhiteSpace(modelId) ? null : modelId, + ExtensionData = extensionData.Count > 0 ? extensionData : null, }; promptTemplateConfig.AddExecutionSettings(defaultExecutionSetting); } @@ -80,17 +79,14 @@ public static PromptTemplateConfig ToPromptTemplateConfig(string promptyTemplate // Add input and output variables. if (prompty.Inputs is not null) { - foreach (var kvp in prompty.Inputs) + foreach (var input in prompty.Inputs) { - var input = kvp.Value; promptTemplateConfig.InputVariables.Add(new() { - Name = kvp.Key, + Name = input.Name, Default = input.Default, - IsRequired = input.Required, - Description = input.Description, - AllowDangerouslySetContent = !input.Strict, - JsonSchema = ToJsonSchema(input.JsonSchema), + IsRequired = input.Required ?? false, + Description = input.Description ?? string.Empty, }); } } @@ -100,35 +96,79 @@ public static PromptTemplateConfig ToPromptTemplateConfig(string promptyTemplate // contains one and only one, use it. Otherwise, ignore any outputs. if (prompty.Outputs.Count == 1) { - var output = prompty.Outputs.Values.First(); + var output = prompty.Outputs[0]; promptTemplateConfig.OutputVariable = new() { - Description = output.Description, - JsonSchema = ToJsonSchema(output.JsonSchema), + Description = output.Description ?? string.Empty, }; } } // Update template format. If not provided, use Liquid as default. - promptTemplateConfig.TemplateFormat = prompty.Template?.Format ?? LiquidPromptTemplateFactory.LiquidTemplateFormat; + promptTemplateConfig.TemplateFormat = + string.IsNullOrEmpty(prompty.Template?.Format?.Kind) + ? LiquidPromptTemplateFactory.LiquidTemplateFormat + : prompty.Template!.Format!.Kind; return promptTemplateConfig; } #region private - private static string? ToJsonSchema(object? input) + /// + /// Loads a from the provided template text, optionally using a file path + /// so that ${file:...} references resolve securely within the prompty file's directory. + /// + private static PromptyCore.Prompty LoadPrompty(string promptyTemplate, string? promptyFilePath) { - if (input is null) + try { - return null; + // When a real file path is available, use the file loader so that file references + // (${file:...}) are resolved securely, confined to the directory containing the file. + if (!string.IsNullOrEmpty(promptyFilePath) && File.Exists(promptyFilePath)) + { + var fullPath = Path.GetFullPath(promptyFilePath); + var loadOptions = new PromptyCore.PromptyLoadOptions + { + AllowedFileRoots = [Path.GetDirectoryName(fullPath)!], + }; + + return PromptyCore.PromptyLoader.Load(fullPath, loadOptions); + } + + // Otherwise parse the frontmatter (and markdown body) directly from the provided text. + var frontmatter = PromptyCore.FrontmatterParser.Parse(promptyTemplate); + return PromptyCore.Prompty.Load(frontmatter, new PromptyCore.LoadContext()); } + catch (Exception ex) when (ex is not ArgumentException) + { + // Normalize parse/format failures (for example, invalid YAML in the frontmatter) to + // ArgumentException to preserve the plugin's input-validation contract. + throw new ArgumentException($"Invalid prompty template: {ex.Message}", nameof(promptyTemplate), ex); + } + } - if (input is string str) + /// + /// Converts the strongly typed to the loosely typed extension data + /// expected by consumers (for example, the OpenAI connector). + /// Only the strongly typed options are mapped; arbitrary provider-specific options are not forwarded. + /// + private static Dictionary ToExtensionData(PromptyCore.ModelOptions? options) + { + Dictionary extensionData = []; + if (options is null) { - return str; + return extensionData; } - return JsonSerializer.Serialize(input); + if (options.Temperature is not null) { extensionData["temperature"] = options.Temperature; } + if (options.TopP is not null) { extensionData["top_p"] = options.TopP; } + if (options.PresencePenalty is not null) { extensionData["presence_penalty"] = options.PresencePenalty; } + if (options.FrequencyPenalty is not null) { extensionData["frequency_penalty"] = options.FrequencyPenalty; } + if (options.MaxOutputTokens is not null) { extensionData["max_tokens"] = options.MaxOutputTokens; } + if (options.Seed is not null) { extensionData["seed"] = options.Seed; } + if (options.StopSequences is { Count: > 0 }) { extensionData["stop_sequences"] = options.StopSequences; } + + return extensionData; } #endregion } diff --git a/dotnet/src/VectorData/SqliteVec/SqliteVec.csproj b/dotnet/src/VectorData/SqliteVec/SqliteVec.csproj index ab5a3f4798dd..e5c9e680965f 100644 --- a/dotnet/src/VectorData/SqliteVec/SqliteVec.csproj +++ b/dotnet/src/VectorData/SqliteVec/SqliteVec.csproj @@ -32,6 +32,7 @@ +