feat: 新增统一流式转发与统计能力,支持多API类型
新增统一流式处理机制,支持 Completions、Anthropic Messages、OpenAI Responses、Gemini GenerateContent 四种 API 的原封不动 SSE 转发 统一处理 token 用量统计、倍率计算、尊享包扣费与消息记录 新增统一发送接口 ai-chat/unified/send,支持从请求体自动解析模型 ID 提升多模型流式接入的一致性与扩展性
This commit is contained in:
@@ -25,6 +25,7 @@ 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;
|
||||
@@ -270,4 +271,89 @@ public class AiChatService : ApplicationService
|
||||
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">模型ID(Gemini格式需要从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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user