Browse Source
feat(ai): Automatically generate a conversation name for the first dialogue.pull/1467/head
committed by
GitHub
15 changed files with 290 additions and 53 deletions
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.Chats; |
|||
public abstract class ChatMessageRecordEto : EntityEto<Guid>, IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
public string Workspace { get; set; } |
|||
public Guid? ConversationId { get; set; } |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
namespace LINGYUN.Abp.AIManagement.Chats; |
|||
public class TextChatMessageRecordEto : ChatMessageRecordEto |
|||
{ |
|||
} |
|||
@ -0,0 +1,127 @@ |
|||
using LINGYUN.Abp.AI; |
|||
using LINGYUN.Abp.AIManagement.Localization; |
|||
using LINGYUN.Abp.AIManagement.Tokens; |
|||
using Microsoft.Extensions.AI; |
|||
using Microsoft.Extensions.Localization; |
|||
using System.Globalization; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.DistributedLocking; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Guids; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Specifications; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.Chats; |
|||
public class ConversationChangeNameHandler : |
|||
IDistributedEventHandler<EntityCreatedEto<TextChatMessageRecordEto>>, |
|||
ITransientDependency |
|||
{ |
|||
private readonly IGuidGenerator _guidGenerator; |
|||
private readonly IAbpDistributedLock _distributedLock; |
|||
private readonly IChatClientFactory _chatClientFactory; |
|||
private readonly IStringLocalizer<AIManagementResource> _stringLocalizer; |
|||
private readonly ITokenUsageRecordRepository _tokenUsageRecordRepository; |
|||
private readonly IConversationRecordRepository _conversationRecordRepository; |
|||
private readonly ITextChatMessageRecordRepository _textChatMessageRecordRepository; |
|||
|
|||
public ConversationChangeNameHandler( |
|||
IGuidGenerator guidGenerator, |
|||
IAbpDistributedLock distributedLock, |
|||
IChatClientFactory chatClientFactory, |
|||
IStringLocalizer<AIManagementResource> stringLocalizer, |
|||
ITokenUsageRecordRepository tokenUsageRecordRepository, |
|||
IConversationRecordRepository conversationRecordRepository, |
|||
ITextChatMessageRecordRepository textChatMessageRecordRepository) |
|||
{ |
|||
_guidGenerator = guidGenerator; |
|||
_distributedLock = distributedLock; |
|||
_chatClientFactory = chatClientFactory; |
|||
_stringLocalizer = stringLocalizer; |
|||
_tokenUsageRecordRepository = tokenUsageRecordRepository; |
|||
_conversationRecordRepository = conversationRecordRepository; |
|||
_textChatMessageRecordRepository = textChatMessageRecordRepository; |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public async virtual Task HandleEventAsync(EntityCreatedEto<TextChatMessageRecordEto> eventData) |
|||
{ |
|||
/* |
|||
* 业务逻辑: 当用户第一次向智能体发送消息时,系统根据用户消息创建一个额外的智能体 |
|||
* 通过消息摘要生成一个简短有效的问题描述,用作本次对话的名称, 设计灵感: Deepseek |
|||
*/ |
|||
|
|||
if (!eventData.Entity.ConversationId.HasValue) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
await using var lockHandle = await _distributedLock.TryAcquireAsync($"{nameof(ConversationChangeNameHandler)}_{eventData.Entity.ConversationId}_DesignConversationName"); |
|||
|
|||
if (lockHandle == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var conversation = await _conversationRecordRepository.FindAsync(eventData.Entity.ConversationId.Value); |
|||
if (conversation == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var specification = new ExpressionSpecification<TextChatMessageRecord>( |
|||
x => x.ConversationId == eventData.Entity.ConversationId && x.Role == ChatRole.User); |
|||
|
|||
var historyChatMessages = await _textChatMessageRecordRepository.GetListAsync( |
|||
specification: specification, |
|||
sorting: nameof(TextChatMessageRecord.CreationTime), |
|||
maxResultCount: 2); |
|||
|
|||
if (historyChatMessages.Count > 1) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var chatMessage = historyChatMessages[0]; |
|||
var currentCulture = chatMessage.GetProperty(nameof(CultureInfo.CurrentCulture), CultureInfo.CurrentCulture.Name); |
|||
|
|||
using (CultureHelper.Use(currentCulture!)) |
|||
{ |
|||
var chatClient = await _chatClientFactory.CreateAsync(chatMessage.Workspace); |
|||
|
|||
var aiAgent = chatClient |
|||
.AsBuilder() |
|||
.ConfigureOptions(options => |
|||
{ |
|||
// 不受工具影响
|
|||
options.Tools = []; |
|||
}) |
|||
.BuildAIAgent(_stringLocalizer["DesignConversationNamePrompt"].Value); |
|||
|
|||
var agentRunRes = await aiAgent.RunAsync(chatMessage.Content); |
|||
|
|||
conversation.SetName(agentRunRes.Text); |
|||
|
|||
await _conversationRecordRepository.UpdateAsync(conversation); |
|||
|
|||
if (agentRunRes.Usage != null) |
|||
{ |
|||
var tokenUsageRecord = new TokenUsageRecord( |
|||
_guidGenerator.Create(), |
|||
chatMessage.Id, |
|||
conversation.Id, |
|||
agentRunRes.Usage.InputTokenCount, |
|||
agentRunRes.Usage.OutputTokenCount, |
|||
agentRunRes.Usage.TotalTokenCount, |
|||
agentRunRes.Usage.CachedInputTokenCount, |
|||
agentRunRes.Usage.ReasoningTokenCount, |
|||
chatMessage.TenantId); |
|||
|
|||
await _tokenUsageRecordRepository.InsertAsync(tokenUsageRecord); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue