HFPlanner

This commit is contained in:
Haiping Chen 2023-10-30 11:48:18 -05:00
parent 8bf61cb94c
commit 3caa7a5499
39 changed files with 340 additions and 215 deletions

View file

@ -47,4 +47,16 @@ More information about conversation hook please go to [Conversation Hook](../con
```csharp
Task OnStateLoaded(ConversationState state);
Task OnStateChanged(string name, string preValue, string currentValue);
```
### Content Generating Hook
`IContentGeneratingHook`
Model content generating hook, it can be used for logging, metrics and tracing.
```csharp
// Before content generating.
Task BeforeGenerating(Agent agent, List<RoleDialogModel> conversations);
// After content generated.
Task AfterGenerated(RoleDialogModel message, TokenStatsModel tokenStats);
```

View file

@ -3,6 +3,7 @@ namespace BotSharp.Abstraction.Conversations;
public interface IConversationService
{
IConversationStateService States { get; }
string ConversationId { get; }
Task<Conversation> NewConversation(Conversation conversation);
void SetConversationId(string conversationId, List<string> states);
Task<Conversation> GetConversation(string id);

View file

@ -1,10 +1,12 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Models;
namespace BotSharp.Abstraction.Conversations.Models;
public class RoleDialogModel : ITrackableMessage
{
/// <summary>
/// If Role is Assistant, it is same as user's message id.
/// </summary>
public string MessageId { get; set; }
/// <summary>

View file

@ -3,6 +3,7 @@ namespace BotSharp.Abstraction.Conversations.Models;
public class TokenStatsModel
{
public string Model { get; set; }
public string Prompt { get; set; }
public int PromptCount { get; set; }
public int CompletionCount { get; set; }

View file

@ -13,5 +13,5 @@ public interface ITextCompletion
/// <param name="model"></param>
void SetModelName(string model);
Task<string> GetCompletion(string text);
Task<string> GetCompletion(string text, string agentId, string messageId);
}

View file

@ -5,9 +5,8 @@ namespace BotSharp.Abstraction.Planning;
public interface IExecutor
{
Task<bool> Execute(IRoutingService routing,
Agent router,
Task<RoleDialogModel> Execute(IRoutingService routing,
FunctionCallFromLlm inst,
List<RoleDialogModel> dialogs,
RoleDialogModel message);
RoleDialogModel message,
List<RoleDialogModel> dialogs);
}

View file

@ -7,7 +7,7 @@ namespace BotSharp.Abstraction.Planning;
/// </summary>
public interface IPlaner
{
Task<FunctionCallFromLlm> GetNextInstruction(Agent router);
Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId);
Task<bool> AgentExecuting(FunctionCallFromLlm inst, RoleDialogModel message);
Task<bool> AgentExecuted(FunctionCallFromLlm inst, RoleDialogModel message);
}

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
namespace BotSharp.Abstraction.Routing;
@ -15,9 +14,7 @@ public interface IRoutingHandler
bool Enabled => true;
List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>();
void SetRouter(Agent router) { }
void SetDialogs(List<RoleDialogModel> dialogs) { }
void SetDialogs(List<RoleDialogModel> dialogs);
Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message);
}

View file

@ -2,10 +2,9 @@ namespace BotSharp.Abstraction.Routing;
public interface IRoutingService
{
List<RoleDialogModel> Dialogs { get; }
Agent Router { get; }
void ResetRecursiveCounter();
void RefreshDialogs();
Task<bool> InvokeAgent(string agentId, RoleDialogModel message);
Task<bool> InstructLoop(RoleDialogModel message);
Task<bool> ExecuteOnce(Agent agent, RoleDialogModel message);
Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialogs);
Task<RoleDialogModel> InstructLoop(RoleDialogModel message);
Task<RoleDialogModel> ExecuteOnce(Agent agent, RoleDialogModel message);
}

View file

@ -5,7 +5,6 @@ namespace BotSharp.Abstraction.Routing;
public abstract class RoutingHandlerBase
{
protected Agent _router;
protected readonly IServiceProvider _services;
protected readonly ILogger _logger;
protected RoutingSettings _settings;
@ -20,11 +19,6 @@ public abstract class RoutingHandlerBase
_settings = settings;
}
public void SetRouter(Agent router)
{
_router = router;
}
public void SetDialogs(List<RoleDialogModel> dialogs)
{
_dialogs = dialogs;

View file

@ -4,7 +4,9 @@ namespace BotSharp.Core.Agents.Services;
public partial class AgentService
{
#if !DEBUG
[MemoryCache(10 * 60)]
#endif
public async Task<List<Agent>> GetAgents(bool? allowRouting = null)
{
var agents = _db.GetAgents(allowRouting: allowRouting);

View file

@ -71,11 +71,11 @@ public static class BotSharpServiceCollectionExtensions
services.AddSingleton((IServiceProvider x) => routingSettings);
services.AddScoped<NaivePlanner>();
services.AddScoped<ReasoningPlanner>();
services.AddScoped<HFPlanner>();
services.AddScoped<IPlaner>(provider =>
{
if (routingSettings.Planner == nameof(ReasoningPlanner))
return provider.GetRequiredService<ReasoningPlanner>();
if (routingSettings.Planner == nameof(HFPlanner))
return provider.GetRequiredService<HFPlanner>();
else
return provider.GetRequiredService<NaivePlanner>();
});

View file

@ -53,19 +53,18 @@ public partial class ConversationService
var routing = _services.GetRequiredService<IRoutingService>();
var settings = _services.GetRequiredService<RoutingSettings>();
var ret = agentId == settings.RouterId ?
var response = agentId == settings.RouterId ?
await routing.InstructLoop(message) :
await routing.ExecuteOnce(agent, message);
await HandleAssistantMessage(message, onMessageReceived);
await HandleAssistantMessage(response, onMessageReceived);
var statistics = _services.GetRequiredService<ITokenStatistics>();
statistics.PrintStatistics();
routing.ResetRecursiveCounter();
routing.RefreshDialogs();
return ret;
return true;
}
private async Task<Conversation> GetConversationRecord(string agentId)
@ -86,15 +85,15 @@ public partial class ConversationService
return converation;
}
private async Task HandleAssistantMessage(RoleDialogModel message, Func<RoleDialogModel, Task> onMessageReceived)
private async Task HandleAssistantMessage(RoleDialogModel response, Func<RoleDialogModel, Task> onMessageReceived)
{
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.GetAgent(message.CurrentAgentId);
var agent = await agentService.GetAgent(response.CurrentAgentId);
var agentName = agent.Name;
var text = message.Role == AgentRole.Function ?
$"Sending [{agentName}] {message.FunctionName}: {message.Content}" :
$"Sending [{agentName}] {message.Role}: {message.Content}";
var text = response.Role == AgentRole.Function ?
$"Sending [{agentName}] {response.FunctionName}: {response.Content}" :
$"Sending [{agentName}] {response.Role}: {response.Content}";
#if DEBUG
Console.WriteLine(text, Color.Yellow);
#else
@ -103,21 +102,21 @@ public partial class ConversationService
// Only read content from RichContent for UI rendering. When richContent is null, create a basic text message for richContent.
var state = _services.GetRequiredService<IConversationStateService>();
message.RichContent = message.RichContent ?? new RichContent<TextMessage>
response.RichContent = response.RichContent ?? new RichContent<TextMessage>
{
Recipient = new Recipient { Id = state.GetConversationId() },
Message = new TextMessage { Text = message.Content }
Message = new TextMessage { Text = response.Content }
};
var hooks = _services.GetServices<IConversationHook>().ToList();
foreach (var hook in hooks)
{
await hook.OnResponseGenerated(message);
await hook.OnResponseGenerated(response);
}
await onMessageReceived(message);
await onMessageReceived(response);
// Add to dialog history
_storage.Append(_conversationId, message);
_storage.Append(_conversationId, response);
}
}

View file

@ -1,4 +1,3 @@
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Repositories;
namespace BotSharp.Core.Conversations.Services;
@ -12,6 +11,7 @@ public partial class ConversationService : IConversationService
private readonly IConversationStorage _storage;
private readonly IConversationStateService _state;
private string _conversationId;
public string ConversationId => _conversationId;
public IConversationStateService States => _state;

View file

@ -43,14 +43,14 @@ public class EvaluatingService : IEvaluatingService
};
var textCompletion = CompletionProvider.GetTextCompletion(_services);
RoleDialogModel response = default;
RoleDialogModel response = new RoleDialogModel(AgentRole.User, "");
var dialogs = new List<RoleDialogModel>();
int roundCount = 0;
while (true)
{
// var text = string.Join("\r\n", dialogs.Select(x => $"{x.Role}: {x.Content}"));
// text = instruction + $"\r\n###\r\n{text}\r\n{AgentRole.User}: ";
var question = await textCompletion.GetCompletion(prompt);
var question = await textCompletion.GetCompletion(prompt, request.AgentId, response.MessageId);
dialogs.Add(new RoleDialogModel(AgentRole.User, question));
prompt += question.Trim();
@ -61,9 +61,14 @@ public class EvaluatingService : IEvaluatingService
roundCount++;
if (roundCount > 10)
{
Console.WriteLine($"Conversation ended due to execced max round count {roundCount}", Color.Red);
break;
}
if (response.FunctionName == "conversation_end" ||
response.FunctionName == "human_intervention_needed" ||
roundCount > 5)
response.FunctionName == "human_intervention_needed")
{
Console.WriteLine($"Conversation ended by function {response.FunctionName}", Color.Green);
break;

View file

@ -46,7 +46,7 @@ public partial class InstructService : IInstructService
agentService.RenderedTemplate(agent, templateName);
var completer = CompletionProvider.GetTextCompletion(_services);
var result = await completer.GetCompletion(prompt);
var result = await completer.GetCompletion(prompt, agentId, message.MessageId);
var response = new InstructResult
{
MessageId = message.MessageId,

View file

@ -3,30 +3,33 @@ using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Settings;
using BotSharp.Abstraction.Templating;
namespace BotSharp.Core.Planning;
public class ReasoningPlanner : IPlaner
/// <summary>
/// Human feedback based planner
/// </summary>
public class HFPlanner : IPlaner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
public ReasoningPlanner(IServiceProvider services, ILogger<ReasoningPlanner> logger)
public HFPlanner(IServiceProvider services, ILogger<HFPlanner> logger)
{
_services = services;
_logger = logger;
}
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router)
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId)
{
var next = GetNextStepPrompt(router);
RoleDialogModel response = default;
var inst = new FunctionCallFromLlm();
var completion = CompletionProvider.GetChatCompletion(_services,
model: "llm-gpt4");
var completion = CompletionProvider.GetChatCompletion(_services);
int retryCount = 0;
while (retryCount < 3)
@ -36,6 +39,9 @@ public class ReasoningPlanner : IPlaner
response = completion.GetChatCompletions(router, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, next)
{
MessageId = messageId
}
});
inst = response.Content.JsonContent<FunctionCallFromLlm>();
@ -59,14 +65,14 @@ public class ReasoningPlanner : IPlaner
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();
if (!string.IsNullOrEmpty(inst.AgentName))
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agent = db.GetAgents(inst.AgentName).FirstOrDefault();
var context = _services.GetRequiredService<RoutingContext>();
context.Push(agent.Id);
var context = _services.GetRequiredService<RoutingContext>();
context.Push(agent.Id);
}
return true;
}
@ -76,9 +82,6 @@ public class ReasoningPlanner : IPlaner
var context = _services.GetRequiredService<RoutingContext>();
context.Pop();
// push Router to continue
// Make decision according to last agent's response
return true;
}

View file

@ -1,4 +1,3 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Routing;
@ -16,30 +15,24 @@ public class InstructExecutor : IExecutor
_logger = logger;
}
public async Task<bool> Execute(IRoutingService routing,
Agent router,
public async Task<RoleDialogModel> Execute(IRoutingService routing,
FunctionCallFromLlm inst,
List<RoleDialogModel> dialogs,
RoleDialogModel message)
RoleDialogModel message,
List<RoleDialogModel> dialogs)
{
// 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;
// For client display purpose
var response = dialogs.Last();
response.MessageId = message.MessageId;
response.Instruction = inst;
return handled;
return response;
}
}

View file

@ -3,6 +3,7 @@ using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Templating;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
namespace BotSharp.Core.Planning;
@ -17,11 +18,10 @@ public class NaivePlanner : IPlaner
_logger = logger;
}
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router)
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId)
{
var next = GetNextStepPrompt(router);
RoleDialogModel response = default;
var inst = new FunctionCallFromLlm();
var agentService = _services.GetRequiredService<IAgentService>();
@ -36,16 +36,20 @@ public class NaivePlanner : IPlaner
int retryCount = 0;
while (retryCount < 3)
{
string text = string.Empty;
try
{
var text = await completion.GetCompletion(content);
response = new RoleDialogModel(AgentRole.Assistant, text);
text = await completion.GetCompletion(content, router.Id, messageId);
var response = new RoleDialogModel(AgentRole.Assistant, text)
{
MessageId = messageId
};
inst = response.Content.JsonContent<FunctionCallFromLlm>();
break;
}
catch (Exception ex)
{
_logger.LogError($"{ex.Message}: {response.Content}");
_logger.LogError($"{ex.Message}: {text}");
inst.Function = "response_to_user";
inst.Response = ex.Message;
inst.AgentName = "Router";
@ -64,6 +68,10 @@ public class NaivePlanner : IPlaner
public async Task<bool> 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;
}

View file

@ -7,7 +7,7 @@ using BotSharp.Core.Planning;
namespace BotSharp.Core.Routing.Handlers;
public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHandler
public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase//, IRoutingHandler
{
public string Name => "continue_execute_task";
@ -15,7 +15,8 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHan
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
{
new ParameterPropertyDef("agent", "the name of the 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("reason", "why continue to execute current task"),
new ParameterPropertyDef("args", "required parameters extracted from question")
{
@ -25,7 +26,7 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHan
public List<string> Planers => new List<string>
{
nameof(ReasoningPlanner)
nameof(HFPlanner)
};
public ContinueExecuteTaskRoutingHandler(IServiceProvider services, ILogger<ContinueExecuteTaskRoutingHandler> 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;
@ -25,8 +24,15 @@ public class ConversationEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
{
message.Content = inst.Response;
message.FunctionName = inst.Function;
var response = new RoleDialogModel(AgentRole.Assistant, inst.Response)
{
CurrentAgentId = message.CurrentAgentId,
MessageId = message.MessageId,
StopCompletion = true,
FunctionName = inst.Function
};
_dialogs.Add(response);
var hooks = _services.GetServices<IConversationHook>()
.OrderBy(x => x.Priority)
@ -34,7 +40,7 @@ public class ConversationEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
foreach (var hook in hooks)
{
await hook.OnConversationEnding(message);
await hook.OnConversationEnding(response);
}
return true;

View file

@ -24,8 +24,15 @@ public class HumanInterventionNeededHandler : RoutingHandlerBase, IRoutingHandle
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
{
message.Role = AgentRole.Assistant;
message.Content = inst.Response;
var response = new RoleDialogModel(AgentRole.Assistant, inst.Response)
{
CurrentAgentId = message.CurrentAgentId,
MessageId = message.MessageId,
StopCompletion = true,
FunctionName = inst.Function
};
_dialogs.Add(response);
var hooks = _services.GetServices<IConversationHook>()
.OrderBy(x => x.Priority)
@ -33,7 +40,7 @@ public class HumanInterventionNeededHandler : RoutingHandlerBase, IRoutingHandle
foreach (var hook in hooks)
{
await hook.OnHumanInterventionNeeded(message);
await hook.OnHumanInterventionNeeded(response);
}
return true;

View file

@ -5,7 +5,7 @@ using BotSharp.Core.Planning;
namespace BotSharp.Core.Routing.Handlers;
public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase, IRoutingHandler
public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase//, IRoutingHandler
{
public string Name => "interrupt_task_execution";
@ -19,7 +19,7 @@ public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase, IRouting
public List<string> Planers => new List<string>
{
nameof(ReasoningPlanner)
nameof(HFPlanner)
};
public InterruptTaskExecutionRoutingHandler(IServiceProvider services, ILogger<InterruptTaskExecutionRoutingHandler> 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;
@ -24,9 +23,15 @@ public class ResponseToUserRoutingHandler : RoutingHandlerBase, IRoutingHandler
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
{
message.Content = inst.Response;
message.StopCompletion = true;
message.Role = AgentRole.Assistant;
var response = new RoleDialogModel(AgentRole.Assistant, inst.Response)
{
CurrentAgentId = message.CurrentAgentId,
MessageId = message.MessageId,
StopCompletion = true
};
_dialogs.Add(response);
return true;
}
}

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Settings;
@ -21,6 +20,7 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH
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"
@ -29,7 +29,7 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH
public List<string> Planers => new List<string>
{
nameof(ReasoningPlanner)
nameof(HFPlanner)
};
public RetrieveDataFromAgentRoutingHandler(IServiceProvider services, ILogger<RetrieveDataFromAgentRoutingHandler> logger, RoutingSettings settings)
@ -40,7 +40,22 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
{
var context = _services.GetRequiredService<RoutingContext>();
var ret = await routing.InvokeAgent(context.GetCurrentAgentId(), message);
var agentId = context.GetCurrentAgentId();
var dialogs = new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, inst.Question)
{
CurrentAgentId = agentId,
MessageId = message.MessageId
}
};
var ret = await routing.InvokeAgent(agentId, dialogs);
var response = dialogs.Last();
inst.Response = response.Content;
// Add final response to parent dialog
_dialogs.Add(response);
return ret;
}

View file

@ -35,7 +35,11 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
message.FunctionArgs = JsonSerializer.Serialize(inst);
var ret = await function.Execute(message);
ret = await routing.InvokeAgent(context.GetCurrentAgentId(), message);
var agentId = context.GetCurrentAgentId();
ret = await routing.InvokeAgent(agentId, _dialogs);
var response = _dialogs.Last();
inst.Response = response.Content;
return true;
}

View file

@ -18,7 +18,7 @@ public class TaskEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
public List<string> Planers => new List<string>
{
nameof(ReasoningPlanner)
nameof(HFPlanner)
};
public TaskEndRoutingHandler(IServiceProvider services, ILogger<TaskEndRoutingHandler> logger, RoutingSettings settings)
@ -32,10 +32,9 @@ public class TaskEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
.OrderBy(x => x.Priority)
.ToList();
foreach (var hook in hooks)
{
await hook.OnCurrentTaskEnding(message);
}
Task.WaitAll(hooks
.Select(h => h.OnCurrentTaskEnding(message))
.ToArray());
return true;
}

View file

@ -8,7 +8,7 @@ public partial class RoutingService
{
const int MAXIMUM_RECURSION_DEPTH = 3;
private int _currentRecursionDepth = 0;
public async Task<bool> InvokeAgent(string agentId, RoleDialogModel message)
public async Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialogs)
{
_currentRecursionDepth++;
if (_currentRecursionDepth > MAXIMUM_RECURSION_DEPTH)
@ -22,25 +22,22 @@ public partial class RoutingService
var settings = _services.GetRequiredService<ChatCompletionSetting>();
var chatCompletion = CompletionProvider.GetChatCompletion(_services, provider: settings.Provider, model: settings.Model);
RoleDialogModel response = chatCompletion.GetChatCompletions(agent, Dialogs);
message.Role = response.Role;
RoleDialogModel response = chatCompletion.GetChatCompletions(agent, dialogs);
if (response.Role == AgentRole.Function)
{
message.FunctionName = response.FunctionName;
message.FunctionArgs = response.FunctionArgs;
await InvokeFunction(agent, message);
await InvokeFunction(agent, response, dialogs);
}
else
{
message.Content = response.Content;
dialogs.Add(response);
}
return true;
}
private async Task<RoleDialogModel> InvokeFunction(Agent agent, RoleDialogModel message)
private async Task<bool> InvokeFunction(Agent agent, RoleDialogModel message, List<RoleDialogModel> dialogs)
{
// execute function
// Save states
@ -50,8 +47,6 @@ public partial class RoutingService
// Call functions
await conversationService.CallFunctions(message);
Dialogs.Add(message);
// Pass execution result to LLM to get response
if (!message.StopCompletion)
{
@ -60,19 +55,24 @@ public partial class RoutingService
var responseTemplate = await templateService.RenderFunctionResponse(agent.Id, message);
if (!string.IsNullOrEmpty(responseTemplate))
{
message.Role = AgentRole.Assistant;
message.Content = responseTemplate.Trim();
message.Role = AgentRole.Assistant;
dialogs.Add(message);
}
else
{
await InvokeAgent(agent.Id, message);
// Save to memory dialogs
dialogs.Add(new RoleDialogModel(AgentRole.Function, message.Content)
{
FunctionArgs = message.FunctionArgs,
FunctionName = message.FunctionName
});
// Send to LLM
await InvokeAgent(agent.Id, dialogs);
}
}
else
{
message.Role = AgentRole.Assistant;
}
return message;
return true;
}
}

View file

@ -14,30 +14,14 @@ public partial class RoutingService : IRoutingService
private readonly RoutingSettings _settings;
private readonly IRouterInstance _routerInstance;
private readonly ILogger _logger;
private List<RoleDialogModel> _dialogs;
public List<RoleDialogModel> Dialogs {
get
{
if (_dialogs == null)
{
var conv = _services.GetRequiredService<IConversationService>();
_dialogs = conv.GetDialogHistory();
}
return _dialogs;
}
}
private Agent _router;
public Agent Router => _router;
public void ResetRecursiveCounter()
{
_currentRecursionDepth = 0;
}
public void RefreshDialogs()
{
_dialogs = null;
}
public RoutingService(IServiceProvider services,
RoutingSettings settings,
ILogger<RoutingService> logger,
@ -49,44 +33,57 @@ public partial class RoutingService : IRoutingService
_routerInstance = routerInstance;
}
public async Task<bool> ExecuteOnce(Agent agent, RoleDialogModel message)
public async Task<RoleDialogModel> ExecuteOnce(Agent agent, RoleDialogModel message)
{
var handlers = _services.GetServices<IRoutingHandler>();
var handler = handlers.FirstOrDefault(x => x.Name == "route_to_agent");
handler.SetDialogs(Dialogs);
var dialogs = new List<RoleDialogModel> { message };
handler.SetDialogs(dialogs);
var result = await handler.Handle(this, new FunctionCallFromLlm
var inst = new FunctionCallFromLlm
{
Function = "route_to_agent",
Question = message.Content,
Reason = message.Content,
AgentName = agent.Name
}, message);
};
return result;
var result = await handler.Handle(this, inst, message);
var response = dialogs.Last();
response.MessageId = message.MessageId;
response.Instruction = inst;
return response;
}
public async Task<bool> InstructLoop(RoleDialogModel message)
public async Task<RoleDialogModel> InstructLoop(RoleDialogModel message)
{
_routerInstance.Load();
var router = _routerInstance.Router;
_router = _routerInstance.Load()
.Router;
RoleDialogModel response = default;
var conv = _services.GetRequiredService<IConversationService>();
var dialogs = conv.GetDialogHistory();
var context = _services.GetRequiredService<RoutingContext>();
var planner = _services.GetRequiredService<IPlaner>();
var executor = _services.GetRequiredService<IExecutor>();
context.Push(_router.Id);
int loopCount = 0;
var stop = false;
while (!stop && loopCount < 5)
while (loopCount < 5 && !context.IsEmpty)
{
loopCount++;
var conversation = await GetConversationContent(Dialogs);
router.TemplateDict["conversation"] = conversation;
var conversation = await GetConversationContent(dialogs);
_router.TemplateDict["conversation"] = conversation;
// Get instruction from Planner
var inst = await planner.GetNextInstruction(router);
var inst = await planner.GetNextInstruction(_router, message.MessageId);
// Save states
SaveStateByArgs(inst.Arguments);
@ -99,18 +96,12 @@ public partial class RoutingService : IRoutingService
await planner.AgentExecuting(inst, message);
// Handle instruction by Executor
var executed = await executor.Execute(this, router, inst, Dialogs, message);
response = await executor.Execute(this, inst, message, dialogs);
await planner.AgentExecuted(inst, message);
// There is no need for the agent to continue processing, indicating that the task has been completed.
if (context.IsEmpty || context.GetCurrentAgentId() == router.Id)
{
break;
}
await planner.AgentExecuted(inst, response);
}
return true;
return response;
}
protected void SaveStateByArgs(JsonDocument args)

View file

@ -57,7 +57,11 @@ public class ConversationController : ControllerBase, IApiAdapter
await conv.SendMessage(agentId, inputMsg,
async msg =>
{
response.Text = msg.Content;
response.Function = msg.FunctionName;
response.RichContent = msg.RichContent;
response.Instruction = msg.Instruction;
response.Data = msg.Data;
},
async fnExecuting =>
{
@ -69,11 +73,6 @@ public class ConversationController : ControllerBase, IApiAdapter
});
response.MessageId = inputMsg.MessageId;
response.Text = inputMsg.Content;
response.Data = inputMsg.Data;
response.Function = inputMsg.FunctionName;
response.Instruction = inputMsg.Instruction;
response.RichContent = inputMsg.RichContent;
return response;
}

View file

@ -1,10 +1,8 @@
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.ApiAdapters;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Instructs;
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Templating;
using BotSharp.Core.Infrastructures;
using BotSharp.OpenAPI.ViewModels.Instructs;
@ -50,6 +48,6 @@ public class InstructModeController : ControllerBase, IApiAdapter
.SetState("model", input.Model);
var textCompletion = CompletionProvider.GetTextCompletion(_services);
return await textCompletion.GetCompletion(input.Text);
return await textCompletion.GetCompletion(input.Text, Guid.Empty.ToString(), Guid.Empty.ToString());
}
}

View file

@ -1,5 +1,6 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Models;
using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Conversations;
@ -8,7 +9,10 @@ public class MessageResponseModel : ITrackableMessage
public string MessageId { get; set; }
public string Text { get; set; }
public string Function { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public object Data { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public FunctionCallFromLlm Instruction { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public object? RichContent { get; set; }
}

View file

@ -8,6 +8,7 @@ using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Conversations.Settings;
using BotSharp.Abstraction.MLTasks;
using BotSharp.Plugin.AzureOpenAI.Settings;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
@ -45,51 +46,54 @@ public class ChatCompletionProvider : IChatCompletion
hook.BeforeGenerating(agent, conversations)).ToArray());
var client = ProviderHelper.GetClient(_model, _settings);
var chatCompletionsOptions = PrepareOptions(agent, conversations);
var (prompt, chatCompletionsOptions) = PrepareOptions(agent, conversations);
var response = client.GetChatCompletions(_model, chatCompletionsOptions);
var choice = response.Value.Choices[0];
var message = choice.Message;
var msg = new RoleDialogModel(AgentRole.Assistant, message.Content)
var responseMessage = new RoleDialogModel(AgentRole.Assistant, message.Content)
{
CurrentAgentId = agent.Id
CurrentAgentId = agent.Id,
MessageId = conversations.Last().MessageId
};
if (choice.FinishReason == CompletionsFinishReason.FunctionCall)
{
msg = new RoleDialogModel(AgentRole.Function, message.Content)
responseMessage = new RoleDialogModel(AgentRole.Function, message.Content)
{
CurrentAgentId = agent.Id,
MessageId = conversations.Last().MessageId,
FunctionName = message.FunctionCall.Name,
FunctionArgs = message.FunctionCall.Arguments
};
// Somethings LLM will generate a function name with agent name.
if (!string.IsNullOrEmpty(msg.FunctionName))
if (!string.IsNullOrEmpty(responseMessage.FunctionName))
{
msg.FunctionName = msg.FunctionName.Split('.').Last();
responseMessage.FunctionName = responseMessage.FunctionName.Split('.').Last();
}
}
var setting = _services.GetRequiredService<ConversationSetting>();
if (setting.ShowVerboseLog)
{
_logger.LogInformation(msg.Role == AgentRole.Function ?
$"[{agent.Name}]: {msg.FunctionName}({msg.FunctionArgs})" :
$"[{agent.Name}]: {msg.Content}");
_logger.LogInformation(responseMessage.Role == AgentRole.Function ?
$"[{agent.Name}]: {responseMessage.FunctionName}({responseMessage.FunctionArgs})" :
$"[{agent.Name}]: {responseMessage.Content}");
}
// After chat completion hook
Task.WaitAll(hooks.Select(hook =>
hook.AfterGenerated(msg, new TokenStatsModel
hook.AfterGenerated(responseMessage, new TokenStatsModel
{
Prompt = prompt,
Model = _model,
PromptCount = response.Value.Usage.PromptTokens,
CompletionCount = response.Value.Usage.CompletionTokens
})).ToArray());
return msg;
return responseMessage;
}
public async Task<bool> GetChatCompletionsAsync(Agent agent,
@ -104,7 +108,7 @@ public class ChatCompletionProvider : IChatCompletion
hook.BeforeGenerating(agent, conversations)).ToArray());
var client = ProviderHelper.GetClient(_model, _settings);
var chatCompletionsOptions = PrepareOptions(agent, conversations);
var (prompt, chatCompletionsOptions) = PrepareOptions(agent, conversations);
var response = await client.GetChatCompletionsAsync(_model, chatCompletionsOptions);
var choice = response.Value.Choices[0];
@ -119,6 +123,7 @@ public class ChatCompletionProvider : IChatCompletion
Task.WaitAll(hooks.Select(hook =>
hook.AfterGenerated(msg, new TokenStatsModel
{
Prompt = prompt,
Model = _model,
PromptCount = response.Value.Usage.PromptTokens,
CompletionCount = response.Value.Usage.CompletionTokens
@ -156,7 +161,7 @@ public class ChatCompletionProvider : IChatCompletion
public async Task<bool> GetChatCompletionsStreamingAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived)
{
var client = ProviderHelper.GetClient(_model, _settings);
var chatCompletionsOptions = PrepareOptions(agent, conversations);
var (prompt, chatCompletionsOptions) = PrepareOptions(agent, conversations);
var response = await client.GetChatCompletionsStreamingAsync(_model, chatCompletionsOptions);
using StreamingChatCompletions streaming = response.Value;
@ -198,7 +203,7 @@ public class ChatCompletionProvider : IChatCompletion
}
protected ChatCompletionsOptions PrepareOptions(Agent agent, List<RoleDialogModel> conversations)
protected (string, ChatCompletionsOptions) PrepareOptions(Agent agent, List<RoleDialogModel> conversations)
{
var agentService = _services.GetRequiredService<IAgentService>();
@ -255,33 +260,50 @@ public class ChatCompletionProvider : IChatCompletion
// chatCompletionsOptions.FrequencyPenalty = 0;
// chatCompletionsOptions.PresencePenalty = 0;
var prompt = GetPrompt(chatCompletionsOptions);
var convSetting = _services.GetRequiredService<ConversationSetting>();
if (convSetting.ShowVerboseLog)
{
if (chatCompletionsOptions.Messages.Count > 0)
{
_logger.LogInformation("VERBOSE COMPLETION MESSAGES");
var verbose = string.Join("\r\n", chatCompletionsOptions.Messages.Select(x =>
_logger.LogInformation(prompt);
}
return (prompt, chatCompletionsOptions);
}
private string GetPrompt(ChatCompletionsOptions chatCompletionsOptions)
{
var prompt = string.Empty;
if (chatCompletionsOptions.Messages.Count > 0)
{
// System instruction
var verbose = string.Join("\r\n", chatCompletionsOptions.Messages
.Where(x => x.Role == AgentRole.System).Select(x =>
{
return $"{x.Role}: {x.Content}";
}));
prompt += $"\r\n[INSTRUCTION]\r\n{verbose}\r\n";
verbose = string.Join("\r\n", chatCompletionsOptions.Messages
.Where(x => x.Role != AgentRole.System).Select(x =>
{
return x.Role == ChatRole.Function ?
$"{x.Role}: {x.Name} => {x.Content}" :
$"{x.Role}: {x.Content}";
}));
_logger.LogInformation(verbose);
}
if (chatCompletionsOptions.Functions.Count > 0)
{
_logger.LogInformation("VERBOSE FUNCTIONS");
var verbose = string.Join("\r\n", chatCompletionsOptions.Functions.Select(x =>
{
return $"{x.Name}: {x.Description}\r\n{x.Parameters}";
}));
_logger.LogInformation(verbose);
}
prompt += $"\r\n[CONVERSATION]\r\n{verbose}\r\n";
}
return chatCompletionsOptions;
if (chatCompletionsOptions.Functions.Count > 0)
{
var functions = string.Join("\r\n", chatCompletionsOptions.Functions.Select(x =>
{
return $"{x.Name}: {x.Description}\r\n{x.Parameters}";
}));
prompt += $"\r\n[FUNCTIONS]\r\n{functions}\r\n";
}
return prompt;
}
public void SetModelName(string model)

View file

@ -32,16 +32,26 @@ public class TextCompletionProvider : ITextCompletion
_logger = logger;
}
public async Task<string> GetCompletion(string text)
public async Task<string> GetCompletion(string text, string agentId, string messageId)
{
var hooks = _services.GetServices<IContentGeneratingHook>().ToList();
// Before chat completion hook
var agent = new Agent()
{
Id = agentId,
};
var message = new RoleDialogModel(AgentRole.User, text)
{
CurrentAgentId = agentId,
MessageId = messageId
};
Task.WaitAll(hooks.Select(hook =>
hook.BeforeGenerating(new Agent(),
new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, text)
hook.BeforeGenerating(agent,
new List<RoleDialogModel>
{
message
})).ToArray());
var client = ProviderHelper.GetClient(_model, _settings);
@ -85,9 +95,15 @@ public class TextCompletionProvider : ITextCompletion
}
// After chat completion hook
var responseMessage = new RoleDialogModel(AgentRole.Assistant, completion)
{
CurrentAgentId = agentId,
MessageId = messageId
};
Task.WaitAll(hooks.Select(hook =>
hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, completion), new TokenStatsModel
hook.AfterGenerated(responseMessage, new TokenStatsModel
{
Prompt = text,
Model = _model,
PromptCount = response.Value.Usage.PromptTokens,
CompletionCount = response.Value.Usage.CompletionTokens

View file

@ -26,13 +26,21 @@ public class TextCompletionProvider : ITextCompletion
_tokenStatistics = tokenStatistics;
}
public async Task<string> GetCompletion(string text)
public async Task<string> GetCompletion(string text, string agentId, string messageId)
{
var hooks = _services.GetServices<IContentGeneratingHook>().ToList();
// Before chat completion hook
var agent = new Agent()
{
Id = agentId
};
var userMessage = new RoleDialogModel(AgentRole.User, text)
{
MessageId = messageId
};
Task.WaitAll(hooks.Select(hook =>
hook.BeforeGenerating(new Agent(), new List<RoleDialogModel> { new RoleDialogModel(AgentRole.User, text) })).ToArray());
hook.BeforeGenerating(agent, new List<RoleDialogModel> { userMessage })).ToArray());
var client = new GooglePalmClient(apiKey: _settings.PaLM.ApiKey);
_tokenStatistics.StartTimer();

View file

@ -67,7 +67,7 @@ public class KnowledgeService : IKnowledgeService
sb.AppendLine("ANSWER: ");
prompt = sb.ToString().Trim();
var completion = await GetTextCompletion().GetCompletion(prompt);
var completion = await GetTextCompletion().GetCompletion(prompt, Guid.Empty.ToString(), Guid.Empty.ToString());
return JsonSerializer.Deserialize<List<RetrievedResult>>(completion);
}

View file

@ -20,13 +20,21 @@ public class TextCompletionProvider : ITextCompletion
_tokenStatistics = tokenStatistics;
}
public async Task<string> GetCompletion(string text)
public async Task<string> GetCompletion(string text, string agentId, string messageId)
{
var hooks = _services.GetServices<IContentGeneratingHook>().ToList();
// Before chat completion hook
var agent = new Agent()
{
Id = agentId
};
var userMessage = new RoleDialogModel(AgentRole.User, text)
{
MessageId = messageId
};
Task.WaitAll(hooks.Select(hook =>
hook.BeforeGenerating(new Agent(), new List<RoleDialogModel> { new RoleDialogModel(AgentRole.User, text) })).ToArray());
hook.BeforeGenerating(agent, new List<RoleDialogModel> { userMessage })).ToArray());
var llama = _services.GetRequiredService<LlamaAiModel>();
llama.LoadModel(_model);
@ -44,8 +52,13 @@ public class TextCompletionProvider : ITextCompletion
_tokenStatistics.StopTimer();
// After chat completion hook
var responseMessage = new RoleDialogModel(AgentRole.Assistant, completion)
{
CurrentAgentId = agentId,
MessageId = messageId
};
Task.WaitAll(hooks.Select(hook =>
hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, completion), new TokenStatsModel
hook.AfterGenerated(responseMessage, new TokenStatsModel
{
Model = _model
})).ToArray());

View file

@ -5,6 +5,7 @@ Role: You're a customer who is going to buy a pizza.
* Your phone number is +16308926431
Requirments:
* Greeting to clerk.
* You want to know what kind of pizza do they have.
* You want to buy three piece of pizza.
* Say Bye if the order is placed and payment is completed.

View file

@ -0,0 +1,21 @@
using BotSharp.Abstraction.Agents;
namespace BotSharp.Plugin.PizzaBot.Hooks;
public class CommonAgentHook : AgentHookBase
{
public override string SelfId => string.Empty;
public CommonAgentHook(IServiceProvider services, AgentSettings settings)
: base(services, settings)
{
}
public override bool OnInstructionLoaded(string template, Dictionary<string, object> dict)
{
dict["current_date"] = DateTime.Now.ToString("MM/dd/yyyy");
dict["current_time"] = DateTime.Now.ToString("hh:mm tt");
dict["current_weekday"] = DateTime.Now.DayOfWeek;
return base.OnInstructionLoaded(template, dict);
}
}