Merge pull request #536 from visagang/features/vguruparan

Add new email handler utility
This commit is contained in:
C. Oceania 2024-07-11 11:41:32 -05:00 committed by GitHub
commit c27acab711
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 313 additions and 0 deletions

View file

@ -0,0 +1,11 @@
namespace BotSharp.Abstraction.Email.Settings;
public class EmailHandlerSettings
{
public string EmailAddress { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string SMTPServer { get; set; } = string.Empty;
public int SMTPPort { get; set; }
}

View file

@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>$(LangVersion)</LangVersion>
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
<OutputPath>$(SolutionDir)packages</OutputPath>
</PropertyGroup>
<ItemGroup>
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\handle_email_request.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\handle_email_request.fn.liquid" />
</ItemGroup>
<ItemGroup>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\handle_email_request.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\handle_email_request.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="MailKit" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,33 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Email.Settings;
using BotSharp.Abstraction.Settings;
using BotSharp.Plugin.EmailHandler.Hooks;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BotSharp.Plugin.EmailHandler
{
public class EmailHandlerPlugin : IBotSharpPlugin
{
public string Id => "a8e217de-e413-47a8-bbf1-af9207392a63";
public string Name => "Email Handler";
public string Description => "Empower agent to handle sending out emails";
public string IconUrl => "https://cdn-icons-png.freepik.com/512/6711/6711567.png";
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
services.AddScoped(provider =>
{
var settingService = provider.GetRequiredService<ISettingService>();
return settingService.Bind<EmailHandlerSettings>("EmailHandler");
});
services.AddScoped<IAgentHook, EmailHandlerHook>();
services.AddScoped<IAgentUtilityHook, EmailHandlerUtilityHook>();
}
}
}

View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BotSharp.Plugin.EmailHandler.Enums
{
public class Utility
{
public const string EmailHandler = "email-handler";
}
}

View file

@ -0,0 +1,78 @@
using BotSharp.Abstraction.Email.Settings;
using BotSharp.Plugin.EmailHandler.LlmContexts;
using MailKit;
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MimeKit;
using System.Net.Http;
namespace BotSharp.Plugin.EmailHandler.Functions;
public class HandleEmailRequestFn : IFunctionCallback
{
public string Name => "handle_email_request";
public string Indication => "Handling email request";
private readonly IServiceProvider _services;
private readonly ILogger<HandleEmailRequestFn> _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IHttpContextAccessor _context;
private readonly BotSharpOptions _options;
private readonly EmailHandlerSettings _emailSettings;
public HandleEmailRequestFn(IServiceProvider services,
ILogger<HandleEmailRequestFn> logger,
IHttpClientFactory httpClientFactory,
IHttpContextAccessor context,
BotSharpOptions options,
EmailHandlerSettings emailPluginSettings)
{
_services = services;
_logger = logger;
_httpClientFactory = httpClientFactory;
_context = context;
_options = options;
_emailSettings = emailPluginSettings;
}
public async Task<bool> Execute(RoleDialogModel message)
{
var args = JsonSerializer.Deserialize<LlmContextIn>(message.FunctionArgs, _options.JsonSerializerOptions);
var recipient = args?.ToAddress;
var body = args?.Content;
var subject = args?.Subject;
try
{
var mailMessage = new MimeMessage();
mailMessage.From.Add(new MailboxAddress(_emailSettings.Name, _emailSettings.EmailAddress));
mailMessage.To.Add(new MailboxAddress("", recipient));
mailMessage.Subject = subject;
mailMessage.Body = new TextPart("plain")
{
Text = body
};
var response = await HandleSendEmailBySMTP(mailMessage);
_logger.LogWarning($"Email successfully send over to {recipient}. Email Subject: {subject} [{response}]");
message.Content = response;
return true;
}
catch (Exception ex)
{
var msg = $"Failed to send the email. {ex.Message}";
_logger.LogError($"{msg}\n(Error: {ex.Message})");
message.Content = msg;
return false;
}
}
public async Task<string> HandleSendEmailBySMTP(MimeMessage mailMessage)
{
using var smtpClient = new SmtpClient();
await smtpClient.ConnectAsync(_emailSettings.SMTPServer, _emailSettings.SMTPPort, SecureSocketOptions.StartTls);
await smtpClient.AuthenticateAsync(_emailSettings.EmailAddress, _emailSettings.Password);
var response = await smtpClient.SendAsync(mailMessage);
return response;
}
}

View file

@ -0,0 +1,63 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Agents.Settings;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Repositories;
using BotSharp.Plugin.EmailHandler.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BotSharp.Plugin.EmailHandler.Hooks;
public class EmailHandlerHook : AgentHookBase
{
private static string FUNCTION_NAME = "handle_email_request";
public override string SelfId => string.Empty;
public EmailHandlerHook(IServiceProvider services, AgentSettings settings)
: base(services, settings)
{
}
public override void OnAgentLoaded(Agent agent)
{
var conv = _services.GetRequiredService<IConversationService>();
var isConvMode = conv.IsConversationMode();
var isEnabled = !agent.Utilities.IsNullOrEmpty() && agent.Utilities.Contains(Utility.EmailHandler);
if (isConvMode && isEnabled)
{
var (prompt, fn) = GetPromptAndFunction();
if (fn != null)
{
if (!string.IsNullOrWhiteSpace(prompt))
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
}
if (agent.Functions == null)
{
agent.Functions = new List<FunctionDef> { fn };
}
else
{
agent.Functions.Add(fn);
}
}
}
base.OnAgentLoaded(agent);
}
private (string, FunctionDef?) GetPromptAndFunction()
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{FUNCTION_NAME}.fn"))?.Content ?? string.Empty;
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(FUNCTION_NAME));
return (prompt, loadAttachmentFn);
}
}

View file

@ -0,0 +1,18 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Plugin.EmailHandler.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BotSharp.Plugin.EmailHandler.Hooks
{
public class EmailHandlerUtilityHook : IAgentUtilityHook
{
public void AddUtilities(List<string> utilities)
{
utilities.Add(Utility.EmailHandler);
}
}
}

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace BotSharp.Plugin.EmailHandler.LlmContexts
{
public class LlmContextIn
{
[JsonPropertyName("to_address")]
public string? ToAddress { get; set; }
[JsonPropertyName("email_content")]
public string? Content { get; set; }
[JsonPropertyName("subject")]
public string? Subject { get; set; }
}
}

View file

@ -0,0 +1,19 @@
global using System;
global using System.Collections.Generic;
global using System.Text;
global using BotSharp.Abstraction.Conversations;
global using BotSharp.Abstraction.Plugins;
global using System.Text.Json;
global using BotSharp.Abstraction.Conversations.Models;
global using System.Threading.Tasks;
global using BotSharp.Abstraction.Functions;
global using BotSharp.Abstraction.Agents.Models;
global using BotSharp.Abstraction.Templating;
global using Microsoft.Extensions.DependencyInjection;
global using System.Linq;
global using BotSharp.Abstraction.Utilities;
global using BotSharp.Abstraction.Messaging;
global using BotSharp.Abstraction.Messaging.Models.RichContent;
global using BotSharp.Abstraction.Options;
global using BotSharp.Abstraction.Http.Settings;
global using BotSharp.Abstraction.Messaging.Enums;

View file

@ -0,0 +1,22 @@
{
"name": "handle_email_request",
"description": "If the user requests to send an email, you need to capture the email content and the recipient email address. If the user explicitly enter email subject use the same if not intelligently capture the email subject from the content. Then call this function to send out email.",
"parameters": {
"type": "object",
"properties": {
"to_address": {
"type": "string",
"description": "The email address to which the email should be sent to. It needs to be a valid email address in the correct string format."
},
"email_content": {
"type": "string",
"description": "The content of the email which needs to be send over. It can be plain string or a raw html."
},
"subject": {
"type": "string",
"description": "The subject of the email which needs to be send over."
}
},
"required": [ "to_address", "email_content", "subject" ]
}
}

View file

@ -0,0 +1 @@
Please call handle_email_request if user wants to send out an email.