Redefine IFunctionCallback.Execute.

This commit is contained in:
hchen 2023-08-14 17:14:05 -05:00
parent 362ce4a236
commit f6dec6e6e2
8 changed files with 129 additions and 78 deletions

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.MLTasks;
namespace BotSharp.Abstraction.Conversations;
@ -8,8 +9,39 @@ public interface IConversationService
Task<Conversation> GetConversation(string id);
Task<List<Conversation>> GetConversations();
Task DeleteConversation(string id);
Task<bool> SendMessage(string agentId, string conversationId, RoleDialogModel lastDalog, Func<RoleDialogModel, Task> onMessageReceived, Func<RoleDialogModel, Task> onFunctionExecuting);
Task<bool> SendMessage(string agentId, string conversationId, List<RoleDialogModel> wholeDialogs, Func<RoleDialogModel, Task> onMessageReceived);
IChatCompletion GetChatCompletion();
/// <summary>
/// Send message to LLM
/// </summary>
/// <param name="agentId"></param>
/// <param name="conversationId"></param>
/// <param name="lastDalog"></param>
/// <param name="onMessageReceived"></param>
/// <param name="onFunctionExecuting">This delegate is useful when you want to report progress on UI</param>
/// <returns></returns>
Task<bool> SendMessage(string agentId,
string conversationId,
RoleDialogModel lastDalog,
Func<RoleDialogModel, Task> onMessageReceived,
Func<RoleDialogModel, Task> onFunctionExecuting);
/// <summary>
/// Send message to LLM if frontend passed over the dialog history
/// </summary>
/// <param name="agentId"></param>
/// <param name="conversationId"></param>
/// <param name="wholeDialogs"></param>
/// <param name="onMessageReceived"></param>
/// <param name="onFunctionExecuting">This delegate is useful when you want to report progress on UI</param>
/// <returns></returns>
Task<bool> SendMessage(string agentId,
string conversationId,
List<RoleDialogModel> wholeDialogs,
Func<RoleDialogModel, Task> onMessageReceived,
Func<RoleDialogModel, Task> onFunctionExecuting);
List<RoleDialogModel> GetDialogHistory(string conversationId, int lastCount = 20);
Task CleanHistory(string agentId);
}

View file

@ -1,7 +1,9 @@
using BotSharp.Abstraction.Conversations.Models;
namespace BotSharp.Abstraction.Functions;
public interface IFunctionCallback
{
string Name { get; }
Task<string> Execute(string args);
Task<bool> Execute(RoleDialogModel message);
}

View file

@ -48,13 +48,12 @@ public class ConversationController : ControllerBase, IApiAdapter
var response = new MessageResponseModel();
await conv.SendMessage(agentId, conversationId, new RoleDialogModel("user", input.Text), async msg =>
{
response.Text += msg.Content;
}, async fn =>
{
});
await conv.SendMessage(agentId, conversationId,
new RoleDialogModel("user", input.Text),
async msg =>
response.Text += msg.Content,
async fn
=> await Task.CompletedTask);
return response;
}

View file

@ -1,11 +1,7 @@
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Functions;
using BotSharp.Abstraction.Knowledges.Models;
using BotSharp.Abstraction.MLTasks;
using Microsoft.Extensions.Logging;
using System.Text.Json;
namespace BotSharp.Core.Conversations.Services;
@ -74,7 +70,8 @@ public class ConversationService : IConversationService
return record.ToConversation();
}
public async Task<bool> SendMessage(string agentId, string conversationId, RoleDialogModel lastDalog,
public async Task<bool> SendMessage(string agentId, string conversationId,
RoleDialogModel lastDalog,
Func<RoleDialogModel, Task> onMessageReceived,
Func<RoleDialogModel, Task> onFunctionExecuting)
{
@ -82,41 +79,17 @@ public class ConversationService : IConversationService
var wholeDialogs = GetDialogHistory(conversationId);
var response = await SendMessage(agentId, conversationId, wholeDialogs, async msg =>
{
if (msg.Role == "function")
{
// Invoke functions
var functions = _services.GetServices<IFunctionCallback>().Where(x => x.Name == msg.FunctionName);
foreach (var fn in functions)
{
await onFunctionExecuting(msg);
msg.ExecutionResult = await fn.Execute(msg.Content);
var result = msg.ExecutionResult.Replace("\r", " ").Replace("\n", " ");
var content = $"{result}";
// Console.WriteLine($"{msg.Role}: {content}");
_storage.Append(conversationId, new RoleDialogModel(msg.Role, content)
{
FunctionName = msg.FunctionName,
});
}
}
else
{
var content = msg.Content.Replace("\r", " ").Replace("\n", " ");
// Console.WriteLine($"{msg.Role}: {content}");
_storage.Append(conversationId, new RoleDialogModel(msg.Role, content));
await onMessageReceived(msg);
}
});
var response = await SendMessage(agentId, conversationId, wholeDialogs,
onMessageReceived: onMessageReceived,
onFunctionExecuting: onFunctionExecuting);
return response;
}
public async Task<bool> SendMessage(string agentId, string conversationId, List<RoleDialogModel> wholeDialogs, Func<RoleDialogModel, Task> onMessageReceived)
public async Task<bool> SendMessage(string agentId, string conversationId,
List<RoleDialogModel> wholeDialogs,
Func<RoleDialogModel, Task> onMessageReceived,
Func<RoleDialogModel, Task> onFunctionExecuting)
{
var converation = await GetConversation(conversationId);
@ -152,8 +125,6 @@ public class ConversationService : IConversationService
});
}*/
var chatCompletion = GetChatCompletion();
var hooks = _services.GetServices<IConversationHook>().ToList();
// Before chat completion hook
@ -166,48 +137,83 @@ public class ConversationService : IConversationService
await hook.BeforeCompletion();
}
var chatCompletion = GetChatCompletion();
var result = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg =>
{
if (msg.Role == "function")
{
// Before executing functions
foreach (var hook in hooks)
{
await hook.OnFunctionExecuting(msg);
}
// Save states
var jo = JsonSerializer.Deserialize<object>(msg.Content);
if (jo is JsonElement root)
{
foreach (JsonProperty property in root.EnumerateObject())
{
stateService.SetState(property.Name, property.Value.ToString());
}
}
SaveStateByArgs(msg.Content);
// Call functions
await onFunctionExecuting(msg);
await CallFunctions(conversationId, msg);
}
else
{
// Add to dialog history
_storage.Append(conversationId, new RoleDialogModel(msg.Role, msg.Content));
// After chat completion hook
foreach (var hook in hooks)
{
await hook.AfterCompletion(msg);
}
}
await onMessageReceived(msg);
if (msg.Role == AgentRole.Function)
{
// After functions have been executed
foreach (var hook in hooks)
{
await hook.OnFunctionExecuted(msg);
}
await onMessageReceived(msg);
}
});
return result;
}
private void SaveStateByArgs(string args)
{
var stateService = _services.GetRequiredService<IConversationStateService>();
var jo = JsonSerializer.Deserialize<object>(args);
if (jo is JsonElement root)
{
foreach (JsonProperty property in root.EnumerateObject())
{
stateService.SetState(property.Name, property.Value.ToString());
}
}
}
private async Task CallFunctions(string conversationId, RoleDialogModel msg)
{
var hooks = _services.GetServices<IConversationHook>().ToList();
// Invoke functions
var functions = _services.GetServices<IFunctionCallback>()
.Where(x => x.Name == msg.FunctionName)
.ToList();
foreach (var fn in functions)
{
// Before executing functions
foreach (var hook in hooks)
{
await hook.OnFunctionExecuting(msg);
}
// Execute function
await fn.Execute(msg);
// Add to dialog history
_storage.Append(conversationId, new RoleDialogModel(msg.Role, msg.ExecutionResult)
{
FunctionName = msg.FunctionName,
});
// After functions have been executed
foreach (var hook in hooks)
{
await hook.OnFunctionExecuted(msg);
}
}
}
public IChatCompletion GetChatCompletion()
{
var completions = _services.GetServices<IChatCompletion>();
@ -222,6 +228,8 @@ public class ConversationService : IConversationService
public List<RoleDialogModel> GetDialogHistory(string conversationId, int lastCount = 20)
{
var dialogs = _storage.GetDialogs(conversationId);
return dialogs.TakeLast(lastCount).ToList();
return dialogs
.Where(x => x.CreatedAt > DateTime.UtcNow.AddHours(-8))
.TakeLast(lastCount).ToList();
}
}

View file

@ -34,7 +34,7 @@ public class ConversationStateService : IConversationStateService, IDisposable
{
var currentValue = value;
_state[name] = currentValue;
_logger.LogInformation($"Set state: {name} - {value}");
_logger.LogInformation($"Set state: {name} = {value}");
foreach (var hook in hooks)
{
hook.OnStateChanged(name, preValue, currentValue).Wait();

View file

@ -5,11 +5,9 @@ namespace BotSharp.Core.Conversations.Services;
public class ConversationStorage : IConversationStorage
{
private readonly IAgentService _agent;
private readonly MyDatabaseSettings _dbSettings;
public ConversationStorage(IAgentService agent, MyDatabaseSettings dbSettings)
public ConversationStorage(MyDatabaseSettings dbSettings)
{
_agent = agent;
_dbSettings = dbSettings;
}
@ -18,7 +16,15 @@ public class ConversationStorage : IConversationStorage
var conversationFile = GetStorageFile(conversationId);
var sb = new StringBuilder();
sb.AppendLine($"{dialog.Role}|{dialog.CreatedAt}|{dialog.FunctionName}");
sb.AppendLine($" - {dialog.Content}");
var content = dialog.Content.Trim().Replace("\r", " ").Replace("\n", " ");
if (string.IsNullOrEmpty(content))
{
return;
}
sb.AppendLine($" - {content}");
var conversation = sb.ToString();
File.AppendAllText(conversationFile, conversation);
}

View file

@ -3,9 +3,11 @@ 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.DependencyInjection;
global using BotSharp.Abstraction.Plugins;
global using Microsoft.Extensions.Logging;
global using EntityFrameworkCore.BootKit;
global using BotSharp.Abstraction.Plugins;
global using BotSharp.Abstraction.Agents;
global using BotSharp.Abstraction.Conversations;
global using BotSharp.Abstraction.Knowledges;

View file

@ -70,7 +70,9 @@ public class ChatbotUiController : ControllerBase, IApiAdapter
input.ConversationId,
conversations,
async msg =>
await OnChunkReceived(outputStream, msg));
await OnChunkReceived(outputStream, msg),
async fn
=> await Task.CompletedTask);
await OnEventCompleted(outputStream);
}