hymarog1 commited on
Commit
f5f246c
·
verified ·
1 Parent(s): 0925ca5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +226 -234
app.py CHANGED
@@ -48,7 +48,7 @@ if "last_prompt_hash" not in st.session_state:
48
  st.session_state.last_prompt_hash = None
49
 
50
 
51
- st.title("📄 Legal Document Summarizer (Alt Model w/o token doc Aug)")
52
 
53
  USER_AVATAR = "👤"
54
  BOT_AVATAR = "🤖"
@@ -187,53 +187,6 @@ def load_led():
187
  tokenizer_led, model_led = load_led()
188
 
189
 
190
- from transformers import pipeline
191
-
192
- @st.cache_resource
193
- def load_led_summarizer():
194
- # Use “allenai/led-base-16384” (or “led-large-16384”)
195
- return pipeline(
196
- "summarization",
197
- model="allenai/led-base-16384",
198
- tokenizer="allenai/led-base-16384",
199
- device=0 if torch.cuda.is_available() else -1
200
- )
201
-
202
- led_summarizer = load_led_summarizer()
203
-
204
- from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, pipeline
205
-
206
- @st.cache_resource
207
- def load_paraphraser():
208
-
209
- tok = AutoTokenizer.from_pretrained("google/flan-t5-small")
210
- model = AutoModelForSeq2SeqLM.from_pretrained("google/flan-t5-small")
211
- return pipeline(
212
- "text2text-generation",
213
- model=model,
214
- tokenizer=tok,
215
- device=0 if torch.cuda.is_available() else -1,
216
- max_length=256,
217
- num_beams=4,
218
- do_sample=False
219
- )
220
-
221
-
222
- paraphraser = load_paraphraser()
223
-
224
-
225
- def humanize(text):
226
- out = paraphraser(f"paraphrase: {text}",
227
- max_length=256,
228
- num_beams=4,
229
- do_sample=False)[0]["generated_text"]
230
- return out
231
-
232
- # then at the end of rag_query_response:
233
-
234
-
235
-
236
-
237
  def legalbert_extractive_summary(text, top_ratio=0.2):
238
  sentences = sent_tokenize(text)
239
  top_k = max(3, int(len(sentences) * top_ratio))
@@ -329,18 +282,6 @@ def hybrid_summary_hierarchical(text, top_ratio=0.8):
329
  return structured_summary
330
 
331
 
332
- from sentence_transformers import SentenceTransformer
333
-
334
- @st.cache_resource
335
- def load_embedder():
336
- return SentenceTransformer("all-MiniLM-L6-v2")
337
-
338
- embedder = load_embedder()
339
-
340
- import numpy as np
341
-
342
-
343
-
344
  def chunk_text_custom(text, n=1000, overlap=200):
345
  chunks = []
346
  for i in range(0, len(text), n - overlap):
@@ -357,118 +298,151 @@ def get_embedding(text, model="BAAI/bge-en-icl"):
357
  resp = client.embeddings.create(model=model, input=text)
358
  return np.array(resp.data[0].embedding)
359
 
360
- def create_embeddings(text_chunks, model="BAAI/bge-en-icl"):
361
- """
362
- Batch the get_embedding call over your chunks.
363
- Returns a list of numpy arrays.
364
- """
365
- return [get_embedding(chunk, model=model) for chunk in text_chunks]
366
-
367
 
368
 
369
- def generate_questions(text_chunk, num_questions=5):
370
- """
371
- Use LED to generate a small set of probing questions
372
- about this chunk that the final answer should address.
373
- """
374
- prompt = (
375
- "You are a question-generation expert. "
376
- "From the text below, generate "
377
- f"{num_questions} concise questions:\n\n{text_chunk}"
378
- )
379
- out = led_summarizer(
380
- prompt,
381
- max_length=128,
382
- min_length=32,
383
- num_beams=4,
384
- do_sample=False
385
- )[0]["summary_text"]
386
- # assume each question on its own line
387
- questions = [q.strip() for q in out.split("\n") if q.strip()]
388
- return questions[:num_questions]
389
-
390
-
391
- def process_document(document_text):
392
  """
393
- 1) chunk the document
394
- 2) embed each chunk with your SentenceTransformer
395
- returns chunks, embeddings
396
  """
397
- chunks = chunk_text_custom(document_text, n=800, overlap=200)
398
- embeddings = embedder.encode(chunks, convert_to_tensor=False)
399
- return chunks, embeddings
 
 
 
400
 
401
 
402
- def semantic_search(query, chunks, chunk_embeddings, k=5):
403
- """
404
- Score each chunk by cosine similarity to the query embed
405
- and return the top-k chunks (in descending order).
406
- """
407
- q_emb = embedder.encode([query], convert_to_tensor=False)[0]
408
- scores = [
409
- float(np.dot(q_emb, emb) / (np.linalg.norm(q_emb) * np.linalg.norm(emb)))
410
- for emb in chunk_embeddings
411
- ]
412
- top_idxs = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:k]
413
- return [chunks[i] for i in top_idxs]
414
 
415
 
416
- def prepare_context(questions, chunks, chunk_embeddings, k_per_question=2):
417
- """
418
- For each generated question, pick its top-k supporting chunks,
419
- then dedupe & concatenate into one context string.
420
- """
421
- selected = []
422
- for q in questions:
423
- best = semantic_search(q, chunks, chunk_embeddings, k=k_per_question)
424
- selected.extend(best)
 
 
425
 
426
- # dedupe while preserving order
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  seen = set()
428
- context = []
429
- for c in selected:
430
- if c not in seen:
431
- seen.add(c)
432
- context.append(c)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
 
434
- return "\n\n".join(f"• {c}" for c in context)
435
 
436
- def rag_query_response(prompt, document_text):
437
  """
438
- Document-Augmentation RAG:
439
- 1. generate probing sub-questions about the doc
440
- 2. process the doc (chunk + embed)
441
- 3. build minimal context via those questions
442
- 4. feed context + user prompt into LED
443
- 5. paraphrase (humanize)
444
- """
445
- # 1) Probing questions
446
- questions = generate_questions(document_text, num_questions=5)
447
-
448
- # 2) Chunk & embed the document
449
- chunks, chunk_embs = process_document(document_text)
450
-
451
- # 3) Assemble the distilled context
452
- context = prepare_context(questions, chunks, chunk_embs, k_per_question=2)
453
-
454
- # 4) Compose the LED input
455
- led_input = (
456
- "You are a knowledgeable legal assistant. "
457
- "Answer the user’s question **using ONLY** the context below, "
458
- "and speak in a friendly, conversational tone.\n\n"
459
- f"Context:\n{context}\n\n"
460
- f"Question: {prompt}\n\nAnswer:"
461
  )
 
 
462
 
463
- raw = led_summarizer(
464
- led_input,
465
- max_length=512,
466
- min_length=64,
467
- do_sample=False
468
- )[0]["summary_text"]
469
 
470
- # 5) Humanize
471
- return humanize(raw)
472
 
473
  #######################################################################################################################
474
 
@@ -548,13 +522,6 @@ def prepare_text_for_embedding(summary_dict):
548
  return "\n\n".join(combined_chunks)
549
 
550
 
551
- ###################################################################################################################
552
-
553
- # Store cleaned text and FAISS index only when document is processed
554
-
555
- # Embedding for chunking
556
-
557
-
558
  ##############################################################################################################
559
 
560
  user_role = st.sidebar.selectbox(
@@ -583,8 +550,6 @@ def role_based_filter(section, summary, role):
583
 
584
 
585
 
586
-
587
-
588
  #########################################################################################################################
589
 
590
 
@@ -593,75 +558,110 @@ if uploaded_file:
593
  if file_hash != st.session_state.last_uploaded_hash or reprocess_btn:
594
  st.session_state.processed = False
595
 
596
- # if is_new_file or reprocess_btn:
597
- # st.session_state.processed = False
598
-
599
  if not st.session_state.processed:
600
  start_time = time.time()
601
- raw_text = extract_text(uploaded_file)
 
 
602
  summary_dict = hybrid_summary_hierarchical(raw_text)
603
- # timeline_data = extract_timeline(clean_text(raw_text))
604
  embedding_text = prepare_text_for_embedding(summary_dict)
605
 
606
- # Generate and display RAG-based summary
 
 
 
 
 
 
607
 
 
608
  st.session_state.document_context = embedding_text
609
-
610
- role_specific_prompt = f"As a {user_role}, summarize the legal document focusing on the most relevant aspects such as facts, arguments, and judgments tailored for your role. Include key legal reasoning and timeline of events where necessary."
611
- rag_summary = rag_query_response(role_specific_prompt, embedding_text)
612
-
 
 
 
 
 
 
 
 
 
613
 
614
- st.session_state.messages.append({"role": "user", "content": f"📤 Uploaded **{uploaded_file.name}**"})
615
- st.session_state.messages.append({"role": "assistant", "content": rag_summary})
 
 
 
616
 
 
 
 
 
 
 
 
 
617
  with st.chat_message("assistant", avatar=BOT_AVATAR):
618
  display_with_typing_effect(rag_summary)
619
 
620
  processing_time = round((time.time() - start_time) / 60, 2)
621
  st.info(f"⏱️ Response generated in **{processing_time} minutes**.")
622
 
623
-
624
- st.session_state.generated_summary = rag_summary #for Evalution
625
  st.session_state.last_uploaded_hash = file_hash
626
  st.session_state.processed = True
627
  st.session_state.last_prompt_hash = None
628
  save_chat_history(st.session_state.messages)
629
 
630
 
 
631
  if prompt:
632
  words = prompt.split()
633
- word_count = len(words)
634
- prompt_hash = hashlib.md5(prompt.encode("utf-8")).hexdigest()
635
 
636
- # 1) LONG prompts – echo first, then summarize
637
  if word_count > 30 and prompt_hash != st.session_state.last_prompt_hash:
638
- # mark new prompt
639
  st.session_state.last_prompt_hash = prompt_hash
640
 
641
- # raw_text is just the prompt text
642
  raw_text = prompt
643
-
644
  st.session_state.messages.append({
645
  "role": "user",
646
- "content": f"📥 **Pasted Document Text:**\n\n{limit_text(raw_text, word_limit=500)}"
647
  })
648
  with st.chat_message("user", avatar=USER_AVATAR):
649
- st.markdown(limit_text(raw_text, word_limit=500))
650
 
651
  start_time = time.time()
652
- summary_dict = hybrid_summary_hierarchical(raw_text)
653
- emb_text = prepare_text_for_embedding(summary_dict)
654
-
655
  st.session_state.document_context = emb_text
656
  st.session_state.processed = True
657
 
658
- role_prompt = (
659
- f"As a {user_role}, summarize the document focusing on facts, "
660
- "arguments, judgments, plus timeline of events."
661
- )
662
- initial_summary = rag_query_response(role_prompt, emb_text)
663
 
664
- # 3️⃣ Append & display the assistant’s summary with typing effect
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
665
  st.session_state.messages.append({
666
  "role": "assistant",
667
  "content": initial_summary
@@ -672,29 +672,40 @@ if prompt:
672
  st.info(f"⏱️ Summary generated in {round((time.time()-start_time)/60,2)} minutes")
673
  save_chat_history(st.session_state.messages)
674
 
675
- # 2) SHORT prompts: normal RAG against last context
 
676
  elif word_count <= 30 and st.session_state.processed:
677
-
678
- role_query = f"As a {user_role}, {prompt}"
679
- answer = rag_query_response(role_query, st.session_state.document_context)
680
- answer = rag_query_response(prompt, st.session_state.document_context)
681
- st.session_state.messages.append({"role": "user", "content": prompt})
682
- st.session_state.messages.append({"role": "assistant","content": answer})
 
 
 
 
 
 
 
 
 
683
  with st.chat_message("assistant", avatar=BOT_AVATAR):
684
  display_with_typing_effect(answer)
685
  save_chat_history(st.session_state.messages)
686
 
687
- # 3) Ingest prompt to start
 
688
  else:
689
  with st.chat_message("assistant", avatar=BOT_AVATAR):
690
  st.markdown("❗ Paste at least 30 words of your document to ingest it first.")
691
 
692
 
693
- ################################Evaluation###########################
 
694
  # 📚 Imports
695
  import evaluate
696
  from nltk.translate.bleu_score import sentence_bleu, SmoothingFunction
697
- import streamlit as st
698
 
699
  # �� Load Evaluators Once
700
  @st.cache_resource
@@ -713,40 +724,21 @@ def evaluate_summary(generated_summary, ground_truth_summary):
713
  return rouge_result, bert_result
714
 
715
  def compute_bleu(prediction, ground_truth):
716
- """Compute BLEU score for summaries."""
717
  reference = [ground_truth.strip().split()]
718
  candidate = prediction.strip().split()
719
  smoothie = SmoothingFunction().method4
720
  return sentence_bleu(reference, candidate, smoothing_function=smoothie)
721
 
722
- # 📥 Upload and Evaluate
723
- ground_truth_summary_file = st.file_uploader("📄 Upload Ground Truth Summary (.txt)", type=["txt"])
724
-
725
- if ground_truth_summary_file:
726
- ground_truth_summary = ground_truth_summary_file.read().decode("utf-8").strip()
727
-
728
- if "generated_summary" in st.session_state and st.session_state.generated_summary:
729
- prediction = st.session_state.generated_summary
730
-
731
- # Evaluate ROUGE and BERTScore
732
- rouge_result, bert_result = evaluate_summary(prediction, ground_truth_summary)
733
-
734
- # Display ROUGE and BERTScore
735
- st.subheader("📊 Evaluation Results")
736
- st.write("🔹 ROUGE Scores:")
737
- st.json(rouge_result)
738
- st.write("🔹 BERTScore:")
739
- st.json(bert_result)
740
-
741
- # Compute and Display BLEU Score
742
- bleu = compute_bleu(prediction, ground_truth_summary)
743
- st.subheader("🔵 BLEU Score")
744
- st.write(f"BLEU Score: {bleu:.4f}")
745
-
746
- else:
747
- st.warning("⚠️ Please generate a summary first by uploading a document.")
748
 
749
- ######################################################################################################################
750
 
751
 
752
  # Run this along with streamlit run app.py to evaluate the model's performance on a test set
 
48
  st.session_state.last_prompt_hash = None
49
 
50
 
51
+ st.title("📄 Legal Document Summarizer (Document Augmentation RAG)")
52
 
53
  USER_AVATAR = "👤"
54
  BOT_AVATAR = "🤖"
 
187
  tokenizer_led, model_led = load_led()
188
 
189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  def legalbert_extractive_summary(text, top_ratio=0.2):
191
  sentences = sent_tokenize(text)
192
  top_k = max(3, int(len(sentences) * top_ratio))
 
282
  return structured_summary
283
 
284
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  def chunk_text_custom(text, n=1000, overlap=200):
286
  chunks = []
287
  for i in range(0, len(text), n - overlap):
 
298
  resp = client.embeddings.create(model=model, input=text)
299
  return np.array(resp.data[0].embedding)
300
 
 
 
 
 
 
 
 
301
 
302
 
303
+ def semantic_search(query, text_chunks, chunk_embeddings, k=5):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  """
305
+ Compute cosine similarity between the query embedding and each chunk embedding,
306
+ then pick the top-k chunks.
 
307
  """
308
+ q_emb = get_embedding(query)
309
+ # simple cosine:
310
+ def cosine(a, b): return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
311
+ scores = [cosine(q_emb, emb) for emb in chunk_embeddings]
312
+ top_idxs = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:k]
313
+ return [text_chunks[i] for i in top_idxs]
314
 
315
 
316
+ def generate_response(system_prompt, user_message, model="meta-llama/Llama-3.2-3B-Instruct"):
317
+ return client.chat.completions.create(
318
+ model=model,
319
+ temperature=0,
320
+ messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": user_message}]
321
+ ).choices[0].message.content
 
 
 
 
 
 
322
 
323
 
324
+ def generate_questions(text_chunk, num_questions=5,
325
+ model="meta-llama/Llama-3.2-3B-Instruct"):
326
+ system_prompt = (
327
+ "You are an expert at generating relevant questions from text. "
328
+ "Create concise questions that can be answered using only the provided text."
329
+ )
330
+ user_prompt = f"""
331
+ Based on the following text, generate {num_questions} different questions
332
+ that can be answered using only this text:
333
+
334
+ {text_chunk}
335
 
336
+ Format your response as a numbered list of questions only.
337
+ """
338
+ resp = client.chat.completions.create(
339
+ model=model,
340
+ temperature=0.7,
341
+ messages=[
342
+ {"role":"system","content":system_prompt},
343
+ {"role":"user","content":user_prompt}
344
+ ]
345
+ )
346
+ raw = resp.choices[0].message.content.strip()
347
+ questions = []
348
+ for line in raw.split("\n"):
349
+ q = re.sub(r"^\d+\.\s*", "", line).strip()
350
+ if q.endswith("?"):
351
+ questions.append(q)
352
+ return questions
353
+
354
+ # 2) EMBEDDINGS
355
+ def create_embeddings(text, model="BAAI/bge-en-icl"):
356
+ resp = client.embeddings.create(model=model, input=text)
357
+ return resp.data[0].embedding
358
+
359
+ def cosine_similarity(a,b):
360
+ return float(np.dot(a,b)/(np.linalg.norm(a)*np.linalg.norm(b)))
361
+
362
+ # 3) VECTOR STORE
363
+ class SimpleVectorStore:
364
+ def __init__(self):
365
+ self.items = [] # each item is dict {text, embedding, metadata}
366
+ def add_item(self, text, embedding, metadata):
367
+ self.items.append(dict(text=text, embedding=embedding, metadata=metadata))
368
+ def search(self, query, k=5):
369
+ q_emb = create_embeddings(query)
370
+ scores = [(i, cosine_similarity(q_emb, item["embedding"]))
371
+ for i,item in enumerate(self.items)]
372
+ scores.sort(key=lambda x:x[1], reverse=True)
373
+ return [self.items[i] for i,_ in scores[:k]]
374
+
375
+ # 4) DOCUMENT PROCESSOR
376
+ def process_document(raw_text,
377
+ chunk_size=1000,
378
+ chunk_overlap=200,
379
+ questions_per_chunk=5):
380
+ # chunk the text
381
+ chunks = []
382
+ for i in range(0, len(raw_text), chunk_size - chunk_overlap):
383
+ chunks.append(raw_text[i:i+chunk_size])
384
+ store = SimpleVectorStore()
385
+ for idx,chunk in enumerate(chunks):
386
+ # chunk embedding
387
+ emb = create_embeddings(chunk)
388
+ store.add_item(chunk, emb, {"type":"chunk","index":idx})
389
+ # generate Qs + their embeddings
390
+ qs = generate_questions(chunk, num_questions=questions_per_chunk)
391
+ for q in qs:
392
+ q_emb = create_embeddings(q)
393
+ store.add_item(q, q_emb, {
394
+ "type":"question",
395
+ "chunk_index":idx,
396
+ "original_chunk": chunk
397
+ })
398
+ return chunks, store
399
+
400
+ # 5) CONTEXT BUILDER
401
+ def prepare_context(results):
402
  seen = set()
403
+ ctx = []
404
+ # first direct chunks
405
+ for r in results:
406
+ m = r["metadata"]
407
+ if m["type"]=="chunk" and m["index"] not in seen:
408
+ seen.add(m["index"])
409
+ ctx.append(f"Chunk {m['index']}:\n{r['text']}")
410
+ # then referenced by questions
411
+ for r in results:
412
+ m = r["metadata"]
413
+ if m["type"]=="question":
414
+ ci = m["chunk_index"]
415
+ if ci not in seen:
416
+ seen.add(ci)
417
+ ctx.append(f"Chunk {ci} (via Q “{r['text']}”):\n{m['original_chunk']}")
418
+ return "\n\n".join(ctx)
419
+
420
+ # 6) ANSWER GENERATOR (overrides your old generate_response)
421
+ def generate_response_from_context(query, context,
422
+ model="meta-llama/Llama-3.2-3B-Instruct"):
423
+ sp = (
424
+ "You are an AI assistant that strictly answers based on the given context. "
425
+ "If the answer cannot be derived directly from the provided context, "
426
+ "respond with: 'I do not have enough information to answer that.'"
427
+ )
428
+ up = f"""
429
+ Context:
430
+ {context}
431
 
432
+ Question: {query}
433
 
434
+ Please answer the question based only on the context above.
435
  """
436
+ resp = client.chat.completions.create(
437
+ model=model,
438
+ temperature=0,
439
+ messages=[{"role":"system","content":sp},
440
+ {"role":"user","content":up}]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  )
442
+ return resp.choices[0].message.content
443
+
444
 
 
 
 
 
 
 
445
 
 
 
446
 
447
  #######################################################################################################################
448
 
 
522
  return "\n\n".join(combined_chunks)
523
 
524
 
 
 
 
 
 
 
 
525
  ##############################################################################################################
526
 
527
  user_role = st.sidebar.selectbox(
 
550
 
551
 
552
 
 
 
553
  #########################################################################################################################
554
 
555
 
 
558
  if file_hash != st.session_state.last_uploaded_hash or reprocess_btn:
559
  st.session_state.processed = False
560
 
 
 
 
561
  if not st.session_state.processed:
562
  start_time = time.time()
563
+
564
+ # 1) extract & summarize as before
565
+ raw_text = extract_text(uploaded_file)
566
  summary_dict = hybrid_summary_hierarchical(raw_text)
 
567
  embedding_text = prepare_text_for_embedding(summary_dict)
568
 
569
+ # ─── NEW: document‐augmentation ingestion ───
570
+ chunks, store = process_document(raw_text,
571
+ chunk_size=1000,
572
+ chunk_overlap=200,
573
+ questions_per_chunk=5)
574
+ st.session_state.vector_store = store
575
+ # ────────────────────────────────────────────
576
 
577
+ # 2) generate your “role‐specific prompt” as before
578
  st.session_state.document_context = embedding_text
579
+
580
+ if user_role == "General":
581
+ role_specific_prompt = (
582
+ "Summarize the legal document focusing on the most relevant aspects "
583
+ "such as facts, arguments, and judgments. Include key legal reasoning "
584
+ "and a timeline of events where necessary."
585
+ )
586
+ else:
587
+ role_specific_prompt = (
588
+ f"As a {user_role}, summarize the legal document focusing on "
589
+ "the most relevant aspects such as facts, arguments, and judgments "
590
+ "tailored for your role. Include key legal reasoning and timeline of events."
591
+ )
592
 
593
+ # ─── REPLACE rag_query_response with doc‐augmentation RAG ───
594
+ results = store.search(role_specific_prompt, k=5)
595
+ context = prepare_context(results)
596
+ rag_summary = generate_response_from_context(role_specific_prompt, context)
597
+ #
598
 
599
+ st.session_state.messages.append({
600
+ "role": "user",
601
+ "content": f"📤 Uploaded **{uploaded_file.name}**"
602
+ })
603
+ st.session_state.messages.append({
604
+ "role": "assistant",
605
+ "content": rag_summary
606
+ })
607
  with st.chat_message("assistant", avatar=BOT_AVATAR):
608
  display_with_typing_effect(rag_summary)
609
 
610
  processing_time = round((time.time() - start_time) / 60, 2)
611
  st.info(f"⏱️ Response generated in **{processing_time} minutes**.")
612
 
613
+ st.session_state.generated_summary = rag_summary
 
614
  st.session_state.last_uploaded_hash = file_hash
615
  st.session_state.processed = True
616
  st.session_state.last_prompt_hash = None
617
  save_chat_history(st.session_state.messages)
618
 
619
 
620
+
621
  if prompt:
622
  words = prompt.split()
623
+ word_count = len(words)
624
+ prompt_hash = hashlib.md5(prompt.encode("utf-8")).hexdigest()
625
 
626
+ # 1) LONG prompts – echo & ingest like a “paste‐in” document
627
  if word_count > 30 and prompt_hash != st.session_state.last_prompt_hash:
 
628
  st.session_state.last_prompt_hash = prompt_hash
629
 
 
630
  raw_text = prompt
 
631
  st.session_state.messages.append({
632
  "role": "user",
633
+ "content": f"📥 **Pasted Document Text:**\n\n{limit_text(raw_text,500)}"
634
  })
635
  with st.chat_message("user", avatar=USER_AVATAR):
636
+ st.markdown(limit_text(raw_text,500))
637
 
638
  start_time = time.time()
639
+ # summarization + emb_text as before
640
+ summary_dict = hybrid_summary_hierarchical(raw_text)
641
+ emb_text = prepare_text_for_embedding(summary_dict)
642
  st.session_state.document_context = emb_text
643
  st.session_state.processed = True
644
 
645
+ # ─── NEW: ingest via document‐augmentation ───
646
+ chunks, store = process_document(raw_text)
647
+ st.session_state.vector_store = store
 
 
648
 
649
+ if user_role == "General":
650
+ role_prompt = (
651
+ "Summarize the document focusing on facts, arguments, judgments, "
652
+ "and include a timeline of events."
653
+ )
654
+ else:
655
+ role_prompt = (
656
+ f"As a {user_role}, summarize the document focusing on facts, "
657
+ "arguments, judgments, plus timeline of events."
658
+ )
659
+
660
+ # ─── doc‐augmentation RAG here too ───
661
+ results = store.search(role_prompt, k=5)
662
+ context = prepare_context(results)
663
+ initial_summary = generate_response_from_context(role_prompt, context)
664
+
665
  st.session_state.messages.append({
666
  "role": "assistant",
667
  "content": initial_summary
 
672
  st.info(f"⏱️ Summary generated in {round((time.time()-start_time)/60,2)} minutes")
673
  save_chat_history(st.session_state.messages)
674
 
675
+
676
+ # 2) SHORT prompts – normal RAG against last ingested context
677
  elif word_count <= 30 and st.session_state.processed:
678
+
679
+ with st.chat_message("user", avatar=USER_AVATAR):
680
+ st.markdown(prompt)
681
+
682
+ # 2) save to history
683
+ st.session_state.messages.append({"role": "user", "content": prompt})
684
+ store = st.session_state.vector_store
685
+
686
+ # ─── instead of rag_query_response, do doc‐augmentation RAG ───
687
+ results = store.search(prompt, k=5)
688
+ context = prepare_context(results)
689
+ answer = generate_response_from_context(prompt, context)
690
+
691
+ # st.session_state.messages.append({"role":"user", "content":prompt})
692
+ st.session_state.messages.append({"role":"assistant","content":answer})
693
  with st.chat_message("assistant", avatar=BOT_AVATAR):
694
  display_with_typing_effect(answer)
695
  save_chat_history(st.session_state.messages)
696
 
697
+
698
+ # 3) not enough input
699
  else:
700
  with st.chat_message("assistant", avatar=BOT_AVATAR):
701
  st.markdown("❗ Paste at least 30 words of your document to ingest it first.")
702
 
703
 
704
+ ######################################################################################################################
705
+
706
  # 📚 Imports
707
  import evaluate
708
  from nltk.translate.bleu_score import sentence_bleu, SmoothingFunction
 
709
 
710
  # �� Load Evaluators Once
711
  @st.cache_resource
 
724
  return rouge_result, bert_result
725
 
726
  def compute_bleu(prediction, ground_truth):
 
727
  reference = [ground_truth.strip().split()]
728
  candidate = prediction.strip().split()
729
  smoothie = SmoothingFunction().method4
730
  return sentence_bleu(reference, candidate, smoothing_function=smoothie)
731
 
732
+ def evaluate_metrics(prediction, ground_truth):
733
+ """Only compute ROUGE, BLEU, and BERTScore."""
734
+ rouge_result, bert_result = evaluate_summary(prediction, ground_truth)
735
+ bleu_score = compute_bleu(prediction, ground_truth)
736
+ return {
737
+ "ROUGE": rouge_result,
738
+ "BERTScore": bert_result,
739
+ "BLEU Score": bleu_score
740
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
741
 
 
742
 
743
 
744
  # Run this along with streamlit run app.py to evaluate the model's performance on a test set