32 changed files with 423 additions and 347 deletions
@ -0,0 +1,11 @@ |
|||
using LINGYUN.Abp.AI.Models; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.AI.Chats; |
|||
public interface IChatMessageStore |
|||
{ |
|||
Task<string> SaveMessageAsync(ChatMessage message); |
|||
|
|||
Task<IEnumerable<ChatMessage>> GetHistoryMessagesAsync(string conversationId); |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
using LINGYUN.Abp.AI.Models; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.AI.Chats; |
|||
|
|||
[Dependency(ServiceLifetime.Singleton, TryRegister = true)] |
|||
public class InMemoryChatMessageStore : IChatMessageStore |
|||
{ |
|||
private static readonly ConcurrentDictionary<string, List<ChatMessage>> _userMessageCache = new ConcurrentDictionary<string, List<ChatMessage>>(); |
|||
|
|||
public Task<IEnumerable<ChatMessage>> GetHistoryMessagesAsync(string conversationId) |
|||
{ |
|||
var messages = new List<ChatMessage>(); |
|||
|
|||
foreach (var userMessages in _userMessageCache.Values) |
|||
{ |
|||
messages.AddRange(userMessages.Where(x => x.ConversationId == conversationId)); |
|||
} |
|||
|
|||
return Task.FromResult<IEnumerable<ChatMessage>>( |
|||
messages |
|||
.OrderByDescending(x => x.CreatedAt) |
|||
.Take(5) |
|||
.OrderBy(x => x.CreatedAt)); |
|||
} |
|||
|
|||
public Task<string> SaveMessageAsync(ChatMessage message) |
|||
{ |
|||
var messageId = message.Id; |
|||
if (messageId.IsNullOrWhiteSpace()) |
|||
{ |
|||
messageId = Guid.NewGuid().ToString(); |
|||
message.WithMessageId(messageId); |
|||
} |
|||
if (_userMessageCache.ContainsKey(messageId)) |
|||
{ |
|||
_userMessageCache[messageId].Add(message); |
|||
} |
|||
else |
|||
{ |
|||
_userMessageCache[messageId] = new List<ChatMessage>() { message }; |
|||
} |
|||
|
|||
return Task.FromResult(messageId); |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
using LINGYUN.Abp.AI.Models; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.AI.Messages; |
|||
public interface IUserMessageStore |
|||
{ |
|||
Task<string> SaveMessageAsync(UserMessage message); |
|||
|
|||
Task<IEnumerable<UserMessage>> GetHistoryMessagesAsync(string conversationId); |
|||
} |
|||
@ -1,46 +0,0 @@ |
|||
using LINGYUN.Abp.AI.Models; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.AI.Messages; |
|||
|
|||
[Dependency(ServiceLifetime.Singleton, TryRegister = true)] |
|||
public class InMemoryUserMessageStore : IUserMessageStore |
|||
{ |
|||
private static readonly ConcurrentDictionary<string, List<UserMessage>> _userMessageCache = new ConcurrentDictionary<string, List<UserMessage>>(); |
|||
|
|||
public Task<IEnumerable<UserMessage>> GetHistoryMessagesAsync(string conversationId) |
|||
{ |
|||
if (_userMessageCache.TryGetValue(conversationId, out var messages)) |
|||
{ |
|||
return Task.FromResult(messages.Take(5)); |
|||
} |
|||
|
|||
return Task.FromResult<IEnumerable<UserMessage>>(Array.Empty<UserMessage>()); |
|||
} |
|||
|
|||
public Task<string> SaveMessageAsync(UserMessage message) |
|||
{ |
|||
var messageId = message.Id; |
|||
if (messageId.IsNullOrWhiteSpace()) |
|||
{ |
|||
messageId = Guid.NewGuid().ToString(); |
|||
message.WithMessageId(messageId); |
|||
} |
|||
if (_userMessageCache.ContainsKey(messageId)) |
|||
{ |
|||
_userMessageCache[messageId].Add(message); |
|||
} |
|||
else |
|||
{ |
|||
_userMessageCache[messageId] = new List<UserMessage>() { message }; |
|||
} |
|||
|
|||
return Task.FromResult(messageId); |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
using Microsoft.Extensions.AI; |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.AI.Models; |
|||
public abstract class ChatMessage |
|||
{ |
|||
public string Workspace { get; } |
|||
|
|||
public string? Id { get; private set; } |
|||
|
|||
public string? ConversationId { get; private set; } |
|||
|
|||
public string? ReplyMessage { get; private set; } |
|||
|
|||
public DateTime? ReplyAt { get; private set; } |
|||
|
|||
public ChatRole Role { get; private set; } |
|||
|
|||
public DateTime CreatedAt { get; private set; } |
|||
protected ChatMessage( |
|||
string workspace, |
|||
ChatRole? role = null, |
|||
DateTime? createdAt = null) |
|||
{ |
|||
Workspace = workspace; |
|||
Role = role ?? ChatRole.User; |
|||
CreatedAt = createdAt ?? DateTime.Now; |
|||
} |
|||
|
|||
public virtual ChatMessage WithMessageId(string id) |
|||
{ |
|||
Id = id; |
|||
return this; |
|||
} |
|||
|
|||
public virtual ChatMessage WithConversationId(string conversationId) |
|||
{ |
|||
ConversationId = conversationId; |
|||
return this; |
|||
} |
|||
|
|||
public virtual ChatMessage WithReply(string replyMessage, DateTime replyAt) |
|||
{ |
|||
ReplyMessage = replyMessage; |
|||
ReplyAt = replyAt; |
|||
return this; |
|||
} |
|||
|
|||
public virtual string GetMessagePrompt() |
|||
{ |
|||
return string.Empty; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using Microsoft.Extensions.AI; |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.AI.Models; |
|||
public class TextChatMessage : ChatMessage |
|||
{ |
|||
/// <summary>
|
|||
/// 消息内容
|
|||
/// </summary>
|
|||
public string Content { get; } |
|||
public TextChatMessage( |
|||
string workspace, |
|||
string content, |
|||
ChatRole? role = null, |
|||
DateTime? createdAt = null) |
|||
: base(workspace, role, createdAt) |
|||
{ |
|||
Content = content; |
|||
} |
|||
|
|||
public override string GetMessagePrompt() |
|||
{ |
|||
return Content; |
|||
} |
|||
} |
|||
@ -1,53 +0,0 @@ |
|||
namespace LINGYUN.Abp.AI.Models; |
|||
public abstract class UserMessage |
|||
{ |
|||
/// <summary>
|
|||
/// 工作区
|
|||
/// </summary>
|
|||
public string Workspace { get; } |
|||
/// <summary>
|
|||
/// 消息Id
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 在持久化设施处更新
|
|||
/// </remarks>
|
|||
public string? Id { get; private set; } |
|||
/// <summary>
|
|||
/// 对话Id
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 用于从客户端存储中持久化和检索聊天历史的唯一标识符,如果未指定则与AI对话时无上下文关联
|
|||
/// </remarks>
|
|||
public string? ConversationId { get; private set; } |
|||
/// <summary>
|
|||
/// AI回复消息
|
|||
/// </summary>
|
|||
public string ReplyMessage { get; private set; } |
|||
protected UserMessage(string workspace) |
|||
{ |
|||
Workspace = workspace; |
|||
} |
|||
|
|||
public virtual UserMessage WithMessageId(string id) |
|||
{ |
|||
Id = id; |
|||
return this; |
|||
} |
|||
|
|||
public virtual UserMessage WithConversationId(string conversationId) |
|||
{ |
|||
ConversationId = conversationId; |
|||
return this; |
|||
} |
|||
|
|||
public virtual UserMessage WithReply(string replyMessage) |
|||
{ |
|||
ReplyMessage = replyMessage; |
|||
return this; |
|||
} |
|||
|
|||
public virtual string GetMessagePrompt() |
|||
{ |
|||
return string.Empty; |
|||
} |
|||
} |
|||
@ -1,20 +0,0 @@ |
|||
namespace LINGYUN.Abp.AI.Models; |
|||
public class UserTextMessage : UserMessage |
|||
{ |
|||
/// <summary>
|
|||
/// 消息内容
|
|||
/// </summary>
|
|||
public string Content { get; } |
|||
public UserTextMessage( |
|||
string workspace, |
|||
string content) |
|||
: base(workspace) |
|||
{ |
|||
Content = content; |
|||
} |
|||
|
|||
public override string GetMessagePrompt() |
|||
{ |
|||
return Content; |
|||
} |
|||
} |
|||
@ -1,9 +1,8 @@ |
|||
using LINGYUN.Abp.AI.Models; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.AI.Tokens; |
|||
public interface ITokenUsageStore |
|||
{ |
|||
Task SaveTokenUsagesAsync(IEnumerable<TokenUsageInfo> usageInfos); |
|||
Task SaveTokenUsageAsync(TokenUsageInfo tokenUsageInfo); |
|||
} |
|||
|
|||
@ -0,0 +1,6 @@ |
|||
namespace LINGYUN.Abp.AIManagement.Chats; |
|||
public static class ChatMessageRecordConsts |
|||
{ |
|||
public static int MaxConversationIdLength { get; set; } = 64; |
|||
public static int MaxChatRoleLength { get; set; } = 20; |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
namespace LINGYUN.Abp.AIManagement.Chats; |
|||
public static class TextChatMessageRecordConsts |
|||
{ |
|||
public static int MaxContentLength { get; set; } = 1024; |
|||
} |
|||
@ -1,5 +0,0 @@ |
|||
namespace LINGYUN.Abp.AIManagement.Messages; |
|||
public static class UserMessageRecordConsts |
|||
{ |
|||
public static int MaxConversationIdLength { get; set; } = 64; |
|||
} |
|||
@ -1,5 +0,0 @@ |
|||
namespace LINGYUN.Abp.AIManagement.Messages; |
|||
public static class UserTextMessageRecordConsts |
|||
{ |
|||
public static int MaxContentLength { get; set; } = 1024; |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
using Microsoft.Extensions.AI; |
|||
using System; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.Chats; |
|||
public abstract class ChatMessageRecord : AuditedAggregateRoot<Guid>, IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; private set; } |
|||
|
|||
public string Workspace { get; private set; } |
|||
|
|||
public ChatRole Role { get; private set; } |
|||
|
|||
public DateTime CreatedAt { get; private set; } |
|||
|
|||
public string? ConversationId { get; private set; } |
|||
|
|||
public string? ReplyMessage { get; private set; } |
|||
|
|||
public DateTime? ReplyAt { get; private set; } |
|||
|
|||
protected ChatMessageRecord() |
|||
{ |
|||
ExtraProperties = new ExtraPropertyDictionary(); |
|||
this.SetDefaultsForExtraProperties(); |
|||
} |
|||
|
|||
public ChatMessageRecord( |
|||
Guid id, |
|||
string workspace, |
|||
ChatRole role, |
|||
DateTime createdAt, |
|||
Guid? tenantId = null) |
|||
: base(id) |
|||
{ |
|||
Workspace = workspace; |
|||
Role = role; |
|||
CreatedAt = createdAt; |
|||
TenantId = tenantId; |
|||
} |
|||
|
|||
public virtual ChatMessageRecord SetConversationId(string conversationId) |
|||
{ |
|||
ConversationId = Check.NotNullOrWhiteSpace(conversationId, nameof(conversationId), ChatMessageRecordConsts.MaxConversationIdLength); |
|||
return this; |
|||
} |
|||
|
|||
public virtual ChatMessageRecord SetReply(string replyMessage, DateTime? replyAt) |
|||
{ |
|||
ReplyMessage = replyMessage; |
|||
ReplyAt = replyAt; |
|||
return this; |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using Microsoft.Extensions.AI; |
|||
using System; |
|||
using Volo.Abp; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.Chats; |
|||
public class TextChatMessageRecord : ChatMessageRecord |
|||
{ |
|||
public string Content { get; private set; } |
|||
|
|||
public TextChatMessageRecord() |
|||
{ |
|||
} |
|||
|
|||
public TextChatMessageRecord( |
|||
Guid id, |
|||
string workspace, |
|||
string content, |
|||
ChatRole role, |
|||
DateTime createdAt, |
|||
Guid? tenantId = null) |
|||
: base(id, workspace, role, createdAt, tenantId) |
|||
{ |
|||
SetContent(content); |
|||
} |
|||
|
|||
public virtual TextChatMessageRecord SetContent(string content) |
|||
{ |
|||
Content = Check.NotNullOrWhiteSpace(content, nameof(content), TextChatMessageRecordConsts.MaxContentLength); |
|||
return this; |
|||
} |
|||
} |
|||
@ -1,69 +0,0 @@ |
|||
using LINGYUN.Abp.AIManagement.Workspaces; |
|||
using System; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.Messages; |
|||
public abstract class UserMessageRecord : AuditedAggregateRoot<Guid>, IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; private set; } |
|||
|
|||
public string Workspace { get; private set; } |
|||
|
|||
public string? ConversationId { get; private set; } |
|||
|
|||
public string? ReplyMessage { get; private set; } |
|||
|
|||
protected UserMessageRecord() |
|||
{ |
|||
ExtraProperties = new ExtraPropertyDictionary(); |
|||
this.SetDefaultsForExtraProperties(); |
|||
} |
|||
|
|||
public UserMessageRecord( |
|||
Guid id, |
|||
string workspace, |
|||
Guid? tenantId = null) |
|||
: base(id) |
|||
{ |
|||
TenantId = tenantId; |
|||
Workspace = workspace; |
|||
} |
|||
|
|||
public virtual UserMessageRecord WithConversationId(string conversationId) |
|||
{ |
|||
ConversationId = Check.NotNullOrWhiteSpace(conversationId, nameof(conversationId), UserMessageRecordConsts.MaxConversationIdLength); |
|||
return this; |
|||
} |
|||
|
|||
public virtual UserMessageRecord WithReply(string replyMessage) |
|||
{ |
|||
ReplyMessage = replyMessage; |
|||
return this; |
|||
} |
|||
|
|||
public void Patch(UserMessageRecord otherMessage) |
|||
{ |
|||
if (ConversationId != otherMessage.ConversationId) |
|||
{ |
|||
ConversationId = otherMessage.ConversationId; |
|||
} |
|||
|
|||
if (ReplyMessage != otherMessage.ReplyMessage) |
|||
{ |
|||
ReplyMessage = otherMessage.ReplyMessage; |
|||
} |
|||
|
|||
if (!this.HasSameExtraProperties(otherMessage)) |
|||
{ |
|||
ExtraProperties.Clear(); |
|||
|
|||
foreach (var property in otherMessage.ExtraProperties) |
|||
{ |
|||
ExtraProperties.Add(property.Key, property.Value); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
using System; |
|||
using Volo.Abp; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.Messages; |
|||
public class UserTextMessageRecord : UserMessageRecord |
|||
{ |
|||
public string Content { get; private set; } |
|||
|
|||
public UserTextMessageRecord() |
|||
{ |
|||
} |
|||
|
|||
public UserTextMessageRecord( |
|||
Guid id, |
|||
string workspace, |
|||
string content, |
|||
Guid? tenantId = null) : base(id, workspace, tenantId) |
|||
{ |
|||
WithContent(content); |
|||
} |
|||
|
|||
public virtual UserTextMessageRecord WithContent(string content) |
|||
{ |
|||
Content = Check.NotNullOrWhiteSpace(content, nameof(content), UserTextMessageRecordConsts.MaxContentLength); |
|||
return this; |
|||
} |
|||
} |
|||
9
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/EfCoreUserTextMessageRecordRepository.cs → aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/EfCoreTextChatMessageRecordRepository.cs
9
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/EfCoreUserTextMessageRecordRepository.cs → aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.EntityFrameworkCore/LINGYUN/Abp/AIManagement/EntityFrameworkCore/EfCoreTextChatMessageRecordRepository.cs
@ -0,0 +1,11 @@ |
|||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; |
|||
using Microsoft.Extensions.AI; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.EntityFrameworkCore.ValueConversions; |
|||
public class ChatRoleValueConverter(ConverterMappingHints? mappingHints = null) : ValueConverter<ChatRole, string>( |
|||
value => value.Value, |
|||
value => new ChatRole(value), |
|||
mappingHints |
|||
) |
|||
{ |
|||
} |
|||
Loading…
Reference in new issue