From e4d8524376f8991c885f5a95924dafd2da272a64 Mon Sep 17 00:00:00 2001
From: hchen2020 <101423@smsassist.com>
Date: Sun, 27 Aug 2023 06:07:22 -0500
Subject: [PATCH 1/3] Add disabled property to routing table.
---
.../BotSharp.Abstraction/Agents/Models/RoutingRecord.cs | 3 +++
src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj | 4 ++++
src/Infrastructure/BotSharp.Core/Hooks/AgentHook.cs | 4 +++-
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingRecord.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingRecord.cs
index 8f83c74b..3d8e1868 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingRecord.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingRecord.cs
@@ -19,6 +19,9 @@ public class RoutingRecord
[JsonPropertyName("redirect_to")]
public string RedirectTo { get; set; }
+ [JsonPropertyName("disabled")]
+ public bool Disabled { get; set; }
+
public override string ToString()
{
return Name;
diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
index ab77723b..20b036e4 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
+++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
@@ -82,4 +82,8 @@
+
+
+
+
diff --git a/src/Infrastructure/BotSharp.Core/Hooks/AgentHook.cs b/src/Infrastructure/BotSharp.Core/Hooks/AgentHook.cs
index e030a904..559445a9 100644
--- a/src/Infrastructure/BotSharp.Core/Hooks/AgentHook.cs
+++ b/src/Infrastructure/BotSharp.Core/Hooks/AgentHook.cs
@@ -10,7 +10,9 @@ public class AgentHook : AgentHookBase
public override bool OnInstructionLoaded(string template, Dictionary dict)
{
var router = _services.GetRequiredService();
- dict["routing_records"] = router.GetRoutingRecords();
+ dict["routing_records"] = router.GetRoutingRecords()
+ .Where(x => !x.Disabled)
+ .ToList();
return true;
}
}
From 6f5cf2fcae9711259dfac32105aab70290482097 Mon Sep 17 00:00:00 2001
From: hchen2020 <101423@smsassist.com>
Date: Sun, 27 Aug 2023 22:50:10 -0500
Subject: [PATCH 2/3] Draft of Reasoning
---
.../Agents/IAgentRouting.cs | 5 +-
.../Agents/Settings/AgentSettings.cs | 7 +-
.../Conversations/Models/RoleDialogModel.cs | 2 +-
.../Settings/ConversationSetting.cs | 2 +
.../Functions/Models/FunctionCallFromLlm.cs | 14 +
.../Routing/Models/RetrievalArgs.cs | 16 ++
.../{Agents => Routing}/Models/RoutingArgs.cs | 2 +-
.../Models/RoutingRecord.cs | 2 +-
.../Routing/Settings/GPT4Settings.cs | 8 +
.../Agents/Services/AgentRouter.cs | 53 ----
.../BotSharp.Core/BotSharp.Core.csproj | 4 -
.../BotSharpServiceCollectionExtensions.cs | 14 +-
...vice.GetChatCompletionsAsyncRecursively.cs | 5 +-
.../ConversationService.SendMessage.cs | 43 +++-
.../Services/ConversationStorage.cs | 1 +
.../BotSharp.Core/Functions/RouteToAgentFn.cs | 5 +-
.../BotSharp.Core/Hooks/ReasoningHook.cs | 14 +
.../Hooks/{AgentHook.cs => RoutingHook.cs} | 4 +-
.../BotSharp.Core/Routing/Reasoner.cs | 12 +
.../BotSharp.Core/Routing/Router.cs | 43 ++++
.../BotSharp.Core/Routing/Simulator.cs | 133 ++++++++++
.../Templating/TemplateRender.cs | 3 +-
.../AzureOpenAiPlugin.cs | 1 +
.../Providers/GPT4CompletionProvider.cs | 240 ++++++++++++++++++
.../Settings/AzureOpenAiSettings.cs | 4 +
25 files changed, 560 insertions(+), 77 deletions(-)
create mode 100644 src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs
create mode 100644 src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs
rename src/Infrastructure/BotSharp.Abstraction/{Agents => Routing}/Models/RoutingArgs.cs (82%)
rename src/Infrastructure/BotSharp.Abstraction/{Agents => Routing}/Models/RoutingRecord.cs (92%)
create mode 100644 src/Infrastructure/BotSharp.Abstraction/Routing/Settings/GPT4Settings.cs
delete mode 100644 src/Infrastructure/BotSharp.Core/Agents/Services/AgentRouter.cs
create mode 100644 src/Infrastructure/BotSharp.Core/Hooks/ReasoningHook.cs
rename src/Infrastructure/BotSharp.Core/Hooks/{AgentHook.cs => RoutingHook.cs} (77%)
create mode 100644 src/Infrastructure/BotSharp.Core/Routing/Reasoner.cs
create mode 100644 src/Infrastructure/BotSharp.Core/Routing/Router.cs
create mode 100644 src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
create mode 100644 src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/GPT4CompletionProvider.cs
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRouting.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRouting.cs
index 4df491db..d1086c26 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRouting.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRouting.cs
@@ -1,8 +1,11 @@
+using BotSharp.Abstraction.Routing.Models;
+
namespace BotSharp.Abstraction.Agents;
public interface IAgentRouting
{
+ string AgentId { get; }
Task LoadRouter();
- Task LoadCurrentAgent();
RoutingRecord[] GetRoutingRecords();
+ RoutingRecord GetRecordByName(string name);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs
index 84ff0274..60307439 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs
@@ -6,7 +6,12 @@ public class AgentSettings
/// Router Agent Id
///
public string RouterId { get; set; }
+
+ ///
+ /// Reasoner Agent Id
+ ///
+ public string ReasonerId { get; set; }
+
public string DataDir { get; set; }
public string TemplateFormat { get; set; }
- public int MaxRecursiveDepth { get; set; } = 3;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
index b681c2c3..e80c9427 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
@@ -45,7 +45,7 @@ public class RoleDialogModel
{
if (Role == AgentRole.Function)
{
- return $"{Role}: {FunctionName}";
+ return $"{Role}: {FunctionName} => {ExecutionResult}";
}
else
{
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs
index 80897657..c9836d4b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs
@@ -6,4 +6,6 @@ public class ConversationSetting
public string ChatCompletion { get; set; }
public bool EnableKnowledgeBase { get; set; }
public bool ShowVerboseLog { get; set; }
+ public int MaxRecursiveDepth { get; set; } = 3;
+ public bool EnableReasoning { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs
new file mode 100644
index 00000000..5d9b9881
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs
@@ -0,0 +1,14 @@
+using BotSharp.Abstraction.Routing.Models;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace BotSharp.Abstraction.Functions.Models;
+
+public class FunctionCallFromLlm
+{
+ [JsonPropertyName("function")]
+ public string Function { get; set; }
+
+ [JsonPropertyName("parameters")]
+ public RetrievalArgs Parameters { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs
new file mode 100644
index 00000000..835cda46
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs
@@ -0,0 +1,16 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace BotSharp.Abstraction.Routing.Models;
+
+public class RetrievalArgs : RoutingArgs
+{
+ [JsonPropertyName("question")]
+ public string Question { get; set; }
+
+ [JsonPropertyName("reason")]
+ public string Reason { get; set; }
+
+ [JsonPropertyName("args")]
+ public JsonDocument Arguments { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs
similarity index 82%
rename from src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingArgs.cs
rename to src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs
index e02b1c7b..e70d787d 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace BotSharp.Abstraction.Agents.Models;
+namespace BotSharp.Abstraction.Routing.Models;
public class RoutingArgs
{
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingRecord.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRecord.cs
similarity index 92%
rename from src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingRecord.cs
rename to src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRecord.cs
index 3d8e1868..f0d6d742 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingRecord.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRecord.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace BotSharp.Abstraction.Agents.Models;
+namespace BotSharp.Abstraction.Routing.Models;
public class RoutingRecord
{
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/GPT4Settings.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/GPT4Settings.cs
new file mode 100644
index 00000000..21925c1c
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/GPT4Settings.cs
@@ -0,0 +1,8 @@
+namespace BotSharp.Abstraction.Routing.Settings;
+
+public class GPT4Settings
+{
+ public string ApiKey { get; set; }
+ public string Endpoint { get; set; }
+ public string DeploymentModel { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentRouter.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentRouter.cs
deleted file mode 100644
index 0aed1531..00000000
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentRouter.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using BotSharp.Abstraction.Agents.Models;
-using System.IO;
-
-namespace BotSharp.Core.Agents.Services;
-
-public class AgentRouter : IAgentRouting
-{
- private readonly IServiceProvider _services;
- private readonly ILogger _logger;
- private readonly AgentSettings _settings;
-
- public AgentRouter(IServiceProvider services,
- ILogger logger,
- AgentSettings settings)
- {
- _services = services;
- _logger = logger;
- _settings = settings;
- }
-
- public async Task LoadRouter()
- {
- var agentService = _services.GetRequiredService();
- var agent = await agentService.LoadAgent(_settings.RouterId);
- return agent;
- }
-
- public async Task LoadCurrentAgent()
- {
- // Load current agent from state
- var state = _services.GetRequiredService();
- var currentAgentId = state.GetState("agent_id");
- if (string.IsNullOrEmpty(currentAgentId))
- {
- currentAgentId = _settings.RouterId;
- }
- var agentService = _services.GetRequiredService();
- var agent = await agentService.LoadAgent(currentAgentId);
-
- // Set agent and trigger state changed
- state.SetState("agent_id", currentAgentId);
-
- return agent;
- }
-
- public RoutingRecord[] GetRoutingRecords()
- {
- var agentSettings = _services.GetRequiredService();
- var dbSettings = _services.GetRequiredService();
- var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json");
- return JsonSerializer.Deserialize(File.ReadAllText(filePath));
- }
-}
diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
index 20b036e4..ab77723b 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
+++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
@@ -82,8 +82,4 @@
-
-
-
-
diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs
index 1b38659c..b200c729 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs
+++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs
@@ -1,6 +1,8 @@
+using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions;
using BotSharp.Core.Functions;
using BotSharp.Core.Hooks;
+using BotSharp.Core.Routing;
using BotSharp.Core.Templating;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
@@ -41,13 +43,21 @@ public static class BotSharpServiceCollectionExtensions
services.AddSingleton();
// Register router
- services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped(p =>
+ {
+ var setting = p.GetRequiredService();
+ return setting.EnableReasoning ? p.GetRequiredService() : p.GetRequiredService();
+ });
// Register function callback
services.AddScoped();
// Register Hooks
- services.AddScoped();
+ services.AddScoped();
+
+ services.AddScoped();
return services;
}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs
index 40856809..4e5a0d23 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs
@@ -13,13 +13,12 @@ public partial class ConversationService
string conversationId,
Agent agent,
List wholeDialogs,
- int maxRecursiveDepth,
Func onMessageReceived,
Func onFunctionExecuting,
Func onFunctionExecuted)
{
currentRecursiveDepth++;
- if (currentRecursiveDepth > maxRecursiveDepth)
+ if (currentRecursiveDepth > _settings.MaxRecursiveDepth)
{
_logger.LogWarning($"Exceeded max recursive depth.");
@@ -65,7 +64,6 @@ public partial class ConversationService
fn.Content = fn.FunctionArgs.Replace("\r", " ").Replace("\n", " ").Trim() + " => " + fn.ExecutionResult;
// Agent has been transferred
- var agentSettings = _services.GetRequiredService();
if (fn.CurrentAgentId != preAgentId)
{
var agentService = _services.GetRequiredService();
@@ -83,7 +81,6 @@ public partial class ConversationService
conversationId,
agent,
wholeDialogs,
- maxRecursiveDepth,
onMessageReceived,
onFunctionExecuting,
onFunctionExecuted);
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
index 8d53971a..c05406a5 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
@@ -1,5 +1,8 @@
+using BotSharp.Abstraction.Agents.Enums;
+using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.MLTasks;
+using BotSharp.Core.Routing;
namespace BotSharp.Core.Conversations.Services;
@@ -31,7 +34,7 @@ public partial class ConversationService
stateService.SetState("channel", lastDialog.Channel);
var router = _services.GetRequiredService();
- var agent = await router.LoadRouter();
+ Agent agent = await router.LoadRouter();
_logger.LogInformation($"[{agent.Name}] {lastDialog.Role}: {lastDialog.Content}");
@@ -65,14 +68,42 @@ public partial class ConversationService
await hook.BeforeCompletion();
}
- var agentSettings = _services.GetRequiredService();
+ // reasoning
+ if (_settings.EnableReasoning)
+ {
+ var simulator = _services.GetRequiredService();
+ var reasonedContext = await simulator.Enter(agent, wholeDialogs);
+
+ if (reasonedContext.FunctionName == "interrupt_task_execution")
+ {
+ await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, reasonedContext.Content)
+ {
+ CurrentAgentId = agent.Id,
+ Channel = lastDialog.Channel
+ }, onMessageReceived);
+ return true;
+ }
+ else if (reasonedContext.FunctionName == "continue_execute_task")
+ {
+ if (reasonedContext.CurrentAgentId != agent.Id)
+ {
+ var agentService = _services.GetRequiredService();
+ agent = await agentService.LoadAgent(reasonedContext.CurrentAgentId);
+ }
+ }
+
+ simulator.Dialogs.ForEach(x =>
+ {
+ wholeDialogs.Add(x);
+ _storage.Append(conversationId, agent.Id, x);
+ });
+ }
var chatCompletion = GetChatCompletion();
var result = await GetChatCompletionsAsyncRecursively(chatCompletion,
conversationId,
agent,
wholeDialogs,
- agentSettings.MaxRecursiveDepth,
onMessageReceived,
onFunctionExecuting,
onFunctionExecuted);
@@ -101,4 +132,10 @@ public partial class ConversationService
var completions = _services.GetServices();
return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith(_settings.ChatCompletion));
}
+
+ public IChatCompletion GetGpt4ChatCompletion()
+ {
+ var completions = _services.GetServices();
+ return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith("GPT4CompletionProvider"));
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
index 8e0b08dd..7525532b 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
@@ -72,6 +72,7 @@ public class ConversationStorage : IConversationStorage
CurrentAgentId = currentAgentId,
FunctionName = funcName,
FunctionArgs = funcArgs,
+ ExecutionResult = text,
CreatedAt = createdAt
});
}
diff --git a/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs b/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs
index 3834b035..4991dc99 100644
--- a/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs
+++ b/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs
@@ -1,6 +1,6 @@
-using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Functions;
+using BotSharp.Abstraction.Routing.Models;
using System.IO;
namespace BotSharp.Core.Functions;
@@ -51,8 +51,7 @@ public class RouteToAgentFn : IFunctionCallback
{
var args = JsonSerializer.Deserialize(message.FunctionArgs);
var router = _services.GetRequiredService();
- var records = router.GetRoutingRecords();
- var routingRule = records.FirstOrDefault(x => x.Name.ToLower() == args.AgentName.ToLower());
+ var routingRule = router.GetRecordByName(args.AgentName);
if (routingRule == null)
{
diff --git a/src/Infrastructure/BotSharp.Core/Hooks/ReasoningHook.cs b/src/Infrastructure/BotSharp.Core/Hooks/ReasoningHook.cs
new file mode 100644
index 00000000..f754733f
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Hooks/ReasoningHook.cs
@@ -0,0 +1,14 @@
+namespace BotSharp.Core.Hooks;
+
+public class ReasoningHook : AgentHookBase
+{
+ public ReasoningHook(IServiceProvider services, AgentSettings settings)
+ : base(services, settings)
+ {
+ }
+
+ public override bool OnInstructionLoaded(string template, Dictionary dict)
+ {
+ return true;
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Hooks/AgentHook.cs b/src/Infrastructure/BotSharp.Core/Hooks/RoutingHook.cs
similarity index 77%
rename from src/Infrastructure/BotSharp.Core/Hooks/AgentHook.cs
rename to src/Infrastructure/BotSharp.Core/Hooks/RoutingHook.cs
index 559445a9..aba0ffc2 100644
--- a/src/Infrastructure/BotSharp.Core/Hooks/AgentHook.cs
+++ b/src/Infrastructure/BotSharp.Core/Hooks/RoutingHook.cs
@@ -1,8 +1,8 @@
namespace BotSharp.Core.Hooks;
-public class AgentHook : AgentHookBase
+public class RoutingHook : AgentHookBase
{
- public AgentHook(IServiceProvider services, AgentSettings settings)
+ public RoutingHook(IServiceProvider services, AgentSettings settings)
: base(services, settings)
{
}
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Reasoner.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoner.cs
new file mode 100644
index 00000000..c08f83ac
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoner.cs
@@ -0,0 +1,12 @@
+namespace BotSharp.Core.Routing;
+
+public class Reasoner : Router
+{
+ public override string AgentId => _settings.ReasonerId;
+
+ public Reasoner(IServiceProvider services,
+ ILogger logger,
+ AgentSettings settings) : base(services, logger, settings)
+ {
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Router.cs b/src/Infrastructure/BotSharp.Core/Routing/Router.cs
new file mode 100644
index 00000000..594ee734
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Routing/Router.cs
@@ -0,0 +1,43 @@
+using BotSharp.Abstraction.Agents.Models;
+using BotSharp.Abstraction.Routing.Models;
+using System.IO;
+using static Tensorflow.ApiDef.Types;
+
+namespace BotSharp.Core.Routing;
+
+public class Router : IAgentRouting
+{
+ protected readonly IServiceProvider _services;
+ protected readonly ILogger _logger;
+ protected readonly AgentSettings _settings;
+
+ public virtual string AgentId => _settings.RouterId;
+
+ public Router(IServiceProvider services,
+ ILogger logger,
+ AgentSettings settings)
+ {
+ _services = services;
+ _logger = logger;
+ _settings = settings;
+ }
+
+ public virtual async Task LoadRouter()
+ {
+ var agentService = _services.GetRequiredService();
+ return await agentService.LoadAgent(AgentId);
+ }
+
+ public RoutingRecord[] GetRoutingRecords()
+ {
+ var agentSettings = _services.GetRequiredService();
+ var dbSettings = _services.GetRequiredService();
+ var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json");
+ return JsonSerializer.Deserialize(File.ReadAllText(filePath));
+ }
+
+ public RoutingRecord GetRecordByName(string name)
+ {
+ return GetRoutingRecords().First(x => x.Name.ToLower() == name.ToLower());
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs b/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
new file mode 100644
index 00000000..d5c3d935
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
@@ -0,0 +1,133 @@
+using BotSharp.Abstraction.Agents.Enums;
+using BotSharp.Abstraction.Agents.Models;
+using BotSharp.Abstraction.Conversations.Models;
+using BotSharp.Abstraction.Functions.Models;
+using BotSharp.Abstraction.MLTasks;
+
+namespace BotSharp.Core.Routing;
+
+///
+/// Simulate the dialogue between different agents.
+///
+public class Simulator
+{
+ private readonly IServiceProvider _services;
+ private readonly ILogger _logger;
+ private List _dialogs;
+ public List Dialogs => _dialogs;
+
+ public Simulator(IServiceProvider services, ILogger logger)
+ {
+ _services = services;
+ _logger = logger;
+ }
+
+ public async Task Enter(Agent agent, List whileDialogs)
+ {
+ _dialogs = new List();
+
+ foreach (var dialog in whileDialogs.TakeLast(10))
+ {
+ agent.Instruction += $"\r\n{dialog.Role}: {dialog.Content}";
+ }
+
+ var response = await SendMessageToReasoner(agent);
+ var args = JsonSerializer.Deserialize(response.Content);
+ response.FunctionName = args.Function;
+ response.Content = args.Parameters.Reason;
+ if (args.Function == "continue_execute_task")
+ {
+ response.FunctionArgs = JsonSerializer.Serialize(args.Parameters.Arguments);
+
+ var router = _services.GetRequiredService();
+ var record = router.GetRecordByName(args.Parameters.AgentName);
+ response.CurrentAgentId = record.AgentId;
+ }
+
+ return response;
+ }
+
+ private async Task SendMessageToReasoner(Agent reasoner)
+ {
+ var wholeDialogs = new List
+ {
+ new RoleDialogModel(AgentRole.User, @"What's the next step, your response must be in JSON format with ""function"" and ""parameters"". ")
+ };
+
+ var chatCompletion = GetGpt4ChatCompletion();
+
+ RoleDialogModel response = null;
+ await chatCompletion.GetChatCompletionsAsync(reasoner, wholeDialogs, async msg
+ => response = msg, fn
+ => Task.CompletedTask);
+
+ var args = JsonSerializer.Deserialize(response.Content);
+
+ SaveStateByArgs(args.Parameters.Arguments);
+
+ // Retrieve information from specific agent
+ var router = _services.GetRequiredService();
+ var record = router.GetRecordByName(args.Parameters.AgentName);
+ response = await SendMessageToAgent(record.AgentId, new List
+ {
+ new RoleDialogModel(AgentRole.User, args.Parameters.Question)
+ });
+
+ _dialogs.Add(new RoleDialogModel(AgentRole.Function, $"{record.Name}: {response.Content}")
+ {
+ FunctionName = args.Function,
+ FunctionArgs = JsonSerializer.Serialize(args.Parameters.Arguments),
+ ExecutionResult = response.Content
+ });
+
+ reasoner.Instruction += $"\r\n{record.Name}: {response.Content}";
+ // Got the response from agent, then send to reasoner again to make the decision
+ await chatCompletion.GetChatCompletionsAsync(reasoner, wholeDialogs, async msg
+ => response = msg, fn
+ => Task.CompletedTask);
+
+ return response;
+ }
+
+ private async Task SendMessageToAgent(string agentId, List wholeDialogs)
+ {
+ var agentService = _services.GetRequiredService();
+ var agent = await agentService.LoadAgent(agentId);
+
+ var chatCompletion = GetChatCompletion();
+
+ RoleDialogModel response = null;
+ await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg
+ => response = msg, fn
+ => Task.CompletedTask);
+ return response;
+ }
+
+ public IChatCompletion GetChatCompletion()
+ {
+ var completions = _services.GetServices();
+ var settings = _services.GetRequiredService();
+ return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith(settings.ChatCompletion));
+ }
+
+ public IChatCompletion GetGpt4ChatCompletion()
+ {
+ var completions = _services.GetServices();
+ return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith("GPT4CompletionProvider"));
+ }
+
+ private void SaveStateByArgs(JsonDocument args)
+ {
+ 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.ToString());
+ }
+ }
+ }
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs
index ca80356a..97263119 100644
--- a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs
+++ b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs
@@ -1,4 +1,5 @@
using BotSharp.Abstraction.Agents.Models;
+using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Templating;
using Fluid;
using Microsoft.Extensions.Options;
@@ -17,7 +18,7 @@ public class TemplateRender : ITemplateRender
_services = services;
_logger = logger;
_options = new TemplateOptions();
- _options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.CamelCase;
+ _options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.SnakeCase;
_options.MemberAccessStrategy.Register();
}
diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs
index 1232e668..b966d167 100644
--- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs
+++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs
@@ -23,5 +23,6 @@ public class AzureOpenAiPlugin : IBotSharpPlugin
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
}
}
\ No newline at end of file
diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/GPT4CompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/GPT4CompletionProvider.cs
new file mode 100644
index 00000000..8767e7df
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/GPT4CompletionProvider.cs
@@ -0,0 +1,240 @@
+using Azure;
+using Azure.AI.OpenAI;
+using BotSharp.Abstraction.Agents.Enums;
+using BotSharp.Abstraction.Agents.Models;
+using BotSharp.Abstraction.Conversations.Models;
+using BotSharp.Abstraction.Conversations.Settings;
+using BotSharp.Abstraction.Functions.Models;
+using BotSharp.Abstraction.MLTasks;
+using BotSharp.Plugin.AzureOpenAI.Settings;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace BotSharp.Plugin.AzureOpenAI.Providers;
+
+public class GPT4CompletionProvider : IChatCompletion
+{
+ private readonly AzureOpenAiSettings _settings;
+ private readonly IServiceProvider _services;
+ private readonly ILogger _logger;
+
+ public GPT4CompletionProvider(AzureOpenAiSettings settings,
+ ILogger logger,
+ IServiceProvider services)
+ {
+ _settings = settings;
+ _logger = logger;
+ _services = services;
+ }
+
+ private OpenAIClient GetClient()
+ {
+ var client = new OpenAIClient(new Uri(_settings.GPT4.Endpoint), new AzureKeyCredential(_settings.GPT4.ApiKey));
+ return client;
+ }
+
+ public List GetChatSamples(string sampleText)
+ {
+ var samples = new List();
+ if (string.IsNullOrEmpty(sampleText))
+ {
+ return samples;
+ }
+
+ var lines = sampleText.Split('\n');
+ for (int i = 0; i < lines.Length; i++)
+ {
+ var line = lines[i];
+ if (string.IsNullOrEmpty(line.Trim()))
+ {
+ continue;
+ }
+ var role = line.Substring(0, line.IndexOf(' ') - 1).Trim();
+ var content = line.Substring(line.IndexOf(' ') + 1).Trim();
+
+ // comments
+ if (role == "##")
+ {
+ continue;
+ }
+
+ samples.Add(new RoleDialogModel(role, content));
+ }
+
+ return samples;
+ }
+
+ public List GetFunctions(string functionsJson)
+ {
+ var functions = new List();
+ if (!string.IsNullOrEmpty(functionsJson))
+ {
+ functions = JsonSerializer.Deserialize>(functionsJson, new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true,
+ AllowTrailingCommas = true
+ });
+ }
+
+ return functions;
+ }
+
+ public async Task GetChatCompletionsAsync(Agent agent,
+ List conversations,
+ Func onMessageReceived,
+ Func onFunctionExecuting)
+ {
+ var client = GetClient();
+ var chatCompletionsOptions = PrepareOptions(agent, conversations);
+
+ var response = await client.GetChatCompletionsAsync(_settings.GPT4.DeploymentModel, chatCompletionsOptions);
+ var choice = response.Value.Choices[0];
+ var message = choice.Message;
+
+ if (choice.FinishReason == CompletionsFinishReason.FunctionCall)
+ {
+ _logger.LogInformation($"[{agent.Name}]: {message.FunctionCall.Name} => {message.FunctionCall.Arguments}");
+
+ var funcContextIn = new RoleDialogModel(AgentRole.Function, message.Content)
+ {
+ CurrentAgentId = agent.Id,
+ FunctionName = message.FunctionCall.Name,
+ FunctionArgs = message.FunctionCall.Arguments,
+ Channel = conversations.Last().Channel
+ };
+
+ // Execute functions
+ await onFunctionExecuting(funcContextIn);
+ }
+ else
+ {
+ _logger.LogInformation($"[{agent.Name}] {message.Role}: {message.Content}");
+
+ var msg = new RoleDialogModel(AgentRole.Assistant, message.Content)
+ {
+ CurrentAgentId= agent.Id,
+ Channel = conversations.Last().Channel
+ };
+
+ // Text response received
+ await onMessageReceived(msg);
+ }
+
+ return true;
+ }
+
+ public async Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived)
+ {
+ var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey));
+ var chatCompletionsOptions = PrepareOptions(agent, conversations);
+
+ var response = await client.GetChatCompletionsStreamingAsync(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions);
+ using StreamingChatCompletions streaming = response.Value;
+
+ string output = "";
+ await foreach (var choice in streaming.GetChoicesStreaming())
+ {
+ if (choice.FinishReason == CompletionsFinishReason.FunctionCall)
+ {
+ var args = "";
+ await foreach (var message in choice.GetMessageStreaming())
+ {
+ if (message.FunctionCall == null || message.FunctionCall.Arguments == null)
+ continue;
+ Console.Write(message.FunctionCall.Arguments);
+ args += message.FunctionCall.Arguments;
+
+ }
+ await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), args));
+ continue;
+ }
+
+ await foreach (var message in choice.GetMessageStreaming())
+ {
+ if (message.Content == null)
+ continue;
+ Console.Write(message.Content);
+ output += message.Content;
+
+ _logger.LogInformation(message.Content);
+
+ await onMessageReceived(new RoleDialogModel(message.Role.ToString(), message.Content));
+ }
+
+ output = "";
+ }
+
+ return true;
+ }
+
+
+ private ChatCompletionsOptions PrepareOptions(Agent agent, List conversations)
+ {
+ var chatCompletionsOptions = new ChatCompletionsOptions();
+
+ if (!string.IsNullOrEmpty(agent.Instruction))
+ {
+ chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, agent.Instruction));
+ }
+
+ if (!string.IsNullOrEmpty(agent.Knowledges))
+ {
+ chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, agent.Knowledges));
+ }
+
+ var samples = GetChatSamples(agent.Samples);
+ foreach (var message in samples)
+ {
+ chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content));
+ }
+
+ var functions = GetFunctions(agent.Functions);
+ foreach (var function in functions)
+ {
+ chatCompletionsOptions.Functions.Add(new FunctionDefinition
+ {
+ Name = function.Name,
+ Description = function.Description,
+ Parameters = BinaryData.FromObjectAsJson(function.Parameters)
+ });
+ }
+
+ foreach (var message in conversations)
+ {
+ if (message.Role == ChatRole.Function)
+ {
+ chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content)
+ {
+ Name = message.FunctionName
+ });
+ }
+ else
+ {
+ chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content));
+ }
+ }
+
+ // https://community.openai.com/t/cheat-sheet-mastering-temperature-and-top-p-in-chatgpt-api-a-few-tips-and-tricks-on-controlling-the-creativity-deterministic-output-of-prompt-responses/172683
+ chatCompletionsOptions.Temperature = 0.5f;
+ chatCompletionsOptions.NucleusSamplingFactor = 0.5f;
+
+ var convSetting = _services.GetRequiredService();
+ if (convSetting.ShowVerboseLog)
+ {
+ var verbose = string.Join("\n", chatCompletionsOptions.Messages.Select(x =>
+ {
+ return x.Role == ChatRole.Function ?
+ $"{x.Role}: {x.Name} {x.Content}" :
+ $"{x.Role}: {x.Content}";
+ }));
+ _logger.LogInformation(verbose);
+ }
+
+ return chatCompletionsOptions;
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/AzureOpenAiSettings.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/AzureOpenAiSettings.cs
index 2a3cbf6e..510bec5a 100644
--- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/AzureOpenAiSettings.cs
+++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/AzureOpenAiSettings.cs
@@ -1,3 +1,5 @@
+using BotSharp.Abstraction.Routing.Settings;
+
namespace BotSharp.Plugin.AzureOpenAI.Settings;
public class AzureOpenAiSettings
@@ -6,4 +8,6 @@ public class AzureOpenAiSettings
public string Endpoint { get; set; } = string.Empty;
public DeploymentModelSetting DeploymentModel { get; set; }
= new DeploymentModelSetting();
+
+ public GPT4Settings GPT4 { get; set; }
}
From 5413f4f71b309529b656891b48a5c9158a77a3f9 Mon Sep 17 00:00:00 2001
From: hchen2020 <101423@smsassist.com>
Date: Mon, 28 Aug 2023 10:58:35 -0500
Subject: [PATCH 3/3] response_to_user after reasoning.
---
.../Routing/Models/RetrievalArgs.cs | 3 +++
.../ConversationService.SendMessage.cs | 9 ++++++++
.../BotSharp.Core/Routing/Simulator.cs | 21 +++++++++++++++++--
3 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs
index 835cda46..e0b3f9a1 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs
@@ -8,6 +8,9 @@ public class RetrievalArgs : RoutingArgs
[JsonPropertyName("question")]
public string Question { get; set; }
+ [JsonPropertyName("answer")]
+ public string Answer { get; set; }
+
[JsonPropertyName("reason")]
public string Reason { get; set; }
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
index c05406a5..24376c21 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
@@ -83,6 +83,15 @@ public partial class ConversationService
}, onMessageReceived);
return true;
}
+ else if (reasonedContext.FunctionName == "response_to_user")
+ {
+ await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, reasonedContext.Content)
+ {
+ CurrentAgentId = agent.Id,
+ Channel = lastDialog.Channel
+ }, onMessageReceived);
+ return true;
+ }
else if (reasonedContext.FunctionName == "continue_execute_task")
{
if (reasonedContext.CurrentAgentId != agent.Id)
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs b/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
index d5c3d935..4b8123d5 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
@@ -34,7 +34,7 @@ public class Simulator
var response = await SendMessageToReasoner(agent);
var args = JsonSerializer.Deserialize(response.Content);
response.FunctionName = args.Function;
- response.Content = args.Parameters.Reason;
+
if (args.Function == "continue_execute_task")
{
response.FunctionArgs = JsonSerializer.Serialize(args.Parameters.Arguments);
@@ -43,6 +43,16 @@ public class Simulator
var record = router.GetRecordByName(args.Parameters.AgentName);
response.CurrentAgentId = record.AgentId;
}
+ else if (args.Function == "interrupt_task_execution")
+ {
+ response.Content = args.Parameters.Reason;
+ response.ExecutionResult = args.Parameters.Reason;
+ }
+ else if (args.Function == "response_to_user")
+ {
+ response.Content = args.Parameters.Answer;
+ response.ExecutionResult = args.Parameters.Answer;
+ }
return response;
}
@@ -63,7 +73,14 @@ public class Simulator
var args = JsonSerializer.Deserialize(response.Content);
- SaveStateByArgs(args.Parameters.Arguments);
+ if (args.Function == "retrieve_data_from_agent")
+ {
+ SaveStateByArgs(args.Parameters.Arguments);
+ }
+ else if (args.Function == "response_to_user")
+ {
+ return response;
+ }
// Retrieve information from specific agent
var router = _services.GetRequiredService();