Merge branch 'master' of https://github.com/visagang/BotSharp into features/vguruparan
This commit is contained in:
commit
6abc7aba43
15
BotSharp.sln
15
BotSharp.sln
|
|
@ -89,9 +89,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.MetaGLM", "
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.AnthropicAI", "src\Plugins\BotSharp.Plugin.AnthropicAI\BotSharp.Plugin.AnthropicAI.csproj", "{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.ServiceDefaults", "src\BotSharp.ServiceDefaults\BotSharp.ServiceDefaults.csproj", "{6406DC61-0F30-42E8-A1DB-B38CDF454273}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.ServiceDefaults", "src\BotSharp.ServiceDefaults\BotSharp.ServiceDefaults.csproj", "{6406DC61-0F30-42E8-A1DB-B38CDF454273}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.AppHost", "src\BotSharp.AppHost\BotSharp.AppHost.csproj", "{E04FBBEF-744E-4EF3-B634-42AD9F8B68B1}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.AppHost", "src\BotSharp.AppHost\BotSharp.AppHost.csproj", "{E04FBBEF-744E-4EF3-B634-42AD9F8B68B1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.OpenAI", "src\Plugins\BotSharp.Plugin.OpenAI\BotSharp.Plugin.OpenAI.csproj", "{6507D336-3A4D-41D4-81C0-2B900173A5FE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
@ -373,6 +375,14 @@ Global
|
|||
{E04FBBEF-744E-4EF3-B634-42AD9F8B68B1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E04FBBEF-744E-4EF3-B634-42AD9F8B68B1}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{E04FBBEF-744E-4EF3-B634-42AD9F8B68B1}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6507D336-3A4D-41D4-81C0-2B900173A5FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6507D336-3A4D-41D4-81C0-2B900173A5FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6507D336-3A4D-41D4-81C0-2B900173A5FE}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6507D336-3A4D-41D4-81C0-2B900173A5FE}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6507D336-3A4D-41D4-81C0-2B900173A5FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6507D336-3A4D-41D4-81C0-2B900173A5FE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6507D336-3A4D-41D4-81C0-2B900173A5FE}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6507D336-3A4D-41D4-81C0-2B900173A5FE}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -417,6 +427,7 @@ Global
|
|||
{289E25C8-63F1-4D52-9909-207724DB40CB} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
{CCF745F2-0C95-4ED0-983B-507C528B39EA} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
{6507D336-3A4D-41D4-81C0-2B900173A5FE} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
|
||||
|
|
|
|||
|
|
@ -5,4 +5,5 @@ public class BuiltInAgentId
|
|||
public const string AIAssistant = "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a";
|
||||
public const string Chatbot = "01e2fc5c-2c89-4ec7-8470-7688608b496c";
|
||||
public const string HumanSupport = "01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b";
|
||||
public const string UtilityAssistant = "6745151e-6d46-4a02-8de4-1c4f21c7da95";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public interface IBotSharpPlugin
|
|||
/// <summary>
|
||||
/// Has build-in agent profile with this plugin
|
||||
/// </summary>
|
||||
string[] AgentIds => new string[0];
|
||||
string[] AgentIds => [];
|
||||
|
||||
void RegisterDI(IServiceCollection services, IConfiguration config);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BotSharp.Abstraction.Utilities;
|
||||
|
||||
public static class EnumHelper
|
||||
{
|
||||
public static string GetDescription(Enum value)
|
||||
{
|
||||
FieldInfo fi = value.GetType().GetField(value.ToString());
|
||||
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
|
||||
if (attributes != null && attributes.Length > 0)
|
||||
return attributes[0].Description;
|
||||
else
|
||||
return value.ToString();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,6 @@ public interface IVectorDb
|
|||
{
|
||||
Task<List<string>> GetCollections();
|
||||
Task CreateCollection(string collectionName, int dim);
|
||||
Task Upsert(string collectionName, int id, float[] vector, string text);
|
||||
Task Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload = null);
|
||||
Task<List<string>> Search(string collectionName, float[] vector, int limit = 5);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Templating;
|
||||
|
||||
namespace BotSharp.Core.Agents.Services;
|
||||
|
||||
public partial class AgentService
|
||||
|
|
@ -7,7 +5,7 @@ public partial class AgentService
|
|||
[MemoryCache(10 * 60, perInstanceCache: true)]
|
||||
public async Task<Agent> LoadAgent(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
if (string.IsNullOrEmpty(id) || id == Guid.Empty.ToString())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="data\agents\00000000-0000-0000-0000-000000000000\agent.json" />
|
||||
<None Remove="data\agents\00000000-0000-0000-0000-000000000000\functions\generate_image.json" />
|
||||
<None Remove="data\agents\00000000-0000-0000-0000-000000000000\functions\read_file.json" />
|
||||
<None Remove="data\agents\00000000-0000-0000-0000-000000000000\instruction.liquid" />
|
||||
<None Remove="data\agents\00000000-0000-0000-0000-000000000000\templates\generate_image.fn.liquid" />
|
||||
<None Remove="data\agents\00000000-0000-0000-0000-000000000000\templates\read_file.fn.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\agent.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\generate_image.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\read_file.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instruction.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\generate_image.fn.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\read_file.fn.liquid" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\agent.json" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions.json" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions\human_intervention_needed.json" />
|
||||
|
|
@ -153,22 +153,22 @@
|
|||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\conversation.summary.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\00000000-0000-0000-0000-000000000000\agent.json">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\00000000-0000-0000-0000-000000000000\instruction.liquid">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\00000000-0000-0000-0000-000000000000\functions\read_file.json">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\read_file.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\00000000-0000-0000-0000-000000000000\templates\read_file.fn.liquid">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\read_file.fn.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\00000000-0000-0000-0000-000000000000\functions\generate_image.json">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\generate_image.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\00000000-0000-0000-0000-000000000000\templates\generate_image.fn.liquid">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\generate_image.fn.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\plugins\config.json">
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ public class GenerateImageFn : IFunctionCallback
|
|||
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<GenerateImageFn> _logger;
|
||||
private static string UTILITY_ASSISTANT = Guid.Empty.ToString();
|
||||
private string _conversationId;
|
||||
private string _messageId;
|
||||
|
||||
|
|
@ -29,7 +28,7 @@ public class GenerateImageFn : IFunctionCallback
|
|||
SetImageOptions();
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(UTILITY_ASSISTANT);
|
||||
var agent = await agentService.LoadAgent(BuiltInAgentId.UtilityAssistant);
|
||||
var imageAgent = new Agent
|
||||
{
|
||||
Id = agent?.Id ?? Guid.Empty.ToString(),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ public class ReadFileFn : IFunctionCallback
|
|||
private readonly ILogger<ReadFileFn> _logger;
|
||||
private readonly IEnumerable<string> _imageTypes = new List<string> { "image", "images", "png", "jpg", "jpeg" };
|
||||
private readonly IEnumerable<string> _pdfTypes = new List<string> { "pdf" };
|
||||
private static string UTILITY_ASSISTANT = Guid.Empty.ToString();
|
||||
|
||||
public ReadFileFn(
|
||||
IServiceProvider services,
|
||||
|
|
@ -31,7 +30,7 @@ public class ReadFileFn : IFunctionCallback
|
|||
var wholeDialogs = conv.GetDialogHistory();
|
||||
var fileTypes = args?.FileTypes?.Split(",", StringSplitOptions.RemoveEmptyEntries)?.ToList() ?? new List<string>();
|
||||
var dialogs = await AssembleFiles(conv.ConversationId, wholeDialogs, fileTypes);
|
||||
var agent = await agentService.LoadAgent(UTILITY_ASSISTANT);
|
||||
var agent = await agentService.LoadAgent(BuiltInAgentId.UtilityAssistant);
|
||||
var fileAgent = new Agent
|
||||
{
|
||||
Id = agent?.Id ?? Guid.Empty.ToString(),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ namespace BotSharp.Core.Files.Hooks;
|
|||
|
||||
public class FileReaderHook : AgentHookBase
|
||||
{
|
||||
private static string UTILITY_ASSISTANT = Guid.Empty.ToString();
|
||||
private static string FUNCTION_NAME = "read_file";
|
||||
|
||||
public override string SelfId => string.Empty;
|
||||
|
|
@ -45,7 +44,7 @@ public class FileReaderHook : AgentHookBase
|
|||
private (string, FunctionDef?) GetPromptAndFunction()
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var agent = db.GetAgent(UTILITY_ASSISTANT);
|
||||
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
|
||||
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{FUNCTION_NAME}.fn"))?.Content ?? string.Empty;
|
||||
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(FUNCTION_NAME));
|
||||
return (prompt, loadAttachmentFn);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ namespace BotSharp.Core.Files.Hooks;
|
|||
|
||||
public class ImageGeneratorHook : AgentHookBase
|
||||
{
|
||||
private static string UTILITY_ASSISTANT = Guid.Empty.ToString();
|
||||
private static string FUNCTION_NAME = "generate_image";
|
||||
|
||||
public override string SelfId => string.Empty;
|
||||
|
|
@ -45,7 +44,7 @@ public class ImageGeneratorHook : AgentHookBase
|
|||
private (string, FunctionDef?) GetPromptAndFunction()
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var agent = db.GetAgent(UTILITY_ASSISTANT);
|
||||
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
|
||||
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{FUNCTION_NAME}.fn"))?.Content ?? string.Empty;
|
||||
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(FUNCTION_NAME));
|
||||
return (prompt, loadAttachmentFn);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,10 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
}
|
||||
|
||||
message.FunctionArgs = JsonSerializer.Serialize(inst);
|
||||
var ret = await routing.InvokeFunction(message.FunctionName, message);
|
||||
if (message.FunctionName != null)
|
||||
{
|
||||
var ret = await routing.InvokeFunction(message.FunctionName, message);
|
||||
}
|
||||
|
||||
var agentId = routing.Context.GetCurrentAgentId();
|
||||
|
||||
|
|
@ -82,7 +85,7 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
}
|
||||
else
|
||||
{
|
||||
ret = await routing.InvokeAgent(agentId, _dialogs, onFunctionExecuting);
|
||||
var ret = await routing.InvokeAgent(agentId, _dialogs, onFunctionExecuting);
|
||||
}
|
||||
|
||||
var response = _dialogs.Last();
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ public class UserService : IUserService
|
|||
var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization));
|
||||
var (id, password) = base64.SplitAsTuple(":");
|
||||
|
||||
var hooks = _services.GetServices<IAuthenticationHook>();
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id);
|
||||
if (record == null)
|
||||
|
|
@ -96,7 +97,7 @@ public class UserService : IUserService
|
|||
}
|
||||
|
||||
//verify password is correct or not.
|
||||
if (record != null)
|
||||
if (record != null && !hooks.Any())
|
||||
{
|
||||
var hashPassword = Utilities.HashTextMd5($"{password}{record.Salt}");
|
||||
if (hashPassword != record.Password)
|
||||
|
|
@ -107,7 +108,6 @@ public class UserService : IUserService
|
|||
|
||||
User? user = record;
|
||||
var isAuthenticatedByHook = false;
|
||||
var hooks = _services.GetServices<IAuthenticationHook>();
|
||||
if (record == null || record.Source != "internal")
|
||||
{
|
||||
// check 3rd party user
|
||||
|
|
@ -146,7 +146,7 @@ public class UserService : IUserService
|
|||
}
|
||||
}
|
||||
|
||||
if ((hooks != null && hooks.Any() && user == null) || record == null)
|
||||
if ((hooks.Any() && user == null) || record == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"id": "6745151e-6d46-4a02-8de4-1c4f21c7da95",
|
||||
"name": "Utility Assistant",
|
||||
"description": "Utility assistant that can be used to complete many different tasks",
|
||||
"type": "static",
|
||||
|
|
@ -116,7 +116,7 @@ public class InstructModeController : ControllerBase
|
|||
try
|
||||
{
|
||||
var completion = CompletionProvider.GetImageGeneration(_services, provider: input.Provider ?? "openai",
|
||||
modelId: input.ModelId ?? "dall-e", imageGenerate: true);
|
||||
model: input.Model ?? "dall-e-3", imageGenerate: true);
|
||||
var message = await completion.GetImageGeneration(new Agent()
|
||||
{
|
||||
Id = Guid.Empty.ToString(),
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ namespace BotSharp.Platform.AzureAi;
|
|||
public class AzureOpenAiPlugin : IBotSharpPlugin
|
||||
{
|
||||
public string Id => "65185362-392c-44fd-a023-95a198824436";
|
||||
public string Name => "OpenAI/ Azure OpenAI";
|
||||
public string Description => "OpenAI/ Azure OpenAI Service including text generation, text to image and other AI services.";
|
||||
public string Name => "Azure OpenAI";
|
||||
public string Description => "Azure OpenAI Service including text generation, text to image and other AI services.";
|
||||
public string IconUrl => "https://nanfor.com/cdn/shop/files/cursos-propios-Azure-openAI.jpg?v=1692877741";
|
||||
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
|
|
@ -27,12 +27,8 @@ public class AzureOpenAiPlugin : IBotSharpPlugin
|
|||
});
|
||||
|
||||
services.AddScoped<ITextCompletion, TextCompletionProvider>();
|
||||
services.AddScoped<ITextCompletion, OpenAiTextCompletionProvider>();
|
||||
services.AddScoped<IChatCompletion, ChatCompletionProvider>();
|
||||
services.AddScoped<IChatCompletion, OpenAiChatCompletionProvider>();
|
||||
services.AddScoped<IImageGeneration, ImageGenerationProvider>();
|
||||
services.AddScoped<IImageGeneration, OpenAiImageGenerationProvider>();
|
||||
services.AddScoped<ITextEmbedding, TextEmbeddingProvider>();
|
||||
services.AddScoped<ITextEmbedding, OpenAiTextEmbeddingProvider>();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Plugin.AzureOpenAI.Models;
|
||||
|
||||
public class OpenAiTextCompletionRequest : TextCompletionRequest
|
||||
{
|
||||
[JsonPropertyName("model")]
|
||||
public string Model { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ namespace BotSharp.Plugin.AzureOpenAI.Models;
|
|||
|
||||
public class TextCompletionRequest
|
||||
{
|
||||
|
||||
[JsonPropertyName("prompt")]
|
||||
public string Prompt { get; set; } = string.Empty;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
|
||||
public virtual string Provider => "azure-openai";
|
||||
|
||||
public ChatCompletionProvider(AzureOpenAiSettings settings,
|
||||
public ChatCompletionProvider(
|
||||
AzureOpenAiSettings settings,
|
||||
ILogger<ChatCompletionProvider> logger,
|
||||
IServiceProvider services)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
namespace BotSharp.Plugin.AzureOpenAI.Providers.Chat;
|
||||
|
||||
public class OpenAiChatCompletionProvider : ChatCompletionProvider
|
||||
{
|
||||
public override string Provider => "openai";
|
||||
|
||||
public OpenAiChatCompletionProvider(AzureOpenAiSettings settings,
|
||||
ILogger<OpenAiChatCompletionProvider> logger,
|
||||
IServiceProvider services) : base(settings, logger, services)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
namespace BotSharp.Plugin.AzureOpenAI.Providers.Embedding;
|
||||
|
||||
public class OpenAiTextEmbeddingProvider : TextEmbeddingProvider
|
||||
{
|
||||
public override string Provider => "openai";
|
||||
|
||||
public OpenAiTextEmbeddingProvider(AzureOpenAiSettings settings,
|
||||
ILogger<OpenAiTextEmbeddingProvider> logger,
|
||||
IServiceProvider services) : base(settings, logger, services) { }
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using BotSharp.Abstraction.Files.Models;
|
||||
using OpenAI.Images;
|
||||
|
||||
namespace BotSharp.Plugin.AzureOpenAI.Providers.Image;
|
||||
|
|
@ -10,7 +9,7 @@ public class ImageGenerationProvider : IImageGeneration
|
|||
protected readonly ILogger _logger;
|
||||
|
||||
private const int DEFAULT_IMAGE_COUNT = 1;
|
||||
private const int IMAGE_COUNT_LIMIT = 3;
|
||||
private const int IMAGE_COUNT_LIMIT = 5;
|
||||
|
||||
protected string _model;
|
||||
|
||||
|
|
@ -44,7 +43,6 @@ public class ImageGenerationProvider : IImageGeneration
|
|||
var response = imageClient.GenerateImages(prompt, imageCount, options);
|
||||
var values = response.Value;
|
||||
|
||||
var content = string.Empty;
|
||||
var images = new List<ImageGeneration>();
|
||||
foreach (var value in values)
|
||||
{
|
||||
|
|
@ -67,9 +65,9 @@ public class ImageGenerationProvider : IImageGeneration
|
|||
}
|
||||
|
||||
images.Add(image);
|
||||
content += $"{image.Description}\r\n";
|
||||
}
|
||||
|
||||
var content = string.Join("\r\n", images.Select(x => x.Description));
|
||||
var responseMessage = new RoleDialogModel(AgentRole.Assistant, content)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
namespace BotSharp.Plugin.AzureOpenAI.Providers.Image;
|
||||
|
||||
public class OpenAiImageGenerationProvider : ImageGenerationProvider
|
||||
{
|
||||
public override string Provider => "openai";
|
||||
|
||||
public OpenAiImageGenerationProvider(AzureOpenAiSettings settings,
|
||||
ILogger<OpenAiImageGenerationProvider> logger,
|
||||
IServiceProvider services) : base(settings, logger, services)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,15 @@
|
|||
using Azure.AI.OpenAI;
|
||||
using Azure;
|
||||
using OpenAI;
|
||||
using System.ClientModel;
|
||||
|
||||
namespace BotSharp.Plugin.AzureOpenAI.Providers;
|
||||
|
||||
public class ProviderHelper
|
||||
{
|
||||
public static OpenAIClient GetClient(string provider, string model, IServiceProvider services)
|
||||
public static AzureOpenAIClient GetClient(string provider, string model, IServiceProvider services)
|
||||
{
|
||||
var settingsService = services.GetRequiredService<ILlmProviderService>();
|
||||
var settings = settingsService.GetSetting(provider, model);
|
||||
var client = provider == "openai" ?
|
||||
new OpenAIClient(new ApiKeyCredential(settings.ApiKey)) :
|
||||
new AzureOpenAIClient(new Uri(settings.Endpoint), new AzureKeyCredential(settings.ApiKey));
|
||||
var client = new AzureOpenAIClient(new Uri(settings.Endpoint), new AzureKeyCredential(settings.ApiKey));
|
||||
return client;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
using BotSharp.Abstraction.MLTasks.Settings;
|
||||
using BotSharp.Plugin.AzureOpenAI.Models;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BotSharp.Plugin.AzureOpenAI.Providers.Text;
|
||||
|
||||
public class OpenAiTextCompletionProvider : TextCompletionProvider
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<OpenAiTextCompletionProvider> _logger;
|
||||
|
||||
public override string Provider => "openai";
|
||||
|
||||
public OpenAiTextCompletionProvider(AzureOpenAiSettings settings,
|
||||
ILogger<OpenAiTextCompletionProvider> logger,
|
||||
IServiceProvider services) : base(settings, logger, services)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task<TextCompletionResponse> GetTextCompletion(string apiUrl, string apiKey, string prompt, float temperature, int maxTokens = 256)
|
||||
{
|
||||
try
|
||||
{
|
||||
var http = _services.GetRequiredService<IHttpClientFactory>();
|
||||
using var httpClient = http.CreateClient();
|
||||
AddHeader(httpClient, apiKey);
|
||||
|
||||
var request = new OpenAiTextCompletionRequest
|
||||
{
|
||||
Model = _model,
|
||||
Prompt = prompt,
|
||||
MaxTokens = maxTokens,
|
||||
Temperature = temperature
|
||||
};
|
||||
var data = JsonSerializer.Serialize(request, _jsonOptions);
|
||||
var httpRequest = new HttpRequestMessage
|
||||
{
|
||||
Method = HttpMethod.Post,
|
||||
RequestUri = new Uri(apiUrl),
|
||||
Content = new StringContent(data, Encoding.UTF8, MediaTypeNames.Application.Json)
|
||||
};
|
||||
var rawResponse = await httpClient.SendAsync(httpRequest);
|
||||
rawResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var responseStr = await rawResponse.Content.ReadAsStringAsync();
|
||||
var response = JsonSerializer.Deserialize<TextCompletionResponse>(responseStr, _jsonOptions);
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error when {Provider}-{_model} generating text... {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string BuildApiUrl(LlmModelSetting modelSetting)
|
||||
{
|
||||
var endpoint = modelSetting.Endpoint.EndsWith("/") ?
|
||||
modelSetting.Endpoint.Substring(0, modelSetting.Endpoint.Length - 1) : modelSetting.Endpoint;
|
||||
return endpoint ?? string.Empty;
|
||||
}
|
||||
|
||||
protected override void AddHeader(HttpClient httpClient, string apiKey)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using BotSharp.Abstraction.MLTasks.Settings;
|
||||
using BotSharp.Plugin.AzureOpenAI.Models;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
|
|
@ -96,7 +95,7 @@ public class TextCompletionProvider : ITextCompletion
|
|||
_model = model;
|
||||
}
|
||||
|
||||
protected virtual async Task<TextCompletionResponse> GetTextCompletion(string apiUrl, string apiKey, string prompt, float temperature, int maxTokens = 256)
|
||||
private async Task<TextCompletionResponse> GetTextCompletion(string apiUrl, string apiKey, string prompt, float temperature, int maxTokens = 256)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -117,10 +116,10 @@ public class TextCompletionProvider : ITextCompletion
|
|||
RequestUri = new Uri(apiUrl),
|
||||
Content = new StringContent(data, Encoding.UTF8, MediaTypeNames.Application.Json)
|
||||
};
|
||||
var rawResponse = await httpClient.SendAsync(httpRequest);
|
||||
rawResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var responseStr = await rawResponse.Content.ReadAsStringAsync();
|
||||
var httpResponse = await httpClient.SendAsync(httpRequest);
|
||||
httpResponse.EnsureSuccessStatusCode();
|
||||
var responseStr = await httpResponse.Content.ReadAsStringAsync();
|
||||
var response = JsonSerializer.Deserialize<TextCompletionResponse>(responseStr, _jsonOptions);
|
||||
return response;
|
||||
}
|
||||
|
|
@ -131,7 +130,7 @@ public class TextCompletionProvider : ITextCompletion
|
|||
}
|
||||
}
|
||||
|
||||
protected virtual string BuildApiUrl(LlmModelSetting modelSetting)
|
||||
private string BuildApiUrl(LlmModelSetting modelSetting)
|
||||
{
|
||||
var url = string.Empty;
|
||||
var endpoint = modelSetting.Endpoint.EndsWith("/") ?
|
||||
|
|
@ -141,7 +140,7 @@ public class TextCompletionProvider : ITextCompletion
|
|||
return url;
|
||||
}
|
||||
|
||||
protected virtual void AddHeader(HttpClient httpClient, string apiKey)
|
||||
private void AddHeader(HttpClient httpClient, string apiKey)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Add("api-key", $"{apiKey}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,4 +14,6 @@ global using BotSharp.Abstraction.MLTasks;
|
|||
global using BotSharp.Abstraction.Agents;
|
||||
global using BotSharp.Abstraction.Files;
|
||||
global using BotSharp.Abstraction.Utilities;
|
||||
global using BotSharp.Abstraction.Files.Models;
|
||||
global using BotSharp.Plugin.AzureOpenAI.Models;
|
||||
global using BotSharp.Plugin.AzureOpenAI.Settings;
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="data\agents\00000000-0000-0000-0000-000000000000\functions\handle_http_request.json" />
|
||||
<None Remove="data\agents\00000000-0000-0000-0000-000000000000\templates\handle_http_request.fn.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\handle_http_request.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\handle_http_request.fn.liquid" />
|
||||
<None Remove="data\agents\87c458fc-ec5f-40ae-8ed6-05dda8a07523\agent.json" />
|
||||
<None Remove="data\agents\87c458fc-ec5f-40ae-8ed6-05dda8a07523\functions.json" />
|
||||
<None Remove="data\agents\87c458fc-ec5f-40ae-8ed6-05dda8a07523\instruction.liquid" />
|
||||
|
|
@ -25,10 +25,10 @@
|
|||
<Content Include="data\agents\87c458fc-ec5f-40ae-8ed6-05dda8a07523\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\00000000-0000-0000-0000-000000000000\functions\handle_http_request.json">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\handle_http_request.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\00000000-0000-0000-0000-000000000000\templates\handle_http_request.fn.liquid">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\handle_http_request.fn.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using BotSharp.Abstraction.Agents;
|
||||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Agents.Settings;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
|
|
@ -8,7 +9,6 @@ namespace BotSharp.Plugin.HttpHandler.Hooks;
|
|||
|
||||
public class HttpHandlerHook : AgentHookBase
|
||||
{
|
||||
private static string UTILITY_ASSISTANT = Guid.Empty.ToString();
|
||||
private static string FUNCTION_NAME = "handle_http_request";
|
||||
|
||||
public override string SelfId => string.Empty;
|
||||
|
|
@ -51,7 +51,7 @@ public class HttpHandlerHook : AgentHookBase
|
|||
private (string, FunctionDef?) GetPromptAndFunction()
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var agent = db.GetAgent(UTILITY_ASSISTANT);
|
||||
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
|
||||
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{FUNCTION_NAME}.fn"))?.Content ?? string.Empty;
|
||||
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(FUNCTION_NAME));
|
||||
return (prompt, loadAttachmentFn);
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class MemVectorDatabase : IVectorDb
|
|||
return texts;
|
||||
}
|
||||
|
||||
public async Task Upsert(string collectionName, int id, float[] vector, string text)
|
||||
public async Task Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload = null)
|
||||
{
|
||||
_vectors[collectionName].Add(new VecRecord
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ namespace BotSharp.Plugin.KnowledgeBase.MemVecDb;
|
|||
|
||||
public class VecRecord
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Id { get; set; }
|
||||
public float[] Vector { get; set; }
|
||||
public string Text { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public partial class KnowledgeService : IKnowledgeService
|
|||
foreach (var line in lines)
|
||||
{
|
||||
var vec = await textEmbedding.GetVectorAsync(line);
|
||||
await db.Upsert("shared", idStart, vec, line);
|
||||
await db.Upsert("shared", idStart.ToString(), vec, line);
|
||||
idStart++;
|
||||
Console.WriteLine($"Saved vector {idStart}/{lines.Count}: {line}\n");
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ public partial class KnowledgeService : IKnowledgeService
|
|||
foreach (var line in lines)
|
||||
{
|
||||
var vec = await textEmbedding.GetVectorAsync(line);
|
||||
await db.Upsert(knowledge.AgentId, idStart, vec, line);
|
||||
await db.Upsert(knowledge.AgentId, idStart.ToString(), vec, line);
|
||||
idStart++;
|
||||
Console.WriteLine($"Saved vector {idStart}/{lines.Count}: {line}\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public class FaissDb : IVectorDb
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Upsert(string collectionName, int id, float[] vector, string text)
|
||||
public Task Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>$(LangVersion)</LangVersion>
|
||||
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
|
||||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenAI" Version="2.0.0-beta.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Plugin.OpenAI.Models;
|
||||
|
||||
public class TextCompletionRequest
|
||||
{
|
||||
[JsonPropertyName("model")]
|
||||
public string Model { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("prompt")]
|
||||
public string Prompt { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("max_tokens")]
|
||||
public int MaxTokens { get; set; } = 256;
|
||||
|
||||
[JsonPropertyName("temperature")]
|
||||
public float Temperature { get; set; } = 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Plugin.OpenAI.Models;
|
||||
|
||||
public class TextCompletionResponse
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonPropertyName("choices")]
|
||||
public IEnumerable<TexCompletionChoice> Choices { get; set; } = new List<TexCompletionChoice>();
|
||||
|
||||
[JsonPropertyName("usage")]
|
||||
public TexCompletionUsage Usage { get; set; }
|
||||
}
|
||||
|
||||
public class TexCompletionChoice
|
||||
{
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
[JsonPropertyName("finish_reason")]
|
||||
public string FinishReason { get; set; }
|
||||
}
|
||||
|
||||
public class TexCompletionUsage
|
||||
{
|
||||
[JsonPropertyName("prompt_tokens")]
|
||||
public int PromptTokens { get; set; }
|
||||
|
||||
[JsonPropertyName("completion_tokens")]
|
||||
public int CompletionTokens { get; set; }
|
||||
|
||||
[JsonPropertyName("total_tokens")]
|
||||
public int TotalTokens { get; set; }
|
||||
}
|
||||
34
src/Plugins/BotSharp.Plugin.OpenAI/OpenAiPlugin.cs
Normal file
34
src/Plugins/BotSharp.Plugin.OpenAI/OpenAiPlugin.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using BotSharp.Abstraction.Plugins;
|
||||
using BotSharp.Abstraction.Settings;
|
||||
using BotSharp.Plugin.OpenAI.Providers.Embedding;
|
||||
using BotSharp.Plugin.OpenAI.Providers.Image;
|
||||
using BotSharp.Plugin.OpenAI.Providers.Text;
|
||||
using BotSharp.Plugin.OpenAI.Providers.Chat;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace BotSharp.Plugin.OpenAI;
|
||||
|
||||
/// <summary>
|
||||
/// OpenAI Service
|
||||
/// </summary>
|
||||
public class OpenAiPlugin : IBotSharpPlugin
|
||||
{
|
||||
public string Id => "a743e90f-7cbc-4e47-b8a0-2f8e44f894c7";
|
||||
public string Name => "OpenAI";
|
||||
public string Description => "OpenAI Service including text generation, text to image and other AI services.";
|
||||
public string IconUrl => "https://logosandtypes.com/wp-content/uploads/2022/07/openai.svg";
|
||||
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
services.AddScoped(provider =>
|
||||
{
|
||||
var settingService = provider.GetRequiredService<ISettingService>();
|
||||
return settingService.Bind<OpenAiSettings>("OpenAi");
|
||||
});
|
||||
|
||||
services.AddScoped<ITextCompletion, TextCompletionProvider>();
|
||||
services.AddScoped<IChatCompletion, ChatCompletionProvider>();
|
||||
services.AddScoped<IImageGeneration, ImageGenerationProvider>();
|
||||
services.AddScoped<ITextEmbedding, TextEmbeddingProvider>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,376 @@
|
|||
using OpenAI.Chat;
|
||||
|
||||
namespace BotSharp.Plugin.OpenAI.Providers.Chat;
|
||||
|
||||
public class ChatCompletionProvider : IChatCompletion
|
||||
{
|
||||
protected readonly OpenAiSettings _settings;
|
||||
protected readonly IServiceProvider _services;
|
||||
protected readonly ILogger<ChatCompletionProvider> _logger;
|
||||
|
||||
protected string _model;
|
||||
|
||||
public virtual string Provider => "openai";
|
||||
|
||||
public ChatCompletionProvider(
|
||||
OpenAiSettings settings,
|
||||
ILogger<ChatCompletionProvider> logger,
|
||||
IServiceProvider services)
|
||||
{
|
||||
_settings = settings;
|
||||
_logger = logger;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> GetChatCompletions(Agent agent, List<RoleDialogModel> conversations)
|
||||
{
|
||||
var contentHooks = _services.GetServices<IContentGeneratingHook>().ToList();
|
||||
|
||||
// Before chat completion hook
|
||||
foreach (var hook in contentHooks)
|
||||
{
|
||||
await hook.BeforeGenerating(agent, conversations);
|
||||
}
|
||||
|
||||
var client = ProviderHelper.GetClient(Provider, _model, _services);
|
||||
var chatClient = client.GetChatClient(_model);
|
||||
var (prompt, messages, options) = PrepareOptions(agent, conversations);
|
||||
|
||||
var response = chatClient.CompleteChat(messages, options);
|
||||
var value = response.Value;
|
||||
var reason = value.FinishReason;
|
||||
var content = value.Content;
|
||||
var text = content.FirstOrDefault()?.Text ?? string.Empty;
|
||||
|
||||
RoleDialogModel responseMessage;
|
||||
if (reason == ChatFinishReason.FunctionCall)
|
||||
{
|
||||
responseMessage = new RoleDialogModel(AgentRole.Function, text)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
MessageId = conversations.Last().MessageId,
|
||||
FunctionName = value.FunctionCall.FunctionName,
|
||||
FunctionArgs = value.FunctionCall.FunctionArguments
|
||||
};
|
||||
|
||||
// Somethings LLM will generate a function name with agent name.
|
||||
if (!string.IsNullOrEmpty(responseMessage.FunctionName))
|
||||
{
|
||||
responseMessage.FunctionName = responseMessage.FunctionName.Split('.').Last();
|
||||
}
|
||||
}
|
||||
else if (reason == ChatFinishReason.ToolCalls)
|
||||
{
|
||||
var toolCall = value.ToolCalls.FirstOrDefault();
|
||||
responseMessage = new RoleDialogModel(AgentRole.Function, text)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
MessageId = conversations.Last().MessageId,
|
||||
FunctionName = toolCall?.FunctionName,
|
||||
FunctionArgs = toolCall?.FunctionArguments
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
responseMessage = new RoleDialogModel(AgentRole.Assistant, text)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
MessageId = conversations.Last().MessageId
|
||||
};
|
||||
}
|
||||
|
||||
// After chat completion hook
|
||||
foreach (var hook in contentHooks)
|
||||
{
|
||||
await hook.AfterGenerated(responseMessage, new TokenStatsModel
|
||||
{
|
||||
Prompt = prompt,
|
||||
Provider = Provider,
|
||||
Model = _model,
|
||||
PromptCount = response.Value.Usage.InputTokens,
|
||||
CompletionCount = response.Value.Usage.OutputTokens
|
||||
});
|
||||
}
|
||||
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
public async Task<bool> GetChatCompletionsAsync(Agent agent,
|
||||
List<RoleDialogModel> conversations,
|
||||
Func<RoleDialogModel, Task> onMessageReceived,
|
||||
Func<RoleDialogModel, Task> onFunctionExecuting)
|
||||
{
|
||||
var hooks = _services.GetServices<IContentGeneratingHook>().ToList();
|
||||
|
||||
// Before chat completion hook
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.BeforeGenerating(agent, conversations);
|
||||
}
|
||||
|
||||
var client = ProviderHelper.GetClient(Provider, _model, _services);
|
||||
var chatClient = client.GetChatClient(_model);
|
||||
var (prompt, messages, options) = PrepareOptions(agent, conversations);
|
||||
|
||||
var response = await chatClient.CompleteChatAsync(messages, options);
|
||||
var value = response.Value;
|
||||
var reason = value.FinishReason;
|
||||
var content = value.Content;
|
||||
var text = content.FirstOrDefault()?.Text ?? string.Empty;
|
||||
|
||||
var msg = new RoleDialogModel(AgentRole.Assistant, text)
|
||||
{
|
||||
CurrentAgentId = agent.Id
|
||||
};
|
||||
|
||||
// After chat completion hook
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.AfterGenerated(msg, new TokenStatsModel
|
||||
{
|
||||
Prompt = prompt,
|
||||
Provider = Provider,
|
||||
Model = _model,
|
||||
PromptCount = response.Value.Usage.InputTokens,
|
||||
CompletionCount = response.Value.Usage.OutputTokens
|
||||
});
|
||||
}
|
||||
|
||||
if (reason == ChatFinishReason.FunctionCall)
|
||||
{
|
||||
_logger.LogInformation($"[{agent.Name}]: {value.FunctionCall.FunctionName}({value.FunctionCall.FunctionArguments})");
|
||||
|
||||
var funcContextIn = new RoleDialogModel(AgentRole.Function, text)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
FunctionName = value.FunctionCall?.FunctionName,
|
||||
FunctionArgs = value.FunctionCall?.FunctionArguments
|
||||
};
|
||||
|
||||
// Somethings LLM will generate a function name with agent name.
|
||||
if (!string.IsNullOrEmpty(funcContextIn.FunctionName))
|
||||
{
|
||||
funcContextIn.FunctionName = funcContextIn.FunctionName.Split('.').Last();
|
||||
}
|
||||
|
||||
// Execute functions
|
||||
await onFunctionExecuting(funcContextIn);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Text response received
|
||||
await onMessageReceived(msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> GetChatCompletionsStreamingAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived)
|
||||
{
|
||||
var client = ProviderHelper.GetClient(Provider, _model, _services);
|
||||
var chatClient = client.GetChatClient(_model);
|
||||
var (prompt, messages, options) = PrepareOptions(agent, conversations);
|
||||
|
||||
var response = chatClient.CompleteChatStreamingAsync(messages, options);
|
||||
|
||||
await foreach (var choice in response)
|
||||
{
|
||||
if (choice.FinishReason == ChatFinishReason.FunctionCall)
|
||||
{
|
||||
Console.Write(choice.FunctionCallUpdate?.FunctionArgumentsUpdate);
|
||||
|
||||
await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, choice.FunctionCallUpdate?.FunctionArgumentsUpdate));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (choice.ContentUpdate.IsNullOrEmpty()) continue;
|
||||
|
||||
_logger.LogInformation(choice.ContentUpdate[0]?.Text);
|
||||
|
||||
await onMessageReceived(new RoleDialogModel(choice.Role.ToString(), choice.ContentUpdate[0]?.Text ?? string.Empty));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected (string, IEnumerable<ChatMessage>, ChatCompletionOptions) PrepareOptions(Agent agent, List<RoleDialogModel> conversations)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var settingsService = _services.GetRequiredService<ILlmProviderService>();
|
||||
var settings = settingsService.GetSetting(Provider, _model);
|
||||
var allowMultiModal = settings != null && settings.MultiModal;
|
||||
|
||||
var messages = new List<ChatMessage>();
|
||||
|
||||
var temperature = float.Parse(state.GetState("temperature", "0.0"));
|
||||
var maxTokens = int.Parse(state.GetState("max_tokens", "1024"));
|
||||
var options = new ChatCompletionOptions()
|
||||
{
|
||||
Temperature = temperature,
|
||||
MaxTokens = maxTokens
|
||||
};
|
||||
|
||||
foreach (var function in agent.Functions)
|
||||
{
|
||||
if (!agentService.RenderFunction(agent, function)) continue;
|
||||
|
||||
var property = agentService.RenderFunctionProperty(agent, function);
|
||||
|
||||
options.Tools.Add(ChatTool.CreateFunctionTool(
|
||||
functionName: function.Name,
|
||||
functionDescription: function.Description,
|
||||
functionParameters: BinaryData.FromObjectAsJson(property)));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(agent.Instruction))
|
||||
{
|
||||
var instruction = agentService.RenderedInstruction(agent);
|
||||
messages.Add(new SystemChatMessage(instruction));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(agent.Knowledges))
|
||||
{
|
||||
messages.Add(new SystemChatMessage(agent.Knowledges));
|
||||
}
|
||||
|
||||
var samples = ProviderHelper.GetChatSamples(agent.Samples);
|
||||
foreach (var sample in samples)
|
||||
{
|
||||
messages.Add(sample.Role == AgentRole.User ? new UserChatMessage(sample.Content) : new AssistantChatMessage(sample.Content));
|
||||
}
|
||||
|
||||
foreach (var message in conversations)
|
||||
{
|
||||
if (message.Role == AgentRole.Function)
|
||||
{
|
||||
messages.Add(new AssistantChatMessage(string.Empty)
|
||||
{
|
||||
FunctionCall = new ChatFunctionCall(message.FunctionName, message.FunctionArgs ?? string.Empty)
|
||||
});
|
||||
|
||||
messages.Add(new FunctionChatMessage(message.FunctionName, message.Content));
|
||||
}
|
||||
else if (message.Role == AgentRole.User)
|
||||
{
|
||||
var text = !string.IsNullOrWhiteSpace(message.Payload) ? message.Payload : message.Content;
|
||||
var textPart = ChatMessageContentPart.CreateTextMessageContentPart(text);
|
||||
var chat = new UserChatMessage(textPart)
|
||||
{
|
||||
ParticipantName = message.FunctionName
|
||||
};
|
||||
|
||||
if (allowMultiModal)
|
||||
{
|
||||
if (!message.Files.IsNullOrEmpty())
|
||||
{
|
||||
foreach (var file in message.Files)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(file.FileUrl))
|
||||
{
|
||||
var uri = new Uri(file.FileUrl);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(uri, ImageChatMessageContentPartDetail.Low);
|
||||
chat = new UserChatMessage(textPart, contentPart) { ParticipantName = message.FunctionName };
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileData))
|
||||
{
|
||||
var (contentType, bytes) = fileService.GetFileInfoFromData(file.FileData);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(BinaryData.FromBytes(bytes), contentType, ImageChatMessageContentPartDetail.Low);
|
||||
chat = new UserChatMessage(textPart, contentPart) { ParticipantName = message.FunctionName };
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
|
||||
{
|
||||
var contentType = fileService.GetFileContentType(file.FileStorageUrl);
|
||||
using var stream = File.OpenRead(file.FileStorageUrl);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(BinaryData.FromStream(stream), contentType, ImageChatMessageContentPartDetail.Low);
|
||||
chat = new UserChatMessage(textPart, contentPart) { ParticipantName = message.FunctionName };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
messages.Add(chat);
|
||||
}
|
||||
else if (message.Role == AgentRole.Assistant)
|
||||
{
|
||||
messages.Add(new AssistantChatMessage(message.Content));
|
||||
}
|
||||
}
|
||||
|
||||
var prompt = GetPrompt(messages, options);
|
||||
return (prompt, messages, options);
|
||||
}
|
||||
|
||||
|
||||
private string GetPrompt(IEnumerable<ChatMessage> messages, ChatCompletionOptions options)
|
||||
{
|
||||
var prompt = string.Empty;
|
||||
|
||||
if (!messages.IsNullOrEmpty())
|
||||
{
|
||||
// System instruction
|
||||
var verbose = string.Join("\r\n", messages
|
||||
.Select(x => x as SystemChatMessage)
|
||||
.Where(x => x != null)
|
||||
.Select(x =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(x.ParticipantName))
|
||||
{
|
||||
// To display Agent name in log
|
||||
return $"[{x.ParticipantName}]: {x.Content.FirstOrDefault()?.Text ?? string.Empty}";
|
||||
}
|
||||
return $"{AgentRole.System}: {x.Content.FirstOrDefault()?.Text ?? string.Empty}";
|
||||
}));
|
||||
prompt += $"{verbose}\r\n";
|
||||
|
||||
prompt += "\r\n[CONVERSATION]";
|
||||
verbose = string.Join("\r\n", messages
|
||||
.Where(x => x as SystemChatMessage == null)
|
||||
.Select(x =>
|
||||
{
|
||||
var fnMessage = x as FunctionChatMessage;
|
||||
if (fnMessage != null)
|
||||
{
|
||||
return $"{AgentRole.Function}: {fnMessage.Content.FirstOrDefault()?.Text ?? string.Empty}";
|
||||
}
|
||||
|
||||
var userMessage = x as UserChatMessage;
|
||||
if (userMessage != null)
|
||||
{
|
||||
var content = x.Content.FirstOrDefault()?.Text ?? string.Empty;
|
||||
return !string.IsNullOrEmpty(userMessage.ParticipantName) && userMessage.ParticipantName != "route_to_agent" ?
|
||||
$"{userMessage.ParticipantName}: {content}" :
|
||||
$"{AgentRole.User}: {content}";
|
||||
}
|
||||
|
||||
var assistMessage = x as AssistantChatMessage;
|
||||
if (assistMessage != null)
|
||||
{
|
||||
return assistMessage.FunctionCall != null ?
|
||||
$"{AgentRole.Assistant}: Call function {assistMessage.FunctionCall.FunctionName}({assistMessage.FunctionCall.FunctionArguments})" :
|
||||
$"{AgentRole.Assistant}: {assistMessage.Content.FirstOrDefault()?.Text ?? string.Empty}";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}));
|
||||
prompt += $"\r\n{verbose}\r\n";
|
||||
}
|
||||
|
||||
if (!options.Tools.IsNullOrEmpty())
|
||||
{
|
||||
var functions = string.Join("\r\n", options.Tools.Select(fn =>
|
||||
{
|
||||
return $"\r\n{fn.FunctionName}: {fn.FunctionDescription}\r\n{fn.FunctionParameters}";
|
||||
}));
|
||||
prompt += $"\r\n[FUNCTIONS]{functions}\r\n";
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
public void SetModelName(string model)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
using OpenAI.Embeddings;
|
||||
|
||||
namespace BotSharp.Plugin.OpenAI.Providers.Embedding;
|
||||
|
||||
public class TextEmbeddingProvider : ITextEmbedding
|
||||
{
|
||||
protected readonly OpenAiSettings _settings;
|
||||
protected readonly IServiceProvider _services;
|
||||
protected readonly ILogger<TextEmbeddingProvider> _logger;
|
||||
|
||||
private const int DEFAULT_DIMENSION = 1536;
|
||||
protected string _model;
|
||||
|
||||
public virtual string Provider => "openai";
|
||||
|
||||
public int Dimension { get; set; }
|
||||
|
||||
public TextEmbeddingProvider(
|
||||
OpenAiSettings settings,
|
||||
ILogger<TextEmbeddingProvider> logger,
|
||||
IServiceProvider services)
|
||||
{
|
||||
_settings = settings;
|
||||
_logger = logger;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task<float[]> GetVectorAsync(string text)
|
||||
{
|
||||
var client = ProviderHelper.GetClient(Provider, _model, _services);
|
||||
var embeddingClient = client.GetEmbeddingClient(_model);
|
||||
var options = PrepareOptions();
|
||||
var response = await embeddingClient.GenerateEmbeddingAsync(text, options);
|
||||
var value = response.Value;
|
||||
return value.Vector.ToArray();
|
||||
}
|
||||
|
||||
public async Task<List<float[]>> GetVectorsAsync(List<string> texts)
|
||||
{
|
||||
var client = ProviderHelper.GetClient(Provider, _model, _services);
|
||||
var embeddingClient = client.GetEmbeddingClient(_model);
|
||||
var options = PrepareOptions();
|
||||
var response = await embeddingClient.GenerateEmbeddingsAsync(texts, options);
|
||||
var value = response.Value;
|
||||
return value.Select(x => x.Vector.ToArray()).ToList();
|
||||
}
|
||||
|
||||
public void SetModelName(string model)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
|
||||
private EmbeddingGenerationOptions PrepareOptions()
|
||||
{
|
||||
return new EmbeddingGenerationOptions
|
||||
{
|
||||
Dimensions = GetDimension()
|
||||
};
|
||||
}
|
||||
|
||||
private int GetDimension()
|
||||
{
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var stateDimension = state.GetState("embedding_dimension");
|
||||
var defaultDimension = Dimension > 0 ? Dimension : DEFAULT_DIMENSION;
|
||||
|
||||
if (int.TryParse(stateDimension, out var dimension))
|
||||
{
|
||||
return dimension > 0 ? dimension : defaultDimension;
|
||||
}
|
||||
return defaultDimension;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
using OpenAI.Images;
|
||||
|
||||
namespace BotSharp.Plugin.OpenAI.Providers.Image;
|
||||
|
||||
public class ImageGenerationProvider : IImageGeneration
|
||||
{
|
||||
protected readonly OpenAiSettings _settings;
|
||||
protected readonly IServiceProvider _services;
|
||||
protected readonly ILogger _logger;
|
||||
|
||||
private const int DEFAULT_IMAGE_COUNT = 1;
|
||||
private const int IMAGE_COUNT_LIMIT = 5;
|
||||
|
||||
protected string _model;
|
||||
|
||||
public virtual string Provider => "openai";
|
||||
|
||||
public ImageGenerationProvider(
|
||||
OpenAiSettings settings,
|
||||
ILogger<ImageGenerationProvider> logger,
|
||||
IServiceProvider services)
|
||||
{
|
||||
_settings = settings;
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public async Task<RoleDialogModel> GetImageGeneration(Agent agent, List<RoleDialogModel> conversations)
|
||||
{
|
||||
var contentHooks = _services.GetServices<IContentGeneratingHook>().ToList();
|
||||
|
||||
// Before
|
||||
foreach (var hook in contentHooks)
|
||||
{
|
||||
await hook.BeforeGenerating(agent, conversations);
|
||||
}
|
||||
|
||||
var client = ProviderHelper.GetClient(Provider, _model, _services);
|
||||
var (prompt, imageCount, options) = PrepareOptions(conversations);
|
||||
var imageClient = client.GetImageClient(_model);
|
||||
|
||||
var response = imageClient.GenerateImages(prompt, imageCount, options);
|
||||
var values = response.Value;
|
||||
|
||||
var images = new List<ImageGeneration>();
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (value == null) continue;
|
||||
|
||||
var image = new ImageGeneration { Description = value?.RevisedPrompt ?? string.Empty };
|
||||
if (options.ResponseFormat == GeneratedImageFormat.Uri)
|
||||
{
|
||||
image.ImageUrl = value?.ImageUri?.AbsoluteUri ?? string.Empty;
|
||||
}
|
||||
else if (options.ResponseFormat == GeneratedImageFormat.Bytes)
|
||||
{
|
||||
var base64Str = string.Empty;
|
||||
var bytes = value?.ImageBytes?.ToArray();
|
||||
if (!bytes.IsNullOrEmpty())
|
||||
{
|
||||
base64Str = Convert.ToBase64String(bytes);
|
||||
}
|
||||
image.ImageData = base64Str;
|
||||
}
|
||||
|
||||
images.Add(image);
|
||||
}
|
||||
|
||||
var content = string.Join("\r\n", images.Select(x => x.Description));
|
||||
var responseMessage = new RoleDialogModel(AgentRole.Assistant, content)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
|
||||
GeneratedImages = images
|
||||
};
|
||||
|
||||
// After
|
||||
foreach (var hook in contentHooks)
|
||||
{
|
||||
await hook.AfterGenerated(responseMessage, new TokenStatsModel
|
||||
{
|
||||
Prompt = prompt,
|
||||
Provider = Provider,
|
||||
Model = _model,
|
||||
PromptCount = prompt.Split(' ', StringSplitOptions.RemoveEmptyEntries).Count(),
|
||||
CompletionCount = content.Split(' ', StringSplitOptions.RemoveEmptyEntries).Count()
|
||||
});
|
||||
}
|
||||
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
private (string, int, ImageGenerationOptions) PrepareOptions(List<RoleDialogModel> conversations)
|
||||
{
|
||||
var prompt = conversations.LastOrDefault()?.Payload ?? conversations.LastOrDefault()?.Content ?? string.Empty;
|
||||
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var size = state.GetState("image_size");
|
||||
var quality = state.GetState("image_quality");
|
||||
var style = state.GetState("image_style");
|
||||
var format = state.GetState("image_format");
|
||||
var count = GetImageCount(state.GetState("image_count", "1"));
|
||||
|
||||
var options = new ImageGenerationOptions
|
||||
{
|
||||
Size = GetImageSize(size),
|
||||
Quality = GetImageQuality(quality),
|
||||
Style = GetImageStyle(style),
|
||||
ResponseFormat = GetImageFormat(format)
|
||||
};
|
||||
return (prompt, count, options);
|
||||
}
|
||||
|
||||
public void SetModelName(string model)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
|
||||
private GeneratedImageSize GetImageSize(string size)
|
||||
{
|
||||
var value = !string.IsNullOrEmpty(size) ? size : "1024x1024";
|
||||
|
||||
GeneratedImageSize retSize;
|
||||
switch (value)
|
||||
{
|
||||
case "256x256":
|
||||
retSize = GeneratedImageSize.W256xH256;
|
||||
break;
|
||||
case "512x512":
|
||||
retSize = GeneratedImageSize.W512xH512;
|
||||
break;
|
||||
case "1024x1024":
|
||||
retSize = GeneratedImageSize.W1024xH1024;
|
||||
break;
|
||||
case "1024x1792":
|
||||
retSize = GeneratedImageSize.W1024xH1792;
|
||||
break;
|
||||
case "1792x1024":
|
||||
retSize = GeneratedImageSize.W1792xH1024;
|
||||
break;
|
||||
default:
|
||||
retSize = GeneratedImageSize.W1024xH1024;
|
||||
break;
|
||||
}
|
||||
|
||||
return retSize;
|
||||
}
|
||||
|
||||
private GeneratedImageQuality GetImageQuality(string quality)
|
||||
{
|
||||
var value = !string.IsNullOrEmpty(quality) ? quality : "standard";
|
||||
|
||||
GeneratedImageQuality retQuality;
|
||||
switch (value)
|
||||
{
|
||||
case "standard":
|
||||
retQuality = GeneratedImageQuality.Standard;
|
||||
break;
|
||||
case "hd":
|
||||
retQuality = GeneratedImageQuality.High;
|
||||
break;
|
||||
default:
|
||||
retQuality = GeneratedImageQuality.Standard;
|
||||
break;
|
||||
}
|
||||
|
||||
return retQuality;
|
||||
}
|
||||
|
||||
private GeneratedImageStyle GetImageStyle(string style)
|
||||
{
|
||||
var value = !string.IsNullOrEmpty(style) ? style : "natural";
|
||||
|
||||
GeneratedImageStyle retStyle;
|
||||
switch (value)
|
||||
{
|
||||
case "natural":
|
||||
retStyle = GeneratedImageStyle.Natural;
|
||||
break;
|
||||
case "vivid":
|
||||
retStyle = GeneratedImageStyle.Vivid;
|
||||
break;
|
||||
default:
|
||||
retStyle = GeneratedImageStyle.Natural;
|
||||
break;
|
||||
}
|
||||
|
||||
return retStyle;
|
||||
}
|
||||
|
||||
private GeneratedImageFormat GetImageFormat(string format)
|
||||
{
|
||||
var value = !string.IsNullOrEmpty(format) ? format : "uri";
|
||||
|
||||
GeneratedImageFormat retFormat;
|
||||
switch (value)
|
||||
{
|
||||
case "uri":
|
||||
retFormat = GeneratedImageFormat.Uri;
|
||||
break;
|
||||
case "bytes":
|
||||
retFormat = GeneratedImageFormat.Bytes;
|
||||
break;
|
||||
default:
|
||||
retFormat = GeneratedImageFormat.Uri;
|
||||
break;
|
||||
}
|
||||
|
||||
return retFormat;
|
||||
}
|
||||
|
||||
private int GetImageCount(string count)
|
||||
{
|
||||
if (!int.TryParse(count, out var retCount))
|
||||
{
|
||||
return DEFAULT_IMAGE_COUNT;
|
||||
}
|
||||
|
||||
return retCount > 0 && retCount <= IMAGE_COUNT_LIMIT ? retCount : DEFAULT_IMAGE_COUNT;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using OpenAI;
|
||||
using System.ClientModel;
|
||||
|
||||
namespace BotSharp.Plugin.OpenAI.Providers;
|
||||
|
||||
public class ProviderHelper
|
||||
{
|
||||
public static OpenAIClient GetClient(string provider, string model, IServiceProvider services)
|
||||
{
|
||||
var settingsService = services.GetRequiredService<ILlmProviderService>();
|
||||
var settings = settingsService.GetSetting(provider, model);
|
||||
var client = new OpenAIClient(new ApiKeyCredential(settings.ApiKey));
|
||||
return client;
|
||||
}
|
||||
|
||||
public static List<RoleDialogModel> GetChatSamples(List<string> lines)
|
||||
{
|
||||
var samples = new List<RoleDialogModel>();
|
||||
|
||||
for (int i = 0; i < lines.Count; 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
using BotSharp.Abstraction.MLTasks.Settings;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Plugin.OpenAI.Providers.Text;
|
||||
|
||||
public class TextCompletionProvider : ITextCompletion
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<TextCompletionProvider> _logger;
|
||||
private readonly OpenAiSettings _settings;
|
||||
protected string _model;
|
||||
|
||||
protected readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = true,
|
||||
AllowTrailingCommas = true,
|
||||
};
|
||||
|
||||
public virtual string Provider => "openai";
|
||||
|
||||
public TextCompletionProvider(
|
||||
OpenAiSettings settings,
|
||||
ILogger<TextCompletionProvider> logger,
|
||||
IServiceProvider services)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public async Task<string> GetCompletion(string text, string agentId, string messageId)
|
||||
{
|
||||
var contentHooks = _services.GetServices<IContentGeneratingHook>().ToList();
|
||||
|
||||
// Before chat completion hook
|
||||
var agent = new Agent()
|
||||
{
|
||||
Id = agentId,
|
||||
};
|
||||
var message = new RoleDialogModel(AgentRole.User, text)
|
||||
{
|
||||
CurrentAgentId = agentId,
|
||||
MessageId = messageId
|
||||
};
|
||||
|
||||
Task.WaitAll(contentHooks.Select(hook =>
|
||||
hook.BeforeGenerating(agent,
|
||||
new List<RoleDialogModel>
|
||||
{
|
||||
message
|
||||
})).ToArray());
|
||||
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var temperature = float.Parse(state.GetState("temperature", "0.0"));
|
||||
|
||||
var settingsService = _services.GetRequiredService<ILlmProviderService>();
|
||||
var modelSetting = settingsService.GetSetting(Provider, _model);
|
||||
var apiUrl = BuildApiUrl(modelSetting);
|
||||
var apiKey = modelSetting.ApiKey;
|
||||
var response = await GetTextCompletion(apiUrl, apiKey, text, temperature);
|
||||
|
||||
// OpenAI
|
||||
var completion = "";
|
||||
foreach (var t in response.Choices)
|
||||
{
|
||||
completion += t?.Text ?? string.Empty;
|
||||
};
|
||||
|
||||
// After chat completion hook
|
||||
var responseMessage = new RoleDialogModel(AgentRole.Assistant, completion)
|
||||
{
|
||||
CurrentAgentId = agentId,
|
||||
MessageId = messageId
|
||||
};
|
||||
Task.WaitAll(contentHooks.Select(hook =>
|
||||
hook.AfterGenerated(responseMessage, new TokenStatsModel
|
||||
{
|
||||
Prompt = text,
|
||||
Provider = Provider,
|
||||
Model = _model,
|
||||
PromptCount = response.Usage?.PromptTokens ?? default,
|
||||
CompletionCount = response.Usage?.CompletionTokens ?? default
|
||||
})).ToArray());
|
||||
|
||||
return completion.Trim();
|
||||
}
|
||||
|
||||
public void SetModelName(string model)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
|
||||
private async Task<TextCompletionResponse> GetTextCompletion(string apiUrl, string apiKey, string prompt, float temperature, int maxTokens = 256)
|
||||
{
|
||||
try
|
||||
{
|
||||
var http = _services.GetRequiredService<IHttpClientFactory>();
|
||||
using var httpClient = http.CreateClient();
|
||||
AddHeader(httpClient, apiKey);
|
||||
|
||||
var request = new TextCompletionRequest
|
||||
{
|
||||
Model = _model,
|
||||
Prompt = prompt,
|
||||
MaxTokens = maxTokens,
|
||||
Temperature = temperature
|
||||
};
|
||||
var data = JsonSerializer.Serialize(request, _jsonOptions);
|
||||
var httpRequest = new HttpRequestMessage
|
||||
{
|
||||
Method = HttpMethod.Post,
|
||||
RequestUri = new Uri(apiUrl),
|
||||
Content = new StringContent(data, Encoding.UTF8, MediaTypeNames.Application.Json)
|
||||
};
|
||||
|
||||
var httpResponse = await httpClient.SendAsync(httpRequest);
|
||||
httpResponse.EnsureSuccessStatusCode();
|
||||
var responseStr = await httpResponse.Content.ReadAsStringAsync();
|
||||
var response = JsonSerializer.Deserialize<TextCompletionResponse>(responseStr, _jsonOptions);
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error when {Provider}-{_model} generating text... {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildApiUrl(LlmModelSetting modelSetting)
|
||||
{
|
||||
var endpoint = modelSetting.Endpoint.EndsWith("/") ?
|
||||
modelSetting.Endpoint.Substring(0, modelSetting.Endpoint.Length - 1) : modelSetting.Endpoint;
|
||||
return endpoint ?? string.Empty;
|
||||
}
|
||||
|
||||
private void AddHeader(HttpClient httpClient, string apiKey)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
namespace BotSharp.Plugin.OpenAI.Settings;
|
||||
|
||||
public class OpenAiSettings
|
||||
{
|
||||
}
|
||||
19
src/Plugins/BotSharp.Plugin.OpenAI/Using.cs
Normal file
19
src/Plugins/BotSharp.Plugin.OpenAI/Using.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
global using System.IO;
|
||||
global using System.Threading.Tasks;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
global using BotSharp.Abstraction.Agents.Enums;
|
||||
global using BotSharp.Abstraction.Agents.Models;
|
||||
global using BotSharp.Abstraction.Conversations;
|
||||
global using BotSharp.Abstraction.Conversations.Models;
|
||||
global using BotSharp.Abstraction.Loggers;
|
||||
global using BotSharp.Abstraction.MLTasks;
|
||||
global using BotSharp.Abstraction.Agents;
|
||||
global using BotSharp.Abstraction.Files;
|
||||
global using BotSharp.Abstraction.Files.Models;
|
||||
global using BotSharp.Abstraction.Utilities;
|
||||
global using BotSharp.Plugin.OpenAI.Models;
|
||||
global using BotSharp.Plugin.OpenAI.Settings;
|
||||
|
|
@ -11,8 +11,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Qdrant.Client" Version="8.0.1" />
|
||||
<PackageReference Include="Qdrant.Client" Version="1.9.0" />
|
||||
<PackageReference Include="Qdrant.Client" Version="1.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ public class QdrantDb : IVectorDb
|
|||
_client = new QdrantClient
|
||||
(
|
||||
host: _setting.Url,
|
||||
https: true,
|
||||
apiKey: _setting.ApiKey
|
||||
);
|
||||
}
|
||||
|
|
@ -41,7 +42,7 @@ public class QdrantDb : IVectorDb
|
|||
public async Task<List<string>> GetCollections()
|
||||
{
|
||||
// List all the collections
|
||||
var collections = await _client.ListCollectionsAsync();
|
||||
var collections = await GetClient().ListCollectionsAsync();
|
||||
return collections.ToList();
|
||||
}
|
||||
|
||||
|
|
@ -56,11 +57,6 @@ public class QdrantDb : IVectorDb
|
|||
Size = (ulong)dim,
|
||||
Distance = Distance.Cosine
|
||||
});
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agentDataDir = agentService.GetAgentDataDir(collectionName);
|
||||
var knowledgePath = Path.Combine(agentDataDir, "knowledge.txt");
|
||||
File.WriteAllLines(knowledgePath, new string[0]);
|
||||
}
|
||||
|
||||
// Get collection info
|
||||
|
|
@ -71,26 +67,29 @@ public class QdrantDb : IVectorDb
|
|||
}
|
||||
}
|
||||
|
||||
public async Task Upsert(string collectionName, int id, float[] vector, string text)
|
||||
public async Task Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload = null)
|
||||
{
|
||||
// Insert vectors
|
||||
await GetClient().UpsertAsync(collectionName, points: new List<PointStruct>
|
||||
var point = new PointStruct()
|
||||
{
|
||||
new PointStruct()
|
||||
Id = new PointId()
|
||||
{
|
||||
Id = new PointId()
|
||||
{
|
||||
Num = (ulong)id,
|
||||
},
|
||||
Vectors = vector
|
||||
}
|
||||
});
|
||||
Uuid = id
|
||||
},
|
||||
Vectors = vector,
|
||||
|
||||
// Store chunks in local file system
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agentDataDir = agentService.GetAgentDataDir(collectionName);
|
||||
var knowledgePath = Path.Combine(agentDataDir, "knowledge.txt");
|
||||
File.AppendAllLines(knowledgePath, new[] { text });
|
||||
Payload = { }
|
||||
};
|
||||
|
||||
foreach (var item in payload)
|
||||
{
|
||||
point.Payload.Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
var result = await GetClient().UpsertAsync(collectionName, points: new List<PointStruct>
|
||||
{
|
||||
point
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<List<string>> Search(string collectionName, float[] vector, int limit = 5)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ namespace BotSharp.Plugin.SemanticKernel
|
|||
|
||||
}
|
||||
|
||||
public async Task Upsert(string collectionName, int id, float[] vector, string text)
|
||||
public async Task Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload = null)
|
||||
{
|
||||
#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
await _memoryStore.UpsertAsync(collectionName, MemoryRecord.LocalRecord(id.ToString(), text, null, vector));
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Playwright" Version="1.44.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.21.0" />
|
||||
<PackageReference Include="Microsoft.Playwright" Version="1.45.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.22.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.61" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -111,9 +111,20 @@ public class PlaywrightInstance : IDisposable
|
|||
e.Headers["content-type"].Contains("application/json") &&
|
||||
e.Request.ResourceType == "fetch")
|
||||
{
|
||||
Serilog.Log.Information($"Response: {e.Url}");
|
||||
var json = await e.JsonAsync();
|
||||
fetched(e.Url.ToLower(), JsonSerializer.Serialize(json));
|
||||
Serilog.Log.Information($"fetched: {e.Url}");
|
||||
JsonElement? json = null;
|
||||
try
|
||||
{
|
||||
json = await e.JsonAsync();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
fetched(e.Url.ToLower(), JsonSerializer.Serialize(json ?? new JsonElement()));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,19 @@ public partial class PlaywrightWebDriver
|
|||
public async Task DoAction(MessageInfo message, ElementActionArgs action, BrowserActionResult result)
|
||||
{
|
||||
var page = _instance.GetPage(message.ContextId);
|
||||
if (string.IsNullOrEmpty(result.Selector))
|
||||
{
|
||||
Serilog.Log.Error($"Selector is not set.");
|
||||
return;
|
||||
}
|
||||
|
||||
ILocator locator = page.Locator(result.Selector);
|
||||
var count = await locator.CountAsync();
|
||||
if (count == 0)
|
||||
{
|
||||
Serilog.Log.Error($"Element not found: {result.Selector}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.Action == BroswerActionEnum.Click)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public partial class PlaywrightWebDriver
|
|||
var page = args.OpenNewTab ? await _instance.NewPage(message.ContextId, fetched: args.OnDataFetched) :
|
||||
_instance.GetPage(message.ContextId);
|
||||
|
||||
Serilog.Log.Information($"goto page: {args.Url}");
|
||||
var response = await page.GotoAsync(args.Url, new PageGotoOptions
|
||||
{
|
||||
Timeout = args.Timeout
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
<ItemGroup Condition="$(SolutionName)==PizzaBot">
|
||||
<PackageReference Include="BotSharp.Logger" Version="$(BotSharpVersion)" />
|
||||
<PackageReference Include="BotSharp.OpenAPI" Version="$(BotSharpVersion)" />
|
||||
<ProjectReference Include="BotSharp.Plugin.OpenAI" Version="$(BotSharpVersion)" />
|
||||
<PackageReference Include="BotSharp.Plugin.AzureOpenAI" Version="$(BotSharpVersion)" />
|
||||
<PackageReference Include="BotSharp.Plugin.GoogleAI" Version="$(BotSharpVersion)" />
|
||||
<PackageReference Include="BotSharp.Plugin.HuggingFace" Version="$(BotSharpVersion)" />
|
||||
|
|
@ -39,6 +40,7 @@
|
|||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.Dashboard\BotSharp.Plugin.Dashboard.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.GoogleAI\BotSharp.Plugin.GoogleAI.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.MongoStorage\BotSharp.Plugin.MongoStorage.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.OpenAI\BotSharp.Plugin.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.AzureOpenAI\BotSharp.Plugin.AzureOpenAI.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.SparkDesk\BotSharp.Plugin.SparkDesk.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.ChatbotUI\BotSharp.Plugin.ChatbotUI.csproj" />
|
||||
|
|
|
|||
|
|
@ -279,6 +279,7 @@
|
|||
"BotSharp.Logger",
|
||||
"BotSharp.Plugin.MongoStorage",
|
||||
"BotSharp.Plugin.Dashboard",
|
||||
"BotSharp.Plugin.OpenAI",
|
||||
"BotSharp.Plugin.AzureOpenAI",
|
||||
"BotSharp.Plugin.AnthropicAI",
|
||||
"BotSharp.Plugin.GoogleAI",
|
||||
|
|
|
|||
Loading…
Reference in a new issue