.gitattributes CHANGED
@@ -32,4 +32,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
32
  *.xz filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
 
 
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
32
  *.xz filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *.pdf filter=lfs diff=lfs merge=lfs -text
36
+ *.jsonl filter=lfs diff=lfs merge=lfs -text
37
  *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore CHANGED
@@ -1,3 +1,10 @@
 
 
 
 
 
 
1
  .venv/
2
- __pycache__/
3
- .env
 
 
1
+
2
+ .env
3
+ *.log
4
+ # *.pdf
5
+ *.DS_Store
6
+ .ragatouille/
7
  .venv/
8
+ db/
9
+ scripts/__pycache__
10
+ scripts/tmp_trainer
Dockerfile CHANGED
@@ -2,22 +2,24 @@
2
  FROM python:3.11.1
3
 
4
  # Set the working directory in the container
5
- WORKDIR /app
6
 
7
  # Install poetry
8
- # RUN pip3 install poetry==1.7.1
9
 
10
- # Copy the current directory contents into the container at /usr/src/app
11
- COPY . .
12
 
13
- # Install dependencies
14
- # RUN poetry config virtualenvs.create false \
15
- # && poetry install --no-interaction --no-ansi
16
- # Streamlit must be installed separately. Potentially this will cause an issue with dependencies in the future, but it's the only way it works.
17
- # RUN pip3 install streamlit
18
 
19
  # Install dependencies
20
- RUN pip3 install -r requirements.txt
 
 
 
 
21
 
22
  # Make a port available to the world outside this container
23
  # The EXPOSE instruction informs Docker that the container listens on the specified network ports at runtime. Your container needs to listen to Streamlit’s (default) port 8501.
@@ -27,7 +29,7 @@ EXPOSE 8501
27
  HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
28
 
29
  # An ENTRYPOINT allows you to configure a container that will run as an executable. Here, it also contains the entire streamlit run command for your app, so you don’t have to call it from the command line
30
- ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
31
 
32
  # Execute with:
33
  # docker build -t <image_name> .
 
2
  FROM python:3.11.1
3
 
4
  # Set the working directory in the container
5
+ WORKDIR /usr/src/app
6
 
7
  # Install poetry
8
+ RUN pip3 install poetry
9
 
10
+ # Copy only the necessary files for installing dependencies
11
+ COPY pyproject.toml poetry.lock ./
12
 
13
+ # Disable virtual environments creation by Poetry
14
+ # as the Docker container itself is an isolated environment
15
+ RUN poetry config virtualenvs.create false
 
 
16
 
17
  # Install dependencies
18
+ # RUN pip3 install -r requirements.txt
19
+ RUN poetry install
20
+
21
+ # Copy the current directory contents into the container at /usr/src/app
22
+ COPY . .
23
 
24
  # Make a port available to the world outside this container
25
  # The EXPOSE instruction informs Docker that the container listens on the specified network ports at runtime. Your container needs to listen to Streamlit’s (default) port 8501.
 
29
  HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
30
 
31
  # An ENTRYPOINT allows you to configure a container that will run as an executable. Here, it also contains the entire streamlit run command for your app, so you don’t have to call it from the command line
32
+ ENTRYPOINT ["streamlit", "run", "Start.py", "--server.port=8501", "--server.address=0.0.0.0"]
33
 
34
  # Execute with:
35
  # docker build -t <image_name> .
README.md CHANGED
@@ -8,4 +8,10 @@ pinned: false
8
  app_port: 8501
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
8
  app_port: 8501
9
  ---
10
 
11
+ # aerospace_chatbot
12
+ Aerospace discipline-specific chatbots and AI tools.
13
+
14
+ ## Dependencies
15
+ Dependencies are managed with [poetry](https://python-poetry.org/). Detailed install instructions are located [here](https://www.evernote.com/shard/s84/sh/f37de730-ce37-cd28-789c-86c3dc024a7c/90VLNref38KARua10p4am7IZkwsOxo93fXuBNqba-HpeIkMqGpRZrRkmjw)
16
+ * Once poetry is installed, run the following to install all dependencies: <code>poetry install</code>
17
+ * poetry.lock and pyproject.toml are committed to this directory and are the working dependencies.
Start.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+
4
+ # Set up page
5
+ st.set_page_config(
6
+ page_title="Aerospace Chatbot: AMS",
7
+ )
8
+ st.title("Aerospace Chatbot Homepage")
9
+ st.markdown("Code base: https://github.com/dsmueller3760/aerospace_chatbot/tree/rag_study")
10
+ st.markdown('---')
11
+ st.title("Chatbots")
12
+ st.markdown("""
13
+ Chatbots for aerospace mechanisms symposia, using all available papers published since 2000
14
+ * Modular version meant to study retrieval methods
15
+ """)
16
+ st.subheader("AMS")
17
+ '''
18
+ This chatbot will look up from all Aerospace Mechanism Symposia in the following location: https://github.com/dsmueller3760/aerospace_chatbot/tree/main/data/AMS
19
+ * Available models: https://platform.openai.com/docs/models
20
+ * Model parameters: https://platform.openai.com/docs/api-reference/chat/create
21
+ * Pinecone: https://docs.pinecone.io/docs/projects#api-keys
22
+ * OpenAI API: https://platform.openai.com/api-keys
23
+ '''
24
+
25
+ # # Establish secrets
26
+ # PINECONE_ENVIRONMENT=os.getenv('PINECONE_ENVIRONMENT')
27
+ # PINECONE_API_KEY=os.getenv('PINECONE_API_KEY')
config/config.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "databases": [
3
+ {
4
+ "name": "Pinecone",
5
+ "embedding_models": ["Openai", "Voyage"]
6
+ },
7
+ {
8
+ "name": "ChromaDB",
9
+ "embedding_models": ["Openai"]
10
+ },
11
+ {
12
+ "name": "RAGatouille",
13
+ "hf_rag_models": [
14
+ "colbert-ir/colbertv2.0"
15
+ ]
16
+ }
17
+ ],
18
+ "llms": [
19
+ {
20
+ "name": "OpenAI",
21
+ "models": [
22
+ "gpt-3.5-turbo-1106",
23
+ "gpt-3.5-turbo-instruct",
24
+ "gpt-4",
25
+ "gpt-4-32k",
26
+ "gpt-4-1106-preview"
27
+ ]
28
+ },
29
+ {
30
+ "name": "Hugging Face",
31
+ "models": [
32
+ "mistralai/Mixtral-8x7B-Instruct-v0.1",
33
+ "ai-aerospace/autotrain-ams_v0.1_100_Mistral-7B-Instruct-v0.1",
34
+ "meta-llama/Llama-2-7b-chat-hf"
35
+ ]
36
+ }
37
+ ],
38
+ "rag_types": [
39
+ "Standard",
40
+ "Parent-Child",
41
+ "Hypothetical Questions",
42
+ "Summaries"
43
+ ]
44
+ }
config/index_data.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "Pinecone": {
3
+ "Openai": "pinecone-openai-ams",
4
+ "Voyage": "pinecone-voyage-ams"
5
+ },
6
+ "ChromaDB": {
7
+ "Openai": "chromadb-openai-ams",
8
+ "Voyage": "chromadb-voyage-ams"
9
+ },
10
+ "RAGatouille": {
11
+ "colbert-ir/colbertv2.0": "RAGatouille-colbertv2.0-ams"
12
+ }
13
+ }
data/AMS/AMS_1996.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3626fd4a0769b8a73a12ee79a1bec7c264c541a5bf90df6f6c13c1ff00011b24
3
+ size 152158068
data/AMS/AMS_1997.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:34442bbc794415ea8d778ebd57e1dd368e20c5e6f65aff35fa008af54dbb900a
3
+ size 22719877
data/AMS/AMS_1998.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1523ca03cd1254b81cd0cb285182b7ac40208cba7932972ca00e0942e43f3539
3
+ size 122280718
data/AMS/AMS_1999.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1c631364761565d749e6bafb0ab1e84611e773ccdb640ab08f6b32b1fcc49e1e
3
+ size 27850919
data/AMS/AMS_2000.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ddf89c5cd9ddbd225e77198b19274535d4f003fdc20b5823239f51ad48230549
3
+ size 24061146
data/AMS/AMS_2001.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c63b2bba5a892759a7298097ee2388f353cc974285a73bfd8635d48af9f7d945
3
+ size 23264984
data/AMS/AMS_2002.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1b8b60c30ea9843face46e021a80bd1072901596b8e0f98a63601b31ecac2076
3
+ size 41615570
data/AMS/AMS_2004.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:986a7f046ba336d35d9d0db974931940543d612dad2c9bb6d5976d778777b659
3
+ size 28914300
data/AMS/AMS_2006.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:af4fb8e67c1ebf7b51fddd947d531d68ab05ff187fe915528811676ae0083d55
3
+ size 61039456
data/AMS/AMS_2008.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3d74dcd8ef68ae324f9246a35e1ccf538c4fd676d8b1ae733191c8ad6a055c90
3
+ size 31961158
data/AMS/AMS_2010.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:30d9ad0b75d0d41c75926dd97361f1548b79920df61d8d7486978d4b69a00ef6
3
+ size 30161812
data/AMS/AMS_2012.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e26a981f74c9d0c3526ad5152c83ad9fabde8f197f69cb24a0fd1d4004c1f026
3
+ size 31088140
data/AMS/AMS_2014.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:73dea6c8c45d0103404e3e3bd764e6efcd0f5bf5f45d505ce98e6c07528d9322
3
+ size 35199422
data/AMS/AMS_2016.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f7d8a0e558abd59b94abcbe013f263755f3c525eaf73702662293a3d8b5e2ec5
3
+ size 35244294
data/AMS/AMS_2018.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7b929f0c6d71116e23d4f52011e82eda07280aabb177300e37419ca38b047c60
3
+ size 30251124
data/AMS/AMS_2020.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cb6aaaa2cb700bc7d460a1f222756e6a795b629780087a477acd9713982fc0b9
3
+ size 45426669
data/AMS/AMS_2022.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ccc90819f501fca9445d415c1ceca8d3991300f8e08724cf7043f1a103aa4231
3
+ size 17636761
data/AMS/README.txt ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Documents are not uploaded to git. The list of documents which were uploaded to pinecone database AMS:
2
+ AMS_1996, https://ntrs.nasa.gov/citations/19960025595
3
+ AMS_1997, https://ntrs.nasa.gov/citations/19970021613
4
+ AMS_1998, https://ntrs.nasa.gov/citations/19980193156
5
+ AMS_1999, https://ntrs.nasa.gov/citations/19990053852
6
+ AMS_2000, https://ntrs.nasa.gov/citations/20000048380
7
+ AMS_2001, https://ntrs.nasa.gov/citations/20010071164
8
+ AMS_2002, https://ntrs.nasa.gov/citations/20020050182
9
+ AMS_2004, https://ntrs.nasa.gov/citations/20040084272
10
+ AMS_2006, https://ntrs.nasa.gov/citations/20060028221
11
+ AMS_2008, https://ntrs.nasa.gov/citations/20080023060
12
+ AMS_2010, https://ntrs.nasa.gov/citations/20100021914
13
+ AMS_2012, https://ntrs.nasa.gov/citations/20130008824
14
+ AMS_2014, https://ntrs.nasa.gov/citations/20140008875
15
+ AMS_2016, https://ntrs.nasa.gov/citations/20160004038
16
+ AMS_2018, https://ntrs.nasa.gov/citations/20180002828
17
+ AMS_2020, https://ntrs.nasa.gov/citations/20205009766
18
+ AMS_2022, https://ntrs.nasa.gov/citations/20220006415
data/AMS/ams_data-400-0-50.json ADDED
The diff for this file is too large to render. See raw diff
 
data/AMS/ams_data-400-0.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ef248f60645d1def4d3624351c90cbb5d91554d0a8bfd35615514f4a71a20159
3
+ size 18183603
data/AMS/ams_data-5000-0.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0472930c89ad2c13f997789b070049c99640c6ddcd114cc635110409854435b5
3
+ size 17283048
data_import.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import logging
4
+ import shutil
5
+ import string
6
+
7
+ import pinecone
8
+ import chromadb
9
+
10
+ import json, jsonlines
11
+ from tqdm import tqdm
12
+
13
+ from langchain_community.vectorstores import Pinecone
14
+ from langchain_community.vectorstores import Chroma
15
+
16
+ from langchain.text_splitter import RecursiveCharacterTextSplitter, CharacterTextSplitter
17
+
18
+ from langchain_openai import OpenAIEmbeddings
19
+ from langchain_community.embeddings import VoyageEmbeddings
20
+
21
+ from langchain_community.document_loaders import PyPDFLoader
22
+ from langchain_core.documents import Document as lancghain_Document
23
+
24
+ from ragatouille import RAGPretrainedModel
25
+
26
+ from dotenv import load_dotenv,find_dotenv
27
+ load_dotenv(find_dotenv(),override=True)
28
+
29
+ # Set secrets from environment file
30
+ OPENAI_API_KEY=os.getenv('OPENAI_API_KEY')
31
+ VOYAGE_API_KEY=os.getenv('VOYAGE_API_KEY')
32
+ PINECONE_API_KEY=os.getenv('PINECONE_API_KEY')
33
+ HUGGINGFACEHUB_API_TOKEN=os.getenv('HUGGINGFACEHUB_API_TOKEN')
34
+
35
+ def chunk_docs(docs,
36
+ chunk_method='tiktoken_recursive',
37
+ file=None,
38
+ chunk_size=500,
39
+ chunk_overlap=0,
40
+ use_json=False):
41
+ docs_out=[]
42
+ if file:
43
+ logging.info('Jsonl file to be used: '+file)
44
+ if use_json and os.path.exists(file):
45
+ logging.info('Jsonl file found, using this instead of parsing docs.')
46
+ with open(file, "r") as file_in:
47
+ file_data = [json.loads(line) for line in file_in]
48
+ # Process the file data and put it into the same format as docs_out
49
+ for line in file_data:
50
+ doc_temp = lancghain_Document(page_content=line['page_content'],
51
+ source=line['metadata']['source'],
52
+ page=line['metadata']['page'],
53
+ metadata=line['metadata'])
54
+ if has_meaningful_content(doc_temp):
55
+ docs_out.append(doc_temp)
56
+ logging.info('Parsed: '+file)
57
+ logging.info('Number of entries: '+str(len(docs_out)))
58
+ logging.info('Sample entries:')
59
+ logging.info(str(docs_out[0]))
60
+ logging.info(str(docs_out[-1]))
61
+ else:
62
+ logging.info('No jsonl found. Reading and parsing docs.')
63
+ logging.info('Chunk size (tokens): '+str(chunk_size))
64
+ logging.info('Chunk overlap (tokens): '+str(chunk_overlap))
65
+ for doc in tqdm(docs,desc='Reading and parsing docs'):
66
+ logging.info('Parsing: '+doc)
67
+ loader = PyPDFLoader(doc)
68
+ data = loader.load_and_split()
69
+
70
+ if chunk_method=='tiktoken_recursive':
71
+ text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
72
+ else:
73
+ raise NotImplementedError
74
+ pages = text_splitter.split_documents(data)
75
+
76
+ # Tidy up text by removing unnecessary characters
77
+ for page in pages:
78
+ page.metadata['source']=os.path.basename(page.metadata['source']) # Strip path
79
+ page.metadata['page']=int(page.metadata['page'])+1 # Pages are 0 based, update
80
+ page.page_content=re.sub(r"(\w+)-\n(\w+)", r"\1\2", page.page_content) # Merge hyphenated words
81
+ page.page_content = re.sub(r"(?<!\n\s)\n(?!\s\n)", " ", page.page_content.strip()) # Fix newlines in the middle of sentences
82
+ page.page_content = re.sub(r"\n\s*\n", "\n\n", page.page_content) # Remove multiple newlines
83
+ # Add metadata to the end of the page content, some RAG models don't have metadata.
84
+ page.page_content += str(page.metadata)
85
+ doc_temp=lancghain_Document(page_content=page.page_content,
86
+ source=page.metadata['source'],
87
+ page=page.metadata['page'],
88
+ metadata=page.metadata)
89
+ if has_meaningful_content(page):
90
+ docs_out.append(doc_temp)
91
+ logging.info('Parsed: '+doc)
92
+ logging.info('Sample entries:')
93
+ logging.info(str(docs_out[0]))
94
+ logging.info(str(docs_out[-1]))
95
+ if file:
96
+ # Write to a jsonl file, save it.
97
+ logging.info('Writing to jsonl file: '+file)
98
+ with jsonlines.open(file, mode='w') as writer:
99
+ for doc in docs_out:
100
+ writer.write(doc.dict())
101
+ logging.info('Written: '+file)
102
+ return docs_out
103
+ def load_docs(index_type,
104
+ docs,
105
+ query_model,
106
+ index_name=None,
107
+ chunk_method='tiktoken_recursive',
108
+ chunk_size=500,
109
+ chunk_overlap=0,
110
+ clear=False,
111
+ use_json=False,
112
+ file=None,
113
+ batch_size=50):
114
+ """
115
+ Loads PDF documents. If index_name is blank, it will return a list of the data (texts). If it is a name of a pinecone storage, it will return the vector_store.
116
+ """
117
+ # Chunk docs
118
+ docs_out=chunk_docs(docs,
119
+ chunk_method=chunk_method,
120
+ file=file,
121
+ chunk_size=chunk_size,
122
+ chunk_overlap=chunk_overlap,
123
+ use_json=use_json)
124
+ # Initialize client
125
+ db_path='../db/'
126
+ if index_name:
127
+ if index_type=="Pinecone":
128
+ # Import and initialize Pinecone client
129
+ pinecone.init(
130
+ api_key=PINECONE_API_KEY
131
+ )
132
+ # Find the existing index, clear for new start
133
+ if clear:
134
+ try:
135
+ pinecone.describe_index(index_name)
136
+ except:
137
+ raise Exception(f"Cannot clear index {index_name} because it does not exist.")
138
+ index=pinecone.Index(index_name)
139
+ index.delete(delete_all=True) # Clear the index first, then upload
140
+ logging.info('Cleared database '+index_name)
141
+ # Upsert docs
142
+ try:
143
+ pinecone.describe_index(index_name)
144
+ except:
145
+ logging.info(f"Index {index_name} does not exist. Creating new index.")
146
+ logging.info('Size of embedding used: '+str(embedding_size(query_model))) # TODO: set this to be backed out of the embedding size
147
+ pinecone.create_index(index_name,dimension=embedding_size(query_model))
148
+ logging.info(f"Index {index_name} created. Adding {len(docs_out)} entries to index.")
149
+ pass
150
+ else:
151
+ logging.info(f"Index {index_name} exists. Adding {len(docs_out)} entries to index.")
152
+ index = pinecone.Index(index_name)
153
+ vectorstore = Pinecone(index, query_model, "page_content") # Set the vector store to calculate embeddings on page_content
154
+ vectorstore = batch_upsert(index_type,
155
+ vectorstore,
156
+ docs_out,
157
+ batch_size=batch_size)
158
+ elif index_type=="ChromaDB":
159
+ # Upsert docs. Defaults to putting this in the ../db directory
160
+ logging.info(f"Creating new index {index_name}.")
161
+ persistent_client = chromadb.PersistentClient(path=db_path+'/chromadb')
162
+ vectorstore = Chroma(client=persistent_client,
163
+ collection_name=index_name,
164
+ embedding_function=query_model)
165
+ logging.info(f"Index {index_name} created. Adding {len(docs_out)} entries to index.")
166
+ vectorstore = batch_upsert(index_type,
167
+ vectorstore,
168
+ docs_out,
169
+ batch_size=batch_size)
170
+ logging.info("Documents upserted to f{index_name}.")
171
+ # Test query
172
+ test_query = vectorstore.similarity_search('What are examples of aerosapce adhesives to avoid?')
173
+ logging.info('Test query: '+str(test_query))
174
+ if not test_query:
175
+ raise ValueError("Chroma vector database is not configured properly. Test query failed.")
176
+ elif index_type=="RAGatouille":
177
+ logging.info(f'Setting up RAGatouille model {query_model}')
178
+ vectorstore = RAGPretrainedModel.from_pretrained(query_model)
179
+ logging.info('RAGatouille model set: '+str(vectorstore))
180
+
181
+ # Create an index from the vectorstore.
182
+ docs_out_colbert = [doc.page_content for doc in docs_out]
183
+ if chunk_size>500:
184
+ raise ValueError("RAGatouille cannot handle chunks larger than 500 tokens. Reduce token count.")
185
+ vectorstore.index(
186
+ collection=docs_out_colbert,
187
+ index_name=index_name,
188
+ max_document_length=chunk_size,
189
+ overwrite_index=True,
190
+ split_documents=True,
191
+ )
192
+ logging.info(f"Index created: {vectorstore}")
193
+
194
+ # Move the directory to the db folder
195
+ logging.info(f"Moving RAGatouille index to {db_path}")
196
+ ragatouille_path = os.path.join(db_path, '.ragatouille')
197
+ if os.path.exists(ragatouille_path):
198
+ shutil.rmtree(ragatouille_path)
199
+ logging.info(f"RAGatouille index deleted from {ragatouille_path}")
200
+ shutil.move('./.ragatouille', db_path)
201
+ logging.info(f"RAGatouille index created in {db_path}:"+str(vectorstore))
202
+
203
+ # Return vectorstore or docs
204
+ if index_name:
205
+ return vectorstore
206
+ else:
207
+ return docs_out
208
+ def delete_index(index_type,index_name):
209
+ """
210
+ Deletes an existing Pinecone index with the given index_name.
211
+ """
212
+ if index_type=="Pinecone":
213
+ # Import and initialize Pinecone client
214
+ pinecone.init(
215
+ api_key=PINECONE_API_KEY
216
+ )
217
+ try:
218
+ pinecone.describe_index(index_name)
219
+ logging.info(f"Index {index_name} exists.")
220
+ except:
221
+ raise Exception(f"Index {index_name} does not exist, cannot delete.")
222
+ else:
223
+ pinecone.delete_index(index_name)
224
+ logging.info(f"Index {index_name} deleted.")
225
+ elif index_type=="ChromaDB":
226
+ # Delete existing collection
227
+ logging.info(f"Deleting index {index_name}.")
228
+ persistent_client = chromadb.PersistentClient(path='../db/chromadb')
229
+ persistent_client.delete_collection(name=index_name)
230
+ logging.info("Index deleted.")
231
+ elif index_type=="RAGatouille":
232
+ raise NotImplementedError
233
+ def batch_upsert(index_type,vectorstore,docs_out,batch_size=50):
234
+ # Batch insert the chunks into the vector store
235
+ for i in range(0, len(docs_out), batch_size):
236
+ chunk_batch = docs_out[i:i + batch_size]
237
+ if index_type=="Pinecone":
238
+ vectorstore.add_documents(chunk_batch)
239
+ elif index_type=="ChromaDB":
240
+ vectorstore.add_documents(chunk_batch) # Happens to be same for chroma/pinecone, leaving if statement just in case
241
+ return vectorstore
242
+ def has_meaningful_content(page):
243
+ """
244
+ Test whether the page has more than 30% words and is more than 5 words.
245
+ """
246
+ text=page.page_content
247
+ num_words = len(text.split())
248
+ alphanumeric_pct = sum(c.isalnum() for c in text) / len(text)
249
+ if num_words < 5 or alphanumeric_pct < 0.3:
250
+ return False
251
+ else:
252
+ return True
253
+ def embedding_size(embedding_model):
254
+ """
255
+ Returns the embedding size of the model.
256
+ """
257
+ if isinstance(embedding_model,OpenAIEmbeddings):
258
+ return 1536 # https://platform.openai.com/docs/models/embeddings, test-embedding-ada-002
259
+ elif isinstance(embedding_model,VoyageEmbeddings):
260
+ return 1024 # https://docs.voyageai.com/embeddings/, voyage-02
261
+ else:
262
+ raise NotImplementedError
pages/1_Chatbot_AMS_Langchain.py DELETED
@@ -1,152 +0,0 @@
1
- import os
2
- import queries
3
- import pinecone
4
- from dotenv import load_dotenv, find_dotenv
5
- from langchain.embeddings import OpenAIEmbeddings
6
- from langchain.llms import OpenAI
7
- import streamlit as st
8
- import openai
9
- import time
10
-
11
- from dotenv import load_dotenv,find_dotenv,dotenv_values
12
- load_dotenv(find_dotenv(),override=True)
13
-
14
- # Set secrets
15
- # PINECONE_ENVIRONMENT=db.secrets.get('PINECONE_ENVIRONMENT')
16
- # PINECONE_API_KEY=db.secrets.get('PINECONE_API_KEY')
17
- PINECONE_ENVIRONMENT=os.getenv('PINECONE_ENVIRONMENT')
18
- PINECONE_API_KEY=os.getenv('PINECONE_API_KEY')
19
-
20
- # Set the page title
21
- st.set_page_config(
22
- page_title='Aerospace Chatbot: AMS w/Langchain',
23
- )
24
- st.title('Aerospace Mechanisms Chatbot')
25
- with st.expander('''What's under the hood?'''):
26
- st.markdown('''
27
- This chatbot will look up from all Aerospace Mechanism Symposia in the following location: https://github.com/dsmueller3760/aerospace_chatbot/tree/main/data/AMS
28
- * Source code: https://github.com/dsmueller3760/aerospace_chatbot/blob/main/scripts/setup_page_langchain.py
29
- * Uses custom langchain functions with QA retrieval: https://js.langchain.com/docs/modules/chains/popular/chat_vector_db_legacy
30
- * All prompts will query entire database unless 'filter response with last received sources' is activated.
31
- * **Repsonse time ~10 seconds per prompt**.
32
- ''')
33
- filter_toggle=st.checkbox('Filter response with last received sources?')
34
-
35
- # Add a sidebar for input options
36
- st.title('Input')
37
-
38
- # Add input fields in the sidebar
39
- st.sidebar.title('Input options')
40
- output_level = st.sidebar.selectbox('Level of Output', ['Concise', 'Detailed'], index=1)
41
- k = st.sidebar.number_input('Number of items per prompt', min_value=1, step=1, value=4)
42
- search_type = st.sidebar.selectbox('Search Type', ['similarity', 'mmr'], index=1)
43
- temperature = st.sidebar.slider('Temperature', min_value=0.0, max_value=2.0, value=0.0, step=0.1)
44
- verbose = st.sidebar.checkbox('Verbose output')
45
- chain_type = st.sidebar.selectbox('Chain Type', ['stuff', 'map_reduce'], index=0)
46
-
47
- # Vector databases
48
- st.sidebar.title('Vector database')
49
- index_type=st.sidebar.selectbox('Index type', ['Pinecone'], index=0)
50
- index_name=st.sidebar.selectbox('Index name', ['canopy--ams'], index=0)
51
-
52
- # Embeddings
53
- st.sidebar.title('Embeddings')
54
- embedding_type=st.sidebar.selectbox('Embedding type', ['Openai'], index=0)
55
- embedding_name=st.sidebar.selectbox('Embedding name', ['text-embedding-ada-002'], index=0)
56
-
57
- # Add a section for secret keys
58
- st.sidebar.title('Secret keys')
59
- OPENAI_API_KEY = st.sidebar.text_input('OpenAI API Key', type='password')
60
-
61
- # Pinecone
62
- pinecone.init(
63
- api_key=PINECONE_API_KEY,
64
- environment=PINECONE_ENVIRONMENT
65
- )
66
-
67
- if OPENAI_API_KEY:
68
- openai.api_key = OPENAI_API_KEY
69
- embeddings_model = OpenAIEmbeddings(model=embedding_name,openai_api_key=OPENAI_API_KEY)
70
-
71
- # Set up chat history
72
- qa_model_obj = st.session_state.get('qa_model_obj',[])
73
- message_id = st.session_state.get('message_id', 0)
74
-
75
- if 'messages' not in st.session_state:
76
- st.session_state.messages = []
77
- for message in st.session_state.messages:
78
- with st.chat_message(message['role']):
79
- st.markdown(message['content'])
80
-
81
- # Process some items
82
- if output_level == 'Concise':
83
- out_token = 50
84
- else:
85
- out_token = 516
86
-
87
- # Define LLM parameters and qa model object
88
- llm = OpenAI(temperature=temperature,
89
- openai_api_key=OPENAI_API_KEY,
90
- max_tokens=out_token)
91
- qa_model_obj=queries.QA_Model(index_name,
92
- embeddings_model,
93
- llm,
94
- k,
95
- search_type,
96
- verbose,
97
- filter_arg=False)
98
-
99
- # Display assistant response in chat message container
100
- if prompt := st.chat_input('Prompt here'):
101
- st.session_state.messages.append({'role': 'user', 'content': prompt})
102
- with st.chat_message('user'):
103
- st.markdown(prompt)
104
- with st.chat_message('assistant'):
105
- message_placeholder = st.empty()
106
-
107
- with st.status('Generating response...') as status:
108
- t_start=time.time()
109
-
110
- # Process some items
111
- if output_level == 'Concise':
112
- out_token = 50
113
- else:
114
- out_token = 516
115
-
116
- # Define LLM parameters and qa model object
117
- llm = OpenAI(temperature=temperature,
118
- openai_api_key=OPENAI_API_KEY,
119
- max_tokens=out_token)
120
-
121
- message_id += 1
122
- st.write('Message: '+str(message_id))
123
-
124
- if message_id>1:
125
- qa_model_obj=st.session_state['qa_model_obj']
126
- qa_model_obj.update_model(llm,
127
- k=k,
128
- search_type=search_type,
129
- verbose=verbose,
130
- filter_arg=filter_toggle)
131
- if filter_toggle:
132
- filter_list = list(set(item['source'] for item in qa_model_obj.sources[-1]))
133
- filter_items=[]
134
- for item in filter_list:
135
- filter_item={'source': item}
136
- filter_items.append(filter_item)
137
- filter={'$or':filter_items}
138
-
139
- st.write('Searching vector database, generating prompt...')
140
- qa_model_obj.query_docs(prompt)
141
- ai_response=qa_model_obj.result['answer']
142
- message_placeholder.markdown(ai_response)
143
- t_delta=time.time() - t_start
144
- status.update(label='Prompt generated in '+"{:10.3f}".format(t_delta)+' seconds', state='complete', expanded=False)
145
-
146
- st.session_state['qa_model_obj'] = qa_model_obj
147
- st.session_state['message_id'] = message_id
148
- st.session_state.messages.append({'role': 'assistant', 'content': ai_response})
149
-
150
- else:
151
- st.warning('No API key found. Add your API key in the sidebar under Secret Keys. Find it or create one here: https://platform.openai.com/api-keys')
152
- st.info('Your API-key is not stored in any form by this app. However, for transparency it is recommended to delete your API key once used.')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/1_Chatbot_AMS_Modular.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import queries, setup
2
+
3
+ import os
4
+ import time
5
+ import logging
6
+ import json
7
+
8
+ import pinecone
9
+ import openai
10
+
11
+ from langchain_community.vectorstores import Pinecone
12
+ from langchain_community.vectorstores import Chroma
13
+
14
+ from langchain_openai import OpenAIEmbeddings
15
+ from langchain_community.embeddings import VoyageEmbeddings
16
+
17
+ from langchain_openai import OpenAI, ChatOpenAI
18
+ from langchain_community.llms import HuggingFaceHub
19
+
20
+ from ragatouille import RAGPretrainedModel
21
+
22
+ import streamlit as st
23
+
24
+ # Set up the page, enable logging
25
+ from dotenv import load_dotenv,find_dotenv
26
+ load_dotenv(find_dotenv(),override=True)
27
+ logging.basicConfig(filename='app_1_chatbot_ams_modular.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s', level=logging.DEBUG)
28
+
29
+ # Set the page title
30
+ st.set_page_config(
31
+ page_title='Aerospace Chatbot: Modular',
32
+ )
33
+ st.title('Aerospace Mechanisms Chatbot')
34
+ with st.expander('''What's under the hood?'''):
35
+ st.markdown('''
36
+ This chatbot will look up from all Aerospace Mechanism Symposia in the following location: https://github.com/dsmueller3760/aerospace_chatbot/tree/main/data/AMS
37
+ Example questions:
38
+ * What are examples of latch failures which have occurred due to improper fitup?
39
+ * What are examples of lubricants which should be avoided for space mechanism applications?
40
+ ''')
41
+ filter_toggle=st.checkbox('Filter response with last received sources?')
42
+
43
+ sb=setup.load_sidebar(config_file='../config/config.json',
44
+ index_data_file='../config/index_data.json',
45
+ vector_databases=True,
46
+ embeddings=True,
47
+ rag_type=True,
48
+ index_name=True,
49
+ llm=True,
50
+ model_options=True,
51
+ secret_keys=True)
52
+
53
+ secrets=setup.set_secrets(sb) # Take secrets from .env file first, otherwise from sidebar
54
+
55
+ # Set up chat history
56
+ if 'qa_model_obj' not in st.session_state:
57
+ st.session_state.qa_model_obj = []
58
+ if 'message_id' not in st.session_state:
59
+ st.session_state.message_id = 0
60
+ if 'messages' not in st.session_state:
61
+ st.session_state.messages = []
62
+ for message in st.session_state.messages:
63
+ with st.chat_message(message['role']):
64
+ st.markdown(message['content'])
65
+
66
+ # Define chat
67
+ if prompt := st.chat_input('Prompt here'):
68
+ # User prompt
69
+ st.session_state.messages.append({'role': 'user', 'content': prompt})
70
+ with st.chat_message('user'):
71
+ st.markdown(prompt)
72
+ # Assistant response
73
+ with st.chat_message('assistant'):
74
+ message_placeholder = st.empty()
75
+
76
+ with st.status('Generating response...') as status:
77
+ t_start=time.time()
78
+
79
+ st.session_state.message_id += 1
80
+ st.write('Starting reponse generation for message: '+str(st.session_state.message_id))
81
+ logging.info('Starting reponse generation for message: '+str(st.session_state.message_id))
82
+
83
+ # Process some items
84
+ if sb['model_options']['output_level'] == 'Concise':
85
+ out_token = 50
86
+ else:
87
+ out_token = 516
88
+ logging.info('Output tokens: '+str(out_token))
89
+
90
+ if st.session_state.message_id==1:
91
+ # Define embeddings
92
+ if sb['query_model']=='Openai':
93
+ query_model=OpenAIEmbeddings(model=sb['embedding_name'],openai_api_key=secrets['OPENAI_API_KEY'])
94
+ elif sb['query_model']=='Voyage':
95
+ query_model=VoyageEmbeddings(model=sb['embedding_name'],voyage_api_key=secrets['VOYAGE_API_KEY'])
96
+ elif sb['index_type']=='RAGatouille':
97
+ query_model=RAGPretrainedModel.from_index('../db/.ragatouille/colbert/indexes/'+sb['index_name'])
98
+ logging.info('Query model set: '+str(query_model))
99
+
100
+ # Define LLM
101
+ if sb['llm_source']=='OpenAI':
102
+ llm = ChatOpenAI(model_name=sb['llm_model'],
103
+ temperature=sb['model_options']['temperature'],
104
+ openai_api_key=secrets['OPENAI_API_KEY'],
105
+ max_tokens=out_token)
106
+ elif sb['llm_source']=='Hugging Face':
107
+ llm = HuggingFaceHub(repo_id=sb['llm_model'],
108
+ model_kwargs={"temperature": sb['model_options']['temperature'], "max_length": out_token})
109
+ logging.info('LLM model set: '+str(llm))
110
+
111
+ # Initialize QA model object
112
+ if 'search_type' in sb['model_options']:
113
+ search_type=sb['model_options']['search_type']
114
+ else:
115
+ search_type=None
116
+ st.session_state.qa_model_obj=queries.QA_Model(sb['index_type'],
117
+ sb['index_name'],
118
+ query_model,
119
+ llm,
120
+ k=sb['model_options']['k'],
121
+ search_type=search_type,
122
+ filter_arg=False)
123
+ logging.info('QA model object set: '+str(st.session_state.qa_model_obj))
124
+ if st.session_state.message_id>1:
125
+ logging.info('Updating model with sidebar settings...')
126
+ # Update LLM
127
+ if sb['llm_source']=='OpenAI':
128
+ llm = ChatOpenAI(model_name=sb['llm_model'],
129
+ temperature=sb['model_options']['temperature'],
130
+ openai_api_key=secrets['OPENAI_API_KEY'],
131
+ max_tokens=out_token)
132
+ elif sb['llm_source']=='Hugging Face':
133
+ llm = HuggingFaceHub(repo_id=sb['llm_model'],
134
+ model_kwargs={"temperature": sb['model_options']['temperature'], "max_length": out_token})
135
+ logging.info('LLM model set: '+str(llm))
136
+
137
+ st.session_state.qa_model_obj.update_model(llm,
138
+ k=sb['model_options']['k'],
139
+ search_type=sb['model_options']['search_type'],
140
+ filter_arg=filter_toggle)
141
+ logging.info('QA model object updated: '+str(st.session_state.qa_model_obj))
142
+
143
+ st.write('Searching vector database, generating prompt...')
144
+ logging.info('Searching vector database, generating prompt...')
145
+ st.session_state.qa_model_obj.query_docs(prompt)
146
+ ai_response=st.session_state.qa_model_obj.result['answer'].content
147
+ message_placeholder.markdown(ai_response)
148
+ t_delta=time.time() - t_start
149
+ status.update(label='Prompt generated in '+"{:10.3f}".format(t_delta)+' seconds', state='complete', expanded=False)
150
+
151
+ st.session_state.messages.append({'role': 'assistant', 'content': ai_response})
152
+ logging.info(f'Messaging complete for {st.session_state.message_id}.')
153
+
154
+ # Add reset button
155
+ if st.button('Restart session'):
156
+ st.session_state.qa_model_obj = []
157
+ st.session_state.message_id = 0
158
+ st.session_state.messages = []
pages/2_Chatbot_AMS_Canopy.py DELETED
@@ -1,157 +0,0 @@
1
- import os
2
- import queries
3
- import pinecone
4
- from langchain.embeddings import OpenAIEmbeddings
5
- from langchain.llms import OpenAI
6
- import streamlit as st
7
- import openai
8
- import time
9
-
10
- from tqdm.auto import tqdm
11
- from typing import Tuple
12
-
13
- # from dotenv import load_dotenv,find_dotenv,dotenv_values
14
- # load_dotenv(find_dotenv(),override=True)
15
-
16
- from canopy.tokenizer import Tokenizer
17
- from canopy.knowledge_base import KnowledgeBase
18
- from canopy.context_engine import ContextEngine
19
- from canopy.chat_engine import ChatEngine
20
- from canopy.llm.openai import OpenAILLM
21
- # from canopy.llm.models import ModelParams
22
- from canopy.models.data_models import Document, Messages, UserMessage, AssistantMessage
23
- from canopy.models.api_models import ChatResponse
24
-
25
- def chat(new_message: str, history: Messages) -> Tuple[str, Messages, ChatResponse]:
26
- messages = history + [UserMessage(content=new_message)]
27
- response = chat_engine.chat(messages)
28
- assistant_response = response.choices[0].message.content
29
- return assistant_response, messages + [AssistantMessage(content=assistant_response)], response
30
-
31
- # Set secrets
32
- # PINECONE_ENVIRONMENT=db.secrets.get('PINECONE_ENVIRONMENT')
33
- # PINECONE_API_KEY=db.secrets.get('PINECONE_API_KEY')
34
- PINECONE_ENVIRONMENT=os.getenv('PINECONE_ENVIRONMENT')
35
- PINECONE_API_KEY=os.getenv('PINECONE_API_KEY')
36
-
37
- # Set the page title
38
- st.set_page_config(
39
- page_title='Aerospace Chatbot: AMS w/Langchain',
40
- )
41
- st.title('Aerospace Mechanisms Chatbot')
42
- with st.expander('''What's under the hood?'''):
43
- st.markdown('''
44
- This chatbot will look up from all Aerospace Mechanism Symposia in the following location: https://github.com/dsmueller3760/aerospace_chatbot/tree/main/data/AMS
45
- * Source code: https://github.com/dsmueller3760/aerospace_chatbot/blob/main/scripts/setup_page_canopy.py
46
- * Uses pinecone canopy: https://www.pinecone.io/blog/canopy-rag-framework/
47
- * **Response time ~45 seconds per prompt**
48
- ''')
49
-
50
- # Add a sidebar for input options
51
- st.title('Input')
52
- st.sidebar.title('Input Options')
53
-
54
- # Add input fields in the sidebar
55
- model_name=st.sidebar.selectbox('Model', ['gpt-3.5-turbo''gpt-3.5-turbo-16k','gpt-3.5-turbo','gpt-3.5-turbo-1106','gpt-4','gpt-4-32k'], index=1)
56
- model_list={'gpt-3.5-turbo':4096,
57
- 'gpt-3.5-turbo-16k':16385,
58
- 'gpt-3.5-turbo-1106':16385,
59
- 'gpt-4':8192,
60
- 'gpt-4-32k':32768}
61
- temperature = st.sidebar.slider('Temperature', min_value=0.0, max_value=2.0, value=0.0, step=0.1)
62
- n=None # Not used. How many chat completion choices to generate for each input message.
63
- top_p=None # Not used. Only use this or temperature. Where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.
64
-
65
- k=st.sidebar.number_input('Number document chunks per query', min_value=1, step=1, value=15)
66
- output_level=st.sidebar.selectbox('Level of Output', ['Concise', 'Detailed', 'No Limit'], index=2)
67
- max_prompt_tokens=model_list[model_name]
68
-
69
- # Vector databases
70
- st.sidebar.title('Vector Database')
71
- index_name=st.sidebar.selectbox('Index name', ['canopy--ams'], index=0)
72
-
73
- # Embeddings
74
- st.sidebar.title('Embeddings')
75
- embedding_type=st.sidebar.selectbox('Embedding type', ['Openai'], index=0)
76
- embedding_name=st.sidebar.selectbox('Embedding name', ['text-embedding-ada-002'], index=0)
77
-
78
- # Add a section for secret keys
79
- st.sidebar.title('Secret Keys')
80
- OPENAI_API_KEY = st.sidebar.text_input('OpenAI API Key', type='password')
81
-
82
-
83
-
84
- if OPENAI_API_KEY:
85
- openai.api_key = OPENAI_API_KEY
86
- embeddings_model = OpenAIEmbeddings(model=embedding_name,openai_api_key=OPENAI_API_KEY)
87
-
88
- # Set up chat history
89
- qa_model_obj = st.session_state.get('qa_model_obj',[])
90
- message_id = st.session_state.get('message_id', 0)
91
- history = st.session_state.get('history',[])
92
-
93
- if 'messages' not in st.session_state:
94
- st.session_state.messages = []
95
- for message in st.session_state.messages:
96
- with st.chat_message(message['role']):
97
- st.markdown(message['content'])
98
-
99
- # Process some items
100
- if output_level == 'Concise':
101
- out_token = 50
102
- else:
103
- out_token = 516
104
-
105
- # Display assistant response in chat message container
106
- if prompt := st.chat_input('Prompt here'):
107
- st.session_state.messages.append({'role': 'user', 'content': prompt})
108
- with st.chat_message('user'):
109
- st.markdown(prompt)
110
- with st.chat_message('assistant'):
111
- message_placeholder = st.empty()
112
-
113
- with st.status('Generating response...') as status:
114
- t_start=time.time()
115
- message_id += 1
116
- st.write('Message: '+str(message_id))
117
-
118
- # Process some items
119
- if output_level == 'Concise':
120
- max_generated_tokens = 50
121
- elif output_level == 'Detailed':
122
- max_generated_tokens = 516
123
- else:
124
- max_generated_tokens = None
125
-
126
- # Inialize canopy
127
- Tokenizer.initialize()
128
- pinecone.init(
129
- api_key=PINECONE_API_KEY,
130
- environment=PINECONE_ENVIRONMENT
131
- )
132
-
133
- kb = KnowledgeBase(index_name=index_name,
134
- default_top_k=k)
135
- kb.connect()
136
- context_engine = ContextEngine(kb)
137
- llm=OpenAILLM(model_name=model_name)
138
- chat_engine = ChatEngine(context_engine,
139
- llm=llm,
140
- max_generated_tokens=max_generated_tokens,
141
- max_prompt_tokens=max_prompt_tokens)
142
-
143
- st.write('Searching vector database, generating prompt...')
144
- response, history, chat_response = chat(prompt, history)
145
-
146
- message_placeholder.markdown(response)
147
- t_delta=time.time() - t_start
148
- status.update(label='Prompt generated in '+"{:10.3f}".format(t_delta)+' seconds', state='complete', expanded=False)
149
-
150
- st.session_state['history'] = history
151
- st.session_state['qa_model_obj'] = qa_model_obj
152
- st.session_state['message_id'] = message_id
153
- st.session_state.messages.append({'role': 'assistant', 'content': response})
154
-
155
- else:
156
- st.warning('No API key found. Add your API key in the sidebar under Secret Keys. Find it or create one here: https://platform.openai.com/api-keys')
157
- st.info('Your API-key is not stored in any form by this app. However, for transparency it is recommended to delete your API key once used.')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/2_Document_Upload.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import data_import, setup
2
+
3
+ import os
4
+ import time
5
+ import logging
6
+ import glob
7
+
8
+ from langchain_openai import OpenAIEmbeddings
9
+ from langchain_community.embeddings import VoyageEmbeddings
10
+
11
+ from ragatouille import RAGPretrainedModel
12
+
13
+ import streamlit as st
14
+
15
+ # Set up the page, enable logging
16
+ from dotenv import load_dotenv,find_dotenv
17
+ load_dotenv(find_dotenv(),override=True)
18
+ logging.basicConfig(filename='app_2_document_upload.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s', level=logging.DEBUG)
19
+
20
+ # Set the page title
21
+ st.set_page_config(
22
+ page_title='Upload PDFs',
23
+ )
24
+ st.title('Upload PDFs')
25
+
26
+ sb=setup.load_sidebar(config_file='../config/config.json',
27
+ index_data_file='../config/index_data.json',
28
+ vector_databases=True,
29
+ embeddings=True,
30
+ index_name=True,
31
+ secret_keys=True)
32
+
33
+ secrets=setup.set_secrets(sb) # Take secrets from .env file first, otherwise from sidebar
34
+
35
+ # Populate the main screen
36
+ logging.info(f'index_type test, {sb["index_type"]}')
37
+
38
+ if sb["index_type"]=='RAGatouille':
39
+ logging.info('Set hugging face model for queries.')
40
+ query_model=sb['query_model']
41
+ elif sb['query_model']=='Openai' or 'Voyage':
42
+ logging.info('Set embeddings model for queries.')
43
+ if sb['query_model']=='Openai':
44
+ query_model=OpenAIEmbeddings(model=sb['embedding_name'],openai_api_key=secrets['OPENAI_API_KEY'])
45
+ elif sb['query_model']=='Voyage':
46
+ query_model=VoyageEmbeddings(voyage_api_key=secrets['VOYAGE_API_KEY'])
47
+ logging.info('Query model set: '+str(query_model))
48
+
49
+ # Find docs
50
+ index_name_md=st.markdown('Enter a directory relative to the current directory, or an absolute path.')
51
+ data_folder = st.text_input('Enter a directory','../data/AMS/')
52
+ if not os.path.isdir(data_folder):
53
+ st.error('The entered directory does not exist')
54
+ docs = glob.glob(data_folder+'*.pdf') # Only get the PDFs in the directory
55
+ st.markdown('PDFs found: '+str(docs))
56
+ st.markdown('Number of PDFs found: ' + str(len(docs)))
57
+ logging.info('Docs: '+str(docs))
58
+
59
+ # Add an expandable box for options
60
+ with st.expander("Options"):
61
+ use_json = st.checkbox('Use existing jsonl, if available (will ignore chunk method, size, and overlap)?', value=True)
62
+ json_file=st.text_input('Jsonl file',data_folder+'ams_data.jsonl')
63
+ clear_database = st.checkbox('Clear existing database?')
64
+ chunk_method= st.selectbox('Chunk method', ['tiktoken_recursive'], index=0)
65
+ if sb['query_model']=='Openai' or 'ChromaDB':
66
+ # OpenAI will time out if the batch size is too large
67
+ batch_size=st.number_input('Batch size for upsert', min_value=1, step=1, value=100)
68
+ else:
69
+ batch_size=None
70
+ if chunk_method=='tiktoken_recursive':
71
+ chunk_size=st.number_input('Chunk size (tokens)', min_value=1, step=1, value=500)
72
+ chunk_overlap=st.number_input('Chunk overlap (tokens)', min_value=0, step=1, value=0)
73
+ else:
74
+ raise NotImplementedError
75
+
76
+ # Add a button to run the function
77
+ if st.button('Chunk docs to jsonl file'):
78
+ start_time = time.time() # Start the timer
79
+ data_import.chunk_docs(docs,
80
+ file=json_file,
81
+ chunk_method=chunk_method,
82
+ chunk_size=chunk_size,
83
+ chunk_overlap=chunk_overlap,
84
+ use_json=False)
85
+ end_time = time.time() # Stop the timer
86
+ elapsed_time = end_time - start_time
87
+ st.write(f"Elapsed Time: {elapsed_time:.2f} seconds")
88
+ if st.button('Load docs into vector database'):
89
+ start_time = time.time() # Start the timer
90
+ data_import.load_docs(sb['index_type'],
91
+ docs,
92
+ query_model=query_model,
93
+ index_name=sb['index_name'],
94
+ chunk_size=chunk_size,
95
+ chunk_overlap=chunk_overlap,
96
+ use_json=use_json,
97
+ clear=clear_database,
98
+ file=json_file,
99
+ batch_size=batch_size)
100
+ end_time = time.time() # Stop the timer
101
+ elapsed_time = end_time - start_time
102
+ st.write(f"Elapsed Time: {elapsed_time:.2f} seconds")
103
+ # Add a button to delete the index
104
+ if st.button('Delete existing index'):
105
+ start_time = time.time() # Start the timer
106
+ data_import.delete_index(sb['index_type'],sb['index_name'])
107
+ end_time = time.time() # Stop the timer
108
+ elapsed_time = end_time - start_time
109
+ st.write(f"Elapsed Time: {elapsed_time:.2f} seconds")
pages/3_Visualize_Data.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import setup
2
+
3
+ import time
4
+ import logging
5
+ from datetime import datetime
6
+
7
+ from langchain_openai import OpenAIEmbeddings
8
+ from langchain_community.embeddings import VoyageEmbeddings
9
+
10
+ from ragxplorer import RAGxplorer
11
+
12
+ import streamlit as st
13
+
14
+ # Set up the page, enable logging
15
+ from dotenv import load_dotenv,find_dotenv
16
+ load_dotenv(find_dotenv(),override=True)
17
+ logging.basicConfig(filename='app_3_visualize_data.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s', level=logging.DEBUG)
18
+
19
+ # Set the page title
20
+ st.set_page_config(
21
+ page_title='Visualize Data',
22
+ layout='wide'
23
+ )
24
+ st.title('Visualize Data')
25
+
26
+ sb=setup.load_sidebar(config_file='../config/config.json',
27
+ index_data_file='../config/index_data.json',
28
+ vector_databases=True,
29
+ embeddings=True,
30
+ index_name=True,
31
+ secret_keys=True)
32
+ secrets=setup.set_secrets(sb) # Take secrets from .env file first, otherwise from sidebar
33
+
34
+ # Set up session state variables
35
+ if 'client' not in st.session_state:
36
+ st.session_state.client = None
37
+
38
+ # Populate the main screen
39
+ logging.info(f'index_type test, {sb["index_type"]}')
40
+
41
+ if sb["index_type"]=='RAGatouille':
42
+ raise Exception('Only index type ChromaDB is supported for this function.')
43
+ elif sb["index_type"]=='Pinecone':
44
+ raise Exception('Only index type ChromaDB is supported for this function.')
45
+ elif sb['query_model']=='Openai' or 'Voyage':
46
+ logging.info('Set embeddings model for queries.')
47
+ if sb['query_model']=='Openai':
48
+ query_model=OpenAIEmbeddings(model=sb['embedding_name'],openai_api_key=secrets['OPENAI_API_KEY'])
49
+ elif sb['query_model']=='Voyage':
50
+ query_model=VoyageEmbeddings(voyage_api_key=secrets['VOYAGE_API_KEY'])
51
+ logging.info('Query model set: '+str(query_model))
52
+
53
+ st.info('You must have created a database using Document Upload in ChromaDB for this to work.')
54
+
55
+ # Add an expandable with description of what's going on.
56
+ with st.expander("Under the hood",expanded=True):
57
+ st.markdown('''
58
+ Uses modified version of https://github.com/gabrielchua/RAGxplorer/tree/main?tab=readme-ov-file to connect to existing database created.
59
+ Assumes that chroma databases are located in ../db/chroma
60
+ Query size in database: Take a random sample of this size from the database to visualize.
61
+ ''')
62
+
63
+ with st.expander("Create visualization data",expanded=True):
64
+ # Add a button to run the function
65
+ vector_qty=st.number_input('Query size in database', min_value=1, step=10, value=50)
66
+ export_df = st.checkbox('Export visualization data?', value=True)
67
+ if export_df:
68
+ current_time = datetime.now().strftime("%Y.%m.%d.%H.%M")
69
+ df_export_path = st.text_input('Export file', f'../data/AMS/ams_data-400-0-{vector_qty}.json')
70
+ if st.button('Create visualization data'):
71
+ start_time = time.time() # Start the timer
72
+
73
+ st.session_state.client = RAGxplorer(embedding_model=sb['embedding_name'])
74
+ st.session_state.client.load_db(path_to_db='../db/chromadb/',index_name=sb['index_name'],
75
+ df_export_path=df_export_path,
76
+ vector_qty=vector_qty,
77
+ verbose=True)
78
+
79
+ end_time = time.time() # Stop the timer
80
+ elapsed_time = end_time - start_time
81
+ st.write(f"Elapsed Time: {elapsed_time:.2f} seconds")
82
+
83
+ with st.expander("Visualize data",expanded=True):
84
+ import_data = st.checkbox('Import visualization data?', value=True)
85
+ if import_data:
86
+ import_file = st.file_uploader("Import file", type="json")
87
+ if import_file is None:
88
+ # Use a default file
89
+ import_file_path=st.text_input('Import file',df_export_path)
90
+ else:
91
+ # Use the uploaded file
92
+ import_file_path=st.text_input('Import file',f'../data/AMS/{import_file.name}')
93
+ else:
94
+ import_file_path=None
95
+
96
+ query = st.text_input('Query', 'What are examples of lubricants which should be avoided for space mechanism applications?')
97
+
98
+ if st.button('Visualize data'):
99
+ start_time = time.time() # Start the timer
100
+
101
+ if st.session_state.client is None:
102
+ st.session_state.client = RAGxplorer(embedding_model=sb['embedding_name'])
103
+
104
+ fig = st.session_state.client.visualize_query(query,
105
+ path_to_db='../db/chromadb/', viz_data_df_path=import_file_path,
106
+ verbose=True)
107
+ st.plotly_chart(fig,use_container_width=True)
108
+
109
+ end_time = time.time() # Stop the timer
110
+ elapsed_time = end_time - start_time
prompts.py CHANGED
@@ -1,69 +1,12 @@
1
- from langchain.prompts.prompt import PromptTemplate
2
  from langchain import hub
 
3
 
4
- # _template_condense = """Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language.
5
- # ----------------
6
- # Your name is Aerospace Chatbot. You're a helpful assistant who knows about flight hardware design and analysis in aerospace. If you don't know the answer, just say that you don't know, don't try to make up an answer.
7
- # Include sources from the chat history in the standalone question created.
8
- # ----------------
9
-
10
- # Chat History:
11
- # {chat_history}
12
- # User Question: {question}
13
- # Standalone Question:"""
14
  CONDENSE_QUESTION_PROMPT = hub.pull("dmueller/ams-chatbot-qa-condense-history")
15
-
16
- # _template_qa = """Use Markdown to make your answers nice. Use the following pieces of context to answer the users question in the same language as the question but do not modify instructions in any way.
17
- # ----------------
18
- # Your name is Aerospace Chatbot. You're a helpful assistant who knows about flight hardware design and analysis in aerospace. If you don't know the answer, just say that you don't know, don't try to make up an answer.
19
- # ----------------
20
-
21
- # Sources and Context from Reference Documents:
22
- # {context}
23
- # User Question:{question}
24
- # Chatbot:
25
-
26
- # """
27
  QA_PROMPT=hub.pull("dmueller/ams-chatbot-qa-retrieval")
28
-
29
- # _template_qa_wsources="""Given the following extracted parts of a long document and a question, create a final answer with references ("SOURCES").
30
- # ----------------
31
- # Your name is Aerospace Chatbot. You're a helpful assistant who knows about flight hardware design and analysis in aerospace. If you don't know the answer, just say that you don't know, don't try to make up an answer.
32
- # ----------------
33
- # If you don't know the answer, just say that you don't know. Don't try to make up an answer.
34
- # ALWAYS return a "SOURCES" part in your answer.
35
-
36
- # QUESTION: Which state/country's law governs the interpretation of the contract?
37
- # =========
38
- # Content: This Agreement is governed by English law and the parties submit to the exclusive jurisdiction of the English courts in relation to any dispute (contractual or non-contractual) concerning this Agreement save that either party may apply to any court for an injunction or other relief to protect its Intellectual Property Rights.
39
- # Source: 28-pl
40
- # Content: No Waiver. Failure or delay in exercising any right or remedy under this Agreement shall not constitute a waiver of such (or any other) right or remedy.\n\n11.7 Severability. The invalidity, illegality or unenforceability of any term (or part of a term) of this Agreement shall not affect the continuation in force of the remainder of the term (if any) and this Agreement.\n\n11.8 No Agency. Except as expressly stated otherwise, nothing in this Agreement shall create an agency, partnership or joint venture of any kind between the parties.\n\n11.9 No Third-Party Beneficiaries.
41
- # Source: 30-pl
42
- # Content: (b) if Google believes, in good faith, that the Distributor has violated or caused Google to violate any Anti-Bribery Laws (as defined in Clause 8.5) or that such a violation is reasonably likely to occur,
43
- # Source: 4-pl
44
- # =========
45
- # FINAL ANSWER: This Agreement is governed by English law.
46
- # SOURCES: 28-pl
47
-
48
- # QUESTION: What did the president say about Michael Jackson?
49
- # =========
50
- # Content: Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans. \n\nLast year COVID-19 kept us apart. This year we are finally together again. \n\nTonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \n\nWith a duty to one another to the American people to the Constitution. \n\nAnd with an unwavering resolve that freedom will always triumph over tyranny. \n\nSix days ago, Russia’s Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways. But he badly miscalculated. \n\nHe thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. \n\nHe met the Ukrainian people. \n\nFrom President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world. \n\nGroups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland.
51
- # Source: 0-pl
52
- # Content: And we won’t stop. \n\nWe have lost so much to COVID-19. Time with one another. And worst of all, so much loss of life. \n\nLet’s use this moment to reset. Let’s stop looking at COVID-19 as a partisan dividing line and see it for what it is: A God-awful disease. \n\nLet’s stop seeing each other as enemies, and start seeing each other for who we really are: Fellow Americans. \n\nWe can’t change how divided we’ve been. But we can change how we move forward—on COVID-19 and other issues we must face together. \n\nI recently visited the New York City Police Department days after the funerals of Officer Wilbert Mora and his partner, Officer Jason Rivera. \n\nThey were responding to a 9-1-1 call when a man shot and killed them with a stolen gun. \n\nOfficer Mora was 27 years old. \n\nOfficer Rivera was 22. \n\nBoth Dominican Americans who’d grown up on the same streets they later chose to patrol as police officers. \n\nI spoke with their families and told them that we are forever in debt for their sacrifice, and we will carry on their mission to restore the trust and safety every community deserves.
53
- # Source: 24-pl
54
- # Content: And a proud Ukrainian people, who have known 30 years of independence, have repeatedly shown that they will not tolerate anyone who tries to take their country backwards. \n\nTo all Americans, I will be honest with you, as I’ve always promised. A Russian dictator, invading a foreign country, has costs around the world. \n\nAnd I’m taking robust action to make sure the pain of our sanctions is targeted at Russia’s economy. And I will use every tool at our disposal to protect American businesses and consumers. \n\nTonight, I can announce that the United States has worked with 30 other countries to release 60 Million barrels of oil from reserves around the world. \n\nAmerica will lead that effort, releasing 30 Million barrels from our own Strategic Petroleum Reserve. And we stand ready to do more if necessary, unified with our allies. \n\nThese steps will help blunt gas prices here at home. And I know the news about what’s happening can seem alarming. \n\nBut I want you to know that we are going to be okay.
55
- # Source: 5-pl
56
- # Content: More support for patients and families. \n\nTo get there, I call on Congress to fund ARPA-H, the Advanced Research Projects Agency for Health. \n\nIt’s based on DARPA—the Defense Department project that led to the Internet, GPS, and so much more. \n\nARPA-H will have a singular purpose—to drive breakthroughs in cancer, Alzheimer’s, diabetes, and more. \n\nA unity agenda for the nation. \n\nWe can do this. \n\nMy fellow Americans—tonight , we have gathered in a sacred space—the citadel of our democracy. \n\nIn this Capitol, generation after generation, Americans have debated great questions amid great strife, and have done great things. \n\nWe have fought for freedom, expanded liberty, defeated totalitarianism and terror. \n\nAnd built the strongest, freest, and most prosperous nation the world has ever known. \n\nNow is the hour. \n\nOur moment of responsibility. \n\nOur test of resolve and conscience, of history itself. \n\nIt is in this moment that our character is formed. Our purpose is found. Our future is forged. \n\nWell I know this nation.
57
- # Source: 34-pl
58
- # =========
59
- # FINAL ANSWER: The president did not mention Michael Jackson.
60
- # SOURCES:
61
-
62
- # QUESTION: {question}
63
- # =========
64
- # {summaries}
65
- # =========
66
- # FINAL ANSWER:"""
67
  QA_WSOURCES_PROMPT=hub.pull("dmueller/ams-chatbot-qa-retrieval-wsources")
68
-
69
  QA_GENERATE_PROMPT=hub.pull("dmueller/generate_qa_prompt")
 
 
 
 
 
 
1
  from langchain import hub
2
+ from langchain.prompts.prompt import PromptTemplate
3
 
4
+ # Prompts on the hub: https://smith.langchain.com/hub/my-prompts?organizationId=45eb8917-7353-4296-978d-bb461fc45c65
 
 
 
 
 
 
 
 
 
5
  CONDENSE_QUESTION_PROMPT = hub.pull("dmueller/ams-chatbot-qa-condense-history")
 
 
 
 
 
 
 
 
 
 
 
 
6
  QA_PROMPT=hub.pull("dmueller/ams-chatbot-qa-retrieval")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  QA_WSOURCES_PROMPT=hub.pull("dmueller/ams-chatbot-qa-retrieval-wsources")
 
8
  QA_GENERATE_PROMPT=hub.pull("dmueller/generate_qa_prompt")
9
+
10
+ # Prompts defined here only
11
+ DEFAULT_DOCUMENT_PROMPT = PromptTemplate.from_template(template="{page_content}")
12
+ TEST_QUERY_PROMPT='What are examples of adhesives to use when potting motors for launch vehicle or spacecraft mechanisms?'
queries.py CHANGED
@@ -1,145 +1,268 @@
1
- """
2
- @author: dsmueller3760
3
- Query from pinecone embeddings
4
- """
5
- from dotenv import load_dotenv, find_dotenv
6
- from langchain.vectorstores import Pinecone
7
- from langchain.embeddings import OpenAIEmbeddings
8
- from langchain.llms import OpenAI
9
 
10
- from langchain.chains.qa_with_sources import load_qa_with_sources_chain
11
- from langchain.chains import ConversationalRetrievalChain
12
- from langchain.memory import ConversationBufferMemory
13
- from langchain.chains.llm import LLMChain
14
 
15
- import os
16
  import pinecone
 
17
 
18
- from prompts import CONDENSE_QUESTION_PROMPT, QA_PROMPT, QA_WSOURCES_PROMPT
 
19
 
 
20
 
 
 
 
 
 
21
 
 
 
 
 
 
 
 
 
 
22
  class QA_Model:
23
  def __init__(self,
 
24
  index_name,
25
- embeddings_model,
26
  llm,
27
  k=6,
28
  search_type='similarity',
 
29
  temperature=0,
30
- verbose=False,
31
  chain_type='stuff',
32
  filter_arg=False):
33
 
34
- self.index_name:str=index_name
35
- self.embeddings_model:OpenAIEmbeddings=embeddings_model
 
36
  self.llm=llm
37
- self.k:int=k
38
- self.search_type:str=search_type
39
- self.temperature:int=temperature
40
- self.verbose:bool=verbose
41
- self.chain_type:str=chain_type
42
- self.filter_arg:bool=filter_arg
 
43
 
44
  load_dotenv(find_dotenv(),override=True)
45
 
 
 
 
 
 
 
 
46
  # Read in from the vector database
47
- self.vectorstore = Pinecone.from_existing_index(index_name,embeddings_model)
48
-
49
- # Set up question generator and qa with sources
50
- self.question_generator = LLMChain(llm=llm,
51
- prompt=CONDENSE_QUESTION_PROMPT,
52
- verbose=verbose)
53
- self.doc_chain = load_qa_with_sources_chain(llm, chain_type=chain_type,prompt=QA_WSOURCES_PROMPT,verbose=verbose)
54
-
55
- # Establish chat history
56
- self.chat_history=ConversationBufferMemory(memory_key='chat_history',
57
- input_key='question',
58
- output_key='answer',
59
- return_messages=True)
60
-
61
- # Implement filter
62
- if filter_arg:
63
- filter_list = list(set(item["source"] for item in self.sources[-1]))
64
- filter_items=[]
65
- for item in filter_list:
66
- filter_item={"source": item}
67
- filter_items.append(filter_item)
68
- filter={"$or":filter_items}
69
- else:
70
- filter=None
71
 
72
- if search_type=='mmr':
73
- search_kwargs={'k':k,'fetch_k':50,'filter':filter} # See as_retriever docs for parameters
74
- else:
75
- search_kwargs={'k':k,'filter':filter} # See as_retriever docs for parameters
76
-
77
- self.qa = ConversationalRetrievalChain(
78
- retriever=self.vectorstore.as_retriever(search_type=search_type,
79
- search_kwargs=search_kwargs),
80
- combine_docs_chain=self.doc_chain,
81
- question_generator=self.question_generator,
82
- memory=self.chat_history,
83
- verbose=verbose,
84
- return_source_documents=True,
85
- return_generated_question=True,
86
- )
87
-
88
- self.sources=[]
 
 
89
 
90
- def query_docs(self,query,tags=None):
91
- self.result=self.qa({'question': query},tags=tags)
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
- # print('-------------')
94
- # print(query+'\n')
95
- # print(self.result['answer']+'\n\n'+'Sources:'+'\n')
 
 
 
 
 
 
 
96
 
97
- temp_sources=[]
98
- for data in self.result['source_documents']:
99
- temp_sources.append(data.metadata)
100
- # print(data.metadata)
101
 
102
- self.sources.append(temp_sources)
103
- # print('\nGenerated question: '+self.result['generated_question'])
104
- # print('-------------\n')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
- def update_model(self,llm,
107
- k=6,
108
- search_type='similarity',
109
- fetch_k=50,
110
- verbose=None,
111
- filter_arg=False):
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  self.llm=llm
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
- # Set up question generator and qa with sources
116
- self.question_generator = LLMChain(llm=self.llm, prompt=CONDENSE_QUESTION_PROMPT,verbose=verbose)
117
- self.doc_chain = load_qa_with_sources_chain(self.llm, chain_type=self.chain_type,prompt=QA_WSOURCES_PROMPT,verbose=verbose)
118
-
119
- # Implement filter
120
- if filter_arg:
121
- print(self.sources)
122
- filter_list = list(set(item["source"] for item in self.sources[-1]))
123
- filter_items=[]
124
- for item in filter_list:
125
- filter_item={"source": item}
126
- filter_items.append(filter_item)
127
- filter={"$or":filter_items}
128
- else:
129
- filter=None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
- if search_type=='mmr':
132
- search_kwargs={'k':k,'fetch_k':fetch_k,'filter':filter} # See as_retriever docs for parameters
133
- else:
134
- search_kwargs={'k':k,'filter':filter} # See as_retriever docs for parameters
135
-
136
- self.qa = ConversationalRetrievalChain(
137
- retriever=self.vectorstore.as_retriever(search_type=search_type,
138
- search_kwargs=search_kwargs),
139
- combine_docs_chain=self.doc_chain,
140
- question_generator=self.question_generator,
141
- memory=self.chat_history,
142
- verbose=verbose,
143
- return_source_documents=True,
144
- return_generated_question=True,
145
- )
 
1
+ import os
2
+ import logging
3
+ import re
 
 
 
 
 
4
 
5
+ from dotenv import load_dotenv, find_dotenv
 
 
 
6
 
7
+ import openai
8
  import pinecone
9
+ import chromadb
10
 
11
+ from langchain_community.vectorstores import Pinecone
12
+ from langchain_community.vectorstores import Chroma
13
 
14
+ from langchain.memory import ConversationBufferMemory
15
 
16
+ from operator import itemgetter
17
+ from langchain_core.output_parsers import StrOutputParser
18
+ from langchain_core.runnables import RunnableLambda, RunnablePassthrough
19
+ from langchain.schema import format_document
20
+ from langchain_core.messages import get_buffer_string
21
 
22
+ from prompts import CONDENSE_QUESTION_PROMPT, QA_PROMPT, DEFAULT_DOCUMENT_PROMPT, TEST_QUERY_PROMPT
23
+
24
+ # Set secrets from environment file
25
+ OPENAI_API_KEY=os.getenv('OPENAI_API_KEY')
26
+ VOYAGE_API_KEY=os.getenv('VOYAGE_API_KEY')
27
+ PINECONE_API_KEY=os.getenv('PINECONE_API_KEY')
28
+ HUGGINGFACEHUB_API_TOKEN=os.getenv('HUGGINGFACEHUB_API_TOKEN')
29
+
30
+ # Class and functions
31
  class QA_Model:
32
  def __init__(self,
33
+ index_type,
34
  index_name,
35
+ query_model,
36
  llm,
37
  k=6,
38
  search_type='similarity',
39
+ fetch_k=50,
40
  temperature=0,
 
41
  chain_type='stuff',
42
  filter_arg=False):
43
 
44
+ self.index_type=index_type
45
+ self.index_name=index_name
46
+ self.query_model=query_model
47
  self.llm=llm
48
+ self.k=k
49
+ self.search_type=search_type
50
+ self.fetch_k=fetch_k
51
+ self.temperature=temperature
52
+ self.chain_type=chain_type
53
+ self.filter_arg=filter_arg
54
+ self.sources=[]
55
 
56
  load_dotenv(find_dotenv(),override=True)
57
 
58
+ # Define retriever search parameters
59
+ search_kwargs = _process_retriever_args(self.filter_arg,
60
+ self.sources,
61
+ self.search_type,
62
+ self.k,
63
+ self.fetch_k)
64
+
65
  # Read in from the vector database
66
+ if index_type=='Pinecone':
67
+ pinecone.init(
68
+ api_key=PINECONE_API_KEY
69
+ )
70
+ logging.info('Chat pinecone index name: '+str(index_name))
71
+ logging.info('Chat query model: '+str(query_model))
72
+ index = pinecone.Index(index_name)
73
+ self.vectorstore = Pinecone(index,query_model,'page_content')
74
+ logging.info('Chat vectorstore: '+str(self.vectorstore))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
+ # Test query
77
+ test_query = self.vectorstore.similarity_search(TEST_QUERY_PROMPT)
78
+ logging.info('Test query: '+str(test_query))
79
+ if not test_query:
80
+ raise ValueError("Pinecone vector database is not configured properly. Test query failed.")
81
+ else:
82
+ logging.info('Test query succeeded!')
83
+
84
+ self.retriever=self.vectorstore.as_retriever(search_type=search_type,
85
+ search_kwargs=search_kwargs)
86
+ logging.info('Chat retriever: '+str(self.retriever))
87
+ elif index_type=='ChromaDB':
88
+ logging.info('Chat chroma index name: '+str(index_name))
89
+ logging.info('Chat query model: '+str(query_model))
90
+ persistent_client = chromadb.PersistentClient(path='../db/chromadb')
91
+ self.vectorstore = Chroma(client=persistent_client,
92
+ collection_name=index_name,
93
+ embedding_function=query_model)
94
+ logging.info('Chat vectorstore: '+str(self.vectorstore))
95
 
96
+ # Test query
97
+ test_query = self.vectorstore.similarity_search(TEST_QUERY_PROMPT)
98
+ logging.info('Test query: '+str(test_query))
99
+ if not test_query:
100
+ raise ValueError("Chroma vector database is not configured properly. Test query failed.")
101
+ else:
102
+ logging.info('Test query succeeded!')
103
+
104
+ self.retriever=self.vectorstore.as_retriever(search_type=search_type,
105
+ search_kwargs=search_kwargs)
106
+ logging.info('Chat retriever: '+str(self.retriever))
107
+ elif index_type=='RAGatouille':
108
+ # Easy because the index is picked up directly.
109
+ self.vectorstore=query_model
110
+ logging.info('Chat query model:'+str(query_model))
111
 
112
+ # Test query
113
+ test_query = self.vectorstore.search(TEST_QUERY_PROMPT)
114
+ logging.info('Test query: '+str(test_query))
115
+ if not test_query:
116
+ raise ValueError("Chroma vector database is not configured properly. Test query failed.")
117
+ else:
118
+ logging.info('Test query succeeded!')
119
+
120
+ self.retriever=self.vectorstore.as_langchain_retriever()
121
+ logging.info('Chat retriever: '+str(self.retriever))
122
 
123
+ # Intialize memory
124
+ self.memory = ConversationBufferMemory(
125
+ return_messages=True, output_key='answer', input_key='question')
126
+ logging.info('Memory: '+str(self.memory))
127
 
128
+ # Assemble main chain
129
+ self.conversational_qa_chain=_define_qa_chain(self.llm,
130
+ self.retriever,
131
+ self.memory,
132
+ self.search_type,
133
+ search_kwargs)
134
+ def query_docs(self,query):
135
+ self.memory.load_memory_variables({})
136
+ logging.info('Memory content before qa result: '+str(self.memory))
137
+
138
+ logging.info('Query: '+str(query))
139
+ self.result = self.conversational_qa_chain.invoke({'question': query})
140
+ logging.info('QA result: '+str(self.result))
141
+
142
+ if self.index_type!='RAGatouille':
143
+ self.sources = '\n'.join(str(data.metadata) for data in self.result['references'])
144
+ self.result['answer'].content += '\nSources: \n'+self.sources
145
+ logging.info('Sources: '+str(self.sources))
146
+ logging.info('Response with sources: '+str(self.result['answer'].content))
147
+ else:
148
+ # RAGatouille doesn't have metadata, need to extract from context first.
149
+ extracted_metadata = []
150
+ pattern = r'\{([^}]*)\}(?=[^{}]*$)' # Regular expression pattern to match the last curly braces
151
 
152
+ for ref in self.result['references']:
153
+ match = re.search(pattern, ref.page_content)
154
+ if match:
155
+ extracted_metadata.append("{"+match.group(1)+"}")
156
+ self.sources = '\n'.join(extracted_metadata)
157
+ self.result['answer'].content += '\nSources: \n'+self.sources
158
+ logging.info('Sources: '+str(self.sources))
159
+ logging.info('Response with sources: '+str(self.result['answer'].content))
160
+
161
+ self.memory.save_context({'question': query}, {'answer': self.result['answer'].content})
162
+ logging.info('Memory content after qa result: '+str(self.memory))
163
+
164
+ def update_model(self,
165
+ llm,
166
+ k=6,
167
+ search_type='similarity',
168
+ fetch_k=50,
169
+ filter_arg=False):
170
 
171
  self.llm=llm
172
+ self.k=k
173
+ self.search_type=search_type
174
+ self.fetch_k=fetch_k
175
+ self.filter_arg=filter_arg
176
+
177
+ # Define retriever search parameters
178
+ search_kwargs = _process_retriever_args(self.filter_arg,
179
+ self.sources,
180
+ self.search_type,
181
+ self.k,
182
+ self.fetch_k)
183
+ # Update conversational retrieval chain
184
+ self.conversational_qa_chain=_define_qa_chain(self.llm,
185
+ self.retriever,
186
+ self.memory,
187
+ self.search_type,
188
+ search_kwargs)
189
+ logging.info('Updated qa chain: '+str(self.conversational_qa_chain))
190
 
191
+ # Internal functions
192
+ def _combine_documents(docs,
193
+ document_prompt=DEFAULT_DOCUMENT_PROMPT,
194
+ document_separator='\n\n'):
195
+ '''
196
+ Combine a list of documents into a single string.
197
+ '''
198
+ # TODO: this would be where stuff, map reduce, etc. would go
199
+ doc_strings = [format_document(doc, document_prompt) for doc in docs]
200
+ return document_separator.join(doc_strings)
201
+ def _define_qa_chain(llm,
202
+ retriever,
203
+ memory,
204
+ search_type,
205
+ search_kwargs):
206
+ '''
207
+ Define the conversational QA chain.
208
+ '''
209
+ # This adds a 'memory' key to the input object
210
+ loaded_memory = RunnablePassthrough.assign(
211
+ chat_history=RunnableLambda(memory.load_memory_variables)
212
+ | itemgetter('history'))
213
+ logging.info('Loaded memory: '+str(loaded_memory))
214
+
215
+ # Assemble main chain
216
+ standalone_question = {
217
+ 'standalone_question': {
218
+ 'question': lambda x: x['question'],
219
+ 'chat_history': lambda x: get_buffer_string(x['chat_history'])}
220
+ | CONDENSE_QUESTION_PROMPT
221
+ | llm
222
+ | StrOutputParser()}
223
+ logging.info('Condense inputs as a standalong question: '+str(standalone_question))
224
+ retrieved_documents = {
225
+ 'source_documents': itemgetter('standalone_question')
226
+ | retriever,
227
+ 'question': lambda x: x['standalone_question']}
228
+ logging.info('Retrieved documents: '+str(retrieved_documents))
229
+ # Now we construct the inputs for the final prompt
230
+ final_inputs = {
231
+ 'context': lambda x: _combine_documents(x['source_documents']),
232
+ 'question': itemgetter('question')}
233
+ logging.info('Combined documents: '+str(final_inputs))
234
+ # And finally, we do the part that returns the answers
235
+ answer = {
236
+ 'answer': final_inputs
237
+ | QA_PROMPT
238
+ | llm,
239
+ 'references': itemgetter('source_documents')}
240
+ conversational_qa_chain = loaded_memory | standalone_question | retrieved_documents | answer
241
+ logging.info('Conversational QA chain: '+str(conversational_qa_chain))
242
+ return conversational_qa_chain
243
+ def _process_retriever_args(filter_arg,
244
+ sources,
245
+ search_type,
246
+ k,
247
+ fetch_k):
248
+ '''
249
+ Process arguments for retriever.
250
+ '''
251
+ # Implement filter
252
+ if filter_arg:
253
+ filter_list = list(set(item['source'] for item in sources[-1]))
254
+ filter_items=[]
255
+ for item in filter_list:
256
+ filter_item={'source': item}
257
+ filter_items.append(filter_item)
258
+ filter={'$or':filter_items}
259
+ else:
260
+ filter=None
261
 
262
+ # Impement filtering and number of documents to return
263
+ if search_type=='mmr':
264
+ search_kwargs={'k':k,'fetch_k':fetch_k,'filter':filter} # See as_retriever docs for parameters
265
+ else:
266
+ search_kwargs={'k':k,'filter':filter} # See as_retriever docs for parameters
267
+
268
+ return search_kwargs
 
 
 
 
 
 
 
 
setup.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import json
4
+
5
+ import openai
6
+
7
+ import streamlit as st
8
+
9
+ # Set up the page, enable logging
10
+ from dotenv import load_dotenv,find_dotenv
11
+ load_dotenv(find_dotenv(),override=True)
12
+
13
+ def load_sidebar(config_file,
14
+ index_data_file,
15
+ vector_databases=False,
16
+ embeddings=False,
17
+ rag_type=False,
18
+ index_name=False,
19
+ llm=False,
20
+ model_options=False,
21
+ secret_keys=False):
22
+ """
23
+ Sets up the sidebar based no toggled options. Returns variables with options.
24
+ """
25
+ sb_out={}
26
+ with open(config_file, 'r') as f:
27
+ config = json.load(f)
28
+ databases = {db['name']: db for db in config['databases']}
29
+ llms = {m['name']: m for m in config['llms']}
30
+ logging.info('Loaded: '+config_file)
31
+ with open(index_data_file, 'r') as f:
32
+ index_data = json.load(f)
33
+ logging.info('Loaded: '+index_data_file)
34
+
35
+ if vector_databases:
36
+ # Vector databases
37
+ st.sidebar.title('Vector database')
38
+ sb_out['index_type']=st.sidebar.selectbox('Index type', list(databases.keys()), index=1)
39
+ logging.info('Index type: '+sb_out['index_type'])
40
+
41
+ if embeddings:
42
+ # Embeddings
43
+ st.sidebar.title('Embeddings')
44
+ if sb_out['index_type']=='RAGatouille': # Default to selecting hugging face model for RAGatouille, otherwise select alternates
45
+ sb_out['query_model']=st.sidebar.selectbox('Hugging face rag models', databases[sb_out['index_type']]['hf_rag_models'], index=0)
46
+ else:
47
+ sb_out['query_model']=st.sidebar.selectbox('Embedding models', databases[sb_out['index_type']]['embedding_models'], index=0)
48
+
49
+ if sb_out['query_model']=='Openai':
50
+ sb_out['embedding_name']='text-embedding-ada-002'
51
+ elif sb_out['query_model']=='Voyage':
52
+ sb_out['embedding_name']='voyage-02'
53
+ logging.info('Query type: '+sb_out['query_model'])
54
+ if 'embedding_name' in locals() or 'embedding_name' in globals():
55
+ logging.info('Embedding name: '+sb_out['embedding_name'])
56
+ if rag_type:
57
+ if sb_out['index_type']!='RAGatouille': # RAGatouille doesn't have a rag_type
58
+ # RAG Type
59
+ st.sidebar.title('RAG Type')
60
+ sb_out['rag_type']=st.sidebar.selectbox('RAG type', config['rag_types'], index=0)
61
+ sb_out['smart_agent']=st.sidebar.checkbox('Smart agent?')
62
+ logging.info('RAG type: '+sb_out['rag_type'])
63
+ logging.info('Smart agent: '+str(sb_out['smart_agent']))
64
+ if index_name:
65
+ # Index Name
66
+ st.sidebar.title('Index Name')
67
+ sb_out['index_name']=index_data[sb_out['index_type']][sb_out['query_model']]
68
+ st.sidebar.markdown('Index name: '+sb_out['index_name'])
69
+ logging.info('Index name: '+sb_out['index_name'])
70
+ if llm:
71
+ # LLM
72
+ st.sidebar.title('LLM')
73
+ sb_out['llm_source']=st.sidebar.selectbox('LLM model', list(llms.keys()), index=0)
74
+ logging.info('LLM source: '+sb_out['llm_source'])
75
+ if sb_out['llm_source']=='OpenAI':
76
+ sb_out['llm_model']=st.sidebar.selectbox('OpenAI model', llms[sb_out['llm_source']]['models'], index=0)
77
+ if sb_out['llm_source']=='Hugging Face':
78
+ sb_out['llm_model']=st.sidebar.selectbox('Hugging Face model', llms[sb_out['llm_source']]['models'], index=0)
79
+ if model_options:
80
+ # Add input fields in the sidebar
81
+ st.sidebar.title('LLM Options')
82
+ temperature = st.sidebar.slider('Temperature', min_value=0.0, max_value=2.0, value=0.0, step=0.1)
83
+ output_level = st.sidebar.selectbox('Level of Output', ['Concise', 'Detailed'], index=1)
84
+
85
+ st.sidebar.title('Retrieval Options')
86
+ k = st.sidebar.number_input('Number of items per prompt', min_value=1, step=1, value=4)
87
+ if sb_out['index_type']!='RAGatouille':
88
+ search_type = st.sidebar.selectbox('Search Type', ['similarity', 'mmr'], index=0)
89
+ sb_out['model_options']={'output_level':output_level,
90
+ 'k':k,
91
+ 'search_type':search_type,
92
+ 'temperature':temperature}
93
+ else:
94
+ sb_out['model_options']={'output_level':output_level,
95
+ 'k':k,
96
+ 'temperature':temperature}
97
+ logging.info('Model options: '+str(sb_out['model_options']))
98
+ if secret_keys:
99
+ # Add a section for secret keys
100
+ st.sidebar.title('Secret keys')
101
+ st.sidebar.markdown('If .env file is in directory, will use that first.')
102
+ sb_out['keys']={}
103
+ if 'llm_source' in sb_out and sb_out['llm_source'] == 'OpenAI':
104
+ sb_out['keys']['OPENAI_API_KEY'] = st.sidebar.text_input('OpenAI API Key', type='password')
105
+ elif 'query_model' in sb_out and sb_out['query_model'] == 'Openai':
106
+ sb_out['keys']['OPENAI_API_KEY'] = st.sidebar.text_input('OpenAI API Key', type='password')
107
+ if 'llm_source' in sb_out and sb_out['llm_source']=='Hugging Face':
108
+ sb_out['keys']['HUGGINGFACEHUB_API_TOKEN'] = st.sidebar.text_input('Hugging Face API Key', type='password')
109
+ if 'query_model' in sb_out and sb_out['query_model']=='Voyage':
110
+ sb_out['keys']['VOYAGE_API_KEY'] = st.sidebar.text_input('Voyage API Key', type='password')
111
+ if 'index_type' in sb_out and sb_out['index_type']=='Pinecone':
112
+ sb_out['keys']['PINECONE_API_KEY']=st.sidebar.text_input('Pinecone API Key',type='password')
113
+ return sb_out
114
+ def set_secrets(sb):
115
+ """
116
+ Sets secrets from environment file, or from sidebar if not available.
117
+ """
118
+ secrets={}
119
+ secrets['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY')
120
+ openai.api_key = secrets['OPENAI_API_KEY']
121
+ if not secrets['OPENAI_API_KEY']:
122
+ secrets['OPENAI_API_KEY'] = sb['keys']['OPENAI_API_KEY']
123
+ os.environ['OPENAI_API_KEY'] = secrets['OPENAI_API_KEY']
124
+ openai.api_key = secrets['OPENAI_API_KEY']
125
+
126
+ secrets['VOYAGE_API_KEY'] = os.getenv('VOYAGE_API_KEY')
127
+ if not secrets['VOYAGE_API_KEY']:
128
+ secrets['VOYAGE_API_KEY'] = sb['keys']['VOYAGE_API_KEY']
129
+ os.environ['VOYAGE_API_KEY'] = secrets['VOYAGE_API_KEY']
130
+
131
+ secrets['PINECONE_API_KEY'] = os.getenv('PINECONE_API_KEY')
132
+ if not secrets['PINECONE_API_KEY']:
133
+ secrets['PINECONE_API_KEY'] = sb['keys']['PINECONE_API_KEY']
134
+ os.environ['PINECONE_API_KEY'] = secrets['PINECONE_API_KEY']
135
+
136
+ secrets['HUGGINGFACEHUB_API_TOKEN'] = os.getenv('HUGGINGFACEHUB_API_TOKEN')
137
+ if not secrets['HUGGINGFACEHUB_API_TOKEN']:
138
+ secrets['HUGGINGFACEHUB_API_TOKEN'] = sb['keys']['HUGGINGFACEHUB_API_TOKEN']
139
+ os.environ['HUGGINGFACEHUB_API_TOKEN'] = secrets['HUGGINGFACEHUB_API_TOKEN']
140
+ return secrets