File size: 6,750 Bytes
45b17a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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