diff --git a/Directory.Build.props b/Directory.Build.props
index 5ca4cb96..0879abfc 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,7 +2,7 @@
net8.0
12.0
- 5.0.0
+ 5.1.0
true
false
diff --git a/src/Infrastructure/BotSharp.Abstraction/Models/MessageState.cs b/src/Infrastructure/BotSharp.Abstraction/Models/MessageState.cs
index ff26e981..a8709810 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Models/MessageState.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Models/MessageState.cs
@@ -19,4 +19,9 @@ public class MessageState
Value = value;
ActiveRounds = activeRounds;
}
+
+ public override string ToString()
+ {
+ return $"Key: {Key} => Value: {Value}, ActiveRounds: {ActiveRounds}";
+ }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs
index c10ffd8d..833cb013 100644
--- a/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs
@@ -54,16 +54,23 @@ public class SideCarAttribute : AsyncMoAttribute
private (IConversationSideCar?, MethodInfo?) GetSideCarMethod(IServiceProvider serviceProvider, string methodName, Type retType, object[] args)
{
- var sidecar = serviceProvider.GetService();
- var argTypes = args.Select(x => x.GetType()).ToArray();
- var sidecarMethod = sidecar?.GetType()?.GetMethods(BindingFlags.Public | BindingFlags.Instance)
- .FirstOrDefault(x => x.Name == methodName
- && x.ReturnType == retType
- && x.GetParameters().Length == argTypes.Length
- && x.GetParameters().Select(p => p.ParameterType)
- .Zip(argTypes, (paramType, argType) => paramType.IsAssignableFrom(argType)).All(y => y));
+ try
+ {
+ var sidecar = serviceProvider.GetService();
+ var argTypes = args.Select(x => x.GetType()).ToArray();
+ var sidecarMethod = sidecar?.GetType()?.GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .FirstOrDefault(x => x.Name == methodName
+ && x.ReturnType == retType
+ && x.GetParameters().Length == argTypes.Length
+ && x.GetParameters().Select(p => p.ParameterType)
+ .Zip(argTypes, (paramType, argType) => paramType.IsAssignableFrom(argType)).All(y => y));
- return (sidecar, sidecarMethod);
+ return (sidecar, sidecarMethod);
+ }
+ catch
+ {
+ return (null, null);
+ }
}
private async Task<(bool, object?)> CallAsyncMethod(IConversationSideCar instance, MethodInfo method, Type retType, object[] args)
diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs
index 84b65573..28162a7e 100644
--- a/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs
@@ -9,6 +9,7 @@ public interface IConversationSideCar
List GetConversationDialogs(string conversationId);
void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint);
ConversationBreakpoint? GetConversationBreakpoint(string conversationId);
+ void UpdateConversationStates(string conversationId, List states);
Task SendMessage(string agentId, string text,
PostbackMessageModel? postback = null, List? states = null, List? dialogs = null);
}
diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs
index 34a98017..a948e5e2 100644
--- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs
+++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs
@@ -23,9 +23,10 @@ public class BotSharpConversationSideCar : IConversationSideCar
private readonly IServiceProvider _services;
private readonly ILogger _logger;
- private Stack contextStack = new();
+ private Stack _contextStack = new();
- private bool enabled = false;
+ private bool _enabled = false;
+ private string _conversationId = string.Empty;
public string Provider => "botsharp";
@@ -39,46 +40,63 @@ public class BotSharpConversationSideCar : IConversationSideCar
public bool IsEnabled()
{
- return enabled;
+ return _enabled;
}
public void AppendConversationDialogs(string conversationId, List messages)
{
- if (contextStack.IsNullOrEmpty()) return;
+ if (!IsValid(conversationId))
+ {
+ return;
+ }
- var top = contextStack.Peek();
+ var top = _contextStack.Peek();
top.Dialogs.AddRange(messages);
}
public List GetConversationDialogs(string conversationId)
{
- if (contextStack.IsNullOrEmpty())
+ if (!IsValid(conversationId))
{
return new List();
}
- return contextStack.Peek().Dialogs;
+ return _contextStack.Peek().Dialogs;
}
public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint)
{
- if (contextStack.IsNullOrEmpty()) return;
+ if (!IsValid(conversationId))
+ {
+ return;
+ }
- var top = contextStack.Peek().Breakpoints;
+ var top = _contextStack.Peek().Breakpoints;
top.Add(breakpoint);
}
public ConversationBreakpoint? GetConversationBreakpoint(string conversationId)
{
- if (contextStack.IsNullOrEmpty())
+ if (!IsValid(conversationId))
{
return null;
}
- var top = contextStack.Peek().Breakpoints;
+ var top = _contextStack.Peek().Breakpoints;
return top.LastOrDefault();
}
+ public void UpdateConversationStates(string conversationId, List states)
+ {
+ if (!IsValid(conversationId))
+ {
+ return;
+ }
+
+ var top = _contextStack.Peek();
+ top.State = new ConversationState(states);
+ }
+
public async Task SendMessage(string agentId, string text,
PostbackMessageModel? postback = null, List? states = null, List? dialogs = null)
{
@@ -94,6 +112,7 @@ public class BotSharpConversationSideCar : IConversationSideCar
var conv = _services.GetRequiredService();
var routing = _services.GetRequiredService();
var state = _services.GetRequiredService();
+ _conversationId = conv.ConversationId;
var inputMsg = new RoleDialogModel(AgentRole.User, text);
routing.Context.SetMessageId(conv.ConversationId, inputMsg.MessageId);
@@ -116,7 +135,7 @@ public class BotSharpConversationSideCar : IConversationSideCar
private void BeforeExecute(List? dialogs)
{
- enabled = true;
+ _enabled = true;
var state = _services.GetRequiredService();
var routing = _services.GetRequiredService();
@@ -129,7 +148,7 @@ public class BotSharpConversationSideCar : IConversationSideCar
RecursiveCounter = routing.Context.GetRecursiveCounter(),
RoutingStack = routing.Context.GetAgentStack()
};
- contextStack.Push(node);
+ _contextStack.Push(node);
// Reset
state.ResetCurrentState();
@@ -144,7 +163,7 @@ public class BotSharpConversationSideCar : IConversationSideCar
var state = _services.GetRequiredService();
var routing = _services.GetRequiredService();
- var node = contextStack.Pop();
+ var node = _contextStack.Pop();
// Recover
state.SetCurrentState(node.State);
@@ -152,6 +171,14 @@ public class BotSharpConversationSideCar : IConversationSideCar
routing.Context.SetAgentStack(node.RoutingStack);
routing.Context.SetDialogs(node.RoutingDialogs);
Utilities.ClearCache();
- enabled = false;
+ _enabled = false;
+ }
+
+ private bool IsValid(string conversationId)
+ {
+ return !_contextStack.IsNullOrEmpty()
+ && _conversationId == conversationId
+ && !string.IsNullOrEmpty(conversationId)
+ && !string.IsNullOrEmpty(_conversationId);
}
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs
index 3b60a61a..3d03e816 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs
@@ -307,6 +307,7 @@ public partial class FileRepository
return new ConversationState(states);
}
+ [SideCar]
public void UpdateConversationStates(string conversationId, List states)
{
if (states.IsNullOrEmpty()) return;
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs
index 59ff3b9a..a37e2407 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs
@@ -65,6 +65,12 @@ public static class ReasonerHelper
}
}
+ if (args.AgentName == "response_to_user")
+ {
+ args.AgentName = "";
+ malformed = true;
+ }
+
if (malformed)
{
Console.WriteLine($"Captured LLM malformed response");
diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/functions/response_to_user.json b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/functions/response_to_user.json
index 9e7f8929..6f6c995a 100644
--- a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/functions/response_to_user.json
+++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/functions/response_to_user.json
@@ -7,6 +7,10 @@
"content": {
"type": "string",
"description": "Response content"
+ },
+ "conversation_end": {
+ "type": "boolean",
+ "description": "User is ending the conversation."
}
},
"required": [ "content" ]
diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/KnowledgeBasePlugin.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/KnowledgeBasePlugin.cs
index 31fa964c..2bbc950a 100644
--- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/KnowledgeBasePlugin.cs
+++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/KnowledgeBasePlugin.cs
@@ -35,7 +35,7 @@ public class KnowledgeBasePlugin : IBotSharpPlugin
var section = menu.First(x => x.Label == "Apps");
menu.Add(new PluginMenuDef("Knowledge Base", icon: "bx bx-book-open", weight: section.Weight + 1)
{
- Roles = new List { UserRole.Root, UserRole.Admin },
+ Roles = new List { UserRole.Root, UserRole.Admin, UserRole.Engineer },
SubMenu = new List
{
new PluginMenuDef("Q & A", link: "page/knowledge-base/question-answer"),
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs
index 02f5a695..edcf3a5f 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs
@@ -266,6 +266,7 @@ public partial class MongoRepository
return new ConversationState(savedStates);
}
+ [SideCar]
public void UpdateConversationStates(string conversationId, List states)
{
if (string.IsNullOrEmpty(conversationId) || states == null) return;
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs
index 5ffd692e..8d34dd22 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs
@@ -1,7 +1,9 @@
using BotSharp.Abstraction.Files;
using BotSharp.Abstraction.Infrastructures.Enums;
using BotSharp.Abstraction.Options;
+using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Routing;
+using BotSharp.Abstraction.Utilities;
using BotSharp.Core.Infrastructures;
using BotSharp.Plugin.Twilio.Interfaces;
using BotSharp.Plugin.Twilio.Models;
@@ -134,9 +136,10 @@ public class OutboundPhoneCallFn : IFunctionCallback
}
}
- private async Task ForkConversation(LlmContextIn args,
- string entryAgentId,
- string originConversationId,
+ private async Task ForkConversation(
+ LlmContextIn args,
+ string entryAgentId,
+ string originConversationId,
string newConversationId,
CallResource call)
{
@@ -145,6 +148,8 @@ public class OutboundPhoneCallFn : IFunctionCallback
var services = scope.ServiceProvider;
var convService = services.GetRequiredService();
var convStorage = services.GetRequiredService();
+ var state = _services.GetRequiredService();
+ var db = _services.GetRequiredService();
var newConv = await convService.NewConversation(new Conversation
{
@@ -170,15 +175,45 @@ public class OutboundPhoneCallFn : IFunctionCallback
}
});
- convService.SetConversationId(newConversationId,
- [
- new MessageState(StateConst.ORIGIN_CONVERSATION_ID, originConversationId),
- new MessageState("channel", "phone"),
- new MessageState("phone_from", call.From),
- new MessageState("phone_direction", call.Direction),
- new MessageState("phone_number", call.To),
- new MessageState("twilio_call_sid", call.Sid)
- ]);
- convService.SaveStates();
+ var utcNow = DateTime.UtcNow;
+ var excludStates = new List
+ {
+ "provider",
+ "model",
+ "prompt_total",
+ "completion_total",
+ "llm_total_cost"
+ };
+
+ var curStates = state.GetStates().Select(x => new MessageState(x.Key, x.Value)).ToList();
+ var subConvStates = new List
+ {
+ new(StateConst.ORIGIN_CONVERSATION_ID, originConversationId),
+ new("channel", "phone"),
+ new("phone_from", call.From),
+ new("phone_direction", call.Direction),
+ new("phone_number", call.To),
+ new("twilio_call_sid", call.Sid)
+ };
+ var subStateKeys = subConvStates.Select(x => x.Key).ToList();
+ var included = curStates.Where(x => !subStateKeys.Contains(x.Key) && !excludStates.Contains(x.Key));
+ var newStates = subConvStates.Concat(included).Select(x => new StateKeyValue
+ {
+ Key = x.Key,
+ Versioning = true,
+ Values = [
+ new StateValue
+ {
+ Data = x.Value.ConvertToString(_options.JsonSerializerOptions),
+ MessageId = messageId,
+ Active = true,
+ ActiveRounds = x.ActiveRounds,
+ Source = StateSource.Application,
+ UpdateTime = utcNow
+ }
+ ]
+ }).ToList();
+
+ db.UpdateConversationStates(newConversationId, newStates);
}
}