Add PaddleOcrConverter

This commit is contained in:
Wenbo Cao 2023-08-28 10:28:52 -05:00
commit 780bb4ed79
19 changed files with 164 additions and 77 deletions

View file

@ -1,3 +1,3 @@
# Prompt Engineering
LLM uses prompt as input, and the model produces different outputs according to the input.
LLM uses prompt as input, and the model produces different outputs according to the input.

View file

@ -1,3 +1,9 @@
# Template
We can define the prompt as a template, and the template can be changed according to variables, so that a instruction file can be used to generate a dynamic prompt.
We can define the prompt as a template, and the template can be changed according to variables, so that a instruction file can be used to generate a dynamic prompt.
`BotSharp` uses [liquid](https://shopify.github.io/liquid/) templates to support various complex dynamic prompt engineering.
`ITemplateRender`
```csharp
bool Render(Agent agent, Dictionary<string, object> dict)
```

View file

@ -4,4 +4,5 @@ public interface IAgentRouting
{
Task<Agent> LoadRouter();
Task<Agent> LoadCurrentAgent();
RoutingRecord[] GetRoutingRecords();
}

View file

@ -2,19 +2,25 @@ using System.Text.Json.Serialization;
namespace BotSharp.Abstraction.Agents.Models;
public class RoutingTable
public class RoutingRecord
{
[JsonPropertyName("agent_id")]
public string AgentId { get; set; }
[JsonPropertyName("name")]
public string AgentName { get; set; }
public string Name { get; set; }
[JsonPropertyName("description")]
public string Description { get; set; }
[JsonPropertyName("required")]
public List<string> RequiredFields { get; set; }
[JsonPropertyName("redirect_to")]
public string RedirectTo { get; set; }
public override string ToString()
{
return AgentName;
return Name;
}
}

View file

@ -8,4 +8,5 @@ public class AgentSettings
public string RouterId { get; set; }
public string DataDir { get; set; }
public string TemplateFormat { get; set; }
public int MaxRecursiveDepth { get; set; } = 3;
}

View file

@ -4,7 +4,7 @@
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<VersionPrefix>0.9.0</VersionPrefix>
<VersionPrefix>0.9.4</VersionPrefix>
<PackageIcon>Icon.png</PackageIcon>
</PropertyGroup>

View file

@ -1,24 +0,0 @@
using System.Text.Json.Serialization;
namespace BotSharp.Abstraction.Functions.Models;
public class FunctionExecutionValidationResult
{
public FunctionExecutionValidationResult()
{
}
public FunctionExecutionValidationResult(string validationStatus, string? validationMessage = null)
{
ValidationStatus = validationStatus;
ValidationMessage = validationMessage;
}
[JsonPropertyName("validation_status")]
public string ValidationStatus { get; set; }
[JsonPropertyName("validation_message")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string ValidationMessage { get; set; }
}

View file

@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.Templating;
public interface ITemplateRender
{
bool Render(Agent agent, Dictionary<string, object> dict);
}

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Agents.Models;
using Fluid;
namespace BotSharp.Core.Agents.Services;
@ -7,13 +6,14 @@ public abstract class AgentHookBase : IAgentHook
{
protected Agent _agent;
public Agent Agent => _agent;
private static readonly FluidParser _parser = new FluidParser();
private readonly IServiceProvider _services;
protected readonly IServiceProvider _services;
protected readonly AgentSettings _settings;
public AgentHookBase(IServiceProvider services)
public AgentHookBase(IServiceProvider services, AgentSettings settings)
{
_services = services;
_settings = settings;
}
public void SetAget(Agent agent)
@ -28,27 +28,7 @@ public abstract class AgentHookBase : IAgentHook
public virtual bool OnInstructionLoaded(string template, Dictionary<string, object> dict)
{
if (_parser.TryParse(template, out var t, out var error))
{
PopulateStateTokens(dict);
var context = new TemplateContext(dict);
_agent.Instruction = t.Render(context);
return true;
}
else
{
return false;
}
}
private void PopulateStateTokens(Dictionary<string, object> dict)
{
var stateService = _services.GetRequiredService<IConversationStateService>();
var state = stateService.Load();
foreach (var t in state)
{
dict[t.Key] = t.Value;
}
return true;
}
public virtual bool OnFunctionsLoaded(ref string functions)

View file

@ -1,5 +1,5 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Agents.Models;
using System.IO;
namespace BotSharp.Core.Agents.Services;
@ -42,4 +42,12 @@ public class AgentRouter : IAgentRouting
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");
return JsonSerializer.Deserialize<RoutingRecord[]>(File.ReadAllText(filePath));
}
}

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Core.Templating;
namespace BotSharp.Core.Agents.Services;
@ -15,6 +16,8 @@ public partial class AgentService
}
var agent = await GetAgent(id);
var templateDict = new Dictionary<string, object>();
PopulateState(templateDict);
// After agent is loaded
foreach (var hook in hooks)
@ -23,7 +26,7 @@ public partial class AgentService
if (!string.IsNullOrEmpty(agent.Instruction))
{
hook.OnInstructionLoaded(agent.Instruction, new Dictionary<string, object>());
hook.OnInstructionLoaded(agent.Instruction, templateDict);
}
if (!string.IsNullOrEmpty(agent.Functions))
@ -41,8 +44,22 @@ public partial class AgentService
hook.OnAgentLoaded(agent);
}
// render liquid template
var render = _services.GetRequiredService<TemplateRender>();
render.Render(agent, templateDict);
_logger.LogInformation($"Loaded agent {agent}.");
return agent;
}
private void PopulateState(Dictionary<string, object> dict)
{
var stateService = _services.GetRequiredService<IConversationStateService>();
var state = stateService.Load();
foreach (var t in state)
{
dict[t.Key] = t.Value;
}
}
}

View file

@ -4,7 +4,7 @@
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>10.0</LangVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<VersionPrefix>0.9.0</VersionPrefix>
<VersionPrefix>0.9.4</VersionPrefix>
</PropertyGroup>
<PropertyGroup Label="Globals">

View file

@ -1,5 +1,7 @@
using BotSharp.Abstraction.Functions;
using BotSharp.Core.Functions;
using BotSharp.Core.Hooks;
using BotSharp.Core.Templating;
using BotSharp.Core.Plugins.Knowledges.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
@ -36,10 +38,18 @@ public static class BotSharpServiceCollectionExtensions
RegisterPlugins(services, config);
// Register template render
services.AddSingleton<TemplateRender>();
// Register router
services.AddScoped<IAgentRouting, AgentRouter>();
// Register function callback
services.AddScoped<IFunctionCallback, RouteToAgentFn>();
// Register Hooks
services.AddScoped<IAgentHook, AgentHook>();
return services;
}

View file

@ -29,9 +29,17 @@ public partial class ConversationService
await hook.OnFunctionExecuting(msg);
}
// Execute function
await fn.Execute(msg);
try
{
// Execute function
await fn.Execute(msg);
}
catch (Exception ex)
{
msg.ExecutionResult = ex.Message;
_logger.LogError(msg.ExecutionResult);
}
// After functions have been executed
foreach (var hook in hooks)
{

View file

@ -7,13 +7,13 @@ namespace BotSharp.Core.Conversations.Services;
public partial class ConversationService
{
const int maxRecursiveDepth = 3;
int currentRecursiveDepth = 0;
private async Task<bool> GetChatCompletionsAsyncRecursively(IChatCompletion chatCompletion,
string conversationId,
Agent agent,
List<RoleDialogModel> wholeDialogs,
int maxRecursiveDepth,
Func<RoleDialogModel, Task> onMessageReceived,
Func<RoleDialogModel, Task> onFunctionExecuting,
Func<RoleDialogModel, Task> onFunctionExecuted)
@ -83,6 +83,7 @@ public partial class ConversationService
conversationId,
agent,
wholeDialogs,
maxRecursiveDepth,
onMessageReceived,
onFunctionExecuting,
onFunctionExecuted);

View file

@ -65,11 +65,14 @@ public partial class ConversationService
await hook.BeforeCompletion();
}
var agentSettings = _services.GetRequiredService<AgentSettings>();
var chatCompletion = GetChatCompletion();
var result = await GetChatCompletionsAsyncRecursively(chatCompletion,
conversationId,
agent,
wholeDialogs,
agentSettings.MaxRecursiveDepth,
onMessageReceived,
onFunctionExecuting,
onFunctionExecuted);

View file

@ -28,7 +28,12 @@ public class RouteToAgentFn : IFunctionCallback
}
else
{
if (!HasMissingRequiredField(message, out var agentId))
var missingfield = HasMissingRequiredField(message, out var agentId);
if (missingfield && message.CurrentAgentId != agentId)
{
message.CurrentAgentId = agentId;
}
else
{
message.CurrentAgentId = agentId;
message.ExecutionResult = $"Routed to {args.AgentName}";
@ -45,23 +50,23 @@ public class RouteToAgentFn : IFunctionCallback
private bool HasMissingRequiredField(RoleDialogModel message, out string agentId)
{
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 routes = GetRoutingTable();
var agent = routes.FirstOrDefault(x => x.AgentName.ToLower() == args.AgentName.ToLower());
if (agent == null)
if (routingRule == null)
{
agentId = message.CurrentAgentId;
message.ExecutionResult = $"Can't find agent {args.AgentName}";
return true;
}
agentId = agent.AgentId;
agentId = routingRule.AgentId;
// Check required fields
var jo = JsonSerializer.Deserialize<object>(message.FunctionArgs);
bool hasMissingField = false;
foreach (var field in agent.RequiredFields)
foreach (var field in routingRule.RequiredFields)
{
if (jo is JsonElement root)
{
@ -71,17 +76,21 @@ public class RouteToAgentFn : IFunctionCallback
hasMissingField = true;
break;
}
else if (root.EnumerateObject().Any(x => x.Name == field) &&
string.IsNullOrEmpty(root.EnumerateObject().FirstOrDefault(x => x.Name == field).Value.ToString()))
{
message.ExecutionResult = $"missing {field}.";
hasMissingField = true;
break;
}
}
}
if (hasMissingField && !string.IsNullOrEmpty(routingRule.RedirectTo))
{
agentId = routingRule.RedirectTo;
}
return hasMissingField;
}
private RoutingTable[] GetRoutingTable()
{
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<RoutingTable[]>(File.ReadAllText(filePath));
}
}

View file

@ -0,0 +1,16 @@
namespace BotSharp.Core.Hooks;
public class AgentHook : AgentHookBase
{
public AgentHook(IServiceProvider services, AgentSettings settings)
: base(services, settings)
{
}
public override bool OnInstructionLoaded(string template, Dictionary<string, object> dict)
{
var router = _services.GetRequiredService<IAgentRouting>();
dict["routing_records"] = router.GetRoutingRecords();
return true;
}
}

View file

@ -0,0 +1,39 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Templating;
using Fluid;
using Microsoft.Extensions.Options;
namespace BotSharp.Core.Templating;
public class TemplateRender : ITemplateRender
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
private static readonly FluidParser _parser = new FluidParser();
private TemplateOptions _options;
public TemplateRender(IServiceProvider services, ILogger<TemplateRender> logger)
{
_services = services;
_logger = logger;
_options = new TemplateOptions();
_options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.CamelCase;
_options.MemberAccessStrategy.Register<RoutingRecord>();
}
public bool Render(Agent agent, Dictionary<string, object> dict)
{
var template = agent.Instruction;
if (_parser.TryParse(template, out var t, out var error))
{
var context = new TemplateContext(dict, _options);
agent.Instruction = t.Render(context);
return true;
}
else
{
return false;
}
}
}