aravindkb7 commited on
Commit
e57ad8c
Β·
verified Β·
1 Parent(s): ffea066

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -289
app.py CHANGED
@@ -3,7 +3,6 @@ import os
3
  import re
4
  import gradio as gr
5
  import torch
6
- torch.backends.cuda.matmul.allow_tf32 = True
7
  import faiss
8
  from dataclasses import dataclass
9
  from typing import Optional
@@ -11,18 +10,18 @@ from transformers import AutoModelForCausalLM, AutoTokenizer
11
  from sentence_transformers import SentenceTransformer
12
  from huggingface_hub import login
13
 
14
- # ── Auth ───────────────────────────────────────────────────────────────────────
15
  _token = os.environ.get("HF_TOKEN")
16
  if _token:
17
  login(token=_token)
18
 
19
- # ── Config ─────────────────────────────────────────────────────────────────────
20
  CHECKPOINT = "selfrag/selfrag_llama2_7b"
21
  K_PASSAGES = 5
22
- MAX_NEW_TOKS = 150
23
  W_REL = 1.0; W_SUP = 1.0; W_USE = 0.5
24
 
25
- # ── Reflection tokens ──────────────────────────────────────────────────────────
26
  class RT:
27
  RETRIEVAL = "[Retrieval]"
28
  NO_RETRIEVAL = "[No Retrieval]"
@@ -42,307 +41,98 @@ class RT:
42
  ABSTAIN_PHRASES = [
43
  "not specified in the input","not provided in the input",
44
  "not mentioned in the input","no information","cannot be determined",
45
- "is not specified","are not specified","not available in the input",
46
- "cannot answer","no relevant information","not found in",
47
- "does not contain","not in the context","not mentioned in the context",
48
  ]
49
 
50
- # ── Corpus ─────────────────────────────────────────────────────────────────────
51
  DOCUMENTS = [
52
- {"id":"doc_00","title":"RAG Overview",
53
- "text":"Retrieval-Augmented Generation (RAG) improves factual correctness in large language "
54
- "models by combining a neural generator with a non-parametric retrieval system. The model "
55
- "is given relevant documents at inference time instead of relying solely on memorised "
56
- "parametric knowledge. RAG was introduced by Lewis et al. in 2020."},
57
- {"id":"doc_01","title":"Vanilla RAG Limitations",
58
- "text":"Vanilla RAG always retrieves a fixed number of top-k documents regardless of whether "
59
- "retrieval is actually necessary for the query. This indiscriminate retrieval can introduce "
60
- "noise when the documents are not relevant to the question, and the model may produce "
61
- "outputs that are inconsistent with retrieved passages."},
62
- {"id":"doc_02","title":"SELF-RAG Framework",
63
- "text":"SELF-RAG is a framework that trains a single language model to retrieve, generate, and "
64
- "critique text through self-reflection. It uses four special reflection tokens: Retrieve "
65
- "(yes/no/continue), IsRel (relevant/irrelevant), IsSupport (fully/partially/no support), "
66
- "and IsUse (1 to 5). These tokens allow the model to decide adaptively when to retrieve "
67
- "and evaluate the quality of its own generation at inference time. SELF-RAG was published "
68
- "at ICLR 2024 by Asai et al."},
69
- {"id":"doc_03","title":"CRAG β€” Corrective RAG",
70
- "text":"Corrective Retrieval-Augmented Generation (CRAG) addresses the problem of inaccurate or "
71
- "irrelevant retrieved documents by adding a lightweight evaluator that grades each retrieved "
72
- "document's relevance. When all retrieved documents are judged irrelevant, CRAG triggers a "
73
- "corrective web search to obtain better evidence before generation."},
74
- {"id":"doc_04","title":"RAGAs Evaluation Framework",
75
- "text":"RAGAs is an automated evaluation framework for retrieval-augmented generation systems. "
76
- "It provides metrics including faithfulness, which measures whether the generated answer "
77
- "is supported by the retrieved context, and answer relevance, which measures whether the "
78
- "answer addresses the original question."},
79
- {"id":"doc_05","title":"Hallucination in LLMs",
80
- "text":"Hallucination in large language models refers to the generation of fluent and confident "
81
- "statements that are not supported by evidence or are factually incorrect. RAG reduces "
82
- "hallucination by grounding generation in retrieved documents, but does not eliminate it "
83
- "entirely when the model ignores the retrieved context."},
84
- {"id":"doc_06","title":"Adaptive Retrieval in SELF-RAG",
85
- "text":"SELF-RAG uses an adaptive retrieval threshold delta to control how often retrieval is "
86
- "triggered. The model predicts a Retrieve token and if the probability of Retrieve=Yes "
87
- "exceeds the threshold delta, retrieval is triggered. The default threshold is 0.2."},
88
- {"id":"doc_07","title":"Segment-Level Beam Search",
89
- "text":"SELF-RAG uses segment-level beam search with beam width B=2 during inference. For each "
90
- "retrieved passage, the model generates a candidate scored using IsRel, IsSupport, and "
91
- "IsUse token probabilities combined with log-probability of the generated segment. "
92
- "Weights: 1.0 for IsRel, 1.0 for IsSupport, 0.5 for IsUse."},
93
  ]
94
 
95
  @dataclass
96
  class Passage:
97
  id: str; title: str; text: str
98
 
99
- @dataclass
100
- class Candidate:
101
- text: str; passage: Passage
102
- isrel: Optional[str] = None
103
- issup: Optional[str] = None
104
- isuse: Optional[str] = None
105
- score: float = 0.0
106
-
107
- # ── Retriever β€” CPU, loaded at startup ────────────────────────────────────────
108
  print("Loading retriever...")
109
- _retriever = SentenceTransformer("all-MiniLM-L6-v2", device="cpu")
110
- _passages = [Passage(**d) for d in DOCUMENTS]
111
- _embs = _retriever.encode(
112
- [f"{p.title} {p.text}" for p in _passages],
113
- convert_to_numpy=True, normalize_embeddings=True
114
- ).astype("float32")
115
- _index = faiss.IndexFlatIP(_embs.shape[1])
116
- _index.add(_embs)
117
- print(f"Retriever ready: {_index.ntotal} passages")
118
-
119
- def retrieve(query: str):
120
- q = _retriever.encode([query], convert_to_numpy=True,
121
- normalize_embeddings=True).astype("float32")
122
- _, idxs = _index.search(q, K_PASSAGES)
123
- return [_passages[i] for i in idxs[0] if i < len(_passages)]
124
 
125
- # ── Generator β€” loaded at module level on CUDA (ZeroGPU emulates outside GPU fn)
126
- # Per official ZeroGPU docs: place model on cuda at module level.
127
- # ZeroGPU emulation mode allows this even when no real GPU is present at startup.
128
- print(f"Loading {CHECKPOINT}...")
129
- _tokenizer = AutoTokenizer.from_pretrained(CHECKPOINT)
130
- _model = AutoModelForCausalLM.from_pretrained(
131
- CHECKPOINT,
132
- torch_dtype=torch.float16,
133
- low_cpu_mem_usage=True,
134
- )
135
- _model = AutoModelForCausalLM.from_pretrained(
136
- CHECKPOINT,
137
- torch_dtype=torch.float16,
138
- device_map="auto",
139
- )
140
- _model.eval()
141
- print("Generator ready.")
142
-
143
- # ── GPU-decorated inference ────────────────────────────────────────────────────
144
- @spaces.GPU
145
- def generate_candidates(query: str, passage_texts: list) -> list:
146
- """Runs inside real ZeroGPU GPU context."""
147
- results = []
148
- for pt in passage_texts:
149
- prompt = (f"### Instruction:\n{query}\n\n"
150
- f"### Input:\n<p>{pt}</p>\n\n"
151
- f"### Response:\n")
152
- enc = _tokenizer(prompt, return_tensors="pt",
153
- truncation=True, max_length=2048)
154
- enc = {k: v.to("cuda") for k, v in enc.items()}
155
- with torch.no_grad():
156
- out = _model.generate(
157
- **enc, max_new_tokens=MAX_NEW_TOKS, do_sample=False
158
- )
159
- new_ids = out[0, enc["input_ids"].shape[1]:]
160
- raw = _tokenizer.decode(new_ids, skip_special_tokens=True,
161
- clean_up_tokenization_spaces=True)
162
- results.append(raw)
163
- return results
164
 
165
- # ── Parse and score ────────────────────────────────────────────────────────────
166
- def parse(raw: str) -> dict:
167
- norm = raw.replace("_", " ")
168
- r = {"isrel":None,"issup":None,"isuse":None,"answer":"","raw":raw}
169
- for t in [RT.RELEVANT, RT.IRRELEVANT]:
170
- if t in raw: r["isrel"]=t; break
171
- for t in [RT.FULLY, RT.PARTIALLY, RT.NO_SUPPORT]:
172
- if t in raw: r["issup"]=t; break
173
- for t in [RT.U5,RT.U4,RT.U3,RT.U2,RT.U1]:
174
- if t in raw: r["isuse"]=t; break
175
- ans = raw
176
- for t in RT.ALL + ["<paragraph>"]:
177
- if t in ans: ans = ans[:ans.index(t)]
178
- ans = re.sub(r"<[^>]+>","",ans)
179
- ans = re.sub(r"#+\s*\w+.*?:","",ans)
180
- r["answer"] = " ".join(ans.split()).strip()
181
- for ph in ABSTAIN_PHRASES:
182
- if ph.lower() in norm.lower():
183
- r["issup"] = RT.NO_SUPPORT; break
184
- return r
185
 
186
- def calc_score(p: dict) -> float:
187
- s = 0.0
188
- if p["isrel"] == RT.RELEVANT: s += W_REL * 1.0
189
- if p["issup"] == RT.FULLY: s += W_SUP * 1.0
190
- elif p["issup"] == RT.PARTIALLY: s += W_SUP * 0.5
191
- if p["isuse"]: s += W_USE * RT.USE_SCORES.get(p["isuse"], 0.0)
192
- return s
193
 
194
- # ── Main inference ─────────────────────────────────────────────────────────────
195
- def run(query: str):
196
- retrieved = retrieve(query)
197
- raws = generate_candidates(query, [p.text for p in retrieved])
198
- candidates = []
199
- for passage, raw in zip(retrieved, raws):
200
- pr = parse(raw)
201
- if not pr["answer"]: continue
202
- candidates.append(Candidate(
203
- text=pr["answer"], passage=passage,
204
- isrel=pr["isrel"], issup=pr["issup"],
205
- isuse=pr["isuse"], score=calc_score(pr),
206
- ))
207
- if not candidates:
208
- return None, retrieved, True
209
- candidates.sort(key=lambda c: c.score, reverse=True)
210
- best = candidates[0]
211
- abstained = (best.issup == RT.NO_SUPPORT and best.score <= 0.5)
212
- return best, retrieved, abstained
213
 
214
- # ── HTML rendering ─────────────────────────────────────────────────────────────
215
- TC = {
216
- RT.RETRIEVAL:("#EEEDFE","#3C3489"), RT.RELEVANT:("#EAF3DE","#27500A"),
217
- RT.IRRELEVANT:("#FCEBEB","#791F1F"), RT.FULLY:("#EAF3DE","#27500A"),
218
- RT.PARTIALLY:("#FAEEDA","#633806"), RT.NO_SUPPORT:("#FCEBEB","#791F1F"),
219
- RT.U5:("#EAF3DE","#27500A"), RT.U4:("#EAF3DE","#27500A"),
220
- RT.U3:("#FAEEDA","#633806"), RT.U2:("#FCEBEB","#791F1F"),
221
- RT.U1:("#FCEBEB","#791F1F"),
222
- }
223
 
224
- def badge(tok):
225
- if not tok: return ""
226
- bg,fg = TC.get(tok,("#F1EFE8","#444441"))
227
- return (f"<span style='background:{bg};color:{fg};font-size:11px;"
228
- f"font-weight:600;padding:3px 8px;border-radius:4px;"
229
- f"margin:2px;display:inline-block'>{tok}</span>")
230
 
231
- def render(best, retrieved, abstained):
232
- if abstained or best is None:
233
- return f"""
234
- <div style='font-family:sans-serif;padding:16px;border-radius:10px;
235
- background:#FCEBEB;border:1.5px solid #A32D2D;margin-top:8px'>
236
- <div style='font-size:15px;font-weight:700;color:#A32D2D;margin-bottom:8px'>
237
- &#9888; ABSTAINED β€” Insufficient Evidence
238
- </div>
239
- <div style='font-size:13px;color:#791F1F;line-height:1.6'>
240
- The system could not find sufficient evidence in the document corpus
241
- to answer this question. Rather than hallucinating an answer, the system abstains.
242
- </div>
243
- <div style='margin-top:10px;font-size:11px;color:#aaa'>
244
- Passages retrieved: {', '.join(f'[{p.id}] {p.title}' for p in retrieved)}
245
- </div>
246
- </div>"""
247
- return f"""
248
- <div style='font-family:sans-serif;padding:16px;border-radius:10px;
249
- background:#F7F7F9;border:0.5px solid #D3D1C7;margin-top:8px'>
250
- <div style='font-size:11px;color:#888;margin-bottom:4px'>Best passage retrieved</div>
251
- <div style='background:#E6F1FB;padding:8px 12px;border-radius:6px;
252
- font-size:11px;color:#185FA5;margin-bottom:12px'>
253
- [{best.passage.id}] {best.passage.title}
254
- </div>
255
- <div style='margin-bottom:10px'>
256
- {badge(RT.RETRIEVAL)}{badge(best.isrel)}{badge(best.issup)}{badge(best.isuse)}
257
- <span style='font-size:10px;color:#bbb;margin-left:6px'>score={best.score:.2f}</span>
258
- </div>
259
- <div style='background:#fff;padding:14px;border-radius:8px;
260
- border:0.5px solid #D3D1C7;font-size:14px;color:#1E2761;
261
- line-height:1.8;margin-bottom:10px'>
262
- {best.text}
263
- </div>
264
- <div style='font-size:11px;color:#bbb'>
265
- All passages considered: {', '.join(f'[{p.id}]' for p in retrieved)}
266
- </div>
267
- </div>"""
268
 
269
- # ── Gradio UI ──────────────────────────────────────────────────────────────────
270
- def answer(question):
271
- if not question.strip():
272
- return "<div style='color:#888;padding:12px'>Please enter a question.</div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  try:
274
- best, retrieved, abstained = run(question)
275
- return render(best, retrieved, abstained)
 
 
276
  except Exception as e:
277
- return f"<div style='color:red;padding:12px'>Error: {str(e)}</div>"
278
-
279
- with gr.Blocks(title="SELF-RAG Demo") as demo:
280
- gr.Markdown("""
281
- # SELF-RAG Phase 1 β€” Live Demo
282
- **Agentic Self-Reflective Retrieval-Augmented Generation**
283
- Aravind Kashyap | PES2PGE24DS068 | Guide: Shankar DK | PES University
284
-
285
- > The system retrieves passages, generates an answer, and critiques its output
286
- > using reflection tokens. If no passage supports the answer, the system **abstains**.
287
-
288
- ⏳ *First query may take ~2 min for model warm-up.*
289
- """)
290
-
291
- gr.Markdown("""
292
- ### Document Corpus (8 documents)
293
- | ID | Topic |
294
- |---|---|
295
- | doc_00 | RAG Overview |
296
- | doc_01 | Vanilla RAG Limitations |
297
- | doc_02 | SELF-RAG Framework |
298
- | doc_03 | CRAG β€” Corrective RAG |
299
- | doc_04 | RAGAs Evaluation |
300
- | doc_05 | Hallucination in LLMs |
301
- | doc_06 | Adaptive Retrieval in SELF-RAG |
302
- | doc_07 | Segment-Level Beam Search |
303
-
304
- **Ask anything outside these topics** (e.g. geography, sports, cooking) to trigger abstention.
305
- """)
306
-
307
- with gr.Row():
308
- with gr.Column(scale=4):
309
- qbox = gr.Textbox(
310
- label="Your Question",
311
- placeholder="e.g. What is SELF-RAG? | What is hallucination? | What is the capital of France?",
312
- lines=2,
313
- )
314
- with gr.Column(scale=1, min_width=120):
315
- btn = gr.Button("Ask β†’", variant="primary", size="lg")
316
-
317
- gr.Examples(
318
- examples=[
319
- ["What is SELF-RAG and how does it use reflection tokens?"],
320
- ["What is the main limitation of Vanilla RAG?"],
321
- ["How does CRAG handle irrelevant retrieved documents?"],
322
- ["What is hallucination in language models?"],
323
- ["What is the capital of France?"],
324
- ["What is the deployment date of the AuthService in the Pegasus project?"],
325
- ],
326
- inputs=qbox,
327
- label="Try these β€” last two should abstain",
328
- )
329
-
330
- out = gr.HTML(label="Response")
331
- btn.click(fn=answer, inputs=qbox, outputs=out)
332
- qbox.submit(fn=answer, inputs=qbox, outputs=out)
333
-
334
- gr.Markdown("""
335
- ---
336
- ### How it works
337
- | Step | What happens |
338
- |---|---|
339
- | 1. Retrieve | Top-5 passages from FAISS using all-MiniLM-L6-v2 |
340
- | 2. Generate | selfrag/selfrag_llama2_7b generates response for each passage |
341
- | 3. Parse | Reflection tokens extracted from generated text |
342
- | 4. Score | `score = w_relΓ—s(IsRel) + w_supΓ—s(IsSupport) + w_useΓ—s(IsUse)` |
343
- | 5. Abstain | If best has `[No support / Contradictory]` β†’ "Insufficient evidence" |
344
-
345
- *Asai et al., ICLR 2024 β€” SELF-RAG: Learning to Retrieve, Generate and Critique*
346
- """)
347
-
348
- demo.queue(default_concurrency_limit=1).launch()
 
3
  import re
4
  import gradio as gr
5
  import torch
 
6
  import faiss
7
  from dataclasses import dataclass
8
  from typing import Optional
 
10
  from sentence_transformers import SentenceTransformer
11
  from huggingface_hub import login
12
 
13
+ # ── Auth ─────────────────────────────────────────────
14
  _token = os.environ.get("HF_TOKEN")
15
  if _token:
16
  login(token=_token)
17
 
18
+ # ── Config ───────────────────────────────────────────
19
  CHECKPOINT = "selfrag/selfrag_llama2_7b"
20
  K_PASSAGES = 5
21
+ MAX_NEW_TOKS = 120 # reduced for stability
22
  W_REL = 1.0; W_SUP = 1.0; W_USE = 0.5
23
 
24
+ # ── Reflection Tokens ─────────────────────────────────
25
  class RT:
26
  RETRIEVAL = "[Retrieval]"
27
  NO_RETRIEVAL = "[No Retrieval]"
 
41
  ABSTAIN_PHRASES = [
42
  "not specified in the input","not provided in the input",
43
  "not mentioned in the input","no information","cannot be determined",
 
 
 
44
  ]
45
 
46
+ # ── Documents ─────────────────────────────────────────
47
  DOCUMENTS = [
48
+ {"id":"doc_00","title":"RAG Overview","text":"Retrieval-Augmented Generation improves factual correctness."},
49
+ {"id":"doc_01","title":"Vanilla RAG Limitations","text":"Vanilla RAG retrieves fixed documents even when unnecessary."},
50
+ {"id":"doc_02","title":"SELF-RAG Framework","text":"SELF-RAG uses reflection tokens to improve responses."},
51
+ {"id":"doc_03","title":"CRAG","text":"CRAG adds evaluator for retrieval correction."},
52
+ {"id":"doc_04","title":"RAGAs","text":"Evaluation framework for RAG systems."},
53
+ {"id":"doc_05","title":"Hallucination","text":"LLMs may generate unsupported content."},
54
+ {"id":"doc_06","title":"Adaptive Retrieval","text":"SELF-RAG triggers retrieval dynamically."},
55
+ {"id":"doc_07","title":"Beam Search","text":"Uses scoring with reflection tokens."},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  ]
57
 
58
  @dataclass
59
  class Passage:
60
  id: str; title: str; text: str
61
 
62
+ # ── Retriever ─────────────────────────────────────────
 
 
 
 
 
 
 
 
63
  print("Loading retriever...")
64
+ retriever = SentenceTransformer("all-MiniLM-L6-v2", device="cpu")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
+ passages = [Passage(**d) for d in DOCUMENTS]
67
+ embs = retriever.encode(
68
+ [f"{p.title} {p.text}" for p in passages],
69
+ convert_to_numpy=True,
70
+ normalize_embeddings=True
71
+ ).astype("float32")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
+ index = faiss.IndexFlatIP(embs.shape[1])
74
+ index.add(embs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
+ print("Retriever ready")
 
 
 
 
 
 
77
 
78
+ def retrieve(query):
79
+ q = retriever.encode([query], convert_to_numpy=True,
80
+ normalize_embeddings=True).astype("float32")
81
+ _, idxs = index.search(q, K_PASSAGES)
82
+ return [passages[i] for i in idxs[0]]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
+ # ── MODEL LOADING (FIXED) ─────────────────────────────
85
+ print("Loading model...")
 
 
 
 
 
 
 
86
 
87
+ tokenizer = AutoTokenizer.from_pretrained(CHECKPOINT)
 
 
 
 
 
88
 
89
+ model = AutoModelForCausalLM.from_pretrained(
90
+ CHECKPOINT,
91
+ torch_dtype=torch.float16,
92
+ device_map="auto", # βœ… KEY FIX
93
+ low_cpu_mem_usage=True # βœ… KEY FIX
94
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
+ model.eval()
97
+
98
+ print("Model loaded successfully")
99
+
100
+ # ── GENERATION ───────────────────────────────────────
101
+ def generate_answer(query, context):
102
+ prompt = f"Question: {query}\nContext: {context}\nAnswer:"
103
+
104
+ inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=1024)
105
+
106
+ # move to correct device
107
+ inputs = {k: v.to(model.device) for k, v in inputs.items()}
108
+
109
+ with torch.no_grad():
110
+ output = model.generate(
111
+ **inputs,
112
+ max_new_tokens=MAX_NEW_TOKS,
113
+ do_sample=False
114
+ )
115
+
116
+ return tokenizer.decode(output[0], skip_special_tokens=True)
117
+
118
+ # ── MAIN FUNCTION ─────────────────────────────────────
119
+ def answer(query):
120
  try:
121
+ docs = retrieve(query)
122
+ context = " ".join([d.text for d in docs])
123
+ result = generate_answer(query, context)
124
+ return result
125
  except Exception as e:
126
+ return f"Error: {str(e)}"
127
+
128
+ # ── UI ───────────────────────────────────────────────
129
+ with gr.Blocks() as demo:
130
+ gr.Markdown("# SELF-RAG Demo")
131
+
132
+ q = gr.Textbox(label="Ask a question")
133
+ out = gr.Textbox(label="Answer")
134
+
135
+ btn = gr.Button("Ask")
136
+ btn.click(answer, inputs=q, outputs=out)
137
+
138
+ demo.launch()