18 changed files with 347 additions and 5 deletions
@ -1,5 +1,7 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"DisplayName:MaxLatestHistoryMessagesToKeep": "Carry the recent conversation records", |
|||
"Description:MaxLatestHistoryMessagesToKeep": "When a user initiates a conversation with a large model, the upper limit of the user's recent conversation history that is carried along." |
|||
} |
|||
} |
|||
@ -1,5 +1,7 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"DisplayName:MaxLatestHistoryMessagesToKeep": "携带最近对话记录", |
|||
"Description:MaxLatestHistoryMessagesToKeep": "用户发起与大模型对话时,携带用户最近对话记录的上限." |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
namespace LINGYUN.Abp.AIManagement.Messages; |
|||
public static class UserMessageRecordConsts |
|||
{ |
|||
public static int MaxConversationIdLength { get; set; } = 64; |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
namespace LINGYUN.Abp.AIManagement.Messages; |
|||
public static class UserTextMessageRecordConsts |
|||
{ |
|||
public static int MaxContentLength { get; set; } = 1024; |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using LINGYUN.Abp.AI.Models; |
|||
using LINGYUN.Abp.AIManagement.Messages; |
|||
using Riok.Mapperly.Abstractions; |
|||
using Volo.Abp.Mapperly; |
|||
using Volo.Abp.ObjectExtending; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement; |
|||
|
|||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] |
|||
[MapExtraProperties(DefinitionChecks = MappingPropertyDefinitionChecks.None)] |
|||
public partial class UserTextMessageRecordToUserTextMessageMapper : MapperBase<UserTextMessageRecord, UserTextMessage> |
|||
{ |
|||
public override partial UserTextMessage Map(UserTextMessageRecord source); |
|||
public override partial void Map(UserTextMessageRecord source, UserTextMessage destination); |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.Messages; |
|||
public interface IUserTextMessageRecordRepository : IBasicRepository<UserTextMessageRecord, Guid> |
|||
{ |
|||
Task<IEnumerable<UserTextMessageRecord>> GetHistoryMessagesAsync( |
|||
string conversationId, |
|||
int maxResultCount = 0, |
|||
CancellationToken cancellationToken = default); |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
using LINGYUN.Abp.AI.Messages; |
|||
using LINGYUN.Abp.AI.Models; |
|||
using LINGYUN.Abp.AIManagement.Settings; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Guids; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Volo.Abp.Settings; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.Messages; |
|||
|
|||
[Dependency(ReplaceServices = true)] |
|||
public class UserMessageStore : IUserMessageStore, ITransientDependency |
|||
{ |
|||
private readonly ICurrentTenant _currentTenant; |
|||
private readonly IGuidGenerator _guidGenerator; |
|||
private readonly ISettingProvider _settingProvider; |
|||
private readonly IObjectMapper<AbpAIManagementDomainModule> _objectMapper; |
|||
private readonly IUserTextMessageRecordRepository _messageRecordRepository; |
|||
|
|||
public UserMessageStore( |
|||
ICurrentTenant currentTenant, |
|||
IGuidGenerator guidGenerator, |
|||
ISettingProvider settingProvider, |
|||
IObjectMapper<AbpAIManagementDomainModule> objectMapper, |
|||
IUserTextMessageRecordRepository messageRecordRepository) |
|||
{ |
|||
_currentTenant = currentTenant; |
|||
_guidGenerator = guidGenerator; |
|||
_settingProvider = settingProvider; |
|||
_objectMapper = objectMapper; |
|||
_messageRecordRepository = messageRecordRepository; |
|||
} |
|||
|
|||
public async virtual Task<IEnumerable<UserMessage>> GetHistoryMessagesAsync(string conversationId) |
|||
{ |
|||
var maxLatestHistoryMessagesToKeep = await _settingProvider.GetAsync(AIManagementSettingNames.UserMessage.MaxLatestHistoryMessagesToKeep, 0); |
|||
if (maxLatestHistoryMessagesToKeep < 1) |
|||
{ |
|||
return Array.Empty<UserMessage>(); |
|||
} |
|||
|
|||
var userTextMessages = await _messageRecordRepository.GetHistoryMessagesAsync(conversationId, maxLatestHistoryMessagesToKeep); |
|||
|
|||
return _objectMapper.Map<IEnumerable<UserTextMessageRecord>, IEnumerable<UserTextMessage>>(userTextMessages); |
|||
} |
|||
|
|||
public async virtual Task<string> SaveMessageAsync(UserMessage message) |
|||
{ |
|||
var messageId = message.Id; |
|||
if (messageId.IsNullOrWhiteSpace()) |
|||
{ |
|||
messageId = _guidGenerator.Create().ToString(); |
|||
message.WithMessageId(messageId); |
|||
} |
|||
|
|||
await StoreMessageAsync(Guid.Parse(messageId), message); |
|||
|
|||
return messageId; |
|||
} |
|||
|
|||
protected async virtual Task StoreMessageAsync(Guid messageId, UserMessage message) |
|||
{ |
|||
switch (message) |
|||
{ |
|||
case UserTextMessage textMessage: |
|||
await StoreUserTextMessageAsync(messageId, textMessage); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
protected async virtual Task StoreUserTextMessageAsync(Guid messageId, UserTextMessage textMessage) |
|||
{ |
|||
var textMessageRecord = await _messageRecordRepository.FindAsync(messageId); |
|||
if (textMessageRecord == null) |
|||
{ |
|||
textMessageRecord = new UserTextMessageRecord( |
|||
messageId, |
|||
textMessage.Workspace, |
|||
textMessage.Content, |
|||
_currentTenant.Id); |
|||
|
|||
UpdateUserMessageRecord(textMessageRecord, textMessage); |
|||
|
|||
await _messageRecordRepository.InsertAsync(textMessageRecord); |
|||
} |
|||
else |
|||
{ |
|||
textMessageRecord.WithContent(textMessage.Content); |
|||
|
|||
UpdateUserMessageRecord(textMessageRecord, textMessage); |
|||
|
|||
await _messageRecordRepository.UpdateAsync(textMessageRecord); |
|||
} |
|||
} |
|||
|
|||
private static void UpdateUserMessageRecord(UserMessageRecord messageRecord, UserMessage message) |
|||
{ |
|||
if (!message.ConversationId.IsNullOrWhiteSpace()) |
|||
{ |
|||
messageRecord.WithConversationId(message.ConversationId); |
|||
} |
|||
if (!message.ReplyMessage.IsNullOrWhiteSpace()) |
|||
{ |
|||
messageRecord.WithConversationId(message.ReplyMessage); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
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; |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using LINGYUN.Abp.AIManagement.Localization; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Settings; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.Settings; |
|||
public class AIManagementSettingDefinitionProvider : SettingDefinitionProvider |
|||
{ |
|||
public override void Define(ISettingDefinitionContext context) |
|||
{ |
|||
context.Add( |
|||
new SettingDefinition( |
|||
AIManagementSettingNames.UserMessage.MaxLatestHistoryMessagesToKeep, |
|||
defaultValue: "5", |
|||
displayName: L("DisplayName:MaxLatestHistoryMessagesToKeep"), |
|||
description: L("Description:MaxLatestHistoryMessagesToKeep"))); |
|||
} |
|||
|
|||
private static ILocalizableString L(string name) |
|||
{ |
|||
return LocalizableString.Create<AIManagementResource>(name); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace LINGYUN.Abp.AIManagement.Settings; |
|||
public static class AIManagementSettingNames |
|||
{ |
|||
public const string Prefix = "Abp.AIManagement"; |
|||
|
|||
public static class UserMessage |
|||
{ |
|||
public const string MaxLatestHistoryMessagesToKeep = Prefix + ".MaxLatestHistoryMessagesToKeep"; |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using LINGYUN.Abp.AIManagement.Messages; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.EntityFrameworkCore; |
|||
public class EfCoreUserTextMessageRecordRepository : EfCoreRepository<IAIManagementDbContext, UserTextMessageRecord, Guid>, IUserTextMessageRecordRepository |
|||
{ |
|||
public EfCoreUserTextMessageRecordRepository( |
|||
IDbContextProvider<IAIManagementDbContext> dbContextProvider) |
|||
: base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public async virtual Task<IEnumerable<UserTextMessageRecord>> GetHistoryMessagesAsync( |
|||
string conversationId, |
|||
int maxResultCount = 0, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
return await (await GetQueryableAsync()) |
|||
.Where(x => x.ConversationId == conversationId) |
|||
.OrderByDescending(x => x.CreationTime) |
|||
.Take(maxResultCount) |
|||
.ToListAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue