/***************************************************************************** Copyright 2024 Written by Haiping Chen. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ******************************************************************************/ using BotSharp.Abstraction.Routing.Reasoning; using BotSharp.Abstraction.Templating; namespace BotSharp.Core.Routing.Reasoning; /// /// Human feedback based reasoner /// public class HFReasoner : IRoutingReasoner { private readonly IServiceProvider _services; private readonly ILogger _logger; public HFReasoner(IServiceProvider services, ILogger logger) { _services = services; _logger = logger; } public async Task GetNextInstruction(Agent router, string messageId, List dialogs) { var next = GetNextStepPrompt(router); RoleDialogModel response = default; var inst = new FunctionCallFromLlm(); var completion = CompletionProvider.GetChatCompletion(_services, provider: router?.LlmConfig?.Provider, model: router?.LlmConfig?.Model); int retryCount = 0; while (retryCount < 3) { try { dialogs = new List { new RoleDialogModel(AgentRole.User, next) { FunctionName = nameof(HFReasoner), MessageId = messageId } }; response = await completion.GetChatCompletions(router, dialogs); inst = response.Content.JsonContent(); break; } catch (Exception ex) { _logger.LogError($"{ex.Message}: {response.Content}"); inst.Function = "response_to_user"; inst.Response = ex.Message; inst.AgentName = "Router"; } finally { retryCount++; } } // Fix LLM malformed response ReasonerHelper.FixMalformedResponse(_services, inst); return inst; } public async Task AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs) { if (!string.IsNullOrEmpty(inst.AgentName)) { var db = _services.GetRequiredService(); var filter = new AgentFilter { AgentName = inst.AgentName }; var agent = db.GetAgents(filter).FirstOrDefault(); var context = _services.GetRequiredService(); context.Push(agent.Id, reason: inst.NextActionReason); // 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(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs) { var context = _services.GetRequiredService(); context.Empty(reason: $"Agent queue is cleared by {nameof(HFReasoner)}"); return true; } private string GetNextStepPrompt(Agent router) { var template = router.Templates.First(x => x.Name == "reasoner.hf").Content; var render = _services.GetRequiredService(); // update states var conv = _services.GetRequiredService(); foreach (var t in conv.States.GetStates()) { router.TemplateDict[t.Key] = t.Value; } var prompt = render.Render(template, router.TemplateDict); return prompt.Trim(); } }