Skip to content

Commit fa61662

Browse files
committed
add groq support
1 parent f128413 commit fa61662

4 files changed

Lines changed: 73 additions & 28 deletions

File tree

.github/workflows/check.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ jobs:
9494
UISPsw: ${{ secrets.UISPSW }}
9595
COURSE_IDS: ${{ secrets.COURSE_IDS }}
9696
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
97+
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
9798
SMTP_EMAIL: ${{ secrets.SMTP_EMAIL }}
9899
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
99100
RECEIVER_EMAIL: ${{ secrets.RECEIVER_EMAIL }}

.github/workflows/single_run.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ jobs:
9595
UISPsw: ${{ secrets.UISPSW }}
9696
COURSE_IDS: ${{ inputs.course_ids }}
9797
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
98+
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
9899
SMTP_EMAIL: ${{ secrets.SMTP_EMAIL }}
99100
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
100101
RECEIVER_EMAIL: ${{ secrets.RECEIVER_EMAIL }}

src/config.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,18 @@
2626
"ZhipuAI/GLM-5",
2727
"deepseek-ai/DeepSeek-V3.2",
2828
"MiniMax/MiniMax-M2.5",
29-
"ZhipuAI/GLM-4.7",
30-
"deepseek-ai/DeepSeek-V3-0324",
3129
"Qwen/Qwen3.5-397B-A17B",
32-
"Qwen/Qwen2.5-72B-Instruct"
30+
"ZhipuAI/GLM-4.7"
31+
]
32+
33+
# Groq fallback (for content policy bypass)
34+
GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "")
35+
GROQ_BASE_URL = "https://api.groq.com/openai/v1"
36+
GROQ_MODELS = [
37+
"llama-3.3-70b-versatile",
38+
"llama-4-maverick",
39+
"llama-4-scout",
40+
"openai/gpt-oss-120b",
3341
]
3442

3543
# QQ SMTP

src/summarizer.py

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,24 @@
88

99
SYSTEM_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+
2329
class 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

Comments
 (0)