From 610db23d2e441a0d88d23dd5a7b86bc560d670f2 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Tue, 7 Jan 2025 11:55:37 -0600 Subject: [PATCH 1/6] Fork conversation when making a new call. --- .../Functions/HandleOutboundPhoneCallFn.cs | 25 ++++++++++++++++--- .../Services/TwilioMessageQueueService.cs | 7 ++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs index a14564ba..c371bec2 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Files; using BotSharp.Abstraction.Options; +using BotSharp.Abstraction.Routing; using BotSharp.Core.Infrastructures; using BotSharp.Plugin.Twilio.Interfaces; using BotSharp.Plugin.Twilio.Models; @@ -49,10 +50,28 @@ namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Functions return false; } + var convService = _services.GetRequiredService(); + var convStorage = _services.GetRequiredService(); + var routing = _services.GetRequiredService(); var fileStorage = _services.GetRequiredService(); var sessionManager = _services.GetRequiredService(); - var convService = _services.GetRequiredService(); - var conversationId = convService.ConversationId; + + // Fork conversation + var entryAgentId = routing.EntryAgentId; + var newConv = await convService.NewConversation(new Abstraction.Conversations.Models.Conversation + { + AgentId = entryAgentId, + Channel = ConversationChannel.Phone + }); + var conversationId = newConv.Id; + convStorage.Append(conversationId, new RoleDialogModel(AgentRole.User, "Hi, I'm calling to check my work order quote status, please help me locate my work order number and let me know what to do next.") + { + CurrentAgentId = entryAgentId + }); + convStorage.Append(conversationId, new RoleDialogModel(AgentRole.Assistant, args.InitialMessage) + { + CurrentAgentId = entryAgentId + }); // Generate audio var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1"); @@ -72,7 +91,7 @@ namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Functions to: new PhoneNumber(args.PhoneNumber), from: new PhoneNumber(_twilioSetting.PhoneNumber)); - message.Content = $"The generated phone message: {args.InitialMessage}" ?? message.Content; + message.Content = $"The generated phone message: {args.InitialMessage}. \r\n[Conversation ID: {conversationId}]" ?? message.Content; message.StopCompletion = true; return true; } diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs index ac1da435..0d843602 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs @@ -75,8 +75,11 @@ namespace BotSharp.Plugin.Twilio.Services var progressService = sp.GetRequiredService(); InitProgressService(message, sessionManager, progressService); InitConversation(message, inputMsg, conv, routing); - - var result = await conv.SendMessage(config.AgentId, + + var conversation = await conv.GetConversation(message.ConversationId); + var agentId = string.IsNullOrWhiteSpace(conversation.AgentId) ? config.AgentId : conversation.AgentId; + + var result = await conv.SendMessage(agentId, inputMsg, replyMessage: BuildPostbackMessageModel(conv, message), async msg => From a09eaffe9217b438e36c54c6e6212cfea7ee5457 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 8 Jan 2025 11:07:13 -0600 Subject: [PATCH 2/6] rename --- .../BotSharp.Abstraction/Agents/Models/AgentRule.cs | 5 ++++- .../BotSharp.OpenAPI/Controllers/AgentController.cs | 2 +- .../Models/AgentRuleMongoElement.cs | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs index 5c3a276e..70ae7fd5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs @@ -2,7 +2,10 @@ namespace BotSharp.Abstraction.Agents.Models; public class AgentRule { - public string Name { get; set; } + [JsonPropertyName("trigger_name")] + public string TriggerName { get; set; } + + [JsonPropertyName("disabled")] public bool Disabled { get; set; } [JsonPropertyName("event_name")] diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs index 6efb7104..c8e1e889 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs @@ -170,6 +170,6 @@ public class AgentController : ControllerBase { hook.AddRules(rules); } - return rules.Where(x => !string.IsNullOrWhiteSpace(x.Name)).OrderBy(x => x.Name).ToList(); + return rules.Where(x => !string.IsNullOrWhiteSpace(x.TriggerName)).OrderBy(x => x.TriggerName).ToList(); } } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs index 1b346768..0f178506 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs @@ -4,7 +4,7 @@ namespace BotSharp.Plugin.MongoStorage.Models; public class AgentRuleMongoElement { - public string Name { get; set; } + public string TriggerName { get; set; } public bool Disabled { get; set; } public string EventName { get; set; } public string EntityType { get; set; } @@ -13,7 +13,7 @@ public class AgentRuleMongoElement { return new AgentRuleMongoElement { - Name = rule.Name, + TriggerName = rule.TriggerName, Disabled = rule.Disabled, EventName = rule.EventName, EntityType = rule.EntityType @@ -24,7 +24,7 @@ public class AgentRuleMongoElement { return new AgentRule { - Name = rule.Name, + TriggerName = rule.TriggerName, Disabled = rule.Disabled, EventName = rule.EventName, EntityType = rule.EntityType From c5f5ebd7edbc2e1304404196c10fd1e884d528a3 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 8 Jan 2025 11:20:57 -0600 Subject: [PATCH 3/6] add criteria --- .../BotSharp.Abstraction/Agents/Models/AgentRule.cs | 3 +++ .../Models/AgentRuleMongoElement.cs | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs index 70ae7fd5..e18e21ef 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs @@ -13,4 +13,7 @@ public class AgentRule [JsonPropertyName("entity_type")] public string EntityType { get; set; } + + [JsonPropertyName("criteria")] + public string Criteria { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs index 0f178506..a8888749 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs @@ -8,6 +8,7 @@ public class AgentRuleMongoElement public bool Disabled { get; set; } public string EventName { get; set; } public string EntityType { get; set; } + public string Criteria { get; set; } public static AgentRuleMongoElement ToMongoElement(AgentRule rule) { @@ -16,7 +17,8 @@ public class AgentRuleMongoElement TriggerName = rule.TriggerName, Disabled = rule.Disabled, EventName = rule.EventName, - EntityType = rule.EntityType + EntityType = rule.EntityType, + Criteria = rule.Criteria }; } @@ -27,7 +29,8 @@ public class AgentRuleMongoElement TriggerName = rule.TriggerName, Disabled = rule.Disabled, EventName = rule.EventName, - EntityType = rule.EntityType + EntityType = rule.EntityType, + Criteria = rule.Criteria }; } } From 5d8fa5a398d3de14ff0c71c69edd86475503ecf7 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 8 Jan 2025 13:51:27 -0600 Subject: [PATCH 4/6] remove fields --- .../BotSharp.Abstraction/Agents/Models/AgentRule.cs | 6 ------ .../Models/AgentRuleMongoElement.cs | 6 ------ 2 files changed, 12 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs index e18e21ef..87a08a89 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs @@ -8,12 +8,6 @@ public class AgentRule [JsonPropertyName("disabled")] public bool Disabled { get; set; } - [JsonPropertyName("event_name")] - public string EventName { get; set; } - - [JsonPropertyName("entity_type")] - public string EntityType { get; set; } - [JsonPropertyName("criteria")] public string Criteria { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs index a8888749..744fab0c 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs @@ -6,8 +6,6 @@ public class AgentRuleMongoElement { public string TriggerName { get; set; } public bool Disabled { get; set; } - public string EventName { get; set; } - public string EntityType { get; set; } public string Criteria { get; set; } public static AgentRuleMongoElement ToMongoElement(AgentRule rule) @@ -16,8 +14,6 @@ public class AgentRuleMongoElement { TriggerName = rule.TriggerName, Disabled = rule.Disabled, - EventName = rule.EventName, - EntityType = rule.EntityType, Criteria = rule.Criteria }; } @@ -28,8 +24,6 @@ public class AgentRuleMongoElement { TriggerName = rule.TriggerName, Disabled = rule.Disabled, - EventName = rule.EventName, - EntityType = rule.EntityType, Criteria = rule.Criteria }; } From 15363f47657b733cf8f19d1579eb672f1776534a Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Wed, 8 Jan 2025 14:49:15 -0600 Subject: [PATCH 5/6] X-Twilio-BotSharp --- .../Enums/ConversationChannel.cs | 1 + .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 71 +++++++++++++++++-- .../Triggers/IRuleTrigger.cs | 3 +- .../agent.json | 2 +- .../templates/criteria_check.liquid | 2 +- .../Functions/HandleHttpRequestFn.cs | 2 +- .../Services/TwilioMessageQueueService.cs | 1 + 7 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs index d03a4208..f23ff4ba 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs @@ -8,4 +8,5 @@ public class ConversationChannel public const string Messenger = "messenger"; public const string Email = "email"; public const string Cron = "cron"; + public const string Database = "database"; } diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index c69d654b..5841d08a 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -1,26 +1,85 @@ +using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.Conversations.Enums; +using BotSharp.Abstraction.Models; +using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Routing; +using BotSharp.Abstraction.Utilities; using BotSharp.Core.Rules.Triggers; +using Microsoft.Extensions.Logging; +using System.Data; namespace BotSharp.Core.Rules.Engines; public class RuleEngine : IRuleEngine { private readonly IServiceProvider _services; - public RuleEngine(IServiceProvider services) + private readonly ILogger _logger; + + public RuleEngine(IServiceProvider services, ILogger logger) { _services = services; + _logger = logger; } public async Task Triggered(IRuleTrigger trigger, string data) { // Pull all user defined rules - + var agentService = _services.GetRequiredService(); + var agents = await agentService.GetAgents(new AgentFilter + { + Pager = new Pagination + { + Size = 1000 + } + }); + + var preFilteredAgents = agents.Items.Where(x => + x.Rules.Exists(r => r.TriggerName == trigger.Name && + !x.Disabled)).ToList(); + + // Trigger the agents var instructService = _services.GetRequiredService(); + var convService = _services.GetRequiredService(); - var userSay = $"===Input data===\r\n{data}\r\n\r\nWhen WO NTE is greater than 100, notify resident."; + foreach (var agent in preFilteredAgents) + { + var conv = await convService.NewConversation(new Conversation + { + AgentId = agent.Id + }); - var result = await instructService.Execute(BuiltInAgentId.RulesInterpreter, new RoleDialogModel(AgentRole.User, data), "criteria_check", "#TEMPLATE#"); + var message = new RoleDialogModel(AgentRole.User, data); - string[] rules = []; - // Check if meet the criteria + var states = new List + { + new("channel", ConversationChannel.Database), + new("channel_id", trigger.EntityId) + }; + convService.SetConversationId(conv.Id, states); + + await convService.SendMessage(agent.Id, + message, + null, + msg => Task.CompletedTask); + + /*foreach (var rule in agent.Rules) + { + var userSay = $"===Input data with Before and After values===\r\n{data}\r\n\r\n===Trigger Criteria===\r\n{rule.Criteria}\r\n\r\nJust output 1 or 0 without explanation: "; + + var result = await instructService.Execute(BuiltInAgentId.RulesInterpreter, new RoleDialogModel(AgentRole.User, userSay), "criteria_check", "#TEMPLATE#"); + + // Check if meet the criteria + if (result.Text == "1") + { + // Hit rule + _logger.LogInformation($"Hit rule {rule.TriggerName} {rule.EntityType} {rule.EventName}, {data}"); + + await convService.SendMessage(agent.Id, + new RoleDialogModel(AgentRole.User, $"The conversation was triggered by {rule.Criteria}"), + null, + msg => Task.CompletedTask); + } + }*/ + } } } diff --git a/src/Infrastructure/BotSharp.Core.Rules/Triggers/IRuleTrigger.cs b/src/Infrastructure/BotSharp.Core.Rules/Triggers/IRuleTrigger.cs index f4925bfe..dbf88331 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Triggers/IRuleTrigger.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Triggers/IRuleTrigger.cs @@ -4,7 +4,8 @@ public interface IRuleTrigger { string Channel => throw new NotImplementedException("Please set the channel of trigger"); - string EventName { get; set; } + string Name { get; set; } + string EntityType { get; set; } string EntityId { get; set; } diff --git a/src/Infrastructure/BotSharp.Core.Rules/data/agents/201e49a2-40b3-4ccd-b8cc-2476565a1b40/agent.json b/src/Infrastructure/BotSharp.Core.Rules/data/agents/201e49a2-40b3-4ccd-b8cc-2476565a1b40/agent.json index a58a2ae8..f685413f 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/data/agents/201e49a2-40b3-4ccd-b8cc-2476565a1b40/agent.json +++ b/src/Infrastructure/BotSharp.Core.Rules/data/agents/201e49a2-40b3-4ccd-b8cc-2476565a1b40/agent.json @@ -1,7 +1,7 @@ { "id": "201e49a2-40b3-4ccd-b8cc-2476565a1b40", "name": "Rules Agent", - "description": "Utility assistant that can be used to complete many different tasks", + "description": "Rules understands and converts user-defined rules into Triggers, Criterias and Actions", "type": "static", "createdDateTime": "2024-12-30T00:00:00Z", "updatedDateTime": "2024-12-30T00:00:00Z", diff --git a/src/Infrastructure/BotSharp.Core.Rules/data/agents/201e49a2-40b3-4ccd-b8cc-2476565a1b40/templates/criteria_check.liquid b/src/Infrastructure/BotSharp.Core.Rules/data/agents/201e49a2-40b3-4ccd-b8cc-2476565a1b40/templates/criteria_check.liquid index 3052e417..834e901d 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/data/agents/201e49a2-40b3-4ccd-b8cc-2476565a1b40/templates/criteria_check.liquid +++ b/src/Infrastructure/BotSharp.Core.Rules/data/agents/201e49a2-40b3-4ccd-b8cc-2476565a1b40/templates/criteria_check.liquid @@ -1,4 +1,4 @@ You are a rule interpreter, analyze and respond according to the following steps: 1. Understand the rules customized by the user; 2. Determine whether the input data meets the user's action execution conditions; -3. If it meets the conditions, output "true", otherwise output "false" \ No newline at end of file +3. If it meets the conditions, output number "1", otherwise output "0" \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.HttpHandler/Functions/HandleHttpRequestFn.cs b/src/Plugins/BotSharp.Plugin.HttpHandler/Functions/HandleHttpRequestFn.cs index 52f5075b..607ae5a9 100644 --- a/src/Plugins/BotSharp.Plugin.HttpHandler/Functions/HandleHttpRequestFn.cs +++ b/src/Plugins/BotSharp.Plugin.HttpHandler/Functions/HandleHttpRequestFn.cs @@ -9,7 +9,7 @@ namespace BotSharp.Plugin.HttpHandler.Functions; public class HandleHttpRequestFn : IFunctionCallback { public string Name => "util-http-handle_http_request"; - public string Indication => "Handling http request"; + public string Indication => "Give me a second, I'm taking care of it!"; private readonly IServiceProvider _services; private readonly ILogger _logger; diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs index 0d843602..0260320f 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs @@ -65,6 +65,7 @@ namespace BotSharp.Plugin.Twilio.Services var httpContext = sp.GetRequiredService(); httpContext.HttpContext = new DefaultHttpContext(); httpContext.HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); + httpContext.HttpContext.Request.Headers["X-Twilio-BotSharp"] = "LOST"; AssistantMessage reply = null; var inputMsg = new RoleDialogModel(AgentRole.User, message.Content); From c69d750dc50bb1bbd5d7ce8964b422a902b38e4f Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 8 Jan 2025 16:05:29 -0600 Subject: [PATCH 6/6] refine agent load --- .../Controllers/TranslationController.cs | 9 +++++---- src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs index 7845e8cf..a4fa2b50 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Options; +using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Translation; using BotSharp.OpenAPI.ViewModels.Translations; @@ -21,8 +22,8 @@ public class TranslationController : ControllerBase [HttpPost("/translate")] public async Task Translate([FromBody] TranslationRequestModel model) { - var agentService = _services.GetRequiredService(); - var agent = await agentService.LoadAgent(BuiltInAgentId.AIAssistant); + var db = _services.GetRequiredService(); + var agent = db.GetAgent(BuiltInAgentId.AIAssistant); var translator = _services.GetRequiredService(); var states = _services.GetRequiredService(); states.SetState("max_tokens", "8192"); @@ -36,8 +37,8 @@ public class TranslationController : ControllerBase [HttpPost("/translate/long-text")] public async Task SendMessageSse([FromBody] TranslationLongTextRequestModel model) { - var agentService = _services.GetRequiredService(); - var agent = await agentService.LoadAgent(BuiltInAgentId.AIAssistant); + var db = _services.GetRequiredService(); + var agent = db.GetAgent(BuiltInAgentId.AIAssistant); var translator = _services.GetRequiredService(); Response.StatusCode = 200; diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs index abbe89dd..43bc839a 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs @@ -25,11 +25,11 @@ public class WelcomeHook : ConversationHookBase public override async Task OnUserAgentConnectedInitially(Conversation conversation) { - var agentService = _services.GetRequiredService(); - var agent = await agentService.LoadAgent(conversation.AgentId); + var db = _services.GetRequiredService(); + var agent = db.GetAgent(conversation.AgentId); // Check if the Welcome template exists. - var welcomeTemplate = agent.Templates?.FirstOrDefault(x => x.Name == ".welcome"); + var welcomeTemplate = agent?.Templates?.FirstOrDefault(x => x.Name == ".welcome"); if (welcomeTemplate != null) { // Render template