diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs index c6af0438..d4e88991 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs @@ -8,10 +8,10 @@ namespace BotSharp.Abstraction.Conversations; public interface IConversationStateService { string GetConversationId(); - ConversationState Load(string conversationId); + Dictionary Load(string conversationId); string GetState(string name, string defaultValue = ""); bool ContainsState(string name); - ConversationState GetStates(); + Dictionary GetStates(); IConversationStateService SetState(string name, T value, bool isNeedVersion = true); void SaveStateByArgs(JsonDocument args); void CleanState(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs index d98032fe..1903e913 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs @@ -13,7 +13,7 @@ public class Conversation public List Dialogs { get; set; } = new List(); [JsonIgnore] - public ConversationState States { get; set; } = new ConversationState(); + public Dictionary States { get; set; } = new Dictionary(); public string Status { get; set; } = ConversationStatus.Open; diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationHistoryState.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationHistoryState.cs deleted file mode 100644 index 2b8816a3..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationHistoryState.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace BotSharp.Abstraction.Conversations.Models; - -public class ConversationHistoryState : Dictionary> -{ - public ConversationHistoryState() - { - - } - - public ConversationHistoryState(List pairs) - { - foreach (var pair in pairs) - { - this[pair.Key] = pair.Values; - } - } -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationState.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationState.cs index f957f184..82cbcc02 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationState.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationState.cs @@ -1,22 +1,17 @@ namespace BotSharp.Abstraction.Conversations.Models; -public class ConversationState : Dictionary +public class ConversationState : Dictionary> { public ConversationState() { - + } public ConversationState(List pairs) { foreach (var pair in pairs) { - this[pair.Key] = pair.Value; + this[pair.Key] = pair.Values; } } - - public List ToKeyValueList() - { - return this.Select(x => new StateKeyValue(x.Key, x.Value)).ToList(); - } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/HistoryStateKeyValue.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/HistoryStateKeyValue.cs deleted file mode 100644 index e5368f42..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/HistoryStateKeyValue.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace BotSharp.Abstraction.Conversations.Models; - -public class HistoryStateKeyValue -{ - public string Key { get; set; } - public List Values { get; set; } = new List(); - - public HistoryStateKeyValue() - { - - } - - public HistoryStateKeyValue(string key, List values) - { - Key = key; - Values = values; - } -} - -public class HistoryStateValue -{ - public string Data { get; set; } - public DateTime UpdateTime { get; set; } - - public HistoryStateValue() - { - - } -} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateKeyValue.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateKeyValue.cs index ad02628f..3ad34ac0 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateKeyValue.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateKeyValue.cs @@ -3,16 +3,27 @@ namespace BotSharp.Abstraction.Conversations.Models; public class StateKeyValue { public string Key { get; set; } - public string Value { get; set; } + public List Values { get; set; } = new List(); public StateKeyValue() { } - public StateKeyValue(string key, string value) + public StateKeyValue(string key, List values) { Key = key; - Value = value; + Values = values; } } + +public class StateValue +{ + public string Data { get; set; } + public DateTime UpdateTime { get; set; } + + public StateValue() + { + + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructResult.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructResult.cs index 6790d866..34b9f76d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructResult.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructResult.cs @@ -6,5 +6,5 @@ public class InstructResult : ITrackableMessage public string MessageId { get; set; } public string Text { get; set; } public object Data { get; set; } - public ConversationState States { get; set; } + public Dictionary States { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 860de96b..1834dfd5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -33,8 +33,8 @@ public interface IBotSharpRepository List GetConversationDialogs(string conversationId); void UpdateConversationDialogElements(string conversationId, List updateElements); void AppendConversationDialogs(string conversationId, List dialogs); - List GetConversationStates(string conversationId); - void UpdateConversationStates(string conversationId, List states); + List GetConversationStates(string conversationId); + void UpdateConversationStates(string conversationId, List states); void UpdateConversationStatus(string conversationId, string status); Conversation GetConversation(string conversationId); List GetConversations(ConversationFilter filter); diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Records/ConversationRecord.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Records/ConversationRecord.cs index 7a2f7dde..5375859d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Records/ConversationRecord.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Records/ConversationRecord.cs @@ -1,5 +1,3 @@ -using System.Text.Json.Serialization; - namespace BotSharp.Abstraction.Repositories.Records; public class ConversationRecord : RecordBase @@ -18,9 +16,6 @@ public class ConversationRecord : RecordBase [JsonIgnore] public string Dialog { get; set; } - [JsonIgnore] - public List States { get; set; } - [Required] public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index fec0dcdb..a6c4642e 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Repositories; +using System.Linq; namespace BotSharp.Core.Conversations.Services; @@ -10,7 +11,6 @@ public class ConversationStateService : IConversationStateService, IDisposable private readonly ILogger _logger; private readonly IServiceProvider _services; private ConversationState _states; - private ConversationHistoryState _historyStates; private string _conversationId; private readonly IBotSharpRepository _db; @@ -22,7 +22,6 @@ public class ConversationStateService : IConversationStateService, IDisposable _services = services; _db = db; _states = new ConversationState(); - _historyStates = new ConversationHistoryState(); } public string GetConversationId() => _conversationId; @@ -43,51 +42,56 @@ public class ConversationStateService : IConversationStateService, IDisposable return this; } + var preValue = string.Empty; var currentValue = value.ToString(); var hooks = _services.GetServices(); - string preValue = _states.ContainsKey(name) ? _states[name] : ""; - if (!_states.ContainsKey(name) || _states[name] != currentValue) + if (_states.TryGetValue(name, out var values)) + { + preValue = values.LastOrDefault()?.Data ?? string.Empty; + } + + if (!_states.ContainsKey(name) || preValue != currentValue) { - _states[name] = currentValue; _logger.LogInformation($"[STATE] {name} = {value}"); foreach (var hook in hooks) { hook.OnStateChanged(name, preValue, currentValue).Wait(); } - var historyStateValue = new HistoryStateValue + var stateValue = new StateValue { Data = currentValue, UpdateTime = DateTime.UtcNow }; - if (!_historyStates.ContainsKey(name) || !isNeedVersion) + if (!_states.ContainsKey(name) || !isNeedVersion) { - _historyStates[name] = new List { historyStateValue }; + _states[name] = new List { stateValue }; } else { - _historyStates[name].Add(historyStateValue); + _states[name].Add(stateValue); } } return this; } - public ConversationState Load(string conversationId) + public Dictionary Load(string conversationId) { _conversationId = conversationId; var savedStates = _db.GetConversationStates(_conversationId).ToList(); - _historyStates = new ConversationHistoryState(savedStates); + _states = new ConversationState(savedStates); + var curStates = new Dictionary(); if (!savedStates.IsNullOrEmpty()) { foreach (var state in savedStates) { var value = state.Values.LastOrDefault()?.Data ?? string.Empty; - _states[state.Key] = value; + curStates[state.Key] = value; _logger.LogInformation($"[STATE] {state.Key} : {value}"); } } @@ -99,7 +103,7 @@ public class ConversationStateService : IConversationStateService, IDisposable hook.OnStateLoaded(_states).Wait(); } - return _states; + return curStates; } public void Save() @@ -109,32 +113,40 @@ public class ConversationStateService : IConversationStateService, IDisposable return; } - var historyStates = new List(); + var states = new List(); - foreach (var dic in _historyStates) + foreach (var dic in _states) { - historyStates.Add(new HistoryStateKeyValue(dic.Key, dic.Value)); + states.Add(new StateKeyValue(dic.Key, dic.Value)); } - _db.UpdateConversationStates(_conversationId, historyStates); + _db.UpdateConversationStates(_conversationId, states); _logger.LogInformation($"Saved states of conversation {_conversationId}"); } public void CleanState() { - + _states.Clear(); } - public ConversationState GetStates() => _states; + public Dictionary GetStates() + { + var curStates = new Dictionary(); + foreach (var state in _states) + { + curStates[state.Key] = state.Value.LastOrDefault()?.Data ?? string.Empty; + } + return curStates; + } public string GetState(string name, string defaultValue = "") { - if (!_states.ContainsKey(name)) + if (!_states.ContainsKey(name) || _states[name].IsNullOrEmpty()) { return defaultValue; } - return _states[name]; + return _states[name].Last().Data; } public void Dispose() @@ -144,7 +156,9 @@ public class ConversationStateService : IConversationStateService, IDisposable public bool ContainsState(string name) { - return _states.ContainsKey(name) && !string.IsNullOrEmpty(_states[name]); + return _states.ContainsKey(name) + && !_states[name].IsNullOrEmpty() + && !string.IsNullOrEmpty(_states[name].Last().Data); } public void SaveStateByArgs(JsonDocument args) diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs index 901440d5..ec8ac5f6 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs @@ -152,7 +152,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository throw new NotImplementedException(); } - public List GetConversationStates(string conversationId) + public List GetConversationStates(string conversationId) { throw new NotImplementedException(); } @@ -165,7 +165,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository { throw new NotImplementedException(); } - public void UpdateConversationStates(string conversationId, List states) + public void UpdateConversationStates(string conversationId, List states) { throw new NotImplementedException(); } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository.cs index aa364f34..030ac91a 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository.cs @@ -643,22 +643,31 @@ public class FileRepository : IBotSharpRepository Directory.CreateDirectory(dir); } - var convDir = Path.Combine(dir, CONVERSATION_FILE); - if (!File.Exists(convDir)) + var convFile = Path.Combine(dir, CONVERSATION_FILE); + if (!File.Exists(convFile)) { - File.WriteAllText(convDir, JsonSerializer.Serialize(conversation, _options)); + File.WriteAllText(convFile, JsonSerializer.Serialize(conversation, _options)); } - var dialogDir = Path.Combine(dir, DIALOG_FILE); - if (!File.Exists(dialogDir)) + var dialogFile = Path.Combine(dir, DIALOG_FILE); + if (!File.Exists(dialogFile)) { - File.WriteAllText(dialogDir, string.Empty); + File.WriteAllText(dialogFile, string.Empty); } - var stateDir = Path.Combine(dir, STATE_FILE); - if (!File.Exists(stateDir)) + var stateFile = Path.Combine(dir, STATE_FILE); + if (!File.Exists(stateFile)) { - File.WriteAllText(stateDir, "[]"); + var states = conversation.States ?? new Dictionary(); + var initialStates = states.Select(x => new StateKeyValue + { + Key = x.Key, + Values = new List + { + new StateValue { Data = x.Value, UpdateTime = DateTime.UtcNow } + } + }).ToList(); + File.WriteAllText(stateFile, JsonSerializer.Serialize(initialStates, _options)); } } @@ -725,9 +734,8 @@ public class FileRepository : IBotSharpRepository File.AppendAllLines(dialogDir, texts); } } - - return; } + public void UpdateConversationTitle(string conversationId, string title) { var convDir = FindConversationDirectory(conversationId); @@ -744,20 +752,21 @@ public class FileRepository : IBotSharpRepository } } } - public List GetConversationStates(string conversationId) + + public List GetConversationStates(string conversationId) { - var curStates = new List(); + var states = new List(); var convDir = FindConversationDirectory(conversationId); if (!string.IsNullOrEmpty(convDir)) { var stateFile = Path.Combine(convDir, STATE_FILE); - curStates = CollectConversationStates(stateFile); + states = CollectConversationStates(stateFile); } - return curStates; + return states; } - public void UpdateConversationStates(string conversationId, List states) + public void UpdateConversationStates(string conversationId, List states) { if (states.IsNullOrEmpty()) return; @@ -808,13 +817,13 @@ public class FileRepository : IBotSharpRepository var stateFile = Path.Combine(convDir, STATE_FILE); if (record != null) { - var historyStates = CollectConversationStates(stateFile); - var recentStates = historyStates.Select(x => new StateKeyValue + var states = CollectConversationStates(stateFile); + var curStates = new Dictionary(); + states.ForEach(x => { - Key = x.Key, - Value = x.Values.LastOrDefault()?.Data ?? string.Empty - }).ToList(); - record.States = new ConversationState(recentStates); + curStates[x.Key] = x.Values.LastOrDefault()?.Data ?? string.Empty; + }); + record.States = curStates; } return record; @@ -1099,16 +1108,16 @@ public class FileRepository : IBotSharpRepository return dialogTexts; } - private List CollectConversationStates(string stateFile) + private List CollectConversationStates(string stateFile) { - var states = new List(); + var states = new List(); if (!File.Exists(stateFile)) return states; var stateStr = File.ReadAllText(stateFile); if (string.IsNullOrEmpty(stateStr)) return states; - states = JsonSerializer.Deserialize>(stateStr, _options); - return states ?? new List(); + states = JsonSerializer.Deserialize>(stateStr, _options); + return states ?? new List(); } private int GetNextLlmCompletionLogIndex(string logDir, string id) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs index 36c48476..de1060dd 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs @@ -15,6 +15,13 @@ public class AgentController : ControllerBase _services = services; } + [HttpGet("/test")] + public async Task Test() + { + + } + + [HttpGet("/agent/settings")] public AgentSettings GetSettings() { diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs index 556991ab..a863763e 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs @@ -20,7 +20,7 @@ public class ConversationViewModel public string Channel { get; set; } = ConversationChannel.OpenAPI; public string Status { get; set; } - public ConversationState States { get; set; } + public Dictionary States { get; set; } [JsonPropertyName("updated_time")] public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/StateMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/StateMongoElement.cs index ee4228fd..03f0bda1 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/StateMongoElement.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/StateMongoElement.cs @@ -7,7 +7,7 @@ public class StateMongoElement public string Key { get; set; } public List Values { get; set; } - public static StateMongoElement ToMongoElement(HistoryStateKeyValue state) + public static StateMongoElement ToMongoElement(StateKeyValue state) { return new StateMongoElement { @@ -16,12 +16,12 @@ public class StateMongoElement }; } - public static HistoryStateKeyValue ToDomainElement(StateMongoElement state) + public static StateKeyValue ToDomainElement(StateMongoElement state) { - return new HistoryStateKeyValue + return new StateKeyValue { Key = state.Key, - Values = state.Values?.Select(x => StateValueMongoElement.ToDomainElement(x))?.ToList() ?? new List() + Values = state.Values?.Select(x => StateValueMongoElement.ToDomainElement(x))?.ToList() ?? new List() }; } } @@ -31,7 +31,7 @@ public class StateValueMongoElement public string Data { get; set; } public DateTime UpdateTime { get; set; } - public static StateValueMongoElement ToMongoElement(HistoryStateValue element) + public static StateValueMongoElement ToMongoElement(StateValue element) { return new StateValueMongoElement { @@ -40,9 +40,9 @@ public class StateValueMongoElement }; } - public static HistoryStateValue ToDomainElement(StateValueMongoElement element) + public static StateValue ToDomainElement(StateValueMongoElement element) { - return new HistoryStateValue + return new StateValue { Data = element.Data, UpdateTime = element.UpdateTime diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs index 649f9013..7c57ec43 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs @@ -617,7 +617,7 @@ public class MongoRepository : IBotSharpRepository { if (conversation == null) return; - var conv = new ConversationDocument + var convDoc = new ConversationDocument { Id = !string.IsNullOrEmpty(conversation.Id) ? conversation.Id : Guid.NewGuid().ToString(), AgentId = conversation.AgentId, @@ -629,23 +629,33 @@ public class MongoRepository : IBotSharpRepository UpdatedTime = DateTime.UtcNow, }; - var dialog = new ConversationDialogDocument + var dialogDoc = new ConversationDialogDocument { Id = Guid.NewGuid().ToString(), - ConversationId = conv.Id, + ConversationId = convDoc.Id, Dialogs = new List() }; - var states = new ConversationStateDocument + var states = conversation.States ?? new Dictionary(); + var initialStates = states.Select(x => new StateMongoElement + { + Key = x.Key, + Values = new List + { + new StateValueMongoElement { Data = x.Value, UpdateTime = DateTime.UtcNow } + } + }).ToList(); + + var stateDoc = new ConversationStateDocument { Id = Guid.NewGuid().ToString(), - ConversationId = conv.Id, - States = new List() + ConversationId = convDoc.Id, + States = initialStates }; - _dc.Conversations.InsertOne(conv); - _dc.ConversationDialogs.InsertOne(dialog); - _dc.ConversationStates.InsertOne(states); + _dc.Conversations.InsertOne(convDoc); + _dc.ConversationDialogs.InsertOne(dialogDoc); + _dc.ConversationStates.InsertOne(stateDoc); } public bool DeleteConversation(string conversationId) @@ -736,9 +746,9 @@ public class MongoRepository : IBotSharpRepository _dc.Conversations.UpdateOne(filterConv, updateConv); } - public List GetConversationStates(string conversationId) + public List GetConversationStates(string conversationId) { - var states = new List(); + var states = new List(); if (string.IsNullOrEmpty(conversationId)) return states; var filter = Builders.Filter.Eq(x => x.ConversationId, conversationId); @@ -749,7 +759,7 @@ public class MongoRepository : IBotSharpRepository return savedStates; } - public void UpdateConversationStates(string conversationId, List states) + public void UpdateConversationStates(string conversationId, List states) { if (string.IsNullOrEmpty(conversationId) || states.IsNullOrEmpty()) return; @@ -793,11 +803,11 @@ public class MongoRepository : IBotSharpRepository if (conv == null) return null; var dialogElements = dialog?.Dialogs?.Select(x => DialogMongoElement.ToDomainElement(x))?.ToList() ?? new List(); - var recentStates = states.States?.Select(x => new StateKeyValue + var curStates = new Dictionary(); + states.States.ForEach(x => { - Key = x.Key, - Value = x.Values.LastOrDefault()?.Data ?? string.Empty - })?.ToList() ?? new List(); + curStates[x.Key] = x.Values.LastOrDefault()?.Data ?? string.Empty; + }); return new Conversation { @@ -808,7 +818,7 @@ public class MongoRepository : IBotSharpRepository Channel = conv.Channel, Status = conv.Status, Dialogs = dialogElements, - States = new ConversationState(recentStates), + States = curStates, CreatedTime = conv.CreatedTime, UpdatedTime = conv.UpdatedTime };