Spaces:
Running
Running
File size: 5,399 Bytes
f565efa | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | """
Data loading module: reads .txt/.md/.pdf files and splits them into
paragraph-level LangChain Documents with metadata (source, chunk_id,
section, and page for PDFs).
Requirements:
pip install langchain-core pypdf
"""
import os
from typing import Any, Dict, List, Optional
from langchain_core.documents import Document
# PDF text extraction
from pypdf import PdfReader
SUPPORTED_EXTENSIONS = (".txt", ".md", ".pdf")
# Set the path to the file you want to load here — change this to
# point at your .txt, .md, or .pdf file. This is the single place
# the file path is configured; hybrid_rag_pipeline.py doesn't set it.
FILE_PATH = "test.txt"
# ---------------------------------------------------
# Shared paragraph -> Document chunking logic
# ---------------------------------------------------
def _paragraphs_to_documents(
paragraphs: List[str],
source_name: str,
start_chunk_id: int = 0,
extra_metadata: Optional[Dict[str, Any]] = None,
current_section: str = "unknown",
) -> List[Document]:
"""
Convert a list of paragraph strings into Documents, carrying forward
a naive "section" heading guess across paragraphs.
"""
docs = []
for offset, para in enumerate(paragraphs):
first_line = para.splitlines()[0].strip()
if len(first_line) < 60 and not first_line.endswith((".", "?", "!")):
current_section = first_line
metadata = {
"source": source_name,
"chunk_id": start_chunk_id + offset,
"section": current_section,
}
if extra_metadata:
metadata.update(extra_metadata)
docs.append(Document(page_content=para, metadata=metadata))
return docs
# ---------------------------------------------------
# Load .txt Documents (with metadata)
# ---------------------------------------------------
def load_text_documents(file_path: str) -> List[Document]:
"""
Load a .txt/.md file and split it into paragraph-level Documents.
Each Document gets metadata you can later filter on:
- source: the file it came from
- chunk_id: its position in the file
- section: a naive heading guess (first line-like token),
useful as an example metadata filter field
"""
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
paragraphs = [p.strip() for p in content.split("\n\n") if p.strip()]
return _paragraphs_to_documents(
paragraphs,
source_name=os.path.basename(file_path),
)
# ---------------------------------------------------
# Load .pdf Documents (with metadata)
# ---------------------------------------------------
def load_pdf_documents(file_path: str) -> List[Document]:
"""
Load a .pdf file, extract text page by page, and split each page
into paragraph-level Documents.
Each Document gets the same metadata fields as load_text_documents,
plus:
- page: the 1-indexed PDF page number the chunk came from
Note: extraction quality depends on the PDF — scanned/image-only
PDFs will yield little or no text (they'd need OCR first).
"""
reader = PdfReader(file_path)
source_name = os.path.basename(file_path)
all_docs: List[Document] = []
current_section = "unknown"
chunk_id = 0
for page_num, page in enumerate(reader.pages, start=1):
text = page.extract_text() or ""
paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
if not paragraphs:
continue
page_docs = _paragraphs_to_documents(
paragraphs,
source_name=source_name,
start_chunk_id=chunk_id,
extra_metadata={"page": page_num},
current_section=current_section,
)
# carry section guess forward into the next page
if page_docs:
current_section = page_docs[-1].metadata["section"]
chunk_id += len(page_docs)
all_docs.extend(page_docs)
return all_docs
# ---------------------------------------------------
# Generic dispatcher: pick the right loader by extension
# ---------------------------------------------------
def load_documents(file_path: Optional[str] = None) -> List[Document]:
"""
Load a document file into paragraph-level Documents, dispatching to
the right loader based on file extension.
If file_path is omitted, falls back to the FILE_PATH constant
defined above — so callers (like hybrid_rag_pipeline.py) don't
need to know or set a path themselves.
Supported: .txt, .md, .pdf
"""
path = file_path or FILE_PATH
ext = os.path.splitext(path)[1].lower()
if ext == ".pdf":
return load_pdf_documents(path)
elif ext in (".txt", ".md"):
return load_text_documents(path)
else:
raise ValueError(
f"Unsupported file type: '{ext}'. Supported extensions: "
f"{', '.join(SUPPORTED_EXTENSIONS)}"
)
if __name__ == "__main__":
# Quick standalone check: run `python data_loader.py` to verify a
# file loads and chunks as expected before wiring it into the
# full pipeline.
docs = load_documents()
print(f"Loaded {len(docs)} documents from '{FILE_PATH}'.")
for doc in docs[:3]:
print("\n---")
print(doc.metadata)
print(doc.page_content[:200])
|