diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentRole.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentRole.cs index f9547d5a..226313e0 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentRole.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentRole.cs @@ -6,4 +6,5 @@ public class AgentRole public const string Assistant = "assistant"; public const string User = "user"; public const string Function = "function"; + public const string Model = "model"; } diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/BotSharp.Plugin.GoogleAI.csproj b/src/Plugins/BotSharp.Plugin.GoogleAI/BotSharp.Plugin.GoogleAI.csproj index 93c0f9df..b6e0b24e 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/BotSharp.Plugin.GoogleAI.csproj +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/BotSharp.Plugin.GoogleAI.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/GoogleAiPlugin.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/GoogleAiPlugin.cs index aac88308..58a384a7 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/GoogleAiPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/GoogleAiPlugin.cs @@ -1,9 +1,9 @@ using BotSharp.Abstraction.Plugins; using BotSharp.Abstraction.Settings; -using BotSharp.Plugin.GoogleAI.Providers; -using BotSharp.Plugin.GoogleAI.Settings; +using BotSharp.Plugin.GoogleAi.Providers.Chat; +using BotSharp.Plugin.GoogleAi.Providers.Text; -namespace BotSharp.Plugin.GoogleAI; +namespace BotSharp.Plugin.GoogleAi; public class GoogleAiPlugin : IBotSharpPlugin { @@ -19,7 +19,9 @@ public class GoogleAiPlugin : IBotSharpPlugin return settingService.Bind("GoogleAi"); }); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } } diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs new file mode 100644 index 00000000..b94dd534 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs @@ -0,0 +1,194 @@ +using BotSharp.Abstraction.Agents; +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Loggers; +using Microsoft.Extensions.Logging; +using Mscc.GenerativeAI; + +namespace BotSharp.Plugin.GoogleAi.Providers.Chat; + +public class GeminiChatCompletionProvider : IChatCompletion +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + private string _model; + + public string Provider => "google-gemini"; + + public GeminiChatCompletionProvider( + IServiceProvider services, + ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task GetChatCompletions(Agent agent, List conversations) + { + var contentHooks = _services.GetServices().ToList(); + + // Before chat completion hook + foreach (var hook in contentHooks) + { + await hook.BeforeGenerating(agent, conversations); + } + + var client = ProviderHelper.GetGeminiClient(_services); + var aiModel = client.GenerativeModel(_model); + var (prompt, request) = PrepareOptions(aiModel, agent, conversations); + + var response = await aiModel.GenerateContent(request); + var candidate = response.Candidates.First(); + var part = candidate.Content?.Parts?.FirstOrDefault(); + var text = part?.Text ?? string.Empty; + + RoleDialogModel responseMessage; + if (part?.FunctionCall != null) + { + responseMessage = new RoleDialogModel(AgentRole.Function, text) + { + CurrentAgentId = agent.Id, + MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, + ToolCallId = part.FunctionCall.Name, + FunctionName = part.FunctionCall.Name, + FunctionArgs = part.FunctionCall.Args?.ToString() + }; + } + else + { + responseMessage = new RoleDialogModel(AgentRole.Assistant, text) + { + CurrentAgentId = agent.Id, + MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, + }; + } + + // After chat completion hook + foreach (var hook in contentHooks) + { + await hook.AfterGenerated(responseMessage, new TokenStatsModel + { + Prompt = prompt, + Provider = Provider, + Model = _model + }); + } + + return responseMessage; + } + + public Task GetChatCompletionsAsync(Agent agent, List conversations, Func onMessageReceived, Func onFunctionExecuting) + { + throw new NotImplementedException(); + } + + public Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived) + { + throw new NotImplementedException(); + } + + public void SetModelName(string model) + { + _model = model; + } + + private (string, GenerateContentRequest) PrepareOptions(GenerativeModel aiModel, Agent agent, List conversations) + { + var agentService = _services.GetRequiredService(); + var googleSettings = _services.GetRequiredService(); + + // Add settings + aiModel.UseGoogleSearch = googleSettings.Gemini.UseGoogleSearch; + aiModel.UseGrounding = googleSettings.Gemini.UseGrounding; + + // Assembly messages + var prompt = string.Empty; + var contents = new List(); + var tools = new List(); + var funcDeclarations = new List(); + + if (!string.IsNullOrEmpty(agent.Instruction)) + { + var instruction = agentService.RenderedInstruction(agent); + contents.Add(new Content(instruction) + { + Role = AgentRole.User + }); + + prompt += $"{instruction}\r\n"; + } + + prompt += "\r\n[FUNCTIONS]\r\n"; + foreach (var function in agent.Functions) + { + if (!agentService.RenderFunction(agent, function)) continue; + + var def = agentService.RenderFunctionProperty(agent, function); + + funcDeclarations.Add(new FunctionDeclaration + { + Name = function.Name, + Description = function.Description, + Parameters = new() + { + Type = ParameterType.Object, + Properties = def.Properties, + Required = def.Required + } + }); + + prompt += $"{function.Name}: {function.Description} {def}\r\n\r\n"; + } + + if (!funcDeclarations.IsNullOrEmpty()) + { + tools.Add(new Tool { FunctionDeclarations = funcDeclarations }); + } + + prompt += "\r\n[CONVERSATIONS]\r\n"; + foreach (var message in conversations) + { + if (message.Role == AgentRole.Function) + { + contents.Add(new Content(message.Content) + { + Role = AgentRole.Function, + Parts = new() + { + new FunctionCall + { + Name = message.FunctionName, + Args = JsonSerializer.Deserialize(message.FunctionArgs ?? "{}") + } + } + }); + + prompt += $"{AgentRole.Assistant}: Call function {message.FunctionName}({message.FunctionArgs})\r\n"; + } + else if (message.Role == AgentRole.User) + { + var text = !string.IsNullOrWhiteSpace(message.Payload) ? message.Payload : message.Content; + contents.Add(new Content(text) + { + Role = AgentRole.User + }); + prompt += $"{AgentRole.User}: {text}\r\n"; + } + else if (message.Role == AgentRole.Assistant) + { + contents.Add(new Content(message.Content) + { + Role = AgentRole.Model + }); + prompt += $"{AgentRole.Assistant}: {message.Content}\r\n"; + } + } + + var request = new GenerateContentRequest + { + Contents = contents, + Tools = tools + }; + return (prompt, request); + } +} diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/PalmChatCompletionProvider.cs similarity index 85% rename from src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ChatCompletionProvider.cs rename to src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/PalmChatCompletionProvider.cs index d278b110..851792a6 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/PalmChatCompletionProvider.cs @@ -3,44 +3,44 @@ using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Loggers; using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Routing; -using BotSharp.Plugin.GoogleAI.Settings; using LLMSharp.Google.Palm; -using Microsoft.Extensions.Logging; using LLMSharp.Google.Palm.DiscussService; +using Microsoft.Extensions.Logging; -namespace BotSharp.Plugin.GoogleAI.Providers; +namespace BotSharp.Plugin.GoogleAi.Providers.Chat; -public class ChatCompletionProvider : IChatCompletion +public class PalmChatCompletionProvider : IChatCompletion { - public string Provider => "google-ai"; private readonly IServiceProvider _services; - private readonly GoogleAiSettings _settings; - private readonly ILogger _logger; + private readonly ILogger _logger; + private string _model; - public ChatCompletionProvider(IServiceProvider services, - GoogleAiSettings settings, - ILogger logger) + public string Provider => "google-ai"; + + public PalmChatCompletionProvider( + IServiceProvider services, + ILogger logger) { _services = services; - _settings = settings; _logger = logger; } public async Task GetChatCompletions(Agent agent, List conversations) { - var hooks = _services.GetServices().ToList(); + var contentHooks = _services.GetServices().ToList(); // Before chat completion hook - Task.WaitAll(hooks.Select(hook => - hook.BeforeGenerating(agent, conversations)).ToArray()); - - var client = new GooglePalmClient(apiKey: _settings.PaLM.ApiKey); + foreach (var hook in contentHooks) + { + await hook.BeforeGenerating(agent, conversations); + } + var client = ProviderHelper.GetPalmClient(_services); var (prompt, messages, hasFunctions) = PrepareOptions(agent, conversations); RoleDialogModel msg; - + if (hasFunctions) { // use text completion @@ -80,12 +80,15 @@ public class ChatCompletionProvider : IChatCompletion } // After chat completion hook - Task.WaitAll(hooks.Select(hook => - hook.AfterGenerated(msg, new TokenStatsModel + foreach (var hook in contentHooks) + { + await hook.AfterGenerated(msg, new TokenStatsModel { Prompt = prompt, + Provider = Provider, Model = _model - })).ToArray()); + }); + } return msg; } diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ProviderHelper.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ProviderHelper.cs new file mode 100644 index 00000000..75435f90 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ProviderHelper.cs @@ -0,0 +1,21 @@ +using LLMSharp.Google.Palm; +using Mscc.GenerativeAI; + +namespace BotSharp.Plugin.GoogleAi.Providers; + +public static class ProviderHelper +{ + public static GoogleAI GetGeminiClient(IServiceProvider services) + { + var settings = services.GetRequiredService(); + var client = new GoogleAI(settings.Gemini.ApiKey); + return client; + } + + public static GooglePalmClient GetPalmClient(IServiceProvider services) + { + var settings = services.GetRequiredService(); + var client = new GooglePalmClient(settings.PaLM.ApiKey); + return client; + } +} diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Text/GeminiTextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Text/GeminiTextCompletionProvider.cs new file mode 100644 index 00000000..e6e3f4b3 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Text/GeminiTextCompletionProvider.cs @@ -0,0 +1,84 @@ +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.Loggers; +using Microsoft.Extensions.Logging; +using Mscc.GenerativeAI; + +namespace BotSharp.Plugin.GoogleAi.Providers.Text; + +public class GeminiTextCompletionProvider : ITextCompletion +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + private readonly ITokenStatistics _tokenStatistics; + private string _model; + + public string Provider => "google-gemini"; + + public GeminiTextCompletionProvider( + IServiceProvider services, + ILogger logger, + ITokenStatistics tokenStatistics) + { + _services = services; + _logger = logger; + _tokenStatistics = tokenStatistics; + } + + + public async Task GetCompletion(string text, string agentId, string messageId) + { + var contentHooks = _services.GetServices().ToList(); + + // Before completion hook + var agent = new Agent() + { + Id = agentId + }; + var userMessage = new RoleDialogModel(AgentRole.User, text) + { + MessageId = messageId + }; + + foreach (var hook in contentHooks) + { + await hook.BeforeGenerating(agent, new List { userMessage }); + } + + var client = ProviderHelper.GetGeminiClient(_services); + var aiModel = client.GenerativeModel(_model); + PrepareOptions(aiModel); + + _tokenStatistics.StartTimer(); + var response = await aiModel.GenerateContent(text); + _tokenStatistics.StopTimer(); + + var completion = response.Text ?? string.Empty; + + // After completion hook + foreach (var hook in contentHooks) + { + await hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, completion), new TokenStatsModel + { + Prompt = text, + Provider = Provider, + Model = _model + }); + } + + return completion; + } + + public void SetModelName(string model) + { + _model = model; + } + + + private void PrepareOptions(GenerativeModel aiModel) + { + var settings = _services.GetRequiredService(); + aiModel.UseGoogleSearch = settings.Gemini.UseGoogleSearch; + aiModel.UseGrounding = settings.Gemini.UseGrounding; + } +} diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Text/PalmTextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Text/PalmTextCompletionProvider.cs new file mode 100644 index 00000000..c7e64fe8 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Text/PalmTextCompletionProvider.cs @@ -0,0 +1,66 @@ +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.Loggers; +using Microsoft.Extensions.Logging; + +namespace BotSharp.Plugin.GoogleAi.Providers.Text; + +public class PalmTextCompletionProvider : ITextCompletion +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + private readonly ITokenStatistics _tokenStatistics; + + private string _model; + + public string Provider => "google-ai"; + + public PalmTextCompletionProvider( + IServiceProvider services, + ILogger logger, + ITokenStatistics tokenStatistics) + { + _services = services; + _logger = logger; + _tokenStatistics = tokenStatistics; + } + + public async Task GetCompletion(string text, string agentId, string messageId) + { + var contentHooks = _services.GetServices().ToList(); + + // Before completion hook + var agent = new Agent() { Id = agentId }; + var userMessage = new RoleDialogModel(AgentRole.User, text) { MessageId = messageId }; + + foreach (var hook in contentHooks) + { + await hook.BeforeGenerating(agent, new List { userMessage }); + } + + var client = ProviderHelper.GetPalmClient(_services); + _tokenStatistics.StartTimer(); + var response = await client.GenerateTextAsync(text, null); + _tokenStatistics.StopTimer(); + + var message = response.Candidates.First(); + var completion = message.Output.Trim(); + + // After completion hook + foreach (var hook in contentHooks) + { + await hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, completion), new TokenStatsModel + { + Prompt = text, + Provider = Provider + }); + } + + return completion; + } + + public void SetModelName(string model) + { + _model = model; + } +} diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/TextCompletionProvider.cs deleted file mode 100644 index c35df64f..00000000 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/TextCompletionProvider.cs +++ /dev/null @@ -1,68 +0,0 @@ -using BotSharp.Abstraction.Agents.Enums; -using BotSharp.Abstraction.Conversations; -using BotSharp.Abstraction.Loggers; -using BotSharp.Plugin.GoogleAI.Settings; -using LLMSharp.Google.Palm; -using Microsoft.Extensions.Logging; - -namespace BotSharp.Plugin.GoogleAI.Providers; - -public class TextCompletionProvider : ITextCompletion -{ - public string Provider => "google-ai"; - private readonly IServiceProvider _services; - private readonly GoogleAiSettings _settings; - private readonly ILogger _logger; - private readonly ITokenStatistics _tokenStatistics; - private string _model; - - public TextCompletionProvider(IServiceProvider services, - GoogleAiSettings settings, - ILogger logger, - ITokenStatistics tokenStatistics) - { - _services = services; - _settings = settings; - _logger = logger; - _tokenStatistics = tokenStatistics; - } - - public async Task GetCompletion(string text, string agentId, string messageId) - { - var hooks = _services.GetServices().ToList(); - - // Before chat completion hook - var agent = new Agent() - { - Id = agentId - }; - var userMessage = new RoleDialogModel(AgentRole.User, text) - { - MessageId = messageId - }; - Task.WaitAll(hooks.Select(hook => - hook.BeforeGenerating(agent, new List { userMessage })).ToArray()); - - var client = new GooglePalmClient(apiKey: _settings.PaLM.ApiKey); - _tokenStatistics.StartTimer(); - var response = await client.GenerateTextAsync(text, null); - _tokenStatistics.StopTimer(); - - var message = response.Candidates.First(); - var completion = message.Output.Trim(); - - // After chat completion hook - Task.WaitAll(hooks.Select(hook => - hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, completion), new TokenStatsModel - { - Model = _model - })).ToArray()); - - return completion; - } - - public void SetModelName(string model) - { - _model = model; - } -} diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Settings/GoogleAiSettings.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Settings/GoogleAiSettings.cs index d515b23f..a4e3468a 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Settings/GoogleAiSettings.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Settings/GoogleAiSettings.cs @@ -1,6 +1,22 @@ -namespace BotSharp.Plugin.GoogleAI.Settings; +namespace BotSharp.Plugin.GoogleAi.Settings; public class GoogleAiSettings { public PaLMSetting PaLM { get; set; } + + public GeminiSetting Gemini { get; set; } +} + +public class PaLMSetting +{ + public string Endpoint { get; set; } = string.Empty; + public string ApiKey { get; set; } +} + + +public class GeminiSetting +{ + public string ApiKey { get; set; } + public bool UseGoogleSearch { get; set; } + public bool UseGrounding { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Settings/PaLMSetting.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Settings/PaLMSetting.cs deleted file mode 100644 index 29894614..00000000 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Settings/PaLMSetting.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BotSharp.Plugin.GoogleAI.Settings; - -public class PaLMSetting -{ - public string Endpoint { get; set; } = string.Empty; - public string ApiKey { get; set; } -} diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Using.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Using.cs index 8cb9a723..17152bf4 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Using.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Using.cs @@ -10,4 +10,5 @@ global using BotSharp.Abstraction.MLTasks; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using System.Text.Json.Serialization; -global using BotSharp.Abstraction.Utilities; \ No newline at end of file +global using BotSharp.Abstraction.Utilities; +global using BotSharp.Plugin.GoogleAi.Settings; \ No newline at end of file diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index c3d4bdd5..c600215a 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -197,6 +197,11 @@ "PaLM": { "Endpoint": "https://generativelanguage.googleapis.com", "ApiKey": "" + }, + "Gemini": { + "ApiKey": "", + "UseGoogleSearch": false, + "UseGrounding": false } },