Alshargi commited on
Commit
65b2019
·
verified ·
1 Parent(s): 982950d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +256 -86
app.py CHANGED
@@ -16,33 +16,43 @@ client = OpenAI(
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: ...
@@ -51,89 +61,248 @@ For each hadith, use exactly this structure:
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):
@@ -141,21 +310,22 @@ def chat(message, history):
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
 
 
16
 
17
  FINAL_NOTE = "Responses are generated from retrieved hadith evidence and the system is still under improvement."
18
 
19
+
20
  SYSTEM_PROMPT = """
21
  You are Hadithi AI, a professional assistant for explaining retrieved hadith evidence.
22
 
23
+ The user message contains:
24
+ 1) a question or topic
25
+ 2) retrieved hadith evidence from the API
26
 
27
+ Your task:
28
+ - Read the retrieved hadiths carefully
29
+ - Explain only what is supported by the retrieved hadiths
30
+ - Do not invent extra hadiths or unsupported claims
31
+ - Do not produce a generic bullet summary
32
+ - Do not produce the old flat style like:
33
+ "The hadiths provided emphasize..."
34
+ followed by bullet points
35
 
36
  STRICT OUTPUT FORMAT:
37
+ You must output exactly these parts and in this order:
38
 
39
  Answer:
40
+ Write one single polished paragraph in natural English.
41
+ This paragraph must:
42
+ - begin in a smooth explanatory style similar to:
43
+ "The retrieved hadiths show that..."
44
+ - explain the meaning of the hadith evidence clearly
45
+ - sound human, thoughtful, and elegant
46
+ - include short Arabic phrases only when useful
47
+ - place Arabic phrases naturally inside the explanation, with meaning
48
+ - not use bullet points
49
+ - not sound robotic
50
+ - not repeat the same idea
51
+ - not quote long hadith text
52
+ - summarize the evidence faithfully
53
 
54
  Hadith Evidence:
55
+ Then list all retrieved hadiths in a clean way.
56
  For each hadith, use exactly this structure:
57
 
58
  - Source: ...
 
61
  - Text: ...
62
 
63
  FINAL LINE:
64
+ End with this exact line:
65
  Responses are generated from retrieved hadith evidence and the system is still under improvement.
66
 
67
+ FORBIDDEN:
68
+ - Do not create headings like:
69
+ Main Insight
70
+ What the Hadiths Show
71
+ Key meanings
72
+ Supporting evidence summary
73
+ - Do not start with bullet points
74
+ - Do not write a short outline before the paragraph
75
+ - Do not say "The hadiths provided emphasize..." and then list bullets
76
+ - Do not skip the Answer paragraph
77
+
78
+ EXAMPLE OF THE DESIRED ANSWER STYLE:
79
+ Answer:
80
+ The retrieved hadiths show that mercy (raḥma / الرحمة) in Islam is both something believers ask from Allah and something they hope to experience in healing, forgiveness, and worship. One hadith includes the supplication "place Your mercy on earth" ("فاجعل رحمتك في الأرض"), presenting mercy as a source of relief and cure, while another links moments of worship with pausing at verses of mercy and praying for it, which shows that mercy is not only a theological idea but also a lived spiritual practice. Together, these narrations present mercy as divine care that the believer actively seeks in prayer, illness, and devotion.
81
 
82
+ Hadith Evidence:
83
+ - Source: Example
84
+ - Grade: Example
85
+ - Why it matters: Example
86
+ - Text: Example
87
 
88
+ Responses are generated from retrieved hadith evidence and the system is still under improvement.
89
+ """.strip()
 
 
90
 
91
 
92
  def normalize_quotes(text: str) -> str:
93
  if not text:
94
  return ""
95
+ return (
96
+ text.replace("", '"')
97
+ .replace("”", '"')
98
+ .replace("‘", "'")
99
+ .replace("’", "'")
100
+ )
101
+
102
+
103
+ def extract_answer_and_evidence(raw_text: str):
104
+ """
105
+ Try to split model output into Answer and Hadith Evidence.
106
+ If headings are missing, do a best-effort fallback.
107
+ """
108
+ text = raw_text.strip()
109
+
110
+ # Normalize common variants
111
+ text = re.sub(r"(?im)^#+\s*answer\s*:?\s*$", "Answer:", text)
112
+ text = re.sub(r"(?im)^#+\s*hadith evidence\s*:?\s*$", "Hadith Evidence:", text)
113
+ text = text.replace("Supporting Hadiths:", "Hadith Evidence:")
114
+ text = text.replace("Referenced Hadiths:", "Hadith Evidence:")
115
+ text = text.replace("Evidence:", "Hadith Evidence:")
116
 
117
+ answer_match = re.search(r"(?is)Answer:\s*(.*?)(?:\n\s*Hadith Evidence:|\Z)", text)
118
+ evidence_match = re.search(r"(?is)Hadith Evidence:\s*(.*)$", text)
119
 
120
+ answer = answer_match.group(1).strip() if answer_match else ""
121
+ evidence = evidence_match.group(1).strip() if evidence_match else ""
 
122
 
123
+ if not answer and not evidence:
124
+ return text.strip(), ""
125
+
126
+ return answer, evidence
127
+
128
+
129
+ def clean_intro_bullets(text: str) -> str:
130
+ """
131
+ Remove old-style bullet summaries at the beginning of the answer if the model adds them.
132
+ """
133
+ lines = [line.rstrip() for line in text.splitlines()]
134
+ cleaned = []
135
+ bullet_phase = True
136
+
137
+ for line in lines:
138
+ stripped = line.strip()
139
+
140
+ if bullet_phase and (
141
+ stripped.startswith("- ")
142
+ or re.match(r"^\d+\.\s+", stripped)
143
+ or stripped.lower().startswith("the hadiths provided")
144
+ ):
145
+ continue
146
+
147
+ if stripped:
148
+ bullet_phase = False
149
+
150
+ cleaned.append(line)
151
+
152
+ result = "\n".join(cleaned).strip()
153
+ return result
154
+
155
+
156
+ def clean_answer_paragraph(answer: str) -> str:
157
+ answer = normalize_quotes(answer)
158
+ answer = clean_intro_bullets(answer)
159
+
160
+ # remove obvious unwanted headings inside answer
161
+ bad_headings = [
162
+ "Main Insight:",
163
+ "What the Hadiths Show:",
164
+ "Key meanings:",
165
+ "Supporting evidence summary:",
166
+ "Short answer:",
167
+ ]
168
+ for h in bad_headings:
169
+ answer = answer.replace(h, "")
170
+
171
+ # remove leftover bullets inside answer start
172
+ answer = re.sub(r"(?m)^\s*-\s+", "", answer)
173
+ answer = re.sub(r"\n{2,}", "\n\n", answer).strip()
174
+
175
+ # force single paragraph
176
+ answer = re.sub(r"\s*\n\s*", " ", answer)
177
+ answer = re.sub(r"\s{2,}", " ", answer).strip()
178
+
179
+ # if model starts badly, nudge it
180
+ if answer and not answer.lower().startswith("the retrieved hadiths show"):
181
+ answer = "The retrieved hadiths show that " + answer[0].lower() + answer[1:] if len(answer) > 1 else "The retrieved hadiths show that " + answer
182
 
183
+ return answer
184
+
185
+
186
+ def parse_hadith_blocks_from_user_message(user_message: str):
187
+ """
188
+ Fallback parser:
189
+ Extract hadith entries directly from the user message if the model fails
190
+ to produce a proper Hadith Evidence section.
191
+ """
192
+ lines = user_message.splitlines()
193
+ blocks = []
194
+ i = 0
195
+
196
+ source_line_pattern = re.compile(r'^[A-Za-z0-9_\-]+(?:\s+[A-Za-z0-9_\-]+)*\s+#\d+', re.IGNORECASE)
197
+
198
+ while i < len(lines):
199
+ line = lines[i].strip()
200
+
201
+ if source_line_pattern.match(line):
202
+ source = line
203
+ grade = ""
204
+ text_lines = []
205
+ score = ""
206
+
207
+ if i + 1 < len(lines) and lines[i + 1].strip().lower().startswith("grade:"):
208
+ grade = lines[i + 1].strip()
209
+ # keep score if included on same line
210
+ i += 2
211
+ else:
212
+ i += 1
213
+
214
+ while i < len(lines):
215
+ current = lines[i].strip()
216
+ if source_line_pattern.match(current):
217
+ break
218
+ if current:
219
+ text_lines.append(current)
220
+ i += 1
221
+
222
+ full_text = " ".join(text_lines).strip()
223
+
224
+ why = infer_why_it_matters(full_text)
225
+
226
+ blocks.append({
227
+ "source": source,
228
+ "grade": grade if grade else "Grade: Unknown grade",
229
+ "why": why,
230
+ "text": full_text if full_text else "[No text provided]",
231
+ })
232
+ else:
233
+ i += 1
234
+
235
+ return blocks
236
+
237
+
238
+ def infer_why_it_matters(hadith_text: str) -> str:
239
+ t = hadith_text
240
 
241
+ if "ارحم" in t or "رحمتك" in t or "رحمة" in t:
242
+ if "شفاء" in t or "اشف" in t or "الوجع" in t:
243
+ return "Connects mercy with healing, relief, and supplication."
244
+ return "Directly relates to mercy as a theme in prayer or belief."
245
+
246
+ if "شفاء" in t or "اشف" in t:
247
+ return "Relates to healing and seeking Allah’s cure."
248
+
249
+ if "آية رحمة" in t or "باية رحمة" in t:
250
+ return "Shows how verses of mercy were treated in worship and recitation."
251
+
252
+ if "غفر" in t or "اغفر" in t:
253
+ return "Relates to forgiveness and seeking Allah’s pardon."
254
+
255
+ return "Provides supporting context connected to the retrieved topic."
256
+
257
+
258
+ def format_hadith_evidence(blocks) -> str:
259
+ if not blocks:
260
+ return "- Source: Not available\n- Grade: Not available\n- Why it matters: The retrieved evidence could not be formatted automatically.\n- Text: Not available"
261
+
262
+ formatted = []
263
+ for b in blocks:
264
+ formatted.append(
265
+ f"- Source: {b['source']}\n"
266
+ f"- Grade: {b['grade']}\n"
267
+ f"- Why it matters: {b['why']}\n"
268
+ f"- Text: {b['text']}"
269
+ )
270
+ return "\n\n".join(formatted)
271
+
272
+
273
+ def clean_answer(model_text: str, user_message: str) -> str:
274
+ text = normalize_quotes(model_text.strip())
275
+
276
+ # Remove final note if model already included it; we add once at the end
277
  text = re.sub(rf"(?s)\n*{re.escape(FINAL_NOTE)}\s*$", "", text).strip()
278
 
279
+ answer, evidence = extract_answer_and_evidence(text)
280
+ answer = clean_answer_paragraph(answer if answer else text)
281
 
282
+ # If model failed to give usable evidence, build it from the user message
283
+ if not evidence or "- Source:" not in evidence:
284
+ parsed_blocks = parse_hadith_blocks_from_user_message(user_message)
285
+ evidence = format_hadith_evidence(parsed_blocks)
286
+ else:
287
+ evidence = normalize_quotes(evidence).strip()
288
 
289
+ final = f"Answer:\n{answer}\n\nHadith Evidence:\n{evidence}\n\n{FINAL_NOTE}"
290
+ final = re.sub(r"\n{3,}", "\n\n", final).strip()
291
+ return final
292
 
 
 
 
 
293
 
294
+ def build_user_message(message: str) -> str:
295
+ """
296
+ Wrap the incoming API text so the model better understands that the user's
297
+ message contains both the question/topic and retrieved hadith evidence.
298
+ """
299
+ return f"""User request and retrieved hadith evidence are below.
300
+
301
+ Please answer using only this evidence.
302
+
303
+ Retrieved content:
304
+ {message}
305
+ """.strip()
306
 
307
 
308
  def chat(message, history):
 
310
 
311
  for user_msg, assistant_msg in history:
312
  if user_msg:
313
+ messages.append({"role": "user", "content": build_user_message(user_msg)})
314
  if assistant_msg:
315
  messages.append({"role": "assistant", "content": assistant_msg})
316
 
317
+ wrapped_message = build_user_message(message)
318
+ messages.append({"role": "user", "content": wrapped_message})
319
 
320
  try:
321
  response = client.chat.completions.create(
322
  model=MODEL_ID,
323
  messages=messages,
324
+ temperature=0.08,
325
+ max_tokens=1200,
326
  )
327
+ model_text = response.choices[0].message.content.strip()
328
+ answer = clean_answer(model_text, user_message=message)
329
  except Exception as e:
330
  answer = f"Error: {str(e)}"
331