Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Conversations.Models;
using Fluid;

namespace BotSharp.Core.Agents.Services;
Expand Down Expand Up @@ -67,9 +66,4 @@ public virtual bool OnSamplesLoaded(ref string samples)
public virtual void OnAgentLoaded(Agent agent)
{
}

public virtual bool OnAgentRouting(RoleDialogModel message, ref string id)
{
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Functions;

namespace BotSharp.Core.Conversations.Services;

public partial class ConversationService
{
private async Task CallFunctions(RoleDialogModel msg)
{
var hooks = _services.GetServices<IConversationHook>().ToList();

// Invoke functions
var functions = _services.GetServices<IFunctionCallback>()
.Where(x => x.Name == msg.FunctionName)
.ToList();

if (functions.Count == 0)
{
msg.Content = $"Can't find function implementation of {msg.FunctionName}.";
_logger.LogError(msg.Content);
return;
}

foreach (var fn in functions)
{
// Before executing functions
foreach (var hook in hooks)
{
await hook.OnFunctionExecuting(msg);
}

// Execute function
await fn.Execute(msg);

// After functions have been executed
foreach (var hook in hooks)
{
await hook.OnFunctionExecuted(msg);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.MLTasks;

namespace BotSharp.Core.Conversations.Services;

public partial class ConversationService
{
const int maxRecursiveDepth = 3;
int currentRecursiveDepth = 0;

private async Task<bool> GetChatCompletionsAsyncRecursively(IChatCompletion chatCompletion,
string conversationId,
Agent agent,
List<RoleDialogModel> wholeDialogs,
Func<RoleDialogModel, Task> onMessageReceived,
Func<RoleDialogModel, Task> onFunctionExecuting)
{
currentRecursiveDepth++;
if (currentRecursiveDepth > maxRecursiveDepth)
{
_logger.LogError($"Exceed max current recursive depth.");
await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, "System has exception, please try later.")
{
CurrentAgentId = agent.Id
}, onMessageReceived);
return false;
}

var result = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg =>
{
await HandleAssistantMessage(msg, onMessageReceived);

// Add to dialog history
_storage.Append(conversationId, agent.Id, msg);
}, async fn =>
{
var preAgentId = agent.Id;

await HandleFunctionMessage(fn, onFunctionExecuting);

// Function executed has exception
if (fn.ExecutionResult == null)
{
await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, fn.Content)
{
CurrentAgentId = fn.CurrentAgentId
}, onMessageReceived);
return;
}

fn.Content = fn.ExecutionResult;

// Agent has been transferred
if (fn.CurrentAgentId != preAgentId)
{
var agentSettings = _services.GetRequiredService<AgentSettings>();
var agentService = _services.GetRequiredService<IAgentService>();
agent = await agentService.LoadAgent(fn.CurrentAgentId);

// Set state to make next conversation will go to this agent directly
// var state = _services.GetRequiredService<IConversationStateService>();
// state.SetState("agentId", fn.CurrentAgentId);
}

// Add to dialog history
_storage.Append(conversationId, preAgentId, fn);

// After function is executed, pass the result to LLM to get a natural response
wholeDialogs.Add(fn);

await GetChatCompletionsAsyncRecursively(chatCompletion, conversationId, agent, wholeDialogs, onMessageReceived, onFunctionExecuting);
});

return result;
}

private async Task HandleAssistantMessage(RoleDialogModel msg, Func<RoleDialogModel, Task> onMessageReceived)
{
var hooks = _services.GetServices<IConversationHook>().ToList();

// After chat completion hook
foreach (var hook in hooks)
{
await hook.AfterCompletion(msg);
}

await onMessageReceived(msg);
}

private async Task HandleFunctionMessage(RoleDialogModel msg, Func<RoleDialogModel, Task> onFunctionExecuting)
{
// Save states
SaveStateByArgs(msg.FunctionArgs);

// Call functions
await onFunctionExecuting(msg);
await CallFunctions(msg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.MLTasks;

namespace BotSharp.Core.Conversations.Services;

public partial class ConversationService
{
public async Task<bool> SendMessage(string agentId, string conversationId,
RoleDialogModel lastDialog,
Func<RoleDialogModel, Task> onMessageReceived,
Func<RoleDialogModel, Task> onFunctionExecuting)
{
var converation = await GetConversation(conversationId);

// Create conversation if this conversation not exists
if (converation == null)
{
var sess = new Conversation
{
Id = conversationId,
AgentId = agentId
};
converation = await NewConversation(sess);
}

// conversation state
var stateService = _services.GetRequiredService<IConversationStateService>();
stateService.SetConversation(conversationId);
stateService.Load();

var router = _services.GetRequiredService<IAgentRouting>();
var agent = await router.LoadCurrentAgent();

_logger.LogInformation($"[{agent.Name}] {lastDialog.Role}: {lastDialog.Content}");

lastDialog.CurrentAgentId = agent.Id;
_storage.Append(conversationId, agent.Id, lastDialog);

var wholeDialogs = GetDialogHistory(conversationId);

// Get relevant domain knowledge
/*if (_settings.EnableKnowledgeBase)
{
var knowledge = _services.GetRequiredService<IKnowledgeService>();
agent.Knowledges = await knowledge.GetKnowledges(new KnowledgeRetrievalModel
{
AgentId = agentId,
Question = string.Join("\n", wholeDialogs.Select(x => x.Content))
});
}*/

var hooks = _services.GetServices<IConversationHook>().ToList();

// Before chat completion hook
foreach (var hook in hooks)
{
hook.SetAgent(agent)
.SetConversation(converation);

await hook.OnDialogsLoaded(wholeDialogs);
await hook.BeforeCompletion();
}

var chatCompletion = GetChatCompletion();
var result = await GetChatCompletionsAsyncRecursively(chatCompletion,
conversationId,
agent,
wholeDialogs,
onMessageReceived,
onFunctionExecuting);

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());
}
}
}

public IChatCompletion GetChatCompletion()
{
var completions = _services.GetServices<IChatCompletion>();
return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith(_settings.ChatCompletion));
}
}
Loading