chuyk/Math_Adaptation
0
1import os2import gradio as gr3from groq import Groq4 5GROQ_API_KEY = os.environ.get("GROQ_API_KEY")6assert GROQ_API_KEY and GROQ_API_KEY.startswith("gsk_")7client = Groq(api_key=GROQ_API_KEY)8 9# --------- 全集中管理的PROMPT ---------10MATH_BASE_PROMPT = """11你是一位數學教師,請直接產生改編題目和簡短說明,**只產生一個全新題目,不要產生多於一題。不要顯示任何推理、思考過程、<think>內容,只輸出[改編題目]與[說明]。**12If you see this, DO NOT output <think>, reasoning or COT section.13假設輸入題目為一數學問題,請依據勾選規則進行下方改編(未被勾選的維度,必須與原題完全相同)。14"""15 16CONTEXT_PROMPT_GENERAL = """17- 必須更換完整題目中的生活情境/敘述描述,換成與原題不同的全新情境與描述,題目敘述結構也可改變,必須確保考點不變,不得更動原考題想測驗的觀念與知識。18"""19# --------- 修正後:注重敘事邏輯的創新情境 PROMPT ---------20CONTEXT_PROMPT_CREATIVE = """21- **任務:將數學題改編為具備「敘事感」與「當代風格」的段考試題。**22- **核心要求**:情境必須「合乎邏輯且好讀」,不要將不相干的詞彙硬湊在一起。23- **推薦命題方向(隨機選一,並深化其細節)**:24 1. **【現代生活痛點】**:如:外送平台抽成與利潤分配、社群媒體的觸及率演算、手搖飲配方的黃金比例、排隊買限量球鞋的心理博弈。25 2. **【專業職人領域】**:如:調香師的香氣前中後調配置、遊戲開發者的角色屬性平衡、咖啡師的萃取率與水溫關係、建築師的空間受力分配。26 3. **【奇幻/科幻冒險】**:如:RPG 遊戲中的技能點分配、星際航行中的燃料消耗比、煉金術的材料融合規律。27- **寫作指南**:28 - **單一主線**:整題只講一個故事,不要同時出現阿嬤、克蘇魯跟掃地機器人。29 - **專有名詞**:適度加入該領域的術語(如:觸及率、萃取率、HP/MP、毛利),增加真實感。30 - **優化數據**:務必調整數值使常數 k 為整數。31- **輸出限制**:敘述必須流暢,像是一篇短小的報導或故事背景說明。32"""33GUFENG_PROMPT = """34 35# 核心指令36請將原本的數學題改編為「符合[指定朝代]背景」的古風題目,並遵守以下規則:37 38# 風格調控系統391. **敘事風格**(AI自主選擇以下之一):40 - 🅰️ 白話古風:「用現代人易懂的古裝劇對白風格」 41 *特徵*:保留「在下/掌櫃」等稱謂,但避免艱澀文言 42 - 🅱️ 典雅書面:「模仿明清小說敘事,適度用典」 43 *特徵*:可出現「蓋聞」「然則」等連接詞 44 45# 防護機制461. **文言過濾器**: 47 - 禁止使用「之乎者也」等虛詞,除非直接引用典籍 48 - 複雜單位自動換算(如「3石6斗」→「約270公斤」) 49 - 每題含「白話註解」如「註:1文錢可買2個炊餅」50 51# 輸出結構52 -描述情境有敘事流暢度,包括故事化條件描述,及明確問題53 54"""55 56# ---------- 主組合prompt函數 ----------57def make_full_prompt(58 change_number, change_option, change_unit, change_code, 59 change_context, change_context_creative, change_context_gufeng, question60):61 instructions = []62 63 # 數字64 if change_number:65 instructions.append(66 "- 題目中的數字必須與原題**不同**(請更換為合理新數字,符合邏輯與原意)。"67 )68 else:69 instructions.append("- 題目中的數字必須與原題**相同**。")70 71 # 選項72 if change_option:73 instructions.append(74 "- 題目的選項內容(若有)必須與原題**不同**,且配合題目新數字或情境合理設計,須含正確答案。"75 )76 else:77 instructions.append("- 題目的選項必須與原題**相同**(包括數字與排列)。")78 79 # 單位80 if change_unit:81 instructions.append(82 "- 單位必須更換成合理的新單位(如公尺換為公分、平方公尺變平方公分等,並確保數值一致性)。"83 )84 else:85 instructions.append("- 單位必須與原題**相同**。")86 87 # 英文代碼88 if change_code:89 instructions.append(90 "- 變數、英文代碼(如a、b、x等)必須更換新的,不可以與原題相同。"91 )92 else:93 instructions.append("- 變數、英文代碼必須與原題**相同**。")94 95 # 情境/描述(三選一,但允許都不選)96 if change_context_gufeng:97 instructions.append(GUFENG_PROMPT)98 elif change_context_creative:99 instructions.append(CONTEXT_PROMPT_CREATIVE)100 elif change_context:101 instructions.append(CONTEXT_PROMPT_GENERAL)102 else:103 instructions.append("- 題目的情境、描述方式必須與原題**相同**。")104 105 # 是否需要新增四選一選項106 auto_add_choices_instruction = ""107 if change_option:108 if not any(x in question for x in ["選項", "是", "否", "A)", "A.", "B)", "B.", "(A)", "(B)", "(C)", "(D)", "①", "②", "③", "④"]):109 auto_add_choices_instruction = """110- 原題沒有選項,請為此題目新增**四個數值選項(四選一)**。111 1. 請先完整嚴謹地計算題目的正確答案(僅一次)。112 2. 設計三個誘導錯誤的答案(常見誤算近似值)。113 3. 將正確答案和三個誘答隨機排列成為四個選項,並明示正確答案。114 4. 正確答案必須直接包含於四個選項之一,且僅做一次答案與選項設計,不得檢查產生出的正確答案是否在選項中,也不得再次計算或產生更多誘答。所有步驟請嚴格一次完成,不得反覆修正。115 5. 禁止在選項產生後再檢查或重新計算,不得出現「答案不在四個選項」這類提示。116 6. 僅允許一次推導及選項製作,嚴格遵守請勿反覆修改。117"""118 119 prompt = MATH_BASE_PROMPT + "\n"120 prompt += "\n".join(instructions) + "\n"121 if auto_add_choices_instruction:122 prompt += auto_add_choices_instruction123 prompt += "備註:未被上述條件要求變動的部分,都必須「與原題完全相同」。\n"124 125 extra_output = change_number or change_option126 127 prompt += """128### 輸出要求129請只**產生一個**改編後的完整題目(不要產生多題!),並提供下列內容:1301. 改編後的完整題目叙述(包含圖形描述及選項,若原題有或根據上方指示需要新增)。1312. 簡短說明,解釋改編如何滿足上述條件(例如哪些部分更換,哪些保持,如何確保合理性)。132"""133 if extra_output:134 prompt += """1353. 請直接給出改編後題目的正確答案與詳細解題過程。136"""137 prompt += """138輸出格式:139**改編題目** 140[題目敘述,必須包含圖形說明和選項(若有或本題新增)]141 142**說明** 143[簡短說明改編如何符合條件]144"""145 if extra_output:146 prompt += """147**正確答案與解題過程** 148[答案與完整解題步驟]149"""150 151 # 嚴格禁止模型輸出 markdown 程式碼區塊外衣,確保渲染正常152 prompt += "\n⚠️ 注意:請直接輸出排版好的文本,**絕對不要**使用 ```markdown 與 ``` 將整個回答包裝成程式碼區塊!"153 154 return prompt155 156# 將 Chatbot 的內容轉換成 Markdown 文字格式157def export_to_markdown(history):158 if not history:159 return ""160 try:161 last_msg = history[-1]162 if isinstance(last_msg, dict):163 return last_msg.get('content', '')164 else:165 return getattr(last_msg, 'content', str(last_msg))166 except Exception:167 return ""168 169MODEL_OPTIONS = [170 ("GPT-OSS 120B", "openai/gpt-oss-120b"),171 ("Llama 3.3 70B Versatile", "llama-3.3-70b-versatile"),172 ("Llama 4 Scout", "meta-llama/llama-4-scout-17b-16e-instruct"),173 ("Compound Beta", "groq/compound"),174 ("Llama 3.1 8B Instant", "llama-3.1-8b-instant"),175 ("Qwen3 32B", "qwen/qwen3-32b"),176]177 178# 通用的文本清洗函數,確保不被程式碼區塊吃掉渲染179def clean_output(reply):180 text = reply.strip()181 182 # 剝除頭部的 ```markdown183 if text.startswith("```markdown"):184 text = text.replace("```markdown\n", "", 1).replace("```markdown", "", 1)185 # 剝除可能出現的 ``` 包裝186 if text.startswith("```"):187 text = text.replace("```\n", "", 1).replace("```", "", 1)188 189 if "**改編題目**" in text:190 idx = text.find("**改編題目**")191 text = text[idx:]192 193 # 如果結尾還有殘留的 ```,也予以剝除194 if text.endswith("```"):195 text = text[:-3].strip()196 197 return text198 199def chat_app_stream(200 history, question, model, qwen_streaming,201 change_number, change_option, change_unit, change_code,202 change_context, change_context_creative, change_context_gufeng203):204 205 # -------- 防呆區段開始 ---------206 if not (change_number or change_option or change_unit or change_code or change_context or change_context_creative or change_context_gufeng):207 user_msg = {"role": "user", "content": question}208 assistant_msg = {"role": "assistant", "content": "⚠️ 請至少勾選一個改編選項!"}209 yield (history + [user_msg, assistant_msg], question, model, qwen_streaming,210 change_number, change_option, change_unit, change_code, change_context, change_context_creative, change_context_gufeng)211 return212 213 num_context_style = sum([change_context, change_context_creative, change_context_gufeng])214 if num_context_style > 1:215 user_msg = {"role": "user", "content": question}216 assistant_msg = {"role": "assistant", "content": "⚠️『改情境描述(一般)』『改情境描述(創新)』『改情境描述(古風)』三者只能擇一勾選,請修正!"}217 yield (history + [user_msg, assistant_msg], question, model, qwen_streaming,218 change_number, change_option, change_unit, change_code, change_context, change_context_creative, change_context_gufeng)219 return220 221 if not question or len(question.strip()) < 5:222 user_msg = {"role": "user", "content": question}223 assistant_msg = {"role": "assistant", "content": "⚠️ 請提供一個完整的數學題目描述!"}224 yield (history + [user_msg, assistant_msg], question, model, qwen_streaming,225 change_number, change_option, change_unit, change_code, change_context, change_context_creative, change_context_gufeng)226 return227 # ----------- 防呆區段結束 ----------228 229 if not history:230 history = []231 system_prompt = make_full_prompt(232 change_number, change_option, change_unit, change_code, 233 change_context, change_context_creative, change_context_gufeng, question234 )235 question_new = question + "\n(請直接輸出改編題目與說明,嚴禁顯示推理、<think>內容。)"236 messages = [{"role": "system", "content": system_prompt},237 {"role": "user", "content": question_new}]238 239 try:240 # 將串流與非串流統一使用 clean_output 洗淨241 if model in ["qwen/qwen3-32b"] and qwen_streaming:242 stream = client.chat.completions.create(243 model=model,244 messages=messages,245 temperature=0.6,246 max_completion_tokens=4096,247 top_p=0.95,248 stream=True,249 stop=None250 )251 reply = ""252 user_msg = {"role": "user", "content": question}253 assistant_msg = {"role": "assistant", "content": ""}254 cur_history = history + [user_msg, assistant_msg]255 for chunk in stream:256 delta = getattr(chunk.choices[0].delta, "content", "") or ""257 reply += delta258 cleaned = clean_output(reply)259 assistant_msg['content'] = cleaned260 yield ([dict(m) for m in cur_history],261 question, model, qwen_streaming,262 change_number, change_option, change_unit, change_code, change_context, change_context_creative, change_context_gufeng)263 elif model in ["qwen/qwen3-32b"]:264 resp = client.chat.completions.create(265 model=model,266 messages=messages,267 temperature=0.6,268 max_completion_tokens=4096,269 top_p=0.95,270 stream=False,271 stop=None272 )273 answer = getattr(resp.choices[0].message, "content", None)274 if not answer:275 answer = "[API 無內容]"276 else:277 answer = clean_output(answer)278 user_msg = {"role": "user", "content": question}279 assistant_msg = {"role": "assistant", "content": answer}280 yield (history + [user_msg, assistant_msg],281 question, model, qwen_streaming,282 change_number, change_option, change_unit, change_code, change_context, change_context_creative, change_context_gufeng)283 else:284 stream = client.chat.completions.create(285 model=model,286 messages=messages,287 temperature=1,288 max_tokens=4096,289 top_p=1,290 stream=True,291 )292 reply = ""293 user_msg = {"role": "user", "content": question}294 assistant_msg = {"role": "assistant", "content": ""}295 cur_history = history + [user_msg, assistant_msg]296 for chunk in stream:297 delta = getattr(chunk.choices[0].delta, "content", "") or ""298 reply += delta299 cleaned = clean_output(reply)300 assistant_msg['content'] = cleaned301 yield ([dict(m) for m in cur_history],302 question, model, qwen_streaming,303 change_number, change_option, change_unit, change_code, change_context, change_context_creative, change_context_gufeng)304 except Exception as e:305 user_msg = {"role": "user", "content": question}306 assistant_msg = {"role": "assistant", "content": f"[API錯誤] {e}"}307 yield (history + [user_msg, assistant_msg],308 question, model, qwen_streaming,309 change_number, change_option, change_unit, change_code, change_context, change_context_creative, change_context_gufeng)310 311 312CORRECT_CODE = "kai"313 314with gr.Blocks() as demo:315 gr.Markdown("## 🚀 數學改題助手")316 317 # 增加 latex_delimiters 參數,確保數學符號能夠完美渲染!318 chatbot = gr.Chatbot(319 label="對話框", 320 type="messages",321 render_markdown=True,322 latex_delimiters=[323 {"left": "$$", "right": "$$", "display": True},324 {"left": "$", "right": "$", "display": False},325 {"left": "\\(", "right": "\\)", "display": False},326 {"left": "\\[", "right": "\\]", "display": True}327 ]328 )329 330 markdown_output = gr.Textbox(label="產出題目的 Markdown 原始碼 (可直接複製)", lines=8, show_copy_button=True)331 332 question = gr.Textbox(label="請輸入數學題目", lines=3)333 334 activation_code_box = gr.Textbox(label="啟動碼", type="password", value="")335 code_status = gr.Markdown("⚠️ 請輸入啟動碼。先驗證啟動碼才能送出或清除!")336 337 with gr.Row():338 change_number = gr.Checkbox(label="改數字", value=False)339 change_option = gr.Checkbox(label="改選項", value=False)340 change_unit = gr.Checkbox(label="改單位", value=False)341 change_code = gr.Checkbox(label="改英文代碼", value=False)342 change_context = gr.Checkbox(label="改情境描述(一般)", value=False)343 change_context_creative = gr.Checkbox(label="改情境描述(創新)", value=False)344 change_context_gufeng = gr.Checkbox(label="改情境描述(古風)", value=False)345 346 send_btn = gr.Button("送出", interactive=False)347 clear_btn = gr.Button("清除", interactive=False)348 qwen_streaming = gr.Checkbox(label="Qwen 使用 stream", value=True)349 model_selection = gr.Dropdown(350 label="模型",351 choices=[(name, ident) for name, ident in MODEL_OPTIONS],352 value="qwen/qwen3-32b"353 )354 state = gr.State([])355 356 CORRECT_CODE = "kai"357 def activation_code_feedback_and_toggle_btn(activation_code):358 code = activation_code.strip()359 if code == "":360 return (361 "⚠️ 請輸入啟動碼",362 gr.update(interactive=False),363 gr.update(interactive=False),364 )365 elif code == CORRECT_CODE:366 return (367 "✅ 啟動碼正確,可以送出或清除。",368 gr.update(interactive=True),369 gr.update(interactive=True),370 )371 else:372 return (373 "❌ 啟動碼錯誤,請重新輸入",374 gr.update(interactive=False),375 gr.update(interactive=False),376 )377 378 activation_code_box.change(379 activation_code_feedback_and_toggle_btn,380 inputs=activation_code_box,381 outputs=[code_status, send_btn, clear_btn]382 )383 384 def safe_chat_app_stream(385 history, question, model, qwen_streaming,386 change_number, change_option, change_unit, change_code,387 change_context, change_context_creative, change_context_gufeng,388 activation_code389 ):390 code = activation_code.strip()391 if code == "" or code != CORRECT_CODE:392 user_msg = {"role": "user", "content": question}393 assistant_msg = {"role": "assistant", "content": "⚠️ 請先輸入正確啟動碼!"}394 return (395 [user_msg, assistant_msg],396 question, model, qwen_streaming,397 change_number, change_option, change_unit, change_code,398 change_context, change_context_creative, change_context_gufeng,399 "⚠️ 請輸入正確啟動碼"400 )401 for result in chat_app_stream(history, question, model, qwen_streaming,402 change_number, change_option, change_unit, change_code,403 change_context, change_context_creative, change_context_gufeng):404 yield (*result, "")405 406 def safe_clear_input(question, chatbot, activation_code, model, qwen_streaming,407 change_number, change_option, change_unit, change_code,408 change_context, change_context_creative, change_context_gufeng):409 code = activation_code.strip()410 if code == "" or code != CORRECT_CODE:411 return (412 question, chatbot, model, qwen_streaming,413 change_number, change_option, change_unit, change_code,414 change_context, change_context_creative, change_context_gufeng,415 "⚠️ 請輸入正確啟動碼"416 )417 return (418 "", [], model, qwen_streaming,419 change_number, change_option, change_unit, change_code,420 change_context, change_context_creative, change_context_gufeng,421 ""422 )423 424 send_btn.click(425 safe_chat_app_stream,426 [state, question, model_selection, qwen_streaming,427 change_number, change_option, change_unit, change_code,428 change_context, change_context_creative, change_context_gufeng,429 activation_code_box],430 [chatbot, question, model_selection, qwen_streaming,431 change_number, change_option, change_unit, change_code,432 change_context, change_context_creative, change_context_gufeng,433 code_status]434 )435 436 clear_btn.click(437 safe_clear_input,438 [question, chatbot, activation_code_box, model_selection, qwen_streaming,439 change_number, change_option, change_unit, change_code,440 change_context, change_context_creative, change_context_gufeng],441 [question, chatbot, model_selection, qwen_streaming,442 change_number, change_option, change_unit, change_code,443 change_context, change_context_creative, change_context_gufeng,444 code_status]445 )446 447 # 綁定 Chatbot 內容變更事件448 chatbot.change(449 fn=export_to_markdown,450 inputs=[chatbot],451 outputs=[markdown_output]452 )453 454if __name__ == "__main__":455 demo.launch()