Compare commits

..

2 Commits

Author SHA1 Message Date
小肥羊 613019da82 修复 deepseek 流式传入失败问题
新增 阿里云api
2025-02-13 18:07:52 +08:00
小肥羊 67f3c4fb57 新增 绑定时间片段知识点 2025-02-11 15:18:44 +08:00
13 changed files with 201 additions and 152 deletions

View File

@ -11,7 +11,9 @@
@for (int i = 0; i < videoKnows.Length; i++) @for (int i = 0; i < videoKnows.Length; i++)
{ {
var item = videoKnows[i]; var item = videoKnows[i];
<button class="kBtn" onclick="spClick(@i,this)">@getF(item) @item.Theme </button> <button class="kBtn" onclick="spClick(@i,this)" title="@item.Content">
<span>@getF(item) @item.Theme</span>
<br /><span class="kSpan">#@item.KnowPointId @item.KnowPoint</span></button>
} }
</div> </div>
} }

View File

@ -11,7 +11,10 @@
float: left; float: left;
overflow-x: hidden; overflow-x: hidden;
} }
.kSpan {
color:rgba(120, 120, 120,0.66);
font-size:0.8rem;
}
video { video {
width: 94%; width: 94%;
height: 85%; height: 85%;

View File

@ -34,6 +34,10 @@
"DeepSeek": { "DeepSeek": {
"Host": "https://api.deepseek.com/chat/completions", "Host": "https://api.deepseek.com/chat/completions",
"ApiKey": "sk-88d3d2bc3dae4d50854b2569b281cf76" "ApiKey": "sk-88d3d2bc3dae4d50854b2569b281cf76"
},
"aliyun": {
"Host": "https://dashscope.aliyuncs.com/compatible-mode/",
"ApiKey": "sk-1742c2bf7b9846ae835de598dc6c427b"
} }
}, },
"DB": { "DB": {

View File

@ -37,22 +37,22 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
/// <summary> /// <summary>
/// 一个对象,用于指定模型必须输出的格式。设置为 enable 结构化输出,确保模型与您提供的 JSON 匹配 图式。 /// 一个对象,用于指定模型必须输出的格式。设置为 enable 结构化输出,确保模型与您提供的 JSON 匹配 图式。
/// </summary> /// </summary>
public object response_format = new { type = "json_object" }; public object response_format { get; set; } = new { type = "json_object" };
/// <summary> /// <summary>
/// 流式返回 /// 流式返回
/// </summary> /// </summary>
public bool stream =false; public bool stream { get; set; } = false;
/// <summary> /// <summary>
/// 您希望模型为此请求生成的 Output types。 大多数模型都能够生成文本,这是 /// 您希望模型为此请求生成的 Output types。 大多数模型都能够生成文本,这是
/// <para>默认设置: ["text"]</para> /// <para>默认设置: ["text"]</para>
/// <para>该模型还可用于生成音频。自 请求此模型同时生成文本和音频响应,您可以 用gpt-4o-audio-preview["text", "audio"]</para> /// <para>该模型还可用于生成音频。自 请求此模型同时生成文本和音频响应,您可以 用gpt-4o-audio-preview["text", "audio"]</para>
/// </summary> /// </summary>
public string modalities = "[\"json\"]"; public string modalities { get; set; } = "[\"json\"]";
/// <summary> /// <summary>
/// ai引导新话题 /// ai引导新话题
/// <para>默认-2 范围[-2~2]</para> /// <para>默认-2 范围[-2~2]</para>
/// </summary> /// </summary>
public int presence_penalty = -2; public int presence_penalty { get; set; } = -2;
} }
public class Message public class Message

View File

@ -148,9 +148,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
if (questionRes.Length <= 3) if (questionRes.Length <= 3)
throw new Exception("视频分段数量过低 =>" + questionRes.Length); throw new Exception("视频分段数量过低 =>" + questionRes.Length);
if (questionRes.Count(s => s.ThemeDetalis == questionRes.First().ThemeDetalis) >= 3)
throw new Exception("视频分段主题重复 =>" + questionRes.First().ThemeDetalis);
for (int i = 0; i < questionRes.Length; i++) for (int i = 0; i < questionRes.Length; i++)
{ {
var item = questionRes[i]; var item = questionRes[i];

View File

@ -10,14 +10,29 @@ using AntDesign;
using OneOf.Types; using OneOf.Types;
using System.Net; using System.Net;
using VideoAnalysisCore.AICore.GPT.KIMI; using VideoAnalysisCore.AICore.GPT.KIMI;
using System.Threading;
using System;
using System.IO;
using VideoAnalysisCore.AICore.GPT.ChatGPT;
namespace VideoAnalysisCore.AICore.GPT.DeepSeek namespace VideoAnalysisCore.AICore.GPT.DeepSeek
{ {
public class DeepSeekGPTClient public class DeepSeekGPTClient
{ {
public static string Host = AppCommon.Config.ChatGpt.DeepSeek.Host;
public static string ApiKey = AppCommon.Config.ChatGpt.DeepSeek.ApiKey; private readonly string Path = "v1/chat/completions";
public static string Host = AppCommon.Config.ChatGpt.aliyun.Host;
public static string ApiKey = AppCommon.Config.ChatGpt.aliyun.ApiKey;
//private readonly string Path = "";
//public static string Host = AppCommon.Config.ChatGpt.DeepSeek.Host;
//public static string ApiKey = AppCommon.Config.ChatGpt.DeepSeek.ApiKey;
//public static string Host = AppCommon.Config.ChatGpt.ChatGpt.Host;
//public static string ApiKey = AppCommon.Config.ChatGpt.ChatGpt.ApiKey;
//private readonly string Path = "v1/chat/completions";
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
@ -32,27 +47,47 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
/// </summary> /// </summary>
/// <param name="chatReq"></param> /// <param name="chatReq"></param>
/// <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,string reasoning)?> Chat(ChatRequest chatReq)
{ {
chatReq.model = "deepseek-r1";
if (chatReq.stream) return await ChatSSE(chatReq); if (chatReq.stream) return await ChatSSE(chatReq);
postStar:
var requestBody = System.Text.Json.JsonSerializer.Serialize(chatReq); var requestBody = System.Text.Json.JsonSerializer.Serialize(chatReq);
var chatResp = await PostJsonStreamAsync("v1/chat/completions", requestBody); HttpResponseMessage chatResp = PostJsonStream(Path, requestBody);
var res1 = await chatResp.Content.ReadAsStringAsync(); var res1 = await chatResp.Content.ReadAsStringAsync();
if (res1 == null || string.IsNullOrEmpty(res1)|| !chatResp.IsSuccessStatusCode)
{
Console.WriteLine(DateTime.Now + $"=>GPT请求失败重试 Code = {chatResp.StatusCode} Res={res1}");
goto postStar;
}
//throw new Exception($" GPT模型返回异常 返回参数: " +
// $" {System.Text.Json.JsonSerializer.Serialize(res1)}");
var res = await chatResp.Content.ReadFromJsonAsync<ChatRes>(); var res = await chatResp.Content.ReadFromJsonAsync<ChatRes>();
if (res is null || res.error != null) if (res is null || res.error != null)
throw new Exception($" ChatGPT模型返回异常 返回参数: " + throw new Exception($" GPT模型返回异常 返回参数: " +
$" {System.Text.Json.JsonSerializer.Serialize(res)}"); $" {System.Text.Json.JsonSerializer.Serialize(res)}");
var chatResContent = res?.choices.FirstOrDefault()?.message.content.Trim(); var chatResContent = res?.choices.FirstOrDefault()?.message.content.Trim();
var chatResReasoning = string.Empty;
if (chatResContent.StartsWith("<think"))
{
chatResReasoning = chatResContent.Substring(7, chatResContent.IndexOf("</think")-7);
chatResContent = chatResContent
.Substring(chatResContent.IndexOf("</think>")+8);
}
else
chatResReasoning = res?.choices.FirstOrDefault()?.message.reasoning_content?.Trim();
if (string.IsNullOrEmpty(chatResContent)) if (string.IsNullOrEmpty(chatResContent))
return null; return null;
return (res.usage, chatResContent); return (res.usage, chatResContent, chatResReasoning);
} }
private async Task<HttpResponseMessage> PostJsonStreamAsync(string path, string json) private HttpResponseMessage PostJsonStream(string path, string json)
{ {
var uriBuilder = new UriBuilder(Host + path); var uriBuilder = new UriBuilder(Host + Path);
var maxRestart = 4; var maxRestart = 4;
var errorMSG = new Exception[maxRestart]; var errorMSG = new Exception[maxRestart];
for (int i = 0; i < maxRestart; i++) for (int i = 0; i < maxRestart; i++)
@ -67,8 +102,10 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
client.DefaultRequestHeaders.ConnectionClose = true; client.DefaultRequestHeaders.ConnectionClose = true;
var content = new StringContent(json, Encoding.UTF8, "application/json"); var request = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri);
return await client.PostAsync(uriBuilder.Uri, content); request.Content = new StringContent(json, Encoding.UTF8, "application/json");
return client.Send(request, HttpCompletionOption.ResponseHeadersRead);
} }
catch (Exception e) catch (Exception e)
{ {
@ -79,8 +116,8 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
Console.WriteLine(e.StackTrace); Console.WriteLine(e.StackTrace);
Console.WriteLine("=============================================="); Console.WriteLine("==============================================");
}
Thread.Sleep(1000); Thread.Sleep(1000);
}
} }
throw errorMSG.Last(s => s != null); throw errorMSG.Last(s => s != null);
} }
@ -93,28 +130,32 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
/// </summary> /// </summary>
/// <param name="chatReq"></param> /// <param name="chatReq"></param>
/// <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, string reasoning)?> ChatSSE(ChatRequest chatReq)
{ {
chatReq.stream = true; chatReq.stream = true;
var requestBody = System.Text.Json.JsonSerializer.Serialize(chatReq); var requestBody = System.Text.Json.JsonSerializer.Serialize(chatReq);
var chatResp = await PostJsonStreamAsync("/v1/chat/completions", requestBody); var chatResp = PostJsonStream(string.Empty, requestBody);
using var stream = await chatResp.Content.ReadAsStreamAsync(); using var stream = chatResp.Content.ReadAsStream();
using var reader = new StreamReader(stream, Encoding.UTF8); using var reader = new StreamReader(stream, Encoding.UTF8);
string line; string line;
var messageBuilder = new StringBuilder(); var messageBuilder = new StringBuilder();
var messageBuilder1 = new StringBuilder();
var lastChat = new ChatResSSE(); var lastChat = new ChatResSSE();
var splitCount = "data:".Length; var splitCount = "data:".Length;
while ((line = await reader.ReadLineAsync()) != null) while (true)
{ {
if (line.EndsWith("[DONE]")) line = reader.ReadLine();
if (line is null || string.IsNullOrEmpty(line)) continue;
else if (line.EndsWith("[DONE]"))
{ {
// 表示一条消息结束 // 表示一条消息结束
string message = messageBuilder.ToString(); string message = messageBuilder.ToString();
string message2 = messageBuilder1.ToString();
messageBuilder.Clear(); messageBuilder.Clear();
var u = lastChat?.usage; var u = lastChat?.usage;
if (u == null || string.IsNullOrEmpty(message)) if (u == null || string.IsNullOrEmpty(message))
return null; return null;
return (u, message); return (u, message, message2);
} }
else if (line.StartsWith("data:")) else if (line.StartsWith("data:"))
{ {
@ -122,9 +163,13 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
{ {
var data = System.Text.Json.JsonSerializer.Deserialize<ChatResSSE>(line.Substring(splitCount).Trim()); var data = System.Text.Json.JsonSerializer.Deserialize<ChatResSSE>(line.Substring(splitCount).Trim());
lastChat = data; lastChat = data;
var str = data?.choices.FirstOrDefault()?.delta.content; var delta = data?.choices.FirstOrDefault()?.delta;
var str = delta?.content;
var strReasoning = delta?.reasoning_content;
if (!string.IsNullOrEmpty(str)) if (!string.IsNullOrEmpty(str))
messageBuilder.Append(str); messageBuilder.Append(str);
if (!string.IsNullOrEmpty(strReasoning))
messageBuilder1.Append(strReasoning);
} }
catch (Exception e) catch (Exception e)
{ {
@ -135,7 +180,6 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
} }
} }
} }
return null;
} }
} }

View File

@ -42,17 +42,18 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
/// <summary> /// <summary>
/// 一个对象,用于指定模型必须输出的格式。设置为 enable 结构化输出,确保模型与您提供的 JSON 匹配 图式。 /// 一个对象,用于指定模型必须输出的格式。设置为 enable 结构化输出,确保模型与您提供的 JSON 匹配 图式。
/// </summary> /// </summary>
public object response_format = new { type = "json_object" }; public object response_format { get; set; } = new { type = "json_object" };
/// <summary> /// <summary>
/// 流式返回 /// 流式返回
/// </summary> /// </summary>
public bool stream =false; public bool stream { get; set; } = false;
/// <summary> /// <summary>
/// 您希望模型为此请求生成的 Output types。 大多数模型都能够生成文本,这是 /// 您希望模型为此请求生成的 Output types。 大多数模型都能够生成文本,这是
/// <para>默认设置: ["text"]</para> /// <para>默认设置: ["text"]</para>
/// <para>该模型还可用于生成音频。自 请求此模型同时生成文本和音频响应,您可以 用gpt-4o-audio-preview["text", "audio"]</para> /// <para>该模型还可用于生成音频。自 请求此模型同时生成文本和音频响应,您可以 用gpt-4o-audio-preview["text", "audio"]</para>
/// </summary> /// </summary>
public string modalities = "[\"json\"]"; public string modalities { get; set; } = "[\"json\"]";
public object stream_options { get; set; } = new { include_usage = true };
} }
public class Message public class Message

View File

@ -20,18 +20,20 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
private readonly DeepSeekGPTClient chatClient; private readonly DeepSeekGPTClient chatClient;
private readonly Repository<CourseGradingCriteria> criteriaDB; private readonly Repository<CourseGradingCriteria> criteriaDB;
private readonly Repository<VideoTask> videoTaskDB; private readonly Repository<VideoTask> videoTaskDB;
private readonly Repository<VideoKonwPoint> videoKonwPointDB;
private readonly Repository<KnowledgeInfo> knowledgeInfoDB; private readonly Repository<KnowledgeInfo> knowledgeInfoDB;
/// <summary> /// <summary>
/// 初始化 /// 初始化
/// </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, Repository<KnowledgeInfo> knowledgeInfoDB) public DeepSeek_GPT(DeepSeekGPTClient moonshotClient, Repository<CourseGradingCriteria> criteria, Repository<VideoTask> videoTaskDB, Repository<KnowledgeInfo> knowledgeInfoDB, Repository<VideoKonwPoint> videoKonwPointDB)
{ {
chatClient = moonshotClient; chatClient = moonshotClient;
criteriaDB = criteria; criteriaDB = criteria;
this.videoTaskDB = videoTaskDB; this.videoTaskDB = videoTaskDB;
this.knowledgeInfoDB = knowledgeInfoDB; this.knowledgeInfoDB = knowledgeInfoDB;
this.videoKonwPointDB = videoKonwPointDB;
} }
@ -49,79 +51,47 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
var subject = "数学"; var subject = "数学";
var xkwKnows = await knowledgeInfoDB.AsQueryable() var xkwKnows = await knowledgeInfoDB.AsQueryable()
.Where(s => s.Course_Id == 27 .Where(s => s.Course_Id == 27
&& s.Depth == 2) && (s.Depth == 3
|| s.Depth == 2 ))
.Select(s => s.Name).ToArrayAsync(); .Select(s => s.Name).ToArrayAsync();
string title = taskInfo.MediaName; string title = taskInfo.MediaName;
var fileNameResFormat = "{授课章节: string|null, 授课内容:string}"; var fileNameResFormat = "{授课章节: string|null}";
var fileNamePostMessages = title + var fileNamePostMessages = title +
" 这是一堂课的标题,请你帮我分析一些关于课堂方面的内容." + " 这是一堂课的标题,请你基于标题帮我分析出这堂课所讲授的内容与最恰当的授课章节(尽可能的关联最贴切的章节并且就保留一个)." +
$"1.分析出高中{subject}课堂授课的主要章节(例如 章节: 数列),章节范围限定在[{string.Join(',', xkwKnows)}]范围." + $"章节范围限定在[{string.Join(',', xkwKnows)}]范围." +
$"2.分析出这堂课的主要授课内容." +
$"输出格式 json字符串 对象格式{fileNameResFormat}"; $"输出格式 json字符串 对象格式{fileNameResFormat}";
var fileNameInfoRes = await ChatAsync<FileNameInfo>(task, fileNamePostMessages, null, "deepseek-chat"); var fileNameInfoRes = await ChatAsync<FileNameInfo>(task, fileNamePostMessages, null);//, "deepseek-chat");
var captions = ExpandFunction.GetSpeakerCaptions(task); var captions = ExpandFunction.GetSpeakerCaptions(task);
var maxVideoTime = captions?.TimeBase?.LastOrDefault()?.End ?? 0; var maxVideoTime = captions?.TimeBase?.LastOrDefault()?.End ?? 0;
var criteriaBuilder = new StringBuilder(); var criteriaBuilder = new StringBuilder();
//var resFormat = """[{"StartTime":开始秒(number),"EndTime":结束秒(number),"Section":章节(string),"Theme":主题(string),"ThemeDetalis":主题详情(string),"Content":内容总结(string)}]"""; var resFormat = """[{"StartTime":开始秒(number),"Theme":主题(string),"KnowPoint":主题方法点(string),"KnowPointId":主题方法点Id(string)"Content":内容总结(string)}]""";
var resFormat = """[{"StartTime":开始秒(number),"Section":章节(string),"Theme":主题(string),"ThemeDetalis":主题详情(string),"Content":内容总结(string)}]"""; var know = await knowledgeInfoDB.GetFirstAsync(s =>s.Course_Id==27 && s.Name == fileNameInfoRes.);
var know = await knowledgeInfoDB.GetFirstAsync(s => s.Name == fileNameInfoRes.); if (know is null)
throw new Exception("未能找到对应知识点=>"+ fileNameInfoRes.);
var knowledgeInfos = await knowledgeInfoDB.AsQueryable().ToChildListAsync(s => s.Parent_Id, know.Id); var knowledgeInfos = await knowledgeInfoDB.AsQueryable().ToChildListAsync(s => s.Parent_Id, know.Id);
var knows = string.Join(',', knowledgeInfos.Select(s => s.Name)); var knows = string.Join(',', knowledgeInfos.Select(s => s.Id + "|"+ s.Name));
var postMessages = var postMessages =
$"你的任务是分析视频字幕内容并提取出中国高考考试试题方法点,然后根据步骤分析出知识片段" + $"你的任务是分析视频字幕内容并提取出中国高考考试试题方法点,然后根据步骤分析出知识片段" +
$"按以下步骤完成:" + $"按以下步骤完成:" +
$"1.识别方法点:提取字幕内容中与{subject}考试属于{fileNameInfoRes.授课章节}章节相关的方法点。" + $"1.识别方法点:提取字幕内容中与{subject}考试属于{fileNameInfoRes.授课章节}章节相关的方法点。" +
$"2.分析总结:基于提取出的方法点名称来匹配我提供的方法点名称" + $"2.关联合并知识内容相似的知识点来合并为知识片段。如果知识片段时长超过20分钟则考虑拆封为2个更加细微贴切的知识片段" +
$"提供的方法点名称({fileNameInfoRes.授课章节}的基本概念,{fileNameInfoRes.授课章节}的练习与应用,{fileNameInfoRes.授课章节}的例题讲解,{knows})。" + $"3.分配空余未使用的时间段到内容相近的知识片段时间区间来获取更加详细的上下文,但是请避免知识片段之间时间重合。" +
$"3.关联合并知识内容相似的知识点来合并为知识片段。" + $"4.分析总结:基于提取出的知识片段内容称来匹配我提供的知识点,来作为片段的知识点(请确保匹配的知识点是我提供的,否者片段知识点值为空字符串)" +
$"知识片段使用关联知识点中的最小开始时间主题为关联知识点的主题分析,内容总结为关联知识点的内容总结分析。" + $"提供的方法点名称({knows})。 格式 (方法点Id|方法点名称) 如果一个知识片段出现多个知识点那么知识点ID与知识点名称都用逗号','分割" +
$"4.基于'知识片段'的'内容总结'加上'主题'来分析这个片段对主题的讲解内容为新的主题 例如(数列的基本概念)。" +
$"5.分配空余未使用的时间段到内容相近的知识片段时间区间来获取更加详细的上下文,但是请避免知识片段之间时间重合。" +
$"输入:包含时间戳的视频字幕文本。" +
$"以下是包含时间的视频字幕文本。" +
$"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" +
$"输出格式({resFormat})";
postMessages =
$"你现在需要处理用户提供的视频字幕内容,从中提取与中国高考数学考试{fileNameInfoRes.授课章节}相关的方法点,并按照指定的步骤进行分析。首先,你得仔细阅读用户的需求,确保每一步都正确执行" +
$"用户的需求:" +
$"1.要求识别字幕中的方法点,这些方法点需要属于{fileNameInfoRes.授课章节}章节。需要你通读字幕,找到这些关键词出现的地方,并记录对应的时间戳" +
$"2.是根据提取的方法点名称匹配用户提供的列表。例如,如果字幕中提到“递增数列”或“通项公式”,你需要确认这些是否在用户给出的方法点列表中,并将它们归类到正确的名称下" +
$"用户提供的方法点名称({fileNameInfoRes.授课章节}的基本概念,{fileNameInfoRes.授课章节}的练习与应用,{fileNameInfoRes.授课章节}的例题讲解,{knows})。" +
$"3.关联合并相似的知识点,形成知识片段。比如,如果多个时间段都讨论数列的基本概念,你需要将它们合并,并选择最早的时间作为开始时间。同时,需要总结这些片段的内容,并生成新的主题名称,如“数列的基本概念”。" +
$"如果多个片段都涉及数列的定义和例子,可能合并为“数列的基本概念”" +
$"4.分配未使用的时间段到相近的知识片段,避免时间重叠。这可能需要检查是否有剩余的时间段未被归类,并将其合并到合适的知识片段中,以丰富上下文" +
$"输入:包含时间戳的视频字幕文本。" + $"输入:包含时间戳的视频字幕文本。" +
$"以下是包含时间的视频字幕文本。" + $"以下是包含时间的视频字幕文本。" +
$"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" + $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" +
$"输出格式({resFormat})"; $"输出格式({resFormat})";
Console.WriteLine("=>开始分析视频内容");
var questionRes = await ChatAsync<VideoKnowRes[]>(task, postMessages, null); var questionRes = await ChatAsync<VideoKnowRes[]>(task, postMessages, null);
//var postMessages =
// $"你的任务是分析视频字幕内容,精准提取出与中国高考数学考试相关的试题方法点。" +
// $"按以下步骤完成:" +
// $"1.准确识别方法点:从字幕中提取与考试紧密相关的方法点,尤其关注与给定方法点类别相关的内容" +
// $"2.深入分析总结:依据给定的方法点类别进行约束,确定提取出的方法点所属类别。" +
// $"给定方法点包括(基本概念,课堂练习,例题讲解,解题技巧...)。" +
// $"3.细致分类方法点:按照学科方法点进行分类,具体细化到特定章节与主题,格式为 “章节:具体章节名称 主题:具体主题名称" +
// $"4.合理合并相似方法点为内容片段,确保内容的连贯性和逻辑性" +
// $"5.关联每个内容片段的方法点所有的时间并结构化输出。" +
// $"6.基于内容片段的内容总结加上主题来分析这个片段对主题的讲解内容为新的主题 例(数列的基本概念)。" +
// $"尽可能延内容片段时间区间,以获取详细的方法点上下文。" +
// $"输入:包含时间戳的视频字幕文本。输出格式:开始秒,结束秒,主题,内容总结" ;
//var postM2 = $"以下是包含时间的视频字幕文本。" +
// $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" +
// $"返回固定的JSON格式({resFormat})";
//var questionRes = await ChatAsync<VideoKnowRes[]>(task, postMessages, postM2, resFormat);
if (questionRes.Length <= 3) if (questionRes.Length <= 3)
throw new Exception("视频分段数量过低 =>" + questionRes.Length); throw new Exception("视频分段数量过低 =>" + questionRes.Length);
if (questionRes.Count(s => s.ThemeDetalis == questionRes.First().ThemeDetalis) >= 3)
throw new Exception("视频分段主题重复 =>" + questionRes.First().ThemeDetalis);
for (int i = 0; i < questionRes.Length; i++) for (int i = 0; i < questionRes.Length; i++)
{ {
var item = questionRes[i]; var item = questionRes[i];
@ -131,24 +101,26 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
item.EndTime = (int)(questionRes[i + 1]?.StartTime ?? 0) - 1; item.EndTime = (int)(questionRes[i + 1]?.StartTime ?? 0) - 1;
} }
await videoKonwPointDB.DeleteAsync(s => s.VideoTaskId == taskId);
var data = questionRes.Select(s => new VideoKonwPoint()
{
Content = s.Content,
Theme = s.Theme,
StartTime = s.StartTime,
EndTime =s.EndTime,
KnowPoint = s.KnowPoint,
KnowPointId = s.KnowPointId,
TagId = taskInfo.TagId,
VideoTaskId = taskInfo.Id,
}).ToList();
await videoKonwPointDB.InsertRangeAsync(data);
await RedisExpand.Redis await RedisExpand.Redis
.HMSetAsync(RedisExpandKey.Task(task), "VideoKnows", questionRes); .HMSetAsync(RedisExpandKey.Task(task), "VideoKnows", questionRes);
//var postMessages1 =
// $"你的任务是分析json内容并合并含义相似的主题为新的主题" +
// $"按以下步骤完成:" +
// $"1.合理合并主题字段重复相似的对象为新的json对象确保内容的连贯性和逻辑性。" +
// $"2.合并对象属性持续时间低于60秒的对象" +
// $"3.结构化输出。" +
// $"输入json对象 包含总结开始秒,结束秒,持续时间,主题,章节,内容总结" +
// $"以下是包含json内容的文本。" +
// $" {JsonSerializer.Serialize(questionRes)}" +
// $"返回固定的JSON格式({resFormat})";
//var questionRes1 = await ChatAsync<VideoKnowRes[]>(task, postMessages1, resFormat);
////questionRes1 = MergeRes(questionRes1).ToArray();
var gptRes = new TaskRes(captions); var gptRes = new TaskRes(captions);
await RedisExpand.Redis await RedisExpand.Redis
.HMSetAsync(RedisExpandKey.Task(task), "ChatAnalysis", gptRes); .HMSetAsync(RedisExpandKey.Task(task), "ChatAnalysis", gptRes);
@ -170,6 +142,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
max_tokens = maxTokens, max_tokens = maxTokens,
temperature = 0.2f, temperature = 0.2f,
messages = messageArr messages = messageArr
}; };
RedisExpand.SetTaskGPTReqCached(task, chatRep); RedisExpand.SetTaskGPTReqCached(task, chatRep);
@ -178,7 +151,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
if (string.IsNullOrEmpty(chatResContent)) if (string.IsNullOrEmpty(chatResContent))
throw new Exception("GPT返回message无效结果"); throw new Exception("GPT返回message无效结果");
if (chatResp != null) if (chatResp != null)
RedisExpand.SetTaskGPTCached(task, new object[] { chatResp.Value.res, chatResp.Value.u }); RedisExpand.SetTaskGPTCached(task, new object[] { chatResp.Value.res, chatResp.Value.u, chatResp.Value.reasoning });
chatResContent = chatResContent?.Replace("字幕内容", "课堂情况"); chatResContent = chatResContent?.Replace("字幕内容", "课堂情况");
chatResContent = chatResContent?.Replace("\n", ""); chatResContent = chatResContent?.Replace("\n", "");

View File

@ -26,13 +26,13 @@ namespace VideoAnalysisCore.AICore.GPT.Dto
/// </summary> /// </summary>
public string? Theme { get; set; } public string? Theme { get; set; }
/// <summary> /// <summary>
/// 主题详情 /// 知识点
/// </summary> /// </summary>
public string? ThemeDetalis { get; set; } public string? KnowPoint { get; set; }
/// <summary> /// <summary>
/// 章节 /// 知识点ID
/// </summary> /// </summary>
public string? Section { get; set; } public string? KnowPointId { get; set; }
/// <summary> /// <summary>
/// 内容总结 /// 内容总结
/// </summary> /// </summary>

View File

@ -19,8 +19,6 @@ using VideoAnalysisCore.Enum;
using VideoAnalysisCore.Interface; using VideoAnalysisCore.Interface;
using VideoAnalysisCore.Model; using VideoAnalysisCore.Model;
using VideoAnalysisCore.Model.Dto; using VideoAnalysisCore.Model.Dto;
using Whisper.net;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace VideoAnalysisCore.Common namespace VideoAnalysisCore.Common
{ {
@ -310,6 +308,7 @@ namespace VideoAnalysisCore.Common
public GptConfig ChatGpt { get; set; } = new GptConfig(); public GptConfig ChatGpt { get; set; } = new GptConfig();
public GptConfig DeepSeek { get; set; } = new GptConfig(); public GptConfig DeepSeek { get; set; } = new GptConfig();
public GptConfig KIMI { get; set; } = new GptConfig(); public GptConfig KIMI { get; set; } = new GptConfig();
public GptConfig aliyun { get; set; } = new GptConfig();
} }
/// <summary> /// <summary>

View File

@ -2,8 +2,10 @@
using FreeRedis; using FreeRedis;
using FreeRedis.Internal; using FreeRedis.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Schema;
using SqlSugar.IOC; using SqlSugar.IOC;
using System; using System;
using System.Text.Json;
using System.Threading.Channels; using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
@ -166,7 +168,7 @@ namespace VideoAnalysisCore.Common
var taskData = await DbScoped.SugarScope.Queryable<VideoTask>() var taskData = await DbScoped.SugarScope.Queryable<VideoTask>()
.FirstAsync(s => s.Id == tId); .FirstAsync(s => s.Id == tId);
taskData.ChatAnalysis = gptRes; taskData.ChatAnalysis =JsonSerializer.Serialize(gptRes);
taskData.ChatAnalysisScore = gptRes?.Assessment?.Merit?.Sum(s => s.Score) ?? 0; taskData.ChatAnalysisScore = gptRes?.Assessment?.Merit?.Sum(s => s.Score) ?? 0;
taskData.ErrorMessage = string.Empty; taskData.ErrorMessage = string.Empty;
taskData.LastEnum = RedisChannelEnum.EndTask; taskData.LastEnum = RedisChannelEnum.EndTask;

View File

@ -0,0 +1,68 @@
using SqlSugar;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Text.Json;
using UserCenter.Model.Enum;
using VideoAnalysisCore.AICore.GPT.Dto;
using VideoAnalysisCore.AICore.SherpaOnnx;
using VideoAnalysisCore.Enum;
using VideoAnalysisCore.Interface;
using Whisper.net;
namespace VideoAnalysisCore.Model
{
/// <summary>
/// 视频任务知识片段
/// </summary>
[SugarTable("videokonwpoint")]
public class VideoKonwPoint : IDB
{
/// <summary>
/// id
/// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public long Id { get; set; }
/// <summary>
/// 视频任务id
/// </summary>
public long VideoTaskId { get; set; }
/// <summary>
/// 自定义Id [任务视频自定义id]
/// <see cref="VideoTask.TagId"/>
/// </summary>
[SugarColumn(Length = 500, IsNullable = true)]
public string? TagId { get; set; }
/// <summary>
/// 开始时间
/// </summary>
[SugarColumn( IsNullable = true)]
public float? StartTime { get; set; }
/// <summary>
/// 结束时间
/// </summary>
[SugarColumn(IsNullable = true)]
public float? EndTime { get; set; }
/// <summary>
/// 持续时间
/// </summary>
[SugarColumn(IsIgnore = true)]
public float? KeepTime => (EndTime ?? 0) - StartTime ?? 0;
/// <summary>
/// 主题
/// </summary>
public string? Theme { get; set; }
/// <summary>
/// 知识点
/// </summary>
public string? KnowPoint { get; set; }
/// <summary>
/// 知识点ID
/// </summary>
public string? KnowPointId { get; set; }
/// <summary>
/// 内容总结
/// </summary>
public string? Content { get; set; }
}
}

View File

@ -72,44 +72,17 @@ namespace VideoAnalysisCore.Model
/// 字幕缓存 /// 字幕缓存
/// </summary> /// </summary>
[SugarColumn(ColumnName = "Captions", ColumnDataType = "longtext", IsNullable = true)] [SugarColumn(ColumnName = "Captions", ColumnDataType = "longtext", IsNullable = true)]
public string _Captions { get; set; } = "[]"; public string Captions { get; set; } = "[]";
/// <summary>
/// 字幕缓存
/// </summary>
[SugarColumn(IsIgnore = true)]
public SenseVoiceRes[]? Captions
{
get=> JsonSerializer.Deserialize<SenseVoiceRes[]>(_Captions ?? "[]");
set => _Captions = JsonSerializer.Serialize(value);
}
/// <summary> /// <summary>
/// 说话人日志解析缓存 /// 说话人日志解析缓存
/// </summary> /// </summary>
[SugarColumn(ColumnName = "Speaker", ColumnDataType = "longtext", IsNullable = true)] [SugarColumn(ColumnName = "Speaker", ColumnDataType = "longtext", IsNullable = true)]
public string _Speaker { get; set; } = "[]"; public string Speaker { get; set; } = "[]";
/// <summary>
/// 说话人日志解析缓存
/// </summary>
[SugarColumn(IsIgnore = true)]
public OfflineSpeakerRes[]? Speaker
{
get => JsonSerializer.Deserialize<OfflineSpeakerRes[]>(_Speaker ?? "[]");
set => _Speaker = JsonSerializer.Serialize(value);
}
/// <summary> /// <summary>
/// Chat模型分析缓存 /// Chat模型分析缓存
/// </summary> /// </summary>
[SugarColumn(ColumnName = "ChatAnalysis", ColumnDataType = "longtext", IsNullable = true)] [SugarColumn(ColumnName = "ChatAnalysis", ColumnDataType = "longtext", IsNullable = true)]
public string _ChatAnalysis { get; set; } = "{}"; public string ChatAnalysis { get; set; } = "{}";
/// <summary>
/// Chat模型分析缓存
/// </summary>
[SugarColumn(IsIgnore = true)]
public TaskRes? ChatAnalysis
{
get => JsonSerializer.Deserialize<TaskRes>(_ChatAnalysis ?? "{}");
set => _ChatAnalysis = JsonSerializer.Serialize(value);
}
/// <summary> /// <summary>
/// AI模型评分 /// AI模型评分
/// </summary> /// </summary>
@ -133,24 +106,6 @@ namespace VideoAnalysisCore.Model
/// </summary> /// </summary>
[SugarColumn(ColumnDataType = "varchar", Length = 255)] [SugarColumn(ColumnDataType = "varchar", Length = 255)]
public string StartTime { get; set; } ="{}"; public string StartTime { get; set; } ="{}";
/// <summary>
/// 开始时间轴
/// <para>逻辑字段</para>
/// </summary>
[SugarColumn(IsIgnore = true)]
public Dictionary<RedisChannelEnum, DateTime> StartTimeDic
{
get
{
return JsonSerializer.Deserialize<Dictionary<RedisChannelEnum, DateTime>>(StartTime??"{}")
??new Dictionary<RedisChannelEnum, DateTime>();
}
set
{
StartTime = JsonSerializer.Serialize(value);
}
}
} }
} }