diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 81f5c2725b1d..b72bb4325196 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -155,6 +155,7 @@
+
diff --git a/dotnet/SK-dotnet.slnx b/dotnet/SK-dotnet.slnx
index 835c32f258a1..ea1e02fd7de6 100644
--- a/dotnet/SK-dotnet.slnx
+++ b/dotnet/SK-dotnet.slnx
@@ -27,7 +27,9 @@
-
+
+
+
@@ -37,6 +39,7 @@
+
diff --git a/dotnet/nuget/nuget-package.props b/dotnet/nuget/nuget-package.props
index 274124a1e50c..0c39ae0bf742 100644
--- a/dotnet/nuget/nuget-package.props
+++ b/dotnet/nuget/nuget-package.props
@@ -1,7 +1,7 @@
- 1.66.0
+ 1.67.1
$(VersionPrefix)-$(VersionSuffix)
$(VersionPrefix)
@@ -9,7 +9,7 @@
true
- 1.65.0
+ 1.67.0
$(NoWarn);CP0003
diff --git a/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/AzureOpenAI_Step04_ToolCall_WithOpenAPI.csproj b/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/AzureOpenAI_Step04_ToolCall_WithOpenAPI.csproj
new file mode 100644
index 000000000000..a500b427e031
--- /dev/null
+++ b/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/AzureOpenAI_Step04_ToolCall_WithOpenAPI.csproj
@@ -0,0 +1,30 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+ $(NoWarn);CA1707;CA2007;VSTHRD111
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/OpenAPISpec.json b/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/OpenAPISpec.json
new file mode 100644
index 000000000000..84715914da63
--- /dev/null
+++ b/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/OpenAPISpec.json
@@ -0,0 +1,354 @@
+{
+ "openapi": "3.0.1",
+ "info": {
+ "title": "Github Versions API",
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "https://api.github.com"
+ }
+ ],
+ "components": {
+ "schemas": {
+ "basic-error": {
+ "title": "Basic Error",
+ "description": "Basic Error",
+ "type": "object",
+ "properties": {
+ "message": {
+ "type": "string"
+ },
+ "documentation_url": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string"
+ }
+ }
+ },
+ "label": {
+ "title": "Label",
+ "description": "Color-coded labels help you categorize and filter your issues (just like labels in Gmail).",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "Unique identifier for the label.",
+ "type": "integer",
+ "format": "int64",
+ "example": 208045946
+ },
+ "node_id": {
+ "type": "string",
+ "example": "MDU6TGFiZWwyMDgwNDU5NDY="
+ },
+ "url": {
+ "description": "URL for the label",
+ "example": "https://api.github.com/repositories/42/labels/bug",
+ "type": "string",
+ "format": "uri"
+ },
+ "name": {
+ "description": "The name of the label.",
+ "example": "bug",
+ "type": "string"
+ },
+ "description": {
+ "description": "Optional description of the label, such as its purpose.",
+ "type": "string",
+ "example": "Something isn't working",
+ "nullable": true
+ },
+ "color": {
+ "description": "6-character hex code, without the leading #, identifying the color",
+ "example": "FFFFFF",
+ "type": "string"
+ },
+ "default": {
+ "description": "Whether this label comes by default in a new repository.",
+ "type": "boolean",
+ "example": true
+ }
+ },
+ "required": [
+ "id",
+ "node_id",
+ "url",
+ "name",
+ "description",
+ "color",
+ "default"
+ ]
+ },
+ "tag": {
+ "title": "Tag",
+ "description": "Tag",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "example": "v0.1"
+ },
+ "commit": {
+ "type": "object",
+ "properties": {
+ "sha": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "required": [
+ "sha",
+ "url"
+ ]
+ },
+ "zipball_url": {
+ "type": "string",
+ "format": "uri",
+ "example": "https://github.com/octocat/Hello-World/zipball/v0.1"
+ },
+ "tarball_url": {
+ "type": "string",
+ "format": "uri",
+ "example": "https://github.com/octocat/Hello-World/tarball/v0.1"
+ },
+ "node_id": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "node_id",
+ "commit",
+ "zipball_url",
+ "tarball_url"
+ ]
+ }
+ },
+ "examples": {
+ "label-items": {
+ "value": [
+ {
+ "id": 208045946,
+ "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=",
+ "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
+ "name": "bug",
+ "description": "Something isn't working",
+ "color": "f29513",
+ "default": true
+ },
+ {
+ "id": 208045947,
+ "node_id": "MDU6TGFiZWwyMDgwNDU5NDc=",
+ "url": "https://api.github.com/repos/octocat/Hello-World/labels/enhancement",
+ "name": "enhancement",
+ "description": "New feature or request",
+ "color": "a2eeef",
+ "default": false
+ }
+ ]
+ },
+ "tag-items": {
+ "value": [
+ {
+ "name": "v0.1",
+ "commit": {
+ "sha": "c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc",
+ "url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc"
+ },
+ "zipball_url": "https://github.com/octocat/Hello-World/zipball/v0.1",
+ "tarball_url": "https://github.com/octocat/Hello-World/tarball/v0.1",
+ "node_id": "MDQ6VXNlcjE="
+ }
+ ]
+ }
+ },
+ "parameters": {
+ "owner": {
+ "name": "owner",
+ "description": "The account owner of the repository. The name is not case sensitive.",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ "repo": {
+ "name": "repo",
+ "description": "The name of the repository without the `.git` extension. The name is not case sensitive.",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ "per-page": {
+ "name": "per_page",
+ "description": "The number of results per page (max 100). For more information, see \"[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api).\"",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "default": 30
+ }
+ },
+ "page": {
+ "name": "page",
+ "description": "The page number of the results to fetch. For more information, see \"[Using pagination in the REST API](https://docs.github.com/rest/using-the-rest-api/using-pagination-in-the-rest-api).\"",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "default": 1
+ }
+ }
+ },
+ "responses": {
+ "not_found": {
+ "description": "Resource not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/basic-error"
+ }
+ }
+ }
+ }
+ },
+ "headers": {
+ "link": {
+ "example": "; rel=\"next\", ; rel=\"last\"",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "paths": {
+ "/repos/{owner}/{repo}/tags": {
+ "get": {
+ "summary": "List repository tags",
+ "description": "",
+ "tags": [
+ "repos"
+ ],
+ "operationId": "repos/list-tags",
+ "externalDocs": {
+ "description": "API method documentation",
+ "url": "https://docs.github.com/rest/repos/repos#list-repository-tags"
+ },
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/owner"
+ },
+ {
+ "$ref": "#/components/parameters/repo"
+ },
+ {
+ "$ref": "#/components/parameters/per-page"
+ },
+ {
+ "$ref": "#/components/parameters/page"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/tag"
+ }
+ },
+ "examples": {
+ "default": {
+ "$ref": "#/components/examples/tag-items"
+ }
+ }
+ }
+ },
+ "headers": {
+ "Link": {
+ "$ref": "#/components/headers/link"
+ }
+ }
+ }
+ },
+ "x-github": {
+ "githubCloudOnly": false,
+ "enabledForGitHubApps": true,
+ "category": "repos",
+ "subcategory": "repos"
+ }
+ }
+ },
+ "/repos/{owner}/{repo}/labels": {
+ "get": {
+ "summary": "List labels for a repository",
+ "description": "Lists all labels for a repository.",
+ "tags": [
+ "issues"
+ ],
+ "operationId": "issues/list-labels-for-repo",
+ "externalDocs": {
+ "description": "API method documentation",
+ "url": "https://docs.github.com/rest/issues/labels#list-labels-for-a-repository"
+ },
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/owner"
+ },
+ {
+ "$ref": "#/components/parameters/repo"
+ },
+ {
+ "$ref": "#/components/parameters/per-page"
+ },
+ {
+ "$ref": "#/components/parameters/page"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/label"
+ }
+ },
+ "examples": {
+ "default": {
+ "$ref": "#/components/examples/label-items"
+ }
+ }
+ }
+ },
+ "headers": {
+ "Link": {
+ "$ref": "#/components/headers/link"
+ }
+ }
+ },
+ "404": {
+ "$ref": "#/components/responses/not_found"
+ }
+ },
+ "x-github": {
+ "githubCloudOnly": false,
+ "enabledForGitHubApps": true,
+ "category": "issues",
+ "subcategory": "labels"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/Program.cs b/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/Program.cs
new file mode 100644
index 000000000000..5898050f5d7a
--- /dev/null
+++ b/dotnet/samples/AgentFrameworkMigration/AzureOpenAI/Step04_ToolCall_WithOpenAPI/Program.cs
@@ -0,0 +1,66 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates how to use an Agent with function tools provided via an OpenAPI spec with both Semantic Kernel and Agent Framework.
+
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Plugins.OpenApi;
+using OpenAI;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+
+await SKAgent();
+await AFAgent();
+
+async Task SKAgent()
+{
+ Console.WriteLine("\n=== SK Agent ===\n");
+
+ // Create a kernel with an Azure OpenAI chat client.
+ var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatClient(deploymentName, endpoint, new AzureCliCredential()).Build();
+
+ // Load the OpenAPI Spec from a file.
+ var plugin = await kernel.ImportPluginFromOpenApiAsync("github", "OpenAPISpec.json");
+
+ // Create the agent, and provide the kernel with the OpenAPI function tools to the agent.
+ var agent = new ChatCompletionAgent()
+ {
+ Kernel = kernel,
+ Instructions = "You are a helpful assistant",
+ Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
+ };
+
+ // Run the agent with the OpenAPI function tools.
+ await foreach (var result in agent.InvokeAsync("Please list the names, colors and descriptions of all the labels available in the microsoft/agent-framework repository on github."))
+ {
+ Console.WriteLine(result.Message);
+ }
+}
+
+async Task AFAgent()
+{
+ Console.WriteLine("\n=== AF Agent ===\n");
+
+ // Load the OpenAPI Spec from a file.
+ KernelPlugin plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync("github", "OpenAPISpec.json");
+
+ // Convert the Semantic Kernel plugin to Agent Framework function tools.
+ // This requires a dummy Kernel instance, since KernelFunctions cannot execute without one.
+ Kernel kernel = new();
+ List tools = plugin.Select(x => x.WithKernel(kernel)).Cast().ToList();
+
+ // Create the chat client and agent, and provide the OpenAPI function tools to the agent.
+ AIAgent agent = new AzureOpenAIClient(
+ new Uri(endpoint),
+ new AzureCliCredential())
+ .GetChatClient(deploymentName)
+ .CreateAIAgent(instructions: "You are a helpful assistant", tools: tools);
+
+ // Run the agent with the OpenAPI function tools.
+ Console.WriteLine(await agent.RunAsync("Please list the names, colors and descriptions of all the labels available in the microsoft/agent-framework repository on github."));
+}
diff --git a/dotnet/samples/Concepts/ChatCompletion/Google_GeminiChatCompletionWithFile.cs b/dotnet/samples/Concepts/ChatCompletion/Google_GeminiChatCompletionWithFile.cs
new file mode 100644
index 000000000000..b62006558376
--- /dev/null
+++ b/dotnet/samples/Concepts/ChatCompletion/Google_GeminiChatCompletionWithFile.cs
@@ -0,0 +1,145 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.ChatCompletion;
+using Resources;
+
+namespace ChatCompletion;
+
+///
+/// This sample shows how to use binary file and inline Base64 inputs, like PDFs, with Google Gemini's chat completion.
+///
+public class Google_GeminiChatCompletionWithFile(ITestOutputHelper output) : BaseTest(output)
+{
+ [Fact]
+ public async Task GoogleAIChatCompletionWithLocalFile()
+ {
+ Console.WriteLine("============= Google AI - Gemini Chat Completion With Local File =============");
+
+ Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
+ Assert.NotNull(TestConfiguration.GoogleAI.Gemini.ModelId);
+
+ Kernel kernel = Kernel.CreateBuilder()
+ .AddGoogleAIGeminiChatCompletion(TestConfiguration.GoogleAI.Gemini.ModelId, TestConfiguration.GoogleAI.ApiKey)
+ .Build();
+
+ var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
+
+ var chatHistory = new ChatHistory("You are a friendly assistant.");
+ chatHistory.AddUserMessage(
+ [
+ new TextContent("What's in this file?"),
+ new BinaryContent(fileBytes, "application/pdf")
+ ]);
+
+ var chatCompletionService = kernel.GetRequiredService();
+
+ var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
+
+ Console.WriteLine(reply.Content);
+ }
+
+ [Fact]
+ public async Task VertexAIChatCompletionWithLocalFile()
+ {
+ Console.WriteLine("============= Vertex AI - Gemini Chat Completion With Local File =============");
+
+ Assert.NotNull(TestConfiguration.VertexAI.BearerKey);
+ Assert.NotNull(TestConfiguration.VertexAI.Location);
+ Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
+ Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
+
+ Kernel kernel = Kernel.CreateBuilder()
+ .AddVertexAIGeminiChatCompletion(
+ modelId: TestConfiguration.VertexAI.Gemini.ModelId,
+ bearerKey: TestConfiguration.VertexAI.BearerKey,
+ location: TestConfiguration.VertexAI.Location,
+ projectId: TestConfiguration.VertexAI.ProjectId)
+ .Build();
+
+ var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
+
+ var chatHistory = new ChatHistory("You are a friendly assistant.");
+ chatHistory.AddUserMessage(
+ [
+ new TextContent("What's in this file?"),
+ new BinaryContent(fileBytes, "application/pdf"),
+ ]);
+
+ var chatCompletionService = kernel.GetRequiredService();
+
+ var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
+
+ Console.WriteLine(reply.Content);
+ }
+
+ [Fact]
+ public async Task GoogleAIChatCompletionWithBase64DataUri()
+ {
+ Console.WriteLine("============= Google AI - Gemini Chat Completion With Base64 Data Uri =============");
+
+ Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
+ Assert.NotNull(TestConfiguration.GoogleAI.Gemini.ModelId);
+
+ Kernel kernel = Kernel.CreateBuilder()
+ .AddGoogleAIGeminiChatCompletion(TestConfiguration.GoogleAI.Gemini.ModelId, TestConfiguration.GoogleAI.ApiKey)
+ .Build();
+
+ var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
+ var fileBase64 = Convert.ToBase64String(fileBytes.ToArray());
+ var dataUri = $"data:application/pdf;base64,{fileBase64}";
+
+ var chatHistory = new ChatHistory("You are a friendly assistant.");
+ chatHistory.AddUserMessage(
+ [
+ new TextContent("What's in this file?"),
+ new BinaryContent(dataUri)
+ // Google AI Gemini AI does not support arbitrary URIs but we can convert a Base64 URI into InlineData with the correct mimeType.
+ ]);
+
+ var chatCompletionService = kernel.GetRequiredService();
+
+ var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
+
+ Console.WriteLine(reply.Content);
+ }
+
+ [Fact]
+ public async Task VertexAIChatCompletionWithBase64DataUri()
+ {
+ Console.WriteLine("============= Vertex AI - Gemini Chat Completion With Base64 Data Uri =============");
+
+ Assert.NotNull(TestConfiguration.VertexAI.BearerKey);
+ Assert.NotNull(TestConfiguration.VertexAI.Location);
+ Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
+ Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
+
+ Kernel kernel = Kernel.CreateBuilder()
+ .AddVertexAIGeminiChatCompletion(
+ modelId: TestConfiguration.VertexAI.Gemini.ModelId,
+ bearerKey: TestConfiguration.VertexAI.BearerKey,
+ location: TestConfiguration.VertexAI.Location,
+ projectId: TestConfiguration.VertexAI.ProjectId)
+ .Build();
+
+ var fileBytes = await EmbeddedResource.ReadAllAsync("employees.pdf");
+ var fileBase64 = Convert.ToBase64String(fileBytes.ToArray());
+ var dataUri = $"data:application/pdf;base64,{fileBase64}";
+
+ var chatHistory = new ChatHistory("You are a friendly assistant.");
+ chatHistory.AddUserMessage(
+ [
+ new TextContent("What's in this file?"),
+ new BinaryContent(dataUri)
+ // Vertex AI API does not support URIs outside of inline Base64 or GCS buckets within the same project. The bucket that stores the file must be in the same Google Cloud project that's sending the request. You must always provide the mimeType via the metadata property.
+ // var content = new BinaryContent(gs://generativeai-downloads/files/employees.pdf);
+ // content.Metadata = new Dictionary { { "mimeType", "application/pdf" } };
+ ]);
+
+ var chatCompletionService = kernel.GetRequiredService();
+
+ var reply = await chatCompletionService.GetChatMessageContentAsync(chatHistory);
+
+ Console.WriteLine(reply.Content);
+ }
+}
diff --git a/dotnet/samples/Concepts/README.md b/dotnet/samples/Concepts/README.md
index 0facc49d7196..77fe10e7a8ab 100644
--- a/dotnet/samples/Concepts/README.md
+++ b/dotnet/samples/Concepts/README.md
@@ -64,6 +64,7 @@ dotnet test -l "console;verbosity=detailed" --filter "FullyQualifiedName=ChatCom
- [Google_GeminiChatCompletion](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Google_GeminiChatCompletion.cs)
- [Google_GeminiChatCompletionStreaming](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Google_GeminiChatCompletionStreaming.cs)
- [Google_GeminiChatCompletionWithThinkingBudget](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Google_GeminiChatCompletionWithThinkingBudget.cs)
+- [Google_GeminiChatCompletionWithFile.cs](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Google_GeminiChatCompletionWithFile.cs)
- [Google_GeminiGetModelResult](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Google_GeminiGetModelResult.cs)
- [Google_GeminiStructuredOutputs](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Google_GeminiStructuredOutputs.cs)
- [Google_GeminiVision](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/ChatCompletion/Google_GeminiVision.cs)
diff --git a/dotnet/samples/Demos/ProcessFrameworkWithSignalR/package-lock.json b/dotnet/samples/Demos/ProcessFrameworkWithSignalR/package-lock.json
index d116ddee57fe..8c3670dc3cf8 100644
--- a/dotnet/samples/Demos/ProcessFrameworkWithSignalR/package-lock.json
+++ b/dotnet/samples/Demos/ProcessFrameworkWithSignalR/package-lock.json
@@ -5,7 +5,7 @@
"packages": {
"": {
"dependencies": {
- "axios": "^1.9.0"
+ "axios": "^1.12.0"
}
},
"node_modules/asynckit": {
@@ -15,13 +15,13 @@
"license": "MIT"
},
"node_modules/axios": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
- "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz",
+ "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
- "form-data": "^4.0.0",
+ "form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
@@ -139,14 +139,15 @@
}
},
"node_modules/form-data": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
- "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
diff --git a/dotnet/samples/Demos/ProcessFrameworkWithSignalR/package.json b/dotnet/samples/Demos/ProcessFrameworkWithSignalR/package.json
index 9467b64db4d6..b541fde4f83e 100644
--- a/dotnet/samples/Demos/ProcessFrameworkWithSignalR/package.json
+++ b/dotnet/samples/Demos/ProcessFrameworkWithSignalR/package.json
@@ -1,5 +1,5 @@
{
"dependencies": {
- "axios": "^1.9.0"
+ "axios": "^1.12.0"
}
}
diff --git a/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Client/package.json b/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Client/package.json
index efba0d8337bb..5685498af825 100644
--- a/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Client/package.json
+++ b/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Client/package.json
@@ -32,6 +32,6 @@
"globals": "^15.15.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
- "vite": "^6.2.7"
+ "vite": "^6.4.1"
}
}
diff --git a/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Client/yarn.lock b/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Client/yarn.lock
index b8e7449ffb6d..744c89bf01c8 100644
--- a/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Client/yarn.lock
+++ b/dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Client/yarn.lock
@@ -1568,105 +1568,115 @@
resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime/-/runtime-2.9.6.tgz#a687ce90d76e053b3b8a3b1abbe99264d2d6bda2"
integrity sha512-C0CfpKx4n4LBbUrajOdRj2BTbd3qBoK0SiKWLq7RgCoU6xiN4wesBMFHUOBp3fFzKeZwgU8Q2KtzaqzIvPLRXg==
-"@rollup/rollup-android-arm-eabi@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz#1d8cc5dd3d8ffe569d8f7f67a45c7909828a0f66"
- integrity sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==
-
-"@rollup/rollup-android-arm64@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz#9c136034d3d9ed29d0b138c74dd63c5744507fca"
- integrity sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==
-
-"@rollup/rollup-darwin-arm64@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz#830d07794d6a407c12b484b8cf71affd4d3800a6"
- integrity sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==
-
-"@rollup/rollup-darwin-x64@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz#b26f0f47005c1fa5419a880f323ed509dc8d885c"
- integrity sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==
-
-"@rollup/rollup-freebsd-arm64@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz#2b60c81ac01ff7d1bc8df66aee7808b6690c6d19"
- integrity sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==
-
-"@rollup/rollup-freebsd-x64@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz#4826af30f4d933d82221289068846c9629cc628c"
- integrity sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==
-
-"@rollup/rollup-linux-arm-gnueabihf@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz#a1f4f963d5dcc9e5575c7acf9911824806436bf7"
- integrity sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==
-
-"@rollup/rollup-linux-arm-musleabihf@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz#e924b0a8b7c400089146f6278446e6b398b75a06"
- integrity sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==
-
-"@rollup/rollup-linux-arm64-gnu@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz#cb43303274ec9a716f4440b01ab4e20c23aebe20"
- integrity sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==
-
-"@rollup/rollup-linux-arm64-musl@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz#531c92533ce3d167f2111bfcd2aa1a2041266987"
- integrity sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==
-
-"@rollup/rollup-linux-loongarch64-gnu@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz#53403889755d0c37c92650aad016d5b06c1b061a"
- integrity sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==
-
-"@rollup/rollup-linux-powerpc64le-gnu@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz#f669f162e29094c819c509e99dbeced58fc708f9"
- integrity sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==
-
-"@rollup/rollup-linux-riscv64-gnu@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz#4bab37353b11bcda5a74ca11b99dea929657fd5f"
- integrity sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==
-
-"@rollup/rollup-linux-riscv64-musl@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz#4d66be1ce3cfd40a7910eb34dddc7cbd4c2dd2a5"
- integrity sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==
-
-"@rollup/rollup-linux-s390x-gnu@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz#7181c329395ed53340a0c59678ad304a99627f6d"
- integrity sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==
-
-"@rollup/rollup-linux-x64-gnu@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz#00825b3458094d5c27cb4ed66e88bfe9f1e65f90"
- integrity sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==
-
-"@rollup/rollup-linux-x64-musl@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz#81caac2a31b8754186f3acc142953a178fcd6fba"
- integrity sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==
-
-"@rollup/rollup-win32-arm64-msvc@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz#3a3f421f5ce9bd99ed20ce1660cce7cee3e9f199"
- integrity sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==
-
-"@rollup/rollup-win32-ia32-msvc@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz#a44972d5cdd484dfd9cf3705a884bf0c2b7785a7"
- integrity sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==
-
-"@rollup/rollup-win32-x64-msvc@4.39.0":
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz#bfe0214e163f70c4fec1c8f7bb8ce266f4c05b7e"
- integrity sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==
+"@rollup/rollup-android-arm-eabi@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz#0f44a2f8668ed87b040b6fe659358ac9239da4db"
+ integrity sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==
+
+"@rollup/rollup-android-arm64@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz#25b9a01deef6518a948431564c987bcb205274f5"
+ integrity sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==
+
+"@rollup/rollup-darwin-arm64@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz#8a102869c88f3780c7d5e6776afd3f19084ecd7f"
+ integrity sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==
+
+"@rollup/rollup-darwin-x64@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz#8e526417cd6f54daf1d0c04cf361160216581956"
+ integrity sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==
+
+"@rollup/rollup-freebsd-arm64@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz#0e7027054493f3409b1f219a3eac5efd128ef899"
+ integrity sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==
+
+"@rollup/rollup-freebsd-x64@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz#72b204a920139e9ec3d331bd9cfd9a0c248ccb10"
+ integrity sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==
+
+"@rollup/rollup-linux-arm-gnueabihf@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz#ab1b522ebe5b7e06c99504cc38f6cd8b808ba41c"
+ integrity sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==
+
+"@rollup/rollup-linux-arm-musleabihf@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz#f8cc30b638f1ee7e3d18eac24af47ea29d9beb00"
+ integrity sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==
+
+"@rollup/rollup-linux-arm64-gnu@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz#7af37a9e85f25db59dc8214172907b7e146c12cc"
+ integrity sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==
+
+"@rollup/rollup-linux-arm64-musl@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz#a623eb0d3617c03b7a73716eb85c6e37b776f7e0"
+ integrity sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==
+
+"@rollup/rollup-linux-loong64-gnu@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz#76ea038b549c5c6c5f0d062942627c4066642ee2"
+ integrity sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==
+
+"@rollup/rollup-linux-ppc64-gnu@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz#d9a4c3f0a3492bc78f6fdfe8131ac61c7359ccd5"
+ integrity sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==
+
+"@rollup/rollup-linux-riscv64-gnu@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz#87ab033eebd1a9a1dd7b60509f6333ec1f82d994"
+ integrity sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==
+
+"@rollup/rollup-linux-riscv64-musl@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz#bda3eb67e1c993c1ba12bc9c2f694e7703958d9f"
+ integrity sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==
+
+"@rollup/rollup-linux-s390x-gnu@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz#f7bc10fbe096ab44694233dc42a2291ed5453d4b"
+ integrity sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==
+
+"@rollup/rollup-linux-x64-gnu@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz#a151cb1234cc9b2cf5e8cfc02aa91436b8f9e278"
+ integrity sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==
+
+"@rollup/rollup-linux-x64-musl@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz#7859e196501cc3b3062d45d2776cfb4d2f3a9350"
+ integrity sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==
+
+"@rollup/rollup-openharmony-arm64@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz#85d0df7233734df31e547c1e647d2a5300b3bf30"
+ integrity sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==
+
+"@rollup/rollup-win32-arm64-msvc@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz#e62357d00458db17277b88adbf690bb855cac937"
+ integrity sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==
+
+"@rollup/rollup-win32-ia32-msvc@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz#fc7cd40f44834a703c1f1c3fe8bcc27ce476cd50"
+ integrity sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==
+
+"@rollup/rollup-win32-x64-gnu@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz#1a22acfc93c64a64a48c42672e857ee51774d0d3"
+ integrity sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==
+
+"@rollup/rollup-win32-x64-msvc@4.52.5":
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz#1657f56326bbe0ac80eedc9f9c18fc1ddd24e107"
+ integrity sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==
"@swc/helpers@^0.5.1", "@swc/helpers@~0.5.1":
version "0.5.15"
@@ -1722,11 +1732,16 @@
dependencies:
"@types/estree" "*"
-"@types/estree@*", "@types/estree@1.0.7", "@types/estree@^1.0.0", "@types/estree@^1.0.6":
+"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6":
version "1.0.7"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8"
integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==
+"@types/estree@1.0.8":
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
+ integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
+
"@types/hast@^3.0.0":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa"
@@ -2273,6 +2288,11 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
+fdir@^6.4.4, fdir@^6.5.0:
+ version "6.5.0"
+ resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
+ integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
+
file-entry-cache@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f"
@@ -2968,6 +2988,11 @@ picomatch@^2.3.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+picomatch@^4.0.2, picomatch@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042"
+ integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
+
postcss@^8.5.3:
version "8.5.3"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb"
@@ -3096,33 +3121,35 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f"
integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
-rollup@^4.30.1:
- version "4.39.0"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.39.0.tgz#9dc1013b70c0e2cb70ef28350142e9b81b3f640c"
- integrity sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==
+rollup@^4.34.9:
+ version "4.52.5"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.52.5.tgz#96982cdcaedcdd51b12359981f240f94304ec235"
+ integrity sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==
dependencies:
- "@types/estree" "1.0.7"
+ "@types/estree" "1.0.8"
optionalDependencies:
- "@rollup/rollup-android-arm-eabi" "4.39.0"
- "@rollup/rollup-android-arm64" "4.39.0"
- "@rollup/rollup-darwin-arm64" "4.39.0"
- "@rollup/rollup-darwin-x64" "4.39.0"
- "@rollup/rollup-freebsd-arm64" "4.39.0"
- "@rollup/rollup-freebsd-x64" "4.39.0"
- "@rollup/rollup-linux-arm-gnueabihf" "4.39.0"
- "@rollup/rollup-linux-arm-musleabihf" "4.39.0"
- "@rollup/rollup-linux-arm64-gnu" "4.39.0"
- "@rollup/rollup-linux-arm64-musl" "4.39.0"
- "@rollup/rollup-linux-loongarch64-gnu" "4.39.0"
- "@rollup/rollup-linux-powerpc64le-gnu" "4.39.0"
- "@rollup/rollup-linux-riscv64-gnu" "4.39.0"
- "@rollup/rollup-linux-riscv64-musl" "4.39.0"
- "@rollup/rollup-linux-s390x-gnu" "4.39.0"
- "@rollup/rollup-linux-x64-gnu" "4.39.0"
- "@rollup/rollup-linux-x64-musl" "4.39.0"
- "@rollup/rollup-win32-arm64-msvc" "4.39.0"
- "@rollup/rollup-win32-ia32-msvc" "4.39.0"
- "@rollup/rollup-win32-x64-msvc" "4.39.0"
+ "@rollup/rollup-android-arm-eabi" "4.52.5"
+ "@rollup/rollup-android-arm64" "4.52.5"
+ "@rollup/rollup-darwin-arm64" "4.52.5"
+ "@rollup/rollup-darwin-x64" "4.52.5"
+ "@rollup/rollup-freebsd-arm64" "4.52.5"
+ "@rollup/rollup-freebsd-x64" "4.52.5"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.52.5"
+ "@rollup/rollup-linux-arm-musleabihf" "4.52.5"
+ "@rollup/rollup-linux-arm64-gnu" "4.52.5"
+ "@rollup/rollup-linux-arm64-musl" "4.52.5"
+ "@rollup/rollup-linux-loong64-gnu" "4.52.5"
+ "@rollup/rollup-linux-ppc64-gnu" "4.52.5"
+ "@rollup/rollup-linux-riscv64-gnu" "4.52.5"
+ "@rollup/rollup-linux-riscv64-musl" "4.52.5"
+ "@rollup/rollup-linux-s390x-gnu" "4.52.5"
+ "@rollup/rollup-linux-x64-gnu" "4.52.5"
+ "@rollup/rollup-linux-x64-musl" "4.52.5"
+ "@rollup/rollup-openharmony-arm64" "4.52.5"
+ "@rollup/rollup-win32-arm64-msvc" "4.52.5"
+ "@rollup/rollup-win32-ia32-msvc" "4.52.5"
+ "@rollup/rollup-win32-x64-gnu" "4.52.5"
+ "@rollup/rollup-win32-x64-msvc" "4.52.5"
fsevents "~2.3.2"
rtl-css-js@^1.16.1:
@@ -3223,6 +3250,14 @@ tabster@^8.5.0:
keyborg "2.6.0"
tslib "^2.3.1"
+tinyglobby@^0.2.13:
+ version "0.2.15"
+ resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
+ integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
+ dependencies:
+ fdir "^6.5.0"
+ picomatch "^4.0.3"
+
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -3373,14 +3408,17 @@ vfile@^6.0.0:
"@types/unist" "^3.0.0"
vfile-message "^4.0.0"
-vite@^6.2.7:
- version "6.2.7"
- resolved "https://registry.yarnpkg.com/vite/-/vite-6.2.7.tgz#699fb6e4b3e65d749480e0087cdbe3f3f0de00fa"
- integrity sha512-qg3LkeuinTrZoJHHF94coSaTfIPyBYoywp+ys4qu20oSJFbKMYoIJo0FWJT9q6Vp49l6z9IsJRbHdcGtiKbGoQ==
+vite@^6.4.1:
+ version "6.4.1"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-6.4.1.tgz#afbe14518cdd6887e240a4b0221ab6d0ce733f96"
+ integrity sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==
dependencies:
esbuild "^0.25.0"
+ fdir "^6.4.4"
+ picomatch "^4.0.2"
postcss "^8.5.3"
- rollup "^4.30.1"
+ rollup "^4.34.9"
+ tinyglobby "^0.2.13"
optionalDependencies:
fsevents "~2.3.3"
diff --git a/dotnet/src/Agents/Orchestration/OrchestrationResult.cs b/dotnet/src/Agents/Orchestration/OrchestrationResult.cs
index 978e7bcca74b..755e164c963b 100644
--- a/dotnet/src/Agents/Orchestration/OrchestrationResult.cs
+++ b/dotnet/src/Agents/Orchestration/OrchestrationResult.cs
@@ -73,12 +73,24 @@ public async ValueTask GetValueAsync(TimeSpan? timeout = null, Cancellat
if (timeout.HasValue)
{
- Task[] tasks = { this._completion.Task };
- if (!Task.WaitAll(tasks, timeout.Value))
+#if NET
+ try
+ {
+ await this._completion.Task.WaitAsync(timeout.Value, cancellationToken).ConfigureAwait(false);
+ }
+ catch (TimeoutException)
+ {
+ this._logger.LogOrchestrationResultTimeout(this.Orchestration, this.Topic);
+ throw;
+ }
+#else
+ Task completedTask = await Task.WhenAny(this._completion.Task, Task.Delay(timeout.Value, cancellationToken)).ConfigureAwait(false);
+ if (completedTask != this._completion.Task)
{
this._logger.LogOrchestrationResultTimeout(this.Orchestration, this.Topic);
throw new TimeoutException($"Orchestration did not complete within the allowed duration ({timeout}).");
}
+#endif
}
this._logger.LogOrchestrationResultComplete(this.Orchestration, this.Topic);
diff --git a/dotnet/src/Agents/UnitTests/Orchestration/OrchestrationResultTests.cs b/dotnet/src/Agents/UnitTests/Orchestration/OrchestrationResultTests.cs
index 69471949847c..d65a2c5e8b8c 100644
--- a/dotnet/src/Agents/UnitTests/Orchestration/OrchestrationResultTests.cs
+++ b/dotnet/src/Agents/UnitTests/Orchestration/OrchestrationResultTests.cs
@@ -77,7 +77,6 @@ public async Task GetValueAsync_WithTimeout_ThrowsTimeoutException_WhenTaskDoesN
// Act & Assert
TimeoutException exception = await Assert.ThrowsAsync(() => result.GetValueAsync(timeout).AsTask());
- Assert.Contains("Orchestration did not complete within the allowed duration", exception.Message);
}
[Fact]
diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/Clients/GeminiChatGenerationTests.cs b/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/Clients/GeminiChatGenerationTests.cs
index 2c19b210b2c8..82d804d500b3 100644
--- a/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/Clients/GeminiChatGenerationTests.cs
+++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/Clients/GeminiChatGenerationTests.cs
@@ -164,6 +164,8 @@ public async Task ShouldReturnValidGeminiMetadataAsync()
}
Assert.Equal(testDataResponse.UsageMetadata!.PromptTokenCount, metadata.PromptTokenCount);
+ Assert.Equal(testDataResponse.UsageMetadata!.CachedContentTokenCount, metadata.CachedContentTokenCount);
+ Assert.Equal(testDataResponse.UsageMetadata!.ThoughtsTokenCount, metadata.ThoughtsTokenCount);
Assert.Equal(testDataCandidate.TokenCount, metadata.CurrentCandidateTokenCount);
Assert.Equal(testDataResponse.UsageMetadata.CandidatesTokenCount, metadata.CandidatesTokenCount);
Assert.Equal(testDataResponse.UsageMetadata.TotalTokenCount, metadata.TotalTokenCount);
@@ -207,6 +209,8 @@ public async Task ShouldReturnValidDictionaryMetadataAsync()
}
Assert.Equal(testDataResponse.UsageMetadata!.PromptTokenCount, metadata[nameof(GeminiMetadata.PromptTokenCount)]);
+ Assert.Equal(testDataResponse.UsageMetadata!.CachedContentTokenCount, metadata[nameof(GeminiMetadata.CachedContentTokenCount)]);
+ Assert.Equal(testDataResponse.UsageMetadata!.ThoughtsTokenCount, metadata[nameof(GeminiMetadata.ThoughtsTokenCount)]);
Assert.Equal(testDataCandidate.TokenCount, metadata[nameof(GeminiMetadata.CurrentCandidateTokenCount)]);
Assert.Equal(testDataResponse.UsageMetadata.CandidatesTokenCount, metadata[nameof(GeminiMetadata.CandidatesTokenCount)]);
Assert.Equal(testDataResponse.UsageMetadata.TotalTokenCount, metadata[nameof(GeminiMetadata.TotalTokenCount)]);
diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/Clients/GeminiChatStreamingTests.cs b/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/Clients/GeminiChatStreamingTests.cs
index 692da9146b04..808ce2eb6fc4 100644
--- a/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/Clients/GeminiChatStreamingTests.cs
+++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/Clients/GeminiChatStreamingTests.cs
@@ -174,6 +174,8 @@ public async Task ShouldReturnValidGeminiMetadataAsync()
}
Assert.Equal(testDataResponse.UsageMetadata!.PromptTokenCount, metadata.PromptTokenCount);
+ Assert.Equal(testDataResponse.UsageMetadata!.CachedContentTokenCount, metadata.CachedContentTokenCount);
+ Assert.Equal(testDataResponse.UsageMetadata!.ThoughtsTokenCount, metadata.ThoughtsTokenCount);
Assert.Equal(testDataCandidate.TokenCount, metadata.CurrentCandidateTokenCount);
Assert.Equal(testDataResponse.UsageMetadata.CandidatesTokenCount, metadata.CandidatesTokenCount);
Assert.Equal(testDataResponse.UsageMetadata.TotalTokenCount, metadata.TotalTokenCount);
@@ -218,6 +220,8 @@ public async Task ShouldReturnValidDictionaryMetadataAsync()
}
Assert.Equal(testDataResponse.UsageMetadata!.PromptTokenCount, metadata[nameof(GeminiMetadata.PromptTokenCount)]);
+ Assert.Equal(testDataResponse.UsageMetadata!.CachedContentTokenCount, metadata[nameof(GeminiMetadata.CachedContentTokenCount)]);
+ Assert.Equal(testDataResponse.UsageMetadata!.ThoughtsTokenCount, metadata[nameof(GeminiMetadata.ThoughtsTokenCount)]);
Assert.Equal(testDataCandidate.TokenCount, metadata[nameof(GeminiMetadata.CurrentCandidateTokenCount)]);
Assert.Equal(testDataResponse.UsageMetadata.CandidatesTokenCount, metadata[nameof(GeminiMetadata.CandidatesTokenCount)]);
Assert.Equal(testDataResponse.UsageMetadata.TotalTokenCount, metadata[nameof(GeminiMetadata.TotalTokenCount)]);
diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/GeminiRequestTests.cs b/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/GeminiRequestTests.cs
index 31ce6e9946fd..ac50bed9efa9 100644
--- a/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/GeminiRequestTests.cs
+++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/GeminiRequestTests.cs
@@ -371,6 +371,33 @@ public void FromChatHistoryAudioAsAudioContentItReturnsWithChatHistory()
.SequenceEqual(Convert.FromBase64String(c.Parts![0].InlineData!.InlineData))));
}
+ [Fact]
+ public void FromChatHistoryPdfAsBinaryContentItReturnsWithChatHistory()
+ {
+ // Arrange
+ ReadOnlyMemory pdfAsBytes = new byte[] { 0x00, 0x01, 0x02, 0x03 };
+ ChatHistory chatHistory = [];
+ chatHistory.AddUserMessage("user-message");
+ chatHistory.AddAssistantMessage("assist-message");
+ chatHistory.AddUserMessage(contentItems:
+ [new BinaryContent(new Uri("https://example-file.com/file.pdf")) { MimeType = "application/pdf" }]);
+ chatHistory.AddUserMessage(contentItems:
+ [new BinaryContent(pdfAsBytes, "application/pdf")]);
+ var executionSettings = new GeminiPromptExecutionSettings();
+
+ // Act
+ var request = GeminiRequest.FromChatHistoryAndExecutionSettings(chatHistory, executionSettings);
+
+ // Assert
+ Assert.Collection(request.Contents,
+ c => Assert.Equal(chatHistory[0].Content, c.Parts![0].Text),
+ c => Assert.Equal(chatHistory[1].Content, c.Parts![0].Text),
+ c => Assert.Equal(chatHistory[2].Items.Cast().Single().Uri,
+ c.Parts![0].FileData!.FileUri),
+ c => Assert.True(pdfAsBytes.ToArray()
+ .SequenceEqual(Convert.FromBase64String(c.Parts![0].InlineData!.InlineData))));
+ }
+
[Fact]
public void FromChatHistoryUnsupportedContentItThrowsNotSupportedException()
{
diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/Services/GoogleAIGeminiChatCompletionServiceTests.cs b/dotnet/src/Connectors/Connectors.Google.UnitTests/Services/GoogleAIGeminiChatCompletionServiceTests.cs
index 06e5361de75e..8b1ee874314b 100644
--- a/dotnet/src/Connectors/Connectors.Google.UnitTests/Services/GoogleAIGeminiChatCompletionServiceTests.cs
+++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/Services/GoogleAIGeminiChatCompletionServiceTests.cs
@@ -5,7 +5,9 @@
using System.IO;
using System.Net.Http;
using System.Text;
+using System.Text.Json;
using System.Threading.Tasks;
+using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.Google;
using Microsoft.SemanticKernel.Services;
@@ -144,6 +146,78 @@ public async Task RequestBodyIncludesThinkingConfigWhenSetAsync(int? thinkingBud
}
}
+ [Fact]
+ public async Task GetChatMessageContentsAsyncThrowsExceptionWithEmptyBinaryContentAsync()
+ {
+ // Arrange
+ var sut = new GoogleAIGeminiChatCompletionService("gemini-2.5-pro", "key");
+
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage([new BinaryContent()]);
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() => sut.GetChatMessageContentsAsync(chatHistory));
+ }
+
+ [Fact]
+ public async Task GetChatMessageContentsThrowsExceptionUriOnlyReferenceBinaryContentAsync()
+ {
+ // Arrange
+ var sut = new GoogleAIGeminiChatCompletionService("gemini-2.5-pro", "key");
+
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage([new BinaryContent(new Uri("file://testfile.pdf"))]);
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() => sut.GetChatMessageContentsAsync(chatHistory));
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ItSendsBinaryContentCorrectlyAsync(bool useUriData)
+ {
+ // Arrange
+ var sut = new GoogleAIGeminiChatCompletionService("gemini-2.5-pro", "key", httpClient: this._httpClient);
+
+ var mimeType = "application/pdf";
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage([
+ new TextContent("What's in this file?"),
+ useUriData
+ ? new BinaryContent($"data:{mimeType};base64,{PdfBase64Data}")
+ : new BinaryContent(Convert.FromBase64String(PdfBase64Data), mimeType)
+ ]);
+
+ // Act
+ await sut.GetChatMessageContentsAsync(chatHistory);
+
+ // Assert
+ var actualRequestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
+ Assert.NotNull(actualRequestContent);
+ var optionsJson = JsonSerializer.Deserialize(actualRequestContent);
+
+ var contents = optionsJson.GetProperty("contents");
+ Assert.Equal(1, contents.GetArrayLength());
+
+ var parts = contents[0].GetProperty("parts");
+ Assert.Equal(2, parts.GetArrayLength());
+
+ Assert.True(parts[0].TryGetProperty("text", out var prompt));
+ Assert.Equal("What's in this file?", prompt.ToString());
+
+ // Check for the file data
+ Assert.True(parts[1].TryGetProperty("inlineData", out var inlineData));
+ Assert.Equal(JsonValueKind.Object, inlineData.ValueKind);
+ Assert.Equal(mimeType, inlineData.GetProperty("mimeType").GetString());
+ Assert.Equal(PdfBase64Data, inlineData.GetProperty("data").ToString());
+ }
+
+ ///
+ /// Sample PDF data URI for testing.
+ ///
+ private const string PdfBase64Data = "JVBERi0xLjQKMSAwIG9iago8PC9UeXBlIC9DYXRhbG9nCi9QYWdlcyAyIDAgUgo+PgplbmRvYmoKMiAwIG9iago8PC9UeXBlIC9QYWdlcwovS2lkcyBbMyAwIFJdCi9Db3VudCAxCj4+CmVuZG9iagozIDAgb2JqCjw8L1R5cGUgL1BhZ2UKL1BhcmVudCAyIDAgUgovTWVkaWFCb3ggWzAgMCA1OTUgODQyXQovQ29udGVudHMgNSAwIFIKL1Jlc291cmNlcyA8PC9Qcm9jU2V0IFsvUERGIC9UZXh0XQovRm9udCA8PC9GMSA0IDAgUj4+Cj4+Cj4+CmVuZG9iago0IDAgb2JqCjw8L1R5cGUgL0ZvbnQKL1N1YnR5cGUgL1R5cGUxCi9OYW1lIC9GMQovQmFzZUZvbnQgL0hlbHZldGljYQovRW5jb2RpbmcgL01hY1JvbWFuRW5jb2RpbmcKPj4KZW5kb2JqCjUgMCBvYmoKPDwvTGVuZ3RoIDUzCj4+CnN0cmVhbQpCVAovRjEgMjAgVGYKMjIwIDQwMCBUZAooRHVtbXkgUERGKSBUagpFVAplbmRzdHJlYW0KZW5kb2JqCnhyZWYKMCA2CjAwMDAwMDAwMDAgNjU1MzUgZgowMDAwMDAwMDA5IDAwMDAwIG4KMDAwMDAwMDA2MyAwMDAwMCBuCjAwMDAwMDAxMjQgMDAwMDAgbgowMDAwMDAwMjc3IDAwMDAwIG4KMDAwMDAwMDM5MiAwMDAwMCBuCnRyYWlsZXIKPDwvU2l6ZSA2Ci9Sb290IDEgMCBSCj4+CnN0YXJ0eHJlZgo0OTUKJSVFT0YK";
+
public void Dispose()
{
this._httpClient.Dispose();
diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/Services/VertexAIGeminiChatCompletionServiceTests.cs b/dotnet/src/Connectors/Connectors.Google.UnitTests/Services/VertexAIGeminiChatCompletionServiceTests.cs
index 179705981186..b36bc32f88ac 100644
--- a/dotnet/src/Connectors/Connectors.Google.UnitTests/Services/VertexAIGeminiChatCompletionServiceTests.cs
+++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/Services/VertexAIGeminiChatCompletionServiceTests.cs
@@ -5,7 +5,9 @@
using System.IO;
using System.Net.Http;
using System.Text;
+using System.Text.Json;
using System.Threading.Tasks;
+using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.Google;
using Microsoft.SemanticKernel.Services;
@@ -156,6 +158,78 @@ public async Task RequestBodyIncludesThinkingConfigWhenSetAsync(int? thinkingBud
}
}
+ [Fact]
+ public async Task GetChatMessageContentsAsyncThrowsExceptionWithEmptyBinaryContentAsync()
+ {
+ // Arrange
+ var sut = new VertexAIGeminiChatCompletionService("gemini-2.5-pro", "key", "location", "project");
+
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage([new BinaryContent()]);
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() => sut.GetChatMessageContentsAsync(chatHistory));
+ }
+
+ [Fact]
+ public async Task GetChatMessageContentsThrowsExceptionUriOnlyReferenceBinaryContentAsync()
+ {
+ // Arrange
+ var sut = new VertexAIGeminiChatCompletionService("gemini-2.5-pro", "key", "location", "project");
+
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage([new BinaryContent(new Uri("file://testfile.pdf"))]);
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() => sut.GetChatMessageContentsAsync(chatHistory));
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ItSendsBinaryContentCorrectlyAsync(bool useUriData)
+ {
+ // Arrange
+ var sut = new VertexAIGeminiChatCompletionService("gemini-2.5-pro", "key", "location", "project", httpClient: this._httpClient);
+
+ var mimeType = "application/pdf";
+ var chatHistory = new ChatHistory();
+ chatHistory.AddUserMessage([
+ new TextContent("What's in this file?"),
+ useUriData
+ ? new BinaryContent($"data:{mimeType};base64,{PdfBase64Data}")
+ : new BinaryContent(Convert.FromBase64String(PdfBase64Data), mimeType)
+ ]);
+
+ // Act
+ await sut.GetChatMessageContentsAsync(chatHistory);
+
+ // Assert
+ var actualRequestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
+ Assert.NotNull(actualRequestContent);
+ var optionsJson = JsonSerializer.Deserialize(actualRequestContent);
+
+ var contents = optionsJson.GetProperty("contents");
+ Assert.Equal(1, contents.GetArrayLength());
+
+ var parts = contents[0].GetProperty("parts");
+ Assert.Equal(2, parts.GetArrayLength());
+
+ Assert.True(parts[0].TryGetProperty("text", out var prompt));
+ Assert.Equal("What's in this file?", prompt.ToString());
+
+ // Check for the file data
+ Assert.True(parts[1].TryGetProperty("inlineData", out var inlineData));
+ Assert.Equal(JsonValueKind.Object, inlineData.ValueKind);
+ Assert.Equal(mimeType, inlineData.GetProperty("mimeType").GetString());
+ Assert.Equal(PdfBase64Data, inlineData.GetProperty("data").ToString());
+ }
+
+ ///
+ /// Sample PDF data URI for testing.
+ ///
+ private const string PdfBase64Data = "JVBERi0xLjQKMSAwIG9iago8PC9UeXBlIC9DYXRhbG9nCi9QYWdlcyAyIDAgUgo+PgplbmRvYmoKMiAwIG9iago8PC9UeXBlIC9QYWdlcwovS2lkcyBbMyAwIFJdCi9Db3VudCAxCj4+CmVuZG9iagozIDAgb2JqCjw8L1R5cGUgL1BhZ2UKL1BhcmVudCAyIDAgUgovTWVkaWFCb3ggWzAgMCA1OTUgODQyXQovQ29udGVudHMgNSAwIFIKL1Jlc291cmNlcyA8PC9Qcm9jU2V0IFsvUERGIC9UZXh0XQovRm9udCA8PC9GMSA0IDAgUj4+Cj4+Cj4+CmVuZG9iago0IDAgb2JqCjw8L1R5cGUgL0ZvbnQKL1N1YnR5cGUgL1R5cGUxCi9OYW1lIC9GMQovQmFzZUZvbnQgL0hlbHZldGljYQovRW5jb2RpbmcgL01hY1JvbWFuRW5jb2RpbmcKPj4KZW5kb2JqCjUgMCBvYmoKPDwvTGVuZ3RoIDUzCj4+CnN0cmVhbQpCVAovRjEgMjAgVGYKMjIwIDQwMCBUZAooRHVtbXkgUERGKSBUagpFVAplbmRzdHJlYW0KZW5kb2JqCnhyZWYKMCA2CjAwMDAwMDAwMDAgNjU1MzUgZgowMDAwMDAwMDA5IDAwMDAwIG4KMDAwMDAwMDA2MyAwMDAwMCBuCjAwMDAwMDAxMjQgMDAwMDAgbgowMDAwMDAwMjc3IDAwMDAwIG4KMDAwMDAwMDM5MiAwMDAwMCBuCnRyYWlsZXIKPDwvU2l6ZSA2Ci9Sb290IDEgMCBSCj4+CnN0YXJ0eHJlZgo0OTUKJSVFT0YK";
+
public void Dispose()
{
this._httpClient.Dispose();
diff --git a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Clients/GeminiChatCompletionClient.cs b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Clients/GeminiChatCompletionClient.cs
index fbd5b86ee863..ea5d3103029a 100644
--- a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Clients/GeminiChatCompletionClient.cs
+++ b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Clients/GeminiChatCompletionClient.cs
@@ -791,6 +791,8 @@ private static GeminiMetadata GetResponseMetadata(
FinishReason = candidate.FinishReason,
Index = candidate.Index,
PromptTokenCount = geminiResponse.UsageMetadata?.PromptTokenCount ?? 0,
+ CachedContentTokenCount = geminiResponse.UsageMetadata?.CachedContentTokenCount ?? 0,
+ ThoughtsTokenCount = geminiResponse.UsageMetadata?.ThoughtsTokenCount ?? 0,
CurrentCandidateTokenCount = candidate.TokenCount,
CandidatesTokenCount = geminiResponse.UsageMetadata?.CandidatesTokenCount ?? 0,
TotalTokenCount = geminiResponse.UsageMetadata?.TotalTokenCount ?? 0,
diff --git a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs
index b70ad9a0fbab..29ac3c53f63c 100644
--- a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs
+++ b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs
@@ -224,6 +224,7 @@ private static List CreateGeminiParts(ChatMessageContent content)
TextContent textContent => new GeminiPart { Text = textContent.Text },
ImageContent imageContent => CreateGeminiPartFromImage(imageContent),
AudioContent audioContent => CreateGeminiPartFromAudio(audioContent),
+ BinaryContent binaryContent => CreateGeminiPartFromBinary(binaryContent),
_ => throw new NotSupportedException($"Unsupported content type. {item.GetType().Name} is not supported by Gemini.")
};
@@ -299,6 +300,42 @@ private static string GetMimeTypeFromAudioContent(AudioContent audioContent)
?? throw new InvalidOperationException("Audio content MimeType is empty.");
}
+ private static GeminiPart CreateGeminiPartFromBinary(BinaryContent binaryContent)
+ {
+ // Binary data takes precedence over URI.
+ if (binaryContent.Data is { IsEmpty: false })
+ {
+ return new GeminiPart
+ {
+ InlineData = new GeminiPart.InlineDataPart
+ {
+ MimeType = GetMimeTypeFromBinaryContent(binaryContent),
+ InlineData = Convert.ToBase64String(binaryContent.Data.Value.ToArray())
+ }
+ };
+ }
+
+ if (binaryContent.Uri is not null)
+ {
+ return new GeminiPart
+ {
+ FileData = new GeminiPart.FileDataPart
+ {
+ MimeType = GetMimeTypeFromBinaryContent(binaryContent),
+ FileUri = binaryContent.Uri ?? throw new InvalidOperationException("Binary content URI is empty.")
+ }
+ };
+ }
+
+ throw new InvalidOperationException("Binary content does not contain any data or uri.");
+ }
+
+ private static string GetMimeTypeFromBinaryContent(BinaryContent binaryContent)
+ {
+ return binaryContent.MimeType
+ ?? throw new InvalidOperationException("Binary content MimeType is empty.");
+ }
+
private static void AddConfiguration(GeminiPromptExecutionSettings executionSettings, GeminiRequest request)
{
request.Configuration = new ConfigurationElement
diff --git a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiResponse.cs b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiResponse.cs
index 5a028c459a14..04802e3dfd15 100644
--- a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiResponse.cs
+++ b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiResponse.cs
@@ -39,6 +39,18 @@ internal sealed class UsageMetadataElement
[JsonPropertyName("promptTokenCount")]
public int PromptTokenCount { get; set; }
+ ///
+ /// Gets the number of cached content tokens used.
+ ///
+ [JsonPropertyName("cachedContentTokenCount")]
+ public int CachedContentTokenCount { get; set; }
+
+ ///
+ /// Gets the number of thoughts tokens used.
+ ///
+ [JsonPropertyName("thoughtsTokenCount")]
+ public int ThoughtsTokenCount { get; set; }
+
///
/// Gets the count of used tokens for all candidates.
///
diff --git a/dotnet/src/Connectors/Connectors.Google/Models/Gemini/GeminiMetadata.cs b/dotnet/src/Connectors/Connectors.Google/Models/Gemini/GeminiMetadata.cs
index bd03d4cba9ea..1765cebb6ce5 100644
--- a/dotnet/src/Connectors/Connectors.Google/Models/Gemini/GeminiMetadata.cs
+++ b/dotnet/src/Connectors/Connectors.Google/Models/Gemini/GeminiMetadata.cs
@@ -44,12 +44,21 @@ public int PromptTokenCount
}
///
- /// The count of token in the current candidate.
+ /// The count of cached content tokens.
///
- public int CurrentCandidateTokenCount
+ public int CachedContentTokenCount
{
- get => (this.GetValueFromDictionary(nameof(this.CurrentCandidateTokenCount)) as int?) ?? 0;
- internal init => this.SetValueInDictionary(value, nameof(this.CurrentCandidateTokenCount));
+ get => (this.GetValueFromDictionary(nameof(this.CachedContentTokenCount)) as int?) ?? 0;
+ internal init => this.SetValueInDictionary(value, nameof(this.CachedContentTokenCount));
+ }
+
+ ///
+ /// The count of thoughts tokens.
+ ///
+ public int ThoughtsTokenCount
+ {
+ get => (this.GetValueFromDictionary(nameof(this.ThoughtsTokenCount)) as int?) ?? 0;
+ internal init => this.SetValueInDictionary(value, nameof(this.ThoughtsTokenCount));
}
///
@@ -61,6 +70,15 @@ public int CandidatesTokenCount
internal init => this.SetValueInDictionary(value, nameof(this.CandidatesTokenCount));
}
+ ///
+ /// The count of token in the current candidate.
+ ///
+ public int CurrentCandidateTokenCount
+ {
+ get => (this.GetValueFromDictionary(nameof(this.CurrentCandidateTokenCount)) as int?) ?? 0;
+ internal init => this.SetValueInDictionary(value, nameof(this.CurrentCandidateTokenCount));
+ }
+
///
/// The total count of tokens (prompt + total candidates token count).
///
diff --git a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Core/ClientCoreTests.cs b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Core/ClientCoreTests.cs
index 954dc539d609..5986b89564bc 100644
--- a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Core/ClientCoreTests.cs
+++ b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Core/ClientCoreTests.cs
@@ -399,4 +399,78 @@ private void AddMessagesWithFunctionCallsWithValidFunctionName()
}), false);
}
}
+
+ [Fact]
+ public void NonInvocableToolHasValidParametersSchema()
+ {
+ // Arrange & Act
+ // Access the NonInvocableTool through reflection since it's protected
+ var clientCoreType = typeof(ClientCore);
+ var nonInvocableToolField = clientCoreType.GetField("s_nonInvocableFunctionTool",
+ System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
+
+ Assert.NotNull(nonInvocableToolField);
+
+ var nonInvocableTool = (ChatTool)nonInvocableToolField.GetValue(null)!;
+
+ // Assert
+ Assert.NotNull(nonInvocableTool);
+ Assert.Equal("NonInvocableTool", nonInvocableTool.FunctionName);
+ Assert.Equal("A placeholder tool used when no real tools are available", nonInvocableTool.FunctionDescription);
+
+ // Verify that parameters are not null (this is the key fix for Mistral compatibility)
+ Assert.NotNull(nonInvocableTool.FunctionParameters);
+
+ // Verify the parameters contain a valid JSON schema
+ var parametersJson = nonInvocableTool.FunctionParameters.ToString();
+ Assert.Contains("\"type\":\"object\"", parametersJson);
+ Assert.Contains("\"required\":[]", parametersJson);
+ Assert.Contains("\"properties\":{}", parametersJson);
+
+ // Verify it's valid JSON
+ var parsedJson = JsonSerializer.Deserialize(parametersJson);
+ Assert.Equal(JsonValueKind.Object, parsedJson.ValueKind);
+ Assert.True(parsedJson.TryGetProperty("type", out var typeProperty));
+ Assert.Equal("object", typeProperty.GetString());
+ Assert.True(parsedJson.TryGetProperty("required", out var requiredProperty));
+ Assert.Equal(JsonValueKind.Array, requiredProperty.ValueKind);
+ Assert.Equal(0, requiredProperty.GetArrayLength());
+ Assert.True(parsedJson.TryGetProperty("properties", out var propertiesProperty));
+ Assert.Equal(JsonValueKind.Object, propertiesProperty.ValueKind);
+ }
+
+ [Fact]
+ public void NonInvocableToolSchemaIsCompatibleWithMistral()
+ {
+ // Arrange & Act
+ var clientCoreType = typeof(ClientCore);
+ var nonInvocableToolField = clientCoreType.GetField("s_nonInvocableFunctionTool",
+ System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
+
+ var nonInvocableTool = (ChatTool)nonInvocableToolField!.GetValue(null)!;
+
+ // Assert
+ // This test verifies that the tool schema meets Mistral's requirements:
+ // 1. Has a parameters field (not null)
+ // 2. Parameters field contains valid JSON schema
+ // 3. Schema has required type, properties, and required fields
+
+ Assert.NotNull(nonInvocableTool.FunctionParameters);
+
+ var parametersJson = nonInvocableTool.FunctionParameters.ToString();
+ var schema = JsonSerializer.Deserialize(parametersJson);
+
+ // Verify all required fields for Mistral compatibility
+ Assert.True(schema.TryGetProperty("type", out _), "Schema must have 'type' field");
+ Assert.True(schema.TryGetProperty("properties", out _), "Schema must have 'properties' field");
+ Assert.True(schema.TryGetProperty("required", out _), "Schema must have 'required' field");
+
+ // Verify the schema structure matches what Mistral expects
+ Assert.Equal("object", schema.GetProperty("type").GetString());
+ Assert.Equal(JsonValueKind.Object, schema.GetProperty("properties").ValueKind);
+ Assert.Equal(JsonValueKind.Array, schema.GetProperty("required").ValueKind);
+
+ // This ensures the tool won't cause 422 errors with Mistral APIs
+ // as described in GitHub issue #13232
+ }
}
diff --git a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs
index 2dce2194b57e..604bd25170ca 100644
--- a/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs
+++ b/dotnet/src/Connectors/Connectors.OpenAI/Core/ClientCore.ChatCompletion.cs
@@ -58,7 +58,10 @@ protected record ToolCallingConfig(IList? Tools, ChatToolChoice? Choic
protected const int MaxInflightAutoInvokes = 128;
/// Singleton tool used when tool call count drops to 0 but we need to supply tools to keep the service happy.
- protected static readonly ChatTool s_nonInvocableFunctionTool = ChatTool.CreateFunctionTool("NonInvocableTool");
+ protected static readonly ChatTool s_nonInvocableFunctionTool = ChatTool.CreateFunctionTool(
+ functionName: "NonInvocableTool",
+ functionDescription: "A placeholder tool used when no real tools are available",
+ functionParameters: BinaryData.FromString("""{"type":"object","required":[],"properties":{}}"""));
///
/// Instance of for metrics.
diff --git a/dotnet/src/Experimental/Process.LocalRuntime/LocalAgentStep.cs b/dotnet/src/Experimental/Process.LocalRuntime/LocalAgentStep.cs
index 6ce3fc248b06..990afc54113a 100644
--- a/dotnet/src/Experimental/Process.LocalRuntime/LocalAgentStep.cs
+++ b/dotnet/src/Experimental/Process.LocalRuntime/LocalAgentStep.cs
@@ -45,7 +45,16 @@ internal override async Task HandleMessageAsync(ProcessMessage message)
await this._initializeTask.Value.ConfigureAwait(false);
string targetFunction = "Invoke";
- KernelArguments arguments = new() { { "message", message.TargetEventData }, { "writtenToThread", message.writtenToThread == this._agentThread.ThreadId } };
+ KernelArguments arguments = new()
+ {
+ { "message", message.TargetEventData switch
+ {
+ KernelProcessEventData proxyData => proxyData.ToObject(),
+ _ => message.TargetEventData
+ }
+ },
+ { "writtenToThread", message.writtenToThread == this._agentThread.ThreadId }
+ };
if (!this._functions.TryGetValue(targetFunction, out KernelFunction? function) || function == null)
{
throw new ArgumentException($"Function Invoke not found in plugin {this.Name}");
diff --git a/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiChatCompletionTests.cs b/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiChatCompletionTests.cs
index 7645d9cf107e..80aa7aeb5853 100644
--- a/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiChatCompletionTests.cs
+++ b/dotnet/src/IntegrationTests/Connectors/Google/Gemini/GeminiChatCompletionTests.cs
@@ -381,6 +381,32 @@ public async Task ChatGenerationAudioUriAsync(ServiceType serviceType)
Assert.Contains("brooklyn bridge", response.Content, StringComparison.OrdinalIgnoreCase);
}
+ [RetryTheory]
+ [InlineData(ServiceType.GoogleAI, Skip = "This test is for manual verification.")]
+ [InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
+ public async Task ChatGenerationWithBinaryFileDataAsync(ServiceType serviceType)
+ {
+ // Arrange
+ Memory file = await File.ReadAllBytesAsync(Path.Combine("TestData", "employees.pdf"));
+ var chatHistory = new ChatHistory();
+ var messageContent = new ChatMessageContent(AuthorRole.User, items:
+ [
+ new TextContent("What positions do the employees have?"),
+ new BinaryContent(file, "application/pdf")
+ ]);
+ chatHistory.Add(messageContent);
+
+ var sut = this.GetChatService(serviceType);
+
+ // Act
+ var response = await sut.GetChatMessageContentAsync(chatHistory);
+
+ // Assert
+ Assert.NotNull(response.Content);
+ this.Output.WriteLine(response.Content);
+ Assert.Contains("accountant", response.Content, StringComparison.OrdinalIgnoreCase);
+ }
+
[RetryTheory]
[InlineData(ServiceType.GoogleAI, Skip = "Currently GoogleAI always returns zero tokens.")]
[InlineData(ServiceType.VertexAI, Skip = "This test is for manual verification.")]
@@ -409,6 +435,8 @@ public async Task ChatGenerationReturnsUsedTokensAsync(ServiceType serviceType)
Assert.True(geminiMetadata.CandidatesTokenCount > 0);
Assert.True(geminiMetadata.PromptTokenCount > 0);
Assert.True(geminiMetadata.CurrentCandidateTokenCount > 0);
+ Assert.True(geminiMetadata.CachedContentTokenCount > 0);
+ Assert.True(geminiMetadata.ThoughtsTokenCount > 0);
}
[RetryTheory]
@@ -433,10 +461,14 @@ public async Task ChatStreamingReturnsUsedTokensAsync(ServiceType serviceType)
this.Output.WriteLine($"TotalTokenCount: {geminiMetadata.TotalTokenCount}");
this.Output.WriteLine($"CandidatesTokenCount: {geminiMetadata.CandidatesTokenCount}");
this.Output.WriteLine($"PromptTokenCount: {geminiMetadata.PromptTokenCount}");
+ this.Output.WriteLine($"CachedContentTokenCount: {geminiMetadata.CachedContentTokenCount}");
+ this.Output.WriteLine($"ThoughtsTokenCount: {geminiMetadata.ThoughtsTokenCount}");
this.Output.WriteLine($"CurrentCandidateTokenCount: {geminiMetadata.CurrentCandidateTokenCount}");
Assert.True(geminiMetadata.TotalTokenCount > 0);
Assert.True(geminiMetadata.CandidatesTokenCount > 0);
Assert.True(geminiMetadata.PromptTokenCount > 0);
+ Assert.True(geminiMetadata.CachedContentTokenCount > 0);
+ Assert.True(geminiMetadata.ThoughtsTokenCount > 0);
Assert.True(geminiMetadata.CurrentCandidateTokenCount > 0);
}
diff --git a/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoDynamicMapper.cs b/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoDynamicMapper.cs
index 91f732856047..71931f75ed1d 100644
--- a/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoDynamicMapper.cs
+++ b/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoDynamicMapper.cs
@@ -30,13 +30,15 @@ public BsonDocument MapFromDataToStorageModel(Dictionary dataMo
: keyValue switch
{
string s => s,
+ Guid g => BsonValue.Create(g),
+ ObjectId o => o,
+ long i => i,
+ int i => i,
+
null => throw new InvalidOperationException($"Key property '{model.KeyProperty.ModelName}' is null."),
_ => throw new InvalidCastException($"Key property '{model.KeyProperty.ModelName}' must be a string.")
};
- document[MongoConstants.MongoReservedKeyPropertyName] = (string)(dataModel[model.KeyProperty.ModelName]
- ?? throw new InvalidOperationException($"Key property '{model.KeyProperty.ModelName}' is null."));
-
foreach (var property in model.DataProperties)
{
if (dataModel.TryGetValue(property.ModelName, out var dataValue))
@@ -88,9 +90,22 @@ Embedding e
switch (property)
{
case KeyPropertyModel keyProperty:
- result[keyProperty.ModelName] = storageModel.TryGetValue(MongoConstants.MongoReservedKeyPropertyName, out var keyValue)
- ? keyValue.AsString
- : throw new InvalidOperationException("No key property was found in the record retrieved from storage.");
+ if (!storageModel.TryGetValue(MongoConstants.MongoReservedKeyPropertyName, out var keyValue))
+ {
+ throw new InvalidOperationException("No key property was found in the record retrieved from storage.");
+ }
+
+ result[keyProperty.ModelName] = keyProperty.Type switch
+ {
+ var t when t == typeof(string) => keyValue.AsString,
+ var t when t == typeof(Guid) => keyValue.AsGuid,
+ var t when t == typeof(ObjectId) => keyValue.AsObjectId,
+ var t when t == typeof(long) => keyValue.AsInt64,
+ var t when t == typeof(int) => keyValue.AsInt32,
+
+ _ => throw new UnreachableException()
+ };
+
continue;
case DataPropertyModel dataProperty:
diff --git a/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs b/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs
index 43ca3cfdb1d8..bb6d9e2daa2d 100644
--- a/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs
+++ b/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs
@@ -44,9 +44,9 @@ protected override void ProcessTypeProperties(Type type, VectorStoreCollectionDe
protected override bool IsKeyPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
{
- supportedTypes = "string, Guid, ObjectId";
+ supportedTypes = "string, int, long, Guid, ObjectId";
- return type == typeof(string) || type == typeof(Guid) || type == typeof(ObjectId);
+ return type == typeof(string) || type == typeof(int) || type == typeof(long) || type == typeof(Guid) || type == typeof(ObjectId);
}
protected override bool IsDataPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
diff --git a/dotnet/src/InternalUtilities/src/Diagnostics/ModelDiagnostics.cs b/dotnet/src/InternalUtilities/src/Diagnostics/ModelDiagnostics.cs
index 433ee30703b4..02f531dbe9b5 100644
--- a/dotnet/src/InternalUtilities/src/Diagnostics/ModelDiagnostics.cs
+++ b/dotnet/src/InternalUtilities/src/Diagnostics/ModelDiagnostics.cs
@@ -53,7 +53,7 @@ internal static class ModelDiagnostics
return null;
}
- const string OperationName = "text.completions";
+ const string OperationName = "text_completion";
var activity = s_activitySource.StartActivityWithTags(
$"{OperationName} {modelName}",
[
@@ -105,7 +105,7 @@ internal static class ModelDiagnostics
return null;
}
- const string OperationName = "chat.completions";
+ const string OperationName = "chat";
var activity = s_activitySource.StartActivityWithTags(
$"{OperationName} {modelName}",
[
@@ -651,13 +651,15 @@ private static class ModelDiagnosticsTags
public const string UserMessage = "gen_ai.user.message";
public const string AssistantMessage = "gen_ai.assistant.message";
public const string ToolMessage = "gen_ai.tool.message";
+ public const string DeveloperMessage = "gen_ai.tool.developer";
public const string Choice = "gen_ai.choice";
public static readonly Dictionary RoleToEventMap = new()
{
{ AuthorRole.System, SystemMessage },
{ AuthorRole.User, UserMessage },
{ AuthorRole.Assistant, AssistantMessage },
- { AuthorRole.Tool, ToolMessage }
+ { AuthorRole.Tool, ToolMessage },
+ { AuthorRole.Developer, DeveloperMessage }
};
}
# endregion
diff --git a/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollection.cs b/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollection.cs
index 9792adfdfffc..2a718a006f8e 100644
--- a/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollection.cs
+++ b/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollection.cs
@@ -83,9 +83,9 @@ internal AzureAISearchCollection(SearchIndexClient searchIndexClient, string nam
Verify.NotNull(searchIndexClient);
Verify.NotNullOrWhiteSpace(name);
- if (typeof(TKey) != typeof(string) && typeof(TKey) != typeof(object))
+ if (typeof(TKey) != typeof(string) && typeof(TKey) != typeof(Guid) && typeof(TKey) != typeof(object))
{
- throw new NotSupportedException("Only string keys are supported.");
+ throw new NotSupportedException("Only string and Guid keys are supported.");
}
options ??= AzureAISearchCollectionOptions.Default;
@@ -400,8 +400,8 @@ public override async IAsyncEnumerable> SearchAsync<
options,
top,
floatVector is null
- ? new VectorizableTextQuery((string)(object)searchValue) { KNearestNeighborsCount = top, Fields = { vectorProperty.StorageName } }
- : new VectorizedQuery(floatVector.Value) { KNearestNeighborsCount = top, Fields = { vectorProperty.StorageName } });
+ ? new VectorizableTextQuery((string)(object)searchValue) { KNearestNeighborsCount = top + options.Skip, Fields = { vectorProperty.StorageName } }
+ : new VectorizedQuery(floatVector.Value) { KNearestNeighborsCount = top + options.Skip, Fields = { vectorProperty.StorageName } });
await foreach (var record in this.SearchAndMapToDataModelAsync(null, searchOptions, options.IncludeVectors, cancellationToken).ConfigureAwait(false))
{
@@ -442,8 +442,8 @@ public async IAsyncEnumerable> HybridSearchAsync s,
+ Guid g => g.ToString(),
+
+ _ => throw new UnreachableException("string key should have been validated during model building")
+ };
Verify.NotNullOrWhiteSpace(stringKey, nameof(key));
diff --git a/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollectionCreateMapping.cs b/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollectionCreateMapping.cs
index f56042479fe1..d39019a729ad 100644
--- a/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollectionCreateMapping.cs
+++ b/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollectionCreateMapping.cs
@@ -76,8 +76,10 @@ public static (VectorSearchField vectorSearchField, VectorSearchAlgorithmConfigu
{
IndexKind.Hnsw => new HnswAlgorithmConfiguration(algorithmConfigName) { Parameters = new HnswParameters { Metric = algorithmMetric } },
IndexKind.Flat => new ExhaustiveKnnAlgorithmConfiguration(algorithmConfigName) { Parameters = new ExhaustiveKnnParameters { Metric = algorithmMetric } },
- _ => throw new InvalidOperationException($"Index kind '{indexKind}' on {nameof(VectorStoreVectorProperty)} '{vectorProperty.ModelName}' is not supported by the Azure AI Search VectorStore.")
+
+ _ => throw new NotSupportedException($"Index kind '{indexKind}' on {nameof(VectorStoreVectorProperty)} '{vectorProperty.ModelName}' is not supported by the Azure AI Search VectorStore.")
};
+
var vectorSearchProfile = new VectorSearchProfile(vectorSearchProfileName, algorithmConfigName);
return (new VectorSearchField(vectorProperty.StorageName, vectorProperty.Dimensions, vectorSearchProfileName), algorithmConfiguration, vectorSearchProfile);
@@ -105,7 +107,8 @@ public static VectorSearchAlgorithmMetric GetSDKDistanceAlgorithm(VectorProperty
DistanceFunction.CosineSimilarity or null => VectorSearchAlgorithmMetric.Cosine,
DistanceFunction.DotProductSimilarity => VectorSearchAlgorithmMetric.DotProduct,
DistanceFunction.EuclideanDistance => VectorSearchAlgorithmMetric.Euclidean,
- _ => throw new InvalidOperationException($"Distance function '{vectorProperty.DistanceFunction}' for {nameof(VectorStoreVectorProperty)} '{vectorProperty.ModelName}' is not supported by the Azure AI Search VectorStore.")
+
+ _ => throw new NotSupportedException($"Distance function '{vectorProperty.DistanceFunction}' for {nameof(VectorStoreVectorProperty)} '{vectorProperty.ModelName}' is not supported by the Azure AI Search VectorStore.")
};
///
@@ -142,6 +145,6 @@ public static SearchFieldDataType GetSDKFieldDataType(Type propertyType)
Type t when t == typeof(DateTimeOffset[]) => SearchFieldDataType.Collection(SearchFieldDataType.DateTimeOffset),
Type t when t == typeof(List) => SearchFieldDataType.Collection(SearchFieldDataType.DateTimeOffset),
- _ => throw new InvalidOperationException($"Data type '{propertyType}' for {nameof(VectorStoreDataProperty)} is not supported by the Azure AI Search VectorStore.")
+ _ => throw new NotSupportedException($"Data type '{propertyType}' for {nameof(VectorStoreDataProperty)} is not supported by the Azure AI Search VectorStore.")
};
}
diff --git a/dotnet/src/VectorData/AzureAISearch/AzureAISearchDynamicMapper.cs b/dotnet/src/VectorData/AzureAISearch/AzureAISearchDynamicMapper.cs
index ddf1366a2fe7..bf7ec744f269 100644
--- a/dotnet/src/VectorData/AzureAISearch/AzureAISearchDynamicMapper.cs
+++ b/dotnet/src/VectorData/AzureAISearch/AzureAISearchDynamicMapper.cs
@@ -29,8 +29,10 @@ public JsonObject MapFromDataToStorageModel(Dictionary dataMode
: keyValue switch
{
string s => s,
+ Guid g => g.ToString(),
+
null => throw new InvalidOperationException($"Key property '{model.KeyProperty.ModelName}' is null."),
- _ => throw new InvalidCastException($"Key property '{model.KeyProperty.ModelName}' must be a string.")
+ _ => throw new InvalidCastException($"Key property '{model.KeyProperty.ModelName}' must be a string or Guid.")
};
foreach (var dataProperty in model.DataProperties)
@@ -97,9 +99,16 @@ public JsonObject MapFromDataToStorageModel(Dictionary dataMode
switch (property)
{
case KeyPropertyModel keyProperty:
- result[keyProperty.ModelName] = (string?)storageModel[keyProperty.StorageName]
+ var key = (string?)storageModel[keyProperty.StorageName]
?? throw new InvalidOperationException($"The key property '{keyProperty.StorageName}' is missing from the record retrieved from storage.");
+ result[keyProperty.ModelName] = keyProperty.Type switch
+ {
+ var t when t == typeof(string) => key,
+ var t when t == typeof(Guid) => Guid.Parse(key),
+ _ => throw new UnreachableException()
+ };
+
continue;
case DataPropertyModel dataProperty:
diff --git a/dotnet/src/VectorData/AzureAISearch/AzureAISearchModelBuilder.cs b/dotnet/src/VectorData/AzureAISearch/AzureAISearchModelBuilder.cs
index f5101e741ec2..b281a79cbfc6 100644
--- a/dotnet/src/VectorData/AzureAISearch/AzureAISearchModelBuilder.cs
+++ b/dotnet/src/VectorData/AzureAISearch/AzureAISearchModelBuilder.cs
@@ -31,9 +31,9 @@ protected override bool IsVectorPropertyTypeValid(Type type, [NotNullWhen(false)
internal static bool IsKeyPropertyTypeValidCore(Type type, [NotNullWhen(false)] out string? supportedTypes)
{
- supportedTypes = "string";
+ supportedTypes = "string, Guid";
- return type == typeof(string);
+ return type == typeof(string) || type == typeof(Guid);
}
internal static bool IsDataPropertyTypeValidCore(Type type, [NotNullWhen(false)] out string? supportedTypes)
diff --git a/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoCollection.cs b/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoCollection.cs
index 02883f452c5c..16dba308f78f 100644
--- a/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoCollection.cs
+++ b/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoCollection.cs
@@ -67,6 +67,8 @@ public class CosmosMongoCollection : VectorStoreCollectionThe size of the dynamic candidate list for search.
private readonly int _efSearch;
+ private static readonly Type[] s_validKeyTypes = [typeof(string), typeof(Guid), typeof(ObjectId), typeof(int), typeof(long)];
+
///
/// Initializes a new instance of the class.
///
@@ -95,9 +97,9 @@ internal CosmosMongoCollection(IMongoDatabase mongoDatabase, string name, Func.
///
private static DistanceFunction GetDistanceFunction(string? distanceFunction, string vectorPropertyName)
- {
- if (string.IsNullOrWhiteSpace(distanceFunction))
- {
- // Use default distance function.
- return DistanceFunction.Cosine;
- }
-
- return distanceFunction switch
+ => distanceFunction switch
{
- SKDistanceFunction.CosineSimilarity => DistanceFunction.Cosine,
+ SKDistanceFunction.CosineSimilarity or null => DistanceFunction.Cosine,
SKDistanceFunction.DotProductSimilarity => DistanceFunction.DotProduct,
SKDistanceFunction.EuclideanDistance => DistanceFunction.Euclidean,
- _ => throw new InvalidOperationException($"Distance function '{distanceFunction}' for {nameof(VectorStoreVectorProperty)} '{vectorPropertyName}' is not supported by the Azure CosmosDB NoSQL VectorStore.")
+
+ _ => throw new NotSupportedException($"Distance function '{distanceFunction}' for {nameof(VectorStoreVectorProperty)} '{vectorPropertyName}' is not supported by the Azure CosmosDB NoSQL VectorStore.")
};
- }
///
/// Returns based on vector property type.
@@ -852,13 +848,24 @@ private static IEnumerable GetCompositeKeys(IEnumerable
=> keys switch
{
IEnumerable k => k,
+
IEnumerable k => k.Select(key => new CosmosNoSqlCompositeKey(recordKey: key, partitionKey: key)),
+
+ IEnumerable k => k.Select(key =>
+ {
+ var guidString = key.ToString();
+ return new CosmosNoSqlCompositeKey(recordKey: guidString, partitionKey: guidString);
+ }),
+
IEnumerable
- private readonly Type[] _validKeyTypes = [typeof(string), typeof(Guid), typeof(ObjectId)];
+ private static readonly Type[] s_validKeyTypes = [typeof(string), typeof(Guid), typeof(ObjectId), typeof(int), typeof(long)];
///
/// Initializes a new instance of the class.
@@ -106,9 +106,9 @@ internal MongoCollection(IMongoDatabase mongoDatabase, string name, Func "cosine",
DistanceFunction.DotProductSimilarity => "dotProduct",
DistanceFunction.EuclideanDistance => "euclidean",
- _ => throw new InvalidOperationException($"Distance function '{distanceFunction}' for {nameof(VectorStoreVectorProperty)} '{vectorPropertyName}' is not supported by the MongoDB VectorStore.")
+
+ _ => throw new NotSupportedException($"Distance function '{distanceFunction}' for {nameof(VectorStoreVectorProperty)} '{vectorPropertyName}' is not supported by the MongoDB VectorStore.")
};
}
diff --git a/dotnet/src/VectorData/PgVector/PostgresCollection.cs b/dotnet/src/VectorData/PgVector/PostgresCollection.cs
index 7cfcfc74ac5b..da650d150828 100644
--- a/dotnet/src/VectorData/PgVector/PostgresCollection.cs
+++ b/dotnet/src/VectorData/PgVector/PostgresCollection.cs
@@ -199,6 +199,11 @@ public override async Task UpsertAsync(TRecord record, CancellationToken cancell
generatedEmbeddings[vectorProperty] = [await halfTask.ConfigureAwait(false)];
}
#endif
+ else if (vectorProperty.TryGenerateEmbedding(record, cancellationToken, out var binaryTask))
+ {
+ generatedEmbeddings ??= new Dictionary>(vectorPropertyCount);
+ generatedEmbeddings[vectorProperty] = [await binaryTask.ConfigureAwait(false)];
+ }
else
{
throw new InvalidOperationException(
@@ -420,10 +425,9 @@ _ when vectorProperty.EmbeddingGenerator is IEmbeddingGenerator b,
- // TODO: Uncomment once we sync to the latest MEAI
- // BinaryEmbedding e => e.Vector,
- // _ when vectorProperty.EmbeddingGenerator is IEmbeddingGenerator generator
- // => (await generator.GenerateEmbeddingAsync(value, cancellationToken: cancellationToken).ConfigureAwait(false)).Vector,
+ BinaryEmbedding e => e.Vector,
+ _ when vectorProperty.EmbeddingGenerator is IEmbeddingGenerator generator
+ => await generator.GenerateAsync(searchValue, cancellationToken: cancellationToken).ConfigureAwait(false),
// Sparse
SparseVector sv => sv,
diff --git a/dotnet/src/VectorData/PgVector/PostgresMapper.cs b/dotnet/src/VectorData/PgVector/PostgresMapper.cs
index 70aafb7e2ebb..f824f4f5894b 100644
--- a/dotnet/src/VectorData/PgVector/PostgresMapper.cs
+++ b/dotnet/src/VectorData/PgVector/PostgresMapper.cs
@@ -77,6 +77,10 @@ public TRecord MapFromStorageToDataModel(NpgsqlDataReader reader, bool includeVe
}
#endif
+ case BitArray bitArray when vectorProperty.Type == typeof(BinaryEmbedding):
+ vectorProperty.SetValueAsObject(record, new BinaryEmbedding(bitArray));
+ continue;
+
case BitArray bitArray:
vectorProperty.SetValueAsObject(record, bitArray);
continue;
diff --git a/dotnet/src/VectorData/PgVector/PostgresModelBuilder.cs b/dotnet/src/VectorData/PgVector/PostgresModelBuilder.cs
index 4cadfd765bdb..5e3b18509e5f 100644
--- a/dotnet/src/VectorData/PgVector/PostgresModelBuilder.cs
+++ b/dotnet/src/VectorData/PgVector/PostgresModelBuilder.cs
@@ -12,7 +12,7 @@ namespace Microsoft.SemanticKernel.Connectors.PgVector;
internal class PostgresModelBuilder() : CollectionModelBuilder(PostgresModelBuilder.ModelBuildingOptions)
{
- internal const string SupportedVectorTypes = "ReadOnlyMemory, Embedding, float[], ReadOnlyMemory, Embedding, Half[], BitArray, or SparseVector";
+ internal const string SupportedVectorTypes = "ReadOnlyMemory, Embedding, float[], ReadOnlyMemory, Embedding, Half[], BinaryEmbedding, BitArray, or SparseVector";
public static readonly CollectionModelBuildingOptions ModelBuildingOptions = new()
{
@@ -80,6 +80,7 @@ internal static bool IsVectorPropertyTypeValidCore(Type type, [NotNullWhen(false
type == typeof(Embedding) ||
type == typeof(Half[]) ||
#endif
+ type == typeof(BinaryEmbedding) ||
type == typeof(BitArray) ||
type == typeof(SparseVector);
}
@@ -93,5 +94,5 @@ internal static bool IsVectorPropertyTypeValidCore(Type type, [NotNullWhen(false
#if NET8_0_OR_GREATER
?? vectorProperty.ResolveEmbeddingType>(embeddingGenerator, userRequestedEmbeddingType)
#endif
- ;
+ ?? vectorProperty.ResolveEmbeddingType(embeddingGenerator, userRequestedEmbeddingType);
}
diff --git a/dotnet/src/VectorData/PgVector/PostgresPropertyMapping.cs b/dotnet/src/VectorData/PgVector/PostgresPropertyMapping.cs
index 1160cf543986..5130cbdfcce7 100644
--- a/dotnet/src/VectorData/PgVector/PostgresPropertyMapping.cs
+++ b/dotnet/src/VectorData/PgVector/PostgresPropertyMapping.cs
@@ -30,6 +30,7 @@ internal static class PostgresPropertyMapping
#endif
BitArray bitArray => bitArray,
+ BinaryEmbedding binaryEmbedding => binaryEmbedding.Vector,
SparseVector sparseVector => sparseVector,
null => null,
@@ -136,6 +137,7 @@ public static (string PgType, bool IsNullable) GetPgVectorTypeName(VectorPropert
Type t when t == typeof(SparseVector) => "SPARSEVEC",
Type t when t == typeof(BitArray) => "BIT",
+ Type t when t == typeof(BinaryEmbedding) => "BIT",
_ => throw new NotSupportedException($"Type {vectorProperty.EmbeddingType.Name} is not supported by this store.")
};
diff --git a/dotnet/src/VectorData/PgVector/PostgresSqlBuilder.cs b/dotnet/src/VectorData/PgVector/PostgresSqlBuilder.cs
index acfa930a67ec..322820f6d9af 100644
--- a/dotnet/src/VectorData/PgVector/PostgresSqlBuilder.cs
+++ b/dotnet/src/VectorData/PgVector/PostgresSqlBuilder.cs
@@ -269,7 +269,7 @@ internal static void BuildGetCommand(NpgsqlCommand command, string schema,
internal static void BuildGetBatchCommand(NpgsqlCommand command, string schema, string tableName, CollectionModel model, List keys, bool includeVectors = false)
where TKey : notnull
{
- NpgsqlDbType? keyType = PostgresPropertyMapping.GetNpgsqlDbType(typeof(TKey)) ?? throw new ArgumentException($"Unsupported key type {typeof(TKey).Name}");
+ NpgsqlDbType? keyType = PostgresPropertyMapping.GetNpgsqlDbType(model.KeyProperty.Type) ?? throw new UnreachableException($"Unsupported key type {model.KeyProperty.Type.Name}");
// Generate the column names
var columns = model.Properties
diff --git a/dotnet/src/VectorData/Pinecone/PineconeCollection.cs b/dotnet/src/VectorData/Pinecone/PineconeCollection.cs
index 183c22d07b6a..5195ff29f00a 100644
--- a/dotnet/src/VectorData/Pinecone/PineconeCollection.cs
+++ b/dotnet/src/VectorData/Pinecone/PineconeCollection.cs
@@ -76,9 +76,9 @@ internal PineconeCollection(PineconeClient pineconeClient, string name, Func s,
+ Guid g => g.ToString(),
+
+ _ => throw new UnreachableException()
+ };
Verify.NotNullOrWhiteSpace(stringKey, nameof(key));
diff --git a/dotnet/src/VectorData/Pinecone/PineconeFilterTranslator.cs b/dotnet/src/VectorData/Pinecone/PineconeFilterTranslator.cs
index bfe02e9b013a..33516d2c5418 100644
--- a/dotnet/src/VectorData/Pinecone/PineconeFilterTranslator.cs
+++ b/dotnet/src/VectorData/Pinecone/PineconeFilterTranslator.cs
@@ -23,13 +23,21 @@ internal class PineconeFilterTranslator
private Extensions.VectorData.ProviderServices.CollectionModel _model = null!;
private ParameterExpression _recordParameter = null!;
- internal Metadata Translate(LambdaExpression lambdaExpression, Extensions.VectorData.ProviderServices.CollectionModel model)
+ internal Metadata? Translate(LambdaExpression lambdaExpression, Extensions.VectorData.ProviderServices.CollectionModel model)
{
this._model = model;
Debug.Assert(lambdaExpression.Parameters.Count == 1);
this._recordParameter = lambdaExpression.Parameters[0];
+ // Pinecone doesn't seem to have a native way of expressing "always true" filters; since this scenario is important for fetching
+ // all records (via GetAsync with filter), we special-case and support it here. Note that false isn't supported (useless),
+ // nor is 'x && true'.
+ if (lambdaExpression.Body is ConstantExpression { Value: true })
+ {
+ return null;
+ }
+
var preprocessor = new FilterTranslationPreprocessor { SupportsParameterization = false };
var preprocessedExpression = preprocessor.Preprocess(lambdaExpression.Body);
diff --git a/dotnet/src/VectorData/Pinecone/PineconeMapper.cs b/dotnet/src/VectorData/Pinecone/PineconeMapper.cs
index 3e0ce4b78ab9..5c43ab1b7191 100644
--- a/dotnet/src/VectorData/Pinecone/PineconeMapper.cs
+++ b/dotnet/src/VectorData/Pinecone/PineconeMapper.cs
@@ -44,7 +44,12 @@ public Vector MapFromDataToStorageModel(TRecord dataModel, Embedding? gen
// TODO: what about sparse values?
var result = new Vector
{
- Id = (string)keyObject,
+ Id = keyObject switch
+ {
+ string s => s,
+ Guid g => g.ToString(),
+ _ => throw new UnreachableException()
+ },
Values = values,
Metadata = metadata,
SparseValues = null
@@ -58,7 +63,13 @@ public TRecord MapFromStorageToDataModel(Vector storageModel, bool includeVector
{
var outputRecord = model.CreateRecord()!;
- model.KeyProperty.SetValueAsObject(outputRecord, storageModel.Id);
+ model.KeyProperty.SetValueAsObject(outputRecord, model.KeyProperty.Type switch
+ {
+ var t when t == typeof(string) => storageModel.Id,
+ var t when t == typeof(Guid) => Guid.Parse(storageModel.Id),
+
+ _ => throw new UnreachableException()
+ });
if (includeVectors is true)
{
diff --git a/dotnet/src/VectorData/Pinecone/PineconeModelBuilder.cs b/dotnet/src/VectorData/Pinecone/PineconeModelBuilder.cs
index 60e8bbb1de1d..cdba6c103e92 100644
--- a/dotnet/src/VectorData/Pinecone/PineconeModelBuilder.cs
+++ b/dotnet/src/VectorData/Pinecone/PineconeModelBuilder.cs
@@ -21,9 +21,9 @@ internal class PineconeModelBuilder() : CollectionModelBuilder(s_validationOptio
protected override bool IsKeyPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
{
- supportedTypes = "string";
+ supportedTypes = "string, Guid";
- return type == typeof(string);
+ return type == typeof(string) || type == typeof(Guid);
}
protected override bool IsDataPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
diff --git a/dotnet/src/VectorData/Qdrant/QdrantCollectionCreateMapping.cs b/dotnet/src/VectorData/Qdrant/QdrantCollectionCreateMapping.cs
index 6a53847215ba..0928685c1702 100644
--- a/dotnet/src/VectorData/Qdrant/QdrantCollectionCreateMapping.cs
+++ b/dotnet/src/VectorData/Qdrant/QdrantCollectionCreateMapping.cs
@@ -58,7 +58,7 @@ public static VectorParams MapSingleVector(VectorPropertyModel vectorProperty)
{
if (vectorProperty!.IndexKind is not null && vectorProperty!.IndexKind != IndexKind.Hnsw)
{
- throw new InvalidOperationException($"Index kind '{vectorProperty!.IndexKind}' for {nameof(VectorStoreVectorProperty)} '{vectorProperty.ModelName}' is not supported by the Qdrant VectorStore.");
+ throw new NotSupportedException($"Index kind '{vectorProperty!.IndexKind}' for {nameof(VectorStoreVectorProperty)} '{vectorProperty.ModelName}' is not supported by the Qdrant VectorStore.");
}
return new VectorParams { Size = (ulong)vectorProperty.Dimensions, Distance = QdrantCollectionCreateMapping.GetSDKDistanceAlgorithm(vectorProperty) };
@@ -98,6 +98,6 @@ public static Distance GetSDKDistanceAlgorithm(VectorPropertyModel vectorPropert
DistanceFunction.EuclideanDistance => Distance.Euclid,
DistanceFunction.ManhattanDistance => Distance.Manhattan,
- _ => throw new InvalidOperationException($"Distance function '{vectorProperty.DistanceFunction}' for {nameof(VectorStoreVectorProperty)} '{vectorProperty.ModelName}' is not supported by the Qdrant VectorStore.")
+ _ => throw new NotSupportedException($"Distance function '{vectorProperty.DistanceFunction}' for {nameof(VectorStoreVectorProperty)} '{vectorProperty.ModelName}' is not supported by the Qdrant VectorStore.")
};
}
diff --git a/dotnet/src/VectorData/Redis/RedisCollectionCreateMapping.cs b/dotnet/src/VectorData/Redis/RedisCollectionCreateMapping.cs
index a0b4691a13ef..f276e8a3a429 100644
--- a/dotnet/src/VectorData/Redis/RedisCollectionCreateMapping.cs
+++ b/dotnet/src/VectorData/Redis/RedisCollectionCreateMapping.cs
@@ -161,7 +161,8 @@ public static string GetSDKDistanceAlgorithm(VectorPropertyModel vectorProperty)
DistanceFunction.CosineDistance => "COSINE",
DistanceFunction.DotProductSimilarity => "IP",
DistanceFunction.EuclideanSquaredDistance => "L2",
- _ => throw new InvalidOperationException($"Distance function '{vectorProperty.DistanceFunction}' for {nameof(VectorStoreVectorProperty)} '{vectorProperty.ModelName}' is not supported by the Redis VectorStore.")
+
+ _ => throw new NotSupportedException($"Distance function '{vectorProperty.DistanceFunction}' for {nameof(VectorStoreVectorProperty)} '{vectorProperty.ModelName}' is not supported by the Redis VectorStore.")
};
///
diff --git a/dotnet/src/VectorData/Redis/RedisHashSetCollection.cs b/dotnet/src/VectorData/Redis/RedisHashSetCollection.cs
index 672bfba900b4..34dd631df5f8 100644
--- a/dotnet/src/VectorData/Redis/RedisHashSetCollection.cs
+++ b/dotnet/src/VectorData/Redis/RedisHashSetCollection.cs
@@ -88,9 +88,9 @@ internal RedisHashSetCollection(IDatabase database, string name, Func s,
+ Guid g => g.ToString(),
+
+ _ => throw new UnreachableException("string key should have been validated during model building")
+ };
Verify.NotNullOrWhiteSpace(stringKey, nameof(key));
diff --git a/dotnet/src/VectorData/Redis/RedisHashSetMapper.cs b/dotnet/src/VectorData/Redis/RedisHashSetMapper.cs
index 90b9fa3d7be9..b19b9e9ad96a 100644
--- a/dotnet/src/VectorData/Redis/RedisHashSetMapper.cs
+++ b/dotnet/src/VectorData/Redis/RedisHashSetMapper.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Extensions.AI;
@@ -19,8 +20,13 @@ internal sealed class RedisHashSetMapper(CollectionModel mod
///
public (string Key, HashEntry[] HashEntries) MapFromDataToStorageModel(TConsumerDataModel dataModel, int recordIndex, IReadOnlyList?[]? generatedEmbeddings)
{
- var keyValue = model.KeyProperty.GetValueAsObject(dataModel!) as string ??
- throw new InvalidOperationException($"Missing key property {model.KeyProperty.ModelName} on provided record of type '{typeof(TConsumerDataModel).Name}'.");
+ var keyValue = model.KeyProperty.GetValueAsObject(dataModel!) switch
+ {
+ string s => s,
+ Guid g => g.ToString(),
+
+ _ => throw new InvalidOperationException($"Missing key property {model.KeyProperty.ModelName} on provided record of type '{typeof(TConsumerDataModel).Name}'.")
+ };
var hashEntries = new List();
foreach (var property in model.DataProperties)
@@ -67,7 +73,15 @@ public TConsumerDataModel MapFromStorageToDataModel((string Key, HashEntry[] Has
var outputRecord = model.CreateRecord()!;
// Set Key.
- model.KeyProperty.SetValueAsObject(outputRecord, storageModel.Key);
+ model.KeyProperty.SetValueAsObject(outputRecord, model.KeyProperty.Type switch
+ {
+ Type t when t == typeof(string)
+ => storageModel.Key,
+ Type t when t == typeof(Guid)
+ => Guid.Parse(storageModel.Key),
+
+ _ => throw new UnreachableException()
+ });
// Set each vector property if embeddings should be returned.
if (includeVectors)
diff --git a/dotnet/src/VectorData/Redis/RedisJsonCollection.cs b/dotnet/src/VectorData/Redis/RedisJsonCollection.cs
index 6db5dbf92e9e..625ffe64ad33 100644
--- a/dotnet/src/VectorData/Redis/RedisJsonCollection.cs
+++ b/dotnet/src/VectorData/Redis/RedisJsonCollection.cs
@@ -97,9 +97,9 @@ internal RedisJsonCollection(IDatabase database, string name, Func);
@@ -596,7 +596,13 @@ private string GetStringKey(TKey key)
{
Verify.NotNull(key);
- var stringKey = key as string ?? throw new UnreachableException("string key should have been validated during model building");
+ var stringKey = key switch
+ {
+ string s => s,
+ Guid g => g.ToString(),
+
+ _ => throw new UnreachableException("string key should have been validated during model building")
+ };
Verify.NotNullOrWhiteSpace(stringKey, nameof(key));
diff --git a/dotnet/src/VectorData/Redis/RedisJsonDynamicMapper.cs b/dotnet/src/VectorData/Redis/RedisJsonDynamicMapper.cs
index 58673ef03078..6f6a38bac619 100644
--- a/dotnet/src/VectorData/Redis/RedisJsonDynamicMapper.cs
+++ b/dotnet/src/VectorData/Redis/RedisJsonDynamicMapper.cs
@@ -86,7 +86,15 @@ internal class RedisJsonDynamicMapper(CollectionModel model, JsonSerializerOptio
}
}
- return ((string)dataModel[model.KeyProperty.ModelName]!, jsonObject);
+ var storageKey = dataModel[model.KeyProperty.ModelName] switch
+ {
+ string s => s,
+ Guid g => g.ToString(),
+
+ _ => throw new UnreachableException()
+ };
+
+ return (storageKey, jsonObject);
}
///
@@ -94,14 +102,23 @@ internal class RedisJsonDynamicMapper(CollectionModel model, JsonSerializerOptio
{
var dataModel = new Dictionary
{
- [model.KeyProperty.ModelName] = storageModel.Key,
+ [model.KeyProperty.ModelName] = model.KeyProperty.Type switch
+ {
+ Type t when t == typeof(string) => storageModel.Key,
+ Type t when t == typeof(Guid) => Guid.Parse(storageModel.Key),
+
+ _ => throw new UnreachableException()
+ },
};
// The redis result can be either a single object or an array with a single object in the case where we are doing an MGET.
+ // If there's a single data property, we get a simple value (no object wrapper).
var jsonObject = storageModel.Node switch
{
- JsonObject topLevelJsonObject => topLevelJsonObject,
- JsonArray jsonArray and [JsonObject arrayEntryJsonObject] => arrayEntryJsonObject,
+ JsonValue v when model.DataProperties is [var singleDataProperty] => new JsonObject([new(singleDataProperty.StorageName, v)]),
+ JsonObject o => o,
+ JsonArray a and [JsonObject arrayEntryJsonObject] => arrayEntryJsonObject,
+
_ => throw new InvalidOperationException($"Invalid data format for document with key '{storageModel.Key}'"),
};
diff --git a/dotnet/src/VectorData/Redis/RedisJsonDynamicModelBuilder.cs b/dotnet/src/VectorData/Redis/RedisJsonDynamicModelBuilder.cs
index b27cc1f570fd..5edc3e68d1ce 100644
--- a/dotnet/src/VectorData/Redis/RedisJsonDynamicModelBuilder.cs
+++ b/dotnet/src/VectorData/Redis/RedisJsonDynamicModelBuilder.cs
@@ -19,9 +19,9 @@ internal class RedisJsonDynamicModelBuilder(CollectionModelBuildingOptions optio
protected override bool IsKeyPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
{
- supportedTypes = "string";
+ supportedTypes = "string, Guid";
- return type == typeof(string);
+ return type == typeof(string) || type == typeof(Guid);
}
protected override bool IsDataPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
diff --git a/dotnet/src/VectorData/Redis/RedisJsonModelBuilder.cs b/dotnet/src/VectorData/Redis/RedisJsonModelBuilder.cs
index 82115b0f6f60..2e831563fe85 100644
--- a/dotnet/src/VectorData/Redis/RedisJsonModelBuilder.cs
+++ b/dotnet/src/VectorData/Redis/RedisJsonModelBuilder.cs
@@ -19,9 +19,9 @@ internal class RedisJsonModelBuilder(CollectionModelBuildingOptions options) : C
protected override bool IsKeyPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
{
- supportedTypes = "string";
+ supportedTypes = "string, Guid";
- return type == typeof(string);
+ return type == typeof(string) || type == typeof(Guid);
}
protected override bool IsDataPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
diff --git a/dotnet/src/VectorData/Redis/RedisModelBuilder.cs b/dotnet/src/VectorData/Redis/RedisModelBuilder.cs
index c8ddf3c0c34c..5ae92c695ca7 100644
--- a/dotnet/src/VectorData/Redis/RedisModelBuilder.cs
+++ b/dotnet/src/VectorData/Redis/RedisModelBuilder.cs
@@ -21,9 +21,9 @@ internal class RedisModelBuilder(CollectionModelBuildingOptions options) : Colle
protected override bool IsKeyPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
{
- supportedTypes = "string";
+ supportedTypes = "string, Guid";
- return type == typeof(string);
+ return type == typeof(string) || type == typeof(Guid);
}
protected override bool IsDataPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
diff --git a/dotnet/src/VectorData/SqlServer/SqlServerModelBuilder.cs b/dotnet/src/VectorData/SqlServer/SqlServerModelBuilder.cs
index 688861f72928..70e444e88544 100644
--- a/dotnet/src/VectorData/SqlServer/SqlServerModelBuilder.cs
+++ b/dotnet/src/VectorData/SqlServer/SqlServerModelBuilder.cs
@@ -22,14 +22,12 @@ internal class SqlServerModelBuilder() : CollectionModelBuilder(s_modelBuildingO
protected override bool IsKeyPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
{
- supportedTypes = "int, long, string, Guid, DateTime, or byte[]";
+ supportedTypes = "int, long, string, Guid";
return type == typeof(int) // INT
|| type == typeof(long) // BIGINT
|| type == typeof(string) // VARCHAR
- || type == typeof(Guid) // UNIQUEIDENTIFIER
- || type == typeof(DateTime) // DATETIME2
- || type == typeof(byte[]); // VARBINARY
+ || type == typeof(Guid); // UNIQUEIDENTIFIER
}
protected override bool IsDataPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
diff --git a/dotnet/src/VectorData/SqliteVec/SqliteCollection.cs b/dotnet/src/VectorData/SqliteVec/SqliteCollection.cs
index 570865cc2acb..169ec78f8061 100644
--- a/dotnet/src/VectorData/SqliteVec/SqliteCollection.cs
+++ b/dotnet/src/VectorData/SqliteVec/SqliteCollection.cs
@@ -85,9 +85,13 @@ internal SqliteCollection(string connectionString, string name, Func Parameters => this._parameters;
+ protected override void TranslateConstant(object? value, bool isSearchCondition)
+ {
+ switch (value)
+ {
+ case Guid g:
+ // Microsoft.Data.Sqlite writes GUIDs as upper-case strings, align our constant formatting with that.
+ this._sql.Append('\'').Append(g.ToString().ToUpperInvariant()).Append('\'');
+ break;
+ default:
+ base.TranslateConstant(value, isSearchCondition);
+ break;
+ }
+ }
+
// TODO: support Contains over array fields (#10343)
protected override void TranslateContainsOverArrayColumn(Expression source, Expression item)
=> throw new NotSupportedException("Unsupported Contains expression");
diff --git a/dotnet/src/VectorData/SqliteVec/SqliteMapper.cs b/dotnet/src/VectorData/SqliteVec/SqliteMapper.cs
index b5aa0e2c6ed8..e06dacdf0179 100644
--- a/dotnet/src/VectorData/SqliteVec/SqliteMapper.cs
+++ b/dotnet/src/VectorData/SqliteVec/SqliteMapper.cs
@@ -96,6 +96,7 @@ public TRecord MapFromStorageToDataModel(DbDataReader reader, bool includeVector
Type t when t == typeof(float) => reader.GetFloat(ordinal),
Type t when t == typeof(double) => reader.GetDouble(ordinal),
Type t when t == typeof(string) => reader.GetString(ordinal),
+ Type t when t == typeof(Guid) => reader.GetGuid(ordinal),
Type t when t == typeof(byte[]) => (byte[])reader[ordinal],
Type t when t == typeof(ReadOnlyMemory) => (byte[])reader[ordinal],
Type t when t == typeof(Embedding) => (byte[])reader[ordinal],
diff --git a/dotnet/src/VectorData/SqliteVec/SqliteModelBuilder.cs b/dotnet/src/VectorData/SqliteVec/SqliteModelBuilder.cs
index ef74a1a2b7e1..d771bc016f9b 100644
--- a/dotnet/src/VectorData/SqliteVec/SqliteModelBuilder.cs
+++ b/dotnet/src/VectorData/SqliteVec/SqliteModelBuilder.cs
@@ -20,14 +20,14 @@ internal class SqliteModelBuilder() : CollectionModelBuilder(s_modelBuildingOpti
protected override bool IsKeyPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
{
- supportedTypes = "int, long, string";
+ supportedTypes = "int, long, string, Guid";
- return type == typeof(int) || type == typeof(long) || type == typeof(string);
+ return type == typeof(int) || type == typeof(long) || type == typeof(string) || type == typeof(Guid);
}
protected override bool IsDataPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
{
- supportedTypes = "int, long, short, string, bool, float, double, byte[]";
+ supportedTypes = "int, long, short, string, bool, float, double, byte[], Guid";
if (Nullable.GetUnderlyingType(type) is Type underlyingType)
{
@@ -41,7 +41,8 @@ protected override bool IsDataPropertyTypeValid(Type type, [NotNullWhen(false)]
|| type == typeof(bool)
|| type == typeof(float)
|| type == typeof(double)
- || type == typeof(byte[]);
+ || type == typeof(byte[])
+ || type == typeof(Guid);
}
protected override bool IsVectorPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes)
diff --git a/dotnet/src/VectorData/SqliteVec/SqlitePropertyMapping.cs b/dotnet/src/VectorData/SqliteVec/SqlitePropertyMapping.cs
index ae369bad9b39..20f745e1799f 100644
--- a/dotnet/src/VectorData/SqliteVec/SqlitePropertyMapping.cs
+++ b/dotnet/src/VectorData/SqliteVec/SqlitePropertyMapping.cs
@@ -107,9 +107,12 @@ private static string GetStorageDataPropertyType(PropertyModel property)
// String type
Type t when t == typeof(string) => "TEXT",
- // Boolean types - SQLite doesn't have a boolean type, represent it as 0/1
+ // Boolean type - represent it as INTEGER (0/1 (this is standard SQLite)
Type t when t == typeof(bool) || t == typeof(bool?) => "INTEGER",
+ // Guid type - represent as TEXT
+ Type t when t == typeof(Guid) || t == typeof(Guid?) => "TEXT",
+
// Byte array (BLOB)
Type t when t == typeof(byte[]) => "BLOB",
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearch.ConformanceTests.csproj b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearch.ConformanceTests.csproj
index cf64171e6123..74bb15fb42e6 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearch.ConformanceTests.csproj
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearch.ConformanceTests.csproj
@@ -23,6 +23,7 @@
+
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchCollectionManagementTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchCollectionManagementTests.cs
index b2b6fb7605eb..dfa4a2de373c 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchCollectionManagementTests.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchCollectionManagementTests.cs
@@ -7,8 +7,4 @@
namespace AzureAISearch.ConformanceTests;
public class AzureAISearchCollectionManagementTests(AzureAISearchFixture fixture)
- : CollectionManagementTests(fixture), IClassFixture
-{
- // Azure AI search only supports lowercase letters, digits or dashes.
- public override string CollectionName => "collection-tests";
-}
+ : CollectionManagementTests(fixture), IClassFixture;
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchDependencyInjectionTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchDependencyInjectionTests.cs
index ba683b71d478..bffb8a6a08fb 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchDependencyInjectionTests.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchDependencyInjectionTests.cs
@@ -6,13 +6,12 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel.Connectors.AzureAISearch;
using VectorData.ConformanceTests;
-using VectorData.ConformanceTests.Models;
using Xunit;
namespace AzureAISearch.ConformanceTests;
public class AzureAISearchDependencyInjectionTests
- : DependencyInjectionTests>, string, SimpleRecord>
+ : DependencyInjectionTests.Record>, string, DependencyInjectionTests.Record>
{
private static readonly Uri s_endpoint = new("https://localhost");
private static readonly AzureKeyCredential s_keyCredential = new("fakeKey");
@@ -36,24 +35,24 @@ private static AzureKeyCredential KeyProvider(IServiceProvider sp, object? servi
{
yield return (services, serviceKey, name, lifetime) => serviceKey is null
? services
- .AddAzureAISearchCollection>(name,
+ .AddAzureAISearchCollection(name,
sp => new SearchIndexClient(EndpointProvider(sp), KeyProvider(sp)), lifetime: lifetime)
: services
- .AddKeyedAzureAISearchCollection>(serviceKey, name,
+ .AddKeyedAzureAISearchCollection(serviceKey, name,
sp => new SearchIndexClient(EndpointProvider(sp, serviceKey), KeyProvider(sp, serviceKey)), lifetime: lifetime);
yield return (services, serviceKey, name, lifetime) => serviceKey is null
? services
.AddSingleton(sp => new SearchIndexClient(s_endpoint, s_keyCredential))
- .AddAzureAISearchCollection>(name, lifetime: lifetime)
+ .AddAzureAISearchCollection(name, lifetime: lifetime)
: services
.AddSingleton(sp => new SearchIndexClient(s_endpoint, s_keyCredential))
- .AddKeyedAzureAISearchCollection>(serviceKey, name, lifetime: lifetime);
+ .AddKeyedAzureAISearchCollection(serviceKey, name, lifetime: lifetime);
yield return (services, serviceKey, name, lifetime) => serviceKey is null
- ? services.AddAzureAISearchCollection>(
+ ? services.AddAzureAISearchCollection(
name, s_endpoint, s_keyCredential, lifetime: lifetime)
- : services.AddKeyedAzureAISearchCollection>(
+ : services.AddKeyedAzureAISearchCollection(
serviceKey, name, s_endpoint, s_keyCredential, lifetime: lifetime);
}
}
@@ -89,9 +88,9 @@ public void EndpointCantBeNull()
{
IServiceCollection services = new ServiceCollection();
- Assert.Throws(() => services.AddAzureAISearchCollection>(
+ Assert.Throws(() => services.AddAzureAISearchCollection(
name: "notNull", endpoint: null!, s_keyCredential));
- Assert.Throws(() => services.AddKeyedAzureAISearchCollection>(
+ Assert.Throws(() => services.AddKeyedAzureAISearchCollection(
serviceKey: "notNull", name: "notNull", endpoint: null!, s_keyCredential));
}
@@ -100,9 +99,9 @@ public void KeyCredentialCantBeNull()
{
IServiceCollection services = new ServiceCollection();
- Assert.Throws(() => services.AddAzureAISearchCollection>(
+ Assert.Throws(() => services.AddAzureAISearchCollection(
name: "notNull", s_endpoint, keyCredential: null!));
- Assert.Throws(() => services.AddKeyedAzureAISearchCollection>(
+ Assert.Throws(() => services.AddKeyedAzureAISearchCollection(
serviceKey: "notNull", name: "notNull", s_endpoint, keyCredential: null!));
}
@@ -111,9 +110,9 @@ public void TokenCredentialCantBeNull()
{
IServiceCollection services = new ServiceCollection();
- Assert.Throws(() => services.AddAzureAISearchCollection>(
+ Assert.Throws(() => services.AddAzureAISearchCollection(
name: "notNull", s_endpoint, tokenCredential: null!));
- Assert.Throws(() => services.AddKeyedAzureAISearchCollection>(
+ Assert.Throws(() => services.AddKeyedAzureAISearchCollection(
serviceKey: "notNull", name: "notNull", s_endpoint, tokenCredential: null!));
}
}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchDistanceFunctionTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchDistanceFunctionTests.cs
new file mode 100644
index 000000000000..0e3c3ff7dc19
--- /dev/null
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchDistanceFunctionTests.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using AzureAISearch.ConformanceTests.Support;
+using VectorData.ConformanceTests;
+using VectorData.ConformanceTests.Support;
+using Xunit;
+
+namespace AzureAISearch.ConformanceTests;
+
+public class AzureAISearchDistanceFunctionTests(AzureAISearchDistanceFunctionTests.Fixture fixture)
+ : DistanceFunctionTests(fixture), IClassFixture
+{
+ public override Task CosineDistance() => Assert.ThrowsAsync(base.CosineDistance);
+ public override Task NegativeDotProductSimilarity() => Assert.ThrowsAsync(base.NegativeDotProductSimilarity);
+ public override Task EuclideanSquaredDistance() => Assert.ThrowsAsync(base.EuclideanSquaredDistance);
+ public override Task HammingDistance() => Assert.ThrowsAsync(base.HammingDistance);
+ public override Task ManhattanDistance() => Assert.ThrowsAsync(base.ManhattanDistance);
+
+ public new class Fixture() : DistanceFunctionTests.Fixture
+ {
+ public override TestStore TestStore => AzureAISearchTestStore.Instance;
+
+ // AzureAISearch does not return the expected standard mathematical result for each distance function
+ public override bool AssertScores => false;
+ }
+}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchEmbeddingGenerationTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchEmbeddingGenerationTests.cs
index b0ccd06b1798..b3fa6e1e6ed6 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchEmbeddingGenerationTests.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchEmbeddingGenerationTests.cs
@@ -21,9 +21,6 @@ public override Task SearchAsync_string_without_generator_throws()
{
public override TestStore TestStore => AzureAISearchTestStore.Instance;
- // Azure AI search only supports lowercase letters, digits or dashes.
- public override string CollectionName => "embedding-gen-tests" + AzureAISearchTestEnvironment.TestIndexPostfix;
-
public override VectorStore CreateVectorStore(IEmbeddingGenerator? embeddingGenerator)
=> AzureAISearchTestStore.Instance.GetVectorStore(new() { EmbeddingGenerator = embeddingGenerator });
@@ -46,9 +43,6 @@ public override VectorStore CreateVectorStore(IEmbeddingGenerator? embeddingGene
{
public override TestStore TestStore => AzureAISearchTestStore.Instance;
- // Azure AI search only supports lowercase letters, digits or dashes.
- public override string CollectionName => "search-only-embedding-gen-tests" + AzureAISearchTestEnvironment.TestIndexPostfix;
-
public override VectorStore CreateVectorStore(IEmbeddingGenerator? embeddingGenerator)
=> AzureAISearchTestStore.Instance.GetVectorStore(new() { EmbeddingGenerator = embeddingGenerator });
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchFilterTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchFilterTests.cs
index 206bb7cb41bb..ca9b4f3a54ad 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchFilterTests.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchFilterTests.cs
@@ -17,8 +17,5 @@ public override Task Contains_over_inline_int_array()
public new class Fixture : FilterTests.Fixture
{
public override TestStore TestStore => AzureAISearchTestStore.Instance;
-
- // Azure AI search only supports lowercase letters, digits or dashes.
- public override string CollectionName => "filter-tests" + AzureAISearchTestEnvironment.TestIndexPostfix;
}
}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchHybridSearchTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchHybridSearchTests.cs
new file mode 100644
index 000000000000..475a773cd1d1
--- /dev/null
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchHybridSearchTests.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using AzureAISearch.ConformanceTests.Support;
+using VectorData.ConformanceTests;
+using VectorData.ConformanceTests.Support;
+using Xunit;
+
+namespace AzureAISearch.ConformanceTests;
+
+public class AzureAISearchHybridSearchTests(
+ AzureAISearchHybridSearchTests.VectorAndStringFixture vectorAndStringFixture,
+ AzureAISearchHybridSearchTests.MultiTextFixture multiTextFixture)
+ : HybridSearchTests(vectorAndStringFixture, multiTextFixture),
+ IClassFixture,
+ IClassFixture
+{
+ public new class VectorAndStringFixture : HybridSearchTests.VectorAndStringFixture
+ {
+ public override TestStore TestStore => AzureAISearchTestStore.Instance;
+ }
+
+ public new class MultiTextFixture : HybridSearchTests.MultiTextFixture
+ {
+ public override TestStore TestStore => AzureAISearchTestStore.Instance;
+ }
+}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchIndexKindTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchIndexKindTests.cs
new file mode 100644
index 000000000000..844e905999f3
--- /dev/null
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchIndexKindTests.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using AzureAISearch.ConformanceTests.Support;
+using Microsoft.Extensions.VectorData;
+using VectorData.ConformanceTests;
+using VectorData.ConformanceTests.Support;
+using VectorData.ConformanceTests.Xunit;
+using Xunit;
+
+namespace AzureAISearch.ConformanceTests;
+
+public class AzureAISearchIndexKindTests(AzureAISearchIndexKindTests.Fixture fixture)
+ : IndexKindTests(fixture), IClassFixture
+{
+ [ConditionalFact]
+ public virtual Task Hnsw()
+ => this.Test(IndexKind.Hnsw);
+
+ public new class Fixture() : IndexKindTests.Fixture
+ {
+ public override TestStore TestStore => AzureAISearchTestStore.Instance;
+ }
+}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchTestSuiteImplementationTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchTestSuiteImplementationTests.cs
index e4be1b2ab7e1..c6d31ec313f1 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchTestSuiteImplementationTests.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchTestSuiteImplementationTests.cs
@@ -1,15 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using VectorData.ConformanceTests;
-using VectorData.ConformanceTests.VectorSearch;
namespace AzureAISearch.ConformanceTests;
-public class AzureAISearchTestSuiteImplementationTests : TestSuiteImplementationTests
-{
- protected override ICollection IgnoredTestBases { get; } =
- [
- typeof(VectorSearchDistanceFunctionComplianceTests<>),
- typeof(VectorSearchWithFilterConformanceTests<>),
- ];
-}
+public class AzureAISearchTestSuiteImplementationTests : TestSuiteImplementationTests;
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/HybridSearch/AzureAISearchKeywordVectorizedHybridSearchTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/HybridSearch/AzureAISearchKeywordVectorizedHybridSearchTests.cs
deleted file mode 100644
index 652278fc7b7c..000000000000
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/HybridSearch/AzureAISearchKeywordVectorizedHybridSearchTests.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using AzureAISearch.ConformanceTests.Support;
-using Microsoft.Extensions.VectorData;
-using VectorData.ConformanceTests.HybridSearch;
-using VectorData.ConformanceTests.Support;
-using Xunit;
-
-namespace AzureAISearch.ConformanceTests.HybridSearch;
-
-///
-/// Inherits common integration tests that should pass for any .
-///
-public class AzureAISearchKeywordVectorizedHybridSearchTests(
- AzureAISearchKeywordVectorizedHybridSearchTests.VectorAndStringFixture vectorAndStringFixture,
- AzureAISearchKeywordVectorizedHybridSearchTests.MultiTextFixture multiTextFixture)
- : KeywordVectorizedHybridSearchComplianceTests(vectorAndStringFixture, multiTextFixture),
- IClassFixture,
- IClassFixture
-{
- public new class VectorAndStringFixture : KeywordVectorizedHybridSearchComplianceTests.VectorAndStringFixture
- {
- public override TestStore TestStore => AzureAISearchTestStore.Instance;
-
- // Azure AI search only supports lowercase letters, digits or dashes.
- public override string CollectionName => "vecstring-hybrid-search-" + AzureAISearchTestEnvironment.TestIndexPostfix;
- }
-
- public new class MultiTextFixture : KeywordVectorizedHybridSearchComplianceTests.MultiTextFixture
- {
- public override TestStore TestStore => AzureAISearchTestStore.Instance;
-
- // Azure AI search only supports lowercase letters, digits or dashes.
- public override string CollectionName => "multitext-hybrid-search-" + AzureAISearchTestEnvironment.TestIndexPostfix;
- }
-}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchAllSupportedTypesTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchAllSupportedTypesTests.cs
deleted file mode 100644
index 001506c722c7..000000000000
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchAllSupportedTypesTests.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using AzureAISearch.ConformanceTests.Support;
-using Microsoft.Extensions.VectorData;
-using VectorData.ConformanceTests.Xunit;
-using Xunit;
-
-namespace AzureAISearch.ConformanceTests.CRUD;
-
-public class AzureAISearchAllSupportedTypesTests(AzureAISearchFixture fixture) : IClassFixture
-{
- [ConditionalFact]
- public async Task AllTypesBatchGetAsync()
- {
- var collection = fixture.TestStore.DefaultVectorStore.GetCollection("all-types", AzureAISearchAllTypes.GetRecordDefinition());
- await collection.EnsureCollectionExistsAsync();
-
- List records =
- [
- new()
- {
- Id = "all-types-1",
- BoolProperty = true,
- NullableBoolProperty = false,
- StringProperty = "string prop 1",
- NullableStringProperty = "nullable prop 1",
- IntProperty = 1,
- NullableIntProperty = 10,
- LongProperty = 100L,
- NullableLongProperty = 1000L,
- FloatProperty = 10.5f,
- NullableFloatProperty = 100.5f,
- DoubleProperty = 23.75d,
- NullableDoubleProperty = 233.75d,
- DateTimeOffsetProperty = DateTimeOffset.UtcNow,
- NullableDateTimeOffsetProperty = DateTimeOffset.UtcNow,
- StringArray = ["one", "two"],
- StringList = ["eleven", "twelve"],
- BoolArray = [true, false],
- BoolList = [true, false],
- IntArray = [1, 2],
- IntList = [11, 12],
- LongArray = [100L, 200L],
- LongList = [1100L, 1200L],
- FloatArray = [1.5f, 2.5f],
- FloatList = [11.5f, 12.5f],
- DoubleArray = [1.5d, 2.5d],
- DoubleList = [11.5d, 12.5d],
- DateTimeOffsetArray = [DateTimeOffset.UtcNow, DateTimeOffset.UtcNow],
- DateTimeOffsetList = [DateTimeOffset.UtcNow, DateTimeOffset.UtcNow],
- Embedding = new ReadOnlyMemory([1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f, 8.5f])
- },
- new()
- {
- Id = "all-types-2",
- BoolProperty = false,
- NullableBoolProperty = null,
- StringProperty = "string prop 2",
- NullableStringProperty = null,
- IntProperty = 2,
- NullableIntProperty = null,
- LongProperty = 200L,
- NullableLongProperty = null,
- FloatProperty = 20.5f,
- NullableFloatProperty = null,
- DoubleProperty = 43.75,
- NullableDoubleProperty = null,
- Embedding = ReadOnlyMemory.Empty,
- // From https://learn.microsoft.com/en-us/rest/api/searchservice/supported-data-types:
- // "All of the above types are nullable, except for collections of primitive and complex types, for example, Collection(Edm.String)"
- // So for collections, we can't use nulls.
- StringArray = [],
- StringList = [],
- BoolArray = [],
- BoolList = [],
- IntArray = [],
- IntList = [],
- LongArray = [],
- LongList = [],
- FloatArray = [],
- FloatList = [],
- DoubleArray = [],
- DoubleList = [],
- DateTimeOffsetArray = [],
- DateTimeOffsetList = [],
- }
- ];
-
- try
- {
- await collection.UpsertAsync(records);
-
- var allTypes = await collection.GetAsync(records.Select(r => r.Id), new RecordRetrievalOptions { IncludeVectors = true }).ToListAsync();
-
- var allTypes1 = allTypes.Single(x => x.Id == records[0].Id);
- var allTypes2 = allTypes.Single(x => x.Id == records[1].Id);
-
- records[0].AssertEqual(allTypes1);
- records[1].AssertEqual(allTypes2);
- }
- finally
- {
- await collection.EnsureCollectionDeletedAsync();
- }
- }
-}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchBasicModelTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchBasicModelTests.cs
index 772d2cb40047..a54b8fb1688f 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchBasicModelTests.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchBasicModelTests.cs
@@ -12,8 +12,6 @@ public class AzureAISearchBasicModelTests(AzureAISearchBasicModelTests.Fixture f
{
public new class Fixture : BasicModelTests.Fixture
{
- public override string CollectionName => "basic-" + AzureAISearchTestEnvironment.TestIndexPostfix;
-
public override TestStore TestStore => AzureAISearchTestStore.Instance;
}
}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchDynamicModelTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchDynamicModelTests.cs
index e89ab37442d6..12f70fe06769 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchDynamicModelTests.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchDynamicModelTests.cs
@@ -12,8 +12,6 @@ public class AzureAISearchDynamicModelTests(AzureAISearchDynamicModelTests.Fixtu
{
public new class Fixture : DynamicModelTests.Fixture
{
- public override string CollectionName => "dynamic-" + AzureAISearchTestEnvironment.TestIndexPostfix;
-
public override TestStore TestStore => AzureAISearchTestStore.Instance;
}
}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchMultiVectorModelTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchMultiVectorModelTests.cs
new file mode 100644
index 000000000000..a7ef05da6134
--- /dev/null
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchMultiVectorModelTests.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using AzureAISearch.ConformanceTests.Support;
+using VectorData.ConformanceTests.ModelTests;
+using VectorData.ConformanceTests.Support;
+using Xunit;
+
+namespace AzureAISearch.ConformanceTests.ModelTests;
+
+public class AzureAISearchMultiVectorModelTests(AzureAISearchMultiVectorModelTests.Fixture fixture)
+ : MultiVectorModelTests(fixture), IClassFixture
+{
+ public new class Fixture : MultiVectorModelTests.Fixture
+ {
+ public override string CollectionName => "multi-vector-" + AzureAISearchTestEnvironment.TestIndexPostfix;
+
+ public override TestStore TestStore => AzureAISearchTestStore.Instance;
+ }
+}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchNoDataModelTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchNoDataModelTests.cs
index 87b0acac8939..483dc997e76b 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchNoDataModelTests.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchNoDataModelTests.cs
@@ -12,8 +12,6 @@ public class AzureAISearchNoDataModelTests(AzureAISearchNoDataModelTests.Fixture
{
public new class Fixture : NoDataModelTests.Fixture
{
- public override string CollectionName => "nodata-" + AzureAISearchTestEnvironment.TestIndexPostfix;
-
public override TestStore TestStore => AzureAISearchTestStore.Instance;
}
}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchNoVectorModelTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchNoVectorModelTests.cs
index d61c2ddfac37..ec78ad4963c8 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchNoVectorModelTests.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/ModelTests/AzureAISearchNoVectorModelTests.cs
@@ -12,8 +12,6 @@ public class AzureAISearchNoVectorModelTests(AzureAISearchNoVectorModelTests.Fix
{
public new class Fixture : NoVectorModelTests.Fixture
{
- public override string CollectionName => "novector-" + AzureAISearchTestEnvironment.TestIndexPostfix;
-
public override TestStore TestStore => AzureAISearchTestStore.Instance;
}
}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/Support/AzureAISearchTestEnvironment.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/Support/AzureAISearchTestEnvironment.cs
index 8c19e77ab15c..dc63ea196fae 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/Support/AzureAISearchTestEnvironment.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/Support/AzureAISearchTestEnvironment.cs
@@ -10,7 +10,7 @@ namespace AzureAISearch.ConformanceTests.Support;
internal static class AzureAISearchTestEnvironment
{
#pragma warning disable CA1308 // Normalize strings to uppercase
- public static readonly string TestIndexPostfix = new Regex("[^a-zA-Z0-9]").Replace(Environment.MachineName.ToLowerInvariant(), "");
+ public static readonly string TestIndexPostfix = '-' + new Regex("[^a-zA-Z0-9]").Replace(Environment.MachineName.ToLowerInvariant(), "");
#pragma warning restore CA1308 // Normalize strings to uppercase
public static readonly string? ServiceUrl, ApiKey;
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/Support/AzureAISearchTestStore.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/Support/AzureAISearchTestStore.cs
index 9664377ac897..ae0c48034b20 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/Support/AzureAISearchTestStore.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/Support/AzureAISearchTestStore.cs
@@ -4,6 +4,7 @@
using Azure;
using Azure.Identity;
using Azure.Search.Documents.Indexes;
+using Humanizer;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Connectors.AzureAISearch;
using VectorData.ConformanceTests.Support;
@@ -44,14 +45,20 @@ protected override Task StartAsync()
return Task.CompletedTask;
}
+ // Azure AI search only supports lowercase letters, digits or dashes.
+ // Also, add a suffix containing machine name to allow multiple developers to work against the same cloud instance.
+ public override string AdjustCollectionName(string baseName)
+ => baseName.Kebaberize() + AzureAISearchTestEnvironment.TestIndexPostfix;
+
public override async Task WaitForDataAsync(
VectorStoreCollection collection,
int recordCount,
Expression>? filter = null,
+ Expression>? vectorProperty = null,
int? vectorSize = null,
object? dummyVector = null)
{
- await base.WaitForDataAsync(collection, recordCount, filter, vectorSize, dummyVector);
+ await base.WaitForDataAsync(collection, recordCount, filter, vectorProperty, vectorSize, dummyVector);
// There seems to be some asynchronicity/race condition specific to Azure AI Search which isn't taken care
// of by the generic retry loop in the base implementation.
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchDataTypeTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/TypeTests/AzureAISearchDataTypeTests.cs
similarity index 91%
rename from dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchDataTypeTests.cs
rename to dotnet/test/VectorData/AzureAISearch.ConformanceTests/TypeTests/AzureAISearchDataTypeTests.cs
index be9fe82afc9f..64845e939638 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchDataTypeTests.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/TypeTests/AzureAISearchDataTypeTests.cs
@@ -2,12 +2,12 @@
using AzureAISearch.ConformanceTests.Support;
using Microsoft.Extensions.VectorData;
-using VectorData.ConformanceTests;
using VectorData.ConformanceTests.Support;
+using VectorData.ConformanceTests.TypeTests;
using VectorData.ConformanceTests.Xunit;
using Xunit;
-namespace AzureAISearch.ConformanceTests;
+namespace AzureAISearch.ConformanceTests.TypeTests;
public class AzureAISearchDataTypeTests(AzureAISearchDataTypeTests.Fixture fixture)
: DataTypeTests(fixture),
@@ -49,9 +49,6 @@ public class AzureAISearchDataTypeTests(AzureAISearchDataTypeTests.Fixture fixtu
{
public override TestStore TestStore => AzureAISearchTestStore.Instance;
- // Azure AI search only supports lowercase letters, digits or dashes.
- public override string CollectionName => "data-type-tests" + AzureAISearchTestEnvironment.TestIndexPostfix;
-
public override IList GetDataProperties()
=> base.GetDataProperties().Where(p =>
p.Type != typeof(byte)
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchEmbeddingTypeTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/TypeTests/AzureAISearchEmbeddingTypeTests.cs
similarity index 67%
rename from dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchEmbeddingTypeTests.cs
rename to dotnet/test/VectorData/AzureAISearch.ConformanceTests/TypeTests/AzureAISearchEmbeddingTypeTests.cs
index 795dd8869df4..1e94b4d182b7 100644
--- a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/AzureAISearchEmbeddingTypeTests.cs
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/TypeTests/AzureAISearchEmbeddingTypeTests.cs
@@ -1,13 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
using AzureAISearch.ConformanceTests.Support;
-using VectorData.ConformanceTests;
using VectorData.ConformanceTests.Support;
+using VectorData.ConformanceTests.TypeTests;
using Xunit;
#pragma warning disable CA2000 // Dispose objects before losing scope
-namespace AzureAISearch.ConformanceTests;
+namespace AzureAISearch.ConformanceTests.TypeTests;
public class AzureAISearchEmbeddingTypeTests(AzureAISearchEmbeddingTypeTests.Fixture fixture)
: EmbeddingTypeTests(fixture), IClassFixture
@@ -15,8 +15,5 @@ public class AzureAISearchEmbeddingTypeTests(AzureAISearchEmbeddingTypeTests.Fix
public new class Fixture : EmbeddingTypeTests.Fixture
{
public override TestStore TestStore => AzureAISearchTestStore.Instance;
-
- // Azure AI search only supports lowercase letters, digits or dashes.
- public override string CollectionName => "embedding-type-tests" + AzureAISearchTestEnvironment.TestIndexPostfix;
}
}
diff --git a/dotnet/test/VectorData/AzureAISearch.ConformanceTests/TypeTests/AzureAISearchKeyTypeTests.cs b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/TypeTests/AzureAISearchKeyTypeTests.cs
new file mode 100644
index 000000000000..1d7fa4c356c1
--- /dev/null
+++ b/dotnet/test/VectorData/AzureAISearch.ConformanceTests/TypeTests/AzureAISearchKeyTypeTests.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using AzureAISearch.ConformanceTests.Support;
+using VectorData.ConformanceTests.Support;
+using VectorData.ConformanceTests.TypeTests;
+using VectorData.ConformanceTests.Xunit;
+using Xunit;
+
+namespace AzureAISearch.ConformanceTests.TypeTests;
+
+public class AzureAISearchKeyTypeTests(AzureAISearchKeyTypeTests.Fixture fixture)
+ : KeyTypeTests(fixture), IClassFixture
+{
+ [ConditionalFact]
+ public virtual Task String() => this.Test("foo");
+
+ public new class Fixture : KeyTypeTests.Fixture
+ {
+ public override TestStore TestStore => AzureAISearchTestStore.Instance;
+ }
+}
diff --git a/dotnet/test/VectorData/AzureAISearch.UnitTests/AzureAISearchCollectionCreateMappingTests.cs b/dotnet/test/VectorData/AzureAISearch.UnitTests/AzureAISearchCollectionCreateMappingTests.cs
index 26992f38d761..e361fa66f139 100644
--- a/dotnet/test/VectorData/AzureAISearch.UnitTests/AzureAISearchCollectionCreateMappingTests.cs
+++ b/dotnet/test/VectorData/AzureAISearch.UnitTests/AzureAISearchCollectionCreateMappingTests.cs
@@ -195,7 +195,7 @@ public void MapVectorFieldThrowsForUnsupportedDistanceFunction()
};
// Act & Assert
- Assert.Throws(() => AzureAISearchCollectionCreateMapping.MapVectorField(vectorProperty));
+ Assert.Throws(() => AzureAISearchCollectionCreateMapping.MapVectorField(vectorProperty));
}
[Theory]
diff --git a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoDependencyInjectionTests.cs b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoDependencyInjectionTests.cs
index ccbc48e3e931..8b7c1ab8df0d 100644
--- a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoDependencyInjectionTests.cs
+++ b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoDependencyInjectionTests.cs
@@ -5,13 +5,12 @@
using Microsoft.SemanticKernel.Connectors.CosmosMongoDB;
using MongoDB.Driver;
using VectorData.ConformanceTests;
-using VectorData.ConformanceTests.Models;
using Xunit;
namespace CosmosMongoDB.ConformanceTests;
public class CosmosMongoDependencyInjectionTests
- : DependencyInjectionTests>, string, SimpleRecord>
+ : DependencyInjectionTests.Record>, string, DependencyInjectionTests.Record>
{
protected const string ConnectionString = "mongodb://localhost:27017";
protected const string DatabaseName = "dbName";
@@ -43,22 +42,22 @@ private static string DatabaseNameProvider(IServiceProvider sp, object serviceKe
? services
.AddSingleton(sp => new MongoClient(MongoClientSettings.FromConnectionString(ConnectionString)))
.AddSingleton(sp => sp.GetRequiredService().GetDatabase(DatabaseName))
- .AddCosmosMongoCollection>(name, lifetime: lifetime)
+ .AddCosmosMongoCollection(name, lifetime: lifetime)
: services
.AddSingleton(sp => new MongoClient(MongoClientSettings.FromConnectionString(ConnectionString)))
.AddSingleton(sp => sp.GetRequiredService().GetDatabase(DatabaseName))
- .AddKeyedCosmosMongoCollection>(serviceKey, name, lifetime: lifetime);
+ .AddKeyedCosmosMongoCollection(serviceKey, name, lifetime: lifetime);
yield return (services, serviceKey, name, lifetime) => serviceKey is null
- ? services.AddCosmosMongoCollection>(
+ ? services.AddCosmosMongoCollection(
name, ConnectionString, DatabaseName, lifetime: lifetime)
- : services.AddKeyedCosmosMongoCollection>(
+ : services.AddKeyedCosmosMongoCollection(
serviceKey, name, ConnectionString, DatabaseName, lifetime: lifetime);
yield return (services, serviceKey, name, lifetime) => serviceKey is null
- ? services.AddCosmosMongoCollection>(
+ ? services.AddCosmosMongoCollection(
name, ConnectionStringProvider, DatabaseNameProvider, lifetime: lifetime)
- : services.AddKeyedCosmosMongoCollection>(
+ : services.AddKeyedCosmosMongoCollection(
serviceKey, name, sp => ConnectionStringProvider(sp, serviceKey), sp => DatabaseNameProvider(sp, serviceKey), lifetime: lifetime);
}
}
@@ -90,9 +89,9 @@ public void ConnectionStringProviderCantBeNull()
{
IServiceCollection services = new ServiceCollection();
- Assert.Throws(() => services.AddCosmosMongoCollection>(
+ Assert.Throws(() => services.AddCosmosMongoCollection(
name: "notNull", connectionStringProvider: null!, databaseNameProvider: DatabaseNameProvider));
- Assert.Throws(() => services.AddKeyedCosmosMongoCollection>(
+ Assert.Throws(() => services.AddKeyedCosmosMongoCollection(
serviceKey: "notNull", name: "notNull", connectionStringProvider: null!, databaseNameProvider: DatabaseNameProvider));
}
@@ -101,9 +100,9 @@ public void DatabaseNameProviderCantBeNull()
{
IServiceCollection services = new ServiceCollection();
- Assert.Throws(() => services.AddCosmosMongoCollection>(
+ Assert.Throws(() => services.AddCosmosMongoCollection(
name: "notNull", connectionStringProvider: ConnectionStringProvider, databaseNameProvider: null!));
- Assert.Throws(() => services.AddKeyedCosmosMongoCollection>(
+ Assert.Throws(() => services.AddKeyedCosmosMongoCollection(
serviceKey: "notNull", name: "notNull", connectionStringProvider: ConnectionStringProvider, databaseNameProvider: null!));
}
@@ -114,13 +113,13 @@ public void ConnectionStringCantBeNullOrEmpty()
Assert.Throws(() => services.AddCosmosMongoVectorStore(connectionString: null!, DatabaseName));
Assert.Throws(() => services.AddKeyedCosmosMongoVectorStore(serviceKey: "notNull", connectionString: null!, DatabaseName));
- Assert.Throws(() => services.AddCosmosMongoCollection>(
+ Assert.Throws(() => services.AddCosmosMongoCollection(
name: "notNull", connectionString: null!, DatabaseName));
- Assert.Throws(() => services.AddCosmosMongoCollection>(
+ Assert.Throws(() => services.AddCosmosMongoCollection(
name: "notNull", connectionString: "", DatabaseName));
- Assert.Throws(() => services.AddKeyedCosmosMongoCollection>(
+ Assert.Throws(() => services.AddKeyedCosmosMongoCollection(
serviceKey: "notNull", name: "notNull", connectionString: null!, DatabaseName));
- Assert.Throws(() => services.AddKeyedCosmosMongoCollection>(
+ Assert.Throws(() => services.AddKeyedCosmosMongoCollection(
serviceKey: "notNull", name: "notNull", connectionString: "", DatabaseName));
}
@@ -131,13 +130,13 @@ public void DatabaseNameCantBeNullOrEmpty()
Assert.Throws(() => services.AddCosmosMongoVectorStore(ConnectionString, databaseName: null!));
Assert.Throws(() => services.AddKeyedCosmosMongoVectorStore(serviceKey: "notNull", ConnectionString, databaseName: null!));
- Assert.Throws(() => services.AddCosmosMongoCollection>(
+ Assert.Throws(() => services.AddCosmosMongoCollection(
name: "notNull", ConnectionString, databaseName: null!));
- Assert.Throws(() => services.AddCosmosMongoCollection>(
+ Assert.Throws(() => services.AddCosmosMongoCollection(
name: "notNull", ConnectionString, databaseName: ""));
- Assert.Throws(() => services.AddKeyedCosmosMongoCollection>(
+ Assert.Throws(() => services.AddKeyedCosmosMongoCollection(
serviceKey: "notNull", name: "notNull", ConnectionString, databaseName: null!));
- Assert.Throws(() => services.AddKeyedCosmosMongoCollection>(
+ Assert.Throws(() => services.AddKeyedCosmosMongoCollection(
serviceKey: "notNull", name: "notNull", ConnectionString, databaseName: ""));
}
}
diff --git a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoDistanceFunctionTests.cs b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoDistanceFunctionTests.cs
new file mode 100644
index 000000000000..7cf58f65da31
--- /dev/null
+++ b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoDistanceFunctionTests.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using CosmosMongoDB.ConformanceTests.Support;
+using VectorData.ConformanceTests;
+using VectorData.ConformanceTests.Support;
+using Xunit;
+
+namespace CosmosMongoDB.ConformanceTests;
+
+public class CosmosMongoDistanceFunctionTests(CosmosMongoDistanceFunctionTests.Fixture fixture)
+ : DistanceFunctionTests(fixture), IClassFixture
+{
+ public override Task CosineDistance() => Assert.ThrowsAsync(base.CosineDistance);
+ public override Task EuclideanSquaredDistance() => Assert.ThrowsAsync(base.EuclideanSquaredDistance);
+ public override Task NegativeDotProductSimilarity() => Assert.ThrowsAsync(base.NegativeDotProductSimilarity);
+ public override Task HammingDistance() => Assert.ThrowsAsync(base.HammingDistance);
+ public override Task ManhattanDistance() => Assert.ThrowsAsync(base.ManhattanDistance);
+
+ public new class Fixture() : DistanceFunctionTests.Fixture
+ {
+ public override TestStore TestStore => CosmosMongoTestStore.Instance;
+
+ public override bool AssertScores { get; } = false;
+ }
+}
diff --git a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoIndexKindTests.cs b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoIndexKindTests.cs
new file mode 100644
index 000000000000..421005184060
--- /dev/null
+++ b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoIndexKindTests.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using CosmosMongoDB.ConformanceTests.Support;
+using VectorData.ConformanceTests;
+using VectorData.ConformanceTests.Support;
+using Xunit;
+
+namespace CosmosMongoDB.ConformanceTests;
+
+public class CosmosMongoIndexKindTests(CosmosMongoIndexKindTests.Fixture fixture)
+ : IndexKindTests(fixture), IClassFixture
+{
+ public new class Fixture() : IndexKindTests.Fixture
+ {
+ public override TestStore TestStore => CosmosMongoTestStore.Instance;
+ }
+}
diff --git a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoTestSuiteImplementationTests.cs b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoTestSuiteImplementationTests.cs
index 086512e47bd2..f95db52f500e 100644
--- a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoTestSuiteImplementationTests.cs
+++ b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoTestSuiteImplementationTests.cs
@@ -1,9 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using VectorData.ConformanceTests;
-using VectorData.ConformanceTests.HybridSearch;
using VectorData.ConformanceTests.ModelTests;
-using VectorData.ConformanceTests.VectorSearch;
namespace CosmosMongoDB.ConformanceTests;
@@ -11,11 +9,9 @@ public class CosmosMongoTestSuiteImplementationTests : TestSuiteImplementationTe
{
protected override ICollection IgnoredTestBases { get; } =
[
- typeof(VectorSearchDistanceFunctionComplianceTests<>),
- typeof(VectorSearchWithFilterConformanceTests<>),
typeof(DynamicModelTests<>),
// Hybrid search not supported
- typeof(KeywordVectorizedHybridSearchComplianceTests<>),
+ typeof(HybridSearchTests<>),
];
}
diff --git a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/ModelTests/CosmosMongoMultiVectorModelTests.cs b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/ModelTests/CosmosMongoMultiVectorModelTests.cs
new file mode 100644
index 000000000000..fccab93351aa
--- /dev/null
+++ b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/ModelTests/CosmosMongoMultiVectorModelTests.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using CosmosMongoDB.ConformanceTests.Support;
+using VectorData.ConformanceTests.ModelTests;
+using VectorData.ConformanceTests.Support;
+using Xunit;
+
+namespace CosmosMongoDB.ConformanceTests.ModelTests;
+
+public class CosmosMongoMultiVectorModelTests(CosmosMongoMultiVectorModelTests.Fixture fixture)
+ : MultiVectorModelTests(fixture), IClassFixture
+{
+ public new class Fixture : MultiVectorModelTests.Fixture
+ {
+ public override TestStore TestStore => CosmosMongoTestStore.Instance;
+ }
+}
diff --git a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoDataTypeTests.cs b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/TypeTests/CosmosMongoDataTypeTests.cs
similarity index 94%
rename from dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoDataTypeTests.cs
rename to dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/TypeTests/CosmosMongoDataTypeTests.cs
index 789a54a76ba3..0ba4de3cecf5 100644
--- a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoDataTypeTests.cs
+++ b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/TypeTests/CosmosMongoDataTypeTests.cs
@@ -1,11 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
using CosmosMongoDB.ConformanceTests.Support;
-using VectorData.ConformanceTests;
using VectorData.ConformanceTests.Support;
+using VectorData.ConformanceTests.TypeTests;
using Xunit;
-namespace CosmosMongoDB.ConformanceTests;
+namespace CosmosMongoDB.ConformanceTests.TypeTests;
public class CosmosMongoDataTypeTests(CosmosMongoDataTypeTests.Fixture fixture)
: DataTypeTests.DefaultRecord>(fixture), IClassFixture
diff --git a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoEmbeddingTypeTests.cs b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/TypeTests/CosmosMongoEmbeddingTypeTests.cs
similarity index 85%
rename from dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoEmbeddingTypeTests.cs
rename to dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/TypeTests/CosmosMongoEmbeddingTypeTests.cs
index 12e6241bd22f..708989399d28 100644
--- a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/CosmosMongoEmbeddingTypeTests.cs
+++ b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/TypeTests/CosmosMongoEmbeddingTypeTests.cs
@@ -1,13 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
using CosmosMongoDB.ConformanceTests.Support;
-using VectorData.ConformanceTests;
using VectorData.ConformanceTests.Support;
+using VectorData.ConformanceTests.TypeTests;
using Xunit;
#pragma warning disable CA2000 // Dispose objects before losing scope
-namespace CosmosMongoDB.ConformanceTests;
+namespace CosmosMongoDB.ConformanceTests.TypeTests;
public class CosmosMongoEmbeddingTypeTests(CosmosMongoEmbeddingTypeTests.Fixture fixture)
: EmbeddingTypeTests(fixture), IClassFixture
diff --git a/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/TypeTests/CosmosMongoKeyTypeTests.cs b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/TypeTests/CosmosMongoKeyTypeTests.cs
new file mode 100644
index 000000000000..9aa3c1a14a04
--- /dev/null
+++ b/dotnet/test/VectorData/CosmosMongoDB.ConformanceTests/TypeTests/CosmosMongoKeyTypeTests.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using CosmosMongoDB.ConformanceTests.Support;
+using MongoDB.Bson;
+using VectorData.ConformanceTests.Support;
+using VectorData.ConformanceTests.TypeTests;
+using VectorData.ConformanceTests.Xunit;
+using Xunit;
+
+namespace CosmosMongoDB.ConformanceTests.TypeTests;
+
+public class CosmosMongoKeyTypeTests(CosmosMongoKeyTypeTests.Fixture fixture)
+ : KeyTypeTests(fixture), IClassFixture
+{
+ [ConditionalFact]
+ public virtual Task ObjectId() => this.Test(new("652f8c3e8f9b2c1a4d3e6a7b"));
+
+ [ConditionalFact]
+ public virtual Task String() => this.Test("foo");
+
+ [ConditionalFact]
+ public virtual Task Int() => this.Test(8);
+
+ [ConditionalFact]
+ public virtual Task Long() => this.Test