Merge pull request #993 from gunpal5/master
feat: Added Google Multimodal Live APIs
This commit is contained in:
commit
18c6598af3
11
BotSharp.sln
11
BotSharp.sln
|
|
@ -141,6 +141,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Abstraction.Comput
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Test.BrowserUse", "tests\BotSharp.Test.BrowserUse\BotSharp.Test.BrowserUse.csproj", "{7D0DB012-9798-4BB9-B15B-A5B0B7B3B094}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Test.BrowserUse", "tests\BotSharp.Test.BrowserUse\BotSharp.Test.BrowserUse.csproj", "{7D0DB012-9798-4BB9-B15B-A5B0B7B3B094}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.LLM.Tests", "tests\BotSharp.LLM.Tests\BotSharp.LLM.Tests.csproj", "{7C0C7D13-D161-4AB0-9C29-83A0F1FF990E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -589,6 +591,14 @@ Global
|
||||||
{7D0DB012-9798-4BB9-B15B-A5B0B7B3B094}.Release|Any CPU.Build.0 = Release|Any CPU
|
{7D0DB012-9798-4BB9-B15B-A5B0B7B3B094}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{7D0DB012-9798-4BB9-B15B-A5B0B7B3B094}.Release|x64.ActiveCfg = Release|Any CPU
|
{7D0DB012-9798-4BB9-B15B-A5B0B7B3B094}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{7D0DB012-9798-4BB9-B15B-A5B0B7B3B094}.Release|x64.Build.0 = Release|Any CPU
|
{7D0DB012-9798-4BB9-B15B-A5B0B7B3B094}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{7C0C7D13-D161-4AB0-9C29-83A0F1FF990E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7C0C7D13-D161-4AB0-9C29-83A0F1FF990E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7C0C7D13-D161-4AB0-9C29-83A0F1FF990E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{7C0C7D13-D161-4AB0-9C29-83A0F1FF990E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{7C0C7D13-D161-4AB0-9C29-83A0F1FF990E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7C0C7D13-D161-4AB0-9C29-83A0F1FF990E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{7C0C7D13-D161-4AB0-9C29-83A0F1FF990E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{7C0C7D13-D161-4AB0-9C29-83A0F1FF990E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -657,6 +667,7 @@ Global
|
||||||
{B268E2F0-060F-8466-7D81-ABA4D735CA59} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
|
{B268E2F0-060F-8466-7D81-ABA4D735CA59} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
|
||||||
{970BE341-9AC8-99A5-6572-E703C1E02FCB} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
|
{970BE341-9AC8-99A5-6572-E703C1E02FCB} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
|
||||||
{7D0DB012-9798-4BB9-B15B-A5B0B7B3B094} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
|
{7D0DB012-9798-4BB9-B15B-A5B0B7B3B094} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
|
||||||
|
{7C0C7D13-D161-4AB0-9C29-83A0F1FF990E} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
|
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="EntityFramework" Version="6.4.4" />
|
<PackageVersion Include="EntityFramework" Version="6.4.4" />
|
||||||
<PackageVersion Include="Google_GenerativeAI" Version="2.4.6" />
|
<PackageVersion Include="Google_GenerativeAI" Version="2.5.5" />
|
||||||
|
<PackageVersion Include="Google_GenerativeAI.Live" Version="2.5.5" />
|
||||||
<PackageVersion Include="LLMSharp.Google.Palm" Version="1.0.2" />
|
<PackageVersion Include="LLMSharp.Google.Palm" Version="1.0.2" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(AspNetCoreVersion)" />
|
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.StaticFiles" Version="$(AspNetCoreVersion)" />
|
<PackageVersion Include="Microsoft.AspNetCore.StaticFiles" Version="$(AspNetCoreVersion)" />
|
||||||
|
|
@ -110,7 +111,8 @@
|
||||||
<PackageVersion Include="MSTest.TestAdapter" Version="3.1.1" />
|
<PackageVersion Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||||
<PackageVersion Include="MSTest.TestFramework" Version="3.1.1" />
|
<PackageVersion Include="MSTest.TestFramework" Version="3.1.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||||
|
<PackageVersion Include="Shouldly" Version="4.3.0" />
|
||||||
<PackageVersion Include="ModelContextProtocol" Version="0.1.0-preview.5" />
|
<PackageVersion Include="ModelContextProtocol" Version="0.1.0-preview.5" />
|
||||||
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.1.0-preview.5" />
|
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.1.0-preview.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
@ -144,4 +146,4 @@
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.25" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.25" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.26" />
|
<PackageVersion Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.26" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -23,7 +23,7 @@ public class FunctionDef
|
||||||
public string? Impact { get; set; }
|
public string? Impact { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("parameters")]
|
[JsonPropertyName("parameters")]
|
||||||
public FunctionParametersDef Parameters { get; set; } = new FunctionParametersDef();
|
public FunctionParametersDef? Parameters { get; set; } = new FunctionParametersDef();
|
||||||
|
|
||||||
[JsonPropertyName("output")]
|
[JsonPropertyName("output")]
|
||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
|
|
||||||
|
|
@ -89,17 +89,20 @@ public class ChatCompletionProvider : IChatCompletion
|
||||||
return responseMessage;
|
return responseMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> GetChatCompletionsAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived, Func<RoleDialogModel, Task> onFunctionExecuting)
|
public Task<bool> GetChatCompletionsAsync(Agent agent, List<RoleDialogModel> conversations,
|
||||||
|
Func<RoleDialogModel, Task> onMessageReceived, Func<RoleDialogModel, Task> onFunctionExecuting)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> GetChatCompletionsStreamingAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived)
|
public Task<bool> GetChatCompletionsStreamingAsync(Agent agent, List<RoleDialogModel> conversations,
|
||||||
|
Func<RoleDialogModel, Task> onMessageReceived)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private (string, MessageParameters) PrepareOptions(Agent agent, List<RoleDialogModel> conversations, LlmModelSetting settings)
|
private (string, MessageParameters) PrepareOptions(Agent agent, List<RoleDialogModel> conversations,
|
||||||
|
LlmModelSetting settings)
|
||||||
{
|
{
|
||||||
var instruction = "";
|
var instruction = "";
|
||||||
renderedInstructions = [];
|
renderedInstructions = [];
|
||||||
|
|
@ -178,8 +181,8 @@ public class ChatCompletionProvider : IChatCompletion
|
||||||
var state = _services.GetRequiredService<IConversationStateService>();
|
var state = _services.GetRequiredService<IConversationStateService>();
|
||||||
var temperature = decimal.Parse(state.GetState("temperature", "0.0"));
|
var temperature = decimal.Parse(state.GetState("temperature", "0.0"));
|
||||||
var maxTokens = int.TryParse(state.GetState("max_tokens"), out var tokens)
|
var maxTokens = int.TryParse(state.GetState("max_tokens"), out var tokens)
|
||||||
? tokens
|
? tokens
|
||||||
: agent.LlmConfig?.MaxOutputTokens ?? LlmConstant.DEFAULT_MAX_OUTPUT_TOKEN;
|
: agent.LlmConfig?.MaxOutputTokens ?? LlmConstant.DEFAULT_MAX_OUTPUT_TOKEN;
|
||||||
|
|
||||||
var parameters = new MessageParameters()
|
var parameters = new MessageParameters()
|
||||||
{
|
{
|
||||||
|
|
@ -188,7 +191,7 @@ public class ChatCompletionProvider : IChatCompletion
|
||||||
Model = settings.Name,
|
Model = settings.Name,
|
||||||
Stream = false,
|
Stream = false,
|
||||||
Temperature = temperature,
|
Temperature = temperature,
|
||||||
Tools = new List<Anthropic.SDK.Common.Tool>()
|
Tools = new List<Anthropic.SDK.Common.Tool>()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(instruction))
|
if (!string.IsNullOrEmpty(instruction))
|
||||||
|
|
@ -197,9 +200,11 @@ public class ChatCompletionProvider : IChatCompletion
|
||||||
{
|
{
|
||||||
new SystemMessage(instruction)
|
new SystemMessage(instruction)
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
JsonSerializerOptions jsonSerializationOptions = new()
|
;
|
||||||
|
|
||||||
|
JsonSerializerOptions? jsonSerializationOptions = new()
|
||||||
{
|
{
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
Converters = { new JsonStringEnumConverter() },
|
Converters = { new JsonStringEnumConverter() },
|
||||||
|
|
@ -238,7 +243,7 @@ public class ChatCompletionProvider : IChatCompletion
|
||||||
|
|
||||||
private string GetPrompt(MessageParameters parameters)
|
private string GetPrompt(MessageParameters parameters)
|
||||||
{
|
{
|
||||||
var prompt = $"{string.Join("\r\n", parameters.System.Select(x => x.Text))}\r\n";
|
var prompt = $"{string.Join("\r\n", (parameters.System ?? new List<SystemMessage>()).Select(x => x.Text))}\r\n";
|
||||||
prompt += "\r\n[CONVERSATION]";
|
prompt += "\r\n[CONVERSATION]";
|
||||||
|
|
||||||
var verbose = string.Join("\r\n", parameters.Messages
|
var verbose = string.Join("\r\n", parameters.Messages
|
||||||
|
|
@ -272,6 +277,7 @@ public class ChatCompletionProvider : IChatCompletion
|
||||||
}));
|
}));
|
||||||
return $"{role}: {content}";
|
return $"{role}: {content}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -279,10 +285,12 @@ public class ChatCompletionProvider : IChatCompletion
|
||||||
|
|
||||||
if (parameters.Tools != null && parameters.Tools.Count > 0)
|
if (parameters.Tools != null && parameters.Tools.Count > 0)
|
||||||
{
|
{
|
||||||
var functions = string.Join("\r\n", parameters.Tools.Select(x =>
|
var functions = string.Join("\r\n",
|
||||||
{
|
parameters.Tools.Select(x =>
|
||||||
return $"\r\n{x.Function.Name}: {x.Function.Description}\r\n{JsonSerializer.Serialize(x.Function.Parameters)}";
|
{
|
||||||
}));
|
return
|
||||||
|
$"\r\n{x.Function.Name}: {x.Function.Description}\r\n{JsonSerializer.Serialize(x.Function.Parameters)}";
|
||||||
|
}));
|
||||||
prompt += $"\r\n[FUNCTIONS]\r\n{functions}\r\n";
|
prompt += $"\r\n[FUNCTIONS]\r\n{functions}\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,4 +301,4 @@ public class ChatCompletionProvider : IChatCompletion
|
||||||
{
|
{
|
||||||
_model = model;
|
_model = model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,10 +12,12 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Google_GenerativeAI" />
|
<PackageReference Include="Google_GenerativeAI" />
|
||||||
|
<PackageReference Include="Google_GenerativeAI.Live" />
|
||||||
<PackageReference Include="LLMSharp.Google.Palm" />
|
<PackageReference Include="LLMSharp.Google.Palm" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using BotSharp.Abstraction.Plugins;
|
||||||
using BotSharp.Abstraction.Settings;
|
using BotSharp.Abstraction.Settings;
|
||||||
using BotSharp.Plugin.GoogleAi.Providers.Chat;
|
using BotSharp.Plugin.GoogleAi.Providers.Chat;
|
||||||
using BotSharp.Plugin.GoogleAI.Providers.Embedding;
|
using BotSharp.Plugin.GoogleAI.Providers.Embedding;
|
||||||
|
using BotSharp.Plugin.GoogleAi.Providers.Realtime;
|
||||||
using BotSharp.Plugin.GoogleAi.Providers.Text;
|
using BotSharp.Plugin.GoogleAi.Providers.Text;
|
||||||
|
|
||||||
namespace BotSharp.Plugin.GoogleAi;
|
namespace BotSharp.Plugin.GoogleAi;
|
||||||
|
|
@ -24,6 +25,7 @@ public class GoogleAiPlugin : IBotSharpPlugin
|
||||||
services.AddScoped<ITextCompletion, GeminiTextCompletionProvider>();
|
services.AddScoped<ITextCompletion, GeminiTextCompletionProvider>();
|
||||||
services.AddScoped<IChatCompletion, PalmChatCompletionProvider>();
|
services.AddScoped<IChatCompletion, PalmChatCompletionProvider>();
|
||||||
services.AddScoped<IChatCompletion, GeminiChatCompletionProvider>();
|
services.AddScoped<IChatCompletion, GeminiChatCompletionProvider>();
|
||||||
|
services.AddScoped<IRealTimeCompletion, GoogleRealTimeProvider>();
|
||||||
services.AddScoped<ITextEmbedding, TextEmbeddingProvider>();
|
services.AddScoped<ITextEmbedding, TextEmbeddingProvider>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,13 @@ public class GeminiChatCompletionProvider : IChatCompletion
|
||||||
public string Provider => "google-ai";
|
public string Provider => "google-ai";
|
||||||
public string Model => _model;
|
public string Model => _model;
|
||||||
|
|
||||||
|
private GoogleAiSettings _settings;
|
||||||
public GeminiChatCompletionProvider(
|
public GeminiChatCompletionProvider(
|
||||||
IServiceProvider services,
|
IServiceProvider services,
|
||||||
|
GoogleAiSettings googleSettings,
|
||||||
ILogger<GeminiChatCompletionProvider> logger)
|
ILogger<GeminiChatCompletionProvider> logger)
|
||||||
{
|
{
|
||||||
|
_settings = googleSettings;
|
||||||
_services = services;
|
_services = services;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using LLMSharp.Google.Palm;
|
using LLMSharp.Google.Palm;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace BotSharp.Plugin.GoogleAi.Providers;
|
namespace BotSharp.Plugin.GoogleAi.Providers;
|
||||||
|
|
||||||
|
|
@ -6,10 +7,18 @@ public static class ProviderHelper
|
||||||
{
|
{
|
||||||
public static GenerativeAI.GoogleAi GetGeminiClient(string provider, string model, IServiceProvider services)
|
public static GenerativeAI.GoogleAi GetGeminiClient(string provider, string model, IServiceProvider services)
|
||||||
{
|
{
|
||||||
var settingsService = services.GetRequiredService<ILlmProviderService>();
|
var aiSettings = services.GetRequiredService<GoogleAiSettings>();
|
||||||
var settings = settingsService.GetSetting(provider, model);
|
if (aiSettings == null || aiSettings.Gemini ==null || string.IsNullOrEmpty(aiSettings.Gemini.ApiKey))
|
||||||
var client = new GenerativeAI.GoogleAi(settings.ApiKey);
|
{
|
||||||
return client;
|
var settingsService = services.GetRequiredService<ILlmProviderService>();
|
||||||
|
var settings = settingsService.GetSetting(provider, model);
|
||||||
|
var client = new GenerativeAI.GoogleAi(settings.ApiKey);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new GenerativeAI.GoogleAi(aiSettings.Gemini.ApiKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GooglePalmClient GetPalmClient(string provider, string model, IServiceProvider services)
|
public static GooglePalmClient GetPalmClient(string provider, string model, IServiceProvider services)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,509 @@
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Threading;
|
||||||
|
using BotSharp.Abstraction.Agents;
|
||||||
|
using BotSharp.Abstraction.Agents.Enums;
|
||||||
|
using BotSharp.Abstraction.Conversations;
|
||||||
|
using BotSharp.Abstraction.Conversations.Enums;
|
||||||
|
using BotSharp.Abstraction.Files;
|
||||||
|
using BotSharp.Abstraction.Files.Utilities;
|
||||||
|
using BotSharp.Abstraction.Functions.Models;
|
||||||
|
using BotSharp.Abstraction.Loggers;
|
||||||
|
using BotSharp.Abstraction.Options;
|
||||||
|
using BotSharp.Abstraction.Realtime;
|
||||||
|
using BotSharp.Abstraction.Realtime.Models;
|
||||||
|
using BotSharp.Core.Infrastructures;
|
||||||
|
using BotSharp.Plugin.GoogleAi.Providers.Chat;
|
||||||
|
using GenerativeAI;
|
||||||
|
using GenerativeAI.Core;
|
||||||
|
using GenerativeAI.Live;
|
||||||
|
using GenerativeAI.Live.Extensions;
|
||||||
|
using GenerativeAI.Types;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace BotSharp.Plugin.GoogleAi.Providers.Realtime
|
||||||
|
{
|
||||||
|
public class GoogleRealTimeProvider : IRealTimeCompletion
|
||||||
|
{
|
||||||
|
public string Provider => "google-ai";
|
||||||
|
private string _model = GoogleAIModels.Gemini2FlashExp;
|
||||||
|
public string Model { get; }
|
||||||
|
private MultiModalLiveClient? _client;
|
||||||
|
private readonly IServiceProvider _services;
|
||||||
|
private readonly ILogger<GeminiChatCompletionProvider> _logger;
|
||||||
|
private List<string> renderedInstructions = [];
|
||||||
|
|
||||||
|
private readonly GoogleAiSettings _settings;
|
||||||
|
|
||||||
|
public GoogleRealTimeProvider(
|
||||||
|
IServiceProvider services,
|
||||||
|
GoogleAiSettings googleSettings,
|
||||||
|
ILogger<GeminiChatCompletionProvider> logger)
|
||||||
|
{
|
||||||
|
_settings = googleSettings;
|
||||||
|
_services = services;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetModelName(string model)
|
||||||
|
{
|
||||||
|
_model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Action onModelReady;
|
||||||
|
Action<string, string> onModelAudioDeltaReceived;
|
||||||
|
private Action onModelAudioResponseDone;
|
||||||
|
Action<string> onModelAudioTranscriptDone;
|
||||||
|
private Action<List<RoleDialogModel>> onModelResponseDone;
|
||||||
|
Action<string> onConversationItemCreated;
|
||||||
|
private Action<RoleDialogModel> onInputAudioTranscriptionCompleted;
|
||||||
|
Action onUserInterrupted;
|
||||||
|
RealtimeHubConnection conn;
|
||||||
|
|
||||||
|
public async Task Connect(RealtimeHubConnection conn,
|
||||||
|
Action onModelReady,
|
||||||
|
Action<string, string> onModelAudioDeltaReceived,
|
||||||
|
Action onModelAudioResponseDone,
|
||||||
|
Action<string> onModelAudioTranscriptDone,
|
||||||
|
Action<List<RoleDialogModel>> onModelResponseDone,
|
||||||
|
Action<string> onConversationItemCreated,
|
||||||
|
Action<RoleDialogModel> onInputAudioTranscriptionCompleted,
|
||||||
|
Action onUserInterrupted)
|
||||||
|
{
|
||||||
|
this.conn = conn;
|
||||||
|
this.onModelReady = onModelReady;
|
||||||
|
this.onModelAudioDeltaReceived = onModelAudioDeltaReceived;
|
||||||
|
this.onModelAudioResponseDone = onModelAudioResponseDone;
|
||||||
|
this.onModelAudioTranscriptDone = onModelAudioTranscriptDone;
|
||||||
|
this.onModelResponseDone = onModelResponseDone;
|
||||||
|
this.onConversationItemCreated = onConversationItemCreated;
|
||||||
|
this.onInputAudioTranscriptionCompleted = onInputAudioTranscriptionCompleted;
|
||||||
|
this.onUserInterrupted = onUserInterrupted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Disconnect()
|
||||||
|
{
|
||||||
|
if (_client != null)
|
||||||
|
await _client.DisconnectAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AppenAudioBuffer(string message)
|
||||||
|
{
|
||||||
|
await _client.SendAudioAsync(Convert.FromBase64String(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TriggerModelInference(string? instructions = null)
|
||||||
|
{
|
||||||
|
await _client.SendClientContentAsync(new BidiGenerateContentClientContent()
|
||||||
|
{
|
||||||
|
TurnComplete = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CancelModelResponse()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveConversationItem(string itemId)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AttachEvents()
|
||||||
|
{
|
||||||
|
_client.MessageReceived += async (sender, e) =>
|
||||||
|
{
|
||||||
|
if (e.Payload.SetupComplete != null)
|
||||||
|
{
|
||||||
|
onModelReady();
|
||||||
|
onConversationItemCreated(_client.ConnectionId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.Payload.ServerContent != null)
|
||||||
|
{
|
||||||
|
if (e.Payload.ServerContent.TurnComplete == true)
|
||||||
|
{
|
||||||
|
var responseDone = await ResponseDone(conn, e.Payload.ServerContent);
|
||||||
|
onModelResponseDone(responseDone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_client.AudioChunkReceived += async (sender, e) =>
|
||||||
|
{
|
||||||
|
onModelAudioDeltaReceived(Convert.ToBase64String(e.Buffer), Guid.NewGuid().ToString());
|
||||||
|
};
|
||||||
|
|
||||||
|
_client.TextChunkReceived += async (sender, e) =>
|
||||||
|
{
|
||||||
|
onInputAudioTranscriptionCompleted(new RoleDialogModel(AgentRole.Assistant, e.Text));
|
||||||
|
};
|
||||||
|
_client.GenerationInterrupted += async (sender, e) => { onUserInterrupted(); };
|
||||||
|
_client.AudioReceiveCompleted += async (sender, e) => { onModelAudioResponseDone(); };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<RoleDialogModel>> ResponseDone(RealtimeHubConnection conn,
|
||||||
|
BidiGenerateContentServerContent serverContent)
|
||||||
|
{
|
||||||
|
var outputs = new List<RoleDialogModel>();
|
||||||
|
|
||||||
|
var parts = serverContent.ModelTurn?.Parts;
|
||||||
|
if (parts != null)
|
||||||
|
{
|
||||||
|
foreach (var part in parts)
|
||||||
|
{
|
||||||
|
var call = part.FunctionCall;
|
||||||
|
if (call != null)
|
||||||
|
{
|
||||||
|
var item = new RoleDialogModel(AgentRole.Assistant, part.Text)
|
||||||
|
{
|
||||||
|
CurrentAgentId = conn.CurrentAgentId,
|
||||||
|
MessageId = call.Id ?? String.Empty,
|
||||||
|
MessageType = MessageTypeName.FunctionCall
|
||||||
|
};
|
||||||
|
outputs.Add(item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var item = new RoleDialogModel(AgentRole.Assistant, call.Args?.ToJsonString() ?? string.Empty)
|
||||||
|
{
|
||||||
|
CurrentAgentId = conn.CurrentAgentId,
|
||||||
|
FunctionName = call.Name,
|
||||||
|
FunctionArgs = call.Args?.ToJsonString() ?? string.Empty,
|
||||||
|
ToolCallId = call.Id ?? String.Empty,
|
||||||
|
MessageId = call.Id ?? String.Empty,
|
||||||
|
MessageType = MessageTypeName.FunctionCall
|
||||||
|
};
|
||||||
|
outputs.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentHooks = _services.GetServices<IContentGeneratingHook>().ToList();
|
||||||
|
// After chat completion hook
|
||||||
|
foreach (var hook in contentHooks)
|
||||||
|
{
|
||||||
|
await hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, "response.done")
|
||||||
|
{
|
||||||
|
CurrentAgentId = conn.CurrentAgentId
|
||||||
|
}, new TokenStatsModel
|
||||||
|
{
|
||||||
|
Provider = Provider,
|
||||||
|
Model = _model,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendEventToModel(object message)
|
||||||
|
{
|
||||||
|
//todo Send Audio Chunks to Model, Botsharp RealTime Implementation seems to be incomplete
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RealtimeSession> CreateSession(Agent agent, List<RoleDialogModel> conversations)
|
||||||
|
{
|
||||||
|
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
|
||||||
|
var chatClient = client.CreateGenerativeModel(_model);
|
||||||
|
var (prompt, request) = PrepareOptions(chatClient, agent, conversations);
|
||||||
|
|
||||||
|
var config = request.GenerationConfig;
|
||||||
|
|
||||||
|
//Output Modality can either be text or audio
|
||||||
|
config.ResponseModalities = new List<Modality>([Modality.AUDIO]);
|
||||||
|
|
||||||
|
var settingsService = _services.GetRequiredService<ILlmProviderService>();
|
||||||
|
var settings = settingsService.GetSetting(Provider, _model);
|
||||||
|
|
||||||
|
_client = chatClient.CreateMultiModalLiveClient(config,
|
||||||
|
systemInstruction: request.SystemInstruction?.Parts.FirstOrDefault()?.Text);
|
||||||
|
_client.UseGoogleSearch = _settings.Gemini.UseGoogleSearch;
|
||||||
|
|
||||||
|
if (_settings.Gemini.UseGoogleSearch)
|
||||||
|
{
|
||||||
|
if (request.Tools == null)
|
||||||
|
request.Tools = new List<Tool>();
|
||||||
|
request.Tools.Add(new Tool()
|
||||||
|
{
|
||||||
|
GoogleSearch = new GoogleSearchTool()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await AttachEvents();
|
||||||
|
|
||||||
|
await _client.ConnectAsync();
|
||||||
|
|
||||||
|
await _client.SendSetupAsync(new BidiGenerateContentSetup()
|
||||||
|
{
|
||||||
|
GenerationConfig = config,
|
||||||
|
Model = Model,
|
||||||
|
SystemInstruction = request.SystemInstruction,
|
||||||
|
Tools = request.Tools?.ToArray(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return new RealtimeSession()
|
||||||
|
{
|
||||||
|
Id = _client.ConnectionId.ToString(),
|
||||||
|
Model = _model,
|
||||||
|
Voice = "default"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> UpdateSession(RealtimeHubConnection conn, bool interruptResponse = true)
|
||||||
|
{
|
||||||
|
var convService = _services.GetRequiredService<IConversationService>();
|
||||||
|
var conv = await convService.GetConversation(conn.ConversationId);
|
||||||
|
|
||||||
|
var agentService = _services.GetRequiredService<IAgentService>();
|
||||||
|
var agent = await agentService.LoadAgent(conn.CurrentAgentId);
|
||||||
|
|
||||||
|
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
|
||||||
|
var chatClient = client.CreateGenerativeModel(_model);
|
||||||
|
var (prompt, request) = PrepareOptions(chatClient, agent, new List<RoleDialogModel>());
|
||||||
|
|
||||||
|
|
||||||
|
var config = request.GenerationConfig;
|
||||||
|
//Output Modality can either be text or audio
|
||||||
|
if (config != null)
|
||||||
|
{
|
||||||
|
config.ResponseModalities = new List<Modality>([Modality.AUDIO]);
|
||||||
|
|
||||||
|
var words = new List<string>();
|
||||||
|
HookEmitter.Emit<IRealtimeHook>(_services, hook => words.AddRange(hook.OnModelTranscriptPrompt(agent)));
|
||||||
|
|
||||||
|
var realtimeModelSettings = _services.GetRequiredService<RealtimeModelSettings>();
|
||||||
|
|
||||||
|
config.Temperature = Math.Max(realtimeModelSettings.Temperature, 0.6f);
|
||||||
|
config.MaxOutputTokens = realtimeModelSettings.MaxResponseOutputTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var functions = request.Tools?.SelectMany(s => s.FunctionDeclarations).Select(x =>
|
||||||
|
{
|
||||||
|
var fn = new FunctionDef
|
||||||
|
{
|
||||||
|
Name = x.Name ?? string.Empty,
|
||||||
|
Description = x.Description ?? string.Empty,
|
||||||
|
};
|
||||||
|
fn.Parameters = x.Parameters != null
|
||||||
|
? JsonSerializer.Deserialize<FunctionParametersDef>(JsonSerializer.Serialize(x.Parameters))
|
||||||
|
: null;
|
||||||
|
return fn;
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
await HookEmitter.Emit<IContentGeneratingHook>(_services,
|
||||||
|
async hook => { await hook.OnSessionUpdated(agent, prompt, functions); });
|
||||||
|
|
||||||
|
if (_settings.Gemini.UseGoogleSearch)
|
||||||
|
{
|
||||||
|
if (request.Tools == null)
|
||||||
|
request.Tools = new List<Tool>();
|
||||||
|
request.Tools.Add(new Tool()
|
||||||
|
{
|
||||||
|
GoogleSearch = new GoogleSearchTool()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//ToDo: Not sure what's the purpose of UpdateSession, Google Realtime conversion works right away after sending the message!
|
||||||
|
|
||||||
|
// await _client.SendSetupAsync(new BidiGenerateContentSetup()
|
||||||
|
// {
|
||||||
|
// GenerationConfig = config,
|
||||||
|
// Model = Model,
|
||||||
|
// SystemInstruction = request.SystemInstruction,
|
||||||
|
// Tools = request.Tools?.ToArray(),
|
||||||
|
// });
|
||||||
|
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InsertConversationItem(RoleDialogModel message)
|
||||||
|
{
|
||||||
|
if (_client == null)
|
||||||
|
throw new Exception("Client is not initialized");
|
||||||
|
if (message.Role == AgentRole.Function)
|
||||||
|
{
|
||||||
|
var function = new FunctionResponse()
|
||||||
|
{
|
||||||
|
Name = message.FunctionName ?? string.Empty,
|
||||||
|
Response = JsonNode.Parse(message.Content ?? "{}")
|
||||||
|
};
|
||||||
|
|
||||||
|
await _client.SendToolResponseAsync(new BidiGenerateContentToolResponse()
|
||||||
|
{
|
||||||
|
FunctionResponses = [function]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (message.Role == AgentRole.Assistant)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
else if (message.Role == AgentRole.User)
|
||||||
|
{
|
||||||
|
await _client.SentTextAsync(message.Content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<RoleDialogModel>> OnResponsedDone(RealtimeHubConnection conn, string response)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Task<RoleDialogModel> OnConversationItemCreated(RealtimeHubConnection conn, string response)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new RoleDialogModel(AgentRole.User, response));
|
||||||
|
}
|
||||||
|
|
||||||
|
private (string, GenerateContentRequest) PrepareOptions(GenerativeModel aiModel, Agent agent,
|
||||||
|
List<RoleDialogModel> conversations)
|
||||||
|
{
|
||||||
|
var agentService = _services.GetRequiredService<IAgentService>();
|
||||||
|
var googleSettings = _settings;
|
||||||
|
renderedInstructions = [];
|
||||||
|
|
||||||
|
// Add settings
|
||||||
|
aiModel.UseGoogleSearch = googleSettings.Gemini.UseGoogleSearch;
|
||||||
|
aiModel.UseGrounding = googleSettings.Gemini.UseGrounding;
|
||||||
|
|
||||||
|
aiModel.FunctionCallingBehaviour = new FunctionCallingBehaviour()
|
||||||
|
{
|
||||||
|
AutoCallFunction = false
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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) || !agent.SecondaryInstructions.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
var instruction = agentService.RenderedInstruction(agent);
|
||||||
|
renderedInstructions.Add(instruction);
|
||||||
|
systemPrompts.Add(instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
var funcPrompts = new List<string>();
|
||||||
|
var functions = agent.Functions.Concat(agent.SecondaryFunctions ?? []);
|
||||||
|
foreach (var function in functions)
|
||||||
|
{
|
||||||
|
if (!agentService.RenderFunction(agent, function)) continue;
|
||||||
|
|
||||||
|
var def = agentService.RenderFunctionProperty(agent, function);
|
||||||
|
var props = JsonSerializer.Serialize(def?.Properties);
|
||||||
|
var parameters = !string.IsNullOrWhiteSpace(props) && props != "{}"
|
||||||
|
? new Schema()
|
||||||
|
{
|
||||||
|
Type = "object",
|
||||||
|
Properties = JsonSerializer.Deserialize<Dictionary<string, Schema>>(props),
|
||||||
|
Required = def?.Required ?? []
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
|
||||||
|
funcDeclarations.Add(new FunctionDeclaration
|
||||||
|
{
|
||||||
|
Name = function.Name,
|
||||||
|
Description = function.Description,
|
||||||
|
Parameters = parameters
|
||||||
|
});
|
||||||
|
|
||||||
|
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([
|
||||||
|
new Part()
|
||||||
|
{
|
||||||
|
FunctionCall = new FunctionCall
|
||||||
|
{
|
||||||
|
Name = message.FunctionName,
|
||||||
|
Args = JsonNode.Parse(message.FunctionArgs ?? "{}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
], AgentRole.Model));
|
||||||
|
|
||||||
|
contents.Add(new Content([
|
||||||
|
new Part()
|
||||||
|
{
|
||||||
|
FunctionResponse = new FunctionResponse
|
||||||
|
{
|
||||||
|
Name = message.FunctionName ?? string.Empty,
|
||||||
|
Response = new JsonObject()
|
||||||
|
{
|
||||||
|
["result"] = message.Content ?? string.Empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
], AgentRole.Function));
|
||||||
|
|
||||||
|
convPrompts.Add(
|
||||||
|
$"{AgentRole.Assistant}: Call function {message.FunctionName}({message.FunctionArgs}) => {message.Content}");
|
||||||
|
}
|
||||||
|
else if (message.Role == AgentRole.User)
|
||||||
|
{
|
||||||
|
var text = !string.IsNullOrWhiteSpace(message.Payload) ? message.Payload : message.Content;
|
||||||
|
contents.Add(new Content(text, AgentRole.User));
|
||||||
|
convPrompts.Add($"{AgentRole.User}: {text}");
|
||||||
|
}
|
||||||
|
else if (message.Role == AgentRole.Assistant)
|
||||||
|
{
|
||||||
|
contents.Add(new Content(message.Content, AgentRole.Model));
|
||||||
|
convPrompts.Add($"{AgentRole.Assistant}: {message.Content}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = _services.GetRequiredService<IConversationStateService>();
|
||||||
|
var temperature = float.Parse(state.GetState("temperature", "0.0"));
|
||||||
|
var maxTokens = int.TryParse(state.GetState("max_tokens"), out var tokens)
|
||||||
|
? tokens
|
||||||
|
: agent.LlmConfig?.MaxOutputTokens ?? LlmConstant.DEFAULT_MAX_OUTPUT_TOKEN;
|
||||||
|
var request = new GenerateContentRequest
|
||||||
|
{
|
||||||
|
SystemInstruction = !systemPrompts.IsNullOrEmpty()
|
||||||
|
? new Content(systemPrompts[0], AgentRole.System)
|
||||||
|
: null,
|
||||||
|
Contents = contents,
|
||||||
|
Tools = tools,
|
||||||
|
GenerationConfig = new()
|
||||||
|
{
|
||||||
|
Temperature = temperature,
|
||||||
|
MaxOutputTokens = maxTokens
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var prompt = GetPrompt(systemPrompts, funcPrompts, convPrompts);
|
||||||
|
return (prompt, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPrompt(IEnumerable<string> systemPrompts, IEnumerable<string> funcPrompts,
|
||||||
|
IEnumerable<string> convPrompts)
|
||||||
|
{
|
||||||
|
string 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,11 +17,14 @@ public class GeminiTextCompletionProvider : ITextCompletion
|
||||||
public string Provider => "google-ai";
|
public string Provider => "google-ai";
|
||||||
public string Model => _model;
|
public string Model => _model;
|
||||||
|
|
||||||
|
private GoogleAiSettings _settings;
|
||||||
public GeminiTextCompletionProvider(
|
public GeminiTextCompletionProvider(
|
||||||
IServiceProvider services,
|
IServiceProvider services,
|
||||||
|
GoogleAiSettings googleSettings,
|
||||||
ILogger<GeminiTextCompletionProvider> logger,
|
ILogger<GeminiTextCompletionProvider> logger,
|
||||||
ITokenStatistics tokenStatistics)
|
ITokenStatistics tokenStatistics)
|
||||||
{
|
{
|
||||||
|
_settings = googleSettings;
|
||||||
_services = services;
|
_services = services;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_tokenStatistics = tokenStatistics;
|
_tokenStatistics = tokenStatistics;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ string[] allowedOrigins = builder.Configuration.GetSection("AllowedOrigins").Get
|
||||||
"https://botsharp.scisharpstack.org",
|
"https://botsharp.scisharpstack.org",
|
||||||
"https://chat.scisharpstack.org"
|
"https://chat.scisharpstack.org"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Add BotSharp
|
// Add BotSharp
|
||||||
builder.Services.AddBotSharpCore(builder.Configuration, options =>
|
builder.Services.AddBotSharpCore(builder.Configuration, options =>
|
||||||
|
|
|
||||||
24
src/WebStarter/WebStarter.sln
Normal file
24
src/WebStarter/WebStarter.sln
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.2.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebStarter", "WebStarter.csproj", "{6CC8C2A2-9A39-A975-2286-93F838537659}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{6CC8C2A2-9A39-A975-2286-93F838537659}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6CC8C2A2-9A39-A975-2286-93F838537659}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6CC8C2A2-9A39-A975-2286-93F838537659}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6CC8C2A2-9A39-A975-2286-93F838537659}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {E179BD61-3AB6-406D-8EDC-EBF0110A082F}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
|
|
@ -164,6 +164,27 @@
|
||||||
"CompletionCost": 0.002
|
"CompletionCost": 0.002
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Provider": "google-ai",
|
||||||
|
"Models": [
|
||||||
|
{
|
||||||
|
"Name": "gemini-2.0-flash",
|
||||||
|
"ApiKey": "",
|
||||||
|
"Type": "chat",
|
||||||
|
"MultiModal": true,
|
||||||
|
"PromptCost": 0.0015,
|
||||||
|
"CompletionCost": 0.002
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "gemini-2.0-flash-exp",
|
||||||
|
"ApiKey": "",
|
||||||
|
"Type": "realtime",
|
||||||
|
"MultiModal": true,
|
||||||
|
"PromptCost": 0.0015,
|
||||||
|
"CompletionCost": 0.002
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
@ -186,7 +207,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"MCP": {
|
"MCP": {
|
||||||
"Enabled": true,
|
"Enabled": false,
|
||||||
"McpClientOptions": {
|
"McpClientOptions": {
|
||||||
"ClientInfo": {
|
"ClientInfo": {
|
||||||
"Name": "SimpleToolsBotsharp",
|
"Name": "SimpleToolsBotsharp",
|
||||||
|
|
|
||||||
39
tests/BotSharp.LLM.Tests/BotSharp.LLM.Tests.csproj
Normal file
39
tests/BotSharp.LLM.Tests/BotSharp.LLM.Tests.csproj
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<RootNamespace>BotSharp.Plugin.Google</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||||
|
<PackageReference Include="Shouldly" />
|
||||||
|
<PackageReference Include="xunit" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Plugins\BotSharp.Plugin.AnthropicAI\BotSharp.Plugin.AnthropicAI.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Plugins\BotSharp.Plugin.GoogleAI\BotSharp.Plugin.GoogleAI.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Plugins\BotSharp.Plugin.OpenAI\BotSharp.Plugin.OpenAI.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="appsettings.Development.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
110
tests/BotSharp.LLM.Tests/ChatCompletionTests.cs
Normal file
110
tests/BotSharp.LLM.Tests/ChatCompletionTests.cs
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
using BotSharp.Abstraction.Agents.Enums;
|
||||||
|
using BotSharp.Abstraction.Agents.Models;
|
||||||
|
using BotSharp.Abstraction.Conversations.Models;
|
||||||
|
using BotSharp.Abstraction.MLTasks;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Logging.Console;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace BotSharp.Plugin.Google.Core
|
||||||
|
{
|
||||||
|
public class ChatCompletionTests:TestBase
|
||||||
|
{
|
||||||
|
protected static Agent CreateTestAgent()
|
||||||
|
{
|
||||||
|
return new Agent()
|
||||||
|
{
|
||||||
|
Id = "test-agent-id",
|
||||||
|
Name = "TestAgent",
|
||||||
|
Description = "This is a test agent used for unit testing purposes.",
|
||||||
|
Type = "Chat",
|
||||||
|
CreatedDateTime = DateTime.UtcNow,
|
||||||
|
UpdatedDateTime = DateTime.UtcNow,
|
||||||
|
IsPublic = false,
|
||||||
|
Disabled = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static IEnumerable<object[]> CreateTestLLMProviders()
|
||||||
|
{
|
||||||
|
//Common
|
||||||
|
var agent = CreateTestAgent();
|
||||||
|
IServiceCollection services;
|
||||||
|
IConfiguration configuration;
|
||||||
|
string modelName;
|
||||||
|
|
||||||
|
if (LLMProvider.CanRunGemini)
|
||||||
|
{
|
||||||
|
//Google Gemini
|
||||||
|
(services, configuration, modelName) = LLMProvider.CreateGemini();
|
||||||
|
yield return new object[] { services.BuildServiceProvider().GetService<IChatCompletion>() ?? throw new Exception("Error while initializing"), agent, modelName };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LLMProvider.CanRunOpenAI)
|
||||||
|
{
|
||||||
|
//OpenAI
|
||||||
|
(services, configuration, modelName) = LLMProvider.CreateOpenAI();
|
||||||
|
yield return new object[] { services.BuildServiceProvider().GetService<IChatCompletion>() ?? throw new Exception("Error while initializing"), agent, modelName };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LLMProvider.CanRunAnthropic)
|
||||||
|
{
|
||||||
|
//Anthropic
|
||||||
|
(services, configuration, modelName) = LLMProvider.CreateAnthropic();
|
||||||
|
yield return new object[] { services.BuildServiceProvider().GetService<IChatCompletion>() ?? throw new Exception("Error while initializing"), agent, modelName };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public ChatCompletionTests()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(CreateTestLLMProviders))]
|
||||||
|
public async Task GetChatCompletions_Test(IChatCompletion chatCompletion, Agent agent, string modelName)
|
||||||
|
{
|
||||||
|
chatCompletion.SetModelName(modelName);
|
||||||
|
var conversation = new List<RoleDialogModel>([new RoleDialogModel(AgentRole.User, "write a poem about stars")]);
|
||||||
|
|
||||||
|
var result = await chatCompletion.GetChatCompletions(agent,conversation);
|
||||||
|
result.Content.ShouldNotBeNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(CreateTestLLMProviders))]
|
||||||
|
public async Task GetChatCompletionsAsync_Test(IChatCompletion chatCompletion, Agent agent, string modelName)
|
||||||
|
{
|
||||||
|
chatCompletion.SetModelName(modelName);
|
||||||
|
var conversation = new List<RoleDialogModel>([new RoleDialogModel(AgentRole.User, "write a poem about stars")]);
|
||||||
|
RoleDialogModel reply = null;
|
||||||
|
var result = await chatCompletion.GetChatCompletionsAsync(agent,conversation, async (received) =>
|
||||||
|
{
|
||||||
|
reply = received;
|
||||||
|
}, async (func) =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
result.ShouldBeTrue();
|
||||||
|
reply.ShouldNotBeNull();
|
||||||
|
reply.Content.ShouldNotBeNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(CreateTestLLMProviders))]
|
||||||
|
public async Task GetChatCompletionsStreamingAsync_Test(IChatCompletion chatCompletion, Agent agent, string modelName)
|
||||||
|
{
|
||||||
|
chatCompletion.SetModelName(modelName);
|
||||||
|
var conversation = new List<RoleDialogModel>([new RoleDialogModel(AgentRole.User, "write a poem about stars")]);
|
||||||
|
RoleDialogModel reply = null;
|
||||||
|
var result = await chatCompletion.GetChatCompletionsStreamingAsync(agent,conversation, async (received) =>
|
||||||
|
{
|
||||||
|
reply = received;
|
||||||
|
});
|
||||||
|
result.ShouldBeTrue();
|
||||||
|
reply.ShouldNotBeNull();
|
||||||
|
reply.Content.ShouldNotBeNullOrEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
161
tests/BotSharp.LLM.Tests/Core/LLMProvider.cs
Normal file
161
tests/BotSharp.LLM.Tests/Core/LLMProvider.cs
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
using BotSharp.Abstraction.Agents;
|
||||||
|
using BotSharp.Abstraction.Conversations;
|
||||||
|
using BotSharp.Abstraction.Files;
|
||||||
|
using BotSharp.Abstraction.MLTasks;
|
||||||
|
using BotSharp.Abstraction.MLTasks.Settings;
|
||||||
|
using BotSharp.Abstraction.Repositories;
|
||||||
|
using BotSharp.Abstraction.Settings;
|
||||||
|
using BotSharp.Core;
|
||||||
|
using BotSharp.Core.Infrastructures;
|
||||||
|
using BotSharp.Plugin.AnthropicAI;
|
||||||
|
using BotSharp.Plugin.AnthropicAI.Settings;
|
||||||
|
using BotSharp.Plugin.GoogleAi;
|
||||||
|
using BotSharp.Plugin.GoogleAi.Settings;
|
||||||
|
using BotSharp.Plugin.OpenAI;
|
||||||
|
using BotSharp.Plugin.OpenAI.Settings;
|
||||||
|
using GenerativeAI;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace BotSharp.Plugin.Google.Core
|
||||||
|
{
|
||||||
|
public static class LLMProvider
|
||||||
|
{
|
||||||
|
public static bool CanRunGemini => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GOOGLE_API_KEY"));
|
||||||
|
public static bool CanRunOpenAI => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OPEN_AI_APIKEY"));
|
||||||
|
public static bool CanRunAnthropic => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"));
|
||||||
|
|
||||||
|
private static ILoggerFactory _loggerFactory = LoggerFactory.Create((builder) => builder.AddConsole());
|
||||||
|
public static (IServiceCollection services, IConfiguration config, string modelName) CreateGemini()
|
||||||
|
{
|
||||||
|
string modelName = GoogleAIModels.Gemini2FlashLitePreview;
|
||||||
|
|
||||||
|
var apiKey = Environment.GetEnvironmentVariable("GOOGLE_API_KEY") ??
|
||||||
|
throw new Exception("GOOGLE_API_KEY is not set");
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").AddInMemoryCollection(
|
||||||
|
new Dictionary<string, string?>(new[] { new KeyValuePair<string,string?>("GoogleAi:Gemini:ApiKey",apiKey) })).Build();
|
||||||
|
|
||||||
|
LlmProviderSetting setting = new LlmProviderSetting();
|
||||||
|
setting.Provider = "google-ai";
|
||||||
|
setting.Models = new List<LlmModelSetting>([
|
||||||
|
new LlmModelSetting()
|
||||||
|
{
|
||||||
|
Name = modelName,
|
||||||
|
ApiKey = apiKey
|
||||||
|
},
|
||||||
|
new LlmModelSetting()
|
||||||
|
{
|
||||||
|
Name = GoogleAIModels.Gemini2FlashExp,
|
||||||
|
ApiKey = apiKey
|
||||||
|
},
|
||||||
|
new LlmModelSetting()
|
||||||
|
{
|
||||||
|
Name = GoogleAIModels.TextEmbedding,
|
||||||
|
ApiKey = apiKey
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
services.AddSingleton(new GoogleAiSettings()
|
||||||
|
{
|
||||||
|
Gemini = new GeminiSetting()
|
||||||
|
{
|
||||||
|
ApiKey = apiKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddSingleton<List<LlmProviderSetting>>(new List<LlmProviderSetting>([ setting]));
|
||||||
|
|
||||||
|
AddCommonServices(services, configuration);
|
||||||
|
|
||||||
|
new GoogleAiPlugin().RegisterDI(services, configuration);
|
||||||
|
return (services, configuration, modelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (IServiceCollection services, IConfiguration config, string modelName) CreateOpenAI()
|
||||||
|
{
|
||||||
|
string modelName = "gpt-4o-mini";
|
||||||
|
|
||||||
|
var apiKey = Environment.GetEnvironmentVariable("OPEN_AI_APIKEY") ??
|
||||||
|
throw new Exception("OPEN_AI_APIKEY is not set");
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").AddInMemoryCollection(
|
||||||
|
new Dictionary<string, string?>(new[] { new KeyValuePair<string,string?>("GoogleAi:Gemini:ApiKey",apiKey) })).Build();
|
||||||
|
|
||||||
|
LlmProviderSetting setting = new LlmProviderSetting();
|
||||||
|
setting.Provider = "OpenAi";
|
||||||
|
setting.Models = new List<LlmModelSetting>([
|
||||||
|
new LlmModelSetting()
|
||||||
|
{
|
||||||
|
Name = modelName,
|
||||||
|
ApiKey = apiKey
|
||||||
|
},
|
||||||
|
new LlmModelSetting()
|
||||||
|
{
|
||||||
|
Name = "text-embedding-3-small",
|
||||||
|
ApiKey = apiKey
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
services.AddSingleton(new OpenAiSettings()
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddSingleton<List<LlmProviderSetting>>(new List<LlmProviderSetting>([ setting]));
|
||||||
|
|
||||||
|
AddCommonServices(services, configuration);
|
||||||
|
|
||||||
|
|
||||||
|
new OpenAiPlugin().RegisterDI(services, configuration);
|
||||||
|
return (services, configuration, modelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddCommonServices(ServiceCollection services, IConfigurationRoot configuration)
|
||||||
|
{
|
||||||
|
services.AddSingleton<IConfiguration>(configuration);
|
||||||
|
services.AddSingleton<IAgentService, TestAgentService>();
|
||||||
|
services.AddSingleton<ILlmProviderService, LlmProviderService>();
|
||||||
|
services.AddSingleton<ISettingService, SettingService>();
|
||||||
|
services.AddSingleton<IConversationStateService, NullConversationStateService>();
|
||||||
|
services.AddSingleton<IFileStorageService, NullFileStorageService>();
|
||||||
|
services.AddLogging(s=>s.AddConsole());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (IServiceCollection services, IConfiguration configuration, string modelName) CreateAnthropic()
|
||||||
|
{
|
||||||
|
string modelName = "claude-3-5-haiku-20241022";
|
||||||
|
|
||||||
|
var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ??
|
||||||
|
throw new Exception("ANTHROPIC_API_KEY is not set");
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
|
||||||
|
|
||||||
|
LlmProviderSetting setting = new LlmProviderSetting();
|
||||||
|
setting.Provider = "anthropic";
|
||||||
|
setting.Models = new List<LlmModelSetting>([
|
||||||
|
new LlmModelSetting()
|
||||||
|
{
|
||||||
|
Name = modelName,
|
||||||
|
ApiKey = apiKey
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
services.AddSingleton(new AnthropicSettings()
|
||||||
|
{
|
||||||
|
Claude = new ClaudeSetting()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddSingleton<List<LlmProviderSetting>>(new List<LlmProviderSetting>([ setting]));
|
||||||
|
|
||||||
|
AddCommonServices(services, configuration);
|
||||||
|
|
||||||
|
new AnthropicPlugin().RegisterDI(services, configuration);
|
||||||
|
return (services, configuration, modelName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using BotSharp.Abstraction.Conversations;
|
||||||
|
using BotSharp.Abstraction.Conversations.Enums;
|
||||||
|
using BotSharp.Abstraction.Conversations.Models;
|
||||||
|
|
||||||
|
namespace BotSharp.Plugin.Google.Core
|
||||||
|
{
|
||||||
|
public class NullConversationStateService:IConversationStateService
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// TODO release managed resources here
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetConversationId()
|
||||||
|
{
|
||||||
|
return "fake-conversation-id";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, string> Load(string conversationId, bool isReadOnly = false)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, string> { { "Key", "Value" } };
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetState(string name, string defaultValue = "")
|
||||||
|
{
|
||||||
|
var states = GetStates();
|
||||||
|
if (!states.ContainsKey(name))
|
||||||
|
return defaultValue;
|
||||||
|
return states[name]??defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsState(string name)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, string> GetStates()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, string> { { "temperature", "0.5" }, { "max_tokens", "8000" }, { "top_p", "1.0" }, { "frequency_penalty", "0.0" } };
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConversationStateService SetState<T>(string name, T value, bool isNeedVersion = true, int activeRounds = -1,
|
||||||
|
string valueType = StateDataType.String, string source = StateSource.User, bool readOnly = false)
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveStateByArgs(JsonDocument args)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveState(string name)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanStates(params string[] excludedStates)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConversationState GetCurrentState()
|
||||||
|
{
|
||||||
|
|
||||||
|
return new ConversationState { { "StateKey", new StateKeyValue { Key = "Key", Values = new List<StateValue>()} } };
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCurrentState(ConversationState state)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void ResetCurrentState()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
147
tests/BotSharp.LLM.Tests/Core/NullFileStorageService.cs
Normal file
147
tests/BotSharp.LLM.Tests/Core/NullFileStorageService.cs
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
using BotSharp.Abstraction.Files;
|
||||||
|
using BotSharp.Abstraction.Files.Models;
|
||||||
|
|
||||||
|
namespace BotSharp.Plugin.Google.Core
|
||||||
|
{
|
||||||
|
public class NullFileStorageService:IFileStorageService
|
||||||
|
{
|
||||||
|
public string GetDirectory(string conversationId)
|
||||||
|
{
|
||||||
|
return $"FakeDirectory/{conversationId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> GetFiles(string relativePath, string? searchQuery = null)
|
||||||
|
{
|
||||||
|
return new List<string> { "FakeFile1.txt", "FakeFile2.txt" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetFileBytes(string fileStorageUrl)
|
||||||
|
{
|
||||||
|
return new byte[] { 0x00, 0x01, 0x02 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SaveFileStreamToPath(string filePath, Stream stream)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SaveFileBytesToPath(string filePath, byte[] bytes)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetParentDir(string dir, int level = 1)
|
||||||
|
{
|
||||||
|
return "FakeParentDirectory";
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ExistDirectory(string? dir)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateDirectory(string dir)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteDirectory(string dir)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BuildDirectory(params string[] segments)
|
||||||
|
{
|
||||||
|
return string.Join("/", segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<MessageFileModel>> GetMessageFileScreenshotsAsync(string conversationId, IEnumerable<string> messageIds)
|
||||||
|
{
|
||||||
|
return Task.FromResult<IEnumerable<MessageFileModel>>(new List<MessageFileModel>
|
||||||
|
{
|
||||||
|
new MessageFileModel { FileName = "Screenshot1.png" },
|
||||||
|
new MessageFileModel { FileName = "Screenshot2.png" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<MessageFileModel> GetMessageFiles(string conversationId, IEnumerable<string> messageIds, string source,
|
||||||
|
IEnumerable<string>? contentTypes = null)
|
||||||
|
{
|
||||||
|
return new List<MessageFileModel>
|
||||||
|
{
|
||||||
|
new MessageFileModel { FileName = "File1.docx" },
|
||||||
|
new MessageFileModel { FileName = "File2.pdf" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetMessageFile(string conversationId, string messageId, string source, string index, string fileName)
|
||||||
|
{
|
||||||
|
return $"FakePath/{fileName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<MessageFileModel> GetMessagesWithFile(string conversationId, IEnumerable<string> messageIds)
|
||||||
|
{
|
||||||
|
return new List<MessageFileModel>
|
||||||
|
{
|
||||||
|
new MessageFileModel { FileName = "MessageFile1.jpg" },
|
||||||
|
new MessageFileModel { FileName = "MessageFile2.png" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SaveMessageFiles(string conversationId, string messageId, string source, List<FileDataModel> files)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DeleteMessageFiles(string conversationId, IEnumerable<string> messageIds, string targetMessageId,
|
||||||
|
string? newMessageId = null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DeleteConversationFiles(IEnumerable<string> conversationIds)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetUserAvatar()
|
||||||
|
{
|
||||||
|
return "FakeUserAvatar.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SaveUserAvatar(FileDataModel file)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SaveSpeechFile(string conversationId, string fileName, BinaryData data)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BinaryData GetSpeechFile(string conversationId, string fileName)
|
||||||
|
{
|
||||||
|
return BinaryData.FromBytes(new byte[] { 0x03, 0x04, 0x05 });
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SaveKnowledgeBaseFile(string collectionName, string vectorStoreProvider, Guid fileId, string fileName,
|
||||||
|
BinaryData fileData)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DeleteKnowledgeFile(string collectionName, string vectorStoreProvider, Guid? fileId = null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetKnowledgeBaseFileUrl(string collectionName, string vectorStoreProvider, Guid fileId, string fileName)
|
||||||
|
{
|
||||||
|
return $"https://fakeurl.com/{fileName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public BinaryData GetKnowledgeBaseFileBinaryData(string collectionName, string vectorStoreProvider, Guid fileId,
|
||||||
|
string fileName)
|
||||||
|
{
|
||||||
|
return BinaryData.FromBytes(new byte[] { 0x06, 0x07, 0x08 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
tests/BotSharp.LLM.Tests/Core/TestAgentService.cs
Normal file
109
tests/BotSharp.LLM.Tests/Core/TestAgentService.cs
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
using BotSharp.Abstraction.Agents;
|
||||||
|
using BotSharp.Abstraction.Agents.Enums;
|
||||||
|
using BotSharp.Abstraction.Agents.Models;
|
||||||
|
using BotSharp.Abstraction.Functions.Models;
|
||||||
|
using BotSharp.Abstraction.Models;
|
||||||
|
using BotSharp.Abstraction.Plugins.Models;
|
||||||
|
using BotSharp.Abstraction.Repositories.Filters;
|
||||||
|
using BotSharp.Abstraction.Utilities;
|
||||||
|
|
||||||
|
namespace BotSharp.Plugin.Google.Core
|
||||||
|
{
|
||||||
|
public class TestAgentService : IAgentService
|
||||||
|
{
|
||||||
|
public Task<Agent> CreateAgent(Agent agent)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new Agent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string> RefreshAgents()
|
||||||
|
{
|
||||||
|
return Task.FromResult("Refreshed successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<PagedItems<Agent>> GetAgents(AgentFilter filter)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new PagedItems<Agent>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<IdName>> GetAgentOptions(List<string>? agentIds = null)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new List<IdName> { new IdName(id: "1", name: "Fake Agent") });
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Agent> LoadAgent(string id, bool loadUtility = true)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new Agent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InheritAgent(Agent agent)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RenderedInstruction(Agent agent)
|
||||||
|
{
|
||||||
|
return "Fake Instruction";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RenderedTemplate(Agent agent, string templateName)
|
||||||
|
{
|
||||||
|
return $"Rendered template for {templateName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RenderFunction(Agent agent, FunctionDef def)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def)
|
||||||
|
{
|
||||||
|
return def.Parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Agent> GetAgent(string id)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new Agent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> DeleteAgent(string id)
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task UpdateAgent(Agent agent, AgentField updateField)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string> PatchAgentTemplate(Agent agent)
|
||||||
|
{
|
||||||
|
return Task.FromResult("Patched successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string> UpdateAgentFromFile(string id)
|
||||||
|
{
|
||||||
|
return Task.FromResult("Updated from file successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetDataDir()
|
||||||
|
{
|
||||||
|
return "Fake Data Directory";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetAgentDataDir(string agentId)
|
||||||
|
{
|
||||||
|
return $"Fake Data Directory for agent {agentId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<UserAgent>> GetUserAgents(string userId)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new List<UserAgent> { new UserAgent() });
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginDef GetPlugin(string agentId)
|
||||||
|
{
|
||||||
|
return new PluginDef();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
65
tests/BotSharp.LLM.Tests/EmbeddingTests.cs
Normal file
65
tests/BotSharp.LLM.Tests/EmbeddingTests.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
|
||||||
|
|
||||||
|
using BotSharp.Abstraction.Agents.Enums;
|
||||||
|
using BotSharp.Abstraction.Agents.Models;
|
||||||
|
using BotSharp.Abstraction.Conversations.Models;
|
||||||
|
using BotSharp.Abstraction.MLTasks;
|
||||||
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace BotSharp.Plugin.Google.Core
|
||||||
|
{
|
||||||
|
public class EmbeddingTests:TestBase
|
||||||
|
{
|
||||||
|
protected static Agent CreateTestAgent()
|
||||||
|
{
|
||||||
|
return new Agent()
|
||||||
|
{
|
||||||
|
Id = "test-agent-id",
|
||||||
|
Name = "TestAgent",
|
||||||
|
Description = "This is a test agent used for unit testing purposes.",
|
||||||
|
Type = "Chat",
|
||||||
|
CreatedDateTime = DateTime.UtcNow,
|
||||||
|
UpdatedDateTime = DateTime.UtcNow,
|
||||||
|
IsPublic = false,
|
||||||
|
Disabled = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static IEnumerable<object[]> CreateTestLLMProviders()
|
||||||
|
{
|
||||||
|
//Common
|
||||||
|
var agent = CreateTestAgent();
|
||||||
|
IServiceCollection services;
|
||||||
|
IConfiguration configuration;
|
||||||
|
string modelName;
|
||||||
|
|
||||||
|
if (LLMProvider.CanRunGemini)
|
||||||
|
{
|
||||||
|
//Google Gemini
|
||||||
|
(services, configuration, modelName) = LLMProvider.CreateGemini();
|
||||||
|
yield return new object[] { services.BuildServiceProvider().GetService<ITextEmbedding>() ?? throw new Exception("Error while initializing"), agent, modelName };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LLMProvider.CanRunOpenAI)
|
||||||
|
{
|
||||||
|
//OpenAI
|
||||||
|
(services, configuration, modelName) = LLMProvider.CreateOpenAI();
|
||||||
|
yield return new object[] { services.BuildServiceProvider().GetService<ITextEmbedding>() ?? throw new Exception("Error while initializing"), agent, modelName };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(CreateTestLLMProviders))]
|
||||||
|
public async Task GetChatCompletions_Test(ITextEmbedding chatCompletion, Agent agent, string modelName)
|
||||||
|
{
|
||||||
|
var text = "This is a placeholder for a really long text used for testing, generated for simulation purposes. The text simulates a verbose input and can be modified to any required content.";
|
||||||
|
|
||||||
|
var result = await chatCompletion.GetVectorAsync(text);
|
||||||
|
result.ShouldNotBeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
124
tests/BotSharp.LLM.Tests/FunctionCallingTests.cs
Normal file
124
tests/BotSharp.LLM.Tests/FunctionCallingTests.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using BotSharp.Abstraction.Agents.Enums;
|
||||||
|
using BotSharp.Abstraction.Agents.Models;
|
||||||
|
using BotSharp.Abstraction.Conversations.Models;
|
||||||
|
using BotSharp.Abstraction.Functions.Models;
|
||||||
|
using BotSharp.Abstraction.MLTasks;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace BotSharp.Plugin.Google.Core
|
||||||
|
{
|
||||||
|
public class FunctionCallingTests : TestBase
|
||||||
|
{
|
||||||
|
public static IEnumerable<object[]> CreateTestLLMProviders()
|
||||||
|
{
|
||||||
|
//Common
|
||||||
|
var agent = CreateTestAgent();
|
||||||
|
IServiceCollection services;
|
||||||
|
IConfiguration configuration;
|
||||||
|
string modelName;
|
||||||
|
|
||||||
|
if (LLMProvider.CanRunGemini)
|
||||||
|
{
|
||||||
|
//Google Gemini
|
||||||
|
(services, configuration, modelName) = LLMProvider.CreateGemini();
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
services.BuildServiceProvider().GetService<IChatCompletion>() ??
|
||||||
|
throw new Exception("Error while initializing"),
|
||||||
|
agent, modelName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LLMProvider.CanRunOpenAI)
|
||||||
|
{
|
||||||
|
//OpenAI
|
||||||
|
(services, configuration, modelName) = LLMProvider.CreateOpenAI();
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
services.BuildServiceProvider().GetService<IChatCompletion>() ??
|
||||||
|
throw new Exception("Error while initializing"),
|
||||||
|
agent, modelName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LLMProvider.CanRunAnthropic)
|
||||||
|
{
|
||||||
|
//Anthropic
|
||||||
|
(services, configuration, modelName) = LLMProvider.CreateAnthropic();
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
services.BuildServiceProvider().GetService<IChatCompletion>() ??
|
||||||
|
throw new Exception("Error while initializing"),
|
||||||
|
agent, modelName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected static Agent CreateTestAgent()
|
||||||
|
{
|
||||||
|
return new Agent()
|
||||||
|
{
|
||||||
|
Id = "test-agent-id",
|
||||||
|
Name = "TestAgent",
|
||||||
|
Description = "This is a test agent used for unit testing purposes.",
|
||||||
|
Type = "Chat",
|
||||||
|
CreatedDateTime = DateTime.UtcNow,
|
||||||
|
UpdatedDateTime = DateTime.UtcNow,
|
||||||
|
IsPublic = false,
|
||||||
|
Disabled = false,
|
||||||
|
Functions = new List<FunctionDef>([JsonSerializer.Deserialize<FunctionDef>("{\n \"name\": \"get_weather_info\",\n \"description\": \"get current weather info for a given city\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"city\": {\n \"type\": \"string\",\n \"description\": \"city name.\"\n }\n },\n \"required\": [ \"city\" ]\n }\n}")])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(CreateTestLLMProviders))]
|
||||||
|
public async Task GetChatCompletions_Test(IChatCompletion chatCompletion, Agent agent, string modelName)
|
||||||
|
{
|
||||||
|
chatCompletion.SetModelName(modelName);
|
||||||
|
var conversation =
|
||||||
|
new List<RoleDialogModel>([new RoleDialogModel(AgentRole.User, "how's the weather in Sydney?")]);
|
||||||
|
|
||||||
|
var result = await chatCompletion.GetChatCompletions(agent, conversation);
|
||||||
|
result.FunctionName.ShouldBe("get_weather_info");
|
||||||
|
result.FunctionArgs.ShouldContain("Sydney",Case.Insensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(CreateTestLLMProviders))]
|
||||||
|
public async Task GetChatCompletionsAsync_Test(IChatCompletion chatCompletion, Agent agent, string modelName)
|
||||||
|
{
|
||||||
|
chatCompletion.SetModelName(modelName);
|
||||||
|
var conversation =
|
||||||
|
new List<RoleDialogModel>([new RoleDialogModel(AgentRole.User, "how's the weather in Sydney?")]);
|
||||||
|
RoleDialogModel reply = null;
|
||||||
|
RoleDialogModel function = null;
|
||||||
|
var result = await chatCompletion.GetChatCompletionsAsync(agent, conversation,
|
||||||
|
async (received) => { reply = received; }, async (func) => { function = func; });
|
||||||
|
result.ShouldBeTrue();
|
||||||
|
function.ShouldNotBeNull();
|
||||||
|
function.FunctionName.ShouldNotBeNullOrEmpty();
|
||||||
|
function.FunctionArgs.ShouldContain("Sydney",Case.Insensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(CreateTestLLMProviders))]
|
||||||
|
public async Task GetChatCompletionsStreamingAsync_Test(IChatCompletion chatCompletion, Agent agent,
|
||||||
|
string modelName)
|
||||||
|
{
|
||||||
|
//Not sure about support of function calling with Streaming
|
||||||
|
|
||||||
|
|
||||||
|
// chatCompletion.SetModelName(modelName);
|
||||||
|
// var conversation =
|
||||||
|
// new List<RoleDialogModel>([new RoleDialogModel(AgentRole.User, "how's the weather in Sydney?")]);
|
||||||
|
// RoleDialogModel reply = null;
|
||||||
|
// var result = await chatCompletion.GetChatCompletionsStreamingAsync(agent, conversation,
|
||||||
|
// async (received) => { reply = received; });
|
||||||
|
// result.ShouldBeTrue();
|
||||||
|
// reply.ShouldNotBeNull();
|
||||||
|
// reply.Content.ShouldNotBeNullOrEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
tests/BotSharp.LLM.Tests/GoogleRealTimeTests.cs
Normal file
57
tests/BotSharp.LLM.Tests/GoogleRealTimeTests.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
using BotSharp.Abstraction.Agents.Enums;
|
||||||
|
using BotSharp.Abstraction.Agents.Models;
|
||||||
|
using BotSharp.Abstraction.Conversations.Models;
|
||||||
|
using BotSharp.Abstraction.MLTasks;
|
||||||
|
using BotSharp.Abstraction.Realtime.Models;
|
||||||
|
using GenerativeAI;
|
||||||
|
using Microsoft.EntityFrameworkCore.Internal;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace BotSharp.Plugin.Google.Core
|
||||||
|
{
|
||||||
|
public class GoogleRealTimeTests : TestBase
|
||||||
|
{
|
||||||
|
protected static Agent CreateTestAgent()
|
||||||
|
{
|
||||||
|
return new Agent()
|
||||||
|
{
|
||||||
|
Id = "test-agent-id",
|
||||||
|
Name = "TestAgent",
|
||||||
|
Description = "This is a test agent used for unit testing purposes.",
|
||||||
|
Type = "Chat",
|
||||||
|
CreatedDateTime = DateTime.UtcNow,
|
||||||
|
UpdatedDateTime = DateTime.UtcNow,
|
||||||
|
IsPublic = false,
|
||||||
|
Disabled = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ShouldConnect_Tests()
|
||||||
|
{
|
||||||
|
if (!LLMProvider.CanRunGemini)
|
||||||
|
return;
|
||||||
|
|
||||||
|
(IServiceCollection services, IConfiguration config, string modelName) = LLMProvider.CreateGemini();
|
||||||
|
|
||||||
|
var agent = CreateTestAgent();
|
||||||
|
var realTimeCompleter = services.BuildServiceProvider().GetService<IRealTimeCompletion>();
|
||||||
|
realTimeCompleter.SetModelName(GoogleAIModels.Gemini2FlashExp);
|
||||||
|
bool modelReady = false;
|
||||||
|
await realTimeCompleter.Connect(new RealtimeHubConnection(), () => { modelReady = true; },
|
||||||
|
(s, s1) => { Console.WriteLine(s); }, () => { }, (s) => { Console.WriteLine(s); },
|
||||||
|
(list => { Console.WriteLine(list); }),
|
||||||
|
(s => { Console.WriteLine(s); }),
|
||||||
|
(model => { Console.WriteLine(model); }), (() => { Console.WriteLine("UserInterrupted"); }));
|
||||||
|
var session = await realTimeCompleter.CreateSession(agent, new List<RoleDialogModel>());
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
modelReady.ShouldBeTrue();
|
||||||
|
|
||||||
|
await realTimeCompleter.InsertConversationItem(new RoleDialogModel(AgentRole.User,
|
||||||
|
"tell me something about Albert Einstein."));
|
||||||
|
Thread.Sleep(10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
tests/BotSharp.LLM.Tests/TestBase.cs
Normal file
13
tests/BotSharp.LLM.Tests/TestBase.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
using BotSharp.Abstraction.Agents.Models;
|
||||||
|
using BotSharp.Abstraction.MLTasks;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace BotSharp.Plugin.Google.Core
|
||||||
|
{
|
||||||
|
public abstract class TestBase
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
8
tests/BotSharp.LLM.Tests/appsettings.Development.json
Normal file
8
tests/BotSharp.LLM.Tests/appsettings.Development.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
414
tests/BotSharp.LLM.Tests/appsettings.json
Normal file
414
tests/BotSharp.LLM.Tests/appsettings.json
Normal file
|
|
@ -0,0 +1,414 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"AllowedOrigins": [
|
||||||
|
"http://localhost:5015",
|
||||||
|
"http://0.0.0.0:5015",
|
||||||
|
"https://botsharp.scisharpstack.org",
|
||||||
|
"https://chat.scisharpstack.org"
|
||||||
|
],
|
||||||
|
|
||||||
|
"Jwt": {
|
||||||
|
"Issuer": "botsharp",
|
||||||
|
"Audience": "botsharp",
|
||||||
|
"Key": "31ba6052aa6f4569901facc3a41fcb4adfd9b46dd00c40af8a753fbdc2b89869"
|
||||||
|
},
|
||||||
|
|
||||||
|
"OAuth": {
|
||||||
|
"GitHub": {
|
||||||
|
"ClientId": "",
|
||||||
|
"ClientSecret": ""
|
||||||
|
},
|
||||||
|
"Google": {
|
||||||
|
"ClientId": "",
|
||||||
|
"ClientSecret": ""
|
||||||
|
},
|
||||||
|
"Keycloak": {
|
||||||
|
"BaseAddress": "",
|
||||||
|
"Realm": "",
|
||||||
|
"ClientId": "",
|
||||||
|
"ClientSecret": "",
|
||||||
|
"Version": 22
|
||||||
|
},
|
||||||
|
"Weixin": {
|
||||||
|
"AppId": "",
|
||||||
|
"AppSecret": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"LlmProviders": [
|
||||||
|
{
|
||||||
|
"Provider": "azure-openai",
|
||||||
|
"Models": [
|
||||||
|
{
|
||||||
|
"Id": "gpt-3.5-turbo",
|
||||||
|
"Name": "gpt-35-turbo",
|
||||||
|
"Version": "1106",
|
||||||
|
"ApiKey": "",
|
||||||
|
"Endpoint": "https://gpt-35-turbo-instruct.openai.azure.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "gpt-35-turbo-instruct",
|
||||||
|
"Version": "0914",
|
||||||
|
"ApiKey": "",
|
||||||
|
"Endpoint": "https://gpt-35-turbo-instruct.openai.azure.com/",
|
||||||
|
"Type": "text",
|
||||||
|
"PromptCost": 0.0015,
|
||||||
|
"CompletionCost": 0.002
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Provider": "llama-sharp",
|
||||||
|
"Models": [
|
||||||
|
{
|
||||||
|
"Name": "llama-2-7b-guanaco-qlora.Q2_K.gguf",
|
||||||
|
"Type": "chat"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Provider": "huggingface",
|
||||||
|
"Models": [
|
||||||
|
{
|
||||||
|
"Name": "mistralai/Mistral-7B-v0.1",
|
||||||
|
"Type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "TinyLlama/TinyLlama-1.1B-Chat-v1.0",
|
||||||
|
"Type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Provider": "sparkdesk",
|
||||||
|
"Models": [
|
||||||
|
{
|
||||||
|
"Name": "gpt-35-turbo",
|
||||||
|
"Type": "chat",
|
||||||
|
"PromptCost": 0.0015,
|
||||||
|
"CompletionCost": 0.002
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Provider": "metaglm",
|
||||||
|
"Models": [
|
||||||
|
{
|
||||||
|
"Name": "chatglm3_6b",
|
||||||
|
"Type": "chat",
|
||||||
|
"PromptCost": 0.0015,
|
||||||
|
"CompletionCost": 0.002
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Provider": "openai",
|
||||||
|
"Models": [
|
||||||
|
{
|
||||||
|
"Id": "gpt-4",
|
||||||
|
"Name": "gpt-4o-mini",
|
||||||
|
"Version": "2024-07-18",
|
||||||
|
"ApiKey": "",
|
||||||
|
"Type": "chat",
|
||||||
|
"MultiModal": true,
|
||||||
|
"PromptCost": 0.00015,
|
||||||
|
"CompletionCost": 0.0006
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "gpt-4",
|
||||||
|
"Name": "gpt-4o-2024-11-20",
|
||||||
|
"Version": "2024-11-20",
|
||||||
|
"ApiKey": "",
|
||||||
|
"Type": "chat",
|
||||||
|
"MultiModal": true,
|
||||||
|
"PromptCost": 0.0025,
|
||||||
|
"CompletionCost": 0.01
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "gpt-4",
|
||||||
|
"Name": "gpt-4o-mini-realtime-preview-2024-12-17",
|
||||||
|
"Version": "2024-12-17",
|
||||||
|
"ApiKey": "",
|
||||||
|
"Type": "chat",
|
||||||
|
"MultiModal": true,
|
||||||
|
"RealTime": true,
|
||||||
|
"PromptCost": 0.0025,
|
||||||
|
"CompletionCost": 0.01
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "text-embedding-3",
|
||||||
|
"Name": "text-embedding-3-small",
|
||||||
|
"Version": "3-small",
|
||||||
|
"ApiKey": "",
|
||||||
|
"Type": "embedding",
|
||||||
|
"Dimension": 1536,
|
||||||
|
"PromptCost": 0.00002
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"Router": {
|
||||||
|
},
|
||||||
|
|
||||||
|
"Evaluator": {
|
||||||
|
"AgentId": "dfd9b46d-d00c-40af-8a75-3fbdc2b89869"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Agent": {
|
||||||
|
"DataDir": "agents",
|
||||||
|
"TemplateFormat": "liquid",
|
||||||
|
"HostAgentId": "01e2fc5c-2c89-4ec7-8470-7688608b496c",
|
||||||
|
"EnableTranslator": false,
|
||||||
|
"LlmConfig": {
|
||||||
|
"Provider": "azure-openai",
|
||||||
|
"Model": "gpt-4o-mini"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//"MCPSettings": {
|
||||||
|
// "McpClientOptions": {
|
||||||
|
// "ClientInfo": {
|
||||||
|
// "Name": "SimpleToolsBotsharp",
|
||||||
|
// "Version": "1.0.0"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "McpServerConfigs": [
|
||||||
|
// {
|
||||||
|
// "Id": "PizzaServer",
|
||||||
|
// "Name": "PizzaServer",
|
||||||
|
// "TransportType": "sse",
|
||||||
|
// "TransportOptions": [],
|
||||||
|
// "Location": "http://localhost:58905/sse"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
//},
|
||||||
|
"Conversation": {
|
||||||
|
"DataDir": "conversations",
|
||||||
|
"ShowVerboseLog": false,
|
||||||
|
"EnableLlmCompletionLog": false,
|
||||||
|
"EnableExecutionLog": true,
|
||||||
|
"EnableContentLog": true,
|
||||||
|
"EnableStateLog": true,
|
||||||
|
"EnableTranslationMemory": false,
|
||||||
|
"CleanSetting": {
|
||||||
|
"Enable": true,
|
||||||
|
"BatchSize": 50,
|
||||||
|
"MessageLimit": 2,
|
||||||
|
"BufferHours": 12,
|
||||||
|
"ExcludeAgentIds": []
|
||||||
|
},
|
||||||
|
"RateLimit": {
|
||||||
|
"MaxConversationPerDay": 100,
|
||||||
|
"MaxInputLengthPerRequest": 256,
|
||||||
|
"MinTimeSecondsBetweenMessages": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"SideCar": {
|
||||||
|
"Conversation": {
|
||||||
|
"Provider": "botsharp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"WebBrowsing": {
|
||||||
|
"Driver": "Playwright"
|
||||||
|
},
|
||||||
|
|
||||||
|
"HttpHandler": {
|
||||||
|
"BaseAddress": "",
|
||||||
|
"Origin": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
"SqlDriver": {
|
||||||
|
"MySqlConnectionString": "",
|
||||||
|
"SqlServerConnectionString": "",
|
||||||
|
"SqlLiteConnectionString": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
"Statistics": {
|
||||||
|
"Enabled": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"Instruction": {
|
||||||
|
"Logging": {
|
||||||
|
"Enabled": true,
|
||||||
|
"ExcludedAgentIds": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"ChatHub": {
|
||||||
|
"EventDispatchBy": "group"
|
||||||
|
},
|
||||||
|
|
||||||
|
"SharpCache": {
|
||||||
|
"Enabled": true,
|
||||||
|
"CacheType": 1,
|
||||||
|
"Prefix": "botsharp"
|
||||||
|
},
|
||||||
|
|
||||||
|
"LlamaSharp": {
|
||||||
|
"Interactive": true,
|
||||||
|
"ModelDir": "C:/Users/haipi/Downloads",
|
||||||
|
"DefaultModel": "llama-2-7b-chat.Q8_0.gguf",
|
||||||
|
"MaxContextLength": 1024,
|
||||||
|
"NumberOfGpuLayer": 20
|
||||||
|
},
|
||||||
|
|
||||||
|
"AzureOpenAi": {
|
||||||
|
},
|
||||||
|
|
||||||
|
"AnthropicAi": {
|
||||||
|
"Claude": {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"GoogleAi": {
|
||||||
|
"PaLM": {
|
||||||
|
"Endpoint": "https://generativelanguage.googleapis.com",
|
||||||
|
"ApiKey": ""
|
||||||
|
},
|
||||||
|
"Gemini": {
|
||||||
|
"ApiKey": "",
|
||||||
|
"UseGoogleSearch": false,
|
||||||
|
"UseGrounding": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"HuggingFace": {
|
||||||
|
"Endpoint": "https://api-inference.huggingface.co",
|
||||||
|
"Model": "tiiuae/falcon-180B-chat",
|
||||||
|
"Token": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
"MetaAi": {
|
||||||
|
"fastText": {
|
||||||
|
"ModelPath": "dbpedia.ftz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"RoutingSpeeder": {
|
||||||
|
},
|
||||||
|
|
||||||
|
"MetaMessenger": {
|
||||||
|
"Endpoint": "https://graph.facebook.com",
|
||||||
|
"ApiVersion": "v17.0",
|
||||||
|
"PageId": "",
|
||||||
|
"PageAccessToken": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
"Twilio": {
|
||||||
|
"PhoneNumber": "+1",
|
||||||
|
"AccountSID": "",
|
||||||
|
"AuthToken": "",
|
||||||
|
"CallbackHost": "https://",
|
||||||
|
"AgentId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Database": {
|
||||||
|
"Default": "FileRepository",
|
||||||
|
"TablePrefix": "BotSharp",
|
||||||
|
"BotSharpMongoDb": "",
|
||||||
|
"Redis": "botsharp.redis.cache.windows.net:6380,password=,ssl=True,abortConnect=False",
|
||||||
|
"FileRepository": "data",
|
||||||
|
"Assemblies": [ "BotSharp.Core" ]
|
||||||
|
},
|
||||||
|
|
||||||
|
"FileCore": {
|
||||||
|
"Storage": "LocalFileStorage",
|
||||||
|
"Pdf2TextConverter": {
|
||||||
|
"Provider": ""
|
||||||
|
},
|
||||||
|
"Pdf2ImageConverter": {
|
||||||
|
"Provider": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"TencentCos": {
|
||||||
|
"AppId": "",
|
||||||
|
"SecretId": "",
|
||||||
|
"SecretKey": "",
|
||||||
|
"BucketName": "",
|
||||||
|
"Region": ""
|
||||||
|
},
|
||||||
|
"Qdrant": {
|
||||||
|
"Url": "",
|
||||||
|
"ApiKey": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
"Graph": {
|
||||||
|
"BaseUrl": "",
|
||||||
|
"SearchPath": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
"WeChat": {
|
||||||
|
"AgentId": "437bed34-1169-4833-95ce-c24b8b56154a",
|
||||||
|
"Token": "#{Token}#",
|
||||||
|
"EncodingAESKey": "#{EncodingAESKey}#",
|
||||||
|
"WeixinAppId": "#{WeixinAppId}#",
|
||||||
|
"WeixinAppSecret": "#{WeixinAppSecret}#"
|
||||||
|
},
|
||||||
|
|
||||||
|
"KnowledgeBase": {
|
||||||
|
"VectorDb": {
|
||||||
|
"Provider": "Qdrant"
|
||||||
|
},
|
||||||
|
"GraphDb": {
|
||||||
|
"Provider": "Remote"
|
||||||
|
},
|
||||||
|
"Default": {
|
||||||
|
"CollectionName": "BotSharp",
|
||||||
|
"TextEmbedding": {
|
||||||
|
"Provider": "openai",
|
||||||
|
"Model": "text-embedding-3-small",
|
||||||
|
"Dimension": 1536
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"SparkDesk": {
|
||||||
|
"AppId": "",
|
||||||
|
"ApiKey": "",
|
||||||
|
"ApiSecret": "",
|
||||||
|
"ModelVersion": "V3_5"
|
||||||
|
},
|
||||||
|
"MetaGLM": {
|
||||||
|
"ApiKey": "6b6c8b3fca3e5da21d633e350980744d.938gruOqrK4BDqW8",
|
||||||
|
"BaseAddress": "http://localhost:8100/v1/",
|
||||||
|
"ModelId": "chatglm3_6b",
|
||||||
|
"Temperature": 0.7,
|
||||||
|
"TopP": 0.7
|
||||||
|
},
|
||||||
|
|
||||||
|
"GoogleApi": {
|
||||||
|
"ApiKey": "",
|
||||||
|
"Map": {
|
||||||
|
"Endpoint": "https://maps.googleapis.com/maps/api/geocode/json",
|
||||||
|
"Components": "country=US|country=CA"
|
||||||
|
},
|
||||||
|
"Youtube": {
|
||||||
|
"Endpoint": "https://www.googleapis.com/youtube/v3/search",
|
||||||
|
"RegionCode": "US",
|
||||||
|
"Part": "id,snippet",
|
||||||
|
"Channels": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"Interpreter": {
|
||||||
|
"Python": {
|
||||||
|
"PythonDLL": "C:/Users/xxx/AppData/Local/Programs/Python/Python311/python311.dll"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"PluginLoader": {
|
||||||
|
"Assemblies": [
|
||||||
|
"BotSharp.Plugin.GoogleAI"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -100,5 +100,4 @@
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue