using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Planning; using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Routing.Settings; 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 logger) { _services = services; _logger = logger; } public async Task GetNextInstruction(Agent router, string messageId) { var next = GetNextStepPrompt(router); var inst = new FunctionCallFromLlm(); // text completion /*var agentService = _services.GetRequiredService(); var instruction = agentService.RenderedInstruction(router); var content = $"{instruction}\r\n###\r\n{next}"; content = content + "\r\nResponse: "; var completion = CompletionProvider.GetTextCompletion(_services);*/ // chat completion var routerSetting = _services.GetRequiredService(); var completion = CompletionProvider.GetChatCompletion(_services, provider: routerSetting.Provider, model: routerSetting.Model); int retryCount = 0; while (retryCount < 3) { string text = string.Empty; try { // text completion // text = await completion.GetCompletion(content, router.Id, messageId); var dialogs = new List { new RoleDialogModel(AgentRole.User, next) { MessageId = messageId } }; var response = completion.GetChatCompletions(router, dialogs); inst = response.Content.JsonContent(); break; } catch (Exception ex) { _logger.LogError($"{ex.Message}: {text}"); inst.Function = "response_to_user"; inst.Response = ex.Message; inst.AgentName = "Router"; } finally { retryCount++; } } // Fix LLM malformed response FixMalformedResponse(inst); return inst; } public async Task AgentExecuting(FunctionCallFromLlm inst, RoleDialogModel message) { // Set user content as Planner's question message.FunctionName = inst.Function; message.FunctionArgs = inst.Arguments == null ? "{}" : JsonSerializer.Serialize(inst.Arguments); return true; } public async Task AgentExecuted(FunctionCallFromLlm inst, RoleDialogModel message) { var context = _services.GetRequiredService(); context.Empty(); return true; } private string GetNextStepPrompt(Agent router) { var template = router.Templates.First(x => x.Name == "next_step_prompt").Content; var render = _services.GetRequiredService(); return render.Render(template, new Dictionary { }); } /// /// Sometimes LLM hallucinates and fails to set function names correctly. /// /// private void FixMalformedResponse(FunctionCallFromLlm args) { var agentService = _services.GetRequiredService(); 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"); } } }