优化 新版AI分析流程
This commit is contained in:
parent
383d0b4c32
commit
2609f13ea5
|
|
@ -94,7 +94,7 @@ namespace Learn.VideoAnalysis.Components.Pages
|
||||||
StageId = s.First().StageId,
|
StageId = s.First().StageId,
|
||||||
KnowPoint = string.Join(',', s.Select(x => x.KnowPoint))
|
KnowPoint = string.Join(',', s.Select(x => x.KnowPoint))
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
videoPath = AppCommon.GetVideoPath(nowTask.Id.ToString());
|
videoPath = nowTask.MediaUrl; //AppCommon.GetVideoPath(nowTask.Id.ToString());
|
||||||
|
|
||||||
if (nowTask.VideoType == AttachmentsInfoType.复习)
|
if (nowTask.VideoType == AttachmentsInfoType.复习)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@
|
||||||
"ApiKey": "sk-8BvvhESZIkgUbiaaJhglPxFa4o2X9H3xEv9lXELrWWwGxHWY"
|
"ApiKey": "sk-8BvvhESZIkgUbiaaJhglPxFa4o2X9H3xEv9lXELrWWwGxHWY"
|
||||||
},
|
},
|
||||||
"ChatGpt": {
|
"ChatGpt": {
|
||||||
"Host": "https://api.g4f.icu/",
|
//"Host": "https://api.g4f.icu/",
|
||||||
//"Host": "https://api.oaibest.com/",
|
"Host": "https://api.oaibest.com/",
|
||||||
"ApiKey": "sk-D15tBln31N7dI9Fi7lds7OySFv5tOEK7DMNsG5rY2E6DCr4s"
|
"ApiKey": "sk-D15tBln31N7dI9Fi7lds7OySFv5tOEK7DMNsG5rY2E6DCr4s"
|
||||||
},
|
},
|
||||||
"DeepSeek": {
|
"DeepSeek": {
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,7 @@ using Whisper.net;
|
||||||
|
|
||||||
namespace VideoAnalysisCore.AICore.GPT
|
namespace VideoAnalysisCore.AICore.GPT
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// GPT 接口
|
|
||||||
/// </summary>
|
|
||||||
public interface IBserGPT
|
public interface IBserGPT
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -29,4 +27,145 @@ namespace VideoAnalysisCore.AICore.GPT
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task GetVideoType(string task);
|
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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ using System.Net.Http;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using VideoAnalysisCore.AICore.GPT.DeepSeek;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
|
||||||
namespace VideoAnalysisCore.AICore.GPT.ChatGPT
|
namespace VideoAnalysisCore.AICore.GPT.ChatGPT
|
||||||
|
|
@ -18,10 +20,48 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
|
||||||
public static string ApiKey = AppCommon.Config.ChatGpt.ChatGpt.ApiKey;
|
public static string ApiKey = AppCommon.Config.ChatGpt.ChatGpt.ApiKey;
|
||||||
|
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly RedisManager redisManager;
|
||||||
|
|
||||||
public ChatGPTClient(IHttpClientFactory httpClientFactory)
|
public ChatGPTClient(IHttpClientFactory httpClientFactory, RedisManager redisManager)
|
||||||
{
|
{
|
||||||
_httpClientFactory = httpClientFactory;
|
_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>
|
/// <summary>
|
||||||
|
|
@ -31,9 +71,159 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
|
||||||
/// <returns>Return HttpResponseMessage for SSE</returns>
|
/// <returns>Return HttpResponseMessage for SSE</returns>
|
||||||
public async Task<(Usage u, string res)?> ChatSSE(ChatRequest chatReq)
|
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>
|
/// <summary>
|
||||||
/// Chat
|
/// Chat
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -41,6 +231,11 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
|
||||||
/// <returns>Return HttpResponseMessage for SSE</returns>
|
/// <returns>Return HttpResponseMessage for SSE</returns>
|
||||||
public async Task<(Usage u, string res)?> Chat(ChatRequest chatReq)
|
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 requestBody = chatReq.ToJson();
|
||||||
var chatResp = await PostJsonStreamAsync("v1/chat/completions", requestBody);
|
var chatResp = await PostJsonStreamAsync("v1/chat/completions", requestBody);
|
||||||
var res = await chatResp.Content.ReadFromJsonAsync<ChatRes>();
|
var res = await chatResp.Content.ReadFromJsonAsync<ChatRes>();
|
||||||
|
|
|
||||||
|
|
@ -6,104 +6,5 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace VideoAnalysisCore.AICore.GPT.ChatGPT
|
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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,22 +8,9 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
|
||||||
{
|
{
|
||||||
public class ChatGPTType
|
public class ChatGPTType
|
||||||
{
|
{
|
||||||
public static string GPT4oLatest = "chatgpt-4o-latest";
|
public static string GPT5_mini = "gpt-5-mini-2025-08-07";
|
||||||
public static string GPT4o241120 = "gpt-4o-2024-11-20";
|
public static string GPT5 = "gpt-5-2025-08-07";
|
||||||
public static string GPT4o240513 = "gpt-4o-2024-05-13";
|
public static string GPT5_nano = "gpt-5-nano-2025-08-07";
|
||||||
/// <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";
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
|
||||||
messageArr = messageArr.Where(s => s != null).ToArray();
|
messageArr = messageArr.Where(s => s != null).ToArray();
|
||||||
var chatRep = new ChatRequest
|
var chatRep = new ChatRequest
|
||||||
{
|
{
|
||||||
model = ChatGPTType.GPT4o241120,
|
model = ChatGPTType.GPT5_mini,
|
||||||
max_completion_tokens = maxTokens,
|
max_completion_tokens = maxTokens,
|
||||||
temperature = 0.2f,
|
temperature = 0.2f,
|
||||||
messages = messageArr
|
messages = messageArr
|
||||||
|
|
@ -240,6 +240,8 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
|
||||||
//}
|
//}
|
||||||
return questionRes;
|
return questionRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 访问GPT
|
/// 访问GPT
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using VideoAnalysisCore.AICore.GPT.ChatGPT;
|
using VideoAnalysisCore.AICore.GPT.ChatGPT;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
{
|
{
|
||||||
|
|
@ -155,13 +156,13 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
var messageBuilder1 = new StringBuilder();
|
var messageBuilder1 = new StringBuilder();
|
||||||
var lastChat = new ChatResSSE();
|
var lastChat = new ChatResSSE();
|
||||||
var splitCount = "data:".Length;
|
var splitCount = "data:".Length;
|
||||||
var maxLoop = 60*10000;
|
var maxLoop = 60*100000;
|
||||||
int threshold = 0;
|
int threshold = 0;
|
||||||
while (maxLoop>0)
|
while (maxLoop>0)
|
||||||
{
|
{
|
||||||
line = reader.ReadLine();
|
line = reader.ReadLine();
|
||||||
if (line is null || string.IsNullOrEmpty(line)|| line.StartsWith(": keep-alive")) {
|
if (line is null || string.IsNullOrEmpty(line)|| line.StartsWith(": keep-alive")) {
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(10);
|
||||||
maxLoop--;
|
maxLoop--;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -210,5 +211,88 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
return null;
|
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请求失败次数过多!!!");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,140 +6,6 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ using Yitter.IdGenerator;
|
||||||
using VideoAnalysisCore.Common.Expand;
|
using VideoAnalysisCore.Common.Expand;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UserCenter.Model.Enum;
|
using UserCenter.Model.Enum;
|
||||||
|
using Dm.filter;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
{
|
{
|
||||||
|
|
@ -29,6 +31,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
public class DeepSeek_GPT : IBserGPT
|
public class DeepSeek_GPT : IBserGPT
|
||||||
{
|
{
|
||||||
private readonly DeepSeekGPTClient chatClient;
|
private readonly DeepSeekGPTClient chatClient;
|
||||||
|
private readonly ChatGPTClient chatGPTClient;
|
||||||
private readonly Repository<CourseGradingCriteria> criteriaDB;
|
private readonly Repository<CourseGradingCriteria> criteriaDB;
|
||||||
private readonly RedisManager redisManager;
|
private readonly RedisManager redisManager;
|
||||||
private readonly Repository<VideoTask> videoTaskDB;
|
private readonly Repository<VideoTask> videoTaskDB;
|
||||||
|
|
@ -43,9 +46,9 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="moonshotClient"></param>
|
/// <param name="moonshotClient"></param>
|
||||||
/// <param name="logger"></param>
|
/// <param name="logger"></param>
|
||||||
public DeepSeek_GPT(DeepSeekGPTClient moonshotClient, Repository<CourseGradingCriteria> criteria, Repository<VideoTask> videoTaskDB,
|
public DeepSeek_GPT(DeepSeekGPTClient moonshotClient, Repository<CourseGradingCriteria> criteria, Repository<VideoTask> videoTaskDB,
|
||||||
Repository<KnowledgeInfo> knowledgeInfoDB, Repository<VideoKonwPoint> videoKonwPointDB, SimpLetexClient simpLetexClient,
|
Repository<KnowledgeInfo> knowledgeInfoDB, Repository<VideoKonwPoint> videoKonwPointDB, SimpLetexClient simpLetexClient,
|
||||||
Repository<VideoQuestion> videoQuestionDB, OssClient ossClient, Repository<VideoQuestionKonw> videoQuestionKonwDB, RedisManager redisManager)
|
Repository<VideoQuestion> videoQuestionDB, OssClient ossClient, Repository<VideoQuestionKonw> videoQuestionKonwDB, RedisManager redisManager, ChatGPTClient chatGPTClient)
|
||||||
{
|
{
|
||||||
chatClient = moonshotClient;
|
chatClient = moonshotClient;
|
||||||
criteriaDB = criteria;
|
criteriaDB = criteria;
|
||||||
|
|
@ -57,6 +60,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
this.ossClient = ossClient;
|
this.ossClient = ossClient;
|
||||||
this.videoQuestionKonwDB = videoQuestionKonwDB;
|
this.videoQuestionKonwDB = videoQuestionKonwDB;
|
||||||
this.redisManager = redisManager;
|
this.redisManager = redisManager;
|
||||||
|
this.chatGPTClient = chatGPTClient;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取内容对应的章节
|
/// 获取内容对应的章节
|
||||||
|
|
@ -84,7 +88,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
$" 格式 (方法点Id|方法点名称) " +
|
$" 格式 (方法点Id|方法点名称) " +
|
||||||
$"提供的知识点名称({knows})。";
|
$"提供的知识点名称({knows})。";
|
||||||
Console.WriteLine(DateTime.Now + "=>2.开始分析视频内容知识点");
|
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++)
|
for (int i = 0; i < konwRes.Count(); i++)
|
||||||
questionRes[i].KnowPoint = konwRes[i].KnowPoint;
|
questionRes[i].KnowPoint = konwRes[i].KnowPoint;
|
||||||
|
|
@ -147,7 +151,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
$"字幕列表 {rCaptionArr}。" +
|
$"字幕列表 {rCaptionArr}。" +
|
||||||
$"输出格式 json字符串 对象格式{fileNameResFormat}";
|
$"输出格式 json字符串 对象格式{fileNameResFormat}";
|
||||||
var task = taskInfo.Id.ToString();
|
var task = taskInfo.Id.ToString();
|
||||||
var fileNameInfoRes = await ChatAsync<FileNameInfo>
|
var fileNameInfoRes = await chatGPTClient.ChatAsync<FileNameInfo>
|
||||||
(task, fileNamePostMessages, "授课章节");
|
(task, fileNamePostMessages, "授课章节");
|
||||||
taskInfo.Sections = fileNameInfoRes.授课章节;
|
taskInfo.Sections = fileNameInfoRes.授课章节;
|
||||||
await videoTaskDB.AsUpdateable()
|
await videoTaskDB.AsUpdateable()
|
||||||
|
|
@ -169,20 +173,42 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
//校验结果质量
|
//校验结果质量
|
||||||
var thems = questionRes.Adapt<VideoKnowQueryDto[]>().ToJson();
|
var thems = questionRes.Adapt<VideoKnowQueryDto[]>().ToJson();
|
||||||
var pptFormat = taskInfo.VideoType == AttachmentsInfoType.复习
|
var pptFormat = taskInfo.VideoType == AttachmentsInfoType.复习
|
||||||
? "这堂课是习题课,所讲解内容都是试题。"
|
? "这堂课是习题课,所讲解内容几乎都是试题。"
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
var checkResFormat = """{"Score":打分(number),"Evaluation":评价(string)""";//,"Data":优化后的分段(array)}""";
|
var checkResFormat = """{"Score":打分(number),"Evaluation":评价(string)""";//,"Data":优化后的分段(array)}""";
|
||||||
var checkMessage = "我为视频的讲解内容做了一些分段,希望你能通读字幕内容后检查下的分段是否符合我的要求?" +
|
var checkMessage =
|
||||||
$"检查这些分段的时间是否合理 与相邻的时间段间隔是否大于30秒?" +
|
$"""
|
||||||
$"分段的主题内容,知识点分配是否合理符合实际吗?" +
|
请你担任一位专业的视频内容分析教研老师,擅长评估视频内容的结构和逻辑流暢度。
|
||||||
$"{pptFormat}" +
|
核心任务: 请根据我提供的【视频分段方案】【完整字幕文本】【完整字幕文本】,对该分段方案进行严谨评估。
|
||||||
$"请给出你的打分(0-100,70分及格)以及打分原因。" +
|
评估维度与具体标准:
|
||||||
$"这是我的分段 {thems}。" +
|
时间间隔检查(硬性指标)
|
||||||
$"后续的内容是包含时间戳的视频字幕的固定格式文本。" +
|
严格检查每个分段与下一个分段的开始时间之间的间隔是否大于30秒。
|
||||||
$"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).以下是包含时间的视频字幕文本。字幕列表 {captions.Captions}。" +
|
输出要求:请列出所有不满足此条件的分段对,并说明具体的时间差。
|
||||||
$"最后输出格式为json({checkResFormat})";
|
内容结构与主题合理性:
|
||||||
Console.WriteLine(DateTime.Now + "=>3.开始检查视频分段结果");
|
主题凝聚力:评估单个分段内的内容是否围绕一个清晰、统一的主题展开,是否存在主题混杂或跳跃的情况。
|
||||||
return await ChatAsync<CheckMessageDto>(taskInfo.Id.ToString(), checkMessage, "结果检查", "deepseek-chat");
|
逻辑过渡:评估分段之间的过渡是否自然流畅,后一段是否是前一段内容的合理延伸或转折。
|
||||||
|
知识点分配:检查分段内的知识点是否与分段有关联,知识点分布是否合理。
|
||||||
|
综合评分与改进建议:
|
||||||
|
请基于以上分析,提供一个0-100的综合得分(70分及格)。
|
||||||
|
详细说明打分理由,并逐条对应到上述评估维度。
|
||||||
|
输入数据格式说明:
|
||||||
|
分段方案: {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>
|
/// <summary>
|
||||||
|
|
@ -223,7 +249,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
$"字幕结束。" +
|
$"字幕结束。" +
|
||||||
$"最后请确保输出字幕条数与输入字幕条数一致!!! 如果不一致则重新优化并且确保字幕条数一致!!!!";
|
$"最后请确保输出字幕条数与输入字幕条数一致!!! 如果不一致则重新优化并且确保字幕条数一致!!!!";
|
||||||
//Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id}字幕优化 分段{s}开始...");
|
//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())
|
if (resData.Count() != cArr.Count())
|
||||||
{
|
{
|
||||||
resData = cStrArr.ToArray();
|
resData = cStrArr.ToArray();
|
||||||
|
|
@ -276,19 +302,18 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
$"{keyFrameArr}" +
|
$"{keyFrameArr}" +
|
||||||
$"完整的课堂标准流程包含以下5个阶段:课程引入/新知讲解/例题精讲/课堂练习/知识总结。" +
|
$"完整的课堂标准流程包含以下5个阶段:课程引入/新知讲解/例题精讲/课堂练习/知识总结。" +
|
||||||
$"每个类型的授课阶段允许有多个 例如 多个新知讲解/例题精讲..." +
|
$"每个类型的授课阶段允许有多个 例如 多个新知讲解/例题精讲..." +
|
||||||
$"授课阶段主要由每个知识点或者例题内容的讲解或练习组成。" +
|
$"1.初步划分阶段:基于PPT变化时间点,将字幕内容分割成时间段。每个时间段的起始和结束应接近这些时间点(例如,以时间点为中心,扩展至内容自然过渡处)。" +
|
||||||
$"通过授课阶段的主要讲解内容分析出对应的授课阶段内容总结。" +
|
$"2.内容分析:对每个时间段,提取主要讲解内容:识别关键词(如“例题”“证明”“练习”“总结”)和内容结构。" +
|
||||||
$"通过生成的内容总结分析出对应的授课阶段主题。 " +
|
$"3.判断阶段类型:如果内容以解题为主,归类为“例题精讲”;如果涉及新知识讲解,归类为“新知讲解”;以此类推。" +
|
||||||
$"最后请检查每个授课阶段的时长,不允许出现超出800秒或者低于50秒的授课阶段。" +
|
$"4.生成内容总结与主题:内容总结:用1-2句话简述该阶段的核心讲解内容(例如,“通过例题演示柯西不等式在求最值中的应用”)。" +
|
||||||
$"如果授课阶段的时长不符合标准那么请合并/拆分附近相邻内容相近的阶段。" +
|
$"5.阶段主题:基于内容总结,提炼一个具体主题(例如,“柯西不等式的基本应用”)。" +
|
||||||
|
$"6.时长检查与调整:计算每个阶段的时长(结束时间减开始时间)。如果阶段时长低于50秒,则合并相邻的类似内容阶段(例如,将两个连续的例题讲解合并为一个阶段),或扩展时间段以确保最低50秒。调整时需保持内容连贯性。" +
|
||||||
|
$"7.输出要求:最终分析结果应列出每个阶段的开始时间、结束时间、阶段类型、主题和内容总结(不包含提示词内容),确保划分合理、无重叠,且时长符合要求。" +
|
||||||
$"输出内容只返回json格式({resFormat})" +
|
$"输出内容只返回json格式({resFormat})" +
|
||||||
$"字幕格式(开始秒:内容|下一段字幕).以下是包含时间的视频字幕文本。" +
|
$"字幕格式(开始秒:内容|下一段字幕).以下是包含时间的视频字幕文本。" +
|
||||||
$"字幕列表 {captions.Captions} 字幕结束!";
|
$"字幕列表 {captions.Captions} 字幕结束!";
|
||||||
break;
|
break;
|
||||||
case AttachmentsInfoType.复习:
|
case AttachmentsInfoType.复习:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
postMessages =
|
postMessages =
|
||||||
$"请通过视频字幕内容分析出视频中课堂的授课阶段。" +
|
$"请通过视频字幕内容分析出视频中课堂的授课阶段。" +
|
||||||
$"课堂内容与{taskInfo.Subject}学科下的{sections}章节相关。" +
|
$"课堂内容与{taskInfo.Subject}学科下的{sections}章节相关。" +
|
||||||
|
|
@ -313,7 +338,8 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id.ToString()}.开始分析视频内容 {tryCount}");
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -325,89 +351,8 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
return null;
|
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>
|
/// <summary>
|
||||||
|
|
@ -428,6 +373,13 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
.ToDictionary(s => s.Key, s => s.First());
|
.ToDictionary(s => s.Key, s => s.First());
|
||||||
var insertData = new List<VideoQuestionOSSDto>();
|
var insertData = new List<VideoQuestionOSSDto>();
|
||||||
var insertQuestionKonw = new List<VideoQuestionKonw>();
|
var insertQuestionKonw = new List<VideoQuestionKonw>();
|
||||||
|
|
||||||
|
|
||||||
|
insertData = await videoQuestionDB.AsQueryable()
|
||||||
|
.Where(s => s.VideoTaskId == taskInfo.Id)
|
||||||
|
.Select<VideoQuestionOSSDto>()
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
foreach (var item in farmeArr)
|
foreach (var item in farmeArr)
|
||||||
{
|
{
|
||||||
var knowInfoArr = videoKnowArr
|
var knowInfoArr = videoKnowArr
|
||||||
|
|
@ -451,29 +403,29 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id} 提取{knowInfoArr.StartTime}秒试题的试题内容");
|
Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id} 提取{knowInfoArr.StartTime}秒试题的试题内容");
|
||||||
Console.WriteLine(sRes.Result.res.value);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//var knowArr=JsonSerializer.Serialize(knowInfoArr.Select(s => new { s.KnowPointId, s.KnowPoint }));
|
//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 =
|
var postMessages =
|
||||||
$"我将提供一段内容是Markdown格式的试题。" +
|
$"我将提供一段内容是Markdown格式的试题。" +
|
||||||
$"请提取出其中有效的试题内容(包括 题干,公式试题内提出的问题 )。" +
|
$"请提取出其中有效的试题内容(包括 题干,公式试题内提出的问题 )。" +
|
||||||
$"为每个问题关联上限定范围内的知识点(多个则用逗号分割),知识点格式 (知识点Id|知识点名称)知识点范围[{knowArrStr}]。" +
|
$"为每个问题关联上限定范围内的知识点 知识点格式 (知识点Id|知识点名称,下一个知识点)知识点范围[{knowArrStr}]。" +
|
||||||
$"排除不是试题内容的文字,优化试题排版并且去除题号,尽量保留latex数学公式。" +
|
$"排除不是试题内容的文字,优化试题排版并且去除题号,尽量保留latex数学公式。" +
|
||||||
$"如果存在多道题,则需要拆分成为多个试题对象!" +
|
$"如果内容中存在多道题,则需要拆分成为多个试题JSON对象!" +
|
||||||
$"试题的类型约束在 填空题/判断题/选择题/解答题/填空题 范围内。" +
|
$"试题的类型约束在 填空题/判断题/选择题/解答题/填空题 范围内。" +
|
||||||
$"如果是有效试题且题干中存在下划线则试题的题型应该是填空题。" +
|
$"如果是有效试题且题干中存在下划线则试题的题型应该是填空题。" +
|
||||||
$"请检查我提供的字符串内容,如果不能识别知识点则不处理知识点,如不包含问题试题则返回`[]`" +
|
$"请检查我提供的字符串内容,如果不能识别知识点则不处理知识点!" +
|
||||||
|
$"传入的内容可能只是知识点,可能不包含试题,如果不包含问题试题则最终内容返回空数组JSON`[]`" +
|
||||||
$"输出内容只返回json格式为({resFormat})" +
|
$"输出内容只返回json格式为({resFormat})" +
|
||||||
$"以下是试题内容" +
|
$"以下是试题内容" +
|
||||||
$"`{sRes.Result.res.value}`";
|
$"`{sRes.Result.res.value}`";
|
||||||
var resData = await ChatAsync<VideoQuestionOSSDto[]>(taskInfo.Id.ToString(), postMessages, "提取试题", "deepseek-chat");
|
var resData = await chatGPTClient.ChatAsync<VideoQuestionOSSDto[]>(taskInfo.Id.ToString(), postMessages, "提取试题");
|
||||||
//var resData = await ChatAsync<VideoQuestionOSSDto[]>(taskInfo.Id.ToString(), postMessages, "提取试题");
|
//var resData = await chatClient.ChatAsync<VideoQuestionOSSDto[]>(taskInfo.Id.ToString(), postMessages, "提取试题");
|
||||||
if (resData is null || resData.Count() == 0)
|
if (resData is null || resData.Count() == 0)
|
||||||
break;
|
break;
|
||||||
foreach (var q in resData)
|
foreach (var q in resData)
|
||||||
{
|
{
|
||||||
var TopicId = YitIdHelper.NextId();
|
var TopicId = YitIdHelper.NextId();
|
||||||
foreach (var qt in q.QuestionArr)
|
foreach (var qt in q.QuestionArr)
|
||||||
{
|
{
|
||||||
|
|
@ -513,8 +465,9 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
insertData = insertData.GroupBy(s => string.Join("", Regex.Matches(s.StartTime+s.TopicStem+s.Question, "[\u4e00-\u9fa5a-zA-Z0-9]+")))
|
||||||
if(insertData==null || insertData.Count==0|| insertQuestionKonw.Count==0)
|
.Select(s => s.First()).ToList();
|
||||||
|
if (insertData == null || insertData.Count == 0 || insertQuestionKonw.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
//上传oss 并更新imageUrl
|
//上传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);
|
var know = await knowledgeInfoDB.GetFirstAsync(s => s.Course_Id == Course_Id && s.Name == sections);
|
||||||
if (know is null)
|
if (know is null)
|
||||||
throw new Exception("未能找到对应知识点=>" + sections);
|
throw new Exception("未能找到对应知识点=>" + sections);
|
||||||
|
List<KnowledgeInfo>? knowledgeInfos = new List<KnowledgeInfo>();
|
||||||
var kInfo = await knowledgeInfoDB.GetByIdAsync(know.Parent_Id);
|
var kInfo = await knowledgeInfoDB.GetByIdAsync(know.Parent_Id);
|
||||||
var knowledgeInfos = await knowledgeInfoDB.AsQueryable()
|
try
|
||||||
.ToChildListAsync(s => s.Parent_Id, kInfo.Parent_Id == 0 ? kInfo.Id : kInfo.Parent_Id);
|
{
|
||||||
|
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优化字幕
|
//AI优化字幕
|
||||||
captionsArr = await OptimizeSubtitles(taskInfo, captionsArr, sections);
|
captionsArr = await OptimizeSubtitles(taskInfo, captionsArr, sections);
|
||||||
//合并字幕
|
//合并字幕
|
||||||
var captions = ExpandFunction.GetSpeakerCaptions(captionsArr);
|
var captions = ExpandFunction.GetSpeakerCaptions(captionsArr);
|
||||||
var maxVideoTime = captions?.TimeBase?.LastOrDefault()?.End ?? 0;
|
var maxVideoTime = captions?.TimeBase?.LastOrDefault()?.End ?? 0;
|
||||||
VideoKnowRes[]? questionRes =null;
|
VideoKnowRes[]? questionRes = null;
|
||||||
var tryCount = 20;
|
var tryCount = 20;
|
||||||
while (tryCount-- > 0)
|
while (tryCount-- > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -582,11 +543,10 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
|
|
||||||
if (questionRes is null) continue;
|
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);
|
var checkRes = await VerifySpanQuality(questionRes, taskInfo, captions, sections, Course_Id);
|
||||||
|
|
||||||
|
|
||||||
if (checkRes != null && checkRes.Score >= 80)
|
if (checkRes != null && checkRes.Score >= 80)
|
||||||
{
|
{
|
||||||
//写入知识点
|
//写入知识点
|
||||||
|
|
@ -606,7 +566,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tryCount == 0)
|
if (tryCount == 0)
|
||||||
{
|
{
|
||||||
throw new Exception("重试次数过多!");
|
throw new Exception("重试次数过多!");
|
||||||
}
|
}
|
||||||
|
|
@ -654,14 +614,14 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="task"></param>
|
/// <param name="task"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task GetVideoType(string task)
|
public async Task GetVideoType(string task)
|
||||||
{
|
{
|
||||||
|
|
||||||
var taskId = long.Parse(task);
|
var taskId = long.Parse(task);
|
||||||
var taskInfo = await videoTaskDB.AsQueryable()
|
var taskInfo = await videoTaskDB.AsQueryable()
|
||||||
.Where(s => s.Id == taskId)
|
.Where(s => s.Id == taskId)
|
||||||
.FirstAsync();
|
.FirstAsync();
|
||||||
if (taskInfo.VideoType != null)
|
if (taskInfo.VideoType != null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -687,7 +647,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
||||||
$"输出内容只返回json格式为({resFormat})" +
|
$"输出内容只返回json格式为({resFormat})" +
|
||||||
$"以下是字幕内容" +
|
$"以下是字幕内容" +
|
||||||
$"`{captions.Captions}`";
|
$"`{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>();
|
var res = resData.VideoType.ToEnum<AttachmentsInfoType>();
|
||||||
if (res != null)
|
if (res != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,13 @@ namespace VideoAnalysisCore.Common
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tid"></param>
|
/// <param name="tid"></param>
|
||||||
/// <returns></returns>
|
/// <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>
|
/// <summary>
|
||||||
|
|
@ -84,7 +90,7 @@ namespace VideoAnalysisCore.Common
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ExpandFunction
|
public static class ExpandFunction
|
||||||
{
|
{
|
||||||
|
|
||||||
static Dictionary<string, string> FormulaData;
|
static Dictionary<string, string> FormulaData;
|
||||||
static string FormulaDataKey;
|
static string FormulaDataKey;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -99,21 +105,31 @@ namespace VideoAnalysisCore.Common
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static bool DeleteTaskFile(long? taskId)
|
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();
|
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);
|
try
|
||||||
Console.WriteLine($"已删除缓存文件: {taskId}");
|
{
|
||||||
}
|
File.Delete(filePath);
|
||||||
catch (Exception ex)
|
Console.WriteLine($"已成功删除文件: {filePath}");
|
||||||
{
|
}
|
||||||
Console.WriteLine($"删除缓存文件 {taskId} 时出错: {ex.Message}");
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"删除文件 {filePath} 时出错: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
// Console.WriteLine($"文件不存在,跳过删除: {filePath}");
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
else return false;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using Coravel.Invocable;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -28,7 +29,46 @@ namespace VideoAnalysisCore.Job
|
||||||
{
|
{
|
||||||
this.videotaskDB = videotaskDB;
|
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
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -44,14 +84,15 @@ namespace VideoAnalysisCore.Job
|
||||||
//筛选 结束任务 或者 错误任务
|
//筛选 结束任务 或者 错误任务
|
||||||
t.LastEnum == Model.Enum.RedisChannelEnum.结束任务 ||
|
t.LastEnum == Model.Enum.RedisChannelEnum.结束任务 ||
|
||||||
t.ErrorMessage != null
|
t.ErrorMessage != null
|
||||||
)
|
)
|
||||||
&& t.EndTime < twoDaysAgo
|
&& t.EndTime < twoDaysAgo
|
||||||
&& t.EndTime > endDaysAgo)
|
&& t.EndTime > endDaysAgo)
|
||||||
|
.Select(s => s.Id)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// 遍历查询结果,删除缓存文件
|
// 遍历查询结果,删除缓存文件
|
||||||
foreach (var task in completedTasks)
|
foreach (var taskId in completedTasks)
|
||||||
ExpandFunction.DeleteTaskFile(task?.Id);
|
ExpandFunction.DeleteTaskFile(taskId);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -61,8 +102,8 @@ namespace VideoAnalysisCore.Job
|
||||||
public async Task Invoke()
|
public async Task Invoke()
|
||||||
{
|
{
|
||||||
Console.WriteLine($"{DateTime.Now} 执行=>{this.GetType().FullName}");
|
Console.WriteLine($"{DateTime.Now} 执行=>{this.GetType().FullName}");
|
||||||
|
DeleteTaskVideoCaches();
|
||||||
DeleteOldCompletedTaskCaches();
|
DeleteTaskAllCaches();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue