add open ai plugin

This commit is contained in:
Jicheng Lu 2024-07-09 21:17:36 -05:00
parent 18a08ce58b
commit 97ff19ccc7
27 changed files with 1023 additions and 143 deletions

View file

@ -89,9 +89,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.MetaGLM", "
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.AnthropicAI", "src\Plugins\BotSharp.Plugin.AnthropicAI\BotSharp.Plugin.AnthropicAI.csproj", "{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.AnthropicAI", "src\Plugins\BotSharp.Plugin.AnthropicAI\BotSharp.Plugin.AnthropicAI.csproj", "{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}"
EndProject 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 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution 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|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.ActiveCfg = Release|Any CPU
{E04FBBEF-744E-4EF3-B634-42AD9F8B68B1}.Release|x64.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -417,6 +427,7 @@ Global
{289E25C8-63F1-4D52-9909-207724DB40CB} = {D5293208-2BEF-42FC-A64C-5954F61720BA} {289E25C8-63F1-4D52-9909-207724DB40CB} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
{CCF745F2-0C95-4ED0-983B-507C528B39EA} = {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} {806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
{6507D336-3A4D-41D4-81C0-2B900173A5FE} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}

View file

@ -116,7 +116,7 @@ public class InstructModeController : ControllerBase
try try
{ {
var completion = CompletionProvider.GetImageGeneration(_services, provider: input.Provider ?? "openai", 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() var message = await completion.GetImageGeneration(new Agent()
{ {
Id = Guid.Empty.ToString(), Id = Guid.Empty.ToString(),

View file

@ -14,8 +14,8 @@ namespace BotSharp.Platform.AzureAi;
public class AzureOpenAiPlugin : IBotSharpPlugin public class AzureOpenAiPlugin : IBotSharpPlugin
{ {
public string Id => "65185362-392c-44fd-a023-95a198824436"; public string Id => "65185362-392c-44fd-a023-95a198824436";
public string Name => "OpenAI/ Azure OpenAI"; public string Name => "Azure OpenAI";
public string Description => "OpenAI/ Azure OpenAI Service including text generation, text to image and other AI services."; 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 string IconUrl => "https://nanfor.com/cdn/shop/files/cursos-propios-Azure-openAI.jpg?v=1692877741";
public void RegisterDI(IServiceCollection services, IConfiguration config) public void RegisterDI(IServiceCollection services, IConfiguration config)
@ -27,12 +27,8 @@ public class AzureOpenAiPlugin : IBotSharpPlugin
}); });
services.AddScoped<ITextCompletion, TextCompletionProvider>(); services.AddScoped<ITextCompletion, TextCompletionProvider>();
services.AddScoped<ITextCompletion, OpenAiTextCompletionProvider>();
services.AddScoped<IChatCompletion, ChatCompletionProvider>(); services.AddScoped<IChatCompletion, ChatCompletionProvider>();
services.AddScoped<IChatCompletion, OpenAiChatCompletionProvider>();
services.AddScoped<IImageGeneration, ImageGenerationProvider>(); services.AddScoped<IImageGeneration, ImageGenerationProvider>();
services.AddScoped<IImageGeneration, OpenAiImageGenerationProvider>();
services.AddScoped<ITextEmbedding, TextEmbeddingProvider>(); services.AddScoped<ITextEmbedding, TextEmbeddingProvider>();
services.AddScoped<ITextEmbedding, OpenAiTextEmbeddingProvider>();
} }
} }

View file

@ -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;
}

View file

@ -4,7 +4,6 @@ namespace BotSharp.Plugin.AzureOpenAI.Models;
public class TextCompletionRequest public class TextCompletionRequest
{ {
[JsonPropertyName("prompt")] [JsonPropertyName("prompt")]
public string Prompt { get; set; } = string.Empty; public string Prompt { get; set; } = string.Empty;

View file

@ -12,7 +12,8 @@ public class ChatCompletionProvider : IChatCompletion
public virtual string Provider => "azure-openai"; public virtual string Provider => "azure-openai";
public ChatCompletionProvider(AzureOpenAiSettings settings, public ChatCompletionProvider(
AzureOpenAiSettings settings,
ILogger<ChatCompletionProvider> logger, ILogger<ChatCompletionProvider> logger,
IServiceProvider services) IServiceProvider services)
{ {

View file

@ -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)
{
}
}

View file

@ -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) { }
}

View file

@ -1,4 +1,3 @@
using BotSharp.Abstraction.Files.Models;
using OpenAI.Images; using OpenAI.Images;
namespace BotSharp.Plugin.AzureOpenAI.Providers.Image; namespace BotSharp.Plugin.AzureOpenAI.Providers.Image;
@ -10,7 +9,7 @@ public class ImageGenerationProvider : IImageGeneration
protected readonly ILogger _logger; protected readonly ILogger _logger;
private const int DEFAULT_IMAGE_COUNT = 1; private const int DEFAULT_IMAGE_COUNT = 1;
private const int IMAGE_COUNT_LIMIT = 3; private const int IMAGE_COUNT_LIMIT = 5;
protected string _model; protected string _model;
@ -44,7 +43,6 @@ public class ImageGenerationProvider : IImageGeneration
var response = imageClient.GenerateImages(prompt, imageCount, options); var response = imageClient.GenerateImages(prompt, imageCount, options);
var values = response.Value; var values = response.Value;
var content = string.Empty;
var images = new List<ImageGeneration>(); var images = new List<ImageGeneration>();
foreach (var value in values) foreach (var value in values)
{ {
@ -67,9 +65,9 @@ public class ImageGenerationProvider : IImageGeneration
} }
images.Add(image); 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) var responseMessage = new RoleDialogModel(AgentRole.Assistant, content)
{ {
CurrentAgentId = agent.Id, CurrentAgentId = agent.Id,

View file

@ -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)
{
}
}

View file

@ -1,19 +1,15 @@
using Azure.AI.OpenAI; using Azure.AI.OpenAI;
using Azure; using Azure;
using OpenAI;
using System.ClientModel;
namespace BotSharp.Plugin.AzureOpenAI.Providers; namespace BotSharp.Plugin.AzureOpenAI.Providers;
public class ProviderHelper 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 settingsService = services.GetRequiredService<ILlmProviderService>();
var settings = settingsService.GetSetting(provider, model); var settings = settingsService.GetSetting(provider, model);
var client = provider == "openai" ? var client = new AzureOpenAIClient(new Uri(settings.Endpoint), new AzureKeyCredential(settings.ApiKey));
new OpenAIClient(new ApiKeyCredential(settings.ApiKey)) :
new AzureOpenAIClient(new Uri(settings.Endpoint), new AzureKeyCredential(settings.ApiKey));
return client; return client;
} }

View file

@ -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}");
}
}

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.MLTasks.Settings; using BotSharp.Abstraction.MLTasks.Settings;
using BotSharp.Plugin.AzureOpenAI.Models;
using System.Net.Http; using System.Net.Http;
using System.Net.Mime; using System.Net.Mime;
using System.Text; using System.Text;
@ -96,7 +95,7 @@ public class TextCompletionProvider : ITextCompletion
_model = model; _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 try
{ {
@ -117,10 +116,10 @@ public class TextCompletionProvider : ITextCompletion
RequestUri = new Uri(apiUrl), RequestUri = new Uri(apiUrl),
Content = new StringContent(data, Encoding.UTF8, MediaTypeNames.Application.Json) 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); var response = JsonSerializer.Deserialize<TextCompletionResponse>(responseStr, _jsonOptions);
return response; 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 url = string.Empty;
var endpoint = modelSetting.Endpoint.EndsWith("/") ? var endpoint = modelSetting.Endpoint.EndsWith("/") ?
@ -141,7 +140,7 @@ public class TextCompletionProvider : ITextCompletion
return url; return url;
} }
protected virtual void AddHeader(HttpClient httpClient, string apiKey) private void AddHeader(HttpClient httpClient, string apiKey)
{ {
httpClient.DefaultRequestHeaders.Add("api-key", $"{apiKey}"); httpClient.DefaultRequestHeaders.Add("api-key", $"{apiKey}");
} }

View file

@ -14,4 +14,6 @@ global using BotSharp.Abstraction.MLTasks;
global using BotSharp.Abstraction.Agents; global using BotSharp.Abstraction.Agents;
global using BotSharp.Abstraction.Files; global using BotSharp.Abstraction.Files;
global using BotSharp.Abstraction.Utilities; global using BotSharp.Abstraction.Utilities;
global using BotSharp.Abstraction.Files.Models;
global using BotSharp.Plugin.AzureOpenAI.Models;
global using BotSharp.Plugin.AzureOpenAI.Settings; global using BotSharp.Plugin.AzureOpenAI.Settings;

View file

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

View file

@ -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;
}

View file

@ -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; }
}

View 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>();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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}");
}
}

View file

@ -0,0 +1,5 @@
namespace BotSharp.Plugin.OpenAI.Settings;
public class OpenAiSettings
{
}

View 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;

View file

@ -29,6 +29,7 @@
<ProjectReference Include="..\BotSharp.ServiceDefaults\BotSharp.ServiceDefaults.csproj" /> <ProjectReference Include="..\BotSharp.ServiceDefaults\BotSharp.ServiceDefaults.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.Dashboard\BotSharp.Plugin.Dashboard.csproj" /> <ProjectReference Include="..\Plugins\BotSharp.Plugin.Dashboard\BotSharp.Plugin.Dashboard.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.MetaGLM\BotSharp.Plugin.MetaGLM.csproj" /> <ProjectReference Include="..\Plugins\BotSharp.Plugin.MetaGLM\BotSharp.Plugin.MetaGLM.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.OpenAI\BotSharp.Plugin.OpenAI.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.SparkDesk\BotSharp.Plugin.SparkDesk.csproj" /> <ProjectReference Include="..\Plugins\BotSharp.Plugin.SparkDesk\BotSharp.Plugin.SparkDesk.csproj" />
</ItemGroup> </ItemGroup>

View file

@ -279,6 +279,7 @@
"BotSharp.Logger", "BotSharp.Logger",
"BotSharp.Plugin.MongoStorage", "BotSharp.Plugin.MongoStorage",
"BotSharp.Plugin.Dashboard", "BotSharp.Plugin.Dashboard",
"BotSharp.Plugin.OpenAI",
"BotSharp.Plugin.AzureOpenAI", "BotSharp.Plugin.AzureOpenAI",
"BotSharp.Plugin.AnthropicAI", "BotSharp.Plugin.AnthropicAI",
"BotSharp.Plugin.GoogleAI", "BotSharp.Plugin.GoogleAI",