diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/MessageInputDto.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/MessageInputDto.cs index 8c01b62c..2ce6c8fb 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/MessageInputDto.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/MessageInputDto.cs @@ -5,7 +5,7 @@ namespace Yi.Framework.AiHub.Application.Contracts.Dtos; public class MessageInputDto { - public string Content { get; set; } + public string? Content { get; set; } public string Role { get; set; } public string ModelId { get; set; } public string? Remark { get; set; } diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/ChatCompletionsInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/ChatCompletionsInput.cs index 6a2073d2..c75e604f 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/ChatCompletionsInput.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/ChatCompletionsInput.cs @@ -9,9 +9,9 @@ public class ChatCompletionsInput public string? Prompt { get; set; } public string Model { get; set; } - public decimal Temperature { get; set; } + public decimal? Temperature { get; set; } - public int max_tokens { get; set; } + public int? max_tokens { get; set; } } public class OpenAiMessage diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/SendMessageOutputDto.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/SendMessageOutputDto.cs index 542e7f55..7b73c9ee 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/SendMessageOutputDto.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/SendMessageOutputDto.cs @@ -56,7 +56,7 @@ public class Choice /// /// 结束原因,可能为空 /// - public string FinishReason { get; set; } + public string? FinishReason { get; set; } /// /// 内容过滤结果 diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiChatService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiChatService.cs index 278a4997..5508d6de 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiChatService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiChatService.cs @@ -126,7 +126,7 @@ public class AiChatService : ApplicationService } //ai网关代理httpcontext - await _aiGateWayManager.CompleteChatForHttpContextAsync(_httpContextAccessor.HttpContext, input.Model, history, + await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input.Model, history, CurrentUser.Id, input.SessionId, cancellationToken); } } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs index 9c6c687c..3f0c443f 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs @@ -55,9 +55,18 @@ public class OpenApiService : ApplicationService } } - //ai网关代理httpcontext - await _aiGateWayManager.CompleteChatForHttpContextAsync(_httpContextAccessor.HttpContext, input.Model, history, - userId, null, cancellationToken); + if (input.Stream) + { + //ai网关代理httpcontext + await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input.Model, + history, + userId, null, cancellationToken); + } + else + { + await _aiGateWayManager.CompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext,input.Model, history, userId, null, + cancellationToken); + } } /// @@ -76,7 +85,7 @@ public class OpenApiService : ApplicationService Owned_by = "organization-owner", Permission = new List() }).ToListAsync(); - + return new ModelGetOutput() { Data = data diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/IChatService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/IChatService.cs index 6bd0b422..fa023149 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/IChatService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/IChatService.cs @@ -5,6 +5,23 @@ namespace Yi.Framework.AiHub.Domain.AiChat; public interface IChatService { - public IAsyncEnumerable CompleteChatAsync(AiModelDescribe aiModelDescribe, List messages, + /// + /// 聊天完成-流式 + /// + /// + /// + /// + /// + public IAsyncEnumerable CompleteChatStreamAsync(AiModelDescribe aiModelDescribe, List messages, + CancellationToken cancellationToken); + + /// + /// 聊天完成-非流式 + /// + /// + /// + /// + /// + public Task CompleteChatAsync(AiModelDescribe aiModelDescribe, List messages, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureChatService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureChatService.cs index efa7b790..a4853c68 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureChatService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureChatService.cs @@ -12,7 +12,7 @@ public class AzureChatService : IChatService { } - public async IAsyncEnumerable CompleteChatAsync(AiModelDescribe aiModelDescribe, + public async IAsyncEnumerable CompleteChatStreamAsync(AiModelDescribe aiModelDescribe, List messages, [EnumeratorCancellation] CancellationToken cancellationToken) { @@ -62,4 +62,41 @@ public class AzureChatService : IChatService } } } + + public async Task CompleteChatAsync(AiModelDescribe aiModelDescribe, + List messages, CancellationToken cancellationToken) + { + var endpoint = new Uri(aiModelDescribe.Endpoint); + + var deploymentName = aiModelDescribe.ModelId; + var apiKey = aiModelDescribe.ApiKey; + + AzureOpenAIClient azureClient = new( + endpoint, + new AzureKeyCredential(apiKey), new AzureOpenAIClientOptions() + { + NetworkTimeout = TimeSpan.FromSeconds(600), + }); + + ChatClient chatClient = azureClient.GetChatClient(deploymentName); + + var response = await chatClient.CompleteChatAsync(messages, new ChatCompletionOptions() + { + // MaxOutputTokenCount = 2048 + }, cancellationToken: cancellationToken); + + var output = new CompleteChatResponse + { + TokenUsage = new TokenUsage() + { + OutputTokenCount = response?.Value.Usage?.OutputTokenCount ?? 0, + InputTokenCount = response?.Value.Usage?.InputTokenCount ?? 0, + TotalTokenCount = response?.Value.Usage?.TotalTokenCount ?? 0 + }, + IsFinish = true, + Content = response?.Value.Content.FirstOrDefault()?.Text + }; + + return output; + } } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureRestChatService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureRestChatService.cs index 3bf78c77..efe13e75 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureRestChatService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureRestChatService.cs @@ -14,7 +14,7 @@ public class AzureRestChatService : IChatService { } - public async IAsyncEnumerable CompleteChatAsync(AiModelDescribe aiModelDescribe, + public async IAsyncEnumerable CompleteChatStreamAsync(AiModelDescribe aiModelDescribe, List messages, [EnumeratorCancellation] CancellationToken cancellationToken) { @@ -98,6 +98,11 @@ public class AzureRestChatService : IChatService } } + public Task CompleteChatAsync(AiModelDescribe aiModelDescribe, List messages, CancellationToken cancellationToken) + { + throw new NotImplementedException("暂未实现"); + } + private JObject? MapToJObject(string line) { if (line == "data: [DONE]"||string.IsNullOrWhiteSpace(line) ) diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs index 7692af44..b3f9041b 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs @@ -66,23 +66,78 @@ public class AiGateWayManager : DomainService /// - /// 聊天完成 + /// 聊天完成-流式 /// /// /// /// /// - public async IAsyncEnumerable CompleteChatAsync(string modelId, List messages, + public async IAsyncEnumerable CompleteChatStreamAsync(string modelId, + List messages, [EnumeratorCancellation] CancellationToken cancellationToken) { var modelDescribe = await GetModelAsync(modelId); var chatService = LazyServiceProvider.GetRequiredKeyedService(modelDescribe.HandlerName); - await foreach (var result in chatService.CompleteChatAsync(modelDescribe, messages, cancellationToken)) + await foreach (var result in chatService.CompleteChatStreamAsync(modelDescribe, messages, cancellationToken)) { yield return result; } } + + /// + /// 聊天完成-非流式 + /// + /// + /// + /// + /// + /// + /// + /// + public async Task CompleteChatForStatisticsAsync(HttpContext httpContext, string modelId, + List messages, + Guid? userId = null, + Guid? sessionId = null, + CancellationToken cancellationToken = default) + { + var response = httpContext.Response; + // 设置响应头,声明是 json + response.ContentType = "application/json; charset=UTF-8"; + await using var writer = new StreamWriter(response.Body, Encoding.UTF8, leaveOpen: true); + var modelDescribe = await GetModelAsync(modelId); + var chatService = LazyServiceProvider.GetRequiredKeyedService(modelDescribe.HandlerName); + var output = await chatService.CompleteChatAsync(modelDescribe, messages, cancellationToken); + if (userId is not null) + { + await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId, + new MessageInputDto + { + Content = messages.LastOrDefault().Content.FirstOrDefault()?.Text ?? string.Empty, + ModelId = modelId, + TokenUsage = output.TokenUsage, + }); + + await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId, + new MessageInputDto + { + Content = output.Content, + ModelId = modelId, + TokenUsage = output.TokenUsage + }); + + await _usageStatisticsManager.SetUsageAsync(userId.Value, modelId, output.TokenUsage.InputTokenCount, + output.TokenUsage.OutputTokenCount); + } + + var body = JsonConvert.SerializeObject(output, new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }); + await writer.WriteLineAsync(body); + await writer.FlushAsync(cancellationToken); + } + /// /// 聊天完成-缓存处理 /// @@ -93,7 +148,7 @@ public class AiGateWayManager : DomainService /// /// /// - public async Task CompleteChatForHttpContextAsync( + public async Task CompleteChatStreamForStatisticsAsync( HttpContext httpContext, string modelId, List messages, @@ -109,7 +164,7 @@ public class AiGateWayManager : DomainService var gateWay = LazyServiceProvider.GetRequiredService(); - var completeChatResponse = gateWay.CompleteChatAsync(modelId, messages, cancellationToken); + var completeChatResponse = gateWay.CompleteChatStreamAsync(modelId, messages, cancellationToken); var tokenUsage = new TokenUsage(); await using var writer = new StreamWriter(response.Body, Encoding.UTF8, leaveOpen: true);