优化 AI分析流程文件命名

新增 gemini3的AI分析
This commit is contained in:
小肥羊 2025-12-23 16:14:07 +08:00
parent deb7f80bce
commit fddcdbc1d9
10 changed files with 112 additions and 113 deletions

View File

@ -191,6 +191,7 @@ namespace VideoAnalysisCore.AICore.GPT
{ {
services.AddSingleton<DeepSeekGPTClient>(); services.AddSingleton<DeepSeekGPTClient>();
services.AddSingleton<BestAIClient>(); services.AddSingleton<BestAIClient>();
services.AddSingleton<GeminiGPTClient>();
services.AddSingleton<IBserGPTWorkflow, GTP_Analysis_1>(); services.AddSingleton<IBserGPTWorkflow, GTP_Analysis_1>();

View File

@ -55,6 +55,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
{ {
taskId = task, taskId = task,
model = model, model = model,
title = title,
max_tokens =8000, max_tokens =8000,
stream = true, stream = true,
temperature = 0.2f, temperature = 0.2f,

View File

@ -51,6 +51,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
var chatReq = new ChatRequest var chatReq = new ChatRequest
{ {
taskId = task, taskId = task,
title = title,
model = model ?? ChatGPTType.Deepseek_Reasoner, model = model ?? ChatGPTType.Deepseek_Reasoner,
max_tokens = model == ChatGPTType.Deepseek_Reasoner ? 16000 : max_tokens, max_tokens = model == ChatGPTType.Deepseek_Reasoner ? 16000 : max_tokens,
stream = true, stream = true,

View File

@ -66,10 +66,9 @@ namespace VideoAnalysisCore.AICore.GPT
/// <returns>Return HttpResponseMessage for SSE</returns> /// <returns>Return HttpResponseMessage for SSE</returns>
public async Task<(Usage u, string res, string reasoning)?> ChatSSE(ChatRequest chatReq) public async Task<(Usage u, string res, string reasoning)?> ChatSSE(ChatRequest chatReq)
{ {
var requestBody = chatReq.ToJson();
var i = 5; var i = 5;
PostJsonStream: 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) if (!chatResp.IsSuccessStatusCode)
{ {
await redisManager.AddTaskLog(chatReq.taskId, "=>请求GPT服务器异常 " + chatResp?.StatusCode + " " + await chatResp.Content.ReadAsStringAsync()); await redisManager.AddTaskLog(chatReq.taskId, "=>请求GPT服务器异常 " + chatResp?.StatusCode + " " + await chatResp.Content.ReadAsStringAsync());
@ -182,7 +181,7 @@ namespace VideoAnalysisCore.AICore.GPT
chatResContent = chatResContent?.Trim(); chatResContent = chatResContent?.Trim();
if (string.IsNullOrEmpty(chatResContent)) if (string.IsNullOrEmpty(chatResContent))
throw new Exception("GPT返回结果无有效JSON"); throw new Exception($"GPT返回结果无有效JSON =>{chatResp?.res}");
var startsStr = typeof(T).IsArray ? "[" : "{"; var startsStr = typeof(T).IsArray ? "[" : "{";
var endStr = typeof(T).IsArray ? "]" : "}"; var endStr = typeof(T).IsArray ? "]" : "}";
if (!chatResContent.StartsWith(startsStr)) if (!chatResContent.StartsWith(startsStr))
@ -211,8 +210,10 @@ namespace VideoAnalysisCore.AICore.GPT
} }
public async Task<HttpResponseMessage> PostJsonStreamAsync( 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 uriBuilder = new UriBuilder(path);
var maxRestart = 4; var maxRestart = 4;
var errorMSG = new Exception[maxRestart]; var errorMSG = new Exception[maxRestart];
@ -237,11 +238,14 @@ namespace VideoAnalysisCore.AICore.GPT
catch (Exception e) catch (Exception e)
{ {
errorMSG[i] = e; errorMSG[i] = e;
Console.WriteLine("====================[请求异常,重试]===================="); var msg = $"""
Console.WriteLine(uriBuilder.Uri); ====================[{DateTime.Now.ToString("MM-dd HH:mm:ss")} ,]====================
Console.WriteLine(e.Message); {uriBuilder.Uri}
Console.WriteLine(e.StackTrace); {e.Message}
Console.WriteLine("=============================================="); {e.StackTrace}
==============================================
""";
await redisManager.AddTaskLog(data.taskId, $"=>GPT Http请求失败 {msg} 1秒后重试");
Thread.Sleep(1000); Thread.Sleep(1000);
} }
} }

View File

@ -194,12 +194,12 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
var checkMessage = var checkMessage =
$""" $"""
40 40
Content是否与对应时间段内的字幕文本内容匹配(,)
() ()
@ -213,17 +213,6 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
json {checkResFormat} json {checkResFormat}
"""; """;
return await chatGPTClient.ChatAsync<CheckMessageDto>(taskInfo.Id.ToString(), checkMessage, "结果检查"); 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>
@ -300,27 +289,19 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
{ {
var keyFrameArr = string.IsNullOrEmpty(taskInfo?.PPTVideoCode) || string.IsNullOrEmpty(taskInfo?.PPTKeyFrame) 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 resFormat = """[{"StartTime":开始秒(number),"EndTime":结束秒(number),"Stage":阶段(string),"Theme":主题(string),"Content":内容总结(string)}]""";
var postMessages = string.Empty; var postMessages = string.Empty;
switch (taskInfo?.VideoType)
{
case AttachmentsInfoType.:
case AttachmentsInfoType.:
case AttachmentsInfoType.:
case AttachmentsInfoType.:
case AttachmentsInfoType.:
break;
default:
throw new Exception("无效的课程类型");
}
postMessages = postMessages =
$"请通过视频字幕内容分析出视频中课堂的授课阶段。" + $"请通过视频字幕内容分析出视频中课堂的授课阶段。" +
$"课堂内容与{taskInfo.Subject}学科下的{sections}章节相关。" + $"课堂内容与{taskInfo.Subject}学科下的{sections}章节相关。" +
$"完整的课堂标准流程包含以下5个阶段课程引入/新知讲解/例题精讲/课堂练习/知识总结。" + $"完整的课堂标准流程包含以下5个阶段课程引入/新知讲解/例题精讲/课堂练习/知识总结。" +
(taskInfo?.VideoType == AttachmentsInfoType. ? $"但本堂课是习题课,所以大部分阶段是不同的例题讲解内容。" : string.Empty) + (taskInfo?.VideoType == AttachmentsInfoType.
$"初步划分阶段:{keyFrameArr}" + ? $"但本堂课是习题课,所以大部分阶段是不同的例题讲解内容。"
: string.Empty) +
$"{keyFrameArr}" +
$"内容分析:对每个时间段,提取主要讲解内容:识别关键词(如“例题”“证明”“练习”“总结”)和内容结构。" + $"内容分析:对每个时间段,提取主要讲解内容:识别关键词(如“例题”“证明”“练习”“总结”)和内容结构。" +
$"判断阶段类型:如果内容以解题为主,归类为“例题精讲”;如果涉及新知识讲解,归类为“新知讲解”;以此类推。" + $"判断阶段类型:如果内容以解题为主,归类为“例题精讲”;如果涉及新知识讲解,归类为“新知讲解”;以此类推。" +
$"内容总结简述该阶段的核心讲解内容70~200字,确保内容与阶段时间内授课内容符合。" + $"内容总结简述该阶段的核心讲解内容70~200字,确保内容与阶段时间内授课内容符合。" +
@ -332,7 +313,9 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
await redisManager.AddTaskLog(taskInfo.Id, $"开始分析视频内容 {tryCount}"); await redisManager.AddTaskLog(taskInfo.Id, $"开始分析视频内容 {tryCount}");
//return await chatGPTClient.ChatAsync<VideoKnowRes[]>(taskInfo.Id.ToString(), postMessages, "分析字幕"); //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) catch (Exception ex)
{ {

View File

@ -51,6 +51,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
var chatReq = new ChatRequest var chatReq = new ChatRequest
{ {
taskId = task, taskId = task,
title=title,
model = model, model = model,
max_tokens =12000, max_tokens =12000,
stream = true, stream = true,

View File

@ -12,6 +12,7 @@ using System.Reflection.PortableExecutable;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using UserCenter.Model; using UserCenter.Model;
@ -154,6 +155,8 @@ namespace VideoAnalysisCore.Common
var jsonOptions = new JsonSerializerOptions var jsonOptions = new JsonSerializerOptions
{ {
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
// 忽略所有 null 值属性
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = WriteIndented // 如果需要美化输出 WriteIndented = WriteIndented // 如果需要美化输出
}; };
return JsonSerializer.Serialize(o, jsonOptions); return JsonSerializer.Serialize(o, jsonOptions);

View File

@ -138,6 +138,15 @@ namespace VideoAnalysisCore.Common
if (taskInfo is null ) if (taskInfo is null )
throw new Exception($"任务为null/是教研视频/没有视频课程名称"); throw new Exception($"任务为null/是教研视频/没有视频课程名称");
var fileUrl = taskInfo.MediaUrl; var fileUrl = taskInfo.MediaUrl;
switch (taskInfo?.VideoType)
{
case AttachmentsInfoType.:
case AttachmentsInfoType.:
case AttachmentsInfoType.:
break;
default:
throw new Exception("无效的课程类型");
}
if (string.IsNullOrEmpty(fileUrl)) if (string.IsNullOrEmpty(fileUrl))
{ {
var videoInfo = await vodClient.GetPlayInfoAsync(new AlibabaCloud.SDK.Vod20170321.Models.GetPlayInfoRequest() var videoInfo = await vodClient.GetPlayInfoAsync(new AlibabaCloud.SDK.Vod20170321.Models.GetPlayInfoRequest()

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json; using System.Text.Json;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace VideoAnalysisCore.Common namespace VideoAnalysisCore.Common
{ {
@ -11,81 +12,73 @@ namespace VideoAnalysisCore.Common
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <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>(); char c = input[i];
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;
}
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>(); results.Add(potentialJson);
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; // 括号未完全匹配
} }
startIndex = -1;
}
}
}
return results;
}
public static bool IsValidJson(string candidate) public static bool IsValidJson(string candidate)
{ {
@ -96,7 +89,7 @@ namespace VideoAnalysisCore.Common
JsonDocument.Parse(candidate); JsonDocument.Parse(candidate);
return true; return true;
} }
catch (Exception) catch
{ {
return false; return false;
} }

View File

@ -457,7 +457,10 @@ namespace VideoAnalysisCore.Common
Subscribe = Redis.SubscribeList(RedisExpandKey.ChannelKey, async (taskId) => Subscribe = Redis.SubscribeList(RedisExpandKey.ChannelKey, async (taskId) =>
{ {
if (taskId is null) return; if (taskId is null) return;
Subscribe?.Dispose();//取消接收任务监听 lock (Redis)
{
Subscribe?.Dispose();//取消接收任务监听
}
Redis.LPush(RedisExpandKey.IDTask, taskId); Redis.LPush(RedisExpandKey.IDTask, taskId);
await AddTaskLog(taskId, "-------------> 接收到任务 "); await AddTaskLog(taskId, "-------------> 接收到任务 ");
await InsertChannel(RedisChannelEnum., taskId); await InsertChannel(RedisChannelEnum., taskId);