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 LINGYUN.Abp.AI.Models; |
||||
using System.Collections.Generic; |
|
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
|
|
||||
namespace LINGYUN.Abp.AI.Tokens; |
namespace LINGYUN.Abp.AI.Tokens; |
||||
public interface ITokenUsageStore |
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