From 67f3c4fb575fc4a9ba5dabc902df7ae4c2ed1691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Tue, 11 Feb 2025 15:18:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E7=89=87=E6=AE=B5=E7=9F=A5=E8=AF=86=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AICore/GPT/ChatGPT/Chat_GPT.cs | 4 +- .../AICore/GPT/DeepSeek/DeepSeekClient.cs | 40 +++++++---- .../AICore/GPT/DeepSeek/DeepSeek_GPT.cs | 69 +++---------------- .../AICore/GPT/Dto/QuestionRes.cs | 8 +-- 4 files changed, 43 insertions(+), 78 deletions(-) diff --git a/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs b/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs index 1c60f19..034ce00 100644 --- a/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs +++ b/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs @@ -148,9 +148,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT if (questionRes.Length <= 3) throw new Exception("视频分段数量过低 =>" + questionRes.Length); - if (questionRes.Count(s => s.ThemeDetalis == questionRes.First().ThemeDetalis) >= 3) - throw new Exception("视频分段主题重复 =>" + questionRes.First().ThemeDetalis); - + for (int i = 0; i < questionRes.Length; i++) { var item = questionRes[i]; diff --git a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekClient.cs b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekClient.cs index 0b7f300..8c4da59 100644 --- a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekClient.cs +++ b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekClient.cs @@ -10,6 +10,8 @@ using AntDesign; using OneOf.Types; using System.Net; using VideoAnalysisCore.AICore.GPT.KIMI; +using System.Threading; +using System; namespace VideoAnalysisCore.AICore.GPT.DeepSeek { @@ -32,21 +34,25 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek /// /// /// Return HttpResponseMessage for SSE - public async Task<(Usage u, string res)?> Chat(ChatRequest chatReq) + public async Task<(Usage u, string res,string reasoning)?> Chat(ChatRequest chatReq) { if (chatReq.stream) return await ChatSSE(chatReq); var requestBody = System.Text.Json.JsonSerializer.Serialize(chatReq); - var chatResp = await PostJsonStreamAsync("v1/chat/completions", requestBody); + var chatResp = await PostJsonStreamAsync(string.Empty, requestBody); var res1 = await chatResp.Content.ReadAsStringAsync(); + if (res1 is null || string.IsNullOrEmpty(res1)) + throw new Exception($" GPT模型返回空内容 返回参数: " + + $" {res1}"); var res = await chatResp.Content.ReadFromJsonAsync(); if (res is null || res.error != null) - throw new Exception($" ChatGPT模型返回异常 返回参数: " + + throw new Exception($" GPT模型返回异常 返回参数: " + $" {System.Text.Json.JsonSerializer.Serialize(res)}"); var chatResContent = res?.choices.FirstOrDefault()?.message.content.Trim(); + var chatResReasoning = res?.choices.FirstOrDefault()?.message.reasoning_content?.Trim(); if (string.IsNullOrEmpty(chatResContent)) return null; - return (res.usage, chatResContent); + return (res.usage, chatResContent, chatResReasoning); } @@ -67,8 +73,9 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; client.DefaultRequestHeaders.ConnectionClose = true; - var content = new StringContent(json, Encoding.UTF8, "application/json"); - return await client.PostAsync(uriBuilder.Uri, content); + var request = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri); + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); ; + return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); } catch (Exception e) { @@ -93,28 +100,33 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek /// /// /// Return HttpResponseMessage for SSE - public async Task<(Usage u, string res)?> ChatSSE(ChatRequest chatReq) + public async Task<(Usage u, string res, string reasoning)?> ChatSSE(ChatRequest chatReq) { chatReq.stream = true; var requestBody = System.Text.Json.JsonSerializer.Serialize(chatReq); - var chatResp = await PostJsonStreamAsync("/v1/chat/completions", requestBody); + var chatResp = await PostJsonStreamAsync(string.Empty, requestBody); using var stream = await chatResp.Content.ReadAsStreamAsync(); 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; - while ((line = await reader.ReadLineAsync()) != null) + while (true) { - if (line.EndsWith("[DONE]")) + line = await reader.ReadLineAsync(); + if (line is null || string.IsNullOrEmpty(line)) + throw new Exception("AI返回无效内容 =>Null/Empty"); + else if (line.EndsWith("[DONE]")) { // 表示一条消息结束 string message = messageBuilder.ToString(); + string message2 = messageBuilder.ToString(); messageBuilder.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:")) { @@ -122,9 +134,13 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek { var data = System.Text.Json.JsonSerializer.Deserialize(line.Substring(splitCount).Trim()); lastChat = data; - var str = data?.choices.FirstOrDefault()?.delta.content; + 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); } catch (Exception e) { diff --git a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs index 4a5c50f..60a730d 100644 --- a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs +++ b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs @@ -64,64 +64,30 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek var maxVideoTime = captions?.TimeBase?.LastOrDefault()?.End ?? 0; var criteriaBuilder = new StringBuilder(); - //var resFormat = """[{"StartTime":开始秒(number),"EndTime":结束秒(number),"Section":章节(string),"Theme":主题(string),"ThemeDetalis":主题详情(string),"Content":内容总结(string)}]"""; - var resFormat = """[{"StartTime":开始秒(number),"Section":章节(string),"Theme":主题(string),"ThemeDetalis":主题详情(string),"Content":内容总结(string)}]"""; + var resFormat = """[{"StartTime":开始秒(number),"Theme":主题(string),"KnowPoint":主题方法点(string),"KnowPointId":主题方法点Id(string)"Content":内容总结(string)}]"""; var know = await knowledgeInfoDB.GetFirstAsync(s => s.Name == fileNameInfoRes.授课章节); var knowledgeInfos = await knowledgeInfoDB.AsQueryable().ToChildListAsync(s => s.Parent_Id, know.Id); - var knows = string.Join(',', knowledgeInfos.Select(s => s.Name)); - + var knows = string.Join(',', knowledgeInfos.Select(s => s.Id + "|"+ s.Name)); var postMessages = $"你的任务是分析视频字幕内容并提取出中国高考考试试题方法点,然后根据步骤分析出知识片段" + $"按以下步骤完成:" + $"1.识别方法点:提取字幕内容中与{subject}考试属于{fileNameInfoRes.授课章节}章节相关的方法点。" + - $"2.分析总结:基于提取出的方法点名称来匹配我提供的方法点名称" + - $"提供的方法点名称({fileNameInfoRes.授课章节}的基本概念,{fileNameInfoRes.授课章节}的练习与应用,{fileNameInfoRes.授课章节}的例题讲解,{knows})。" + - $"3.关联合并知识内容相似的知识点来合并为知识片段。" + - $"知识片段使用关联知识点中的最小开始时间主题为关联知识点的主题分析,内容总结为关联知识点的内容总结分析。" + - $"4.基于'知识片段'的'内容总结'加上'主题'来分析这个片段对主题的讲解内容为新的主题 例如(数列的基本概念)。" + - $"5.分配空余未使用的时间段到内容相近的知识片段时间区间来获取更加详细的上下文,但是请避免知识片段之间时间重合。" + - $"输入:包含时间戳的视频字幕文本。" + - $"以下是包含时间的视频字幕文本。" + - $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" + - $"输出格式({resFormat})"; - postMessages = - $"你现在需要处理用户提供的视频字幕内容,从中提取与中国高考数学考试{fileNameInfoRes.授课章节}相关的方法点,并按照指定的步骤进行分析。首先,你得仔细阅读用户的需求,确保每一步都正确执行" + - $"用户的需求:" + - $"1.要求识别字幕中的方法点,这些方法点需要属于{fileNameInfoRes.授课章节}章节。需要你通读字幕,找到这些关键词出现的地方,并记录对应的时间戳" + - $"2.是根据提取的方法点名称匹配用户提供的列表。例如,如果字幕中提到“递增数列”或“通项公式”,你需要确认这些是否在用户给出的方法点列表中,并将它们归类到正确的名称下" + - $"用户提供的方法点名称({fileNameInfoRes.授课章节}的基本概念,{fileNameInfoRes.授课章节}的练习与应用,{fileNameInfoRes.授课章节}的例题讲解,{knows})。" + - $"3.关联合并相似的知识点,形成知识片段。比如,如果多个时间段都讨论数列的基本概念,你需要将它们合并,并选择最早的时间作为开始时间。同时,需要总结这些片段的内容,并生成新的主题名称,如“数列的基本概念”。" + - $"如果多个片段都涉及数列的定义和例子,可能合并为“数列的基本概念”" + - $"4.分配未使用的时间段到相近的知识片段,避免时间重叠。这可能需要检查是否有剩余的时间段未被归类,并将其合并到合适的知识片段中,以丰富上下文" + + $"2.关联合并知识内容相似的知识点来合并为知识片段。" + + $"3.分配空余未使用的时间段到内容相近的知识片段时间区间来获取更加详细的上下文,但是请避免知识片段之间时间重合。" + + $"4.分析总结:基于提取出的知识片段内容称来匹配我提供的知识点,来作为片段的知识点(请确保匹配的知识点是我提供的,否者片段知识点值为空字符串)" + + $"提供的方法点名称(-1|{fileNameInfoRes.授课章节}的基本概念,-2|{fileNameInfoRes.授课章节}的练习与应用,-3|{fileNameInfoRes.授课章节}的例题讲解,{knows})。 格式 (方法点Id|方法点名称)" + $"输入:包含时间戳的视频字幕文本。" + $"以下是包含时间的视频字幕文本。" + $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" + $"输出格式({resFormat})"; + Console.WriteLine("=>开始分析视频内容"); + var questionRes = await ChatAsync(task, postMessages, null); - //var postMessages = - // $"你的任务是分析视频字幕内容,精准提取出与中国高考数学考试相关的试题方法点。" + - // $"按以下步骤完成:" + - // $"1.准确识别方法点:从字幕中提取与考试紧密相关的方法点,尤其关注与给定方法点类别相关的内容" + - // $"2.深入分析总结:依据给定的方法点类别进行约束,确定提取出的方法点所属类别。" + - // $"给定方法点包括(基本概念,课堂练习,例题讲解,解题技巧...)。" + - // $"3.细致分类方法点:按照学科方法点进行分类,具体细化到特定章节与主题,格式为 “章节:具体章节名称 主题:具体主题名称" + - // $"4.合理合并相似方法点为内容片段,确保内容的连贯性和逻辑性" + - // $"5.关联每个内容片段的方法点所有的时间并结构化输出。" + - // $"6.基于内容片段的内容总结加上主题来分析这个片段对主题的讲解内容为新的主题 例(数列的基本概念)。" + - // $"尽可能延内容片段时间区间,以获取详细的方法点上下文。" + - // $"输入:包含时间戳的视频字幕文本。输出格式:开始秒,结束秒,主题,内容总结" ; - //var postM2 = $"以下是包含时间的视频字幕文本。" + - // $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" + - // $"返回固定的JSON格式({resFormat})"; - //var questionRes = await ChatAsync(task, postMessages, postM2, resFormat); if (questionRes.Length <= 3) throw new Exception("视频分段数量过低 =>" + questionRes.Length); - if (questionRes.Count(s => s.ThemeDetalis == questionRes.First().ThemeDetalis) >= 3) - throw new Exception("视频分段主题重复 =>" + questionRes.First().ThemeDetalis); - for (int i = 0; i < questionRes.Length; i++) { var item = questionRes[i]; @@ -134,21 +100,6 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek await RedisExpand.Redis .HMSetAsync(RedisExpandKey.Task(task), "VideoKnows", questionRes); - //var postMessages1 = - // $"你的任务是分析json内容并合并含义相似的主题为新的主题" + - // $"按以下步骤完成:" + - // $"1.合理合并主题字段重复相似的对象为新的json对象,确保内容的连贯性和逻辑性。" + - // $"2.合并对象属性持续时间低于60秒的对象" + - // $"3.结构化输出。" + - // $"输入:json对象 包含总结开始秒,结束秒,持续时间,主题,章节,内容总结" + - // $"以下是包含json内容的文本。" + - // $" {JsonSerializer.Serialize(questionRes)}" + - // $"返回固定的JSON格式({resFormat})"; - - - //var questionRes1 = await ChatAsync(task, postMessages1, resFormat); - ////questionRes1 = MergeRes(questionRes1).ToArray(); - var gptRes = new TaskRes(captions); await RedisExpand.Redis .HMSetAsync(RedisExpandKey.Task(task), "ChatAnalysis", gptRes); @@ -166,7 +117,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek var chatRep = new ChatRequest { model = model, - stream = model== "deepseek-reasoner", + //stream = model== "deepseek-reasoner", max_tokens = maxTokens, temperature = 0.2f, messages = messageArr @@ -178,7 +129,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek if (string.IsNullOrEmpty(chatResContent)) throw new Exception("GPT返回message无效结果"); if (chatResp != null) - RedisExpand.SetTaskGPTCached(task, new object[] { chatResp.Value.res, chatResp.Value.u }); + RedisExpand.SetTaskGPTCached(task, new object[] { chatResp.Value.res, chatResp.Value.u, chatResp.Value.reasoning }); chatResContent = chatResContent?.Replace("字幕内容", "课堂情况"); chatResContent = chatResContent?.Replace("\n", ""); diff --git a/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs b/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs index 70e1349..4eef506 100644 --- a/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs +++ b/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs @@ -26,13 +26,13 @@ namespace VideoAnalysisCore.AICore.GPT.Dto /// public string? Theme { get; set; } /// - /// 主题详情 + /// 知识点 /// - public string? ThemeDetalis { get; set; } + public string? KnowPoint { get; set; } /// - /// 章节 + /// 知识点ID /// - public string? Section { get; set; } + public string? KnowPointId { get; set; } /// /// 内容总结 ///