Add file as conversation storage.#76

This commit is contained in:
Haiping Chen 2023-06-27 13:31:13 -05:00
parent a7b263e2eb
commit 29a65a0086
51 changed files with 337 additions and 482 deletions

9
.gitignore vendored
View file

@ -283,12 +283,5 @@ __pycache__/
*.btm.cs *.btm.cs
*.odx.cs *.odx.cs
*.xsd.cs *.xsd.cs
/BotSharp.WebHost/App_Data/BotSharp.db data
/BotSharp.UI
/BotSharp.WebHost/App_Data/Projects
/BotSharp.WebHost/PublishOutput
/Data
/docs/_build /docs/_build
*.RestApi.xml
/BotSharp.WebHost/App_Data/AgentStorage
/BotSharp.WebHost/App_Data/SessionStorage

View file

@ -9,6 +9,8 @@ public interface IAgentService
{ {
Task<Agent> CreateAgent(Agent agent); Task<Agent> CreateAgent(Agent agent);
Task<List<Agent>> GetAgents(); Task<List<Agent>> GetAgents();
Task<Agent> GetAgent(string id);
Task<bool> DeleteAgent(string id); Task<bool> DeleteAgent(string id);
Task UpdateAgent(Agent agent); Task UpdateAgent(Agent agent);
string GetAgentDataDir(string agentId);
} }

View file

@ -22,4 +22,8 @@
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Infrastructures\" />
</ItemGroup>
</Project> </Project>

View file

@ -1,11 +0,0 @@
using BotSharp.Abstraction.Infrastructures.ContentTransfers;
namespace BotSharp.Abstraction.Conversations;
/// <summary>
/// IChatServiceZone is used to manage the chat function.
/// When user send message to controller, all the registered service zone will process the message respectively.
/// </summary>
public interface IChatServiceZone : IServiceZone
{
}

View file

@ -1,10 +1,14 @@
using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Conversations.Models;
namespace BotSharp.Abstraction.Conversations; namespace BotSharp.Abstraction.Conversations;
public interface IConversationService public interface IConversationService
{ {
void AddDialog(RoleDialogModel dialog); Task<Conversation> NewConversation(Conversation conversation);
List<RoleDialogModel> GetDialogHistory(string sessionId); Task<List<Conversation>> GetConversations();
void CleanHistory(); Task DeleteConversation(string id);
Task<string> SendMessage(string agentId, string conversationId, RoleDialogModel lastDalog);
Task<string> SendMessage(string agentId, string conversationId, List<RoleDialogModel> wholeDialogs);
List<RoleDialogModel> GetDialogHistory(string agentId, string conversationId);
Task CleanHistory(string agentId);
} }

View file

@ -0,0 +1,10 @@
using BotSharp.Abstraction.Conversations.Models;
namespace BotSharp.Abstraction.Conversations;
public interface IConversationStorage
{
void InitStorage(string agentId, string conversationId);
void Append(string agentId, string conversationId, RoleDialogModel dialog);
List<RoleDialogModel> GetDialogs(string agentId, string conversationId);
}

View file

@ -1,10 +0,0 @@
using BotSharp.Abstraction.Conversations.Models;
namespace BotSharp.Abstraction.Conversations;
public interface ISessionService
{
Task<Session> NewSession(Session sess);
Task<List<Session>> GetSessions();
Task DeleteSession(string sessionId);
}

View file

@ -1,11 +1,12 @@
namespace BotSharp.Abstraction.Conversations.Models; namespace BotSharp.Abstraction.Conversations.Models;
public class Session public class Conversation
{ {
public string Id { get; set; } = string.Empty; public string Id { get; set; } = string.Empty;
public string AgentId { get; set; } = string.Empty; public string AgentId { get; set; } = string.Empty;
public string UserId { get; set; } = string.Empty; public string UserId { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty; public string Title { get; set; } = string.Empty;
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; } = DateTime.UtcNow; public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
} }

View file

@ -1,7 +0,0 @@
namespace BotSharp.Abstraction.Conversations.Models;
public class MessageModel
{
public string From { get; set; }
public string Content { get; set; }
}

View file

@ -0,0 +1,15 @@
namespace BotSharp.Abstraction.Conversations.Models;
public class RoleDialogModel
{
/// <summary>
/// user, system, assistant
/// </summary>
public string Role { get; set; }
public string Text { get; set; }
public override string ToString()
{
return $"{Role}: {Text}";
}
}

View file

@ -2,5 +2,5 @@ namespace BotSharp.Abstraction.Conversations.Settings;
public class ConversationSetting public class ConversationSetting
{ {
public string ChatCompletion { get; set; }
} }

View file

@ -1,12 +0,0 @@
using BotSharp.Abstraction.Models;
namespace BotSharp.Abstraction.Infrastructures.ContentTransmitters;
public class ContentContainer
{
public string UserId { get; set; }
public string SessionId { get; set; }
public string AgentId { get; set; }
public List<RoleDialogModel> Conversations { get; set; }
public RoleDialogModel Output { get; set; }
}

View file

@ -1,8 +0,0 @@
using BotSharp.Abstraction.Infrastructures.ContentTransfers;
namespace BotSharp.Abstraction.Infrastructures.ContentTransmitters;
public interface IContentTransfer
{
Task<TransportResult> Transport(ContentContainer input);
}

View file

@ -1,9 +0,0 @@
using BotSharp.Abstraction.Infrastructures.ContentTransmitters;
namespace BotSharp.Abstraction.Infrastructures.ContentTransfers;
public interface IServiceZone
{
int Priority { get; }
Task Serving(ContentContainer content);
}

View file

@ -1,7 +0,0 @@
namespace BotSharp.Abstraction.Infrastructures.ContentTransfers;
public class TransportResult
{
public bool IsSuccess { get; set; }
public List<string> Messages { get; set; }
}

View file

@ -1,8 +1,9 @@
using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Conversations.Models;
namespace BotSharp.Abstraction.MLTasks; namespace BotSharp.Abstraction.MLTasks;
public interface IChatCompletion public interface IChatCompletion
{ {
Task<string> GetChatCompletionsAsync(List<RoleDialogModel> conversations); Task<string> GetChatCompletionsAsync(Agent agent, List<RoleDialogModel> conversations);
} }

View file

@ -1,7 +0,0 @@
namespace BotSharp.Abstraction.Models;
public class RoleDialogModel
{
public string Role { get; set; }
public string Text { get; set; }
}

View file

@ -1,4 +1,3 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.ApiAdapters; using BotSharp.Abstraction.ApiAdapters;
using BotSharp.Core.Agents.ViewModels; using BotSharp.Core.Agents.ViewModels;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View file

@ -1,16 +0,0 @@
namespace BotSharp.Core.Agents.Services;
public partial class AgentService : IChatServiceZone
{
public int Priority => 10;
/// <summary>
/// Prepare agent profile and configurations
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
public async Task Serving(ContentContainer content)
{
}
}

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Agents.Models;
using System.IO;
namespace BotSharp.Core.Agents.Services; namespace BotSharp.Core.Agents.Services;
@ -12,4 +13,20 @@ public partial class AgentService
select agent.ToAgent(); select agent.ToAgent();
return query.ToList(); return query.ToList();
} }
public async Task<Agent> GetAgent(string id)
{
var db = _services.GetRequiredService<AgentDbContext>();
var query = from agent in db.Agent
where agent.OwnerId == _user.Id && agent.Id == id
select agent.ToAgent();
var profile = query.FirstOrDefault();
var dir = GetAgentDataDir(id);
profile.Instruction = File.ReadAllText(Path.Combine(dir, "instruction.txt"));
profile.Samples = File.ReadAllText(Path.Combine(dir, "samples.txt"));
return profile;
}
} }

View file

@ -14,8 +14,6 @@ public partial class AgentService
record.Name = agent.Name; record.Name = agent.Name;
record.Description = agent.Description; record.Description = agent.Description;
record.Instruction = agent.Instruction;
record.Samples = agent.Samples;
record.UpdatedDateTime = DateTime.UtcNow; record.UpdatedDateTime = DateTime.UtcNow;
}); });
} }

View file

@ -1,3 +1,5 @@
using System.IO;
namespace BotSharp.Core.Agents.Services; namespace BotSharp.Core.Agents.Services;
public partial class AgentService : IAgentService public partial class AgentService : IAgentService
@ -10,4 +12,14 @@ public partial class AgentService : IAgentService
_services = services; _services = services;
_user = user; _user = user;
} }
public string GetAgentDataDir(string agentId)
{
var dir = Path.Combine("data", agentId);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
return dir;
}
} }

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Conversations.Settings;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -11,14 +12,14 @@ public static class BotSharpServiceCollectionExtensions
services.AddScoped<IUserService, UserService>(); services.AddScoped<IUserService, UserService>();
services.AddScoped<IAgentService, AgentService>(); services.AddScoped<IAgentService, AgentService>();
services.AddScoped<IChatServiceZone, AgentService>();
services.AddScoped<ISessionService, SessionService>(); var convsationSettings = new ConversationSetting();
config.Bind("Conversation", convsationSettings);
services.AddSingleton((IServiceProvider x) => convsationSettings);
services.AddScoped<IConversationStorage, ConversationStorage>();
services.AddScoped<IConversationService, ConversationService>(); services.AddScoped<IConversationService, ConversationService>();
services.AddScoped<IContentTransfer, ContentTransfer>();
RegisterRepository(services, config); RegisterRepository(services, config);
RegisterPlugins(services, config); RegisterPlugins(services, config);
@ -46,17 +47,11 @@ public static class BotSharpServiceCollectionExtensions
{ {
var databaseSettings = new DatabaseSettings(); var databaseSettings = new DatabaseSettings();
config.Bind("Database", databaseSettings); config.Bind("Database", databaseSettings);
services.AddSingleton((IServiceProvider x) => services.AddSingleton((IServiceProvider x) => databaseSettings);
{
return databaseSettings;
});
var myDatabaseSettings = new MyDatabaseSettings(); var myDatabaseSettings = new MyDatabaseSettings();
config.Bind("Database", myDatabaseSettings); config.Bind("Database", myDatabaseSettings);
services.AddSingleton((IServiceProvider x) => services.AddSingleton((IServiceProvider x) => databaseSettings);
{
return databaseSettings;
});
services.AddScoped((IServiceProvider x) => services.AddScoped((IServiceProvider x) =>
{ {

View file

@ -21,50 +21,39 @@ public class ConversationController : ControllerBase, IApiAdapter
} }
[HttpPost("/conversation/{agentId}")] [HttpPost("/conversation/{agentId}")]
public async Task<SessionViewModel> NewSession([FromRoute] string agentId) public async Task<ConversationViewModel> NewConversation([FromRoute] string agentId)
{ {
var service = _services.GetRequiredService<ISessionService>(); var service = _services.GetRequiredService<IConversationService>();
var sess = new Session var sess = new Conversation
{ {
AgentId = agentId AgentId = agentId
}; };
sess = await service.NewSession(sess); sess = await service.NewConversation(sess);
return SessionViewModel.FromSession(sess); return ConversationViewModel.FromSession(sess);
} }
[HttpDelete("/conversation/{agentId}/{sessionId}")] [HttpDelete("/conversation/{agentId}/{conversationId}")]
public async Task DeleteSession([FromRoute] string agentId, [FromRoute] string sessionId) public async Task DeleteConversation([FromRoute] string agentId, [FromRoute] string conversationId)
{ {
var service = _services.GetRequiredService<ISessionService>(); var service = _services.GetRequiredService<IConversationService>();
} }
[HttpPost("/conversation/{agentId}/{sessionId}")] [HttpPost("/conversation/{agentId}/{conversationId}")]
public async Task<MessageResponseModel> SendMessage([FromRoute] string agentId, public async Task<MessageResponseModel> SendMessage([FromRoute] string agentId,
[FromRoute] string sessionId, [FromRoute] string conversationId,
[FromBody] NewMessageModel input) [FromBody] NewMessageModel input)
{ {
var transmitter = _services.GetRequiredService<IContentTransfer>(); var conv = _services.GetRequiredService<IConversationService>();
var container = new ContentContainer var result = await conv.SendMessage(agentId, conversationId, new RoleDialogModel
{ {
AgentId = agentId, Role = "user",
SessionId = sessionId, Text = input.Text
Conversations = new List<RoleDialogModel> });
{
new RoleDialogModel
{
Role = "user",
Text = input.Text
}
},
UserId = _user.Id
};
var result = await transmitter.Transport(container);
return new MessageResponseModel return new MessageResponseModel
{ {
Content = result.IsSuccess ? container.Output.Text : result.Messages.First() Content = result
}; };
} }
} }

View file

@ -1,52 +1,100 @@
using BotSharp.Abstraction.Conversations; using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Conversations.Settings;
using System; using BotSharp.Abstraction.MLTasks;
using System.Collections.Generic;
using System.Text;
namespace BotSharp.Core.Conversations.Services; namespace BotSharp.Core.Conversations.Services;
public class ConversationService : IConversationService public class ConversationService : IConversationService
{ {
Dictionary<string, List<RoleDialogModel>> _history; private readonly IServiceProvider _services;
private readonly IUserIdentity _user;
private readonly ConversationSetting _settings;
private readonly IConversationStorage _storage;
public ConversationService() public ConversationService(IServiceProvider services,
IUserIdentity user,
ConversationSetting settings,
IConversationStorage storage)
{ {
_history = new Dictionary<string, List<RoleDialogModel>>(); _services = services;
_user = user;
_settings = settings;
_storage = storage;
} }
public void AddDialog(RoleDialogModel dialog) public Task DeleteConversation(string id)
{
_history[Guid.Empty.ToString()].Add(dialog);
}
public void CleanHistory()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void DeleteSession() public async Task<List<Conversation>> GetConversations()
{
var db = _services.GetRequiredService<AgentDbContext>();
var query = from sess in db.Conversation
where sess.UserId == _user.Id
orderby sess.CreatedTime descending
select sess.ToConversation();
return query.ToList();
}
public async Task<Conversation> NewConversation(Conversation sess)
{
var db = _services.GetRequiredService<AgentDbContext>();
var record = ConversationRecord.FromConversation(sess);
record.Id = Guid.NewGuid().ToString();
record.UserId = _user.Id;
record.Title = "New Conversation";
db.Transaction<IAgentTable>(delegate
{
db.Add<IAgentTable>(record);
});
_storage.InitStorage(sess.AgentId, record.Id);
return record.ToConversation();
}
public async Task<string> SendMessage(string agentId, string conversationId, RoleDialogModel lastDalog)
{
_storage.Append(agentId, conversationId, lastDalog);
var wholeDialogs = GetDialogHistory(agentId, conversationId);
var response = await SendMessage(agentId, conversationId, wholeDialogs);
_storage.Append(agentId, conversationId, new RoleDialogModel
{
Role = "assistant",
Text = response
});
return response;
}
public async Task<string> SendMessage(string agentId, string conversationId, List<RoleDialogModel> wholeDialogs)
{
var agent = await _services.GetRequiredService<IAgentService>().GetAgent(agentId);
var chat = GetChatCompletion();
var response = await chat.GetChatCompletionsAsync(agent, wholeDialogs);
return response;
}
public IChatCompletion GetChatCompletion()
{
var completions = _services.GetServices<IChatCompletion>();
return completions.FirstOrDefault(x => x.GetType().FullName.Contains(_settings.ChatCompletion));
}
public Task CleanHistory(string agentId)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public List<string> GetAllSessions() public List<RoleDialogModel> GetDialogHistory(string agentId, string conversationId)
{ {
throw new NotImplementedException(); return _storage.GetDialogs(agentId, conversationId);
}
public List<RoleDialogModel> GetDialogHistory()
{
return _history[Guid.Empty.ToString()];
}
public List<RoleDialogModel> GetDialogHistory(string sessionId)
{
throw new NotImplementedException();
}
public string NewSession()
{
throw new NotImplementedException();
} }
} }

View file

@ -0,0 +1,59 @@
using BotSharp.Abstraction.Conversations.Models;
using System.IO;
namespace BotSharp.Core.Conversations.Services;
public class ConversationStorage : IConversationStorage
{
private readonly IAgentService _agent;
public ConversationStorage(IAgentService agent)
{
_agent = agent;
}
public void Append(string agentId, string conversationId, RoleDialogModel dialog)
{
var conversationFile = GetStorageFile(agentId, conversationId);
File.AppendAllText(conversationFile, $"{dialog.Role}: {dialog.Text}\n");
}
public List<RoleDialogModel> GetDialogs(string agentId, string conversationId)
{
var conversationFile = GetStorageFile(agentId, conversationId);
var dialogs = File.ReadAllLines(conversationFile);
return dialogs.Select(x =>
{
var pos = x.IndexOf(':');
var role = x.Substring(0, pos);
var text = x.Substring(pos + 1);
return new RoleDialogModel
{
Role = role,
Text = text
};
}).ToList();
}
public void InitStorage(string agentId, string conversationId)
{
var dir = _agent.GetAgentDataDir(agentId);
var dialogDir = Path.Combine(dir, "conversations");
if (!Directory.Exists(dialogDir))
{
Directory.CreateDirectory(dialogDir);
}
var conversationFile = Path.Combine(dialogDir, conversationId + ".txt");
if (!File.Exists(conversationFile))
{
File.WriteAllLines(conversationFile, new string[0]);
}
}
private string GetStorageFile(string agentId, string conversationId)
{
var dir = _agent.GetAgentDataDir(agentId);
var dialogDir = Path.Combine(dir, "conversations");
return Path.Combine(dialogDir, conversationId + ".txt");
}
}

View file

@ -1,49 +0,0 @@
using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Users;
namespace BotSharp.Core.Conversations.Services;
public class SessionService : ISessionService
{
private readonly IServiceProvider _services;
private readonly IUserIdentity _user;
public SessionService(IServiceProvider services, IUserIdentity user)
{
_services = services;
_user = user;
}
public Task DeleteSession(string sessionId)
{
throw new NotImplementedException();
}
public async Task<List<Session>> GetSessions()
{
var db = _services.GetRequiredService<AgentDbContext>();
var query = from sess in db.Session
where sess.UserId == _user.Id
orderby sess.CreatedTime descending
select sess.ToSession();
return query.ToList();
}
public async Task<Session> NewSession(Session sess)
{
var db = _services.GetRequiredService<AgentDbContext>();
var record = SessionRecord.FromSession(sess);
record.Id = Guid.NewGuid().ToString();
record.UserId = _user.Id;
record.Title = "New Session";
db.Transaction<IAgentTable>(delegate
{
db.Add<IAgentTable>(record);
});
return record.ToSession();
}
}

View file

@ -0,0 +1,6 @@
namespace BotSharp.Core.Conversations.ViewModels;
public class ConversationCreationModel
{
}

View file

@ -2,7 +2,7 @@ using BotSharp.Abstraction.Conversations.Models;
namespace BotSharp.Core.Conversations.ViewModels; namespace BotSharp.Core.Conversations.ViewModels;
public class SessionViewModel public class ConversationViewModel
{ {
public string Id { get; set; } public string Id { get; set; }
public string AgentId { get; set; } public string AgentId { get; set; }
@ -10,9 +10,9 @@ public class SessionViewModel
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; } = DateTime.UtcNow; public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
public static SessionViewModel FromSession(Session sess) public static ConversationViewModel FromSession(Conversation sess)
{ {
return new SessionViewModel return new ConversationViewModel
{ {
Id = sess.Id, Id = sess.Id,
AgentId = sess.AgentId, AgentId = sess.AgentId,

View file

@ -1,8 +0,0 @@
using BotSharp.Abstraction.Conversations.Models;
namespace BotSharp.Core.Conversations.ViewModels;
public class SessionCreationModel
{
}

View file

@ -1,41 +0,0 @@
namespace BotSharp.Core.Infrastructures;
public class ContentTransfer : IContentTransfer
{
private readonly IServiceProvider _services;
public ContentTransfer(IServiceProvider services)
{
_services = services;
}
public async Task<TransportResult> Transport(ContentContainer input)
{
input.Output = new RoleDialogModel();
var result = new TransportResult
{
IsSuccess = true,
Messages = new List<string>()
};
var zones = _services.GetServices<IChatServiceZone>()
.OrderBy(x => x.Priority)
.ToList();
foreach (var zone in zones)
{
try
{
await zone.Serving(input);
}
catch (Exception ex)
{
result.IsSuccess = false;
result.Messages.Add(ex.Message);
}
}
return result;
}
}

View file

@ -9,14 +9,17 @@ public class KnowledgeService : IKnowledgeService
{ {
private readonly IServiceProvider _services; private readonly IServiceProvider _services;
private readonly KnowledgeBaseSettings _settings; private readonly KnowledgeBaseSettings _settings;
private readonly IAgentService _agentService;
private readonly ITextChopper _textChopper; private readonly ITextChopper _textChopper;
public KnowledgeService(IServiceProvider services, public KnowledgeService(IServiceProvider services,
KnowledgeBaseSettings settings, KnowledgeBaseSettings settings,
IAgentService agentService,
ITextChopper textChopper) ITextChopper textChopper)
{ {
_services = services; _services = services;
_settings = settings; _settings = settings;
_agentService = agentService;
_textChopper = textChopper; _textChopper = textChopper;
} }
@ -30,13 +33,8 @@ public class KnowledgeService : IKnowledgeService
}); });
// Store chunks in local file system // Store chunks in local file system
var knowledgeStoreDir = Path.Combine("knowledge_base"); var agentDataDir = _agentService.GetAgentDataDir(knowledge.AgentId);
if (!Directory.Exists(knowledgeStoreDir)) var knowledgePath = Path.Combine(agentDataDir, "knowledge.txt");
{
Directory.CreateDirectory(knowledgeStoreDir);
}
var knowledgePath = Path.Combine(knowledgeStoreDir, knowledge.AgentId + ".txt");
File.WriteAllLines(knowledgePath, lines); File.WriteAllLines(knowledgePath, lines);
var db = GetVectorDb(); var db = GetVectorDb();
@ -57,7 +55,8 @@ public class KnowledgeService : IKnowledgeService
var vector = textEmbedding.GetVector(retrievalModel.Question); var vector = textEmbedding.GetVector(retrievalModel.Question);
// Scan local knowledge directory // Scan local knowledge directory
var chunks = File.ReadAllLines(Path.Combine("knowledge_base", retrievalModel.AgentId + ".txt")); var agentDataDir = _agentService.GetAgentDataDir(retrievalModel.AgentId);
var chunks = File.ReadAllLines(Path.Combine(agentDataDir, "knowledge.txt"));
// Vector search // Vector search
var result = await GetVectorDb().Search(retrievalModel.AgentId, vector); var result = await GetVectorDb().Search(retrievalModel.AgentId, vector);

View file

@ -1,9 +1,12 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.MLTasks;
using LLama; using LLama;
using System.IO; using System.IO;
namespace BotSharp.Core.Plugins.LLamaSharp; namespace BotSharp.Core.Plugins.LLamaSharp;
public class ChatCompletionProvider : IChatServiceZone public class ChatCompletionProvider : IChatCompletion
{ {
private readonly IChatModel _model; private readonly IChatModel _model;
private readonly LlamaSharpSettings _settings; private readonly LlamaSharpSettings _settings;
@ -42,6 +45,21 @@ public class ChatCompletionProvider : IChatServiceZone
Console.WriteLine(); Console.WriteLine();
} }
public Task<string> GetChatCompletionsAsync(Agent agent, List<RoleDialogModel> conversations)
{
string totalResponse = "";
var prompt = GetInstruction();
var content = string.Join("\n ", conversations.Select(x => $"{x.Role}: {x.Text.Replace("user:", "")}")).Trim();
content += "\n assistant: ";
foreach (var response in _model.Chat(content, prompt, "UTF-8"))
{
Console.Write(response);
totalResponse += response;
}
return Task.FromResult(totalResponse);
}
public List<RoleDialogModel> GetChatSamples() public List<RoleDialogModel> GetChatSamples()
{ {
var samples = new List<RoleDialogModel>(); var samples = new List<RoleDialogModel>();
@ -80,19 +98,4 @@ public class ChatCompletionProvider : IChatServiceZone
return instruction; return instruction;
} }
public async Task Serving(ContentContainer content)
{
string output = "";
var prompt = GetInstruction();
var conversations = string.Join("\n ", content.Conversations.Select(x => $"{x.Role}: {x.Text.Replace("user:", "")}")).Trim();
conversations += "\n assistant: ";
foreach (var response in _model.Chat(conversations, prompt, "UTF-8"))
{
Console.Write(response);
output += response;
}
Console.WriteLine();
}
} }

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.MLTasks;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
namespace BotSharp.Core.Plugins.LLamaSharp; namespace BotSharp.Core.Plugins.LLamaSharp;
@ -10,6 +11,6 @@ public class LLamaSharpPlugin : IBotSharpPlugin
config.Bind("LlamaSharp", llamaSharpSettings); config.Bind("LlamaSharp", llamaSharpSettings);
services.AddSingleton(x => llamaSharpSettings); services.AddSingleton(x => llamaSharpSettings);
services.AddScoped<IServiceZone, ChatCompletionProvider>(); services.AddScoped<IChatCompletion, ChatCompletionProvider>();
} }
} }

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.VectorStorage; using BotSharp.Abstraction.VectorStorage;
using Tensorflow;
using Tensorflow.NumPy; using Tensorflow.NumPy;
namespace BotSharp.Core.Plugins.MemVecDb; namespace BotSharp.Core.Plugins.MemVecDb;
@ -21,15 +22,17 @@ public class MemVectorDatabase : IVectorDb
public Task<List<int>> Search(string collectionName, float[] vector, int limit = 10) public Task<List<int>> Search(string collectionName, float[] vector, int limit = 10)
{ {
var cosineList = new List<double>(); var similarities = new float[_vectors[collectionName].Count];
for (int i = 0; i < _vectors[collectionName].Count; i++) for (int i = 0; i < _vectors[collectionName].Count; i++)
{ {
var p = CalCosineSimilarity(vector, _vectors[collectionName][i].Vector); similarities[i] = CalCosineSimilarity(vector, _vectors[collectionName][i].Vector);
cosineList.Add(p);
} }
var similarities = cosineList.ToArray();
var indice = np.argsort(similarities).ToArray<int>() var indice = np.argsort(similarities).ToArray<int>()
.Reverse().Take(limit).ToList(); .Reverse()
.Take(limit)
.ToList();
return Task.FromResult(indice); return Task.FromResult(indice);
} }
@ -44,24 +47,8 @@ public class MemVectorDatabase : IVectorDb
return Task.CompletedTask; return Task.CompletedTask;
} }
private double CalCosineSimilarity(float[] vector1, float[] vector2) private float CalCosineSimilarity(float[] a, float[] b)
{ {
NDArray a = vector1; return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b));
NDArray b = vector2;
double num = np.dot(a, b);
if(num == 0)
{
return 0.0;
}
b = np.square(a);
var x = np.sqrt(np.sum(b));
var x3 = np.sum(np.square(vector2));
double num2 = np.sqrt(x) * np.sqrt(x3);
if(num2 == 0)
{
return 0.0;
}
return num / num2;
} }
} }

View file

@ -4,5 +4,5 @@ public class AgentDbContext : Database
{ {
public IQueryable<UserRecord> User => Table<UserRecord>(); public IQueryable<UserRecord> User => Table<UserRecord>();
public IQueryable<AgentRecord> Agent => Table<AgentRecord>(); public IQueryable<AgentRecord> Agent => Table<AgentRecord>();
public IQueryable<SessionRecord> Session => Table<SessionRecord>(); public IQueryable<ConversationRecord> Conversation => Table<ConversationRecord>();
} }

View file

@ -5,7 +5,7 @@ using MongoDB.Bson.Serialization.IdGenerators;
namespace BotSharp.Core.Repository.Collections; namespace BotSharp.Core.Repository.Collections;
public class Conversation : IMongoDbCollection public class ConversationCollection : IMongoDbCollection
{ {
[BsonId(IdGenerator = typeof(ObjectIdGenerator))] [BsonId(IdGenerator = typeof(ObjectIdGenerator))]
public ObjectId Id { get; set; } public ObjectId Id { get; set; }
@ -15,7 +15,7 @@ public class Conversation : IMongoDbCollection
public string Model { get; set; } public string Model { get; set; }
public string Title { get; set; } public string Title { get; set; }
public List<MessageModel> Messages { get; set; } public List<RoleDialogModel> Messages { get; set; }
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; } public DateTime UpdatedAt { get; set; }

View file

@ -18,18 +18,6 @@ public class AgentRecord : DbRecord, IAgentTable
[MaxLength(512)] [MaxLength(512)]
public string Description { get; set; } public string Description { get; set; }
/// <summary>
/// Instruction
/// </summary>
[StringLength(int.MaxValue)]
public string Instruction { get; set; }
/// <summary>
/// Samples
/// </summary>
[StringLength(int.MaxValue)]
public string Samples { get; set; }
[Required] [Required]
public DateTime CreatedDateTime { get; set; } public DateTime CreatedDateTime { get; set; }

View file

@ -4,8 +4,8 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace BotSharp.Core.Repository.DbTables; namespace BotSharp.Core.Repository.DbTables;
[Table("Session")] [Table("Conversation")]
public class SessionRecord : DbRecord, IAgentTable public class ConversationRecord : DbRecord, IAgentTable
{ {
[Required] [Required]
[MaxLength(36)] [MaxLength(36)]
@ -24,22 +24,22 @@ public class SessionRecord : DbRecord, IAgentTable
[Required] [Required]
public DateTime CreatedTime { get; set; } = DateTime.UtcNow; public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
public static SessionRecord FromSession(Session sess) public static ConversationRecord FromConversation(Conversation conv)
{ {
return new SessionRecord return new ConversationRecord
{ {
AgentId = sess.AgentId, AgentId = conv.AgentId,
UserId = sess.UserId, UserId = conv.UserId,
Id = sess.Id, Id = conv.Id,
Title = sess.Title, Title = conv.Title,
CreatedTime = sess.CreatedTime, CreatedTime = conv.CreatedTime,
UpdatedTime = sess.UpdatedTime UpdatedTime = conv.UpdatedTime
}; };
} }
public Session ToSession() public Conversation ToConversation()
{ {
return new Session return new Conversation
{ {
Id = Id, Id = Id,
Title = Title, Title = Title,

View file

@ -1,11 +1,10 @@
using BotSharp.Core.Repository.Collections; using BotSharp.Core.Repository.Collections;
using EntityFrameworkCore.BootKit;
using MongoDB.Driver; using MongoDB.Driver;
namespace BotSharp.Core.Repository; namespace BotSharp.Core.Repository;
public class MongoDbContext : Database public class MongoDbContext : Database
{ {
public IMongoCollection<Conversation> Conversations public IMongoCollection<ConversationCollection> Conversations
=> Collection<Conversation>("conversations"); => Collection<ConversationCollection>("conversations");
} }

View file

@ -8,11 +8,8 @@ global using BotSharp.Abstraction.Plugins;
global using EntityFrameworkCore.BootKit; global using EntityFrameworkCore.BootKit;
global using BotSharp.Abstraction.Agents; global using BotSharp.Abstraction.Agents;
global using BotSharp.Abstraction.Conversations; global using BotSharp.Abstraction.Conversations;
global using BotSharp.Abstraction.Infrastructures.ContentTransmitters;
global using BotSharp.Abstraction.Infrastructures.ContentTransfers;
global using BotSharp.Abstraction.Knowledges; global using BotSharp.Abstraction.Knowledges;
global using BotSharp.Abstraction.Users; global using BotSharp.Abstraction.Users;
global using BotSharp.Abstraction.Models;
global using BotSharp.Core.Repository; global using BotSharp.Core.Repository;
global using BotSharp.Core.Repository.Abstraction; global using BotSharp.Core.Repository.Abstraction;
global using BotSharp.Core.Repository.DbTables; global using BotSharp.Core.Repository.DbTables;

View file

@ -1,8 +1,6 @@
using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Plugins; using BotSharp.Abstraction.Plugins;
using BotSharp.Plugin.AzureOpenAI.Providers; using BotSharp.Plugin.AzureOpenAI.Providers;
using BotSharp.Plugin.AzureOpenAI.Services;
using BotSharp.Plugin.AzureOpenAI.Settings; using BotSharp.Plugin.AzureOpenAI.Settings;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -19,6 +17,5 @@ public class AzureOpenAiPlugin : IBotSharpPlugin
services.AddSingleton<ITextCompletion, TextCompletionProvider>(); services.AddSingleton<ITextCompletion, TextCompletionProvider>();
services.AddScoped<IChatCompletion, ChatCompletionProvider>(); services.AddScoped<IChatCompletion, ChatCompletionProvider>();
services.AddScoped<IChatServiceZone, ChatService>();
} }
} }

View file

@ -1,9 +1,8 @@
using Azure; using Azure;
using Azure.AI.OpenAI; using Azure.AI.OpenAI;
using BotSharp.Abstraction.Infrastructures.ContentTransfers; using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Infrastructures.ContentTransmitters; using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Models;
using BotSharp.Plugin.AzureOpenAI.Settings; using BotSharp.Plugin.AzureOpenAI.Settings;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -21,7 +20,7 @@ public class ChatCompletionProvider : IChatCompletion
_settings = settings; _settings = settings;
} }
public async Task GetChatCompletionsAsync(List<RoleDialogModel> conversations, /*public async Task GetChatCompletionsAsync(List<RoleDialogModel> conversations,
Func<string, Task> onChunkReceived) Func<string, Task> onChunkReceived)
{ {
var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey)); var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey));
@ -44,14 +43,14 @@ public class ChatCompletionProvider : IChatCompletion
} }
Console.WriteLine(); Console.WriteLine();
} }*/
public List<RoleDialogModel> GetChatSamples() public List<RoleDialogModel> GetChatSamples(string sampleText)
{ {
var samples = new List<RoleDialogModel>(); var samples = new List<RoleDialogModel>();
if (!string.IsNullOrEmpty(_settings.ChatSampleFile)) if (!string.IsNullOrEmpty(sampleText))
{ {
var lines = File.ReadAllLines(_settings.ChatSampleFile); var lines = sampleText.Split('\n');
for (int i = 0; i < lines.Length; i++) for (int i = 0; i < lines.Length; i++)
{ {
var line = lines[i]; var line = lines[i];
@ -68,19 +67,11 @@ public class ChatCompletionProvider : IChatCompletion
return samples; return samples;
} }
public string GetInstruction()
{
if (!string.IsNullOrEmpty(_settings.InstructionFile))
{
return File.ReadAllText(_settings.InstructionFile);
}
return string.Empty;
}
public async Task<string> GetChatCompletionsAsync(List<RoleDialogModel> conversations) public async Task<string> GetChatCompletionsAsync(Agent agent, List<RoleDialogModel> conversations)
{ {
var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey)); var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey));
var chatCompletionsOptions = PrepareOptions(conversations); var chatCompletionsOptions = PrepareOptions(agent, conversations);
var response = await client.GetChatCompletionsStreamingAsync(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions); var response = await client.GetChatCompletionsStreamingAsync(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions);
using StreamingChatCompletions streaming = response.Value; using StreamingChatCompletions streaming = response.Value;
@ -100,18 +91,17 @@ public class ChatCompletionProvider : IChatCompletion
return output; return output;
} }
private ChatCompletionsOptions PrepareOptions(List<RoleDialogModel> conversations) private ChatCompletionsOptions PrepareOptions(Agent agent, List<RoleDialogModel> conversations)
{ {
var prompt = GetInstruction();
var chatCompletionsOptions = new ChatCompletionsOptions() var chatCompletionsOptions = new ChatCompletionsOptions()
{ {
Messages = Messages =
{ {
new ChatMessage(ChatRole.System, prompt) new ChatMessage(ChatRole.System, agent.Instruction)
} }
}; };
foreach (var message in GetChatSamples()) foreach (var message in GetChatSamples(agent.Samples))
{ {
chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Text)); chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Text));
} }

View file

@ -1,31 +0,0 @@
using Azure.AI.OpenAI;
using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Infrastructures.ContentTransmitters;
using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Models;
using System.Threading.Tasks;
namespace BotSharp.Plugin.AzureOpenAI.Services;
public class ChatService : IChatServiceZone
{
private readonly IChatCompletion _chatCompletion;
public ChatService(IChatCompletion chatCompletion)
{
_chatCompletion = chatCompletion;
}
public int Priority => 100;
public async Task Serving(ContentContainer content)
{
var output = await _chatCompletion.GetChatCompletionsAsync(content.Conversations);
content.Output = new RoleDialogModel
{
Role = ChatRole.Assistant.ToString(),
Text = output
};
}
}

View file

@ -13,8 +13,9 @@ using System;
using Azure.AI.OpenAI; using Azure.AI.OpenAI;
using BotSharp.Abstraction.ApiAdapters; using BotSharp.Abstraction.ApiAdapters;
using BotSharp.Plugin.ChatbotUI.ViewModels; using BotSharp.Plugin.ChatbotUI.ViewModels;
using BotSharp.Abstraction.Infrastructures.ContentTransmitters;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Conversations.Models;
namespace BotSharp.Plugin.ChatbotUI.Controllers; namespace BotSharp.Plugin.ChatbotUI.Controllers;
@ -64,22 +65,11 @@ public class ChatbotUiController : ControllerBase, IApiAdapter
Text = x.Content Text = x.Content
}).ToList(); }).ToList();
/*await _chatCompletionProvider.GetChatCompletionsAsync(conversations, var conv = _services.GetRequiredService<IConversationService>();
async content =>
{
await OnChunkReceived(outputStream, content);
});*/
var transmitter = _services.GetRequiredService<IContentTransfer>(); var result = await conv.SendMessage("", "", conversations.Last());
var container = new ContentContainer await OnChunkReceived(outputStream, result);
{
Conversations = conversations
};
var result = await transmitter.Transport(container);
await OnChunkReceived(outputStream, container.Output.Text);
await OnEventCompleted(outputStream); await OnEventCompleted(outputStream);
} }

View file

@ -1,6 +1,3 @@
using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Infrastructures.ContentTransmitters;
using BotSharp.Abstraction.Models;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Senparc.NeuChar.App.AppStore; using Senparc.NeuChar.App.AppStore;
using Senparc.NeuChar.Entities; using Senparc.NeuChar.Entities;

View file

@ -1,5 +1,5 @@
using BotSharp.Abstraction.Conversations; using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Infrastructures.ContentTransmitters; using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Models;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@ -22,41 +22,23 @@ namespace BotSharp.Plugin.WeChat
ILogger<WeChatBackgroundService> logger) ILogger<WeChatBackgroundService> logger)
{ {
this._service = service; _service = service;
this._logger = logger; _logger = logger;
this._queue = Channel.CreateUnbounded<WeChatMessage>(); _queue = Channel.CreateUnbounded<WeChatMessage>();
} }
private async Task HandleTextMessageAsync(string openid, string message) private async Task HandleTextMessageAsync(string openid, string message)
{ {
var scoped = _service.CreateScope().ServiceProvider; var scoped = _service.CreateScope().ServiceProvider;
var conversationService = scoped.GetRequiredService<IConversationService>(); var conversationService = scoped.GetRequiredService<IConversationService>();
var contentTransfer = scoped.GetRequiredService<IContentTransfer>();
var conversations = conversationService.GetDialogHistory(openid); var result = await conversationService.SendMessage(openid, Guid.Empty.ToString(), new RoleDialogModel
conversations.Add(new RoleDialogModel
{ {
Role = "User", Role = "user",
Text = message, Text = message,
}); });
var container = new ContentContainer await ReplyTextMessageAsync(openid, result);
{
Conversations = conversations
};
var result = await contentTransfer.Transport(container);
if (result.IsSuccess)
{
var output = container.Output.Text.Trim();
await ReplyTextMessageAsync(openid, output);
conversationService.AddDialog(new RoleDialogModel()
{
Role = "Assistant",
Text = output,
});
}
} }
private async Task ReplyTextMessageAsync(string openid, string content) private async Task ReplyTextMessageAsync(string openid, string content)

View file

@ -1,2 +0,0 @@
user: Hi
assistant: Hello, I'm a AI assistant to help you schedule meeting.

View file

@ -1,7 +0,0 @@
Transcript of a dialog, where the User interacts with an Assistant named Bob. Bob is helpful, kind, honest, good at writing, and never fails to answer the User's requests immediately and with precision.
User: Hello, Bob.
Bob: Hello. How may I help you today?
User: Please tell me the largest city in Europe.
Bob: Sure. The largest city in Europe is Moscow, the capital of Russia.
User:

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
@ -8,25 +8,22 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="data\**" />
<Compile Remove="knowledge_chunks\**" /> <Compile Remove="knowledge_chunks\**" />
<Compile Remove="Prompts\**" />
<Content Remove="data\**" />
<Content Remove="knowledge_chunks\**" /> <Content Remove="knowledge_chunks\**" />
<Content Remove="Prompts\**" />
<EmbeddedResource Remove="data\**" />
<EmbeddedResource Remove="knowledge_chunks\**" /> <EmbeddedResource Remove="knowledge_chunks\**" />
<EmbeddedResource Remove="Prompts\**" />
<None Remove="data\**" />
<None Remove="knowledge_chunks\**" /> <None Remove="knowledge_chunks\**" />
<None Remove="Prompts\**" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="crawl-300d-2M-subword.bin" /> <None Remove="crawl-300d-2M-subword.bin" />
<None Remove="Prompts\chat-samples.txt" />
<None Remove="Prompts\chat-with-bob.txt" />
</ItemGroup>
<ItemGroup>
<Content Include="Prompts\chat-samples.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Prompts\chat-with-bob.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -14,7 +14,7 @@
}, },
"Conversation": { "Conversation": {
"ChatCompletion": "AzureOpenAI.Providers.ChatCompletionProvider"
}, },
"LlamaSharp": { "LlamaSharp": {
@ -48,7 +48,7 @@
"Master": "mongodb://localhost:27017/chat-ui" "Master": "mongodb://localhost:27017/chat-ui"
}, },
"Agent": { "Agent": {
"Master": "Data Source=(localdb)\\ProjectModels;Initial Catalog=Agent;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False", "Master": "Data Source=(localdb)\\ProjectModels;Initial Catalog=BotSharp;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False",
"Slavers": [] "Slavers": []
}, },
"UseCamelCase": true, "UseCamelCase": true,
@ -70,7 +70,7 @@
"KnowledgeBase": { "KnowledgeBase": {
"VectorDb": "MemVectorDatabase", "VectorDb": "MemVectorDatabase",
"TextEmbedding": "fastTextEmbeddingProvider", "TextEmbedding": "fastTextEmbeddingProvider",
"TextCompletion": "TextCompletionProvider" "TextCompletion": "AzureOpenAI.Providers.TextCompletionProvider"
}, },
"PluginLoader": { "PluginLoader": {