quran-explainer / app.py
Hamid191's picture
Update app.py
46d904d verified
Raw
History Blame Contribute Delete
7.29 kB
import os
import requests
from flask import Flask, request, jsonify, send_from_directory
from dotenv import load_dotenv
# --- LangChain Imports ---
from langchain_groq import ChatGroq
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
# Load environment variables
load_dotenv()
app = Flask(__name__)
# --- Configuration ---
TAFSIR_OPTIONS = [
{"slug": "en-tafisr-ibn-kathir", "name": "Tafsir Ibn Kathir", "author": "Hafiz Ibn Kathir", "highlight": "A famous, highly respected classical commentary."},
{"slug": "en-tafsir-maarif-ul-quran", "name": "Maarif-ul-Quran", "author": "Mufti Muhammad Shafi", "highlight": "A renowned comprehensive modern commentary."},
{"slug": "en-al-jalalayn", "name": "Tafsir al-Jalalayn", "author": "Jalal al-Din al-Mahalli & al-Suyuti", "highlight": "A famous concise classical commentary."},
{"slug": "en-tafsir-ibn-abbas", "name": "Tanwîr al-Miqbâs", "author": "Attributed to Ibn 'Abbâs", "highlight": "One of the earliest commentaries."}
]
# We will always fetch from Al-Wahidi for Shan-e-Nazool
SHAN_E_NAZOOL_SLUG = "en-asbab-al-nuzul-by-al-wahidi"
QURAN_API_BASE_URL = "http://api.alquran.cloud/v1/ayah"
TAFSIR_API_BASE_URL = "https://cdn.jsdelivr.net/gh/spa5k/tafsir_api@main/tafsir"
DATA_UNAVAILABLE_MESSAGE = "No specific commentary was found for this verse in this source."
# --- LangChain Setup ---
try:
llm = ChatGroq(model="llama-3.3-70b-versatile", temperature=0.6)
# This is our new, sophisticated, all-in-one prompt
prompt = PromptTemplate(
template="""
You are an expert assistant for Quranic studies. Your task is to provide a clear, multi-source explanation for Surah {surah}, Ayah {ayah}.
**Your response MUST be structured in the following order, using the provided data:**
1. **The Verse:**
- Start with the heading: "### The Verse (Surah {surah}:{ayah})".
- Quote the English translation: "{ayah_text}".
2. **Shan-e-Nazool (Reason for Revelation):**
- Use the heading: "### Shan-e-Nazool (Reason for Revelation)".
- Based **only** on the provided text from "Asbab Al-Nuzul by Al-Wahidi", summarize the reason for revelation.
- If the text from Al-Wahidi is '{data_unavailable}', you MUST state: "No specific reason for revelation for this verse was found in Al-Wahidi's Asbab al-Nuzul."
3. **Commentary from {tafsir_name}:**
- Use the heading: "### Commentary from {tafsir_name}".
- Provide a comprehensive summary of the commentary from the user's chosen Tafsir ({tafsir_name} by {tafsir_author}).
- If the text for this commentary is '{data_unavailable}', you MUST state: "The selected source, {tafsir_name}, does not provide a detailed commentary for this specific verse."
4. **Answer to Specific Question (If provided):**
- If a user question is provided, use the heading: "### Answer to Your Question".
- Answer the user's question: "{question}"
- **CRITICAL:** Your answer must be based **only** on the combined information from the Al-Wahidi text and the {tafsir_name} text.
- If the provided texts do not contain enough information to answer the question, state that clearly. For example: "The provided commentaries do not contain specific information to answer the question: '{question}'."
- If no question is provided (i.e., the question is 'N/A'), DO NOT include this section in your output.
5. **Summary:**
- Use the heading: "### Summary".
- Provide a brief, final summary synthesizing the key points from the available commentaries.
---
**DATA FOR YOUR TASK:**
[Verse Translation]:
{ayah_text}
[Asbab Al-Nuzul by Al-Wahidi]:
{shan_e_nazool_text}
[{tafsir_name} by {tafsir_author}]:
{tafsir_text}
[User's Question]:
{question}
---
Begin your response now.
""",
input_variables=["surah", "ayah", "ayah_text", "shan_e_nazool_text", "tafsir_name", "tafsir_author", "tafsir_text", "question", "data_unavailable"]
)
output_parser = StrOutputParser()
main_chain = prompt | llm | output_parser
except Exception as e:
print(f"Error initializing LangChain components: {e}")
main_chain = None
# --- Helper Functions ---
def get_data(url: str) -> str:
"""Generic function to fetch data and handle errors."""
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
text = data.get("text", "").strip()
return text if text else DATA_UNAVAILABLE_MESSAGE
except requests.exceptions.RequestException:
return DATA_UNAVAILABLE_MESSAGE
def get_ayah_text(surah: int, ayah: int) -> str:
url = f"{QURAN_API_BASE_URL}/{surah}:{ayah}/en.asad"
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
if data.get('code') == 200 and data.get('data', {}).get('text'):
return data['data']['text']
return "Could not retrieve the translation for this verse."
except requests.exceptions.RequestException:
return "Could not retrieve the translation due to a network error."
# --- Flask Routes ---
@app.route('/')
def index():
return send_from_directory('.', 'index.html')
@app.route('/tafsirs', methods=['GET'])
def get_tafsirs():
return jsonify(TAFSIR_OPTIONS)
@app.route('/explain', methods=['POST'])
def explain_ayah():
if not main_chain:
return jsonify({"error": "LangChain services are not initialized."}), 500
data = request.get_json()
surah = data.get('surah')
ayah = data.get('ayah')
tafsir_slug = data.get('tafsir_slug')
question = data.get('question', '').strip()
try:
selected_tafsir = next((t for t in TAFSIR_OPTIONS if t['slug'] == tafsir_slug), None)
if not selected_tafsir:
return jsonify({"error": "Invalid Tafsir slug."}), 400
# --- Multi-Source Data Retrieval ---
ayah_text = get_ayah_text(surah, ayah)
shan_e_nazool_text = get_data(f"{TAFSIR_API_BASE_URL}/{SHAN_E_NAZOOL_SLUG}/{surah}/{ayah}.json")
tafsir_text = get_data(f"{TAFSIR_API_BASE_URL}/{tafsir_slug}/{surah}/{ayah}.json")
# --- Invoke the LangChain Chain ---
result = main_chain.invoke({
"surah": surah,
"ayah": ayah,
"ayah_text": ayah_text,
"shan_e_nazool_text": shan_e_nazool_text,
"tafsir_name": selected_tafsir['name'],
"tafsir_author": selected_tafsir['author'],
"tafsir_text": tafsir_text,
"question": question if question else "N/A", # Pass 'N/A' if no question
"data_unavailable": DATA_UNAVAILABLE_MESSAGE
})
return jsonify({"explanation": result})
except Exception as e:
print(f"An error occurred in the /explain route: {e}")
return jsonify({"error": "An internal server error occurred."}), 500
if __name__ == '__main__':
app.run(debug=True)
# #llama-3.3-70b-versatile