feat: 支持codex

This commit is contained in:
ccnetcore
2025-12-11 01:17:31 +08:00
parent 53aa575ad4
commit 433d616b9b
10 changed files with 531 additions and 36 deletions

View File

@@ -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;

View File

@@ -130,7 +130,7 @@ public sealed class OpenAiChatCompletionsService(ILogger<OpenAiChatCompletionsSe
using var openai =
Activity.Current?.Source.StartActivity("OpenAI 对话补全");
var response = await HttpClientFactory.GetHttpClient(options.Endpoint).PostJsonAsync(
var response = await httpClientFactory.CreateClient().PostJsonAsync(
options?.Endpoint.TrimEnd('/') + "/chat/completions",
chatCompletionCreate, options.ApiKey).ConfigureAwait(false);

View File

@@ -0,0 +1,123 @@
using System.Diagnostics;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Yi.Framework.AiHub.Domain.AiGateWay.Exceptions;
using Yi.Framework.AiHub.Domain.Shared.Dtos;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Responses;
namespace Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorCustomOpenAI.Chats;
public class OpenAiResponseService(ILogger<OpenAiResponseService> 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<dynamic>(data,
ThorJsonSerializer.DefaultOptions);
yield return (eventType, result);
}
}
public async Task<OpenAiResponsesOutput> 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<OpenAiResponsesOutput>(
cancellationToken: cancellationToken).ConfigureAwait(false);
return result;
}
}