Skip to content

Spark desk #347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion BotSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.HttpHandler
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.SqlDriver", "src\Plugins\BotSharp.Plugin.SqlDriver\BotSharp.Plugin.SqlDriver.csproj", "{D775DB67-A4B4-44E5-9144-522689590057}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.Dashboard", "src\Plugins\BotSharp.Plugin.Dashboard\BotSharp.Plugin.Dashboard.csproj", "{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.Dashboard", "src\Plugins\BotSharp.Plugin.Dashboard\BotSharp.Plugin.Dashboard.csproj", "{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.SparkDesk", "src\Plugins\BotSharp.Plugin.SparkDesk\BotSharp.Plugin.SparkDesk.csproj", "{289E25C8-63F1-4D52-9909-207724DB40CB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -323,6 +325,14 @@ Global
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Release|Any CPU.Build.0 = Release|Any CPU
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Release|x64.ActiveCfg = Release|Any CPU
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Release|x64.Build.0 = Release|Any CPU
{289E25C8-63F1-4D52-9909-207724DB40CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{289E25C8-63F1-4D52-9909-207724DB40CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{289E25C8-63F1-4D52-9909-207724DB40CB}.Debug|x64.ActiveCfg = Debug|Any CPU
{289E25C8-63F1-4D52-9909-207724DB40CB}.Debug|x64.Build.0 = Debug|Any CPU
{289E25C8-63F1-4D52-9909-207724DB40CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{289E25C8-63F1-4D52-9909-207724DB40CB}.Release|Any CPU.Build.0 = Release|Any CPU
{289E25C8-63F1-4D52-9909-207724DB40CB}.Release|x64.ActiveCfg = Release|Any CPU
{289E25C8-63F1-4D52-9909-207724DB40CB}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -364,6 +374,7 @@ Global
{32D9E720-6FE6-4F29-94B1-B10B05BFAD75} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{D775DB67-A4B4-44E5-9144-522689590057} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{289E25C8-63F1-4D52-9909-207724DB40CB} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>$(LangVersion)</LangVersion>
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
<OutputPath>$(SolutionDir)packages</OutputPath>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Sdcb.SparkDesk" Version="3.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Loggers;
using BotSharp.Abstraction.Routing;
using Sdcb.SparkDesk.ResponseInternals;

namespace BotSharp.Plugin.SparkDesk.Providers;

public class ChatCompletionProvider : IChatCompletion
{
public string Provider => "sparkdesk";

private readonly SparkDeskSettings _settings;
private readonly IServiceProvider _services;
private readonly ILogger _logger;
private string _model;

public ChatCompletionProvider(IServiceProvider services,
SparkDeskSettings settings,
ILogger<ChatCompletionProvider> logger)
{
_services = services;
_settings = settings;
_logger = logger;
_model = $"general{settings.ModelVersion.ToString()}";
}


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 = new SparkDeskClient(appId: _settings.AppId, apiKey: _settings.ApiKey, apiSecret: _settings.ApiSecret);
var (prompt, messages, funcall) = PrepareOptions(agent, conversations);

var response = await client.ChatAsync(modelVersion:_settings.ModelVersion, messages,functions: funcall.Length == 0 ? null : funcall);

var responseMessage = new RoleDialogModel(AgentRole.Assistant, response.Text)
{
CurrentAgentId = agent.Id,
MessageId = conversations.Last().MessageId
};

if (response.FunctionCall != null)
{
responseMessage = new RoleDialogModel(AgentRole.Function, response.Text)
{
CurrentAgentId = agent.Id,
MessageId = conversations.Last().MessageId,
FunctionName = response.FunctionCall.Name,
FunctionArgs = response.FunctionCall.Arguments
};

}

// After chat completion hook
foreach (var hook in contentHooks)
{
await hook.AfterGenerated(responseMessage, new TokenStatsModel
{
Prompt = prompt,
Provider = Provider,
Model = _model,
PromptCount = response.Usage.PromptTokens,
CompletionCount = response.Usage.CompletionTokens
});
}

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 = new SparkDeskClient(appId: _settings.AppId, apiKey: _settings.ApiKey, apiSecret: _settings.ApiSecret);
var (prompt, messages, funcall) = PrepareOptions(agent, conversations);

var response = await client.ChatAsync(modelVersion: _settings.ModelVersion, messages, functions: funcall.Length == 0 ?null: funcall);

var msg = new RoleDialogModel(AgentRole.Assistant, response.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.Usage.PromptTokens,
CompletionCount = response.Usage.CompletionTokens
});
}

if (response.FunctionCall != null)
{
_logger.LogInformation($"[{agent.Name}]: {response.FunctionCall.Name}({response.FunctionCall.Arguments})");

var funcContextIn = new RoleDialogModel(AgentRole.Function, response.Text)
{
CurrentAgentId = agent.Id,
FunctionName = response.FunctionCall.Name,
FunctionArgs = response.FunctionCall.Arguments
};

// 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 = new SparkDeskClient(appId: _settings.AppId, apiKey: _settings.ApiKey, apiSecret: _settings.ApiSecret);
var (prompt, messages, funcall) = PrepareOptions(agent, conversations);

await foreach (StreamedChatResponse response in client.ChatAsStreamAsync(modelVersion: _settings.ModelVersion, messages, functions: funcall.Length == 0 ? null : funcall))
{
if (response.FunctionCall !=null)
{
await onMessageReceived(new RoleDialogModel(AgentRole.Function, response.Text)
{
CurrentAgentId = agent.Id,
FunctionName = response.FunctionCall.Name,
FunctionArgs = response.FunctionCall.Arguments
});
continue;
}

await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, response.Text)
{
CurrentAgentId = agent.Id
});

}

return true;
}

public void SetModelName(string model)
{
_model = model;
}

private (string, ChatMessage[], FunctionDef[]?) PrepareOptions(Agent agent, List<RoleDialogModel> conversations)
{
var functions = new List<FunctionDef>();
var agentService = _services.GetRequiredService<IAgentService>();
var messages = new List<ChatMessage>();

if (!string.IsNullOrEmpty(agent.Instruction))
{
var instruction = agentService.RenderedInstruction(agent);
messages.Add(ChatMessage.FromSystem(instruction));
}
if (!string.IsNullOrEmpty(agent.Knowledges))
{
messages.Add(ChatMessage.FromSystem(agent.Knowledges));
}
var samples = ProviderHelper.GetChatSamples(agent.Samples);
foreach (var message in samples)
{
messages.Add(message.Role == AgentRole.User ?
ChatMessage.FromUser(message.Content) :
ChatMessage.FromAssistant(message.Content));
}

foreach (var function in agent.Functions)
{
functions.Add(ConvertToFunctionDef(function));
}

foreach (var message in conversations)
{
if (message.Role == "function")
{
//messages.Add(ChatMessage.FromUser($"function call result: {message.Content}"));
}
else if (message.Role == "user")
{
var userMessage = ChatMessage.FromUser(message.Content);

messages.Add(userMessage);
}
else if (message.Role == "assistant")
{
messages.Add(ChatMessage.FromAssistant(message.Content));
}
}

var prompt = GetPrompt(messages, functions);
return (prompt, messages.ToArray(), functions.ToArray());
}

private string GetPrompt(List<ChatMessage> messages,List<FunctionDef> functions)
{
var prompt = string.Empty;

if (messages.Count > 0)
{
// System instruction
var verbose = string.Join("\r\n", messages
.Where(x => x.Role == AgentRole.System)
.Select(x =>
{
return $"{x.Role}: {x.Content}";
}));
prompt += $"{verbose}\r\n";

verbose = string.Join("\r\n", messages
.Where(x => x.Role != AgentRole.System).Select(x =>
{
return
$"{x.Role}: {x.Content}";

}));
prompt += $"\r\n{verbose}\r\n";
}
return prompt;
}

private FunctionDef ConvertToFunctionDef(BotSharp.Abstraction.Functions.Models.FunctionDef def)
{
var parameters = def.Parameters;
var funcParamsProperties = parameters.Properties;
var requiredList = parameters.Required;
var fundef = new List<FunctionParametersDef>();
if (funcParamsProperties != null)
{
var props = funcParamsProperties.RootElement.EnumerateObject();
while (props.MoveNext())
{
var prop = props.Current;
var name = prop.Name;
bool required = requiredList.Contains(name);
FunctionParametersDef parametersDef = new FunctionParametersDef(name, prop.Value.GetProperty("type").GetRawText(), prop.Value.GetProperty("description").GetRawText(), required);
fundef.Add(parametersDef);
}
}
FunctionDef functionDef = new FunctionDef(def.Name, def.Description, fundef.ToArray());
return functionDef;
}
}
31 changes: 31 additions & 0 deletions src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ProviderHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace BotSharp.Plugin.SparkDesk.Providers;

public class ProviderHelper
{

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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace BotSharp.Plugin.SparkDesk;

public class SparkDeskSettings
{
public string AppId { get; set; }

public string ApiKey { get; set; }

public string ApiSecret { get; set; }

public ModelVersion ModelVersion { get; set; }
}
Loading