Update router instruction.
This commit is contained in:
parent
bb6a80275a
commit
1ff154c682
|
|
@ -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>
|
||||
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
|
||||
"Router": {
|
||||
"RouterId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
|
||||
"EnableReasoning": false
|
||||
"EnableReasoning": false,
|
||||
"Model": "gpt-3.5"
|
||||
},
|
||||
|
||||
"Agent": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
19
tests/BotSharp.Plugin.PizzaBot/Functions/GetOrderStatusFn.cs
Normal file
19
tests/BotSharp.Plugin.PizzaBot/Functions/GetOrderStatusFn.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue