sandy083 commited on
Commit
0ce426f
·
verified ·
1 Parent(s): ab58821

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +241 -0
app.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import gradio as gr
4
+ import aisuite as ai
5
+ import zipfile
6
+ import time # 為了讓檔案解壓縮有時間
7
+ # 移除 Colab 專用的 userdata 匯入
8
+
9
+ # RAG 相關
10
+ from langchain_community.vectorstores import FAISS
11
+ from langchain_community.embeddings import HuggingFaceEmbeddings
12
+
13
+ # =========================================================
14
+ # 1. 檔案解壓縮與環境變數載入 (Hugging Face Secrets 會自動注入 OS 環境變數)
15
+ # =========================================================
16
+
17
+ # Hugging Face 環境中,Secrets 會被注入為環境變數。
18
+ # 我們不再需要 google.colab import userdata 來獲取 Key。
19
+ # 程式將直接從 os.environ 讀取。
20
+
21
+ # 1.1 啟動時解壓縮 FAISS 資料庫
22
+ if not os.path.exists("faiss_dbV2"):
23
+ print("正在解壓縮 faiss_dbV2.zip...")
24
+ try:
25
+ with zipfile.ZipFile("faiss_dbV2.zip", 'r') as zip_ref:
26
+ zip_ref.extractall(".")
27
+ print("✅ faiss_db 解壓縮完成。")
28
+ except FileNotFoundError:
29
+ print("❌ 錯誤:未找到 faiss_dbV2.zip,請檢查是否已上傳。")
30
+ exit()
31
+
32
+ # 1.2 檢查 API Key
33
+ groq_api_key = os.environ.get('GROQ') # 注意:從 Secrets 讀取時 Key 要大寫,如 GROQ
34
+ hf_token = os.environ.get('HuggingFace')
35
+
36
+ if not groq_api_key:
37
+ print("❌ 警告:GROQ API Key 未設定在 Hugging Face Secrets 中!")
38
+ if not hf_token:
39
+ print("❌ 警告:HuggingFace Token 未設定在 Secrets 中!")
40
+
41
+ # 由於 aisuite 依賴 os.environ,我們在這裡確保 Groq Key 再次被設定
42
+ os.environ['GROQ_API_KEY'] = groq_api_key if groq_api_key else ""
43
+
44
+
45
+ # =========================================================
46
+ # 2. 模型與設定 (與 Colab 相同)
47
+ # =========================================================
48
+
49
+ # Embedding 模型配置
50
+ class EmbeddingGemmaEmbeddings(HuggingFaceEmbeddings):
51
+ def __init__(self, **kwargs):
52
+ super().__init__(
53
+ model_name="google/embeddinggemma-300m",
54
+ encode_kwargs={"normalize_embeddings": True},
55
+ **kwargs
56
+ )
57
+
58
+ def embed_documents(self, texts):
59
+ texts = [f"title: none | text: {t}" for t in texts]
60
+ return super().embed_documents(texts)
61
+
62
+ def embed_query(self, text):
63
+ return super().embed_query(f"task: search result | query: {text}")
64
+
65
+ embedding_model = EmbeddingGemmaEmbeddings()
66
+
67
+ # FAISS 資料庫載入
68
+ print("正在載入 FAISS 資料庫...")
69
+ vectorstore = FAISS.load_local(
70
+ "faiss_db",
71
+ embeddings=embedding_model,
72
+ allow_dangerous_deserialization=True
73
+ )
74
+ retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
75
+ print("✅ RAG 系統初始化完成。")
76
+
77
+
78
+ # LLM Agent 配置 (使用您最後成功的配置)
79
+ provider_system_planner = "groq"
80
+ model_system_planner = "llama-3.3-70b-versatile"
81
+ provider_system_writer = "groq"
82
+ model_system_writer = "llama-3.3-70b-versatile"
83
+ provider_system_reviewer = "groq"
84
+ model_system_reviewer = "openai/gpt-oss-120b"
85
+ provider_system_refiner = "groq"
86
+ model_system_refiner = "llama-3.3-70b-versatile"
87
+
88
+ # 初始化 AI Client
89
+ client = ai.Client()
90
+
91
+
92
+ # =========================================================
93
+ # 3. 核心函式定義 (與 Colab 相同)
94
+ # =========================================================
95
+
96
+ def call_ai(system_prompt, user_prompt, provider, model):
97
+ messages = [
98
+ {"role": "system", "content": system_prompt},
99
+ {"role": "user", "content": user_prompt}
100
+ ]
101
+ try:
102
+ # **注意**:我們在這裡使用了 os.environ 裡面的 Key,
103
+ # 如果 Key 失敗,這裡就會報錯。
104
+ response = client.chat.completions.create(
105
+ model=f"{provider}:{model}",
106
+ messages=messages
107
+ )
108
+ return response.choices[0].message.content
109
+ except Exception as e:
110
+ # 如果模型呼叫失敗,拋出詳細錯誤
111
+ raise Exception(f"【AI Agent 呼叫失敗: {provider}:{model}】 錯誤原因: {str(e)}")
112
+
113
+
114
+ # A. CoT Planner: 發想 5 種思路
115
+ system_planner = """
116
+ 你是一位精通實變函數論 (Real Analysis) 的數學家。
117
+ 你的任務不是直接給答案,而是進行「廣度優先思考」。
118
+ 針對使用者的問題與提供的課本內容,請提出 5 種不同的解釋路徑或證明策略,盡可能詳細證明。
119
+ """
120
+ # B. CoT Selector & Writer: 選擇最佳並撰寫初稿
121
+ system_writer = """
122
+ 你是一位嚴謹的數學教授。
123
+ 請參考 Planner 提供的 5 種思路,選出「最符合實變函數論嚴謹定義」的一種。
124
+ 請依此思路撰寫完整的解答:
125
+ 1. 必須使用 LaTeX 格式書寫所有數學符號。
126
+ 2. 【關鍵規則】:所有行內公式 (Inline Math) 的 $ 符號前後必須留一個空格。
127
+ 3. 【關鍵規則】:複雜或長度超過 10 個字元的公式,請務必使用雙錢字號 $$ ... $$ 獨立一行顯示。
128
+ 4. 【新增要求】:在 $$...$$ 區塊公式的**上下**必須各留**一個空行**,以確保 Markdown 渲染器能正確識別。
129
+ 5. 引用相關的定義與定理 (Definition/Theorem)。
130
+ 6. 使用繁體中文 (台灣習慣) 進行解說。
131
+ """
132
+ # C. Reflexion Reviewer: 2diff 審查
133
+ system_reviewer = """
134
+ 你是一位負責嚴格數學審稿人。
135
+ 請檢查這份數學解答。
136
+ 檢查重點:
137
+ 1. 定義引用是否正確?
138
+ 2. 證明邏輯是否有漏洞 (Logical Gaps)?
139
+ 3. 符號使用是否規範?
140
+ 4. 是否有濫用直觀而犧牲嚴謹性的情況?
141
+ 請給出具體的修改建議。如果解答正確,請回答「PASS」。
142
+ """
143
+ # D. Reflexion Refiner: 根據建議修正
144
+ system_refiner = """
145
+ 你負責根據審稿人的建議,修正並潤飾最終的數學解答。
146
+ 請保留原本正確的部分,針對被指出的錯誤進行修正。
147
+ 1. 必須使用 LaTeX 格式書寫所有數學符號。
148
+ 2. 【關鍵規則】:所有行內公式 (Inline Math) 的 $ 符號前後必須留一個空格。
149
+ 3. 【關鍵規則】:複雜或長度超過 10 個字元的公式,請務必使用雙錢字號 $$ ... $$ 獨立一行顯示。
150
+ 4. 【新增要求】:在 $$...$$ 區塊公式的**上下**必須各留**一個空行**,以確保 Markdown 渲染器能正確識別。
151
+ """
152
+
153
+ def fix_latex(text):
154
+ if not text: return text
155
+ # 1. 取代區塊公式 \[ ... \] 為 $$ ... $$
156
+ text = re.sub(r'\\\[(.*?)\\\]', r'\n$$\1$$\n', text, flags=re.DOTALL)
157
+ # 2. 取代行內公式 \( ... \) 為 $ ... $
158
+ text = re.sub(r'\\\((.*?)\\\)', r'$ \1 $', text, flags=re.DOTALL)
159
+ return text
160
+
161
+
162
+ def math_solver_process(user_question):
163
+ try:
164
+ # --- Step 1: RAG 檢索 ---
165
+ docs = retriever.invoke(user_question)
166
+ context = "\n\n".join([doc.page_content for doc in docs])
167
+ rag_info = fix_latex(f"【檢索到的課本內容】:\n{context}")
168
+
169
+ # --- Step 2: CoT (Planner) ---
170
+ plan_prompt = f"使用者問題: {user_question}\n\n參考資料:\n{context}\n\n請列出 5 種解題思路。"
171
+ thoughts_5 = fix_latex(call_ai(system_planner, plan_prompt, provider_system_planner, model_system_planner))
172
+
173
+ # --- Step 3: Draft (Writer) ---
174
+ draft_prompt = f"使用者問題: {user_question}\n\nPlanner 的思路:\n{thoughts_5}\n\n參考資料:\n{context}\n\n請選擇最佳思路並撰寫解答初稿。"
175
+ first_draft = fix_latex(call_ai(system_writer, draft_prompt, provider_system_writer, model_system_writer))
176
+
177
+ # --- Step 4: Reflexion (Reviewer) ---
178
+ review_prompt = f"問題: {user_question}\n\n初稿解答:\n{first_draft}\n\n請審查並給出建議。"
179
+ review_feedback = fix_latex(call_ai(system_reviewer, review_prompt, provider_system_reviewer, model_system_reviewer))
180
+
181
+ # --- Step 5: Final Refinement (Refiner) ---
182
+ if "PASS" in review_feedback:
183
+ raw_answer = first_draft
184
+ status = "✅ 審查通過,初稿即完美。"
185
+ else:
186
+ refine_prompt = f"初稿:\n{first_draft}\n\n審查建議:\n{review_feedback}\n\n請產出最終修正版解答。"
187
+ raw_answer = call_ai(system_refiner, refine_prompt, provider_system_refiner, model_system_refiner)
188
+ status = "🔄 經過審查修正後的最終版。"
189
+
190
+ final_answer = fix_latex(raw_answer)
191
+
192
+ return rag_info, thoughts_5, first_draft, review_feedback, final_answer
193
+
194
+ except Exception as e:
195
+ error_msg = f"系統執行發生錯誤:\n{str(e)}"
196
+ # 如果發生錯誤,將錯誤訊息回傳給 Gradio 介面
197
+ return error_msg, "Error", "Error", "Error", "Error"
198
+
199
+
200
+ # =========================================================
201
+ # 4. Gradio 介面配置 (作為測試介面,確保能跑)
202
+ # =========================================================
203
+
204
+ # 6. Gradio 介面
205
+ with gr.Blocks(title="實變函數論 AI Agent") as demo:
206
+ gr.Markdown("# 📐 實變函數論 AI 助教 (Hugging Face Spaces RAG + CoT)")
207
+ gr.Markdown("輸入你的數學問題,AI 將查詢課本、多角度思考、並經由同儕審查機制產出嚴謹證明。")
208
+
209
+ with gr.Row():
210
+ input_box = gr.Textbox(label="請輸入問題", placeholder="例如:請解釋 Lebesgue 積分與 Riemann 積分在極限交換時的差異")
211
+ run_btn = gr.Button("開始解題", variant="primary")
212
+
213
+ with gr.Accordion("📚 步驟一:檢索資料 (RAG)", open=False):
214
+ out_rag = gr.TextArea(label="RAG Context", interactive=False)
215
+
216
+ with gr.Accordion("🧠 步驟二:思考與選擇 (CoT)", open=False):
217
+ out_thoughts = gr.TextArea(label="5 種解題思路", interactive=False)
218
+
219
+ with gr.Row():
220
+ out_draft = gr.TextArea(label="📝 初稿 (Writer)", interactive=False)
221
+ out_review = gr.TextArea(label="🧐 審查意見 (Reviewer)", interactive=False)
222
+
223
+ gr.Markdown("### ✨ 最終解答 (Final Answer)")
224
+
225
+ out_final = gr.Markdown(
226
+ label="最終解答",
227
+ latex_delimiters=[
228
+ { "left": "$$", "right": "$$", "display": True },
229
+ { "left": "$", "right": "$", "display": False }
230
+ ]
231
+ )
232
+
233
+ # 按鈕事件綁定
234
+ run_btn.click(
235
+ math_solver_process,
236
+ inputs=[input_box],
237
+ outputs=[out_rag, out_thoughts, out_draft, out_review, out_final]
238
+ )
239
+
240
+ # 在 Hugging Face 上執行時,只需呼叫 demo.launch()
241
+ demo.launch()