commit
f0d8a74327
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue