merge with master
This commit is contained in:
commit
45baed5a55
|
|
@ -1,8 +1,11 @@
|
|||
using BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Agents;
|
||||
|
||||
public interface IAgentRouting
|
||||
{
|
||||
string AgentId { get; }
|
||||
Task<Agent> LoadRouter();
|
||||
Task<Agent> LoadCurrentAgent();
|
||||
RoutingRecord[] GetRoutingRecords();
|
||||
RoutingRecord GetRecordByName(string name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ public class AgentSettings
|
|||
/// Router Agent Id
|
||||
/// </summary>
|
||||
public string RouterId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reasoner Agent Id
|
||||
/// </summary>
|
||||
public string ReasonerId { get; set; }
|
||||
|
||||
public string DataDir { get; set; }
|
||||
public string TemplateFormat { get; set; }
|
||||
public int MaxRecursiveDepth { get; set; } = 3;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public class RoleDialogModel
|
|||
{
|
||||
if (Role == AgentRole.Function)
|
||||
{
|
||||
return $"{Role}: {FunctionName}";
|
||||
return $"{Role}: {FunctionName} => {ExecutionResult}";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,4 +6,6 @@ public class ConversationSetting
|
|||
public string ChatCompletion { get; set; }
|
||||
public bool EnableKnowledgeBase { get; set; }
|
||||
public bool ShowVerboseLog { get; set; }
|
||||
public int MaxRecursiveDepth { get; set; } = 3;
|
||||
public bool EnableReasoning { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
using BotSharp.Abstraction.Routing.Models;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Abstraction.Functions.Models;
|
||||
|
||||
public class FunctionCallFromLlm
|
||||
{
|
||||
[JsonPropertyName("function")]
|
||||
public string Function { get; set; }
|
||||
|
||||
[JsonPropertyName("parameters")]
|
||||
public RetrievalArgs Parameters { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges
|
||||
{
|
||||
public interface IPaddleOcrConverter
|
||||
{
|
||||
// void LoadModel();
|
||||
Task<string> ConvertImageToText(string loadPath);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges
|
||||
{
|
||||
public interface IPdf2TextConverter
|
||||
{
|
||||
Task<string> ConvertPdfToText(IFormFile formFile, int? startPageNum, int? endPageNum);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
public class RetrievalArgs : RoutingArgs
|
||||
{
|
||||
[JsonPropertyName("question")]
|
||||
public string Question { get; set; }
|
||||
|
||||
[JsonPropertyName("answer")]
|
||||
public string Answer { get; set; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string Reason { get; set; }
|
||||
|
||||
[JsonPropertyName("args")]
|
||||
public JsonDocument Arguments { get; set; }
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Abstraction.Agents.Models;
|
||||
namespace BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
public class RoutingArgs
|
||||
{
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Abstraction.Agents.Models;
|
||||
namespace BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
public class RoutingRecord
|
||||
{
|
||||
|
|
@ -19,6 +19,9 @@ public class RoutingRecord
|
|||
[JsonPropertyName("redirect_to")]
|
||||
public string RedirectTo { get; set; }
|
||||
|
||||
[JsonPropertyName("disabled")]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
public class GPT4Settings
|
||||
{
|
||||
public string ApiKey { get; set; }
|
||||
public string Endpoint { get; set; }
|
||||
public string DeploymentModel { get; set; }
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Agents.Services;
|
||||
|
||||
public class AgentRouter : IAgentRouting
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger _logger;
|
||||
private readonly AgentSettings _settings;
|
||||
|
||||
public AgentRouter(IServiceProvider services,
|
||||
ILogger<AgentRouter> logger,
|
||||
AgentSettings settings)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public async Task<Agent> LoadRouter()
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(_settings.RouterId);
|
||||
return agent;
|
||||
}
|
||||
|
||||
public async Task<Agent> LoadCurrentAgent()
|
||||
{
|
||||
// Load current agent from state
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var currentAgentId = state.GetState("agent_id");
|
||||
if (string.IsNullOrEmpty(currentAgentId))
|
||||
{
|
||||
currentAgentId = _settings.RouterId;
|
||||
}
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(currentAgentId);
|
||||
|
||||
// Set agent and trigger state changed
|
||||
state.SetState("agent_id", currentAgentId);
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
||||
public RoutingRecord[] GetRoutingRecords()
|
||||
{
|
||||
var agentSettings = _services.GetRequiredService<AgentSettings>();
|
||||
var dbSettings = _services.GetRequiredService<MyDatabaseSettings>();
|
||||
//var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json");
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var agent = db.Agent.FirstOrDefault(x => x.Id == agentSettings.RouterId);
|
||||
var routes = agent?.Routes ?? new List<string>();
|
||||
var routingRecords = new RoutingRecord[routes.Count];
|
||||
|
||||
for (int i = 0; i < routes.Count; i++)
|
||||
{
|
||||
if (routes[i] == null) continue;
|
||||
routingRecords[i] = JsonSerializer.Deserialize<RoutingRecord>(routes[i]);
|
||||
}
|
||||
|
||||
return routingRecords;
|
||||
}
|
||||
}
|
||||
|
|
@ -76,6 +76,7 @@
|
|||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="6.2.1" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.4.0" />
|
||||
<PackageReference Include="TensorFlow.Keras" Version="0.11.2" />
|
||||
<PackageReference Include="PdfPig" Version="0.1.9-alpha-20230806-4a480" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Core.Functions;
|
||||
using BotSharp.Core.Hooks;
|
||||
using BotSharp.Core.Routing;
|
||||
using BotSharp.Core.Templating;
|
||||
using BotSharp.Core.Plugins.Knowledges.Services;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using DatabaseSettings = BotSharp.Abstraction.Repositories.DatabaseSettings;
|
||||
|
|
@ -43,13 +46,21 @@ public static class BotSharpServiceCollectionExtensions
|
|||
services.AddSingleton<TemplateRender>();
|
||||
|
||||
// Register router
|
||||
services.AddScoped<IAgentRouting, AgentRouter>();
|
||||
services.AddScoped<Router>();
|
||||
services.AddScoped<Reasoner>();
|
||||
services.AddScoped<IAgentRouting>(p =>
|
||||
{
|
||||
var setting = p.GetRequiredService<ConversationSetting>();
|
||||
return setting.EnableReasoning ? p.GetRequiredService<Reasoner>() : p.GetRequiredService<Router>();
|
||||
});
|
||||
|
||||
// Register function callback
|
||||
services.AddScoped<IFunctionCallback, RouteToAgentFn>();
|
||||
|
||||
// Register Hooks
|
||||
services.AddScoped<IAgentHook, AgentHook>();
|
||||
services.AddScoped<IAgentHook, RoutingHook>();
|
||||
|
||||
services.AddScoped<Simulator>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
|
@ -97,5 +108,7 @@ public static class BotSharpServiceCollectionExtensions
|
|||
loader.Load();
|
||||
|
||||
services.AddSingleton(loader);
|
||||
|
||||
services.AddSingleton<IPdf2TextConverter, PigPdf2TextConverter>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,12 @@ public partial class ConversationService
|
|||
string conversationId,
|
||||
Agent agent,
|
||||
List<RoleDialogModel> wholeDialogs,
|
||||
int maxRecursiveDepth,
|
||||
Func<RoleDialogModel, Task> onMessageReceived,
|
||||
Func<RoleDialogModel, Task> onFunctionExecuting,
|
||||
Func<RoleDialogModel, Task> onFunctionExecuted)
|
||||
{
|
||||
currentRecursiveDepth++;
|
||||
if (currentRecursiveDepth > maxRecursiveDepth)
|
||||
if (currentRecursiveDepth > _settings.MaxRecursiveDepth)
|
||||
{
|
||||
_logger.LogWarning($"Exceeded max recursive depth.");
|
||||
|
||||
|
|
@ -65,7 +64,6 @@ public partial class ConversationService
|
|||
fn.Content = fn.FunctionArgs.Replace("\r", " ").Replace("\n", " ").Trim() + " => " + fn.ExecutionResult;
|
||||
|
||||
// Agent has been transferred
|
||||
var agentSettings = _services.GetRequiredService<AgentSettings>();
|
||||
if (fn.CurrentAgentId != preAgentId)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
|
|
@ -83,7 +81,6 @@ public partial class ConversationService
|
|||
conversationId,
|
||||
agent,
|
||||
wholeDialogs,
|
||||
maxRecursiveDepth,
|
||||
onMessageReceived,
|
||||
onFunctionExecuting,
|
||||
onFunctionExecuted);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
using BotSharp.Core.Routing;
|
||||
|
||||
namespace BotSharp.Core.Conversations.Services;
|
||||
|
||||
|
|
@ -31,7 +34,7 @@ public partial class ConversationService
|
|||
stateService.SetState("channel", lastDialog.Channel);
|
||||
|
||||
var router = _services.GetRequiredService<IAgentRouting>();
|
||||
var agent = await router.LoadRouter();
|
||||
Agent agent = await router.LoadRouter();
|
||||
|
||||
_logger.LogInformation($"[{agent.Name}] {lastDialog.Role}: {lastDialog.Content}");
|
||||
|
||||
|
|
@ -65,14 +68,51 @@ public partial class ConversationService
|
|||
await hook.BeforeCompletion();
|
||||
}
|
||||
|
||||
var agentSettings = _services.GetRequiredService<AgentSettings>();
|
||||
// reasoning
|
||||
if (_settings.EnableReasoning)
|
||||
{
|
||||
var simulator = _services.GetRequiredService<Simulator>();
|
||||
var reasonedContext = await simulator.Enter(agent, wholeDialogs);
|
||||
|
||||
if (reasonedContext.FunctionName == "interrupt_task_execution")
|
||||
{
|
||||
await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, reasonedContext.Content)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
Channel = lastDialog.Channel
|
||||
}, onMessageReceived);
|
||||
return true;
|
||||
}
|
||||
else if (reasonedContext.FunctionName == "response_to_user")
|
||||
{
|
||||
await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, reasonedContext.Content)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
Channel = lastDialog.Channel
|
||||
}, onMessageReceived);
|
||||
return true;
|
||||
}
|
||||
else if (reasonedContext.FunctionName == "continue_execute_task")
|
||||
{
|
||||
if (reasonedContext.CurrentAgentId != agent.Id)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
agent = await agentService.LoadAgent(reasonedContext.CurrentAgentId);
|
||||
}
|
||||
}
|
||||
|
||||
simulator.Dialogs.ForEach(x =>
|
||||
{
|
||||
wholeDialogs.Add(x);
|
||||
_storage.Append(conversationId, agent.Id, x);
|
||||
});
|
||||
}
|
||||
|
||||
var chatCompletion = GetChatCompletion();
|
||||
var result = await GetChatCompletionsAsyncRecursively(chatCompletion,
|
||||
conversationId,
|
||||
agent,
|
||||
wholeDialogs,
|
||||
agentSettings.MaxRecursiveDepth,
|
||||
onMessageReceived,
|
||||
onFunctionExecuting,
|
||||
onFunctionExecuted);
|
||||
|
|
@ -101,4 +141,10 @@ public partial class ConversationService
|
|||
var completions = _services.GetServices<IChatCompletion>();
|
||||
return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith(_settings.ChatCompletion));
|
||||
}
|
||||
|
||||
public IChatCompletion GetGpt4ChatCompletion()
|
||||
{
|
||||
var completions = _services.GetServices<IChatCompletion>();
|
||||
return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith("GPT4CompletionProvider"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ public class ConversationStorage : IConversationStorage
|
|||
CurrentAgentId = currentAgentId,
|
||||
FunctionName = funcName,
|
||||
FunctionArgs = funcArgs,
|
||||
ExecutionResult = text,
|
||||
CreatedAt = createdAt
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Functions;
|
||||
|
|
@ -52,8 +51,7 @@ public class RouteToAgentFn : IFunctionCallback
|
|||
{
|
||||
var args = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs);
|
||||
var router = _services.GetRequiredService<IAgentRouting>();
|
||||
var records = router.GetRoutingRecords();
|
||||
var routingRule = records.FirstOrDefault(x => x.Name.ToLower() == args.AgentName.ToLower());
|
||||
var routingRule = router.GetRecordByName(args.AgentName);
|
||||
|
||||
if (routingRule == null)
|
||||
{
|
||||
|
|
|
|||
14
src/Infrastructure/BotSharp.Core/Hooks/ReasoningHook.cs
Normal file
14
src/Infrastructure/BotSharp.Core/Hooks/ReasoningHook.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
namespace BotSharp.Core.Hooks;
|
||||
|
||||
public class ReasoningHook : AgentHookBase
|
||||
{
|
||||
public ReasoningHook(IServiceProvider services, AgentSettings settings)
|
||||
: base(services, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool OnInstructionLoaded(string template, Dictionary<string, object> dict)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
namespace BotSharp.Core.Hooks;
|
||||
|
||||
public class AgentHook : AgentHookBase
|
||||
public class RoutingHook : AgentHookBase
|
||||
{
|
||||
public AgentHook(IServiceProvider services, AgentSettings settings)
|
||||
public RoutingHook(IServiceProvider services, AgentSettings settings)
|
||||
: base(services, settings)
|
||||
{
|
||||
}
|
||||
|
|
@ -10,7 +10,9 @@ public class AgentHook : AgentHookBase
|
|||
public override bool OnInstructionLoaded(string template, Dictionary<string, object> dict)
|
||||
{
|
||||
var router = _services.GetRequiredService<IAgentRouting>();
|
||||
dict["routing_records"] = router.GetRoutingRecords();
|
||||
dict["routing_records"] = router.GetRoutingRecords()
|
||||
.Where(x => !x.Disabled)
|
||||
.ToList();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,4 +5,5 @@ public class KnowledgeBaseSettings
|
|||
public string VectorDb { get; set; }
|
||||
public string TextEmbedding { get; set; }
|
||||
public string TextCompletion { get; set; }
|
||||
public string Pdf2TextConverter { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using UglyToad.PdfPig;
|
||||
using UglyToad.PdfPig.Content;
|
||||
|
||||
namespace BotSharp.Core.Plugins.Knowledges.Services;
|
||||
|
||||
public class PigPdf2TextConverter : IPdf2TextConverter
|
||||
{
|
||||
public async Task<string> ConvertPdfToText(IFormFile formFile, int? startPageNum, int? endPageNum)
|
||||
{
|
||||
return await OpenPdfDocumentAsync(formFile, startPageNum, endPageNum);
|
||||
}
|
||||
|
||||
private async Task<string> OpenPdfDocumentAsync(IFormFile formFile, int? startPageNum, int? endPageNum)
|
||||
{
|
||||
if (formFile.Length <= 0)
|
||||
{
|
||||
return await Task.FromResult(string.Empty);
|
||||
}
|
||||
|
||||
var filePath = Path.GetTempFileName();
|
||||
|
||||
using (var stream = System.IO.File.Create(filePath))
|
||||
{
|
||||
await formFile.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
var document = PdfDocument.Open(filePath);
|
||||
var content = "";
|
||||
foreach (Page page in document.GetPages())
|
||||
{
|
||||
if (startPageNum.HasValue && page.Number < startPageNum.Value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (endPageNum.HasValue && page.Number > endPageNum.Value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
content += page.Text;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
}
|
||||
12
src/Infrastructure/BotSharp.Core/Routing/Reasoner.cs
Normal file
12
src/Infrastructure/BotSharp.Core/Routing/Reasoner.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace BotSharp.Core.Routing;
|
||||
|
||||
public class Reasoner : Router
|
||||
{
|
||||
public override string AgentId => _settings.ReasonerId;
|
||||
|
||||
public Reasoner(IServiceProvider services,
|
||||
ILogger<Reasoner> logger,
|
||||
AgentSettings settings) : base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
}
|
||||
43
src/Infrastructure/BotSharp.Core/Routing/Router.cs
Normal file
43
src/Infrastructure/BotSharp.Core/Routing/Router.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using System.IO;
|
||||
using static Tensorflow.ApiDef.Types;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
||||
public class Router : IAgentRouting
|
||||
{
|
||||
protected readonly IServiceProvider _services;
|
||||
protected readonly ILogger _logger;
|
||||
protected readonly AgentSettings _settings;
|
||||
|
||||
public virtual string AgentId => _settings.RouterId;
|
||||
|
||||
public Router(IServiceProvider services,
|
||||
ILogger<Router> logger,
|
||||
AgentSettings settings)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public virtual async Task<Agent> LoadRouter()
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
return await agentService.LoadAgent(AgentId);
|
||||
}
|
||||
|
||||
public RoutingRecord[] GetRoutingRecords()
|
||||
{
|
||||
var agentSettings = _services.GetRequiredService<AgentSettings>();
|
||||
var dbSettings = _services.GetRequiredService<MyDatabaseSettings>();
|
||||
var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json");
|
||||
return JsonSerializer.Deserialize<RoutingRecord[]>(File.ReadAllText(filePath));
|
||||
}
|
||||
|
||||
public RoutingRecord GetRecordByName(string name)
|
||||
{
|
||||
return GetRoutingRecords().First(x => x.Name.ToLower() == name.ToLower());
|
||||
}
|
||||
}
|
||||
150
src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
Normal file
150
src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// Simulate the dialogue between different agents.
|
||||
/// </summary>
|
||||
public class Simulator
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger _logger;
|
||||
private List<RoleDialogModel> _dialogs;
|
||||
public List<RoleDialogModel> Dialogs => _dialogs;
|
||||
|
||||
public Simulator(IServiceProvider services, ILogger<Simulator> logger)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> Enter(Agent agent, List<RoleDialogModel> whileDialogs)
|
||||
{
|
||||
_dialogs = new List<RoleDialogModel>();
|
||||
|
||||
foreach (var dialog in whileDialogs.TakeLast(10))
|
||||
{
|
||||
agent.Instruction += $"\r\n{dialog.Role}: {dialog.Content}";
|
||||
}
|
||||
|
||||
var response = await SendMessageToReasoner(agent);
|
||||
var args = JsonSerializer.Deserialize<FunctionCallFromLlm>(response.Content);
|
||||
response.FunctionName = args.Function;
|
||||
|
||||
if (args.Function == "continue_execute_task")
|
||||
{
|
||||
response.FunctionArgs = JsonSerializer.Serialize(args.Parameters.Arguments);
|
||||
|
||||
var router = _services.GetRequiredService<IAgentRouting>();
|
||||
var record = router.GetRecordByName(args.Parameters.AgentName);
|
||||
response.CurrentAgentId = record.AgentId;
|
||||
}
|
||||
else if (args.Function == "interrupt_task_execution")
|
||||
{
|
||||
response.Content = args.Parameters.Reason;
|
||||
response.ExecutionResult = args.Parameters.Reason;
|
||||
}
|
||||
else if (args.Function == "response_to_user")
|
||||
{
|
||||
response.Content = args.Parameters.Answer;
|
||||
response.ExecutionResult = args.Parameters.Answer;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private async Task<RoleDialogModel> SendMessageToReasoner(Agent reasoner)
|
||||
{
|
||||
var wholeDialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, @"What's the next step, your response must be in JSON format with ""function"" and ""parameters"". ")
|
||||
};
|
||||
|
||||
var chatCompletion = GetGpt4ChatCompletion();
|
||||
|
||||
RoleDialogModel response = null;
|
||||
await chatCompletion.GetChatCompletionsAsync(reasoner, wholeDialogs, async msg
|
||||
=> response = msg, fn
|
||||
=> Task.CompletedTask);
|
||||
|
||||
var args = JsonSerializer.Deserialize<FunctionCallFromLlm>(response.Content);
|
||||
|
||||
if (args.Function == "retrieve_data_from_agent")
|
||||
{
|
||||
SaveStateByArgs(args.Parameters.Arguments);
|
||||
}
|
||||
else if (args.Function == "response_to_user")
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
// Retrieve information from specific agent
|
||||
var router = _services.GetRequiredService<IAgentRouting>();
|
||||
var record = router.GetRecordByName(args.Parameters.AgentName);
|
||||
response = await SendMessageToAgent(record.AgentId, new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, args.Parameters.Question)
|
||||
});
|
||||
|
||||
_dialogs.Add(new RoleDialogModel(AgentRole.Function, $"{record.Name}: {response.Content}")
|
||||
{
|
||||
FunctionName = args.Function,
|
||||
FunctionArgs = JsonSerializer.Serialize(args.Parameters.Arguments),
|
||||
ExecutionResult = response.Content
|
||||
});
|
||||
|
||||
reasoner.Instruction += $"\r\n{record.Name}: {response.Content}";
|
||||
// Got the response from agent, then send to reasoner again to make the decision
|
||||
await chatCompletion.GetChatCompletionsAsync(reasoner, wholeDialogs, async msg
|
||||
=> response = msg, fn
|
||||
=> Task.CompletedTask);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private async Task<RoleDialogModel> SendMessageToAgent(string agentId, List<RoleDialogModel> wholeDialogs)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(agentId);
|
||||
|
||||
var chatCompletion = GetChatCompletion();
|
||||
|
||||
RoleDialogModel response = null;
|
||||
await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg
|
||||
=> response = msg, fn
|
||||
=> Task.CompletedTask);
|
||||
return response;
|
||||
}
|
||||
|
||||
public IChatCompletion GetChatCompletion()
|
||||
{
|
||||
var completions = _services.GetServices<IChatCompletion>();
|
||||
var settings = _services.GetRequiredService<ConversationSetting>();
|
||||
return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith(settings.ChatCompletion));
|
||||
}
|
||||
|
||||
public IChatCompletion GetGpt4ChatCompletion()
|
||||
{
|
||||
var completions = _services.GetServices<IChatCompletion>();
|
||||
return completions.FirstOrDefault(x => x.GetType().FullName.EndsWith("GPT4CompletionProvider"));
|
||||
}
|
||||
|
||||
private void SaveStateByArgs(JsonDocument args)
|
||||
{
|
||||
var stateService = _services.GetRequiredService<IConversationStateService>();
|
||||
if (args.RootElement is JsonElement root)
|
||||
{
|
||||
foreach (JsonProperty property in root.EnumerateObject())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(property.Value.ToString()))
|
||||
{
|
||||
stateService.SetState(property.Name, property.Value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
using Fluid;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
@ -17,7 +18,7 @@ public class TemplateRender : ITemplateRender
|
|||
_services = services;
|
||||
_logger = logger;
|
||||
_options = new TemplateOptions();
|
||||
_options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.CamelCase;
|
||||
_options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.SnakeCase;
|
||||
_options.MemberAccessStrategy.Register<RoutingRecord>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
|
|
@ -9,11 +9,11 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||
<PackageReference Include="PdfPig" Version="0.1.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||
<ProjectReference Include="..\BotSharp.Core\BotSharp.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ using BotSharp.Abstraction.Knowledges.Models;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using UglyToad.PdfPig.Content;
|
||||
using UglyToad.PdfPig;
|
||||
using BotSharp.Core.Plugins.Knowledges;
|
||||
|
||||
|
||||
namespace BotSharp.OpenAPI.Controllers;
|
||||
|
||||
|
|
@ -11,11 +13,13 @@ namespace BotSharp.OpenAPI.Controllers;
|
|||
public class KnowledgeController : ControllerBase, IApiAdapter
|
||||
{
|
||||
private readonly IKnowledgeService _knowledgeService;
|
||||
public KnowledgeController(IKnowledgeService knowledgeService)
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public KnowledgeController(IKnowledgeService knowledgeService, IServiceProvider services)
|
||||
{
|
||||
_knowledgeService = knowledgeService;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
[HttpGet("/knowledge/{agentId}")]
|
||||
public async Task<List<RetrievedResult>> RetrieveKnowledge([FromRoute] string agentId, [FromQuery(Name = "q")] string question)
|
||||
{
|
||||
|
|
@ -27,44 +31,22 @@ public class KnowledgeController : ControllerBase, IApiAdapter
|
|||
}
|
||||
|
||||
[HttpPost("/knowledge/{agentId}")]
|
||||
public async Task<IActionResult> FeedKnowledge([FromRoute] string agentId, List<IFormFile> files, [FromQuery] int? startPageNum, [FromQuery] int? endPageNum)
|
||||
public async Task<IActionResult> FeedKnowledge([FromRoute] string agentId, List<IFormFile> files, [FromQuery] int? startPageNum, [FromQuery] int? endPageNum, [FromQuery] bool? paddleModel)
|
||||
{
|
||||
var setttings = _services.GetRequiredService<KnowledgeBaseSettings>();
|
||||
var textConverter = _services.GetServices<IPdf2TextConverter>().First(x => x.GetType().FullName.EndsWith(setttings.Pdf2TextConverter));
|
||||
long size = files.Sum(f => f.Length);
|
||||
|
||||
foreach (var formFile in files)
|
||||
{
|
||||
if (formFile.Length <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var filePath = Path.GetTempFileName();
|
||||
|
||||
using (var stream = System.IO.File.Create(filePath))
|
||||
{
|
||||
await formFile.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
var document = PdfDocument.Open(filePath);
|
||||
var content = "";
|
||||
foreach (Page page in document.GetPages())
|
||||
{
|
||||
if (startPageNum.HasValue && page.Number < startPageNum.Value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (endPageNum.HasValue && page.Number > endPageNum.Value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
content += page.Text;
|
||||
}
|
||||
content = await textConverter.ConvertPdfToText(formFile, startPageNum, endPageNum);
|
||||
|
||||
// Process uploaded files
|
||||
// Don't rely on or trust the FileName property without validation.
|
||||
|
||||
// Add FeedWithMetaData
|
||||
await _knowledgeService.Feed(new KnowledgeFeedModel
|
||||
{
|
||||
AgentId = agentId,
|
||||
|
|
|
|||
|
|
@ -23,5 +23,6 @@ public class AzureOpenAiPlugin : IBotSharpPlugin
|
|||
|
||||
services.AddScoped<ITextCompletion, TextCompletionProvider>();
|
||||
services.AddScoped<IChatCompletion, ChatCompletionProvider>();
|
||||
services.AddScoped<IChatCompletion, GPT4CompletionProvider>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
using Azure;
|
||||
using Azure.AI.OpenAI;
|
||||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Conversations.Settings;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
using BotSharp.Plugin.AzureOpenAI.Settings;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.Plugin.AzureOpenAI.Providers;
|
||||
|
||||
public class GPT4CompletionProvider : IChatCompletion
|
||||
{
|
||||
private readonly AzureOpenAiSettings _settings;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GPT4CompletionProvider(AzureOpenAiSettings settings,
|
||||
ILogger<GPT4CompletionProvider> logger,
|
||||
IServiceProvider services)
|
||||
{
|
||||
_settings = settings;
|
||||
_logger = logger;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
private OpenAIClient GetClient()
|
||||
{
|
||||
var client = new OpenAIClient(new Uri(_settings.GPT4.Endpoint), new AzureKeyCredential(_settings.GPT4.ApiKey));
|
||||
return client;
|
||||
}
|
||||
|
||||
public List<RoleDialogModel> GetChatSamples(string sampleText)
|
||||
{
|
||||
var samples = new List<RoleDialogModel>();
|
||||
if (string.IsNullOrEmpty(sampleText))
|
||||
{
|
||||
return samples;
|
||||
}
|
||||
|
||||
var lines = sampleText.Split('\n');
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (string.IsNullOrEmpty(line.Trim()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var role = line.Substring(0, line.IndexOf(' ') - 1).Trim();
|
||||
var content = line.Substring(line.IndexOf(' ') + 1).Trim();
|
||||
|
||||
// comments
|
||||
if (role == "##")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
samples.Add(new RoleDialogModel(role, content));
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
public List<FunctionDef> GetFunctions(string functionsJson)
|
||||
{
|
||||
var functions = new List<FunctionDef>();
|
||||
if (!string.IsNullOrEmpty(functionsJson))
|
||||
{
|
||||
functions = JsonSerializer.Deserialize<List<FunctionDef>>(functionsJson, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
AllowTrailingCommas = true
|
||||
});
|
||||
}
|
||||
|
||||
return functions;
|
||||
}
|
||||
|
||||
public async Task<bool> GetChatCompletionsAsync(Agent agent,
|
||||
List<RoleDialogModel> conversations,
|
||||
Func<RoleDialogModel, Task> onMessageReceived,
|
||||
Func<RoleDialogModel, Task> onFunctionExecuting)
|
||||
{
|
||||
var client = GetClient();
|
||||
var chatCompletionsOptions = PrepareOptions(agent, conversations);
|
||||
|
||||
var response = await client.GetChatCompletionsAsync(_settings.GPT4.DeploymentModel, chatCompletionsOptions);
|
||||
var choice = response.Value.Choices[0];
|
||||
var message = choice.Message;
|
||||
|
||||
if (choice.FinishReason == CompletionsFinishReason.FunctionCall)
|
||||
{
|
||||
_logger.LogInformation($"[{agent.Name}]: {message.FunctionCall.Name} => {message.FunctionCall.Arguments}");
|
||||
|
||||
var funcContextIn = new RoleDialogModel(AgentRole.Function, message.Content)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
FunctionName = message.FunctionCall.Name,
|
||||
FunctionArgs = message.FunctionCall.Arguments,
|
||||
Channel = conversations.Last().Channel
|
||||
};
|
||||
|
||||
// Execute functions
|
||||
await onFunctionExecuting(funcContextIn);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation($"[{agent.Name}] {message.Role}: {message.Content}");
|
||||
|
||||
var msg = new RoleDialogModel(AgentRole.Assistant, message.Content)
|
||||
{
|
||||
CurrentAgentId= agent.Id,
|
||||
Channel = conversations.Last().Channel
|
||||
};
|
||||
|
||||
// Text response received
|
||||
await onMessageReceived(msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> GetChatCompletionsStreamingAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived)
|
||||
{
|
||||
var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey));
|
||||
var chatCompletionsOptions = PrepareOptions(agent, conversations);
|
||||
|
||||
var response = await client.GetChatCompletionsStreamingAsync(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions);
|
||||
using StreamingChatCompletions streaming = response.Value;
|
||||
|
||||
string output = "";
|
||||
await foreach (var choice in streaming.GetChoicesStreaming())
|
||||
{
|
||||
if (choice.FinishReason == CompletionsFinishReason.FunctionCall)
|
||||
{
|
||||
var args = "";
|
||||
await foreach (var message in choice.GetMessageStreaming())
|
||||
{
|
||||
if (message.FunctionCall == null || message.FunctionCall.Arguments == null)
|
||||
continue;
|
||||
Console.Write(message.FunctionCall.Arguments);
|
||||
args += message.FunctionCall.Arguments;
|
||||
|
||||
}
|
||||
await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), args));
|
||||
continue;
|
||||
}
|
||||
|
||||
await foreach (var message in choice.GetMessageStreaming())
|
||||
{
|
||||
if (message.Content == null)
|
||||
continue;
|
||||
Console.Write(message.Content);
|
||||
output += message.Content;
|
||||
|
||||
_logger.LogInformation(message.Content);
|
||||
|
||||
await onMessageReceived(new RoleDialogModel(message.Role.ToString(), message.Content));
|
||||
}
|
||||
|
||||
output = "";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private ChatCompletionsOptions PrepareOptions(Agent agent, List<RoleDialogModel> conversations)
|
||||
{
|
||||
var chatCompletionsOptions = new ChatCompletionsOptions();
|
||||
|
||||
if (!string.IsNullOrEmpty(agent.Instruction))
|
||||
{
|
||||
chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, agent.Instruction));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(agent.Knowledges))
|
||||
{
|
||||
chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, agent.Knowledges));
|
||||
}
|
||||
|
||||
var samples = GetChatSamples(agent.Samples);
|
||||
foreach (var message in samples)
|
||||
{
|
||||
chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content));
|
||||
}
|
||||
|
||||
var functions = GetFunctions(agent.Functions);
|
||||
foreach (var function in functions)
|
||||
{
|
||||
chatCompletionsOptions.Functions.Add(new FunctionDefinition
|
||||
{
|
||||
Name = function.Name,
|
||||
Description = function.Description,
|
||||
Parameters = BinaryData.FromObjectAsJson(function.Parameters)
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var message in conversations)
|
||||
{
|
||||
if (message.Role == ChatRole.Function)
|
||||
{
|
||||
chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content)
|
||||
{
|
||||
Name = message.FunctionName
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content));
|
||||
}
|
||||
}
|
||||
|
||||
// https://community.openai.com/t/cheat-sheet-mastering-temperature-and-top-p-in-chatgpt-api-a-few-tips-and-tricks-on-controlling-the-creativity-deterministic-output-of-prompt-responses/172683
|
||||
chatCompletionsOptions.Temperature = 0.5f;
|
||||
chatCompletionsOptions.NucleusSamplingFactor = 0.5f;
|
||||
|
||||
var convSetting = _services.GetRequiredService<ConversationSetting>();
|
||||
if (convSetting.ShowVerboseLog)
|
||||
{
|
||||
var verbose = string.Join("\n", chatCompletionsOptions.Messages.Select(x =>
|
||||
{
|
||||
return x.Role == ChatRole.Function ?
|
||||
$"{x.Role}: {x.Name} {x.Content}" :
|
||||
$"{x.Role}: {x.Content}";
|
||||
}));
|
||||
_logger.LogInformation(verbose);
|
||||
}
|
||||
|
||||
return chatCompletionsOptions;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Plugin.AzureOpenAI.Settings;
|
||||
|
||||
public class AzureOpenAiSettings
|
||||
|
|
@ -6,4 +8,6 @@ public class AzureOpenAiSettings
|
|||
public string Endpoint { get; set; } = string.Empty;
|
||||
public DeploymentModelSetting DeploymentModel { get; set; }
|
||||
= new DeploymentModelSetting();
|
||||
|
||||
public GPT4Settings GPT4 { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,15 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Docnet.Core" Version="2.5.0-alpha.1" />
|
||||
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.2.0" />
|
||||
<PackageReference Include="Magick.NET.Core" Version="13.2.0" />
|
||||
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.7.0.20230115" />
|
||||
<PackageReference Include="Sdcb.PaddleInference" Version="2.4.1.3" />
|
||||
<PackageReference Include="Sdcb.PaddleInference.runtime.win64.mkl" Version="2.5.1" />
|
||||
<PackageReference Include="Sdcb.PaddleOCR" Version="2.6.0.5" />
|
||||
<PackageReference Include="Sdcb.PaddleOCR.Models.LocalV3" Version="2.6.0.5" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.0-preview.7.23375.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
using BotSharp.Abstraction.Knowledges;
|
||||
using BotSharp.Abstraction.Plugins;
|
||||
using BotSharp.Plugin.PaddleSharp.Providers;
|
||||
using BotSharp.Plugin.PaddleSharp.Settings;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
|
|
@ -9,6 +12,9 @@ public class PaddleSharpPlugin : IBotSharpPlugin
|
|||
{
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
|
||||
var settings = new PaddleSharpSettings();
|
||||
config.Bind("PaddleSharp", settings);
|
||||
services.AddSingleton(x => settings);
|
||||
services.AddSingleton<IPdf2TextConverter, Pdf2TextConverter>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Sdcb.PaddleOCR;
|
||||
using Sdcb.PaddleOCR.Models;
|
||||
using Sdcb.PaddleInference;
|
||||
using Sdcb.PaddleOCR.Models.LocalV3;
|
||||
using OpenCvSharp;
|
||||
using System.Threading.Tasks;
|
||||
using BotSharp.Abstraction.Knowledges;
|
||||
using BotSharp.Plugin.PaddleSharp.Settings;
|
||||
|
||||
namespace BotSharp.Plugin.PaddleSharp.Providers;
|
||||
|
||||
public class PaddleOcrConverter : IPaddleOcrConverter
|
||||
{
|
||||
private FullOcrModel _paddleFullOcrmodel;
|
||||
private QueuedPaddleOcrAll _allModel;
|
||||
private readonly PaddleSharpSettings _paddleSharpSettings;
|
||||
|
||||
public PaddleOcrConverter(FullOcrModel paddleFullOcrmodel, QueuedPaddleOcrAll allModel, PaddleSharpSettings paddleSharpSettings)
|
||||
{
|
||||
_paddleFullOcrmodel = paddleFullOcrmodel;
|
||||
_allModel = allModel;
|
||||
_paddleSharpSettings = paddleSharpSettings;
|
||||
}
|
||||
|
||||
private void LoadModel()
|
||||
{
|
||||
_allModel = new(() => new PaddleOcrAll(_paddleFullOcrmodel, _paddleSharpSettings.device)
|
||||
{
|
||||
AllowRotateDetection = _paddleSharpSettings.allowRotateDetection,
|
||||
Enable180Classification = _paddleSharpSettings.enable180Classification,
|
||||
}, consumerCount: _paddleSharpSettings.consumerCount, boundedCapacity: _paddleSharpSettings.boundedCapacity);
|
||||
}
|
||||
|
||||
private void DisposeModel()
|
||||
{
|
||||
_allModel.Dispose();
|
||||
}
|
||||
|
||||
public async Task<string> ConvertImageToText(string loadPath)
|
||||
{
|
||||
_allModel = new(() => new PaddleOcrAll(_paddleFullOcrmodel, _paddleSharpSettings.device)
|
||||
{
|
||||
AllowRotateDetection = _paddleSharpSettings.allowRotateDetection,
|
||||
Enable180Classification = _paddleSharpSettings.enable180Classification,
|
||||
}, consumerCount: _paddleSharpSettings.consumerCount, boundedCapacity: _paddleSharpSettings.boundedCapacity);
|
||||
|
||||
var contents = "";
|
||||
using (Mat src = Cv2.ImRead(loadPath))
|
||||
{
|
||||
PaddleOcrResult result = await _allModel.Run(src);
|
||||
|
||||
foreach (PaddleOcrResultRegion region in result.Regions)
|
||||
{
|
||||
if (region.Score > _paddleSharpSettings.acceptScore)
|
||||
{
|
||||
contents += region.Text + " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_allModel.Dispose();
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using ImageMagick;
|
||||
using OpenCvSharp;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Sdcb.PaddleInference;
|
||||
using Sdcb.PaddleOCR.Models;
|
||||
using Sdcb.PaddleOCR.Models.LocalV3;
|
||||
using Sdcb.PaddleOCR;
|
||||
using System.Threading.Tasks;
|
||||
using BotSharp.Abstraction.Knowledges;
|
||||
using System.Linq;
|
||||
using Docnet;
|
||||
using Docnet.Core.Models;
|
||||
using Docnet.Core;
|
||||
using Docnet.Core.Converters;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
using BotSharp.Plugin.PaddleSharp.Settings;
|
||||
|
||||
namespace BotSharp.Plugin.PaddleSharp.Providers;
|
||||
|
||||
public class Pdf2TextConverter : IPdf2TextConverter
|
||||
{
|
||||
private Dictionary<int, string> _mappings = new Dictionary<int, string>();
|
||||
private FullOcrModel _model = LocalFullModels.EnglishV3;
|
||||
private PaddleSharpSettings _paddleSharpSettings;
|
||||
public Pdf2TextConverter(PaddleSharpSettings paddleSharpSettings)
|
||||
{
|
||||
_paddleSharpSettings = paddleSharpSettings;
|
||||
}
|
||||
|
||||
public async Task<string> ConvertPdfToText(IFormFile formFile, int? startPageNum, int? endPageNum)
|
||||
{
|
||||
await ConvertPdfToLocalImagesAsync(formFile, startPageNum, endPageNum);
|
||||
return await LocalImageToTextsAsync();
|
||||
}
|
||||
|
||||
private async Task<string> LocalImageToTextsAsync()
|
||||
{
|
||||
string loadPath;
|
||||
string contents = "";
|
||||
if (!Directory.Exists(_paddleSharpSettings.tempFolderPath))
|
||||
{
|
||||
throw new Exception("No local temporary files found! Please convert PDF to local images first by \"ConvertPdfToLocalImages\".");
|
||||
}
|
||||
|
||||
QueuedPaddleOcrAll all = new(() => new PaddleOcrAll(_model, PaddleDevice.Mkldnn())
|
||||
{
|
||||
AllowRotateDetection = true,
|
||||
Enable180Classification = false,
|
||||
}, consumerCount: _paddleSharpSettings.consumerCount, boundedCapacity: _paddleSharpSettings.boundedCapacity);
|
||||
|
||||
|
||||
foreach (var item in _mappings.OrderBy(x => x.Key))
|
||||
{
|
||||
loadPath = Path.Combine(_paddleSharpSettings.tempFolderPath, item.Value);
|
||||
|
||||
using (Mat src = Cv2.ImRead(loadPath))
|
||||
{
|
||||
PaddleOcrResult result = await all.Run(src);
|
||||
|
||||
foreach (PaddleOcrResultRegion region in result.Regions)
|
||||
{
|
||||
if (region.Score > _paddleSharpSettings.acceptScore)
|
||||
{
|
||||
contents += region.Text + " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
|
||||
private static void AddBytes(Bitmap bmp, byte[] rawBytes)
|
||||
{
|
||||
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
|
||||
|
||||
var bmpData = bmp.LockBits(rect, ImageLockMode.WriteOnly, bmp.PixelFormat);
|
||||
var pNative = bmpData.Scan0;
|
||||
|
||||
Marshal.Copy(rawBytes, 0, pNative, rawBytes.Length);
|
||||
bmp.UnlockBits(bmpData);
|
||||
}
|
||||
|
||||
public void DocnetConverter(string filePath, int width = 1080, int height = 1920)
|
||||
{
|
||||
var pageSettings = new PageDimensions(width, height);
|
||||
|
||||
// using (var docReader = DocLib.Instance.GetDocReader("C:\\Users\\104199\\Postman\\files\\WM2077CW.pdf", new PageDimensions(1080, 1920)))
|
||||
using (var docReader = DocLib.Instance.GetDocReader(filePath, pageSettings))
|
||||
{
|
||||
using (var pageReader = docReader.GetPageReader(17))
|
||||
{
|
||||
var rawBytes = pageReader.GetImage();
|
||||
var pageWidth = pageReader.GetPageWidth();
|
||||
var pageHeight = pageReader.GetPageHeight();
|
||||
var characters = pageReader.GetCharacters();
|
||||
|
||||
using (var bmp = new Bitmap(pageWidth, pageHeight, PixelFormat.Format32bppArgb))
|
||||
{
|
||||
AddBytes(bmp, rawBytes);
|
||||
|
||||
using (var imageStream = new MemoryStream())
|
||||
{
|
||||
//saving and exporting
|
||||
bmp.Save(imageStream, ImageFormat.Png);
|
||||
System.IO.File.WriteAllBytes(filePath, imageStream.ToArray());
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ConvertPdfToLocalImagesAsync(IFormFile formFile, int? startPageNum, int? endPageNum)
|
||||
{
|
||||
string rootFileName;
|
||||
|
||||
var filePath = Path.GetTempFileName();
|
||||
|
||||
using (var stream = System.IO.File.Create(filePath))
|
||||
{
|
||||
await formFile.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
using var images = new MagickImageCollection();
|
||||
// _magicReadSettings.Density = new Density((double)300);
|
||||
/*
|
||||
using var images = new MagickImageCollection();
|
||||
MagickNET.SetGhostscriptDirectory("C:\\Users\\104199\\Downloads\\ghostpcl-10.01.2-win64\\ghostpcl-10.01.2-win64");
|
||||
|
||||
images.Read("C:\\Users\\104199\\Postman\\files\\page12.pdf", new MagickReadSettings
|
||||
{
|
||||
Density = new Density(300, 300)
|
||||
});
|
||||
*/
|
||||
images.Read(filePath, new MagickReadSettings
|
||||
{
|
||||
Density = new Density(300, 300)
|
||||
});
|
||||
|
||||
if (images.Count == 0)
|
||||
{
|
||||
throw new Exception("PDF loading failed. Please check if the PDF format is correct!");
|
||||
}
|
||||
|
||||
startPageNum = startPageNum.HasValue ? startPageNum : 1;
|
||||
endPageNum = endPageNum.HasValue ? endPageNum : images.Count;
|
||||
|
||||
for (int page = (int)startPageNum; page <= (int)endPageNum; page++)
|
||||
{
|
||||
string tempFileName = Path.GetRandomFileName();
|
||||
tempFileName = Path.ChangeExtension(tempFileName, "png");
|
||||
rootFileName = Path.Combine(_paddleSharpSettings.tempFolderPath, tempFileName);
|
||||
|
||||
// image.Format = MagickFormat.Jpg; Set to "Jpg" format
|
||||
images[page].Write(rootFileName);
|
||||
_mappings[page] = rootFileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Sdcb.PaddleOCR;
|
||||
using ImageMagick;
|
||||
using Sdcb.PaddleOCR.Models;
|
||||
using System.IO;
|
||||
using Sdcb.PaddleInference;
|
||||
|
||||
namespace BotSharp.Plugin.PaddleSharp.Settings
|
||||
{
|
||||
public class PaddleSharpSettings
|
||||
{
|
||||
public MagickReadSettings magickReadSettings { get; set; }
|
||||
public PaddleOcrAll paddleOcrAll { get; set; }
|
||||
public string tempFolderPath { get; set; } = Path.GetTempPath();
|
||||
public PaddleOcrAll paddleSettings { get; set; }
|
||||
public MagickReadSettings magicReadSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
return magicReadSettings;
|
||||
}
|
||||
set
|
||||
{
|
||||
magicReadSettings.Density = new Density(300, 300);
|
||||
}
|
||||
}
|
||||
public int consumerCount { get; set; } = 1;
|
||||
public int boundedCapacity { get; set; } = 64;
|
||||
public double acceptScore { get; set; }
|
||||
public Action<PaddleConfig> device { get; set; } = PaddleDevice.Mkldnn();
|
||||
public bool allowRotateDetection { get; set; }
|
||||
public bool enable180Classification { get; set; }
|
||||
public bool paddleModel { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -81,14 +81,15 @@
|
|||
"WeixinAppSecret": "#{WeixinAppSecret}#"
|
||||
},
|
||||
|
||||
"KnowledgeBase": {
|
||||
"VectorDb": "MemVectorDatabase",
|
||||
// "VectorDb": "QdrantDb",
|
||||
"TextEmbedding": "fastTextEmbeddingProvider",
|
||||
// "TextEmbedding": "LLamaSharp.TextEmbeddingProvider",
|
||||
"TextCompletion": "AzureOpenAI.Providers.TextCompletionProvider"
|
||||
// "TextCompletion": "LLamaSharp.TextCompletionProvider"
|
||||
},
|
||||
"KnowledgeBase": {
|
||||
"VectorDb": "MemVectorDatabase",
|
||||
// "VectorDb": "QdrantDb",
|
||||
"TextEmbedding": "fastTextEmbeddingProvider",
|
||||
// "TextEmbedding": "LLamaSharp.TextEmbeddingProvider",
|
||||
"TextCompletion": "AzureOpenAI.Providers.TextCompletionProvider",
|
||||
// "TextCompletion": "LLamaSharp.TextCompletionProvider",
|
||||
"Pdf2TextConverter": "PaddleSharp.Providers.Pdf2TextConverter"
|
||||
},
|
||||
|
||||
"PluginLoader": {
|
||||
"Assemblies": [
|
||||
|
|
|
|||
Loading…
Reference in a new issue