// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 // // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! using Microsoft.SemanticKernel.ChatCompletion; namespace Admin.NET.Plugin.Ai.Service; /// /// 聊天补全核心服务 /// public class LLMChatCoreService : ITransient { private readonly ILogger _logger; private readonly IChatHistoryReducer _chatHistoryTruncationReducer; //截取器 // private readonly IChatHistoryReducer _chatHistorySummarizationReducer; //摘要器 private readonly ILLMFactory _llmFactory; private readonly SseChannelManager _sseChannelManager; //给sse服务发送消息 private readonly SseDeepThinkingChannelManager _sseDeepThinkingChannelManager; //给sse服务发送深度思考消息 private readonly ChatChannelManager _dbAction; //操作数据库 private readonly SqlSugarRepository _chatSummaryHistoryService; private readonly IOptions _llmOption; private readonly UserManager _userManager; private readonly SysCacheService _sysCacheService; public LLMChatCoreService(ILogger logger, ILLMFactory llmFactory, SseChannelManager sseChannelManager, SseDeepThinkingChannelManager sseDeepThinkingChannelManager, ChatChannelManager chatChannelManager, IOptions llmOption, SysCacheService sysCacheService, UserManager userManager, SqlSugarRepository chatSummaryHistoryService) { _sysCacheService = sysCacheService; _logger = logger; _llmFactory = llmFactory; _sseChannelManager = sseChannelManager; _sseDeepThinkingChannelManager = sseDeepThinkingChannelManager; _dbAction = chatChannelManager; _llmOption = llmOption; _userManager = userManager; _chatSummaryHistoryService = chatSummaryHistoryService; _chatHistoryTruncationReducer = new ChatHistoryTruncationReducer(_llmOption.Value.TargetCount, _llmOption.Value.ThresholdCount); } /// /// 聊天补全 /// /// /// /// public async Task ChatAsync(ChatInput input, CancellationToken cancellationToken) { var kernel = _llmFactory.CreateKernel(new LLMModelInput { ProductName = input.ProviderName, ModelId = input.ModelId }); if (input.UniqueToken == "add_new_chat") { return await NewChatAsync(input, cancellationToken, kernel); } else { return await ContinueChatAsync(input, cancellationToken, kernel); } } #region 新聊天 /// /// 新聊天 /// /// /// /// /// private async Task NewChatAsync(ChatInput input, CancellationToken cancellationToken, Kernel kernel) { if (input.DeepThinking) { return await DeepThinkingNewChatCore(input, kernel, cancellationToken); } else { return await NormalNewChatCore(input, kernel, cancellationToken); } } /// /// 深度思考新聊天 /// /// /// /// /// private async Task DeepThinkingNewChatCore(ChatInput input, Kernel kernel, CancellationToken cancellationToken) { ChatHistory chatHistory = []; #region 深度思考过程 var deepThinkingPrompt = $""" 你是一个逻辑严谨的AI助手,请在回答前,先梳理出完整的推理路径,用户的问题是: ``` {input.Message} ``` 请你一步步进行推理,不要给出最终答案,仅展示思考过程,适当加入emoji表达人类情感,**用第一人称描述**。 """; chatHistory.AddSystemMessage(deepThinkingPrompt); var chat = kernel.GetRequiredService(); var thinkingMessage = ""; var response = chat.GetStreamingChatMessageContentsAsync(chatHistory: chatHistory, kernel: kernel, cancellationToken: cancellationToken); var thinkBegin = true; //发送消息到SSE通道 await foreach (var chunk in response) { if (thinkBegin) { thinkBegin = false; await _sseDeepThinkingChannelManager.SendMessageAsync(_userManager.UserId, "[BEGIN]", cancellationToken); } thinkingMessage += chunk.Content ?? ""; var streamInput = chunk.Content ?? ""; streamInput = streamInput.Replace("\n", "\\x0A"); //发送消息到SSE通道 await _sseDeepThinkingChannelManager.SendMessageAsync(_userManager.UserId, streamInput, cancellationToken); } await _sseDeepThinkingChannelManager.SendMessageAsync(_userManager.UserId, "[DONE]", cancellationToken); #endregion 深度思考过程 //生成最终答案 return await NormalNewChatCore(input, kernel, cancellationToken, thinkingMessage); } /// /// 普通新聊天 /// /// /// /// /// /// private async Task NormalNewChatCore(ChatInput input, Kernel kernel, CancellationToken cancellationToken, string thinkingMessage = "") { ChatHistory chatHistory = []; chatHistory.AddSystemMessage(_llmOption.Value.InitSystemChatMessage == null || _llmOption.Value.InitSystemChatMessage == "" ? "你是一个AI助手,请根据用户的问题给出回答。" : _llmOption.Value.InitSystemChatMessage); if (thinkingMessage != "") { var chatPrompt = $""" 问题:{input.Message} 思考过程: {thinkingMessage} 请基于以上思考,得出结论,并输出最终答案。 """; chatHistory.AddUserMessage(chatPrompt); } else { chatHistory.AddUserMessage(input.Message); } var chat = kernel.GetRequiredService(); var message = ""; var response = chat.GetStreamingChatMessageContentsAsync(chatHistory: chatHistory, kernel: kernel, cancellationToken: cancellationToken); var beginStream = true; //发送消息到SSE通道 await foreach (var chunk in response) { if (beginStream) { beginStream = false; await _sseChannelManager.SendMessageAsync(_userManager.UserId, "[BEGIN]", cancellationToken); } message += chunk.Content ?? ""; var streamInput = chunk.Content ?? ""; streamInput = streamInput.Replace("\n", "\\x0A"); //发送消息到SSE通道 await _sseChannelManager.SendMessageAsync(_userManager.UserId, streamInput, cancellationToken); } await _sseChannelManager.SendMessageAsync(_userManager.UserId, "[DONE]", cancellationToken); //保存聊天记录 return await SaveAddData(input, message, cancellationToken); } private async Task SaveAddData(ChatInput input, string message, CancellationToken cancellationToken) { ChatHistory chatHistory = []; chatHistory.AddSystemMessage(_llmOption.Value.InitSystemChatMessage == null || _llmOption.Value.InitSystemChatMessage == "" ? "你是一个AI助手,请根据用户的问题给出回答。" : _llmOption.Value.InitSystemChatMessage); chatHistory.AddUserMessage(input.Message); chatHistory.AddAssistantMessage(message); return await _AddToDatabase(chatHistory, cancellationToken); } private async Task _AddToDatabase(ChatHistory chatHistory, CancellationToken cancellationToken) { var token = Guid.NewGuid().ToString("N"); LLMChatSummaryHistory chatSummaryHistory = new LLMChatSummaryHistory { UserId = _userManager.UserId, UniqueToken = token, Summary = "New Chat", UtcCreateTime = DateTime.UtcNow.ToLong(), Histories = chatHistory.Select(x => new LLMChatHistory { UserId = _userManager.UserId, Content = x.Content, UtcCreateTime = DateTime.UtcNow.ToLong(), Role = x.Role.ToString(), }).ToList(), }; DataActionInput dataActionInput = new DataActionInput { ActionType = ChatActionEnum.Append, Item = chatSummaryHistory }; await _dbAction.ActionWriter.WriteAsync(dataActionInput); return new ChatOutput { UniqueToken = token, Note = "New Chat", State = true, Summary = "New Chat" }; } #endregion 新聊天 #region 续聊 /// /// 续聊 /// /// /// /// /// private async Task ContinueChatAsync(ChatInput input, CancellationToken cancellationToken, Kernel kernel) { var uniqueToken = input.UniqueToken; var chatHistory = await _GetChatHistory(uniqueToken, input.Message, input.DeepThinking); if (input.DeepThinking) { return await DeepThinkingContinueChatCoreAsync(input, kernel, uniqueToken, chatHistory, cancellationToken); } else { return await NormalContinueChatCoreAsync(input, kernel, uniqueToken, chatHistory, cancellationToken); } } /// /// 深度思考续聊 /// /// /// /// /// /// /// private async Task DeepThinkingContinueChatCoreAsync(ChatInput input, Kernel kernel, string uniqueToken, ChatHistory chatHistory, CancellationToken cancellationToken) { #region 深度思考过程 var deepThinkChatHistory = new ChatHistory(chatHistory); var deepThinkingPrompt = $""" 你是一个逻辑严谨的AI助手,请在回答前,先梳理出完整的推理路径,请你一步步进行推理,不要给出最终答案,仅展示思考过程,适当加入emoji表达人类情感,**用第一人称描述**。 """; deepThinkChatHistory.AddSystemMessage(deepThinkingPrompt); deepThinkChatHistory.AddUserMessage(input.Message); var chat = kernel.GetRequiredService(); var thinkingMessage = ""; var thinkBegin = true; var response = chat.GetStreamingChatMessageContentsAsync(chatHistory: deepThinkChatHistory, kernel: kernel, cancellationToken: cancellationToken); await foreach (var chunk in response) { if (thinkBegin) { thinkBegin = false; await _sseDeepThinkingChannelManager.SendMessageAsync(_userManager.UserId, "[BEGIN]", cancellationToken); } thinkingMessage += chunk.Content ?? ""; var streamInput = chunk.Content ?? ""; streamInput = streamInput.Replace("\n", "\\x0A"); //发送消息到SSE通道 await _sseDeepThinkingChannelManager.SendMessageAsync(_userManager.UserId, streamInput, cancellationToken); } await _sseDeepThinkingChannelManager.SendMessageAsync(_userManager.UserId, "[DONE]", cancellationToken); #endregion 深度思考过程 //生成最终答案 return await NormalContinueChatCoreAsync(input, kernel, uniqueToken, chatHistory, cancellationToken, thinkingMessage); } /// /// 普通续聊 /// /// /// /// /// /// /// /// private async Task NormalContinueChatCoreAsync(ChatInput input, Kernel kernel, string uniqueToken, ChatHistory chatHistory, CancellationToken cancellationToken, string thinkingMessage = "") { ChatHistory updateChatHistory = []; updateChatHistory.AddUserMessage(input.Message); if (thinkingMessage != "") { var deepThinkingPrompot = $""" 问题:{input.Message} 思考过程: {thinkingMessage} 请基于以上思考,得出结论,并输出最终答案。 """; chatHistory.AddUserMessage(deepThinkingPrompot); } var chat = kernel.GetRequiredService(); var message = ""; var beginStream = true; await foreach (var chunk in chat.GetStreamingChatMessageContentsAsync(chatHistory: chatHistory, kernel: kernel, cancellationToken: cancellationToken)) { if (beginStream) { beginStream = false; await _sseChannelManager.SendMessageAsync(_userManager.UserId, "[BEGIN]", cancellationToken); } message += chunk.Content ?? ""; var streamInput = chunk.Content ?? ""; streamInput = streamInput.Replace("\n", "\\x0A"); //发送消息到SSE通道 await _sseChannelManager.SendMessageAsync(_userManager.UserId, streamInput, cancellationToken); } await _sseChannelManager.SendMessageAsync(_userManager.UserId, "[DONE]", cancellationToken); updateChatHistory.AddAssistantMessage(message); return await SaveUpdate(input, kernel, uniqueToken, chatHistory, updateChatHistory, message, cancellationToken); } private async Task SaveUpdate(ChatInput input, Kernel kernel, string uniqueToken, ChatHistory chatHistory, ChatHistory updateChatHistory, string message, CancellationToken cancellationToken) { chatHistory.AddAssistantMessage(message); await _SaveChatItemToDatabase(updateChatHistory, uniqueToken); var summary = await _ProcessSummary(chatHistory, cancellationToken, kernel); await _CheckSummaryId(input); await _UpdateSummaryToDatabase(summary, input); return new ChatOutput { UniqueToken = uniqueToken, Note = "continue chat", Summary = summary == "" ? input.Summary : summary, State = true, SummaryId = input.SummaryId }; } /// /// 检查摘要ID,因为前端记录的是临时摘要ID,所以需要检查 /// /// /// private async Task _CheckSummaryId(ChatInput input) { var chatSummaryHistory = await _chatSummaryHistoryService.AsQueryable().Where(u => u.UniqueToken == input.UniqueToken).FirstAsync(); if (chatSummaryHistory == null) { throw new Exception("聊天摘要记录不存在"); } input.SummaryId = chatSummaryHistory.Id; } private async Task _UpdateSummaryToDatabase(string summary, ChatInput input) { //更新摘要至数据库 if (summary != "" && input.SummaryId != 0) { await _dbAction.ActionWriter.WriteAsync(new DataActionInput { ActionType = ChatActionEnum.RenameSummary, Item = new LLMChatSummaryHistory { Id = input.SummaryId, Summary = summary == "" ? input.Summary : summary } }); } } /// /// 处理摘要 /// /// /// /// /// private async Task _ProcessSummary(ChatHistory chatHistory, CancellationToken cancellationToken, Kernel kernel) { var filterChatHistory = chatHistory.Where(x => x.Role != AuthorRole.System).ToList(); //如果聊天记录为第二轮或第六轮,则自动进行摘要. if (filterChatHistory.Count != 4 && filterChatHistory.Count != 12) { return ""; } var summaryPrompt = "你是摘要专家,请根据历史对话内容,生成一个不超过13个字的简洁标题摘要,不要添加解释说明,只输出摘要内容本身,生成风格像 ChatGPT 聊天记录列表中显示的摘要。"; filterChatHistory.Add(new ChatMessageContent(AuthorRole.User, summaryPrompt)); var chat = kernel.GetRequiredService(); var history = new ChatHistory(filterChatHistory); var result = await chat.GetChatMessageContentAsync(chatHistory: history, kernel: kernel, cancellationToken: cancellationToken ); return result.Content ?? ""; } /// /// 获取聊天历史,并截取 /// /// /// /// /// private async Task _GetChatHistory(string uniqueToken, string message, bool deepThinking = false) { var chatSummaryHistory = await _chatSummaryHistoryService.AsQueryable().Includes(u => u.Histories).Where(u => u.UniqueToken == uniqueToken).FirstAsync(); if (chatSummaryHistory == null) { throw new Exception("聊天记录不存在"); } ChatHistory chatHistory = []; foreach (var history in chatSummaryHistory.Histories) { switch (history.Role) { case "user": chatHistory.AddUserMessage(history.Content); break; case "assistant": chatHistory.AddAssistantMessage(history.Content); break; case "system": chatHistory.AddSystemMessage(history.Content); break; default: break; } } var reducedMessages = await _chatHistoryTruncationReducer.ReduceAsync(chatHistory); if (reducedMessages is not null) { chatHistory = new ChatHistory(reducedMessages); } if (!deepThinking) { chatHistory.AddUserMessage(message); } return chatHistory; } private async Task _SaveChatItemToDatabase(ChatHistory chatHistory, string uniqueToken) { var chatSummaryHistory = await _chatSummaryHistoryService.AsQueryable().Where(u => u.UniqueToken == uniqueToken).FirstAsync(); if (chatSummaryHistory == null) { throw new Exception("聊天摘要记录不存在"); } chatSummaryHistory.Histories = []; foreach (var history in chatHistory) { chatSummaryHistory.Histories.Add(new LLMChatHistory { UserId = _userManager.UserId, Content = history.Content, UtcCreateTime = DateTime.UtcNow.ToLong(), Role = history.Role.ToString(), SummaryId = chatSummaryHistory.Id }); } DataActionInput dataActionInput = new DataActionInput { ActionType = ChatActionEnum.AppendItem, Item = chatSummaryHistory }; await _dbAction.ActionWriter.WriteAsync(dataActionInput); } #endregion 续聊 #region 删除所属摘要的所有聊天记录 /// /// 删除所属摘要的所有聊天记录 /// /// /// public async Task DeleteSummaryAllAsync(ChatInput input) { var chatSummaryHistory = await _chatSummaryHistoryService.AsQueryable().Where(u => u.Id == input.SummaryId).FirstAsync(); if (chatSummaryHistory == null) { throw new Exception("聊天摘要记录不存在"); } DataActionInput dataActionInput = new DataActionInput { ActionType = ChatActionEnum.DeleteAll, Item = chatSummaryHistory }; await _dbAction.ActionWriter.WriteAsync(dataActionInput); return true; } #endregion 删除所属摘要的所有聊天记录 #region 重命名所属摘要的标签 /// /// 重命名所属摘要的标签 /// /// /// public async Task RenameSummaryLable(ChatInput input) { var chatSummaryHistory = await _chatSummaryHistoryService.AsQueryable().Where(u => u.Id == input.SummaryId).FirstAsync(); if (chatSummaryHistory == null) { throw new Exception("聊天摘要记录不存在"); } chatSummaryHistory.Summary = input.Summary; DataActionInput dataActionInput = new DataActionInput { ActionType = ChatActionEnum.RenameSummary, Item = chatSummaryHistory }; await _dbAction.ActionWriter.WriteAsync(dataActionInput); return true; } #endregion 重命名所属摘要的标签 }