doctorbetaq commited on
Commit
68cb93e
1 Parent(s): 7e053f9

Upload 9 files

Browse files
Files changed (9) hide show
  1. .env +6 -0
  2. .pre-commit-config.yaml +44 -0
  3. Dockerfile +12 -0
  4. app.py +18 -0
  5. constants.py +15 -0
  6. ingest.py +167 -0
  7. privateGPT.py +76 -0
  8. requirements.txt +13 -0
  9. trueGPT.py +43 -0
.env ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ PERSIST_DIRECTORY=db
2
+ MODEL_TYPE=GPT4All
3
+ MODEL_PATH=models/ggml-gpt4all-j-v1.3-groovy.bin
4
+ EMBEDDINGS_MODEL_NAME=all-mpnet-base-v2
5
+ MODEL_N_CTX=1000
6
+ TARGET_SOURCE_CHUNKS=4
.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
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用一个官方的 Python 运行时作为父镜像
2
+ FROM python:3.11.0b4
3
+ # 將工作目錄設定為/code
4
+ WORKDIR /code
5
+ # 將你的requirements.txt複製到映像檔中的/code目錄
6
+ COPY ./requirements.txt /code/requirements.txt
7
+ # 在映像檔中安裝requirements.txt中指定的依賴項
8
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
9
+ # 將你的應用程式複製到映像檔中的/code目錄
10
+ COPY . .
11
+
12
+ CMD ["python", "app.py"] # 啟動你的Flask應用程式
app.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Flask 應用程式碼
2
+ from flask import Flask, render_template, request, jsonify
3
+ from trueGPT import get_response
4
+
5
+ app = Flask(__name__)
6
+
7
+ @app.route('/')
8
+ def home():
9
+ return render_template('index.html')
10
+
11
+ @app.route('/get_response', methods=['POST'])
12
+ def process_input():
13
+ user_input = request.json['user_input']
14
+ response = get_response(user_input)
15
+ return jsonify(response=response)
16
+
17
+ if __name__ == '__main__':
18
+ app.run(host='0.0.0.0',port=7860, debug=True)
constants.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from chromadb.config import Settings
4
+
5
+ load_dotenv()
6
+
7
+ # Define the folder for storing database
8
+ PERSIST_DIRECTORY = os.environ.get('PERSIST_DIRECTORY')
9
+
10
+ # Define the Chroma settings
11
+ CHROMA_SETTINGS = Settings(
12
+ chroma_db_impl='duckdb+parquet',
13
+ persist_directory=PERSIST_DIRECTORY,
14
+ anonymized_telemetry=False
15
+ )
ingest.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
9
+ from langchain.document_loaders import (
10
+ CSVLoader,
11
+ EverNoteLoader,
12
+ PDFMinerLoader,
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
+ from constants import CHROMA_SETTINGS
28
+
29
+
30
+ load_dotenv()
31
+
32
+
33
+ # Load environment variables
34
+ persist_directory = os.environ.get('PERSIST_DIRECTORY')
35
+ source_directory = os.environ.get('SOURCE_DIRECTORY', 'source_documents')
36
+ embeddings_model_name = os.environ.get('EMBEDDINGS_MODEL_NAME')
37
+ chunk_size = 500
38
+ chunk_overlap = 50
39
+
40
+
41
+ # Custom document loaders
42
+ class MyElmLoader(UnstructuredEmailLoader):
43
+ """Wrapper to fallback to text/plain when default does not work"""
44
+
45
+ def load(self) -> List[Document]:
46
+ """Wrapper adding fallback for elm without html"""
47
+ try:
48
+ try:
49
+ doc = UnstructuredEmailLoader.load(self)
50
+ except ValueError as e:
51
+ if 'text/html content not found in email' in str(e):
52
+ # Try plain text
53
+ self.unstructured_kwargs["content_source"]="text/plain"
54
+ doc = UnstructuredEmailLoader.load(self)
55
+ else:
56
+ raise
57
+ except Exception as e:
58
+ # Add file_path to exception message
59
+ raise type(e)(f"{self.file_path}: {e}") from e
60
+
61
+ return doc
62
+
63
+
64
+ # Map file extensions to document loaders and their arguments
65
+ LOADER_MAPPING = {
66
+ ".csv": (CSVLoader, {}),
67
+ # ".docx": (Docx2txtLoader, {}),
68
+ ".doc": (UnstructuredWordDocumentLoader, {}),
69
+ ".docx": (UnstructuredWordDocumentLoader, {}),
70
+ ".enex": (EverNoteLoader, {}),
71
+ ".eml": (MyElmLoader, {}),
72
+ ".epub": (UnstructuredEPubLoader, {}),
73
+ ".html": (UnstructuredHTMLLoader, {}),
74
+ ".md": (UnstructuredMarkdownLoader, {}),
75
+ ".odt": (UnstructuredODTLoader, {}),
76
+ ".pdf": (PDFMinerLoader, {}),
77
+ ".ppt": (UnstructuredPowerPointLoader, {}),
78
+ ".pptx": (UnstructuredPowerPointLoader, {}),
79
+ ".txt": (TextLoader, {"encoding": "utf8"}),
80
+ # Add more mappings for other file extensions and loaders as needed
81
+ }
82
+
83
+
84
+ def load_single_document(file_path: str) -> Document:
85
+ ext = "." + file_path.rsplit(".", 1)[-1]
86
+ if ext in LOADER_MAPPING:
87
+ loader_class, loader_args = LOADER_MAPPING[ext]
88
+ loader = loader_class(file_path, **loader_args)
89
+ return loader.load()[0]
90
+
91
+ raise ValueError(f"Unsupported file extension '{ext}'")
92
+
93
+
94
+ def load_documents(source_dir: str, ignored_files: List[str] = []) -> List[Document]:
95
+ """
96
+ Loads all documents from the source documents directory, ignoring specified files
97
+ """
98
+ all_files = []
99
+ for ext in LOADER_MAPPING:
100
+ all_files.extend(
101
+ glob.glob(os.path.join(source_dir, f"**/*{ext}"), recursive=True)
102
+ )
103
+ filtered_files = [file_path for file_path in all_files if file_path not in ignored_files]
104
+
105
+ with Pool(processes=os.cpu_count()) as pool:
106
+ results = []
107
+ with tqdm(total=len(filtered_files), desc='Loading new documents', ncols=80) as pbar:
108
+ for i, doc in enumerate(pool.imap_unordered(load_single_document, filtered_files)):
109
+ results.append(doc)
110
+ pbar.update()
111
+
112
+ return results
113
+
114
+ def process_documents(ignored_files: List[str] = []) -> List[Document]:
115
+ """
116
+ Load documents and split in chunks
117
+ """
118
+ print(f"Loading documents from {source_directory}")
119
+ documents = load_documents(source_directory, ignored_files)
120
+ if not documents:
121
+ print("No new documents to load")
122
+ exit(0)
123
+ print(f"Loaded {len(documents)} new documents from {source_directory}")
124
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
125
+ texts = text_splitter.split_documents(documents)
126
+ print(f"Split into {len(texts)} chunks of text (max. {chunk_size} tokens each)")
127
+ return texts
128
+
129
+ def does_vectorstore_exist(persist_directory: str) -> bool:
130
+ """
131
+ Checks if vectorstore exists
132
+ """
133
+ if os.path.exists(os.path.join(persist_directory, 'index')):
134
+ if os.path.exists(os.path.join(persist_directory, 'chroma-collections.parquet')) and os.path.exists(os.path.join(persist_directory, 'chroma-embeddings.parquet')):
135
+ list_index_files = glob.glob(os.path.join(persist_directory, 'index/*.bin'))
136
+ list_index_files += glob.glob(os.path.join(persist_directory, 'index/*.pkl'))
137
+ # At least 3 documents are needed in a working vectorstore
138
+ if len(list_index_files) > 3:
139
+ return True
140
+ return False
141
+
142
+ def main():
143
+ # Create embeddings
144
+ embeddings = HuggingFaceEmbeddings(model_name=embeddings_model_name)
145
+
146
+ if does_vectorstore_exist(persist_directory):
147
+ # Update and store locally vectorstore
148
+ print(f"Appending to existing vectorstore at {persist_directory}")
149
+ db = Chroma(persist_directory=persist_directory, embedding_function=embeddings, client_settings=CHROMA_SETTINGS)
150
+ collection = db.get()
151
+ texts = process_documents([metadata['source'] for metadata in collection['metadatas']])
152
+ print(f"Creating embeddings. May take some minutes...")
153
+ db.add_documents(texts)
154
+ else:
155
+ # Create and store locally vectorstore
156
+ print("Creating new vectorstore")
157
+ texts = process_documents()
158
+ print(f"Creating embeddings. May take some minutes...")
159
+ db = Chroma.from_documents(texts, embeddings, persist_directory=persist_directory, client_settings=CHROMA_SETTINGS)
160
+ db.persist()
161
+ db = None
162
+
163
+ print(f"Ingestion complete! You can now run privateGPT.py to query your documents")
164
+
165
+
166
+ if __name__ == "__main__":
167
+ main()
privateGPT.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from dotenv import load_dotenv
3
+ from langchain.chains import RetrievalQA
4
+ from langchain.embeddings import HuggingFaceEmbeddings
5
+ from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
6
+ from langchain.vectorstores import Chroma
7
+ from langchain.llms import GPT4All, LlamaCpp
8
+ import os
9
+ import argparse
10
+
11
+ load_dotenv()
12
+
13
+ embeddings_model_name = os.environ.get("EMBEDDINGS_MODEL_NAME")
14
+ persist_directory = os.environ.get('PERSIST_DIRECTORY')
15
+
16
+ model_type = os.environ.get('MODEL_TYPE')
17
+ model_path = os.environ.get('MODEL_PATH')
18
+ model_n_ctx = os.environ.get('MODEL_N_CTX')
19
+
20
+ from constants import CHROMA_SETTINGS
21
+
22
+ def main():
23
+ # Parse the command line arguments
24
+ args = parse_arguments()
25
+ embeddings = HuggingFaceEmbeddings(model_name=embeddings_model_name)
26
+ db = Chroma(persist_directory=persist_directory, embedding_function=embeddings, client_settings=CHROMA_SETTINGS)
27
+ retriever = db.as_retriever()
28
+ # activate/deactivate the streaming StdOut callback for LLMs
29
+ callbacks = [] if args.mute_stream else [StreamingStdOutCallbackHandler()]
30
+ # Prepare the LLM
31
+ match model_type:
32
+ case "LlamaCpp":
33
+ llm = LlamaCpp(model_path=model_path, n_ctx=model_n_ctx, callbacks=callbacks, verbose=False)
34
+ case "GPT4All":
35
+ llm = GPT4All(model=model_path, n_ctx=model_n_ctx, backend='gptj', callbacks=callbacks, verbose=False)
36
+ case _default:
37
+ print(f"Model {model_type} not supported!")
38
+ exit;
39
+ qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=retriever, return_source_documents= not args.hide_source)
40
+ # Interactive questions and answers
41
+ while True:
42
+ query = input("\nEnter a query: ")
43
+ if query == "exit":
44
+ break
45
+
46
+ # Get the answer from the chain
47
+ res = qa(query)
48
+ answer, docs = res['result'], [] if args.hide_source else res['source_documents']
49
+
50
+ # Print the result
51
+ print("\n\n> Question:")
52
+ print(query)
53
+ print("\n> Answer:")
54
+ print(answer)
55
+
56
+ # Print the relevant sources used for the answer
57
+ for document in docs:
58
+ print("\n> " + document.metadata["source"] + ":")
59
+ print(document.page_content)
60
+ return answer
61
+
62
+ def parse_arguments():
63
+ parser = argparse.ArgumentParser(description='privateGPT: Ask questions to your documents without an internet connection, '
64
+ 'using the power of LLMs.')
65
+ parser.add_argument("--hide-source", "-S", action='store_true',
66
+ help='Use this flag to disable printing of source documents used for answers.')
67
+
68
+ parser.add_argument("--mute-stream", "-M",
69
+ action='store_true',
70
+ help='Use this flag to disable the streaming StdOut callback for LLMs.')
71
+
72
+ return parser.parse_args()
73
+
74
+
75
+ if __name__ == "__main__":
76
+ main()
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ langchain==0.0.177
2
+ gpt4all==0.2.3
3
+ chromadb==0.3.23
4
+ llama-cpp-python==0.1.50
5
+ urllib3==2.0.2
6
+ pdfminer.six==20221105
7
+ python-dotenv==1.0.0
8
+ unstructured==0.6.6
9
+ extract-msg==0.41.1
10
+ tabulate==0.9.0
11
+ pandoc==2.3
12
+ pypandoc==1.11
13
+ tqdm==4.65.0
trueGPT.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ from langchain.chains import RetrievalQA
3
+ from langchain.embeddings import HuggingFaceEmbeddings
4
+ from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
5
+ from langchain.vectorstores import Chroma
6
+ from langchain.llms import GPT4All, LlamaCpp
7
+ import os
8
+ import argparse
9
+
10
+ load_dotenv()
11
+
12
+ embeddings_model_name = os.environ.get("EMBEDDINGS_MODEL_NAME")
13
+ persist_directory = os.environ.get('PERSIST_DIRECTORY')
14
+
15
+ model_type = os.environ.get('MODEL_TYPE')
16
+ model_path = os.environ.get('MODEL_PATH')
17
+ model_n_ctx = os.environ.get('MODEL_N_CTX')
18
+
19
+ from constants import CHROMA_SETTINGS
20
+
21
+ def get_response(user_input):
22
+ embeddings = HuggingFaceEmbeddings(model_name=embeddings_model_name)
23
+ db = Chroma(persist_directory=persist_directory, embedding_function=embeddings, client_settings=CHROMA_SETTINGS)
24
+ retriever = db.as_retriever()
25
+ # Activate/deactivate the streaming StdOut callback for LLMs
26
+ callbacks = []
27
+ # Prepare the LLM
28
+ match model_type:
29
+ case "LlamaCpp":
30
+ llm = LlamaCpp(model_path=model_path, n_ctx=model_n_ctx, callbacks=callbacks, verbose=False)
31
+ case "GPT4All":
32
+ llm = GPT4All(model=model_path, n_ctx=model_n_ctx, backend='gptj', callbacks=callbacks, verbose=False)
33
+ case _default:
34
+ print(f"Model {model_type} not supported!")
35
+ exit;
36
+
37
+ qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=retriever, return_source_documents=False)
38
+
39
+ # Get the answer from the chain
40
+ res = qa(user_input)
41
+ answer = res['result']
42
+
43
+ return answer