jeevan commited on
Commit
249d2c8
·
1 Parent(s): abaf897

versions pinned

Browse files
Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM python:3.9
2
  RUN useradd -m -u 1000 user
3
  USER user
4
  ENV HOME=/home/user \
 
1
+ FROM python:3.11.9
2
  RUN useradd -m -u 1000 user
3
  USER user
4
  ENV HOME=/home/user \
aimakerspace/openai_utils/embedding.py CHANGED
@@ -7,11 +7,12 @@ import asyncio
7
 
8
 
9
  class EmbeddingModel:
10
- def __init__(self, embeddings_model_name: str = "text-embedding-3-small"):
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(
@@ -22,28 +23,35 @@ class EmbeddingModel:
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
 
7
 
8
 
9
  class EmbeddingModel:
10
+ def __init__(self, embeddings_model_name: str = "text-embedding-3-small", dimensions: int = None):
11
  load_dotenv()
12
  self.openai_api_key = os.getenv("OPENAI_API_KEY")
13
  self.async_client = AsyncOpenAI()
14
  self.client = OpenAI()
15
+ self.dimensions = dimensions
16
 
17
  if self.openai_api_key is None:
18
  raise ValueError(
 
23
 
24
  async def async_get_embeddings(self, list_of_text: List[str]) -> List[List[float]]:
25
  embedding_response = await self.async_client.embeddings.create(
26
+ input=list_of_text, model=self.embeddings_model_name, dimensions=self.dimensions
27
  )
28
 
29
  return [embeddings.embedding for embeddings in embedding_response.data]
30
 
31
+ async def async_get_embeddings_openai(self, list_of_text: List[str]) :
32
+ embedding_response = await self.async_client.embeddings.create(
33
+ input=list_of_text, model=self.embeddings_model_name, dimensions=self.dimensions
34
+ )
35
+
36
+ return embedding_response
37
+
38
  async def async_get_embedding(self, text: str) -> List[float]:
39
  embedding = await self.async_client.embeddings.create(
40
+ input=text, model=self.embeddings_model_name, dimensions=self.dimensions
41
  )
42
 
43
  return embedding.data[0].embedding
44
 
45
  def get_embeddings(self, list_of_text: List[str]) -> List[List[float]]:
46
  embedding_response = self.client.embeddings.create(
47
+ input=list_of_text, model=self.embeddings_model_name, dimensions=self.dimensions
48
  )
49
 
50
  return [embeddings.embedding for embeddings in embedding_response.data]
51
 
52
  def get_embedding(self, text: str) -> List[float]:
53
  embedding = self.client.embeddings.create(
54
+ input=text, model=self.embeddings_model_name, dimensions=self.dimensions
55
  )
56
 
57
  return embedding.data[0].embedding
aimakerspace/text_utils.py CHANGED
@@ -1,6 +1,8 @@
 
 
1
  import os
2
  from typing import List
3
-
4
 
5
  class TextFileLoader:
6
  def __init__(self, path: str, encoding: str = "utf-8"):
@@ -35,8 +37,39 @@ class TextFileLoader:
35
  self.load()
36
  return self.documents
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
- class CharacterTextSplitter:
40
  def __init__(
41
  self,
42
  chunk_size: int = 1000,
@@ -60,18 +93,3 @@ class CharacterTextSplitter:
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])
 
1
+ # aimakerspace.text_utils.py
2
+
3
  import os
4
  from typing import List
5
+ from langchain_community.document_loaders import PyPDFLoader
6
 
7
  class TextFileLoader:
8
  def __init__(self, path: str, encoding: str = "utf-8"):
 
37
  self.load()
38
  return self.documents
39
 
40
+ class PdfFileLoader:
41
+ def __init__(self, path: str):
42
+ self.documents = []
43
+ self.path = path
44
+
45
+ def load(self):
46
+ if os.path.isdir(self.path):
47
+ self.load_directory()
48
+ elif os.path.isfile(self.path) and self.path.endswith(".pdf"):
49
+ self.load_file()
50
+ else:
51
+ raise ValueError(
52
+ "Provided path is neither a valid directory nor a .pdf file."
53
+ )
54
+
55
+ def load_file(self):
56
+ pdf_loader = PyPDFLoader(self.path)
57
+ pdf_pages = pdf_loader.load_and_split() # Defaults to RecursiveCharacterTextSplitter.
58
+ self.documents = [page.page_content for page in pdf_pages]
59
+
60
+ def load_directory(self):
61
+ for root, _, files in os.walk(self.path):
62
+ for file in files:
63
+ if file.endswith(".pdf"):
64
+ pdf_loader = PyPDFLoader(file.path)
65
+ pdf_pages = pdf_loader.load_and_split()
66
+ self.documents.append([page.page_content for page in pdf_pages])
67
+
68
+ def load_documents(self):
69
+ self.load()
70
+ return self.documents
71
 
72
+ class CharacterTextSplitter():
73
  def __init__(
74
  self,
75
  chunk_size: int = 1000,
 
93
  for text in texts:
94
  chunks.extend(self.split(text))
95
  return chunks
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aimakerspace/vectordatabase.py CHANGED
@@ -1,9 +1,13 @@
 
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."""
@@ -13,10 +17,62 @@ def cosine_similarity(vector_a: np.array, vector_b: np.array) -> float:
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
@@ -41,16 +97,39 @@ class VectorDatabase:
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
 
 
1
+ from enum import Enum
2
  import numpy as np
3
  from collections import defaultdict
4
  from typing import List, Tuple, Callable
5
  from aimakerspace.openai_utils.embedding import EmbeddingModel
6
  import asyncio
7
+ from qdrant_client import models, QdrantClient
8
+ from qdrant_client.models import PointStruct,VectorParams,Distance
9
 
10
+ collection_name = "embedding_collection"
11
 
12
  def cosine_similarity(vector_a: np.array, vector_b: np.array) -> float:
13
  """Computes the cosine similarity between two vectors."""
 
17
  return dot_product / (norm_a * norm_b)
18
 
19
 
20
+ def euclidean_distance(vector_a: np.array, vector_b: np.array) -> float:
21
+ """Computes the Euclidean distance between two vectors."""
22
+ return np.sqrt(np.sum((vector_a - vector_b) ** 2))
23
+
24
+
25
+ def manhattan_distance(vector_a: np.array, vector_b: np.array) -> float:
26
+ """Computes the Manhattan distance between two vectors."""
27
+ return np.sum(np.abs(vector_a - vector_b))
28
+
29
+
30
+ def minkowski_distance(vector_a: np.array, vector_b: np.array, p: float) -> float:
31
+ """
32
+ Computes the Minkowski distance between two vectors.
33
+
34
+ Parameters:
35
+ vector_a (np.array): First vector.
36
+ vector_b (np.array): Second vector.
37
+ p (float): The order of the norm. For example, p=1 gives Manhattan distance, p=2 gives Euclidean distance.
38
+
39
+ Returns:
40
+ float: Minkowski distance between vector_a and vector_b.
41
+ """
42
+ # Ensure the input vectors are NumPy arrays
43
+ vector_a = np.asarray(vector_a)
44
+ vector_b = np.asarray(vector_b)
45
+
46
+ # Compute Minkowski distance
47
+ distance = np.sum(np.abs(vector_a - vector_b) ** p) ** (1 / p)
48
+ return distance
49
+
50
+
51
+ class DistanceMeasure(Enum):
52
+ COSINE_SIMILARITY = cosine_similarity
53
+ EUCLIDEAN_DISTANCE = euclidean_distance
54
+ MANHATTAN_DISTANCE = manhattan_distance
55
+ MINKOWSKI_DISTANCE = minkowski_distance
56
+
57
+
58
+ class VectorDatabaseOptions(Enum):
59
+ DICTIONARY = "dictionary"
60
+ QDRANT = "qdrant"
61
+
62
+
63
  class VectorDatabase:
64
+ def __init__(
65
+ self,
66
+ vector_db_options: VectorDatabaseOptions,
67
+ embedding_model: EmbeddingModel = None,
68
+ ):
69
+ self.vectors = None
70
+ self.vector_db_options = vector_db_options
71
  self.embedding_model = embedding_model or EmbeddingModel()
72
+ if vector_db_options == VectorDatabaseOptions.DICTIONARY:
73
+ self.vectors = defaultdict(np.array)
74
+ if vector_db_options == VectorDatabaseOptions.QDRANT:
75
+ self.qdrant_client = QdrantClient(":memory:")
76
 
77
  def insert(self, key: str, vector: np.array) -> None:
78
  self.vectors[key] = vector
 
97
  return_as_text: bool = False,
98
  ) -> List[Tuple[str, float]]:
99
  query_vector = self.embedding_model.get_embedding(query_text)
100
+ if self.vector_db_options == VectorDatabaseOptions.DICTIONARY:
101
+ results = self.search(query_vector, k, distance_measure)
102
+ return [result[0] for result in results] if return_as_text else results
103
+ if self.vector_db_options == VectorDatabaseOptions.QDRANT:
104
+ search_result = self.qdrant_client.search(collection_name,query_vector=query_vector)
105
+ return [(point.payload["text"],point.score) for point in search_result]
106
 
107
  def retrieve_from_key(self, key: str) -> np.array:
108
  return self.vectors.get(key, None)
109
 
110
  async def abuild_from_list(self, list_of_text: List[str]) -> "VectorDatabase":
111
+ if self.vector_db_options == VectorDatabaseOptions.DICTIONARY:
112
+ embeddings = await self.embedding_model.async_get_embeddings(list_of_text)
113
+ for text, embedding in zip(list_of_text, embeddings):
114
+ self.insert(text, np.array(embedding))
115
+ if self.vector_db_options == VectorDatabaseOptions.QDRANT:
116
+ embeddings_response = await self.embedding_model.async_get_embeddings_openai(list_of_text)
117
+ points = [
118
+ PointStruct(
119
+ id=idx,
120
+ vector=data.embedding,
121
+ payload={"text": text},
122
+ )
123
+ for idx, (data, text) in enumerate(zip(embeddings_response.data, list_of_text))
124
+ ]
125
+ self.qdrant_client.create_collection(
126
+ collection_name,
127
+ vectors_config=VectorParams(
128
+ size=self.embedding_model.dimensions,
129
+ distance=Distance.COSINE,
130
+ ),
131
+ )
132
+ self.qdrant_client.upsert(collection_name, points)
133
  return self
134
 
135
 
app.py CHANGED
@@ -1,20 +1,27 @@
1
  import os
 
2
  from typing import List
3
  from chainlit.types import AskFileResponse
4
- from aimakerspace.text_utils import CharacterTextSplitter, TextFileLoader
 
5
  from aimakerspace.openai_utils.prompts import (
6
  UserRolePrompt,
7
  SystemRolePrompt,
8
  AssistantRolePrompt,
9
  )
10
  from aimakerspace.openai_utils.embedding import EmbeddingModel
11
- from aimakerspace.vectordatabase import VectorDatabase
12
  from aimakerspace.openai_utils.chatmodel import ChatOpenAI
13
  import chainlit as cl
 
14
 
 
 
 
 
 
15
  system_template = """\
16
  Use the following context to answer a users question. If you cannot find the answer in the context, say you don't know the answer."""
17
- system_role_prompt = SystemRolePrompt(system_template)
18
 
19
  user_prompt_template = """\
20
  Context:
@@ -23,100 +30,130 @@ Context:
23
  Question:
24
  {question}
25
  """
26
- user_role_prompt = UserRolePrompt(user_prompt_template)
27
-
28
- class RetrievalAugmentedQAPipeline:
29
- def __init__(self, llm: ChatOpenAI(), vector_db_retriever: VectorDatabase) -> None:
30
- self.llm = llm
31
- self.vector_db_retriever = vector_db_retriever
32
-
33
- async def arun_pipeline(self, user_query: str):
34
- context_list = self.vector_db_retriever.search_by_text(user_query, k=4)
35
-
36
- context_prompt = ""
37
- for context in context_list:
38
- context_prompt += context[0] + "\n"
39
-
40
- formatted_system_prompt = system_role_prompt.create_message()
41
-
42
- formatted_user_prompt = user_role_prompt.create_message(question=user_query, context=context_prompt)
43
-
44
- async def generate_response():
45
- async for chunk in self.llm.astream([formatted_system_prompt, formatted_user_prompt]):
46
- yield chunk
47
 
48
- return {"response": generate_response(), "context": context_list}
49
-
50
- text_splitter = CharacterTextSplitter()
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
- def process_text_file(file: AskFileResponse):
54
  import tempfile
55
 
56
- with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as temp_file:
 
 
57
  temp_file_path = temp_file.name
58
 
59
- with open(temp_file_path, "wb") as f:
60
- f.write(file.content)
 
 
 
61
 
62
  text_loader = TextFileLoader(temp_file_path)
63
  documents = text_loader.load_documents()
64
- texts = text_splitter.split_texts(documents)
 
 
65
  return texts
66
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  @cl.on_chat_start
69
  async def on_chat_start():
 
 
 
 
 
 
70
  files = None
71
 
72
  # Wait for the user to upload a file
73
  while files == None:
 
74
  files = await cl.AskFileMessage(
75
- content="Please upload a Text File file to begin!",
76
- accept=["text/plain"],
77
- max_size_mb=2,
 
78
  timeout=180,
79
  ).send()
80
-
81
- file = files[0]
82
-
83
- msg = cl.Message(
84
- content=f"Processing `{file.name}`...", disable_human_feedback=True
85
- )
 
 
 
86
  await msg.send()
87
 
88
- # load the file
89
- texts = process_text_file(file)
90
-
91
  print(f"Processing {len(texts)} text chunks")
92
-
93
  # Create a dict vector store
94
- vector_db = VectorDatabase()
 
 
95
  vector_db = await vector_db.abuild_from_list(texts)
96
-
97
  chat_openai = ChatOpenAI()
98
 
99
  # Create a chain
100
- retrieval_augmented_qa_pipeline = RetrievalAugmentedQAPipeline(
101
- vector_db_retriever=vector_db,
102
- llm=chat_openai
103
  )
104
-
105
  # Let the user know that the system is ready
106
- msg.content = f"Processing `{file.name}` done. You can now ask questions!"
107
- await msg.update()
108
 
109
  cl.user_session.set("chain", retrieval_augmented_qa_pipeline)
110
 
111
 
112
  @cl.on_message
113
- async def main(message):
114
- chain = cl.user_session.get("chain")
 
 
 
115
 
116
  msg = cl.Message(content="")
117
  result = await chain.arun_pipeline(message.content)
118
 
119
- async for stream_resp in result["response"]:
120
  await msg.stream_token(stream_resp)
121
 
122
- await msg.send()
 
 
 
 
 
 
1
  import os
2
+ from openai import AsyncOpenAI
3
  from typing import List
4
  from chainlit.types import AskFileResponse
5
+ from chainlit.cli import run_chainlit
6
+ from aimakerspace.text_utils import CharacterTextSplitter, PdfFileLoader, TextFileLoader
7
  from aimakerspace.openai_utils.prompts import (
8
  UserRolePrompt,
9
  SystemRolePrompt,
10
  AssistantRolePrompt,
11
  )
12
  from aimakerspace.openai_utils.embedding import EmbeddingModel
13
+ from aimakerspace.vectordatabase import VectorDatabase, VectorDatabaseOptions
14
  from aimakerspace.openai_utils.chatmodel import ChatOpenAI
15
  import chainlit as cl
16
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
17
 
18
+
19
+ # Instrument the OpenAI client
20
+ # cl.instrument_openai()
21
+
22
+ ##### Prompt Templates #####
23
  system_template = """\
24
  Use the following context to answer a users question. If you cannot find the answer in the context, say you don't know the answer."""
 
25
 
26
  user_prompt_template = """\
27
  Context:
 
30
  Question:
31
  {question}
32
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
+ system_role_prompt = SystemRolePrompt(system_template)
35
+ user_role_prompt = UserRolePrompt(user_prompt_template)
 
36
 
37
+ ### Text Chunking ###
38
+
39
+ # text_splitter = CharacterTextSplitter()
40
+ text_splitter = RecursiveCharacterTextSplitter(
41
+ separators=[
42
+ "\n\n",
43
+ "\n",
44
+ " ",
45
+ ".",
46
+ ",",
47
+ "\u200b", # Zero-width space
48
+ "\uff0c", # Fullwidth comma
49
+ "\u3001", # Ideographic comma
50
+ "\uff0e", # Fullwidth full stop
51
+ "\u3002", # Ideographic full stop
52
+ "",
53
+ ],
54
+ )
55
 
56
+ def process_text_file(file: AskFileResponse) -> List[str]:
57
  import tempfile
58
 
59
+ with tempfile.NamedTemporaryFile(
60
+ mode="w", delete=False, suffix=".txt"
61
+ ) as temp_file:
62
  temp_file_path = temp_file.name
63
 
64
+ with open(file.path, "r", encoding="utf-8") as f:
65
+ text = f.read()
66
+
67
+ with open(temp_file_path, "w") as f:
68
+ f.write(text)
69
 
70
  text_loader = TextFileLoader(temp_file_path)
71
  documents = text_loader.load_documents()
72
+ texts = []
73
+ for doc in documents:
74
+ texts.append(text_splitter.split_text(doc))
75
  return texts
76
 
77
+ def process_pdf_file(file: AskFileResponse) -> List[str]:
78
+ pdf_loader = PdfFileLoader(file.path)
79
+ texts = pdf_loader.load_documents() # Also handles splitting the text in this case pages
80
+ return texts
81
+
82
+ async def send_new_message(content, elemets=None):
83
+ msg = cl.Message(content,elements=elemets)
84
+ await msg.send()
85
+ return msg
86
+
87
 
88
  @cl.on_chat_start
89
  async def on_chat_start():
90
+ print("On Chat Start")
91
+ # await send_new_message("Welcome to the Chat with Files app!")
92
+ msg = cl.Message(content="Welcome to the Chat with Files app!")
93
+ await msg.send()
94
+ print("After First message")
95
+
96
  files = None
97
 
98
  # Wait for the user to upload a file
99
  while files == None:
100
+
101
  files = await cl.AskFileMessage(
102
+ content="Please upload a text file to begin!",
103
+ accept=["text/plain", "application/pdf"],
104
+ max_size_mb=10,
105
+ max_files=4,
106
  timeout=180,
107
  ).send()
108
+ texts : List[str] = []
109
+ for file in files:
110
+ if file.type == "application/pdf":
111
+ texts.extend(process_pdf_file(file))
112
+ if file.type == "text/plain":
113
+ texts.extend(process_text_file(file))
114
+
115
+ # await send_new_message(content=f"Processing `{file.name}`...")
116
+ msg = cl.Message(content=f"Processing `{file.name}`...")
117
  await msg.send()
118
 
 
 
 
119
  print(f"Processing {len(texts)} text chunks")
120
+
121
  # Create a dict vector store
122
+ vector_db_options =VectorDatabaseOptions.QDRANT
123
+ embedding_model = EmbeddingModel(embeddings_model_name= "text-embedding-3-small",dimensions=1000)
124
+ vector_db = VectorDatabase(vector_db_options,embedding_model)
125
  vector_db = await vector_db.abuild_from_list(texts)
126
+
127
  chat_openai = ChatOpenAI()
128
 
129
  # Create a chain
130
+ retrieval_augmented_qa_pipeline = RetrievalAugmentedQAPipeline(system_role_prompt, user_role_prompt,
131
+ vector_db_retriever=vector_db, llm=chat_openai
 
132
  )
133
+
134
  # Let the user know that the system is ready
135
+ msg = cl.Message(content=f"Processing `{file.name}` done. You can now ask questions!")
136
+ await msg.send()
137
 
138
  cl.user_session.set("chain", retrieval_augmented_qa_pipeline)
139
 
140
 
141
  @cl.on_message
142
+ async def main(message: cl.Message):
143
+ msg = cl.Message(content="on message")
144
+ await msg.send()
145
+
146
+ chain :RetrievalAugmentedQAPipeline = cl.user_session.get("chain")
147
 
148
  msg = cl.Message(content="")
149
  result = await chain.arun_pipeline(message.content)
150
 
151
+ async for stream_resp in result.get('response'):
152
  await msg.stream_token(stream_resp)
153
 
154
+ await msg.send()
155
+ cl.user_session.set("chain", chain)
156
+
157
+
158
+ if __name__ == "__main__":
159
+ run_chainlit(__file__)
requirements.txt CHANGED
@@ -1,7 +1,7 @@
1
- numpy
2
- chainlit==0.7.700
3
- openai
 
4
  langchain-text-splitters
5
- pypdf
6
  langchain-community
7
- qdrant-client
 
1
+ numpy==1.26.4
2
+ chainlit==0.7.700 # 1.1.402
3
+ openai==1.3.5
4
+ qdrant-client==1.11.0
5
  langchain-text-splitters
 
6
  langchain-community
7
+ pypdf