diff --git a/Directory.Build.props b/Directory.Build.props index b0bfb4ac..84bd21db 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ 10.0 ..\..\..\packages - 0.15.1 + 0.16.0 true \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs index 81e52f92..38e62ced 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs @@ -12,6 +12,7 @@ public class RoutingSettings public string Description { get; set; } = string.Empty; public bool EnableReasoning { get; set; } = false; + public bool UseTextCompletion { get; set; } = false; public string Provider { get; set; } = string.Empty; diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs index c51f59e6..90adac26 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs @@ -18,21 +18,6 @@ public static class StringExtensions return str; } - public static string CleanPhoneNumber(this string phoneNumber) - { - if (phoneNumber != null && !phoneNumber.All(char.IsDigit)) - { - phoneNumber = Regex.Replace(phoneNumber, @"[^\d]", ""); - } - - if (phoneNumber != null && phoneNumber.Length > 10) - { - phoneNumber = phoneNumber.Substring(1); - } - - return phoneNumber; - } - public static string[] SplitByNewLine(this string input) { return input.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs index 54b21928..34feb606 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs @@ -31,4 +31,32 @@ public class CompletionProvider return completer; } + + public static ITextCompletion GetTextCompletion(IServiceProvider services, string? provider = null, string? model = null) + { + var completions = services.GetServices(); + + var state = services.GetRequiredService(); + + if (string.IsNullOrEmpty(provider)) + { + provider = state.GetState("provider", "azure-openai"); + } + + if (string.IsNullOrEmpty(model)) + { + model = state.GetState("model", "gpt-3.5-turbo"); + } + + var completer = completions.FirstOrDefault(x => x.Provider == provider); + if (completer == null) + { + var logger = services.GetRequiredService>(); + logger.LogError($"Can't resolve completion provider by {provider}"); + } + + completer.SetModelName(model); + + return completer; + } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs index b381ec24..636d8cac 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Models; using System.Drawing; using System.Text.RegularExpressions; @@ -19,16 +18,30 @@ public partial class RoutingService var content = $"{prompt} Response must be in JSON format {responseFormat}"; var state = _services.GetRequiredService(); - var provider = state.GetState("provider", _settings.Provider); - var model = state.GetState("model", _settings.Model); - var chatCompletion = CompletionProvider.GetChatCompletion(_services, - provider: provider, - model: model); + - var response = chatCompletion.GetChatCompletions(_routerInstance.Router, new List + RoleDialogModel response = default; + if (_settings.UseTextCompletion) + { + var completion = CompletionProvider.GetTextCompletion(_services, + provider: _settings.Provider, + model: _settings.Model); + + content = _routerInstance.Router.Instruction + "\r\n\r\n" + content + "\r\nResponse: "; + var text = await completion.GetCompletion(content); + response = new RoleDialogModel(AgentRole.Assistant, text); + } + else + { + var completion = CompletionProvider.GetChatCompletion(_services, + provider: _settings.Provider, + model: _settings.Model); + + response = completion.GetChatCompletions(_routerInstance.Router, new List { new RoleDialogModel(AgentRole.User, content) }); + } var args = new FunctionCallFromLlm(); try diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index d09abdc5..33b85a5c 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -37,59 +37,15 @@ public class ChatCompletionProvider : IChatCompletion _tokenStatistics = tokenStatistics; } - protected virtual (OpenAIClient, string) GetClient() - { - if (_model == "gpt-4") - { - var client = new OpenAIClient(new Uri(_settings.GPT4.Endpoint), new AzureKeyCredential(_settings.GPT4.ApiKey)); - return (client, _settings.GPT4.DeploymentModel); - } - else - { - var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey)); - return (client, _settings.DeploymentModel.ChatCompletionModel); - } - } - - public List GetChatSamples(string sampleText) - { - var samples = new List(); - if (string.IsNullOrEmpty(sampleText)) - { - return samples; - } - - var lines = sampleText.Split('\n'); - for (int i = 0; i < lines.Length; i++) - { - var line = lines[i]; - if (string.IsNullOrEmpty(line.Trim())) - { - continue; - } - var role = line.Substring(0, line.IndexOf(' ') - 1).Trim(); - var content = line.Substring(line.IndexOf(' ') + 1).Trim(); - - // comments - if (role == "##") - { - continue; - } - - samples.Add(new RoleDialogModel(role, content)); - } - - return samples; - } - public RoleDialogModel GetChatCompletions(Agent agent, List conversations) { - var (client, deploymentModel) = GetClient(); + var (client, deploymentModel) = ProviderHelper.GetClient(_model, _settings); var chatCompletionsOptions = PrepareOptions(agent, conversations); _tokenStatistics.StartTimer(); var response = client.GetChatCompletions(deploymentModel, chatCompletionsOptions); _tokenStatistics.StopTimer(); + var choice = response.Value.Choices[0]; var message = choice.Message; @@ -104,7 +60,7 @@ public class ChatCompletionProvider : IChatCompletion if (choice.FinishReason == CompletionsFinishReason.FunctionCall) { - _logger.LogInformation($"[{agent.Name}]: {message.FunctionCall.Name} => {message.FunctionCall.Arguments}"); + _logger.LogInformation($"[{agent.Name}]: {message.FunctionCall.Name}({message.FunctionCall.Arguments})"); var funcContextIn = new RoleDialogModel(AgentRole.Function, message.Content) { @@ -137,7 +93,7 @@ public class ChatCompletionProvider : IChatCompletion Func onMessageReceived, Func onFunctionExecuting) { - var (client, deploymentModel) = GetClient(); + var (client, deploymentModel) = ProviderHelper.GetClient(_model, _settings); var chatCompletionsOptions = PrepareOptions(agent, conversations); var response = await client.GetChatCompletionsAsync(deploymentModel, chatCompletionsOptions); @@ -155,7 +111,7 @@ public class ChatCompletionProvider : IChatCompletion if (choice.FinishReason == CompletionsFinishReason.FunctionCall) { - _logger.LogInformation($"[{agent.Name}]: {message.FunctionCall.Name} => {message.FunctionCall.Arguments}"); + _logger.LogInformation($"[{agent.Name}]: {message.FunctionCall.Name}({message.FunctionCall.Arguments})"); var funcContextIn = new RoleDialogModel(AgentRole.Function, message.Content) { @@ -246,7 +202,7 @@ public class ChatCompletionProvider : IChatCompletion chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, agent.Knowledges)); } - var samples = GetChatSamples(agent.Samples); + var samples = ProviderHelper.GetChatSamples(agent.Samples); foreach (var message in samples) { chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content)); diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ProviderHelper.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ProviderHelper.cs new file mode 100644 index 00000000..b829330f --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ProviderHelper.cs @@ -0,0 +1,56 @@ +using Azure.AI.OpenAI; +using Azure; +using System; +using BotSharp.Plugin.AzureOpenAI.Settings; +using BotSharp.Abstraction.Conversations.Models; +using System.Collections.Generic; + +namespace BotSharp.Plugin.AzureOpenAI.Providers; + +public class ProviderHelper +{ + public static (OpenAIClient, string) GetClient(string model, AzureOpenAiSettings settings) + { + if (model == "gpt-4") + { + var client = new OpenAIClient(new Uri(settings.GPT4.Endpoint), new AzureKeyCredential(settings.GPT4.ApiKey)); + return (client, settings.GPT4.DeploymentModel); + } + else + { + var client = new OpenAIClient(new Uri(settings.Endpoint), new AzureKeyCredential(settings.ApiKey)); + return (client, settings.DeploymentModel.ChatCompletionModel); + } + } + + public static List GetChatSamples(string sampleText) + { + var samples = new List(); + if (string.IsNullOrEmpty(sampleText)) + { + return samples; + } + + var lines = sampleText.Split('\n'); + for (int i = 0; i < lines.Length; i++) + { + var line = lines[i]; + if (string.IsNullOrEmpty(line.Trim())) + { + continue; + } + var role = line.Substring(0, line.IndexOf(' ') - 1).Trim(); + var content = line.Substring(line.IndexOf(' ') + 1).Trim(); + + // comments + if (role == "##") + { + continue; + } + + samples.Add(new RoleDialogModel(role, content)); + } + + return samples; + } +} diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/TextCompletionProvider.cs index 820bb3af..a6b037ce 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/TextCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/TextCompletionProvider.cs @@ -1,43 +1,68 @@ using Azure.AI.OpenAI; -using Azure; using BotSharp.Abstraction.MLTasks; using System; using System.Threading.Tasks; using BotSharp.Plugin.AzureOpenAI.Settings; using Microsoft.Extensions.Logging; +using BotSharp.Abstraction.Conversations; +using Microsoft.Extensions.DependencyInjection; +using BotSharp.Abstraction.Conversations.Models; namespace BotSharp.Plugin.AzureOpenAI.Providers; public class TextCompletionProvider : ITextCompletion { + private readonly IServiceProvider _services; private readonly AzureOpenAiSettings _settings; private readonly ILogger _logger; - bool _useAzureOpenAI = true; + private readonly ITokenStatistics _tokenStatistics; private string _model; public string Provider => "azure-openai"; - public TextCompletionProvider(AzureOpenAiSettings settings, ILogger logger) + public TextCompletionProvider(IServiceProvider services, + AzureOpenAiSettings settings, + ILogger logger, + ITokenStatistics tokenStatistics) { + _services = services; _settings = settings; _logger = logger; + _tokenStatistics = tokenStatistics; } public async Task GetCompletion(string text) { - var client = GetOpenAIClient(); + var (client, _) = ProviderHelper.GetClient(_model, _settings); + var completionsOptions = new CompletionsOptions() { Prompts = { text }, - Temperature = 0.7f, MaxTokens = 256 }; + var state = _services.GetRequiredService(); + var temperature = float.Parse(state.GetState("temperature", "0.5")); + var samplingFactor = float.Parse(state.GetState("sampling_factor", "0.5")); + completionsOptions.Temperature = temperature; + completionsOptions.NucleusSamplingFactor = samplingFactor; + + _tokenStatistics.StartTimer(); var response = await client.GetCompletionsAsync( deploymentOrModelName: _settings.DeploymentModel.TextCompletionModel, completionsOptions); + _tokenStatistics.StopTimer(); + + _tokenStatistics.AddToken(new TokenStatsModel + { + Model = _model, + PromptCount = response.Value.Usage.PromptTokens, + CompletionCount = response.Value.Usage.CompletionTokens, + PromptCost = 0.0015f, + CompletionCost = 0.002f + }); // OpenAI var completion = ""; @@ -46,7 +71,7 @@ public class TextCompletionProvider : ITextCompletion completion += t.Text; }; - _logger.LogInformation(text + completion); + _logger.LogInformation(text); return completion.Trim(); } @@ -55,14 +80,4 @@ public class TextCompletionProvider : ITextCompletion { _model = model; } - - private OpenAIClient GetOpenAIClient() - { - OpenAIClient client = _useAzureOpenAI - ? new OpenAIClient( - new Uri(_settings.Endpoint), - new AzureKeyCredential(_settings.ApiKey)) - : new OpenAIClient("your-api-key-from-platform.openai.com"); - return client; - } } diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ChatCompletionProvider.cs index 9e9dbd8b..abe1ca9c 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ChatCompletionProvider.cs @@ -29,10 +29,9 @@ public class ChatCompletionProvider : IChatCompletion public RoleDialogModel GetChatCompletions(Agent agent, List conversations) { var client = new GooglePalmClient(apiKey: _settings.PaLM.ApiKey); - List messages = new() - { - new(conversations.Last().Content, "user"), - }; + var messages = conversations.Select(c => new PalmChatMessage(c.Content, c.Role == AgentRole.User ? "user" : "AI")) + .ToList(); + _tokenStatistics.StartTimer(); var response = client.ChatAsync(messages, agent.Instruction, null).Result; _tokenStatistics.StopTimer(); diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/TextCompletionProvider.cs index 8063d5d5..fc854726 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/TextCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/TextCompletionProvider.cs @@ -1,13 +1,42 @@ +using BotSharp.Abstraction.Conversations; +using BotSharp.Plugin.GoogleAI.Settings; +using LLMSharp.Google.Palm; +using Microsoft.Extensions.Logging; + namespace BotSharp.Plugin.GoogleAI.Providers; public class TextCompletionProvider : ITextCompletion { public string Provider => "google-ai"; + private readonly IServiceProvider _services; + private readonly GoogleAiSettings _settings; + private readonly ILogger _logger; + private readonly ITokenStatistics _tokenStatistics; private string _model; - public Task GetCompletion(string text) + public TextCompletionProvider(IServiceProvider services, + GoogleAiSettings settings, + ILogger logger, + ITokenStatistics tokenStatistics) { - throw new NotImplementedException(); + _services = services; + _settings = settings; + _logger = logger; + _tokenStatistics = tokenStatistics; + } + + public async Task GetCompletion(string text) + { + var client = new GooglePalmClient(apiKey: _settings.PaLM.ApiKey); + _tokenStatistics.StartTimer(); + var response = await client.GenerateTextAsync(text, null); + _tokenStatistics.StopTimer(); + + var message = response.Candidates.First(); + + _logger.LogInformation(text); + + return message.Output.Trim(); } public void SetModelName(string model) diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 1ee508f7..1e6db61e 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -17,6 +17,7 @@ "RouterId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a", "RouterName": "PizzaBot", "Description": "Pizza restaurant AI Bot", + "UseTextCompletion": false, "EnableReasoning": false, "Provider": "azure-openai", "Model": "gpt-3.5-turbo"