优化 新版AI分析流程

This commit is contained in:
小肥羊 2025-10-21 15:02:17 +08:00
parent 383d0b4c32
commit 2609f13ea5
12 changed files with 598 additions and 407 deletions

View File

@ -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.)
{

View File

@ -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": {

View File

@ -5,9 +5,7 @@ using Whisper.net;
namespace VideoAnalysisCore.AICore.GPT
{
/// <summary>
/// GPT 接口
/// </summary>
public interface IBserGPT
{
/// <summary>
@ -29,4 +27,145 @@ namespace VideoAnalysisCore.AICore.GPT
/// <returns></returns>
public Task GetVideoType(string task);
}
/// <summary>
/// 请求数据
/// </summary>
public class ChatRequest
{
/// <summary>
/// 对话
/// </summary>
public Message[] messages { get; set; }
/// <summary>
/// 提问种子值[用来确保 相同参数请求尽可能返回相同参数]
/// <para>默认:null</para>
/// <para>此功能处于 Beta 阶段。 如果指定,我们的系统将尽最大努力确定性地采样,这样具有相同 and 参数的重复请求应该返回相同的结果。 无法保证确定性,您应该参考 response 参数来监控后端的变化</para>
/// </summary>
public int? seed { get; set; } = null;
/// <summary>
/// 推理模型 (deepseek-reasoner)
/// </summary>
public string model { get; set; } = "deepseek-reasoner";
public float? max_tokens { get; set; } = 8000;
public float? max_completion_tokens { get; set; } = 8000;
/// <summary>
/// 要使用的采样温度,介于 0 和 2 之间。较高的值(如 0.8)将使输出更加随机,而较低的值(如 0.2)将使其更加集中和确定。 我们通常建议更改此项或同时更改两者。top_p
/// <para> 默认为 1</para>
/// <para> <see cref="ChatRequest.top_p"/>联动</para>
/// </summary>
public float? temperature { get; set; } = 0.2f;
/// <summary>
/// 一种替代温度采样的方法,称为原子核采样, 其中模型考虑具有top_p概率的标记的结果 质量。所以 0.1 表示仅包含前 10% 概率质量的代币 被考
/// <para>建议与<see cref="ChatRequest.temperature"/>联动</para>
/// </summary>
public float? top_p { get; set; } = 0.1f;
/// <summary>
/// 一个对象,用于指定模型必须输出的格式。设置为 enable 结构化输出,确保模型与您提供的 JSON 匹配 图式。
/// </summary>
//public object response_format { get; set; } = new { type = "json_object" };
/// <summary>
/// 流式返回
/// </summary>
public bool stream { get; set; } = true;
/// <summary>
/// 您希望模型为此请求生成的 Output types。 大多数模型都能够生成文本,这是
/// <para>默认设置: ["text"]</para>
/// <para>该模型还可用于生成音频。自 请求此模型同时生成文本和音频响应,您可以 用gpt-4o-audio-preview["text", "audio"]</para>
/// </summary>
public string modalities { get; set; } = "[\"json\"]";
/// <summary>
/// 任务id
/// </summary>
public string taskId { get; set; }
public object stream_options { get; set; } = new { include_usage = true };
}
/// <summary>
/// GPT 接口
/// </summary>
/// <summary>
/// gpt返回值
/// </summary>
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; }
/// <summary>
/// 系统指纹
/// </summary>
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; }
/// <summary>
/// 模型id
/// </summary>
public string model { get; set; }
/// <summary>
/// 对话
/// </summary>
public ChoiceSSE[] choices { get; set; }
/// <summary>
/// token使用情况
/// </summary>
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; }
/// <summary>
/// 推理内容
/// </summary>
public string reasoning_content { get; set; }
public string refusal { get; set; }
}
}

View File

@ -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);
}
/// <summary>
@ -31,9 +71,159 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
/// <returns>Return HttpResponseMessage for SSE</returns>
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<ChatResSSE>(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;
}
/// <summary>
/// 请求AI
/// </summary>
/// <typeparam name="T">返回JSON类型</typeparam>
/// <param name="task">任务id</param>
/// <param name="postMessages">提示词</param>
/// <param name="title">任务类型</param>
/// <param name="model">GPT版本</param>
/// <param name="max_tokens">最大token <para>不设置默认最大值 16000/8000</para></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<T> ChatAsync<T>(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<T>(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请求失败次数过多!!!");
}
/// <summary>
/// Chat
/// </summary>
@ -41,6 +231,11 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
/// <returns>Return HttpResponseMessage for SSE</returns>
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<ChatRes>();

View File

@ -6,104 +6,5 @@ using System.Threading.Tasks;
namespace VideoAnalysisCore.AICore.GPT.ChatGPT
{
/// <summary>
/// 请求数据
/// </summary>
public class ChatRequest
{
/// <summary>
/// 对话
/// </summary>
public Message[] messages { get; set; }
/// <summary>
/// 提问种子值[用来确保 相同参数请求尽可能返回相同参数]
/// <para>默认:null</para>
/// <para>此功能处于 Beta 阶段。 如果指定,我们的系统将尽最大努力确定性地采样,这样具有相同 and 参数的重复请求应该返回相同的结果。 无法保证确定性,您应该参考 response 参数来监控后端的变化</para>
/// </summary>
public int? seed { get; set; } =null;
public string model { get; set; } = ChatGPTType.GPT4o;
/// <summary>
/// 要使用的采样温度,介于 0 和 2 之间。较高的值(如 0.8)将使输出更加随机,而较低的值(如 0.2)将使其更加集中和确定。 我们通常建议更改此项或同时更改两者。top_p
/// <para> 默认为 1</para>
/// <para> <see cref="ChatRequest.top_p"/>联动</para>
/// </summary>
public float temperature { get; set; } = 0.2f;
/// <summary>
/// 一种替代温度采样的方法,称为原子核采样, 其中模型考虑具有top_p概率的标记的结果 质量。所以 0.1 表示仅包含前 10% 概率质量的代币 被考
/// <para>建议与<see cref="ChatRequest.temperature"/>联动</para>
/// </summary>
public float top_p { get; set; } = 0.5f;
public float max_completion_tokens { get; set; } = 5000;
/// <summary>
/// 一个对象,用于指定模型必须输出的格式。设置为 enable 结构化输出,确保模型与您提供的 JSON 匹配 图式。
/// </summary>
public object response_format { get; set; } = new { type = "json_object" };
/// <summary>
/// 流式返回
/// </summary>
public bool stream { get; set; } = false;
/// <summary>
/// 您希望模型为此请求生成的 Output types。 大多数模型都能够生成文本,这是
/// <para>默认设置: ["text"]</para>
/// <para>该模型还可用于生成音频。自 请求此模型同时生成文本和音频响应,您可以 用gpt-4o-audio-preview["text", "audio"]</para>
/// </summary>
public string modalities { get; set; } = "[\"json\"]";
/// <summary>
/// ai引导新话题
/// <para>默认-2 范围[-2~2]</para>
/// </summary>
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; }
}
/// <summary>
/// gpt返回值
/// </summary>
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; }
/// <summary>
/// 系统指纹
/// </summary>
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; }
}
}

View File

@ -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";
/// <summary>
/// GPT-4O 型 -> gpt-4o-2024-08-06
/// </summary>
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";
/// <summary>
/// gpt-4-turbo-preview gpt-4-0125-preview
/// </summary>
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";
/// <summary>

View File

@ -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;
}
/// <summary>
/// 访问GPT
/// </summary>

View File

@ -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;
}
/// <summary>
/// 请求AI
/// </summary>
/// <typeparam name="T">返回JSON类型</typeparam>
/// <param name="task">任务id</param>
/// <param name="postMessages">提示词</param>
/// <param name="title">任务类型</param>
/// <param name="model">GPT版本</param>
/// <param name="max_tokens">最大token <para>不设置默认最大值 16000/8000</para></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<T> ChatAsync<T>(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<T>(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请求失败次数过多!!!");
}
}
}

View File

@ -6,140 +6,6 @@ using System.Threading.Tasks;
namespace VideoAnalysisCore.AICore.GPT.DeepSeek
{
/// <summary>
/// 请求数据
/// </summary>
public class ChatRequest
{
/// <summary>
/// 对话
/// </summary>
public Message[] messages { get; set; }
/// <summary>
/// 提问种子值[用来确保 相同参数请求尽可能返回相同参数]
/// <para>默认:null</para>
/// <para>此功能处于 Beta 阶段。 如果指定,我们的系统将尽最大努力确定性地采样,这样具有相同 and 参数的重复请求应该返回相同的结果。 无法保证确定性,您应该参考 response 参数来监控后端的变化</para>
/// </summary>
public int? seed { get; set; } =null;
/// <summary>
/// 推理模型 (deepseek-reasoner)
/// </summary>
public string model { get; set; } = "deepseek-reasoner";
public float max_tokens { get; set; } = 8000;
/// <summary>
/// 要使用的采样温度,介于 0 和 2 之间。较高的值(如 0.8)将使输出更加随机,而较低的值(如 0.2)将使其更加集中和确定。 我们通常建议更改此项或同时更改两者。top_p
/// <para> 默认为 1</para>
/// <para> <see cref="ChatRequest.top_p"/>联动</para>
/// </summary>
public float temperature { get; set; } = 0.2f;
/// <summary>
/// 一种替代温度采样的方法,称为原子核采样, 其中模型考虑具有top_p概率的标记的结果 质量。所以 0.1 表示仅包含前 10% 概率质量的代币 被考
/// <para>建议与<see cref="ChatRequest.temperature"/>联动</para>
/// </summary>
public float top_p { get; set; } = 0.1f;
/// <summary>
/// 一个对象,用于指定模型必须输出的格式。设置为 enable 结构化输出,确保模型与您提供的 JSON 匹配 图式。
/// </summary>
//public object response_format { get; set; } = new { type = "json_object" };
/// <summary>
/// 流式返回
/// </summary>
public bool stream { get; set; } = false;
/// <summary>
/// 您希望模型为此请求生成的 Output types。 大多数模型都能够生成文本,这是
/// <para>默认设置: ["text"]</para>
/// <para>该模型还可用于生成音频。自 请求此模型同时生成文本和音频响应,您可以 用gpt-4o-audio-preview["text", "audio"]</para>
/// </summary>
public string modalities { get; set; } = "[\"json\"]";
/// <summary>
/// 任务id
/// </summary>
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; }
/// <summary>
/// 推理内容
/// </summary>
public string reasoning_content { get; set; }
public string refusal { get; set; }
}
/// <summary>
/// gpt返回值
/// </summary>
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; }
/// <summary>
/// 系统指纹
/// </summary>
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; }
/// <summary>
/// 模型id
/// </summary>
public string model { get; set; }
/// <summary>
/// 对话
/// </summary>
public ChoiceSSE[] choices { get; set; }
/// <summary>
/// token使用情况
/// </summary>
public Usage usage { get; set; }
}
public class ChoiceSSE
{
public int index { get; set; }
public Message delta { get; set; }
public string finish_reason { get; set; }
}
}

View File

@ -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<CourseGradingCriteria> criteriaDB;
private readonly RedisManager redisManager;
private readonly Repository<VideoTask> videoTaskDB;
@ -43,9 +46,9 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
/// </summary>
/// <param name="moonshotClient"></param>
/// <param name="logger"></param>
public DeepSeek_GPT(DeepSeekGPTClient moonshotClient, Repository<CourseGradingCriteria> criteria, Repository<VideoTask> videoTaskDB,
Repository<KnowledgeInfo> knowledgeInfoDB, Repository<VideoKonwPoint> videoKonwPointDB, SimpLetexClient simpLetexClient,
Repository<VideoQuestion> videoQuestionDB, OssClient ossClient, Repository<VideoQuestionKonw> videoQuestionKonwDB, RedisManager redisManager)
public DeepSeek_GPT(DeepSeekGPTClient moonshotClient, Repository<CourseGradingCriteria> criteria, Repository<VideoTask> videoTaskDB,
Repository<KnowledgeInfo> knowledgeInfoDB, Repository<VideoKonwPoint> videoKonwPointDB, SimpLetexClient simpLetexClient,
Repository<VideoQuestion> videoQuestionDB, OssClient ossClient, Repository<VideoQuestionKonw> 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;
}
/// <summary>
/// 获取内容对应的章节
@ -84,7 +88,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
$" 格式 (方法点Id|方法点名称) " +
$"提供的知识点名称({knows})。";
Console.WriteLine(DateTime.Now + "=>2.开始分析视频内容知识点");
var konwRes = await ChatAsync<VideoKnowRes[]>(taskInfo.Id.ToString(), knowMessages, "知识点");
var konwRes = await chatGPTClient.ChatAsync<VideoKnowRes[]>(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<FileNameInfo>
var fileNameInfoRes = await chatGPTClient.ChatAsync<FileNameInfo>
(task, fileNamePostMessages, "授课章节");
taskInfo.Sections = fileNameInfoRes.;
await videoTaskDB.AsUpdateable()
@ -169,20 +173,42 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
//校验结果质量
var thems = questionRes.Adapt<VideoKnowQueryDto[]>().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<CheckMessageDto>(taskInfo.Id.ToString(), checkMessage, "结果检查", "deepseek-chat");
var checkMessage =
$"""
30
0-10070
{thems}
:::|{captions.Captions}
json {checkResFormat}
""";
return await chatGPTClient.ChatAsync<CheckMessageDto>(taskInfo.Id.ToString(), checkMessage, "结果检查");
//var res = await chatClient.ChatAsync<CheckMessageDto>(taskInfo.Id.ToString(), checkMessage, "结果检查");
//var checkMessage1 = "我为视频的讲解内容做了一些分段,希望你能通读字幕内容后检查下的分段是否符合我的要求?" +
// $"检查这些分段的时间是否合理 与相邻的时间段间隔是否大于30秒?" +
// $"分段的主题内容,知识点分配是否合理符合实际吗?" +
// $"{pptFormat}" +
// $"请给出你的打分(0-100,70分及格)以及打分原因。" +
// $"这是我的分段 {thems}。" +
// $"后续的内容是包含时间戳的视频字幕的固定格式文本。" +
// $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).以下是包含时间的视频字幕文本。字幕列表 {captions.Captions}。" +
// $"最后输出格式为json({checkResFormat})";
}
/// <summary>
@ -223,7 +249,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
$"字幕结束。" +
$"最后请确保输出字幕条数与输入字幕条数一致!!! 如果不一致则重新优化并且确保字幕条数一致!!!!";
//Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id}字幕优化 分段{s}开始...");
var resData = await ChatAsync<string[]>(taskInfo.Id.ToString(), postMessages, "优化字幕", "deepseek-chat", 3000);
var resData = await chatClient.ChatAsync<string[]>(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<VideoKnowRes[]>(taskInfo.Id.ToString(), postMessages, "分析字幕");
//return await chatGPTClient.ChatAsync<VideoKnowRes[]>(taskInfo.Id.ToString(), postMessages, "分析字幕");
return await chatClient.ChatAsync<VideoKnowRes[]>(taskInfo.Id.ToString(), postMessages, "分析字幕");
}
catch (Exception ex)
{
@ -325,89 +351,8 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
return null;
}
/// <summary>
/// 请求AI
/// </summary>
/// <typeparam name="T">返回JSON类型</typeparam>
/// <param name="task">任务id</param>
/// <param name="postMessages">提示词</param>
/// <param name="title">任务类型</param>
/// <param name="model">GPT版本</param>
/// <param name="max_tokens">最大token <para>不设置默认最大值 16000/8000</para></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<T> ChatAsync<T>(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<T>(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请求失败次数过多!!!");
}
/// <summary>
@ -428,6 +373,13 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
.ToDictionary(s => s.Key, s => s.First());
var insertData = new List<VideoQuestionOSSDto>();
var insertQuestionKonw = new List<VideoQuestionKonw>();
insertData = await videoQuestionDB.AsQueryable()
.Where(s => s.VideoTaskId == taskInfo.Id)
.Select<VideoQuestionOSSDto>()
.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<VideoQuestionOSSDto[]>(taskInfo.Id.ToString(), postMessages, "提取试题", "deepseek-chat");
//var resData = await ChatAsync<VideoQuestionOSSDto[]>(taskInfo.Id.ToString(), postMessages, "提取试题");
var resData = await chatGPTClient.ChatAsync<VideoQuestionOSSDto[]>(taskInfo.Id.ToString(), postMessages, "提取试题");
//var resData = await chatClient.ChatAsync<VideoQuestionOSSDto[]>(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<KnowledgeInfo>? knowledgeInfos = new List<KnowledgeInfo>();
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
/// </summary>
/// <param name="task"></param>
/// <returns></returns>
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<AIVideoTypeDto?>(taskInfo.Id.ToString(), postMessages, "视频类型", "deepseek-chat");
var resData = await chatClient.ChatAsync<AIVideoTypeDto?>(taskInfo.Id.ToString(), postMessages, "视频类型", "deepseek-chat");
var res = resData.VideoType.ToEnum<AttachmentsInfoType>();
if (res != null)
{

View File

@ -76,7 +76,13 @@ namespace VideoAnalysisCore.Common
/// </summary>
/// <param name="tid"></param>
/// <returns></returns>
public static string GetVideoPath(string tid) => $"./video/{tid}/{tid}.mp4";
public static string GetVideoPath(string tid) => $"./video/{tid}/task.mp4";
/// <summary>
/// 获取视频PPT路径
/// </summary>
/// <param name="tid"></param>
/// <returns></returns>
public static string GetVideoPPTPath(string tid) => $"./video/{tid}/ppt.mp4";
}
/// <summary>
@ -84,7 +90,7 @@ namespace VideoAnalysisCore.Common
/// </summary>
public static class ExpandFunction
{
static Dictionary<string, string> FormulaData;
static string FormulaDataKey;
/// <summary>
@ -99,21 +105,31 @@ namespace VideoAnalysisCore.Common
/// <returns></returns>
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;
}

View File

@ -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();
}
}
}