Merge branch 'SciSharp:master' into master

This commit is contained in:
C. Oceania 2024-07-11 19:18:35 -05:00 committed by GitHub
commit bad8fb1b0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 477 additions and 176 deletions

View file

@ -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";
}

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

@ -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))

View file

@ -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;
}
}

View file

@ -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";

View file

@ -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();

View file

@ -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;

View file

@ -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");
}
}
}

View file

@ -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");
}
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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.

View file

@ -1 +1 @@
Break down the users most recent needs and figure out the next steps.
Break down the users most recent needs and figure out the instruction of next step.

View file

@ -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(),

View file

@ -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;

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.

View file

@ -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)
};
}
}

View file

@ -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;

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Plugin.HttpHandler.Enums;
namespace BotSharp.Plugin.HttpHandler.Hooks;

View file

@ -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>();

View file

@ -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;

View file

@ -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;

View file

@ -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" ]

View file

@ -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)
{

View file

@ -154,7 +154,7 @@
"Driver": "Playwright"
},
"Http": {
"HttpHandler": {
"BaseAddress": "",
"Origin": ""
},