18 changed files with 347 additions and 5 deletions
@ -1,5 +1,7 @@ |
|||||
{ |
{ |
||||
"culture": "en", |
"culture": "en", |
||||
"texts": { |
"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", |
"culture": "zh-Hans", |
||||
"texts": { |
"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