From 2544c01e9d3a67f0c9aa1a0142e47cd04214fa3b Mon Sep 17 00:00:00 2001 From: ccnetcore Date: Thu, 8 Jan 2026 23:46:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=94=A8=E9=87=8F?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E7=BA=BF=E7=A8=8B=E9=97=AE=E9=A2=98=E5=B9=B6?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=90=9C=E7=B4=A2=E4=B8=8EToken=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OnlineSearch 增加 daysAgo 非法值保护,避免无效时间范围 - 修复 UsageStatistics 中 Prompt/Completion Token 为 0 时的统计异常 - 引入独立 UnitOfWork,解决流式处理下的并发与事务问题 - 确保用量统计、系统消息与尊享包扣减的原子性 - 补充前端 Element Plus 组件类型声明 - 统一并优化部分代码格式,不影响业务逻辑 --- .../Managers/ChatManager.cs | 110 ++++++++++-------- .../Managers/UsageStatisticsManager.cs | 12 +- .../Mcp/OnlineSearchTool.cs | 53 ++++----- Yi.Ai.Vue3/types/components.d.ts | 3 + 4 files changed, 92 insertions(+), 86 deletions(-) diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs index 6a7b54d5..9c398486 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs @@ -8,17 +8,15 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using ModelContextProtocol.Server; using OpenAI; using OpenAI.Chat; using Volo.Abp.Domain.Services; +using Volo.Abp.Uow; using Yi.Framework.AiHub.Application.Contracts.Dtos.Chat; using Yi.Framework.AiHub.Domain.AiGateWay; using Yi.Framework.AiHub.Domain.Entities.Chat; using Yi.Framework.AiHub.Domain.Entities.Model; -using Yi.Framework.AiHub.Domain.Entities.OpenApi; using Yi.Framework.AiHub.Domain.Shared.Attributes; -using Yi.Framework.AiHub.Domain.Shared.Consts; using Yi.Framework.AiHub.Domain.Shared.Dtos; using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi; using Yi.Framework.AiHub.Domain.Shared.Enums; @@ -36,12 +34,14 @@ public class ChatManager : DomainService private readonly PremiumPackageManager _premiumPackageManager; private readonly AiGateWayManager _aiGateWayManager; private readonly ISqlSugarRepository _aiModelRepository; + private readonly IUnitOfWorkManager _unitOfWorkManager; public ChatManager(ILoggerFactory loggerFactory, ISqlSugarRepository messageRepository, ISqlSugarRepository agentStoreRepository, AiMessageManager aiMessageManager, UsageStatisticsManager usageStatisticsManager, PremiumPackageManager premiumPackageManager, - AiGateWayManager aiGateWayManager, ISqlSugarRepository aiModelRepository) + AiGateWayManager aiGateWayManager, ISqlSugarRepository aiModelRepository, + IUnitOfWorkManager unitOfWorkManager) { _loggerFactory = loggerFactory; _messageRepository = messageRepository; @@ -51,6 +51,7 @@ public class ChatManager : DomainService _premiumPackageManager = premiumPackageManager; _aiGateWayManager = aiGateWayManager; _aiModelRepository = aiModelRepository; + _unitOfWorkManager = unitOfWorkManager; } /// @@ -189,53 +190,59 @@ public class ChatManager : DomainService //用量统计 case UsageContent usageContent: - var usage = new ThorUsageResponse + //由于MAF线程问题 + using (var uow = _unitOfWorkManager.Begin(requiresNew: true)) { - InputTokens = Convert.ToInt32(usageContent.Details.InputTokenCount ?? 0), - OutputTokens = Convert.ToInt32(usageContent.Details.OutputTokenCount ?? 0), - TotalTokens = usageContent.Details.TotalTokenCount ?? 0, - }; - //设置倍率 - usage.SetSupplementalMultiplier(modelDescribe.Multiplier); - - //创建系统回答,用于计费统计 - await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId, new MessageInputDto - { - Content = "不与存储", - ModelId = modelId, - TokenUsage = usage - }, tokenId); - - //创建用量统计,用于统计分析 - await _usageStatisticsManager.SetUsageAsync(userId, modelId, usage, tokenId); - - //扣减尊享token包用量 - var isPremium = await _aiModelRepository._DbQueryable - .Where(x => x.ModelId == modelId) - .Select(x => x.IsPremium) - .FirstAsync(); - - if (isPremium) - { - var totalTokens = usage?.TotalTokens ?? 0; - if (totalTokens > 0) + var usage = new ThorUsageResponse { - await _premiumPackageManager.TryConsumeTokensAsync(userId, totalTokens); + InputTokens = Convert.ToInt32(usageContent.Details.InputTokenCount ?? 0), + OutputTokens = Convert.ToInt32(usageContent.Details.OutputTokenCount ?? 0), + TotalTokens = usageContent.Details.TotalTokenCount ?? 0, + }; + //设置倍率 + usage.SetSupplementalMultiplier(modelDescribe.Multiplier); + + //创建系统回答,用于计费统计 + await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId, new MessageInputDto + { + Content = "不与存储", + ModelId = modelId, + TokenUsage = usage + }, tokenId); + + //创建用量统计,用于统计分析 + await _usageStatisticsManager.SetUsageAsync(userId, modelId, usage, tokenId); + + //扣减尊享token包用量 + var isPremium = await _aiModelRepository._DbQueryable + .Where(x => x.ModelId == modelId) + .Select(x => x.IsPremium) + .FirstAsync(); + + if (isPremium) + { + var totalTokens = usage?.TotalTokens ?? 0; + if (totalTokens > 0) + { + await _premiumPackageManager.TryConsumeTokensAsync(userId, totalTokens); + } } - } - await SendHttpStreamMessageAsync(httpContext, - new AgentResultOutput - { - TypeEnum = update.RawRepresentation is ChatResponseUpdate raw - ? raw.FinishReason?.Value == "tool_calls" - ? AgentResultTypeEnum.ToolCallUsage - : AgentResultTypeEnum.Usage - : AgentResultTypeEnum.Usage, - Content = usage! - }, - isDone: false, cancellationToken); - break; + await uow.CompleteAsync(); + + await SendHttpStreamMessageAsync(httpContext, + new AgentResultOutput + { + TypeEnum = update.RawRepresentation is ChatResponseUpdate raw + ? raw.FinishReason?.Value == "tool_calls" + ? AgentResultTypeEnum.ToolCallUsage + : AgentResultTypeEnum.Usage + : AgentResultTypeEnum.Usage, + Content = usage! + }, + isDone: false, cancellationToken); + break; + } } } } @@ -247,8 +254,13 @@ public class ChatManager : DomainService string serializedJson = currentThread.Serialize(JsonSerializerOptions.Web).GetRawText(); agentStore.Store = serializedJson; - //插入或者更新 - await _agentStoreRepository.InsertOrUpdateAsync(agentStore); + //由于MAF线程问题 + using (var uow = _unitOfWorkManager.Begin(requiresNew: true)) + { + //插入或者更新 + await _agentStoreRepository.InsertOrUpdateAsync(agentStore); + await uow.CompleteAsync(); + } } diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/UsageStatisticsManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/UsageStatisticsManager.cs index a04d1ff5..e1f65096 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/UsageStatisticsManager.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/UsageStatisticsManager.cs @@ -22,13 +22,13 @@ public class UsageStatisticsManager : DomainService { var actualTokenId = tokenId ?? Guid.Empty; - long inputTokenCount = tokenUsage?.PromptTokens - ?? tokenUsage?.InputTokens - ?? 0; + long inputTokenCount = tokenUsage?.PromptTokens > 0 + ? tokenUsage.PromptTokens.Value + : tokenUsage?.InputTokens ?? 0; - long outputTokenCount = tokenUsage?.CompletionTokens - ?? tokenUsage?.OutputTokens - ?? 0; + long outputTokenCount = tokenUsage?.CompletionTokens > 0 + ? tokenUsage.CompletionTokens.Value + : tokenUsage?.OutputTokens ?? 0; await using (await DistributedLock.AcquireLockAsync($"UsageStatistics:{userId?.ToString()}:{actualTokenId}:{modelId}")) { diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Mcp/OnlineSearchTool.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Mcp/OnlineSearchTool.cs index 1e6ba1b5..2cf65398 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Mcp/OnlineSearchTool.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Mcp/OnlineSearchTool.cs @@ -29,8 +29,14 @@ public class OnlineSearchTool : ISingletonDependency } [YiAgentTool("联网搜索"), DisplayName("OnlineSearch"), Description("进行在线搜索,获取最新的网络信息,近期信息是7天,实时信息是1天")] - public async Task OnlineSearch([Description("搜索关键字")]string keyword, [Description("距离现在多久天")]int? daysAgo = null) + public async Task OnlineSearch([Description("搜索关键字")] string keyword, + [Description("距离现在多久天")] int? daysAgo = null) { + if (daysAgo <= 0) + { + daysAgo = 1; + } + if (string.IsNullOrWhiteSpace(keyword)) { return "搜索关键词不能为空"; @@ -149,8 +155,7 @@ public class OnlineSearchTool : ISingletonDependency /// public class BaiduSearchRequest { - [JsonPropertyName("messages")] - public List Messages { get; set; } = new(); + [JsonPropertyName("messages")] public List Messages { get; set; } = new(); [JsonPropertyName("search_filter")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -162,8 +167,7 @@ public class BaiduSearchRequest /// public class BaiduSearchFilter { - [JsonPropertyName("range")] - public BaiduSearchRange? Range { get; set; } + [JsonPropertyName("range")] public BaiduSearchRange? Range { get; set; } } /// @@ -171,8 +175,7 @@ public class BaiduSearchFilter /// public class BaiduSearchRange { - [JsonPropertyName("page_time")] - public BaiduSearchPageTime? PageTime { get; set; } + [JsonPropertyName("page_time")] public BaiduSearchPageTime? PageTime { get; set; } } /// @@ -180,11 +183,9 @@ public class BaiduSearchRange /// public class BaiduSearchPageTime { - [JsonPropertyName("gte")] - public string? Gte { get; set; } + [JsonPropertyName("gte")] public string? Gte { get; set; } - [JsonPropertyName("lte")] - public string? Lte { get; set; } + [JsonPropertyName("lte")] public string? Lte { get; set; } } /// @@ -192,11 +193,9 @@ public class BaiduSearchPageTime /// public class BaiduSearchMessage { - [JsonPropertyName("role")] - public string Role { get; set; } = "user"; + [JsonPropertyName("role")] public string Role { get; set; } = "user"; - [JsonPropertyName("content")] - public string Content { get; set; } = ""; + [JsonPropertyName("content")] public string Content { get; set; } = ""; } /// @@ -204,11 +203,9 @@ public class BaiduSearchMessage /// public class BaiduSearchResponse { - [JsonPropertyName("request_id")] - public string? RequestId { get; set; } + [JsonPropertyName("request_id")] public string? RequestId { get; set; } - [JsonPropertyName("references")] - public List? References { get; set; } + [JsonPropertyName("references")] public List? References { get; set; } } /// @@ -216,23 +213,17 @@ public class BaiduSearchResponse /// public class BaiduSearchReference { - [JsonPropertyName("id")] - public int Id { get; set; } + [JsonPropertyName("id")] public int Id { get; set; } - [JsonPropertyName("url")] - public string? Url { get; set; } + [JsonPropertyName("url")] public string? Url { get; set; } - [JsonPropertyName("title")] - public string? Title { get; set; } + [JsonPropertyName("title")] public string? Title { get; set; } - [JsonPropertyName("date")] - public string? Date { get; set; } + [JsonPropertyName("date")] public string? Date { get; set; } - [JsonPropertyName("snippet")] - public string? Snippet { get; set; } + [JsonPropertyName("snippet")] public string? Snippet { get; set; } - [JsonPropertyName("website")] - public string? Website { get; set; } + [JsonPropertyName("website")] public string? Website { get; set; } } #endregion diff --git a/Yi.Ai.Vue3/types/components.d.ts b/Yi.Ai.Vue3/types/components.d.ts index c794d9cf..9513559e 100644 --- a/Yi.Ai.Vue3/types/components.d.ts +++ b/Yi.Ai.Vue3/types/components.d.ts @@ -20,6 +20,7 @@ declare module 'vue' { ElCard: typeof import('element-plus/es')['ElCard'] ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] + ElCollapseTransition: typeof import('element-plus/es')['ElCollapseTransition'] ElContainer: typeof import('element-plus/es')['ElContainer'] ElDialog: typeof import('element-plus/es')['ElDialog'] ElDivider: typeof import('element-plus/es')['ElDivider'] @@ -34,8 +35,10 @@ declare module 'vue' { ElMain: typeof import('element-plus/es')['ElMain'] ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] + ElOption: typeof import('element-plus/es')['ElOption'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSegmented: typeof import('element-plus/es')['ElSegmented'] + ElSelect: typeof import('element-plus/es')['ElSelect'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs']