Files
Yi.Framework/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiChatService.cs
ccnetcore dbc6b8cf5e feat: 支持消息自定义创建时间并完善TokenUsage初始化
- 用户消息创建支持传入创建时间,用于统计与回放
- TokenUsage 为空时自动初始化,避免空引用问题
- 网关记录消息开始时间并传递至消息管理器
- 标记并停用旧的发送消息接口
- 前端版本号更新至 3.6
- 移除未使用的 VITE_BUILD_COMPRESS 类型声明
2026-01-31 21:22:09 +08:00

362 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Concurrent;
using System.Reflection;
using System.Text;
using System.Text.Encodings.Web;
using Dm.util;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ModelContextProtocol;
using ModelContextProtocol.Server;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using OpenAI.Chat;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos;
using Yi.Framework.AiHub.Application.Contracts.Dtos.Chat;
using Yi.Framework.AiHub.Domain;
using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Chat;
using Yi.Framework.AiHub.Domain.Entities.Model;
using Yi.Framework.AiHub.Domain.Extensions;
using Yi.Framework.AiHub.Domain.Managers;
using Yi.Framework.AiHub.Domain.Shared.Consts;
using System.Text.Json;
using Yi.Framework.AiHub.Domain.Shared.Dtos;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
using Yi.Framework.AiHub.Domain.Shared.Enums;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.Rbac.Domain.Shared.Dtos;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services;
/// <summary>
/// ai服务
/// </summary>
public class AiChatService : ApplicationService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly AiBlacklistManager _aiBlacklistManager;
private readonly ILogger<AiChatService> _logger;
private readonly AiGateWayManager _aiGateWayManager;
private readonly ModelManager _modelManager;
private readonly PremiumPackageManager _premiumPackageManager;
private readonly ChatManager _chatManager;
private readonly TokenManager _tokenManager;
private readonly IAccountService _accountService;
private readonly ISqlSugarRepository<AgentStoreAggregateRoot> _agentStoreRepository;
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
private const string FreeModelId = "DeepSeek-V3-0324";
public AiChatService(IHttpContextAccessor httpContextAccessor,
AiBlacklistManager aiBlacklistManager,
ILogger<AiChatService> logger,
AiGateWayManager aiGateWayManager,
ModelManager modelManager,
PremiumPackageManager premiumPackageManager,
ChatManager chatManager, TokenManager tokenManager, IAccountService accountService,
ISqlSugarRepository<AgentStoreAggregateRoot> agentStoreRepository,
ISqlSugarRepository<AiModelEntity> aiModelRepository)
{
_httpContextAccessor = httpContextAccessor;
_aiBlacklistManager = aiBlacklistManager;
_logger = logger;
_aiGateWayManager = aiGateWayManager;
_modelManager = modelManager;
_premiumPackageManager = premiumPackageManager;
_chatManager = chatManager;
_tokenManager = tokenManager;
_accountService = accountService;
_agentStoreRepository = agentStoreRepository;
_aiModelRepository = aiModelRepository;
}
/// <summary>
/// 查询已登录的账户信息
/// </summary>
/// <returns></returns>
[Route("ai-chat/account")]
[Authorize]
public async Task<UserRoleMenuDto> GetAsync()
{
var accountService = LazyServiceProvider.GetRequiredService<IAccountService>();
var output = await accountService.GetAsync();
return output;
}
/// <summary>
/// 获取对话模型列表
/// </summary>
/// <returns></returns>
public async Task<List<ModelGetListOutput>> GetModelAsync()
{
var output = await _aiModelRepository._DbQueryable
.Where(x => x.IsEnabled == true)
.Where(x => x.ModelType == ModelTypeEnum.Chat)
// .Where(x => x.ModelApiType == ModelApiTypeEnum.Completions)
.OrderByDescending(x => x.OrderNum)
.Select(x => new ModelGetListOutput
{
Id = x.Id,
ModelId = x.ModelId,
ModelName = x.Name,
ModelDescribe = x.Description,
Remark = x.Description,
IsPremiumPackage = x.IsPremium,
ModelApiType = x.ModelApiType,
IconUrl = x.IconUrl,
ProviderName = x.ProviderName
}).ToListAsync();
output.ForEach(x =>
{
if (x.ModelId == FreeModelId)
{
x.IsPremiumPackage = false;
x.IsFree = true;
}
});
return output;
}
// /// <summary>
// /// 发送消息
// /// </summary>
// /// <param name="input"></param>
// /// <param name="sessionId"></param>
// /// <param name="cancellationToken"></param>
// [HttpPost("ai-chat/send")]
// [Obsolete]
// public async Task PostSendAsync([FromBody] ThorChatCompletionsRequest input, [FromQuery] Guid? sessionId,
// CancellationToken cancellationToken)
// {
// //除了免费模型,其他的模型都要校验
// if (input.Model!=FreeModelId)
// {
// //有token需要黑名单校验
// if (CurrentUser.IsAuthenticated)
// {
// await _aiBlacklistManager.VerifiyAiBlacklist(CurrentUser.GetId());
// if (!CurrentUser.IsAiVip())
// {
// throw new UserFriendlyException("该模型需要VIP用户才能使用请购买VIP后重新登录重试");
// }
// }
// else
// {
// throw new UserFriendlyException("未登录用户只能使用未加速的DeepSeek-R1请登录后重试");
// }
// }
//
// //如果是尊享包服务,需要校验是是否尊享包足够
// if (CurrentUser.IsAuthenticated)
// {
// var isPremium = await _modelManager.IsPremiumModelAsync(input.Model);
//
// if (isPremium)
// {
// // 检查尊享token包用量
// var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(CurrentUser.GetId());
// if (availableTokens <= 0)
// {
// throw new UserFriendlyException("尊享token包用量不足请先购买尊享token包");
// }
// }
// }
//
// //ai网关代理httpcontext
// await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
// CurrentUser.Id, sessionId, null, CancellationToken.None);
// }
/// <summary>
/// 发送消息
/// </summary>
/// <param name="input"></param>
/// <param name="cancellationToken"></param>
[HttpPost("ai-chat/FileMaster/send")]
public async Task PostFileMasterSendAsync([FromBody] ThorChatCompletionsRequest input,
CancellationToken cancellationToken)
{
if (!string.IsNullOrWhiteSpace(input.Model))
{
throw new BusinessException("当前接口不支持第三方使用");
}
input.Model = "gpt-5-chat";
if (CurrentUser.IsAuthenticated)
{
await _aiBlacklistManager.VerifiyAiBlacklist(CurrentUser.GetId());
}
//ai网关代理httpcontext
await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
CurrentUser.Id, null, null, CancellationToken.None);
}
/// <summary>
/// Agent 发送消息
/// </summary>
[HttpPost("ai-chat/agent/send")]
public async Task PostAgentSendAsync([FromBody] AgentSendInput input, CancellationToken cancellationToken)
{
var tokenValidation = await _tokenManager.ValidateTokenAsync(input.TokenId, input.ModelId);
await _aiBlacklistManager.VerifiyAiBlacklist(tokenValidation.UserId);
// 验证用户是否为VIP
var userInfo = await _accountService.GetAsync(null, null, tokenValidation.UserId);
if (userInfo == null)
{
throw new UserFriendlyException("用户信息不存在");
}
// 检查是否为VIP使用RoleCodes判断
if (!userInfo.RoleCodes.Contains(AiHubConst.VipRole) && userInfo.User.UserName != "cc")
{
throw new UserFriendlyException("该接口为尊享服务专用需要VIP权限才能使用");
}
//如果是尊享包服务,需要校验是是否尊享包足够
var isPremium = await _modelManager.IsPremiumModelAsync(input.ModelId);
if (isPremium)
{
// 检查尊享token包用量
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(tokenValidation.UserId);
if (availableTokens <= 0)
{
throw new UserFriendlyException("尊享token包用量不足请先购买尊享token包");
}
}
await _chatManager.AgentCompleteChatStreamAsync(_httpContextAccessor.HttpContext,
input.SessionId,
input.Content,
tokenValidation.Token,
tokenValidation.TokenId,
input.ModelId,
tokenValidation.UserId,
input.Tools,
CancellationToken.None);
}
/// <summary>
/// 获取 Agent 工具
/// </summary>
/// <returns></returns>
[HttpPost("ai-chat/agent/tool")]
public List<AgentToolOutput> GetAgentToolAsync()
{
var agentTools = _chatManager.GetTools().Select(x => new AgentToolOutput
{
Code = x.Code,
Name = x.Name
}).ToList();
return agentTools;
}
/// <summary>
/// 获取 Agent 上下文
/// </summary>
/// <returns></returns>
[HttpPost("ai-chat/agent/context/{sessionId}")]
[Authorize]
public async Task<string?> GetAgentContextAsync([FromRoute] Guid sessionId)
{
var data = await _agentStoreRepository.GetFirstAsync(x => x.SessionId == sessionId);
return data?.Store;
}
/// <summary>
/// 统一发送消息 - 支持4种API类型
/// </summary>
/// <param name="apiType">API类型枚举</param>
/// <param name="input">原始请求体JsonElement</param>
/// <param name="modelId">模型IDGemini格式需要从URL传入</param>
/// <param name="sessionId">会话ID</param>
/// <param name="cancellationToken"></param>
[HttpPost("ai-chat/unified/send")]
public async Task PostUnifiedSendAsync(
[FromQuery] ModelApiTypeEnum apiType,
[FromBody] JsonElement input,
[FromQuery] string modelId,
[FromQuery] Guid? sessionId,
CancellationToken cancellationToken)
{
// 从请求体中提取模型ID如果未从URL传入
if (string.IsNullOrEmpty(modelId))
{
modelId = ExtractModelIdFromRequest(apiType, input);
}
// 除了免费模型,其他的模型都要校验
if (modelId != FreeModelId)
{
if (CurrentUser.IsAuthenticated)
{
await _aiBlacklistManager.VerifiyAiBlacklist(CurrentUser.GetId());
if (!CurrentUser.IsAiVip())
{
throw new UserFriendlyException("该模型需要VIP用户才能使用请购买VIP后重新登录重试");
}
}
else
{
throw new UserFriendlyException("未登录用户只能使用未加速的DeepSeek-R1请登录后重试");
}
}
// 如果是尊享包服务,需要校验是否尊享包足够
if (CurrentUser.IsAuthenticated)
{
var isPremium = await _modelManager.IsPremiumModelAsync(modelId);
if (isPremium)
{
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(CurrentUser.GetId());
if (availableTokens <= 0)
{
throw new UserFriendlyException("尊享token包用量不足请先购买尊享token包");
}
}
}
// 调用统一流式处理
await _aiGateWayManager.UnifiedStreamForStatisticsAsync(
_httpContextAccessor.HttpContext!,
apiType,
input,
modelId,
CurrentUser.Id,
sessionId,
null,
CancellationToken.None);
}
/// <summary>
/// 从请求体中提取模型ID
/// </summary>
private string ExtractModelIdFromRequest(ModelApiTypeEnum apiType, JsonElement input)
{
try
{
if (input.TryGetProperty("model", out var modelProperty))
{
return modelProperty.GetString() ?? string.Empty;
}
}
catch
{
// 忽略解析错误
}
throw new UserFriendlyException("无法从请求中获取模型ID请在URL参数中指定modelId");
}
}