task completed hook.
This commit is contained in:
parent
483eb5c1d3
commit
33df282add
|
|
@ -44,7 +44,7 @@ public abstract class ConversationHookBase : IConversationHook
|
||||||
public virtual Task OnConversationEnding(RoleDialogModel message)
|
public virtual Task OnConversationEnding(RoleDialogModel message)
|
||||||
=> Task.CompletedTask;
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
public virtual Task OnCurrentTaskEnding(RoleDialogModel message)
|
public virtual Task OnTaskCompleted(RoleDialogModel message)
|
||||||
=> Task.CompletedTask;
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
public virtual Task OnHumanInterventionNeeded(RoleDialogModel message)
|
public virtual Task OnHumanInterventionNeeded(RoleDialogModel message)
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ public interface IConversationHook
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="conversation"></param>
|
/// <param name="conversation"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task OnCurrentTaskEnding(RoleDialogModel message);
|
Task OnTaskCompleted(RoleDialogModel message);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// LLM detected the whole conversation is going to be end.
|
/// LLM detected the whole conversation is going to be end.
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ public class FunctionCallFromLlm : RoutingArgs
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var route = string.IsNullOrEmpty(AgentName) ? "" : $"<Route to {AgentName.ToUpper()} because {Reason}>";
|
var route = string.IsNullOrEmpty(AgentName) ? "" : $"<Route to {AgentName.ToUpper()} because {NextActionReason}>";
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Response))
|
if (string.IsNullOrEmpty(Response))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,14 @@ public class RoutingArgs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("next_action_reason")]
|
[JsonPropertyName("next_action_reason")]
|
||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
public string Reason { get; set; } = string.Empty;
|
public string? NextActionReason { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("reason")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Reason { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("conversation_end")]
|
||||||
|
public bool ConversationEnd { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The content of replying to user
|
/// The content of replying to user
|
||||||
|
|
@ -39,7 +46,7 @@ public class RoutingArgs
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var route = string.IsNullOrEmpty(AgentName) ? "" : $"<Route to {AgentName.ToUpper()} because {Reason}>";
|
var route = string.IsNullOrEmpty(AgentName) ? "" : $"<Route to {AgentName.ToUpper()} because {NextActionReason}>";
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Response))
|
if (string.IsNullOrEmpty(Response))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@
|
||||||
<PackageReference Include="Aspects.Cache" Version="2.0.4" />
|
<PackageReference Include="Aspects.Cache" Version="2.0.4" />
|
||||||
<PackageReference Include="Colorful.Console" Version="1.2.15" />
|
<PackageReference Include="Colorful.Console" Version="1.2.15" />
|
||||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="6.3.0" />
|
<PackageReference Include="EntityFrameworkCore.BootKit" Version="6.3.0" />
|
||||||
<PackageReference Include="Fluid.Core" Version="2.5.0" />
|
<PackageReference Include="Fluid.Core" Version="2.7.0" />
|
||||||
<PackageReference Include="Nanoid" Version="3.0.0" />
|
<PackageReference Include="Nanoid" Version="3.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,16 @@ public partial class ConversationService
|
||||||
if (response.Instruction != null)
|
if (response.Instruction != null)
|
||||||
{
|
{
|
||||||
var conversation = _services.GetRequiredService<IConversationService>();
|
var conversation = _services.GetRequiredService<IConversationService>();
|
||||||
var updatedConversation = await conversation.UpdateConversationTitle(_conversationId, response.Instruction.Reason);
|
var updatedConversation = await conversation.UpdateConversationTitle(_conversationId, response.Instruction.NextActionReason);
|
||||||
|
|
||||||
|
// Emit conversation ending hook
|
||||||
|
if (response.Instruction.ConversationEnd)
|
||||||
|
{
|
||||||
|
foreach (var hook in hooks)
|
||||||
|
{
|
||||||
|
await hook.OnConversationEnding(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ public partial class RouteToAgentFn : IFunctionCallback
|
||||||
// Push next action agent
|
// Push next action agent
|
||||||
if (!string.IsNullOrEmpty(args.AgentName) && args.AgentName.Length < 32)
|
if (!string.IsNullOrEmpty(args.AgentName) && args.AgentName.Length < 32)
|
||||||
{
|
{
|
||||||
_context.Push(args.AgentName, args.Reason);
|
_context.Push(args.AgentName, args.NextActionReason);
|
||||||
states.SetState("next_action_agent", args.AgentName, isNeedVersion: true);
|
states.SetState("next_action_agent", args.AgentName, isNeedVersion: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using BotSharp.Abstraction.Functions.Models;
|
|
||||||
using BotSharp.Abstraction.Routing;
|
|
||||||
using BotSharp.Abstraction.Routing.Settings;
|
using BotSharp.Abstraction.Routing.Settings;
|
||||||
|
|
||||||
namespace BotSharp.Core.Routing.Handlers;
|
namespace BotSharp.Core.Routing.Handlers;
|
||||||
|
|
@ -16,12 +14,15 @@ public class ConversationEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||||
new ParameterPropertyDef("response", "response content to user")
|
new ParameterPropertyDef("response", "response content to user")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public List<string> Planers => new List<string>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
public ConversationEndRoutingHandler(IServiceProvider services, ILogger<ConversationEndRoutingHandler> logger, RoutingSettings settings)
|
public ConversationEndRoutingHandler(IServiceProvider services, ILogger<ConversationEndRoutingHandler> logger, RoutingSettings settings)
|
||||||
: base(services, logger, settings)
|
: base(services, logger, settings)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
||||||
{
|
{
|
||||||
var response = new RoleDialogModel(AgentRole.Assistant, inst.Response)
|
var response = new RoleDialogModel(AgentRole.Assistant, inst.Response)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using BotSharp.Abstraction.Functions.Models;
|
|
||||||
using BotSharp.Abstraction.Routing;
|
|
||||||
using BotSharp.Abstraction.Routing.Settings;
|
using BotSharp.Abstraction.Routing.Settings;
|
||||||
|
|
||||||
namespace BotSharp.Core.Routing.Handlers;
|
namespace BotSharp.Core.Routing.Handlers;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
using BotSharp.Abstraction.Functions.Models;
|
|
||||||
using BotSharp.Abstraction.Routing;
|
|
||||||
using BotSharp.Abstraction.Routing.Models;
|
|
||||||
using BotSharp.Abstraction.Routing.Settings;
|
using BotSharp.Abstraction.Routing.Settings;
|
||||||
using BotSharp.Core.Routing.Planning;
|
using BotSharp.Core.Routing.Planning;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
using BotSharp.Abstraction.Routing.Settings;
|
||||||
|
using BotSharp.Core.Routing.Planning;
|
||||||
|
|
||||||
|
namespace BotSharp.Core.Routing.Handlers;
|
||||||
|
|
||||||
|
public class TaskCompletedRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||||
|
{
|
||||||
|
public string Name => "task_completed";
|
||||||
|
|
||||||
|
public string Description => "User task is completed.";
|
||||||
|
|
||||||
|
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
|
||||||
|
{
|
||||||
|
new ParameterPropertyDef("reason", "why the task is completed")
|
||||||
|
{
|
||||||
|
Required = true
|
||||||
|
},
|
||||||
|
new ParameterPropertyDef("response", "polite response when the task is completed")
|
||||||
|
{
|
||||||
|
Required = true
|
||||||
|
},
|
||||||
|
new ParameterPropertyDef("conversation_end", "whether to end this conversation, true or false")
|
||||||
|
{
|
||||||
|
Required = true,
|
||||||
|
Type = "boolean"
|
||||||
|
},
|
||||||
|
new ParameterPropertyDef("abandoned_arguments", "the arguments next task can't reuse")
|
||||||
|
};
|
||||||
|
|
||||||
|
public List<string> Planers => new List<string>
|
||||||
|
{
|
||||||
|
nameof(NaivePlanner),
|
||||||
|
nameof(HFPlanner)
|
||||||
|
};
|
||||||
|
|
||||||
|
public TaskCompletedRoutingHandler(IServiceProvider services, ILogger<TaskCompletedRoutingHandler> logger, RoutingSettings settings)
|
||||||
|
: base(services, logger, settings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
||||||
|
{
|
||||||
|
var response = new RoleDialogModel(AgentRole.Assistant, inst.Response)
|
||||||
|
{
|
||||||
|
CurrentAgentId = message.CurrentAgentId,
|
||||||
|
MessageId = message.MessageId,
|
||||||
|
StopCompletion = true,
|
||||||
|
FunctionName = inst.Function,
|
||||||
|
Instruction = inst,
|
||||||
|
};
|
||||||
|
|
||||||
|
_dialogs.Add(response);
|
||||||
|
|
||||||
|
var hooks = _services.GetServices<IConversationHook>()
|
||||||
|
.OrderBy(x => x.Priority)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var hook in hooks)
|
||||||
|
{
|
||||||
|
await hook.OnTaskCompleted(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
using BotSharp.Abstraction.Functions.Models;
|
|
||||||
using BotSharp.Abstraction.Routing;
|
|
||||||
using BotSharp.Abstraction.Routing.Settings;
|
|
||||||
using BotSharp.Core.Routing.Planning;
|
|
||||||
|
|
||||||
namespace BotSharp.Core.Routing.Handlers;
|
|
||||||
|
|
||||||
public class TaskEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|
||||||
{
|
|
||||||
public string Name => "task_end";
|
|
||||||
|
|
||||||
public string Description => "Call this function when current task is completed.";
|
|
||||||
|
|
||||||
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
|
|
||||||
{
|
|
||||||
new ParameterPropertyDef("abandoned_arguments", "the arguments next task can't reuse")
|
|
||||||
};
|
|
||||||
|
|
||||||
public List<string> Planers => new List<string>
|
|
||||||
{
|
|
||||||
nameof(HFPlanner)
|
|
||||||
};
|
|
||||||
|
|
||||||
public TaskEndRoutingHandler(IServiceProvider services, ILogger<TaskEndRoutingHandler> logger, RoutingSettings settings)
|
|
||||||
: base(services, logger, settings)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
|
||||||
{
|
|
||||||
var hooks = _services.GetServices<IConversationHook>()
|
|
||||||
.OrderBy(x => x.Priority)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
Task.WaitAll(hooks
|
|
||||||
.Select(h => h.OnCurrentTaskEnding(message))
|
|
||||||
.ToArray());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -31,7 +31,7 @@ public class SequentialPlanner : IPlaner
|
||||||
if (decomposation.TotalRemainingSteps > 0 && _lastInst != null)
|
if (decomposation.TotalRemainingSteps > 0 && _lastInst != null)
|
||||||
{
|
{
|
||||||
_lastInst.Response = decomposation.Description;
|
_lastInst.Response = decomposation.Description;
|
||||||
_lastInst.Reason = $"Having {decomposation.TotalRemainingSteps} steps left.";
|
_lastInst.NextActionReason = $"Having {decomposation.TotalRemainingSteps} steps left.";
|
||||||
return _lastInst;
|
return _lastInst;
|
||||||
}
|
}
|
||||||
else if (decomposation.TotalRemainingSteps == 0 || decomposation.ShouldStop)
|
else if (decomposation.TotalRemainingSteps == 0 || decomposation.ShouldStop)
|
||||||
|
|
@ -102,7 +102,7 @@ public class SequentialPlanner : IPlaner
|
||||||
if (decomposation.TotalRemainingSteps > 0)
|
if (decomposation.TotalRemainingSteps > 0)
|
||||||
{
|
{
|
||||||
inst.Response = decomposation.Description;
|
inst.Response = decomposation.Description;
|
||||||
inst.Reason = $"{decomposation.TotalRemainingSteps} steps left.";
|
inst.NextActionReason = $"{decomposation.TotalRemainingSteps} steps left.";
|
||||||
inst.HandleDialogsByPlanner = true;
|
inst.HandleDialogsByPlanner = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ public class FirstStagePlan
|
||||||
[JsonPropertyName("related_tables")]
|
[JsonPropertyName("related_tables")]
|
||||||
public string[] Tables { get; set; } = new string[0];
|
public string[] Tables { get; set; } = new string[0];
|
||||||
|
|
||||||
|
[JsonPropertyName("related_urls")]
|
||||||
|
public string[] Urls { get; set; } = new string[0];
|
||||||
|
|
||||||
[JsonPropertyName("input_args")]
|
[JsonPropertyName("input_args")]
|
||||||
public JsonDocument[] Parameters { get; set; } = new JsonDocument[0];
|
public JsonDocument[] Parameters { get; set; } = new JsonDocument[0];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ public class RoutingContext : IRoutingContext
|
||||||
{
|
{
|
||||||
Function = "route_to_agent",
|
Function = "route_to_agent",
|
||||||
AgentName = agent.Name,
|
AgentName = agent.Name,
|
||||||
Reason = $"User manually route to agent {agent.Name}"
|
NextActionReason = $"User manually route to agent {agent.Name}"
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ public partial class RoutingService : IRoutingService
|
||||||
{
|
{
|
||||||
Function = "route_to_agent",
|
Function = "route_to_agent",
|
||||||
Question = message.Content,
|
Question = message.Content,
|
||||||
Reason = message.Content,
|
NextActionReason = message.Content,
|
||||||
AgentName = agent.Name,
|
AgentName = agent.Name,
|
||||||
OriginalAgent = agent.Name,
|
OriginalAgent = agent.Name,
|
||||||
ExecutingDirectly = true
|
ExecutingDirectly = true
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ You're {{router.name}} ({{router.description}}). Follow these steps to handle us
|
||||||
6. Please do not make up any parameters when there is no exact information available, leave it blank.
|
6. Please do not make up any parameters when there is no exact information available, leave it blank.
|
||||||
7. Response must be in JSON format.
|
7. Response must be in JSON format.
|
||||||
|
|
||||||
|
{% if routing_requirements and routing_requirements != empty %}
|
||||||
[REQUIREMENTS]
|
[REQUIREMENTS]
|
||||||
{% for requirement in routing_requirements %}
|
{% for requirement in routing_requirements %}
|
||||||
# {{ requirement }}
|
# {{ requirement }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
[FUNCTIONS]
|
[FUNCTIONS]
|
||||||
{% for handler in routing_handlers %}
|
{% for handler in routing_handlers %}
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,5 @@ Route to the appropriate agent last handled agent based on the context.
|
||||||
{% if expected_next_action_agent != empty -%}
|
{% if expected_next_action_agent != empty -%}
|
||||||
Expected next action agent is {{ expected_next_action_agent }}.
|
Expected next action agent is {{ expected_next_action_agent }}.
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
Try to keep the User Goal Agent be consistent as previous goal agent.
|
If user completes the task, use function task_completed.
|
||||||
If user wants to speak to customer service, use function human_intervention_needed.
|
If user wants to speak to customer service, use function human_intervention_needed.
|
||||||
|
|
@ -41,6 +41,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
||||||
_routingCtx = routingCtx;
|
_routingCtx = routingCtx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region IConversationHook
|
||||||
public override async Task OnMessageReceived(RoleDialogModel message)
|
public override async Task OnMessageReceived(RoleDialogModel message)
|
||||||
{
|
{
|
||||||
var conversationId = _state.GetConversationId();
|
var conversationId = _state.GetConversationId();
|
||||||
|
|
@ -156,6 +157,38 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task OnTaskCompleted(RoleDialogModel message)
|
||||||
|
{
|
||||||
|
var conversationId = _state.GetConversationId();
|
||||||
|
var log = $"{message.Content}";
|
||||||
|
var agent = await _agentService.LoadAgent(message.CurrentAgentId);
|
||||||
|
|
||||||
|
var input = new ContentLogInputModel(conversationId, message)
|
||||||
|
{
|
||||||
|
Name = agent.Name,
|
||||||
|
Source = ContentLogSource.FunctionCall,
|
||||||
|
Log = log
|
||||||
|
};
|
||||||
|
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnConversationEnding(RoleDialogModel message)
|
||||||
|
{
|
||||||
|
var conversationId = _state.GetConversationId();
|
||||||
|
var log = $"Conversation ended";
|
||||||
|
var agent = await _agentService.LoadAgent(message.CurrentAgentId);
|
||||||
|
|
||||||
|
var input = new ContentLogInputModel(conversationId, message)
|
||||||
|
{
|
||||||
|
Name = agent?.Name ?? "System",
|
||||||
|
Source = ContentLogSource.FunctionCall,
|
||||||
|
Log = log
|
||||||
|
};
|
||||||
|
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region IRoutingHook
|
#region IRoutingHook
|
||||||
public async Task OnAgentEnqueued(string agentId, string preAgentId, string? reason = null)
|
public async Task OnAgentEnqueued(string agentId, string preAgentId, string? reason = null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,11 @@ public class PlaywrightInstance : IDisposable
|
||||||
{
|
{
|
||||||
if (_contexts.ContainsKey(id))
|
if (_contexts.ContainsKey(id))
|
||||||
return;
|
return;
|
||||||
|
#if DEBUG
|
||||||
string tempFolderPath = $"{Path.GetTempPath()}\\playwright";
|
string tempFolderPath = $"{Path.GetTempPath()}\\playwright";
|
||||||
|
#else
|
||||||
|
string tempFolderPath = $"{Path.GetTempPath()}\\playwright\\{id}";
|
||||||
|
#endif
|
||||||
_contexts[id] = await _playwright.Chromium.LaunchPersistentContextAsync(tempFolderPath, new BrowserTypeLaunchPersistentContextOptions
|
_contexts[id] = await _playwright.Chromium.LaunchPersistentContextAsync(tempFolderPath, new BrowserTypeLaunchPersistentContextOptions
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,9 @@ public class PizzaTypeConversationHook : ConversationHookBase
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task OnTaskCompleted(RoleDialogModel message)
|
||||||
|
{
|
||||||
|
return base.OnTaskCompleted(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue