Merge pull request #154 from hchen2020/master

Change RoutingArgs.
This commit is contained in:
Haiping 2023-09-25 17:48:35 -05:00 committed by GitHub
commit f0d8a74327
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 76 additions and 85 deletions

View file

@ -7,6 +7,7 @@ public interface IConversationStateService
{
ConversationState Load(string conversationId);
string GetState(string name, string defaultValue = "");
bool ContainsState(string name);
ConversationState GetStates();
IConversationStateService SetState<T>(string name, T value);
void CleanState();

View file

@ -3,13 +3,13 @@ using System.Text.Json;
namespace BotSharp.Abstraction.Functions.Models;
public class FunctionCallFromLlm
public class FunctionCallFromLlm : RoutingArgs
{
[JsonPropertyName("function")]
public string Function { get; set; } = string.Empty;
[JsonPropertyName("route")]
public RoutingArgs Route { get; set; } = new RoutingArgs();
[JsonPropertyName("reason")]
public string Reason { get; set; } = string.Empty;
[JsonPropertyName("question")]
public string? Question { get; set; }
@ -22,13 +22,15 @@ public class FunctionCallFromLlm
public override string ToString()
{
var route = string.IsNullOrEmpty(AgentName) ? "" : $"<Route to {AgentName.ToUpper()} because {Reason}>";
if (string.IsNullOrEmpty(Answer))
{
return $"[{Function} {Route} {JsonSerializer.Serialize(Arguments)}]: {Question}";
return $"[{Function} {route} {JsonSerializer.Serialize(Arguments)}]: {Question}";
}
else
{
return $"[{Function} {Route} {JsonSerializer.Serialize(Arguments)}]: {Question} => {Answer}";
return $"[{Function} {route} {JsonSerializer.Serialize(Arguments)}]: {Question} => {Answer}";
}
}
}

View file

@ -4,7 +4,6 @@ public interface IRoutingService
{
Agent LoadRouter();
List<RoleDialogModel> Dialogs { get; }
void SetDialogs(List<RoleDialogModel> dialogs);
Task<RoleDialogModel> InstructLoop(Agent router);
Task<RoleDialogModel> InstructLoop();
Task<RoleDialogModel> ExecuteOnce(Agent agent);
}

View file

@ -2,14 +2,11 @@ namespace BotSharp.Abstraction.Routing.Models;
public class RoutingArgs
{
[JsonPropertyName("reason")]
public string Reason { get; set; } = string.Empty;
[JsonPropertyName("agent")]
public string AgentName { get; set; } = string.Empty;
public override string ToString()
{
return string.IsNullOrEmpty(AgentName) ? "" : $"<Route to {AgentName.ToUpper()} because {Reason}>";
return AgentName;
}
}

View file

@ -1,5 +1,7 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
namespace BotSharp.Core.Agents.Services;
@ -20,6 +22,13 @@ public partial class AgentService
#endif
public async Task<Agent> GetAgent(string id)
{
var settings = _services.GetRequiredService<RoutingSettings>();
var routingService = _services.GetRequiredService<IRoutingService>();
if (settings.RouterId == id)
{
return routingService.LoadRouter();
}
var profile = _db.GetAgent(id);
var instructionFile = profile?.Instruction;

View file

@ -20,9 +20,7 @@ public partial class AgentService
hook.OnAgentLoading(ref id);
}
var settings = _services.GetRequiredService<RoutingSettings>();
var routingService = _services.GetRequiredService<IRoutingService>();
var agent = settings.RouterId == id ? routingService.LoadRouter() : await GetAgent(id);
var agent = await GetAgent(id);
var templateDict = new Dictionary<string, object>();
PopulateState(templateDict);

View file

@ -8,7 +8,7 @@ namespace BotSharp.Core.Conversations.Services;
public partial class ConversationService
{
public async Task<bool> SendMessage(string agentId,
RoleDialogModel lastDialog,
RoleDialogModel incoming,
Func<RoleDialogModel, Task> onMessageReceived,
Func<RoleDialogModel, Task> onFunctionExecuting,
Func<RoleDialogModel, Task> onFunctionExecuted)
@ -18,14 +18,11 @@ public partial class ConversationService
var agentService = _services.GetRequiredService<IAgentService>();
Agent agent = await agentService.LoadAgent(agentId);
_logger.LogInformation($"[{agent.Name}] {lastDialog.Role}: {lastDialog.Content}");
_logger.LogInformation($"[{agent.Name}] {incoming.Role}: {incoming.Content}");
lastDialog.CurrentAgentId = agent.Id;
var wholeDialogs = GetDialogHistory();
wholeDialogs.Add(lastDialog);
incoming.CurrentAgentId = agent.Id;
_storage.Append(_conversationId, lastDialog);
_storage.Append(_conversationId, incoming);
var hooks = _services.GetServices<IConversationHook>().ToList();
@ -35,18 +32,13 @@ public partial class ConversationService
hook.SetAgent(agent)
.SetConversation(conversation);
await hook.OnDialogsLoaded(wholeDialogs);
await hook.BeforeCompletion(lastDialog);
await hook.BeforeCompletion(incoming);
// Interrupted by hook
if (lastDialog.StopCompletion)
if (incoming.StopCompletion)
{
var message = new RoleDialogModel(AgentRole.Assistant, lastDialog.Content)
{
CurrentAgentId = agent.Id
};
await onMessageReceived(message);
_storage.Append(_conversationId, message);
await onMessageReceived(incoming);
_storage.Append(_conversationId, incoming);
return true;
}
}
@ -55,10 +47,8 @@ public partial class ConversationService
var routing = _services.GetRequiredService<IRoutingService>();
var settings = _services.GetRequiredService<RoutingSettings>();
routing.SetDialogs(wholeDialogs);
var response = settings.RouterId == agent.Id ?
await routing.InstructLoop(agent) :
var response = agentId == settings.RouterId ?
await routing.InstructLoop() :
await routing.ExecuteOnce(agent);
await HandleAssistantMessage(response, onMessageReceived);

View file

@ -13,7 +13,6 @@ public class ConversationStateService : IConversationStateService, IDisposable
private ConversationState _states;
private BotSharpDatabaseSettings _dbSettings;
private string _conversationId;
private string _file;
private readonly IBotSharpRepository _db;
private List<StateKeyValue> _savedStates;
@ -100,22 +99,6 @@ public class ConversationStateService : IConversationStateService, IDisposable
//File.Delete(_file);
}
private string GetStorageFile(string conversationId)
{
var dir = Path.Combine(_dbSettings.FileRepository, "conversations", conversationId);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
var stateFile = Path.Combine(dir, "state.dict");
if (!File.Exists(stateFile))
{
File.WriteAllText(stateFile, "");
}
return stateFile;
}
public ConversationState GetStates()
=> _states;
@ -138,4 +121,9 @@ public class ConversationStateService : IConversationStateService, IDisposable
{
Save();
}
public bool ContainsState(string name)
{
return _states.ContainsKey(name) && !string.IsNullOrEmpty(_states[name]);
}
}

View file

@ -30,7 +30,7 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHan
{
var routing = _services.GetRequiredService<IAgentRouting>();
var db = _services.GetRequiredService<IBotSharpRepository>();
var record = db.Agents.First(x => x.Name.ToLower() == inst.Route.AgentName.ToLower());
var record = db.Agents.First(x => x.Name.ToLower() == inst.AgentName.ToLower());
var result = new RoleDialogModel(AgentRole.Function, inst.Question)
{

View file

@ -26,7 +26,7 @@ public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase, IRouting
public async Task<RoleDialogModel> Handle(FunctionCallFromLlm inst)
{
var result = new RoleDialogModel(AgentRole.User, inst.Route.Reason)
var result = new RoleDialogModel(AgentRole.User, inst.Reason)
{
FunctionName = inst.Function,
StopCompletion = true

View file

@ -29,14 +29,14 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH
public async Task<RoleDialogModel> Handle(FunctionCallFromLlm inst)
{
if (string.IsNullOrEmpty(inst.Route.AgentName))
if (string.IsNullOrEmpty(inst.AgentName))
{
inst = await GetNextInstructionFromReasoner($"What's the next step? your response must have agent name.");
}
// Retrieve information from specific agent
var db = _services.GetRequiredService<IBotSharpRepository>();
var record = db.Agents.First(x => x.Name.ToLower() == inst.Route.AgentName.ToLower());
var record = db.Agents.First(x => x.Name.ToLower() == inst.AgentName.ToLower());
var response = await InvokeAgent(record.Id);
inst.Answer = response.Content;

View file

@ -15,9 +15,9 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
public List<NameDesc> Parameters => new List<NameDesc>
{
new NameDesc("agent", "the name of the agent from AGENTS"),
new NameDesc("agent", "the name of the agent"),
new NameDesc("reason", "why route to this agent"),
new NameDesc("args", "parameters extracted from context")
new NameDesc("args", "the agent required parameters")
};
public bool IsReasoning => false;
@ -29,18 +29,13 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
public async Task<RoleDialogModel> Handle(FunctionCallFromLlm inst)
{
if (string.IsNullOrEmpty(inst.Route.AgentName))
{
inst = await GetNextInstructionFromReasoner($"What's the next step? your response must have agent name.");
}
var function = _services.GetServices<IFunctionCallback>().FirstOrDefault(x => x.Name == inst.Function);
var message = new RoleDialogModel(AgentRole.Function, inst.Question)
{
FunctionName = inst.Function,
FunctionArgs = JsonSerializer.Serialize(new RoutingArgs
{
AgentName = inst.Route.AgentName
AgentName = inst.AgentName
}),
};

View file

@ -63,13 +63,20 @@ public abstract class RoutingHandlerBase
var pattern = @"\{(?:[^{}]|(?<open>\{)|(?<-open>\}))+(?(open)(?!))\}";
response.Content = Regex.Match(response.Content, pattern).Value;
args = JsonSerializer.Deserialize<FunctionCallFromLlm>(response.Content);
// Sometimes it populate malformed Function in Agent name
if (args.Function == args.AgentName)
{
args.Function = "route_to_agent";
_logger.LogWarning($"Captured LLM response ");
}
}
catch (Exception ex)
{
_logger.LogError($"{ex.Message}: {response.Content}");
args.Function = "response_to_user";
args.Answer = ex.Message;
args.Route.AgentName = _settings.RouterName;
args.AgentName = _settings.RouterName;
}
if (args.Arguments != null)

View file

@ -12,7 +12,18 @@ public class RoutingService : IRoutingService
private readonly RoutingSettings _settings;
private readonly ILogger _logger;
private List<RoleDialogModel> _dialogs;
public List<RoleDialogModel> Dialogs => _dialogs;
public List<RoleDialogModel> Dialogs {
get
{
if (_dialogs == null)
{
var conv = _services.GetRequiredService<IConversationService>();
_dialogs = conv.GetDialogHistory();
}
return _dialogs;
}
}
public RoutingService(IServiceProvider services,
RoutingSettings settings,
@ -23,42 +34,36 @@ public class RoutingService : IRoutingService
_logger = logger;
}
public void SetDialogs(List<RoleDialogModel> dialogs)
{
_dialogs = dialogs;
}
public async Task<RoleDialogModel> ExecuteOnce(Agent agent)
{
var message = _dialogs.Last().Content;
var message = Dialogs.Last().Content;
var handlers = _services.GetServices<IRoutingHandler>();
var handler = handlers.FirstOrDefault(x => x.Name == "route_to_agent");
handler.SetDialogs(_dialogs);
handler.SetDialogs(Dialogs);
var result = await handler.Handle(new FunctionCallFromLlm
{
Function = "route_to_agent",
Question = message,
Route = new RoutingArgs
{
Reason = message,
AgentName = agent.Name,
}
Function = "route_to_agent",
Question = message,
Reason = message,
AgentName = agent.Name
});
return result;
}
public async Task<RoleDialogModel> InstructLoop(Agent router)
public async Task<RoleDialogModel> InstructLoop()
{
var router = LoadRouter();
var result = new RoleDialogModel(AgentRole.Assistant, "Can you repeat your request again?")
{
CurrentAgentId = router.Id
};
var message = _dialogs.Last().Content;
foreach (var dialog in _dialogs.TakeLast(20))
var message = Dialogs.Last().Content;
foreach (var dialog in Dialogs.TakeLast(20))
{
router.Instruction += $"\r\n{dialog.Role}: {dialog.Content}";
}
@ -67,7 +72,7 @@ public class RoutingService : IRoutingService
var handler = handlers.FirstOrDefault(x => x.Name == "get_next_instruction");
handler.SetRouter(router);
handler.SetDialogs(_dialogs);
handler.SetDialogs(Dialogs);
int loopCount = 0;
var stop = false;
@ -86,7 +91,7 @@ public class RoutingService : IRoutingService
continue;
}
handler.SetRouter(router);
handler.SetDialogs(_dialogs);
handler.SetDialogs(Dialogs);
result = await handler.Handle(inst);
@ -115,7 +120,7 @@ public class RoutingService : IRoutingService
var prompt = @"You're a Router with reasoning. Follow these steps to handle user's request:
1. Read the CONVERSATION context.
2. Select a appropriate function from FUNCTIONS.
3. Determine which agent from AGENTS is suitable for the current task.
3. Determine which agent is suitable according to conversation context.
4. Re-think about selected function is from FUNCTIONS to handle the request.";
// Append function
@ -133,7 +138,7 @@ public class RoutingService : IRoutingService
prompt += "\r\nParameters:";
handler.Parameters.Select((p, i) =>
{
prompt += $"\r\n{i + 1}. {p.Name}: {p.Description}";
prompt += $"\r\n - {p.Name}: {p.Description}";
return p;
}).ToList();
}