KalooAI commited on
Commit
4a597f7
ยท
verified ยท
1 Parent(s): b1e4a16

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -73
app.py CHANGED
@@ -1,134 +1,124 @@
1
  import os
2
- import io
3
  import gradio as gr
4
  import numpy as np
5
  import faiss
6
- from huggingface_hub import InferenceClient
7
- from sentence_transformers import SentenceTransformer
8
  from PyPDF2 import PdfReader
 
 
9
 
10
- # =========================
11
- # Config
12
- # =========================
13
 
14
- # Set this in your HF Space secrets or local env
15
  HF_TOKEN = os.getenv("HF_TOKEN", None)
16
 
17
- # IBM Granite model on Hugging Face (free tier compatible)
18
  GRANITE_MODEL = "ibm-granite/granite-8b-instruct"
19
 
20
- # Embedding model (runs locally in the Space CPU)
21
- EMBEDDING_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
22
 
23
  # Load models once per process
24
- embedding_model = SentenceTransformer(EMBEDDING_MODEL_NAME)
25
- llm_client = InferenceClient(model=GRANITE_MODEL, token=HF_TOKEN)
26
 
27
 
28
- # =========================
29
- # PDF & RAG utilities
30
- # =========================
31
 
32
- def read_pdf(file_obj) -> str:
33
- """Read all text from a PDF file object."""
34
- if file_obj is None:
35
- return ""
36
-
37
- # Gradio gives a tempfile-like object
38
  reader = PdfReader(file_obj)
39
- texts = []
40
  for page in reader.pages:
41
  text = page.extract_text()
42
  if text:
43
- texts.append(text)
44
- return "\n".join(texts)
45
-
46
 
47
- def chunk_text(text: str, chunk_size: int = 800, overlap: int = 200):
48
- """Simple text splitter into overlapping chunks."""
49
- if not text:
50
- return []
51
 
 
 
52
  words = text.split()
53
  chunks = []
54
  start = 0
 
55
  while start < len(words):
56
  end = start + chunk_size
57
  chunk = " ".join(words[start:end])
58
  chunks.append(chunk)
59
  start += chunk_size - overlap
 
60
  return chunks
61
 
62
 
63
  def build_vectorstore(chunks):
64
- """Create FAISS index and store embeddings."""
65
  if not chunks:
66
  return None, None
67
 
68
  embeddings = embedding_model.encode(chunks, convert_to_numpy=True)
69
  dim = embeddings.shape[1]
 
70
  index = faiss.IndexFlatL2(dim)
71
  index.add(embeddings)
 
72
  return index, embeddings
73
 
74
 
75
- def retrieve_context(question: str, state: dict, k: int = 4) -> str:
76
- """Retrieve top-k relevant chunks as context."""
77
  index = state.get("index")
78
  chunks = state.get("chunks")
79
 
80
- if index is None or chunks is None or len(chunks) == 0:
81
  return ""
82
 
83
  q_emb = embedding_model.encode([question], convert_to_numpy=True)
84
  distances, indices = index.search(q_emb, k)
85
- indices = indices[0]
86
 
87
- selected_chunks = [chunks[i] for i in indices if 0 <= i < len(chunks)]
88
- return "\n\n---\n\n".join(selected_chunks)
 
89
 
 
 
 
90
 
91
- def generate_answer(question: str, context: str) -> str:
92
- """Call Granite via HF Inference to generate an answer."""
93
- if not context:
94
- system_prompt = (
95
- "You are a helpful assistant. The user asked a question, "
96
- "but you have no document context. Answer briefly and honestly."
 
 
97
  )
98
- prompt = f"{system_prompt}\n\nQuestion: {question}\nAnswer:"
99
  else:
100
- system_prompt = (
101
- "You are a helpful assistant answering questions about a document. "
102
- "Use ONLY the information in the context. If the answer is not in "
103
- "the context, say you don't know."
104
- )
105
  prompt = (
106
- f"{system_prompt}\n\n"
107
- f"Context:\n{context}\n\n"
108
  f"Question: {question}\nAnswer:"
109
  )
110
 
111
- # Granite models on HF typically support text_generation-style calls
112
- response = llm_client.text_generation(
113
  prompt,
114
  max_new_tokens=512,
115
  temperature=0.2,
116
  top_p=0.9,
117
  repetition_penalty=1.05,
118
  )
 
119
  return response.strip()
120
 
121
 
122
- # =========================
123
- # Gradio callbacks
124
- # =========================
125
 
126
  def load_pdf(file, state):
127
- """
128
- Called when a new PDF is uploaded.
129
- We RESET all previous state here so no old PDF is kept in memory.
130
- """
131
- # Hard reset of PDF-related state
132
  state["chunks"] = None
133
  state["index"] = None
134
 
@@ -140,7 +130,7 @@ def load_pdf(file, state):
140
  chunks = chunk_text(text)
141
 
142
  if not chunks:
143
- return "Could not extract any text from the PDF.", state
144
 
145
  index, _ = build_vectorstore(chunks)
146
 
@@ -150,18 +140,15 @@ def load_pdf(file, state):
150
  return f"PDF loaded successfully. {len(chunks)} chunks created.", state
151
 
152
  except Exception as e:
153
- return f"Error while processing PDF: {e}", state
154
 
155
 
156
  def answer_question(question, state):
157
- """
158
- Called when the user submits a question.
159
- Uses the current session's vectorstore only.
160
- """
161
- if not question or question.strip() == "":
162
  return "Please enter a question."
163
 
164
- if state.get("index") is None or state.get("chunks") is None:
165
  return "Please upload a PDF first."
166
 
167
  context = retrieve_context(question, state)
@@ -169,14 +156,13 @@ def answer_question(question, state):
169
  return answer
170
 
171
 
172
- # =========================
173
- # Gradio UI
174
- # =========================
175
 
176
  with gr.Blocks(title="Granite PDF QA") as demo:
177
- gr.Markdown("# ๐Ÿ“„๐Ÿ” PDF Q&A with IBM Granite\nUpload a PDF and ask questions about its content.")
178
 
179
- # Per-session state: nothing leaks between users
180
  state = gr.State({"chunks": None, "index": None})
181
 
182
  with gr.Row():
 
1
  import os
 
2
  import gradio as gr
3
  import numpy as np
4
  import faiss
 
 
5
  from PyPDF2 import PdfReader
6
+ from sentence_transformers import SentenceTransformer
7
+ from huggingface_hub import InferenceClient
8
 
9
+ # ============================================================
10
+ # CONFIG
11
+ # ============================================================
12
 
 
13
  HF_TOKEN = os.getenv("HF_TOKEN", None)
14
 
15
+ # Free IBM Granite model on Hugging Face
16
  GRANITE_MODEL = "ibm-granite/granite-8b-instruct"
17
 
18
+ # Local embedding model (fast + CPU friendly)
19
+ EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
20
 
21
  # Load models once per process
22
+ embedding_model = SentenceTransformer(EMBEDDING_MODEL)
23
+ llm = InferenceClient(model=GRANITE_MODEL, token=HF_TOKEN)
24
 
25
 
26
+ # ============================================================
27
+ # PDF PROCESSING
28
+ # ============================================================
29
 
30
+ def read_pdf(file_obj):
31
+ """Extract raw text from a PDF file."""
 
 
 
 
32
  reader = PdfReader(file_obj)
33
+ pages = []
34
  for page in reader.pages:
35
  text = page.extract_text()
36
  if text:
37
+ pages.append(text)
38
+ return "\n".join(pages)
 
39
 
 
 
 
 
40
 
41
+ def chunk_text(text, chunk_size=800, overlap=200):
42
+ """Split text into overlapping chunks."""
43
  words = text.split()
44
  chunks = []
45
  start = 0
46
+
47
  while start < len(words):
48
  end = start + chunk_size
49
  chunk = " ".join(words[start:end])
50
  chunks.append(chunk)
51
  start += chunk_size - overlap
52
+
53
  return chunks
54
 
55
 
56
  def build_vectorstore(chunks):
57
+ """Embed chunks and build FAISS index."""
58
  if not chunks:
59
  return None, None
60
 
61
  embeddings = embedding_model.encode(chunks, convert_to_numpy=True)
62
  dim = embeddings.shape[1]
63
+
64
  index = faiss.IndexFlatL2(dim)
65
  index.add(embeddings)
66
+
67
  return index, embeddings
68
 
69
 
70
+ def retrieve_context(question, state, k=4):
71
+ """Retrieve top-k relevant chunks."""
72
  index = state.get("index")
73
  chunks = state.get("chunks")
74
 
75
+ if index is None or chunks is None:
76
  return ""
77
 
78
  q_emb = embedding_model.encode([question], convert_to_numpy=True)
79
  distances, indices = index.search(q_emb, k)
 
80
 
81
+ selected = [chunks[i] for i in indices[0] if i < len(chunks)]
82
+ return "\n\n---\n\n".join(selected)
83
+
84
 
85
+ # ============================================================
86
+ # LLM ANSWERING (IBM GRANITE)
87
+ # ============================================================
88
 
89
+ def generate_answer(question, context):
90
+ """Generate an answer using IBM Granite."""
91
+ if context:
92
+ prompt = (
93
+ "You are a helpful assistant answering questions strictly from the provided context.\n"
94
+ "If the answer is not in the context, say you don't know.\n\n"
95
+ f"Context:\n{context}\n\n"
96
+ f"Question: {question}\nAnswer:"
97
  )
 
98
  else:
 
 
 
 
 
99
  prompt = (
100
+ "You are a helpful assistant. The user asked a question but no document context is available.\n\n"
 
101
  f"Question: {question}\nAnswer:"
102
  )
103
 
104
+ response = llm.text_generation(
 
105
  prompt,
106
  max_new_tokens=512,
107
  temperature=0.2,
108
  top_p=0.9,
109
  repetition_penalty=1.05,
110
  )
111
+
112
  return response.strip()
113
 
114
 
115
+ # ============================================================
116
+ # GRADIO CALLBACKS
117
+ # ============================================================
118
 
119
  def load_pdf(file, state):
120
+ """Reset state and load a new PDF."""
121
+ # Reset everything to avoid memory persistence
 
 
 
122
  state["chunks"] = None
123
  state["index"] = None
124
 
 
130
  chunks = chunk_text(text)
131
 
132
  if not chunks:
133
+ return "Could not extract text from PDF.", state
134
 
135
  index, _ = build_vectorstore(chunks)
136
 
 
140
  return f"PDF loaded successfully. {len(chunks)} chunks created.", state
141
 
142
  except Exception as e:
143
+ return f"Error processing PDF: {e}", state
144
 
145
 
146
  def answer_question(question, state):
147
+ """Answer a question using the current PDF context."""
148
+ if not question.strip():
 
 
 
149
  return "Please enter a question."
150
 
151
+ if state.get("index") is None:
152
  return "Please upload a PDF first."
153
 
154
  context = retrieve_context(question, state)
 
156
  return answer
157
 
158
 
159
+ # ============================================================
160
+ # GRADIO UI
161
+ # ============================================================
162
 
163
  with gr.Blocks(title="Granite PDF QA") as demo:
164
+ gr.Markdown("## ๐Ÿ“„๐Ÿ” PDF Q&A using IBM Granite\nUpload a PDF and ask questions about its content.")
165
 
 
166
  state = gr.State({"chunks": None, "index": None})
167
 
168
  with gr.Row():