Update router instruction.

This commit is contained in:
Haiping Chen 2023-09-21 08:08:27 -05:00
parent bb6a80275a
commit 1ff154c682
15 changed files with 102 additions and 248 deletions

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<LangVersion>10.0</LangVersion>
<OutputPath>..\..\..\packages</OutputPath>
<BotSharpVersion>0.13.0</BotSharpVersion>
<BotSharpVersion>0.14.1</BotSharpVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
</Project>

View file

@ -5,13 +5,16 @@ namespace BotSharp.Abstraction.Functions.Models;
public class FunctionCallFromLlm
{
[JsonPropertyName("function")]
public string Function { get; set; }
public string Function { get; set; } = string.Empty;
[JsonPropertyName("reason")]
public string Reason { get; set; } = string.Empty;
[JsonPropertyName("parameters")]
public RetrievalArgs Parameters { get; set; }
public RetrievalArgs Parameters { get; set; } = new RetrievalArgs();
public override string ToString()
{
return $"{Function} {Parameters}";
return $"{Function} ({Reason}) {Parameters}";
}
}

View file

@ -10,14 +10,18 @@ public class RetrievalArgs : RoutingArgs
[JsonPropertyName("answer")]
public string Answer { get; set; }
[JsonPropertyName("reason")]
public string Reason { get; set; }
[JsonPropertyName("args")]
public JsonDocument Arguments { get; set; }
public JsonDocument Arguments { get; set; } = JsonDocument.Parse("{}");
public override string ToString()
{
return $"[{AgentName}, {Reason}]: ({JsonSerializer.Serialize(Arguments)}) {Question}";
if (string.IsNullOrEmpty(Answer))
{
return $"[{AgentName}]: ({JsonSerializer.Serialize(Arguments)}) {Question}";
}
else
{
return $"[{AgentName}]: ({JsonSerializer.Serialize(Arguments)}) {Question} => {Answer}";
}
}
}

View file

@ -5,7 +5,7 @@ namespace BotSharp.Abstraction.Routing.Models;
public class RoutingArgs
{
[JsonPropertyName("agent_name")]
public string AgentName { get; set; }
public string AgentName { get; set; } = string.Empty;
public override string ToString()
{

View file

@ -56,7 +56,6 @@ public static class BotSharpServiceCollectionExtensions
// Register function callback
services.AddScoped<IFunctionCallback, RouteToAgentFn>();
services.AddScoped<Simulator>();
services.AddScoped<IRoutingService, RoutingService>();
if (myDatabaseSettings.Default == "FileRepository")

View file

@ -1,22 +1,26 @@
# retrieve_data_from_agent
* retrieve_data_from_agent
Retrieve data from appropriate agent.
Parameters:
1. agent_name: the name of the agent;
2. question: the question you will ask the agent to get the necessary data
3. args: required parameters extracted from question and hand over to the next agent. The args should be in JSON format.
1. agent_name: the name of the agent;
2. question: the question you will ask the agent to get the necessary data;
3. reason: why retrieve data;
4. args: required parameters extracted from question and hand over to the next agent. The args should be in JSON format;
# continue_execute_task
* continue_execute_task
Continue to execute user's request without further information retrival.
Parameters:
1. agent_name: the name of the agent;
2. args: required parameters extracted from question.
1. agent_name: the name of the agent;
2. args: required parameters extracted from question;
3. reason: why continue to execute current task;
# interrupt_task_execution
Can't continue user's request becauase the requirements are not met or you have already known the answer.
* interrupt_task_execution
Can't continue user's request becauase the requirements are not met.
Parameters:
1. reason: the reason why the request is interrupted;
1. reason: the reason why the request is interrupted;
2. answer: the content response to user;
# response_to_user
* response_to_user
You have already known the answer according the dialogs.
Parameters:
1. answer: the answer of user's question;
1. answer: the response of user's request;
2. reason: why response to user;

View file

@ -1,32 +1,38 @@
You're a Agent Router with reasoning, you can dispatch request to different agent to complete the task.
You're a Router with reasoning, you can dispatch request to different agent to complete the task.
### Agents:
{% for agent in routing_records %}
# Agent: {{ agent.name }}
* {{ agent.name }}
{{ agent.description }}
{% if agent.required_fields != empty -%}Required information: {{ agent.required_fields }}.{%- endif %}
{% endfor %}
### Function instructions
# route_to_agent
### Functions
{% if enable_reasoning == false -%}
* route_to_agent
Route request to appropriate agent.
Parameters:
1. agent_name: the name of the agent;
2. reason: why route to this agent;
1. agent_name: the name of the agent;
2. reason: why route to this agent;
3. args: parameters extracted from context;
{%- endif %}
# task_end
* task_end
Call this function when current task is completed.
Parameters:
1. abandoned_arguments: the arguments next task can't reuse;
1. abandoned_arguments: the arguments next task can't reuse;
# conversation_end
* conversation_end
Call this function when user wants to end this conversation or all tasks have been completed.
# transfer_to_csr
* transfer_to_csr
Reach out to a real customer representative to help.
{{ reasoning_functions }}
### Your response must meet below requirements strictly
{% if enable_reasoning == false %}
* If you can find an appropriate Agent, you must call function route_to_agent with required arguments.
{% endif %}
### Below are the dialogs between user and different agents:
### Conversation context:

View file

@ -27,24 +27,24 @@ public class RoutingService : IRoutingService
_logger = logger;
}
public async Task<RoleDialogModel> Enter(Agent agent, List<RoleDialogModel> whileDialogs)
public async Task<RoleDialogModel> Enter(Agent router, List<RoleDialogModel> whileDialogs)
{
_dialogs = new List<RoleDialogModel>();
RoleDialogModel result = new RoleDialogModel(AgentRole.Assistant, "not handled");
foreach (var dialog in whileDialogs.TakeLast(20))
{
agent.Instruction += $"\r\n{dialog.Role}: {dialog.Content}";
router.Instruction += $"\r\n{dialog.Role}: {dialog.Content}";
}
var inst = await GetNextInstructionFromReasoner(agent);
var inst = await GetNextInstructionFromReasoner($"What's the next step to make user's original goal?", router);
int loopCount = 0;
while (loopCount < 3)
{
loopCount++;
if (inst.Function == "continue_execute_task")
{
var router = _services.GetRequiredService<IAgentRouting>();
var routing = _services.GetRequiredService<IAgentRouting>();
var db = _services.GetRequiredService<IBotSharpRepository>();
var record = db.Agents.First(x => x.Name.ToLower() == inst.Parameters.AgentName.ToLower());
@ -73,7 +73,7 @@ public class RoutingService : IRoutingService
}
else if (inst.Function == "interrupt_task_execution")
{
result = new RoleDialogModel(AgentRole.User, inst.Parameters.Reason)
result = new RoleDialogModel(AgentRole.User, inst.Reason)
{
FunctionName = inst.Function
};
@ -98,32 +98,39 @@ public class RoutingService : IRoutingService
});
inst.Parameters.Answer = response.Content;
response.Content += $"\r\nDo you want to continue current task?";
_dialogs.Add(new RoleDialogModel(AgentRole.Function, $"{record.Name}: {response.Content}")
_dialogs.Add(new RoleDialogModel(AgentRole.Assistant, inst.Parameters.Question)
{
FunctionName = inst.Function,
FunctionArgs = JsonSerializer.Serialize(inst.Parameters.Arguments),
ExecutionResult = response.Content,
CurrentAgentId = record.Id
});
agent.Instruction += $"\r\n{record.Name}: {response.Content}";
router.Instruction += $"\r\n{AgentRole.Assistant}: {inst.Parameters.Question}";
_dialogs.Add(new RoleDialogModel(AgentRole.Function, inst.Parameters.Answer)
{
FunctionName = inst.Function,
FunctionArgs = JsonSerializer.Serialize(inst.Parameters.Arguments),
ExecutionResult = inst.Parameters.Answer,
ExecutionData = response.ExecutionData,
CurrentAgentId = record.Id
});
router.Instruction += $"\r\n{AgentRole.Function}: {response.Content}";
// Got the response from agent, then send to reasoner again to make the decision
inst = await GetNextInstructionFromReasoner(agent);
inst = await GetNextInstructionFromReasoner($"What's the next step based on user's original goal and function result?", router);
}
}
return result;
}
private async Task<FunctionCallFromLlm> GetNextInstructionFromReasoner(Agent reasoner)
private async Task<FunctionCallFromLlm> GetNextInstructionFromReasoner(string prompt, Agent reasoner)
{
var responseFormat = "{\"function\": \"\", \"parameters\": {\"agent_name\": \"\", \"reason\":\"\", \"args\":{}}";
var responseFormat = JsonSerializer.Serialize(new FunctionCallFromLlm());
var wholeDialogs = new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.System, $"What's the next step? Response in JSON format {responseFormat}.")
new RoleDialogModel(AgentRole.User, $"{prompt} Response in JSON format {responseFormat}")
};
var chatCompletion = CompletionProvider.GetChatCompletion(_services,
@ -143,6 +150,7 @@ public class RoutingService : IRoutingService
}
args.Function = args.Function.Split('.').Last();
args.Parameters.AgentName = args.Parameters.AgentName.Split(':').Last().Trim();
_logger.LogInformation($"*** Next Instruction *** {args}");
@ -218,7 +226,8 @@ public class RoutingService : IRoutingService
var dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Routing", "Prompts");
var template = File.ReadAllText(Path.Combine(dir, "router_prompt.liquid"));
dict["enable_reasoning"] = _settings.EnableReasoning;
if (_settings.EnableReasoning)
{
dict["reasoning_functions"] = File.ReadAllText(Path.Combine(dir, "reasoning_functions.liquid"));

View file

@ -1,179 +0,0 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Routing.Models;
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);
if (response.Role == AgentRole.Function)
{
}
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 db = _services.GetRequiredService<IBotSharpRepository>();
var record = db.Agents.First(x => x.Name.ToLower() == args.Parameters.AgentName.ToLower());
response.CurrentAgentId = record.Id;
}
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? Response in JSON format with ""function"" and ""parameters"".")
};
var chatCompletion = CompletionProvider.GetChatCompletion(_services);
RoleDialogModel response = null;
await chatCompletion.GetChatCompletionsAsync(reasoner, wholeDialogs, async msg
=> response = msg, fn
=> Task.CompletedTask);
var args = JsonSerializer.Deserialize<FunctionCallFromLlm>(response.Content);
if (args.Parameters.Arguments != null)
{
SaveStateByArgs(args.Parameters.Arguments);
}
else if (args.Function == "response_to_user")
{
return response;
}
if (args.Function == "route_to_agent")
{
var function = _services.GetServices<IFunctionCallback>().FirstOrDefault(x => x.Name == args.Function);
var message = new RoleDialogModel(AgentRole.Function, args.Parameters.Question)
{
FunctionName = args.Function,
FunctionArgs = JsonSerializer.Serialize(new RoutingArgs
{
AgentName = args.Parameters.AgentName
}),
};
var ret = await function.Execute(message);
if (ret)
{
return message;
}
}
// Retrieve information from specific agent
var router = _services.GetRequiredService<IAgentRouting>();
var db = _services.GetRequiredService<IBotSharpRepository>();
var record = db.Agents.First(x => x.Name.ToLower() == args.Parameters.AgentName.ToLower());
response = await SendMessageToAgent(record.Id, 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 = CompletionProvider.GetChatCompletion(_services);
RoleDialogModel response = null;
await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg
=> response = msg, async fn
=>
{
// execute function
// Save states
SaveStateByArgs(JsonSerializer.Deserialize<JsonDocument>(fn.FunctionArgs));
var conversationService = _services.GetRequiredService<IConversationService>();
// Call functions
await conversationService.CallFunctions(fn);
response = fn;
response.Content = fn.ExecutionResult;
});
return response;
}
private void SaveStateByArgs(JsonDocument args)
{
if (args == null)
{
return;
}
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);
}
}
}
}
}

View file

@ -237,6 +237,8 @@ public class ChatCompletionProvider : IChatCompletion
var samplingFactor = float.Parse(state.GetState("sampling_factor", "0.5"));
chatCompletionsOptions.Temperature = temperature;
chatCompletionsOptions.NucleusSamplingFactor = samplingFactor;
chatCompletionsOptions.FrequencyPenalty = 0;
chatCompletionsOptions.PresencePenalty = 0;
var convSetting = _services.GetRequiredService<ConversationSetting>();
if (convSetting.ShowVerboseLog)

View file

@ -15,7 +15,8 @@
"Router": {
"RouterId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
"EnableReasoning": false
"EnableReasoning": false,
"Model": "gpt-3.5"
},
"Agent": {

View file

@ -1,7 +1,7 @@
[
{
"name": "get_delivery_time",
"description": "get order delivery remaining time",
"name": "get_order_status",
"description": "get order status like delivery remaining time",
"parameters": {
"type": "object",
"properties": {

View file

@ -1,14 +0,0 @@
using BotSharp.Abstraction.Conversations.Models;
namespace BotSharp.Plugin.PizzaBot.Functions;
public class GetDeliveryTimeFn : IFunctionCallback
{
public string Name => "get_delivery_time";
public async Task<bool> Execute(RoleDialogModel message)
{
message.ExecutionResult = "15 minutes remaining";
return true;
}
}

View file

@ -0,0 +1,19 @@
using BotSharp.Abstraction.Conversations.Models;
namespace BotSharp.Plugin.PizzaBot.Functions;
public class GetOrderStatusFn : IFunctionCallback
{
public string Name => "get_order_status";
public async Task<bool> Execute(RoleDialogModel message)
{
message.ExecutionResult = "ready to deliver, will arrived in about 15 minutes.";
message.ExecutionData = new
{
Status = "Ready to deliver",
EstimatedTime = "15 minuts"
};
return true;
}
}

View file

@ -12,7 +12,7 @@ public class PizzaBotPlugin : IBotSharpPlugin
services.AddScoped<IFunctionCallback, GetPizzaPricesFn>();
services.AddScoped<IFunctionCallback, PlaceOrderFn>();
services.AddScoped<IFunctionCallback, OrderFoundFn>();
services.AddScoped<IFunctionCallback, GetDeliveryTimeFn>();
services.AddScoped<IFunctionCallback, GetOrderStatusFn>();
services.AddScoped<IFunctionCallback, MakePaymentFn>();
// Register hooks