Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
import gradio as gr
|
| 4 |
+
from openai import OpenAI
|
| 5 |
+
|
| 6 |
+
HF_TOKEN = os.environ.get("HF_TOKEN")
|
| 7 |
+
MODEL_ID = "Qwen/Qwen2.5-7B-Instruct"
|
| 8 |
+
|
| 9 |
+
if not HF_TOKEN:
|
| 10 |
+
raise ValueError("HF_TOKEN is missing. Add it in Space Settings -> Secrets.")
|
| 11 |
+
|
| 12 |
+
client = OpenAI(
|
| 13 |
+
base_url="https://router.huggingface.co/v1",
|
| 14 |
+
api_key=HF_TOKEN,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
FINAL_NOTE = "Responses are generated from retrieved hadith evidence and the system is still under improvement."
|
| 18 |
+
|
| 19 |
+
SYSTEM_PROMPT = """
|
| 20 |
+
You are Hadithi AI, a professional assistant for explaining retrieved hadith evidence.
|
| 21 |
+
|
| 22 |
+
The user's message already includes the retrieved hadith evidence from the API.
|
| 23 |
+
Your job is to explain that evidence clearly, naturally, and faithfully.
|
| 24 |
+
|
| 25 |
+
You must base your answer only on the hadiths provided in the user's message.
|
| 26 |
+
Do not invent extra hadiths, extra sources, or unsupported claims.
|
| 27 |
+
|
| 28 |
+
STRICT OUTPUT FORMAT:
|
| 29 |
+
Write the final answer in exactly this order:
|
| 30 |
+
|
| 31 |
+
Answer:
|
| 32 |
+
Write exactly one polished explanatory paragraph in English unless the user clearly asks for Arabic.
|
| 33 |
+
The paragraph must:
|
| 34 |
+
- begin naturally in a style like: "The retrieved hadiths show that..."
|
| 35 |
+
- explain the meaning of the retrieved hadiths in a smooth, human, article-like way
|
| 36 |
+
- mention short Arabic phrases from the hadith only when useful
|
| 37 |
+
- include the meaning of the Arabic phrase naturally in the sentence
|
| 38 |
+
- avoid bullets
|
| 39 |
+
- avoid robotic summary language
|
| 40 |
+
- avoid repeating the same point
|
| 41 |
+
- avoid quoting full hadiths
|
| 42 |
+
- stay grounded in the actual retrieved evidence only
|
| 43 |
+
|
| 44 |
+
Hadith Evidence:
|
| 45 |
+
After the paragraph, list the hadiths provided by the user.
|
| 46 |
+
For each hadith, use exactly this structure:
|
| 47 |
+
|
| 48 |
+
- Source: ...
|
| 49 |
+
- Grade: ...
|
| 50 |
+
- Why it matters: ...
|
| 51 |
+
- Text: ...
|
| 52 |
+
|
| 53 |
+
FINAL LINE:
|
| 54 |
+
End with this exact sentence:
|
| 55 |
+
Responses are generated from retrieved hadith evidence and the system is still under improvement.
|
| 56 |
+
|
| 57 |
+
IMPORTANT RULES:
|
| 58 |
+
- Do not create extra headings such as Main Insight, Key meanings, or What the Hadiths Show.
|
| 59 |
+
- Do not separate Arabic and English into different blocks in the Answer section.
|
| 60 |
+
- Keep full Arabic hadith text only in the Hadith Evidence section.
|
| 61 |
+
- If the evidence only partially answers the question, say so clearly.
|
| 62 |
+
- Be elegant, clear, modern, and trustworthy.
|
| 63 |
+
|
| 64 |
+
EXAMPLE STYLE FOR THE ANSWER SECTION:
|
| 65 |
+
The retrieved hadiths show that mercy (raḥma / الرحمة) in Islam is both a divine attribute and a quality believers should live by. In one hadith, the Prophet ﷺ teaches a duʿā’ that ends with “forgive me … and have mercy on me” (“فاغفر لي … وارحمني”), showing that a Muslim constantly needs Allah’s mercy in addition to forgiveness. Another hadith connects mercy with healing through the supplication “place Your mercy on earth” (“فاجعل رحمتك في الأرض”), which presents mercy as something that brings relief and cure. The Prophet ﷺ also said, “Mercy is not removed except from one who is truly wretched” (“لا تنزع الرحمة إلا من شقي”), which shows that mercy is a sign of goodness in the heart, while losing it reflects spiritual hardness. The clearest summary appears in the hadith, “The merciful are shown mercy by the Most Merciful” (“الراحمون يرحمهم الرحمن”), teaching that those who show mercy to people receive mercy from Allah. Together, these hadiths present mercy as something to seek from Allah, to embody in the heart, and to extend to others.
|
| 66 |
+
""".strip()
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def is_arabic_request(text: str) -> bool:
|
| 70 |
+
if not text:
|
| 71 |
+
return False
|
| 72 |
+
return bool(re.search(r'[\u0600-\u06FF]', text))
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def normalize_quotes(text: str) -> str:
|
| 76 |
+
if not text:
|
| 77 |
+
return ""
|
| 78 |
+
text = text.replace("“", '"').replace("”", '"')
|
| 79 |
+
text = text.replace("‘", "'").replace("’", "'")
|
| 80 |
+
return text
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def clean_answer(text: str, user_message: str = "") -> str:
|
| 84 |
+
if not text:
|
| 85 |
+
return ""
|
| 86 |
+
|
| 87 |
+
text = normalize_quotes(text.strip())
|
| 88 |
+
|
| 89 |
+
replacements = {
|
| 90 |
+
"Answer Insight:": "Answer:",
|
| 91 |
+
"Short answer:": "Answer:",
|
| 92 |
+
"Main Insight:": "Answer:",
|
| 93 |
+
"Key Finding:": "Answer:",
|
| 94 |
+
"Supporting Hadiths:": "Hadith Evidence:",
|
| 95 |
+
"Referenced Hadiths:": "Hadith Evidence:",
|
| 96 |
+
"What the Hadiths Show:": "Hadith Evidence:",
|
| 97 |
+
"What the Hadiths Cover:": "Hadith Evidence:",
|
| 98 |
+
"Evidence:": "Hadith Evidence:",
|
| 99 |
+
"Hadiths:": "Hadith Evidence:",
|
| 100 |
+
"Closing Note:": FINAL_NOTE,
|
| 101 |
+
"Note:": FINAL_NOTE,
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
for old, new in replacements.items():
|
| 105 |
+
text = text.replace(old, new)
|
| 106 |
+
|
| 107 |
+
# Normalize headings if model outputs markdown headings
|
| 108 |
+
text = re.sub(r"(?im)^#+\s*answer\s*:?\s*$", "Answer:", text)
|
| 109 |
+
text = re.sub(r"(?im)^#+\s*hadith evidence\s*:?\s*$", "Hadith Evidence:", text)
|
| 110 |
+
|
| 111 |
+
# Remove numbering before headings
|
| 112 |
+
text = re.sub(r"(?im)^\s*\d+\.\s*(Answer:)", r"\1", text)
|
| 113 |
+
text = re.sub(r"(?im)^\s*\d+\.\s*(Hadith Evidence:)", r"\1", text)
|
| 114 |
+
|
| 115 |
+
# Remove duplicate final note before re-adding once
|
| 116 |
+
text = re.sub(rf"(?s)\n*{re.escape(FINAL_NOTE)}\s*$", "", text).strip()
|
| 117 |
+
|
| 118 |
+
# Reduce excessive blank lines
|
| 119 |
+
text = re.sub(r"\n{3,}", "\n\n", text).strip()
|
| 120 |
+
|
| 121 |
+
# Ensure Answer section exists
|
| 122 |
+
if "Answer:" not in text:
|
| 123 |
+
text = "Answer:\n" + text
|
| 124 |
+
|
| 125 |
+
# Ensure Hadith Evidence section exists
|
| 126 |
+
if "Hadith Evidence:" not in text:
|
| 127 |
+
text += "\n\nHadith Evidence:\n"
|
| 128 |
+
|
| 129 |
+
# If Arabic requested, lightly relabel only headings, keep body as model returned
|
| 130 |
+
if is_arabic_request(user_message):
|
| 131 |
+
text = text.replace("Answer:", "الجواب:")
|
| 132 |
+
text = text.replace("Hadith Evidence:", "الأحاديث المسترجعة:")
|
| 133 |
+
|
| 134 |
+
# Add final note once
|
| 135 |
+
text = text.rstrip() + "\n\n" + FINAL_NOTE
|
| 136 |
+
return text
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def chat(message, history):
|
| 140 |
+
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
|
| 141 |
+
|
| 142 |
+
for user_msg, assistant_msg in history:
|
| 143 |
+
if user_msg:
|
| 144 |
+
messages.append({"role": "user", "content": user_msg})
|
| 145 |
+
if assistant_msg:
|
| 146 |
+
messages.append({"role": "assistant", "content": assistant_msg})
|
| 147 |
+
|
| 148 |
+
messages.append({"role": "user", "content": message})
|
| 149 |
+
|
| 150 |
+
try:
|
| 151 |
+
response = client.chat.completions.create(
|
| 152 |
+
model=MODEL_ID,
|
| 153 |
+
messages=messages,
|
| 154 |
+
temperature=0.1,
|
| 155 |
+
max_tokens=1100,
|
| 156 |
+
)
|
| 157 |
+
answer = response.choices[0].message.content.strip()
|
| 158 |
+
answer = clean_answer(answer, user_message=message)
|
| 159 |
+
except Exception as e:
|
| 160 |
+
answer = f"Error: {str(e)}"
|
| 161 |
+
|
| 162 |
+
return answer
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
demo = gr.ChatInterface(
|
| 166 |
+
fn=chat,
|
| 167 |
+
title="Hadithi AI",
|
| 168 |
+
description="Clear paragraph-based hadith answers followed by full hadith evidence",
|
| 169 |
+
textbox=gr.Textbox(
|
| 170 |
+
placeholder="Ask about a concept, meaning, value, or theme in hadith...",
|
| 171 |
+
container=True,
|
| 172 |
+
scale=7,
|
| 173 |
+
),
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
if __name__ == "__main__":
|
| 177 |
+
demo.launch()
|