diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/GoogleAI/GoogleAIClientEmbeddingsGenerationTests.cs b/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/GoogleAI/GoogleAIClientEmbeddingsGenerationTests.cs index 24b095874cb0..8216e8b78c91 100644 --- a/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/GoogleAI/GoogleAIClientEmbeddingsGenerationTests.cs +++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/GoogleAI/GoogleAIClientEmbeddingsGenerationTests.cs @@ -219,6 +219,35 @@ public async Task ShouldNotIncludeDimensionsInAllRequestsWhenNotProvidedAsync() Assert.All(request.Requests, item => Assert.Null(item.Dimensions)); } + [Fact] + public async Task GenerateEmbeddingsUsingEmbeddingGenerationOptionsShouldOverrideDimensionsAndModelAsync() + { + // Arrange + var client = this.CreateEmbeddingsClient(); + var dataToEmbed = new List() + { + "First text to embed", + "Second text to embed", + "Third text to embed" + }; + + var options = new Microsoft.Extensions.AI.EmbeddingGenerationOptions { Dimensions = 10, ModelId = "override-model" }; + + // Act + await client.GenerateEmbeddingsAsync(dataToEmbed, options); + + // Assert + var request = JsonSerializer.Deserialize(this._messageHandlerStub.RequestContent); + Assert.NotNull(request); + Assert.Equal(dataToEmbed.Count, request.Requests.Count); + Assert.All(request.Requests, + item => + { + Assert.Contains(options.ModelId, item.Model); + Assert.Equal(options.Dimensions, item.Dimensions); + }); + } + private GoogleAIEmbeddingClient CreateEmbeddingsClient( string modelId = "fake-model", int? dimensions = null) diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/GoogleAI/GoogleAIEmbeddingRequestTests.cs b/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/GoogleAI/GoogleAIEmbeddingRequestTests.cs index 731e20dda585..d3ce4ab7e28d 100644 --- a/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/GoogleAI/GoogleAIEmbeddingRequestTests.cs +++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/Core/GoogleAI/GoogleAIEmbeddingRequestTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; +using Microsoft.Extensions.AI; using Microsoft.SemanticKernel.Connectors.Google.Core; using Xunit; @@ -83,4 +84,32 @@ public void FromDataJsonIncludesDimensionsWhenProvided() // Assert Assert.Contains($"{DimensionalityJsonPropertyName}:{Dimensions}", json); } + + [Theory] + [InlineData("TaskType")] + [InlineData("Task_Type")] + [InlineData("taskType")] + [InlineData("task_Type")] + [InlineData("tasktype")] + [InlineData("task_type")] + public void FromDataShouldIncludeTaskTypeWhenProvided(string additionalPropertyKeyName) + { + // Arrange + var input = new[] { "This is a retrieval document." }; + var modelId = "embedding-001"; + var dimensions = 1024; + var taskType = "RETRIEVAL_DOCUMENT"; + + var options = new EmbeddingGenerationOptions { AdditionalProperties = new AdditionalPropertiesDictionary { [additionalPropertyKeyName] = taskType } }; + + // Act + var request = GoogleAIEmbeddingRequest.FromData(input, modelId, dimensions, options); + + // Serialize to JSON (this is what would be sent in the HTTP request) + var json = System.Text.Json.JsonSerializer.Serialize(request); + + // Assert + Assert.Contains("\"taskType\":\"RETRIEVAL_DOCUMENT\"", json); + Assert.Contains("\"model\":\"models/embedding-001\"", json); + } } diff --git a/dotnet/src/Connectors/Connectors.Google/Core/GoogleAI/GoogleAIEmbeddingClient.cs b/dotnet/src/Connectors/Connectors.Google/Core/GoogleAI/GoogleAIEmbeddingClient.cs index 6a801acff76e..8f009366dc46 100644 --- a/dotnet/src/Connectors/Connectors.Google/Core/GoogleAI/GoogleAIEmbeddingClient.cs +++ b/dotnet/src/Connectors/Connectors.Google/Core/GoogleAI/GoogleAIEmbeddingClient.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; namespace Microsoft.SemanticKernel.Connectors.Google.Core; @@ -54,15 +55,18 @@ public GoogleAIEmbeddingClient( /// Generates embeddings for the given data asynchronously. /// /// The list of strings to generate embeddings for. + /// The embedding generation options. /// The cancellation token to cancel the operation. /// Result contains a list of read-only memories of floats representing the generated embeddings. public async Task>> GenerateEmbeddingsAsync( IList data, + EmbeddingGenerationOptions? options = null, CancellationToken cancellationToken = default) { Verify.NotNullOrEmpty(data); - var geminiRequest = this.GetEmbeddingRequest(data); + var geminiRequest = this.GetEmbeddingRequest(data, options); + using var httpRequestMessage = await this.CreateHttpRequestAsync(geminiRequest, this._embeddingEndpoint).ConfigureAwait(false); string body = await this.SendRequestAndGetStringBodyAsync(httpRequestMessage, cancellationToken) @@ -71,8 +75,8 @@ public async Task>> GenerateEmbeddingsAsync( return DeserializeAndProcessEmbeddingsResponse(body); } - private GoogleAIEmbeddingRequest GetEmbeddingRequest(IEnumerable data) - => GoogleAIEmbeddingRequest.FromData(data, this._embeddingModelId, this._dimensions); + private GoogleAIEmbeddingRequest GetEmbeddingRequest(IEnumerable data, EmbeddingGenerationOptions? options = null) + => GoogleAIEmbeddingRequest.FromData(data, options?.ModelId ?? this._embeddingModelId, options?.Dimensions ?? this._dimensions, options); private static List> DeserializeAndProcessEmbeddingsResponse(string body) => ProcessEmbeddingsResponse(DeserializeResponse(body)); diff --git a/dotnet/src/Connectors/Connectors.Google/Core/GoogleAI/GoogleAIEmbeddingRequest.cs b/dotnet/src/Connectors/Connectors.Google/Core/GoogleAI/GoogleAIEmbeddingRequest.cs index d69953dc5423..d7fd3443e94c 100644 --- a/dotnet/src/Connectors/Connectors.Google/Core/GoogleAI/GoogleAIEmbeddingRequest.cs +++ b/dotnet/src/Connectors/Connectors.Google/Core/GoogleAI/GoogleAIEmbeddingRequest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using Microsoft.Extensions.AI; namespace Microsoft.SemanticKernel.Connectors.Google.Core; @@ -11,24 +12,48 @@ internal sealed class GoogleAIEmbeddingRequest [JsonPropertyName("requests")] public IList Requests { get; set; } = null!; - public static GoogleAIEmbeddingRequest FromData(IEnumerable data, string modelId, int? dimensions = null) => new() + public static GoogleAIEmbeddingRequest FromData(IEnumerable data, string modelId, int? dimensions = null, EmbeddingGenerationOptions? options = null) { - Requests = data.Select(text => new RequestEmbeddingContent + static string? GetTaskType(EmbeddingGenerationOptions? options) { - Model = $"models/{modelId}", - Content = new() + if (options?.AdditionalProperties is not null) { - Parts = - [ - new() - { - Text = text - } - ] - }, - Dimensions = dimensions - }).ToList() - }; + object? taskType = null; + object? task_type = null; + + // AdditionalProperties is case-insensitive + if (options?.AdditionalProperties.TryGetValue("task_type", out task_type) == true || + options?.AdditionalProperties.TryGetValue("tasktype", out taskType) == true) + { + return (task_type ?? taskType)?.ToString(); + } + } + + return null; + } + + var request = new GoogleAIEmbeddingRequest + { + Requests = [.. data.Select(text => new RequestEmbeddingContent + { + Model = $"models/{modelId}", + Content = new() + { + Parts = + [ + new() + { + Text = text + } + ] + }, + Dimensions = dimensions, + TaskType = GetTaskType(options) + })] + }; + + return request; + } internal sealed class RequestEmbeddingContent { diff --git a/dotnet/src/Connectors/Connectors.Google/Services/GoogleAITextEmbeddingGenerationService.cs b/dotnet/src/Connectors/Connectors.Google/Services/GoogleAITextEmbeddingGenerationService.cs index d526801c52c9..43470e9f9b82 100644 --- a/dotnet/src/Connectors/Connectors.Google/Services/GoogleAITextEmbeddingGenerationService.cs +++ b/dotnet/src/Connectors/Connectors.Google/Services/GoogleAITextEmbeddingGenerationService.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.Connectors.Google.Core; using Microsoft.SemanticKernel.Embeddings; @@ -68,6 +69,31 @@ public Task>> GenerateEmbeddingsAsync( Kernel? kernel = null, CancellationToken cancellationToken = default) { - return this._embeddingClient.GenerateEmbeddingsAsync(data, cancellationToken); + return this._embeddingClient.GenerateEmbeddingsAsync(data, null, cancellationToken); + } + + /// + /// Generates an embedding from the given . + /// + /// List of strings to generate embeddings for + /// Additional options for embedding generation + /// The containing services, plugins, and other state for use throughout the operation. + /// The to monitor for cancellation requests. The default is . + /// List of embeddings + /// + /// + /// The parameter can be used to override default settings such as and + /// + /// + /// Additionally a key/value of "taskType" can be provided in the for specific embedding tasks. + /// + /// + public Task>> GenerateEmbeddingsAsync( + IList data, + EmbeddingGenerationOptions? options, + Kernel? kernel = null, + CancellationToken cancellationToken = default) + { + return this._embeddingClient.GenerateEmbeddingsAsync(data, options, cancellationToken); } }