diff --git a/Directory.Build.props b/Directory.Build.props
index 6940e0bc..86eafbe7 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,7 +2,7 @@
10.0
..\..\..\packages
- 0.13.0
+ 0.14.1
true
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs
index 66360468..6e5963e8 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs
@@ -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}";
}
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs
index 816b6324..51f2f777 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs
@@ -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}";
+ }
}
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs
index e70d787d..474e26da 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs
@@ -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()
{
diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs
index 02c5a515..89e1c57f 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs
+++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs
@@ -56,7 +56,6 @@ public static class BotSharpServiceCollectionExtensions
// Register function callback
services.AddScoped();
- services.AddScoped();
services.AddScoped();
if (myDatabaseSettings.Default == "FileRepository")
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Prompts/reasoning_functions.liquid b/src/Infrastructure/BotSharp.Core/Routing/Prompts/reasoning_functions.liquid
index 44b29104..0831261b 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Prompts/reasoning_functions.liquid
+++ b/src/Infrastructure/BotSharp.Core/Routing/Prompts/reasoning_functions.liquid
@@ -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;
\ No newline at end of file
+1. answer: the response of user's request;
+2. reason: why response to user;
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Prompts/router_prompt.liquid b/src/Infrastructure/BotSharp.Core/Routing/Prompts/router_prompt.liquid
index 4ff1d7df..941e7bdd 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Prompts/router_prompt.liquid
+++ b/src/Infrastructure/BotSharp.Core/Routing/Prompts/router_prompt.liquid
@@ -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:
\ No newline at end of file
+### Conversation context:
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs
index f4337723..0feb3cb1 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs
@@ -27,24 +27,24 @@ public class RoutingService : IRoutingService
_logger = logger;
}
- public async Task Enter(Agent agent, List whileDialogs)
+ public async Task Enter(Agent router, List whileDialogs)
{
_dialogs = new List();
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();
+ var routing = _services.GetRequiredService();
var db = _services.GetRequiredService();
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 GetNextInstructionFromReasoner(Agent reasoner)
+ private async Task GetNextInstructionFromReasoner(string prompt, Agent reasoner)
{
- var responseFormat = "{\"function\": \"\", \"parameters\": {\"agent_name\": \"\", \"reason\":\"\", \"args\":{}}";
+ var responseFormat = JsonSerializer.Serialize(new FunctionCallFromLlm());
var wholeDialogs = new List
{
- 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"));
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs b/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
deleted file mode 100644
index b13eb5bb..00000000
--- a/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs
+++ /dev/null
@@ -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;
-
-///
-/// Simulate the dialogue between different agents.
-///
-public class Simulator
-{
- private readonly IServiceProvider _services;
- private readonly ILogger _logger;
- private List _dialogs;
- public List Dialogs => _dialogs;
-
- public Simulator(IServiceProvider services, ILogger logger)
- {
- _services = services;
- _logger = logger;
- }
-
- public async Task Enter(Agent agent, List whileDialogs)
- {
- _dialogs = new List();
-
- 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(response.Content);
- response.FunctionName = args.Function;
-
- if (args.Function == "continue_execute_task")
- {
- response.FunctionArgs = JsonSerializer.Serialize(args.Parameters.Arguments);
-
- var router = _services.GetRequiredService();
- var db = _services.GetRequiredService();
- 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 SendMessageToReasoner(Agent reasoner)
- {
- var wholeDialogs = new List
- {
- 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(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().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();
- var db = _services.GetRequiredService();
- var record = db.Agents.First(x => x.Name.ToLower() == args.Parameters.AgentName.ToLower());
- response = await SendMessageToAgent(record.Id, new List
- {
- 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 SendMessageToAgent(string agentId, List wholeDialogs)
- {
- var agentService = _services.GetRequiredService();
- 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(fn.FunctionArgs));
-
- var conversationService = _services.GetRequiredService();
- // 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();
- if (args.RootElement is JsonElement root)
- {
- foreach (JsonProperty property in root.EnumerateObject())
- {
- if (!string.IsNullOrEmpty(property.Value.ToString()))
- {
- stateService.SetState(property.Name, property.Value);
- }
- }
- }
- }
-}
diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs
index 5434d586..acc839c4 100644
--- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs
+++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs
@@ -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();
if (convSetting.ShowVerboseLog)
diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json
index caa2bb49..cb89eecd 100644
--- a/src/WebStarter/appsettings.json
+++ b/src/WebStarter/appsettings.json
@@ -15,7 +15,8 @@
"Router": {
"RouterId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
- "EnableReasoning": false
+ "EnableReasoning": false,
+ "Model": "gpt-3.5"
},
"Agent": {
diff --git a/src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/functions.json b/src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/functions.json
index 17065598..fbcb82dd 100644
--- a/src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/functions.json
+++ b/src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/functions.json
@@ -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": {
diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/GetDeliveryTimeFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/GetDeliveryTimeFn.cs
deleted file mode 100644
index db3179af..00000000
--- a/tests/BotSharp.Plugin.PizzaBot/Functions/GetDeliveryTimeFn.cs
+++ /dev/null
@@ -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 Execute(RoleDialogModel message)
- {
- message.ExecutionResult = "15 minutes remaining";
- return true;
- }
-}
diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/GetOrderStatusFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/GetOrderStatusFn.cs
new file mode 100644
index 00000000..1cadc455
--- /dev/null
+++ b/tests/BotSharp.Plugin.PizzaBot/Functions/GetOrderStatusFn.cs
@@ -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 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;
+ }
+}
diff --git a/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs b/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs
index bc106e70..087b6d8a 100644
--- a/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs
+++ b/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs
@@ -12,7 +12,7 @@ public class PizzaBotPlugin : IBotSharpPlugin
services.AddScoped();
services.AddScoped();
services.AddScoped();
- services.AddScoped();
+ services.AddScoped();
services.AddScoped();
// Register hooks