Update nullability, clean up formatting, and improve naming

This commit is contained in:
Gunpal Jain 2025-04-04 06:48:14 +05:30
parent 98d706415c
commit f46737e118
14 changed files with 75 additions and 72 deletions

View file

@ -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)]

View file

@ -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 = [];
@ -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??new List<SystemMessage>()).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,9 +285,11 @@ 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";
} }

View file

@ -21,13 +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 _googleSettings; private GoogleAiSettings _settings;
public GeminiChatCompletionProvider( public GeminiChatCompletionProvider(
IServiceProvider services, IServiceProvider services,
GoogleAiSettings googleSettings, GoogleAiSettings googleSettings,
ILogger<GeminiChatCompletionProvider> logger) ILogger<GeminiChatCompletionProvider> logger)
{ {
_googleSettings = googleSettings; _settings = googleSettings;
_services = services; _services = services;
_logger = logger; _logger = logger;
} }
@ -42,7 +42,7 @@ public class GeminiChatCompletionProvider : IChatCompletion
await hook.BeforeGenerating(agent, conversations); await hook.BeforeGenerating(agent, conversations);
} }
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _googleSettings, _logger); var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
var aiModel = client.CreateGenerativeModel(_model); var aiModel = client.CreateGenerativeModel(_model);
var (prompt, request) = PrepareOptions(aiModel, agent, conversations); var (prompt, request) = PrepareOptions(aiModel, agent, conversations);
@ -101,7 +101,7 @@ public class GeminiChatCompletionProvider : IChatCompletion
await hook.BeforeGenerating(agent, conversations); await hook.BeforeGenerating(agent, conversations);
} }
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _googleSettings, _logger); var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
var chatClient = client.CreateGenerativeModel(_model); var chatClient = client.CreateGenerativeModel(_model);
var (prompt, messages) = PrepareOptions(chatClient, agent, conversations); var (prompt, messages) = PrepareOptions(chatClient, agent, conversations);
@ -166,7 +166,7 @@ public class GeminiChatCompletionProvider : IChatCompletion
public async Task<bool> GetChatCompletionsStreamingAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived) public async Task<bool> GetChatCompletionsStreamingAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived)
{ {
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _googleSettings, _logger); var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
var chatClient = client.CreateGenerativeModel(_model); var chatClient = client.CreateGenerativeModel(_model);
var (prompt, messages) = PrepareOptions(chatClient,agent, conversations); var (prompt, messages) = PrepareOptions(chatClient,agent, conversations);

View file

@ -30,7 +30,7 @@ public class TextEmbeddingProvider : ITextEmbedding
public async Task<float[]> GetVectorAsync(string text) public async Task<float[]> GetVectorAsync(string text)
{ {
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _settings, _logger); var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
var embeddingClient = client.CreateEmbeddingModel(_model); var embeddingClient = client.CreateEmbeddingModel(_model);
var response = await embeddingClient.EmbedContentAsync(text); var response = await embeddingClient.EmbedContentAsync(text);
@ -40,7 +40,7 @@ public class TextEmbeddingProvider : ITextEmbedding
public async Task<List<float[]>> GetVectorsAsync(List<string> texts) public async Task<List<float[]>> GetVectorsAsync(List<string> texts)
{ {
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _settings, _logger); var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
var embeddingClient = client.CreateEmbeddingModel(_model); var embeddingClient = client.CreateEmbeddingModel(_model);
var response = await embeddingClient.BatchEmbedContentAsync(texts.Select(s=>new Content(s, Roles.User))); var response = await embeddingClient.BatchEmbedContentAsync(texts.Select(s=>new Content(s, Roles.User)));

View file

@ -5,20 +5,20 @@ namespace BotSharp.Plugin.GoogleAi.Providers;
public static class ProviderHelper public static class ProviderHelper
{ {
public static GenerativeAI.GoogleAi GetGeminiClient(string provider, string model, IServiceProvider services, GoogleAiSettings? aiSettings, ILogger? _logger) public static GenerativeAI.GoogleAi GetGeminiClient(string provider, string model, IServiceProvider services)
{ {
var aiSettings = services.GetRequiredService<GoogleAiSettings>();
if (aiSettings == null || aiSettings.Gemini ==null || string.IsNullOrEmpty(aiSettings.Gemini.ApiKey)) if (aiSettings == null || aiSettings.Gemini ==null || string.IsNullOrEmpty(aiSettings.Gemini.ApiKey))
{ {
var settingsService = services.GetRequiredService<ILlmProviderService>(); var settingsService = services.GetRequiredService<ILlmProviderService>();
var settings = settingsService.GetSetting(provider, model); var settings = settingsService.GetSetting(provider, model);
var client = new GenerativeAI.GoogleAi(settings.ApiKey, logger:_logger); var client = new GenerativeAI.GoogleAi(settings.ApiKey);
return client; return client;
} }
else else
{ {
return new GenerativeAI.GoogleAi(aiSettings.Gemini.ApiKey, logger:_logger); 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)

View file

@ -33,13 +33,14 @@ namespace BotSharp.Plugin.GoogleAi.Providers.Realtime
private readonly ILogger<GeminiChatCompletionProvider> _logger; private readonly ILogger<GeminiChatCompletionProvider> _logger;
private List<string> renderedInstructions = []; private List<string> renderedInstructions = [];
private readonly GoogleAiSettings _googleSettings; private readonly GoogleAiSettings _settings;
public GoogleRealTimeProvider( public GoogleRealTimeProvider(
IServiceProvider services, IServiceProvider services,
GoogleAiSettings googleSettings, GoogleAiSettings googleSettings,
ILogger<GeminiChatCompletionProvider> logger) ILogger<GeminiChatCompletionProvider> logger)
{ {
_googleSettings = googleSettings; _settings = googleSettings;
_services = services; _services = services;
_logger = logger; _logger = logger;
} }
@ -101,13 +102,10 @@ namespace BotSharp.Plugin.GoogleAi.Providers.Realtime
public async Task CancelModelResponse() public async Task CancelModelResponse()
{ {
} }
public async Task RemoveConversationItem(string itemId) public async Task RemoveConversationItem(string itemId)
{ {
} }
private async Task AttachEvents() private async Task AttachEvents()
@ -124,7 +122,7 @@ namespace BotSharp.Plugin.GoogleAi.Providers.Realtime
{ {
if (e.Payload.ServerContent.TurnComplete == true) if (e.Payload.ServerContent.TurnComplete == true)
{ {
var responseDone = await ResponseDone(conn,e.Payload.ServerContent); var responseDone = await ResponseDone(conn, e.Payload.ServerContent);
onModelResponseDone(responseDone); onModelResponseDone(responseDone);
} }
} }
@ -139,13 +137,11 @@ namespace BotSharp.Plugin.GoogleAi.Providers.Realtime
onInputAudioTranscriptionCompleted(new RoleDialogModel(AgentRole.Assistant, e.Text)); onInputAudioTranscriptionCompleted(new RoleDialogModel(AgentRole.Assistant, e.Text));
}; };
_client.GenerationInterrupted += async (sender, e) => { onUserInterrupted(); }; _client.GenerationInterrupted += async (sender, e) => { onUserInterrupted(); };
_client.AudioReceiveCompleted += async (sender, e) => _client.AudioReceiveCompleted += async (sender, e) => { onModelAudioResponseDone(); };
{
onModelAudioResponseDone();
};
} }
private async Task<List<RoleDialogModel>> ResponseDone(RealtimeHubConnection conn, BidiGenerateContentServerContent serverContent) private async Task<List<RoleDialogModel>> ResponseDone(RealtimeHubConnection conn,
BidiGenerateContentServerContent serverContent)
{ {
var outputs = new List<RoleDialogModel>(); var outputs = new List<RoleDialogModel>();
@ -194,6 +190,7 @@ namespace BotSharp.Plugin.GoogleAi.Providers.Realtime
Model = _model, Model = _model,
}); });
} }
return outputs; return outputs;
} }
@ -206,29 +203,25 @@ namespace BotSharp.Plugin.GoogleAi.Providers.Realtime
{ {
var contentHooks = _services.GetServices<IContentGeneratingHook>().ToList(); var contentHooks = _services.GetServices<IContentGeneratingHook>().ToList();
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _googleSettings, _logger); var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
var chatClient = client.CreateGenerativeModel(_model); var chatClient = client.CreateGenerativeModel(_model);
var (prompt, request) = PrepareOptions(chatClient, agent, conversations); var (prompt, request) = PrepareOptions(chatClient, agent, conversations);
var config = request.GenerationConfig; var config = request.GenerationConfig;
//Output Modality can either be text or audio //Output Modality can either be text or audio
config.ResponseModalities = new List<Modality>([Modality.AUDIO]); config.ResponseModalities = new List<Modality>([Modality.AUDIO]);
var settingsService = _services.GetRequiredService<ILlmProviderService>(); var settingsService = _services.GetRequiredService<ILlmProviderService>();
var settings = settingsService.GetSetting(Provider, _model); var settings = settingsService.GetSetting(Provider, _model);
_client = chatClient.CreateMultiModalLiveClient(config, _client = chatClient.CreateMultiModalLiveClient(config,
systemInstruction: request.SystemInstruction?.Parts.FirstOrDefault()?.Text); systemInstruction: request.SystemInstruction?.Parts.FirstOrDefault()?.Text);
_client.UseGoogleSearch = _googleSettings.Gemini.UseGoogleSearch; _client.UseGoogleSearch = _settings.Gemini.UseGoogleSearch;
if (request.Tools != null && request.Tools.Count > 0) if (request.Tools != null && request.Tools.Count > 0)
{ {
var lst = (request.Tools.Select(s => (IFunctionTool)new FakeFunctionTool(s)).ToList()); var lst = (request.Tools.Select(s => (IFunctionTool)new TemporaryFunctionTool(s)).ToList());
_client.AddFunctionTools(lst, new ToolConfig() _client.AddFunctionTools(lst, new ToolConfig()
{ {
@ -261,7 +254,7 @@ namespace BotSharp.Plugin.GoogleAi.Providers.Realtime
var agentService = _services.GetRequiredService<IAgentService>(); var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(conn.CurrentAgentId); var agent = await agentService.LoadAgent(conn.CurrentAgentId);
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _googleSettings, _logger); var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
var chatClient = client.CreateGenerativeModel(_model); var chatClient = client.CreateGenerativeModel(_model);
var (prompt, request) = PrepareOptions(chatClient, agent, new List<RoleDialogModel>()); var (prompt, request) = PrepareOptions(chatClient, agent, new List<RoleDialogModel>());
@ -330,7 +323,7 @@ namespace BotSharp.Plugin.GoogleAi.Providers.Realtime
await _client.SendClientContentAsync(new BidiGenerateContentClientContent() await _client.SendClientContentAsync(new BidiGenerateContentClientContent()
{ {
TurnComplete = true, TurnComplete = true,
Turns = new []{new Content(message.Content, AgentRole.User)} Turns = new[] { new Content(message.Content, AgentRole.User) }
}); });
} }
else else
@ -354,7 +347,7 @@ namespace BotSharp.Plugin.GoogleAi.Providers.Realtime
List<RoleDialogModel> conversations) List<RoleDialogModel> conversations)
{ {
var agentService = _services.GetRequiredService<IAgentService>(); var agentService = _services.GetRequiredService<IAgentService>();
var googleSettings = _googleSettings; var googleSettings = _settings;
renderedInstructions = []; renderedInstructions = [];
// Add settings // Add settings

View file

@ -4,11 +4,11 @@ using GenerativeAI.Types;
namespace BotSharp.Plugin.GoogleAi.Providers.Realtime namespace BotSharp.Plugin.GoogleAi.Providers.Realtime
{ {
public class FakeFunctionTool:IFunctionTool public class TemporaryFunctionTool:IFunctionTool
{ {
public Tool Tool { get; set; } public Tool Tool { get; set; }
public FakeFunctionTool(Tool tool) public TemporaryFunctionTool(Tool tool)
{ {
this.Tool = tool; this.Tool = tool;
} }

View file

@ -17,14 +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 _googleSettings; private GoogleAiSettings _settings;
public GeminiTextCompletionProvider( public GeminiTextCompletionProvider(
IServiceProvider services, IServiceProvider services,
GoogleAiSettings googleSettings, GoogleAiSettings googleSettings,
ILogger<GeminiTextCompletionProvider> logger, ILogger<GeminiTextCompletionProvider> logger,
ITokenStatistics tokenStatistics) ITokenStatistics tokenStatistics)
{ {
_googleSettings = googleSettings; _settings = googleSettings;
_services = services; _services = services;
_logger = logger; _logger = logger;
_tokenStatistics = tokenStatistics; _tokenStatistics = tokenStatistics;
@ -50,7 +50,7 @@ public class GeminiTextCompletionProvider : ITextCompletion
await hook.BeforeGenerating(agent, new List<RoleDialogModel> { userMessage }); await hook.BeforeGenerating(agent, new List<RoleDialogModel> { userMessage });
} }
var client = ProviderHelper.GetGeminiClient(Provider, _model, _services, _googleSettings, _logger); var client = ProviderHelper.GetGeminiClient(Provider, _model, _services);
var aiModel = client.CreateGenerativeModel(_model); var aiModel = client.CreateGenerativeModel(_model);
PrepareOptions(aiModel); PrepareOptions(aiModel);

View file

@ -169,11 +169,18 @@
"Provider": "google-ai", "Provider": "google-ai",
"Models": [ "Models": [
{ {
"Name": "gemini-2.0-flash-exp", "Name": "gemini-2.0-flash",
"ApiKey": "", "ApiKey": "",
"Type": "chat", "Type": "chat",
"MultiModal": true, "MultiModal": true,
"RealTime": true, "PromptCost": 0.0015,
"CompletionCost": 0.002
},
{
"Name": "gemini-2.0-flash-exp",
"ApiKey": "",
"Type": "realtime",
"MultiModal": true,
"PromptCost": 0.0015, "PromptCost": 0.0015,
"CompletionCost": 0.002 "CompletionCost": 0.002
} }

View file

@ -11,7 +11,7 @@ using Shouldly;
namespace BotSharp.Plugin.Google.Core namespace BotSharp.Plugin.Google.Core
{ {
public class ChatCompletion_Tests:TestBase public class ChatCompletionTests:TestBase
{ {
protected static Agent CreateTestAgent() protected static Agent CreateTestAgent()
{ {
@ -56,7 +56,7 @@ namespace BotSharp.Plugin.Google.Core
yield return new object[] { services.BuildServiceProvider().GetService<IChatCompletion>() ?? throw new Exception("Error while initializing"), agent, modelName }; yield return new object[] { services.BuildServiceProvider().GetService<IChatCompletion>() ?? throw new Exception("Error while initializing"), agent, modelName };
} }
} }
public ChatCompletion_Tests() public ChatCompletionTests()
{ {
} }

View file

@ -9,7 +9,7 @@ using Shouldly;
namespace BotSharp.Plugin.Google.Core namespace BotSharp.Plugin.Google.Core
{ {
public class Embedding_Tests:TestBase public class EmbeddingTests:TestBase
{ {
protected static Agent CreateTestAgent() protected static Agent CreateTestAgent()
{ {

View file

@ -10,7 +10,7 @@ using Shouldly;
namespace BotSharp.Plugin.Google.Core namespace BotSharp.Plugin.Google.Core
{ {
public class FunctionCalling_Tests : TestBase public class FunctionCallingTests : TestBase
{ {
public static IEnumerable<object[]> CreateTestLLMProviders() public static IEnumerable<object[]> CreateTestLLMProviders()
{ {

View file

@ -11,7 +11,7 @@ using Shouldly;
namespace BotSharp.Plugin.Google.Core namespace BotSharp.Plugin.Google.Core
{ {
public class GoogleRealTime_Tests : TestBase public class GoogleRealTimeTests : TestBase
{ {
protected static Agent CreateTestAgent() protected static Agent CreateTestAgent()
{ {

View file

@ -100,9 +100,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
</ItemGroup>
</Project> </Project>