Merge branch 'SciSharp:master' into master
This commit is contained in:
commit
bad8fb1b0a
|
|
@ -6,4 +6,5 @@ public class BuiltInAgentId
|
|||
public const string Chatbot = "01e2fc5c-2c89-4ec7-8470-7688608b496c";
|
||||
public const string HumanSupport = "01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b";
|
||||
public const string UtilityAssistant = "6745151e-6d46-4a02-8de4-1c4f21c7da95";
|
||||
public const string Fallback = "01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ public partial class ConversationService
|
|||
// Enqueue receiving agent first in case it stop completion by OnMessageReceived
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
routing.Context.SetMessageId(_conversationId, message.MessageId);
|
||||
routing.Context.Push(agent.Id);
|
||||
routing.Context.Push(agent.Id, reason: "request started");
|
||||
|
||||
// Save payload
|
||||
if (replyMessage != null && !string.IsNullOrEmpty(replyMessage.Payload))
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ public partial class RouteToAgentFn : IFunctionCallback
|
|||
{
|
||||
// Stack redirection agent
|
||||
_context.Push(agentId, reason: $"REDIRECTION {reason}");
|
||||
message.Content = reason;
|
||||
message.Role = AgentRole.Function;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace BotSharp.Core.Routing.Handlers;
|
|||
/// <summary>
|
||||
/// Retrieve information from specific agent
|
||||
/// </summary>
|
||||
public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase//, IRoutingHandler
|
||||
{
|
||||
public string Name => "retrieve_data_from_agent";
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
message.FunctionArgs = JsonSerializer.Serialize(inst);
|
||||
if (message.FunctionName != null)
|
||||
{
|
||||
var ret = await routing.InvokeFunction(message.FunctionName, message);
|
||||
var msg = RoleDialogModel.From(message);
|
||||
var ret = await routing.InvokeFunction(message.FunctionName, msg);
|
||||
_dialogs.Add(msg);
|
||||
}
|
||||
|
||||
var agentId = routing.Context.GetCurrentAgentId();
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ public class HFPlanner : IPlaner
|
|||
}
|
||||
}
|
||||
|
||||
// Fix LLM malformed response
|
||||
PlannerHelper.FixMalformedResponse(_services, inst);
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +74,11 @@ public class HFPlanner : IPlaner
|
|||
var agent = db.GetAgents(filter).FirstOrDefault();
|
||||
|
||||
var context = _services.GetRequiredService<IRoutingContext>();
|
||||
context.Push(agent.Id);
|
||||
context.Push(agent.Id, reason: inst.NextActionReason);
|
||||
|
||||
// Set user content as Planner's question
|
||||
message.FunctionName = inst.Function;
|
||||
message.FunctionArgs = inst.Arguments == null ? "{}" : JsonSerializer.Serialize(inst.Arguments);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public class NaivePlanner : IPlaner
|
|||
}
|
||||
|
||||
// Fix LLM malformed response
|
||||
FixMalformedResponse(inst);
|
||||
PlannerHelper.FixMalformedResponse(_services, inst);
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
|
@ -117,73 +117,4 @@ public class NaivePlanner : IPlaner
|
|||
{ StateConst.EXPECTED_GOAL_AGENT, states.GetState(StateConst.EXPECTED_GOAL_AGENT) }
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sometimes LLM hallucinates and fails to set function names correctly.
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
private void FixMalformedResponse(FunctionCallFromLlm args)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agents = agentService.GetAgents(new AgentFilter
|
||||
{
|
||||
Type = AgentType.Task
|
||||
}).Result.Items.ToList();
|
||||
var malformed = false;
|
||||
|
||||
// Sometimes it populate malformed Function in Agent name
|
||||
if (!string.IsNullOrEmpty(args.Function) &&
|
||||
args.Function == args.AgentName)
|
||||
{
|
||||
args.Function = "route_to_agent";
|
||||
malformed = true;
|
||||
}
|
||||
|
||||
// Another case of malformed response
|
||||
if (string.IsNullOrEmpty(args.AgentName) &&
|
||||
agents.Select(x => x.Name).Contains(args.Function))
|
||||
{
|
||||
args.AgentName = args.Function;
|
||||
args.Function = "route_to_agent";
|
||||
malformed = true;
|
||||
}
|
||||
|
||||
// It should be Route to agent, but it is used as Response to user.
|
||||
if (!string.IsNullOrEmpty(args.AgentName) &&
|
||||
agents.Select(x => x.Name).Contains(args.AgentName) &&
|
||||
args.Function != "route_to_agent")
|
||||
{
|
||||
args.Function = "route_to_agent";
|
||||
malformed = true;
|
||||
}
|
||||
|
||||
// Function name shouldn't contain dot symbol
|
||||
if (!string.IsNullOrEmpty(args.Function) &&
|
||||
args.Function.Contains('.'))
|
||||
{
|
||||
args.Function = args.Function.Split('.').Last();
|
||||
malformed = true;
|
||||
}
|
||||
|
||||
// Agent Name is contaminated.
|
||||
if (args.Function == "route_to_agent")
|
||||
{
|
||||
// Action agent name
|
||||
if (!agents.Any(x => x.Name == args.AgentName) && !string.IsNullOrEmpty(args.AgentName))
|
||||
{
|
||||
args.AgentName = agents.FirstOrDefault(x => args.AgentName.Contains(x.Name))?.Name ?? args.AgentName;
|
||||
}
|
||||
|
||||
// Goal agent name
|
||||
if (!agents.Any(x => x.Name == args.OriginalAgent) && !string.IsNullOrEmpty(args.OriginalAgent))
|
||||
{
|
||||
args.OriginalAgent = agents.FirstOrDefault(x => args.OriginalAgent.Contains(x.Name))?.Name ?? args.OriginalAgent;
|
||||
}
|
||||
}
|
||||
|
||||
if (malformed)
|
||||
{
|
||||
_logger.LogWarning($"Captured LLM malformed response");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
namespace BotSharp.Core.Routing.Planning;
|
||||
|
||||
public static class PlannerHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Sometimes LLM hallucinates and fails to set function names correctly.
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
public static void FixMalformedResponse(IServiceProvider services, FunctionCallFromLlm args)
|
||||
{
|
||||
var agentService = services.GetRequiredService<IAgentService>();
|
||||
var agents = agentService.GetAgents(new AgentFilter
|
||||
{
|
||||
Type = AgentType.Task
|
||||
}).Result.Items.ToList();
|
||||
var malformed = false;
|
||||
|
||||
// Sometimes it populate malformed Function in Agent name
|
||||
if (!string.IsNullOrEmpty(args.Function) &&
|
||||
args.Function == args.AgentName)
|
||||
{
|
||||
args.Function = "route_to_agent";
|
||||
malformed = true;
|
||||
}
|
||||
|
||||
// Another case of malformed response
|
||||
if (string.IsNullOrEmpty(args.AgentName) &&
|
||||
agents.Select(x => x.Name).Contains(args.Function))
|
||||
{
|
||||
args.AgentName = args.Function;
|
||||
args.Function = "route_to_agent";
|
||||
malformed = true;
|
||||
}
|
||||
|
||||
// It should be Route to agent, but it is used as Response to user.
|
||||
if (!string.IsNullOrEmpty(args.AgentName) &&
|
||||
agents.Select(x => x.Name).Contains(args.AgentName) &&
|
||||
args.Function != "route_to_agent")
|
||||
{
|
||||
args.Function = "route_to_agent";
|
||||
malformed = true;
|
||||
}
|
||||
|
||||
// Function name shouldn't contain dot symbol
|
||||
if (!string.IsNullOrEmpty(args.Function) &&
|
||||
args.Function.Contains('.'))
|
||||
{
|
||||
args.Function = args.Function.Split('.').Last();
|
||||
malformed = true;
|
||||
}
|
||||
|
||||
// Agent Name is contaminated.
|
||||
if (args.Function == "route_to_agent")
|
||||
{
|
||||
// Action agent name
|
||||
if (!agents.Any(x => x.Name == args.AgentName) && !string.IsNullOrEmpty(args.AgentName))
|
||||
{
|
||||
args.AgentName = agents.FirstOrDefault(x => args.AgentName.Contains(x.Name))?.Name ?? args.AgentName;
|
||||
}
|
||||
|
||||
// Goal agent name
|
||||
if (!agents.Any(x => x.Name == args.OriginalAgent) && !string.IsNullOrEmpty(args.OriginalAgent))
|
||||
{
|
||||
args.OriginalAgent = agents.FirstOrDefault(x => args.OriginalAgent.Contains(x.Name))?.Name ?? args.OriginalAgent;
|
||||
}
|
||||
}
|
||||
|
||||
if (malformed)
|
||||
{
|
||||
Console.WriteLine($"Captured LLM malformed response");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
|
||||
|
|
@ -13,11 +12,12 @@ public partial class TwoStagePlanner
|
|||
var plan = new FirstStagePlan[0];
|
||||
|
||||
var llmProviderService = _services.GetRequiredService<ILlmProviderService>();
|
||||
var model = llmProviderService.GetProviderModel("azure-openai", "gpt-4");
|
||||
var provider = router.LlmConfig.Provider ?? "openai";
|
||||
var model = llmProviderService.GetProviderModel(provider, router.LlmConfig.Model ?? "gpt-4o");
|
||||
|
||||
// chat completion
|
||||
var completion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: "azure-openai",
|
||||
provider: provider,
|
||||
model: model.Name);
|
||||
|
||||
string text = string.Empty;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ public partial class RoutingService
|
|||
}
|
||||
|
||||
// Set result to original message
|
||||
message.Role = clonedMessage.Role;
|
||||
message.PostbackFunctionName = clonedMessage.PostbackFunctionName;
|
||||
message.CurrentAgentId = clonedMessage.CurrentAgentId;
|
||||
message.Content = clonedMessage.Content;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ global using BotSharp.Abstraction.Files.Models;
|
|||
global using BotSharp.Abstraction.Files.Enums;
|
||||
global using BotSharp.Abstraction.Translation.Attributes;
|
||||
global using BotSharp.Abstraction.Messaging.Enums;
|
||||
global using BotSharp.Abstraction.Http.Settings;
|
||||
global using BotSharp.Core.Repository;
|
||||
global using BotSharp.Core.Routing;
|
||||
global using BotSharp.Core.Agents.Services;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
You're {{router.name}} ({{router.description}}).
|
||||
You can understand messages sent by users in different languages.
|
||||
You can understand messages sent by users in different languages, and route the request to appropriate agent.
|
||||
Follow these steps to handle user request:
|
||||
1. Read the [CONVERSATION] content.
|
||||
2. Determine which agent is suitable to handle this conversation.
|
||||
3. For agent required arguments, think carefully, leave it as blank object if user didn't provide the specific arguments.
|
||||
2. Determine which agent is suitable to handle this conversation. Try to minimize the routing of human service.
|
||||
3. Extract and populate agent required arguments, think carefully, leave it as blank object if user didn't provide the specific arguments.
|
||||
4. You must include all required args for the selected agent, but you must not make up any parameters when there is no exact value provided, those parameters must set value as null if not declared.
|
||||
5. Response must be in JSON format.
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Break down the user’s most recent needs and figure out the next steps.
|
||||
Break down the user’s most recent needs and figure out the instruction of next step.
|
||||
|
|
@ -85,7 +85,7 @@ public class InstructModeController : ControllerBase
|
|||
try
|
||||
{
|
||||
var completion = CompletionProvider.GetChatCompletion(_services, provider: input.Provider ?? "openai",
|
||||
modelId: input.ModelId ?? "gpt-4", multiModal: true);
|
||||
model: input.Model ?? "gpt-4o", multiModal: true);
|
||||
var message = await completion.GetChatCompletions(new Agent()
|
||||
{
|
||||
Id = Guid.Empty.ToString(),
|
||||
|
|
|
|||
|
|
@ -193,7 +193,6 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected (string, IEnumerable<ChatMessage>, ChatCompletionOptions) PrepareOptions(Agent agent, List<RoleDialogModel> conversations)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
|
|
@ -257,40 +256,34 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
{
|
||||
var text = !string.IsNullOrWhiteSpace(message.Payload) ? message.Payload : message.Content;
|
||||
var textPart = ChatMessageContentPart.CreateTextMessageContentPart(text);
|
||||
var chat = new UserChatMessage(textPart)
|
||||
{
|
||||
ParticipantName = message.FunctionName
|
||||
};
|
||||
var contentParts = new List<ChatMessageContentPart> { textPart };
|
||||
|
||||
if (allowMultiModal)
|
||||
if (allowMultiModal && !message.Files.IsNullOrEmpty())
|
||||
{
|
||||
if (!message.Files.IsNullOrEmpty())
|
||||
foreach (var file in message.Files)
|
||||
{
|
||||
foreach (var file in message.Files)
|
||||
if (!string.IsNullOrEmpty(file.FileUrl))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(file.FileUrl))
|
||||
{
|
||||
var uri = new Uri(file.FileUrl);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(uri, ImageChatMessageContentPartDetail.Low);
|
||||
chat = new UserChatMessage(textPart, contentPart) { ParticipantName = message.FunctionName };
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileData))
|
||||
{
|
||||
var (contentType, bytes) = fileService.GetFileInfoFromData(file.FileData);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(BinaryData.FromBytes(bytes), contentType, ImageChatMessageContentPartDetail.Low);
|
||||
chat = new UserChatMessage(textPart, contentPart) { ParticipantName = message.FunctionName };
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
|
||||
{
|
||||
var contentType = fileService.GetFileContentType(file.FileStorageUrl);
|
||||
using var stream = File.OpenRead(file.FileStorageUrl);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(BinaryData.FromStream(stream), contentType, ImageChatMessageContentPartDetail.Low);
|
||||
chat = new UserChatMessage(textPart, contentPart) { ParticipantName = message.FunctionName };
|
||||
}
|
||||
var uri = new Uri(file.FileUrl);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(uri, ImageChatMessageContentPartDetail.Low);
|
||||
contentParts.Add(contentPart);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileData))
|
||||
{
|
||||
var (contentType, bytes) = fileService.GetFileInfoFromData(file.FileData);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(BinaryData.FromBytes(bytes), contentType, ImageChatMessageContentPartDetail.Low);
|
||||
contentParts.Add(contentPart);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
|
||||
{
|
||||
var contentType = fileService.GetFileContentType(file.FileStorageUrl);
|
||||
using var stream = File.OpenRead(file.FileStorageUrl);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(BinaryData.FromStream(stream), contentType, ImageChatMessageContentPartDetail.Low);
|
||||
contentParts.Add(contentPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
messages.Add(chat);
|
||||
messages.Add(new UserChatMessage(contentParts) { ParticipantName = message.FunctionName });
|
||||
}
|
||||
else if (message.Role == AgentRole.Assistant)
|
||||
{
|
||||
|
|
@ -302,7 +295,6 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
return (prompt, messages, options);
|
||||
}
|
||||
|
||||
|
||||
private string GetPrompt(IEnumerable<ChatMessage> messages, ChatCompletionOptions options)
|
||||
{
|
||||
var prompt = string.Empty;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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
src/Plugins/BotSharp.Plugin.EmailHandler/Enums/Utility.cs
Normal file
13
src/Plugins/BotSharp.Plugin.EmailHandler/Enums/Utility.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
src/Plugins/BotSharp.Plugin.EmailHandler/Using.cs
Normal file
19
src/Plugins/BotSharp.Plugin.EmailHandler/Using.cs
Normal 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;
|
||||
|
|
@ -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" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
Please call handle_email_request if user wants to send out an email.
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Net.Http;
|
||||
using BotSharp.Plugin.HttpHandler.LlmContexts;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
@ -40,7 +39,7 @@ public class HandleHttpRequestFn : IFunctionCallback
|
|||
{
|
||||
var response = await SendHttpRequest(url, method, content);
|
||||
var responseContent = await HandleHttpResponse(response);
|
||||
message.RichContent = BuildRichContent(responseContent);
|
||||
message.Content = responseContent;
|
||||
message.StopCompletion = true;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -48,7 +47,7 @@ public class HandleHttpRequestFn : IFunctionCallback
|
|||
{
|
||||
var msg = $"Fail when sending http request. Url: {url}, method: {method}, content: {content}";
|
||||
_logger.LogWarning($"{msg}\n(Error: {ex.Message})");
|
||||
message.RichContent = BuildRichContent($"{msg}");
|
||||
message.Content = msg;
|
||||
message.StopCompletion = true;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -58,21 +57,14 @@ public class HandleHttpRequestFn : IFunctionCallback
|
|||
{
|
||||
if (string.IsNullOrEmpty(url)) return null;
|
||||
|
||||
var settings = _services.GetRequiredService<HttpSettings>();
|
||||
using var client = _httpClientFactory.CreateClient();
|
||||
AddRequestHeaders(client);
|
||||
|
||||
var (uri, request) = BuildHttpRequest(url, method, content);
|
||||
if (string.IsNullOrEmpty(uri.Host))
|
||||
{
|
||||
client.BaseAddress = new Uri(settings.BaseAddress);
|
||||
}
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
if (response == null || !response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new Exception($"Status code: {response?.StatusCode}");
|
||||
_logger.LogWarning($"Response status code: {response?.StatusCode}");
|
||||
}
|
||||
|
||||
return response;
|
||||
|
|
@ -82,7 +74,7 @@ public class HandleHttpRequestFn : IFunctionCallback
|
|||
{
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"{_context.HttpContext.Request.Headers["Authorization"]}");
|
||||
|
||||
var settings = _services.GetRequiredService<HttpSettings>();
|
||||
var settings = _services.GetRequiredService<HttpHandlerSettings>();
|
||||
var origin = !string.IsNullOrEmpty(settings.Origin) ? settings.Origin : $"{_context.HttpContext.Request.Headers["Origin"]}";
|
||||
if (!string.IsNullOrEmpty(origin))
|
||||
{
|
||||
|
|
@ -95,17 +87,24 @@ public class HandleHttpRequestFn : IFunctionCallback
|
|||
var httpMethod = GetHttpMethod(method);
|
||||
StringContent httpContent;
|
||||
|
||||
var requestUrl = url;
|
||||
if (httpMethod == HttpMethod.Get)
|
||||
{
|
||||
httpContent = BuildHttpContent("{}");
|
||||
requestUrl = BuildQuery(url, content);
|
||||
}
|
||||
else
|
||||
{
|
||||
httpContent = BuildHttpContent(content);
|
||||
}
|
||||
|
||||
var requestUrl = BuildQuery(url, content);
|
||||
var uri = new Uri(requestUrl);
|
||||
if (!Uri.TryCreate(requestUrl, UriKind.Absolute, out var uri))
|
||||
{
|
||||
var settings = _services.GetRequiredService<HttpHandlerSettings>();
|
||||
var baseUri = new Uri(settings.BaseAddress);
|
||||
uri = new Uri(baseUri, requestUrl);
|
||||
}
|
||||
|
||||
return (uri, new HttpRequestMessage
|
||||
{
|
||||
RequestUri = uri,
|
||||
|
|
@ -196,17 +195,4 @@ public class HandleHttpRequestFn : IFunctionCallback
|
|||
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
private RichContent<IRichMessage> BuildRichContent(string? content)
|
||||
{
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
|
||||
var text = !string.IsNullOrEmpty(content) ? content : "Cannot get any response from the http request.";
|
||||
return new RichContent<IRichMessage>
|
||||
{
|
||||
Recipient = new Recipient { Id = state.GetConversationId() },
|
||||
Editor = EditorTypeEnum.Text,
|
||||
Message = new TextMessage(text)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using BotSharp.Abstraction.Agents.Enums;
|
|||
using BotSharp.Abstraction.Agents.Settings;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Plugin.HttpHandler.Enums;
|
||||
|
||||
namespace BotSharp.Plugin.HttpHandler.Hooks;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using BotSharp.Abstraction.Agents;
|
||||
using BotSharp.Plugin.HttpHandler.Enums;
|
||||
|
||||
namespace BotSharp.Plugin.HttpHandler.Hooks;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using BotSharp.Abstraction.Agents;
|
||||
using BotSharp.Abstraction.Settings;
|
||||
using BotSharp.Plugin.HttpHandler.Hooks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace BotSharp.Plugin.HttpHandler;
|
||||
|
|
@ -18,7 +17,7 @@ public class HttpHandlerPlugin : IBotSharpPlugin
|
|||
services.AddScoped(provider =>
|
||||
{
|
||||
var settingService = provider.GetRequiredService<ISettingService>();
|
||||
return settingService.Bind<HttpSettings>("Http");
|
||||
return settingService.Bind<HttpHandlerSettings>("HttpHandler");
|
||||
});
|
||||
|
||||
services.AddScoped<IAgentHook, HttpHandlerHook>();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Http.Settings;
|
||||
namespace BotSharp.Plugin.HttpHandler.Settings;
|
||||
|
||||
public class HttpSettings
|
||||
public class HttpHandlerSettings
|
||||
{
|
||||
public string BaseAddress { get; set; } = string.Empty;
|
||||
public string Origin { get; set; } = string.Empty;
|
||||
|
|
@ -15,5 +15,8 @@ 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;
|
||||
global using BotSharp.Abstraction.Messaging.Enums;
|
||||
global using BotSharp.Plugin.HttpHandler.Hooks;
|
||||
global using BotSharp.Plugin.HttpHandler.Settings;
|
||||
global using BotSharp.Plugin.HttpHandler.LlmContexts;
|
||||
global using BotSharp.Plugin.HttpHandler.Enums;
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
},
|
||||
"request_content": {
|
||||
"type": "string",
|
||||
"description": "The http request content. It must be in json format.."
|
||||
"description": "The http request content. It must be in serialized json string."
|
||||
}
|
||||
},
|
||||
"required": [ "request_url", "http_method" ]
|
||||
|
|
|
|||
|
|
@ -257,40 +257,34 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
{
|
||||
var text = !string.IsNullOrWhiteSpace(message.Payload) ? message.Payload : message.Content;
|
||||
var textPart = ChatMessageContentPart.CreateTextMessageContentPart(text);
|
||||
var chat = new UserChatMessage(textPart)
|
||||
{
|
||||
ParticipantName = message.FunctionName
|
||||
};
|
||||
var contentParts = new List<ChatMessageContentPart> { textPart };
|
||||
|
||||
if (allowMultiModal)
|
||||
if (allowMultiModal && !message.Files.IsNullOrEmpty())
|
||||
{
|
||||
if (!message.Files.IsNullOrEmpty())
|
||||
foreach (var file in message.Files)
|
||||
{
|
||||
foreach (var file in message.Files)
|
||||
if (!string.IsNullOrEmpty(file.FileUrl))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(file.FileUrl))
|
||||
{
|
||||
var uri = new Uri(file.FileUrl);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(uri, ImageChatMessageContentPartDetail.Low);
|
||||
chat = new UserChatMessage(textPart, contentPart) { ParticipantName = message.FunctionName };
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileData))
|
||||
{
|
||||
var (contentType, bytes) = fileService.GetFileInfoFromData(file.FileData);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(BinaryData.FromBytes(bytes), contentType, ImageChatMessageContentPartDetail.Low);
|
||||
chat = new UserChatMessage(textPart, contentPart) { ParticipantName = message.FunctionName };
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
|
||||
{
|
||||
var contentType = fileService.GetFileContentType(file.FileStorageUrl);
|
||||
using var stream = File.OpenRead(file.FileStorageUrl);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(BinaryData.FromStream(stream), contentType, ImageChatMessageContentPartDetail.Low);
|
||||
chat = new UserChatMessage(textPart, contentPart) { ParticipantName = message.FunctionName };
|
||||
}
|
||||
var uri = new Uri(file.FileUrl);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(uri, ImageChatMessageContentPartDetail.Low);
|
||||
contentParts.Add(contentPart);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileData))
|
||||
{
|
||||
var (contentType, bytes) = fileService.GetFileInfoFromData(file.FileData);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(BinaryData.FromBytes(bytes), contentType, ImageChatMessageContentPartDetail.Low);
|
||||
contentParts.Add(contentPart);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
|
||||
{
|
||||
var contentType = fileService.GetFileContentType(file.FileStorageUrl);
|
||||
using var stream = File.OpenRead(file.FileStorageUrl);
|
||||
var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(BinaryData.FromStream(stream), contentType, ImageChatMessageContentPartDetail.Low);
|
||||
contentParts.Add(contentPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
messages.Add(chat);
|
||||
messages.Add(new UserChatMessage(contentParts) { ParticipantName = message.FunctionName });
|
||||
}
|
||||
else if (message.Role == AgentRole.Assistant)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@
|
|||
"Driver": "Playwright"
|
||||
},
|
||||
|
||||
"Http": {
|
||||
"HttpHandler": {
|
||||
"BaseAddress": "",
|
||||
"Origin": ""
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue