Merge branch 'SciSharp:master' into master

This commit is contained in:
Haiping 2024-12-25 19:40:24 +00:00 committed by GitHub
commit 67d4342f6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 452 additions and 112 deletions

View file

@ -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";
}

View file

@ -290,20 +290,20 @@ public class ChatCompletionProvider : IChatCompletion
if (!string.IsNullOrEmpty(file.FileData))
{
var (contentType, bytes) = FileUtility.GetFileInfoFromData(file.FileData);
var contentPart = ChatMessageContentPart.CreateImagePart(BinaryData.FromBytes(bytes), contentType, ChatImageDetailLevel.Low);
var contentPart = ChatMessageContentPart.CreateImagePart(BinaryData.FromBytes(bytes), contentType, ChatImageDetailLevel.Auto);
contentParts.Add(contentPart);
}
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
{
var contentType = FileUtility.GetFileContentType(file.FileStorageUrl);
var bytes = fileStorage.GetFileBytes(file.FileStorageUrl);
var contentPart = ChatMessageContentPart.CreateImagePart(BinaryData.FromBytes(bytes), contentType, ChatImageDetailLevel.Low);
var contentPart = ChatMessageContentPart.CreateImagePart(BinaryData.FromBytes(bytes), contentType, ChatImageDetailLevel.Auto);
contentParts.Add(contentPart);
}
else if (!string.IsNullOrEmpty(file.FileUrl))
{
var uri = new Uri(file.FileUrl);
var contentPart = ChatMessageContentPart.CreateImagePart(uri, ChatImageDetailLevel.Low);
var contentPart = ChatMessageContentPart.CreateImagePart(uri, ChatImageDetailLevel.Auto);
contentParts.Add(contentPart);
}
}

View file

@ -12,6 +12,7 @@
<ItemGroup>
<PackageReference Include="LLMSharp.Google.Palm" Version="1.0.2" />
<PackageReference Include="Mscc.GenerativeAI" Version="2.0.1" />
</ItemGroup>
<ItemGroup>

View file

@ -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<GoogleAiSettings>("GoogleAi");
});
services.AddScoped<IChatCompletion, ChatCompletionProvider>();
services.AddScoped<ITextCompletion, TextCompletionProvider>();
services.AddScoped<ITextCompletion, PalmTextCompletionProvider>();
services.AddScoped<ITextCompletion, GeminiTextCompletionProvider>();
services.AddScoped<IChatCompletion, PalmChatCompletionProvider>();
services.AddScoped<IChatCompletion, GeminiChatCompletionProvider>();
}
}

View file

@ -0,0 +1,217 @@
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<GeminiChatCompletionProvider> _logger;
private string _model;
public string Provider => "google-gemini";
public GeminiChatCompletionProvider(
IServiceProvider services,
ILogger<GeminiChatCompletionProvider> logger)
{
_services = services;
_logger = logger;
}
public async Task<RoleDialogModel> GetChatCompletions(Agent agent, List<RoleDialogModel> conversations)
{
var contentHooks = _services.GetServices<IContentGeneratingHook>().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<bool> GetChatCompletionsAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived, Func<RoleDialogModel, Task> onFunctionExecuting)
{
throw new NotImplementedException();
}
public Task<bool> GetChatCompletionsStreamingAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived)
{
throw new NotImplementedException();
}
public void SetModelName(string model)
{
_model = model;
}
private (string, GenerateContentRequest) PrepareOptions(GenerativeModel aiModel, Agent agent, List<RoleDialogModel> conversations)
{
var agentService = _services.GetRequiredService<IAgentService>();
var googleSettings = _services.GetRequiredService<GoogleAiSettings>();
// Add settings
aiModel.UseGoogleSearch = googleSettings.Gemini.UseGoogleSearch;
aiModel.UseGrounding = googleSettings.Gemini.UseGrounding;
// Assembly messages
var contents = new List<Content>();
var tools = new List<Tool>();
var funcDeclarations = new List<FunctionDeclaration>();
var systemPrompts = new List<string>();
if (!string.IsNullOrEmpty(agent.Instruction))
{
var instruction = agentService.RenderedInstruction(agent);
contents.Add(new Content(instruction)
{
Role = AgentRole.User
});
systemPrompts.Add(instruction);
}
var funcPrompts = new List<string>();
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
}
});
funcPrompts.Add($"{function.Name}: {function.Description} {def}");
}
if (!funcDeclarations.IsNullOrEmpty())
{
tools.Add(new Tool { FunctionDeclarations = funcDeclarations });
}
var convPrompts = new List<string>();
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<object>(message.FunctionArgs ?? "{}")
}
}
});
convPrompts.Add($"{AgentRole.Assistant}: Call function {message.FunctionName}({message.FunctionArgs})");
}
else if (message.Role == AgentRole.User)
{
var text = !string.IsNullOrWhiteSpace(message.Payload) ? message.Payload : message.Content;
contents.Add(new Content(text)
{
Role = AgentRole.User
});
convPrompts.Add($"{AgentRole.User}: {text}");
}
else if (message.Role == AgentRole.Assistant)
{
contents.Add(new Content(message.Content)
{
Role = AgentRole.Model
});
convPrompts.Add($"{AgentRole.Assistant}: {message.Content}");
}
}
var request = new GenerateContentRequest
{
Contents = contents,
Tools = tools
};
var prompt = GetPrompt(systemPrompts, funcPrompts, convPrompts);
return (prompt, request);
}
private string GetPrompt(IEnumerable<string> systemPrompts, IEnumerable<string> funcPrompts, IEnumerable<string> convPrompts)
{
var prompt = string.Empty;
prompt = string.Join("\r\n\r\n", systemPrompts);
if (!funcPrompts.IsNullOrEmpty())
{
prompt += "\r\n\r\n[FUNCTIONS]\r\n";
prompt += string.Join("\r\n", funcPrompts);
}
if (!convPrompts.IsNullOrEmpty())
{
prompt += "\r\n\r\n[CONVERSATION]\r\n";
prompt += string.Join("\r\n", convPrompts);
}
return prompt;
}
}

View file

@ -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<PalmChatCompletionProvider> _logger;
private string _model;
public ChatCompletionProvider(IServiceProvider services,
GoogleAiSettings settings,
ILogger<ChatCompletionProvider> logger)
public string Provider => "google-ai";
public PalmChatCompletionProvider(
IServiceProvider services,
ILogger<PalmChatCompletionProvider> logger)
{
_services = services;
_settings = settings;
_logger = logger;
}
public async Task<RoleDialogModel> GetChatCompletions(Agent agent, List<RoleDialogModel> conversations)
{
var hooks = _services.GetServices<IContentGeneratingHook>().ToList();
var contentHooks = _services.GetServices<IContentGeneratingHook>().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;
}

View file

@ -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<GoogleAiSettings>();
var client = new GoogleAI(settings.Gemini.ApiKey);
return client;
}
public static GooglePalmClient GetPalmClient(IServiceProvider services)
{
var settings = services.GetRequiredService<GoogleAiSettings>();
var client = new GooglePalmClient(settings.PaLM.ApiKey);
return client;
}
}

View file

@ -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<GeminiTextCompletionProvider> _logger;
private readonly ITokenStatistics _tokenStatistics;
private string _model;
public string Provider => "google-gemini";
public GeminiTextCompletionProvider(
IServiceProvider services,
ILogger<GeminiTextCompletionProvider> logger,
ITokenStatistics tokenStatistics)
{
_services = services;
_logger = logger;
_tokenStatistics = tokenStatistics;
}
public async Task<string> GetCompletion(string text, string agentId, string messageId)
{
var contentHooks = _services.GetServices<IContentGeneratingHook>().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<RoleDialogModel> { 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<GoogleAiSettings>();
aiModel.UseGoogleSearch = settings.Gemini.UseGoogleSearch;
aiModel.UseGrounding = settings.Gemini.UseGrounding;
}
}

View file

@ -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<PalmTextCompletionProvider> _logger;
private readonly ITokenStatistics _tokenStatistics;
private string _model;
public string Provider => "google-ai";
public PalmTextCompletionProvider(
IServiceProvider services,
ILogger<PalmTextCompletionProvider> logger,
ITokenStatistics tokenStatistics)
{
_services = services;
_logger = logger;
_tokenStatistics = tokenStatistics;
}
public async Task<string> GetCompletion(string text, string agentId, string messageId)
{
var contentHooks = _services.GetServices<IContentGeneratingHook>().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<RoleDialogModel> { 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;
}
}

View file

@ -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<TextCompletionProvider> logger,
ITokenStatistics tokenStatistics)
{
_services = services;
_settings = settings;
_logger = logger;
_tokenStatistics = tokenStatistics;
}
public async Task<string> GetCompletion(string text, string agentId, string messageId)
{
var hooks = _services.GetServices<IContentGeneratingHook>().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<RoleDialogModel> { 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;
}
}

View file

@ -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; }
}

View file

@ -1,7 +0,0 @@
namespace BotSharp.Plugin.GoogleAI.Settings;
public class PaLMSetting
{
public string Endpoint { get; set; } = string.Empty;
public string ApiKey { get; set; }
}

View file

@ -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;
global using BotSharp.Abstraction.Utilities;
global using BotSharp.Plugin.GoogleAi.Settings;

View file

@ -268,20 +268,20 @@ public class ChatCompletionProvider : IChatCompletion
if (!string.IsNullOrEmpty(file.FileData))
{
var (contentType, bytes) = FileUtility.GetFileInfoFromData(file.FileData);
var contentPart = ChatMessageContentPart.CreateImagePart(BinaryData.FromBytes(bytes), contentType, ChatImageDetailLevel.Low);
var contentPart = ChatMessageContentPart.CreateImagePart(BinaryData.FromBytes(bytes), contentType, ChatImageDetailLevel.Auto);
contentParts.Add(contentPart);
}
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
{
var contentType = FileUtility.GetFileContentType(file.FileStorageUrl);
var bytes = fileStorage.GetFileBytes(file.FileStorageUrl);
var contentPart = ChatMessageContentPart.CreateImagePart(BinaryData.FromBytes(bytes), contentType, ChatImageDetailLevel.Low);
var contentPart = ChatMessageContentPart.CreateImagePart(BinaryData.FromBytes(bytes), contentType, ChatImageDetailLevel.Auto);
contentParts.Add(contentPart);
}
else if (!string.IsNullOrEmpty(file.FileUrl))
{
var uri = new Uri(file.FileUrl);
var contentPart = ChatMessageContentPart.CreateImagePart(uri, ChatImageDetailLevel.Low);
var contentPart = ChatMessageContentPart.CreateImagePart(uri, ChatImageDetailLevel.Auto);
contentParts.Add(contentPart);
}
}

View file

@ -9,10 +9,8 @@ public class ProviderHelper
{
var settingsService = services.GetRequiredService<ILlmProviderService>();
var settings = settingsService.GetSetting(provider, model);
var options = string.IsNullOrEmpty(settings.Endpoint)
? null
: new OpenAIClientOptions { Endpoint = new Uri(settings.Endpoint) };
return new OpenAIClient(new ApiKeyCredential(settings.ApiKey), options);
var client = new OpenAIClient(new ApiKeyCredential(settings.ApiKey));
return client;
}
public static List<RoleDialogModel> GetChatSamples(List<string> lines)

View file

@ -197,6 +197,11 @@
"PaLM": {
"Endpoint": "https://generativelanguage.googleapis.com",
"ApiKey": ""
},
"Gemini": {
"ApiKey": "",
"UseGoogleSearch": false,
"UseGrounding": false
}
},