parent
deb7f80bce
commit
fddcdbc1d9
|
|
@ -191,6 +191,7 @@ namespace VideoAnalysisCore.AICore.GPT
|
|||
{
|
||||
services.AddSingleton<DeepSeekGPTClient>();
|
||||
services.AddSingleton<BestAIClient>();
|
||||
services.AddSingleton<GeminiGPTClient>();
|
||||
services.AddSingleton<IBserGPTWorkflow, GTP_Analysis_1>();
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
|
|||
{
|
||||
taskId = task,
|
||||
model = model,
|
||||
max_tokens =8000,
|
||||
title = title,
|
||||
max_tokens =8000,
|
||||
stream = true,
|
||||
temperature = 0.2f,
|
||||
messages = messageArr
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
|
|||
var chatReq = new ChatRequest
|
||||
{
|
||||
taskId = task,
|
||||
title = title,
|
||||
model = model ?? ChatGPTType.Deepseek_Reasoner,
|
||||
max_tokens = model == ChatGPTType.Deepseek_Reasoner ? 16000 : max_tokens,
|
||||
stream = true,
|
||||
|
|
|
|||
|
|
@ -66,10 +66,9 @@ namespace VideoAnalysisCore.AICore.GPT
|
|||
/// <returns>Return HttpResponseMessage for SSE</returns>
|
||||
public async Task<(Usage u, string res, string reasoning)?> ChatSSE(ChatRequest chatReq)
|
||||
{
|
||||
var requestBody = chatReq.ToJson();
|
||||
var i = 5;
|
||||
PostJsonStream:
|
||||
var chatResp = await PostJsonStreamAsync(Config.Host + Config.Path, requestBody, Config.ApiKey);
|
||||
var chatResp = await PostJsonStreamAsync(Config.Host + Config.Path, chatReq, Config.ApiKey);
|
||||
if (!chatResp.IsSuccessStatusCode)
|
||||
{
|
||||
await redisManager.AddTaskLog(chatReq.taskId, "=>请求GPT服务器异常 " + chatResp?.StatusCode + " " + await chatResp.Content.ReadAsStringAsync());
|
||||
|
|
@ -182,7 +181,7 @@ namespace VideoAnalysisCore.AICore.GPT
|
|||
chatResContent = chatResContent?.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(chatResContent))
|
||||
throw new Exception("GPT返回结果无有效JSON");
|
||||
throw new Exception($"GPT返回结果无有效JSON =>{chatResp?.res}");
|
||||
var startsStr = typeof(T).IsArray ? "[" : "{";
|
||||
var endStr = typeof(T).IsArray ? "]" : "}";
|
||||
if (!chatResContent.StartsWith(startsStr))
|
||||
|
|
@ -211,8 +210,10 @@ namespace VideoAnalysisCore.AICore.GPT
|
|||
}
|
||||
|
||||
public async Task<HttpResponseMessage> PostJsonStreamAsync(
|
||||
string path, string json, string apiKey, bool readAll = false)
|
||||
string path, ChatRequest data, string apiKey, bool readAll = false)
|
||||
{
|
||||
|
||||
var json = data.ToJson();
|
||||
var uriBuilder = new UriBuilder(path);
|
||||
var maxRestart = 4;
|
||||
var errorMSG = new Exception[maxRestart];
|
||||
|
|
@ -237,11 +238,14 @@ namespace VideoAnalysisCore.AICore.GPT
|
|||
catch (Exception e)
|
||||
{
|
||||
errorMSG[i] = e;
|
||||
Console.WriteLine("====================[请求异常,重试]====================");
|
||||
Console.WriteLine(uriBuilder.Uri);
|
||||
Console.WriteLine(e.Message);
|
||||
Console.WriteLine(e.StackTrace);
|
||||
Console.WriteLine("==============================================");
|
||||
var msg = $"""
|
||||
====================[{DateTime.Now.ToString("MM-dd HH:mm:ss")} 请求异常,重试]====================
|
||||
{uriBuilder.Uri}
|
||||
{e.Message}
|
||||
{e.StackTrace}
|
||||
==============================================
|
||||
""";
|
||||
await redisManager.AddTaskLog(data.taskId, $"=>GPT Http请求失败 {msg} 1秒后重试");
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,12 +194,12 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
|||
var checkMessage =
|
||||
$"""
|
||||
请你担任一位专业的视频内容分析教研老师,擅长评估视频内容的结构和逻辑流暢度。
|
||||
核心任务: 请根据我提供的【视频分段方案】【完整字幕文本】【完整字幕文本】,对该分段方案进行严谨评估。
|
||||
核心任务: 请根据我提供的【视频分段方案】【完整字幕文本】,对该分段方案进行严谨评估。
|
||||
评估维度与具体标准:
|
||||
时间间隔检查(硬性指标)
|
||||
严格检查每个分段与下一个分段的开始时间之间的间隔是否大于40秒。
|
||||
输出要求:请列出所有不满足此条件的分段对,并说明具体的时间差。
|
||||
内容结构与主题合理性:
|
||||
分段方案的准确度:评估单个分段内的Content是否与对应时间段内的字幕文本内容匹配(硬性指标,不能存在捏造的内容)
|
||||
主题凝聚力:评估单个分段内的内容是否围绕一个清晰、统一的主题展开,是否存在主题混杂或跳跃的情况。
|
||||
逻辑过渡:评估分段之间的过渡是否自然流畅,后一段是否是前一段内容的合理延伸或转折。
|
||||
知识点分配:检查分段内的知识点是否与分段内容有关联,知识点分配给这个分段是否合理(这项很重要)。
|
||||
|
|
@ -213,17 +213,6 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
|||
输出格式为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>
|
||||
|
|
@ -300,27 +289,19 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
|||
{
|
||||
var keyFrameArr = string.IsNullOrEmpty(taskInfo?.PPTVideoCode) || string.IsNullOrEmpty(taskInfo?.PPTKeyFrame)
|
||||
? $""
|
||||
: $"授课PPT发生了变化的时间是{taskInfo.PPTKeyFrame},基于PPT变化时间点,将字幕内容分割成时间段。每个时间段的起始和结束应接近这些时间点(例如,以时间点为中心,扩展至内容自然过渡处)。";
|
||||
: $"初步划分阶段:授课PPT发生了变化的时间是{taskInfo.PPTKeyFrame},基于PPT变化时间点,将字幕内容分割成时间段。每个时间段的起始和结束应接近这些时间点(例如,以时间点为中心,扩展至内容自然过渡处)。";
|
||||
var resFormat = """[{"StartTime":开始秒(number),"EndTime":结束秒(number),"Stage":阶段(string),"Theme":主题(string),"Content":内容总结(string)}]""";
|
||||
|
||||
var postMessages = string.Empty;
|
||||
switch (taskInfo?.VideoType)
|
||||
{
|
||||
case AttachmentsInfoType.其他资料:
|
||||
case AttachmentsInfoType.新课:
|
||||
case AttachmentsInfoType.复习:
|
||||
case AttachmentsInfoType.活动:
|
||||
case AttachmentsInfoType.班会:
|
||||
break;
|
||||
default:
|
||||
throw new Exception("无效的课程类型");
|
||||
}
|
||||
|
||||
postMessages =
|
||||
$"请通过视频字幕内容分析出视频中课堂的授课阶段。" +
|
||||
$"课堂内容与{taskInfo.Subject}学科下的{sections}章节相关。" +
|
||||
$"完整的课堂标准流程包含以下5个阶段:课程引入/新知讲解/例题精讲/课堂练习/知识总结。" +
|
||||
(taskInfo?.VideoType == AttachmentsInfoType.复习 ? $"但本堂课是习题课,所以大部分阶段是不同的例题讲解内容。" : string.Empty) +
|
||||
$"初步划分阶段:{keyFrameArr}" +
|
||||
(taskInfo?.VideoType == AttachmentsInfoType.复习
|
||||
? $"但本堂课是习题课,所以大部分阶段是不同的例题讲解内容。"
|
||||
: string.Empty) +
|
||||
$"{keyFrameArr}" +
|
||||
$"内容分析:对每个时间段,提取主要讲解内容:识别关键词(如“例题”“证明”“练习”“总结”)和内容结构。" +
|
||||
$"判断阶段类型:如果内容以解题为主,归类为“例题精讲”;如果涉及新知识讲解,归类为“新知讲解”;以此类推。" +
|
||||
$"内容总结:简述该阶段的核心讲解内容70~200字,确保内容与阶段时间内授课内容符合。" +
|
||||
|
|
@ -329,10 +310,12 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
|
|||
$"输出格式要求:内容只返回json格式({resFormat})" +
|
||||
$"字幕格式(开始秒:内容|下一段字幕).以下是包含时间的视频字幕文本。" +
|
||||
$"字幕列表 {captions.Captions} 字幕结束!";
|
||||
|
||||
|
||||
await redisManager.AddTaskLog(taskInfo.Id, $"开始分析视频内容 {tryCount}");
|
||||
//return await chatGPTClient.ChatAsync<VideoKnowRes[]>(taskInfo.Id.ToString(), postMessages, "分析字幕");
|
||||
return await chatClient.ChatAsync<VideoKnowRes[]>(taskInfo.Id.ToString(), postMessages, "分析字幕");
|
||||
var res = await geminiClient.ChatAsync<VideoKnowRes[]>(taskInfo.Id.ToString(), postMessages, "分析字幕");
|
||||
//var r2 = await chatClient.ChatAsync<VideoKnowRes[]>(taskInfo.Id.ToString(), postMessages, "分析字幕");
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
|
|||
var chatReq = new ChatRequest
|
||||
{
|
||||
taskId = task,
|
||||
title=title,
|
||||
model = model,
|
||||
max_tokens =12000,
|
||||
stream = true,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using System.Reflection.PortableExecutable;
|
|||
using System.Runtime.Loader;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using UserCenter.Model;
|
||||
|
|
@ -154,6 +155,8 @@ namespace VideoAnalysisCore.Common
|
|||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
// 忽略所有 null 值属性
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
WriteIndented = WriteIndented // 如果需要美化输出
|
||||
};
|
||||
return JsonSerializer.Serialize(o, jsonOptions);
|
||||
|
|
|
|||
|
|
@ -138,6 +138,15 @@ namespace VideoAnalysisCore.Common
|
|||
if (taskInfo is null )
|
||||
throw new Exception($"任务为null/是教研视频/没有视频课程名称");
|
||||
var fileUrl = taskInfo.MediaUrl;
|
||||
switch (taskInfo?.VideoType)
|
||||
{
|
||||
case AttachmentsInfoType.无:
|
||||
case AttachmentsInfoType.新课:
|
||||
case AttachmentsInfoType.复习:
|
||||
break;
|
||||
default:
|
||||
throw new Exception("无效的课程类型");
|
||||
}
|
||||
if (string.IsNullOrEmpty(fileUrl))
|
||||
{
|
||||
var videoInfo = await vodClient.GetPlayInfoAsync(new AlibabaCloud.SDK.Vod20170321.Models.GetPlayInfoRequest()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace VideoAnalysisCore.Common
|
||||
{
|
||||
|
|
@ -11,81 +12,73 @@ namespace VideoAnalysisCore.Common
|
|||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static List<string> ExtractJsonStrings(this string input)
|
||||
public static List<string> ExtractJsonStrings(this string input)
|
||||
{
|
||||
var results = new List<string>();
|
||||
if (string.IsNullOrWhiteSpace(input)) return results;
|
||||
|
||||
int braceCount = 0;
|
||||
int bracketCount = 0;
|
||||
int startIndex = -1;
|
||||
bool inString = false;
|
||||
bool isEscaped = false;
|
||||
|
||||
for (int i = 0; i<input.Length; i++)
|
||||
{
|
||||
List<string> jsonList = new List<string>();
|
||||
int index = 0;
|
||||
while (index < input.Length)
|
||||
{
|
||||
if (input[index] == '{' || input[index] == '[')
|
||||
{
|
||||
int? endIndex = FindMatchingBracket(input, index);
|
||||
if (endIndex.HasValue)
|
||||
{
|
||||
string candidate = input.Substring(index, endIndex.Value - index + 1);
|
||||
if (IsValidJson(candidate))
|
||||
{
|
||||
jsonList.Add(candidate);
|
||||
index = endIndex.Value + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return jsonList;
|
||||
}
|
||||
char c = input[i];
|
||||
|
||||
private static int? FindMatchingBracket(string str, int start)
|
||||
// 1. 处理转义字符 (例如 \")
|
||||
if (isEscaped)
|
||||
{
|
||||
isEscaped = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\\')
|
||||
{
|
||||
isEscaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. 处理字符串边界
|
||||
if (c == '"')
|
||||
{
|
||||
inString = !inString;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. 如果在字符串内,忽略括号逻辑
|
||||
if (inString) continue;
|
||||
|
||||
// 4. 处理 JSON 对象和数组的开始
|
||||
if (c == '{' || c == '[')
|
||||
{
|
||||
if (braceCount == 0 && bracketCount == 0)
|
||||
{
|
||||
startIndex = i;
|
||||
}
|
||||
if (c == '{') braceCount++;
|
||||
else bracketCount++;
|
||||
}
|
||||
// 5. 处理 JSON 对象和数组的结束
|
||||
else if (c == '}' || c == ']')
|
||||
{
|
||||
if (c == '}') braceCount--;
|
||||
else bracketCount--;
|
||||
|
||||
if (braceCount == 0 && bracketCount == 0 && startIndex != -1)
|
||||
{
|
||||
string potentialJson = input.Substring(startIndex, i - startIndex + 1);
|
||||
if (IsValidJson(potentialJson))
|
||||
{
|
||||
Stack<char> stack = new Stack<char>();
|
||||
bool inString = false;
|
||||
bool inEscape = false;
|
||||
|
||||
for (int i = start; i < str.Length; i++)
|
||||
{
|
||||
char c = str[i];
|
||||
|
||||
if (inEscape)
|
||||
{
|
||||
inEscape = false;
|
||||
}
|
||||
else if (inString)
|
||||
{
|
||||
if (c == '\\')
|
||||
inEscape = true;
|
||||
else if (c == '"')
|
||||
inString = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '{':
|
||||
case '[':
|
||||
stack.Push(c);
|
||||
break;
|
||||
case '}':
|
||||
if (stack.Count == 0 || stack.Peek() != '{')
|
||||
return null;
|
||||
stack.Pop();
|
||||
break;
|
||||
case ']':
|
||||
if (stack.Count == 0 || stack.Peek() != '[')
|
||||
return null;
|
||||
stack.Pop();
|
||||
break;
|
||||
case '"':
|
||||
inString = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stack.Count == 0)
|
||||
return i;
|
||||
}
|
||||
return null; // 括号未完全匹配
|
||||
results.Add(potentialJson);
|
||||
}
|
||||
startIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public static bool IsValidJson(string candidate)
|
||||
{
|
||||
|
|
@ -96,7 +89,7 @@ namespace VideoAnalysisCore.Common
|
|||
JsonDocument.Parse(candidate);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ namespace VideoAnalysisCore.Common
|
|||
if (!SubscribeList.ContainsKey(@enum))
|
||||
throw new Exception(@enum + " 未实现");
|
||||
var tId = taskId.ToString();
|
||||
|
||||
|
||||
await AddTaskLog(tId, "-------------> 开始执行任务 ");
|
||||
try
|
||||
{
|
||||
|
|
@ -457,7 +457,10 @@ namespace VideoAnalysisCore.Common
|
|||
Subscribe = Redis.SubscribeList(RedisExpandKey.ChannelKey, async (taskId) =>
|
||||
{
|
||||
if (taskId is null) return;
|
||||
Subscribe?.Dispose();//取消接收任务监听
|
||||
lock (Redis)
|
||||
{
|
||||
Subscribe?.Dispose();//取消接收任务监听
|
||||
}
|
||||
Redis.LPush(RedisExpandKey.IDTask, taskId);
|
||||
await AddTaskLog(taskId, "-------------> 接收到任务 ");
|
||||
await InsertChannel(RedisChannelEnum.下载文件, taskId);
|
||||
|
|
|
|||
Loading…
Reference in New Issue