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