Merge pull request #846 from iceljc/features/add-deep-seek
Features/add deep seek
This commit is contained in:
commit
b1e501cbfc
11
BotSharp.sln
11
BotSharp.sln
|
|
@ -125,6 +125,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Crontab", "sr
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Rules", "src\Infrastructure\BotSharp.Core.Rules\BotSharp.Core.Rules.csproj", "{AFD64412-4D6A-452E-82A2-79E5D8842E29}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.DeepSeekAI", "src\Plugins\BotSharp.Plugin.DeepSeekAI\BotSharp.Plugin.DeepSeekAI.csproj", "{AF329442-B48E-4B48-A18A-1C869D1BA6F5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -509,6 +511,14 @@ Global
|
|||
{AFD64412-4D6A-452E-82A2-79E5D8842E29}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AFD64412-4D6A-452E-82A2-79E5D8842E29}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{AFD64412-4D6A-452E-82A2-79E5D8842E29}.Release|x64.Build.0 = Release|Any CPU
|
||||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -569,6 +579,7 @@ Global
|
|||
{7DA2DCD0-551B-432E-AA5C-22DDD3ED459B} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
{F812BAAE-5A7D-4DF7-8E71-70696B51C61F} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
|
||||
{AFD64412-4D6A-452E-82A2-79E5D8842E29} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
|
||||
{AF329442-B48E-4B48-A18A-1C869D1BA6F5} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Statistics.Enums;
|
||||
|
||||
public static class StatCategory
|
||||
public static class StatsCategory
|
||||
{
|
||||
public static string LlmCost = "llm-cost";
|
||||
public static string AgentLlmCost = "agent-llm-cost";
|
||||
public static string AgentCall = "agent-call";
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace BotSharp.Abstraction.Statistics.Enums;
|
||||
|
||||
public enum StatsOperation
|
||||
{
|
||||
Add = 1,
|
||||
Subtract = 2,
|
||||
Reset = 3
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ public class BotSharpStats
|
|||
public string Group { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public IDictionary<string, object> Data { get; set; } = new Dictionary<string, object>();
|
||||
public IDictionary<string, double> Data { get; set; } = new Dictionary<string, double>();
|
||||
|
||||
private DateTime innerRecordTime;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.Statistics.Models;
|
||||
|
||||
public class BotSharpStatsInput
|
||||
{
|
||||
public string Category { get; set; }
|
||||
public string Group { get; set; }
|
||||
public List<StatsKeyValuePair> Data { get; set; } = [];
|
||||
public DateTime RecordTime { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using BotSharp.Abstraction.Statistics.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Statistics.Models;
|
||||
|
||||
public class StatsKeyValuePair
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public double Value { get; set; }
|
||||
public StatsOperation Operation { get; set; }
|
||||
|
||||
public StatsKeyValuePair()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public StatsKeyValuePair(string key, double value, StatsOperation operation = StatsOperation.Add)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
Operation = operation;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Key}]: {Value} ({Operation})";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
using BotSharp.Abstraction.Statistics.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Statistics.Services;
|
||||
|
||||
public interface IBotSharpStatService
|
||||
{
|
||||
bool UpdateLlmCost(BotSharpStats stats);
|
||||
bool UpdateAgentCall(BotSharpStats stats);
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using BotSharp.Abstraction.Statistics.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Statistics.Services;
|
||||
|
||||
public interface IBotSharpStatsService
|
||||
{
|
||||
bool UpdateStats(string resourceKey, BotSharpStatsInput input);
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ public class AgentPlugin : IBotSharpPlugin
|
|||
services.AddScoped<ILlmProviderService, LlmProviderService>();
|
||||
services.AddScoped<IAgentService, AgentService>();
|
||||
services.AddScoped<IAgentHook, BasicAgentHook>();
|
||||
services.AddScoped<IBotSharpStatService, BotSharpStatService>();
|
||||
services.AddScoped<IBotSharpStatsService, BotSharpStatsService>();
|
||||
|
||||
services.AddScoped(provider =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -59,21 +59,20 @@ public class TokenStatistics : ITokenStatistics
|
|||
stat.SetState("llm_total_cost", total_cost, isNeedVersion: false, source: StateSource.Application);
|
||||
|
||||
|
||||
var globalStats = _services.GetRequiredService<IBotSharpStatService>();
|
||||
var body = new BotSharpStats
|
||||
var globalStats = _services.GetRequiredService<IBotSharpStatsService>();
|
||||
var body = new BotSharpStatsInput
|
||||
{
|
||||
Category = StatCategory.LlmCost,
|
||||
Group = $"Agent: {message.CurrentAgentId}",
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
{ "prompt_token_count_total", stats.PromptCount },
|
||||
{ "completion_token_count_total", stats.CompletionCount },
|
||||
{ "prompt_cost_total", deltaPromptCost },
|
||||
{ "completion_cost_total", deltaCompletionCost }
|
||||
},
|
||||
RecordTime = DateTime.UtcNow
|
||||
Category = StatsCategory.AgentLlmCost,
|
||||
Group = message.CurrentAgentId,
|
||||
RecordTime = DateTime.UtcNow,
|
||||
Data = [
|
||||
new StatsKeyValuePair("prompt_token_count_total", stats.PromptCount),
|
||||
new StatsKeyValuePair("completion_token_count_total", stats.CompletionCount),
|
||||
new StatsKeyValuePair("prompt_cost_total", deltaPromptCost),
|
||||
new StatsKeyValuePair("completion_cost_total", deltaCompletionCost)
|
||||
]
|
||||
};
|
||||
globalStats.UpdateLlmCost(body);
|
||||
globalStats.UpdateStats("global-llm-cost", body);
|
||||
}
|
||||
|
||||
public void PrintStatistics()
|
||||
|
|
|
|||
|
|
@ -13,11 +13,12 @@ public partial class FileRepository
|
|||
var file = Directory.GetFiles(dir).FirstOrDefault(x => Path.GetFileName(x) == STATS_FILE);
|
||||
if (file == null) return null;
|
||||
|
||||
var time = BuildRecordTime(recordTime);
|
||||
var text = File.ReadAllText(file);
|
||||
var list = JsonSerializer.Deserialize<List<BotSharpStats>>(text, _options);
|
||||
var found = list?.FirstOrDefault(x => x.Category.IsEqualTo(category)
|
||||
&& x.Group.IsEqualTo(group)
|
||||
&& x.RecordTime == recordTime);
|
||||
&& x.RecordTime == time);
|
||||
return found;
|
||||
}
|
||||
|
||||
|
|
@ -38,11 +39,12 @@ public partial class FileRepository
|
|||
}
|
||||
else
|
||||
{
|
||||
var time = BuildRecordTime(body.RecordTime);
|
||||
var text = File.ReadAllText(file);
|
||||
var list = JsonSerializer.Deserialize<List<BotSharpStats>>(text, _options);
|
||||
var found = list?.FirstOrDefault(x => x.Category.IsEqualTo(body.Category)
|
||||
&& x.Group.IsEqualTo(body.Group)
|
||||
&& x.RecordTime == body.RecordTime);
|
||||
&& x.RecordTime == time);
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
|
|
@ -65,4 +67,12 @@ public partial class FileRepository
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Private methods
|
||||
private DateTime BuildRecordTime(DateTime date)
|
||||
{
|
||||
var recordDate = new DateTime(date.Year, date.Month, date.Day, date.Hour, 0, 0);
|
||||
return DateTime.SpecifyKind(recordDate, DateTimeKind.Utc);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,132 +0,0 @@
|
|||
using BotSharp.Abstraction.Infrastructures;
|
||||
using BotSharp.Abstraction.Statistics.Settings;
|
||||
|
||||
namespace BotSharp.Core.Statistics.Services;
|
||||
|
||||
public class BotSharpStatService : IBotSharpStatService
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<BotSharpStatService> _logger;
|
||||
private readonly StatisticsSettings _settings;
|
||||
|
||||
private const string GLOBAL_LLM_COST = "global-llm-cost";
|
||||
private const string GLOBAL_AGENT_CALL = "global-agent-call";
|
||||
private const int TIMEOUT_SECONDS = 5;
|
||||
|
||||
public BotSharpStatService(
|
||||
IServiceProvider services,
|
||||
ILogger<BotSharpStatService> logger,
|
||||
StatisticsSettings settings)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public bool UpdateLlmCost(BotSharpStats stats)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_settings.Enabled) return false;
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var locker = _services.GetRequiredService<IDistributedLocker>();
|
||||
|
||||
var res = locker.Lock(GLOBAL_LLM_COST, () =>
|
||||
{
|
||||
var body = db.GetGlobalStats(stats.Category, stats.Group, stats.RecordTime);
|
||||
if (body == null)
|
||||
{
|
||||
db.SaveGlobalStats(stats);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in stats.Data)
|
||||
{
|
||||
var curValue = item.Value;
|
||||
if (body.Data.TryGetValue(item.Key, out var preValue))
|
||||
{
|
||||
var preValStr = preValue?.ToString();
|
||||
var curValStr = curValue?.ToString();
|
||||
try
|
||||
{
|
||||
if (int.TryParse(preValStr, out var count))
|
||||
{
|
||||
curValue = int.Parse(curValStr ?? "0") + count;
|
||||
}
|
||||
else if (double.TryParse(preValStr, out var num))
|
||||
{
|
||||
curValue = double.Parse(curValStr ?? "0") + num;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
body.Data[item.Key] = curValue;
|
||||
}
|
||||
|
||||
db.SaveGlobalStats(body);
|
||||
}, TIMEOUT_SECONDS);
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error when updating global llm cost stats {stats}. {ex.Message}\r\n{ex.InnerException}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool UpdateAgentCall(BotSharpStats stats)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_settings.Enabled) return false;
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var locker = _services.GetRequiredService<IDistributedLocker>();
|
||||
|
||||
var res = locker.Lock(GLOBAL_AGENT_CALL, () =>
|
||||
{
|
||||
var body = db.GetGlobalStats(stats.Category, stats.Group, stats.RecordTime);
|
||||
if (body == null)
|
||||
{
|
||||
db.SaveGlobalStats(stats);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in stats.Data)
|
||||
{
|
||||
var curValue = item.Value;
|
||||
if (body.Data.TryGetValue(item.Key, out var preValue))
|
||||
{
|
||||
var preValStr = preValue?.ToString();
|
||||
var curValStr = curValue?.ToString();
|
||||
try
|
||||
{
|
||||
if (int.TryParse(preValStr, out var count))
|
||||
{
|
||||
curValue = int.Parse(curValStr ?? "0") + count;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
body.Data[item.Key] = curValue;
|
||||
}
|
||||
|
||||
db.SaveGlobalStats(body);
|
||||
}, TIMEOUT_SECONDS);
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error when updating global agent call stats {stats}. {ex.Message}\r\n{ex.InnerException}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
using BotSharp.Abstraction.Infrastructures;
|
||||
using BotSharp.Abstraction.Statistics.Settings;
|
||||
|
||||
namespace BotSharp.Core.Statistics.Services;
|
||||
|
||||
public class BotSharpStatsService : IBotSharpStatsService
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<BotSharpStatsService> _logger;
|
||||
private readonly StatisticsSettings _settings;
|
||||
|
||||
private const int TIMEOUT_SECONDS = 5;
|
||||
|
||||
public BotSharpStatsService(
|
||||
IServiceProvider services,
|
||||
ILogger<BotSharpStatsService> logger,
|
||||
StatisticsSettings settings)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
|
||||
public bool UpdateStats(string resourceKey, BotSharpStatsInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_settings.Enabled
|
||||
|| string.IsNullOrEmpty(resourceKey)
|
||||
|| input == null
|
||||
|| string.IsNullOrEmpty(input.Category)
|
||||
|| string.IsNullOrEmpty(input.Group))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var locker = _services.GetRequiredService<IDistributedLocker>();
|
||||
var res = locker.Lock(resourceKey, () =>
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var body = db.GetGlobalStats(input.Category, input.Group, input.RecordTime);
|
||||
if (body == null)
|
||||
{
|
||||
var stats = new BotSharpStats
|
||||
{
|
||||
Category = input.Category,
|
||||
Group = input.Group,
|
||||
RecordTime = input.RecordTime,
|
||||
Data = input.Data.ToDictionary(x => x.Key, x => x.Value)
|
||||
};
|
||||
db.SaveGlobalStats(stats);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in input.Data)
|
||||
{
|
||||
var curValue = item.Value;
|
||||
if (body.Data.TryGetValue(item.Key, out var preValue))
|
||||
{
|
||||
switch (item.Operation)
|
||||
{
|
||||
case StatsOperation.Add:
|
||||
preValue += curValue;
|
||||
break;
|
||||
case StatsOperation.Subtract:
|
||||
preValue -= curValue;
|
||||
break;
|
||||
case StatsOperation.Reset:
|
||||
preValue = 0;
|
||||
break;
|
||||
}
|
||||
body.Data[item.Key] = preValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
body.Data[item.Key] = curValue;
|
||||
}
|
||||
}
|
||||
|
||||
db.SaveGlobalStats(body);
|
||||
}, TIMEOUT_SECONDS);
|
||||
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error when updating global stats {input.Category}-{input.Group}. {ex.Message}\r\n{ex.InnerException}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,19 +27,17 @@ public class GlobalStatsConversationHook : ConversationHookBase
|
|||
private void UpdateAgentCall(RoleDialogModel message)
|
||||
{
|
||||
// record agent call
|
||||
var globalStats = _services.GetRequiredService<IBotSharpStatService>();
|
||||
var globalStats = _services.GetRequiredService<IBotSharpStatsService>();
|
||||
|
||||
var body = new BotSharpStats
|
||||
var body = new BotSharpStatsInput
|
||||
{
|
||||
Category = StatCategory.AgentCall,
|
||||
Group = $"Agent: {message.CurrentAgentId}",
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
{ "agent_id", message.CurrentAgentId },
|
||||
{ "agent_call_count", 1 }
|
||||
},
|
||||
Category = StatsCategory.AgentCall,
|
||||
Group = message.CurrentAgentId,
|
||||
Data = [
|
||||
new StatsKeyValuePair("agent_call_count", 1)
|
||||
],
|
||||
RecordTime = DateTime.UtcNow
|
||||
};
|
||||
globalStats.UpdateAgentCall(body);
|
||||
globalStats.UpdateStats("global-agent-call", body);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<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.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
18
src/Plugins/BotSharp.Plugin.DeepSeekAI/DeepSeekAiPlugin.cs
Normal file
18
src/Plugins/BotSharp.Plugin.DeepSeekAI/DeepSeekAiPlugin.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using BotSharp.Abstraction.Plugins;
|
||||
using BotSharp.Plugin.DeepSeek.Providers.Text;
|
||||
using BotSharp.Plugin.DeepSeekAI.Providers.Chat;
|
||||
|
||||
namespace BotSharp.Plugin.DeepSeek;
|
||||
|
||||
public class DeepSeekAiPlugin : IBotSharpPlugin
|
||||
{
|
||||
public string Id => "1f0e73a5-bcaa-44e9-adde-e46cd94d244b";
|
||||
public string Name => "DeepSeek";
|
||||
public string Description => "DeepSeek AI";
|
||||
public string IconUrl => "https://cdn.deepseek.com/logo.png";
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
services.AddScoped<ITextCompletion, TextCompletionProvider>();
|
||||
services.AddScoped<IChatCompletion, ChatCompletionProvider>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,335 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using OpenAI.Chat;
|
||||
using BotSharp.Abstraction.Files;
|
||||
using BotSharp.Plugin.DeepSeek.Providers;
|
||||
|
||||
namespace BotSharp.Plugin.DeepSeekAI.Providers.Chat;
|
||||
|
||||
public class ChatCompletionProvider : IChatCompletion
|
||||
{
|
||||
protected readonly IServiceProvider _services;
|
||||
protected readonly ILogger<ChatCompletionProvider> _logger;
|
||||
|
||||
protected string _model;
|
||||
public virtual string Provider => "deepseek-ai";
|
||||
|
||||
public ChatCompletionProvider(
|
||||
IServiceProvider services,
|
||||
ILogger<ChatCompletionProvider> logger)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
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 || reason == ChatFinishReason.ToolCalls)
|
||||
{
|
||||
var toolCall = value.ToolCalls.FirstOrDefault();
|
||||
responseMessage = new RoleDialogModel(AgentRole.Function, text)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
|
||||
ToolCallId = toolCall?.Id,
|
||||
FunctionName = toolCall?.FunctionName,
|
||||
FunctionArgs = toolCall?.FunctionArguments?.ToString()
|
||||
};
|
||||
|
||||
// Somethings LLM will generate a function name with agent name.
|
||||
if (!string.IsNullOrEmpty(responseMessage.FunctionName))
|
||||
{
|
||||
responseMessage.FunctionName = responseMessage.FunctionName.Split('.').Last();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
responseMessage = new RoleDialogModel(AgentRole.Assistant, text)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
// 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?.InputTokenCount ?? 0,
|
||||
CompletionCount = response.Value?.Usage?.OutputTokenCount ?? 0
|
||||
});
|
||||
}
|
||||
|
||||
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?.InputTokenCount ?? 0,
|
||||
CompletionCount = response.Value?.Usage?.OutputTokenCount ?? 0
|
||||
});
|
||||
}
|
||||
|
||||
if (reason == ChatFinishReason.FunctionCall || reason == ChatFinishReason.ToolCalls)
|
||||
{
|
||||
var toolCall = value.ToolCalls?.FirstOrDefault();
|
||||
_logger.LogInformation($"[{agent.Name}]: {toolCall?.FunctionName}({toolCall?.FunctionArguments})");
|
||||
|
||||
var funcContextIn = new RoleDialogModel(AgentRole.Function, text)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
|
||||
ToolCallId = toolCall?.Id,
|
||||
FunctionName = toolCall?.FunctionName,
|
||||
FunctionArgs = toolCall?.FunctionArguments?.ToString()
|
||||
};
|
||||
|
||||
// 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 || choice.FinishReason == ChatFinishReason.ToolCalls)
|
||||
{
|
||||
var update = choice.ToolCallUpdates?.FirstOrDefault()?.FunctionArgumentsUpdate?.ToString() ?? string.Empty;
|
||||
_logger.LogInformation(update);
|
||||
|
||||
await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, update));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (choice.ContentUpdate.IsNullOrEmpty()) continue;
|
||||
|
||||
_logger.LogInformation(choice.ContentUpdate[0]?.Text);
|
||||
|
||||
await onMessageReceived(new RoleDialogModel(choice.Role?.ToString() ?? ChatMessageRole.Assistant.ToString(), choice.ContentUpdate[0]?.Text ?? string.Empty));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetModelName(string model)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
|
||||
protected (string, IEnumerable<ChatMessage>, ChatCompletionOptions) PrepareOptions(Agent agent, List<RoleDialogModel> conversations)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
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,
|
||||
MaxOutputTokenCount = maxTokens
|
||||
};
|
||||
|
||||
var functions = agent.Functions.Concat(agent.SecondaryFunctions ?? []);
|
||||
foreach (var function in 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) || !agent.SecondaryInstructions.IsNullOrEmpty())
|
||||
{
|
||||
var text = agentService.RenderedInstruction(agent);
|
||||
messages.Add(new SystemChatMessage(text));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(agent.Knowledges))
|
||||
{
|
||||
messages.Add(new SystemChatMessage(agent.Knowledges));
|
||||
}
|
||||
|
||||
var filteredMessages = conversations.Select(x => x).ToList();
|
||||
var firstUserMsgIdx = filteredMessages.FindIndex(x => x.Role == AgentRole.User);
|
||||
if (firstUserMsgIdx > 0)
|
||||
{
|
||||
filteredMessages = filteredMessages.Where((_, idx) => idx >= firstUserMsgIdx).ToList();
|
||||
}
|
||||
|
||||
foreach (var message in filteredMessages)
|
||||
{
|
||||
if (message.Role == AgentRole.Function)
|
||||
{
|
||||
messages.Add(new AssistantChatMessage(new List<ChatToolCall>
|
||||
{
|
||||
ChatToolCall.CreateFunctionToolCall(message.ToolCallId, message.FunctionName, BinaryData.FromString(message.FunctionArgs ?? string.Empty))
|
||||
}));
|
||||
|
||||
messages.Add(new ToolChatMessage(message.ToolCallId, message.Content));
|
||||
}
|
||||
else if (message.Role == AgentRole.User)
|
||||
{
|
||||
var text = !string.IsNullOrWhiteSpace(message.Payload) ? message.Payload : message.Content;
|
||||
messages.Add(new UserChatMessage(text));
|
||||
}
|
||||
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 ToolChatMessage;
|
||||
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)
|
||||
{
|
||||
var toolCall = assistMessage.ToolCalls?.FirstOrDefault();
|
||||
return toolCall != null ?
|
||||
$"{AgentRole.Assistant}: Call function {toolCall?.FunctionName}({toolCall?.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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using OpenAI;
|
||||
using System.ClientModel;
|
||||
|
||||
namespace BotSharp.Plugin.DeepSeek.Providers;
|
||||
|
||||
public static class ProviderHelper
|
||||
{
|
||||
public static OpenAIClient GetClient(string provider, string model, IServiceProvider services)
|
||||
{
|
||||
var settingsService = services.GetRequiredService<ILlmProviderService>();
|
||||
var settings = settingsService.GetSetting(provider, model);
|
||||
var options = !string.IsNullOrEmpty(settings.Endpoint) ?
|
||||
new OpenAIClientOptions { Endpoint = new Uri(settings.Endpoint) } : null;
|
||||
return new OpenAIClient(new ApiKeyCredential(settings.ApiKey), options);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace BotSharp.Plugin.DeepSeek.Providers.Text;
|
||||
|
||||
public class TextCompletionProvider : ITextCompletion
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<TextCompletionProvider> _logger;
|
||||
protected string _model;
|
||||
|
||||
public string Provider => "deepseek-ai";
|
||||
|
||||
public TextCompletionProvider(
|
||||
IServiceProvider services,
|
||||
ILogger<TextCompletionProvider> logger)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<string> GetCompletion(string text, string agentId, string messageId)
|
||||
{
|
||||
var contentHooks = _services.GetServices<IContentGeneratingHook>().ToList();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
|
||||
// Before chat completion hook
|
||||
var agent = new Agent()
|
||||
{
|
||||
Id = agentId,
|
||||
};
|
||||
var message = new RoleDialogModel(AgentRole.User, text)
|
||||
{
|
||||
CurrentAgentId = agentId,
|
||||
MessageId = messageId
|
||||
};
|
||||
|
||||
foreach (var hook in contentHooks)
|
||||
{
|
||||
await hook.BeforeGenerating(agent, new List<RoleDialogModel> { message });
|
||||
}
|
||||
|
||||
var client = ProviderHelper.GetClient(Provider, _model, _services);
|
||||
var chatClient = client.GetChatClient(_model);
|
||||
var options = PrepareOptions();
|
||||
var response = chatClient.CompleteChat([ new UserChatMessage(text) ], options);
|
||||
|
||||
// AI response
|
||||
var content = response.Value?.Content ?? [];
|
||||
var completion = string.Empty;
|
||||
foreach (var t in content)
|
||||
{
|
||||
completion += t?.Text ?? string.Empty;
|
||||
};
|
||||
|
||||
// After chat completion hook
|
||||
var responseMessage = new RoleDialogModel(AgentRole.Assistant, completion)
|
||||
{
|
||||
CurrentAgentId = agentId,
|
||||
MessageId = messageId
|
||||
};
|
||||
|
||||
foreach (var hook in contentHooks)
|
||||
{
|
||||
await hook.AfterGenerated(responseMessage, new TokenStatsModel
|
||||
{
|
||||
Prompt = text,
|
||||
Provider = Provider,
|
||||
Model = _model,
|
||||
PromptCount = response?.Value?.Usage?.InputTokenCount ?? default,
|
||||
CompletionCount = response?.Value?.Usage?.OutputTokenCount ?? default
|
||||
});
|
||||
}
|
||||
|
||||
return completion.Trim();
|
||||
}
|
||||
|
||||
public void SetModelName(string model)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
|
||||
private ChatCompletionOptions PrepareOptions()
|
||||
{
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var temperature = float.Parse(state.GetState("temperature", "0.0"));
|
||||
var maxTokens = int.Parse(state.GetState("max_tokens", "1024"));
|
||||
|
||||
return new ChatCompletionOptions
|
||||
{
|
||||
Temperature = temperature,
|
||||
MaxOutputTokenCount = maxTokens
|
||||
};
|
||||
}
|
||||
}
|
||||
17
src/Plugins/BotSharp.Plugin.DeepSeekAI/Using.cs
Normal file
17
src/Plugins/BotSharp.Plugin.DeepSeekAI/Using.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Text;
|
||||
global using System.Threading.Tasks;
|
||||
global using System.Linq;
|
||||
global using System.Text.Json;
|
||||
global using Microsoft.Extensions.Configuration;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using BotSharp.Abstraction.Conversations.Models;
|
||||
global using BotSharp.Abstraction.Agents.Models;
|
||||
global using BotSharp.Abstraction.MLTasks;
|
||||
global using BotSharp.Abstraction.Agents;
|
||||
global using BotSharp.Abstraction.Agents.Enums;
|
||||
global using BotSharp.Abstraction.Conversations;
|
||||
global using BotSharp.Abstraction.Loggers;
|
||||
global using BotSharp.Abstraction.Functions.Models;
|
||||
global using BotSharp.Abstraction.Utilities;
|
||||
|
|
@ -4,6 +4,6 @@ public class GlobalStatisticsDocument : MongoBase
|
|||
{
|
||||
public string Category { get; set; }
|
||||
public string Group { get; set; }
|
||||
public IDictionary<string, object> Data { get; set; } = new Dictionary<string, object>();
|
||||
public IDictionary<string, double> Data { get; set; } = new Dictionary<string, double>();
|
||||
public DateTime RecordTime { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
using BotSharp.Abstraction.Files.Utilities;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
using OpenAI.Chat;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace BotSharp.Plugin.OpenAI.Providers.Chat;
|
||||
|
||||
|
|
@ -254,10 +252,10 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
{
|
||||
messages.Add(new AssistantChatMessage(new List<ChatToolCall>
|
||||
{
|
||||
ChatToolCall.CreateFunctionToolCall(message.FunctionName, message.FunctionName, BinaryData.FromString(message.FunctionArgs ?? string.Empty))
|
||||
ChatToolCall.CreateFunctionToolCall(message.ToolCallId, message.FunctionName, BinaryData.FromString(message.FunctionArgs ?? string.Empty))
|
||||
}));
|
||||
|
||||
messages.Add(new ToolChatMessage(message.FunctionName, message.Content));
|
||||
messages.Add(new ToolChatMessage(message.ToolCallId, message.Content));
|
||||
}
|
||||
else if (message.Role == AgentRole.User)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
<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.DeepSeekAI\BotSharp.Plugin.DeepSeekAI.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.ChatbotUI\BotSharp.Plugin.ChatbotUI.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.HuggingFace\BotSharp.Plugin.HuggingFace.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.KnowledgeBase\BotSharp.Plugin.KnowledgeBase.csproj" />
|
||||
|
|
|
|||
|
|
@ -348,6 +348,7 @@
|
|||
"BotSharp.Plugin.AnthropicAI",
|
||||
"BotSharp.Plugin.GoogleAI",
|
||||
"BotSharp.Plugin.MetaAI",
|
||||
"BotSharp.Plugin.DeepSeekAI",
|
||||
"BotSharp.Plugin.MetaMessenger",
|
||||
"BotSharp.Plugin.HuggingFace",
|
||||
"BotSharp.Plugin.KnowledgeBase",
|
||||
|
|
|
|||
Loading…
Reference in a new issue