Merge pull request #85 from hchen2020/master

Fix IConversationCompletionHook
This commit is contained in:
Haiping 2023-07-21 12:06:21 -05:00 committed by GitHub
commit 1ecb441ae8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 181 additions and 58 deletions

View file

@ -8,6 +8,12 @@
<PackageIcon>Icon.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Infrastructures\**" />
<EmbeddedResource Remove="Infrastructures\**" />
<None Remove="Infrastructures\**" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\..\arts\Icon.png">
<Pack>True</Pack>
@ -22,8 +28,4 @@
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Infrastructures\" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,53 @@
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.MLTasks;
namespace BotSharp.Abstraction.Conversations;
public abstract class ConversationCompletionHookBase : IConversationCompletionHook
{
protected Agent _agent;
public Agent Agent => _agent;
protected Conversation _conversation;
public Conversation Conversation => _conversation;
protected List<RoleDialogModel> _dialogs;
public List<RoleDialogModel> Dialogs => _dialogs;
protected IChatCompletion _chatCompletion;
public IChatCompletion ChatCompletion => _chatCompletion;
public IConversationCompletionHook SetAgent(Agent agent)
{
_agent = agent;
return this;
}
public IConversationCompletionHook SetConversation(Conversation conversation)
{
_conversation = conversation;
return this;
}
public IConversationCompletionHook SetDialogs(List<RoleDialogModel> dialogs)
{
_dialogs = dialogs;
return this;
}
public IConversationCompletionHook SetChatCompletion(IChatCompletion chatCompletion)
{
_chatCompletion = chatCompletion;
return this;
}
public virtual Task BeforeCompletion()
{
return Task.CompletedTask;
}
public virtual Task<string> AfterCompletion(string response)
{
return Task.FromResult(response);
}
}

View file

@ -1,9 +1,22 @@
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.MLTasks;
namespace BotSharp.Abstraction.Conversations;
public interface IConversationCompletionHook
{
Task BeforeCompletion(Agent agent, List<RoleDialogModel> conversations);
Task<string> AfterCompletion(Agent agent, string response);
Agent Agent { get; }
IConversationCompletionHook SetAgent(Agent agent);
Conversation Conversation { get; }
IConversationCompletionHook SetConversation(Conversation conversation);
List<RoleDialogModel> Dialogs { get; }
IConversationCompletionHook SetDialogs(List<RoleDialogModel> dialogs);
IChatCompletion ChatCompletion { get; }
IConversationCompletionHook SetChatCompletion(IChatCompletion chatCompletion);
Task BeforeCompletion();
Task<string> AfterCompletion(string response);
}

View file

@ -5,6 +5,7 @@ namespace BotSharp.Abstraction.Conversations;
public interface IConversationService
{
Task<Conversation> NewConversation(Conversation conversation);
Task<Conversation> GetConversation(string id);
Task<List<Conversation>> GetConversations();
Task DeleteConversation(string id);
Task<string> SendMessage(string agentId, string conversationId, RoleDialogModel lastDalog);

View file

@ -8,6 +8,12 @@ public class RoleDialogModel
public string Role { get; set; }
public string Text { get; set; }
public RoleDialogModel(string role, string text)
{
Role = role;
Text = text;
}
public override string ToString()
{
return $"{Role}: {Text}";

View file

@ -0,0 +1,7 @@
namespace BotSharp.Abstraction.Utilities;
public static class StringExtensions
{
public static string IfNullOrEmptyAs(this string str, string defaultValue)
=> string.IsNullOrEmpty(str) ? defaultValue : str;
}

View file

@ -34,14 +34,25 @@
<PackageIconUrl>https://raw.githubusercontent.com/SciSharp/BotSharp/master/docs/static/logos/BotSharp.png</PackageIconUrl>
<PackageLicenseUrl>https://raw.githubusercontent.com/SciSharp/BotSharp/master/LICENSE</PackageLicenseUrl>
<PackageIcon>Icon.png</PackageIcon>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;DEBUG</DefineConstants>
<NoWarn>1701;1702</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE;</DefineConstants>
<NoWarn>1701;1702</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<NoWarn>1701;1702</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<NoWarn>1701;1702</NoWarn>
</PropertyGroup>
<ItemGroup>

View file

@ -26,6 +26,7 @@ public class ConversationController : ControllerBase, IApiAdapter
var service = _services.GetRequiredService<IConversationService>();
var sess = new Conversation
{
UserId = _user.Id,
AgentId = agentId
};
sess = await service.NewConversation(sess);
@ -45,15 +46,11 @@ public class ConversationController : ControllerBase, IApiAdapter
{
var conv = _services.GetRequiredService<IConversationService>();
var result = await conv.SendMessage(agentId, conversationId, new RoleDialogModel
{
Role = "user",
Text = input.Text
});
var result = await conv.SendMessage(agentId, conversationId, new RoleDialogModel("user", input.Text));
return new MessageResponseModel
{
Content = result
Text = result
};
}
}

View file

@ -28,6 +28,16 @@ public class ConversationService : IConversationService
throw new NotImplementedException();
}
public async Task<Conversation> GetConversation(string id)
{
var db = _services.GetRequiredService<AgentDbContext>();
var query = from sess in db.Conversation
where sess.Id == id
orderby sess.CreatedTime descending
select sess.ToConversation();
return query.FirstOrDefault();
}
public async Task<List<Conversation>> GetConversations()
{
var db = _services.GetRequiredService<AgentDbContext>();
@ -43,8 +53,8 @@ public class ConversationService : IConversationService
var db = _services.GetRequiredService<AgentDbContext>();
var record = ConversationRecord.FromConversation(sess);
record.Id = Guid.NewGuid().ToString();
record.UserId = _user.Id;
record.Id = sess.Id.IfNullOrEmptyAs(Guid.NewGuid().ToString());
record.UserId = sess.UserId.IfNullOrEmptyAs(_user.Id);
record.Title = "New Conversation";
db.Transaction<IAgentTable>(delegate
@ -65,11 +75,7 @@ public class ConversationService : IConversationService
var response = await SendMessage(agentId, conversationId, wholeDialogs);
_storage.Append(agentId, conversationId, new RoleDialogModel
{
Role = "assistant",
Text = response
});
_storage.Append(agentId, conversationId, new RoleDialogModel("assistant", response));
return response;
}
@ -77,6 +83,7 @@ public class ConversationService : IConversationService
public async Task<string> SendMessage(string agentId, string conversationId, List<RoleDialogModel> wholeDialogs)
{
var agent = await _services.GetRequiredService<IAgentService>().GetAgent(agentId);
var converation = await GetConversation(conversationId);
// Get relevant domain knowledge
if (_settings.EnableKnowledgeBase)
@ -94,14 +101,21 @@ public class ConversationService : IConversationService
// Before chat completion hook
var hooks = _services.GetServices<IConversationCompletionHook>().ToList();
hooks.ForEach(hook => hook.BeforeCompletion(agent, wholeDialogs));
hooks.ForEach(hook =>
{
hook.SetAgent(agent)
.SetConversation(converation)
.SetDialogs(wholeDialogs)
.SetChatCompletion(chatCompletion)
.BeforeCompletion();
});
var response = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs);
// After chat completion hook
hooks.ForEach(async hook =>
{
response = await hook.AfterCompletion(agent, response);
response = await hook.AfterCompletion(response);
});
return response;

View file

@ -26,11 +26,7 @@ public class ConversationStorage : IConversationStorage
var pos = x.IndexOf(':');
var role = x.Substring(0, pos);
var text = x.Substring(pos + 1);
return new RoleDialogModel
{
Role = role,
Text = text
};
return new RoleDialogModel(role, text);
}).ToList();
}

View file

@ -2,5 +2,5 @@ namespace BotSharp.Core.Conversations.ViewModels;
public class MessageResponseModel
{
public string Content { get; set; }
public string Text { get; set; }
}

View file

@ -30,10 +30,10 @@ public static class DataContextHelper
dc.BindDbContext<IAgentTable, DbContext4SqlServer2>(new DatabaseBind
{
ServiceProvider = serviceProvider,
MasterConnection = new SqlConnection(settings.Agent.Master),
SlaveConnections = settings.Agent.Slavers.Length == 0 ?
new List<DbConnection> { new SqlConnection(settings.Agent.Master) } :
settings.Agent.Slavers.Select(x => new SqlConnection(x) as DbConnection).ToList(),
MasterConnection = new SqlConnection(settings.BotSharp.Master),
SlaveConnections = settings.BotSharp.Slavers.Length == 0 ?
new List<DbConnection> { new SqlConnection(settings.BotSharp.Master) } :
settings.BotSharp.Slavers.Select(x => new SqlConnection(x) as DbConnection).ToList(),
CreateDbIfNotExist = true
});
}

View file

@ -1,10 +1,8 @@
using EntityFrameworkCore.BootKit;
namespace BotSharp.Core.Repository;
public class MyDatabaseSettings : DatabaseSettings
{
public string[] Assemblies { get; set; }
public DbConnectionSetting MongoDb { get; set; }
public DbConnectionSetting Agent { get; set; }
public DbConnectionSetting BotSharp { get; set; }
}

View file

@ -10,6 +10,7 @@ global using BotSharp.Abstraction.Agents;
global using BotSharp.Abstraction.Conversations;
global using BotSharp.Abstraction.Knowledges;
global using BotSharp.Abstraction.Users;
global using BotSharp.Abstraction.Utilities;
global using BotSharp.Core.Repository;
global using BotSharp.Core.Repository.Abstraction;
global using BotSharp.Core.Repository.DbTables;

View file

@ -15,7 +15,7 @@ public class AzureOpenAiPlugin : IBotSharpPlugin
config.Bind("AzureOpenAi", settings);
services.AddSingleton(x => settings);
services.AddSingleton<ITextCompletion, TextCompletionProvider>();
services.AddScoped<ITextCompletion, TextCompletionProvider>();
services.AddScoped<IChatCompletion, ChatCompletionProvider>();
}
}

View file

@ -48,22 +48,31 @@ public class ChatCompletionProvider : IChatCompletion
public List<RoleDialogModel> GetChatSamples(string sampleText)
{
var samples = new List<RoleDialogModel>();
if (!string.IsNullOrEmpty(sampleText))
if (string.IsNullOrEmpty(sampleText))
{
var lines = sampleText.Split('\n');
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i];
var role = line.Substring(0, line.IndexOf(' ') - 1);
var content = line.Substring(line.IndexOf(' ') + 1);
samples.Add(new RoleDialogModel
{
Role = role,
Text = content
});
}
return samples;
}
var lines = sampleText.Split('\n');
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i];
if (string.IsNullOrEmpty(line.Trim()))
{
continue;
}
var role = line.Substring(0, line.IndexOf(' ') - 1).Trim();
var content = line.Substring(line.IndexOf(' ') + 1).Trim();
// comments
if (role == "##")
{
continue;
}
samples.Add(new RoleDialogModel(role, content));
}
return samples;
}
@ -104,8 +113,9 @@ public class ChatCompletionProvider : IChatCompletion
{
chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, agent.Knowledges));
}
foreach (var message in GetChatSamples(agent.Samples))
var samples = GetChatSamples(agent.Samples);
foreach (var message in samples)
{
chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Text));
}

View file

@ -16,9 +16,11 @@ using BotSharp.Plugin.ChatbotUI.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Conversations.Models;
using Microsoft.AspNetCore.Authorization;
namespace BotSharp.Plugin.ChatbotUI.Controllers;
[Authorize]
[ApiController]
public class ChatbotUiController : ControllerBase, IApiAdapter
{
@ -59,15 +61,25 @@ public class ChatbotUiController : ControllerBase, IApiAdapter
Response.Headers.Add(HeaderNames.Connection, "keep-alive");
var outputStream = Response.Body;
var conversations = input.Messages.Skip(1).Select(x => new RoleDialogModel
var conversations = input.Messages
.Select(x => new RoleDialogModel(x.Role, x.Content))
.ToList();
var conversationService = _services.GetRequiredService<IConversationService>();
// Check if this conversation exists
var converation = await conversationService.GetConversation(input.ConversationId);
if(converation == null)
{
Role = x.Role,
Text = x.Content
}).ToList();
var sess = new Conversation
{
Id = input.ConversationId,
AgentId = input.AgentId
};
converation = await conversationService.NewConversation(sess);
}
var conv = _services.GetRequiredService<IConversationService>();
var result = await conv.SendMessage("", "", conversations.Last());
var result = await conversationService.SendMessage(input.AgentId, input.ConversationId, conversations);
await OnChunkReceived(outputStream, result);
await OnEventCompleted(outputStream);

View file

@ -6,6 +6,8 @@ namespace BotSharp.Plugin.ChatbotUI.ViewModels;
public class OpenAiMessageInput
{
public string AgentId { get; set; }
public string ConversationId { get; set; }
public string Model { get; set; } = string.Empty;
public List<OpenAiMessageBody> Messages { get; set; } = new List<OpenAiMessageBody>();
[JsonPropertyName("max_tokens")]