diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs
index 60145a29..0ccf376a 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs
@@ -12,6 +12,7 @@ using Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Embeddings;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Images;
+using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Responses;
using Yi.Framework.AiHub.Domain.Shared.Enums;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.SqlSugarCore.Abstractions;
@@ -85,6 +86,7 @@ public class OpenApiService : ApplicationService
}
}
+
///
/// 图片生成
///
@@ -102,6 +104,7 @@ public class OpenApiService : ApplicationService
await _aiGateWayManager.CreateImageForStatisticsAsync(httpContext, userId, null, input, tokenId);
}
+
///
/// 向量生成
///
@@ -145,7 +148,7 @@ public class OpenApiService : ApplicationService
};
}
-
+
///
/// Anthropic对话(尊享服务专用)
///
@@ -185,17 +188,69 @@ public class OpenApiService : ApplicationService
//ai网关代理httpcontext
if (input.Stream)
{
- await _aiGateWayManager.AnthropicCompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
+ await _aiGateWayManager.AnthropicCompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext,
+ input,
userId, null, tokenId, cancellationToken);
}
else
{
- await _aiGateWayManager.AnthropicCompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext, input, userId,
+ await _aiGateWayManager.AnthropicCompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext, input,
+ userId,
null, tokenId,
cancellationToken);
}
}
+ ///
+ /// 响应-Openai新规范 (尊享服务专用)
+ ///
+ ///
+ ///
+ [HttpPost("openApi/v1/responses")]
+ public async Task ResponsesAsync([FromBody] OpenAiResponsesInput input, CancellationToken cancellationToken)
+ {
+ //前面都是校验,后面才是真正的调用
+ var httpContext = this._httpContextAccessor.HttpContext;
+ var tokenValidation = await _tokenManager.ValidateTokenAsync(GetTokenByHttpContext(httpContext), input.Model);
+ var userId = tokenValidation.UserId;
+ var tokenId = tokenValidation.TokenId;
+ await _aiBlacklistManager.VerifiyAiBlacklist(userId);
+
+ // 验证用户是否为VIP
+ var userInfo = await _accountService.GetAsync(null, null, userId);
+ if (userInfo == null)
+ {
+ throw new UserFriendlyException("用户信息不存在");
+ }
+
+ // 检查是否为VIP(使用RoleCodes判断)
+ if (!userInfo.RoleCodes.Contains(AiHubConst.VipRole) && userInfo.User.UserName != "cc")
+ {
+ throw new UserFriendlyException("该接口为尊享服务专用,需要VIP权限才能使用");
+ }
+
+ // 检查尊享token包用量
+ var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId);
+ if (availableTokens <= 0)
+ {
+ throw new UserFriendlyException("尊享token包用量不足,请先购买尊享token包");
+ }
+
+ //ai网关代理httpcontext
+ if (input.Stream == true)
+ {
+ await _aiGateWayManager.OpenAiResponsesStreamForStatisticsAsync(_httpContextAccessor.HttpContext,
+ input,
+ userId, null, tokenId, cancellationToken);
+ }
+ else
+ {
+ await _aiGateWayManager.OpenAiResponsesAsyncForStatisticsAsync(_httpContextAccessor.HttpContext, input,
+ userId,
+ null, tokenId,
+ cancellationToken);
+ }
+ }
#region 私有
@@ -210,7 +265,8 @@ public class OpenApiService : ApplicationService
// 再检查 Authorization 头
string authHeader = httpContext.Request.Headers["Authorization"];
- if (!string.IsNullOrWhiteSpace(authHeader) && authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrWhiteSpace(authHeader) &&
+ authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
return authHeader.Substring("Bearer ".Length).Trim();
}
@@ -227,5 +283,4 @@ public class OpenApiService : ApplicationService
}
#endregion
-
}
\ No newline at end of file
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/OpenAi/Responses/OpenAiResponsesInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/OpenAi/Responses/OpenAiResponsesInput.cs
new file mode 100644
index 00000000..2e17d351
--- /dev/null
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/OpenAi/Responses/OpenAiResponsesInput.cs
@@ -0,0 +1,46 @@
+using System.Text.Json.Serialization;
+
+namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Responses;
+
+public class OpenAiResponsesInput
+{
+ [JsonPropertyName("stream")] public bool? Stream { get; set; }
+
+ [JsonPropertyName("model")] public string Model { get; set; }
+ [JsonPropertyName("input")] public dynamic Input { get; set; }
+
+ [JsonPropertyName("max_output_tokens")]
+ public int? MaxOutputTokens { get; set; }
+
+ [JsonPropertyName("max_tool_calls")] public dynamic? MaxToolCalls { get; set; }
+ [JsonPropertyName("instructions")] public string? Instructions { get; set; }
+ [JsonPropertyName("metadata")] public dynamic? Metadata { get; set; }
+
+ [JsonPropertyName("parallel_tool_calls")]
+ public bool? ParallelToolCalls { get; set; }
+
+ [JsonPropertyName("previous_response_id")]
+ public string? PreviousResponseId { get; set; }
+
+ [JsonPropertyName("prompt")] public dynamic? Prompt { get; set; }
+ [JsonPropertyName("prompt_cache_key")] public string? PromptCacheKey { get; set; }
+
+ [JsonPropertyName("prompt_cache_retention")]
+ public string? PromptCacheRetention { get; set; }
+
+ [JsonPropertyName("reasoning")] public dynamic? Reasoning { get; set; }
+
+ [JsonPropertyName("safety_identifier")]
+ public string? SafetyIdentifier { get; set; }
+
+ [JsonPropertyName("service_tier")] public string? ServiceTier { get; set; }
+ [JsonPropertyName("store")] public bool? Store { get; set; }
+ [JsonPropertyName("stream_options")] public dynamic? StreamOptions { get; set; }
+ [JsonPropertyName("temperature")] public decimal? Temperature { get; set; }
+ [JsonPropertyName("text")] public dynamic? Text { get; set; }
+ [JsonPropertyName("tool_choice")] public dynamic? ToolChoice { get; set; }
+ [JsonPropertyName("tools")] public dynamic? Tools { get; set; }
+ [JsonPropertyName("top_logprobs")] public int? TopLogprobs { get; set; }
+ [JsonPropertyName("top_p")] public decimal? TopP { get; set; }
+ [JsonPropertyName("truncation")] public string? Truncation { get; set; }
+}
\ No newline at end of file
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/OpenAi/Responses/OpenAiResponsesOutput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/OpenAi/Responses/OpenAiResponsesOutput.cs
new file mode 100644
index 00000000..6ddcc5d6
--- /dev/null
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/OpenAi/Responses/OpenAiResponsesOutput.cs
@@ -0,0 +1,79 @@
+using System.Text.Json.Serialization;
+
+namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Responses;
+
+public class OpenAiResponsesOutput
+{
+ [JsonPropertyName("id")]
+ public string? Id { get; set; }
+ [JsonPropertyName("object")]
+ public string? Object { get; set; }
+ [JsonPropertyName("created_at")]
+ public long CreatedAt { get; set; }
+ [JsonPropertyName("status")]
+ public string? Status { get; set; }
+ [JsonPropertyName("error")]
+ public dynamic? Error { get; set; }
+ [JsonPropertyName("incomplete_details")]
+ public dynamic? IncompleteDetails { get; set; }
+ [JsonPropertyName("instructions")]
+ public dynamic? Instructions { get; set; }
+ [JsonPropertyName("max_output_tokens")]
+ public dynamic? MaxOutputTokens { get; set; }
+ [JsonPropertyName("model")]
+ public string? Model { get; set; }
+ // output 是复杂对象
+ [JsonPropertyName("output")]
+ public List? Output { get; set; }
+ [JsonPropertyName("parallel_tool_calls")]
+ public bool ParallelToolCalls { get; set; }
+ [JsonPropertyName("previous_response_id")]
+ public dynamic? PreviousResponseId { get; set; }
+ [JsonPropertyName("reasoning")]
+ public dynamic? Reasoning { get; set; }
+ [JsonPropertyName("store")]
+ public bool Store { get; set; }
+ [JsonPropertyName("temperature")]
+ public double Temperature { get; set; }
+ [JsonPropertyName("text")]
+ public dynamic? Text { get; set; }
+ [JsonPropertyName("tool_choice")]
+ public string? ToolChoice { get; set; }
+ [JsonPropertyName("tools")]
+ public List? Tools { get; set; }
+ [JsonPropertyName("top_p")]
+ public double TopP { get; set; }
+ [JsonPropertyName("truncation")]
+ public string? Truncation { get; set; }
+ // usage 为唯一强类型
+ [JsonPropertyName("usage")]
+ public OpenAiResponsesUsageOutput? Usage { get; set; }
+ [JsonPropertyName("user")]
+ public dynamic? User { get; set; }
+ [JsonPropertyName("metadata")]
+ public dynamic? Metadata { get; set; }
+}
+
+public class OpenAiResponsesUsageOutput
+{
+ [JsonPropertyName("input_tokens")]
+ public int InputTokens { get; set; }
+ [JsonPropertyName("input_tokens_details")]
+ public OpenAiResponsesInputTokensDetails? InputTokensDetails { get; set; }
+ [JsonPropertyName("output_tokens")]
+ public int OutputTokens { get; set; }
+ [JsonPropertyName("output_tokens_details")]
+ public OpenAiResponsesOutputTokensDetails? OutputTokensDetails { get; set; }
+ [JsonPropertyName("total_tokens")]
+ public int TotalTokens { get; set; }
+}
+public class OpenAiResponsesInputTokensDetails
+{
+ [JsonPropertyName("cached_tokens")]
+ public int CachedTokens { get; set; }
+}
+public class OpenAiResponsesOutputTokensDetails
+{
+ [JsonPropertyName("reasoning_tokens")]
+ public int ReasoningTokens { get; set; }
+}
\ No newline at end of file
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/ModelApiTypeEnum.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/ModelApiTypeEnum.cs
index cc5cb30c..5b407f60 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/ModelApiTypeEnum.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/ModelApiTypeEnum.cs
@@ -8,5 +8,8 @@ public enum ModelApiTypeEnum
OpenAi,
[Description("Claude")]
- Claude
+ Claude,
+
+ [Description("Response")]
+ Response
}
\ No newline at end of file
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/IOpenAiResponseService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/IOpenAiResponseService.cs
new file mode 100644
index 00000000..ab719e86
--- /dev/null
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/IOpenAiResponseService.cs
@@ -0,0 +1,29 @@
+using Yi.Framework.AiHub.Domain.Shared.Dtos;
+using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Responses;
+
+namespace Yi.Framework.AiHub.Domain.AiGateWay;
+
+public interface IOpenAiResponseService
+{
+ ///
+ /// 响应-流式
+ ///
+ ///
+ ///
+ ///
+ ///
+ public IAsyncEnumerable<(string, dynamic?)> ResponsesStreamAsync(AiModelDescribe aiModelDescribe,
+ OpenAiResponsesInput input,
+ CancellationToken cancellationToken);
+
+ ///
+ /// 响应-非流式
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Task ResponsesAsync(AiModelDescribe aiModelDescribe,
+ OpenAiResponsesInput input,
+ CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorCustomOpenAI/Chats/CustomOpenAIAnthropicChatCompletionsService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorCustomOpenAI/Chats/CustomOpenAIAnthropicChatCompletionsService.cs
index 04f06d66..7240e37e 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorCustomOpenAI/Chats/CustomOpenAIAnthropicChatCompletionsService.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorCustomOpenAI/Chats/CustomOpenAIAnthropicChatCompletionsService.cs
@@ -26,19 +26,7 @@ public class CustomOpenAIAnthropicChatCompletionsService(
{
// 转换请求格式:Claude -> OpenAI
var openAIRequest = AnthropicToOpenAi.ConvertAnthropicToOpenAi(request);
-
- if (openAIRequest.Model.StartsWith("gpt-5"))
- {
- openAIRequest.MaxCompletionTokens = request.MaxTokens;
- openAIRequest.MaxTokens = null;
- }
- else if (openAIRequest.Model.StartsWith("o3-mini") || openAIRequest.Model.StartsWith("o4-mini"))
- {
- openAIRequest.MaxCompletionTokens = request.MaxTokens;
- openAIRequest.MaxTokens = null;
- openAIRequest.Temperature = null;
- }
-
+
// 调用OpenAI服务
var openAIResponse =
await GetChatCompletionService().CompleteChatAsync(aiModelDescribe,openAIRequest, cancellationToken);
@@ -55,19 +43,7 @@ public class CustomOpenAIAnthropicChatCompletionsService(
{
var openAIRequest = AnthropicToOpenAi.ConvertAnthropicToOpenAi(request);
openAIRequest.Stream = true;
-
- if (openAIRequest.Model.StartsWith("gpt-5"))
- {
- openAIRequest.MaxCompletionTokens = request.MaxTokens;
- openAIRequest.MaxTokens = null;
- }
- else if (openAIRequest.Model.StartsWith("o3-mini") || openAIRequest.Model.StartsWith("o4-mini"))
- {
- openAIRequest.MaxCompletionTokens = request.MaxTokens;
- openAIRequest.MaxTokens = null;
- openAIRequest.Temperature = null;
- }
-
+
var messageId = Guid.NewGuid().ToString();
var hasStarted = false;
var hasTextContentBlockStarted = false;
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorCustomOpenAI/Chats/OpenAIChatCompletionsService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorCustomOpenAI/Chats/OpenAIChatCompletionsService.cs
index 32094c8a..1598d0ff 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorCustomOpenAI/Chats/OpenAIChatCompletionsService.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorCustomOpenAI/Chats/OpenAIChatCompletionsService.cs
@@ -130,7 +130,7 @@ public sealed class OpenAiChatCompletionsService(ILogger logger,IHttpClientFactory httpClientFactory):IOpenAiResponseService
+{
+
+ public async IAsyncEnumerable<(string, dynamic?)> ResponsesStreamAsync(AiModelDescribe options, OpenAiResponsesInput input,
+ CancellationToken cancellationToken)
+ {
+ using var openai =
+ Activity.Current?.Source.StartActivity("OpenAi 响应");
+
+
+ var client = httpClientFactory.CreateClient();
+
+ var response = await client.HttpRequestRaw(options.Endpoint.TrimEnd('/') + "/responses", input, options.ApiKey);
+
+ openai?.SetTag("Model", input.Model);
+ openai?.SetTag("Response", response.StatusCode.ToString());
+
+ // 大于等于400的状态码都认为是异常
+ if (response.StatusCode >= HttpStatusCode.BadRequest)
+ {
+ var error = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
+ logger.LogError("OpenAI响应异常 请求地址:{Address}, StatusCode: {StatusCode} Response: {Response}",
+ options.Endpoint,
+ response.StatusCode, error);
+
+ throw new Exception("OpenAI响应异常" + response.StatusCode);
+ }
+
+ using var stream = new StreamReader(await response.Content.ReadAsStreamAsync(cancellationToken));
+
+ using StreamReader reader = new(await response.Content.ReadAsStreamAsync(cancellationToken));
+ string? line = string.Empty;
+
+ string? data = null;
+ string eventType = string.Empty;
+
+ while ((line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false)) != null)
+ {
+ line += Environment.NewLine;
+
+ if (line.StartsWith('{'))
+ {
+ logger.LogInformation("OpenAI响应异常 , StatusCode: {StatusCode} Response: {Response}", response.StatusCode,
+ line);
+
+ throw new Exception("OpenAI响应异常" + line);
+ }
+
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
+
+ if (line.StartsWith("event:"))
+ {
+ eventType = line;
+ continue;
+ }
+
+ if (!line.StartsWith(OpenAIConstant.Data)) continue;
+
+ data = line[OpenAIConstant.Data.Length..].Trim();
+
+ var result = JsonSerializer.Deserialize(data,
+ ThorJsonSerializer.DefaultOptions);
+
+ yield return (eventType, result);
+ }
+ }
+
+ public async Task ResponsesAsync(AiModelDescribe options, OpenAiResponsesInput chatCompletionCreate,
+ CancellationToken cancellationToken)
+ {
+ using var openai =
+ Activity.Current?.Source.StartActivity("OpenAI 响应");
+
+ var response = await httpClientFactory.CreateClient().PostJsonAsync(
+ options?.Endpoint.TrimEnd('/') + "/responses",
+ chatCompletionCreate, options.ApiKey).ConfigureAwait(false);
+
+ openai?.SetTag("Model", chatCompletionCreate.Model);
+ openai?.SetTag("Response", response.StatusCode.ToString());
+
+ if (response.StatusCode == HttpStatusCode.Unauthorized)
+ {
+ throw new BusinessException("渠道未登录,请联系管理人员", "401");
+ }
+
+ // 如果限流则抛出限流异常
+ if (response.StatusCode == HttpStatusCode.TooManyRequests)
+ {
+ throw new ThorRateLimitException();
+ }
+
+ // 大于等于400的状态码都认为是异常
+ if (response.StatusCode >= HttpStatusCode.BadRequest)
+ {
+ var error = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
+ logger.LogError("OpenAI 响应异常 请求地址:{Address}, StatusCode: {StatusCode} Response: {Response}", options.Endpoint,
+ response.StatusCode, error);
+
+ throw new BusinessException("OpenAI响应异常", response.StatusCode.ToString());
+ }
+
+ var result =
+ await response.Content.ReadFromJsonAsync(
+ cancellationToken: cancellationToken).ConfigureAwait(false);
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs
index 4e2d3645..d0346507 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs
@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Volo.Abp.Domain.Services;
using Yi.Framework.AiHub.Domain.AiGateWay;
@@ -18,6 +19,7 @@ using Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Embeddings;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Images;
+using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Responses;
using Yi.Framework.AiHub.Domain.Shared.Enums;
using Yi.Framework.Core.Extensions;
using Yi.Framework.SqlSugarCore.Abstractions;
@@ -546,9 +548,9 @@ public class AiGateWayManager : DomainService
var chatService =
LazyServiceProvider.GetRequiredKeyedService(modelDescribe.HandlerName);
var data = await chatService.ChatCompletionsAsync(modelDescribe, request, cancellationToken);
-
+
data.SupplementalMultiplier(modelDescribe.Multiplier);
-
+
if (userId is not null)
{
await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId,
@@ -660,6 +662,183 @@ public class AiGateWayManager : DomainService
}
}
+
+ ///
+ /// OpenAi 响应-非流式-缓存处理
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task OpenAiResponsesAsyncForStatisticsAsync(HttpContext httpContext,
+ OpenAiResponsesInput request,
+ Guid? userId = null,
+ Guid? sessionId = null,
+ Guid? tokenId = null,
+ CancellationToken cancellationToken = default)
+ {
+ //todo 1
+ // _specialCompatible.AnthropicCompatible(request);
+ var response = httpContext.Response;
+ // 设置响应头,声明是 json
+ //response.ContentType = "application/json; charset=UTF-8";
+ var modelDescribe = await GetModelAsync(ModelApiTypeEnum.Response, request.Model);
+
+ var chatService =
+ LazyServiceProvider.GetRequiredKeyedService(modelDescribe.HandlerName);
+ var data = await chatService.ResponsesAsync(modelDescribe, request, cancellationToken);
+
+ //todo 2
+ // data.SupplementalMultiplier(modelDescribe.Multiplier);
+
+ //todo 3
+
+ // if (userId is not null)
+ // {
+ // await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId,
+ // new MessageInputDto
+ // {
+ // Content = sessionId is null ? "不予存储" : request.Messages?.FirstOrDefault()?.Content ?? string.Empty,
+ // ModelId = request.Model,
+ // TokenUsage = data.TokenUsage,
+ // }, tokenId);
+ //
+ // await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId,
+ // new MessageInputDto
+ // {
+ // Content = sessionId is null ? "不予存储" : data.content?.FirstOrDefault()?.text,
+ // ModelId = request.Model,
+ // TokenUsage = data.TokenUsage
+ // }, tokenId);
+ //
+ // await _usageStatisticsManager.SetUsageAsync(userId.Value, request.Model, data.TokenUsage, tokenId);
+ //
+ // // 扣减尊享token包用量
+ // var totalTokens = data.TokenUsage.TotalTokens ?? 0;
+ // if (totalTokens > 0)
+ // {
+ // await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
+ // }
+ // }
+
+ await response.WriteAsJsonAsync(data, cancellationToken);
+ }
+
+
+ ///
+ /// OpenAi 响应-流式
+ ///
+ ///
+ ///
+ ///
+ public async IAsyncEnumerable<(string, dynamic?)> OpenAiResponsesAsync(
+ OpenAiResponsesInput request,
+ [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ //todo cc
+ // _specialCompatible.AnthropicCompatible(request);
+ var modelDescribe = await GetModelAsync(ModelApiTypeEnum.Response, request.Model);
+ var chatService =
+ LazyServiceProvider.GetRequiredKeyedService(modelDescribe.HandlerName);
+
+ await foreach (var result in chatService.ResponsesStreamAsync(modelDescribe, request, cancellationToken))
+ {
+ //todo 倍率
+ // result.Item2.SupplementalMultiplier(modelDescribe.Multiplier);
+ yield return result;
+ }
+ }
+
+
+ ///
+ /// OpenAi响应-流式-缓存处理
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Token Id(Web端传null或Guid.Empty)
+ ///
+ ///
+ public async Task OpenAiResponsesStreamForStatisticsAsync(
+ HttpContext httpContext,
+ OpenAiResponsesInput request,
+ Guid? userId = null,
+ Guid? sessionId = null,
+ Guid? tokenId = null,
+ CancellationToken cancellationToken = default)
+ {
+ var response = httpContext.Response;
+ // 设置响应头,声明是 SSE 流
+ response.ContentType = "text/event-stream;charset=utf-8;";
+ response.Headers.TryAdd("Cache-Control", "no-cache");
+ response.Headers.TryAdd("Connection", "keep-alive");
+
+ var completeChatResponse = OpenAiResponsesAsync(request, cancellationToken);
+ ThorUsageResponse? tokenUsage = null;
+ try
+ {
+ await foreach (var responseResult in completeChatResponse)
+ {
+ //message_start是为了保底机制
+ if (responseResult.Item1.Contains("response.completed"))
+ {
+ JObject obj = JObject.FromObject(responseResult.Item2);
+ int inputTokens = (int?)obj["response"]?["usage"]?["input_tokens"] ?? 0;
+ int outputTokens = (int?)obj["response"]?["usage"]?["output_tokens"] ?? 0;
+ tokenUsage = new ThorUsageResponse
+ {
+ PromptTokens = inputTokens,
+ InputTokens = inputTokens,
+ OutputTokens = outputTokens,
+ CompletionTokens = outputTokens,
+ TotalTokens = inputTokens+outputTokens,
+ };
+ }
+
+ await WriteAsEventStreamDataAsync(httpContext, responseResult.Item1, responseResult.Item2,
+ cancellationToken);
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, $"Ai响应异常");
+ var errorContent = $"响应Ai异常,异常信息:\n当前Ai模型:{request.Model}\n异常信息:{e.Message}\n异常堆栈:{e}";
+ throw new UserFriendlyException(errorContent);
+ }
+
+ await _aiMessageManager.CreateUserMessageAsync(userId, sessionId,
+ new MessageInputDto
+ {
+ Content = "不予存储" ,
+ ModelId = request.Model,
+ TokenUsage = tokenUsage,
+ }, tokenId);
+
+ await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId,
+ new MessageInputDto
+ {
+ Content = "不予存储" ,
+ ModelId = request.Model,
+ TokenUsage = tokenUsage
+ }, tokenId);
+
+ await _usageStatisticsManager.SetUsageAsync(userId, request.Model, tokenUsage, tokenId);
+
+ // 扣减尊享token包用量
+ if (userId.HasValue && tokenUsage is not null)
+ {
+ var totalTokens = tokenUsage.TotalTokens ?? 0;
+ if (tokenUsage.TotalTokens > 0)
+ {
+ await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
+ }
+ }
+ }
+
+
#region Anthropic格式Http响应
private static readonly byte[] EventPrefix = "event: "u8.ToArray();
@@ -675,7 +854,6 @@ public class AiGateWayManager : DomainService
string @event,
T value,
CancellationToken cancellationToken = default)
- where T : class
{
var response = context.Response;
var bodyStream = response.Body;
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/YiFrameworkAiHubDomainModule.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/YiFrameworkAiHubDomainModule.cs
index 823643da..eeb497e9 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/YiFrameworkAiHubDomainModule.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/YiFrameworkAiHubDomainModule.cs
@@ -56,6 +56,12 @@ namespace Yi.Framework.AiHub.Domain
#endregion
+ #region OpenAi Response
+
+ services.AddKeyedTransient(
+ nameof(OpenAiResponseService));
+
+ #endregion
#region Image