feat: Added Google Live APIs
added Unit Test cases for LLM
This commit is contained in:
parent
8ec7ecdf55
commit
8039f7e702
11
BotSharp.sln
11
BotSharp.sln
|
|
@ -141,6 +141,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Abstraction.Comput
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Test.BrowserUse", "tests\BotSharp.Test.BrowserUse\BotSharp.Test.BrowserUse.csproj", "{7D0DB012-9798-4BB9-B15B-A5B0B7B3B094}"
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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|x64.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -657,6 +667,7 @@ Global
|
|||
{B268E2F0-060F-8466-7D81-ABA4D735CA59} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
|
||||
{970BE341-9AC8-99A5-6572-E703C1E02FCB} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
|
||||
{7D0DB012-9798-4BB9-B15B-A5B0B7B3B094} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
|
||||
{7C0C7D13-D161-4AB0-9C29-83A0F1FF990E} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="EntityFramework" Version="6.4.4" />
|
||||
<PackageVersion Include="Google_GenerativeAI" Version="2.4.6" />
|
||||
<PackageVersion Include="Google_GenerativeAI" Version="2.5.3" />
|
||||
<PackageVersion Include="Google_GenerativeAI.Live" Version="2.5.3" />
|
||||
<PackageVersion Include="LLMSharp.Google.Palm" Version="1.0.2" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(AspNetCoreVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.StaticFiles" Version="$(AspNetCoreVersion)" />
|
||||
|
|
@ -112,6 +113,7 @@
|
|||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageVersion Include="ModelContextProtocol" Version="0.1.0-preview.2" />
|
||||
<PackageVersion Include="Shouldly" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="BotSharp.Core" Version="$(BotSharpVersion)" />
|
||||
|
|
|
|||
|
|
@ -238,6 +238,9 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
|
||||
private string GetPrompt(MessageParameters parameters)
|
||||
{
|
||||
//parameters.System can be null?
|
||||
if(parameters.System == null)
|
||||
parameters.System = new List<SystemMessage>();
|
||||
var prompt = $"{string.Join("\r\n", parameters.System.Select(x => x.Text))}\r\n";
|
||||
prompt += "\r\n[CONVERSATION]";
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,12 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google_GenerativeAI" />
|
||||
<PackageReference Include="Google_GenerativeAI.Live" />
|
||||
<PackageReference Include="LLMSharp.Google.Palm" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using BotSharp.Abstraction.Plugins;
|
|||
using BotSharp.Abstraction.Settings;
|
||||
using BotSharp.Plugin.GoogleAi.Providers.Chat;
|
||||
using BotSharp.Plugin.GoogleAI.Providers.Embedding;
|
||||
using BotSharp.Plugin.GoogleAi.Providers.Realtime;
|
||||
using BotSharp.Plugin.GoogleAi.Providers.Text;
|
||||
|
||||
namespace BotSharp.Plugin.GoogleAi;
|
||||
|
|
@ -24,6 +25,7 @@ public class GoogleAiPlugin : IBotSharpPlugin
|
|||
services.AddScoped<ITextCompletion, GeminiTextCompletionProvider>();
|
||||
services.AddScoped<IChatCompletion, PalmChatCompletionProvider>();
|
||||
services.AddScoped<IChatCompletion, GeminiChatCompletionProvider>();
|
||||
services.AddScoped<IRealTimeCompletion, GoogleRealTimeProvider>();
|
||||
services.AddScoped<ITextEmbedding, TextEmbeddingProvider>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,13 @@ public class GeminiChatCompletionProvider : IChatCompletion
|
|||
public string Provider => "google-ai";
|
||||
public string Model => _model;
|
||||
|
||||
private GoogleAiSettings _googleSettings;
|
||||
public GeminiChatCompletionProvider(
|
||||
IServiceProvider services,
|
||||
GoogleAiSettings googleSettings,
|
||||
ILogger<GeminiChatCompletionProvider> logger)
|
||||
{
|
||||
_googleSettings = googleSettings;
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
|
@ -39,7 +42,7 @@ public class GeminiChatCompletionProvider : IChatCompletion
|
|||
await hook.BeforeGenerating(agent, conversations);
|
||||
}
|
||||
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _googleSettings, _logger);
|
||||
var aiModel = client.CreateGenerativeModel(_model);
|
||||
var (prompt, request) = PrepareOptions(aiModel, agent, conversations);
|
||||
|
||||
|
|
@ -98,7 +101,7 @@ public class GeminiChatCompletionProvider : IChatCompletion
|
|||
await hook.BeforeGenerating(agent, conversations);
|
||||
}
|
||||
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _googleSettings, _logger);
|
||||
var chatClient = client.CreateGenerativeModel(_model);
|
||||
var (prompt, messages) = PrepareOptions(chatClient, agent, conversations);
|
||||
|
||||
|
|
@ -163,7 +166,7 @@ public class GeminiChatCompletionProvider : IChatCompletion
|
|||
|
||||
public async Task<bool> GetChatCompletionsStreamingAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived)
|
||||
{
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _googleSettings, _logger);
|
||||
var chatClient = client.CreateGenerativeModel(_model);
|
||||
var (prompt, messages) = PrepareOptions(chatClient,agent, conversations);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public class TextEmbeddingProvider : ITextEmbedding
|
|||
|
||||
public async Task<float[]> GetVectorAsync(string text)
|
||||
{
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _settings, _logger);
|
||||
var embeddingClient = client.CreateEmbeddingModel(_model);
|
||||
|
||||
var response = await embeddingClient.EmbedContentAsync(text);
|
||||
|
|
@ -40,7 +40,7 @@ public class TextEmbeddingProvider : ITextEmbedding
|
|||
|
||||
public async Task<List<float[]>> GetVectorsAsync(List<string> texts)
|
||||
{
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _settings, _logger);
|
||||
var embeddingClient = client.CreateEmbeddingModel(_model);
|
||||
|
||||
var response = await embeddingClient.BatchEmbedContentAsync(texts.Select(s=>new Content(s, Roles.User)));
|
||||
|
|
|
|||
|
|
@ -1,15 +1,24 @@
|
|||
using LLMSharp.Google.Palm;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BotSharp.Plugin.GoogleAi.Providers;
|
||||
|
||||
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, GoogleAiSettings? aiSettings, ILogger? _logger)
|
||||
{
|
||||
var settingsService = services.GetRequiredService<ILlmProviderService>();
|
||||
var settings = settingsService.GetSetting(provider, model);
|
||||
var client = new GenerativeAI.GoogleAi(settings.ApiKey);
|
||||
return client;
|
||||
if (aiSettings == null || aiSettings.Gemini ==null || string.IsNullOrEmpty(aiSettings.Gemini.ApiKey))
|
||||
{
|
||||
var settingsService = services.GetRequiredService<ILlmProviderService>();
|
||||
var settings = settingsService.GetSetting(provider, model);
|
||||
var client = new GenerativeAI.GoogleAi(settings.ApiKey, logger:_logger);
|
||||
return client;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new GenerativeAI.GoogleAi(aiSettings.Gemini.ApiKey, logger:_logger);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static GooglePalmClient GetPalmClient(string provider, string model, IServiceProvider services)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
using System.Threading;
|
||||
using GenerativeAI.Core;
|
||||
using GenerativeAI.Types;
|
||||
|
||||
namespace BotSharp.Plugin.GoogleAi.Providers.Realtime
|
||||
{
|
||||
public class FakeFunctionTool:IFunctionTool
|
||||
{
|
||||
public Tool Tool { get; set; }
|
||||
|
||||
public FakeFunctionTool(Tool tool)
|
||||
{
|
||||
this.Tool = tool;
|
||||
}
|
||||
public Tool AsTool()
|
||||
{
|
||||
return Tool;
|
||||
}
|
||||
|
||||
public async Task<FunctionResponse?> CallAsync(FunctionCall functionCall, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsContainFunction(string name)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,508 @@
|
|||
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 _googleSettings;
|
||||
public GoogleRealTimeProvider(
|
||||
IServiceProvider services,
|
||||
GoogleAiSettings googleSettings,
|
||||
ILogger<GeminiChatCompletionProvider> logger)
|
||||
{
|
||||
_googleSettings = 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)
|
||||
{
|
||||
var audioAppend = new
|
||||
{
|
||||
type = "input_audio_buffer.append",
|
||||
audio = message
|
||||
};
|
||||
|
||||
await SendEventToModel(audioAppend);
|
||||
}
|
||||
|
||||
public async Task TriggerModelInference(string? instructions = null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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 contentHooks = _services.GetServices<IContentGeneratingHook>().ToList();
|
||||
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _googleSettings, _logger);
|
||||
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 = _googleSettings.Gemini.UseGoogleSearch;
|
||||
|
||||
if (request.Tools != null && request.Tools.Count > 0)
|
||||
{
|
||||
var lst = (request.Tools.Select(s => (IFunctionTool)new FakeFunctionTool(s)).ToList());
|
||||
|
||||
_client.AddFunctionTools(lst, new ToolConfig()
|
||||
{
|
||||
FunctionCallingConfig = new FunctionCallingConfig()
|
||||
{
|
||||
Mode = FunctionCallingMode.AUTO
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await AttachEvents();
|
||||
|
||||
await _client.ConnectAsync();
|
||||
|
||||
_client.FunctionTools?.Clear();
|
||||
|
||||
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, _googleSettings, _logger);
|
||||
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
|
||||
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
|
||||
};
|
||||
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); });
|
||||
|
||||
//ToDo: Not sure what's the purpose of UpdateSession, Google Realtime conversion works right after sending the message away!
|
||||
|
||||
// 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 (message.Role == AgentRole.Function)
|
||||
{
|
||||
var function = new FunctionResponse()
|
||||
{
|
||||
Name = message.FunctionName,
|
||||
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.SendClientContentAsync(new BidiGenerateContentClientContent()
|
||||
{
|
||||
TurnComplete = true,
|
||||
Turns = new []{new Content(message.Content, AgentRole.User)}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<RoleDialogModel>> OnResponsedDone(RealtimeHubConnection conn, string response)
|
||||
{
|
||||
throw new NotImplementedException("");
|
||||
}
|
||||
|
||||
|
||||
public async Task<RoleDialogModel> OnConversationItemCreated(RealtimeHubConnection conn, string response)
|
||||
{
|
||||
return new RoleDialogModel(AgentRole.User, response);
|
||||
}
|
||||
|
||||
private (string, GenerateContentRequest) PrepareOptions(GenerativeModel aiModel, Agent agent,
|
||||
List<RoleDialogModel> conversations)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var googleSettings = _googleSettings;
|
||||
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,
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,11 +17,14 @@ public class GeminiTextCompletionProvider : ITextCompletion
|
|||
public string Provider => "google-ai";
|
||||
public string Model => _model;
|
||||
|
||||
private GoogleAiSettings _googleSettings;
|
||||
public GeminiTextCompletionProvider(
|
||||
IServiceProvider services,
|
||||
GoogleAiSettings googleSettings,
|
||||
ILogger<GeminiTextCompletionProvider> logger,
|
||||
ITokenStatistics tokenStatistics)
|
||||
{
|
||||
_googleSettings = googleSettings;
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_tokenStatistics = tokenStatistics;
|
||||
|
|
@ -47,7 +50,7 @@ public class GeminiTextCompletionProvider : ITextCompletion
|
|||
await hook.BeforeGenerating(agent, new List<RoleDialogModel> { userMessage });
|
||||
}
|
||||
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
|
||||
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _googleSettings, _logger);
|
||||
var aiModel = client.CreateGenerativeModel(_model);
|
||||
PrepareOptions(aiModel);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ string[] allowedOrigins = builder.Configuration.GetSection("AllowedOrigins").Get
|
|||
"https://botsharp.scisharpstack.org",
|
||||
"https://chat.scisharpstack.org"
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Add BotSharp
|
||||
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,20 @@
|
|||
"CompletionCost": 0.002
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Provider": "google-ai",
|
||||
"Models": [
|
||||
{
|
||||
"Name": "gemini-2.0-flash-exp",
|
||||
"ApiKey": "",
|
||||
"Type": "chat",
|
||||
"MultiModal": true,
|
||||
"RealTime": true,
|
||||
"PromptCost": 0.0015,
|
||||
"CompletionCost": 0.002
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
|
|
@ -186,7 +200,7 @@
|
|||
},
|
||||
|
||||
"MCP": {
|
||||
"Enabled": true,
|
||||
"Enabled": false,
|
||||
"McpClientOptions": {
|
||||
"ClientInfo": {
|
||||
"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/ChatCompletion_Tests.cs
Normal file
110
tests/BotSharp.LLM.Tests/ChatCompletion_Tests.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 ChatCompletion_Tests: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 ChatCompletion_Tests()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[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();
|
||||
}
|
||||
}
|
||||
}
|
||||
64
tests/BotSharp.LLM.Tests/Embedding_Tests.cs
Normal file
64
tests/BotSharp.LLM.Tests/Embedding_Tests.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
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 Embedding_Tests: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/FunctionCalling_Tests.cs
Normal file
124
tests/BotSharp.LLM.Tests/FunctionCalling_Tests.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 FunctionCalling_Tests : 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/GoogleRealTime_Tests.cs
Normal file
57
tests/BotSharp.LLM.Tests/GoogleRealTime_Tests.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 GoogleRealTime_Tests : 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -101,4 +101,8 @@
|
|||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
Loading…
Reference in a new issue