shivareddy-03 commited on
Commit
b238d44
·
0 Parent(s):

Deploy FastAPI backend for HF Space

Browse files
.gitignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .env
6
+ .venv/
7
+ static/audio/
8
+ temp_input_file/
9
+ *.log
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y --no-install-recommends \
6
+ tesseract-ocr \
7
+ espeak-ng \
8
+ ffmpeg \
9
+ libsndfile1 \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ COPY requirements.txt ./
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+
15
+ COPY . .
16
+
17
+ ENV PYTHONUNBUFFERED=1
18
+
19
+ EXPOSE 7860
20
+
21
+ CMD ["uvicorn", "main_fastapi:app", "--host", "0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Offline Chatbot RAG Backend
3
+ emoji: ⚡
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: docker
7
+ app_port: 7860
8
+ ---
9
+
10
+ FastAPI backend for Offline Chatbot RAG.
11
+
12
+ ## Required Secrets
13
+
14
+ Set these in your Space settings:
15
+
16
+ - `GROQ_API_KEY`
17
+
18
+ ## Endpoints
19
+
20
+ - `GET /health`
21
+ - `POST /api/process` with form-data fields:
22
+ - `file`
23
+ - `query`
audio_result.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # import os
2
+ # import streamlit as st
3
+ from functions.translate import speech_to_text
4
+ from functions.data_to_vectors import create_vectorstore
5
+ # import pyttsx3
6
+ from functions.llm_comm import llm_communication
7
+ def rag(file, question):
8
+ print("🎤 Processing Audio File...")
9
+ # st.write("🎤 Processing Audio File...")
10
+ text = speech_to_text(file)
11
+ print(text)
12
+ vectorstore = create_vectorstore(str(text), "audio_store_chroma")
13
+
14
+ retrieval_chain = llm_communication(vectorstore)
15
+ # 8️⃣ Query pipeline
16
+ if question:
17
+ response = retrieval_chain.invoke({"input": question})
18
+ print(f"🧠 {question} {response['answer']}\n")
19
+ return response['answer']
20
+ # if st.button("🔊 Play Answer"):
21
+ # # Initialize TTS engine
22
+ # engine = pyttsx3.init()
23
+ # engine.say(response['answer'])
24
+ # engine.runAndWait()
csv_result.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1️⃣ Imports
2
+ import pandas as pd
3
+ from langchain.schema import Document
4
+ import os
5
+ from dotenv import load_dotenv
6
+ from functions.data_to_vectors import create_vectorstore
7
+ from functions.llm_comm import llm_communication
8
+ # --- Load environment variables ---
9
+ load_dotenv()
10
+ os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY")
11
+ def rag(file, question):
12
+ print("📊 Processing CSV File...")
13
+ df = pd.read_csv(file)
14
+ print("Extracted DataFrame:")
15
+ print(df)
16
+ print(f"Chat Input: {question}")
17
+ # Convert each row into a Document
18
+ documents = [
19
+ Document(page_content=row.to_string())
20
+ for _, row in df.iterrows()
21
+ ]
22
+ vectorstore = create_vectorstore(str(documents), "csv_store_chroma")
23
+
24
+ retrieval_chain = llm_communication(vectorstore)
25
+ # 8️⃣ Query pipeline
26
+ if question:
27
+ response = retrieval_chain.invoke({"input": question})
28
+ print(f"🧠 {question} {response['answer']}\n")
29
+ return response['answer']
functions/__init__.py ADDED
File without changes
functions/data_to_vectors.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # __package__ = "functions"
2
+ # from langchain.text_splitter import RecursiveCharacterTextSplitter
3
+ # from langchain_huggingface import HuggingFaceEmbeddings
4
+ # from langchain_community.vectorstores import Chroma
5
+ # from langchain.schema import Document
6
+ # def create_vectorstore(text,store):
7
+ # print("data loaded......")
8
+ # documents = [Document(page_content=text)]
9
+ # # Chunk text
10
+ # text_splitter = RecursiveCharacterTextSplitter(
11
+ # chunk_size=500,
12
+ # chunk_overlap=200
13
+ # )
14
+ # docs_chunks = text_splitter.split_documents(documents)
15
+
16
+ # # Embeddings
17
+ # embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
18
+
19
+ # # Chroma vectorstore
20
+ # vectorstore = Chroma.from_documents(
21
+ # documents=docs_chunks,
22
+ # embedding=embeddings,
23
+ # persist_directory=store
24
+ # )
25
+ # print(f"✅ Stored {len(docs_chunks)} chunks in ChromaDB")
26
+ # return vectorstore
27
+
28
+ __package__ = "functions"
29
+
30
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
31
+ from langchain_community.vectorstores import Chroma
32
+ from langchain.schema import Document
33
+ from sentence_transformers import SentenceTransformer
34
+ from langchain.embeddings.base import Embeddings # 👈 base class
35
+
36
+ # Custom wrapper for SentenceTransformer
37
+ class SentenceTransformerEmbeddings(Embeddings):
38
+ def __init__(self, model_name="sentence-transformers/all-MiniLM-L6-v2", device="cpu"):
39
+ self.model = SentenceTransformer(model_name, device=device)
40
+
41
+ def embed_documents(self, texts):
42
+ return self.model.encode(texts, convert_to_numpy=True).tolist()
43
+
44
+ def embed_query(self, text):
45
+ return self.model.encode([text], convert_to_numpy=True)[0].tolist()
46
+
47
+
48
+ def create_vectorstore(text, store):
49
+ print("data loaded......")
50
+ documents = [Document(page_content=text)]
51
+ print(text)
52
+ # Chunk text
53
+ text_splitter = RecursiveCharacterTextSplitter(
54
+ chunk_size=500,
55
+ chunk_overlap=200
56
+ )
57
+ docs_chunks = text_splitter.split_documents(documents)
58
+
59
+ # Use custom embedding wrapper
60
+ embeddings = SentenceTransformerEmbeddings()
61
+
62
+ # Chroma vectorstore
63
+ vectorstore = Chroma.from_documents(
64
+ documents=docs_chunks,
65
+ embedding=embeddings,
66
+ persist_directory=store
67
+ )
68
+
69
+ print(f"✅ Stored {len(docs_chunks)} chunks in ChromaDB")
70
+ return vectorstore
functions/llm_comm.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from langchain_groq import ChatGroq
3
+ from langchain.chains.combine_documents import create_stuff_documents_chain
4
+ from langchain.chains import create_retrieval_chain
5
+ from functions.main_prompt import in_prompt
6
+ def llm_communication(vectorstore):
7
+ llm = ChatGroq(model="llama-3.3-70b-versatile")
8
+ # 4️⃣ Create prompt template
9
+ prompt = in_prompt
10
+ # 5️⃣ Convert vectorstore into retriever
11
+ retriever = vectorstore.as_retriever(search_kwargs={"k":3}) # retrieve top 3 chunks
12
+ # 6️⃣ Build document chain (stuffing docs into prompt)
13
+ doc_chain = create_stuff_documents_chain(llm, prompt)
14
+ # 7️⃣ Create retrieval chain
15
+ return create_retrieval_chain(retriever, doc_chain)
functions/main_prompt.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.prompts import ChatPromptTemplate
2
+ in_prompt = ChatPromptTemplate.from_template(
3
+ """You are a precise document QA assistant.
4
+ Answer the user's question ONLY using the provided context.
5
+ Keep your response short, factual, and directly to the point.
6
+ Do NOT add extra explanations, summaries, or outside knowledge.
7
+ If the answer is not in the context, reply exactly:
8
+ "The information required to answer this question was not found in the provided document."
9
+ --- CONTEXT ---
10
+ {context}
11
+ --- USER QUESTION ---
12
+ {input}
13
+ --- ANSWER ---""")
functions/translate.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __package__ = "functions"
2
+ from vosk import Model, KaldiRecognizer
3
+ import wave
4
+ import json
5
+ # Load Vosk model (download small model first, e.g., vosk-model-small-en-us-0.15)
6
+ def speech_to_text(input_file):
7
+ model = Model(r"vosk-model-small-en-us-0.15")
8
+ # Open WAV file
9
+ wf = wave.open(input_file, "rb")
10
+ rec = KaldiRecognizer(model, wf.getframerate())
11
+
12
+ text = ""
13
+ while True:
14
+ data = wf.readframes(4000)
15
+ if len(data) == 0:
16
+ break
17
+ if rec.AcceptWaveform(data):
18
+ res = json.loads(rec.Result())
19
+ text += res.get("text", "") + " "
20
+
21
+ # Get final part
22
+ res = json.loads(rec.FinalResult())
23
+ text += res.get("text", "")
24
+ print(text)
25
+ print("🎤 Transcription:")
26
+ return text
image_result.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1️⃣ Imports
2
+ import pytesseract
3
+ from PIL import Image
4
+ import os
5
+ from dotenv import load_dotenv
6
+ from functions.data_to_vectors import create_vectorstore
7
+ from functions.llm_comm import llm_communication
8
+ # --- Load environment variables ---
9
+ load_dotenv()
10
+ os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY")
11
+
12
+ # Example: create/store vectorstore
13
+ def rag(file_path,question):
14
+ image = Image.open(file_path)
15
+ text = pytesseract.image_to_string(image)
16
+ vectorstore = create_vectorstore(text,"image_store_chroma" )
17
+
18
+ retrieval_chain = llm_communication(vectorstore)
19
+ # 8️⃣ Query pipeline
20
+ if question:
21
+ response = retrieval_chain.invoke({"input": question})
22
+ print(f"🧠 {question} {response['answer']}\n")
23
+ return response['answer']
24
+
main_fastapi.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, Form, HTTPException, UploadFile
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.staticfiles import StaticFiles
4
+ import aiofiles
5
+ import asyncio
6
+ import importlib
7
+ from pathlib import Path
8
+ import os
9
+ import uuid
10
+
11
+ BASE_DIR = Path(__file__).resolve().parent
12
+ TEMP_DIR = BASE_DIR / "temp_input_file"
13
+ STATIC_DIR = BASE_DIR / "static"
14
+ STATIC_AUDIO_DIR = STATIC_DIR / "audio"
15
+
16
+ TEMP_DIR.mkdir(parents=True, exist_ok=True)
17
+ STATIC_AUDIO_DIR.mkdir(parents=True, exist_ok=True)
18
+
19
+ app = FastAPI(title="Multi-Modal RAG Backend")
20
+
21
+ # Keep open CORS so frontend hosted on GitHub Pages can call this API.
22
+ app.add_middleware(
23
+ CORSMiddleware,
24
+ allow_origins=["*"],
25
+ allow_credentials=False,
26
+ allow_methods=["*"],
27
+ allow_headers=["*"],
28
+ )
29
+
30
+ app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
31
+
32
+
33
+ def _module_for_content_type(content_type: str):
34
+ if content_type.startswith("image/"):
35
+ return "image_result", "binary"
36
+ if content_type == "text/plain":
37
+ return "text_result", "text"
38
+ if content_type == "application/pdf":
39
+ return "pdf_result", "path"
40
+ if content_type in ("text/csv", "application/csv"):
41
+ return "csv_result", "path"
42
+ if content_type.startswith("audio/"):
43
+ return "audio_result", "path"
44
+ raise ValueError("Unsupported file type")
45
+
46
+
47
+ async def save_file_tmp(file: UploadFile):
48
+ suffix = os.path.splitext(file.filename or "upload.bin")[1]
49
+ tmp_path = TEMP_DIR / f"{uuid.uuid4().hex}{suffix}"
50
+ async with aiofiles.open(tmp_path, "wb") as f:
51
+ while True:
52
+ chunk = await file.read(1024 * 1024)
53
+ if not chunk:
54
+ break
55
+ await f.write(chunk)
56
+ return tmp_path
57
+
58
+
59
+ def process_file(path: Path, content_type: str, query: str):
60
+ if not content_type:
61
+ raise ValueError("Missing file content type")
62
+
63
+ module_name, mode = _module_for_content_type(content_type)
64
+
65
+ if module_name == "audio_result":
66
+ vosk_model = BASE_DIR / "vosk-model-small-en-us-0.15"
67
+ if not vosk_model.exists():
68
+ raise ValueError(
69
+ "Audio processing is not enabled on this deployment (Vosk model missing)."
70
+ )
71
+
72
+ module = importlib.import_module(module_name)
73
+
74
+ if mode == "binary":
75
+ with open(path, "rb") as f:
76
+ return module.rag(f, query)
77
+ if mode == "text":
78
+ with open(path, "r", encoding="utf-8") as f:
79
+ return module.rag(f, query)
80
+ return module.rag(str(path), query)
81
+
82
+
83
+ def generate_tts(text: str):
84
+ try:
85
+ import pyttsx3
86
+ except Exception:
87
+ return None
88
+
89
+ filename = f"{uuid.uuid4().hex}.wav"
90
+ output_path = STATIC_AUDIO_DIR / filename
91
+
92
+ try:
93
+ engine = pyttsx3.init()
94
+ engine.save_to_file(text, str(output_path))
95
+ engine.runAndWait()
96
+ except Exception:
97
+ return None
98
+
99
+ return f"/static/audio/{filename}"
100
+
101
+
102
+ @app.post("/api/process")
103
+ async def process_endpoint(file: UploadFile = File(...), query: str = Form(...)):
104
+ tmp_path = await save_file_tmp(file)
105
+ loop = asyncio.get_running_loop()
106
+
107
+ try:
108
+ response = await loop.run_in_executor(
109
+ None,
110
+ process_file,
111
+ tmp_path,
112
+ file.content_type or "",
113
+ query,
114
+ )
115
+ except ValueError as e:
116
+ raise HTTPException(status_code=400, detail=str(e))
117
+ except Exception as e:
118
+ raise HTTPException(status_code=500, detail=str(e))
119
+
120
+ audio_url = await loop.run_in_executor(None, generate_tts, response)
121
+ return {"response": response, "audio_url": audio_url}
122
+
123
+
124
+ @app.get("/")
125
+ def root():
126
+ return {"status": "RAG backend running"}
127
+
128
+
129
+ @app.get("/health")
130
+ def health():
131
+ return {"ok": True}
pdf_result.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1️⃣ Imports
2
+ import os
3
+ from PyPDF2 import PdfReader
4
+ from dotenv import load_dotenv
5
+ from functions.data_to_vectors import create_vectorstore
6
+ from functions.llm_comm import llm_communication
7
+ # --- Load environment variables ---
8
+ load_dotenv()
9
+ os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY")
10
+ def rag(file_path,question):
11
+ print("📕 Processing PDF File...")
12
+ pdf_reader = PdfReader(file_path)
13
+ text = ""
14
+ for page in pdf_reader.pages:
15
+ text += page.extract_text() + "\n"
16
+ # Wrap in Document
17
+ vectorstore = create_vectorstore(text,"pdf_store_chroma" )
18
+
19
+ retrieval_chain = llm_communication(vectorstore)
20
+ # 8️⃣ Query pipeline
21
+ if question:
22
+ response = retrieval_chain.invoke({"input": question})
23
+ print(f"🧠 {question} {response['answer']}\n")
24
+ return response['answer']
25
+
requirements.txt ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ aiofiles
4
+ python-multipart
5
+ python-dotenv
6
+ pandas
7
+ PyPDF2
8
+ pillow
9
+ pytesseract
10
+ chromadb
11
+ langchain
12
+ langchain-community
13
+ langchain-core
14
+ langchain-text-splitters
15
+ langchain-groq
16
+ sentence-transformers
17
+ vosk
18
+ pyttsx3
text_result.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import functions.data_to_vectors as dtv
3
+ from functions.llm_comm import llm_communication
4
+ from functions.data_to_vectors import create_vectorstore
5
+ import sys # Import the sys module
6
+
7
+ def rag(file_path, question):
8
+ print("📄 Processing TXT File...")
9
+ # file_path is already a file-like object
10
+ raw_bytes = file_path.read()
11
+ if isinstance(raw_bytes, str):
12
+ text = raw_bytes # already decoded
13
+ else:
14
+ text = raw_bytes.decode("utf-8")
15
+
16
+ print(text)
17
+ vectorstore = create_vectorstore(text,"text_store_chroma" )
18
+
19
+ retrieval_chain = llm_communication(vectorstore)
20
+ # 8️⃣ Query pipeline
21
+ if question:
22
+ response = retrieval_chain.invoke({"input": question})
23
+ print(f"🧠 {question} {response['answer']}\n")
24
+ return response['answer']