From 0932bd4922c36cf574a82cc6bc3c506e1247f5e8 Mon Sep 17 00:00:00 2001 From: vguruparan Date: Wed, 16 Apr 2025 13:43:57 -0500 Subject: [PATCH 01/14] Update invoice submission file url --- .../PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs index 922e8f58..ca29b9ca 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs @@ -52,7 +52,13 @@ public partial class PlaywrightWebDriver { page = await _instance.NewPage(message, args); } + var cdpSession = await context.NewCDPSessionAsync(page); + // Set CPU throttling rate + await cdpSession.SendAsync("Emulation.setCPUThrottlingRate", new Dictionary + { + { "rate", 25 } + }); // Active current tab await page.BringToFrontAsync(); var response = await page.GotoAsync(args.Url, new PageGotoOptions From 4d1cfb8e236528607051dd0ef4ee132ed37d2ec9 Mon Sep 17 00:00:00 2001 From: vguruparan Date: Wed, 16 Apr 2025 13:44:17 -0500 Subject: [PATCH 02/14] Revert "Update invoice submission file url" This reverts commit 0932bd4922c36cf574a82cc6bc3c506e1247f5e8. --- .../PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs index ca29b9ca..922e8f58 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs @@ -52,13 +52,7 @@ public partial class PlaywrightWebDriver { page = await _instance.NewPage(message, args); } - var cdpSession = await context.NewCDPSessionAsync(page); - // Set CPU throttling rate - await cdpSession.SendAsync("Emulation.setCPUThrottlingRate", new Dictionary - { - { "rate", 25 } - }); // Active current tab await page.BringToFrontAsync(); var response = await page.GotoAsync(args.Url, new PageGotoOptions From 16e0553eb0e454f95c55e018e0338fd678e214b6 Mon Sep 17 00:00:00 2001 From: vguruparan Date: Fri, 18 Apr 2025 16:12:30 -0500 Subject: [PATCH 03/14] Add webdriver hook to support file uploads --- .../Browsing/IWebDriverHook.cs | 8 ++++++++ .../Browsing/Models/MessageInfo.cs | 1 + .../PlaywrightWebDriver.DoAction.cs | 16 ++++++++++++++-- .../PlaywrightDriver/PlaywrightWebDriver.cs | 2 +- .../UtilFunctions/UtilWebActionOnElementFn.cs | 3 +++ .../functions/util-web-action_on_element.json | 4 ++++ 6 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Browsing/IWebDriverHook.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebDriverHook.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebDriverHook.cs new file mode 100644 index 00000000..ce5d03b6 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebDriverHook.cs @@ -0,0 +1,8 @@ +using BotSharp.Abstraction.Browsing.Models; + +namespace BotSharp.Abstraction.Browsing; + +public interface IWebDriverHook +{ + Task> GetUploadFiles(MessageInfo message); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/MessageInfo.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/MessageInfo.cs index 92c9d42d..3e81ad56 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/MessageInfo.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/MessageInfo.cs @@ -12,6 +12,7 @@ public class MessageInfo : ICacheKey public string? MessageId { get; set; } public string? TaskId { get; set; } public string StepId { get; set; } = Guid.NewGuid().ToString(); + public string? FunctionArgs { get; set; } public string GetCacheKey() => $"{nameof(MessageInfo)}"; diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs index 865864a0..2f3bf7e0 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs @@ -80,8 +80,20 @@ public partial class PlaywrightWebDriver } else if (action.Action == BroswerActionEnum.FileUpload) { - if (action.FileUrl.Length == 0) + var _states = _services.GetRequiredService(); + var files = new List(); + if (action.FileUrl != null && action.FileUrl.Length > 0) { + files.AddRange(action.FileUrl); + } + var hooks = _services.GetServices(); + foreach (var hook in hooks) + { + files.AddRange(await hook.GetUploadFiles(message)); + } + if (files.Count == 0) + { + Serilog.Log.Warning($"No files found to upload: {action.Content}"); return; } var fileChooser = await page.RunAndWaitForFileChooserAsync(async () => @@ -97,7 +109,7 @@ public partial class PlaywrightWebDriver Directory.CreateDirectory(directory); var localPaths = new List(); using var httpClient = new HttpClient(); - foreach (var fileUrl in action.FileUrl) + foreach (var fileUrl in files) { var bytes = await httpClient.GetByteArrayAsync(fileUrl); var fileName = new Uri(fileUrl).AbsolutePath; diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs index 56df622c..3a4d62f2 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs @@ -71,7 +71,7 @@ public partial class PlaywrightWebDriver : IWebBrowser public void SetServiceProvider(IServiceProvider services) { - _instance.SetServiceProvider(_services); + _instance.SetServiceProvider(services); } public async Task PressKey(MessageInfo message, string key) diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebActionOnElementFn.cs b/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebActionOnElementFn.cs index 33f0313a..5c0c40a6 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebActionOnElementFn.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebActionOnElementFn.cs @@ -42,7 +42,10 @@ public class UtilWebActionOnElementFn : IFunctionCallback AgentId = message.CurrentAgentId, MessageId = message.MessageId, ContextId = webDriverService.GetMessageContext(message), + FunctionArgs = message.FunctionArgs }; + browser.SetServiceProvider(_services); + var _states = _services.GetRequiredService(); var result = await browser.ActionOnElement(msg, locatorArgs, actionArgs); message.Content = $"{actionArgs.Action} executed {(result.IsSuccess ? "success" : "failed")}."; diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-web-action_on_element.json b/src/Plugins/BotSharp.Plugin.WebDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-web-action_on_element.json index 364858c4..8265bc03 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-web-action_on_element.json +++ b/src/Plugins/BotSharp.Plugin.WebDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-web-action_on_element.json @@ -37,6 +37,10 @@ "wait_time": { "type": "number", "description": "wait time after action in seconds" + }, + "metadata": { + "type": "string", + "description": "meta data information if user provided" } }, "required": [ "selector", "action" ] From dc0c2ca8adf066db7d6e4d073df6b6664c0df05d Mon Sep 17 00:00:00 2001 From: vguruparan Date: Fri, 18 Apr 2025 16:25:13 -0500 Subject: [PATCH 04/14] clean code --- .../UtilFunctions/UtilWebActionOnElementFn.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebActionOnElementFn.cs b/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebActionOnElementFn.cs index 5c0c40a6..a23d1654 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebActionOnElementFn.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebActionOnElementFn.cs @@ -32,8 +32,6 @@ public class UtilWebActionOnElementFn : IFunctionCallback actionArgs.WaitTime = actionArgs.WaitTime > 0 ? actionArgs.WaitTime : 2; - var conv = _services.GetRequiredService(); - var services = _services.CreateScope().ServiceProvider; var browser = services.GetRequiredService(); var webDriverService = _services.GetRequiredService(); @@ -45,7 +43,6 @@ public class UtilWebActionOnElementFn : IFunctionCallback FunctionArgs = message.FunctionArgs }; browser.SetServiceProvider(_services); - var _states = _services.GetRequiredService(); var result = await browser.ActionOnElement(msg, locatorArgs, actionArgs); message.Content = $"{actionArgs.Action} executed {(result.IsSuccess ? "success" : "failed")}."; From 7b5c88906872fbed1b1140f3c8fdcebd7ee105b8 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Fri, 18 Apr 2025 16:34:46 -0500 Subject: [PATCH 05/14] ResponseDoneStatusDetail --- .../Handlers/RouteToAgentRoutingHandler.cs | 2 +- .../BotSharp.Logger/Hooks/VerboseLogHook.cs | 2 +- .../Models/Realtime/ResponseDone.cs | 27 ++++++++++++++++++- .../Realtime/RealTimeCompletionProvider.cs | 1 + 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs index f79c6719..1dc808a0 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -71,7 +71,7 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler // Update next action agent's name var agentService = _services.GetRequiredService(); - var agent = await agentService.LoadAgent(agentId); + var agent = await agentService.GetAgent(agentId); inst.AgentName = agent.Name; if (inst.ExecutingDirectly) diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/VerboseLogHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/VerboseLogHook.cs index 7affc3a4..fb3c37a2 100644 --- a/src/Infrastructure/BotSharp.Logger/Hooks/VerboseLogHook.cs +++ b/src/Infrastructure/BotSharp.Logger/Hooks/VerboseLogHook.cs @@ -38,7 +38,7 @@ public class VerboseLogHook : IContentGeneratingHook if (!_convSettings.ShowVerboseLog || string.IsNullOrEmpty(tokenStats.Prompt)) return; var agentService = _services.GetRequiredService(); - var agent = await agentService.LoadAgent(message.CurrentAgentId); + var agent = await agentService.GetAgent(message.CurrentAgentId); var log = message.Role == AgentRole.Function ? $"[{agent?.Name}]: {message.Indication} {message.FunctionName}({message.FunctionArgs})" : diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Models/Realtime/ResponseDone.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Models/Realtime/ResponseDone.cs index ae3db58d..421747b9 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Models/Realtime/ResponseDone.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Models/Realtime/ResponseDone.cs @@ -89,7 +89,32 @@ public class ResponseDoneStatusDetail public string Type { get; set; } = null!; [JsonPropertyName("reason")] - public string Reason { get; set; } = null!; + public string? Reason { get; set; } = null!; + + [JsonPropertyName("error")] + public ResponseDoneErrorStatus? Error { get; set; } = null!; + + public override string ToString() + { + return $"{Type}: {Reason} ({Error})"; + } +} + +public class ResponseDoneErrorStatus +{ + [JsonPropertyName("type")] + public string Type { get; set; } = null!; + + [JsonPropertyName("message")] + public string? Message { get; set; } = null!; + + [JsonPropertyName("code")] + public string? Code { get; set; } = null!; + + public override string ToString() + { + return $"{Type}: {Message} ({Code})"; + } } public class ResponseDoneOutputContent diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs index 959ed457..b0b655ce 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs @@ -560,6 +560,7 @@ public class RealTimeCompletionProvider : IRealTimeCompletion var data = JsonSerializer.Deserialize(response).Body; if (data.Status != "completed") { + _logger.LogError(data.StatusDetails.ToString()); return []; } From 74d38a5132c31b458b8abf0c549fd672752a3d8d Mon Sep 17 00:00:00 2001 From: vguruparan Date: Fri, 18 Apr 2025 17:03:24 -0500 Subject: [PATCH 06/14] add error handler for actiononelement util --- .../UtilFunctions/UtilWebActionOnElementFn.cs | 72 ++++++++++--------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebActionOnElementFn.cs b/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebActionOnElementFn.cs index a23d1654..7e14c135 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebActionOnElementFn.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebActionOnElementFn.cs @@ -19,44 +19,52 @@ public class UtilWebActionOnElementFn : IFunctionCallback { var locatorArgs = JsonSerializer.Deserialize(message.FunctionArgs); var actionArgs = JsonSerializer.Deserialize(message.FunctionArgs); - if (actionArgs.Action == BroswerActionEnum.InputText) + try { - // Replace variable in input text - if (actionArgs.Content.StartsWith("@")) + if (actionArgs.Action == BroswerActionEnum.InputText) { - var config = _services.GetRequiredService(); - var key = actionArgs.Content.Replace("@", string.Empty); - actionArgs.Content = key.Replace(key, config[key]); + // Replace variable in input text + if (actionArgs.Content.StartsWith("@")) + { + var config = _services.GetRequiredService(); + var key = actionArgs.Content.Replace("@", string.Empty); + actionArgs.Content = key.Replace(key, config[key]); + } } + + actionArgs.WaitTime = actionArgs.WaitTime > 0 ? actionArgs.WaitTime : 2; + + var services = _services.CreateScope().ServiceProvider; + var browser = services.GetRequiredService(); + var webDriverService = _services.GetRequiredService(); + var msg = new MessageInfo + { + AgentId = message.CurrentAgentId, + MessageId = message.MessageId, + ContextId = webDriverService.GetMessageContext(message), + FunctionArgs = message.FunctionArgs + }; + browser.SetServiceProvider(_services); + var result = await browser.ActionOnElement(msg, locatorArgs, actionArgs); + + message.Content = $"{actionArgs.Action} executed {(result.IsSuccess ? "success" : "failed")}."; + + // Add Current Url info to the message + if (actionArgs.ShowCurrentUrl) + { + message.Content += $" Current page url: '{result.UrlAfterAction}'."; + } + + var path = webDriverService.GetScreenshotFilePath(message.MessageId); + + message.Data = await browser.ScreenshotAsync(msg, path); + } - - actionArgs.WaitTime = actionArgs.WaitTime > 0 ? actionArgs.WaitTime : 2; - - var services = _services.CreateScope().ServiceProvider; - var browser = services.GetRequiredService(); - var webDriverService = _services.GetRequiredService(); - var msg = new MessageInfo + catch (Exception ex) { - AgentId = message.CurrentAgentId, - MessageId = message.MessageId, - ContextId = webDriverService.GetMessageContext(message), - FunctionArgs = message.FunctionArgs - }; - browser.SetServiceProvider(_services); - var result = await browser.ActionOnElement(msg, locatorArgs, actionArgs); - - message.Content = $"{actionArgs.Action} executed {(result.IsSuccess ? "success" : "failed")}."; - - // Add Current Url info to the message - if (actionArgs.ShowCurrentUrl) - { - message.Content += $" Current page url: '{result.UrlAfterAction}'."; + message.Data = $"{actionArgs.Action} execution failed."; + _logger.LogError($"UtilWebActionOnElementFn exception: {ex.Message}. StackTrace: {ex.StackTrace}"); } - - var path = webDriverService.GetScreenshotFilePath(message.MessageId); - - message.Data = await browser.ScreenshotAsync(msg, path); - return true; } } From 8131f7f47b1303a6feb44d139d490eb708ee16f9 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Fri, 18 Apr 2025 20:44:42 -0500 Subject: [PATCH 07/14] Remove unnecessary agent load --- .../Realtime/Models/RealtimeModelSettings.cs | 1 + .../Agents/Services/AgentService.GetAgents.cs | 6 ---- .../Services/ConversationStateService.cs | 4 +-- .../BotSharp.Core/Routing/RoutingContext.cs | 2 +- .../Hooks/StreamingLogHook.cs | 28 +++++++++---------- .../Realtime/RealTimeCompletionProvider.cs | 2 +- .../Functions/HangupPhoneCallFn.cs | 4 +-- .../Services/TwilioService.cs | 6 ++++ 8 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Realtime/Models/RealtimeModelSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Realtime/Models/RealtimeModelSettings.cs index 7ebe2c42..daf8714a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Realtime/Models/RealtimeModelSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Realtime/Models/RealtimeModelSettings.cs @@ -4,6 +4,7 @@ public class RealtimeModelSettings { public string Provider { get; set; } = "openai"; public string Model { get; set; } = "gpt-4o-mini-realtime-preview"; + public string[] Modalities { get; set; } = ["text", "audio"]; public bool InterruptResponse { get; set; } = true; public string InputAudioFormat { get; set; } = "g711_ulaw"; public string OutputAudioFormat { get; set; } = "g711_ulaw"; diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs index 4cac4c86..b07a64ff 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs @@ -5,9 +5,7 @@ namespace BotSharp.Core.Agents.Services; public partial class AgentService { -#if !DEBUG [SharpCache(10)] -#endif public async Task> GetAgents(AgentFilter filter) { var agents = _db.GetAgents(filter); @@ -27,9 +25,7 @@ public partial class AgentService }; } -#if !DEBUG [SharpCache(10)] -#endif public async Task> GetAgentOptions(List? agentIds) { var agents = _db.GetAgents(new AgentFilter @@ -39,9 +35,7 @@ public partial class AgentService return agents?.Select(x => new IdName(x.Id, x.Name))?.OrderBy(x => x.Name)?.ToList() ?? []; } -#if !DEBUG [SharpCache(10)] -#endif public async Task GetAgent(string id) { var profile = _db.GetAgent(id); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index 9e101b74..853b1561 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -86,7 +86,7 @@ public class ConversationStateService : IConversationStateService preValue = prevLeafNode?.Data ?? string.Empty; } - _logger.LogInformation($"[STATE] {name} = {value}"); + _logger.LogDebug($"[STATE] {name} = {value}"); var routingCtx = _services.GetRequiredService(); var isNoChange = ContainsState(name) @@ -221,7 +221,7 @@ public class ConversationStateService : IConversationStateService var data = leafNode.Data ?? string.Empty; endNodes[state.Key] = data; - _logger.LogInformation($"[STATE] {key} : {data}"); + _logger.LogDebug($"[STATE] {key} : {data}"); } _logger.LogInformation($"Loaded conversation states: {conversationId}"); diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs index 47e47631..63049738 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs @@ -129,7 +129,7 @@ public class RoutingContext : IRoutingContext // Run the routing rule var agency = _services.GetRequiredService(); - var agent = agency.LoadAgent(currentAgentId).Result; + var agent = agency.GetAgent(currentAgentId).Result; var message = new RoleDialogModel(AgentRole.User, $"Try to route to agent {agent.Name}") { diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index 22ec7d4d..069b6fc2 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -146,7 +146,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR if (message.FunctionName == "route_to_agent") return; - var agent = await _agentService.LoadAgent(message.CurrentAgentId); + var agent = await _agentService.GetAgent(message.CurrentAgentId); message.FunctionArgs = message.FunctionArgs ?? "{}"; var args = message.FunctionArgs.FormatJson(); var log = $"*{message.Indication.Replace("\r", string.Empty).Replace("\n", string.Empty)}* \r\n\r\n **{message.FunctionName}**()"; @@ -169,7 +169,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR if (message.FunctionName == "route_to_agent") return; - var agent = await _agentService.LoadAgent(message.CurrentAgentId); + var agent = await _agentService.GetAgent(message.CurrentAgentId); message.FunctionArgs = message.FunctionArgs ?? "{}"; var log = $"{message.FunctionName} =>\r\n*{message.Content?.Trim()}*"; @@ -196,7 +196,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR var conversationId = _state.GetConversationId(); if (string.IsNullOrEmpty(conversationId)) return; - var agent = await _agentService.LoadAgent(message.CurrentAgentId); + var agent = await _agentService.GetAgent(message.CurrentAgentId); var log = tokenStats.Prompt; @@ -226,7 +226,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR if (message.Role == AgentRole.Assistant) { - var agent = await _agentService.LoadAgent(message.CurrentAgentId); + var agent = await _agentService.GetAgent(message.CurrentAgentId); var log = $"{GetMessageContent(message)}"; if (message.RichContent != null || message.SecondaryRichContent != null) { @@ -251,7 +251,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR if (string.IsNullOrEmpty(conversationId)) return; var log = $"{GetMessageContent(message)}"; - var agent = await _agentService.LoadAgent(message.CurrentAgentId); + var agent = await _agentService.GetAgent(message.CurrentAgentId); var input = new ContentLogInputModel(conversationId, message) { @@ -268,7 +268,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR if (string.IsNullOrEmpty(conversationId)) return; var log = $"Conversation ended"; - var agent = await _agentService.LoadAgent(message.CurrentAgentId); + var agent = await _agentService.GetAgent(message.CurrentAgentId); var input = new ContentLogInputModel(conversationId, message) { @@ -290,7 +290,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR } var routing = _services.GetRequiredService(); var agentId = routing.Context.GetCurrentAgentId(); - var agent = await _agentService.LoadAgent(agentId); + var agent = await _agentService.GetAgent(agentId); var input = new ContentLogInputModel() { @@ -324,7 +324,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR var conversationId = _state.GetConversationId(); if (string.IsNullOrEmpty(conversationId)) return; - var agent = await _agentService.LoadAgent(agentId); + var agent = await _agentService.GetAgent(agentId); // Agent queue log var log = $"{agent.Name} is enqueued"; @@ -351,8 +351,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR var conversationId = _state.GetConversationId(); if (string.IsNullOrEmpty(conversationId)) return; - var agent = await _agentService.LoadAgent(agentId); - var currentAgent = await _agentService.LoadAgent(currentAgentId); + var agent = await _agentService.GetAgent(agentId); + var currentAgent = await _agentService.GetAgent(currentAgentId); // Agent queue log var log = $"{agent.Name} is dequeued"; @@ -379,8 +379,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR var conversationId = _state.GetConversationId(); if (string.IsNullOrEmpty(conversationId)) return; - var fromAgent = await _agentService.LoadAgent(fromAgentId); - var toAgent = await _agentService.LoadAgent(toAgentId); + var fromAgent = await _agentService.GetAgent(fromAgentId); + var toAgent = await _agentService.GetAgent(toAgentId); // Agent queue log var log = $"Agent queue is replaced from {fromAgent.Name} to {toAgent.Name}"; @@ -432,7 +432,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR var conversationId = _state.GetConversationId(); if (string.IsNullOrEmpty(conversationId)) return; - var agent = await _agentService.LoadAgent(message.CurrentAgentId); + var agent = await _agentService.GetAgent(message.CurrentAgentId); var log = JsonSerializer.Serialize(instruct, _options.JsonSerializerOptions); log = $"```json\r\n{log}\r\n```"; @@ -451,7 +451,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR var conversationId = _state.GetConversationId(); if (string.IsNullOrEmpty(conversationId)) return; - var agent = await _agentService.LoadAgent(message.CurrentAgentId); + var agent = await _agentService.GetAgent(message.CurrentAgentId); var log = $"Revised user goal agent to {instruct.OriginalAgent}"; var input = new ContentLogInputModel(conversationId, message) diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs index b0b655ce..e87f2226 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs @@ -258,7 +258,7 @@ public class RealTimeCompletionProvider : IRealTimeCompletion Instructions = instruction, ToolChoice = "auto", Tools = functions, - Modalities = [ "text", "audio" ], + Modalities = realtimeModelSettings.Modalities, Temperature = Math.Max(options.Temperature ?? realtimeModelSettings.Temperature, 0.6f), MaxResponseOutputTokens = realtimeModelSettings.MaxResponseOutputTokens, TurnDetection = new RealtimeSessionTurnDetection diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HangupPhoneCallFn.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HangupPhoneCallFn.cs index 89d770ac..3bba99e4 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HangupPhoneCallFn.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HangupPhoneCallFn.cs @@ -44,7 +44,7 @@ public class HangupPhoneCallFn : IFunctionCallback var processUrl = $"{_twilioSetting.CallbackHost}/twilio/voice/hang-up?agent-id={message.CurrentAgentId}&conversation-id={conversationId}"; // Generate initial assistant audio - string initAudioFile = null; + /*string initAudioFile = null; if (!string.IsNullOrEmpty(args.ResponseContent)) { var completion = CompletionProvider.GetAudioSynthesizer(_services); @@ -53,7 +53,7 @@ public class HangupPhoneCallFn : IFunctionCallback fileStorage.SaveSpeechFile(conversationId, initAudioFile, data); processUrl += $"&init-audio-file={initAudioFile}"; - } + }*/ var call = CallResource.Update( url: new Uri(processUrl), diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs index 4b074a43..d93f39fb 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs @@ -139,6 +139,12 @@ public class TwilioService response.Play(new Uri(uri)); } } + else + { + response.Pause(5); + response.Say("Goodbye."); + } + response.Hangup(); return response; } From 3deb6deee53f331b200ff44dc54320be0b666dd3 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Sun, 20 Apr 2025 08:05:20 -0500 Subject: [PATCH 08/14] Optimize realtime route_to_agent --- .../Hooks/RealtimeConversationHook.cs | 28 +++++++++---------- .../Services/TwilioService.cs | 1 - .../TwilioStreamMiddleware.cs | 20 ++++++------- .../util-twilio-hangup_phone_call.json | 6 +--- 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs b/src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs index bb019996..85c7d24c 100644 --- a/src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs +++ b/src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Utilities; -using BotSharp.Core.Infrastructures; namespace BotSharp.Core.Realtime.Hooks; @@ -40,33 +39,32 @@ public class RealtimeConversationHook : ConversationHookBase, IConversationHook if (message.FunctionName == "route_to_agent") { - var inst = JsonSerializer.Deserialize(message.FunctionArgs ?? "{}") ?? new(); - message.Content = $"I'm your AI assistant '{inst.AgentName}' to help with: '{inst.NextActionReason}'"; hub.HubConn.CurrentAgentId = routing.Context.GetCurrentAgentId(); - var instruction = await hub.Completer.UpdateSession(hub.HubConn); - await hub.Completer.InsertConversationItem(message); - await hub.Completer.TriggerModelInference($"{instruction}\r\n\r\nAssist user task: {inst.NextActionReason}"); + await hub.Completer.UpdateSession(hub.HubConn); + await hub.Completer.TriggerModelInference(); } else if (message.FunctionName == "util-routing-fallback_to_router") { - var inst = JsonSerializer.Deserialize(message.FunctionArgs ?? "{}") ?? new(); - message.Content = $"Returned to Router due to {inst.Reason}"; hub.HubConn.CurrentAgentId = routing.Context.GetCurrentAgentId(); - var instruction = await hub.Completer.UpdateSession(hub.HubConn); - await hub.Completer.InsertConversationItem(message); - await hub.Completer.TriggerModelInference(instruction); + await hub.Completer.UpdateSession(hub.HubConn); + await hub.Completer.TriggerModelInference(); } else { - // Clear cache to force to rebuild the agent instruction - Utilities.ClearCache(); - // Update session for changed states var instruction = await hub.Completer.UpdateSession(hub.HubConn); await hub.Completer.InsertConversationItem(message); - await hub.Completer.TriggerModelInference(instruction); + + if (message.StopCompletion) + { + await hub.Completer.TriggerModelInference($"Say to user: \"{message.Content}\""); + } + else + { + await hub.Completer.TriggerModelInference($"{instruction}\r\n\r\nResponse user based on function result"); + } } } } diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs index d93f39fb..a792f72d 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs @@ -141,7 +141,6 @@ public class TwilioService } else { - response.Pause(5); response.Say("Goodbye."); } diff --git a/src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs b/src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs index 1c6f525e..ce4ad144 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs @@ -94,6 +94,13 @@ public class TwilioStreamMiddleware } else if (eventType == "user_dtmf_receiving") { + // Send a Stop command to Twilio + string clearEvent = JsonSerializer.Serialize(new + { + @event = "clear", + streamSid = conn.StreamId + }); + await SendEventToUser(webSocket, clearEvent); } else if (eventType == "user_dtmf_received") { @@ -183,14 +190,6 @@ public class TwilioStreamMiddleware streamSid = response.StreamSid }); - /*if (response.Event == "dtmf") - { - // Send a Stop command to Twilio - string stopPlaybackCommand = "{ \"action\": \"stop_playback\" }"; - var stopBytes = Encoding.UTF8.GetBytes(stopPlaybackCommand); - webSocket.SendAsync(new ArraySegment(stopBytes), WebSocketMessageType.Text, true, CancellationToken.None); - }*/ - return (eventType, data); } @@ -225,7 +224,7 @@ public class TwilioStreamMiddleware var routing = _services.GetRequiredService(); var hookProvider = _services.GetRequiredService(); var agentService = _services.GetRequiredService(); - var agent = await agentService.LoadAgent(conn.CurrentAgentId); + var agent = await agentService.GetAgent(conn.CurrentAgentId); var dialogs = routing.Context.GetDialogs(); var convService = _services.GetRequiredService(); var conversation = await convService.GetConversation(conn.ConversationId); @@ -248,7 +247,6 @@ public class TwilioStreamMiddleware } await completer.InsertConversationItem(message); - var instruction = await completer.UpdateSession(conn); - await completer.TriggerModelInference($"{instruction}\r\n\r\nReply based on the user input: {message.Content}"); + await completer.TriggerModelInference($"Response based on the user input: {message.Content}"); } } diff --git a/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-twilio-hangup_phone_call.json b/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-twilio-hangup_phone_call.json index 9f0320f3..61f68692 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-twilio-hangup_phone_call.json +++ b/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-twilio-hangup_phone_call.json @@ -8,12 +8,8 @@ "reason": { "type": "string", "description": "The reason why user wants to end the phone call." - }, - "response_content": { - "type": "string", - "description": "A response statement said to the user to politely and gratefully ending a conversation before hanging up." } }, - "required": [ "reason", "response_content" ] + "required": [ "reason" ] } } \ No newline at end of file From a791600c40dca43f90aa2191871548d97292db5f Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Sun, 20 Apr 2025 18:24:13 -0500 Subject: [PATCH 09/14] Allow hook to intercept function. --- .../BotSharp.Abstraction/Agents/IAgentService.cs | 2 +- .../Conversations/Models/RoleDialogModel.cs | 6 ++++++ .../Hooks/RealtimeConversationHook.cs | 2 +- .../Agents/Services/AgentService.GetAgents.cs | 16 +++++++++++----- .../BotSharp.Core/Routing/RoutingContext.cs | 8 +++++--- .../Routing/RoutingService.InvokeFunction.cs | 9 ++++++++- .../Realtime/RealTimeCompletionProvider.cs | 7 ++++++- 7 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs index 7c2466d9..45fd3251 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs @@ -12,7 +12,7 @@ public interface IAgentService Task CreateAgent(Agent agent); Task RefreshAgents(); Task> GetAgents(AgentFilter filter); - Task> GetAgentOptions(List? agentIds = null); + Task> GetAgentOptions(List? agentIds = null, bool byName = false); /// /// Load agent configurations and trigger hooks diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index 6f96eec5..de269759 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -66,6 +66,12 @@ public class RoleDialogModel : ITrackableMessage [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? FunctionArgs { get; set; } + /// + /// Set this flag is in OnFunctionExecuting, if true, it won't be executed by InvokeFunction. + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Always)] + public bool Handled { get; set; } = false; + /// /// Function execution structured data, this data won't pass to LLM. /// It's ideal to render in rich content in UI. diff --git a/src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs b/src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs index 85c7d24c..ed53be79 100644 --- a/src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs +++ b/src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs @@ -63,7 +63,7 @@ public class RealtimeConversationHook : ConversationHookBase, IConversationHook } else { - await hub.Completer.TriggerModelInference($"{instruction}\r\n\r\nResponse user based on function result"); + await hub.Completer.TriggerModelInference(instruction); } } } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs index b07a64ff..6db0819a 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs @@ -26,12 +26,18 @@ public partial class AgentService } [SharpCache(10)] - public async Task> GetAgentOptions(List? agentIds) + public async Task> GetAgentOptions(List? agentIdsOrNames, bool byName = false) { - var agents = _db.GetAgents(new AgentFilter - { - AgentIds = !agentIds.IsNullOrEmpty() ? agentIds : null - }); + var agents = byName ? + _db.GetAgents(new AgentFilter + { + AgentNames = !agentIdsOrNames.IsNullOrEmpty() ? agentIdsOrNames : null + }) : + _db.GetAgents(new AgentFilter + { + AgentIds = !agentIdsOrNames.IsNullOrEmpty() ? agentIdsOrNames : null + }); + return agents?.Select(x => new IdName(x.Id, x.Name))?.OrderBy(x => x.Name)?.ToList() ?? []; } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs index 63049738..4cba2e2d 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs @@ -86,10 +86,12 @@ public class RoutingContext : IRoutingContext if (!Guid.TryParse(agentId, out _)) { var agentService = _services.GetRequiredService(); - agentId = agentService.GetAgents(new AgentFilter + var agents = agentService.GetAgentOptions([agentId], byName: true).Result; + + if (agents.Count > 0) { - AgentNames = [agentId] - }).Result.Items.First().Id; + agentId = agents.First().Id; + } } if (_stack.Count == 0 || _stack.Peek() != agentId) diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index ee403ff8..8c37536c 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -49,8 +49,11 @@ public partial class RoutingService await progressService.OnFunctionExecuting(clonedMessage); } + var agentService = _services.GetRequiredService(); + var agent = await agentService.GetAgent(clonedMessage.CurrentAgentId); foreach (var hook in hooks) { + hook.SetAgent(agent); await hook.OnFunctionExecuting(clonedMessage); } @@ -58,7 +61,11 @@ public partial class RoutingService try { - if (!isFillDummyContent) + if (clonedMessage.Handled) + { + clonedMessage.Content = clonedMessage.Content; + } + else if (!isFillDummyContent) { result = await function.Execute(clonedMessage); } diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs index e87f2226..924ac7f1 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs @@ -162,7 +162,7 @@ public class RealTimeCompletionProvider : IRealTimeCompletion } else if (response.Type == "response.audio_transcript.delta") { - + _logger.LogDebug($"{response.Type}: {receivedText}"); } else if (response.Type == "response.audio_transcript.done") { @@ -211,9 +211,14 @@ public class RealTimeCompletionProvider : IRealTimeCompletion } else if (response.Type == "input_audio_buffer.speech_started") { + _logger.LogInformation($"{response.Type}: {receivedText}"); // Handle user interuption onInterruptionDetected(); } + else if (response.Type == "input_audio_buffer.speech_stopped") + { + _logger.LogInformation($"{response.Type}: {receivedText}"); + } } } From 8f6e50d13e598440de18f67c4cfd24d16e2d2ed7 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Sun, 20 Apr 2025 18:29:13 -0500 Subject: [PATCH 10/14] Fix compile issue --- tests/BotSharp.LLM.Tests/Core/TestAgentService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs b/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs index e9d306af..6716519a 100644 --- a/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs +++ b/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs @@ -1,4 +1,4 @@ -using BotSharp.Abstraction.Agents; +using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Functions.Models; @@ -26,7 +26,7 @@ namespace BotSharp.Plugin.Google.Core return Task.FromResult(new PagedItems()); } - public Task> GetAgentOptions(List? agentIds = null) + public Task> GetAgentOptions(List? agentIds = null, bool byName = false) { return Task.FromResult(new List { new IdName(id: "1", name: "Fake Agent") }); } From 948b0bb3374cd6b247f97ae1de83d7816edea9d5 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Sun, 20 Apr 2025 21:18:56 -0500 Subject: [PATCH 11/14] Add agent routing mode --- .../Agents/Enums/AgentField.cs | 1 + .../Agents/Models/Agent.cs | 18 ++++++++++++++++++ .../Services/AgentService.UpdateAgent.cs | 2 ++ .../FileRepository/FileRepository.Agent.cs | 14 ++++++++++++++ .../ViewModels/Agents/View/AgentViewModel.cs | 2 ++ .../Collections/AgentDocument.cs | 1 + .../Repository/MongoRepository.Agent.cs | 16 ++++++++++++++++ .../Controllers/TwilioInboundController.cs | 5 ++--- 8 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs index 2ecddf0c..0cab0dfb 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs @@ -8,6 +8,7 @@ public enum AgentField IsPublic, Disabled, Type, + Mode, InheritAgentId, Profile, Label, diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs index a880a0c6..81cb7a3b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs @@ -13,6 +13,12 @@ public class Agent /// Agent Type /// public string Type { get; set; } = AgentType.Task; + + /// + /// Routing Mode: lazy or eager + /// + public string Mode { get; set; } = "eager"; + public DateTime CreatedDateTime { get; set; } public DateTime UpdatedDateTime { get; set; } @@ -156,6 +162,7 @@ public class Agent Name = agent.Name, Description = agent.Description, Type = agent.Type, + Mode = agent.Mode, Instruction = agent.Instruction, ChannelInstructions = agent.ChannelInstructions, Functions = agent.Functions, @@ -275,6 +282,17 @@ public class Agent return this; } + /// + /// Set agent mode: lazy or eager + /// + /// + /// + public Agent SetAgentMode(string mode) + { + Mode = mode; + return this; + } + public Agent SetProfiles(List profiles) { Profiles = profiles ?? []; diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs index 387cdfd5..29caa972 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs @@ -30,6 +30,7 @@ public partial class AgentService record.MergeUtility = agent.MergeUtility; record.MaxMessageCount = agent.MaxMessageCount; record.Type = agent.Type; + record.Mode = agent.Mode; record.Profiles = agent.Profiles ?? []; record.Labels = agent.Labels ?? []; record.RoutingRules = agent.RoutingRules ?? []; @@ -97,6 +98,7 @@ public partial class AgentService .SetDisabled(foundAgent.Disabled) .SetMergeUtility(foundAgent.MergeUtility) .SetAgentType(foundAgent.Type) + .SetAgentMode(foundAgent.Mode) .SetProfiles(foundAgent.Profiles) .SetLabels(foundAgent.Labels) .SetRoutingRules(foundAgent.RoutingRules) diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index 5ccc7088..930eb13d 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -27,6 +27,9 @@ namespace BotSharp.Core.Repository case AgentField.Type: UpdateAgentType(agent.Id, agent.Type); break; + case AgentField.Mode: + UpdateAgentMode(agent.Id, agent.Mode); + break; case AgentField.InheritAgentId: UpdateAgentInheritAgentId(agent.Id, agent.InheritAgentId); break; @@ -142,6 +145,17 @@ namespace BotSharp.Core.Repository File.WriteAllText(agentFile, json); } + private void UpdateAgentMode(string agentId, string mode) + { + var (agent, agentFile) = GetAgentFromFile(agentId); + if (agent == null) return; + + agent.Mode = mode; + agent.UpdatedDateTime = DateTime.UtcNow; + var json = JsonSerializer.Serialize(agent, _options); + File.WriteAllText(agentFile, json); + } + private void UpdateAgentInheritAgentId(string agentId, string? inheritAgentId) { var (agent, agentFile) = GetAgentFromFile(agentId); diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentViewModel.cs index 8a9a790c..4fede545 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentViewModel.cs @@ -12,6 +12,7 @@ public class AgentViewModel public string Name { get; set; } public string Description { get; set; } public string Type { get; set; } = AgentType.Task; + public string Mode { get; set; } = null!; public string Instruction { get; set; } [JsonPropertyName("channel_instructions")] @@ -82,6 +83,7 @@ public class AgentViewModel Name = agent.Name, Description = agent.Description, Type = agent.Type, + Mode = agent.Mode, Instruction = agent.Instruction, ChannelInstructions = agent.ChannelInstructions ?? [], Templates = agent.Templates ?? [], diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs index ae628456..7ebded25 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs @@ -5,6 +5,7 @@ public class AgentDocument : MongoBase public string Name { get; set; } = default!; public string Description { get; set; } = default!; public string Type { get; set; } = default!; + public string Mode { get; set; } = default!; public string? InheritAgentId { get; set; } public string? IconUrl { get; set; } public string Instruction { get; set; } = default!; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 75b2d470..6eda94ed 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -28,6 +28,9 @@ public partial class MongoRepository case AgentField.Type: UpdateAgentType(agent.Id, agent.Type); break; + case AgentField.Mode: + UpdateAgentMode(agent.Id, agent.Mode); + break; case AgentField.InheritAgentId: UpdateAgentInheritAgentId(agent.Id, agent.InheritAgentId); break; @@ -136,6 +139,16 @@ public partial class MongoRepository _dc.Agents.UpdateOne(filter, update); } + private void UpdateAgentMode(string agentId, string mode) + { + var filter = Builders.Filter.Eq(x => x.Id, agentId); + var update = Builders.Update + .Set(x => x.Mode, mode) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + + _dc.Agents.UpdateOne(filter, update); + } + private void UpdateAgentInheritAgentId(string agentId, string? inheritAgentId) { var filter = Builders.Filter.Eq(x => x.Id, agentId); @@ -335,6 +348,7 @@ public partial class MongoRepository .Set(x => x.Disabled, agent.Disabled) .Set(x => x.MergeUtility, agent.MergeUtility) .Set(x => x.Type, agent.Type) + .Set(x => x.Mode, agent.Mode) .Set(x => x.MaxMessageCount, agent.MaxMessageCount) .Set(x => x.Profiles, agent.Profiles) .Set(x => x.Labels, agent.Labels) @@ -514,6 +528,7 @@ public partial class MongoRepository Samples = x.Samples ?? [], IsPublic = x.IsPublic, Type = x.Type, + Mode = x.Mode, InheritAgentId = x.InheritAgentId, Disabled = x.Disabled, MergeUtility = x.MergeUtility, @@ -611,6 +626,7 @@ public partial class MongoRepository Disabled = agentDoc.Disabled, MergeUtility = agentDoc.MergeUtility, Type = agentDoc.Type, + Mode = agentDoc.Mode, InheritAgentId = agentDoc.InheritAgentId, Profiles = agentDoc.Profiles ?? [], Labels = agentDoc.Labels ?? [], diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs index b37e09ea..ff59e470 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs @@ -169,10 +169,9 @@ public class TwilioInboundController : TwilioController var agentService = _services.GetRequiredService(); // Get agent from storage var agent = await agentService.GetAgent(request.AgentId); - // Enable lazy routing mode to optimize realtime experience - if (agent.Profiles.Contains("realtime") && agent.Type == AgentType.Routing) + if (agent.Type == AgentType.Routing) { - states.Add(new(StateConst.ROUTING_MODE, "lazy")); + states.Add(new(StateConst.ROUTING_MODE, agent.Mode)); } convService.SetConversationId(conversation.Id, states); convService.SaveStates(); From 77eeeab234e5a54eaedf115d99584ecbe19f6cef Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Sun, 20 Apr 2025 22:49:53 -0500 Subject: [PATCH 12/14] Twilio SpeechModel --- src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs | 4 ++-- src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs index a792f72d..220a0fc7 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs @@ -66,7 +66,7 @@ public class TwilioService }, Action = new Uri($"{_settings.CallbackHost}/{conversationalVoiceResponse.CallbackPath}"), Enhanced = true, - SpeechModel = Gather.SpeechModelEnum.PhoneCall, + SpeechModel = _settings.SpeechModel, SpeechTimeout = "auto", // timeout > 0 ? timeout.ToString() : "3", Timeout = conversationalVoiceResponse.Timeout > 0 ? conversationalVoiceResponse.Timeout : 3, ActionOnEmptyResult = conversationalVoiceResponse.ActionOnEmptyResult, @@ -106,7 +106,7 @@ public class TwilioService }, Action = new Uri($"{_settings.CallbackHost}/{voiceResponse.CallbackPath}"), Enhanced = true, - SpeechModel = Gather.SpeechModelEnum.PhoneCall, + SpeechModel = _settings.SpeechModel, SpeechTimeout = "auto", // conversationalVoiceResponse.Timeout > 0 ? conversationalVoiceResponse.Timeout.ToString() : "3", Timeout = voiceResponse.Timeout > 0 ? voiceResponse.Timeout : 3, ActionOnEmptyResult = voiceResponse.ActionOnEmptyResult, diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs b/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs index a7cf6f9e..ddb86121 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs @@ -14,6 +14,7 @@ public class TwilioSetting public string ApiSecret { get; set; } public string CallbackHost { get; set; } + public string SpeechModel { get; set; } = "googlev2_telephony"; public string? MessagingShortCode { get; set; } /// From 1fc3e1b9d06a705ed06e0f1601a8a722457bceb8 Mon Sep 17 00:00:00 2001 From: "nick.yi" Date: Mon, 21 Apr 2025 20:05:36 +0800 Subject: [PATCH 13/14] optimize naming, ConfigureAwait --- src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs | 2 +- src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs | 2 +- .../BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs | 2 +- src/Infrastructure/BotSharp.Core/Infrastructures/Utilities.cs | 2 +- .../Providers/Realtime/Session/RealtimeChatSession.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs index 5c8ad126..770acd01 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs @@ -19,7 +19,7 @@ public abstract class AgentHookBase : IAgentHook _settings = settings; } - public void SetAget(Agent agent) + public void SetAgent(Agent agent) { _agent = agent; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs index c37688ed..ae6a4eaf 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs @@ -9,7 +9,7 @@ public interface IAgentHook /// string SelfId { get; } Agent Agent { get; } - void SetAget(Agent agent); + void SetAgent(Agent agent); /// /// Triggered when agent is loading. diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index 12c2637e..636bc811 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -50,7 +50,7 @@ public partial class AgentService continue; } - hook.SetAget(agent); + hook.SetAgent(agent); if (!string.IsNullOrEmpty(agent.Instruction)) { diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Utilities.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Utilities.cs index 6293c1f6..64309f05 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/Utilities.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Utilities.cs @@ -49,7 +49,7 @@ public static class Utilities { // Clear whole cache. var sharpCache = new SharpCacheAttribute(0); - sharpCache.ClearCacheAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + sharpCache.ClearCacheAsync().GetAwaiter().GetResult(); } public static string HideMiddleDigits(string input, bool isEmail = false) diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/Session/RealtimeChatSession.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/Session/RealtimeChatSession.cs index 855dd31a..827b1c98 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/Session/RealtimeChatSession.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/Session/RealtimeChatSession.cs @@ -76,7 +76,7 @@ public class RealtimeChatSession : IDisposable return; } - await _clientEventSemaphore.WaitAsync().ConfigureAwait(false); + await _clientEventSemaphore.WaitAsync(); try { From 0af3a8c01dd0e0c56493db6b23da797958e18e5a Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Mon, 21 Apr 2025 09:25:13 -0500 Subject: [PATCH 14/14] Allow settings to disable audio generation. --- .../Controllers/TwilioVoiceController.cs | 12 ++++++++++-- .../Models/ConversationalVoiceResponse.cs | 6 +----- .../Services/TwilioMessageQueueService.cs | 10 ++++++++-- .../BotSharp.Plugin.Twilio/Services/TwilioService.cs | 10 ++++++++-- .../BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs | 11 +++++++---- 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs index 4f8b7806..1017c0be 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs @@ -215,7 +215,7 @@ public class TwilioVoiceController : TwilioController var reply = await sessionManager.GetAssistantReplyAsync(request.ConversationId, request.SeqNum); VoiceResponse response; - if (request.AIResponseWaitTime > 5) + if (request.AIResponseWaitTime > 10) { // Wait AI Response Timeout await HookEmitter.Emit(_services, async hook => @@ -256,12 +256,20 @@ public class TwilioVoiceController : TwilioController { AgentId = request.AgentId, ConversationId = request.ConversationId, - SpeechPaths = [$"twilio/voice/speeches/{request.ConversationId}/{reply.SpeechFileName}"], CallbackPath = $"twilio/voice/receive/{nextSeqNum}?agent-id={request.AgentId}&conversation-id={request.ConversationId}&{twilio.GenerateStatesParameter(request.States)}", ActionOnEmptyResult = true, Hints = reply.Hints }; + if (!string.IsNullOrEmpty(reply.SpeechFileName)) + { + instruction.SpeechPaths = [$"twilio/voice/speeches/{request.ConversationId}/{reply.SpeechFileName}"]; + } + else + { + instruction.Text = reply.Content; + } + await HookEmitter.Emit(_services, async hook => { await hook.OnAgentResponsing(request, instruction); diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Models/ConversationalVoiceResponse.cs b/src/Plugins/BotSharp.Plugin.Twilio/Models/ConversationalVoiceResponse.cs index 12094fad..06f49dac 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Models/ConversationalVoiceResponse.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Models/ConversationalVoiceResponse.cs @@ -5,14 +5,10 @@ public class ConversationalVoiceResponse public string AgentId { get; set; } = null!; public string ConversationId { get; set; } = null!; public List SpeechPaths { get; set; } = []; + public string? Text { get; set; } public string CallbackPath { get; set; } public bool ActionOnEmptyResult { get; set; } - /// - /// Timeout in seconds - /// - public int Timeout { get; set; } = 3; - public string Hints { get; set; } /// diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs index 35bc3f12..fb23155e 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs @@ -25,7 +25,7 @@ public class TwilioMessageQueueService : BackgroundService { _queue = queue; _serviceProvider = serviceProvider; - _throttler = new SemaphoreSlim(10, 10); + _throttler = new SemaphoreSlim(20, 20); _logger = logger; } @@ -103,7 +103,13 @@ public class TwilioMessageQueueService : BackgroundService }; } ); - reply.SpeechFileName = await GetReplySpeechFileName(message.ConversationId, reply, sp); + + var settings = sp.GetRequiredService(); + if (settings.GenerateReplyAudio) + { + reply.SpeechFileName = await GetReplySpeechFileName(message.ConversationId, reply, sp); + } + reply.Hints = GetHints(reply); await sessionManager.SetAssistantReplyAsync(message.ConversationId, message.SeqNumber, reply); } diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs index 220a0fc7..75649763 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs @@ -68,7 +68,7 @@ public class TwilioService Enhanced = true, SpeechModel = _settings.SpeechModel, SpeechTimeout = "auto", // timeout > 0 ? timeout.ToString() : "3", - Timeout = conversationalVoiceResponse.Timeout > 0 ? conversationalVoiceResponse.Timeout : 3, + Timeout = Math.Max(_settings.GatherTimeout, 1), ActionOnEmptyResult = conversationalVoiceResponse.ActionOnEmptyResult, Hints = conversationalVoiceResponse.Hints }; @@ -80,6 +80,12 @@ public class TwilioService gather.Play(new Uri($"{_settings.CallbackHost}/{speechPath}")); } } + + if (!string.IsNullOrEmpty(conversationalVoiceResponse.Text)) + { + gather.Say(conversationalVoiceResponse.Text); + } + response.Append(gather); return response; } @@ -108,7 +114,7 @@ public class TwilioService Enhanced = true, SpeechModel = _settings.SpeechModel, SpeechTimeout = "auto", // conversationalVoiceResponse.Timeout > 0 ? conversationalVoiceResponse.Timeout.ToString() : "3", - Timeout = voiceResponse.Timeout > 0 ? voiceResponse.Timeout : 3, + Timeout = Math.Max(_settings.GatherTimeout, 1), ActionOnEmptyResult = voiceResponse.ActionOnEmptyResult, }; response.Append(gather); diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs b/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs index ddb86121..dac1e9dc 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs @@ -7,12 +7,11 @@ public class TwilioSetting /// public string? PhoneNumber { get; set; } - public string AccountSID { get; set; } - public string AuthToken { get; set; } + public string AccountSID { get; set; } = null!; public string AppSID { get; set; } public string ApiKeySID { get; set; } public string ApiSecret { get; set; } - public string CallbackHost { get; set; } + public string CallbackHost { get; set; } = null!; public string SpeechModel { get; set; } = "googlev2_telephony"; public string? MessagingShortCode { get; set; } @@ -22,11 +21,15 @@ public class TwilioSetting /// public string? CsrAgentNumber { get; set; } - public int MaxGatherAttempts { get; set; } = 4; + public int MaxGatherAttempts { get; set; } = 10; + + public int GatherTimeout { get; set; } = 1; public string? MachineDetection { get; set; } public int MachineDetectionSilenceTimeout { get; set; } = 2500; public bool RecordingEnabled { get; set; } = false; public bool TranscribeEnabled { get; set; } = false; + + public bool GenerateReplyAudio { get; set; } = true; }