feat: 支持非流式传输功能
This commit is contained in:
@@ -5,7 +5,7 @@ namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
|||||||
|
|
||||||
public class MessageInputDto
|
public class MessageInputDto
|
||||||
{
|
{
|
||||||
public string Content { get; set; }
|
public string? Content { get; set; }
|
||||||
public string Role { get; set; }
|
public string Role { get; set; }
|
||||||
public string ModelId { get; set; }
|
public string ModelId { get; set; }
|
||||||
public string? Remark { get; set; }
|
public string? Remark { get; set; }
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ public class ChatCompletionsInput
|
|||||||
public string? Prompt { get; set; }
|
public string? Prompt { get; set; }
|
||||||
public string Model { 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
|
public class OpenAiMessage
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class Choice
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 结束原因,可能为空
|
/// 结束原因,可能为空
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string FinishReason { get; set; }
|
public string? FinishReason { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 内容过滤结果
|
/// 内容过滤结果
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ public class AiChatService : ApplicationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
//ai网关代理httpcontext
|
//ai网关代理httpcontext
|
||||||
await _aiGateWayManager.CompleteChatForHttpContextAsync(_httpContextAccessor.HttpContext, input.Model, history,
|
await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input.Model, history,
|
||||||
CurrentUser.Id, input.SessionId, cancellationToken);
|
CurrentUser.Id, input.SessionId, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,9 +55,18 @@ public class OpenApiService : ApplicationService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//ai网关代理httpcontext
|
if (input.Stream)
|
||||||
await _aiGateWayManager.CompleteChatForHttpContextAsync(_httpContextAccessor.HttpContext, input.Model, history,
|
{
|
||||||
userId, null, cancellationToken);
|
//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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -76,7 +85,7 @@ public class OpenApiService : ApplicationService
|
|||||||
Owned_by = "organization-owner",
|
Owned_by = "organization-owner",
|
||||||
Permission = new List<string>()
|
Permission = new List<string>()
|
||||||
}).ToListAsync();
|
}).ToListAsync();
|
||||||
|
|
||||||
return new ModelGetOutput()
|
return new ModelGetOutput()
|
||||||
{
|
{
|
||||||
Data = data
|
Data = data
|
||||||
|
|||||||
@@ -5,6 +5,23 @@ namespace Yi.Framework.AiHub.Domain.AiChat;
|
|||||||
|
|
||||||
public interface IChatService
|
public interface IChatService
|
||||||
{
|
{
|
||||||
public IAsyncEnumerable<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages,
|
/// <summary>
|
||||||
|
/// 聊天完成-流式
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="aiModelDescribe"></param>
|
||||||
|
/// <param name="messages"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IAsyncEnumerable<CompleteChatResponse> CompleteChatStreamAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 聊天完成-非流式
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="aiModelDescribe"></param>
|
||||||
|
/// <param name="messages"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages,
|
||||||
CancellationToken cancellationToken);
|
CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ public class AzureChatService : IChatService
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe,
|
public async IAsyncEnumerable<CompleteChatResponse> CompleteChatStreamAsync(AiModelDescribe aiModelDescribe,
|
||||||
List<ChatMessage> messages,
|
List<ChatMessage> messages,
|
||||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -62,4 +62,41 @@ public class AzureChatService : IChatService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe,
|
||||||
|
List<ChatMessage> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@ public class AzureRestChatService : IChatService
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe,
|
public async IAsyncEnumerable<CompleteChatResponse> CompleteChatStreamAsync(AiModelDescribe aiModelDescribe,
|
||||||
List<ChatMessage> messages,
|
List<ChatMessage> messages,
|
||||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -98,6 +98,11 @@ public class AzureRestChatService : IChatService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("暂未实现");
|
||||||
|
}
|
||||||
|
|
||||||
private JObject? MapToJObject(string line)
|
private JObject? MapToJObject(string line)
|
||||||
{
|
{
|
||||||
if (line == "data: [DONE]"||string.IsNullOrWhiteSpace(line) )
|
if (line == "data: [DONE]"||string.IsNullOrWhiteSpace(line) )
|
||||||
|
|||||||
@@ -66,23 +66,78 @@ public class AiGateWayManager : DomainService
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 聊天完成
|
/// 聊天完成-流式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="modelId"></param>
|
/// <param name="modelId"></param>
|
||||||
/// <param name="messages"></param>
|
/// <param name="messages"></param>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async IAsyncEnumerable<CompleteChatResponse> CompleteChatAsync(string modelId, List<ChatMessage> messages,
|
public async IAsyncEnumerable<CompleteChatResponse> CompleteChatStreamAsync(string modelId,
|
||||||
|
List<ChatMessage> messages,
|
||||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var modelDescribe = await GetModelAsync(modelId);
|
var modelDescribe = await GetModelAsync(modelId);
|
||||||
var chatService = LazyServiceProvider.GetRequiredKeyedService<IChatService>(modelDescribe.HandlerName);
|
var chatService = LazyServiceProvider.GetRequiredKeyedService<IChatService>(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;
|
yield return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 聊天完成-非流式
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext"></param>
|
||||||
|
/// <param name="modelId"></param>
|
||||||
|
/// <param name="messages"></param>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <param name="sessionId"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task CompleteChatForStatisticsAsync(HttpContext httpContext, string modelId,
|
||||||
|
List<ChatMessage> 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<IChatService>(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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 聊天完成-缓存处理
|
/// 聊天完成-缓存处理
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -93,7 +148,7 @@ public class AiGateWayManager : DomainService
|
|||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <param name="userId"></param>
|
/// <param name="userId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task CompleteChatForHttpContextAsync(
|
public async Task CompleteChatStreamForStatisticsAsync(
|
||||||
HttpContext httpContext,
|
HttpContext httpContext,
|
||||||
string modelId,
|
string modelId,
|
||||||
List<ChatMessage> messages,
|
List<ChatMessage> messages,
|
||||||
@@ -109,7 +164,7 @@ public class AiGateWayManager : DomainService
|
|||||||
|
|
||||||
|
|
||||||
var gateWay = LazyServiceProvider.GetRequiredService<AiGateWayManager>();
|
var gateWay = LazyServiceProvider.GetRequiredService<AiGateWayManager>();
|
||||||
var completeChatResponse = gateWay.CompleteChatAsync(modelId, messages, cancellationToken);
|
var completeChatResponse = gateWay.CompleteChatStreamAsync(modelId, messages, cancellationToken);
|
||||||
var tokenUsage = new TokenUsage();
|
var tokenUsage = new TokenUsage();
|
||||||
await using var writer = new StreamWriter(response.Body, Encoding.UTF8, leaveOpen: true);
|
await using var writer = new StreamWriter(response.Body, Encoding.UTF8, leaveOpen: true);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user