Skip to content

Commit af5dd8c

Browse files
authored
Merge pull request #568 from visagang/features/vguruparan
Add new email reader utility to read emails using IMAP.
2 parents 8396e11 + 0bb98a1 commit af5dd8c

18 files changed

+517
-21
lines changed

src/Plugins/BotSharp.Plugin.EmailHandler/BotSharp.Plugin.EmailHandler.csproj

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,29 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14-
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\handle_email_request.json" />
15-
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\handle_email_request.fn.liquid" />
14+
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\handle_email_reader.json" />
15+
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\handle_email_sender.json" />
16+
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\handle_email_sender.fn.liquid" />
17+
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\handle_email_reader.fn.liquid" />
1618
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\select_attachment_prompt.liquid" />
1719
</ItemGroup>
1820

1921
<ItemGroup>
20-
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\handle_email_request.json">
22+
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\handle_email_reader.json">
2123
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2224
</Content>
23-
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\handle_email_request.fn.liquid">
25+
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\handle_email_sender.json">
26+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
27+
</Content>
28+
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\handle_email_sender.fn.liquid">
2429
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2530
</Content>
2631
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\select_attachment_prompt.liquid">
2732
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2833
</Content>
34+
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\handle_email_reader.fn.liquid">
35+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
36+
</Content>
2937
</ItemGroup>
3038

3139
<ItemGroup>

src/Plugins/BotSharp.Plugin.EmailHandler/EmailHandlerPlugin.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using BotSharp.Abstraction.Agents;
22
using BotSharp.Abstraction.Settings;
3+
using BotSharp.Plugin.EmailHandler.Providers;
34
using Microsoft.Extensions.Configuration;
45

56
namespace BotSharp.Plugin.EmailHandler
@@ -16,11 +17,22 @@ public void RegisterDI(IServiceCollection services, IConfiguration config)
1617
services.AddScoped(provider =>
1718
{
1819
var settingService = provider.GetRequiredService<ISettingService>();
19-
return settingService.Bind<EmailHandlerSettings>("EmailHandler");
20+
return settingService.Bind<EmailSenderSettings>("EmailSender");
2021
});
2122

22-
services.AddScoped<IAgentHook, EmailHandlerHook>();
23+
services.AddScoped<IAgentHook, EmailSenderHook>();
24+
services.AddScoped<IAgentHook, EmailReaderHook>();
2325
services.AddScoped<IAgentUtilityHook, EmailHandlerUtilityHook>();
26+
27+
var emailReaderSettings = new EmailReaderSettings();
28+
config.Bind("EmailReader", emailReaderSettings);
29+
services.AddScoped(provider =>
30+
{
31+
var settingService = provider.GetRequiredService<ISettingService>();
32+
return settingService.Bind<EmailReaderSettings>("EmailReader");
33+
});
34+
services.AddSingleton(provider => emailReaderSettings);
35+
services.AddScoped<IEmailReader, DefaultEmailReader>();
2436
}
2537
}
2638
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
using BotSharp.Abstraction.Agents.Enums;
2+
using BotSharp.Abstraction.Files;
3+
using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
4+
using BotSharp.Abstraction.MLTasks;
5+
using BotSharp.Core.Infrastructures;
6+
using BotSharp.Plugin.EmailHandler.Models;
7+
using BotSharp.Plugin.EmailHandler.Providers;
8+
using MailKit;
9+
using MailKit.Net.Imap;
10+
using MailKit.Search;
11+
using MailKit.Security;
12+
using Microsoft.AspNetCore.Http;
13+
using Microsoft.Extensions.Logging;
14+
using MimeKit;
15+
16+
namespace BotSharp.Plugin.EmailReader.Functions;
17+
18+
public class HandleEmailReaderFn : IFunctionCallback
19+
{
20+
public string Name => "handle_email_reader";
21+
public readonly static string PROMPT_SUMMARY = "Provide a text summary of the following content.";
22+
public readonly static string RICH_CONTENT_SUMMARIZE = "is_email_summarize: true. messageId";
23+
public readonly static string RICH_CONTENT_READ_EMAIL = "Read the email by messageId";
24+
public readonly static string RICH_CONTENT_MARK_READ = "mark_as_read: true. messageId";
25+
public string Indication => "Handling email read";
26+
private readonly IServiceProvider _services;
27+
private readonly ILogger<HandleEmailReaderFn> _logger;
28+
private readonly IHttpContextAccessor _context;
29+
private readonly BotSharpOptions _options;
30+
private readonly EmailReaderSettings _emailSettings;
31+
private readonly IConversationStateService _state;
32+
private readonly IEmailReader _emailProvider;
33+
34+
public HandleEmailReaderFn(IServiceProvider services,
35+
ILogger<HandleEmailReaderFn> logger,
36+
IHttpContextAccessor context,
37+
BotSharpOptions options,
38+
EmailReaderSettings emailPluginSettings,
39+
IConversationStateService state,
40+
IEmailReader emailProvider)
41+
{
42+
_services = services;
43+
_logger = logger;
44+
_context = context;
45+
_options = options;
46+
_emailSettings = emailPluginSettings;
47+
_state = state;
48+
_emailProvider = emailProvider;
49+
}
50+
public async Task<bool> Execute(RoleDialogModel message)
51+
{
52+
var args = JsonSerializer.Deserialize<LlmContextReader>(message.FunctionArgs, _options.JsonSerializerOptions);
53+
var isMarkRead = args?.IsMarkRead ?? false;
54+
var isSummarize = args?.IsSummarize ?? false;
55+
var messageId = args?.MessageId;
56+
try
57+
{
58+
if (!string.IsNullOrEmpty(messageId))
59+
{
60+
if (isMarkRead)
61+
{
62+
await _emailProvider.MarkEmailAsReadById(messageId);
63+
message.Content = $"The email message has been marked as read.";
64+
return true;
65+
}
66+
var emailMessage = await _emailProvider.GetEmailById(messageId);
67+
if (isSummarize)
68+
{
69+
var prompt = $"{PROMPT_SUMMARY} The content was sent by {emailMessage.From.ToString()}. Details: {emailMessage.TextBody}";
70+
var agent = new Agent
71+
{
72+
Id = BuiltInAgentId.UtilityAssistant,
73+
Name = "Utility Assistant",
74+
Instruction = prompt
75+
};
76+
77+
var llmProviderService = _services.GetRequiredService<ILlmProviderService>();
78+
var provider = llmProviderService.GetProviders().FirstOrDefault(x => x == "openai");
79+
var model = llmProviderService.GetProviderModel(provider: provider, id: "gpt-4");
80+
var completion = CompletionProvider.GetChatCompletion(_services, provider: provider, model: model.Name);
81+
var convService = _services.GetService<IConversationService>();
82+
var conversationId = convService.ConversationId;
83+
var dialogs = convService.GetDialogHistory(fromBreakpoint: false);
84+
var response = await completion.GetChatCompletions(agent, dialogs);
85+
var content = response?.Content ?? string.Empty;
86+
message.Content = content;
87+
message.RichContent = BuildRichContentForSummary(_state.GetConversationId(), message.Content);
88+
return true;
89+
}
90+
UniqueId.TryParse(messageId, out UniqueId uid);
91+
message.RichContent = BuildRichContentForEmail(emailMessage, uid.ToString());
92+
return true;
93+
}
94+
var emails = await _emailProvider.GetUnreadEmails();
95+
message.Content = "Please choose which one to read for you.";
96+
message.RichContent = BuildRichContentForSubject(emails.OrderByDescending(x => x.CreateDate).ToList());
97+
return true;
98+
}
99+
catch (Exception ex)
100+
{
101+
var msg = $"Failed to read the emails. {ex.Message}";
102+
_logger.LogError($"{msg}\n(Error: {ex.Message})");
103+
message.Content = msg;
104+
return false;
105+
}
106+
}
107+
public RichContent<IRichMessage> BuildRichContentForSummary(string conversationId, string content, string editorType = EditorTypeEnum.Text)
108+
{
109+
return new RichContent<IRichMessage>()
110+
{
111+
FillPostback = true,
112+
Editor = editorType,
113+
Recipient = new Recipient() { Id = conversationId },
114+
Message = new ButtonTemplateMessage()
115+
{
116+
Text = content,
117+
}
118+
};
119+
}
120+
private RichContent<IRichMessage> BuildRichContentForSubject(List<EmailModel> emailSubjects)
121+
{
122+
var text = "Please let me know which message I need to read?";
123+
124+
return new RichContent<IRichMessage>
125+
{
126+
FillPostback = true,
127+
Editor = EditorTypeEnum.None,
128+
Recipient = new Recipient
129+
{
130+
Id = _state.GetConversationId()
131+
},
132+
Message = new GenericTemplateMessage<EmailSubjectElement>
133+
{
134+
Text = text,
135+
Elements = GetElements(emailSubjects)
136+
}
137+
};
138+
}
139+
private RichContent<IRichMessage> BuildRichContentForEmail(EmailModel email, string uid)
140+
{
141+
var text = "The email details are given below. \n";
142+
143+
return new RichContent<IRichMessage>
144+
{
145+
FillPostback = true,
146+
Editor = EditorTypeEnum.None,
147+
Recipient = new Recipient
148+
{
149+
Id = _state.GetConversationId()
150+
},
151+
Message = new GenericTemplateMessage<EmailSubjectElement>
152+
{
153+
Text = $"{text}<b>From</b>: {email.From.ToString()}\n<b>Subject</b>: {email.Subject}\n{email.Body}",
154+
Elements = GetElements(uid)
155+
}
156+
};
157+
}
158+
private static List<EmailSubjectElement> GetElements(string uid)
159+
{
160+
var element = new EmailSubjectElement()
161+
{
162+
Buttons = new ElementButton[]
163+
{
164+
BuildMarkReadElementButton(uid)
165+
}
166+
};
167+
return new List<EmailSubjectElement>() { element };
168+
}
169+
private static List<EmailSubjectElement> GetElements(List<EmailModel> emails)
170+
{
171+
var elements = emails.Select(e => new EmailSubjectElement
172+
{
173+
Title = $"Subject: {e.Subject}",
174+
Subtitle = $"From: {e.From}<br/> Date: {e.CreateDate}",
175+
Buttons = BuildElementButton(e)
176+
}).ToList();
177+
return elements;
178+
}
179+
private static ElementButton[] BuildElementButton(EmailModel email)
180+
{
181+
var elements = new List<ElementButton>() { };
182+
elements.Add(new ElementButton
183+
{
184+
Title = "Read",
185+
Payload = $"{RICH_CONTENT_READ_EMAIL}: {email.UId}.",
186+
Type = "text",
187+
});
188+
elements.Add(new ElementButton
189+
{
190+
Title = "Summarize",
191+
Payload = $"{RICH_CONTENT_SUMMARIZE}: {email.UId}.",
192+
Type = "text",
193+
});
194+
return elements.ToArray();
195+
}
196+
private static ElementButton BuildMarkReadElementButton(string uId)
197+
{
198+
return new ElementButton
199+
{
200+
Title = "Mark as read",
201+
Payload = $"{RICH_CONTENT_MARK_READ}: {uId}.",
202+
Type = "text",
203+
};
204+
}
205+
}

src/Plugins/BotSharp.Plugin.EmailHandler/Functions/HandleEmailRequestFn.cs renamed to src/Plugins/BotSharp.Plugin.EmailHandler/Functions/HandleEmailSenderFn.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,25 @@
55

66
namespace BotSharp.Plugin.EmailHandler.Functions;
77

8-
public class HandleEmailRequestFn : IFunctionCallback
8+
public class HandleEmailSenderFn : IFunctionCallback
99
{
10-
public string Name => "handle_email_request";
11-
public string Indication => "Handling email request";
10+
public string Name => "handle_email_sender";
11+
public string Indication => "Handling email send request";
1212

1313
private readonly IServiceProvider _services;
14-
private readonly ILogger<HandleEmailRequestFn> _logger;
14+
private readonly ILogger<HandleEmailSenderFn> _logger;
1515
private readonly IHttpClientFactory _httpClientFactory;
1616
private readonly IHttpContextAccessor _context;
1717
private readonly BotSharpOptions _options;
18-
private readonly EmailHandlerSettings _emailSettings;
18+
private readonly EmailSenderSettings _emailSettings;
1919

20-
public HandleEmailRequestFn(
20+
public HandleEmailSenderFn(
2121
IServiceProvider services,
22-
ILogger<HandleEmailRequestFn> logger,
22+
ILogger<HandleEmailSenderFn> logger,
2323
IHttpClientFactory httpClientFactory,
2424
IHttpContextAccessor context,
2525
BotSharpOptions options,
26-
EmailHandlerSettings emailPluginSettings)
26+
EmailSenderSettings emailPluginSettings)
2727
{
2828
_services = services;
2929
_logger = logger;

src/Plugins/BotSharp.Plugin.EmailHandler/Hooks/EmailHandlerHook.cs renamed to src/Plugins/BotSharp.Plugin.EmailHandler/Hooks/EmailReaderHook.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212

1313
namespace BotSharp.Plugin.EmailHandler.Hooks;
1414

15-
public class EmailHandlerHook : AgentHookBase
15+
public class EmailReaderHook : AgentHookBase
1616
{
17-
private static string FUNCTION_NAME = "handle_email_request";
17+
private static string FUNCTION_NAME = "handle_email_reader";
1818

1919
public override string SelfId => string.Empty;
2020

21-
public EmailHandlerHook(IServiceProvider services, AgentSettings settings)
21+
public EmailReaderHook(IServiceProvider services, AgentSettings settings)
2222
: base(services, settings)
2323
{
2424
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using BotSharp.Abstraction.Agents;
2+
using BotSharp.Abstraction.Agents.Enums;
3+
using BotSharp.Abstraction.Agents.Settings;
4+
using BotSharp.Abstraction.Functions.Models;
5+
using BotSharp.Abstraction.Repositories;
6+
using BotSharp.Plugin.EmailHandler.Enums;
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using System.Text;
11+
using System.Threading.Tasks;
12+
13+
namespace BotSharp.Plugin.EmailHandler.Hooks;
14+
15+
public class EmailSenderHook : AgentHookBase
16+
{
17+
private static string FUNCTION_NAME = "handle_email_sender";
18+
19+
public override string SelfId => string.Empty;
20+
21+
public EmailSenderHook(IServiceProvider services, AgentSettings settings)
22+
: base(services, settings)
23+
{
24+
}
25+
public override void OnAgentLoaded(Agent agent)
26+
{
27+
var conv = _services.GetRequiredService<IConversationService>();
28+
var isConvMode = conv.IsConversationMode();
29+
var isEnabled = !agent.Utilities.IsNullOrEmpty() && agent.Utilities.Contains(UtilityName.EmailHandler);
30+
31+
if (isConvMode && isEnabled)
32+
{
33+
var (prompt, fn) = GetPromptAndFunction();
34+
if (fn != null)
35+
{
36+
if (!string.IsNullOrWhiteSpace(prompt))
37+
{
38+
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
39+
}
40+
41+
if (agent.Functions == null)
42+
{
43+
agent.Functions = new List<FunctionDef> { fn };
44+
}
45+
else
46+
{
47+
agent.Functions.Add(fn);
48+
}
49+
}
50+
}
51+
52+
base.OnAgentLoaded(agent);
53+
}
54+
55+
private (string, FunctionDef?) GetPromptAndFunction()
56+
{
57+
var db = _services.GetRequiredService<IBotSharpRepository>();
58+
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
59+
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{FUNCTION_NAME}.fn"))?.Content ?? string.Empty;
60+
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(FUNCTION_NAME));
61+
return (prompt, loadAttachmentFn);
62+
}
63+
}

0 commit comments

Comments
 (0)