NaivePlanner

This commit is contained in:
Haiping Chen 2023-10-28 15:59:26 -05:00
parent 58051e2db0
commit 513ad5814e
42 changed files with 512 additions and 320 deletions

View file

@ -16,6 +16,10 @@ public interface IAgentService
/// <returns></returns>
Task<Agent> LoadAgent(string id);
string RenderedInstruction(Agent agent);
string RenderedTemplate(Agent agent, string templateName);
/// <summary>
/// Get agent detail without trigger any hook.
/// </summary>

View file

@ -65,6 +65,12 @@ public class Agent
public List<RoutingRule> RoutingRules { get; set; }
= new List<RoutingRule>();
/// <summary>
/// For rendering deferral
/// </summary>
[JsonIgnore]
public Dictionary<string, object> TemplateDict { get; set; }
public override string ToString()
=> $"{Name} {Id}";

View file

@ -1,5 +1,3 @@
using BotSharp.Abstraction.Models;
namespace BotSharp.Abstraction.Functions.Models;
public class ParameterPropertyDef : NameDesc
@ -10,6 +8,9 @@ public class ParameterPropertyDef : NameDesc
Type = type;
}
[JsonPropertyName("required")]
public bool Required { get; set; }
/// <summary>
/// string, number, object
/// </summary>

View file

@ -4,5 +4,5 @@ namespace BotSharp.Abstraction.Instructs;
public interface IInstructService
{
Task<InstructResult> Execute(Agent agent, RoleDialogModel message);
Task<InstructResult> Execute(string agentId, RoleDialogModel message, string? templateName = null);
}

View file

@ -0,0 +1,13 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Routing;
namespace BotSharp.Abstraction.Planning;
public interface IExecutor
{
Task<bool> Execute(IRoutingService routing,
Agent router,
FunctionCallFromLlm inst,
List<RoleDialogModel> dialogs,
RoleDialogModel message);
}

View file

@ -0,0 +1,12 @@
using BotSharp.Abstraction.Functions.Models;
namespace BotSharp.Abstraction.Planning;
/// <summary>
/// Task breakdown and execution plan
/// </summary>
public interface IPlaner
{
Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string conversation);
Task<bool> AgentExecuted(FunctionCallFromLlm inst, RoleDialogModel message);
}

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
namespace BotSharp.Abstraction.Routing;
@ -10,9 +11,9 @@ public interface IRoutingHandler
{
string Name { get; }
string Description { get; }
bool IsReasoning => false;
List<string> Planers => null;
bool Enabled => true;
List<NameDesc> Parameters => new List<NameDesc>();
List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>();
void SetRouter(Agent router) { }

View file

@ -1,5 +1,3 @@
using BotSharp.Abstraction.Functions.Models;
namespace BotSharp.Abstraction.Routing;
public interface IRoutingService
@ -7,7 +5,6 @@ public interface IRoutingService
List<RoleDialogModel> Dialogs { get; }
void ResetRecursiveCounter();
void RefreshDialogs();
Task<FunctionCallFromLlm> GetNextInstruction();
Task<bool> InvokeAgent(string agentId, RoleDialogModel message);
Task<bool> InstructLoop(RoleDialogModel message);
Task<bool> ExecuteOnce(Agent agent, RoleDialogModel message);

View file

@ -1,4 +1,4 @@
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Functions.Models;
namespace BotSharp.Abstraction.Routing.Models;
@ -6,7 +6,7 @@ public class RoutingHandlerDef
{
public string Name { get; set; }
public string Description { get; set; }
public List<NameDesc> Parameters { get; set; }
public List<ParameterPropertyDef> Parameters { get; set; }
public override string ToString()
=> $"{Name}: {Description} ({Parameters.Count} Parameters)";

View file

@ -13,6 +13,6 @@ public class RoutingItem
[JsonPropertyName("description")]
public string Description { get; set; } = string.Empty;
[JsonPropertyName("required_fields")]
public List<ParameterPropertyDef> RequiredFields { get; set; } = new List<ParameterPropertyDef>();
[JsonPropertyName("fields")]
public List<ParameterPropertyDef> Fields { get; set; } = new List<ParameterPropertyDef>();
}

View file

@ -7,9 +7,7 @@ public class RoutingSettings
/// </summary>
public string RouterId { get; set; } = string.Empty;
public bool EnableReasoning { get; set; } = false;
public bool UseTextCompletion { get; set; } = false;
public string Planner { get; set; } = string.Empty;
public string Provider { get; set; } = string.Empty;
public string Model { get; set; } = string.Empty;

View file

@ -4,14 +4,16 @@ namespace BotSharp.Core.Agents.Services;
public partial class AgentService
{
[MemoryCache(10 * 60, PerInstanceCache = true)]
[MemoryCache(10 * 60)]
public async Task<List<Agent>> GetAgents(bool? allowRouting = null)
{
var agents = _db.GetAgents(allowRouting: allowRouting);
return await Task.FromResult(agents);
}
[MemoryCache(10 * 60, PerInstanceCache = true)]
#if !DEBUG
[MemoryCache(10 * 60)]
#endif
public async Task<Agent> GetAgent(string id)
{
var profile = _db.GetAgent(id);

View file

@ -27,8 +27,10 @@ public partial class AgentService
throw new Exception($"Can't load agent by id: {id}");
}
var templateDict = new Dictionary<string, object>();
PopulateState(templateDict);
agent.TemplateDict = new Dictionary<string, object>();
// Populate state into dictionary
PopulateState(agent.TemplateDict);
// After agent is loaded
foreach (var hook in hooks)
@ -42,7 +44,7 @@ public partial class AgentService
if (!string.IsNullOrEmpty(agent.Instruction))
{
hook.OnInstructionLoaded(agent.Instruction, templateDict);
hook.OnInstructionLoaded(agent.Instruction, agent.TemplateDict);
}
if (agent.Functions != null)
@ -58,15 +60,25 @@ public partial class AgentService
hook.OnAgentLoaded(agent);
}
// render liquid template
var render = _services.GetRequiredService<ITemplateRender>();
agent.Instruction = render.Render(agent.Instruction, templateDict);
_logger.LogInformation($"Loaded agent {agent}.");
return agent;
}
public string RenderedTemplate(Agent agent, string templateName)
{
// render liquid template
var render = _services.GetRequiredService<ITemplateRender>();
var template = agent.Templates.First(x => x.Name == templateName).Content;
return render.Render(template, agent.TemplateDict);
}
public string RenderedInstruction(Agent agent)
{
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(agent.Instruction, agent.TemplateDict);
}
private void PopulateState(Dictionary<string, object> dict)
{
var conv = _services.GetRequiredService<IConversationService>();

View file

@ -17,6 +17,8 @@ using BotSharp.Abstraction.Evaluations;
using BotSharp.Core.Evaluatings;
using BotSharp.Core.Evaluations;
using BotSharp.Abstraction.MLTasks.Settings;
using BotSharp.Abstraction.Planning;
using BotSharp.Core.Planning;
namespace BotSharp.Core;
@ -68,6 +70,18 @@ public static class BotSharpServiceCollectionExtensions
config.Bind("Router", routingSettings);
services.AddSingleton((IServiceProvider x) => routingSettings);
services.AddScoped<NaivePlanner>();
services.AddScoped<FeedbackReasoningPlanner>();
services.AddScoped<IPlaner>(provider =>
{
if (routingSettings.Planner == "NaivePlanner")
return provider.GetRequiredService<NaivePlanner>();
else if (routingSettings.Planner == "FeedbackReasoningPlanner")
return provider.GetRequiredService<FeedbackReasoningPlanner>();
throw new NotImplementedException();
});
services.AddScoped<IExecutor, InstructExecutor>();
services.AddScoped<IRouterInstance, RouterInstance>();
services.AddScoped<IRoutingService, RoutingService>();

View file

@ -20,7 +20,7 @@ public partial class ConversationService
var content = $"Received [{agent.Name}] {message.Role}: {message.Content}";
#if DEBUG
Console.WriteLine(content, Color.OrangeRed);
Console.WriteLine(content, Color.GreenYellow);
#else
_logger.LogInformation(content);
#endif

View file

@ -1,7 +1,6 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Instructs;
using BotSharp.Abstraction.Instructs.Models;
namespace BotSharp.Core.Instructs;
public partial class InstructService : IInstructService
@ -15,13 +14,13 @@ public partial class InstructService : IInstructService
_logger = logger;
}
public async Task<InstructResult> Execute(Agent agent, RoleDialogModel message)
public async Task<InstructResult> Execute(string agentId, RoleDialogModel message, string? templateName = null)
{
// Trigger before completion hooks
var hooks = _services.GetServices<IInstructHook>();
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agent.Id)
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
@ -39,8 +38,15 @@ public partial class InstructService : IInstructService
}
}
// Render prompt
var agentService = _services.GetRequiredService<IAgentService>();
Agent agent = await agentService.LoadAgent(agentId);
var prompt = string.IsNullOrEmpty(templateName) ?
agentService.RenderedInstruction(agent) :
agentService.RenderedTemplate(agent, templateName);
var completer = CompletionProvider.GetTextCompletion(_services);
var result = await completer.GetCompletion(agent.Instruction);
var result = await completer.GetCompletion(prompt);
var response = new InstructResult
{
MessageId = message.MessageId,
@ -49,7 +55,7 @@ public partial class InstructService : IInstructService
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agent.Id)
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}

View file

@ -0,0 +1,75 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Templating;
namespace BotSharp.Core.Planning;
public class FeedbackReasoningPlanner : IPlaner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
public FeedbackReasoningPlanner(IServiceProvider services, ILogger<FeedbackReasoningPlanner> logger)
{
_services = services;
_logger = logger;
}
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string conversation)
{
var next = GetNextStepPrompt(router);
RoleDialogModel response = default;
var inst = new FunctionCallFromLlm();
var content = $"{conversation}\r\n###\r\n{next}";
var completion = CompletionProvider.GetChatCompletion(_services,
model: "llm-gpt4");
int retryCount = 0;
while (retryCount < 3)
{
try
{
response = completion.GetChatCompletions(router, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, content)
});
inst = response.Content.JsonContent<FunctionCallFromLlm>();
break;
}
catch (Exception ex)
{
_logger.LogError($"{ex.Message}: {response.Content}");
inst.Function = "response_to_user";
inst.Response = ex.Message;
inst.AgentName = "Router";
}
finally
{
retryCount++;
}
}
return inst;
}
public async Task<bool> AgentExecuted(FunctionCallFromLlm inst, RoleDialogModel message)
{
inst.AgentName = null;
return true;
}
private string GetNextStepPrompt(Agent router)
{
var template = router.Templates.First(x => x.Name == "next_step_prompt").Content;
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(template, new Dictionary<string, object>
{
});
}
}

View file

@ -0,0 +1,45 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Routing;
namespace BotSharp.Core.Planning;
public class InstructExecutor : IExecutor
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
public InstructExecutor(IServiceProvider services, ILogger<InstructExecutor> logger)
{
_services = services;
_logger = logger;
}
public async Task<bool> Execute(IRoutingService routing,
Agent router,
FunctionCallFromLlm inst,
List<RoleDialogModel> dialogs,
RoleDialogModel message)
{
// Set user content as Planner's question
inst.Question = message.Content;
message.Instruction = inst;
var handlers = _services.GetServices<IRoutingHandler>();
var handler = handlers.FirstOrDefault(x => x.Name == inst.Function);
handler.SetRouter(router);
handler.SetDialogs(dialogs);
message.FunctionName = inst.Function;
message.Role = AgentRole.Function;
message.FunctionArgs = inst.Arguments == null ? "{}" : JsonSerializer.Serialize(inst.Arguments);
var handled = await handler.Handle(routing, inst, message);
inst.Response = message.Content;
return handled;
}
}

View file

@ -0,0 +1,76 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Templating;
namespace BotSharp.Core.Planning;
public class NaivePlanner : IPlaner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
public NaivePlanner(IServiceProvider services, ILogger<NaivePlanner> logger)
{
_services = services;
_logger = logger;
}
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string conversation)
{
var next = GetNextStepPrompt(router);
RoleDialogModel response = default;
var inst = new FunctionCallFromLlm();
var agentService = _services.GetRequiredService<IAgentService>();
var instruction = agentService.RenderedInstruction(router);
var content = $"{instruction}\r\n{conversation}\r\n###\r\n{next}";
// text completion
content = content + "\r\nResponse: ";
var completion = CompletionProvider.GetTextCompletion(_services);
int retryCount = 0;
while (retryCount < 3)
{
try
{
var text = await completion.GetCompletion(content);
response = new RoleDialogModel(AgentRole.Assistant, text);
inst = response.Content.JsonContent<FunctionCallFromLlm>();
break;
}
catch (Exception ex)
{
_logger.LogError($"{ex.Message}: {response.Content}");
inst.Function = "response_to_user";
inst.Response = ex.Message;
inst.AgentName = "Router";
}
finally
{
retryCount++;
}
}
return inst;
}
public async Task<bool> AgentExecuted(FunctionCallFromLlm inst, RoleDialogModel message)
{
inst.AgentName = null;
return true;
}
private string GetNextStepPrompt(Agent router)
{
var template = router.Templates.First(x => x.Name == "next_step_prompt").Content;
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(template, new Dictionary<string, object>
{
});
}
}

View file

@ -12,14 +12,20 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHan
public string Description => "Continue to execute user's request without further information retrival.";
public List<NameDesc> Parameters => new List<NameDesc>
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
{
new NameDesc("agent", "the name of the agent"),
new NameDesc("args", "required parameters extracted from question"),
new NameDesc("reason", "why continue to execute current task")
new ParameterPropertyDef("agent", "the name of the agent"),
new ParameterPropertyDef("reason", "why continue to execute current task"),
new ParameterPropertyDef("args", "required parameters extracted from question")
{
Type = "object"
}
};
public bool IsReasoning => true;
public List<string> Planers => new List<string>
{
"FeedbackReasoningPlanner"
};
public ContinueExecuteTaskRoutingHandler(IServiceProvider services, ILogger<ContinueExecuteTaskRoutingHandler> logger, RoutingSettings settings)
: base(services, logger, settings)

View file

@ -11,12 +11,10 @@ public class ConversationEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
public string Description => "User completed his task and wants to end the conversation.";
public bool IsReasoning => false;
public List<NameDesc> Parameters => new List<NameDesc>
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
{
new NameDesc("reason", "why end conversation"),
new NameDesc("response", "response content to user")
new ParameterPropertyDef("reason", "why end conversation"),
new ParameterPropertyDef("response", "response content to user")
};
public ConversationEndRoutingHandler(IServiceProvider services, ILogger<ConversationEndRoutingHandler> logger, RoutingSettings settings)

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
@ -11,10 +10,10 @@ public class HumanInterventionNeededHandler : RoutingHandlerBase, IRoutingHandle
public string Description => "Reach out to human being, customer service or customer representative.";
public List<NameDesc> Parameters => new List<NameDesc>
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
{
new NameDesc("reason", "why need customer service"),
new NameDesc("response", "response content to user")
new ParameterPropertyDef("reason", "why need customer service"),
new ParameterPropertyDef("response", "response content to user")
};
public HumanInterventionNeededHandler(IServiceProvider services, ILogger<HumanInterventionNeededHandler> logger, RoutingSettings settings)

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
@ -11,13 +10,16 @@ public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase, IRouting
public string Description => "Can't continue user's request becauase the requirements are not met.";
public List<NameDesc> Parameters => new List<NameDesc>
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
{
new NameDesc("reason", "the reason why the request is interrupted"),
new NameDesc("answer", "the content response to user")
new ParameterPropertyDef("reason", "the reason why the request is interrupted"),
new ParameterPropertyDef("answer", "the content response to user")
};
public bool IsReasoning => true;
public List<string> Planers => new List<string>
{
"FeedbackReasoningPlanner"
};
public InterruptTaskExecutionRoutingHandler(IServiceProvider services, ILogger<InterruptTaskExecutionRoutingHandler> logger, RoutingSettings settings)
: base(services, logger, settings)

View file

@ -11,12 +11,10 @@ public class ResponseToUserRoutingHandler : RoutingHandlerBase, IRoutingHandler
public string Description => "Response according to the context without asking specific agent.";
public bool IsReasoning => false;
public List<NameDesc> Parameters => new List<NameDesc>
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
{
new NameDesc("reason", "why response to user"),
new NameDesc("response", "response content")
new ParameterPropertyDef("reason", "why response to user"),
new ParameterPropertyDef("response", "response content")
};
public ResponseToUserRoutingHandler(IServiceProvider services, ILogger<ResponseToUserRoutingHandler> logger, RoutingSettings settings)

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
@ -12,15 +11,21 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH
public string Description => "Retrieve data from appropriate agent.";
public List<NameDesc> Parameters => new List<NameDesc>
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
{
new NameDesc("agent", "the name of the agent"),
new NameDesc("question", "the question you will ask the agent to get the necessary data"),
new NameDesc("reason", "why retrieve data"),
new NameDesc("args", "required parameters extracted from question and hand over to the next agent")
new ParameterPropertyDef("agent", "the name of the agent"),
new ParameterPropertyDef("question", "the question you will ask the agent to get the necessary data"),
new ParameterPropertyDef("reason", "why retrieve data"),
new ParameterPropertyDef("args", "required parameters extracted from question and hand over to the next agent")
{
Type = "object"
}
};
public bool IsReasoning => true;
public List<string> Planers => new List<string>
{
"FeedbackReasoningPlanner"
};
public RetrieveDataFromAgentRoutingHandler(IServiceProvider services, ILogger<RetrieveDataFromAgentRoutingHandler> logger, RoutingSettings settings)
: base(services, logger, settings)

View file

@ -1,6 +1,5 @@
using BotSharp.Abstraction.Functions;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Settings;
@ -13,16 +12,17 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
public string Description => "Route request to appropriate agent.";
public List<NameDesc> Parameters => new List<NameDesc>
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
{
new NameDesc("reason", "why route to agent"),
new NameDesc("next_action_agent", "agent for next action based on user latest response"),
new NameDesc("user_goal_agent", "agent who can achieve user original goal"),
new NameDesc("args", "useful parameters of next action agent")
new ParameterPropertyDef("reason", "why route to agent"),
new ParameterPropertyDef("next_action_agent", "agent for next action based on user latest response"),
new ParameterPropertyDef("user_goal_agent", "agent who can achieve user original goal"),
new ParameterPropertyDef("args", "useful parameters of next action agent, format: { }")
{
Type = "object"
}
};
public bool IsReasoning => false;
public RouteToAgentRoutingHandler(IServiceProvider services, ILogger<RouteToAgentRoutingHandler> logger, RoutingSettings settings)
: base(services, logger, settings)
{

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
@ -11,12 +10,15 @@ public class TaskEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
public string Description => "Call this function when current task is completed.";
public List<NameDesc> Parameters => new List<NameDesc>
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
{
new NameDesc("abandoned_arguments", "the arguments next task can't reuse")
new ParameterPropertyDef("abandoned_arguments", "the arguments next task can't reuse")
};
public bool IsReasoning => true;
public List<string> Planers => new List<string>
{
"FeedbackReasoningPlanner"
};
public TaskEndRoutingHandler(IServiceProvider services, ILogger<TaskEndRoutingHandler> logger, RoutingSettings settings)
: base(services, logger, settings)

View file

@ -1,6 +1,6 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Models;
@ -36,8 +36,10 @@ public class RouterInstance : IRouterInstance
public List<RoutingHandlerDef> GetHandlers()
{
var planer = _services.GetRequiredService<IPlaner>();
return _services.GetServices<IRoutingHandler>()
.Where(x => x.IsReasoning == _settings.EnableReasoning)
.Where(x => x.Planers == null || x.Planers.Contains(planer.GetType().Name))
.Where(x => !string.IsNullOrEmpty(x.Description))
.Select((x, i) => new RoutingHandlerDef
{
@ -90,10 +92,11 @@ public class RouterInstance : IRouterInstance
AgentId = x.Id,
Description = x.Description,
Name = x.Name,
RequiredFields = x.RoutingRules
.Where(x => x.Required)
.Select(p => new ParameterPropertyDef(p.Field, p.Description, type: p.Type))
.ToList()
Fields = x.RoutingRules
.Select(p => new ParameterPropertyDef(p.Field, p.Description, type: p.Type)
{
Required = p.Required
}).ToList()
}).ToArray();
}

View file

@ -0,0 +1,56 @@
using BotSharp.Abstraction.Functions.Models;
namespace BotSharp.Core.Routing;
public partial class RoutingService
{
/// <summary>
/// Sometimes LLM hallucinates and fails to set function names correctly.
/// </summary>
/// <param name="args"></param>
private void FixMalformedResponse(FunctionCallFromLlm args)
{
var agentService = _services.GetRequiredService<IAgentService>();
var agents = agentService.GetAgents(allowRouting: true).Result;
var malformed = false;
// Sometimes it populate malformed Function in Agent name
if (!string.IsNullOrEmpty(args.Function) &&
args.Function == args.AgentName)
{
args.Function = "route_to_agent";
malformed = true;
}
// Another case of malformed response
if (string.IsNullOrEmpty(args.AgentName) &&
agents.Select(x => x.Name).Contains(args.Function))
{
args.AgentName = args.Function;
args.Function = "route_to_agent";
malformed = true;
}
// It should be Route to agent, but it is used as Response to user.
if (!string.IsNullOrEmpty(args.AgentName) &&
agents.Select(x => x.Name).Contains(args.AgentName) &&
args.Function != "route_to_agent")
{
args.Function = "route_to_agent";
malformed = true;
}
// Function name shouldn't contain dot symbol
if (!string.IsNullOrEmpty(args.Function) &&
args.Function.Contains('.'))
{
args.Function = args.Function.Split('.').Last();
malformed = true;
}
if (malformed)
{
_logger.LogWarning($"Captured LLM malformed response");
}
}
}

View file

@ -0,0 +1,24 @@
namespace BotSharp.Core.Routing;
public partial class RoutingService
{
public async Task<string> GetConversationContent(List<RoleDialogModel> dialogs, int maxDialogCount = 50)
{
var agentService = _services.GetRequiredService<IAgentService>();
var conversation = "";
foreach (var dialog in dialogs.TakeLast(maxDialogCount))
{
var role = dialog.Role;
if (role != AgentRole.User)
{
var agent = await agentService.GetAgent(dialog.CurrentAgentId);
role = agent.Name;
}
conversation += $"{role}: {dialog.Content}\r\n";
}
return conversation;
}
}

View file

@ -1,187 +0,0 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Templating;
using System.Drawing;
using System.Text.RegularExpressions;
namespace BotSharp.Core.Routing;
public partial class RoutingService
{
public async Task<FunctionCallFromLlm> GetNextInstruction()
{
var content = GetNextStepPrompt();
RoleDialogModel response = default;
var args = new FunctionCallFromLlm();
if (_settings.UseTextCompletion)
{
var completion = CompletionProvider.GetTextCompletion(_services,
provider: _settings.Provider,
model: _settings.Model);
content = _routerInstance.Router.Instruction + "\r\n\r\n" + content + "\r\nResponse: ";
int retryCount = 0;
while (retryCount < 3)
{
try
{
var text = await completion.GetCompletion(content);
response = new RoleDialogModel(AgentRole.Assistant, text);
var pattern = @"\{(?:[^{}]|(?<open>\{)|(?<-open>\}))+(?(open)(?!))\}";
response.Content = Regex.Match(response.Content, pattern).Value;
args = JsonSerializer.Deserialize<FunctionCallFromLlm>(response.Content);
break;
}
catch (Exception ex)
{
_logger.LogError($"{ex.Message}: {response.Content}");
args.Function = "response_to_user";
args.Response = ex.Message;
args.AgentName = "Router";
content += "\r\nPlease response in JSON format.";
}
finally
{
retryCount++;
}
}
}
else
{
var completion = CompletionProvider.GetChatCompletion(_services,
provider: _settings.Provider,
model: _settings.Model);
int retryCount = 0;
var agentService = _services.GetRequiredService<IAgentService>();
var dialogs = Dialogs;
while (retryCount < 3)
{
try
{
var conversation = "";
foreach (var dialog in dialogs.TakeLast(50))
{
var role = dialog.Role;
if (role != AgentRole.User)
{
var agent = await agentService.GetAgent(dialog.CurrentAgentId);
role = agent.Name;
}
conversation += $"{role}: {dialog.Content}\r\n";
}
content = $"{conversation}\r\n###\r\n{content}";
response = completion.GetChatCompletions(_routerInstance.Router, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, content)
});
args = response.Content.JsonContent<FunctionCallFromLlm>();
break;
}
catch (Exception ex)
{
_logger.LogError($"{ex.Message}: {response.Content}");
args.Function = "response_to_user";
args.Response = ex.Message;
args.AgentName = "Router";
content += "\r\nPlease response in JSON format.";
}
finally
{
retryCount++;
}
}
}
#if DEBUG
Console.WriteLine(response.Content, Color.Green);
#else
_logger.LogInformation(response.Content);
#endif
// Fix LLM malformed response
FixMalformedResponse(args);
SaveStateByArgs(args.Arguments);
#if DEBUG
Console.WriteLine($"*** Next Instruction *** {args}", Color.Green);
#else
_logger.LogInformation($"*** Next Instruction *** {args}");
#endif
return args;
}
private string GetNextStepPrompt()
{
var template = _routerInstance.Router.Templates.First(x => x.Name == "next_step_prompt").Content;
// If enabled reasoning
// JsonSerializer.Serialize(new FunctionCallFromLlm());
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(template, new Dictionary<string, object>
{
{ "enabled_reasoning", _settings.EnableReasoning }
});
}
/// <summary>
/// Sometimes LLM hallucinates and fails to set function names correctly.
/// </summary>
/// <param name="args"></param>
private void FixMalformedResponse(FunctionCallFromLlm args)
{
var agentService = _services.GetRequiredService<IAgentService>();
var agents = agentService.GetAgents(allowRouting: true).Result;
var malformed = false;
// Sometimes it populate malformed Function in Agent name
if (!string.IsNullOrEmpty(args.Function) &&
args.Function == args.AgentName)
{
args.Function = "route_to_agent";
malformed = true;
}
// Another case of malformed response
if (string.IsNullOrEmpty(args.AgentName) &&
agents.Select(x => x.Name).Contains(args.Function))
{
args.AgentName = args.Function;
args.Function = "route_to_agent";
malformed = true;
}
// It should be Route to agent, but it is used as Response to user.
if (!string.IsNullOrEmpty(args.AgentName) &&
agents.Select(x => x.Name).Contains(args.AgentName) &&
args.Function != "route_to_agent")
{
args.Function = "route_to_agent";
malformed = true;
}
// Function name shouldn't contain dot symbol
if (!string.IsNullOrEmpty(args.Function) &&
args.Function.Contains('.'))
{
args.Function = args.Function.Split('.').Last();
malformed = true;
}
if (malformed)
{
_logger.LogWarning($"Captured LLM malformed response");
}
}
}

View file

@ -1,7 +1,9 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
using System.Drawing;
namespace BotSharp.Core.Routing;
@ -69,7 +71,8 @@ public partial class RoutingService : IRoutingService
_routerInstance.Load();
var router = _routerInstance.Router;
var handlers = _services.GetServices<IRoutingHandler>();
var planner = _services.GetRequiredService<IPlaner>();
var executor = _services.GetRequiredService<IExecutor>();
int loopCount = 0;
var stop = false;
@ -77,28 +80,33 @@ public partial class RoutingService : IRoutingService
{
loopCount++;
var inst = await GetNextInstruction();
message.Instruction = inst;
inst.Question = message.Content;
var conversation = await GetConversationContent(Dialogs);
var handler = handlers.FirstOrDefault(x => x.Name == inst.Function);
if (handler == null)
// Get instruction from Planner
var inst = await planner.GetNextInstruction(router, conversation);
// Fix LLM malformed response
FixMalformedResponse(inst);
// Save states
SaveStateByArgs(inst.Arguments);
#if DEBUG
Console.WriteLine($"*** Next Instruction *** {inst}", Color.GreenYellow);
#else
_logger.LogInformation($"*** Next Instruction *** {inst}");
#endif
// Handle instruction by Executor
var executed = await executor.Execute(this, router, inst, Dialogs, message);
await planner.AgentExecuted(inst, message);
// There is no need for the agent to continue processing, indicating that the task has been completed.
if (inst.AgentName == null)
{
handler = handlers.FirstOrDefault(x => x.Name == "get_next_instruction");
continue;
break;
}
handler.SetRouter(router);
handler.SetDialogs(Dialogs);
message.FunctionName = inst.Function;
message.Role = AgentRole.Function;
message.FunctionArgs = inst.Arguments == null ? "{}" : JsonSerializer.Serialize(inst.Arguments);
await handler.Handle(this, inst, message);
inst.Response = message.Content;
stop = !_settings.EnableReasoning;
}
return true;

View file

@ -39,6 +39,7 @@ public class TemplateRender : ITemplateRender
}
else
{
_logger.LogWarning(error);
return template;
}
}

View file

@ -31,23 +31,10 @@ public class InstructModeController : ControllerBase, IApiAdapter
.SetState("model", input.Model)
.SetState("input_text", input.Text);
var agentService = _services.GetRequiredService<IAgentService>();
Agent agent = await agentService.LoadAgent(agentId);
// switch to different instruction template
if (!string.IsNullOrEmpty(input.Template))
{
var template = agent.Templates.First(x => x.Name == input.Template).Content;
var render = _services.GetRequiredService<ITemplateRender>();
var dict = new Dictionary<string, object>();
state.GetStates().Select(x => dict[x.Key] = x.Value).ToArray();
var prompt = render.Render(template, dict);
agent.Instruction = prompt;
}
var instructor = _services.GetRequiredService<IInstructService>();
var result = await instructor.Execute(agent,
new RoleDialogModel(AgentRole.User, input.Text));
var result = await instructor.Execute(agentId,
new RoleDialogModel(AgentRole.User, input.Text),
templateName: input.Template);
result.States = state.GetStates();

View file

@ -1,5 +1,6 @@
using Azure;
using Azure.AI.OpenAI;
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Conversations;
@ -73,6 +74,12 @@ public class ChatCompletionProvider : IChatCompletion
}
}
var setting = _services.GetRequiredService<ConversationSetting>();
if (setting.ShowVerboseLog)
{
_logger.LogInformation(msg.Content);
}
// After chat completion hook
Task.WaitAll(hooks.Select(hook =>
hook.AfterGenerated(msg, new TokenStatsModel
@ -193,11 +200,14 @@ public class ChatCompletionProvider : IChatCompletion
protected ChatCompletionsOptions PrepareOptions(Agent agent, List<RoleDialogModel> conversations)
{
var agentService = _services.GetRequiredService<IAgentService>();
var chatCompletionsOptions = new ChatCompletionsOptions();
if (!string.IsNullOrEmpty(agent.Instruction))
{
chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, agent.Instruction));
var instruction = agentService.RenderedInstruction(agent);
chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, instruction));
}
if (!string.IsNullOrEmpty(agent.Knowledges))

View file

@ -11,7 +11,7 @@ public class ProviderHelper
{
public static OpenAIClient GetClient(string model, AzureOpenAiSettings settings)
{
if (model == "gpt-4")
if (model == "gpt-4" || model == "llm-gpt4")
{
var client = new OpenAIClient(new Uri(settings.GPT4.Endpoint), new AzureKeyCredential(settings.GPT4.ApiKey));
return client;

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Conversations;
using BotSharp.Plugin.GoogleAI.Settings;
@ -35,7 +36,9 @@ public class ChatCompletionProvider : IChatCompletion
var messages = conversations.Select(c => new PalmChatMessage(c.Content, c.Role == AgentRole.User ? "user" : "AI"))
.ToList();
var response = client.ChatAsync(messages, agent.Instruction, null).Result;
var agentService = _services.GetRequiredService<IAgentService>();
var instruction = agentService.RenderedInstruction(agent);
var response = client.ChatAsync(messages, instruction, null).Result;
var message = response.Candidates.First();
var msg = new RoleDialogModel(AgentRole.Assistant, message.Content)

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Conversations.Settings;
using BotSharp.Plugin.HuggingFace.Services;
@ -97,7 +98,9 @@ public class ChatCompletionProvider : IChatCompletion
var content = string.Join("\r\n", conversations.Select(x => $"{AgentRole.System}: {x.Content}")).Trim();
content += $"\r\n{AgentRole.Assistant}: ";
var prompt = agent.Instruction + "\r\n" + content;
var agentService = _services.GetRequiredService<IAgentService>();
var instruction = agentService.RenderedInstruction(agent);
var prompt = instruction + "\r\n" + content;
var convSetting = _services.GetRequiredService<ConversationSetting>();
if (convSetting.ShowVerboseLog)

View file

@ -1,3 +1,5 @@
using BotSharp.Abstraction.Agents;
namespace BotSharp.Plugin.LLamaSharp.Providers;
public class ChatCompletionProvider : IChatCompletion
@ -42,7 +44,9 @@ public class ChatCompletionProvider : IChatCompletion
string totalResponse = "";
var prompt = agent.Instruction + "\r\n" + content;
var agentService = _services.GetRequiredService<IAgentService>();
var instruction = agentService.RenderedInstruction(agent);
var prompt = instruction + "\r\n" + content;
var convSetting = _services.GetRequiredService<ConversationSetting>();
if (convSetting.ShowVerboseLog)

View file

@ -15,8 +15,7 @@
"Router": {
"RouterId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
"UseTextCompletion": false,
"EnableReasoning": false,
"Planner": "NaivePlanner",
"Provider": "azure-openai",
"Model": "gpt-3.5-turbo"
},

View file

@ -9,23 +9,24 @@ You're {{router.name}} ({{router.description}}). Follow these steps to handle us
{% for handler in routing_handlers %}
# {{ handler.description}}
{% if handler.parameters and handler.parameters != empty -%}
Response: { "function": "{{ handler.name }}",
Parameters:
- function: {{ handler.name }}
{% for p in handler.parameters -%}
"{{ p.name }}": "{{ p.description }}"{{ ",\r\n " }}
{%- endfor %}}
- {{ p.name }}: {{ p.description }}{{ "\r\n " }}
{%- endfor %}
{%- endif %}
{% endfor %}
[AGENTS]
{% for agent in routing_agents %}
* Agent: {{ agent.name }}
{{ agent.description}}
{% if agent.required_fields and agent.required_fields != empty -%}
Required args:
{% for f in agent.required_fields -%}
- {{ f.name }} ({{ f.type }}): {{ f.description }}{{ "\r\n " }}
* {{ agent.description}}
Agent: {{ agent.name }}
{% if agent.fields and agent.fields != empty -%}
Arguments:
{% for f in agent.fields -%}
- {{ f.name }}: {{ f.description }} (type: {{ f.type }}, required: {{f.required}}){{ "\r\n " }}
{%- endfor %}
{%- endif %}
{% endfor %}
[CONVERSATION]
[CONVERSATION]

View file

@ -5,5 +5,13 @@
"updatedDateTime": "2023-08-18T14:39:32.2349686Z",
"id": "b284db86-e9c2-4c25-a59e-4649797dd130",
"allowRouting": true,
"isPublic": true
"isPublic": true,
"routingRules": [
{
"field": "order_number",
"description": "order number",
"type": "string",
"redirectTo": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd"
}
]
}