feat: 完成openai响应接口

This commit is contained in:
chenchun
2025-12-11 17:16:21 +08:00
parent 433d616b9b
commit 69b84f6613
6 changed files with 379 additions and 137 deletions

View File

@@ -1,4 +1,5 @@
using System.Text.Json.Serialization; using System.Text.Json;
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Responses; namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Responses;
@@ -7,14 +8,14 @@ public class OpenAiResponsesInput
[JsonPropertyName("stream")] public bool? Stream { get; set; } [JsonPropertyName("stream")] public bool? Stream { get; set; }
[JsonPropertyName("model")] public string Model { get; set; } [JsonPropertyName("model")] public string Model { get; set; }
[JsonPropertyName("input")] public dynamic Input { get; set; } [JsonPropertyName("input")] public JsonElement Input { get; set; }
[JsonPropertyName("max_output_tokens")] [JsonPropertyName("max_output_tokens")]
public int? MaxOutputTokens { get; set; } public int? MaxOutputTokens { get; set; }
[JsonPropertyName("max_tool_calls")] public dynamic? MaxToolCalls { get; set; } [JsonPropertyName("max_tool_calls")] public JsonElement? MaxToolCalls { get; set; }
[JsonPropertyName("instructions")] public string? Instructions { get; set; } [JsonPropertyName("instructions")] public string? Instructions { get; set; }
[JsonPropertyName("metadata")] public dynamic? Metadata { get; set; } [JsonPropertyName("metadata")] public JsonElement? Metadata { get; set; }
[JsonPropertyName("parallel_tool_calls")] [JsonPropertyName("parallel_tool_calls")]
public bool? ParallelToolCalls { get; set; } public bool? ParallelToolCalls { get; set; }
@@ -22,24 +23,24 @@ public class OpenAiResponsesInput
[JsonPropertyName("previous_response_id")] [JsonPropertyName("previous_response_id")]
public string? PreviousResponseId { get; set; } public string? PreviousResponseId { get; set; }
[JsonPropertyName("prompt")] public dynamic? Prompt { get; set; } [JsonPropertyName("prompt")] public JsonElement? Prompt { get; set; }
[JsonPropertyName("prompt_cache_key")] public string? PromptCacheKey { get; set; } [JsonPropertyName("prompt_cache_key")] public string? PromptCacheKey { get; set; }
[JsonPropertyName("prompt_cache_retention")] [JsonPropertyName("prompt_cache_retention")]
public string? PromptCacheRetention { get; set; } public string? PromptCacheRetention { get; set; }
[JsonPropertyName("reasoning")] public dynamic? Reasoning { get; set; } [JsonPropertyName("reasoning")] public JsonElement? Reasoning { get; set; }
[JsonPropertyName("safety_identifier")] [JsonPropertyName("safety_identifier")]
public string? SafetyIdentifier { get; set; } public string? SafetyIdentifier { get; set; }
[JsonPropertyName("service_tier")] public string? ServiceTier { get; set; } [JsonPropertyName("service_tier")] public string? ServiceTier { get; set; }
[JsonPropertyName("store")] public bool? Store { get; set; } [JsonPropertyName("store")] public bool? Store { get; set; }
[JsonPropertyName("stream_options")] public dynamic? StreamOptions { get; set; } [JsonPropertyName("stream_options")] public JsonElement? StreamOptions { get; set; }
[JsonPropertyName("temperature")] public decimal? Temperature { get; set; } [JsonPropertyName("temperature")] public decimal? Temperature { get; set; }
[JsonPropertyName("text")] public dynamic? Text { get; set; } [JsonPropertyName("text")] public JsonElement? Text { get; set; }
[JsonPropertyName("tool_choice")] public dynamic? ToolChoice { get; set; } [JsonPropertyName("tool_choice")] public JsonElement? ToolChoice { get; set; }
[JsonPropertyName("tools")] public dynamic? Tools { get; set; } [JsonPropertyName("tools")] public JsonElement? Tools { get; set; }
[JsonPropertyName("top_logprobs")] public int? TopLogprobs { get; set; } [JsonPropertyName("top_logprobs")] public int? TopLogprobs { get; set; }
[JsonPropertyName("top_p")] public decimal? TopP { get; set; } [JsonPropertyName("top_p")] public decimal? TopP { get; set; }
[JsonPropertyName("truncation")] public string? Truncation { get; set; } [JsonPropertyName("truncation")] public string? Truncation { get; set; }

View File

@@ -52,6 +52,18 @@ public class OpenAiResponsesOutput
public dynamic? User { get; set; } public dynamic? User { get; set; }
[JsonPropertyName("metadata")] [JsonPropertyName("metadata")]
public dynamic? Metadata { get; set; } public dynamic? Metadata { get; set; }
public void SupplementalMultiplier(decimal multiplier)
{
if (this.Usage is not null)
{
this.Usage.InputTokens =
(int)Math.Round((this.Usage?.InputTokens ?? 0) * multiplier);
this.Usage.OutputTokens =
(int)Math.Round((this.Usage?.OutputTokens ?? 0) * multiplier);
}
}
} }
public class OpenAiResponsesUsageOutput public class OpenAiResponsesUsageOutput

View File

@@ -0,0 +1,279 @@
using System.Text.Json;
namespace Yi.Framework.AiHub.Domain.Shared.Extensions;
public static class JsonElementExtensions
{
#region 访
/// <summary>
/// 链式获取深层属性,支持对象属性和数组索引
/// </summary>
/// <example>
/// root.GetPath("user", "addresses", 0, "city")
/// </example>
public static JsonElement? GetPath(this JsonElement element, params object[] path)
{
JsonElement current = element;
foreach (var key in path)
{
switch (key)
{
case string propertyName:
if (current.ValueKind != JsonValueKind.Object ||
!current.TryGetProperty(propertyName, out current))
return null;
break;
case int index:
if (current.ValueKind != JsonValueKind.Array ||
index < 0 || index >= current.GetArrayLength())
return null;
current = current[index];
break;
default:
return null;
}
}
return current;
}
/// <summary>
/// 安全获取对象属性
/// </summary>
public static JsonElement? Get(this JsonElement element, string propertyName)
{
if (element.ValueKind == JsonValueKind.Object &&
element.TryGetProperty(propertyName, out var value))
return value;
return null;
}
/// <summary>
/// 安全获取数组元素
/// </summary>
public static JsonElement? Get(this JsonElement element, int index)
{
if (element.ValueKind == JsonValueKind.Array &&
index >= 0 && index < element.GetArrayLength())
return element[index];
return null;
}
/// <summary>
/// 链式安全获取对象属性
/// </summary>
public static JsonElement? Get(this JsonElement? element, string propertyName)
=> element?.Get(propertyName);
/// <summary>
/// 链式安全获取数组元素
/// </summary>
public static JsonElement? Get(this JsonElement? element, int index)
=> element?.Get(index);
#endregion
#region
public static string? GetString(this JsonElement? element, string? defaultValue = null)
=> element?.ValueKind == JsonValueKind.String ? element.Value.GetString() : defaultValue;
public static int GetInt(this JsonElement? element, int defaultValue = 0)
=> element?.ValueKind == JsonValueKind.Number ? element.Value.GetInt32() : defaultValue;
public static long GetLong(this JsonElement? element, long defaultValue = 0)
=> element?.ValueKind == JsonValueKind.Number ? element.Value.GetInt64() : defaultValue;
public static double GetDouble(this JsonElement? element, double defaultValue = 0)
=> element?.ValueKind == JsonValueKind.Number ? element.Value.GetDouble() : defaultValue;
public static decimal GetDecimal(this JsonElement? element, decimal defaultValue = 0)
=> element?.ValueKind == JsonValueKind.Number ? element.Value.GetDecimal() : defaultValue;
public static bool GetBool(this JsonElement? element, bool defaultValue = false)
=> element?.ValueKind is JsonValueKind.True or JsonValueKind.False
? element.Value.GetBoolean()
: defaultValue;
public static DateTime GetDateTime(this JsonElement? element, DateTime defaultValue = default)
=> element?.ValueKind == JsonValueKind.String && element.Value.TryGetDateTime(out var dt)
? dt
: defaultValue;
public static Guid GetGuid(this JsonElement? element, Guid defaultValue = default)
=> element?.ValueKind == JsonValueKind.String && element.Value.TryGetGuid(out var guid)
? guid
: defaultValue;
#endregion
#region
public static int? GetIntOrNull(this JsonElement? element)
=> element?.ValueKind == JsonValueKind.Number ? element.Value.GetInt32() : null;
public static long? GetLongOrNull(this JsonElement? element)
=> element?.ValueKind == JsonValueKind.Number ? element.Value.GetInt64() : null;
public static double? GetDoubleOrNull(this JsonElement? element)
=> element?.ValueKind == JsonValueKind.Number ? element.Value.GetDouble() : null;
public static decimal? GetDecimalOrNull(this JsonElement? element)
=> element?.ValueKind == JsonValueKind.Number ? element.Value.GetDecimal() : null;
public static bool? GetBoolOrNull(this JsonElement? element)
=> element?.ValueKind is JsonValueKind.True or JsonValueKind.False
? element.Value.GetBoolean()
: null;
public static DateTime? GetDateTimeOrNull(this JsonElement? element)
=> element?.ValueKind == JsonValueKind.String && element.Value.TryGetDateTime(out var dt)
? dt
: null;
public static Guid? GetGuidOrNull(this JsonElement? element)
=> element?.ValueKind == JsonValueKind.String && element.Value.TryGetGuid(out var guid)
? guid
: null;
#endregion
#region
/// <summary>
/// 安全获取数组,不存在返回空数组
/// </summary>
public static IEnumerable<JsonElement> GetArray(this JsonElement? element)
{
if (element?.ValueKind == JsonValueKind.Array)
{
foreach (var item in element.Value.EnumerateArray())
yield return item;
}
}
/// <summary>
/// 获取数组长度
/// </summary>
public static int GetArrayLength(this JsonElement? element)
=> element?.ValueKind == JsonValueKind.Array ? element.Value.GetArrayLength() : 0;
/// <summary>
/// 数组转 List
/// </summary>
public static List<string?> ToStringList(this JsonElement? element)
=> element.GetArray().Select(e => e.GetString()).ToList();
public static List<int> ToIntList(this JsonElement? element)
=> element.GetArray()
.Where(e => e.ValueKind == JsonValueKind.Number)
.Select(e => e.GetInt32())
.ToList();
#endregion
#region
/// <summary>
/// 安全枚举对象属性
/// </summary>
public static IEnumerable<JsonProperty> GetProperties(this JsonElement? element)
{
if (element?.ValueKind == JsonValueKind.Object)
{
foreach (var prop in element.Value.EnumerateObject())
yield return prop;
}
}
/// <summary>
/// 获取所有属性名
/// </summary>
public static IEnumerable<string> GetPropertyNames(this JsonElement? element)
=> element.GetProperties().Select(p => p.Name);
/// <summary>
/// 判断是否包含某属性
/// </summary>
public static bool HasProperty(this JsonElement? element, string propertyName)
=> element?.ValueKind == JsonValueKind.Object &&
element.Value.TryGetProperty(propertyName, out _);
#endregion
#region
public static bool IsNull(this JsonElement? element)
=> element == null || element.Value.ValueKind == JsonValueKind.Null;
public static bool IsNullOrUndefined(this JsonElement? element)
=> element == null || element.Value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined;
public static bool IsObject(this JsonElement? element)
=> element?.ValueKind == JsonValueKind.Object;
public static bool IsArray(this JsonElement? element)
=> element?.ValueKind == JsonValueKind.Array;
public static bool IsString(this JsonElement? element)
=> element?.ValueKind == JsonValueKind.String;
public static bool IsNumber(this JsonElement? element)
=> element?.ValueKind == JsonValueKind.Number;
public static bool IsBool(this JsonElement? element)
=> element?.ValueKind is JsonValueKind.True or JsonValueKind.False;
public static bool Exists(this JsonElement? element)
=> element != null && element.Value.ValueKind != JsonValueKind.Undefined;
#endregion
#region
/// <summary>
/// 反序列化为指定类型
/// </summary>
public static T? Deserialize<T>(this JsonElement? element, JsonSerializerOptions? options = null)
=> element.HasValue ? element.Value.Deserialize<T>(options) : default;
/// <summary>
/// 反序列化为指定类型,带默认值
/// </summary>
public static T Deserialize<T>(this JsonElement? element, T defaultValue, JsonSerializerOptions? options = null)
=> element.HasValue ? element.Value.Deserialize<T>(options) ?? defaultValue : defaultValue;
#endregion
#region /
/// <summary>
/// 转换为 Dictionary
/// </summary>
public static Dictionary<string, JsonElement>? ToDictionary(this JsonElement? element)
{
if (element?.ValueKind != JsonValueKind.Object)
return null;
var dict = new Dictionary<string, JsonElement>();
foreach (var prop in element.Value.EnumerateObject())
dict[prop.Name] = prop.Value;
return dict;
}
#endregion
#region
/// <summary>
/// 获取原始 JSON 字符串
/// </summary>
public static string? GetRawText(this JsonElement? element)
=> element?.GetRawText();
#endregion
}

View File

@@ -1,4 +1,5 @@
using Yi.Framework.AiHub.Domain.Shared.Dtos; using System.Text.Json;
using Yi.Framework.AiHub.Domain.Shared.Dtos;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Responses; using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Responses;
namespace Yi.Framework.AiHub.Domain.AiGateWay; namespace Yi.Framework.AiHub.Domain.AiGateWay;
@@ -12,7 +13,7 @@ public interface IOpenAiResponseService
/// <param name="input"></param> /// <param name="input"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
public IAsyncEnumerable<(string, dynamic?)> ResponsesStreamAsync(AiModelDescribe aiModelDescribe, public IAsyncEnumerable<(string, JsonElement?)> ResponsesStreamAsync(AiModelDescribe aiModelDescribe,
OpenAiResponsesInput input, OpenAiResponsesInput input,
CancellationToken cancellationToken); CancellationToken cancellationToken);

View File

@@ -13,7 +13,7 @@ namespace Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorCustomOpenAI.Chats;
public class OpenAiResponseService(ILogger<OpenAiResponseService> logger,IHttpClientFactory httpClientFactory):IOpenAiResponseService public class OpenAiResponseService(ILogger<OpenAiResponseService> logger,IHttpClientFactory httpClientFactory):IOpenAiResponseService
{ {
public async IAsyncEnumerable<(string, dynamic?)> ResponsesStreamAsync(AiModelDescribe options, OpenAiResponsesInput input, public async IAsyncEnumerable<(string, JsonElement?)> ResponsesStreamAsync(AiModelDescribe options, OpenAiResponsesInput input,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
using var openai = using var openai =
@@ -73,7 +73,7 @@ public class OpenAiResponseService(ILogger<OpenAiResponseService> logger,IHttpCl
data = line[OpenAIConstant.Data.Length..].Trim(); data = line[OpenAIConstant.Data.Length..].Trim();
var result = JsonSerializer.Deserialize<dynamic>(data, var result = JsonSerializer.Deserialize<JsonElement>(data,
ThorJsonSerializer.DefaultOptions); ThorJsonSerializer.DefaultOptions);
yield return (eventType, result); yield return (eventType, result);

View File

@@ -21,6 +21,7 @@ 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.Images;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Responses; using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Responses;
using Yi.Framework.AiHub.Domain.Shared.Enums; using Yi.Framework.AiHub.Domain.Shared.Enums;
using Yi.Framework.AiHub.Domain.Shared.Extensions;
using Yi.Framework.Core.Extensions; using Yi.Framework.Core.Extensions;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
using JsonSerializer = System.Text.Json.JsonSerializer; using JsonSerializer = System.Text.Json.JsonSerializer;
@@ -91,30 +92,7 @@ public class AiGateWayManager : DomainService
return aiModelDescribe; return aiModelDescribe;
} }
/// <summary>
/// 聊天完成-流式
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async IAsyncEnumerable<ThorChatCompletionsResponse> CompleteChatStreamAsync(
ThorChatCompletionsRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
_specialCompatible.Compatible(request);
var modelDescribe = await GetModelAsync(ModelApiTypeEnum.OpenAi, request.Model);
var chatService =
LazyServiceProvider.GetRequiredKeyedService<IChatCompletionService>(modelDescribe.HandlerName);
await foreach (var result in chatService.CompleteChatStreamAsync(modelDescribe, request, cancellationToken))
{
result.SupplementalMultiplier(modelDescribe.Multiplier);
yield return result;
}
}
/// <summary> /// <summary>
/// 聊天完成-非流式 /// 聊天完成-非流式
/// </summary> /// </summary>
@@ -176,6 +154,7 @@ public class AiGateWayManager : DomainService
await response.WriteAsJsonAsync(data, cancellationToken); await response.WriteAsJsonAsync(data, cancellationToken);
} }
/// <summary> /// <summary>
/// 聊天完成-缓存处理 /// 聊天完成-缓存处理
/// </summary> /// </summary>
@@ -201,8 +180,12 @@ public class AiGateWayManager : DomainService
response.Headers.TryAdd("Connection", "keep-alive"); response.Headers.TryAdd("Connection", "keep-alive");
var gateWay = LazyServiceProvider.GetRequiredService<AiGateWayManager>(); _specialCompatible.Compatible(request);
var completeChatResponse = gateWay.CompleteChatStreamAsync(request, cancellationToken); var modelDescribe = await GetModelAsync(ModelApiTypeEnum.OpenAi, request.Model);
var chatService =
LazyServiceProvider.GetRequiredKeyedService<IChatCompletionService>(modelDescribe.HandlerName);
var completeChatResponse = chatService.CompleteChatStreamAsync(modelDescribe,request, cancellationToken);
var tokenUsage = new ThorUsageResponse(); var tokenUsage = new ThorUsageResponse();
//缓存队列算法 //缓存队列算法
@@ -244,6 +227,7 @@ public class AiGateWayManager : DomainService
{ {
await foreach (var data in completeChatResponse) await foreach (var data in completeChatResponse)
{ {
data.SupplementalMultiplier(modelDescribe.Multiplier);
if (data.Usage is not null && (data.Usage.CompletionTokens > 0 || data.Usage.OutputTokens > 0)) if (data.Usage is not null && (data.Usage.CompletionTokens > 0 || data.Usage.OutputTokens > 0))
{ {
tokenUsage = data.Usage; tokenUsage = data.Usage;
@@ -316,8 +300,8 @@ public class AiGateWayManager : DomainService
} }
} }
} }
/// <summary> /// <summary>
/// 图片生成 /// 图片生成
/// </summary> /// </summary>
@@ -385,8 +369,8 @@ public class AiGateWayManager : DomainService
throw new UserFriendlyException(errorContent); throw new UserFriendlyException(errorContent);
} }
} }
/// <summary> /// <summary>
/// 向量生成 /// 向量生成
/// </summary> /// </summary>
@@ -498,30 +482,7 @@ public class AiGateWayManager : DomainService
throw new UserFriendlyException(errorContent); throw new UserFriendlyException(errorContent);
} }
} }
/// <summary>
/// Anthropic聊天完成-流式
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async IAsyncEnumerable<(string, AnthropicStreamDto?)> AnthropicCompleteChatStreamAsync(
AnthropicInput request,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
_specialCompatible.AnthropicCompatible(request);
var modelDescribe = await GetModelAsync(ModelApiTypeEnum.Claude, request.Model);
var chatService =
LazyServiceProvider.GetRequiredKeyedService<IAnthropicChatCompletionService>(modelDescribe.HandlerName);
await foreach (var result in chatService.StreamChatCompletionsAsync(modelDescribe, request, cancellationToken))
{
result.Item2.SupplementalMultiplier(modelDescribe.Multiplier);
yield return result;
}
}
/// <summary> /// <summary>
/// Anthropic聊天完成-非流式 /// Anthropic聊天完成-非流式
@@ -582,6 +543,7 @@ public class AiGateWayManager : DomainService
await response.WriteAsJsonAsync(data, cancellationToken); await response.WriteAsJsonAsync(data, cancellationToken);
} }
/// <summary> /// <summary>
/// Anthropic聊天完成-缓存处理 /// Anthropic聊天完成-缓存处理
/// </summary> /// </summary>
@@ -605,16 +567,20 @@ public class AiGateWayManager : DomainService
response.ContentType = "text/event-stream;charset=utf-8;"; response.ContentType = "text/event-stream;charset=utf-8;";
response.Headers.TryAdd("Cache-Control", "no-cache"); response.Headers.TryAdd("Cache-Control", "no-cache");
response.Headers.TryAdd("Connection", "keep-alive"); response.Headers.TryAdd("Connection", "keep-alive");
_specialCompatible.AnthropicCompatible(request);
var gateWay = LazyServiceProvider.GetRequiredService<AiGateWayManager>(); var modelDescribe = await GetModelAsync(ModelApiTypeEnum.Claude, request.Model);
var completeChatResponse = gateWay.AnthropicCompleteChatStreamAsync(request, cancellationToken); var chatService =
LazyServiceProvider.GetRequiredKeyedService<IAnthropicChatCompletionService>(modelDescribe.HandlerName);
var completeChatResponse = chatService.StreamChatCompletionsAsync(modelDescribe,request, cancellationToken);
ThorUsageResponse? tokenUsage = null; ThorUsageResponse? tokenUsage = null;
StringBuilder backupSystemContent = new StringBuilder(); StringBuilder backupSystemContent = new StringBuilder();
try try
{ {
await foreach (var responseResult in completeChatResponse) await foreach (var responseResult in completeChatResponse)
{ {
responseResult.Item2.SupplementalMultiplier(modelDescribe.Multiplier);
//message_start是为了保底机制 //message_start是为了保底机制
if (responseResult.Item1.Contains("message_delta") || responseResult.Item1.Contains("message_start")) if (responseResult.Item1.Contains("message_delta") || responseResult.Item1.Contains("message_start"))
{ {
@@ -679,7 +645,6 @@ public class AiGateWayManager : DomainService
Guid? tokenId = null, Guid? tokenId = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
//todo 1
// _specialCompatible.AnthropicCompatible(request); // _specialCompatible.AnthropicCompatible(request);
var response = httpContext.Response; var response = httpContext.Response;
// 设置响应头,声明是 json // 设置响应头,声明是 json
@@ -689,69 +654,47 @@ public class AiGateWayManager : DomainService
var chatService = var chatService =
LazyServiceProvider.GetRequiredKeyedService<IOpenAiResponseService>(modelDescribe.HandlerName); LazyServiceProvider.GetRequiredKeyedService<IOpenAiResponseService>(modelDescribe.HandlerName);
var data = await chatService.ResponsesAsync(modelDescribe, request, cancellationToken); var data = await chatService.ResponsesAsync(modelDescribe, request, cancellationToken);
//todo 2 data.SupplementalMultiplier(modelDescribe.Multiplier);
// data.SupplementalMultiplier(modelDescribe.Multiplier);
var tokenUsage= new ThorUsageResponse
//todo 3 {
InputTokens = data.Usage.InputTokens,
// if (userId is not null) OutputTokens = data.Usage.OutputTokens,
// { TotalTokens = data.Usage.InputTokens + data.Usage.OutputTokens,
// await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId, };
// new MessageInputDto if (userId is not null)
// { {
// Content = sessionId is null ? "不予存储" : request.Messages?.FirstOrDefault()?.Content ?? string.Empty, await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId,
// ModelId = request.Model, new MessageInputDto
// TokenUsage = data.TokenUsage, {
// }, tokenId); Content = "不予存储",
// ModelId = request.Model,
// await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId, TokenUsage = tokenUsage,
// new MessageInputDto }, tokenId);
// {
// Content = sessionId is null ? "不予存储" : data.content?.FirstOrDefault()?.text, await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId,
// ModelId = request.Model, new MessageInputDto
// TokenUsage = data.TokenUsage {
// }, tokenId); Content = "不予存储",
// ModelId = request.Model,
// await _usageStatisticsManager.SetUsageAsync(userId.Value, request.Model, data.TokenUsage, tokenId); TokenUsage = tokenUsage
// }, tokenId);
// // 扣减尊享token包用量
// var totalTokens = data.TokenUsage.TotalTokens ?? 0; await _usageStatisticsManager.SetUsageAsync(userId.Value, request.Model, tokenUsage, tokenId);
// if (totalTokens > 0)
// { // 扣减尊享token包用量
// await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens); var totalTokens = tokenUsage.TotalTokens ?? 0;
// } if (totalTokens > 0)
// } {
await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
}
}
await response.WriteAsJsonAsync(data, cancellationToken); await response.WriteAsJsonAsync(data, cancellationToken);
} }
/// <summary>
/// OpenAi 响应-流式
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
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<IOpenAiResponseService>(modelDescribe.HandlerName);
await foreach (var result in chatService.ResponsesStreamAsync(modelDescribe, request, cancellationToken))
{
//todo 倍率
// result.Item2.SupplementalMultiplier(modelDescribe.Multiplier);
yield return result;
}
}
/// <summary> /// <summary>
/// OpenAi响应-流式-缓存处理 /// OpenAi响应-流式-缓存处理
/// </summary> /// </summary>
@@ -776,7 +719,11 @@ public class AiGateWayManager : DomainService
response.Headers.TryAdd("Cache-Control", "no-cache"); response.Headers.TryAdd("Cache-Control", "no-cache");
response.Headers.TryAdd("Connection", "keep-alive"); response.Headers.TryAdd("Connection", "keep-alive");
var completeChatResponse = OpenAiResponsesAsync(request, cancellationToken); var modelDescribe = await GetModelAsync(ModelApiTypeEnum.Response, request.Model);
var chatService =
LazyServiceProvider.GetRequiredKeyedService<IOpenAiResponseService>(modelDescribe.HandlerName);
var completeChatResponse = chatService.ResponsesStreamAsync(modelDescribe,request, cancellationToken);
ThorUsageResponse? tokenUsage = null; ThorUsageResponse? tokenUsage = null;
try try
{ {
@@ -785,12 +732,14 @@ public class AiGateWayManager : DomainService
//message_start是为了保底机制 //message_start是为了保底机制
if (responseResult.Item1.Contains("response.completed")) if (responseResult.Item1.Contains("response.completed"))
{ {
JObject obj = JObject.FromObject(responseResult.Item2); var obj = responseResult.Item2!.Value;
int inputTokens = (int?)obj["response"]?["usage"]?["input_tokens"] ?? 0; int inputTokens = obj.GetPath("response","usage","input_tokens").GetInt();
int outputTokens = (int?)obj["response"]?["usage"]?["output_tokens"] ?? 0; int outputTokens = obj.GetPath("response","usage","output_tokens").GetInt();
inputTokens=Convert.ToInt32(inputTokens * modelDescribe.Multiplier);
outputTokens=Convert.ToInt32(outputTokens * modelDescribe.Multiplier);
tokenUsage = new ThorUsageResponse tokenUsage = new ThorUsageResponse
{ {
PromptTokens = inputTokens, PromptTokens =inputTokens,
InputTokens = inputTokens, InputTokens = inputTokens,
OutputTokens = outputTokens, OutputTokens = outputTokens,
CompletionTokens = outputTokens, CompletionTokens = outputTokens,
@@ -839,7 +788,7 @@ public class AiGateWayManager : DomainService
} }
#region Anthropic格式Http响应 #region Http响应
private static readonly byte[] EventPrefix = "event: "u8.ToArray(); private static readonly byte[] EventPrefix = "event: "u8.ToArray();
private static readonly byte[] DataPrefix = "data: "u8.ToArray(); private static readonly byte[] DataPrefix = "data: "u8.ToArray();