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;
|
||||||
using Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorAzureDatabricks.Chats;
|
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.ThorAzureOpenAI.Chats;
|
||||||
|
using Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorDeepSeek.Chats;
|
||||||
using Yi.Framework.AiHub.Domain.Shared;
|
using Yi.Framework.AiHub.Domain.Shared;
|
||||||
using Yi.Framework.Mapster;
|
using Yi.Framework.Mapster;
|
||||||
|
|
||||||
@@ -25,6 +26,8 @@ namespace Yi.Framework.AiHub.Domain
|
|||||||
nameof(AzureOpenAiChatCompletionCompletionsService));
|
nameof(AzureOpenAiChatCompletionCompletionsService));
|
||||||
services.AddKeyedTransient<IChatCompletionService, AzureDatabricksChatCompletionsService>(
|
services.AddKeyedTransient<IChatCompletionService, AzureDatabricksChatCompletionsService>(
|
||||||
nameof(AzureDatabricksChatCompletionsService));
|
nameof(AzureDatabricksChatCompletionsService));
|
||||||
|
services.AddKeyedTransient<IChatCompletionService, DeepSeekChatCompletionsService>(
|
||||||
|
nameof(DeepSeekChatCompletionsService));
|
||||||
|
|
||||||
//ai模型特殊性兼容处理
|
//ai模型特殊性兼容处理
|
||||||
Configure<SpecialCompatibleOptions>(options =>
|
Configure<SpecialCompatibleOptions>(options =>
|
||||||
@@ -36,6 +39,15 @@ namespace Yi.Framework.AiHub.Domain
|
|||||||
request.Temperature = null;
|
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