| import os |
| import zipfile |
| import logging |
| from fastapi import FastAPI, HTTPException |
| from pydantic import BaseModel |
|
|
| from langchain_community.vectorstores import FAISS |
| from langchain_huggingface import HuggingFaceEmbeddings |
| from langchain_groq import ChatGroq |
| from langchain.chains import RetrievalQA |
| from langchain.prompts import PromptTemplate |
|
|
| |
| logging.basicConfig( |
| level=logging.INFO, |
| format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" |
| ) |
| logger = logging.getLogger(__name__) |
|
|
| app = FastAPI() |
|
|
| |
| llm = None |
| embeddings = None |
| vectorstore = None |
| retriever = None |
| chain = None |
|
|
| class QueryRequest(BaseModel): |
| question: str |
|
|
| @app.on_event("startup") |
| def load_components(): |
| global llm, embeddings, vectorstore, retriever, chain |
|
|
| try: |
| |
| api_key = os.getenv("API_KEY") |
| if not api_key: |
| logger.error("API_KEY environment variable is not set or empty.") |
| raise RuntimeError("API_KEY environment variable is not set or empty.") |
| logger.info("API_KEY is set.") |
|
|
| |
| llm = ChatGroq( |
| model="meta-llama/llama-4-scout-17b-16e-instruct", |
| temperature=0, |
| max_tokens=1024, |
| api_key=api_key, |
| ) |
| embeddings = HuggingFaceEmbeddings( |
| model_name="intfloat/multilingual-e5-large", |
| model_kwargs={"device": "cpu"}, |
| encode_kwargs={"normalize_embeddings": True}, |
| ) |
|
|
| |
| for zip_name, dir_name in [("faiss_index.zip", "faiss_index"), ("faiss_index(1).zip", "faiss_index_extra")]: |
| if not os.path.exists(dir_name): |
| with zipfile.ZipFile(zip_name, 'r') as z: |
| z.extractall(dir_name) |
| logger.info(f"Unzipped {zip_name} to {dir_name}.") |
| else: |
| logger.info(f"Directory {dir_name} already exists.") |
|
|
| vs1 = FAISS.load_local( |
| "faiss_index", |
| embeddings, |
| allow_dangerous_deserialization=True |
| ) |
| logger.info("FAISS index 1 loaded.") |
|
|
| vs2 = FAISS.load_local( |
| "faiss_index_extra", |
| embeddings, |
| allow_dangerous_deserialization=True |
| ) |
| logger.info("FAISS index 2 loaded.") |
|
|
| |
| vs1.merge_from(vs2) |
| vectorstore = vs1 |
| logger.info("Merged FAISS indexes into a single vectorstore.") |
|
|
| |
| retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) |
| prompt = PromptTemplate( |
| template=""" |
| You are an expert assistant on Islamic knowledge. |
| Use **only** the information in the “Retrieved context” to answer general questions related to Islam. |
| Do **not** add any outside information, personal opinions, or conjecture—if the answer is not contained in the context, reply with "I don't know". |
| Be concise, accurate, and directly address the user’s question. Always write reference from where you answer. |
| |
| Retrieved context: |
| {context} |
| |
| User’s question: |
| {question} |
| |
| Your response: |
| """, |
| input_variables=["context", "question"], |
| ) |
| chain = RetrievalQA.from_chain_type( |
| llm=llm, |
| chain_type="stuff", |
| retriever=retriever, |
| return_source_documents=False, |
| chain_type_kwargs={"prompt": prompt}, |
| ) |
| logger.info("QA chain ready.") |
|
|
| except Exception as e: |
| logger.error("Error loading components", exc_info=True) |
| raise |
|
|
| @app.get("/") |
| def root(): |
| return {"message": "API is up and running!"} |
|
|
| @app.post("/query") |
| def query(request: QueryRequest): |
| try: |
| logger.info("Received query: %s", request.question) |
| result = chain.invoke({"query": request.question}) |
| logger.info("Query processed successfully.") |
| return {"answer": result.get("result")} |
| except Exception as e: |
| logger.error("Error processing query", exc_info=True) |
| |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|