feat: 兼容deepseek协议
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi;
|
||||
using Yi.Framework.AiHub.Domain.AiGateWay.Exceptions;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorDeepSeek.Chats;
|
||||
|
||||
public sealed class DeepSeekChatCompletionsService(ILogger<DeepSeekChatCompletionsService> logger)
|
||||
: IChatCompletionService
|
||||
{
|
||||
public async IAsyncEnumerable<ThorChatCompletionsResponse> CompleteChatStreamAsync(AiModelDescribe options,
|
||||
ThorChatCompletionsRequest chatCompletionCreate,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(options.Endpoint))
|
||||
{
|
||||
options.Endpoint = "https://api.deepseek.com/v1";
|
||||
}
|
||||
|
||||
using var openai =
|
||||
Activity.Current?.Source.StartActivity("OpenAI 对话流式补全");
|
||||
|
||||
var response = await HttpClientFactory.GetHttpClient(options.Endpoint).HttpRequestRaw(
|
||||
options?.Endpoint.TrimEnd('/') + "/chat/completions",
|
||||
chatCompletionCreate, options.ApiKey);
|
||||
|
||||
openai?.SetTag("Model", chatCompletionCreate.Model);
|
||||
openai?.SetTag("Response", response.StatusCode.ToString());
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.PaymentRequired)
|
||||
{
|
||||
throw new PaymentRequiredException();
|
||||
}
|
||||
|
||||
// 如果限流则抛出限流异常
|
||||
if (response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
throw new ThorRateLimitException();
|
||||
}
|
||||
|
||||
// 大于等于400的状态码都认为是异常
|
||||
if (response.StatusCode >= HttpStatusCode.BadRequest)
|
||||
{
|
||||
logger.LogError("OpenAI对话异常 , StatusCode: {StatusCode} ", response.StatusCode);
|
||||
|
||||
throw new BusinessException("OpenAI对话异常", response.StatusCode.ToString());
|
||||
}
|
||||
|
||||
using var stream = new StreamReader(await response.Content.ReadAsStreamAsync(cancellationToken));
|
||||
|
||||
using StreamReader reader = new(await response.Content.ReadAsStreamAsync(cancellationToken));
|
||||
string? line = string.Empty;
|
||||
var first = true;
|
||||
var isThink = false;
|
||||
while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null)
|
||||
{
|
||||
line += Environment.NewLine;
|
||||
|
||||
if (line.StartsWith('{'))
|
||||
{
|
||||
logger.LogInformation("OpenAI对话异常 , StatusCode: {StatusCode} Response: {Response}", response.StatusCode,
|
||||
line);
|
||||
|
||||
throw new BusinessException("OpenAI对话异常", line);
|
||||
}
|
||||
|
||||
if (line.StartsWith(OpenAIConstant.Data))
|
||||
line = line[OpenAIConstant.Data.Length..];
|
||||
|
||||
line = line.Trim();
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
if (line == OpenAIConstant.Done)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.StartsWith(':'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var result = JsonSerializer.Deserialize<ThorChatCompletionsResponse>(line,
|
||||
ThorJsonSerializer.DefaultOptions);
|
||||
|
||||
var content = result?.Choices?.FirstOrDefault()?.Delta;
|
||||
|
||||
if (first && string.IsNullOrWhiteSpace(content?.Content) && string.IsNullOrEmpty(content?.ReasoningContent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (first && content.Content == OpenAIConstant.ThinkStart)
|
||||
{
|
||||
isThink = true;
|
||||
//continue;
|
||||
// 需要将content的内容转换到其他字段
|
||||
}
|
||||
|
||||
if (isThink && content.Content.Contains(OpenAIConstant.ThinkEnd))
|
||||
{
|
||||
isThink = false;
|
||||
// 需要将content的内容转换到其他字段
|
||||
//continue;
|
||||
}
|
||||
|
||||
if (isThink)
|
||||
{
|
||||
// 需要将content的内容转换到其他字段
|
||||
foreach (var choice in result.Choices)
|
||||
{
|
||||
choice.Delta.ReasoningContent = choice.Delta.Content;
|
||||
//choice.Delta.Content = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// first = false;
|
||||
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ThorChatCompletionsResponse> CompleteChatAsync(AiModelDescribe options,
|
||||
ThorChatCompletionsRequest chatCompletionCreate,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
using var openai =
|
||||
Activity.Current?.Source.StartActivity("OpenAI 对话补全");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(options.Endpoint))
|
||||
{
|
||||
options.Endpoint = "https://api.deepseek.com/v1";
|
||||
}
|
||||
|
||||
var response = await HttpClientFactory.GetHttpClient(options.Endpoint).PostJsonAsync(
|
||||
options?.Endpoint.TrimEnd('/') + "/chat/completions",
|
||||
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对话异常 , StatusCode: {StatusCode} Response: {Response}", response.StatusCode, error);
|
||||
|
||||
throw new BusinessException("OpenAI对话异常", response.StatusCode.ToString());
|
||||
}
|
||||
|
||||
var result =
|
||||
await response.Content.ReadFromJsonAsync<ThorChatCompletionsResponse>(
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Volo.Abp.Domain;
|
||||
using Yi.Framework.AiHub.Domain.AiGateWay;
|
||||
using Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorAzureDatabricks.Chats;
|
||||
using Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorAzureOpenAI.Chats;
|
||||
using Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorDeepSeek.Chats;
|
||||
using Yi.Framework.AiHub.Domain.Shared;
|
||||
using Yi.Framework.Mapster;
|
||||
|
||||
@@ -25,6 +26,8 @@ namespace Yi.Framework.AiHub.Domain
|
||||
nameof(AzureOpenAiChatCompletionCompletionsService));
|
||||
services.AddKeyedTransient<IChatCompletionService, AzureDatabricksChatCompletionsService>(
|
||||
nameof(AzureDatabricksChatCompletionsService));
|
||||
services.AddKeyedTransient<IChatCompletionService, DeepSeekChatCompletionsService>(
|
||||
nameof(DeepSeekChatCompletionsService));
|
||||
|
||||
//ai模型特殊性兼容处理
|
||||
Configure<SpecialCompatibleOptions>(options =>
|
||||
@@ -36,6 +39,15 @@ namespace Yi.Framework.AiHub.Domain
|
||||
request.Temperature = null;
|
||||
}
|
||||
});
|
||||
options.Handles.Add(request =>
|
||||
{
|
||||
if (request.Model.StartsWith("o3-mini") || request.Model.StartsWith("o4-mini"))
|
||||
{
|
||||
request.MaxCompletionTokens = request.MaxTokens;
|
||||
request.MaxTokens = null;
|
||||
request.Temperature = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user