daniel-simeone commited on
Commit ·
fbbc5a8
1
Parent(s): 5618d45
add in title info
Browse files- app.py +10 -6
- ingestion.py +36 -3
app.py
CHANGED
|
@@ -204,12 +204,15 @@ class RAGChatbot:
|
|
| 204 |
try:
|
| 205 |
results = self.ingestion.search(query, k=num_results)
|
| 206 |
if results:
|
| 207 |
-
# Build context from retrieved chunks
|
| 208 |
context_parts = []
|
| 209 |
for i, result in enumerate(results, 1):
|
| 210 |
text = result['text'].strip()
|
| 211 |
-
if text:
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
| 213 |
|
| 214 |
context = "\n\n".join(context_parts)
|
| 215 |
|
|
@@ -229,8 +232,8 @@ Do not reveal your internal reasoning. Provide only the final answer.
|
|
| 229 |
|
| 230 |
Structure your answer in the following format:
|
| 231 |
Summary — A brief, high‑level answer.
|
| 232 |
-
Supporting Details — Explain using information only from the provided context.
|
| 233 |
-
Context References —
|
| 234 |
|
| 235 |
Context:
|
| 236 |
{context}
|
|
@@ -260,7 +263,8 @@ Answer:"""
|
|
| 260 |
response_parts = []
|
| 261 |
response_parts.append("I retrieved relevant information, but couldn't generate a synthesized answer. Here are the relevant chunks:\n\n")
|
| 262 |
for i, result in enumerate(results, 1):
|
| 263 |
-
|
|
|
|
| 264 |
text = result['text'].strip()
|
| 265 |
if text:
|
| 266 |
response_parts.append(f"**Relevant information {i}** (from {source}):\n{text}\n")
|
|
|
|
| 204 |
try:
|
| 205 |
results = self.ingestion.search(query, k=num_results)
|
| 206 |
if results:
|
| 207 |
+
# Build context from retrieved chunks; include source/title so the model can cite it
|
| 208 |
context_parts = []
|
| 209 |
for i, result in enumerate(results, 1):
|
| 210 |
text = result['text'].strip()
|
| 211 |
+
if not text:
|
| 212 |
+
continue
|
| 213 |
+
meta = result.get('metadata') or {}
|
| 214 |
+
source_label = meta.get('document_title') or meta.get('source') or f"Source {i}"
|
| 215 |
+
context_parts.append(f"[Context {i}] (Source: {source_label})\n{text}")
|
| 216 |
|
| 217 |
context = "\n\n".join(context_parts)
|
| 218 |
|
|
|
|
| 232 |
|
| 233 |
Structure your answer in the following format:
|
| 234 |
Summary — A brief, high‑level answer.
|
| 235 |
+
Supporting Details — Explain using information only from the provided context. When citing, use the Source label shown for that context (e.g. the document title or name in parentheses after [Context N]).
|
| 236 |
+
Context References — List each reference with the exact Source shown for that context (e.g. "CAN/CGSB-32.312-2018" or the document title). Include section name or page when that information appears in the context text. Format: document/source, section or location if available, and a short quote or paraphrase. Do not use only "Context 1" or "Context 5" as the reference; always include the document title/source.
|
| 237 |
|
| 238 |
Context:
|
| 239 |
{context}
|
|
|
|
| 263 |
response_parts = []
|
| 264 |
response_parts.append("I retrieved relevant information, but couldn't generate a synthesized answer. Here are the relevant chunks:\n\n")
|
| 265 |
for i, result in enumerate(results, 1):
|
| 266 |
+
meta = result.get('metadata') or {}
|
| 267 |
+
source = meta.get('document_title') or meta.get('source', '')
|
| 268 |
text = result['text'].strip()
|
| 269 |
if text:
|
| 270 |
response_parts.append(f"**Relevant information {i}** (from {source}):\n{text}\n")
|
ingestion.py
CHANGED
|
@@ -34,6 +34,30 @@ class DocumentIngestion:
|
|
| 34 |
self.embeddings = None
|
| 35 |
self.index = None
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
def read_pdf(self, file_path: str) -> str:
|
| 38 |
"""
|
| 39 |
Extract text from a PDF file.
|
|
@@ -108,14 +132,14 @@ class DocumentIngestion:
|
|
| 108 |
if not os.path.exists(pdf_path):
|
| 109 |
print(f"Warning: PDF file not found: {pdf_path}")
|
| 110 |
continue
|
| 111 |
-
|
| 112 |
text = self.read_pdf(pdf_path)
|
| 113 |
chunks = self.text_splitter.split_text(text)
|
| 114 |
-
|
| 115 |
for i, chunk in enumerate(chunks):
|
| 116 |
all_texts.append(chunk)
|
| 117 |
all_metadata.append({
|
| 118 |
'source': pdf_path,
|
|
|
|
| 119 |
'type': 'pdf',
|
| 120 |
'chunk_index': i
|
| 121 |
})
|
|
@@ -126,11 +150,20 @@ class DocumentIngestion:
|
|
| 126 |
try:
|
| 127 |
text = self.read_url(url)
|
| 128 |
chunks = self.text_splitter.split_text(text)
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
for i, chunk in enumerate(chunks):
|
| 131 |
all_texts.append(chunk)
|
| 132 |
all_metadata.append({
|
| 133 |
'source': url,
|
|
|
|
| 134 |
'type': 'url',
|
| 135 |
'chunk_index': i
|
| 136 |
})
|
|
|
|
| 34 |
self.embeddings = None
|
| 35 |
self.index = None
|
| 36 |
|
| 37 |
+
def get_pdf_document_title(self, file_path: str) -> str:
|
| 38 |
+
"""
|
| 39 |
+
Get a human-readable document title for a PDF (from metadata or filename).
|
| 40 |
+
|
| 41 |
+
Args:
|
| 42 |
+
file_path: Path to the PDF file
|
| 43 |
+
|
| 44 |
+
Returns:
|
| 45 |
+
Document title (e.g. standard name or filename without extension)
|
| 46 |
+
"""
|
| 47 |
+
try:
|
| 48 |
+
reader = PdfReader(file_path)
|
| 49 |
+
if reader.metadata and getattr(reader.metadata, "title", None):
|
| 50 |
+
title = reader.metadata.title
|
| 51 |
+
if title and title.strip():
|
| 52 |
+
return title.strip()
|
| 53 |
+
except Exception:
|
| 54 |
+
pass
|
| 55 |
+
# Fallback: filename without extension, cleaned for standards (e.g. CAN-CGSB-32.312 -> CAN/CGSB-32.312)
|
| 56 |
+
base = os.path.splitext(os.path.basename(file_path))[0]
|
| 57 |
+
if base:
|
| 58 |
+
return base.replace("-", "/") if "CGSB" in base or "CAN" in base else base
|
| 59 |
+
return file_path
|
| 60 |
+
|
| 61 |
def read_pdf(self, file_path: str) -> str:
|
| 62 |
"""
|
| 63 |
Extract text from a PDF file.
|
|
|
|
| 132 |
if not os.path.exists(pdf_path):
|
| 133 |
print(f"Warning: PDF file not found: {pdf_path}")
|
| 134 |
continue
|
| 135 |
+
document_title = self.get_pdf_document_title(pdf_path)
|
| 136 |
text = self.read_pdf(pdf_path)
|
| 137 |
chunks = self.text_splitter.split_text(text)
|
|
|
|
| 138 |
for i, chunk in enumerate(chunks):
|
| 139 |
all_texts.append(chunk)
|
| 140 |
all_metadata.append({
|
| 141 |
'source': pdf_path,
|
| 142 |
+
'document_title': document_title,
|
| 143 |
'type': 'pdf',
|
| 144 |
'chunk_index': i
|
| 145 |
})
|
|
|
|
| 150 |
try:
|
| 151 |
text = self.read_url(url)
|
| 152 |
chunks = self.text_splitter.split_text(text)
|
| 153 |
+
# Use a short label for URL (domain + path hint) as document_title
|
| 154 |
+
try:
|
| 155 |
+
from urllib.parse import urlparse
|
| 156 |
+
parsed = urlparse(url)
|
| 157 |
+
document_title = parsed.netloc or url
|
| 158 |
+
if parsed.path and parsed.path != "/":
|
| 159 |
+
document_title += " " + parsed.path.strip("/")[:50]
|
| 160 |
+
except Exception:
|
| 161 |
+
document_title = url
|
| 162 |
for i, chunk in enumerate(chunks):
|
| 163 |
all_texts.append(chunk)
|
| 164 |
all_metadata.append({
|
| 165 |
'source': url,
|
| 166 |
+
'document_title': document_title,
|
| 167 |
'type': 'url',
|
| 168 |
'chunk_index': i
|
| 169 |
})
|