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 System.Net; using VideoAnalysisCore.AICore.GPT.DeepSeek; using System.Text.Json; namespace VideoAnalysisCore.AICore.GPT.ChatGPT { public class ChatGPTClient { public static string Host = AppCommon.Config.ChatGpt.ChatGpt.Host; public static string ApiKey = AppCommon.Config.ChatGpt.ChatGpt.ApiKey; private readonly IHttpClientFactory _httpClientFactory; private readonly RedisManager redisManager; public ChatGPTClient(IHttpClientFactory httpClientFactory, RedisManager redisManager) { _httpClientFactory = httpClientFactory; this.redisManager = redisManager; } private HttpResponseMessage PostJsonStream(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.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey); client.Timeout = TimeSpan.FromSeconds(60 * 20);//超时时间20分钟 client.DefaultRequestVersion = HttpVersion.Version20; client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; client.DefaultRequestHeaders.ConnectionClose = true; var request = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri); request.Content = new StringContent(json, Encoding.UTF8, "application/json"); return client.Send(request, HttpCompletionOption.ResponseHeadersRead); } 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); } /// /// ChatSSE[流式传输 更稳定] /// /// /// Return HttpResponseMessage for SSE public async Task<(Usage u, string res)?> ChatSSE(ChatRequest chatReq) { var requestBody = chatReq.ToJson(); PostJsonStream: var chatResp = PostJsonStream("v1/chat/completions", requestBody); if (!chatResp.IsSuccessStatusCode) { Console.WriteLine(DateTime.Now + "=>请求GPT服务器异常 " + chatResp?.StatusCode); Console.WriteLine(await chatResp.Content.ReadAsStringAsync()); goto PostJsonStream; } using var stream = chatResp.Content.ReadAsStream(); using var reader = new StreamReader(stream, Encoding.UTF8); string line; var messageBuilder = new StringBuilder(); var messageBuilder1 = new StringBuilder(); var lastChat = new ChatResSSE(); var splitCount = "data:".Length; var maxLoop = 60 * 100000; int threshold = 0; while (maxLoop > 0) { line = reader.ReadLine(); if (line is null || string.IsNullOrEmpty(line) || line.StartsWith(": keep-alive")) { Thread.Sleep(10); maxLoop--; continue; } else if (line.EndsWith("[DONE]")) { // 表示一条消息结束 string message = messageBuilder.ToString(); string message2 = messageBuilder1.ToString(); messageBuilder.Clear(); messageBuilder1.Clear(); var u = lastChat?.usage; if (u == null || string.IsNullOrEmpty(message)) return null; return (u, message); //return (u, message, message2); } else if (line.StartsWith("data:")) { try { var data = System.Text.Json.JsonSerializer.Deserialize(line.Substring(splitCount).Trim()); lastChat = data; var delta = data?.choices.FirstOrDefault()?.delta; var str = delta?.content; var strReasoning = delta?.reasoning_content; if (!string.IsNullOrEmpty(str)) messageBuilder.Append(str); if (!string.IsNullOrEmpty(strReasoning)) messageBuilder1.Append(strReasoning); var steamCount = messageBuilder.Length + messageBuilder1.Length; if (++threshold % 30 == 0) redisManager.SetTaskProgress(chatReq.taskId, "steam=>" + steamCount); } catch (Exception e) { Console.WriteLine("异常 ChatSSE=>"); Console.WriteLine(line); Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); } } } Console.WriteLine(DateTime.Now + "=>AI请求超时 " + chatReq.taskId); return null; } /// /// 请求AI /// /// 返回JSON类型 /// 任务id /// 提示词 /// 任务类型 /// GPT版本 /// 最大token 不设置默认最大值 16000/8000 /// /// public async Task ChatAsync(string task, string postMessages, string title, string model =null, int max_tokens = -1) { Message[] messageArr = [ new Message(postMessages,"user"), ]; messageArr = messageArr.Where(s => s != null).ToArray(); var chatRep = new ChatRequest { taskId = task, model = model ?? ChatGPTType.GPT5_mini, max_tokens = 8000, stream = true, temperature = 0.2f, messages = messageArr }; if (max_tokens != -1) chatRep.max_tokens = max_tokens; var tryCount = 10; while (tryCount-- > 0) { try { var time = title + DateTime.Now.ToString("MMddHHmmss"); var redisCached = new object[2] { chatRep, null }; redisManager.SetTaskGPTCached(task, time, chatRep); var chatResp = await Chat(chatRep); var chatResContent = chatResp?.res; if (string.IsNullOrEmpty(chatResContent)) throw new Exception("GPT返回message无效结果"); if (chatResp != null) { redisCached[1] = new object[] { chatResp.Value.res, chatResp.Value.u, chatResp.Value }; redisManager.SetTaskGPTCached(task, time, redisCached); } chatResContent = chatResContent?.ExtractJsonStrings()?.FirstOrDefault(); chatResContent = chatResContent?.Replace("\n", ""); chatResContent = chatResContent?.Replace("```json", ""); chatResContent = chatResContent?.Replace("```", ""); chatResContent = chatResContent?.Replace("}{", "},{"); chatResContent = chatResContent?.Replace("}|{", "},{"); chatResContent = chatResContent?.Trim(); if (string.IsNullOrEmpty(chatResContent)) throw new Exception("ChatGPT返回结果无有效JSON"); var startsStr = typeof(T).IsArray ? "[" : "{"; var endStr = typeof(T).IsArray ? "]" : "}"; if (!chatResContent.StartsWith(startsStr)) chatResContent = startsStr + chatResContent; if (!chatResContent.EndsWith(endStr)) chatResContent = chatResContent + endStr; var options = new JsonSerializerOptions { // 允许解析不严格符合 JSON 规范的字符串 AllowTrailingCommas = true, // 处理不匹配的 JSON 字符 ReadCommentHandling = JsonCommentHandling.Skip }; var questionRes = System.Text.Json.JsonSerializer.Deserialize(chatResContent, options); if (questionRes is null) throw new Exception("ChatGPT返回无效结果"); return questionRes; } catch (Exception ex) { Console.WriteLine(ex.Message); Console.WriteLine(DateTime.Now + $"=>ChatGPT结果解析错误 重试剩余{tryCount}"); } } throw new Exception(DateTime.Now + "=>ChatGPT请求失败次数过多!!!"); } /// /// Chat /// /// /// Return HttpResponseMessage for SSE public async Task<(Usage u, string res)?> Chat(ChatRequest chatReq) { chatReq.modalities =null; chatReq.max_tokens = null; chatReq.top_p = null; if (chatReq.stream) return await ChatSSE(chatReq); var requestBody = chatReq.ToJson(); var chatResp = await PostJsonStreamAsync("v1/chat/completions", requestBody); var res = await chatResp.Content.ReadFromJsonAsync(); var chatResContent = res?.choices.FirstOrDefault()?.message.content.Trim(); if (res is null || res.error != null) throw new Exception($" ChatGPT模型返回异常 返回参数: " + $" {res?.ToJson()}"); if (string.IsNullOrEmpty(chatResContent)) return null; return (res.usage, chatResContent); } private async Task 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.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AppCommon.Config.ChatGpt.ChatGpt.ApiKey); client.Timeout = TimeSpan.FromSeconds(60 *20);//超时时间20分钟 client.DefaultRequestVersion = HttpVersion.Version20; client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; client.DefaultRequestHeaders.ConnectionClose = true; 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); } } }