From 995b71e1457efb0f1e1030dd226be9679625c092 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 14 Mar 2025 11:13:13 -0500 Subject: [PATCH 01/12] call dummy func --- .../Functions/Models/FunctionDef.cs | 4 ++ .../Routing/RoutingService.InvokeAgent.cs | 4 +- .../Routing/RoutingService.InvokeFunction.cs | 59 +++++++++++++++++++ .../Models/FunctionDefMongoElement.cs | 7 ++- 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs index e2a56817..bd71009b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs @@ -25,6 +25,10 @@ public class FunctionDef [JsonPropertyName("parameters")] public FunctionParametersDef Parameters { get; set; } = new FunctionParametersDef(); + [JsonPropertyName("output")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Output { get; set; } + public override string ToString() { return $"{Name}: {Description}"; diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs index bec44bc4..7bfd9352 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs @@ -101,8 +101,8 @@ public partial class RoutingService Context.SetDialogs(dialogs); // Send to Next LLM - var agentId = routing.Context.GetCurrentAgentId(); - await InvokeAgent(agentId, dialogs); + var curAgentId = routing.Context.GetCurrentAgentId(); + await InvokeAgent(curAgentId, dialogs); } } else diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index fb074dfc..aaa81694 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -1,4 +1,7 @@ using BotSharp.Abstraction.Functions; +using BotSharp.Abstraction.Options; +using BotSharp.Abstraction.Templating; + namespace BotSharp.Core.Routing; public partial class RoutingService @@ -8,6 +11,12 @@ public partial class RoutingService var function = _services.GetServices().FirstOrDefault(x => x.Name == name); if (function == null) { + var executed = await InvokeDummyFunction(name, message); + if (executed) + { + return true; + } + message.StopCompletion = true; message.Content = $"Can't find function implementation of {name}."; _logger.LogError(message.Content); @@ -87,4 +96,54 @@ public partial class RoutingService return result; } + + private async Task InvokeDummyFunction(string functionName, RoleDialogModel message) + { + if (string.IsNullOrEmpty(message.CurrentAgentId)) + { + return false; + } + + var agentService = _services.GetRequiredService(); + var agent = await agentService.GetAgent(message.CurrentAgentId); + var found = agent?.Functions?.FirstOrDefault(x => x.Name == functionName); + if (string.IsNullOrWhiteSpace(found?.Output)) + { + return false; + } + + var clonedMessage = RoleDialogModel.From(message); + clonedMessage.FunctionName = functionName; + clonedMessage.Indication = "Running"; + + var hooks = _services + .GetRequiredService() + .HooksOrderByPriority; + + foreach (var hook in hooks) + { + await hook.OnFunctionExecuting(clonedMessage); + } + + var render = _services.GetRequiredService(); + var state = _services.GetRequiredService(); + var options = _services.GetRequiredService(); + + var dict = new Dictionary(); + foreach (var item in state.GetStates()) + { + dict[item.Key] = item.Value; + } + + var text = render.Render(found.Output, dict); + message.Content = text; + clonedMessage.Content = text; + + foreach (var hook in hooks) + { + await hook.OnFunctionExecuted(clonedMessage); + } + + return true; + } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/FunctionDefMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/FunctionDefMongoElement.cs index f274eb0c..8ad69be5 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/FunctionDefMongoElement.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/FunctionDefMongoElement.cs @@ -12,6 +12,7 @@ public class FunctionDefMongoElement public string? VisibilityExpression { get; set; } public string? Impact { get; set; } public FunctionParametersDefMongoElement Parameters { get; set; } = new(); + public string? Output { get; set; } public static FunctionDefMongoElement ToMongoElement(FunctionDef function) { @@ -27,7 +28,8 @@ public class FunctionDefMongoElement Type = function.Parameters.Type, Properties = JsonSerializer.Serialize(function.Parameters.Properties), Required = function.Parameters.Required, - } + }, + Output = function.Output }; } @@ -45,7 +47,8 @@ public class FunctionDefMongoElement Type = function.Parameters.Type, Properties = JsonSerializer.Deserialize(function.Parameters.Properties.IfNullOrEmptyAs("{}")), Required = function.Parameters.Required, - } + }, + Output = function.Output }; } } From 5d4dcc3e1325510a7b9139576434b4f3fb9e05c0 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 14 Mar 2025 17:04:54 -0500 Subject: [PATCH 02/12] refine state loading --- .../Conversations/IConversationService.cs | 2 +- .../Filters/ConversationFilter.cs | 2 + .../Repositories/IBotSharpRepository.cs | 2 +- .../Services/ConversationService.cs | 7 +- .../Services/ConversationStateService.cs | 39 +++++++---- .../Repository/BotSharpDbContext.cs | 2 +- .../FileRepository.Conversation.cs | 38 +++++++--- .../Routing/RoutingService.InvokeFunction.cs | 69 +++++++++---------- .../Controllers/ConversationController.cs | 6 +- .../View/ConversationViewModel.cs | 5 +- .../Hooks/ChatHubConversationHook.cs | 2 + .../MongoRepository.Conversation.cs | 52 +++++++++----- 12 files changed, 139 insertions(+), 87 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index 15f7e89e..4eb44ae4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -9,7 +9,7 @@ public interface IConversationService string ConversationId { get; } Task NewConversation(Conversation conversation); void SetConversationId(string conversationId, List states, bool isReadOnly = false); - Task GetConversation(string id); + Task GetConversation(string id, bool isLoadStates = false); Task> GetConversations(ConversationFilter filter); Task UpdateConversationTitle(string id, string title); Task UpdateConversationTitleAlias(string id, string titleAlias); diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs index d05c49c6..e5eed361 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs @@ -27,6 +27,8 @@ public class ConversationFilter public List? Tags { get; set; } + public bool IsLoadLatestStates { get; set; } + public static ConversationFilter Empty() { return new ConversationFilter(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 332ca340..b6b58a6b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -124,7 +124,7 @@ public interface IBotSharpRepository : IHaveServiceProvider => throw new NotImplementedException(); void UpdateConversationStatus(string conversationId, string status) => throw new NotImplementedException(); - Conversation GetConversation(string conversationId) + Conversation GetConversation(string conversationId, bool isLoadStates = false) => throw new NotImplementedException(); PagedItems GetConversations(ConversationFilter filter) => throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 9fc97a72..20e6a7fd 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -71,7 +71,7 @@ public partial class ConversationService : IConversationService return db.UpdateConversationMessage(conversationId, request); } - public async Task GetConversation(string id) + public async Task GetConversation(string id, bool isLoadStates = false) { var db = _services.GetRequiredService(); var conversation = db.GetConversation(id); @@ -80,6 +80,11 @@ public partial class ConversationService : IConversationService public async Task> GetConversations(ConversationFilter filter) { + if (filter == null) + { + filter = ConversationFilter.Empty(); + } + var db = _services.GetRequiredService(); var conversations = db.GetConversations(filter); return conversations; diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index 5d19e2d0..37c8c760 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -69,23 +69,34 @@ public class ConversationStateService : IConversationStateService return this; } + var defaultRound = -1; var preValue = string.Empty; var currentValue = value.ToString(); - var hooks = _services.GetServices(); - var curActiveRounds = activeRounds > 0 ? activeRounds : -1; - int? preActiveRounds = null; + var curActive = true; + StateKeyValue? pair = null; + StateValue? prevLeafNode = null; + var curActiveRounds = activeRounds > 0 ? activeRounds : defaultRound; - if (ContainsState(name) && _curStates.TryGetValue(name, out var pair)) + if (ContainsState(name) && _curStates.TryGetValue(name, out pair)) { - var leafNode = pair?.Values?.LastOrDefault(); - preActiveRounds = leafNode?.ActiveRounds; - preValue = leafNode?.Data ?? string.Empty; + prevLeafNode = pair?.Values?.LastOrDefault(); + preValue = prevLeafNode?.Data ?? string.Empty; } _logger.LogInformation($"[STATE] {name} = {value}"); var routingCtx = _services.GetRequiredService(); - if (!ContainsState(name) || preValue != currentValue || preActiveRounds != curActiveRounds) + var isNoChange = ContainsState(name) + && preValue == currentValue + && prevLeafNode?.ActiveRounds == curActiveRounds + && curActiveRounds == defaultRound + && prevLeafNode?.Source == source + && prevLeafNode?.DataType == valueType + && prevLeafNode?.Active == curActive + && pair?.Readonly == readOnly; + + var hooks = _services.GetServices(); + if (!ContainsState(name) || preValue != currentValue || prevLeafNode?.ActiveRounds != curActiveRounds) { foreach (var hook in hooks) { @@ -95,7 +106,7 @@ public class ConversationStateService : IConversationStateService MessageId = routingCtx.MessageId, Name = name, BeforeValue = preValue, - BeforeActiveRounds = preActiveRounds, + BeforeActiveRounds = prevLeafNode?.ActiveRounds, AfterValue = currentValue, AfterActiveRounds = curActiveRounds, DataType = valueType, @@ -116,7 +127,7 @@ public class ConversationStateService : IConversationStateService { Data = currentValue, MessageId = routingCtx.MessageId, - Active = true, + Active = curActive, ActiveRounds = curActiveRounds, DataType = valueType, Source = source, @@ -128,6 +139,10 @@ public class ConversationStateService : IConversationStateService newPair.Values = new List { newValue }; _curStates[name] = newPair; } + else if (isNoChange) + { + // do nothing + } else { _curStates[name].Values.Add(newValue); @@ -415,14 +430,14 @@ public class ConversationStateService : IConversationStateService { var values = _curStates.Values.ToList(); var copy = JsonSerializer.Deserialize>(JsonSerializer.Serialize(values)); - return new ConversationState(copy ?? new()); + return new ConversationState(copy ?? []); } public void SetCurrentState(ConversationState state) { var values = _curStates.Values.ToList(); var copy = JsonSerializer.Deserialize>(JsonSerializer.Serialize(values)); - _curStates = new ConversationState(copy ?? new()); + _curStates = new ConversationState(copy ?? []); } public void ResetCurrentState() diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs index 587de372..492dcde3 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs @@ -80,7 +80,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository public bool DeleteConversations(IEnumerable conversationIds) => throw new NotImplementedException(); - public Conversation GetConversation(string conversationId) + public Conversation GetConversation(string conversationId, bool isLoadStates = false) => throw new NotImplementedException(); public PagedItems GetConversations(ConversationFilter filter) diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 647282a4..b8ff420e 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Loggers.Models; using BotSharp.Abstraction.Users.Models; +using System; using System.IO; namespace BotSharp.Core.Repository; @@ -346,7 +347,7 @@ public partial class FileRepository } } - public Conversation GetConversation(string conversationId) + public Conversation GetConversation(string conversationId, bool isLoadStates = false) { var convDir = FindConversationDirectory(conversationId); if (string.IsNullOrEmpty(convDir)) return null; @@ -361,18 +362,20 @@ public partial class FileRepository record.Dialogs = CollectDialogElements(dialogFile); } - var stateFile = Path.Combine(convDir, STATE_FILE); - if (record != null) + if (isLoadStates) { - var states = CollectConversationStates(stateFile); - var curStates = new Dictionary(); - states.ForEach(x => + var latestStateFile = Path.Combine(convDir, CONV_LATEST_STATE_FILE); + if (record != null && File.Exists(latestStateFile)) { - curStates[x.Key] = x.Values?.LastOrDefault()?.Data ?? string.Empty; - }); - record.States = curStates; + var stateJson = File.ReadAllText(latestStateFile); + var states = JsonSerializer.Deserialize>(stateJson, _options) ?? []; + record.States = states.ToDictionary(x => x.Key, x => + { + var elem = x.Value.RootElement.GetProperty("data"); + return elem.ValueKind != JsonValueKind.Null ? elem.ToString() : null; + }); + } } - return record; } @@ -508,6 +511,21 @@ public partial class FileRepository if (!matched) continue; + if (filter.IsLoadLatestStates) + { + var latestStateFile = Path.Combine(d, CONV_LATEST_STATE_FILE); + if (File.Exists(latestStateFile)) + { + var stateJson = File.ReadAllText(latestStateFile); + var states = JsonSerializer.Deserialize>(stateJson, _options) ?? []; + record.States = states.ToDictionary(x => x.Key, x => + { + var elem = x.Value.RootElement.GetProperty("data"); + return elem.ValueKind != JsonValueKind.Null ? elem.ToString() : null; + }); + } + } + records.Add(record); } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index aaa81694..ee403ff8 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Functions; -using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Templating; namespace BotSharp.Core.Routing; @@ -9,18 +8,20 @@ public partial class RoutingService public async Task InvokeFunction(string name, RoleDialogModel message) { var function = _services.GetServices().FirstOrDefault(x => x.Name == name); + + var isFillDummyContent = false; + var dummyFuncResponse = string.Empty; if (function == null) { - var executed = await InvokeDummyFunction(name, message); - if (executed) + dummyFuncResponse = await GetDummyFunctionOutput(name, message); + isFillDummyContent = !string.IsNullOrEmpty(dummyFuncResponse); + if (!isFillDummyContent) { - return true; + message.StopCompletion = true; + message.Content = $"Can't find function implementation of {name}."; + _logger.LogError(message.Content); + return false; } - - message.StopCompletion = true; - message.Content = $"Can't find function implementation of {name}."; - _logger.LogError(message.Content); - return false; } // Clone message @@ -34,7 +35,15 @@ public partial class RoutingService var progressService = _services.GetService(); // Before executing functions - clonedMessage.Indication = await function.GetIndication(message); + if (!isFillDummyContent) + { + clonedMessage.Indication = await function.GetIndication(message); + } + else + { + clonedMessage.Indication = "Running"; + } + if (progressService?.OnFunctionExecuting != null) { await progressService.OnFunctionExecuting(clonedMessage); @@ -49,7 +58,15 @@ public partial class RoutingService try { - result = await function.Execute(clonedMessage); + if (!isFillDummyContent) + { + result = await function.Execute(clonedMessage); + } + else + { + clonedMessage.Content = dummyFuncResponse; + result = true; + } // After functions have been executed foreach (var hook in hooks) @@ -97,11 +114,11 @@ public partial class RoutingService return result; } - private async Task InvokeDummyFunction(string functionName, RoleDialogModel message) + private async Task GetDummyFunctionOutput(string functionName, RoleDialogModel message) { if (string.IsNullOrEmpty(message.CurrentAgentId)) { - return false; + return null; } var agentService = _services.GetRequiredService(); @@ -109,25 +126,11 @@ public partial class RoutingService var found = agent?.Functions?.FirstOrDefault(x => x.Name == functionName); if (string.IsNullOrWhiteSpace(found?.Output)) { - return false; - } - - var clonedMessage = RoleDialogModel.From(message); - clonedMessage.FunctionName = functionName; - clonedMessage.Indication = "Running"; - - var hooks = _services - .GetRequiredService() - .HooksOrderByPriority; - - foreach (var hook in hooks) - { - await hook.OnFunctionExecuting(clonedMessage); + return null; } var render = _services.GetRequiredService(); var state = _services.GetRequiredService(); - var options = _services.GetRequiredService(); var dict = new Dictionary(); foreach (var item in state.GetStates()) @@ -136,14 +139,6 @@ public partial class RoutingService } var text = render.Render(found.Output, dict); - message.Content = text; - clonedMessage.Content = text; - - foreach (var hook in hooks) - { - await hook.OnFunctionExecuted(clonedMessage); - } - - return true; + return text; } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index f032f504..dc0f2b0f 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -138,7 +138,7 @@ public class ConversationController : ControllerBase } [HttpGet("/conversation/{conversationId}")] - public async Task GetConversation([FromRoute] string conversationId) + public async Task GetConversation([FromRoute] string conversationId, [FromQuery] bool isLoadStates = false) { var service = _services.GetRequiredService(); var userService = _services.GetRequiredService(); @@ -151,7 +151,8 @@ public class ConversationController : ControllerBase var filter = new ConversationFilter { Id = conversationId, - UserId = !isAdmin ? user.Id : null + UserId = !isAdmin ? user.Id : null, + IsLoadLatestStates = isLoadStates }; var conversations = await service.GetConversations(filter); if (conversations.Items.IsNullOrEmpty()) @@ -161,7 +162,6 @@ public class ConversationController : ControllerBase var result = ConversationViewModel.FromSession(conversations.Items.First()); var state = _services.GetRequiredService(); - result.States = state.Load(conversationId, isReadOnly: true); user = await userService.GetUser(result.User.Id); result.User = UserViewModel.FromUser(user); diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/View/ConversationViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/View/ConversationViewModel.cs index a660a525..686974b9 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/View/ConversationViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/View/ConversationViewModel.cs @@ -31,7 +31,7 @@ public class ConversationViewModel public string? TaskId { get; set; } public string Status { get; set; } - public Dictionary States { get; set; } + public Dictionary States { get; set; } = []; public List Tags { get; set; } = new(); @@ -55,7 +55,8 @@ public class ConversationViewModel Channel = sess.Channel, Status = sess.Status, TaskId = sess.TaskId, - Tags = sess.Tags ?? new(), + Tags = sess.Tags ?? [], + States = sess.States ?? [], CreatedTime = sess.CreatedTime, UpdatedTime = sess.UpdatedTime }; diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index 99b734e6..7d848eb0 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -106,6 +106,7 @@ public class ChatHubConversationHook : ConversationHookBase if (!AllowSendingMessage()) return; var conv = _services.GetRequiredService(); + var state = _services.GetRequiredService(); var json = JsonSerializer.Serialize(new ChatResponseModel() { ConversationId = conv.ConversationId, @@ -114,6 +115,7 @@ public class ChatHubConversationHook : ConversationHookBase Function = message.FunctionName, RichContent = message.SecondaryRichContent ?? message.RichContent, Data = message.Data, + States = state.GetStates(), Sender = new UserViewModel() { FirstName = "AI", diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index ebae075b..f0e89473 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -299,26 +299,25 @@ public partial class MongoRepository _dc.Conversations.UpdateOne(filter, update); } - public Conversation GetConversation(string conversationId) + public Conversation GetConversation(string conversationId, bool isLoadStates = false) { if (string.IsNullOrEmpty(conversationId)) return null; var filterConv = Builders.Filter.Eq(x => x.Id, conversationId); var filterDialog = Builders.Filter.Eq(x => x.ConversationId, conversationId); - var filterState = Builders.Filter.Eq(x => x.ConversationId, conversationId); var conv = _dc.Conversations.Find(filterConv).FirstOrDefault(); var dialog = _dc.ConversationDialogs.Find(filterDialog).FirstOrDefault(); - var states = _dc.ConversationStates.Find(filterState).FirstOrDefault(); if (conv == null) return null; var dialogElements = dialog?.Dialogs?.Select(x => DialogMongoElement.ToDomainElement(x))?.ToList() ?? new List(); - var curStates = new Dictionary(); - states.States.ForEach(x => + var curStates = conv.LatestStates?.ToDictionary(x => x.Key, x => { - curStates[x.Key] = x.Values?.LastOrDefault()?.Data ?? string.Empty; - }); + var jsonDoc = JsonDocument.Parse(x.Value.ToJson()); + var data = jsonDoc.RootElement.GetProperty("data"); + return data.ValueKind != JsonValueKind.Null ? data.ToString() : null; + }) ?? []; return new Conversation { @@ -456,19 +455,34 @@ public partial class MongoRepository var conversationDocs = _dc.Conversations.Find(filterDef).Sort(sortDef).Skip(pager.Offset).Limit(pager.Size).ToList(); var count = _dc.Conversations.CountDocuments(filterDef); - var conversations = conversationDocs.Select(x => new Conversation + var conversations = conversationDocs.Select(x => { - Id = x.Id.ToString(), - AgentId = x.AgentId.ToString(), - UserId = x.UserId.ToString(), - TaskId = x.TaskId, - Title = x.Title, - Channel = x.Channel, - Status = x.Status, - DialogCount = x.DialogCount, - Tags = x.Tags ?? new(), - CreatedTime = x.CreatedTime, - UpdatedTime = x.UpdatedTime + var states = new Dictionary(); + if (filter.IsLoadLatestStates) + { + states = x.LatestStates.ToDictionary(p => p.Key, p => + { + var jsonDoc = JsonDocument.Parse(p.Value.ToJson()); + var data = jsonDoc.RootElement.GetProperty("data"); + return data.ValueKind != JsonValueKind.Null ? data.ToString() : null; + }); + } + + return new Conversation + { + Id = x.Id.ToString(), + AgentId = x.AgentId.ToString(), + UserId = x.UserId.ToString(), + TaskId = x.TaskId, + Title = x.Title, + Channel = x.Channel, + Status = x.Status, + DialogCount = x.DialogCount, + Tags = x.Tags ?? [], + States = states, + CreatedTime = x.CreatedTime, + UpdatedTime = x.UpdatedTime + }; }).ToList(); return new PagedItems From bcadc7e940196eb434d85f6afe2bada682bf0b04 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 17 Mar 2025 14:13:31 -0500 Subject: [PATCH 03/12] add execute template utility --- .../Instructs/Models/ExecuteTemplateArgs.cs | 7 ++ .../BotSharp.Core/BotSharp.Core.csproj | 7 ++ .../Instructs/Functions/ExecuteTemplateFn.cs | 69 +++++++++++++++++++ .../Instructs/Hooks/InstructUtilityHook.cs | 17 +++++ .../Instructs/InsturctionPlugin.cs | 3 + .../Routing/Hooks/RoutingUtilityHook.cs | 6 +- .../util-instruct-execute_template.json | 14 ++++ .../util-instruct-execute_template.fn.liquid | 3 + 8 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Instructs/Models/ExecuteTemplateArgs.cs create mode 100644 src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs create mode 100644 src/Infrastructure/BotSharp.Core/Instructs/Hooks/InstructUtilityHook.cs create mode 100644 src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-instruct-execute_template.json create mode 100644 src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-instruct-execute_template.fn.liquid diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/ExecuteTemplateArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/ExecuteTemplateArgs.cs new file mode 100644 index 00000000..e3d2405e --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/ExecuteTemplateArgs.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Abstraction.Instructs.Models; + +public class ExecuteTemplateArgs +{ + [JsonPropertyName("template_name")] + public string? TemplateName { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 00e1f30a..ad6b4e19 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -90,6 +90,7 @@ + @@ -187,6 +188,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs b/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs new file mode 100644 index 00000000..b09019e3 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs @@ -0,0 +1,69 @@ +using BotSharp.Abstraction.Functions; +using BotSharp.Abstraction.Instructs.Models; + +namespace BotSharp.Core.Instructs.Functions; + +public class ExecuteTemplateFn : IFunctionCallback +{ + public string Name => "util-instruct-execute_template"; + + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public ExecuteTemplateFn( + IServiceProvider services, + ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task Execute(RoleDialogModel message) + { + var args = JsonSerializer.Deserialize(message.FunctionArgs); + if (string.IsNullOrEmpty(args.TemplateName)) + { + message.Content = $"Empty template name."; + return false; + } + + var agentService = _services.GetRequiredService(); + var agent = await agentService.GetAgent(message.CurrentAgentId); + var template = agent.Templates.FirstOrDefault(x => x.Name.IsEqualTo(args.TemplateName)); + + if (template == null) + { + message.Content = $"Cannot find template ({args.TemplateName}) in agent {agent.Name}"; + return false; + } + + var prompt = agentService.RenderedTemplate(agent, args.TemplateName); + var response = await GetAiResponse(agent, prompt); + message.Content = response; + return true; + } + + private async Task GetAiResponse(Agent agent, string text) + { + try + { + var completion = CompletionProvider.GetChatCompletion(_services, provider: agent.LlmConfig?.Provider, model: agent.LlmConfig?.Model); + var response = await completion.GetChatCompletions(new Agent() + { + Id = agent.Id + }, + new List + { + new(AgentRole.User, text) + }); + return response.Content; + } + catch (Exception ex) + { + var error = $"Error when getting agent {agent.Name} instruction response."; + _logger.LogWarning($"{error} {ex.Message}\r\n{ex.InnerException}"); + return error; + } + + } +} diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Hooks/InstructUtilityHook.cs b/src/Infrastructure/BotSharp.Core/Instructs/Hooks/InstructUtilityHook.cs new file mode 100644 index 00000000..e0c6d686 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Instructs/Hooks/InstructUtilityHook.cs @@ -0,0 +1,17 @@ +namespace BotSharp.Core.Instructs.Hooks; + +public class InstructUtilityHook : IAgentUtilityHook +{ + private static string PREFIX = "util-instruct-"; + private static string EXECUTE_TEMPLATE = $"{PREFIX}execute_template"; + + public void AddUtilities(List utilities) + { + utilities.Add(new AgentUtility + { + Name = "instruct.template", + Functions = [new($"{EXECUTE_TEMPLATE}")], + Templates = [new($"{EXECUTE_TEMPLATE}.fn")] + }); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InsturctionPlugin.cs b/src/Infrastructure/BotSharp.Core/Instructs/InsturctionPlugin.cs index c44cb874..7bde20e8 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/InsturctionPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/InsturctionPlugin.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Instructs.Settings; using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Settings; +using BotSharp.Core.Instructs.Hooks; using Microsoft.Extensions.Configuration; namespace BotSharp.Core.Instructs; @@ -18,6 +19,8 @@ public class InsturctionPlugin : IBotSharpPlugin var settingService = provider.GetRequiredService(); return settingService.Bind("Instruction"); }); + + services.AddScoped(); } public bool AttachMenu(List menu) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingUtilityHook.cs b/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingUtilityHook.cs index e6fedc05..de7a556b 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingUtilityHook.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingUtilityHook.cs @@ -8,13 +8,11 @@ public class RoutingUtilityHook : IAgentUtilityHook public void AddUtilities(List utilities) { - var utility = new AgentUtility + utilities.Add(new AgentUtility { Name = "routing.tools", Functions = [new($"{REDIRECT_TO_AGENT}"), new($"{FALLBACK_TO_ROUTER}")], Templates = [new($"{REDIRECT_TO_AGENT}.fn"), new($"{FALLBACK_TO_ROUTER}.fn")] - }; - - utilities.Add(utility); + }); } } diff --git a/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-instruct-execute_template.json b/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-instruct-execute_template.json new file mode 100644 index 00000000..52780e61 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-instruct-execute_template.json @@ -0,0 +1,14 @@ +{ + "name": "util-instruct-execute_template", + "description": "Select a specific template that can handle the user's request.", + "parameters": { + "type": "object", + "properties": { + "template_name": { + "type": "string", + "description": "The template name that is selected for handling the request." + } + }, + "required": [ "template_name" ] + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-instruct-execute_template.fn.liquid b/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-instruct-execute_template.fn.liquid new file mode 100644 index 00000000..78291d3b --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-instruct-execute_template.fn.liquid @@ -0,0 +1,3 @@ +please call function util-routing-execute_template if user wants to use a template to fulfill a specific task. +Please ensure the template is executed only once. +Please output the template response directly without changing anthything. \ No newline at end of file From fe638753155e25f551e1f9616ee105d71eab2322 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 17 Mar 2025 14:26:39 -0500 Subject: [PATCH 04/12] minor change --- .../templates/util-instruct-execute_template.fn.liquid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-instruct-execute_template.fn.liquid b/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-instruct-execute_template.fn.liquid index 78291d3b..aa33961d 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-instruct-execute_template.fn.liquid +++ b/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-instruct-execute_template.fn.liquid @@ -1,3 +1,3 @@ please call function util-routing-execute_template if user wants to use a template to fulfill a specific task. -Please ensure the template is executed only once. +Please ensure each template is executed only once. Please output the template response directly without changing anthything. \ No newline at end of file From f562152b78ba6baa309c8d587c2e62c857f7bbff Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 17 Mar 2025 17:02:24 -0500 Subject: [PATCH 05/12] add instruct hooks --- .../Instructs/Functions/ExecuteTemplateFn.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs b/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs index b09019e3..7ff892ed 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Functions; +using BotSharp.Abstraction.Instructs; using BotSharp.Abstraction.Instructs.Models; namespace BotSharp.Core.Instructs.Functions; @@ -23,7 +24,7 @@ public class ExecuteTemplateFn : IFunctionCallback var args = JsonSerializer.Deserialize(message.FunctionArgs); if (string.IsNullOrEmpty(args.TemplateName)) { - message.Content = $"Empty template name."; + message.Content = $"Invalid template name."; return false; } @@ -37,16 +38,18 @@ public class ExecuteTemplateFn : IFunctionCallback return false; } - var prompt = agentService.RenderedTemplate(agent, args.TemplateName); - var response = await GetAiResponse(agent, prompt); + var response = await GetAiResponse(agent, args.TemplateName); message.Content = response; return true; } - private async Task GetAiResponse(Agent agent, string text) + private async Task GetAiResponse(Agent agent, string templateName) { try { + var agentService = _services.GetRequiredService(); + var text = agentService.RenderedTemplate(agent, templateName); + var completion = CompletionProvider.GetChatCompletion(_services, provider: agent.LlmConfig?.Provider, model: agent.LlmConfig?.Model); var response = await completion.GetChatCompletions(new Agent() { @@ -56,6 +59,21 @@ public class ExecuteTemplateFn : IFunctionCallback { new(AgentRole.User, text) }); + + var hooks = _services.GetServices(); + foreach (var hook in hooks) + { + await hook.OnResponseGenerated(new InstructResponseModel + { + AgentId = agent.Id, + TemplateName = templateName, + Provider = completion.Provider, + Model = completion.Model, + UserMessage = text, + CompletionText = response.Content + }); + } + return response.Content; } catch (Exception ex) From cbd5185f510343141d46005e2e651f391594c53c Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 18 Mar 2025 23:32:06 -0500 Subject: [PATCH 06/12] minor change --- .../BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs b/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs index 7ff892ed..8b4763f8 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs @@ -63,6 +63,11 @@ public class ExecuteTemplateFn : IFunctionCallback var hooks = _services.GetServices(); foreach (var hook in hooks) { + if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agent.Id) + { + continue; + } + await hook.OnResponseGenerated(new InstructResponseModel { AgentId = agent.Id, From db013c46ba26e6c80cee2b84f6aedd3f70340a94 Mon Sep 17 00:00:00 2001 From: Gil Zhang Date: Fri, 21 Mar 2025 10:25:10 +0800 Subject: [PATCH 07/12] Fixed agent label bug --- .../Repository/MongoRepository.Agent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 90dd9cdf..f433ee77 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -35,7 +35,7 @@ public partial class MongoRepository UpdateAgentProfiles(agent.Id, agent.Profiles); break; case AgentField.Label: - UpdateAgentLabels(agent.Id, agent.Profiles); + UpdateAgentLabels(agent.Id, agent.Labels); break; case AgentField.RoutingRule: UpdateAgentRoutingRules(agent.Id, agent.RoutingRules); From 52e6c6a4b026e724f13bec7f688b4afa2e5dc50b Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 21 Mar 2025 15:00:53 -0500 Subject: [PATCH 08/12] refine instruction log settings --- .../Instructs/InstructHookBase.cs | 6 +++--- .../Instructs/Settings/InstructionSettings.cs | 8 +++++++- .../Loggers/Models/InstructionLogModel.cs | 1 + .../BotSharp.Logger/Hooks/InstructionLogHook.cs | 12 ++++++++++-- src/WebStarter/appsettings.json | 5 ++++- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs index f19cca5c..9209684c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs @@ -8,16 +8,16 @@ public class InstructHookBase : IInstructHook public virtual async Task BeforeCompletion(Agent agent, RoleDialogModel message) { - return; + await Task.CompletedTask; } public virtual async Task AfterCompletion(Agent agent, InstructResult result) { - return; + await Task.CompletedTask; } public virtual async Task OnResponseGenerated(InstructResponseModel response) { - return; + await Task.CompletedTask; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Settings/InstructionSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Settings/InstructionSettings.cs index d1f51348..aaa4092f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Settings/InstructionSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Settings/InstructionSettings.cs @@ -2,5 +2,11 @@ namespace BotSharp.Abstraction.Instructs.Settings; public class InstructionSettings { - public bool EnableLog { get; set; } + public InstructionLogSetting Logging { get; set; } = new(); } + +public class InstructionLogSetting +{ + public bool Enabled { get; set; } = true; + public List ExcludedAgentIds { get; set; } = []; +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/InstructionLogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/InstructionLogModel.cs index de843023..36f17274 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/InstructionLogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/InstructionLogModel.cs @@ -5,6 +5,7 @@ namespace BotSharp.Abstraction.Loggers.Models; public class InstructionLogModel { [JsonPropertyName("id")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string Id { get; set; } = default!; [JsonPropertyName("agent_id")] diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs index 30e1d066..8de3db70 100644 --- a/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs +++ b/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs @@ -26,7 +26,14 @@ public class InstructionLogHook : InstructHookBase public override async Task OnResponseGenerated(InstructResponseModel response) { var settings = _services.GetRequiredService(); - if (!settings.EnableLog || response == null) return; + if (response == null + || string.IsNullOrWhiteSpace(response.AgentId) + || settings == null + || !settings.Logging.Enabled + || settings.Logging.ExcludedAgentIds.Contains(response.AgentId)) + { + return; + } var db = _services.GetRequiredService(); var state = _services.GetRequiredService(); @@ -49,6 +56,7 @@ public class InstructionLogHook : InstructHookBase UserId = user?.Id } }); - return; + + await base.OnResponseGenerated(response); } } diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 9d8afd80..f3b27149 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -223,7 +223,10 @@ }, "Instruction": { - "EnableLog": true + "Logging": { + "Enabled": true, + "ExcludedAgentIds": [] + } }, "ChatHub": { From f0db503ff0e29ff726d58543212b46ed1265847f Mon Sep 17 00:00:00 2001 From: Visagan Guruparan <103048@smsassist.com> Date: Fri, 21 Mar 2025 15:23:37 -0500 Subject: [PATCH 09/12] Update default timeout for web driver --- .../Drivers/PlaywrightDriver/PlaywrightInstance.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs index c2c1e8a2..e9d90fbd 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs @@ -45,6 +45,7 @@ public class PlaywrightInstance : IDisposable public async Task InitContext(string ctxId, BrowserActionArgs args) { + var _webDriver = _services.GetRequiredService(); if (_contexts.ContainsKey(ctxId)) return _contexts[ctxId]; @@ -83,6 +84,7 @@ public class PlaywrightInstance : IDisposable // "--start-maximized" ] }); + _contexts[ctxId].SetDefaultTimeout(_webDriver.DefaultTimeout); } _pages[ctxId] = new List(); From 93ae5327cf177772ea65ce1ddd7113f6884629dc Mon Sep 17 00:00:00 2001 From: Visagan Guruparan <103048@smsassist.com> Date: Fri, 21 Mar 2025 15:33:52 -0500 Subject: [PATCH 10/12] Update webdriver settings to support defaultnavigationtimeout --- .../Browsing/Settings/WebBrowsingSettings.cs | 1 + .../Drivers/PlaywrightDriver/PlaywrightInstance.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Settings/WebBrowsingSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Settings/WebBrowsingSettings.cs index f876c449..aa983ad6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Settings/WebBrowsingSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Settings/WebBrowsingSettings.cs @@ -6,6 +6,7 @@ public class WebBrowsingSettings public bool Headless { get; set; } // Default timeout in milliseconds public float DefaultTimeout { get; set; } = 30000; + public float DefaultNavigationTimeout { get; set; } = 30000; public bool IsEnableScreenshot { get; set; } // Default wait time in seconds after page is opened public int DefaultWaitTime { get; set; } = 5; diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs index e9d90fbd..b698141d 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs @@ -85,6 +85,7 @@ public class PlaywrightInstance : IDisposable ] }); _contexts[ctxId].SetDefaultTimeout(_webDriver.DefaultTimeout); + _contexts[ctxId].SetDefaultNavigationTimeout(_webDriver.DefaultNavigationTimeout); } _pages[ctxId] = new List(); From ba12125a1eccc7103a23662d40e80ac42cf6469d Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 21 Mar 2025 17:32:29 -0500 Subject: [PATCH 11/12] add additional llm cost --- .../Conversations/Models/TokenStatsModel.cs | 1 + .../MLTasks/Settings/LlmModelSetting.cs | 10 ++++++++++ .../Conversations/Services/TokenStatistics.cs | 8 ++++++-- .../Providers/Chat/ChatCompletionProvider.cs | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs index 23388807..67c560a1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs @@ -6,6 +6,7 @@ public class TokenStatsModel public string Model { get; set; } public string Prompt { get; set; } public int PromptCount { get; set; } + public int CachedTokenCount { get; set; } public int CompletionCount { get; set; } public AgentLlmConfig LlmConfig { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs index a40415c7..2a68ade6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs @@ -62,12 +62,22 @@ public class LlmModelSetting /// public int Dimension { get; set; } + public LlmCost AdditionalCost { get; set; } = new(); + public override string ToString() { return $"[{Type}] {Name} {Endpoint}"; } } +public class LlmCost +{ + public float CachedPromptCost { get; set; } = 0f; + public float AudioPromptCost { get; set; } = 0f; + public float ReasoningCompletionCost { get; } = 0f; + public float AudioCompletionCost { get; } = 0f; +} + public enum LlmModelType { Text = 1, diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs index 0698c0cb..3fb9dc0e 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs @@ -41,9 +41,11 @@ public class TokenStatistics : ITokenStatistics var settingsService = _services.GetRequiredService(); var settings = settingsService.GetSetting(stats.Provider, _model); - var deltaPromptCost = stats.PromptCount / 1000f * settings.PromptCost; + var deltaPromptCost = (stats.PromptCount - stats.CachedTokenCount) / 1000f * settings.PromptCost; + var deltaCachedPromptCost = stats.CachedTokenCount / 1000f * (settings.AdditionalCost?.CachedPromptCost ?? 0f); var deltaCompletionCost = stats.CompletionCount / 1000f * settings.CompletionCost; - var deltaTotal = deltaPromptCost + deltaCompletionCost; + + var deltaTotal = deltaPromptCost + deltaCachedPromptCost + deltaCompletionCost; _promptCost += deltaPromptCost; _completionCost += deltaCompletionCost; @@ -53,6 +55,8 @@ public class TokenStatistics : ITokenStatistics stat.SetState("prompt_total", stats.PromptCount + inputCount, isNeedVersion: false, source: StateSource.Application); var outputCount = int.Parse(stat.GetState("completion_total", "0")); stat.SetState("completion_total", stats.CompletionCount + outputCount, isNeedVersion: false, source: StateSource.Application); + var cachedCount = int.Parse(stat.GetState("cached_token_total", "0")); + stat.SetState("cached_token_total", stats.CachedTokenCount + cachedCount, isNeedVersion: false, source: StateSource.Application); // Total cost var total_cost = float.Parse(stat.GetState("llm_total_cost", "0")); diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index 031fab9c..5550d7f0 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -84,6 +84,7 @@ public class ChatCompletionProvider : IChatCompletion Provider = Provider, Model = _model, PromptCount = response.Value?.Usage?.InputTokenCount ?? 0, + CachedTokenCount = response.Value?.Usage?.InputTokenDetails?.CachedTokenCount ?? 0, CompletionCount = response.Value?.Usage?.OutputTokenCount ?? 0 }); } From bdc1529686d7a906b5295067fd24b524e2d308a7 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 21 Mar 2025 17:34:00 -0500 Subject: [PATCH 12/12] minor change --- .../Conversations/Models/TokenStatsModel.cs | 2 +- .../Conversations/Services/TokenStatistics.cs | 8 ++++---- .../Providers/Chat/ChatCompletionProvider.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs index 67c560a1..ca55e385 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs @@ -6,7 +6,7 @@ public class TokenStatsModel public string Model { get; set; } public string Prompt { get; set; } public int PromptCount { get; set; } - public int CachedTokenCount { get; set; } + public int CachedPromptCount { get; set; } public int CompletionCount { get; set; } public AgentLlmConfig LlmConfig { get; set; } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs index 3fb9dc0e..5a0614ed 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs @@ -41,8 +41,8 @@ public class TokenStatistics : ITokenStatistics var settingsService = _services.GetRequiredService(); var settings = settingsService.GetSetting(stats.Provider, _model); - var deltaPromptCost = (stats.PromptCount - stats.CachedTokenCount) / 1000f * settings.PromptCost; - var deltaCachedPromptCost = stats.CachedTokenCount / 1000f * (settings.AdditionalCost?.CachedPromptCost ?? 0f); + var deltaPromptCost = (stats.PromptCount - stats.CachedPromptCount) / 1000f * settings.PromptCost; + var deltaCachedPromptCost = stats.CachedPromptCount / 1000f * (settings.AdditionalCost?.CachedPromptCost ?? 0f); var deltaCompletionCost = stats.CompletionCount / 1000f * settings.CompletionCost; var deltaTotal = deltaPromptCost + deltaCachedPromptCost + deltaCompletionCost; @@ -55,8 +55,8 @@ public class TokenStatistics : ITokenStatistics stat.SetState("prompt_total", stats.PromptCount + inputCount, isNeedVersion: false, source: StateSource.Application); var outputCount = int.Parse(stat.GetState("completion_total", "0")); stat.SetState("completion_total", stats.CompletionCount + outputCount, isNeedVersion: false, source: StateSource.Application); - var cachedCount = int.Parse(stat.GetState("cached_token_total", "0")); - stat.SetState("cached_token_total", stats.CachedTokenCount + cachedCount, isNeedVersion: false, source: StateSource.Application); + var cachedCount = int.Parse(stat.GetState("cached_prompt_total", "0")); + stat.SetState("cached_prompt_total", stats.CachedPromptCount + cachedCount, isNeedVersion: false, source: StateSource.Application); // Total cost var total_cost = float.Parse(stat.GetState("llm_total_cost", "0")); diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index 5550d7f0..7d6bdcea 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -84,7 +84,7 @@ public class ChatCompletionProvider : IChatCompletion Provider = Provider, Model = _model, PromptCount = response.Value?.Usage?.InputTokenCount ?? 0, - CachedTokenCount = response.Value?.Usage?.InputTokenDetails?.CachedTokenCount ?? 0, + CachedPromptCount = response.Value?.Usage?.InputTokenDetails?.CachedTokenCount ?? 0, CompletionCount = response.Value?.Usage?.OutputTokenCount ?? 0 }); }