Maia Pelletier commited on
Commit ·
588cdee
1
Parent(s): 3f60e32
add links for PDFs
Browse files- app.py +5 -2
- data/vector_store/documents.pkl +2 -2
- data/vector_store/embeddings.pkl +1 -1
- data/vector_store/index.faiss +1 -1
- ingest_documents.py +8 -1
- ingestion.py +13 -6
app.py
CHANGED
|
@@ -262,17 +262,20 @@ Answer:"""
|
|
| 262 |
)
|
| 263 |
if meta.get("type") == "url" and meta.get("source"):
|
| 264 |
context_index_to_url[i] = meta["source"]
|
|
|
|
|
|
|
| 265 |
|
| 266 |
# Generate response using chat/comversational API (Mistral instruct uses this)
|
| 267 |
try:
|
| 268 |
response_text = self._generate_with_chat(prompt, max_new_tokens=512)
|
| 269 |
if response_text:
|
| 270 |
# Resolve [Context N] to actual source labels in the body
|
| 271 |
-
#
|
|
|
|
| 272 |
def _expand_grouped_context(m):
|
| 273 |
indices = [int(x.strip()) for x in re.split(r'[,\s]+', m.group(1)) if x.strip().isdigit()]
|
| 274 |
return " ".join(f"[Context {idx}]" for idx in indices)
|
| 275 |
-
response_text = re.sub(r'\bContext\s+([\d][,\s\d]*)', _expand_grouped_context, response_text)
|
| 276 |
# Now replace all [Context N] with source labels/links
|
| 277 |
for i, source_label in context_index_to_source.items():
|
| 278 |
url = context_index_to_url.get(i)
|
|
|
|
| 262 |
)
|
| 263 |
if meta.get("type") == "url" and meta.get("source"):
|
| 264 |
context_index_to_url[i] = meta["source"]
|
| 265 |
+
elif meta.get("url"):
|
| 266 |
+
context_index_to_url[i] = meta["url"]
|
| 267 |
|
| 268 |
# Generate response using chat/comversational API (Mistral instruct uses this)
|
| 269 |
try:
|
| 270 |
response_text = self._generate_with_chat(prompt, max_new_tokens=512)
|
| 271 |
if response_text:
|
| 272 |
# Resolve [Context N] to actual source labels in the body
|
| 273 |
+
# Expand bare/grouped "Context N, M" forms to individual [Context N] tags.
|
| 274 |
+
# Use negative lookbehind (?<!\[) so already-bracketed [Context N] are not double-wrapped.
|
| 275 |
def _expand_grouped_context(m):
|
| 276 |
indices = [int(x.strip()) for x in re.split(r'[,\s]+', m.group(1)) if x.strip().isdigit()]
|
| 277 |
return " ".join(f"[Context {idx}]" for idx in indices)
|
| 278 |
+
response_text = re.sub(r'(?<!\[)\bContext\s+([\d][,\s\d]*)', _expand_grouped_context, response_text)
|
| 279 |
# Now replace all [Context N] with source labels/links
|
| 280 |
for i, source_label in context_index_to_source.items():
|
| 281 |
url = context_index_to_url.get(i)
|
data/vector_store/documents.pkl
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0ba58f4cd0cf25aec81a1c328e116e6276dd5648819412e80a9d9cbf9e9a109a
|
| 3 |
+
size 879299
|
data/vector_store/embeddings.pkl
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 4390028
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d6ad8b4a984849be946ed42dc015bc669d4882712e89318f72ea65e4fbc1d79a
|
| 3 |
size 4390028
|
data/vector_store/index.faiss
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 4389933
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:06988afb211c9b69be9e929e45c1b13dbf48cb0b9549b30b24e9f15d78aadcbb
|
| 3 |
size 4389933
|
ingest_documents.py
CHANGED
|
@@ -17,6 +17,13 @@ URLS = [
|
|
| 17 |
"https://cog.ca/faqs/"
|
| 18 |
]
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
def main():
|
| 22 |
"""Main ingestion function."""
|
|
@@ -61,7 +68,7 @@ def main():
|
|
| 61 |
print("=" * 60)
|
| 62 |
|
| 63 |
try:
|
| 64 |
-
documents = ingestion.process_documents(pdf_paths=pdf_paths, urls=urls)
|
| 65 |
print(f"\n[SUCCESS] Successfully processed {len(documents)} document chunks")
|
| 66 |
|
| 67 |
# Build vector store
|
|
|
|
| 17 |
"https://cog.ca/faqs/"
|
| 18 |
]
|
| 19 |
|
| 20 |
+
# Optional: map PDF filenames to their publicly hosted URLs so references are hyperlinked.
|
| 21 |
+
# Keys are bare filenames (no path), values are the public URL for that PDF.
|
| 22 |
+
PDF_URLS = {
|
| 23 |
+
"Organic production systems - General principles and management standards.pdf": "https://publications.gc.ca/collections/collection_2026/ongc-cgsb/P29-32-310-2026-eng.pdf",
|
| 24 |
+
# "another-doc.pdf": "https://example.com/another-doc.pdf",
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
|
| 28 |
def main():
|
| 29 |
"""Main ingestion function."""
|
|
|
|
| 68 |
print("=" * 60)
|
| 69 |
|
| 70 |
try:
|
| 71 |
+
documents = ingestion.process_documents(pdf_paths=pdf_paths, urls=urls, pdf_urls=PDF_URLS)
|
| 72 |
print(f"\n[SUCCESS] Successfully processed {len(documents)} document chunks")
|
| 73 |
|
| 74 |
# Build vector store
|
ingestion.py
CHANGED
|
@@ -115,20 +115,22 @@ class DocumentIngestion:
|
|
| 115 |
except Exception as e:
|
| 116 |
raise Exception(f"Error reading URL {url}: {str(e)}")
|
| 117 |
|
| 118 |
-
def process_documents(self, pdf_paths: List[str] = None, urls: List[str] = None) -> List[Dict]:
|
| 119 |
"""
|
| 120 |
Process PDFs and URLs into chunks.
|
| 121 |
-
|
| 122 |
Args:
|
| 123 |
pdf_paths: List of PDF file paths
|
| 124 |
urls: List of URLs to process
|
| 125 |
-
|
|
|
|
| 126 |
Returns:
|
| 127 |
List of document chunks with metadata
|
| 128 |
"""
|
| 129 |
all_texts = []
|
| 130 |
all_metadata = []
|
| 131 |
-
|
|
|
|
| 132 |
# Process PDFs
|
| 133 |
if pdf_paths:
|
| 134 |
for pdf_path in pdf_paths:
|
|
@@ -138,14 +140,19 @@ class DocumentIngestion:
|
|
| 138 |
document_title = self.get_pdf_document_title(pdf_path)
|
| 139 |
text = self.read_pdf(pdf_path)
|
| 140 |
chunks = self.text_splitter.split_text(text)
|
|
|
|
|
|
|
| 141 |
for i, chunk in enumerate(chunks):
|
| 142 |
all_texts.append(chunk)
|
| 143 |
-
|
| 144 |
'source': pdf_path,
|
| 145 |
'document_title': document_title,
|
| 146 |
'type': 'pdf',
|
| 147 |
'chunk_index': i
|
| 148 |
-
}
|
|
|
|
|
|
|
|
|
|
| 149 |
|
| 150 |
# Process URLs
|
| 151 |
if urls:
|
|
|
|
| 115 |
except Exception as e:
|
| 116 |
raise Exception(f"Error reading URL {url}: {str(e)}")
|
| 117 |
|
| 118 |
+
def process_documents(self, pdf_paths: List[str] = None, urls: List[str] = None, pdf_urls: Dict[str, str] = None) -> List[Dict]:
|
| 119 |
"""
|
| 120 |
Process PDFs and URLs into chunks.
|
| 121 |
+
|
| 122 |
Args:
|
| 123 |
pdf_paths: List of PDF file paths
|
| 124 |
urls: List of URLs to process
|
| 125 |
+
pdf_urls: Optional dict mapping PDF filenames to their public URLs (for hyperlinking references)
|
| 126 |
+
|
| 127 |
Returns:
|
| 128 |
List of document chunks with metadata
|
| 129 |
"""
|
| 130 |
all_texts = []
|
| 131 |
all_metadata = []
|
| 132 |
+
pdf_urls = pdf_urls or {}
|
| 133 |
+
|
| 134 |
# Process PDFs
|
| 135 |
if pdf_paths:
|
| 136 |
for pdf_path in pdf_paths:
|
|
|
|
| 140 |
document_title = self.get_pdf_document_title(pdf_path)
|
| 141 |
text = self.read_pdf(pdf_path)
|
| 142 |
chunks = self.text_splitter.split_text(text)
|
| 143 |
+
filename = os.path.basename(pdf_path)
|
| 144 |
+
public_url = pdf_urls.get(filename)
|
| 145 |
for i, chunk in enumerate(chunks):
|
| 146 |
all_texts.append(chunk)
|
| 147 |
+
meta = {
|
| 148 |
'source': pdf_path,
|
| 149 |
'document_title': document_title,
|
| 150 |
'type': 'pdf',
|
| 151 |
'chunk_index': i
|
| 152 |
+
}
|
| 153 |
+
if public_url:
|
| 154 |
+
meta['url'] = public_url
|
| 155 |
+
all_metadata.append(meta)
|
| 156 |
|
| 157 |
# Process URLs
|
| 158 |
if urls:
|