quantumbit commited on
Commit
c98b1a8
·
verified ·
1 Parent(s): 7cbc6c6

Delete document_processor.py

Browse files
Files changed (1) hide show
  1. document_processor.py +0 -134
document_processor.py DELETED
@@ -1,134 +0,0 @@
1
- import hashlib
2
- import logging
3
- import re
4
- from pathlib import Path
5
- from typing import Optional
6
-
7
- from langchain_core.documents import Document
8
- from langchain_community.document_loaders import (
9
- PyPDFLoader,
10
- TextLoader,
11
- UnstructuredMarkdownLoader,
12
- WebBaseLoader,
13
- )
14
- from langchain_text_splitters import RecursiveCharacterTextSplitter
15
- from .config import get_settings
16
-
17
- logger = logging.getLogger(__name__)
18
- settings = get_settings()
19
-
20
- LOADER_MAP = {
21
- ".pdf": PyPDFLoader,
22
- ".txt": TextLoader,
23
- ".md": UnstructuredMarkdownLoader
24
- }
25
-
26
- #Loaders
27
- def load_file(file_path: str) -> list[Document]:
28
- """This function auto detects the file type and loads to the langchain documents"""
29
- ext = Path(file_path).suffix.lower()
30
- loader_cls = LOADER_MAP.get(ext)
31
- if loader_cls is None:
32
- raise ValueError(f"Unsupported file type: {ext}")
33
- loader = loader_cls(file_path)
34
- docs = loader.load()
35
- logger.info(f"Loaded {len(docs)} pages from {file_path}")
36
- return docs
37
-
38
- def load_url(url: str) -> list[Document]:
39
- """Scrape a webpage and return Documents"""
40
- loader = WebBaseLoader(url)
41
- logger.info(f"Loaded data from {url}")
42
- return loader.load()
43
-
44
- #Cleaning
45
- def clean_text(text: str) -> str:
46
- text = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]", "", text) # control chars
47
- text = re.sub(r"[ \t]+", " ", text) # collapse horizontal whitespace
48
- text = re.sub(r"\n{3,}", "\n\n", text) # collapse excess blank lines
49
- return text.strip()
50
-
51
- #Splitter
52
- def build_splitter() -> RecursiveCharacterTextSplitter:
53
- return RecursiveCharacterTextSplitter(
54
- chunk_size=settings.chunk_size,
55
- chunk_overlap=settings.chunk_overlap,
56
- length_function = len,
57
- separators=["\n\n", "\n", ". ", "? ", "! ", "; ", ", ", " ", ""]
58
- )
59
-
60
- #Metadata Enrichment
61
- def _stable_hash(text:str) -> str:
62
- return hashlib.md5(text.encode()).hexdigest()[:12]
63
-
64
- def enrich_metadata(
65
- chunks: list[Document],
66
- source_id: Optional[str] = None,
67
- extra_meta: Optional[dict] = None
68
- ) -> list[Document]:
69
- """
70
- Production enrichment:
71
- - stable doc_id from content hash (dedup-safe)
72
- - chunk_index for indexing
73
- - char_count for downstream token budget checks
74
- - prev/next chunk IDs for context stitching
75
- """
76
- chunk_ids = [_stable_hash(c.page_content) for c in chunks]
77
- enriched = []
78
- for i, (doc,cid) in enumerate(zip(chunks,chunk_ids)):
79
- meta = {
80
- **doc.metadata,
81
- "doc_id": cid,
82
- "chunk_index": i,
83
- "char_count": len(doc.page_content),
84
- "prev_chunk_id": chunk_ids[i-1] if i > 0 else None,
85
- "next_chunk_id": chunk_ids[i+1] if i < len(chunks) - 1 else None,
86
- "source_id": source_id or "unknown"
87
- }
88
- if extra_meta:
89
- meta.update(extra_meta)
90
- enriched.append(Document(page_content=doc.page_content,metadata=meta))
91
- return enriched
92
-
93
- #Main pipeline
94
- def process_texts(
95
- texts: list[str],
96
- metadatas: Optional[list[dict]] = None,
97
- source_id: Optional[str] = None
98
- ) -> list[Document]:
99
- """
100
- Full ingestion Pipeline:
101
- 1. Wrap raw strings in Documents
102
- 2. Clean_text
103
- 3. Split into Chunks
104
- 4. Filter junk chunks
105
- 5. Enrich Metadata
106
- """
107
- splitter = build_splitter()
108
-
109
- raw_docs = [
110
- Document(page_content=clean_text(t), metadata = m or {})
111
- for t,m in zip(texts,metadatas or [{}]*len(texts))
112
- ]
113
-
114
- chunks = splitter.split_documents(raw_docs)
115
-
116
- #drop tiny or near to empty chunks
117
- chunks = [
118
- c for c in chunks
119
- if len(c.page_content.strip()) >= settings.min_chunk_size
120
- ]
121
-
122
- chunks = enrich_metadata(chunks,source_id=source_id)
123
- logger.info(f"Processed {len(texts)} texts -> {len(chunks)} chunks")
124
- return chunks
125
-
126
- def process_file(file_path: str, display_name: str | None = None) -> list[Document]:
127
- """End to end ingestion of file path. display_name overrides the temp path as source_id."""
128
- docs = load_file(file_path)
129
- texts = [d.page_content for d in docs]
130
- metas = [d.metadata for d in docs]
131
- source = display_name if display_name else file_path
132
- return process_texts(texts, metas, source_id=source)
133
-
134
- print("[document_processor] Module ready")