TwostagePlanner practice

This commit is contained in:
Haiping Chen 2024-07-25 17:11:51 -05:00
parent b07ec2736e
commit 58fb290fc9
37 changed files with 460 additions and 22 deletions

View file

@ -3,6 +3,6 @@ namespace BotSharp.Abstraction.Conversations.Settings;
public class RateLimitSetting
{
public int MaxConversationPerDay { get; set; } = 100;
public int MaxInputLengthPerRequest { get; set; } = 512;
public int MaxInputLengthPerRequest { get; set; } = 1024;
public int MinTimeSecondsBetweenMessages { get; set; } = 2;
}

View file

@ -0,0 +1,9 @@
namespace BotSharp.Abstraction.Planning;
/// <summary>
/// Planning process for Task Agent
/// </summary>
public class ITaskPlanner
{
}

View file

@ -6,7 +6,7 @@ namespace BotSharp.Abstraction.Routing.Planning;
/// Task breakdown and execution plan
/// https://www.promptingguide.ai/techniques/cot
/// </summary>
public interface IPlaner
public interface IRoutingPlaner
{
Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId, List<RoleDialogModel> dialogs);
Task<bool> AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs);

View file

@ -182,6 +182,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Planning\" />
<Folder Include="Translation\Models\" />
</ItemGroup>

View file

@ -1,7 +1,7 @@
using BotSharp.Abstraction.Messaging;
using BotSharp.Abstraction.Messaging.Models.RichContent;
using BotSharp.Abstraction.Routing.Settings;
using System.Drawing;
using BotSharp.Core.Routing.Planning;
namespace BotSharp.Core.Conversations.Services;
@ -76,9 +76,14 @@ public partial class ConversationService
// Routing with reasoning
var settings = _services.GetRequiredService<RoutingSettings>();
response = agent.Type == AgentType.Routing ?
await routing.InstructLoop(message, dialogs, onFunctionExecuting) :
await routing.InstructDirect(agent, message);
if (agent.Type == AgentType.Routing)
{
response = await routing.InstructLoop(message, dialogs, onFunctionExecuting);
}
else
{
response = await routing.InstructDirect(agent, message);
}
routing.ResetRecursiveCounter();
}

View file

@ -6,7 +6,7 @@ namespace BotSharp.Core.Routing.Planning;
/// <summary>
/// Human feedback based planner
/// </summary>
public class HFPlanner : IPlaner
public class HFPlanner : IRoutingPlaner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;

View file

@ -5,7 +5,7 @@ using BotSharp.Abstraction.Templating;
namespace BotSharp.Core.Routing.Planning;
public class NaivePlanner : IPlaner
public class NaivePlanner : IRoutingPlaner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;

View file

@ -10,7 +10,7 @@ using System.Drawing;
namespace BotSharp.Core.Routing.Planning;
public class SequentialPlanner : IPlaner
public class SequentialPlanner : IRoutingPlaner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;

View file

@ -7,7 +7,7 @@ using System.IO;
namespace BotSharp.Core.Routing.Planning;
public partial class TwoStagePlanner : IPlaner
public partial class TwoStagePlanner : IRoutingPlaner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;

View file

@ -35,9 +35,9 @@ public class RoutingPlugin : IBotSharpPlugin
services.AddScoped<IRoutingService, RoutingService>();
services.AddScoped<IAgentHook, RoutingAgentHook>();
services.AddScoped<IPlaner, NaivePlanner>();
services.AddScoped<IPlaner, HFPlanner>();
services.AddScoped<IPlaner, SequentialPlanner>();
services.AddScoped<IPlaner, TwoStagePlanner>();
services.AddScoped<IRoutingPlaner, NaivePlanner>();
services.AddScoped<IRoutingPlaner, HFPlanner>();
services.AddScoped<IRoutingPlaner, SequentialPlanner>();
services.AddScoped<IRoutingPlaner, TwoStagePlanner>();
}
}

View file

@ -7,11 +7,11 @@ namespace BotSharp.Core.Routing;
public partial class RoutingService
{
public IPlaner GetPlanner(Agent router)
public IRoutingPlaner GetPlanner(Agent router)
{
var rule = router.RoutingRules.FirstOrDefault(x => x.Type == RuleType.Planner);
var planner = _services.GetServices<IPlaner>().
var planner = _services.GetServices<IRoutingPlaner>().
FirstOrDefault(x => x.GetType().Name.EndsWith(rule.Field));
if (planner == null)

View file

@ -13,3 +13,7 @@ Task Solutions:
{{ k }}
{% endfor %}
{%- endif %}
=====
Task description:
{{ task_description }}

View file

@ -8,7 +8,7 @@ public class AnthropicPlugin : IBotSharpPlugin
{
public string Id => "012119da-8367-4be8-9a75-ab6ae55071e6";
public string Name => "Anthropic AI";
public string Description => "Anthropic is an AI safety and research company";
public string Description => "Anthropic is an AI company that's working to build reliable and steerable AI systems.";
public string? IconUrl => "https://www.anthropic.com/images/icons/safari-pinned-tab.svg";
public void RegisterDI(IServiceCollection services, IConfiguration config)

View file

@ -18,6 +18,7 @@
<ItemGroup>
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\agent.json" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\functions\confirm_knowledge_persistence.json" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\functions\memorize_knowledge.json" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\instruction.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\knowledge_retrieval.fn.liquid" />
@ -27,6 +28,9 @@
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\functions\confirm_knowledge_persistence.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\functions\memorize_knowledge.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View file

@ -0,0 +1,67 @@
using BotSharp.Abstraction.Functions;
using BotSharp.Abstraction.Messaging.Enums;
using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
using BotSharp.Abstraction.Messaging.Models.RichContent;
using BotSharp.Abstraction.Messaging;
namespace BotSharp.Plugin.KnowledgeBase.Functions;
public class ConfirmKnowledgePersistenceFn : IFunctionCallback
{
public string Name => "confirm_knowledge_persistence";
private readonly IServiceProvider _services;
private readonly KnowledgeBaseSettings _settings;
public ConfirmKnowledgePersistenceFn(IServiceProvider services, KnowledgeBaseSettings settings)
{
_services = services;
_settings = settings;
}
public async Task<bool> Execute(RoleDialogModel message)
{
var args = JsonSerializer.Deserialize<ExtractedKnowledge>(message.FunctionArgs ?? "{}");
message.Content = $"This knowledge is new to me, should I save it to my memory?";
message.RichContent = BuildRichContent(message.Content);
message.StopCompletion = true;
return true;
}
private RichContent<IRichMessage> BuildRichContent(string text)
{
var states = _services.GetRequiredService<IConversationStateService>();
var conversationId = states.GetConversationId();
var recipient = new Recipient { Id = conversationId };
var res = new RichContent<IRichMessage>
{
Recipient = recipient,
FillPostback = true,
Editor = EditorTypeEnum.None,
Message = new ButtonTemplateMessage
{
Text = text,
Buttons =
[
new ElementButton
{
Type = "text",
Title = "Sure, memorize it",
Payload = "Yes, save it to your memory",
IsPrimary = true
},
new ElementButton
{
Type = "text",
Title = "No, skip the useless information",
Payload = "No, skip it"
}
]
}
};
return res;
}
}

View file

@ -33,7 +33,15 @@ public class KnowledgeRetrievalFn : IFunctionCallback
var id = Utilities.HashTextMd5(args.Question);
var knowledges = await vectorDb.Search("lessen", vector[0], "answer");
message.Content = string.Join("\r\n\r\n=====\r\n", knowledges);
if (knowledges.Count > 0)
{
message.Content = string.Join("\r\n\r\n=====\r\n", knowledges);
}
else
{
message.Content = $"I didn't find any useful knowledge related to [{args.Question}]. \r\nCan you tell me the instruction and I'll memorize it.";
message.StopCompletion = true;
}
return true;
}

View file

@ -40,7 +40,7 @@ public class MemorizeKnowledgeFn : IFunctionCallback
{ "answer", args.Answer }
});
message.Content = $"Save result: {(result ? "success" : "failed")}";
message.Content = result ? "Saved to my brain" : "I forgot it";
return true;
}

View file

@ -0,0 +1,14 @@
{
"name": "confirm_knowledge_persistence",
"description": "Confirm with user whether need to save this knowledge to memory",
"parameters": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "knowledge topic"
}
},
"required": [ "question" ]
}
}

View file

@ -1,6 +1,6 @@
{
"name": "memorize_knowledge",
"description": "Retrieve related domain knowledge to handle user request",
"description": "save the knowledge to memory",
"parameters": {
"type": "object",
"properties": {

View file

@ -1,2 +1,5 @@
You an avid learner who is eager to learn and acquire knowledge from the conversation.
You're good at learning useful domain knowledge and experience by interacting with user.
You are an avid learner who is eager to learn and acquire knowledge from the conversation.
You're good at learning useful domain knowledge and experience by interacting with user.
Summarize the answer or solution in a concise steps.
Confirm with user if you're going to save the knowledge to memory.
If user confirmed, use tool of memorize_knowledge to save it.

View file

@ -0,0 +1,39 @@
<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\plan_primary_stage.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\plan_secondary_stage.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\plan_primary_stage.fn.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\plan_secondary_stage.fn.liquid" />
</ItemGroup>
<ItemGroup>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\plan_secondary_stage.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\plan_primary_stage.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\plan_secondary_stage.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\plan_primary_stage.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,6 @@
namespace BotSharp.Plugin.Planner.Enums;
public class UtilityName
{
public const string TwoStagePlanner = "two-stage-planner";
}

View file

@ -0,0 +1,42 @@
using Amazon.Runtime.Internal.Transform;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Functions;
using BotSharp.Abstraction.Templating;
using BotSharp.Plugin.Planner.TwoStaging.Models;
using System.Threading.Tasks;
namespace BotSharp.Plugin.Planner.Functions;
public class PrimaryStagePlanFn : IFunctionCallback
{
public string Name => "plan_primary_stage";
private readonly IServiceProvider _services;
public PrimaryStagePlanFn(IServiceProvider services)
{
_services = services;
}
public async Task<bool> Execute(RoleDialogModel message)
{
var task = JsonSerializer.Deserialize<PrimaryRequirementRequest>(message.FunctionArgs);
if (!task.HasKnowledgeReference)
{
message.Content = "Search knowledge base for the solution instructions";
return false;
}
var agentService = _services.GetRequiredService<IAgentService>();
var aiAssistant = await agentService.GetAgent(BuiltInAgentId.AIAssistant);
var template = aiAssistant.Templates.First(x => x.Name == "planner_prompt.two_stage.1st.plan").Content;
var render = _services.GetRequiredService<ITemplateRender>();
render.Render(template, new Dictionary<string, object>
{
{ "relevant_knowledges", message.Content }
});
//message.Content = task.Requirements;
//message.Content += "\r\n\r\n=====\r\nGet the first primary step, plan the secondary steps.";
return true;
}
}

View file

@ -0,0 +1,19 @@
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Functions;
using BotSharp.Plugin.Planner.TwoStaging.Models;
using System.Threading.Tasks;
namespace BotSharp.Plugin.Planner.Functions;
public class SecondaryStagePlanFn : IFunctionCallback
{
public string Name => "plan_secondary_stage";
public async Task<bool> Execute(RoleDialogModel message)
{
var task = JsonSerializer.Deserialize<SecondaryBreakdownTask>(message.FunctionArgs);
message.Content = task.SolutionQuestion;
message.Content += $"\r\n\r\n=====\r\nUse tool of `knowledge_retrieval` for expert instructions.";
return true;
}
}

View file

@ -0,0 +1,68 @@
namespace BotSharp.Plugin.Planner.Hooks;
public class PlannerAgentHook : AgentHookBase
{
public override string SelfId => string.Empty;
public PlannerAgentHook(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(UtilityName.TwoStagePlanner);
if (isConvMode && isEnabled)
{
var (prompt, fn) = GetPromptAndFunction("plan_primary_stage");
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);
}
}
(prompt, fn) = GetPromptAndFunction("plan_secondary_stage");
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(string functionName)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{functionName}.fn"))?.Content ?? string.Empty;
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(functionName));
return (prompt, loadAttachmentFn);
}
}

View file

@ -0,0 +1,9 @@
namespace BotSharp.Plugin.Planner.Hooks;
public class PlannerUtilityHook : IAgentUtilityHook
{
public void AddUtilities(List<string> utilities)
{
utilities.Add(UtilityName.TwoStagePlanner);
}
}

View file

@ -0,0 +1,15 @@
namespace BotSharp.Plugin.Planner;
public class PlannerPlugin : IBotSharpPlugin
{
public string Id => "571f71fe-1583-46f2-b577-c8577a0a2903";
public string Name => "AI Planning Plugin";
public string Description => "Provide AI with different planning approaches to improve AI's ability to solve complex problems.";
public string IconUrl => "https://library.ucf.edu/wp-content/uploads/sites/5/2015/03/SC-Planning-Icon-300x290.png";
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
services.AddScoped<IAgentHook, PlannerAgentHook>();
services.AddScoped<IAgentUtilityHook, PlannerUtilityHook>();
}
}

View file

@ -0,0 +1,33 @@
namespace BotSharp.Plugin.Planner.TwoStaging.Models;
public class FirstStagePlan
{
[JsonPropertyName("task_detail")]
public string Task { get; set; } = "";
[JsonPropertyName("reason")]
public string Reason { get; set; } = "";
[JsonPropertyName("step")]
public int Step { get; set; } = -1;
[JsonPropertyName("contain_multiple_steps")]
public bool ContainMultipleSteps { get; set; } = false;
[JsonPropertyName("related_tables")]
public string[] Tables { get; set; } = [];
[JsonPropertyName("related_urls")]
public string[] Urls { get; set; } = [];
[JsonPropertyName("input_args")]
public JsonDocument[] Parameters { get; set; } = [];
[JsonPropertyName("output_results")]
public string[] Results { get; set; } = [];
public override string ToString()
{
return $"STEP {Step}: {Task}";
}
}

View file

@ -0,0 +1,10 @@
namespace BotSharp.Plugin.Planner.TwoStaging.Models;
public class PrimaryRequirementRequest
{
[JsonPropertyName("requirement_detail")]
public string Requirements { get; set; } = null!;
[JsonPropertyName("has_knowledge_reference")]
public bool HasKnowledgeReference { get; set; }
}

View file

@ -0,0 +1,10 @@
namespace BotSharp.Plugin.Planner.TwoStaging.Models;
public class SecondaryBreakdownTask
{
[JsonPropertyName("task_description")]
public string TaskDescription { get; set; } = null!;
[JsonPropertyName("solution_search_question")]
public string SolutionQuestion { get; set; } = null!;
}

View file

@ -0,0 +1,11 @@
namespace BotSharp.Plugin.Planner.TwoStaging;
public partial class TwoStageTaskPlanner : ITaskPlanner
{
private readonly IServiceProvider _services;
public TwoStageTaskPlanner(IServiceProvider services)
{
_services = services;
}
}

View file

@ -0,0 +1,22 @@
global using System;
global using System.Text.Json;
global using System.Text.Json.Serialization;
global using System.Collections.Generic;
global using System.Linq;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using BotSharp.Abstraction.Plugins;
global using BotSharp.Abstraction.Planning;
global using BotSharp.Abstraction.Agents;
global using BotSharp.Abstraction.Agents.Enums;
global using BotSharp.Abstraction.Agents.Models;
global using BotSharp.Abstraction.Agents.Settings;
global using BotSharp.Abstraction.Conversations;
global using BotSharp.Abstraction.Functions.Models;
global using BotSharp.Abstraction.Repositories;
global using BotSharp.Abstraction.Utilities;
global using BotSharp.Plugin.Planner.Hooks;
global using BotSharp.Plugin.Planner.Enums;

View file

@ -0,0 +1,18 @@
{
"name": "plan_primary_stage",
"description": "Extract user's original requirements with detail specifications to make the primary plan, you have to include every important informations.",
"parameters": {
"type": "object",
"properties": {
"requirement_detail": {
"type": "string",
"description": "User original requirements in detail, don't miss any information especially for those line items, values and numbers."
},
"has_knowledge_reference": {
"type": "boolean",
"description": "If there is knowledge retrieved from memory"
}
},
"required": [ "requirement_detail", "has_knowledge_reference" ]
}
}

View file

@ -0,0 +1,18 @@
{
"name": "plan_secondary_stage",
"description": "Based on the main tasks of the first phase, plan the implementation steps of the second phase.",
"parameters": {
"type": "object",
"properties": {
"task_description": {
"type": "string",
"description": "task description from primary steps"
},
"solution_search_question": {
"type": "string",
"description": "Provide solution query text"
}
},
"required": [ "task_description", "solution_search_question" ]
}
}

View file

@ -0,0 +1 @@
In order to utilize TwoStage Planning approach, please call plan_primary_stage when you need to plan the first stage steps to solve a complex task.

View file

@ -0,0 +1 @@
For every primary step, you have to call plan_secondary_stage to plan the detail steps to complete the primary step.