diff --git a/Directory.Build.props b/Directory.Build.props
index 249103d7..a0e7305b 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,7 +2,7 @@
10.0
..\..\..\packages
- 0.12.3
+ 0.13.0
true
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs
index 5d9b9881..968ae667 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs
@@ -11,4 +11,9 @@ public class FunctionCallFromLlm
[JsonPropertyName("parameters")]
public RetrievalArgs Parameters { get; set; }
+
+ public override string ToString()
+ {
+ return $"{Function}: {Parameters}";
+ }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs
index 8cf7f5b7..08234983 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs
@@ -2,6 +2,6 @@ namespace BotSharp.Abstraction.Routing;
public interface IRoutingService
{
- Task DeleteRoutingItems();
- Task DeleteRoutingProfiles();
+ List Dialogs { get; }
+ Task Enter(Agent agent, List whileDialogs);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs
index e0b3f9a1..bc173c33 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs
@@ -16,4 +16,9 @@ public class RetrievalArgs : RoutingArgs
[JsonPropertyName("args")]
public JsonDocument Arguments { get; set; }
+
+ public override string ToString()
+ {
+ return $"{AgentName} {Question} ({JsonSerializer.Serialize(Arguments)}) => {Answer} ({Reason})";
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs
index d635d1c0..a0133eac 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs
+++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs
@@ -62,6 +62,7 @@ public static class BotSharpServiceCollectionExtensions
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
if (myDatabaseSettings.Default == "FileRepository")
{
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
index 1c297391..366d13d2 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
@@ -1,6 +1,6 @@
using BotSharp.Abstraction.Agents.Models;
+using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
-using BotSharp.Core.Routing;
namespace BotSharp.Core.Conversations.Services;
@@ -52,10 +52,10 @@ public partial class ConversationService
// reasoning
var settings = _services.GetRequiredService();
- if (settings.ReasonerId == agent.Id)
+ if (settings.RouterId == agent.Id)
{
- var simulator = _services.GetRequiredService();
- var reasonedContext = await simulator.Enter(agent, wholeDialogs);
+ var routing = _services.GetRequiredService();
+ var reasonedContext = await routing.Enter(agent, wholeDialogs);
if (reasonedContext.FunctionName == "interrupt_task_execution")
{
@@ -83,7 +83,7 @@ public partial class ConversationService
}
}
- simulator.Dialogs.ForEach(x =>
+ routing.Dialogs.ForEach(x =>
{
wholeDialogs.Add(x);
if (x.Content != null)
diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs
new file mode 100644
index 00000000..f0566827
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs
@@ -0,0 +1,183 @@
+using BotSharp.Abstraction.Agents.Models;
+using BotSharp.Abstraction.Functions;
+using BotSharp.Abstraction.Functions.Models;
+using BotSharp.Abstraction.Repositories;
+using BotSharp.Abstraction.Routing;
+using BotSharp.Abstraction.Routing.Models;
+
+namespace BotSharp.Core.Routing;
+
+public class RoutingService : IRoutingService
+{
+ private readonly IServiceProvider _services;
+ private readonly ILogger _logger;
+ private List _dialogs;
+ public List Dialogs => _dialogs;
+
+ public RoutingService(IServiceProvider services, ILogger logger)
+ {
+ _services = services;
+ _logger = logger;
+ }
+
+ public async Task Enter(Agent agent, List whileDialogs)
+ {
+ _dialogs = new List();
+ RoleDialogModel result = new RoleDialogModel(AgentRole.Assistant, "not handled");
+
+ foreach (var dialog in whileDialogs.TakeLast(10))
+ {
+ agent.Instruction += $"\r\n{dialog.Role}: {dialog.Content}";
+ }
+
+ var inst = await GetNextInstructionFromReasoner(agent);
+ int loopCount = 0;
+ while (loopCount < 3)
+ {
+ loopCount++;
+ if (inst.Function == "continue_execute_task")
+ {
+ var router = _services.GetRequiredService();
+ var db = _services.GetRequiredService();
+ var record = db.Agents.First(x => x.Name.ToLower() == inst.Parameters.AgentName.ToLower());
+
+ result = new RoleDialogModel(AgentRole.Function, inst.Parameters.Question)
+ {
+ FunctionName = inst.Function,
+ FunctionArgs = JsonSerializer.Serialize(inst.Parameters.Arguments),
+ CurrentAgentId = record.Id,
+ };
+ break;
+ }
+ // Compatible with previous Router, can be removed in the future.
+ else if (inst.Function == "route_to_agent")
+ {
+ var function = _services.GetServices().FirstOrDefault(x => x.Name == inst.Function);
+ result = new RoleDialogModel(AgentRole.Function, inst.Parameters.Question)
+ {
+ FunctionName = inst.Function,
+ FunctionArgs = JsonSerializer.Serialize(new RoutingArgs
+ {
+ AgentName = inst.Parameters.AgentName
+ }),
+ };
+ var ret = await function.Execute(result);
+ break;
+ }
+ else if (inst.Function == "interrupt_task_execution")
+ {
+ result = new RoleDialogModel(AgentRole.User, inst.Parameters.Reason)
+ {
+ FunctionName = inst.Function
+ };
+ break;
+ }
+ else if (inst.Function == "response_to_user")
+ {
+ result = new RoleDialogModel(AgentRole.User, inst.Parameters.Answer)
+ {
+ FunctionName = inst.Function
+ };
+ break;
+ }
+ else if (inst.Function == "retrieve_data_from_agent")
+ {
+ // Retrieve information from specific agent
+ var db = _services.GetRequiredService();
+ var record = db.Agents.First(x => x.Name.ToLower() == inst.Parameters.AgentName.ToLower());
+ var response = await RetrieveDataFromAgent(record.Id, new List
+ {
+ new RoleDialogModel(AgentRole.User, inst.Parameters.Question)
+ });
+
+ response.Content += $"\r\nDo you want to continue current task?";
+ _dialogs.Add(new RoleDialogModel(AgentRole.Function, $"{record.Name}: {response.Content}")
+ {
+ FunctionName = inst.Function,
+ FunctionArgs = JsonSerializer.Serialize(inst.Parameters.Arguments),
+ ExecutionResult = response.Content,
+ CurrentAgentId = record.Id
+ });
+
+ agent.Instruction += $"\r\n{record.Name}: {response.Content}";
+
+ // Got the response from agent, then send to reasoner again to make the decision
+ inst = await GetNextInstructionFromReasoner(agent);
+ }
+ }
+
+ return result;
+ }
+
+ private async Task GetNextInstructionFromReasoner(Agent reasoner)
+ {
+ var wholeDialogs = new List
+ {
+ new RoleDialogModel(AgentRole.User, @"What's the next step? Response in JSON format with ""function"" and ""parameters"".")
+ };
+
+ var chatCompletion = CompletionProvider.GetChatCompletion(_services);
+
+ RoleDialogModel response = null;
+ await chatCompletion.GetChatCompletionsAsync(reasoner, wholeDialogs, async msg
+ => response = msg, fn
+ => Task.CompletedTask);
+
+ var args = JsonSerializer.Deserialize(response.Content);
+
+ if (args.Parameters.Arguments != null)
+ {
+ SaveStateByArgs(args.Parameters.Arguments);
+ }
+
+ args.Function = args.Function.Split('.').Last();
+
+ return args;
+ }
+
+ private async Task RetrieveDataFromAgent(string agentId, List wholeDialogs)
+ {
+ var agentService = _services.GetRequiredService();
+ var agent = await agentService.LoadAgent(agentId);
+
+ var chatCompletion = CompletionProvider.GetChatCompletion(_services);
+
+ RoleDialogModel response = null;
+ await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg
+ => response = msg, async fn
+ =>
+ {
+ // execute function
+ // Save states
+ SaveStateByArgs(JsonSerializer.Deserialize(fn.FunctionArgs));
+
+ var conversationService = _services.GetRequiredService();
+ // Call functions
+ await conversationService.CallFunctions(fn);
+
+ response = fn;
+ response.Content = fn.ExecutionResult;
+ });
+ return response;
+ }
+
+ private void SaveStateByArgs(JsonDocument args)
+ {
+ if (args == null)
+ {
+ return;
+ }
+
+ var stateService = _services.GetRequiredService();
+ if (args.RootElement is JsonElement root)
+ {
+ foreach (JsonProperty property in root.EnumerateObject())
+ {
+ if (!string.IsNullOrEmpty(property.Value.ToString()))
+ {
+ stateService.SetState(property.Name, property.Value);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs b/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
index 7e3cbf3e..b13eb5bb 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
@@ -1,9 +1,8 @@
-using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Agents.Models;
-using BotSharp.Abstraction.Conversations.Models;
+using BotSharp.Abstraction.Functions;
using BotSharp.Abstraction.Functions.Models;
-using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Repositories;
+using BotSharp.Abstraction.Routing.Models;
namespace BotSharp.Core.Routing;
@@ -33,6 +32,11 @@ public class Simulator
}
var response = await SendMessageToReasoner(agent);
+ if (response.Role == AgentRole.Function)
+ {
+
+ }
+
var args = JsonSerializer.Deserialize(response.Content);
response.FunctionName = args.Function;
@@ -42,7 +46,7 @@ public class Simulator
var router = _services.GetRequiredService();
var db = _services.GetRequiredService();
- var record = db.Agents.First(x => x.Name.ToLower() == args.Parameters.AgentName);
+ var record = db.Agents.First(x => x.Name.ToLower() == args.Parameters.AgentName.ToLower());
response.CurrentAgentId = record.Id;
}
else if (args.Function == "interrupt_task_execution")
@@ -75,7 +79,7 @@ public class Simulator
var args = JsonSerializer.Deserialize(response.Content);
- if (args.Function == "retrieve_data_from_agent")
+ if (args.Parameters.Arguments != null)
{
SaveStateByArgs(args.Parameters.Arguments);
}
@@ -84,10 +88,28 @@ public class Simulator
return response;
}
+ if (args.Function == "route_to_agent")
+ {
+ var function = _services.GetServices().FirstOrDefault(x => x.Name == args.Function);
+ var message = new RoleDialogModel(AgentRole.Function, args.Parameters.Question)
+ {
+ FunctionName = args.Function,
+ FunctionArgs = JsonSerializer.Serialize(new RoutingArgs
+ {
+ AgentName = args.Parameters.AgentName
+ }),
+ };
+ var ret = await function.Execute(message);
+ if (ret)
+ {
+ return message;
+ }
+ }
+
// Retrieve information from specific agent
var router = _services.GetRequiredService();
var db = _services.GetRequiredService();
- var record = db.Agents.First(x => x.Name.ToLower() == args.Parameters.AgentName);
+ var record = db.Agents.First(x => x.Name.ToLower() == args.Parameters.AgentName.ToLower());
response = await SendMessageToAgent(record.Id, new List
{
new RoleDialogModel(AgentRole.User, args.Parameters.Question)
diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/GetBakingTimeFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/GetDeliveryTimeFn.cs
similarity index 70%
rename from tests/BotSharp.Plugin.PizzaBot/Functions/GetBakingTimeFn.cs
rename to tests/BotSharp.Plugin.PizzaBot/Functions/GetDeliveryTimeFn.cs
index 0ebc56e7..db3179af 100644
--- a/tests/BotSharp.Plugin.PizzaBot/Functions/GetBakingTimeFn.cs
+++ b/tests/BotSharp.Plugin.PizzaBot/Functions/GetDeliveryTimeFn.cs
@@ -2,9 +2,9 @@ using BotSharp.Abstraction.Conversations.Models;
namespace BotSharp.Plugin.PizzaBot.Functions;
-public class GetBakingTimeFn : IFunctionCallback
+public class GetDeliveryTimeFn : IFunctionCallback
{
- public string Name => "get_cooking_remaining_time";
+ public string Name => "get_delivery_time";
public async Task Execute(RoleDialogModel message)
{
diff --git a/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs b/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs
index fbd3a6a2..1cc5ac5c 100644
--- a/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs
+++ b/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs
@@ -12,7 +12,7 @@ public class PizzaBotPlugin : IBotSharpPlugin
services.AddScoped();
services.AddScoped();
services.AddScoped();
- services.AddScoped();
+ services.AddScoped();
// Register hooks
services.AddScoped();