From 2609f13ea53cdc1d260654f2f39f9b2e37148a3d 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, 21 Oct 2025 15:02:17 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20=E6=96=B0=E7=89=88AI?=
=?UTF-8?q?=E5=88=86=E6=9E=90=E6=B5=81=E7=A8=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Components/Pages/VideoTaskShow.razor.cs | 2 +-
VideoAnalysis/appsettings.json | 4 +-
VideoAnalysisCore/AICore/GPT/BserGPT.cs | 145 +++++++++++-
.../AICore/GPT/ChatGPT/ChatGPTClient.cs | 199 +++++++++++++++-
.../AICore/GPT/ChatGPT/ChatGPTModel.cs | 99 --------
.../AICore/GPT/ChatGPT/ChatGPTType.cs | 19 +-
.../AICore/GPT/ChatGPT/Chat_GPT.cs | 4 +-
.../AICore/GPT/DeepSeek/DeepSeekClient.cs | 88 ++++++-
.../AICore/GPT/DeepSeek/DeepSeekModel.cs | 136 +----------
.../AICore/GPT/DeepSeek/DeepSeek_GPT.cs | 216 +++++++-----------
VideoAnalysisCore/Common/AppCommon.cs | 40 +++-
VideoAnalysisCore/Job/TaskFileClearJob.cs | 53 ++++-
12 files changed, 598 insertions(+), 407 deletions(-)
diff --git a/VideoAnalysis/Components/Pages/VideoTaskShow.razor.cs b/VideoAnalysis/Components/Pages/VideoTaskShow.razor.cs
index 9695a7e..9972ac4 100644
--- a/VideoAnalysis/Components/Pages/VideoTaskShow.razor.cs
+++ b/VideoAnalysis/Components/Pages/VideoTaskShow.razor.cs
@@ -94,7 +94,7 @@ namespace Learn.VideoAnalysis.Components.Pages
StageId = s.First().StageId,
KnowPoint = string.Join(',', s.Select(x => x.KnowPoint))
}).ToArray();
- videoPath = AppCommon.GetVideoPath(nowTask.Id.ToString());
+ videoPath = nowTask.MediaUrl; //AppCommon.GetVideoPath(nowTask.Id.ToString());
if (nowTask.VideoType == AttachmentsInfoType.复习)
{
diff --git a/VideoAnalysis/appsettings.json b/VideoAnalysis/appsettings.json
index dacf58f..ab63f78 100644
--- a/VideoAnalysis/appsettings.json
+++ b/VideoAnalysis/appsettings.json
@@ -42,8 +42,8 @@
"ApiKey": "sk-8BvvhESZIkgUbiaaJhglPxFa4o2X9H3xEv9lXELrWWwGxHWY"
},
"ChatGpt": {
- "Host": "https://api.g4f.icu/",
- //"Host": "https://api.oaibest.com/",
+ //"Host": "https://api.g4f.icu/",
+ "Host": "https://api.oaibest.com/",
"ApiKey": "sk-D15tBln31N7dI9Fi7lds7OySFv5tOEK7DMNsG5rY2E6DCr4s"
},
"DeepSeek": {
diff --git a/VideoAnalysisCore/AICore/GPT/BserGPT.cs b/VideoAnalysisCore/AICore/GPT/BserGPT.cs
index 9b4c914..297dff1 100644
--- a/VideoAnalysisCore/AICore/GPT/BserGPT.cs
+++ b/VideoAnalysisCore/AICore/GPT/BserGPT.cs
@@ -5,9 +5,7 @@ using Whisper.net;
namespace VideoAnalysisCore.AICore.GPT
{
- ///
- /// GPT 接口
- ///
+
public interface IBserGPT
{
///
@@ -29,4 +27,145 @@ namespace VideoAnalysisCore.AICore.GPT
///
public Task GetVideoType(string task);
}
+
+ ///
+ /// 请求数据
+ ///
+ public class ChatRequest
+ {
+ ///
+ /// 对话
+ ///
+ public Message[] messages { get; set; }
+ ///
+ /// 提问种子值[用来确保 相同参数请求尽可能返回相同参数]
+ /// 默认:null
+ /// 此功能处于 Beta 阶段。 如果指定,我们的系统将尽最大努力确定性地采样,这样具有相同 and 参数的重复请求应该返回相同的结果。 无法保证确定性,您应该参考 response 参数来监控后端的变化
+ ///
+ public int? seed { get; set; } = null;
+ ///
+ /// 推理模型 (deepseek-reasoner)
+ ///
+ public string model { get; set; } = "deepseek-reasoner";
+
+ public float? max_tokens { get; set; } = 8000;
+ public float? max_completion_tokens { get; set; } = 8000;
+ ///
+ /// 要使用的采样温度,介于 0 和 2 之间。较高的值(如 0.8)将使输出更加随机,而较低的值(如 0.2)将使其更加集中和确定。 我们通常建议更改此项或同时更改两者。top_p
+ /// 默认为 1
+ /// 联动
+ ///
+ public float? temperature { get; set; } = 0.2f;
+ ///
+ /// 一种替代温度采样的方法,称为原子核采样, 其中,模型考虑具有top_p概率的标记的结果 质量。所以 0.1 表示仅包含前 10% 概率质量的代币 被考
+ /// 建议与联动
+ ///
+ public float? top_p { get; set; } = 0.1f;
+ ///
+ /// 一个对象,用于指定模型必须输出的格式。设置为 enable 结构化输出,确保模型与您提供的 JSON 匹配 图式。
+ ///
+ //public object response_format { get; set; } = new { type = "json_object" };
+ ///
+ /// 流式返回
+ ///
+ public bool stream { get; set; } = true;
+ ///
+ /// 您希望模型为此请求生成的 Output types。 大多数模型都能够生成文本,这是
+ /// 默认设置: ["text"]
+ /// 该模型还可用于生成音频。自 请求此模型同时生成文本和音频响应,您可以 用:gpt-4o-audio-preview["text", "audio"]
+ ///
+ public string modalities { get; set; } = "[\"json\"]";
+ ///
+ /// 任务id
+ ///
+ public string taskId { get; set; }
+ public object stream_options { get; set; } = new { include_usage = true };
+
+ }
+ ///
+ /// GPT 接口
+ ///
+
+
+ ///
+ /// gpt返回值
+ ///
+ public class ChatRes
+ {
+ public string id { get; set; }
+ public string _object { get; set; }
+ public int created { get; set; }
+ public string model { get; set; }
+ public ChatResError error { get; set; }
+ public Choice[] choices { get; set; }
+ public Usage usage { get; set; }
+ ///
+ /// 系统指纹
+ ///
+ public string system_fingerprint { get; set; }
+ }
+ public class Usage
+ {
+ public int prompt_tokens { get; set; }
+ public int completion_tokens { get; set; }
+ public int total_tokens { get; set; }
+ }
+
+
+ public class Choice
+ {
+ public int index { get; set; }
+ public Message message { get; set; }
+ public object logprobs { get; set; }
+ public string finish_reason { get; set; }
+ }
+ public class ChatResError
+ {
+ public string message { get; set; }
+ public string type { get; set; }
+ }
+
+ public class ChatResSSE
+ {
+ public string id { get; set; }
+ public int created { get; set; }
+ ///
+ /// 模型id
+ ///
+ public string model { get; set; }
+ ///
+ /// 对话
+ ///
+ public ChoiceSSE[] choices { get; set; }
+ ///
+ /// token使用情况
+ ///
+ public Usage usage { get; set; }
+ }
+ public class ChoiceSSE
+ {
+ public int index { get; set; }
+ public Message delta { get; set; }
+ public string finish_reason { get; set; }
+ }
+
+ public class Message
+ {
+ public Message()
+ {
+
+ }
+ public Message(string content, string role)
+ {
+ this.role = role;
+ this.content = content;
+ }
+ public string role { get; set; }
+ public string content { get; set; }
+ ///
+ /// 推理内容
+ ///
+ public string reasoning_content { get; set; }
+ public string refusal { get; set; }
+ }
}
diff --git a/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTClient.cs b/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTClient.cs
index e6b3662..06f7b6a 100644
--- a/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTClient.cs
+++ b/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTClient.cs
@@ -7,6 +7,8 @@ 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
@@ -18,10 +20,48 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
public static string ApiKey = AppCommon.Config.ChatGpt.ChatGpt.ApiKey;
private readonly IHttpClientFactory _httpClientFactory;
+ private readonly RedisManager redisManager;
- public ChatGPTClient(IHttpClientFactory httpClientFactory)
+ 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);
}
///
@@ -31,9 +71,159 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
/// Return HttpResponseMessage for SSE
public async Task<(Usage u, string res)?> ChatSSE(ChatRequest chatReq)
{
- throw new Exception($"未实现");
+ 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
///
@@ -41,6 +231,11 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
/// 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();
diff --git a/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTModel.cs b/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTModel.cs
index 569b6d5..352b140 100644
--- a/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTModel.cs
+++ b/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTModel.cs
@@ -6,104 +6,5 @@ using System.Threading.Tasks;
namespace VideoAnalysisCore.AICore.GPT.ChatGPT
{
- ///
- /// 请求数据
- ///
- public class ChatRequest
- {
- ///
- /// 对话
- ///
- public Message[] messages { get; set; }
- ///
- /// 提问种子值[用来确保 相同参数请求尽可能返回相同参数]
- /// 默认:null
- /// 此功能处于 Beta 阶段。 如果指定,我们的系统将尽最大努力确定性地采样,这样具有相同 and 参数的重复请求应该返回相同的结果。 无法保证确定性,您应该参考 response 参数来监控后端的变化
- ///
- public int? seed { get; set; } =null;
- public string model { get; set; } = ChatGPTType.GPT4o;
- ///
- /// 要使用的采样温度,介于 0 和 2 之间。较高的值(如 0.8)将使输出更加随机,而较低的值(如 0.2)将使其更加集中和确定。 我们通常建议更改此项或同时更改两者。top_p
- /// 默认为 1
- /// 联动
- ///
- public float temperature { get; set; } = 0.2f;
- ///
- /// 一种替代温度采样的方法,称为原子核采样, 其中,模型考虑具有top_p概率的标记的结果 质量。所以 0.1 表示仅包含前 10% 概率质量的代币 被考
- /// 建议与联动
- ///
- public float top_p { get; set; } = 0.5f;
- public float max_completion_tokens { get; set; } = 5000;
- ///
- /// 一个对象,用于指定模型必须输出的格式。设置为 enable 结构化输出,确保模型与您提供的 JSON 匹配 图式。
- ///
- public object response_format { get; set; } = new { type = "json_object" };
- ///
- /// 流式返回
- ///
- public bool stream { get; set; } = false;
- ///
- /// 您希望模型为此请求生成的 Output types。 大多数模型都能够生成文本,这是
- /// 默认设置: ["text"]
- /// 该模型还可用于生成音频。自 请求此模型同时生成文本和音频响应,您可以 用:gpt-4o-audio-preview["text", "audio"]
- ///
- public string modalities { get; set; } = "[\"json\"]";
- ///
- /// ai引导新话题
- /// 默认-2 范围[-2~2]
- ///
- public int presence_penalty { get; set; } = -2;
-}
- public class Message
- {
- public Message(string content, string role)
- {
- this.role = role;
- this.content = content;
- }
- public string role { get; set; }
- public string content { get; set; }
- public string refusal { get; set; }
- }
-
-
- ///
- /// gpt返回值
- ///
- public class ChatRes
- {
- public string id { get; set; }
- public string _object { get; set; }
- public int created { get; set; }
- public string model { get; set; }
- public ChatResError error { get; set; }
- public Choice[] choices { get; set; }
- public Usage usage { get; set; }
- ///
- /// 系统指纹
- ///
- public string system_fingerprint { get; set; }
- }
-
- public class Usage
- {
- public int prompt_tokens { get; set; }
- public int completion_tokens { get; set; }
- public int total_tokens { get; set; }
- }
-
-
- public class Choice
- {
- public int index { get; set; }
- public Message message { get; set; }
- public object logprobs { get; set; }
- public string finish_reason { get; set; }
- }
- public class ChatResError
- {
- public string message { get; set; }
- public string type { get; set; }
- }
}
diff --git a/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTType.cs b/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTType.cs
index 9aba558..3b5b740 100644
--- a/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTType.cs
+++ b/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTType.cs
@@ -8,22 +8,9 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
{
public class ChatGPTType
{
- public static string GPT4oLatest = "chatgpt-4o-latest";
- public static string GPT4o241120 = "gpt-4o-2024-11-20";
- public static string GPT4o240513 = "gpt-4o-2024-05-13";
- ///
- /// GPT-4O 型 -> gpt-4o-2024-08-06
- ///
- public static string GPT4o= "gpt-4o";
-
- public static string GPT4oMini= "gpt-4o-mini";
- public static string GPT4oMini240718 = "gpt-4o-mini-2024-07-18";
-
- public static string GPT4Turbo= "gpt-4-turbo-2024-04-09";
- ///
- /// gpt-4-turbo-preview gpt-4-0125-preview
- ///
- public static string GPT4TurboPreview = "gpt-4-turbo-preview";
+ public static string GPT5_mini = "gpt-5-mini-2025-08-07";
+ public static string GPT5 = "gpt-5-2025-08-07";
+ public static string GPT5_nano = "gpt-5-nano-2025-08-07";
///
diff --git a/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs b/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs
index 37ad969..72dad2e 100644
--- a/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs
+++ b/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs
@@ -198,7 +198,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
messageArr = messageArr.Where(s => s != null).ToArray();
var chatRep = new ChatRequest
{
- model = ChatGPTType.GPT4o241120,
+ model = ChatGPTType.GPT5_mini,
max_completion_tokens = maxTokens,
temperature = 0.2f,
messages = messageArr
@@ -240,6 +240,8 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
//}
return questionRes;
}
+
+
///
/// 访问GPT
///
diff --git a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekClient.cs b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekClient.cs
index 5b2f239..33114ac 100644
--- a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekClient.cs
+++ b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekClient.cs
@@ -12,6 +12,7 @@ using System;
using System.IO;
using VideoAnalysisCore.AICore.GPT.ChatGPT;
using System.Threading.Tasks;
+using System.Text.Json;
namespace VideoAnalysisCore.AICore.GPT.DeepSeek
{
@@ -155,13 +156,13 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
var messageBuilder1 = new StringBuilder();
var lastChat = new ChatResSSE();
var splitCount = "data:".Length;
- var maxLoop = 60*10000;
+ 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(100);
+ Thread.Sleep(10);
maxLoop--;
continue;
}
@@ -210,5 +211,88 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
return null;
}
+
+ ///
+ /// 请求AI
+ ///
+ /// 返回JSON类型
+ /// 任务id
+ /// 提示词
+ /// 任务类型
+ /// GPT版本
+ /// 最大token 不设置默认最大值 16000/8000
+ ///
+ ///
+ public async Task ChatAsync(string task, string postMessages, string title, string model = "deepseek-reasoner", 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,
+ max_tokens = model == "deepseek-reasoner" ? 16000 : 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.reasoning };
+ 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请求失败次数过多!!!");
+ }
+
}
}
\ No newline at end of file
diff --git a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekModel.cs b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekModel.cs
index 3e6df08..0428e5b 100644
--- a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekModel.cs
+++ b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekModel.cs
@@ -6,140 +6,6 @@ using System.Threading.Tasks;
namespace VideoAnalysisCore.AICore.GPT.DeepSeek
{
- ///
- /// 请求数据
- ///
- public class ChatRequest
- {
- ///
- /// 对话
- ///
- public Message[] messages { get; set; }
- ///
- /// 提问种子值[用来确保 相同参数请求尽可能返回相同参数]
- /// 默认:null
- /// 此功能处于 Beta 阶段。 如果指定,我们的系统将尽最大努力确定性地采样,这样具有相同 and 参数的重复请求应该返回相同的结果。 无法保证确定性,您应该参考 response 参数来监控后端的变化
- ///
- public int? seed { get; set; } =null;
- ///
- /// 推理模型 (deepseek-reasoner)
- ///
- public string model { get; set; } = "deepseek-reasoner";
-
- public float max_tokens { get; set; } = 8000;
- ///
- /// 要使用的采样温度,介于 0 和 2 之间。较高的值(如 0.8)将使输出更加随机,而较低的值(如 0.2)将使其更加集中和确定。 我们通常建议更改此项或同时更改两者。top_p
- /// 默认为 1
- /// 联动
- ///
- public float temperature { get; set; } = 0.2f;
- ///
- /// 一种替代温度采样的方法,称为原子核采样, 其中,模型考虑具有top_p概率的标记的结果 质量。所以 0.1 表示仅包含前 10% 概率质量的代币 被考
- /// 建议与联动
- ///
- public float top_p { get; set; } = 0.1f;
- ///
- /// 一个对象,用于指定模型必须输出的格式。设置为 enable 结构化输出,确保模型与您提供的 JSON 匹配 图式。
- ///
- //public object response_format { get; set; } = new { type = "json_object" };
- ///
- /// 流式返回
- ///
- public bool stream { get; set; } = false;
- ///
- /// 您希望模型为此请求生成的 Output types。 大多数模型都能够生成文本,这是
- /// 默认设置: ["text"]
- /// 该模型还可用于生成音频。自 请求此模型同时生成文本和音频响应,您可以 用:gpt-4o-audio-preview["text", "audio"]
- ///
- public string modalities { get; set; } = "[\"json\"]";
- ///
- /// 任务id
- ///
- public string taskId { get; set; }
- public object stream_options { get; set; } = new { include_usage = true };
-}
- public class Message
- {
- public Message()
- {
-
- }
- public Message(string content, string role)
- {
- this.role = role;
- this.content = content;
- }
- public string role { get; set; }
- public string content { get; set; }
- ///
- /// 推理内容
- ///
- public string reasoning_content { get; set; }
- public string refusal { get; set; }
- }
-
-
- ///
- /// gpt返回值
- ///
- public class ChatRes
- {
- public string id { get; set; }
- public string _object { get; set; }
- public int created { get; set; }
- public string model { get; set; }
- public ChatResError error { get; set; }
- public Choice[] choices { get; set; }
- public Usage usage { get; set; }
- ///
- /// 系统指纹
- ///
- public string system_fingerprint { get; set; }
- }
-
- public class Usage
- {
- public int prompt_tokens { get; set; }
- public int completion_tokens { get; set; }
- public int total_tokens { get; set; }
- }
-
-
- public class Choice
- {
- public int index { get; set; }
- public Message message { get; set; }
- public object logprobs { get; set; }
- public string finish_reason { get; set; }
- }
- public class ChatResError
- {
- public string message { get; set; }
- public string type { get; set; }
- }
-
- public class ChatResSSE
- {
- public string id { get; set; }
- public int created { get; set; }
- ///
- /// 模型id
- ///
- public string model { get; set; }
- ///
- /// 对话
- ///
- public ChoiceSSE[] choices { get; set; }
- ///
- /// token使用情况
- ///
- public Usage usage { get; set; }
- }
- public class ChoiceSSE
- {
- public int index { get; set; }
- public Message delta { get; set; }
- public string finish_reason { get; set; }
- }
+
}
diff --git a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs
index a917021..8b32a30 100644
--- a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs
+++ b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs
@@ -20,6 +20,8 @@ using Yitter.IdGenerator;
using VideoAnalysisCore.Common.Expand;
using System.Collections.Generic;
using UserCenter.Model.Enum;
+using Dm.filter;
+using System.Text.RegularExpressions;
namespace VideoAnalysisCore.AICore.GPT.DeepSeek
{
@@ -29,6 +31,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
public class DeepSeek_GPT : IBserGPT
{
private readonly DeepSeekGPTClient chatClient;
+ private readonly ChatGPTClient chatGPTClient;
private readonly Repository criteriaDB;
private readonly RedisManager redisManager;
private readonly Repository videoTaskDB;
@@ -43,9 +46,9 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
///
///
///
- public DeepSeek_GPT(DeepSeekGPTClient moonshotClient, Repository criteria, Repository videoTaskDB,
- Repository knowledgeInfoDB, Repository videoKonwPointDB, SimpLetexClient simpLetexClient,
- Repository videoQuestionDB, OssClient ossClient, Repository videoQuestionKonwDB, RedisManager redisManager)
+ public DeepSeek_GPT(DeepSeekGPTClient moonshotClient, Repository criteria, Repository videoTaskDB,
+ Repository knowledgeInfoDB, Repository videoKonwPointDB, SimpLetexClient simpLetexClient,
+ Repository videoQuestionDB, OssClient ossClient, Repository videoQuestionKonwDB, RedisManager redisManager, ChatGPTClient chatGPTClient)
{
chatClient = moonshotClient;
criteriaDB = criteria;
@@ -57,6 +60,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
this.ossClient = ossClient;
this.videoQuestionKonwDB = videoQuestionKonwDB;
this.redisManager = redisManager;
+ this.chatGPTClient = chatGPTClient;
}
///
/// 获取内容对应的章节
@@ -84,7 +88,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
$" 格式 (方法点Id|方法点名称) " +
$"提供的知识点名称({knows})。";
Console.WriteLine(DateTime.Now + "=>2.开始分析视频内容知识点");
- var konwRes = await ChatAsync(taskInfo.Id.ToString(), knowMessages, "知识点");
+ var konwRes = await chatGPTClient.ChatAsync(taskInfo.Id.ToString(), knowMessages, "知识点");
for (int i = 0; i < konwRes.Count(); i++)
questionRes[i].KnowPoint = konwRes[i].KnowPoint;
@@ -147,7 +151,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
$"字幕列表 {rCaptionArr}。" +
$"输出格式 json字符串 对象格式{fileNameResFormat}";
var task = taskInfo.Id.ToString();
- var fileNameInfoRes = await ChatAsync
+ var fileNameInfoRes = await chatGPTClient.ChatAsync
(task, fileNamePostMessages, "授课章节");
taskInfo.Sections = fileNameInfoRes.授课章节;
await videoTaskDB.AsUpdateable()
@@ -169,20 +173,42 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
//校验结果质量
var thems = questionRes.Adapt().ToJson();
var pptFormat = taskInfo.VideoType == AttachmentsInfoType.复习
- ? "这堂课是习题课,所讲解内容都是试题。"
+ ? "这堂课是习题课,所讲解内容几乎都是试题。"
: string.Empty;
var checkResFormat = """{"Score":打分(number),"Evaluation":评价(string)""";//,"Data":优化后的分段(array)}""";
- var checkMessage = "我为视频的讲解内容做了一些分段,希望你能通读字幕内容后检查下的分段是否符合我的要求?" +
- $"检查这些分段的时间是否合理 与相邻的时间段间隔是否大于30秒?" +
- $"分段的主题内容,知识点分配是否合理符合实际吗?" +
- $"{pptFormat}" +
- $"请给出你的打分(0-100,70分及格)以及打分原因。" +
- $"这是我的分段 {thems}。" +
- $"后续的内容是包含时间戳的视频字幕的固定格式文本。" +
- $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).以下是包含时间的视频字幕文本。字幕列表 {captions.Captions}。" +
- $"最后输出格式为json({checkResFormat})";
- Console.WriteLine(DateTime.Now + "=>3.开始检查视频分段结果");
- return await ChatAsync(taskInfo.Id.ToString(), checkMessage, "结果检查", "deepseek-chat");
+ var checkMessage =
+ $"""
+ 请你担任一位专业的视频内容分析教研老师,擅长评估视频内容的结构和逻辑流暢度。
+ 核心任务: 请根据我提供的【视频分段方案】【完整字幕文本】【完整字幕文本】,对该分段方案进行严谨评估。
+ 评估维度与具体标准:
+ 时间间隔检查(硬性指标)
+ 严格检查每个分段与下一个分段的开始时间之间的间隔是否大于30秒。
+ 输出要求:请列出所有不满足此条件的分段对,并说明具体的时间差。
+ 内容结构与主题合理性:
+ 主题凝聚力:评估单个分段内的内容是否围绕一个清晰、统一的主题展开,是否存在主题混杂或跳跃的情况。
+ 逻辑过渡:评估分段之间的过渡是否自然流畅,后一段是否是前一段内容的合理延伸或转折。
+ 知识点分配:检查分段内的知识点是否与分段有关联,知识点分布是否合理。
+ 综合评分与改进建议:
+ 请基于以上分析,提供一个0-100的综合得分(70分及格)。
+ 详细说明打分理由,并逐条对应到上述评估维度。
+ 输入数据格式说明:
+ 分段方案: {thems}
+ 字幕文本: 格式为说话人:开始秒:结束秒:内容|下一段字幕。完整内容为:{captions.Captions}
+ 输出格式:
+ 输出格式为json: {checkResFormat}
+ """;
+ return await chatGPTClient.ChatAsync(taskInfo.Id.ToString(), checkMessage, "结果检查");
+ //var res = await chatClient.ChatAsync(taskInfo.Id.ToString(), checkMessage, "结果检查");
+
+ //var checkMessage1 = "我为视频的讲解内容做了一些分段,希望你能通读字幕内容后检查下的分段是否符合我的要求?" +
+ // $"检查这些分段的时间是否合理 与相邻的时间段间隔是否大于30秒?" +
+ // $"分段的主题内容,知识点分配是否合理符合实际吗?" +
+ // $"{pptFormat}" +
+ // $"请给出你的打分(0-100,70分及格)以及打分原因。" +
+ // $"这是我的分段 {thems}。" +
+ // $"后续的内容是包含时间戳的视频字幕的固定格式文本。" +
+ // $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).以下是包含时间的视频字幕文本。字幕列表 {captions.Captions}。" +
+ // $"最后输出格式为json({checkResFormat})";
}
///
@@ -223,7 +249,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
$"字幕结束。" +
$"最后请确保输出字幕条数与输入字幕条数一致!!! 如果不一致则重新优化并且确保字幕条数一致!!!!";
//Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id}字幕优化 分段{s}开始...");
- var resData = await ChatAsync(taskInfo.Id.ToString(), postMessages, "优化字幕", "deepseek-chat", 3000);
+ var resData = await chatClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "优化字幕", "deepseek-chat", 3000);
if (resData.Count() != cArr.Count())
{
resData = cStrArr.ToArray();
@@ -276,19 +302,18 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
$"{keyFrameArr}" +
$"完整的课堂标准流程包含以下5个阶段:课程引入/新知讲解/例题精讲/课堂练习/知识总结。" +
$"每个类型的授课阶段允许有多个 例如 多个新知讲解/例题精讲..." +
- $"授课阶段主要由每个知识点或者例题内容的讲解或练习组成。" +
- $"通过授课阶段的主要讲解内容分析出对应的授课阶段内容总结。" +
- $"通过生成的内容总结分析出对应的授课阶段主题。 " +
- $"最后请检查每个授课阶段的时长,不允许出现超出800秒或者低于50秒的授课阶段。" +
- $"如果授课阶段的时长不符合标准那么请合并/拆分附近相邻内容相近的阶段。" +
+ $"1.初步划分阶段:基于PPT变化时间点,将字幕内容分割成时间段。每个时间段的起始和结束应接近这些时间点(例如,以时间点为中心,扩展至内容自然过渡处)。" +
+ $"2.内容分析:对每个时间段,提取主要讲解内容:识别关键词(如“例题”“证明”“练习”“总结”)和内容结构。" +
+ $"3.判断阶段类型:如果内容以解题为主,归类为“例题精讲”;如果涉及新知识讲解,归类为“新知讲解”;以此类推。" +
+ $"4.生成内容总结与主题:内容总结:用1-2句话简述该阶段的核心讲解内容(例如,“通过例题演示柯西不等式在求最值中的应用”)。" +
+ $"5.阶段主题:基于内容总结,提炼一个具体主题(例如,“柯西不等式的基本应用”)。" +
+ $"6.时长检查与调整:计算每个阶段的时长(结束时间减开始时间)。如果阶段时长低于50秒,则合并相邻的类似内容阶段(例如,将两个连续的例题讲解合并为一个阶段),或扩展时间段以确保最低50秒。调整时需保持内容连贯性。" +
+ $"7.输出要求:最终分析结果应列出每个阶段的开始时间、结束时间、阶段类型、主题和内容总结(不包含提示词内容),确保划分合理、无重叠,且时长符合要求。" +
$"输出内容只返回json格式({resFormat})" +
$"字幕格式(开始秒:内容|下一段字幕).以下是包含时间的视频字幕文本。" +
$"字幕列表 {captions.Captions} 字幕结束!";
break;
case AttachmentsInfoType.复习:
-
-
-
postMessages =
$"请通过视频字幕内容分析出视频中课堂的授课阶段。" +
$"课堂内容与{taskInfo.Subject}学科下的{sections}章节相关。" +
@@ -313,7 +338,8 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
}
Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id.ToString()}.开始分析视频内容 {tryCount}");
- return await ChatAsync(taskInfo.Id.ToString(), postMessages, "分析字幕");
+ //return await chatGPTClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "分析字幕");
+ return await chatClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "分析字幕");
}
catch (Exception ex)
{
@@ -325,89 +351,8 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
return null;
}
- ///
- /// 请求AI
- ///
- /// 返回JSON类型
- /// 任务id
- /// 提示词
- /// 任务类型
- /// GPT版本
- /// 最大token 不设置默认最大值 16000/8000
- ///
- ///
- public async Task ChatAsync(string task, string postMessages, string title, string model = "deepseek-reasoner", 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,
- max_tokens = model == "deepseek-reasoner" ? 16000 : 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 chatClient.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.reasoning };
- 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 = 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请求失败次数过多!!!");
- }
-
-
///
@@ -428,6 +373,13 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
.ToDictionary(s => s.Key, s => s.First());
var insertData = new List();
var insertQuestionKonw = new List();
+
+
+ insertData = await videoQuestionDB.AsQueryable()
+ .Where(s => s.VideoTaskId == taskInfo.Id)
+ .Select()
+ .ToListAsync();
+
foreach (var item in farmeArr)
{
var knowInfoArr = videoKnowArr
@@ -451,29 +403,29 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
#if DEBUG
Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id} 提取{knowInfoArr.StartTime}秒试题的试题内容");
- Console.WriteLine(sRes.Result.res.value);
#endif
//var knowArr=JsonSerializer.Serialize(knowInfoArr.Select(s => new { s.KnowPointId, s.KnowPoint }));
- var resFormat = """[{"Type":string(试题类型),"TopicStem":string(试题题干),"QuestionArr":[{"Question":string(子问题),"KnowPointId":(string)知识点ID}]}]""";
+ var resFormat = """[{"Type":string(试题类型),"TopicStem":string(试题题干),"QuestionArr":[{"Question":string(问题小问),"KnowPointId":(string)知识点ID}]}]""";
var postMessages =
$"我将提供一段内容是Markdown格式的试题。" +
$"请提取出其中有效的试题内容(包括 题干,公式试题内提出的问题 )。" +
- $"为每个问题关联上限定范围内的知识点(多个则用逗号分割),知识点格式 (知识点Id|知识点名称)知识点范围[{knowArrStr}]。" +
+ $"为每个问题关联上限定范围内的知识点 知识点格式 (知识点Id|知识点名称,下一个知识点)知识点范围[{knowArrStr}]。" +
$"排除不是试题内容的文字,优化试题排版并且去除题号,尽量保留latex数学公式。" +
- $"如果存在多道题,则需要拆分成为多个试题对象!" +
+ $"如果内容中存在多道题,则需要拆分成为多个试题JSON对象!" +
$"试题的类型约束在 填空题/判断题/选择题/解答题/填空题 范围内。" +
$"如果是有效试题且题干中存在下划线则试题的题型应该是填空题。" +
- $"请检查我提供的字符串内容,如果不能识别知识点则不处理知识点,如不包含问题试题则返回`[]`" +
+ $"请检查我提供的字符串内容,如果不能识别知识点则不处理知识点!" +
+ $"传入的内容可能只是知识点,可能不包含试题,如果不包含问题试题则最终内容返回空数组JSON`[]`" +
$"输出内容只返回json格式为({resFormat})" +
$"以下是试题内容" +
$"`{sRes.Result.res.value}`";
- var resData = await ChatAsync(taskInfo.Id.ToString(), postMessages, "提取试题", "deepseek-chat");
- //var resData = await ChatAsync(taskInfo.Id.ToString(), postMessages, "提取试题");
+ var resData = await chatGPTClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "提取试题");
+ //var resData = await chatClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "提取试题");
if (resData is null || resData.Count() == 0)
break;
foreach (var q in resData)
- {
+ {
var TopicId = YitIdHelper.NextId();
foreach (var qt in q.QuestionArr)
{
@@ -513,8 +465,9 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
}
}
}
-
- if(insertData==null || insertData.Count==0|| insertQuestionKonw.Count==0)
+ insertData = insertData.GroupBy(s => string.Join("", Regex.Matches(s.StartTime+s.TopicStem+s.Question, "[\u4e00-\u9fa5a-zA-Z0-9]+")))
+ .Select(s => s.First()).ToList();
+ if (insertData == null || insertData.Count == 0 || insertQuestionKonw.Count == 0)
return null;
//上传oss 并更新imageUrl
@@ -564,16 +517,24 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
var know = await knowledgeInfoDB.GetFirstAsync(s => s.Course_Id == Course_Id && s.Name == sections);
if (know is null)
throw new Exception("未能找到对应知识点=>" + sections);
+ List? knowledgeInfos = new List();
var kInfo = await knowledgeInfoDB.GetByIdAsync(know.Parent_Id);
- var knowledgeInfos = await knowledgeInfoDB.AsQueryable()
- .ToChildListAsync(s => s.Parent_Id, kInfo.Parent_Id == 0 ? kInfo.Id : kInfo.Parent_Id);
+ try
+ {
+ knowledgeInfos = await knowledgeInfoDB.AsQueryable()
+ .ToChildListAsync(s => s.Parent_Id, kInfo.Parent_Id == 0 ? kInfo.Id : kInfo.Parent_Id);
+ }
+ catch (Exception)
+ {
+ throw new Exception("没有对应的子知识点=>" + kInfo.Name);
+ }
//AI优化字幕
captionsArr = await OptimizeSubtitles(taskInfo, captionsArr, sections);
//合并字幕
var captions = ExpandFunction.GetSpeakerCaptions(captionsArr);
var maxVideoTime = captions?.TimeBase?.LastOrDefault()?.End ?? 0;
- VideoKnowRes[]? questionRes =null;
+ VideoKnowRes[]? questionRes = null;
var tryCount = 20;
while (tryCount-- > 0)
{
@@ -582,11 +543,10 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
if (questionRes is null) continue;
//处理分段 知识点
- var insertData = await GetVideoKnow(questionRes, taskInfo, sections, knowledgeInfos);
+ var insertData = await GetVideoKnow(questionRes, taskInfo, sections, knowledgeInfos);//ChatGPT
//校验结果质量
var checkRes = await VerifySpanQuality(questionRes, taskInfo, captions, sections, Course_Id);
-
if (checkRes != null && checkRes.Score >= 80)
{
//写入知识点
@@ -606,7 +566,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
continue;
}
}
- if (tryCount == 0)
+ if (tryCount == 0)
{
throw new Exception("重试次数过多!");
}
@@ -654,14 +614,14 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
///
///
///
- public async Task GetVideoType(string task)
+ public async Task GetVideoType(string task)
{
var taskId = long.Parse(task);
var taskInfo = await videoTaskDB.AsQueryable()
.Where(s => s.Id == taskId)
.FirstAsync();
- if (taskInfo.VideoType != null)
+ if (taskInfo.VideoType != null)
{
return;
}
@@ -687,7 +647,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
$"输出内容只返回json格式为({resFormat})" +
$"以下是字幕内容" +
$"`{captions.Captions}`";
- var resData = await ChatAsync(taskInfo.Id.ToString(), postMessages, "视频类型", "deepseek-chat");
+ var resData = await chatClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "视频类型", "deepseek-chat");
var res = resData.VideoType.ToEnum();
if (res != null)
{
diff --git a/VideoAnalysisCore/Common/AppCommon.cs b/VideoAnalysisCore/Common/AppCommon.cs
index a48e510..35088fd 100644
--- a/VideoAnalysisCore/Common/AppCommon.cs
+++ b/VideoAnalysisCore/Common/AppCommon.cs
@@ -76,7 +76,13 @@ namespace VideoAnalysisCore.Common
///
///
///
- public static string GetVideoPath(string tid) => $"./video/{tid}/{tid}.mp4";
+ public static string GetVideoPath(string tid) => $"./video/{tid}/task.mp4";
+ ///
+ /// 获取视频PPT路径
+ ///
+ ///
+ ///
+ public static string GetVideoPPTPath(string tid) => $"./video/{tid}/ppt.mp4";
}
///
@@ -84,7 +90,7 @@ namespace VideoAnalysisCore.Common
///
public static class ExpandFunction
{
-
+
static Dictionary FormulaData;
static string FormulaDataKey;
///
@@ -99,21 +105,31 @@ namespace VideoAnalysisCore.Common
///
public static bool DeleteTaskFile(long? taskId)
{
- if(taskId is null || taskId == 0) return false;
+ if (taskId is null || taskId == 0) return false;
var path = taskId.ToString().LocalPath();
- if (!string.IsNullOrEmpty(path) && Directory.Exists(path))
+ string[] filesToDelete = { "ppt.mp4", "task.mp4" };
+
+ foreach (string fileName in filesToDelete)
{
- try
+ string filePath = Path.Combine(path, fileName);
+
+ if (File.Exists(filePath))
{
- Directory.Delete(path, true);
- Console.WriteLine($"已删除缓存文件: {taskId}");
- }
- catch (Exception ex)
- {
- Console.WriteLine($"删除缓存文件 {taskId} 时出错: {ex.Message}");
+ try
+ {
+ File.Delete(filePath);
+ Console.WriteLine($"已成功删除文件: {filePath}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"删除文件 {filePath} 时出错: {ex.Message}");
+ }
}
+ //else
+ //{
+ // Console.WriteLine($"文件不存在,跳过删除: {filePath}");
+ //}
}
- else return false;
return true;
}
diff --git a/VideoAnalysisCore/Job/TaskFileClearJob.cs b/VideoAnalysisCore/Job/TaskFileClearJob.cs
index f871f14..a6b5385 100644
--- a/VideoAnalysisCore/Job/TaskFileClearJob.cs
+++ b/VideoAnalysisCore/Job/TaskFileClearJob.cs
@@ -3,6 +3,7 @@ using Coravel.Invocable;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
@@ -28,7 +29,46 @@ namespace VideoAnalysisCore.Job
{
this.videotaskDB = videotaskDB;
}
- public void DeleteOldCompletedTaskCaches()
+ public void DeleteTaskAllCaches()
+ {
+
+ var startTime = -5;
+ var timeSpan = startTime - 999;
+ // 计算 6 天前已完成任务缓存
+ DateTime twoDaysAgo = DateTime.Now.AddDays(startTime);
+ DateTime endDaysAgo = DateTime.Now.AddDays(timeSpan);
+
+ // 查询 2 天前任务执行完成的记录
+ var completedTasks = videotaskDB.AsQueryable()
+ .Where(t => (
+ //筛选 结束任务 或者 错误任务
+ t.LastEnum == Model.Enum.RedisChannelEnum.结束任务 ||
+ t.ErrorMessage != null
+ )
+ && t.EndTime < twoDaysAgo
+ && t.EndTime > endDaysAgo)
+ .Select(s => s.Id)
+ .ToList();
+
+ // 遍历查询结果,删除缓存文件
+ foreach (var taskId in completedTasks)
+ {
+ var path = taskId.ToString().LocalPath();
+ if (!string.IsNullOrEmpty(path) && Directory.Exists(path))
+ {
+ try
+ {
+ Directory.Delete(path, true);
+ Console.WriteLine($"已删除缓存文件: {taskId}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"删除缓存文件 {taskId} 时出错: {ex.Message}");
+ }
+ }
+ }
+ }
+ public void DeleteTaskVideoCaches()
{
try
{
@@ -44,14 +84,15 @@ namespace VideoAnalysisCore.Job
//筛选 结束任务 或者 错误任务
t.LastEnum == Model.Enum.RedisChannelEnum.结束任务 ||
t.ErrorMessage != null
- )
+ )
&& t.EndTime < twoDaysAgo
&& t.EndTime > endDaysAgo)
+ .Select(s => s.Id)
.ToList();
// 遍历查询结果,删除缓存文件
- foreach (var task in completedTasks)
- ExpandFunction.DeleteTaskFile(task?.Id);
+ foreach (var taskId in completedTasks)
+ ExpandFunction.DeleteTaskFile(taskId);
}
catch (Exception ex)
{
@@ -61,8 +102,8 @@ namespace VideoAnalysisCore.Job
public async Task Invoke()
{
Console.WriteLine($"{DateTime.Now} 执行=>{this.GetType().FullName}");
-
- DeleteOldCompletedTaskCaches();
+ DeleteTaskVideoCaches();
+ DeleteTaskAllCaches();
}
}
}