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 ea5d3103029a..7fa89b699136 100644 --- a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Clients/GeminiChatCompletionClient.cs +++ b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Clients/GeminiChatCompletionClient.cs @@ -600,8 +600,8 @@ private async Task SendRequestAndReturnValidGeminiResponseAsync( } /// Checks if a tool call is for a function that was defined. - private static bool IsRequestableTool(IEnumerable functions, GeminiFunctionToolCall ftc) - => functions.Any(geminiFunction => + private static bool IsRequestableTool(IEnumerable? functions, GeminiFunctionToolCall ftc) + => (functions ?? []).Any(geminiFunction => string.Equals(geminiFunction.Name, ftc.FullyQualifiedName, StringComparison.OrdinalIgnoreCase)); private void AddToolResponseMessage( diff --git a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/GeminiNativeToolExtensions.cs b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/GeminiNativeToolExtensions.cs new file mode 100644 index 000000000000..78e2c6fbd112 --- /dev/null +++ b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/GeminiNativeToolExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; + +namespace Microsoft.SemanticKernel.Connectors.Google.Core.Gemini; +internal static class GeminiNativeToolExtensions +{ + public static bool ValidateGeminiNativeTools(this GeminiNativeToolCallConfig? callConfig, GeminiRequest request) + { + if (callConfig is null) + { + return false; + } + + bool hasAnyTool = callConfig.FileSearch is not null; + // ... || callConfig.Grounding is not null; + + if (!hasAnyTool) + { + return false; + } + + // Example: If FileSearch is present, check that its required fields adhere to the schema. + if (callConfig.FileSearch is not null) + { + // Placeholder for future format checking (e.g., regex against store names) + if (callConfig.FileSearch.FileReferences?.Count == 0) + { + // throw an exception or return false here. + throw new InvalidOperationException("You need to supply at least one FileReference for a FileSearch"); + } + } + + return true; + } +} 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 29ac3c53f63c..b0030acee705 100644 --- a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs +++ b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiRequest.cs @@ -9,6 +9,7 @@ using System.Text.Json.Serialization.Metadata; using Microsoft.Extensions.AI; using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.Google.Core.Gemini; namespace Microsoft.SemanticKernel.Connectors.Google.Core; @@ -50,16 +51,23 @@ internal sealed class GeminiRequest [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IDictionary? Labels { get; set; } + // These were added to keep a devide from native tools and functionDeclarations + // I am currently unsure if this is needed as currently you cannot have both enabled in a + // turn. Maybe this will be use in the future. + [JsonIgnore] + private GeminiTool? _functionDefinitionsBlook { get; set; } + public void AddFunction(GeminiFunction function) { - // NOTE: Currently Gemini only supports one tool i.e. function calling. + // NOTE: Adding in additional tool types has forced me to keep a reference to the functionDeclarations this.Tools ??= []; - if (this.Tools.Count == 0) + if (this._functionDefinitionsBlook is null) { - this.Tools.Add(new GeminiTool()); + this._functionDefinitionsBlook = new(); + this.Tools.Add(this._functionDefinitionsBlook); } - - this.Tools[0].Functions.Add(function.ToFunctionDeclaration()); + this._functionDefinitionsBlook.Functions ??= []; + this._functionDefinitionsBlook.Functions!.Add(function.ToFunctionDeclaration()); } /// @@ -76,6 +84,7 @@ public static GeminiRequest FromPromptAndExecutionSettings( AddSafetySettings(executionSettings, obj); AddConfiguration(executionSettings, obj); AddAdditionalBodyFields(executionSettings, obj); + AddNativeTools(executionSettings, obj); return obj; } @@ -93,6 +102,7 @@ public static GeminiRequest FromChatHistoryAndExecutionSettings( AddSafetySettings(executionSettings, obj); AddConfiguration(executionSettings, obj); AddAdditionalBodyFields(executionSettings, obj); + AddNativeTools(executionSettings, obj); return obj; } @@ -480,6 +490,27 @@ internal static JsonSerializerOptions GetDefaultOptions() return s_options; } + private static void AddNativeTools(GeminiPromptExecutionSettings executionSettings, GeminiRequest request) + { + if (!executionSettings.NativeToolCalls.ValidateGeminiNativeTools(request)) + { + return; + } + // initialize Tools if needed + request.Tools ??= []; + + // Add all the tools to the GeminiTool seperately + var settings = executionSettings.NativeToolCalls; + if (settings?.FileSearch is not null) + { + GeminiTool tool = new() + { + FileSearch = settings.FileSearch + }; + request.Tools.Add(tool); + } + } + private static void AddSafetySettings(GeminiPromptExecutionSettings executionSettings, GeminiRequest request) { request.SafetySettings = executionSettings.SafetySettings?.Select(s diff --git a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiTool.cs b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiTool.cs index d750ce4f1054..a98e5b570911 100644 --- a/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiTool.cs +++ b/dotnet/src/Connectors/Connectors.Google/Core/Gemini/Models/GeminiTool.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; +using static Microsoft.SemanticKernel.Connectors.Google.GeminiNativeToolCallConfig; namespace Microsoft.SemanticKernel.Connectors.Google.Core; @@ -23,7 +24,15 @@ internal sealed class GeminiTool /// a [FunctionResponse][content.part.function_response] with the [content.role] "function" generation context for the next model turn. /// [JsonPropertyName("functionDeclarations")] - public IList Functions { get; set; } = []; + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IList? Functions { get; set; } = null; + + /// + /// The specific configuration for FileSearch + /// + [JsonPropertyName("fileSearch")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public GeminiFileSearchNativeTool? FileSearch { get; set; } = null; /// /// Structured representation of a function declaration as defined by the OpenAPI 3.03 specification. diff --git a/dotnet/src/Connectors/Connectors.Google/GeminiNativeToolCallConfig.cs b/dotnet/src/Connectors/Connectors.Google/GeminiNativeToolCallConfig.cs new file mode 100644 index 000000000000..3a865d769a8b --- /dev/null +++ b/dotnet/src/Connectors/Connectors.Google/GeminiNativeToolCallConfig.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Microsoft.SemanticKernel.Connectors.Google.Core; +using Microsoft.SemanticKernel.Connectors.Google.Core.Gemini; + +namespace Microsoft.SemanticKernel.Connectors.Google; + +/// +/// Represents the configuration for a Gemini native tool call, specifically for file search operations. +/// +/// This class is used to configure the parameters for invoking a Gemini native tool that performs file +/// searches. It includes settings for specifying file references to be searched. Additional tool configuration can be +/// added here. NOTE: you will need to update both and AddNativeTools in +/// as well as add validation to +public class GeminiNativeToolCallConfig +{ + /// + /// Gets or sets the file search tool configuration for the Gemini application. + /// + [JsonPropertyName("fileSearch")] + public GeminiFileSearchNativeTool? FileSearch { get; set; } = null; + + /// + /// Represents a tool for searching files using the Gemini native interface. + /// + /// This class is designed to work with the Gemini system to facilitate file searches by + /// maintaining a list of file references. + /// As the Google Connector as this moment doesn't support file uplaod or file search import + /// Operation, you will need to use a seperate library to upload or import files to FileSearch + /// + /// + public sealed class GeminiFileSearchNativeTool(IList fileReferences) + { + /// + /// Gets or sets the list of file reference names used for searching. + /// + /// The file reference is formated as "fileSearchStores/filenane" this should be retrieved from + /// response of the filesearch import operation. As the Google Connector as this moment doesn't support this + /// Operation, you will need to use a seperate library to upload or import files to FileSearch + [JsonPropertyName("fileSearchStoreNames")] + public IList FileReferences { get; set; } = fileReferences; + } +} diff --git a/dotnet/src/Connectors/Connectors.Google/GeminiPromptExecutionSettings.cs b/dotnet/src/Connectors/Connectors.Google/GeminiPromptExecutionSettings.cs index 5f8bc0874cc2..771c113dd9f8 100644 --- a/dotnet/src/Connectors/Connectors.Google/GeminiPromptExecutionSettings.cs +++ b/dotnet/src/Connectors/Connectors.Google/GeminiPromptExecutionSettings.cs @@ -31,6 +31,7 @@ public sealed class GeminiPromptExecutionSettings : PromptExecutionSettings private IDictionary? _labels; private IList? _safetySettings; private GeminiToolCallBehavior? _toolCallBehavior; + private GeminiNativeToolCallConfig? _nativeToolCallConfig; private GeminiThinkingConfig? _thinkingConfig; /// @@ -199,6 +200,21 @@ public GeminiToolCallBehavior? ToolCallBehavior } } + /// + /// Gets or sets the configuration for native tool calls. + /// + /// Modifying this property will throw an exception if the object is in a frozen state. + public GeminiNativeToolCallConfig? NativeToolCalls + { + get => this._nativeToolCallConfig; + + set + { + this.ThrowIfFrozen(); + this._nativeToolCallConfig = value; + } + } + /// /// Indicates if the audio response should include timestamps. /// if enabled, audio timestamp will be included in the request to the model.