Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public interface IConversationService
Task<Conversation> UpdateConversationTitle(string id, string title);
Task<List<Conversation>> GetLastConversations();
Task<bool> DeleteConversation(string id);
Task<bool> TruncateConversation(string conversationId, string messageId);

/// <summary>
/// Send message to LLM
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.Conversations.Models;

public class TruncateMessageRequest
{
public string? TruncateMessageId { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace BotSharp.Abstraction.Models;

public class MessageConfig
public class MessageConfig : TruncateMessageRequest
{
/// <summary>
/// Completion Provider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public interface IBotSharpRepository
PagedItems<Conversation> GetConversations(ConversationFilter filter);
void UpdateConversationTitle(string conversationId, string title);
List<Conversation> GetLastConversations();
bool TruncateConversation(string conversationId, string messageId);
#endregion

#region Execution Log
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using BotSharp.Abstraction.Repositories;

namespace BotSharp.Core.Conversations.Services;

public partial class ConversationService : IConversationService
{
public async Task<bool> TruncateConversation(string conversationId, string messageId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var isSaved = db.TruncateConversation(conversationId, messageId);
return await Task.FromResult(isSaved);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ public void UpdateConversationStatus(string conversationId, string status)
{
throw new NotImplementedException();
}

public bool TruncateConversation(string conversationId, string messageId)
{
throw new NotImplementedException();
}
#endregion


Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Repositories.Models;
using System.Globalization;
using System.IO;

namespace BotSharp.Core.Repository
Expand Down Expand Up @@ -256,6 +257,35 @@ public List<Conversation> GetLastConversations()
}


public bool TruncateConversation(string conversationId, string messageId)
{
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) return false;

var dialogs = new List<DialogElement>();
var convDir = FindConversationDirectory(conversationId);
if (string.IsNullOrEmpty(convDir)) return false;

var dialogDir = Path.Combine(convDir, DIALOG_FILE);
dialogs = CollectDialogElements(dialogDir);
if (dialogs.IsNullOrEmpty()) return false;

var foundIdx = dialogs.FindIndex(x => x.MetaData?.MessageId == messageId);
if (foundIdx < 0) return false;

// Handle truncated dialogs
var isSaved = HandleTruncatedDialogs(dialogDir, dialogs, foundIdx);
if (!isSaved) return false;

// Handle truncated states
var refTime = dialogs.ElementAt(foundIdx).MetaData.CreateTime;
var stateDir = Path.Combine(convDir, STATE_FILE);
var states = CollectConversationStates(stateDir);
isSaved = HandleTruncatedStates(stateDir, states, refTime);

return isSaved;
}


#region Private methods
private string? FindConversationDirectory(string conversationId)
{
Expand Down Expand Up @@ -304,8 +334,9 @@ private List<string> ParseDialogElements(List<DialogElement> dialogs)
foreach (var element in dialogs)
{
var meta = element.MetaData;
var createTime = meta.CreateTime.ToString("MM/dd/yyyy hh:mm:ss.fff tt", CultureInfo.InvariantCulture);
var source = meta.FunctionName ?? meta.SenderId;
var metaStr = $"{meta.CreateTime}|{meta.Role}|{meta.AgentId}|{meta.MessageId}|{source}";
var metaStr = $"{createTime}|{meta.Role}|{meta.AgentId}|{meta.MessageId}|{source}";
dialogTexts.Add(metaStr);
var content = $" - {element.Content}";
dialogTexts.Add(content);
Expand All @@ -325,6 +356,49 @@ private List<StateKeyValue> CollectConversationStates(string stateFile)
states = JsonSerializer.Deserialize<List<StateKeyValue>>(stateStr, _options);
return states ?? new List<StateKeyValue>();
}

private bool HandleTruncatedDialogs(string dialogDir, List<DialogElement> dialogs, int foundIdx)
{
var truncatedDialogs = dialogs.Where((x, idx) => idx < foundIdx).ToList();
var isSaved = SaveTruncatedDialogs(dialogDir, truncatedDialogs);
return isSaved;
}

private bool HandleTruncatedStates(string stateDir, List<StateKeyValue> states, DateTime refTime)
{
var truncatedStates = new List<StateKeyValue>();
foreach (var state in states)
{
var values = state.Values.Where(x => x.UpdateTime < refTime).ToList();
if (values.Count == 0) continue;

state.Values = values;
truncatedStates.Add(state);
}

var isSaved = SaveTruncatedStates(stateDir, truncatedStates);
return isSaved;
}

private bool SaveTruncatedDialogs(string dialogDir, List<DialogElement> dialogs)
{
if (string.IsNullOrEmpty(dialogDir) || dialogs == null) return false;
if (!File.Exists(dialogDir)) File.Create(dialogDir);

var texts = ParseDialogElements(dialogs);
File.WriteAllLines(dialogDir, texts);
return true;
}

private bool SaveTruncatedStates(string stateDir, List<StateKeyValue> states)
{
if (string.IsNullOrEmpty(stateDir) || states == null) return false;
if (!File.Exists(stateDir)) File.Create(stateDir);

var stateStr = JsonSerializer.Serialize(states, _options);
File.WriteAllText(stateDir, stateStr);
return true;
}
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,26 @@ public async Task<bool> DeleteConversation([FromRoute] string conversationId)
return response;
}

[HttpDelete("/conversation/{conversationId}/message/{messageId}")]
public async Task<bool> DeleteConversationMessage([FromRoute] string conversationId, [FromRoute] string messageId)
{
var conversationService = _services.GetRequiredService<IConversationService>();
var response = await conversationService.TruncateConversation(conversationId, messageId);
return response;
}

[HttpPost("/conversation/{agentId}/{conversationId}")]
public async Task<ChatResponseModel> SendMessage([FromRoute] string agentId,
[FromRoute] string conversationId,
[FromBody] NewMessageModel input)
{
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text);

var conv = _services.GetRequiredService<IConversationService>();
if (!string.IsNullOrEmpty(input.TruncateMessageId))
{
await conv.TruncateConversation(conversationId, input.TruncateMessageId);
}

var inputMsg = new RoleDialogModel(AgentRole.User, input.Text);
conv.SetConversationId(conversationId, input.States);
conv.States.SetState("channel", input.Channel)
.SetState("provider", input.Provider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,42 @@ public List<Conversation> GetLastConversations()
UpdatedTime = c.UpdatedTime
}).ToList();
}

public bool TruncateConversation(string conversationId, string messageId)
{
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) return false;

var dialogFilter = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var foundDialog = _dc.ConversationDialogs.Find(dialogFilter).FirstOrDefault();
if (foundDialog == null || foundDialog.Dialogs.IsNullOrEmpty()) return false;

var foundIdx = foundDialog.Dialogs.FindIndex(x => x.MetaData?.MessageId == messageId);
if (foundIdx < 0) return false;

// Handle truncated dialogs
var truncatedDialogs = foundDialog.Dialogs.Where((x, idx) => idx < foundIdx).ToList();

// Handle truncated states
var refTime = foundDialog.Dialogs.ElementAt(foundIdx).MetaData.CreateTime;
var stateFilter = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var foundStates = _dc.ConversationStates.Find(stateFilter).FirstOrDefault();
if (foundStates == null || foundStates.States.IsNullOrEmpty()) return false;

var truncatedStates = new List<StateMongoElement>();
foreach (var state in foundStates.States)
{
var values = state.Values.Where(x => x.UpdateTime < refTime).ToList();
if (values.Count == 0) continue;

state.Values = values;
truncatedStates.Add(state);
}

// Save
foundDialog.Dialogs = truncatedDialogs;
foundStates.States = truncatedStates;
_dc.ConversationDialogs.ReplaceOne(dialogFilter, foundDialog);
_dc.ConversationStates.ReplaceOne(stateFilter, foundStates);
return true;
}
}