RubenAMtz commited on
Commit
31cbd5c
1 Parent(s): cea41ba

added dependencies and app asks user for PDF files

Browse files
.chainlit/config.toml ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ # Whether to enable telemetry (default: true). No personal data is collected.
3
+ enable_telemetry = true
4
+
5
+ # List of environment variables to be provided by each user to use the app.
6
+ user_env = []
7
+
8
+ # Duration (in seconds) during which the session is saved when the connection is lost
9
+ session_timeout = 3600
10
+
11
+ # Enable third parties caching (e.g LangChain cache)
12
+ cache = false
13
+
14
+ # Follow symlink for asset mount (see https://github.com/Chainlit/chainlit/issues/317)
15
+ # follow_symlink = false
16
+
17
+ [features]
18
+ # Show the prompt playground
19
+ prompt_playground = true
20
+
21
+ # Process and display HTML in messages. This can be a security risk (see https://stackoverflow.com/questions/19603097/why-is-it-dangerous-to-render-user-generated-html-or-javascript)
22
+ unsafe_allow_html = false
23
+
24
+ # Process and display mathematical expressions. This can clash with "$" characters in messages.
25
+ latex = false
26
+
27
+ # Authorize users to upload files with messages
28
+ multi_modal = true
29
+
30
+ # Allows user to use speech to text
31
+ [features.speech_to_text]
32
+ enabled = false
33
+ # See all languages here https://github.com/JamesBrill/react-speech-recognition/blob/HEAD/docs/API.md#language-string
34
+ # language = "en-US"
35
+
36
+ [UI]
37
+ # Name of the app and chatbot.
38
+ name = "Chatbot"
39
+
40
+ # Show the readme while the conversation is empty.
41
+ show_readme_as_default = true
42
+
43
+ # Description of the app and chatbot. This is used for HTML tags.
44
+ # description = ""
45
+
46
+ # Large size content are by default collapsed for a cleaner ui
47
+ default_collapse_content = true
48
+
49
+ # The default value for the expand messages settings.
50
+ default_expand_messages = false
51
+
52
+ # Hide the chain of thought details from the user in the UI.
53
+ hide_cot = false
54
+
55
+ # Link to your github repo. This will add a github button in the UI's header.
56
+ # github = ""
57
+
58
+ # Specify a CSS file that can be used to customize the user interface.
59
+ # The CSS file can be served from the public directory or via an external link.
60
+ # custom_css = "/public/test.css"
61
+
62
+ # Override default MUI light theme. (Check theme.ts)
63
+ [UI.theme.light]
64
+ #background = "#FAFAFA"
65
+ #paper = "#FFFFFF"
66
+
67
+ [UI.theme.light.primary]
68
+ #main = "#F80061"
69
+ #dark = "#980039"
70
+ #light = "#FFE7EB"
71
+
72
+ # Override default MUI dark theme. (Check theme.ts)
73
+ [UI.theme.dark]
74
+ #background = "#FAFAFA"
75
+ #paper = "#FFFFFF"
76
+
77
+ [UI.theme.dark.primary]
78
+ #main = "#F80061"
79
+ #dark = "#980039"
80
+ #light = "#FFE7EB"
81
+
82
+
83
+ [meta]
84
+ generated_by = "0.7.700"
.env.sample DELETED
@@ -1 +0,0 @@
1
- OPENAI_API_KEY=###
 
 
aimakerspace/__init__.py ADDED
File without changes
aimakerspace/openai_utils/__init__.py ADDED
File without changes
aimakerspace/openai_utils/chatmodel.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openai import OpenAI
2
+ from dotenv import load_dotenv
3
+ from prompts import UserRolePrompt, SystemRolePrompt
4
+ import os
5
+
6
+ load_dotenv()
7
+
8
+
9
+ class ChatOpenAI:
10
+ def __init__(self, model_name: str = "gpt-3.5-turbo"):
11
+ self.model_name = model_name
12
+ self.openai_api_key = os.getenv("OPENAI_API_KEY")
13
+ if self.openai_api_key is None:
14
+ raise ValueError("OPENAI_API_KEY is not set")
15
+
16
+ def run(self, messages, text_only: bool = True):
17
+ if not isinstance(messages, list):
18
+ raise ValueError("messages must be a list")
19
+
20
+ client = OpenAI()
21
+ response = client.chat.completions.create(
22
+ model=self.model_name, messages=messages
23
+ )
24
+
25
+ if text_only:
26
+ return response.choices[0].message.content
27
+
28
+ return response
29
+
30
+
31
+ if __name__ == "__main__":
32
+ chat = ChatOpenAI()
33
+ prompt = UserRolePrompt("Hello, I am a human.")
34
+ prompt = prompt.create_message()
35
+ print(prompt)
36
+
37
+ response = chat.run([prompt])
38
+ print(response)
aimakerspace/openai_utils/embedding.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ from openai import AsyncOpenAI, OpenAI
3
+ import openai
4
+ from typing import List
5
+ import os
6
+ import asyncio
7
+
8
+
9
+ class EmbeddingModel:
10
+ def __init__(self, embeddings_model_name: str = "text-embedding-ada-002"):
11
+ load_dotenv()
12
+ self.openai_api_key = os.getenv("OPENAI_API_KEY")
13
+ self.async_client = AsyncOpenAI()
14
+ self.client = OpenAI()
15
+
16
+ if self.openai_api_key is None:
17
+ raise ValueError(
18
+ "OPENAI_API_KEY environment variable is not set. Please set it to your OpenAI API key."
19
+ )
20
+ openai.api_key = self.openai_api_key
21
+ self.embeddings_model_name = embeddings_model_name
22
+
23
+ async def async_get_embeddings(self, list_of_text: List[str]) -> List[List[float]]:
24
+ embedding_response = await self.async_client.embeddings.create(
25
+ input=list_of_text, model=self.embeddings_model_name
26
+ )
27
+
28
+ return [embeddings.embedding for embeddings in embedding_response.data]
29
+
30
+ async def async_get_embedding(self, text: str) -> List[float]:
31
+ embedding = await self.async_client.embeddings.create(
32
+ input=text, model=self.embeddings_model_name
33
+ )
34
+
35
+ return embedding.data[0].embedding
36
+
37
+ def get_embeddings(self, list_of_text: List[str]) -> List[List[float]]:
38
+ embedding_response = self.client.embeddings.create(
39
+ input=list_of_text, model=self.embeddings_model_name
40
+ )
41
+
42
+ return [embeddings.embedding for embeddings in embedding_response.data]
43
+
44
+ def get_embedding(self, text: str) -> List[float]:
45
+ embedding = self.client.embeddings.create(
46
+ input=text, model=self.embeddings_model_name
47
+ )
48
+
49
+ return embedding.data[0].embedding
50
+
51
+
52
+ if __name__ == "__main__":
53
+ embedding_model = EmbeddingModel()
54
+ print(asyncio.run(embedding_model.async_get_embedding("Hello, world!")))
55
+ print(
56
+ asyncio.run(
57
+ embedding_model.async_get_embeddings(["Hello, world!", "Goodbye, world!"])
58
+ )
59
+ )
aimakerspace/openai_utils/prompts.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+
3
+
4
+ class BasePrompt:
5
+ def __init__(self, prompt):
6
+ """
7
+ Initializes the BasePrompt object with a prompt template.
8
+
9
+ :param prompt: A string that can contain placeholders within curly braces
10
+ """
11
+ self.prompt = prompt
12
+ self._pattern = re.compile(r"\{([^}]+)\}")
13
+
14
+ def format_prompt(self, **kwargs):
15
+ """
16
+ Formats the prompt string using the keyword arguments provided.
17
+
18
+ :param kwargs: The values to substitute into the prompt string
19
+ :return: The formatted prompt string
20
+ """
21
+ matches = self._pattern.findall(self.prompt)
22
+ return self.prompt.format(**{match: kwargs.get(match, "") for match in matches})
23
+
24
+ def get_input_variables(self):
25
+ """
26
+ Gets the list of input variable names from the prompt string.
27
+
28
+ :return: List of input variable names
29
+ """
30
+ return self._pattern.findall(self.prompt)
31
+
32
+
33
+ class RolePrompt(BasePrompt):
34
+ def __init__(self, prompt, role: str):
35
+ """
36
+ Initializes the RolePrompt object with a prompt template and a role.
37
+
38
+ :param prompt: A string that can contain placeholders within curly braces
39
+ :param role: The role for the message ('system', 'user', or 'assistant')
40
+ """
41
+ super().__init__(prompt)
42
+ self.role = role
43
+
44
+ def create_message(self, **kwargs):
45
+ """
46
+ Creates a message dictionary with a role and a formatted message.
47
+
48
+ :param kwargs: The values to substitute into the prompt string
49
+ :return: Dictionary containing the role and the formatted message
50
+ """
51
+ return {"role": self.role, "content": self.format_prompt(**kwargs)}
52
+
53
+
54
+ class SystemRolePrompt(RolePrompt):
55
+ def __init__(self, prompt: str):
56
+ super().__init__(prompt, "system")
57
+
58
+
59
+ class UserRolePrompt(RolePrompt):
60
+ def __init__(self, prompt: str):
61
+ super().__init__(prompt, "user")
62
+
63
+
64
+ class AssistantRolePrompt(RolePrompt):
65
+ def __init__(self, prompt: str):
66
+ super().__init__(prompt, "assistant")
67
+
68
+
69
+ if __name__ == "__main__":
70
+ prompt = BasePrompt("Hello {name}, you are {age} years old")
71
+ print(prompt.format_prompt(name="John", age=30))
72
+
73
+ prompt = SystemRolePrompt("Hello {name}, you are {age} years old")
74
+ print(prompt.create_message(name="John", age=30))
75
+ print(prompt.get_input_variables())
aimakerspace/text_utils.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import List
3
+
4
+
5
+ class TextFileLoader:
6
+ def __init__(self, path: str, encoding: str = "utf-8"):
7
+ self.documents = []
8
+ self.path = path
9
+ self.encoding = encoding
10
+
11
+ def load(self):
12
+ if os.path.isdir(self.path):
13
+ self.load_directory()
14
+ elif os.path.isfile(self.path) and self.path.endswith(".txt"):
15
+ self.load_file()
16
+ else:
17
+ raise ValueError(
18
+ "Provided path is neither a valid directory nor a .txt file."
19
+ )
20
+
21
+ def load_file(self):
22
+ with open(self.path, "r", encoding=self.encoding) as f:
23
+ self.documents.append(f.read())
24
+
25
+ def load_directory(self):
26
+ for root, _, files in os.walk(self.path):
27
+ for file in files:
28
+ if file.endswith(".txt"):
29
+ with open(
30
+ os.path.join(root, file), "r", encoding=self.encoding
31
+ ) as f:
32
+ self.documents.append(f.read())
33
+
34
+ def load_documents(self):
35
+ self.load()
36
+ return self.documents
37
+
38
+
39
+ class CharacterTextSplitter:
40
+ def __init__(
41
+ self,
42
+ chunk_size: int = 1000,
43
+ chunk_overlap: int = 200,
44
+ ):
45
+ assert (
46
+ chunk_size > chunk_overlap
47
+ ), "Chunk size must be greater than chunk overlap"
48
+
49
+ self.chunk_size = chunk_size
50
+ self.chunk_overlap = chunk_overlap
51
+
52
+ def split(self, text: str) -> List[str]:
53
+ chunks = []
54
+ for i in range(0, len(text), self.chunk_size - self.chunk_overlap):
55
+ chunks.append(text[i : i + self.chunk_size])
56
+ return chunks
57
+
58
+ def split_texts(self, texts: List[str]) -> List[str]:
59
+ chunks = []
60
+ for text in texts:
61
+ chunks.extend(self.split(text))
62
+ return chunks
63
+
64
+
65
+ if __name__ == "__main__":
66
+ loader = TextFileLoader("data/KingLear.txt")
67
+ loader.load()
68
+ splitter = CharacterTextSplitter()
69
+ chunks = splitter.split_texts(loader.documents)
70
+ print(len(chunks))
71
+ print(chunks[0])
72
+ print("--------")
73
+ print(chunks[1])
74
+ print("--------")
75
+ print(chunks[-2])
76
+ print("--------")
77
+ print(chunks[-1])
aimakerspace/vectordatabase.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from collections import defaultdict
3
+ from typing import List, Tuple, Callable
4
+ from aimakerspace.openai_utils.embedding import EmbeddingModel
5
+ import asyncio
6
+
7
+
8
+ def cosine_similarity(vector_a: np.array, vector_b: np.array) -> float:
9
+ """Computes the cosine similarity between two vectors."""
10
+ dot_product = np.dot(vector_a, vector_b)
11
+ norm_a = np.linalg.norm(vector_a)
12
+ norm_b = np.linalg.norm(vector_b)
13
+ return dot_product / (norm_a * norm_b)
14
+
15
+
16
+ class VectorDatabase:
17
+ def __init__(self, embedding_model: EmbeddingModel = None):
18
+ self.vectors = defaultdict(np.array)
19
+ self.embedding_model = embedding_model or EmbeddingModel()
20
+
21
+ def insert(self, key: str, vector: np.array) -> None:
22
+ self.vectors[key] = vector
23
+
24
+ def search(
25
+ self,
26
+ query_vector: np.array,
27
+ k: int,
28
+ distance_measure: Callable = cosine_similarity,
29
+ ) -> List[Tuple[str, float]]:
30
+ scores = [
31
+ (key, distance_measure(query_vector, vector))
32
+ for key, vector in self.vectors.items()
33
+ ]
34
+ return sorted(scores, key=lambda x: x[1], reverse=True)[:k]
35
+
36
+ def search_by_text(
37
+ self,
38
+ query_text: str,
39
+ k: int,
40
+ distance_measure: Callable = cosine_similarity,
41
+ return_as_text: bool = False,
42
+ ) -> List[Tuple[str, float]]:
43
+ query_vector = self.embedding_model.get_embedding(query_text)
44
+ results = self.search(query_vector, k, distance_measure)
45
+ return [result[0] for result in results] if return_as_text else results
46
+
47
+ def retrieve_from_key(self, key: str) -> np.array:
48
+ return self.vectors.get(key, None)
49
+
50
+ async def abuild_from_list(self, list_of_text: List[str]) -> "VectorDatabase":
51
+ embeddings = await self.embedding_model.async_get_embeddings(list_of_text)
52
+ for text, embedding in zip(list_of_text, embeddings):
53
+ self.insert(text, np.array(embedding))
54
+ return self
55
+
56
+
57
+ if __name__ == "__main__":
58
+ list_of_text = [
59
+ "I like to eat broccoli and bananas.",
60
+ "I ate a banana and spinach smoothie for breakfast.",
61
+ "Chinchillas and kittens are cute.",
62
+ "My sister adopted a kitten yesterday.",
63
+ "Look at this cute hamster munching on a piece of broccoli.",
64
+ ]
65
+
66
+ vector_db = VectorDatabase()
67
+ vector_db = asyncio.run(vector_db.abuild_from_list(list_of_text))
68
+ k = 2
69
+
70
+ searched_vector = vector_db.search_by_text("I think fruit is awesome!", k=k)
71
+ print(f"Closest {k} vector(s):", searched_vector)
72
+
73
+ retrieved_vector = vector_db.retrieve_from_key(
74
+ "I like to eat broccoli and bananas."
75
+ )
76
+ print("Retrieved vector:", retrieved_vector)
77
+
78
+ relevant_texts = vector_db.search_by_text(
79
+ "I think fruit is awesome!", k=k, return_as_text=True
80
+ )
81
+ print(f"Closest {k} text(s):", relevant_texts)
app.py CHANGED
@@ -19,6 +19,7 @@ Think through your response step by step.
19
  """
20
 
21
 
 
22
  @cl.on_chat_start # marks a function that will be executed at the start of a user session
23
  async def start_chat():
24
  settings = {
@@ -32,6 +33,28 @@ async def start_chat():
32
 
33
  cl.user_session.set("settings", settings)
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  @cl.on_message # marks a function that should be run each time the chatbot receives a message from a user
37
  async def main(message: cl.Message):
 
19
  """
20
 
21
 
22
+
23
  @cl.on_chat_start # marks a function that will be executed at the start of a user session
24
  async def start_chat():
25
  settings = {
 
33
 
34
  cl.user_session.set("settings", settings)
35
 
36
+ files = None
37
+ while files is None:
38
+ files = await cl.AskFileMessage(
39
+ content="Please upload a PDF file to begin",
40
+ accept=["application/pdf"],
41
+ max_files=10,
42
+ max_size_mb=10,
43
+ timeout=60
44
+ ).send()
45
+
46
+ # let the user know you are processing the file(s)
47
+
48
+ # decode the file
49
+
50
+ # split the text into chunks
51
+
52
+ # create a vector store
53
+
54
+ #
55
+
56
+
57
+
58
 
59
  @cl.on_message # marks a function that should be run each time the chatbot receives a message from a user
60
  async def main(message: cl.Message):
requirements.txt CHANGED
@@ -2,4 +2,9 @@ chainlit==0.7.700
2
  cohere==4.37
3
  openai==1.3.5
4
  tiktoken==0.5.1
5
- python-dotenv==1.0.0
 
 
 
 
 
 
2
  cohere==4.37
3
  openai==1.3.5
4
  tiktoken==0.5.1
5
+ python-dotenv==1.0.0
6
+ numpy==1.25.2
7
+ pandas
8
+ scikit-learn
9
+ matplotlib
10
+ plotly