studentchatbot / src /helper.py
aak007's picture
Initial commit for Hugging Face
45b17a5
Raw
History Blame Contribute Delete
6.75 kB
from langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_core.documents import Document
from datasets import load_dataset
from typing import List, Optional, Dict, Any
import csv
import json
from pathlib import Path
# Extract Data From PDF file(s)
def load_pdf_file(data_path: str) -> List[Document]:
path = Path(data_path)
if not path.exists():
raise FileNotFoundError(f"PDF path not found: {data_path}")
if path.is_file():
if path.suffix.lower() != ".pdf":
raise ValueError(
"When DATA_SOURCE=pdf and path is a file, it must be a .pdf file"
)
return PyPDFLoader(str(path)).load()
loader = DirectoryLoader(str(path), glob="*.pdf", loader_cls=PyPDFLoader)
return loader.load()
# Load Student Q&A Dataset from Hugging Face
def load_hf_dataset(dataset_name: str) -> List[Document]:
dataset = load_dataset(dataset_name, split="train")
documents = []
for row in dataset:
# Build a rich text blob from all available fields in the row
content_parts = []
for key, value in row.items():
if value:
content_parts.append(f"{key.capitalize()}: {value}")
page_content = "\n".join(content_parts)
documents.append(
Document(page_content=page_content, metadata={"source": dataset_name})
)
return documents
def _parse_columns(columns: Optional[str]) -> Optional[List[str]]:
if not columns:
return None
parsed = [col.strip() for col in columns.split(",") if col.strip()]
return parsed or None
def _row_to_document(
row: Dict[str, Any],
source_name: str,
text_columns: Optional[List[str]] = None,
) -> Optional[Document]:
content_parts: List[str] = []
if text_columns:
for key in text_columns:
value = row.get(key)
if value is not None and str(value).strip():
content_parts.append(f"{key.capitalize()}: {value}")
else:
for key, value in row.items():
if value is not None and str(value).strip():
content_parts.append(f"{key.capitalize()}: {value}")
if not content_parts:
return None
return Document(
page_content="\n".join(content_parts),
metadata={"source": source_name},
)
def load_local_dataset(
local_path: str, text_columns: Optional[str] = None
) -> List[Document]:
path = Path(local_path)
if not path.exists():
raise FileNotFoundError(f"Dataset file not found: {local_path}")
parsed_text_columns = _parse_columns(text_columns)
ext = path.suffix.lower()
documents: List[Document] = []
if ext == ".csv":
with path.open("r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
doc = _row_to_document(
row, source_name=str(path.name), text_columns=parsed_text_columns
)
if doc:
documents.append(doc)
return documents
if ext == ".json":
with path.open("r", encoding="utf-8") as f:
payload = json.load(f)
if isinstance(payload, dict):
payload = [payload]
if not isinstance(payload, list):
raise ValueError("JSON dataset must be an object or a list of objects")
for row in payload:
if isinstance(row, dict):
doc = _row_to_document(
row, source_name=str(path.name), text_columns=parsed_text_columns
)
if doc:
documents.append(doc)
return documents
if ext == ".jsonl":
with path.open("r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
row = json.loads(line)
if isinstance(row, dict):
doc = _row_to_document(
row,
source_name=str(path.name),
text_columns=parsed_text_columns,
)
if doc:
documents.append(doc)
return documents
if ext in {".txt", ".md"}:
text = path.read_text(encoding="utf-8")
blocks = [block.strip() for block in text.split("\n\n") if block.strip()]
for block in blocks:
documents.append(
Document(page_content=block, metadata={"source": str(path.name)})
)
return documents
raise ValueError(
"Unsupported dataset format. Use .csv, .json, .jsonl, .txt, or .md"
)
def load_dataset_by_config(
data_source: str,
hf_dataset_name: Optional[str] = None,
local_dataset_path: Optional[str] = None,
text_columns: Optional[str] = None,
) -> List[Document]:
source = (data_source or "hf").strip().lower()
if source == "hf":
if not hf_dataset_name:
raise ValueError("HF_DATASET_NAME is required when DATA_SOURCE=hf")
return load_hf_dataset(hf_dataset_name)
if source == "local":
if not local_dataset_path:
raise ValueError("LOCAL_DATASET_PATH is required when DATA_SOURCE=local")
return load_local_dataset(local_dataset_path, text_columns=text_columns)
if source == "pdf":
if not local_dataset_path:
raise ValueError("LOCAL_DATASET_PATH is required when DATA_SOURCE=pdf")
return load_pdf_file(local_dataset_path)
raise ValueError("DATA_SOURCE must be one of: hf, local, pdf")
def filter_to_minimal_docs(docs: List[Document]) -> List[Document]:
"""
Given a list of Document objects, return a new list of Document objects
containing only 'source' in metadata and the original page_content.
"""
minimal_docs: List[Document] = []
for doc in docs:
src = doc.metadata.get("source")
minimal_docs.append(
Document(page_content=doc.page_content, metadata={"source": src})
)
return minimal_docs
# Split the Data into Text Chunks
def text_split(extracted_data):
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=20)
text_chunks = text_splitter.split_documents(extracted_data)
return text_chunks
# Download the Embeddings from HuggingFace
def download_hugging_face_embeddings():
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2"
) # this model return 384 dimensions
return embeddings