Rename to ReasoningPlanner.
This commit is contained in:
parent
513ad5814e
commit
c7ff0e4c2e
|
|
@ -7,6 +7,7 @@ namespace BotSharp.Abstraction.Planning;
|
|||
/// </summary>
|
||||
public interface IPlaner
|
||||
{
|
||||
Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string conversation);
|
||||
Task<FunctionCallFromLlm> GetNextInstruction(Agent router);
|
||||
Task<bool> AgentExecuting(FunctionCallFromLlm inst, RoleDialogModel message);
|
||||
Task<bool> AgentExecuted(FunctionCallFromLlm inst, RoleDialogModel message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public class RoutingContext
|
|||
public string OriginAgentId
|
||||
=> _stack.Where(x => x != _setting.RouterId).Last();
|
||||
|
||||
public bool IsEmpty => !_stack.Any();
|
||||
public string GetCurrentAgentId()
|
||||
{
|
||||
if (_stack.Count == 0)
|
||||
|
|
@ -44,14 +45,13 @@ public class RoutingContext
|
|||
/// <summary>
|
||||
/// Pop current agent
|
||||
/// </summary>
|
||||
/// <returns>Return next agent</returns>
|
||||
public string Pop()
|
||||
public void Pop()
|
||||
{
|
||||
if (_stack.Count > 1)
|
||||
{
|
||||
_stack.Pop();
|
||||
}
|
||||
_stack.Pop();
|
||||
}
|
||||
|
||||
return _stack.Peek();
|
||||
public void Empty()
|
||||
{
|
||||
_stack.Clear();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,14 +71,13 @@ public static class BotSharpServiceCollectionExtensions
|
|||
services.AddSingleton((IServiceProvider x) => routingSettings);
|
||||
|
||||
services.AddScoped<NaivePlanner>();
|
||||
services.AddScoped<FeedbackReasoningPlanner>();
|
||||
services.AddScoped<ReasoningPlanner>();
|
||||
services.AddScoped<IPlaner>(provider =>
|
||||
{
|
||||
if (routingSettings.Planner == "NaivePlanner")
|
||||
if (routingSettings.Planner == nameof(ReasoningPlanner))
|
||||
return provider.GetRequiredService<ReasoningPlanner>();
|
||||
else
|
||||
return provider.GetRequiredService<NaivePlanner>();
|
||||
else if (routingSettings.Planner == "FeedbackReasoningPlanner")
|
||||
return provider.GetRequiredService<FeedbackReasoningPlanner>();
|
||||
throw new NotImplementedException();
|
||||
});
|
||||
|
||||
services.AddScoped<IExecutor, InstructExecutor>();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Planning;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
|
||||
namespace BotSharp.Core.Planning;
|
||||
|
|
@ -16,7 +17,7 @@ public class NaivePlanner : IPlaner
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string conversation)
|
||||
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router)
|
||||
{
|
||||
var next = GetNextStepPrompt(router);
|
||||
|
||||
|
|
@ -25,7 +26,7 @@ public class NaivePlanner : IPlaner
|
|||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var instruction = agentService.RenderedInstruction(router);
|
||||
var content = $"{instruction}\r\n{conversation}\r\n###\r\n{next}";
|
||||
var content = $"{instruction}\r\n###\r\n{next}";
|
||||
|
||||
// text completion
|
||||
content = content + "\r\nResponse: ";
|
||||
|
|
@ -54,13 +55,22 @@ public class NaivePlanner : IPlaner
|
|||
retryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fix LLM malformed response
|
||||
FixMalformedResponse(inst);
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
public async Task<bool> AgentExecuting(FunctionCallFromLlm inst, RoleDialogModel message)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> AgentExecuted(FunctionCallFromLlm inst, RoleDialogModel message)
|
||||
{
|
||||
inst.AgentName = null;
|
||||
var context = _services.GetRequiredService<RoutingContext>();
|
||||
context.Empty();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -73,4 +83,54 @@ public class NaivePlanner : IPlaner
|
|||
{
|
||||
});
|
||||
}
|
||||
|
||||
/// <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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Planning;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
|
||||
namespace BotSharp.Core.Planning;
|
||||
|
||||
public class FeedbackReasoningPlanner : IPlaner
|
||||
public class ReasoningPlanner : IPlaner
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public FeedbackReasoningPlanner(IServiceProvider services, ILogger<FeedbackReasoningPlanner> logger)
|
||||
public ReasoningPlanner(IServiceProvider services, ILogger<ReasoningPlanner> logger)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string conversation)
|
||||
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router)
|
||||
{
|
||||
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");
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ public class FeedbackReasoningPlanner : IPlaner
|
|||
{
|
||||
response = completion.GetChatCompletions(router, new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, content)
|
||||
new RoleDialogModel(AgentRole.User, next)
|
||||
});
|
||||
|
||||
inst = response.Content.JsonContent<FunctionCallFromLlm>();
|
||||
|
|
@ -57,9 +57,28 @@ public class FeedbackReasoningPlanner : IPlaner
|
|||
return inst;
|
||||
}
|
||||
|
||||
public async Task<bool> AgentExecuting(FunctionCallFromLlm inst, RoleDialogModel message)
|
||||
{
|
||||
message.Content = inst.Question;
|
||||
message.FunctionArgs = JsonSerializer.Serialize(inst.Arguments);
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var agent = db.GetAgents(inst.AgentName).FirstOrDefault();
|
||||
|
||||
var context = _services.GetRequiredService<RoutingContext>();
|
||||
context.Push(agent.Id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> AgentExecuted(FunctionCallFromLlm inst, RoleDialogModel message)
|
||||
{
|
||||
inst.AgentName = null;
|
||||
var context = _services.GetRequiredService<RoutingContext>();
|
||||
context.Pop();
|
||||
|
||||
// push Router to continue
|
||||
// Make decision according to last agent's response
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -3,6 +3,7 @@ using BotSharp.Abstraction.Models;
|
|||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using BotSharp.Core.Planning;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
|
|
@ -24,7 +25,7 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHan
|
|||
|
||||
public List<string> Planers => new List<string>
|
||||
{
|
||||
"FeedbackReasoningPlanner"
|
||||
nameof(ReasoningPlanner)
|
||||
};
|
||||
|
||||
public ContinueExecuteTaskRoutingHandler(IServiceProvider services, ILogger<ContinueExecuteTaskRoutingHandler> logger, RoutingSettings settings)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using BotSharp.Core.Planning;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
|
|
@ -18,7 +19,7 @@ public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase, IRouting
|
|||
|
||||
public List<string> Planers => new List<string>
|
||||
{
|
||||
"FeedbackReasoningPlanner"
|
||||
nameof(ReasoningPlanner)
|
||||
};
|
||||
|
||||
public InterruptTaskExecutionRoutingHandler(IServiceProvider services, ILogger<InterruptTaskExecutionRoutingHandler> logger, RoutingSettings settings)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
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.Core.Planning;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve information from specific agent
|
||||
/// </summary>
|
||||
public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
{
|
||||
public string Name => "retrieve_data_from_agent";
|
||||
|
|
@ -13,9 +18,9 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH
|
|||
|
||||
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
|
||||
{
|
||||
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("question", "the question you will ask the agent to get the necessary data"),
|
||||
new ParameterPropertyDef("next_action_agent", "agent that can handle the question"),
|
||||
new ParameterPropertyDef("args", "required parameters extracted from question and hand over to the next agent")
|
||||
{
|
||||
Type = "object"
|
||||
|
|
@ -24,7 +29,7 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH
|
|||
|
||||
public List<string> Planers => new List<string>
|
||||
{
|
||||
"FeedbackReasoningPlanner"
|
||||
nameof(ReasoningPlanner)
|
||||
};
|
||||
|
||||
public RetrieveDataFromAgentRoutingHandler(IServiceProvider services, ILogger<RetrieveDataFromAgentRoutingHandler> logger, RoutingSettings settings)
|
||||
|
|
@ -34,33 +39,9 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH
|
|||
|
||||
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
||||
{
|
||||
// Retrieve information from specific agent
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.GetAgents(inst.AgentName).FirstOrDefault();
|
||||
var ret = await routing.InvokeAgent(record.Id, message);
|
||||
var context = _services.GetRequiredService<RoutingContext>();
|
||||
var ret = await routing.InvokeAgent(context.GetCurrentAgentId(), message);
|
||||
|
||||
/*_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)
|
||||
{
|
||||
MessageId = inst.MessageId,
|
||||
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}: {message.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 true;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using BotSharp.Core.Planning;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
|
|
@ -17,7 +18,7 @@ public class TaskEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
|
||||
public List<string> Planers => new List<string>
|
||||
{
|
||||
"FeedbackReasoningPlanner"
|
||||
nameof(ReasoningPlanner)
|
||||
};
|
||||
|
||||
public TaskEndRoutingHandler(IServiceProvider services, ILogger<TaskEndRoutingHandler> logger, RoutingSettings settings)
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ public partial class RoutingService
|
|||
}
|
||||
else
|
||||
{
|
||||
await InvokeAgent(message.CurrentAgentId, message);
|
||||
await InvokeAgent(agent.Id, message);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using BotSharp.Abstraction.Agents.Models;
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Planning;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using System.Drawing;
|
||||
|
||||
|
|
@ -71,6 +72,7 @@ public partial class RoutingService : IRoutingService
|
|||
_routerInstance.Load();
|
||||
var router = _routerInstance.Router;
|
||||
|
||||
var context = _services.GetRequiredService<RoutingContext>();
|
||||
var planner = _services.GetRequiredService<IPlaner>();
|
||||
var executor = _services.GetRequiredService<IExecutor>();
|
||||
|
||||
|
|
@ -81,12 +83,10 @@ public partial class RoutingService : IRoutingService
|
|||
loopCount++;
|
||||
|
||||
var conversation = await GetConversationContent(Dialogs);
|
||||
router.TemplateDict["conversation"] = conversation;
|
||||
|
||||
// Get instruction from Planner
|
||||
var inst = await planner.GetNextInstruction(router, conversation);
|
||||
|
||||
// Fix LLM malformed response
|
||||
FixMalformedResponse(inst);
|
||||
var inst = await planner.GetNextInstruction(router);
|
||||
|
||||
// Save states
|
||||
SaveStateByArgs(inst.Arguments);
|
||||
|
|
@ -96,6 +96,7 @@ public partial class RoutingService : IRoutingService
|
|||
#else
|
||||
_logger.LogInformation($"*** Next Instruction *** {inst}");
|
||||
#endif
|
||||
await planner.AgentExecuting(inst, message);
|
||||
|
||||
// Handle instruction by Executor
|
||||
var executed = await executor.Execute(this, router, inst, Dialogs, message);
|
||||
|
|
@ -103,7 +104,7 @@ public partial class RoutingService : IRoutingService
|
|||
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)
|
||||
if (context.IsEmpty || context.GetCurrentAgentId() == router.Id)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,8 +58,6 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
|
||||
if (choice.FinishReason == CompletionsFinishReason.FunctionCall)
|
||||
{
|
||||
_logger.LogInformation($"[{agent.Name}]: {message.FunctionCall.Name}({message.FunctionCall.Arguments})");
|
||||
|
||||
msg = new RoleDialogModel(AgentRole.Function, message.Content)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
|
|
@ -77,7 +75,9 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
var setting = _services.GetRequiredService<ConversationSetting>();
|
||||
if (setting.ShowVerboseLog)
|
||||
{
|
||||
_logger.LogInformation(msg.Content);
|
||||
_logger.LogInformation(msg.Role == AgentRole.Function ?
|
||||
$"[{agent.Name}]: {msg.FunctionName}({msg.FunctionArgs})" :
|
||||
$"[{agent.Name}]: {msg.Content}");
|
||||
}
|
||||
|
||||
// After chat completion hook
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public class ProviderHelper
|
|||
{
|
||||
public static OpenAIClient GetClient(string model, AzureOpenAiSettings settings)
|
||||
{
|
||||
if (model == "gpt-4" || model == "llm-gpt4")
|
||||
if (model.Contains("gpt-4") || model.Contains("gpt4"))
|
||||
{
|
||||
var client = new OpenAIClient(new Uri(settings.GPT4.Endpoint), new AzureKeyCredential(settings.GPT4.ApiKey));
|
||||
return client;
|
||||
|
|
|
|||
|
|
@ -29,4 +29,5 @@ Arguments:
|
|||
{%- endif %}
|
||||
{% endfor %}
|
||||
|
||||
[CONVERSATION]
|
||||
[CONVERSATION]
|
||||
{{ conversation }}
|
||||
Loading…
Reference in a new issue