resolve conflict
This commit is contained in:
commit
11c9b606a2
|
|
@ -3,11 +3,16 @@ namespace BotSharp.Abstraction.Agents.Enums;
|
|||
public class AgentType
|
||||
{
|
||||
/// <summary>
|
||||
/// Routing Agent
|
||||
/// Routing agent
|
||||
/// </summary>
|
||||
public const string Routing = "routing";
|
||||
|
||||
public const string Evaluating = "evaluating";
|
||||
/// <summary>
|
||||
/// Planning agent
|
||||
/// </summary>
|
||||
public const string Planning = "plan";
|
||||
|
||||
public const string Evaluating = "evaluation";
|
||||
|
||||
/// <summary>
|
||||
/// Routable task agent with capability of interaction with external environment
|
||||
|
|
|
|||
|
|
@ -31,4 +31,9 @@ public class BuiltInAgentId
|
|||
/// Used by knowledgebase plugin to acquire domain knowledge
|
||||
/// </summary>
|
||||
public const string Learner = "01acc3e5-0af7-49e6-ad7a-a760bd12dc40";
|
||||
|
||||
/// <summary>
|
||||
/// Plan feasible implementation steps for complex problems
|
||||
/// </summary>
|
||||
public const string Planner = "282a7128-69a1-44b0-878c-a9159b88f3b9";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\database_knowledge.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.summarize.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\agent.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\agent.json" />
|
||||
|
|
@ -75,9 +74,6 @@
|
|||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.naive.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.sequential.get_remaining_task.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.sequential.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.1st.plan.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.2nd.plan.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.2nd.task.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\response_with_function.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\translation_prompt.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\select_file_prompt.liquid" />
|
||||
|
|
@ -119,15 +115,6 @@
|
|||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\database_knowledge.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.1st.plan.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.2nd.plan.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.2nd.task.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.sequential.get_remaining_task.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
@ -140,9 +127,6 @@
|
|||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.naive.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.summarize.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\response_with_function.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public partial class TwoStagePlanner
|
|||
|
||||
private async Task<string> GetFirstStagePlanPrompt(Agent router)
|
||||
{
|
||||
var template = router.Templates.First(x => x.Name == "planner_prompt.two_stage.1st.plan").Content;
|
||||
var template = router.Templates.First(x => x.Name == "two_stage.1st.plan").Content;
|
||||
var responseFormat = JsonSerializer.Serialize(new FirstStagePlan
|
||||
{
|
||||
Parameters = new JsonDocument[]{ JsonDocument.Parse("{}") },
|
||||
|
|
@ -69,7 +69,7 @@ public partial class TwoStagePlanner
|
|||
|
||||
private string GetFirstStageNextPrompt(Agent router)
|
||||
{
|
||||
var template = router.Templates.First(x => x.Name == "planner_prompt.first_stage.next").Content;
|
||||
var template = router.Templates.First(x => x.Name == "first_stage.next").Content;
|
||||
var responseFormat = JsonSerializer.Serialize(new FirstStagePlan
|
||||
{
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Routing.Planning;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Routing.Planning;
|
||||
|
||||
|
|
@ -28,25 +23,9 @@ public partial class TwoStagePlanner : IRoutingPlaner
|
|||
|
||||
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId, List<RoleDialogModel> dialogs)
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), "botsharp", "cache");
|
||||
if (_plan1st.IsNullOrEmpty() && _plan2nd.IsNullOrEmpty())
|
||||
{
|
||||
Directory.CreateDirectory(tempDir);
|
||||
_md5 = Utilities.HashTextMd5($"{string.Join(".", dialogs.Where(x => x.Role == AgentRole.User))}{"botsharp"}");
|
||||
var filePath = Path.Combine(tempDir, $"{_md5}-1st.json");
|
||||
FirstStagePlan[] items = new FirstStagePlan[0];
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
var cache = File.ReadAllText(filePath);
|
||||
items = JsonSerializer.Deserialize<FirstStagePlan[]>(cache);
|
||||
}
|
||||
else
|
||||
{
|
||||
items = await GetFirstStagePlanAsync(router, messageId, dialogs);
|
||||
|
||||
var cache = JsonSerializer.Serialize(items);
|
||||
File.WriteAllText(filePath, cache);
|
||||
}
|
||||
FirstStagePlan[] items = await GetFirstStagePlanAsync(router, messageId, dialogs);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
|
|
@ -61,20 +40,7 @@ public partial class TwoStagePlanner : IRoutingPlaner
|
|||
|
||||
if (plan1.ContainMultipleSteps)
|
||||
{
|
||||
var filePath = Path.Combine(tempDir, $"{_md5}-2nd-{plan1.Step}.json");
|
||||
SecondStagePlan[] items = new SecondStagePlan[0];
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
var cache = File.ReadAllText(filePath);
|
||||
items = JsonSerializer.Deserialize<SecondStagePlan[]>(cache);
|
||||
}
|
||||
else
|
||||
{
|
||||
items = await GetSecondStagePlanAsync(router, messageId, plan1, dialogs);
|
||||
|
||||
var cache = JsonSerializer.Serialize(items);
|
||||
File.WriteAllText(filePath, cache);
|
||||
}
|
||||
SecondStagePlan[] items = await GetSecondStagePlanAsync(router, messageId, plan1, dialogs);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -38,6 +38,5 @@ public class RoutingPlugin : IBotSharpPlugin
|
|||
services.AddScoped<IRoutingPlaner, NaivePlanner>();
|
||||
services.AddScoped<IRoutingPlaner, HFPlanner>();
|
||||
services.AddScoped<IRoutingPlaner, SequentialPlanner>();
|
||||
services.AddScoped<IRoutingPlaner, TwoStagePlanner>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
{{ task_description }}
|
||||
|
||||
{% if related_tables != empty -%}
|
||||
Relevant tables:
|
||||
{% for t in related_tables -%}
|
||||
- {{ t }}{{ "\r\n" }}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Conversations.Enums;
|
||||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
using BotSharp.Abstraction.Users;
|
||||
|
||||
|
|
@ -47,20 +48,26 @@ public class RateLimitConversationHook : ConversationHookBase
|
|||
}
|
||||
}
|
||||
|
||||
// Check the number of conversations
|
||||
var user = _services.GetRequiredService<IUserIdentity>();
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var results = await convService.GetConversations(new ConversationFilter
|
||||
{
|
||||
UserId = user.Id,
|
||||
StartTime = DateTime.UtcNow.AddHours(-24),
|
||||
});
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
var channel = states.GetState("channel");
|
||||
|
||||
if (results.Count > rateLimit.MaxConversationPerDay)
|
||||
// Check the number of conversations
|
||||
if (channel != ConversationChannel.Phone && channel != ConversationChannel.Email)
|
||||
{
|
||||
message.Content = $"The number of conversations you have exceeds the system maximum of {rateLimit.MaxConversationPerDay}";
|
||||
message.StopCompletion = true;
|
||||
return;
|
||||
var user = _services.GetRequiredService<IUserIdentity>();
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var results = await convService.GetConversations(new ConversationFilter
|
||||
{
|
||||
UserId = user.Id,
|
||||
StartTime = DateTime.UtcNow.AddHours(-24),
|
||||
});
|
||||
|
||||
if (results.Count > rateLimit.MaxConversationPerDay)
|
||||
{
|
||||
message.Content = $"The number of conversations you have exceeds the system maximum of {rateLimit.MaxConversationPerDay}";
|
||||
message.StopCompletion = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,25 +11,53 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\agent.json" />
|
||||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\two_stage.2nd.plan.liquid" />
|
||||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\two_stage.summarize.liquid" />
|
||||
<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\functions\plan_summary.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\planner_prompt.two_stage.1st.plan.liquid" />
|
||||
<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" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\plan_summary.fn.liquid" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\instructions\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\two_stage.2nd.plan.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\two_stage.summarize.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<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\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\two_stage.1st.plan.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\plan_summary.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>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\plan_summary.fn.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,8 @@ using BotSharp.Abstraction.Functions;
|
|||
using BotSharp.Abstraction.Templating;
|
||||
using System.Threading.Tasks;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using BotSharp.Plugin.Planner.TwoStaging.Models;
|
||||
using BotSharp.Abstraction.Knowledges;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
|
|
@ -55,7 +53,7 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
var firstPlanningPrompt = await GetFirstStagePlanPrompt(task, message);
|
||||
var plannerAgent = new Agent
|
||||
{
|
||||
Id = "",
|
||||
Id = BuiltInAgentId.Planner,
|
||||
Name = "planning_1st",
|
||||
Instruction = firstPlanningPrompt,
|
||||
TemplateDict = new Dictionary<string, object>(),
|
||||
|
|
@ -64,7 +62,7 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
var response = await GetAIResponse(plannerAgent);
|
||||
message.Content = response.Content;
|
||||
|
||||
await fn.InvokeFunction("plan_secondary_stage", message);
|
||||
/*await fn.InvokeFunction("plan_secondary_stage", message);
|
||||
var items = message.Content.JsonArrayContent<SecondStagePlan>();
|
||||
|
||||
//get all the related tables
|
||||
|
|
@ -85,7 +83,7 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
_logger.LogInformation(summaryPlanningPrompt);
|
||||
plannerAgent = new Agent
|
||||
{
|
||||
Id = "",
|
||||
Id = BuiltInAgentId.Planner,
|
||||
Name = "planner_summary",
|
||||
Instruction = summaryPlanningPrompt,
|
||||
TemplateDict = new Dictionary<string, object>(),
|
||||
|
|
@ -95,19 +93,19 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
_logger.LogInformation(response_summary.Content);
|
||||
|
||||
message.Content = response_summary.Content;
|
||||
message.StopCompletion = true;
|
||||
message.StopCompletion = true;*/
|
||||
return true;
|
||||
}
|
||||
private async Task<string> GetFirstStagePlanPrompt(PrimaryRequirementRequest task, RoleDialogModel message)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var aiAssistant = await agentService.GetAgent(BuiltInAgentId.AIAssistant);
|
||||
var aiAssistant = await agentService.GetAgent(BuiltInAgentId.Planner);
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
var template = aiAssistant.Templates.First(x => x.Name == "planner_prompt.two_stage.1st.plan").Content;
|
||||
var template = aiAssistant.Templates.First(x => x.Name == "two_stage.1st.plan").Content;
|
||||
var responseFormat = JsonSerializer.Serialize(new FirstStagePlan
|
||||
{
|
||||
Parameters = new JsonDocument[] { JsonDocument.Parse("{}") },
|
||||
Results = new string[] { "" }
|
||||
Parameters = [JsonDocument.Parse("{}")],
|
||||
Results = [""]
|
||||
});
|
||||
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
|
|
@ -126,8 +124,8 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
var template = aiAssistant.Templates.First(x => x.Name == "planner_prompt.two_stage.summarize").Content;
|
||||
var responseFormat = JsonSerializer.Serialize(new FirstStagePlan
|
||||
{
|
||||
Parameters = new JsonDocument[] { JsonDocument.Parse("{}") },
|
||||
Results = new string[] { "" }
|
||||
Parameters = [JsonDocument.Parse("{}")],
|
||||
Results = [""]
|
||||
});
|
||||
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public class SecondaryStagePlanFn : IFunctionCallback
|
|||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var fn = _services.GetRequiredService<IRoutingService>();
|
||||
|
|
@ -36,7 +37,7 @@ public class SecondaryStagePlanFn : IFunctionCallback
|
|||
var task_secondary = JsonSerializer.Deserialize<SecondaryBreakdownTask>(msg_secondary.FunctionArgs);
|
||||
var items = msg_secondary.Content.JsonArrayContent<FirstStagePlan>();
|
||||
|
||||
msg_secondary.KnowledgeConfidence = 0.5f;
|
||||
msg_secondary.KnowledgeConfidence = 0.5f;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.NeedAdditionalInformation)
|
||||
|
|
@ -73,9 +74,9 @@ public class SecondaryStagePlanFn : IFunctionCallback
|
|||
private async Task<string> GetSecondStagePlanPrompt(SecondaryBreakdownTask task, RoleDialogModel message)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var aiAssistant = await agentService.GetAgent(BuiltInAgentId.AIAssistant);
|
||||
var planner = await agentService.GetAgent(message.CurrentAgentId);
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
var template = aiAssistant.Templates.First(x => x.Name == "planner_prompt.two_stage.2nd.plan").Content;
|
||||
var template = planner.Templates.First(x => x.Name == "two_stage.2nd.plan").Content;
|
||||
var responseFormat = JsonSerializer.Serialize(new SecondStagePlan
|
||||
{
|
||||
Tool = "tool name if task solution provided",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
using System.Threading.Tasks;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using BotSharp.Plugin.Planner.TwoStaging.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BotSharp.Plugin.Planner.Functions;
|
||||
|
||||
public class SummaryPlanFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "plan_summary";
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger _logger;
|
||||
private object aiAssistant;
|
||||
|
||||
public SummaryPlanFn(IServiceProvider services, ILogger<PrimaryStagePlanFn> logger)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
//debug
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
state.SetState("max_tokens", "4096");
|
||||
|
||||
var task = state.GetState("requirement_detail");
|
||||
|
||||
// summarize and generate query
|
||||
var summaryPlanningPrompt = await GetPlanSummaryPrompt(task, message);
|
||||
_logger.LogInformation(summaryPlanningPrompt);
|
||||
var plannerAgent = new Agent
|
||||
{
|
||||
Id = BuiltInAgentId.Planner,
|
||||
Name = "planner_summary",
|
||||
Instruction = summaryPlanningPrompt,
|
||||
TemplateDict = new Dictionary<string, object>()
|
||||
};
|
||||
var response_summary = await GetAIResponse(plannerAgent);
|
||||
|
||||
message.Content = response_summary.Content;
|
||||
message.StopCompletion = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<string> GetPlanSummaryPrompt(string task, RoleDialogModel message)
|
||||
{
|
||||
// save to knowledge base
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var aiAssistant = await agentService.GetAgent(message.CurrentAgentId);
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
var template = aiAssistant.Templates.First(x => x.Name == "two_stage.summarize").Content;
|
||||
var responseFormat = JsonSerializer.Serialize(new FirstStagePlan
|
||||
{
|
||||
Parameters = [JsonDocument.Parse("{}")],
|
||||
Results = [""]
|
||||
});
|
||||
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
{
|
||||
{ "table_structure", message.SecondaryContent }, ////check
|
||||
{ "task_description", task},
|
||||
{ "relevant_knowledges", message.Content },
|
||||
{ "response_format", responseFormat }
|
||||
});
|
||||
}
|
||||
private async Task<RoleDialogModel> GetAIResponse(Agent plannerAgent)
|
||||
{
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
var wholeDialogs = conv.GetDialogHistory();
|
||||
//add "test" to wholeDialogs' last element
|
||||
if(plannerAgent.Name == "planner_summary")
|
||||
{
|
||||
//add "test" to wholeDialogs' last element in a new paragraph
|
||||
wholeDialogs.Last().Content += "\n\nIf the table structure didn't mention auto incremental, the data field id needs to insert id manually and you need to use max(id) instead of LAST_INSERT_ID function.\nFor example, you should use SET @id = select max(id) from table;";
|
||||
wholeDialogs.Last().Content += "\n\nTry if you can generate a single query to fulfill the needs";
|
||||
}
|
||||
if (plannerAgent.Name == "planning_1st")
|
||||
{
|
||||
//add "test" to wholeDialogs' last element in a new paragraph
|
||||
wholeDialogs.Last().Content += "\n\nYou must analyze the table description to infer the table relations.";
|
||||
}
|
||||
|
||||
var completion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: plannerAgent.LlmConfig.Provider,
|
||||
model: plannerAgent.LlmConfig.Model);
|
||||
|
||||
return await completion.GetChatCompletions(plannerAgent, wholeDialogs);
|
||||
}
|
||||
}
|
||||
|
|
@ -52,6 +52,24 @@ public class PlannerAgentHook : AgentHookBase
|
|||
agent.Functions.Add(fn);
|
||||
}
|
||||
}
|
||||
|
||||
(prompt, fn) = GetPromptAndFunction("plan_summary");
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,23 @@
|
|||
using BotSharp.Abstraction.Routing.Planning;
|
||||
using BotSharp.Plugin.Planner.TwoStaging;
|
||||
|
||||
namespace BotSharp.Plugin.Planner;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin for AI Planning.
|
||||
/// </summary>
|
||||
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 string IconUrl => "https://e7.pngegg.com/pngimages/775/350/png-clipart-action-plan-computer-icons-plan-miscellaneous-text-thumbnail.png";
|
||||
|
||||
public string[] AgentIds => [ BuiltInAgentId.Planner ];
|
||||
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
services.AddScoped<IRoutingPlaner, TwoStageTaskPlanner>();
|
||||
services.AddScoped<IAgentHook, PlannerAgentHook>();
|
||||
services.AddScoped<IAgentUtilityHook, PlannerUtilityHook>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,312 @@
|
|||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Knowledges;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Planning;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using BotSharp.Core.Routing.Planning;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.Plugin.Planner.TwoStaging;
|
||||
|
||||
public partial class TwoStageTaskPlanner : ITaskPlanner
|
||||
public partial class TwoStageTaskPlanner : IRoutingPlaner
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger _logger;
|
||||
public int MaxLoopCount => 100;
|
||||
private bool _isTaskCompleted;
|
||||
|
||||
public TwoStageTaskPlanner(IServiceProvider services)
|
||||
private Queue<FirstStagePlan> _plan1st = new Queue<FirstStagePlan>();
|
||||
private Queue<SecondStagePlan> _plan2nd = new Queue<SecondStagePlan>();
|
||||
|
||||
private List<string> _executionContext = new List<string>();
|
||||
|
||||
public TwoStageTaskPlanner(IServiceProvider services, ILogger<TwoStagePlanner> logger)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId, List<RoleDialogModel> dialogs)
|
||||
{
|
||||
// push agent to routing context
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
routing.Context.Push(BuiltInAgentId.Planner, "Make plan in TwoStage planner");
|
||||
|
||||
return new FunctionCallFromLlm
|
||||
{
|
||||
AgentName = "Planner",
|
||||
UserGoal = "",
|
||||
Response = "",
|
||||
Function = "route_to_agent"
|
||||
};
|
||||
|
||||
/*FirstStagePlan[] items = await GetFirstStagePlanAsync(router, messageId, dialogs);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
_plan1st.Enqueue(item);
|
||||
};
|
||||
|
||||
// Get Second Stage Plan
|
||||
if (_plan2nd.IsNullOrEmpty())
|
||||
{
|
||||
var plan1 = _plan1st.Dequeue();
|
||||
|
||||
if (plan1.ContainMultipleSteps)
|
||||
{
|
||||
SecondStagePlan[] items = await GetSecondStagePlanAsync(router, messageId, plan1, dialogs);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
_plan2nd.Enqueue(item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_plan2nd.Enqueue(new SecondStagePlan
|
||||
{
|
||||
Description = plan1.Task,
|
||||
Tables = plan1.Tables,
|
||||
Parameters = plan1.Parameters,
|
||||
Results = plan1.Results,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var plan2 = _plan2nd.Dequeue();
|
||||
|
||||
var secondStagePrompt = GetSecondStageTaskPrompt(router, plan2);
|
||||
var inst = new FunctionCallFromLlm
|
||||
{
|
||||
AgentName = "SQL Driver",
|
||||
Response = secondStagePrompt,
|
||||
Function = "route_to_agent"
|
||||
};
|
||||
|
||||
inst.HandleDialogsByPlanner = true;
|
||||
_isTaskCompleted = _plan1st.IsNullOrEmpty() && _plan2nd.IsNullOrEmpty();
|
||||
|
||||
return inst;*/
|
||||
}
|
||||
|
||||
public List<RoleDialogModel> BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
|
||||
{
|
||||
var question = inst.Response;
|
||||
if (_executionContext.Count > 0)
|
||||
{
|
||||
var content = GetContext();
|
||||
question = $"CONTEXT:\r\n{content}\r\n" + inst.Response;
|
||||
}
|
||||
else
|
||||
{
|
||||
question = $"CONTEXT:\r\n{question}";
|
||||
}
|
||||
|
||||
var taskAgentDialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, question)
|
||||
{
|
||||
MessageId = message.MessageId,
|
||||
}
|
||||
};
|
||||
|
||||
return taskAgentDialogs;
|
||||
}
|
||||
|
||||
public bool AfterHandleContext(List<RoleDialogModel> dialogs, List<RoleDialogModel> taskAgentDialogs)
|
||||
{
|
||||
dialogs.AddRange(taskAgentDialogs.Skip(1));
|
||||
|
||||
// Keep execution context
|
||||
_executionContext.Add(taskAgentDialogs.Last().Content);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
|
||||
{
|
||||
var context = _services.GetRequiredService<IRoutingContext>();
|
||||
|
||||
if (message.StopCompletion || _isTaskCompleted)
|
||||
{
|
||||
context.Empty(reason: $"Agent queue is cleared by {nameof(TwoStagePlanner)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
routing.ResetRecursiveCounter();
|
||||
return true;
|
||||
}
|
||||
|
||||
public string GetContext()
|
||||
{
|
||||
var content = "";
|
||||
foreach (var c in _executionContext)
|
||||
{
|
||||
content += $"* {c}\r\n";
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private async Task<FirstStagePlan[]> GetFirstStagePlanAsync(Agent router, string messageId, List<RoleDialogModel> dialogs)
|
||||
{
|
||||
/*var fn = _services.GetRequiredService<IRoutingService>();
|
||||
await fn.InvokeFunction("plan_primary_stage", message);
|
||||
var items = message.Content.JsonArrayContent<SecondStagePlan>();*/
|
||||
|
||||
var firstStagePlanPrompt = await GetFirstStagePlanPrompt(router);
|
||||
|
||||
var plan = new FirstStagePlan[0];
|
||||
|
||||
var llmProviderService = _services.GetRequiredService<ILlmProviderService>();
|
||||
var provider = router.LlmConfig.Provider ?? "openai";
|
||||
var model = llmProviderService.GetProviderModel(provider, router.LlmConfig.Model ?? "gpt-4o");
|
||||
|
||||
// chat completion
|
||||
var completion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: provider,
|
||||
model: model.Name);
|
||||
|
||||
string text = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var response = await completion.GetChatCompletions(new Agent
|
||||
{
|
||||
Id = router.Id,
|
||||
Name = nameof(TwoStagePlanner),
|
||||
Instruction = firstStagePlanPrompt
|
||||
}, dialogs);
|
||||
|
||||
text = response.Content;
|
||||
plan = response.Content.JsonArrayContent<FirstStagePlan>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"{ex.Message}: {text}");
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
private async Task<string> GetFirstStagePlanPrompt(Agent router)
|
||||
{
|
||||
var template = router.Templates.First(x => x.Name == "two_stage.1st.plan").Content;
|
||||
var responseFormat = JsonSerializer.Serialize(new FirstStagePlan
|
||||
{
|
||||
Parameters = new JsonDocument[] { JsonDocument.Parse("{}") },
|
||||
Results = new string[] { "" }
|
||||
});
|
||||
|
||||
var relevantKnowledges = new List<string>();
|
||||
var hooks = _services.GetServices<IKnowledgeHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
var k = await hook.GetRelevantKnowledges();
|
||||
relevantKnowledges.AddRange(k);
|
||||
}
|
||||
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
{
|
||||
{ "response_format", responseFormat },
|
||||
{ "relevant_knowledges", relevantKnowledges.ToArray() }
|
||||
});
|
||||
}
|
||||
|
||||
private string GetFirstStageNextPrompt(Agent router)
|
||||
{
|
||||
var template = router.Templates.First(x => x.Name == "first_stage.next").Content;
|
||||
var responseFormat = JsonSerializer.Serialize(new FirstStagePlan
|
||||
{
|
||||
});
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
{
|
||||
{ "response_format", responseFormat },
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<SecondStagePlan[]> GetSecondStagePlanAsync(Agent router, string messageId, FirstStagePlan plan1st, List<RoleDialogModel> dialogs)
|
||||
{
|
||||
var secondStagePrompt = GetSecondStagePlanPrompt(router, plan1st);
|
||||
var firstStageSystemPrompt = await GetFirstStagePlanPrompt(router);
|
||||
|
||||
var plan = new SecondStagePlan[0];
|
||||
|
||||
var llmProviderService = _services.GetRequiredService<ILlmProviderService>();
|
||||
var model = llmProviderService.GetProviderModel("azure-openai", "gpt-4");
|
||||
|
||||
// chat completion
|
||||
var completion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: "azure-openai",
|
||||
model: model.Name);
|
||||
|
||||
string text = string.Empty;
|
||||
|
||||
var conversations = dialogs.Where(x => x.Role != AgentRole.Function).ToList();
|
||||
conversations.Add(new RoleDialogModel(AgentRole.User, secondStagePrompt)
|
||||
{
|
||||
CurrentAgentId = router.Id,
|
||||
MessageId = messageId,
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
var response = await completion.GetChatCompletions(new Agent
|
||||
{
|
||||
Id = router.Id,
|
||||
Name = nameof(TwoStagePlanner),
|
||||
Instruction = firstStageSystemPrompt
|
||||
}, conversations);
|
||||
|
||||
text = response.Content;
|
||||
plan = response.Content.JsonArrayContent<SecondStagePlan>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"{ex.Message}: {text}");
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
private string GetSecondStageTaskPrompt(Agent router, SecondStagePlan plan)
|
||||
{
|
||||
var template = router.Templates.First(x => x.Name == "planner_prompt.two_stage.2nd.task").Content;
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
{
|
||||
{ "task_description", plan.Description },
|
||||
{ "related_tables", plan.Tables },
|
||||
{ "input_arguments", JsonSerializer.Serialize(plan.Parameters) },
|
||||
{ "output_results", JsonSerializer.Serialize(plan.Results) },
|
||||
});
|
||||
}
|
||||
|
||||
private string GetSecondStagePlanPrompt(Agent router, FirstStagePlan plan)
|
||||
{
|
||||
var template = router.Templates.First(x => x.Name == "planner_prompt.two_stage.2nd.plan").Content;
|
||||
var responseFormat = JsonSerializer.Serialize(new SecondStagePlan
|
||||
{
|
||||
Tool = "tool name if task solution provided",
|
||||
Parameters = new JsonDocument[] { JsonDocument.Parse("{}") },
|
||||
Results = new string[] { "" }
|
||||
});
|
||||
var context = GetContext();
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
{
|
||||
{ "task_description", plan.Task },
|
||||
{ "response_format", responseFormat }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "282a7128-69a1-44b0-878c-a9159b88f3b9",
|
||||
"name": "Planner",
|
||||
"description": "Plan feasible implementation steps for complex problems",
|
||||
"type": "task",
|
||||
"createdDateTime": "2023-08-27T10:39:00Z",
|
||||
"updatedDateTime": "2023-08-27T14:39:00Z",
|
||||
"iconUrl": "https://e7.pngegg.com/pngimages/775/350/png-clipart-action-plan-computer-icons-plan-miscellaneous-text-thumbnail.png",
|
||||
"disabled": false,
|
||||
"isPublic": true,
|
||||
"profiles": [ "tool" ],
|
||||
"utilities": [ "two-stage-planner" ],
|
||||
"llmConfig": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o-mini"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
Use the TwoStagePlanner approach to plan the overall implementation steps, call plan_primary_stage.
|
||||
If need_additional_information is true, call plan_secondary_stage for the specific primary stage.
|
||||
Call plan_summary to summarize the final planning steps.
|
||||
|
|
@ -8,15 +8,11 @@
|
|||
"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"
|
||||
},
|
||||
"question": {
|
||||
"type": "string",
|
||||
"description": "Question convert from requirement and reference tables for knowledge search. The question should contain all the detailed information in the requirement"
|
||||
}
|
||||
},
|
||||
"required": [ "requirement_detail", "has_knowledge_reference", "question" ]
|
||||
"required": [ "requirement_detail", "question" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +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" ]
|
||||
// }
|
||||
//}
|
||||
{
|
||||
"name": "plan_secondary_stage",
|
||||
"description": "Based on the primary stage planning, make more detail steps of the second stage if the primary stage needs more information.",
|
||||
"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" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "plan_summary",
|
||||
"description": "Based on the planning steps, summarize the planning steps and output final steps.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
For every primary step, you have to call plan_secondary_stage to plan the detail steps to complete the primary step.
|
||||
For every primary step, if need_additional_information is true, you have to call plan_secondary_stage to plan the detail steps to complete the primary step.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Remove the unnecessary information, and output the final planning steps.
|
||||
|
|
@ -4,6 +4,7 @@ using BotSharp.Plugin.Twilio.Models;
|
|||
using BotSharp.Plugin.Twilio.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Twilio.Http;
|
||||
|
||||
namespace BotSharp.Plugin.Twilio.Controllers;
|
||||
|
||||
|
|
@ -39,13 +40,13 @@ public class TwilioVoiceController : TwilioController
|
|||
string conversationId = $"TwilioVoice_{request.CallSid}";
|
||||
var twilio = _services.GetRequiredService<TwilioService>();
|
||||
var url = $"twilio/voice/{conversationId}/receive/0?states={states}";
|
||||
var response = twilio.ReturnInstructions("twilio/welcome.mp3", url, true);
|
||||
var response = twilio.ReturnNoninterruptedInstructions(new List<string> { "twilio/welcome.mp3" }, url, true);
|
||||
return TwiML(response);
|
||||
}
|
||||
|
||||
[ValidateRequest]
|
||||
[HttpPost("twilio/voice/{conversationId}/receive/{seqNum}")]
|
||||
public async Task<TwiMLResult> ReceiveCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, [FromQuery] string states, VoiceRequest request)
|
||||
public async Task<TwiMLResult> ReceiveCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, [FromQuery] string states, [FromQuery] int attempts, VoiceRequest request)
|
||||
{
|
||||
var twilio = _services.GetRequiredService<TwilioService>();
|
||||
var messageQueue = _services.GetRequiredService<TwilioMessageQueue>();
|
||||
|
|
@ -61,17 +62,8 @@ public class TwilioVoiceController : TwilioController
|
|||
}
|
||||
|
||||
VoiceResponse response;
|
||||
if (messages.Count == 0 && seqNum == 0)
|
||||
{
|
||||
response = twilio.ReturnInstructions("twilio/welcome.mp3", $"twilio/voice/{conversationId}/receive/{seqNum}?states={states}", true, timeout: 2);
|
||||
}
|
||||
else
|
||||
if (messages.Any())
|
||||
{
|
||||
if (messages.Count == 0)
|
||||
{
|
||||
messages = await sessionManager.RetrieveStagedCallerMessagesAsync(conversationId, seqNum - 1);
|
||||
}
|
||||
|
||||
var messageContent = string.Join("\r\n", messages);
|
||||
var callerMessage = new CallerMessage()
|
||||
{
|
||||
|
|
@ -90,9 +82,26 @@ public class TwilioVoiceController : TwilioController
|
|||
}
|
||||
}
|
||||
|
||||
await messageQueue.EnqueueAsync(callerMessage);
|
||||
int audioIndex = Random.Shared.Next(1, 5);
|
||||
response = twilio.ReturnInstructions($"twilio/hold-on-{audioIndex}.mp3", $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true, 1);
|
||||
response = new VoiceResponse()
|
||||
.Redirect(new Uri($"{_settings.CallbackHost}/twilio/voice/{conversationId}/reply/{seqNum}?states={states}"), HttpMethod.Post);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (attempts >= 3)
|
||||
{
|
||||
var speechPaths = new List<string>();
|
||||
if (seqNum == 0)
|
||||
{
|
||||
speechPaths.Add("twilio/welcome.mp3");
|
||||
}
|
||||
else
|
||||
{
|
||||
var lastRepy = await sessionManager.GetAssistantReplyAsync(conversationId, seqNum - 1);
|
||||
speechPaths.Add($"twilio/voice/speeches/{conversationId}/{lastRepy.SpeechFileName}");
|
||||
}
|
||||
response = twilio.ReturnInstructions(speechPaths, $"twilio/voice/{conversationId}/receive/{seqNum}?states={states}", true);
|
||||
}
|
||||
response = twilio.ReturnInstructions(null, $"twilio/voice/{conversationId}/receive/{seqNum}?states={states}&attempts={++attempts}", true);
|
||||
}
|
||||
|
||||
return TwiML(response);
|
||||
|
|
@ -100,7 +109,8 @@ public class TwilioVoiceController : TwilioController
|
|||
|
||||
[ValidateRequest]
|
||||
[HttpPost("twilio/voice/{conversationId}/reply/{seqNum}")]
|
||||
public async Task<TwiMLResult> ReplyCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, [FromQuery] string states, VoiceRequest request)
|
||||
public async Task<TwiMLResult> ReplyCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum,
|
||||
[FromQuery] string states, VoiceRequest request)
|
||||
{
|
||||
var nextSeqNum = seqNum + 1;
|
||||
var sessionManager = _services.GetRequiredService<ITwilioSessionManager>();
|
||||
|
|
@ -118,25 +128,36 @@ public class TwilioVoiceController : TwilioController
|
|||
var indication = await sessionManager.GetReplyIndicationAsync(conversationId, seqNum);
|
||||
if (indication != null)
|
||||
{
|
||||
string speechPath;
|
||||
if (indication.StartsWith('#'))
|
||||
var speechPaths = new List<string>();
|
||||
int segIndex = 0;
|
||||
foreach (var text in indication.Split('|'))
|
||||
{
|
||||
speechPath = $"twilio/{indication.Substring(1)}";
|
||||
var seg = text.Trim();
|
||||
if (seg.StartsWith('#'))
|
||||
{
|
||||
speechPaths.Add($"twilio/{seg.Substring(1)}.mp3");
|
||||
}
|
||||
else
|
||||
{
|
||||
var textToSpeechService = CompletionProvider.GetTextToSpeech(_services, "openai", "tts-1");
|
||||
var fileService = _services.GetRequiredService<IFileStorageService>();
|
||||
var data = await textToSpeechService.GenerateSpeechFromTextAsync(seg);
|
||||
var fileName = $"indication_{seqNum}_{segIndex}.mp3";
|
||||
await fileService.SaveSpeechFileAsync(conversationId, fileName, data);
|
||||
speechPaths.Add($"twilio/voice/speeches/{conversationId}/{fileName}");
|
||||
segIndex++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1");
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var data = await completion.GenerateAudioFromTextAsync(indication);
|
||||
var fileName = $"indication_{seqNum}.mp3";
|
||||
fileStorage.SaveSpeechFile(conversationId, fileName, data);
|
||||
speechPath = $"twilio/voice/speeches/{conversationId}/{fileName}";
|
||||
}
|
||||
response = twilio.ReturnInstructions(speechPath, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true, 2);
|
||||
response = twilio.ReturnInstructions(speechPaths, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true);
|
||||
await sessionManager.RemoveReplyIndicationAsync(conversationId, seqNum);
|
||||
}
|
||||
else
|
||||
{
|
||||
response = twilio.ReturnInstructions(null, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true, 1);
|
||||
response = twilio.ReturnInstructions(new List<string>
|
||||
{
|
||||
$"twilio/hold-on-{Random.Shared.Next(1, 5)}.mp3",
|
||||
$"twilio/typing-{Random.Shared.Next(2, 4)}.mp3"
|
||||
}, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -147,7 +168,7 @@ public class TwilioVoiceController : TwilioController
|
|||
}
|
||||
else
|
||||
{
|
||||
response = twilio.ReturnInstructions($"twilio/voice/speeches/{conversationId}/{reply.SpeechFileName}", $"twilio/voice/{conversationId}/receive/{nextSeqNum}?states={states}", true);
|
||||
response = twilio.ReturnInstructions(new List<string> { $"twilio/voice/speeches/{conversationId}/{reply.SpeechFileName}" }, $"twilio/voice/{conversationId}/receive/{nextSeqNum}?states={states}", true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ namespace BotSharp.Plugin.Twilio.Services
|
|||
Task<List<string>> RetrieveStagedCallerMessagesAsync(string conversationId, int seqNum);
|
||||
Task SetReplyIndicationAsync(string conversationId, int seqNum, string indication);
|
||||
Task<string> GetReplyIndicationAsync(string conversationId, int seqNum);
|
||||
Task RemoveReplyIndicationAsync(string conversationId, int seqNum);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ namespace BotSharp.Plugin.Twilio.Services
|
|||
{
|
||||
reply = new AssistantMessage()
|
||||
{
|
||||
ConversationEnd = msg.Instruction.ConversationEnd,
|
||||
ConversationEnd = msg.Instruction?.ConversationEnd ?? false,
|
||||
Content = msg.Content,
|
||||
MessageId = msg.MessageId
|
||||
};
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ public class TwilioService
|
|||
return response;
|
||||
}
|
||||
|
||||
public VoiceResponse ReturnInstructions(string speechPath, string callbackPath, bool actionOnEmptyResult, int timeout = 3)
|
||||
public VoiceResponse ReturnInstructions(List<string> speechPaths, string callbackPath, bool actionOnEmptyResult, int timeout = 2)
|
||||
{
|
||||
var response = new VoiceResponse();
|
||||
var gather = new Gather()
|
||||
|
|
@ -77,24 +77,48 @@ public class TwilioService
|
|||
},
|
||||
Action = new Uri($"{_settings.CallbackHost}/{callbackPath}"),
|
||||
SpeechModel = Gather.SpeechModelEnum.PhoneCall,
|
||||
SpeechTimeout = timeout > 0 ? timeout.ToString() : "3",
|
||||
Timeout = timeout > 0 ? timeout : 3,
|
||||
SpeechTimeout = timeout > 0 ? timeout.ToString() : "2",
|
||||
Timeout = timeout > 0 ? timeout : 2,
|
||||
ActionOnEmptyResult = actionOnEmptyResult
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(speechPath))
|
||||
if (speechPaths != null && speechPaths.Any())
|
||||
{
|
||||
gather.Play(new Uri($"{_settings.CallbackHost}/{speechPath}"));
|
||||
if (speechPath.Contains("hold-on-"))
|
||||
foreach (var speechPath in speechPaths)
|
||||
{
|
||||
int audioIndex = Random.Shared.Next(1, 4);
|
||||
gather.Play(new Uri($"{_settings.CallbackHost}/twilio/typing-{audioIndex}.mp3"));
|
||||
gather.Play(new Uri($"{_settings.CallbackHost}/{speechPath}"));
|
||||
}
|
||||
}
|
||||
response.Append(gather);
|
||||
return response;
|
||||
}
|
||||
|
||||
public VoiceResponse ReturnNoninterruptedInstructions(List<string> speechPaths, string callbackPath, bool actionOnEmptyResult, int timeout = 2)
|
||||
{
|
||||
var response = new VoiceResponse();
|
||||
if (speechPaths != null && speechPaths.Any())
|
||||
{
|
||||
foreach (var speechPath in speechPaths)
|
||||
{
|
||||
response.Play(new Uri($"{_settings.CallbackHost}/{speechPath}"));
|
||||
}
|
||||
}
|
||||
var gather = new Gather()
|
||||
{
|
||||
Input = new List<Gather.InputEnum>()
|
||||
{
|
||||
Gather.InputEnum.Speech,
|
||||
Gather.InputEnum.Dtmf
|
||||
},
|
||||
Action = new Uri($"{_settings.CallbackHost}/{callbackPath}"),
|
||||
SpeechModel = Gather.SpeechModelEnum.PhoneCall,
|
||||
SpeechTimeout = timeout > 0 ? timeout.ToString() : "2",
|
||||
Timeout = timeout > 0 ? timeout : 2,
|
||||
ActionOnEmptyResult = actionOnEmptyResult
|
||||
};
|
||||
response.Append(gather);
|
||||
return response;
|
||||
}
|
||||
|
||||
public VoiceResponse HangUp(string speechPath)
|
||||
{
|
||||
var response = new VoiceResponse();
|
||||
|
|
|
|||
|
|
@ -57,5 +57,12 @@ namespace BotSharp.Plugin.Twilio.Services
|
|||
var key = $"{conversationId}:Indication:{seqNum}";
|
||||
return await db.StringGetAsync(key);
|
||||
}
|
||||
|
||||
public async Task RemoveReplyIndicationAsync(string conversationId, int seqNum)
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
var key = $"{conversationId}:Indication:{seqNum}";
|
||||
await db.KeyDeleteAsync(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue