Re-define FunctionCallFromLlm.
This commit is contained in:
parent
8abc002f14
commit
4d449ff7d7
|
|
@ -1,4 +1,5 @@
|
|||
using BotSharp.Abstraction.Routing.Models;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BotSharp.Abstraction.Functions.Models;
|
||||
|
||||
|
|
@ -7,14 +8,27 @@ public class FunctionCallFromLlm
|
|||
[JsonPropertyName("function")]
|
||||
public string Function { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
[JsonPropertyName("route")]
|
||||
public RoutingArgs Route { get; set; } = new RoutingArgs();
|
||||
|
||||
[JsonPropertyName("parameters")]
|
||||
public RetrievalArgs Parameters { get; set; } = new RetrievalArgs();
|
||||
[JsonPropertyName("question")]
|
||||
public string? Question { get; set; }
|
||||
|
||||
[JsonPropertyName("answer")]
|
||||
public string? Answer { get; set; }
|
||||
|
||||
[JsonPropertyName("args")]
|
||||
public JsonDocument Arguments { get; set; } = JsonDocument.Parse("{}");
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Function} ({Reason}) {Parameters}";
|
||||
if (string.IsNullOrEmpty(Answer))
|
||||
{
|
||||
return $"[{Function} {Route} {JsonSerializer.Serialize(Arguments)}]: {Question}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"[{Function} {Route} {JsonSerializer.Serialize(Arguments)}]: {Question} => {Answer}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Routing;
|
||||
|
||||
public interface IRoutingHandler
|
||||
{
|
||||
string Name { get; }
|
||||
string Description { get; }
|
||||
bool IsReasoning { get; }
|
||||
bool RequireAgent { get; }
|
||||
List<string> Parameters { get; }
|
||||
void SetRouter(Agent router);
|
||||
void SetDialogs(List<RoleDialogModel> dialogs);
|
||||
Task<FunctionCallFromLlm> GetNextInstructionFromReasoner(string prompt);
|
||||
Task<RoleDialogModel> GetResponseFromReasoner();
|
||||
Task<RoleDialogModel> Handle(FunctionCallFromLlm inst);
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
using System.Text.Json;
|
||||
|
||||
namespace BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
public class RetrievalArgs : RoutingArgs
|
||||
{
|
||||
[JsonPropertyName("question")]
|
||||
public string Question { get; set; }
|
||||
|
||||
[JsonPropertyName("answer")]
|
||||
public string Answer { get; set; }
|
||||
|
||||
[JsonPropertyName("args")]
|
||||
public JsonDocument Arguments { get; set; } = JsonDocument.Parse("{}");
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Answer))
|
||||
{
|
||||
return $"[{AgentName}]: ({JsonSerializer.Serialize(Arguments)}) {Question}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"[{AgentName}]: ({JsonSerializer.Serialize(Arguments)}) {Question} => {Answer}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,18 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
public class RoutingArgs
|
||||
{
|
||||
[JsonPropertyName("user_goal")]
|
||||
public string UserGoal { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("agent_name")]
|
||||
public string AgentName { get; set; } = string.Empty;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return AgentName;
|
||||
return string.IsNullOrEmpty(AgentName) ? "" : $"<Route to {AgentName.ToUpper()} because {Reason}>";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
namespace BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
public class RoutingHandlerDef
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public List<string> Parameters { get; set; }
|
||||
}
|
||||
|
|
@ -87,9 +87,10 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspects.Cache" Version="2.0.3" />
|
||||
<PackageReference Include="Colorful.Console" Version="1.2.15" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="6.2.1" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.4.0" />
|
||||
<PackageReference Include="TensorFlow.Keras" Version="0.11.2" />
|
||||
<PackageReference Include="TensorFlow.Keras" Version="0.11.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using BotSharp.Abstraction.Templating;
|
|||
using BotSharp.Core.Instructs;
|
||||
using BotSharp.Abstraction.Instructs;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Core.Routing.Handlers;
|
||||
|
||||
namespace BotSharp.Core;
|
||||
|
||||
|
|
@ -56,7 +57,17 @@ public static class BotSharpServiceCollectionExtensions
|
|||
// Register function callback
|
||||
services.AddScoped<IFunctionCallback, RouteToAgentFn>();
|
||||
|
||||
// Register routing and handlers
|
||||
services.AddScoped<IRoutingService, RoutingService>();
|
||||
services.AddScoped<IRoutingHandler, GetNextInstructionRoutingHandler>();
|
||||
services.AddScoped<IRoutingHandler, ResponseToUserRoutingHandler>();
|
||||
services.AddScoped<IRoutingHandler, InterruptTaskExecutionRoutingHandler>();
|
||||
services.AddScoped<IRoutingHandler, RouteToAgentRoutingHandler>();
|
||||
services.AddScoped<IRoutingHandler, ContinueExecuteTaskRoutingHandler>();
|
||||
services.AddScoped<IRoutingHandler, RetrieveDataFromAgentRoutingHandler>();
|
||||
services.AddScoped<IRoutingHandler, TaskEndRoutingHandler>();
|
||||
services.AddScoped<IRoutingHandler, ConversationEndRoutingHandler>();
|
||||
services.AddScoped<IRoutingHandler, TransferToCsrRoutingHandler>();
|
||||
|
||||
if (myDatabaseSettings.Default == "FileRepository")
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public partial class ConversationService
|
|||
text = latestResponse.Content.Split("=>").Last();
|
||||
}
|
||||
|
||||
await HandleAssistantMessage(agent, new RoleDialogModel(AgentRole.Assistant, text)
|
||||
await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, text)
|
||||
{
|
||||
CurrentAgentId = agent.Id
|
||||
}, onMessageReceived);
|
||||
|
|
@ -37,7 +37,7 @@ public partial class ConversationService
|
|||
|
||||
var result = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg =>
|
||||
{
|
||||
await HandleAssistantMessage(agent, msg, onMessageReceived);
|
||||
await HandleAssistantMessage(msg, onMessageReceived);
|
||||
}, async fn =>
|
||||
{
|
||||
var preAgentId = agent.Id;
|
||||
|
|
@ -47,7 +47,7 @@ public partial class ConversationService
|
|||
// Function executed has exception
|
||||
if (fn.ExecutionResult == null)
|
||||
{
|
||||
await HandleAssistantMessage(agent, new RoleDialogModel(AgentRole.Assistant, fn.Content)
|
||||
await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, fn.Content)
|
||||
{
|
||||
CurrentAgentId = fn.CurrentAgentId
|
||||
}, onMessageReceived);
|
||||
|
|
@ -56,7 +56,7 @@ public partial class ConversationService
|
|||
}
|
||||
else if (fn.StopCompletion)
|
||||
{
|
||||
await HandleAssistantMessage(agent, new RoleDialogModel(AgentRole.Assistant, fn.Content)
|
||||
await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, fn.Content)
|
||||
{
|
||||
CurrentAgentId = fn.CurrentAgentId,
|
||||
ExecutionData = fn.ExecutionData,
|
||||
|
|
@ -95,7 +95,7 @@ public partial class ConversationService
|
|||
var response = await templateService.RenderFunctionResponse(agent.Id, fn);
|
||||
if (!string.IsNullOrEmpty(response))
|
||||
{
|
||||
await HandleAssistantMessage(agent, new RoleDialogModel(AgentRole.Assistant, response)
|
||||
await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, response)
|
||||
{
|
||||
CurrentAgentId = agent.Id
|
||||
}, onMessageReceived);
|
||||
|
|
@ -124,7 +124,7 @@ public partial class ConversationService
|
|||
return result;
|
||||
}
|
||||
|
||||
private async Task HandleAssistantMessage(Agent agent, RoleDialogModel message, Func<RoleDialogModel, Task> onMessageReceived)
|
||||
private async Task HandleAssistantMessage(RoleDialogModel message, Func<RoleDialogModel, Task> onMessageReceived)
|
||||
{
|
||||
var hooks = _services.GetServices<IConversationHook>().ToList();
|
||||
|
||||
|
|
@ -134,7 +134,9 @@ public partial class ConversationService
|
|||
await hook.AfterCompletion(message);
|
||||
}
|
||||
|
||||
_logger.LogInformation($"[{agent.Name}] {message.Role}: {message.Content}");
|
||||
var agent = await _services.GetRequiredService<IAgentService>().GetAgent(message.CurrentAgentId);
|
||||
|
||||
_logger.LogInformation($"[{agent?.Name ?? "Router"}] {message.Role}: {message.Content}");
|
||||
|
||||
await onMessageReceived(message);
|
||||
|
||||
|
|
|
|||
|
|
@ -57,40 +57,19 @@ public partial class ConversationService
|
|||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
var reasonedContext = await routing.Enter(agent, wholeDialogs);
|
||||
|
||||
if (reasonedContext.FunctionName == "interrupt_task_execution")
|
||||
if (reasonedContext.StopCompletion)
|
||||
{
|
||||
await HandleAssistantMessage(agent, new RoleDialogModel(AgentRole.Assistant, reasonedContext.Content)
|
||||
{
|
||||
CurrentAgentId = agent.Id
|
||||
}, onMessageReceived);
|
||||
|
||||
await HandleAssistantMessage(reasonedContext, onMessageReceived);
|
||||
return true;
|
||||
}
|
||||
else if (reasonedContext.FunctionName == "response_to_user")
|
||||
{
|
||||
await HandleAssistantMessage(agent, new RoleDialogModel(AgentRole.Assistant, reasonedContext.Content)
|
||||
{
|
||||
CurrentAgentId = agent.Id
|
||||
}, onMessageReceived);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (reasonedContext.FunctionName == "continue_execute_task")
|
||||
// Switch agent
|
||||
if (reasonedContext.CurrentAgentId != agent.Id)
|
||||
{
|
||||
if (reasonedContext.CurrentAgentId != agent.Id)
|
||||
{
|
||||
agent = await agentService.LoadAgent(reasonedContext.CurrentAgentId);
|
||||
}
|
||||
}
|
||||
else if (reasonedContext.FunctionName == "route_to_agent")
|
||||
{
|
||||
if (reasonedContext.CurrentAgentId != agent.Id)
|
||||
{
|
||||
agent = await agentService.LoadAgent(reasonedContext.CurrentAgentId);
|
||||
}
|
||||
agent = await agentService.LoadAgent(reasonedContext.CurrentAgentId);
|
||||
}
|
||||
|
||||
routing.Dialogs.ForEach(x =>
|
||||
routing.Dialogs.ForEach(x =>
|
||||
{
|
||||
wholeDialogs.Add(x);
|
||||
if (x.Content != null)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ public class ConversationStorage : IConversationStorage
|
|||
else
|
||||
{
|
||||
var routingSetting = _services.GetRequiredService<RoutingSettings>();
|
||||
var agentName = routingSetting.RouterId == agentId ? "Router" : db.Agents.First(x => x.Id == agentId).Name;
|
||||
var agentName = routingSetting.RouterId == agentId ? routingSetting.RouterName : db.Agents.First(x => x.Id == agentId).Name;
|
||||
|
||||
sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{agentId}|{agentName}|");
|
||||
var content = dialog.Content.Replace("\r", " ").Replace("\n", " ").Trim();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
{
|
||||
public string Name => "continue_execute_task";
|
||||
|
||||
public string Description => "Continue to execute user's request without further information retrival.";
|
||||
|
||||
public List<string> Parameters => new List<string>
|
||||
{
|
||||
"1. agent_name: the name of the agent",
|
||||
"2. args: required parameters extracted from question",
|
||||
"3. reason: why continue to execute current task"
|
||||
};
|
||||
|
||||
public bool IsReasoning => true;
|
||||
|
||||
public ContinueExecuteTaskRoutingHandler(IServiceProvider services, ILogger<ContinueExecuteTaskRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> Handle(FunctionCallFromLlm inst)
|
||||
{
|
||||
var routing = _services.GetRequiredService<IAgentRouting>();
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.Agents.First(x => x.Name.ToLower() == inst.Route.AgentName.ToLower());
|
||||
|
||||
var result = new RoleDialogModel(AgentRole.Function, inst.Question)
|
||||
{
|
||||
FunctionName = inst.Function,
|
||||
FunctionArgs = JsonSerializer.Serialize(inst.Arguments),
|
||||
CurrentAgentId = record.Id
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class ConversationEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
{
|
||||
public string Name => "conversation_end";
|
||||
|
||||
public string Description => "Call this function when user wants to end this conversation or all tasks have been completed.";
|
||||
|
||||
public List<string> Parameters => new List<string>
|
||||
{
|
||||
};
|
||||
|
||||
public bool IsReasoning => false;
|
||||
|
||||
public ConversationEndRoutingHandler(IServiceProvider services, ILogger<ConversationEndRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public Task<RoleDialogModel> Handle(FunctionCallFromLlm inst)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class GetNextInstructionRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
{
|
||||
public string Name => "get_next_instruction";
|
||||
|
||||
public string Description => "";
|
||||
|
||||
public List<string> Parameters => new List<string> { };
|
||||
|
||||
public bool IsReasoning => false;
|
||||
|
||||
public GetNextInstructionRoutingHandler(IServiceProvider services, ILogger<GetNextInstructionRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> Handle(FunctionCallFromLlm inst)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
{
|
||||
public string Name => "interrupt_task_execution";
|
||||
|
||||
public string Description => "Can't continue user's request becauase the requirements are not met.";
|
||||
|
||||
public List<string> Parameters => new List<string>
|
||||
{
|
||||
"1. reason: the reason why the request is interrupted",
|
||||
"2. answer: the content response to user"
|
||||
};
|
||||
|
||||
public bool IsReasoning => true;
|
||||
|
||||
public InterruptTaskExecutionRoutingHandler(IServiceProvider services, ILogger<InterruptTaskExecutionRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> Handle(FunctionCallFromLlm inst)
|
||||
{
|
||||
var result = new RoleDialogModel(AgentRole.User, inst.Route.Reason)
|
||||
{
|
||||
FunctionName = inst.Function,
|
||||
StopCompletion = true
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class ResponseToUserRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
{
|
||||
public string Name => "response_to_user";
|
||||
|
||||
public string Description => "You know how to response according to the context, don't need to ask specific agent.";
|
||||
|
||||
public List<string> Parameters => new List<string>
|
||||
{
|
||||
"1. answer: the content of response",
|
||||
"2. reason: why response to user"
|
||||
};
|
||||
|
||||
public bool IsReasoning => false;
|
||||
|
||||
public ResponseToUserRoutingHandler(IServiceProvider services, ILogger<ResponseToUserRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> Handle(FunctionCallFromLlm inst)
|
||||
{
|
||||
var result = new RoleDialogModel(AgentRole.User, inst.Answer)
|
||||
{
|
||||
FunctionName = inst.Function,
|
||||
StopCompletion = true
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
{
|
||||
public string Name => "retrieve_data_from_agent";
|
||||
|
||||
public string Description => "Retrieve data from appropriate agent.";
|
||||
|
||||
public List<string> Parameters => new List<string>
|
||||
{
|
||||
"1. agent_name: the name of the agent",
|
||||
"2. question: the question you will ask the agent to get the necessary data",
|
||||
"3. reason: why retrieve data",
|
||||
"4. args: required parameters extracted from question and hand over to the next agent. The args should be in JSON format"
|
||||
};
|
||||
|
||||
public bool IsReasoning => true;
|
||||
|
||||
public RetrieveDataFromAgentRoutingHandler(IServiceProvider services, ILogger<RetrieveDataFromAgentRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
// Retrieve information from specific agent
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.Agents.First(x => x.Name.ToLower() == inst.Route.AgentName.ToLower());
|
||||
var response = await InvokeAgent(record.Id, new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, inst.Question)
|
||||
});
|
||||
|
||||
inst.Answer = response.Content;
|
||||
|
||||
/*_dialogs.Add(new RoleDialogModel(AgentRole.Assistant, inst.Parameters.Question)
|
||||
{
|
||||
CurrentAgentId = record.Id
|
||||
});*/
|
||||
|
||||
_router.Instruction += $"\r\n{AgentRole.Assistant}: {inst.Question}";
|
||||
|
||||
/*_dialogs.Add(new RoleDialogModel(AgentRole.Function, inst.Parameters.Answer)
|
||||
{
|
||||
FunctionName = inst.Function,
|
||||
FunctionArgs = JsonSerializer.Serialize(inst.Parameters.Arguments),
|
||||
ExecutionResult = inst.Parameters.Answer,
|
||||
ExecutionData = response.ExecutionData,
|
||||
CurrentAgentId = record.Id
|
||||
});*/
|
||||
|
||||
_router.Instruction += $"\r\n{AgentRole.Function}: {response.Content}";
|
||||
|
||||
// Got the response from agent, then send to reasoner again to make the decision
|
||||
inst = await GetNextInstructionFromReasoner($"What's the next step based on user's original goal and function result?");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
{
|
||||
public string Name => "route_to_agent";
|
||||
|
||||
public string Description => "Route request to appropriate agent.";
|
||||
|
||||
public List<string> Parameters => new List<string>
|
||||
{
|
||||
"1. agent_name: the name of the agent",
|
||||
"2. reason: why route to this agent",
|
||||
"3. args: parameters extracted from context",
|
||||
"4. answer: if you know how to response without asking to other agent",
|
||||
"5. goal: user's original goal"
|
||||
};
|
||||
|
||||
public bool IsReasoning => false;
|
||||
|
||||
public RouteToAgentRoutingHandler(IServiceProvider services, ILogger<RouteToAgentRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
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
|
||||
}),
|
||||
};
|
||||
|
||||
var ret = await function.Execute(message);
|
||||
|
||||
var result = await InvokeAgent(message.CurrentAgentId, _dialogs);
|
||||
result.ExecutionData = result.ExecutionData ?? message.ExecutionData;
|
||||
|
||||
if (result.Role == AgentRole.Function && !result.StopCompletion)
|
||||
{
|
||||
_dialogs.Add(result);
|
||||
result = await InvokeAgent(message.CurrentAgentId, _dialogs);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public abstract class RoutingHandlerBase
|
||||
{
|
||||
protected Agent _router;
|
||||
protected readonly IServiceProvider _services;
|
||||
protected readonly ILogger _logger;
|
||||
protected RoutingSettings _settings;
|
||||
protected List<RoleDialogModel> _dialogs;
|
||||
public virtual bool RequireAgent => true;
|
||||
|
||||
public RoutingHandlerBase(IServiceProvider services,
|
||||
ILogger logger,
|
||||
RoutingSettings settings)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public void SetRouter(Agent router)
|
||||
{
|
||||
_router = router;
|
||||
}
|
||||
|
||||
public void SetDialogs(List<RoleDialogModel> dialogs)
|
||||
{
|
||||
_dialogs = dialogs;
|
||||
}
|
||||
|
||||
public async Task<FunctionCallFromLlm> GetNextInstructionFromReasoner(string prompt)
|
||||
{
|
||||
var responseFormat = JsonSerializer.Serialize(new FunctionCallFromLlm());
|
||||
var content = $"{prompt} Response must be in JSON format {responseFormat}.";
|
||||
|
||||
var chatCompletion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: _settings.Provider,
|
||||
model: _settings.Model);
|
||||
|
||||
RoleDialogModel response = null;
|
||||
await chatCompletion.GetChatCompletionsAsync(_router, new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, content)
|
||||
}, async msg
|
||||
=> response = msg, fn
|
||||
=> Task.CompletedTask);
|
||||
|
||||
FunctionCallFromLlm args = new FunctionCallFromLlm();
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(response.Content);
|
||||
args = JsonSerializer.Deserialize<FunctionCallFromLlm>(response.Content);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"{ex.Message}: {response.Content}");
|
||||
args.Function = "response_to_user";
|
||||
args.Answer = ex.Message;
|
||||
args.Route.AgentName = "";
|
||||
}
|
||||
|
||||
if (args.Arguments != null)
|
||||
{
|
||||
SaveStateByArgs(args.Arguments);
|
||||
}
|
||||
|
||||
args.Function = args.Function.Split('.').Last();
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine($"*** Next Instruction *** {args}", Color.Green);
|
||||
#else
|
||||
_logger.LogInformation($"*** Next Instruction *** {args}");
|
||||
#endif
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> GetResponseFromReasoner()
|
||||
{
|
||||
var wholeDialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, $"How to response to user?")
|
||||
};
|
||||
|
||||
var chatCompletion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: _settings.Provider,
|
||||
model: _settings.Model);
|
||||
|
||||
RoleDialogModel response = null;
|
||||
await chatCompletion.GetChatCompletionsAsync(_router, wholeDialogs, async msg
|
||||
=> response = msg, fn
|
||||
=> Task.CompletedTask);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
protected async Task<RoleDialogModel> InvokeAgent(string agentId, List<RoleDialogModel> wholeDialogs)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(agentId);
|
||||
|
||||
var chatCompletion = CompletionProvider.GetChatCompletion(_services);
|
||||
|
||||
RoleDialogModel response = null;
|
||||
await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs,
|
||||
async msg =>
|
||||
{
|
||||
response = msg;
|
||||
}, async fn =>
|
||||
{
|
||||
// execute function
|
||||
// Save states
|
||||
SaveStateByArgs(JsonSerializer.Deserialize<JsonDocument>(fn.FunctionArgs));
|
||||
|
||||
var conversationService = _services.GetRequiredService<IConversationService>();
|
||||
// Call functions
|
||||
await conversationService.CallFunctions(fn);
|
||||
|
||||
response = fn;
|
||||
|
||||
if (string.IsNullOrEmpty(response.Content))
|
||||
{
|
||||
response.Content = fn.ExecutionResult;
|
||||
}
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
protected void SaveStateByArgs(JsonDocument args)
|
||||
{
|
||||
if (args == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var stateService = _services.GetRequiredService<IConversationStateService>();
|
||||
if (args.RootElement is JsonElement root)
|
||||
{
|
||||
foreach (JsonProperty property in root.EnumerateObject())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(property.Value.ToString()))
|
||||
{
|
||||
stateService.SetState(property.Name, property.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
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<string> Parameters => new List<string>
|
||||
{
|
||||
"1. abandoned_arguments: the arguments next task can't reuse"
|
||||
};
|
||||
|
||||
public bool IsReasoning => true;
|
||||
|
||||
public TaskEndRoutingHandler(IServiceProvider services, ILogger<TaskEndRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public Task<RoleDialogModel> Handle(FunctionCallFromLlm inst)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class TransferToCsrRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
{
|
||||
public string Name => "transfer_to_csr";
|
||||
|
||||
public string Description => "Reach out to a real customer representative to help.";
|
||||
|
||||
public List<string> Parameters => new List<string>
|
||||
{
|
||||
};
|
||||
|
||||
public bool IsReasoning => false;
|
||||
|
||||
public TransferToCsrRoutingHandler(IServiceProvider services, ILogger<TransferToCsrRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> Handle(FunctionCallFromLlm inst)
|
||||
{
|
||||
var result = new RoleDialogModel(AgentRole.User, "I'm transferring to a customer representative, waiting a moment please.")
|
||||
{
|
||||
CurrentAgentId = _settings.RouterId,
|
||||
FunctionName = inst.Function,
|
||||
StopCompletion = true
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,70 +3,31 @@ namespace BotSharp.Core.Routing;
|
|||
public class PromptConst
|
||||
{
|
||||
public const string ROUTER_PROMPT = @"
|
||||
You're a Router with reasoning, you can dispatch request to different agent to complete the task.
|
||||
You're a Router with reasoning, you can dispatch request to different agent to achieve user's goal.
|
||||
|
||||
### Agents:
|
||||
###
|
||||
Router can decide which of below agents can handle user's request:
|
||||
{% for agent in routing_records %}
|
||||
* {{ agent.name }}
|
||||
{{ agent.description }}
|
||||
{% if agent.required_fields != empty -%}Required information: {{ agent.required_fields }}.{%- endif %}
|
||||
{% if agent.required_fields != empty -%}
|
||||
Required: {% for field in agent.required_fields %}{{ field }},{% endfor %}
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
|
||||
### Functions
|
||||
{% if enable_reasoning == false -%}
|
||||
* route_to_agent
|
||||
Route request to appropriate agent.
|
||||
###
|
||||
Agent can utilize below functions:
|
||||
{% for fn in routing_handlers %}
|
||||
* {{ fn.name }}
|
||||
{{ fn.description }}
|
||||
{% if fn.parameters != empty %}
|
||||
Parameters:
|
||||
1. agent_name: the name of the agent;
|
||||
2. reason: why route to this agent;
|
||||
3. args: parameters extracted from context;
|
||||
{%- endif %}
|
||||
|
||||
* task_end
|
||||
Call this function when current task is completed.
|
||||
Parameters:
|
||||
1. abandoned_arguments: the arguments next task can't reuse;
|
||||
|
||||
* conversation_end
|
||||
Call this function when user wants to end this conversation or all tasks have been completed.
|
||||
|
||||
* transfer_to_csr
|
||||
Reach out to a real customer representative to help.
|
||||
|
||||
{{ reasoning_functions }}
|
||||
|
||||
### Your response must meet below requirements strictly
|
||||
{% if enable_reasoning == false %}
|
||||
* If you can find an appropriate Agent, you must call function route_to_agent with required arguments.
|
||||
{% for arg in fn.parameters -%}
|
||||
{{ arg }};
|
||||
{%- endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
### Conversation context:";
|
||||
|
||||
public const string REASONING_FUNCTIONS = @"
|
||||
* retrieve_data_from_agent
|
||||
Retrieve data from appropriate agent.
|
||||
Parameters:
|
||||
1. agent_name: the name of the agent;
|
||||
2. question: the question you will ask the agent to get the necessary data;
|
||||
3. reason: why retrieve data;
|
||||
4. args: required parameters extracted from question and hand over to the next agent. The args should be in JSON format;
|
||||
|
||||
* continue_execute_task
|
||||
Continue to execute user's request without further information retrival.
|
||||
Parameters:
|
||||
1. agent_name: the name of the agent;
|
||||
2. args: required parameters extracted from question;
|
||||
3. reason: why continue to execute current task;
|
||||
|
||||
* interrupt_task_execution
|
||||
Can't continue user's request becauase the requirements are not met.
|
||||
Parameters:
|
||||
1. reason: the reason why the request is interrupted;
|
||||
2. answer: the content response to user;
|
||||
|
||||
* response_to_user
|
||||
You have already known the answer according the dialogs.
|
||||
Parameters:
|
||||
1. answer: the response of user's request;
|
||||
2. reason: why response to user;";
|
||||
###
|
||||
Conversation context:";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
||||
|
|
@ -115,6 +116,12 @@ public class RouteToAgentFn : IFunctionCallback
|
|||
// Add redirected agent
|
||||
message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "redirect_to", record.Name);
|
||||
agentId = routingRule.RedirectTo;
|
||||
var logger = _services.GetRequiredService<ILogger<RouteToAgentFn>>();
|
||||
#if DEBUG
|
||||
Console.WriteLine($"*** Routing redirect to {record.Name.ToUpper()} ***", Color.Yellow);
|
||||
#else
|
||||
logger.LogInformation($"*** Routing redirect to {record.Name.ToUpper()} ***");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
||||
|
|
@ -27,196 +25,56 @@ public class RoutingService : IRoutingService
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> Enter(Agent router, List<RoleDialogModel> whileDialogs)
|
||||
public async Task<RoleDialogModel> Enter(Agent router, List<RoleDialogModel> wholeDialogs)
|
||||
{
|
||||
_dialogs = new List<RoleDialogModel>();
|
||||
RoleDialogModel result = new RoleDialogModel(AgentRole.Assistant, "not handled");
|
||||
var result = new RoleDialogModel(AgentRole.Assistant, "Can you repeat your request again?")
|
||||
{
|
||||
CurrentAgentId = router.Id
|
||||
};
|
||||
|
||||
foreach (var dialog in whileDialogs.TakeLast(20))
|
||||
var message = wholeDialogs.Last().Content;
|
||||
foreach (var dialog in wholeDialogs.TakeLast(20))
|
||||
{
|
||||
router.Instruction += $"\r\n{dialog.Role}: {dialog.Content}";
|
||||
}
|
||||
|
||||
var inst = await GetNextInstructionFromReasoner($"What's the next step to make user's original goal?", router);
|
||||
var handlers = _services.GetServices<IRoutingHandler>();
|
||||
|
||||
var handler = handlers.FirstOrDefault(x => x.Name == "get_next_instruction");
|
||||
handler.SetRouter(router);
|
||||
handler.SetDialogs(wholeDialogs);
|
||||
|
||||
int loopCount = 0;
|
||||
while (loopCount < 3)
|
||||
while (!result.StopCompletion && loopCount < 5)
|
||||
{
|
||||
loopCount++;
|
||||
if (inst.Function == "continue_execute_task")
|
||||
|
||||
var inst = await handler.GetNextInstructionFromReasoner($"What's the next step to achieve user's goal?");
|
||||
inst.Question = inst.Question ?? message;
|
||||
|
||||
handler = handlers.FirstOrDefault(x => x.Name == inst.Function);
|
||||
if (handler == null)
|
||||
{
|
||||
var routing = _services.GetRequiredService<IAgentRouting>();
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.Agents.First(x => x.Name.ToLower() == inst.Parameters.AgentName.ToLower());
|
||||
|
||||
result = new RoleDialogModel(AgentRole.Function, inst.Parameters.Question)
|
||||
{
|
||||
FunctionName = inst.Function,
|
||||
FunctionArgs = JsonSerializer.Serialize(inst.Parameters.Arguments),
|
||||
CurrentAgentId = record.Id,
|
||||
};
|
||||
break;
|
||||
handler = handlers.FirstOrDefault(x => x.Name == "get_next_instruction");
|
||||
router.Instruction += $"\r\n{AgentRole.System}: the function must be one of [{string.Join(",", GetHandlers().Select(x => x.Name))}].";
|
||||
continue;
|
||||
}
|
||||
// Compatible with previous Router, can be removed in the future.
|
||||
else if (inst.Function == "route_to_agent")
|
||||
{
|
||||
// If the agent name is empty, fallback to router
|
||||
if (string.IsNullOrEmpty(inst.Parameters.AgentName))
|
||||
{
|
||||
result = new RoleDialogModel(AgentRole.Function, inst.Reason)
|
||||
{
|
||||
FunctionName = inst.Function,
|
||||
FunctionArgs = JsonSerializer.Serialize(new RoutingArgs
|
||||
{
|
||||
AgentName = inst.Parameters.AgentName
|
||||
}),
|
||||
CurrentAgentId = router.Id
|
||||
};
|
||||
break;
|
||||
}
|
||||
handler.SetRouter(router);
|
||||
handler.SetDialogs(wholeDialogs);
|
||||
|
||||
var function = _services.GetServices<IFunctionCallback>().FirstOrDefault(x => x.Name == inst.Function);
|
||||
result = new RoleDialogModel(AgentRole.Function, inst.Reason)
|
||||
{
|
||||
FunctionName = inst.Function,
|
||||
FunctionArgs = JsonSerializer.Serialize(new RoutingArgs
|
||||
{
|
||||
AgentName = inst.Parameters.AgentName
|
||||
}),
|
||||
};
|
||||
var ret = await function.Execute(result);
|
||||
break;
|
||||
}
|
||||
else if (inst.Function == "interrupt_task_execution")
|
||||
{
|
||||
result = new RoleDialogModel(AgentRole.User, inst.Reason)
|
||||
{
|
||||
FunctionName = inst.Function
|
||||
};
|
||||
break;
|
||||
}
|
||||
else if (inst.Function == "response_to_user")
|
||||
{
|
||||
result = new RoleDialogModel(AgentRole.User, inst.Parameters.Answer)
|
||||
{
|
||||
FunctionName = inst.Function
|
||||
};
|
||||
break;
|
||||
}
|
||||
else if (inst.Function == "retrieve_data_from_agent")
|
||||
{
|
||||
// Retrieve information from specific agent
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.Agents.First(x => x.Name.ToLower() == inst.Parameters.AgentName.ToLower());
|
||||
var response = await RetrieveDataFromAgent(record.Id, new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, inst.Parameters.Question)
|
||||
});
|
||||
result = await handler.Handle(inst);
|
||||
|
||||
inst.Parameters.Answer = response.Content;
|
||||
message = result.Content.Replace("\r\n", " ");
|
||||
router.Instruction += $"\r\n{result.Role}: {message}";
|
||||
|
||||
_dialogs.Add(new RoleDialogModel(AgentRole.Assistant, inst.Parameters.Question)
|
||||
{
|
||||
CurrentAgentId = record.Id
|
||||
});
|
||||
|
||||
router.Instruction += $"\r\n{AgentRole.Assistant}: {inst.Parameters.Question}";
|
||||
|
||||
_dialogs.Add(new RoleDialogModel(AgentRole.Function, inst.Parameters.Answer)
|
||||
{
|
||||
FunctionName = inst.Function,
|
||||
FunctionArgs = JsonSerializer.Serialize(inst.Parameters.Arguments),
|
||||
ExecutionResult = inst.Parameters.Answer,
|
||||
ExecutionData = response.ExecutionData,
|
||||
CurrentAgentId = record.Id
|
||||
});
|
||||
|
||||
router.Instruction += $"\r\n{AgentRole.Function}: {response.Content}";
|
||||
|
||||
// Got the response from agent, then send to reasoner again to make the decision
|
||||
inst = await GetNextInstructionFromReasoner($"What's the next step based on user's original goal and function result?", router);
|
||||
}
|
||||
result.StopCompletion = !_settings.EnableReasoning;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<FunctionCallFromLlm> GetNextInstructionFromReasoner(string prompt, Agent reasoner)
|
||||
{
|
||||
var responseFormat = JsonSerializer.Serialize(new FunctionCallFromLlm());
|
||||
var wholeDialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, $"{prompt} Response in JSON format {responseFormat}")
|
||||
};
|
||||
|
||||
var chatCompletion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: _settings.Provider,
|
||||
model: _settings.Model);
|
||||
|
||||
RoleDialogModel response = null;
|
||||
await chatCompletion.GetChatCompletionsAsync(reasoner, wholeDialogs, async msg
|
||||
=> response = msg, fn
|
||||
=> Task.CompletedTask);
|
||||
|
||||
var args = JsonSerializer.Deserialize<FunctionCallFromLlm>(response.Content);
|
||||
|
||||
if (args.Parameters.Arguments != null)
|
||||
{
|
||||
SaveStateByArgs(args.Parameters.Arguments);
|
||||
}
|
||||
|
||||
args.Function = args.Function.Split('.').Last();
|
||||
args.Parameters.AgentName = args.Parameters.AgentName.Split(':').Last().Trim();
|
||||
|
||||
_logger.LogInformation($"*** Next Instruction *** {args}");
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
private async Task<RoleDialogModel> RetrieveDataFromAgent(string agentId, List<RoleDialogModel> wholeDialogs)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(agentId);
|
||||
|
||||
var chatCompletion = CompletionProvider.GetChatCompletion(_services);
|
||||
|
||||
RoleDialogModel response = null;
|
||||
await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg
|
||||
=> response = msg, async fn
|
||||
=>
|
||||
{
|
||||
// execute function
|
||||
// Save states
|
||||
SaveStateByArgs(JsonSerializer.Deserialize<JsonDocument>(fn.FunctionArgs));
|
||||
|
||||
var conversationService = _services.GetRequiredService<IConversationService>();
|
||||
// Call functions
|
||||
await conversationService.CallFunctions(fn);
|
||||
|
||||
response = fn;
|
||||
response.Content = fn.ExecutionResult;
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
private void SaveStateByArgs(JsonDocument args)
|
||||
{
|
||||
if (args == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var stateService = _services.GetRequiredService<IConversationStateService>();
|
||||
if (args.RootElement is JsonElement root)
|
||||
{
|
||||
foreach (JsonProperty property in root.EnumerateObject())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(property.Value.ToString()))
|
||||
{
|
||||
stateService.SetState(property.Name, property.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Agent LoadRouter()
|
||||
{
|
||||
|
|
@ -241,15 +99,24 @@ public class RoutingService : IRoutingService
|
|||
.ToArray()
|
||||
}).ToArray();
|
||||
|
||||
dict["enable_reasoning"] = _settings.EnableReasoning;
|
||||
if (_settings.EnableReasoning)
|
||||
{
|
||||
dict["reasoning_functions"] = PromptConst.REASONING_FUNCTIONS;
|
||||
}
|
||||
dict["routing_handlers"] = GetHandlers();
|
||||
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
router.Instruction = render.Render(PromptConst.ROUTER_PROMPT, dict);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
private List<RoutingHandlerDef> GetHandlers()
|
||||
{
|
||||
return _services.GetServices<IRoutingHandler>()
|
||||
.Where(x => x.IsReasoning == _settings.EnableReasoning)
|
||||
.Where(x => !string.IsNullOrEmpty(x.Description))
|
||||
.Select(x => new RoutingHandlerDef
|
||||
{
|
||||
Name = x.Name,
|
||||
Description = x.Description,
|
||||
Parameters = x.Parameters
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public class TemplateRender : ITemplateRender
|
|||
_options = new TemplateOptions();
|
||||
_options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.SnakeCase;
|
||||
_options.MemberAccessStrategy.Register<RoutingItem>();
|
||||
_options.MemberAccessStrategy.Register<RoutingHandlerDef>();
|
||||
}
|
||||
|
||||
public string Render(string template, Dictionary<string, object> dict)
|
||||
|
|
|
|||
|
|
@ -22,4 +22,5 @@ global using BotSharp.Core.Agents.Services;
|
|||
global using BotSharp.Core.Conversations.Services;
|
||||
global using BotSharp.Core.Infrastructures;
|
||||
global using BotSharp.Core.Users.Services;
|
||||
global using Aspects.Cache;
|
||||
global using Aspects.Cache;
|
||||
global using Console = Colorful.Console;
|
||||
|
|
@ -70,6 +70,8 @@ public class ConversationController : ControllerBase, IApiAdapter
|
|||
});
|
||||
|
||||
response.Text = string.Join("\r\n", stackMsg.Select(x => x.Content));
|
||||
response.Data = response.Data ?? stackMsg.Last().ExecutionData;
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue