Merge branch 'SciSharp:master' into master

This commit is contained in:
hchen2020 2024-03-22 14:09:05 -05:00 committed by GitHub
commit 531acfc8d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 166 additions and 65 deletions

View file

@ -44,7 +44,7 @@ public abstract class ConversationHookBase : IConversationHook
public virtual Task OnConversationEnding(RoleDialogModel message)
=> Task.CompletedTask;
public virtual Task OnCurrentTaskEnding(RoleDialogModel message)
public virtual Task OnTaskCompleted(RoleDialogModel message)
=> Task.CompletedTask;
public virtual Task OnHumanInterventionNeeded(RoleDialogModel message)

View file

@ -70,7 +70,7 @@ public interface IConversationHook
/// </summary>
/// <param name="conversation"></param>
/// <returns></returns>
Task OnCurrentTaskEnding(RoleDialogModel message);
Task OnTaskCompleted(RoleDialogModel message);
/// <summary>
/// LLM detected the whole conversation is going to be end.

View file

@ -35,7 +35,7 @@ public class FunctionCallFromLlm : RoutingArgs
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))
{

View file

@ -1,3 +1,5 @@
using BotSharp.Abstraction.Messaging.Enums;
namespace BotSharp.Abstraction.Messaging;
public interface IRichMessage
@ -6,5 +8,5 @@ public interface IRichMessage
string Text { get; set; }
[JsonPropertyName("rich_type")]
string RichType => "text";
string RichType => RichTypeEnum.Text;
}

View file

@ -10,7 +10,14 @@ public class RoutingArgs
/// </summary>
[JsonPropertyName("next_action_reason")]
[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>
/// The content of replying to user
@ -39,7 +46,7 @@ public class RoutingArgs
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))
{

View file

@ -23,6 +23,7 @@ public class RoutingRule
public bool Required { get; set; }
public string? RedirectTo { get; set; }
public string? RedirectToAgentName { get; set; }
public override string ToString()
{

View file

@ -134,7 +134,7 @@
<PackageReference Include="Aspects.Cache" Version="2.0.4" />
<PackageReference Include="Colorful.Console" Version="1.2.15" />
<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" />
</ItemGroup>

View file

@ -153,7 +153,16 @@ public partial class ConversationService
if (response.Instruction != null)
{
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);
}
}
}
}
}

View file

@ -57,7 +57,7 @@ public partial class RouteToAgentFn : IFunctionCallback
// Push next action agent
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);
}

View file

@ -1,5 +1,3 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
namespace BotSharp.Core.Routing.Handlers;
@ -16,12 +14,15 @@ public class ConversationEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
new ParameterPropertyDef("response", "response content to user")
};
public List<string> Planers => new List<string>
{
};
public ConversationEndRoutingHandler(IServiceProvider services, ILogger<ConversationEndRoutingHandler> 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)

View file

@ -1,5 +1,3 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
namespace BotSharp.Core.Routing.Handlers;

View file

@ -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.Core.Routing.Planning;

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -31,7 +31,7 @@ public class SequentialPlanner : IPlaner
if (decomposation.TotalRemainingSteps > 0 && _lastInst != null)
{
_lastInst.Response = decomposation.Description;
_lastInst.Reason = $"Having {decomposation.TotalRemainingSteps} steps left.";
_lastInst.NextActionReason = $"Having {decomposation.TotalRemainingSteps} steps left.";
return _lastInst;
}
else if (decomposation.TotalRemainingSteps == 0 || decomposation.ShouldStop)
@ -102,7 +102,7 @@ public class SequentialPlanner : IPlaner
if (decomposation.TotalRemainingSteps > 0)
{
inst.Response = decomposation.Description;
inst.Reason = $"{decomposation.TotalRemainingSteps} steps left.";
inst.NextActionReason = $"{decomposation.TotalRemainingSteps} steps left.";
inst.HandleDialogsByPlanner = true;
}

View file

@ -19,6 +19,9 @@ public class FirstStagePlan
[JsonPropertyName("related_tables")]
public string[] Tables { get; set; } = new string[0];
[JsonPropertyName("related_urls")]
public string[] Urls { get; set; } = new string[0];
[JsonPropertyName("input_args")]
public JsonDocument[] Parameters { get; set; } = new JsonDocument[0];

View file

@ -122,7 +122,7 @@ public class RoutingContext : IRoutingContext
{
Function = "route_to_agent",
AgentName = agent.Name,
Reason = $"User manually route to agent {agent.Name}"
NextActionReason = $"User manually route to agent {agent.Name}"
})
};

View file

@ -54,7 +54,7 @@ public partial class RoutingService : IRoutingService
{
Function = "route_to_agent",
Question = message.Content,
Reason = message.Content,
NextActionReason = message.Content,
AgentName = agent.Name,
OriginalAgent = agent.Name,
ExecutingDirectly = true

View file

@ -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.
7. Response must be in JSON format.
{% if routing_requirements and routing_requirements != empty %}
[REQUIREMENTS]
{% for requirement in routing_requirements %}
# {{ requirement }}
{% endfor %}
{% endif %}
[FUNCTIONS]
{% for handler in routing_handlers %}

View file

@ -3,5 +3,5 @@ Route to the appropriate agent last handled agent based on the context.
{% if expected_next_action_agent != empty -%}
Expected next action agent is {{ expected_next_action_agent }}.
{%- 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.

View file

@ -29,7 +29,23 @@ public class AgentController : ControllerBase
{
AgentIds = new List<string> { id }
});
return agents.Items.FirstOrDefault();
var targetAgent = agents.Items.FirstOrDefault();
var redirectAgentIds = targetAgent.RoutingRules
.Where(x => !string.IsNullOrEmpty(x.RedirectTo))
.Select(x => x.RedirectTo).ToList();
var redirectAgents = await _agentService.GetAgents(new AgentFilter
{
AgentIds = redirectAgentIds
});
foreach (var rule in targetAgent.RoutingRules)
{
var found = redirectAgents.Items.FirstOrDefault(x => x.Id == rule.RedirectTo);
if (found == null) continue;
rule.RedirectToAgentName = found.Name;
}
return targetAgent;
}
[HttpGet("/agents")]

View file

@ -41,6 +41,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
_routingCtx = routingCtx;
}
#region IConversationHook
public override async Task OnMessageReceived(RoleDialogModel message)
{
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
public async Task OnAgentEnqueued(string agentId, string preAgentId, string? reason = null)
{

View file

@ -26,8 +26,11 @@ public class PlaywrightInstance : IDisposable
{
if (_contexts.ContainsKey(id))
return;
#if DEBUG
string tempFolderPath = $"{Path.GetTempPath()}\\playwright";
#else
string tempFolderPath = $"{Path.GetTempPath()}\\playwright\\{id}";
#endif
_contexts[id] = await _playwright.Chromium.LaunchPersistentContextAsync(tempFolderPath, new BrowserTypeLaunchPersistentContextOptions
{
#if DEBUG

View file

@ -13,4 +13,9 @@ public class PizzaTypeConversationHook : ConversationHookBase
}
return;
}
public override Task OnTaskCompleted(RoleDialogModel message)
{
return base.OnTaskCompleted(message);
}
}