Learn.VideoAnalysis/VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotClient.cs

358 lines
13 KiB
C#

using VideoAnalysisCore.Common;
using System.Net.Http.Headers;
using System.Text;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using System.Net.Http;
using Newtonsoft.Json;
using System.Net.Http.Json;
using AntDesign;
using OneOf.Types;
using System.Net;
using Azure;
using System.Reflection.PortableExecutable;
using static System.Runtime.InteropServices.JavaScript.JSType;
/// <summary>
/// https://platform.moonshot.cn/docs/api-reference
/// </summary>
namespace VideoAnalysisCore.AICore.ChatGPT.KIMI
{
public class MoonshotClient
{
private readonly ILogger<MoonshotClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
public MoonshotClient(ILogger<MoonshotClient> logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
}
/// <summary>
/// list models
/// </summary>
/// <returns></returns>
public async Task<ModelListResp> ListModels()
{
var response = await GetAsync("/v1/models");
return await ParseResp<ModelListResp>(response);
}
/// <summary>
/// Chat
/// </summary>
/// <param name="requestBody"></param>
/// <returns>Return HttpResponseMessage for SSE</returns>
public async Task<ChatRes?> Chat(string requestBody)
{
var chatResp = await PostJsonStreamAsync("/v1/chat/completions", requestBody);
return await chatResp.Content.ReadFromJsonAsync<ChatRes>();
}
/// <summary>
/// ChatSSE
/// </summary>
/// <param name="chatReq"></param>
/// <returns>Return HttpResponseMessage for SSE</returns>
public async Task<(Usage u, string res)?> ChatSSE(ChatReq chatReq)
{
chatReq.stream = true;
var requestBody = System.Text.Json.JsonSerializer.Serialize(chatReq);
var chatResp = await PostJsonStreamAsync("/v1/chat/completions", requestBody);
using var stream = await chatResp.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream, Encoding.UTF8);
string line;
StringBuilder messageBuilder = new StringBuilder();
ChatResSSE lastChat = new ChatResSSE();
while ((line = await reader.ReadLineAsync()) != null)
{
if (line.EndsWith("[DONE]"))
{
// 表示一条消息结束
string message = messageBuilder.ToString();
messageBuilder.Clear();
var u = lastChat?.choices?.FirstOrDefault()?.usage;
if (u == null || string.IsNullOrEmpty(message))
return null;
return (u, message);
}
else if (line.StartsWith("data:"))
{
try
{
var data = System.Text.Json.JsonSerializer.Deserialize<ChatResSSE>(line.Substring("data:".Length).Trim());
lastChat = data;
var str = data?.choices.FirstOrDefault()?.delta.content;
if (!string.IsNullOrEmpty(str))
messageBuilder.Append(str);
}
catch (Exception e)
{
Console.WriteLine("异常 ChatSSE=>" );
Console.WriteLine(line);
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
}
}
}
return null;
}
/// <summary>
/// Chat
/// </summary>
/// <param name="chatReq"></param>
/// <returns>Return HttpResponseMessage for SSE</returns>
public async Task<(Usage u, string res)?> Chat(ChatReq chatReq)
{
var requestBody = System.Text.Json.JsonSerializer.Serialize(chatReq);
var chatResp = await PostJsonStreamAsync("/v1/chat/completions", requestBody);
var res = await chatResp.Content.ReadFromJsonAsync<ChatRes>();
var chatResContent = res?.choices.FirstOrDefault()?.message.content.Trim();
if (res is null || res.error != null)
throw new Exception($"KIMI模型返回异常 Chat 返回参数: " +
$" {System.Text.Json.JsonSerializer.Serialize(res)}");
if (string.IsNullOrEmpty(chatResContent))
return null;
return (res.usage, chatResContent);
}
/// <summary>
/// 计算token长度
/// </summary>
/// <param name="chatReqText">文本</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<int?> GetAsTiMateTokenCount(string chatReqText)
{
var reqObject = new
{
model = "moonshot-v1-128k",
messages = new List<MessagesItem>()
{
new MessagesItem(chatReqText,"system"),
}
};
var response = await PostJsonStreamAsync("/v1/tokenizers/estimate-token-count", JsonConvert.SerializeObject(reqObject));
var responseText = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
var responseObj = JToken.Parse(responseText);
return responseObj?["data"]?["total_tokens"]?.ToObject<int>();
}
var error = JsonConvert.DeserializeObject<ErrorResponse>(responseText);
_logger.LogError($"{error?.error?.type}: {error?.error?.message}");
throw new Exception($"{error?.error.type}: {error?.error.message}");
}
/// <summary>
/// Get as timate token count
/// </summary>
/// <param name="chatReq"></param>
/// <returns></returns>
public async Task<int?> GetAsTiMateTokenCount(ChatReq chatReq)
{
var chatReqText = JsonConvert.SerializeObject(chatReq);
return await GetAsTiMateTokenCount(chatReqText);
}
/// <summary>
/// List files
/// </summary>
public virtual async Task<FileListResp> ListFiles()
{
var response = await GetAsync("/v1/files");
return await ParseResp<FileListResp>(response);
}
/// <summary>
/// Upload file
/// </summary>
public virtual async Task<FileItem> UploadFile(string filePath)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"{filePath} not found");
}
var client = _httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
var request = new HttpRequestMessage(HttpMethod.Post, $"{Host}/v1/files");
var content = new MultipartFormDataContent
{
{ new StreamContent(File.OpenRead(filePath)), "file", filePath }
};
request.Content = content;
var response = await client.SendAsync(request);
return await ParseResp<FileItem>(response);
}
/// <summary>
/// Upload file stream
/// </summary>
public virtual async Task<FileItem> UploadFileStream(Stream stream, string fileName)
{
var client = _httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
var request = new HttpRequestMessage(HttpMethod.Post, $"{Host}/v1/files");
var content = new MultipartFormDataContent
{
{ new StreamContent(stream), "file", fileName }
};
request.Content = content;
var response = await client.SendAsync(request);
return await ParseResp<FileItem>(response);
}
/// <summary>
/// Get file content
/// </summary>
public virtual async Task<FileContent> GetFileContent(string fileId)
{
var response = await GetAsync($"/v1/files/{fileId}/content");
return await ParseResp<FileContent>(response);
}
private async Task<HttpResponseMessage> GetAsync(string path)
{
var client = _httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
return await client.GetAsync(Host + path);
}
private async Task<HttpResponseMessage> PostJsonAsync(string path, string json)
{
var client = _httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
return await client.PostAsync(Host + path, new StringContent(json, Encoding.UTF8, "application/json"));
}
private async Task<HttpResponseMessage> PostJsonStreamAsync(string path, string json)
{
var uriBuilder = new UriBuilder(Host + path);
var maxRestart = 4;
var errorMSG = new Exception[maxRestart];
for (int i = 0; i < maxRestart; i++)
{
try
{
var client = _httpClientFactory.CreateClient();
client.Timeout = TimeSpan.FromSeconds(60 * 20);//超时时间20分钟
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
client.DefaultRequestVersion = HttpVersion.Version20;
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
client.DefaultRequestHeaders.ConnectionClose = true;
//var request = ToHttpRequest(path);
//request.Version = HttpVersion.Version20;
//request.VersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
//request.Content = new StringContent(json, Encoding.UTF8, "application/json");
//return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
var content = new StringContent(json, Encoding.UTF8, "application/json");
return await client.PostAsync(uriBuilder.Uri, content);
}
catch (Exception e)
{
errorMSG[i] = e;
Console.WriteLine("====================[请求异常,重试]====================");
Console.WriteLine(uriBuilder.Uri);
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
Console.WriteLine("==============================================");
}
Thread.Sleep(1000);
}
throw errorMSG.Last(s => s != null);
}
private HttpRequestMessage ToHttpRequest(string path)
{
var request = new HttpRequestMessage();
var uriBuilder = new UriBuilder(Host + path);
request.RequestUri = uriBuilder.Uri;
request.Method = new HttpMethod("POST");
request.Headers.Host = (new Uri(Host)).Host;
return request;
}
/// <summary>
/// Parse response
/// </summary>
private async Task<T> ParseResp<T>(HttpResponseMessage response)
{
var responseText = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
return JsonConvert.DeserializeObject<T>(responseText) ?? default;
}
var error = JsonConvert.DeserializeObject<ErrorResponse>(responseText);
_logger.LogError($"{error?.error.type}: {error?.error.message}");
throw new Exception($"{error?.error.type}: {error?.error.message}");
}
private static string _host = "https://api.moonshot.cn";
public static string Host
{
get
{
if (string.IsNullOrEmpty(_host) && !string.IsNullOrEmpty(AppCommon.Config.ChatGpt.KIMI.Host))
{
_host = AppCommon.Config.ChatGpt.KIMI.Host ?? "";
}
return _host;
}
set
{
_host = value;
}
}
private static string _apiKey = "sk_";
public static string ApiKey
{
get
{
if (string.IsNullOrEmpty(_apiKey) && !string.IsNullOrEmpty(AppCommon.Config.ChatGpt.KIMI.ApiKey))
{
_apiKey = AppCommon.Config.ChatGpt.KIMI.ApiKey ?? "";
}
return _apiKey;
}
set
{
_apiKey = value;
}
}
}
}