Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new email handler utility #536

Merged
merged 4 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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; }
}
Original file line number Diff line number Diff line change
@@ -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>
33 changes: 33 additions & 0 deletions src/Plugins/BotSharp.Plugin.EmailHandler/EmailHandlerPlugin.cs
Original file line number Diff line number Diff line change
@@ -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>();
}
}
}
13 changes: 13 additions & 0 deletions src/Plugins/BotSharp.Plugin.EmailHandler/Enums/Utility.cs
Original file line number Diff line number Diff line change
@@ -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";
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
63 changes: 63 additions & 0 deletions src/Plugins/BotSharp.Plugin.EmailHandler/Hooks/EmailHandlerHook.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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; }
}
}
19 changes: 19 additions & 0 deletions src/Plugins/BotSharp.Plugin.EmailHandler/Using.cs
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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" ]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Please call handle_email_request if user wants to send out an email.