88
99SYSTEM_PROMPT = (
1010"你是一个专业的课程助教。你的任务是根据用户提供的课程录音文本,生成用于学生自学和期末复习的详细结构化笔记。\n "
11- "1. **直接输出**:不要包含任何“好的”、“ 没问题”、“ 以下是总结” 等客套话。不要输出全局课程名称大标题(由系统自动生成),直接开始总结即可。\n "
12- "2. **文本清洗**:语言必须通顺、逻辑清晰,严格去除口语化表达(如“呃”、“啊”、“那么” )、重复句和无意义的录音识别错误等。\n "
11+ "1. **直接输出**:不要包含任何" 好的 "、" 没问题 "、" 以下是总结 " 等客套话。不要输出全局课程名称大标题(由系统自动生成),直接开始总结即可。\n "
12+ "2. **文本清洗**:语言必须通顺、逻辑清晰,严格去除口语化表达(如" 呃 "、" 啊 "、" 那么 " )、重复句和无意义的录音识别错误等。\n "
1313"3. **格式严格**:\n "
1414 "- 必须使用 Markdown 格式排版。\n "
1515 " - **标题级别限制**:只允许使用三级及更低级别的标题(即只能使用 `###`、`####`、`#####`),禁止使用 `#` 和 `##`。\n "
1616 "- 合理使用加粗、列表、表格来组织信息,确保结构清晰。\n "
1717"4. **公式规范**:所有数学公式或科学变量必须使用规范的 LaTeX 语法(行内公式用 $...$,行间公式用 $$...$$)。\n "
18- "5. **忠于原文与详尽**:总结必须尽可能详细且长 ,包含具体的推导细节、案例、文献或者核心概念,不要过度概括。禁止捏造录音中未提及的内容。\n "
18+ "5. **忠于原文与详尽**:总结必须尽可能详细且足够长 ,包含具体的推导细节、案例、文献或者核心概念,不要过度概括。禁止捏造录音中未提及的内容。\n "
1919"6. 你需要格外注意课程中是否提及了作业、考试、签到、组队等关键事项,如果有的话,用三级标题【课程事项提醒】标注在开头。"
2020)
2121
2222
23+ def _is_content_blocked (error : Exception ) -> bool :
24+ """Check if an API error is a content policy rejection."""
25+ msg = str (error ).lower ()
26+ return "data_inspection_failed" in msg or "inappropriate content" in msg
27+
28+
2329class Summarizer :
2430 """Course lecture summarizer using ModelScope OpenAI-compatible API."""
2531
@@ -32,12 +38,41 @@ def __init__(self):
3238 )
3339 self .models = list (config .LLM_MODELS )
3440
41+ self ._groq_client = None
42+ if config .GROQ_API_KEY :
43+ self ._groq_client = OpenAI (
44+ api_key = config .GROQ_API_KEY ,
45+ base_url = config .GROQ_BASE_URL ,
46+ )
47+
48+ def _call_llm (self , client : OpenAI , model : str ,
49+ title : str , content : str ) -> str :
50+ """Send a summarization request to a single model. Returns result text."""
51+ t0 = time .time ()
52+ response = client .chat .completions .create (
53+ model = model ,
54+ messages = [
55+ {"role" : "system" , "content" : SYSTEM_PROMPT },
56+ {
57+ "role" : "user" ,
58+ "content" : f"以下是课程《{ title } 》的录音文本,请总结:\n \n { content } " ,
59+ },
60+ ],
61+ temperature = 0.3 ,
62+ timeout = 120 ,
63+ )
64+ result = response .choices [0 ].message .content
65+ elapsed = time .time () - t0
66+ print (
67+ f"[Summarizer] Done ({ model } ): { len (content )} chars input"
68+ f" → { len (result )} chars output in { elapsed :.0f} s"
69+ )
70+ return result
71+
3572 def summarize (self , title : str , content : str ) -> tuple [str , str ]:
3673 """Summarize lecture content, trying multiple models on failure.
3774
38- Args:
39- title: Lecture title for context.
40- content: Full transcript text.
75+ If all primary models fail due to content policy, falls back to Groq.
4176
4277 Returns:
4378 (summary, model_used) tuple.
@@ -46,31 +81,31 @@ def summarize(self, title: str, content: str) -> tuple[str, str]:
4681 return ("(内容为空)" , "" )
4782
4883 errors = []
84+ content_blocked = False
85+
86+ # Primary: ModelScope models
4987 for model in self .models :
5088 try :
51- t0 = time .time ()
52- response = self .client .chat .completions .create (
53- model = model ,
54- messages = [
55- {"role" : "system" , "content" : SYSTEM_PROMPT },
56- {
57- "role" : "user" ,
58- "content" : f"以下是课程《{ title } 》的录音文本,请总结:\n \n { content } " ,
59- },
60- ],
61- temperature = 0.3 ,
62- timeout = 120 ,
63- )
64- result = response .choices [0 ].message .content
65- elapsed = time .time () - t0
66- print (
67- f"[Summarizer] Done ({ model } ): { len (content )} chars input"
68- f" → { len (result )} chars output in { elapsed :.0f} s"
69- )
89+ result = self ._call_llm (self .client , model , title , content )
7090 return (result , model )
7191 except Exception as e :
7292 print (f"[Summarizer] { model } failed: { type (e ).__name__ } : { e } " )
7393 errors .append (f"{ model } : { e } " )
94+ if _is_content_blocked (e ):
95+ content_blocked = True
96+
97+ # Fallback: Groq models (when content policy blocks primary models)
98+ if content_blocked and self ._groq_client :
99+ print ("[Summarizer] Content blocked by primary platform, trying Groq..." )
100+ for model in config .GROQ_MODELS :
101+ try :
102+ result = self ._call_llm (
103+ self ._groq_client , model , title , content ,
104+ )
105+ return (result , f"groq/{ model } " )
106+ except Exception as e :
107+ print (f"[Summarizer] groq/{ model } failed: { type (e ).__name__ } : { e } " )
108+ errors .append (f"groq/{ model } : { e } " )
74109
75110 raise RuntimeError (
76111 "All LLM models failed:\n " + "\n " .join (errors )
0 commit comments