Npps commited on
Commit
434f6ed
1 Parent(s): c059386

Upload 5 files

Browse files
Files changed (5) hide show
  1. .pre-commit-config.yaml +44 -0
  2. CampusX.jfif +0 -0
  3. Q&A logo.jfif +0 -0
  4. htmltemplate.py +0 -0
  5. ingest.py +186 -0
.pre-commit-config.yaml ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ files: ^(.*\.(py|json|md|sh|yaml|cfg|txt))$
3
+ exclude: ^(\.[^/]*cache/.*|.*/_user.py|source_documents/)$
4
+ repos:
5
+ - repo: https://github.com/pre-commit/pre-commit-hooks
6
+ rev: v4.4.0
7
+ hooks:
8
+ #- id: no-commit-to-branch
9
+ # args: [--branch, main]
10
+ - id: check-yaml
11
+ args: [--unsafe]
12
+ # - id: debug-statements
13
+ - id: end-of-file-fixer
14
+ - id: trailing-whitespace
15
+ exclude-files: \.md$
16
+ - id: check-json
17
+ - id: mixed-line-ending
18
+ # - id: check-builtin-literals
19
+ # - id: check-ast
20
+ - id: check-merge-conflict
21
+ - id: check-executables-have-shebangs
22
+ - id: check-shebang-scripts-are-executable
23
+ - id: check-docstring-first
24
+ - id: fix-byte-order-marker
25
+ - id: check-case-conflict
26
+ # - id: check-toml
27
+ - repo: https://github.com/adrienverge/yamllint.git
28
+ rev: v1.29.0
29
+ hooks:
30
+ - id: yamllint
31
+ args:
32
+ - --no-warnings
33
+ - -d
34
+ - '{extends: relaxed, rules: {line-length: {max: 90}}}'
35
+ - repo: https://github.com/codespell-project/codespell
36
+ rev: v2.2.2
37
+ hooks:
38
+ - id: codespell
39
+ args:
40
+ # - --builtin=clear,rare,informal,usage,code,names,en-GB_to_en-US
41
+ - --builtin=clear,rare,informal,usage,code,names
42
+ - --ignore-words-list=hass,master
43
+ - --skip="./.*"
44
+ - --quiet-level=2
CampusX.jfif ADDED
Binary file (29.5 kB). View file
 
Q&A logo.jfif ADDED
Binary file (2.92 kB). View file
 
htmltemplate.py ADDED
File without changes
ingest.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import glob
4
+ from typing import List
5
+ from dotenv import load_dotenv
6
+ from multiprocessing import Pool
7
+ from tqdm import tqdm
8
+ from langchain_cohere import CohereEmbeddings
9
+ from langchain.document_loaders import (
10
+ CSVLoader,
11
+ EverNoteLoader,
12
+ PyMuPDFLoader,
13
+ TextLoader,
14
+ UnstructuredEmailLoader,
15
+ UnstructuredEPubLoader,
16
+ UnstructuredHTMLLoader,
17
+ UnstructuredMarkdownLoader,
18
+ UnstructuredODTLoader,
19
+ UnstructuredPowerPointLoader,
20
+ UnstructuredWordDocumentLoader,
21
+ )
22
+
23
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
24
+ from langchain.vectorstores import Chroma
25
+ from langchain.embeddings import HuggingFaceEmbeddings
26
+ from langchain.docstore.document import Document
27
+
28
+ if not load_dotenv():
29
+ print("Could not load .env file or it is empty. Please check if it exists and is readable.")
30
+ exit(1)
31
+
32
+ from constants import CHROMA_SETTINGS
33
+ import chromadb
34
+ from chromadb.api.segment import API
35
+
36
+ # Load environment variables
37
+ persist_directory = os.environ.get('PERSIST_DIRECTORY')
38
+ source_directory = os.environ.get('SOURCE_DIRECTORY', 'source_documents')
39
+ embeddings_model_name = os.environ.get('EMBEDDINGS_MODEL_NAME')
40
+ chunk_size = 500
41
+ chunk_overlap = 50
42
+
43
+
44
+ # Custom document loaders
45
+ class MyElmLoader(UnstructuredEmailLoader):
46
+ """Wrapper to fallback to text/plain when default does not work"""
47
+
48
+ def load(self) -> List[Document]:
49
+ """Wrapper adding fallback for elm without html"""
50
+ try:
51
+ try:
52
+ doc = UnstructuredEmailLoader.load(self)
53
+ except ValueError as e:
54
+ if 'text/html content not found in email' in str(e):
55
+ # Try plain text
56
+ self.unstructured_kwargs["content_source"]="text/plain"
57
+ doc = UnstructuredEmailLoader.load(self)
58
+ else:
59
+ raise
60
+ except Exception as e:
61
+ # Add file_path to exception message
62
+ raise type(e)(f"{self.file_path}: {e}") from e
63
+
64
+ return doc
65
+
66
+
67
+ # Map file extensions to document loaders and their arguments
68
+ LOADER_MAPPING = {
69
+ ".csv": (CSVLoader, {}),
70
+ # ".docx": (Docx2txtLoader, {}),
71
+ ".doc": (UnstructuredWordDocumentLoader, {}),
72
+ ".docx": (UnstructuredWordDocumentLoader, {}),
73
+ ".enex": (EverNoteLoader, {}),
74
+ ".eml": (MyElmLoader, {}),
75
+ ".epub": (UnstructuredEPubLoader, {}),
76
+ ".html": (UnstructuredHTMLLoader, {}),
77
+ ".md": (UnstructuredMarkdownLoader, {}),
78
+ ".odt": (UnstructuredODTLoader, {}),
79
+ ".pdf": (PyMuPDFLoader, {}),
80
+ ".ppt": (UnstructuredPowerPointLoader, {}),
81
+ ".pptx": (UnstructuredPowerPointLoader, {}),
82
+ ".txt": (TextLoader, {"encoding": "utf8"}),
83
+ # Add more mappings for other file extensions and loaders as needed
84
+ }
85
+
86
+
87
+ def load_single_document(file_path: str) -> List[Document]:
88
+ ext = "." + file_path.rsplit(".", 1)[-1].lower()
89
+ if ext in LOADER_MAPPING:
90
+ loader_class, loader_args = LOADER_MAPPING[ext]
91
+ loader = loader_class(file_path, **loader_args)
92
+ return loader.load()
93
+
94
+ raise ValueError(f"Unsupported file extension '{ext}'")
95
+
96
+ def load_documents(source_dir: str, ignored_files: List[str] = []) -> List[Document]:
97
+ """
98
+ Loads all documents from the source documents directory, ignoring specified files
99
+ """
100
+ all_files = []
101
+ for ext in LOADER_MAPPING:
102
+ all_files.extend(
103
+ glob.glob(os.path.join(source_dir, f"**/*{ext.lower()}"), recursive=True)
104
+ )
105
+ all_files.extend(
106
+ glob.glob(os.path.join(source_dir, f"**/*{ext.upper()}"), recursive=True)
107
+ )
108
+ filtered_files = [file_path for file_path in all_files if file_path not in ignored_files]
109
+
110
+ with Pool(processes=os.cpu_count()) as pool:
111
+ results = []
112
+ with tqdm(total=len(filtered_files), desc='Loading new documents', ncols=80) as pbar:
113
+ for i, docs in enumerate(pool.imap_unordered(load_single_document, filtered_files)):
114
+ results.extend(docs)
115
+ pbar.update()
116
+
117
+ return results
118
+
119
+ def process_documents(ignored_files: List[str] = []) -> List[Document]:
120
+ """
121
+ Load documents and split in chunks
122
+ """
123
+ print(f"Loading documents from {source_directory}")
124
+ documents = load_documents(source_directory, ignored_files)
125
+ if not documents:
126
+ print("No new documents to load")
127
+ exit(0)
128
+ print(f"Loaded {len(documents)} new documents from {source_directory}")
129
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
130
+ documents = text_splitter.split_documents(documents)
131
+ print(f"Split into {len(documents)} chunks of text (max. {chunk_size} tokens each)")
132
+ return documents
133
+
134
+ def batch_chromadb_insertions(chroma_client: API, documents: List[Document]) -> List[Document]:
135
+ """
136
+ Split the total documents to be inserted into batches of documents that the local chroma client can process
137
+ """
138
+ # Get max batch size.
139
+ max_batch_size = chroma_client.max_batch_size
140
+ for i in range(0, len(documents), max_batch_size):
141
+ yield documents[i:i + max_batch_size]
142
+
143
+
144
+ def does_vectorstore_exist(persist_directory: str, embeddings: HuggingFaceEmbeddings) -> bool:
145
+ """
146
+ Checks if vectorstore exists
147
+ """
148
+ db = Chroma(persist_directory=persist_directory, embedding_function=embeddings)
149
+ if not db.get()['documents']:
150
+ return False
151
+ return True
152
+
153
+ def main():
154
+ # Create embeddings
155
+ #embeddings = HuggingFaceEmbeddings(model_name=embeddings_model_name)
156
+ embeddings = CohereEmbeddings()
157
+ # Chroma client
158
+ chroma_client = chromadb.PersistentClient(settings=CHROMA_SETTINGS , path=persist_directory)
159
+
160
+ if does_vectorstore_exist(persist_directory, embeddings):
161
+ # Update and store locally vectorstore
162
+ print(f"Appending to existing vectorstore at {persist_directory}")
163
+ db = Chroma(persist_directory=persist_directory, embedding_function=embeddings, client_settings=CHROMA_SETTINGS, client=chroma_client)
164
+ collection = db.get()
165
+ documents = process_documents([metadata['source'] for metadata in collection['metadatas']])
166
+ print(f"Creating embeddings. May take some minutes...")
167
+ for batched_chromadb_insertion in batch_chromadb_insertions(chroma_client, documents):
168
+ db.add_documents(batched_chromadb_insertion)
169
+ else:
170
+ # Create and store locally vectorstore
171
+ print("Creating new vectorstore")
172
+ documents = process_documents()
173
+ print(f"Creating embeddings. May take some minutes...")
174
+ # Create the db with the first batch of documents to insert
175
+ batched_chromadb_insertions = batch_chromadb_insertions(chroma_client, documents)
176
+ first_insertion = next(batched_chromadb_insertions)
177
+ db = Chroma.from_documents(first_insertion, embeddings, persist_directory=persist_directory, client_settings=CHROMA_SETTINGS, client=chroma_client)
178
+ # Add the rest of batches of documents
179
+ for batched_chromadb_insertion in batched_chromadb_insertions:
180
+ db.add_documents(batched_chromadb_insertion)
181
+
182
+ print(f"Ingestion complete! You can now run privateGPT.py to query your documents")
183
+
184
+
185
+ if __name__ == "__main__":
186
+ main()