commit
893acaa493
|
|
@ -0,0 +1,40 @@
|
|||
namespace BotSharp.Abstraction.Agents;
|
||||
|
||||
public abstract class AgentHookBase : IAgentHook
|
||||
{
|
||||
protected Agent _agent;
|
||||
public Agent Agent => _agent;
|
||||
|
||||
public void SetAget(Agent agent)
|
||||
{
|
||||
_agent = agent;
|
||||
}
|
||||
|
||||
public virtual bool OnAgentLoading(ref string id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool OnInstructionLoaded(ref string instruction)
|
||||
{
|
||||
_agent.Instruction = instruction;
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool OnFunctionsLoaded(ref string functions)
|
||||
{
|
||||
_agent.Functions = functions;
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool OnSamplesLoaded(ref string samples)
|
||||
{
|
||||
_agent.Samples = samples;
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual Agent OnAgentLoaded()
|
||||
{
|
||||
return _agent;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.Agents.Enums;
|
||||
|
||||
public class AgentRole
|
||||
{
|
||||
public const string System = "system";
|
||||
public const string Assistant = "assistant";
|
||||
public const string User = "user";
|
||||
public const string Function = "function";
|
||||
}
|
||||
28
src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs
Normal file
28
src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
namespace BotSharp.Abstraction.Agents;
|
||||
|
||||
public interface IAgentHook
|
||||
{
|
||||
Agent Agent { get; }
|
||||
void SetAget(Agent agent);
|
||||
|
||||
/// <summary>
|
||||
/// Triggered before loading, you can change the returned id to switch agent.
|
||||
/// </summary>
|
||||
/// <param name="id">Agent Id</param>
|
||||
/// <returns></returns>
|
||||
bool OnAgentLoading(ref string id);
|
||||
|
||||
|
||||
bool OnInstructionLoaded(ref string instruction);
|
||||
|
||||
bool OnFunctionsLoaded(ref string functions);
|
||||
|
||||
bool OnSamplesLoaded(ref string samples);
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when agent is loaded completely.
|
||||
/// </summary>
|
||||
/// <param name="agent"></param>
|
||||
/// <returns></returns>
|
||||
Agent OnAgentLoaded();
|
||||
}
|
||||
|
|
@ -7,6 +7,14 @@ public interface IAgentService
|
|||
{
|
||||
Task<Agent> CreateAgent(Agent agent);
|
||||
Task<List<Agent>> GetAgents();
|
||||
|
||||
/// <summary>
|
||||
/// Load agent configurations and triggher hooks
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
Task<Agent> LoadAgent(string id);
|
||||
|
||||
Task<Agent> GetAgent(string id);
|
||||
Task<bool> DeleteAgent(string id);
|
||||
Task UpdateAgent(Agent agent);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
|
||||
namespace BotSharp.Abstraction.Conversations;
|
||||
|
||||
public abstract class ConversationCompletionHookBase : IConversationCompletionHook
|
||||
public abstract class ConversationHookBase : IConversationHook
|
||||
{
|
||||
protected Agent _agent;
|
||||
public Agent Agent => _agent;
|
||||
|
|
@ -14,34 +13,24 @@ public abstract class ConversationCompletionHookBase : IConversationCompletionHo
|
|||
protected List<RoleDialogModel> _dialogs;
|
||||
public List<RoleDialogModel> Dialogs => _dialogs;
|
||||
|
||||
protected IChatCompletion _chatCompletion;
|
||||
public IChatCompletion ChatCompletion => _chatCompletion;
|
||||
|
||||
public IConversationCompletionHook SetAgent(Agent agent)
|
||||
public IConversationHook SetAgent(Agent agent)
|
||||
{
|
||||
_agent = agent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IConversationCompletionHook SetConversation(Conversation conversation)
|
||||
public IConversationHook SetConversation(Conversation conversation)
|
||||
{
|
||||
_conversation = conversation;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IConversationCompletionHook SetDialogs(List<RoleDialogModel> dialogs)
|
||||
public virtual Task OnStateLoaded(ConversationState state)
|
||||
{
|
||||
_dialogs = dialogs;
|
||||
return this;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public IConversationCompletionHook SetChatCompletion(IChatCompletion chatCompletion)
|
||||
{
|
||||
_chatCompletion = chatCompletion;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual Task OnStateLoaded(ConversationState state, Action<Agent>? onAgentSwitched = null)
|
||||
public virtual Task OnStateChanged(string name, string preValue, string currentValue)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
|
@ -51,7 +40,12 @@ public abstract class ConversationCompletionHookBase : IConversationCompletionHo
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual Task OnFunctionExecuting(string name, string args)
|
||||
public virtual Task OnFunctionExecuting(RoleDialogModel message)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual Task OnFunctionExecuted(RoleDialogModel message)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
|
@ -60,4 +54,10 @@ public abstract class ConversationCompletionHookBase : IConversationCompletionHo
|
|||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual Task OnDialogsLoaded(List<RoleDialogModel> dialogs)
|
||||
{
|
||||
_dialogs = dialogs;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
|
||||
namespace BotSharp.Abstraction.Conversations;
|
||||
|
||||
public interface IConversationCompletionHook
|
||||
{
|
||||
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 OnStateLoaded(ConversationState state, Action<Agent>? onAgentSwitched = null);
|
||||
Task BeforeCompletion();
|
||||
Task OnFunctionExecuting(string name, string args);
|
||||
Task AfterCompletion(RoleDialogModel message);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
|
||||
namespace BotSharp.Abstraction.Conversations;
|
||||
|
||||
public interface IConversationHook
|
||||
{
|
||||
Agent Agent { get; }
|
||||
IConversationHook SetAgent(Agent agent);
|
||||
|
||||
Conversation Conversation { get; }
|
||||
IConversationHook SetConversation(Conversation conversation);
|
||||
|
||||
List<RoleDialogModel> Dialogs { get; }
|
||||
/// <summary>
|
||||
/// Triggered when dialog history is loaded
|
||||
/// </summary>
|
||||
/// <param name="dialogs"></param>
|
||||
/// <returns></returns>
|
||||
Task OnDialogsLoaded(List<RoleDialogModel> dialogs);
|
||||
|
||||
Task OnStateLoaded(ConversationState state);
|
||||
Task OnStateChanged(string name, string preValue, string currentValue);
|
||||
|
||||
Task BeforeCompletion();
|
||||
Task OnFunctionExecuting(RoleDialogModel message);
|
||||
Task OnFunctionExecuted(RoleDialogModel message);
|
||||
Task AfterCompletion(RoleDialogModel message);
|
||||
}
|
||||
|
|
@ -7,7 +7,9 @@ namespace BotSharp.Abstraction.Conversations;
|
|||
/// </summary>
|
||||
public interface IConversationStateService
|
||||
{
|
||||
ConversationState Load(string conversationId);
|
||||
void SetConversation(string conversationId);
|
||||
ConversationState Load();
|
||||
string GetState(string name);
|
||||
void SetState(string name, string value);
|
||||
void Save();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,17 @@ public class RoleDialogModel
|
|||
/// </summary>
|
||||
public string? ExecutionResult { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When function callback has been executed, system will pass result to LLM again,
|
||||
/// Set this property to True to stop calling LLM.
|
||||
/// </summary>
|
||||
public bool StopSubsequentInteraction { get;set; }
|
||||
|
||||
/// <summary>
|
||||
/// Channel name
|
||||
/// </summary>
|
||||
public string Channel { get; set; }
|
||||
|
||||
public RoleDialogModel(string role, string text)
|
||||
{
|
||||
Role = role;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
|
||||
namespace BotSharp.Core.Agents.Services;
|
||||
|
||||
public partial class AgentService
|
||||
{
|
||||
public async Task<Agent> LoadAgent(string id)
|
||||
{
|
||||
var hooks = _services.GetServices<IAgentHook>();
|
||||
|
||||
// Before agent is loaded.
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.OnAgentLoading(ref id);
|
||||
}
|
||||
|
||||
var agent = await GetAgent(id);
|
||||
|
||||
// After agent is loaded
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.SetAget(agent);
|
||||
|
||||
if (!string.IsNullOrEmpty(agent.Instruction))
|
||||
{
|
||||
var instruction = agent.Instruction;
|
||||
hook.OnInstructionLoaded(ref instruction);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(agent.Functions))
|
||||
{
|
||||
var functions = agent.Functions;
|
||||
hook.OnFunctionsLoaded(ref functions);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(agent.Samples))
|
||||
{
|
||||
var samples = agent.Samples;
|
||||
hook.OnSamplesLoaded(ref samples);
|
||||
}
|
||||
|
||||
hook.OnAgentLoaded();
|
||||
}
|
||||
|
||||
return agent;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Conversations;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Functions;
|
||||
|
|
@ -117,9 +118,6 @@ public class ConversationService : IConversationService
|
|||
|
||||
public async Task<bool> SendMessage(string agentId, string conversationId, List<RoleDialogModel> wholeDialogs, Func<RoleDialogModel, Task> onMessageReceived)
|
||||
{
|
||||
var agent = await _services.GetRequiredService<IAgentService>()
|
||||
.GetAgent(agentId);
|
||||
|
||||
var converation = await GetConversation(conversationId);
|
||||
|
||||
// Create conversation if this conversation not exists
|
||||
|
|
@ -133,13 +131,18 @@ public class ConversationService : IConversationService
|
|||
converation = await NewConversation(sess);
|
||||
}
|
||||
|
||||
// load state
|
||||
// conversation state
|
||||
var stateService = _services.GetRequiredService<IConversationStateService>();
|
||||
var state = stateService.Load(conversationId);
|
||||
state["agentId"] = agentId;
|
||||
stateService.SetConversation(conversationId);
|
||||
stateService.Load();
|
||||
stateService.SetState("agentId", agentId);
|
||||
|
||||
// load agent
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(agentId);
|
||||
|
||||
// Get relevant domain knowledge
|
||||
if (_settings.EnableKnowledgeBase)
|
||||
/*if (_settings.EnableKnowledgeBase)
|
||||
{
|
||||
var knowledge = _services.GetRequiredService<IKnowledgeService>();
|
||||
agent.Knowledges = await knowledge.GetKnowledges(new KnowledgeRetrievalModel
|
||||
|
|
@ -147,21 +150,19 @@ public class ConversationService : IConversationService
|
|||
AgentId = agentId,
|
||||
Question = string.Join("\n", wholeDialogs.Select(x => x.Content))
|
||||
});
|
||||
}
|
||||
}*/
|
||||
|
||||
var chatCompletion = GetChatCompletion();
|
||||
|
||||
var hooks = _services.GetServices<IConversationCompletionHook>().ToList();
|
||||
var hooks = _services.GetServices<IConversationHook>().ToList();
|
||||
|
||||
// Before chat completion hook
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.SetAgent(agent)
|
||||
.SetConversation(converation)
|
||||
.SetDialogs(wholeDialogs)
|
||||
.SetChatCompletion(chatCompletion);
|
||||
.SetConversation(converation);
|
||||
|
||||
await hook.OnStateLoaded(state, onAgentSwitched: x => agent = x);
|
||||
await hook.OnDialogsLoaded(wholeDialogs);
|
||||
await hook.BeforeCompletion();
|
||||
}
|
||||
|
||||
|
|
@ -172,7 +173,7 @@ public class ConversationService : IConversationService
|
|||
// Before executing functions
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnFunctionExecuting(msg.FunctionName, msg.Content);
|
||||
await hook.OnFunctionExecuting(msg);
|
||||
}
|
||||
// Save states
|
||||
var jo = JsonSerializer.Deserialize<object>(msg.Content);
|
||||
|
|
@ -180,11 +181,7 @@ public class ConversationService : IConversationService
|
|||
{
|
||||
foreach (JsonProperty property in root.EnumerateObject())
|
||||
{
|
||||
string propertyName = property.Name;
|
||||
string propertyValue = property.Value.ToString();
|
||||
|
||||
_logger.LogInformation($"Set conversation state: {propertyName} - {propertyValue}");
|
||||
state[propertyName] = propertyValue;
|
||||
stateService.SetState(property.Name, property.Value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -197,6 +194,15 @@ public class ConversationService : IConversationService
|
|||
}
|
||||
}
|
||||
await onMessageReceived(msg);
|
||||
|
||||
if (msg.Role == AgentRole.Function)
|
||||
{
|
||||
// After functions have been executed
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnFunctionExecuted(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Conversations.Services;
|
||||
|
|
@ -8,27 +10,44 @@ namespace BotSharp.Core.Conversations.Services;
|
|||
/// </summary>
|
||||
public class ConversationStateService : IConversationStateService, IDisposable
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IServiceProvider _services;
|
||||
private ConversationState _state;
|
||||
private MyDatabaseSettings _dbSettings;
|
||||
private string _conversationId;
|
||||
private string _file;
|
||||
|
||||
public ConversationStateService(MyDatabaseSettings dbSettings)
|
||||
public ConversationStateService(ILogger<ConversationStateService> logger,
|
||||
IServiceProvider services,
|
||||
MyDatabaseSettings dbSettings)
|
||||
{
|
||||
_logger = logger;
|
||||
_services = services;
|
||||
_dbSettings = dbSettings;
|
||||
}
|
||||
|
||||
public void SetState(string name, string value)
|
||||
{
|
||||
_state[name] = value;
|
||||
var hooks = _services.GetServices<IConversationHook>();
|
||||
string preValue = _state.ContainsKey(name) ? _state[name] : "";
|
||||
if (!_state.ContainsKey(name) || _state[name] != value)
|
||||
{
|
||||
var currentValue = value;
|
||||
_state[name] = currentValue;
|
||||
_logger.LogInformation($"Set state: {name} - {value}");
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.OnStateChanged(name, preValue, currentValue).Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public void SetConversation(string conversationId)
|
||||
{
|
||||
Save();
|
||||
_conversationId = conversationId;
|
||||
}
|
||||
|
||||
public ConversationState Load(string conversationId)
|
||||
public ConversationState Load()
|
||||
{
|
||||
if (_state != null)
|
||||
{
|
||||
|
|
@ -36,7 +55,6 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
}
|
||||
|
||||
_state = new ConversationState();
|
||||
_conversationId = conversationId;
|
||||
|
||||
_file = GetStorageFile(_conversationId);
|
||||
|
||||
|
|
@ -45,10 +63,17 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
var dict = File.ReadAllLines(_file);
|
||||
foreach (var line in dict)
|
||||
{
|
||||
_state[line.Split(':')[0]] = line.Split(':')[1];
|
||||
_state[line.Split('=')[0]] = line.Split('=')[1];
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Loaded state {_conversationId}");
|
||||
var hooks = _services.GetServices<IConversationHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.OnStateLoaded(_state).Wait();
|
||||
}
|
||||
|
||||
return _state;
|
||||
}
|
||||
|
||||
|
|
@ -58,9 +83,10 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
|
||||
foreach (var dic in _state)
|
||||
{
|
||||
states.Add($"{dic.Key}:{dic.Value}");
|
||||
states.Add($"{dic.Key}={dic.Value}");
|
||||
}
|
||||
File.WriteAllLines(_file, states);
|
||||
_logger.LogInformation($"Saved state {_conversationId}");
|
||||
}
|
||||
|
||||
private string GetStorageFile(string conversationId)
|
||||
|
|
@ -81,4 +107,9 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
}
|
||||
return _state[name];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,13 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
// Execute functions
|
||||
await onMessageReceived(funcContextIn);
|
||||
|
||||
if (funcContextIn.StopSubsequentInteraction)
|
||||
{
|
||||
// Emit a fake message that should be populated by whom set StopSubsequentInteraction as True.
|
||||
await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), ""));
|
||||
return true;
|
||||
}
|
||||
|
||||
// After function is executed, pass the result to LLM
|
||||
chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.Function, funcContextIn.ExecutionResult)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using System.Text.Json;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Refit;
|
||||
using BotSharp.Abstraction.Agents.Enums;
|
||||
|
||||
namespace BotSharp.Plugin.MetaMessenger.Controllers;
|
||||
|
||||
|
|
@ -94,8 +95,15 @@ public class WebhookController : ControllerBase
|
|||
});
|
||||
|
||||
// Go to LLM
|
||||
var result = await conv.SendMessage(agentId, senderId, new RoleDialogModel("user", input), async msg =>
|
||||
var result = await conv.SendMessage(agentId, senderId, new RoleDialogModel("user", input)
|
||||
{
|
||||
Channel = "messenger"
|
||||
}, async msg =>
|
||||
{
|
||||
if (msg.Role == AgentRole.Function)
|
||||
{
|
||||
|
||||
}
|
||||
content = msg.Content;
|
||||
}, async fn =>
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue