Optimize router prompt.

This commit is contained in:
Haiping Chen 2023-10-19 22:47:14 -05:00
parent bdfcbd9a0a
commit 8fcada182f
24 changed files with 79 additions and 194 deletions

View file

@ -63,8 +63,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.MongoStorag
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.GoogleAI", "src\Plugins\BotSharp.Plugin.GoogleAI\BotSharp.Plugin.GoogleAI.csproj", "{8BC29F8A-78D6-422C-B522-10687ADC38ED}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.TestingConsole", "tests\BotSharp.TestingConsole\BotSharp.TestingConsole.csproj", "{5E7EC98F-4A22-4D62-8FF6-746C497CF955}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -225,14 +223,6 @@ Global
{8BC29F8A-78D6-422C-B522-10687ADC38ED}.Release|Any CPU.Build.0 = Release|Any CPU
{8BC29F8A-78D6-422C-B522-10687ADC38ED}.Release|x64.ActiveCfg = Release|Any CPU
{8BC29F8A-78D6-422C-B522-10687ADC38ED}.Release|x64.Build.0 = Release|Any CPU
{5E7EC98F-4A22-4D62-8FF6-746C497CF955}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5E7EC98F-4A22-4D62-8FF6-746C497CF955}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5E7EC98F-4A22-4D62-8FF6-746C497CF955}.Debug|x64.ActiveCfg = Debug|Any CPU
{5E7EC98F-4A22-4D62-8FF6-746C497CF955}.Debug|x64.Build.0 = Debug|Any CPU
{5E7EC98F-4A22-4D62-8FF6-746C497CF955}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5E7EC98F-4A22-4D62-8FF6-746C497CF955}.Release|Any CPU.Build.0 = Release|Any CPU
{5E7EC98F-4A22-4D62-8FF6-746C497CF955}.Release|x64.ActiveCfg = Release|Any CPU
{5E7EC98F-4A22-4D62-8FF6-746C497CF955}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -264,7 +254,6 @@ Global
{5CD330E1-9E5A-4112-8346-6E31CA98EF78} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C}
{DB3DE37B-1208-4ED3-9615-A52AD0AAD69C} = {5CD330E1-9E5A-4112-8346-6E31CA98EF78}
{8BC29F8A-78D6-422C-B522-10687ADC38ED} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
{5E7EC98F-4A22-4D62-8FF6-746C497CF955} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}

View file

@ -9,7 +9,8 @@ public class FunctionCallFromLlm : RoutingArgs
public string? Question { get; set; }
[JsonPropertyName("args")]
public JsonDocument Arguments { get; set; } = JsonDocument.Parse("{}");
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public JsonDocument? Arguments { get; set; }
public override string ToString()
{

View file

@ -9,6 +9,5 @@ public interface IRouterInstance
RoutingItem[] GetRoutingItems();
List<RoutingHandlerDef> GetHandlers();
IRouterInstance Load();
IRouterInstance WithDialogs(List<RoleDialogModel> dialogs);
RoutingRule[] GetRulesByName(string name);
}

View file

@ -21,12 +21,14 @@ public class RoutingArgs
/// Agent for next action based on user latest response
/// </summary>
[JsonPropertyName("next_action_agent")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string AgentName { get; set; }
/// <summary>
/// Agent who can achieve user original goal
/// </summary>
[JsonPropertyName("user_goal_agent")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string OriginalAgent { get; set; }
public override string ToString()

View file

@ -5,6 +5,9 @@ namespace BotSharp.Core.Agents.Services;
public partial class AgentService
{
#if !DEBUG
[MemoryCache(10 * 60)]
#endif
public async Task<Agent> LoadAgent(string id)
{
var hooks = _services.GetServices<IAgentHook>();

View file

@ -41,7 +41,7 @@ public class ConversationStateService : IConversationStateService, IDisposable
if (!_states.ContainsKey(name) || _states[name] != currentValue)
{
_states[name] = currentValue;
_logger.LogInformation($"Set state: {name} = {value}");
_logger.LogInformation($"[STATE] {name} = {value}");
foreach (var hook in hooks)
{
hook.OnStateChanged(name, preValue, currentValue).Wait();
@ -62,7 +62,7 @@ public class ConversationStateService : IConversationStateService, IDisposable
foreach (var data in _savedStates)
{
_states[data.Key] = data.Value;
_logger.LogInformation($"Loaded state: {data.Key}={data.Value}");
_logger.LogInformation($"[STATE] {data.Key} : {data.Value}");
}
}

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
@ -12,6 +13,12 @@ public class ConversationEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
public bool IsReasoning => false;
public List<NameDesc> Parameters => new List<NameDesc>
{
new NameDesc("reason", "why end conversation"),
new NameDesc("response", "response content to user")
};
public ConversationEndRoutingHandler(IServiceProvider services, ILogger<ConversationEndRoutingHandler> logger, RoutingSettings settings)
: base(services, logger, settings)
{

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
@ -12,6 +13,12 @@ public class HumanInterventionNeededHandler : RoutingHandlerBase, IRoutingHandle
private readonly RoutingSettings _settings;
public List<NameDesc> Parameters => new List<NameDesc>
{
new NameDesc("reason", "why need customer service representative (human being)"),
new NameDesc("response", "response content to user")
};
public HumanInterventionNeededHandler(IServiceProvider services, ILogger<HumanInterventionNeededHandler> logger, RoutingSettings settings)
: base(services, logger, settings)
{

View file

@ -9,10 +9,16 @@ public class ResponseToUserRoutingHandler : RoutingHandlerBase, IRoutingHandler
{
public string Name => "response_to_user";
public string Description => "You know how to response according to the context without asking specific agent.";
public string Description => "Response according to the context without asking specific agent.";
public bool IsReasoning => false;
public List<NameDesc> Parameters => new List<NameDesc>
{
new NameDesc("reason", "why response to user"),
new NameDesc("response", "response content")
};
public ResponseToUserRoutingHandler(IServiceProvider services, ILogger<ResponseToUserRoutingHandler> logger, RoutingSettings settings)
: base(services, logger, settings)
{

View file

@ -15,6 +15,9 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
public List<NameDesc> Parameters => new List<NameDesc>
{
new NameDesc("reason", "why route to agent"),
new NameDesc("next_action_agent", "agent for next action based on user latest response"),
new NameDesc("user_goal_agent", "agent who can achieve user original goal"),
new NameDesc("args", "useful parameters of next action agent")
};

View file

@ -1,8 +1,5 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Models;
namespace BotSharp.Core.Routing.Hooks;

View file

@ -33,15 +33,6 @@ public class RouterInstance : IRouterInstance
return this;
}
public IRouterInstance WithDialogs(List<RoleDialogModel> dialogs)
{
foreach (var dialog in dialogs.TakeLast(20))
{
_router.Instruction += $"\r\n{dialog.Role}: {dialog.Content}";
}
return this;
}
public List<RoutingHandlerDef> GetHandlers()
{
return _services.GetServices<IRoutingHandler>()

View file

@ -61,6 +61,13 @@ public partial class RoutingService
{
try
{
var conversation = "";
foreach (var dialog in _dialogs.TakeLast(20))
{
conversation += $"{dialog.Role}: {dialog.Content}\r\n";
}
content = $"{conversation}\r\n###\r\n{content}";
response = completion.GetChatCompletions(_routerInstance.Router, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, content)
@ -107,7 +114,7 @@ public partial class RoutingService
}
#if !DEBUG
[MemoryCache(60 * 60)]
[MemoryCache(10 * 60)]
#endif
private string GetNextStepPrompt()
{

View file

@ -63,7 +63,7 @@ public partial class RoutingService : IRoutingService
public async Task<RoleDialogModel> InstructLoop()
{
_routerInstance.Load().WithDialogs(Dialogs);
_routerInstance.Load();
var router = _routerInstance.Router;
var result = new RoleDialogModel(AgentRole.Assistant, "Can you repeat your request again?")
@ -97,7 +97,6 @@ public partial class RoutingService : IRoutingService
result = await handler.Handle(this, inst);
message = result.Content.Replace("\r\n", " ");
router.Instruction += $"\r\n{result.Role}: {message}";
stop = !_settings.EnableReasoning;
}

View file

@ -1,7 +1,6 @@
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Templating;
using System.IO;
using System.Reflection;
namespace BotSharp.Core.Templating;

View file

@ -21,6 +21,7 @@ public class TemplateRender : ITemplateRender
_options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.SnakeCase;
_options.MemberAccessStrategy.Register<NameDesc>();
_options.MemberAccessStrategy.Register<RoleDialogModel>();
_options.MemberAccessStrategy.Register<Agent>();
_options.MemberAccessStrategy.Register<RoutingItem>();
_options.MemberAccessStrategy.Register<RoutingHandlerDef>();

View file

@ -248,23 +248,27 @@ public class ChatCompletionProvider : IChatCompletion
var convSetting = _services.GetRequiredService<ConversationSetting>();
if (convSetting.ShowVerboseLog)
{
_logger.LogInformation("VERBOSE COMPLETION MESSAGES");
var verbose = string.Join("\r\n", chatCompletionsOptions.Messages.Select(x =>
if (chatCompletionsOptions.Messages.Count > 0)
{
return x.Role == ChatRole.Function ?
$"{x.Role}: {x.Name} => {x.Content}" :
$"{x.Role}: {x.Content}";
}));
_logger.LogInformation("VERBOSE COMPLETION MESSAGES");
var verbose = string.Join("\r\n", chatCompletionsOptions.Messages.Select(x =>
{
return x.Role == ChatRole.Function ?
$"{x.Role}: {x.Name} => {x.Content}" :
$"{x.Role}: {x.Content}";
}));
_logger.LogInformation(verbose);
}
_logger.LogInformation(verbose);
_logger.LogInformation("VERBOSE FUNCTIONS");
verbose = string.Join("\r\n", chatCompletionsOptions.Functions.Select(x =>
if (chatCompletionsOptions.Functions.Count > 0)
{
return $"{x.Name}: {x.Description}\r\n{x.Parameters}";
}));
_logger.LogInformation(verbose);
_logger.LogInformation("VERBOSE FUNCTIONS");
var verbose = string.Join("\r\n", chatCompletionsOptions.Functions.Select(x =>
{
return $"{x.Name}: {x.Description}\r\n{x.Parameters}";
}));
_logger.LogInformation(verbose);
}
}
return chatCompletionsOptions;

View file

@ -1,31 +1,30 @@
You're {{router.name}} ({{router.description}}). Follow these steps to handle user request:
1. Read the CONVERSATION context.
2. Select a appropriate function from FUNCTIONS.
3. Determine which agent is suitable according to CONVERSATION context.
4. Re-think about selected function is from FUNCTIONS to handle the request.
1. Read the [CONVERSATION] content.
2. Select a appropriate function from [FUNCTIONS].
3. Determine which agent is suitable to handle this conversation.
4. Re-think about the selected function or agent is the best choice.
FUNCTIONS
[FUNCTIONS]
{% for handler in routing_handlers %}
# {{ handler.name }}
{{ handler.description}}
# {{ handler.description}}
{% if handler.parameters and handler.parameters != empty -%}
Parameters:
Response: { "function": "{{ handler.name }}",
{% for p in handler.parameters -%}
{{ p.name }}: {{ p.description }}{{ "\r\n " }}
{%- endfor %}
"{{ p.name }}": "{{ p.description }}"{{ ",\r\n " }}
{%- endfor %}}
{%- endif %}
{% endfor %}
AGENTS
[AGENTS]
{% for agent in routing_agents %}
* {{ agent.name }}
* Agent: {{ agent.name }}
{{ agent.description}}
{% if agent.required_fields and agent.required_fields != empty -%}
Required:
Required args: {
{% for f in agent.required_fields -%}
{{ f.name }}: {{ f.description }}{{ "\r\n " }}
{%- endfor %}
"{{ f.name }}": "{{ f.description }}"{{ ",\r\n " }}
{%- endfor %}}
{%- endif %}
{% endfor %}
CONVERSATION
[CONVERSATION]

View file

@ -1,20 +1 @@
What is the next step based on the CONVERSATION? Or you can handle without asking specific agent.
Response must be in JSON format
{% if enabled_reasoning %}
{
"function":"route_to_agent"
}
{% else %}
{
"function":"route_to_agent",
"reason":"the reason why you select this function or agent",
"response":"content of replying to user",
"next_action_agent":"agent for next action based on user latest response",
"user_goal_agent":"agent who can achieve user original goal",
"args": {}
}
{% endif %}
If the user has no other tasks need help with, set function as conversation_end with reason and reply user courteously.
If the user wants to reach out to customer service representative (human being), set function as human_intervention_needed with reason and reply user courteously.
What is the next step based on the CONVERSATION? Response must be in appropriate JSON format.

View file

@ -7,4 +7,4 @@ Follow below step to place order:
4: Ask user how to pay for this order.
Use below information to help ordering process:
* Today is {{current_date}}, the time now is {{current_time}}, day of week is {{current_weekday}}.
* Today is {{current_date}}, the time now is {{current_time}}, day of week is {{current_weekday}}.

View file

@ -8,6 +8,7 @@
"routingRules": [
{
"field": "order_number",
"description": "order number",
"required": true,
"redirectTo": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd"
}

View file

@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Refit" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Infrastructure\BotSharp.OpenAPI\BotSharp.OpenAPI.csproj" />
<ProjectReference Include="..\..\src\Plugins\BotSharp.Plugin.AzureOpenAI\BotSharp.Plugin.AzureOpenAI.csproj" />
</ItemGroup>
</Project>

View file

@ -1,17 +0,0 @@
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.OpenAPI.ViewModels.Conversations;
using Refit;
namespace BotSharp.TestingConsole;
public interface IBotSharpOpenAPI
{
[Post("/conversation/{agentId}")]
Task<ConversationViewModel> NewConversation([Header("Authorization")] string authorization, string agentId);
[Post("/conversation/{agentId}/{conversationId}")]
Task<MessageResponseModel> SendMessage([Header("Authorization")] string authorization, string agentId, string conversationId, [Body] NewMessageModel input);
[Post("/instruct/text-completion")]
Task<string> TextCompletion([Header("Authorization")] string authorization, [Body] IncomingMessageModel message);
}

View file

@ -1,74 +0,0 @@
// See https://aka.ms/new-console-template for more information
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.OpenAPI.ViewModels.Conversations;
using BotSharp.TestingConsole;
using Refit;
using System.Drawing;
using Console = Colorful.Console;
var token = "Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiI0NTZlMzVjNS1jYWYwLTRkNDUtOTA4NC1iNDRhOGNhNzE3ZTQiLCJlbWFpbCI6ImJvdHNoYXJwQGdtYWlsLmNvbSIsImdpdmVuX25hbWUiOiJIYWlwaW5nIiwiZmFtaWx5X25hbWUiOiJDaGVuIiwianRpIjoiMTQ3MDkwMDQtNDM1Ny00NTFkLWExM2MtY2U1NDk5NWM0ZTJlIiwibmJmIjoxNjk3MTY0MzA4LCJleHAiOjE2OTcxNjQ2MDgsImlhdCI6MTY5NzE2NDMwOCwiaXNzIjoiYm90c2hhcnAiLCJhdWQiOiJib3RzaGFycCJ9.1UgOj5esNInTPiiy-_gLmcT2x8NtFswCUyePVHXa-4EeLCkA43nx0LOXPlzb_rmvUIg9bJSRZXbH2aBqtmiDYg";
var agentId = "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a";
// New conversation
var botsharp = RestService.For<IBotSharpOpenAPI>("http://localhost:5500");
var conv = await botsharp.NewConversation(token, agentId);
var instruction = @"You're a customer who is going to buy a pizza.
Below are your requirments:
* You want to know what kind of pizza do they have.
* You want to buy three piece of pizza.
* Say Bye if the order is placed and payment is completed.
Below are your profile:
* You like pepperoni flavor.
* You will pay the order in cash.
* Your address is 347 S Gladstone Ave, Aurora, IL 60506.
* Your phone number is +16308926431";
Action<RoleDialogModel> PrintDialog = message =>
{
Console.Write($"[{DateTime.Now:HH:mm:ss}] {message.Role}:\t");
if (message.Role == AgentRole.User)
{
Console.WriteLine(message.Content);
}
else
{
Console.WriteLine(message.Content, Color.Yellow);
}
};
var dialogs = new List<RoleDialogModel>();
dialogs.Add(new RoleDialogModel(AgentRole.User, "Good morning!"));
PrintDialog(dialogs.Last());
dialogs.Add(new RoleDialogModel(AgentRole.Assistant, "Hi, How can I help you today?"));
PrintDialog(dialogs.Last());
var response = new MessageResponseModel();
while (response.Function != "conversation_end")
{
var text = string.Join("\r\n", dialogs.Select(x => $"{x.Role}: {x.Content}"));
text = instruction + $"\r\n###\r\n{text}\r\n{AgentRole.User}: ";
var question = await botsharp.TextCompletion(token, new IncomingMessageModel
{
Text = text
});
dialogs.Add(new RoleDialogModel(AgentRole.User, question));
PrintDialog(dialogs.Last());
response = await botsharp.SendMessage(token, agentId, conv.Id, new NewMessageModel
{
Text = question
});
dialogs.Add(new RoleDialogModel(AgentRole.Assistant, response.Text.Trim()));
PrintDialog(dialogs.Last());
}
Console.WriteLine();
Console.WriteLine("Conversation End", Color.Green);
Console.ReadLine();