add http handler
This commit is contained in:
parent
3b89064f32
commit
d901fff30f
|
|
@ -6,6 +6,7 @@ public class AgentSettings
|
|||
public string TemplateFormat { get; set; } = "liquid";
|
||||
public string HostAgentId { get; set; } = string.Empty;
|
||||
public bool EnableTranslator { get; set; } = false;
|
||||
public bool EnableHttpHandler { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// This is the default LLM config for agent
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Http.Settings;
|
||||
|
||||
public class HttpSettings
|
||||
{
|
||||
public string BaseAddress { get; set; } = string.Empty;
|
||||
public string Origin { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Routing.Enums;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace BotSharp.Core.Routing.Hooks;
|
||||
|
||||
|
|
@ -106,6 +104,51 @@ public class RoutingAgentHook : AgentHookBase
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
var settings = _services.GetRequiredService<AgentSettings>();
|
||||
if (settings.EnableHttpHandler)
|
||||
{
|
||||
var httpHandlerName = "handle_http_request";
|
||||
var existHttpHandler = functions.Any(x => x.Name == httpHandlerName);
|
||||
var funcs = _services.GetServices<IFunctionCallback>();
|
||||
var httpRequestFunc = funcs.FirstOrDefault(x => x.Name == httpHandlerName);
|
||||
if (!existHttpHandler && httpRequestFunc != null)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(new
|
||||
{
|
||||
request_url = new
|
||||
{
|
||||
type = "string",
|
||||
description = $"The http url that is requested. It can be an absolute url that starts with \"http\" or \"https\", or a relative url that starts with \"/\""
|
||||
},
|
||||
http_method = new
|
||||
{
|
||||
type = "string",
|
||||
description = $"The http method that is requested, e.g., GET, POST, PUT, and DELETE."
|
||||
},
|
||||
request_content = new
|
||||
{
|
||||
type = "string",
|
||||
description = $"The http request content. It must be in json format."
|
||||
}
|
||||
});
|
||||
functions.Add(new FunctionDef
|
||||
{
|
||||
Name = httpRequestFunc.Name,
|
||||
Description = "If the user requests to send an http request, you need to capture the http method and request content, and then call this function to send the http request.",
|
||||
Parameters =
|
||||
{
|
||||
Properties = JsonSerializer.Deserialize<JsonDocument>(json),
|
||||
Required = new List<string>
|
||||
{
|
||||
"request_url",
|
||||
"http_method"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnFunctionsLoaded(functions);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ global using BotSharp.Abstraction.Files;
|
|||
global using BotSharp.Abstraction.Files.Models;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@
|
|||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,210 @@
|
|||
using System.Net.Http;
|
||||
using BotSharp.Plugin.HttpHandler.LlmContexts;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BotSharp.Plugin.HttpHandler.Functions;
|
||||
|
||||
public class HandleHttpRequest : IFunctionCallback
|
||||
{
|
||||
public string Name => "handle_http_request";
|
||||
public string Indication => "Handling http request";
|
||||
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<HandleHttpRequest> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IHttpContextAccessor _context;
|
||||
private readonly BotSharpOptions _options;
|
||||
|
||||
public HandleHttpRequest(IServiceProvider services,
|
||||
ILogger<HandleHttpRequest> logger,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IHttpContextAccessor context,
|
||||
BotSharpOptions options)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_context = context;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var args = JsonSerializer.Deserialize<LlmContextIn>(message.FunctionArgs, _options.JsonSerializerOptions);
|
||||
var url = args?.RequestUrl;
|
||||
var method = args?.HttpMethod;
|
||||
var content = args?.RequestContent;
|
||||
|
||||
try
|
||||
{
|
||||
var response = await SendHttpRequest(url, method, content);
|
||||
var responseContent = await HandleHttpResponse(response);
|
||||
message.RichContent = BuildRichContent(responseContent);
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = $"Fail when sending http request. Url: {url}, method: {method}, content: {content}";
|
||||
_logger.LogWarning($"{msg}\n(Error: {ex.Message})");
|
||||
message.RichContent = BuildRichContent($"{msg}");
|
||||
return await Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage?> SendHttpRequest(string? url, string? method, string? content)
|
||||
{
|
||||
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}");
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void AddRequestHeaders(HttpClient client)
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"{_context.HttpContext.Request.Headers["Authorization"]}");
|
||||
|
||||
var settings = _services.GetRequiredService<HttpSettings>();
|
||||
var origin = !string.IsNullOrEmpty(settings.Origin) ? settings.Origin : $"{_context.HttpContext.Request.Headers["Origin"]}";
|
||||
if (!string.IsNullOrEmpty(origin))
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("Origin", origin);
|
||||
}
|
||||
}
|
||||
|
||||
private (Uri, HttpRequestMessage) BuildHttpRequest(string url, string? method, string? content)
|
||||
{
|
||||
var httpMethod = GetHttpMethod(method);
|
||||
StringContent httpContent;
|
||||
|
||||
if (httpMethod == HttpMethod.Get)
|
||||
{
|
||||
httpContent = BuildHttpContent(string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
httpContent = BuildHttpContent(content);
|
||||
}
|
||||
|
||||
var requestUrl = BuildQuery(url, content);
|
||||
var uri = new Uri(requestUrl);
|
||||
return (uri, new HttpRequestMessage
|
||||
{
|
||||
RequestUri = uri,
|
||||
Method = httpMethod,
|
||||
Content = httpContent
|
||||
});
|
||||
}
|
||||
|
||||
private HttpMethod GetHttpMethod(string? method)
|
||||
{
|
||||
var localMethod = method?.Trim()?.ToUpper();
|
||||
HttpMethod matchMethod;
|
||||
|
||||
switch (localMethod)
|
||||
{
|
||||
case "GET":
|
||||
matchMethod = HttpMethod.Get;
|
||||
break;
|
||||
case "DELETE":
|
||||
matchMethod = HttpMethod.Delete;
|
||||
break;
|
||||
case "PUT":
|
||||
matchMethod = HttpMethod.Put;
|
||||
break;
|
||||
case "Patch":
|
||||
matchMethod = HttpMethod.Patch;
|
||||
break;
|
||||
default:
|
||||
matchMethod = HttpMethod.Post;
|
||||
break;
|
||||
}
|
||||
return matchMethod;
|
||||
}
|
||||
|
||||
private StringContent BuildHttpContent(string? content)
|
||||
{
|
||||
var str = string.Empty;
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Deserialize<JsonDocument>(content ?? string.Empty, _options.JsonSerializerOptions);
|
||||
str = JsonSerializer.Serialize(json, _options.JsonSerializerOptions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when build http content: {content}\n(Error: {ex.Message})");
|
||||
}
|
||||
|
||||
return new StringContent(str, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
private string BuildQuery(string url, string? content)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(content)) return url;
|
||||
|
||||
try
|
||||
{
|
||||
var queries = new List<string>();
|
||||
var json = JsonSerializer.Deserialize<JsonDocument>(content, _options.JsonSerializerOptions);
|
||||
var root = json.RootElement;
|
||||
foreach (var prop in root.EnumerateObject())
|
||||
{
|
||||
var name = prop.Name;
|
||||
var value = prop.Value.ToString();
|
||||
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
queries.Add($"{name}={value}");
|
||||
}
|
||||
|
||||
if (!queries.IsNullOrEmpty())
|
||||
{
|
||||
url += $"?{string.Join('&', queries)}";
|
||||
}
|
||||
return url;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when building url query. Url: {url}, Content: {content}\n(Error: {ex.Message})");
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> HandleHttpResponse(HttpResponseMessage? response)
|
||||
{
|
||||
if (response == null) return string.Empty;
|
||||
|
||||
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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Http.Settings;
|
||||
using BotSharp.Abstraction.Settings;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace BotSharp.Plugin.HttpHandler;
|
||||
|
|
@ -12,6 +14,10 @@ public class HttpHandlerPlugin : IBotSharpPlugin
|
|||
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
|
||||
services.AddScoped(provider =>
|
||||
{
|
||||
var settingService = provider.GetRequiredService<ISettingService>();
|
||||
return settingService.Bind<HttpSettings>("Http");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Plugin.HttpHandler.LlmContexts;
|
||||
|
||||
public class LlmContextIn
|
||||
{
|
||||
[JsonPropertyName("request_url")]
|
||||
public string? RequestUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("http_method")]
|
||||
public string? HttpMethod { get; set; }
|
||||
|
||||
[JsonPropertyName("request_content")]
|
||||
public string? RequestContent { get; set; }
|
||||
}
|
||||
|
|
@ -11,4 +11,9 @@ 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.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;
|
||||
|
|
@ -141,6 +141,11 @@
|
|||
"Driver": "Playwright"
|
||||
},
|
||||
|
||||
"Http": {
|
||||
"BaseAddress": "",
|
||||
"Origin": ""
|
||||
},
|
||||
|
||||
"Statistics": {
|
||||
"DataDir": "stats"
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue