feat: 完成openai响应接口
This commit is contained in:
@@ -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; }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user