Nagesh Muralidhar commited on
Commit
273a5e1
·
1 Parent(s): 806ce95

Commiting fastapi code

Browse files
.gitattributes copy ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm 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
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ .chainlit/
3
+ .venv/
4
+ .env
5
+ DeepSeek_R1.pdf
6
+ node_modules/
7
+ frontend/node_modules/
BuildingAChainlitApp.md ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Building a Chainlit App
2
+
3
+ What if we want to take our Week 1 Day 2 assignment - [Pythonic RAG](https://github.com/AI-Maker-Space/AIE4/tree/main/Week%201/Day%202) - and bring it out of the notebook?
4
+
5
+ Well - we'll cover exactly that here!
6
+
7
+ ## Anatomy of a Chainlit Application
8
+
9
+ [Chainlit](https://docs.chainlit.io/get-started/overview) is a Python package similar to Streamlit that lets users write a backend and a front end in a single (or multiple) Python file(s). It is mainly used for prototyping LLM-based Chat Style Applications - though it is used in production in some settings with 1,000,000s of MAUs (Monthly Active Users).
10
+
11
+ The primary method of customizing and interacting with the Chainlit UI is through a few critical [decorators](https://blog.hubspot.com/website/decorators-in-python).
12
+
13
+ > NOTE: Simply put, the decorators (in Chainlit) are just ways we can "plug-in" to the functionality in Chainlit.
14
+
15
+ We'll be concerning ourselves with three main scopes:
16
+
17
+ 1. On application start - when we start the Chainlit application with a command like `chainlit run app.py`
18
+ 2. On chat start - when a chat session starts (a user opens the web browser to the address hosting the application)
19
+ 3. On message - when the users sends a message through the input text box in the Chainlit UI
20
+
21
+ Let's dig into each scope and see what we're doing!
22
+
23
+ ## On Application Start:
24
+
25
+ The first thing you'll notice is that we have the traditional "wall of imports" this is to ensure we have everything we need to run our application.
26
+
27
+ ```python
28
+ import os
29
+ from typing import List
30
+ from chainlit.types import AskFileResponse
31
+ from aimakerspace.text_utils import CharacterTextSplitter, TextFileLoader
32
+ from aimakerspace.openai_utils.prompts import (
33
+ UserRolePrompt,
34
+ SystemRolePrompt,
35
+ AssistantRolePrompt,
36
+ )
37
+ from aimakerspace.openai_utils.embedding import EmbeddingModel
38
+ from aimakerspace.vectordatabase import VectorDatabase
39
+ from aimakerspace.openai_utils.chatmodel import ChatOpenAI
40
+ import chainlit as cl
41
+ ```
42
+
43
+ Next up, we have some prompt templates. As all sessions will use the same prompt templates without modification, and we don't need these templates to be specific per template - we can set them up here - at the application scope.
44
+
45
+ ```python
46
+ system_template = """\
47
+ 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."""
48
+ system_role_prompt = SystemRolePrompt(system_template)
49
+
50
+ user_prompt_template = """\
51
+ Context:
52
+ {context}
53
+
54
+ Question:
55
+ {question}
56
+ """
57
+ user_role_prompt = UserRolePrompt(user_prompt_template)
58
+ ```
59
+
60
+ > NOTE: You'll notice that these are the exact same prompt templates we used from the Pythonic RAG Notebook in Week 1 Day 2!
61
+
62
+ Following that - we can create the Python Class definition for our RAG pipeline - or *chain*, as we'll refer to it in the rest of this walkthrough.
63
+
64
+ Let's look at the definition first:
65
+
66
+ ```python
67
+ class RetrievalAugmentedQAPipeline:
68
+ def __init__(self, llm: ChatOpenAI(), vector_db_retriever: VectorDatabase) -> None:
69
+ self.llm = llm
70
+ self.vector_db_retriever = vector_db_retriever
71
+
72
+ async def arun_pipeline(self, user_query: str):
73
+ ### RETRIEVAL
74
+ context_list = self.vector_db_retriever.search_by_text(user_query, k=4)
75
+
76
+ context_prompt = ""
77
+ for context in context_list:
78
+ context_prompt += context[0] + "\n"
79
+
80
+ ### AUGMENTED
81
+ formatted_system_prompt = system_role_prompt.create_message()
82
+
83
+ formatted_user_prompt = user_role_prompt.create_message(question=user_query, context=context_prompt)
84
+
85
+
86
+ ### GENERATION
87
+ async def generate_response():
88
+ async for chunk in self.llm.astream([formatted_system_prompt, formatted_user_prompt]):
89
+ yield chunk
90
+
91
+ return {"response": generate_response(), "context": context_list}
92
+ ```
93
+
94
+ Notice a few things:
95
+
96
+ 1. We have modified this `RetrievalAugmentedQAPipeline` from the initial notebook to support streaming.
97
+ 2. In essence, our pipeline is *chaining* a few events together:
98
+ 1. We take our user query, and chain it into our Vector Database to collect related chunks
99
+ 2. We take those contexts and our user's questions and chain them into the prompt templates
100
+ 3. We take that prompt template and chain it into our LLM call
101
+ 4. We chain the response of the LLM call to the user
102
+ 3. We are using a lot of `async` again!
103
+
104
+ Now, we're going to create a helper function for processing uploaded text files.
105
+
106
+ First, we'll instantiate a shared `CharacterTextSplitter`.
107
+
108
+ ```python
109
+ text_splitter = CharacterTextSplitter()
110
+ ```
111
+
112
+ Now we can define our helper.
113
+
114
+ ```python
115
+ def process_text_file(file: AskFileResponse):
116
+ import tempfile
117
+
118
+ with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as temp_file:
119
+ temp_file_path = temp_file.name
120
+
121
+ with open(temp_file_path, "wb") as f:
122
+ f.write(file.content)
123
+
124
+ text_loader = TextFileLoader(temp_file_path)
125
+ documents = text_loader.load_documents()
126
+ texts = text_splitter.split_texts(documents)
127
+ return texts
128
+ ```
129
+
130
+ Simply put, this downloads the file as a temp file, we load it in with `TextFileLoader` and then split it with our `TextSplitter`, and returns that list of strings!
131
+
132
+ #### QUESTION #1:
133
+
134
+ Why do we want to support streaming? What about streaming is important, or useful?
135
+
136
+ ## On Chat Start:
137
+
138
+ The next scope is where "the magic happens". On Chat Start is when a user begins a chat session. This will happen whenever a user opens a new chat window, or refreshes an existing chat window.
139
+
140
+ You'll see that our code is set-up to immediately show the user a chat box requesting them to upload a file.
141
+
142
+ ```python
143
+ while files == None:
144
+ files = await cl.AskFileMessage(
145
+ content="Please upload a Text File file to begin!",
146
+ accept=["text/plain"],
147
+ max_size_mb=2,
148
+ timeout=180,
149
+ ).send()
150
+ ```
151
+
152
+ Once we've obtained the text file - we'll use our processing helper function to process our text!
153
+
154
+ After we have processed our text file - we'll need to create a `VectorDatabase` and populate it with our processed chunks and their related embeddings!
155
+
156
+ ```python
157
+ vector_db = VectorDatabase()
158
+ vector_db = await vector_db.abuild_from_list(texts)
159
+ ```
160
+
161
+ Once we have that piece completed - we can create the chain we'll be using to respond to user queries!
162
+
163
+ ```python
164
+ retrieval_augmented_qa_pipeline = RetrievalAugmentedQAPipeline(
165
+ vector_db_retriever=vector_db,
166
+ llm=chat_openai
167
+ )
168
+ ```
169
+
170
+ Now, we'll save that into our user session!
171
+
172
+ > NOTE: Chainlit has some great documentation about [User Session](https://docs.chainlit.io/concepts/user-session).
173
+
174
+ ### QUESTION #2:
175
+
176
+ Why are we using User Session here? What about Python makes us need to use this? Why not just store everything in a global variable?
177
+
178
+ ## On Message
179
+
180
+ First, we load our chain from the user session:
181
+
182
+ ```python
183
+ chain = cl.user_session.get("chain")
184
+ ```
185
+
186
+ Then, we run the chain on the content of the message - and stream it to the front end - that's it!
187
+
188
+ ```python
189
+ msg = cl.Message(content="")
190
+ result = await chain.arun_pipeline(message.content)
191
+
192
+ async for stream_resp in result["response"]:
193
+ await msg.stream_token(stream_resp)
194
+ ```
195
+
196
+ ## 🎉
197
+
198
+ With that - you've created a Chainlit application that moves our Pythonic RAG notebook to a Chainlit application!
199
+
200
+ ## 🚧 CHALLENGE MODE 🚧
201
+
202
+ For an extra challenge - modify the behaviour of your applciation by integrating changes you made to your Pythonic RAG notebook (using new retrieval methods, etc.)
203
+
204
+ If you're still looking for a challenge, or didn't make any modifications to your Pythonic RAG notebook:
205
+
206
+ 1) Allow users to upload PDFs (this will require you to build a PDF parser as well)
207
+ 2) Modify the VectorStore to leverage [Qdrant](https://python-client.qdrant.tech/)
208
+
209
+ > NOTE: The motivation for these challenges is simple - the beginning of the course is extremely information dense, and people come from all kinds of different technical backgrounds. In order to ensure that all learners are able to engage with the content confidently and comfortably, we want to focus on the basic units of technical competency required. This leads to a situation where some learners, who came in with more robust technical skills, find the introductory material to be too simple - and these open-ended challenges help us do this!
210
+
211
+
212
+
213
+
214
+
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use the official Python image from the Docker Hub with Python 3.10
2
+ FROM python:3.10-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Copy the requirements file and install dependencies
8
+ COPY requirements.txt ./
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ # Copy the rest of the application code
12
+ COPY . .
13
+
14
+ # Expose the port the app runs on
15
+ EXPOSE 8000
16
+
17
+ # Command to run the application
18
+ CMD ["python", "api.py"]
README.md CHANGED
@@ -1,11 +1,400 @@
1
  ---
2
- title: DeployPythonicRAG FastAPI
3
- emoji: 🏃
4
- colorFrom: red
5
- colorTo: yellow
6
  sdk: docker
7
  pinned: false
8
- short_description: DeployPythonicRAG-FastAPI
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: DeployPythonicRAG
3
+ emoji: 📉
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: docker
7
  pinned: false
8
+ license: apache-2.0
9
  ---
10
 
11
+
12
+ # Deploying Pythonic Chat With Your Text File Application
13
+
14
+ In today's breakout rooms, we will be following the process that you saw during the challenge.
15
+
16
+ Today, we will repeat the same process - but powered by our Pythonic RAG implementation we created last week.
17
+
18
+ You'll notice a few differences in the `app.py` logic - as well as a few changes to the `aimakerspace` package to get things working smoothly with Chainlit.
19
+
20
+ > NOTE: If you want to run this locally - be sure to use `uv sync`, and then `uv run chainlit run app.py` to start the application outside of Docker.
21
+
22
+ ## Reference Diagram (It's Busy, but it works)
23
+
24
+ ![image](https://i.imgur.com/IaEVZG2.png)
25
+
26
+ ### Anatomy of a Chainlit Application
27
+
28
+ [Chainlit](https://docs.chainlit.io/get-started/overview) is a Python package similar to Streamlit that lets users write a backend and a front end in a single (or multiple) Python file(s). It is mainly used for prototyping LLM-based Chat Style Applications - though it is used in production in some settings with 1,000,000s of MAUs (Monthly Active Users).
29
+
30
+ The primary method of customizing and interacting with the Chainlit UI is through a few critical [decorators](https://blog.hubspot.com/website/decorators-in-python).
31
+
32
+ > NOTE: Simply put, the decorators (in Chainlit) are just ways we can "plug-in" to the functionality in Chainlit.
33
+
34
+ We'll be concerning ourselves with three main scopes:
35
+
36
+ 1. On application start - when we start the Chainlit application with a command like `chainlit run app.py`
37
+ 2. On chat start - when a chat session starts (a user opens the web browser to the address hosting the application)
38
+ 3. On message - when the users sends a message through the input text box in the Chainlit UI
39
+
40
+ Let's dig into each scope and see what we're doing!
41
+
42
+ ### On Application Start:
43
+
44
+ The first thing you'll notice is that we have the traditional "wall of imports" this is to ensure we have everything we need to run our application.
45
+
46
+ ```python
47
+ import os
48
+ from typing import List
49
+ from chainlit.types import AskFileResponse
50
+ from aimakerspace.text_utils import CharacterTextSplitter, TextFileLoader
51
+ from aimakerspace.openai_utils.prompts import (
52
+ UserRolePrompt,
53
+ SystemRolePrompt,
54
+ AssistantRolePrompt,
55
+ )
56
+ from aimakerspace.openai_utils.embedding import EmbeddingModel
57
+ from aimakerspace.vectordatabase import VectorDatabase
58
+ from aimakerspace.openai_utils.chatmodel import ChatOpenAI
59
+ import chainlit as cl
60
+ ```
61
+
62
+ Next up, we have some prompt templates. As all sessions will use the same prompt templates without modification, and we don't need these templates to be specific per template - we can set them up here - at the application scope.
63
+
64
+ ```python
65
+ system_template = """\
66
+ 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."""
67
+ system_role_prompt = SystemRolePrompt(system_template)
68
+
69
+ user_prompt_template = """\
70
+ Context:
71
+ {context}
72
+
73
+ Question:
74
+ {question}
75
+ """
76
+ user_role_prompt = UserRolePrompt(user_prompt_template)
77
+ ```
78
+
79
+ > NOTE: You'll notice that these are the exact same prompt templates we used from the Pythonic RAG Notebook in Week 1 Day 2!
80
+
81
+ Following that - we can create the Python Class definition for our RAG pipeline - or _chain_, as we'll refer to it in the rest of this walkthrough.
82
+
83
+ Let's look at the definition first:
84
+
85
+ ```python
86
+ class RetrievalAugmentedQAPipeline:
87
+ def __init__(self, llm: ChatOpenAI(), vector_db_retriever: VectorDatabase) -> None:
88
+ self.llm = llm
89
+ self.vector_db_retriever = vector_db_retriever
90
+
91
+ async def arun_pipeline(self, user_query: str):
92
+ ### RETRIEVAL
93
+ context_list = self.vector_db_retriever.search_by_text(user_query, k=4)
94
+
95
+ context_prompt = ""
96
+ for context in context_list:
97
+ context_prompt += context[0] + "\n"
98
+
99
+ ### AUGMENTED
100
+ formatted_system_prompt = system_role_prompt.create_message()
101
+
102
+ formatted_user_prompt = user_role_prompt.create_message(question=user_query, context=context_prompt)
103
+
104
+
105
+ ### GENERATION
106
+ async def generate_response():
107
+ async for chunk in self.llm.astream([formatted_system_prompt, formatted_user_prompt]):
108
+ yield chunk
109
+
110
+ return {"response": generate_response(), "context": context_list}
111
+ ```
112
+
113
+ Notice a few things:
114
+
115
+ 1. We have modified this `RetrievalAugmentedQAPipeline` from the initial notebook to support streaming.
116
+ 2. In essence, our pipeline is _chaining_ a few events together:
117
+ 1. We take our user query, and chain it into our Vector Database to collect related chunks
118
+ 2. We take those contexts and our user's questions and chain them into the prompt templates
119
+ 3. We take that prompt template and chain it into our LLM call
120
+ 4. We chain the response of the LLM call to the user
121
+ 3. We are using a lot of `async` again!
122
+
123
+ Now, we're going to create a helper function for processing uploaded text files.
124
+
125
+ First, we'll instantiate a shared `CharacterTextSplitter`.
126
+
127
+ ```python
128
+ text_splitter = CharacterTextSplitter()
129
+ ```
130
+
131
+ Now we can define our helper.
132
+
133
+ ```python
134
+ def process_file(file: AskFileResponse):
135
+ import tempfile
136
+ import shutil
137
+
138
+ print(f"Processing file: {file.name}")
139
+
140
+ # Create a temporary file with the correct extension
141
+ suffix = f".{file.name.split('.')[-1]}"
142
+ with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
143
+ # Copy the uploaded file content to the temporary file
144
+ shutil.copyfile(file.path, temp_file.name)
145
+ print(f"Created temporary file at: {temp_file.name}")
146
+
147
+ # Create appropriate loader
148
+ if file.name.lower().endswith('.pdf'):
149
+ loader = PDFLoader(temp_file.name)
150
+ else:
151
+ loader = TextFileLoader(temp_file.name)
152
+
153
+ try:
154
+ # Load and process the documents
155
+ documents = loader.load_documents()
156
+ texts = text_splitter.split_texts(documents)
157
+ return texts
158
+ finally:
159
+ # Clean up the temporary file
160
+ try:
161
+ os.unlink(temp_file.name)
162
+ except Exception as e:
163
+ print(f"Error cleaning up temporary file: {e}")
164
+ ```
165
+
166
+ Simply put, this downloads the file as a temp file, we load it in with `TextFileLoader` and then split it with our `TextSplitter`, and returns that list of strings!
167
+
168
+ #### ❓ QUESTION #1:
169
+
170
+ Why do we want to support streaming? What about streaming is important, or useful?
171
+
172
+ ### ✅ ANSWER:
173
+ Let me explain why streaming is important and useful in this context, referencing the relevant code:
174
+
175
+ ```
176
+ @cl.on_chat_start
177
+ async def on_chat_start():
178
+ files = None
179
+
180
+ # Wait for the user to upload a file
181
+ while files == None:
182
+ files = await cl.AskFileMessage(
183
+ ```
184
+
185
+ -The RAG pipeline is implemented with streaming support through the astream method. Here's why streaming is important:
186
+ -Real-time Response Generation
187
+ -Instead of waiting for the entire response to be generated before showing anything to the user, streaming allows the application to display the response as it's being generated
188
+ -This creates a more engaging user experience as users can see the response being built word by word, similar to how ChatGPT works
189
+ -Memory Efficiency
190
+ -Rather than holding the entire response in memory before sending it, streaming sends small chunks of data as they become available
191
+ -This is especially important when dealing with longer responses or when multiple users are using the system simultaneously
192
+ -Reduced Latency
193
+ -Users see the beginning of the response much faster since they don't have to wait for the entire response to be generated
194
+ -This gives the impression of a more responsive system
195
+ -Better Error Handling
196
+ -If there's an error during response generation, it can be caught and handled earlier in the process
197
+ -Users aren't left waiting for a complete response that might fail at the end
198
+ -The implementation shows this in action:
199
+
200
+ ```
201
+ msg = cl.Message(content="")
202
+ result = await chain.arun_pipeline(message.content)
203
+
204
+ async for stream_resp in result["response"]:
205
+ await msg.stream_token(stream_resp)
206
+
207
+ await msg.send()
208
+ ```
209
+
210
+ Here we can see how the application streams tokens to the frontend using Chainlit's streaming capabilities. Each chunk of the response is immediately sent to the user interface as it's generated, rather than waiting for the complete response.
211
+ This is particularly valuable in RAG applications because:
212
+ - Document retrieval and response generation can take time
213
+ - Responses are often longer since they incorporate context from retrieved documents
214
+ - Users get immediate feedback that the system is working on their query
215
+
216
+
217
+ ### On Chat Start:
218
+
219
+ The next scope is where "the magic happens". On Chat Start is when a user begins a chat session. This will happen whenever a user opens a new chat window, or refreshes an existing chat window.
220
+
221
+ You'll see that our code is set-up to immediately show the user a chat box requesting them to upload a file.
222
+
223
+ ```python
224
+ while files == None:
225
+ files = await cl.AskFileMessage(
226
+ content="Please upload a Text or PDF file to begin!",
227
+ accept=["text/plain", "application/pdf"],
228
+ max_size_mb=2,
229
+ timeout=180,
230
+ ).send()
231
+ ```
232
+
233
+ Once we've obtained the text file - we'll use our processing helper function to process our text!
234
+
235
+ After we have processed our text file - we'll need to create a `VectorDatabase` and populate it with our processed chunks and their related embeddings!
236
+
237
+ ```python
238
+ vector_db = VectorDatabase()
239
+ vector_db = await vector_db.abuild_from_list(texts)
240
+ ```
241
+
242
+ Once we have that piece completed - we can create the chain we'll be using to respond to user queries!
243
+
244
+ ```python
245
+ retrieval_augmented_qa_pipeline = RetrievalAugmentedQAPipeline(
246
+ vector_db_retriever=vector_db,
247
+ llm=chat_openai
248
+ )
249
+ ```
250
+
251
+ Now, we'll save that into our user session!
252
+
253
+ > NOTE: Chainlit has some great documentation about [User Session](https://docs.chainlit.io/concepts/user-session).
254
+
255
+ #### ❓ QUESTION #2:
256
+
257
+ Why are we using User Session here? What about Python makes us need to use this? Why not just store everything in a global variable?
258
+
259
+ ### ✅ ANSWER:
260
+
261
+ -We are using User Session here because it allows us to store and retrieve data specific to each user session. This is useful in a web application where multiple users can interact with the application simultaneously.
262
+
263
+ -In Python, variables are typically scoped to the module or function where they are defined. If we stored data in global variables, it would be shared across all users, which would lead to incorrect results and potential errors.
264
+
265
+ -By using User Session, we can ensure that each user's data is isolated and only accessible to them. This is crucial for maintaining the integrity and functionality of the application.
266
+
267
+
268
+ ### On Message
269
+
270
+ First, we load our chain from the user session:
271
+
272
+ ```python
273
+ chain = cl.user_session.get("chain")
274
+ ```
275
+
276
+ Then, we run the chain on the content of the message - and stream it to the front end - that's it!
277
+
278
+ ```python
279
+ msg = cl.Message(content="")
280
+ result = await chain.arun_pipeline(message.content)
281
+
282
+ async for stream_resp in result["response"]:
283
+ await msg.stream_token(stream_resp)
284
+ ```
285
+
286
+ ### 🎉
287
+
288
+ With that - you've created a Chainlit application that moves our Pythonic RAG notebook to a Chainlit application!
289
+
290
+ ## Deploying the Application to Hugging Face Space
291
+
292
+ Due to the way the repository is created - it should be straightforward to deploy this to a Hugging Face Space!
293
+
294
+ > NOTE: If you wish to go through the local deployments using `uv run chainlit run app.py` and Docker - please feel free to do so!
295
+
296
+ <details>
297
+ <summary>Creating a Hugging Face Space</summary>
298
+
299
+ 1. Navigate to the `Spaces` tab.
300
+
301
+ ![image](https://i.imgur.com/aSMlX2T.png)
302
+
303
+ 2. Click on `Create new Space`
304
+
305
+ ![image](https://i.imgur.com/YaSSy5p.png)
306
+
307
+ 3. Create the Space by providing values in the form. Make sure you've selected "Docker" as your Space SDK.
308
+
309
+ ![image](https://i.imgur.com/6h9CgH6.png)
310
+
311
+ </details>
312
+
313
+ <details>
314
+ <summary>Adding this Repository to the Newly Created Space</summary>
315
+
316
+ 1. Collect the SSH address from the newly created Space.
317
+
318
+ ![image](https://i.imgur.com/Oag0m8E.png)
319
+
320
+ > NOTE: The address is the component that starts with `[email protected]:spaces/`.
321
+
322
+ 2. Use the command:
323
+
324
+ ```bash
325
+ git remote add hf HF_SPACE_SSH_ADDRESS_HERE
326
+ ```
327
+
328
+ 3. Use the command:
329
+
330
+ ```bash
331
+ git pull hf main --no-rebase --allow-unrelated-histories -X ours
332
+ ```
333
+
334
+ 4. Use the command:
335
+
336
+ ```bash
337
+ git add .
338
+ ```
339
+
340
+ 5. Use the command:
341
+
342
+ ```bash
343
+ git commit -m "Deploying Pythonic RAG"
344
+ ```
345
+
346
+ 6. Use the command:
347
+
348
+ ```bash
349
+ git push hf main
350
+ ```
351
+
352
+ 7. The Space should automatically build as soon as the push is completed!
353
+
354
+ > NOTE: The build will fail before you complete the following steps!
355
+
356
+ </details>
357
+
358
+ <details>
359
+ <summary>Adding OpenAI Secrets to the Space</summary>
360
+
361
+ 1. Navigate to your Space settings.
362
+
363
+ ![image](https://i.imgur.com/zh0a2By.png)
364
+
365
+ 2. Navigate to `Variables and secrets` on the Settings page and click `New secret`:
366
+
367
+ ![image](https://i.imgur.com/g2KlZdz.png)
368
+
369
+ 3. In the `Name` field - input `OPENAI_API_KEY` in the `Value (private)` field, put your OpenAI API Key.
370
+
371
+ ![image](https://i.imgur.com/eFcZ8U3.png)
372
+
373
+ 4. The Space will begin rebuilding!
374
+
375
+ </details>
376
+
377
+ ## 🎉
378
+
379
+ You just deployed Pythonic RAG!
380
+
381
+ Try uploading a text file and asking some questions!
382
+
383
+ #### ❓ Discussion Question #1:
384
+
385
+ Upload a PDF file of the recent DeepSeek-R1 paper and ask the following questions:
386
+
387
+ 1. What is RL and how does it help reasoning?
388
+ 2. What is the difference between DeepSeek-R1 and DeepSeek-R1-Zero?
389
+ 3. What is this paper about?
390
+
391
+ Does this application pass your vibe check? Are there any immediate pitfalls you're noticing?
392
+
393
+ ## 🚧 CHALLENGE MODE 🚧
394
+
395
+ For the challenge mode, please instead create a simple FastAPI backend with a simple React (or any other JS framework) frontend.
396
+
397
+ You can use the same prompt templates and RAG pipeline as we did here - but you'll need to modify the code to work with FastAPI and React.
398
+
399
+ Deploy this application to Hugging Face Spaces!
400
+
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,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openai import OpenAI, AsyncOpenAI
2
+ from dotenv import load_dotenv
3
+ import os
4
+
5
+ load_dotenv()
6
+
7
+
8
+ class ChatOpenAI:
9
+ def __init__(self, model_name: str = "gpt-4o-mini"):
10
+ self.model_name = model_name
11
+ self.openai_api_key = os.getenv("OPENAI_API_KEY")
12
+ if self.openai_api_key is None:
13
+ raise ValueError("OPENAI_API_KEY is not set")
14
+
15
+ def run(self, messages, text_only: bool = True, **kwargs):
16
+ if not isinstance(messages, list):
17
+ raise ValueError("messages must be a list")
18
+
19
+ client = OpenAI()
20
+ response = client.chat.completions.create(
21
+ model=self.model_name, messages=messages, **kwargs
22
+ )
23
+
24
+ if text_only:
25
+ return response.choices[0].message.content
26
+
27
+ return response
28
+
29
+ async def astream(self, messages, **kwargs):
30
+ if not isinstance(messages, list):
31
+ raise ValueError("messages must be a list")
32
+
33
+ client = AsyncOpenAI()
34
+
35
+ stream = await client.chat.completions.create(
36
+ model=self.model_name,
37
+ messages=messages,
38
+ stream=True,
39
+ **kwargs
40
+ )
41
+
42
+ async for chunk in stream:
43
+ content = chunk.choices[0].delta.content
44
+ if content is not None:
45
+ yield content
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-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(
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,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, format=True, **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
+ if format:
52
+ return {"role": self.role, "content": self.format_prompt(**kwargs)}
53
+
54
+ return {"role": self.role, "content": self.prompt}
55
+
56
+
57
+ class SystemRolePrompt(RolePrompt):
58
+ def __init__(self, prompt: str):
59
+ super().__init__(prompt, "system")
60
+
61
+
62
+ class UserRolePrompt(RolePrompt):
63
+ def __init__(self, prompt: str):
64
+ super().__init__(prompt, "user")
65
+
66
+
67
+ class AssistantRolePrompt(RolePrompt):
68
+ def __init__(self, prompt: str):
69
+ super().__init__(prompt, "assistant")
70
+
71
+
72
+ if __name__ == "__main__":
73
+ prompt = BasePrompt("Hello {name}, you are {age} years old")
74
+ print(prompt.format_prompt(name="John", age=30))
75
+
76
+ prompt = SystemRolePrompt("Hello {name}, you are {age} years old")
77
+ print(prompt.create_message(name="John", age=30))
78
+ print(prompt.get_input_variables())
aimakerspace/text_utils.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import List
3
+ import PyPDF2
4
+
5
+
6
+ class TextFileLoader:
7
+ def __init__(self, path: str, encoding: str = "utf-8"):
8
+ self.documents = []
9
+ self.path = path
10
+ self.encoding = encoding
11
+
12
+ def load(self):
13
+ if os.path.isdir(self.path):
14
+ self.load_directory()
15
+ elif os.path.isfile(self.path) and self.path.endswith(".txt"):
16
+ self.load_file()
17
+ else:
18
+ raise ValueError(
19
+ "Provided path is neither a valid directory nor a .txt file."
20
+ )
21
+
22
+ def load_file(self):
23
+ with open(self.path, "r", encoding=self.encoding) as f:
24
+ self.documents.append(f.read())
25
+
26
+ def load_directory(self):
27
+ for root, _, files in os.walk(self.path):
28
+ for file in files:
29
+ if file.endswith(".txt"):
30
+ with open(
31
+ os.path.join(root, file), "r", encoding=self.encoding
32
+ ) as f:
33
+ self.documents.append(f.read())
34
+
35
+ def load_documents(self):
36
+ self.load()
37
+ return self.documents
38
+
39
+
40
+ class CharacterTextSplitter:
41
+ def __init__(
42
+ self,
43
+ chunk_size: int = 1000,
44
+ chunk_overlap: int = 200,
45
+ ):
46
+ assert (
47
+ chunk_size > chunk_overlap
48
+ ), "Chunk size must be greater than chunk overlap"
49
+
50
+ self.chunk_size = chunk_size
51
+ self.chunk_overlap = chunk_overlap
52
+
53
+ def split(self, text: str) -> List[str]:
54
+ chunks = []
55
+ for i in range(0, len(text), self.chunk_size - self.chunk_overlap):
56
+ chunks.append(text[i : i + self.chunk_size])
57
+ return chunks
58
+
59
+ def split_texts(self, texts: List[str]) -> List[str]:
60
+ chunks = []
61
+ for text in texts:
62
+ chunks.extend(self.split(text))
63
+ return chunks
64
+
65
+
66
+ class PDFLoader:
67
+ def __init__(self, path: str):
68
+ self.documents = []
69
+ self.path = path
70
+ print(f"PDFLoader initialized with path: {self.path}")
71
+
72
+ def load(self):
73
+ print(f"Loading PDF from path: {self.path}")
74
+ print(f"Path exists: {os.path.exists(self.path)}")
75
+ print(f"Is file: {os.path.isfile(self.path)}")
76
+ print(f"Is directory: {os.path.isdir(self.path)}")
77
+ print(f"File permissions: {oct(os.stat(self.path).st_mode)[-3:]}")
78
+
79
+ try:
80
+ # Try to open the file first to verify access
81
+ with open(self.path, 'rb') as test_file:
82
+ pass
83
+
84
+ # If we can open it, proceed with loading
85
+ self.load_file()
86
+
87
+ except IOError as e:
88
+ raise ValueError(f"Cannot access file at '{self.path}': {str(e)}")
89
+ except Exception as e:
90
+ raise ValueError(f"Error processing file at '{self.path}': {str(e)}")
91
+
92
+ def load_file(self):
93
+ with open(self.path, 'rb') as file:
94
+ # Create PDF reader object
95
+ pdf_reader = PyPDF2.PdfReader(file)
96
+
97
+ # Extract text from each page
98
+ text = ""
99
+ for page in pdf_reader.pages:
100
+ text += page.extract_text() + "\n"
101
+
102
+ self.documents.append(text)
103
+
104
+ def load_directory(self):
105
+ for root, _, files in os.walk(self.path):
106
+ for file in files:
107
+ if file.lower().endswith('.pdf'):
108
+ file_path = os.path.join(root, file)
109
+ with open(file_path, 'rb') as f:
110
+ pdf_reader = PyPDF2.PdfReader(f)
111
+
112
+ # Extract text from each page
113
+ text = ""
114
+ for page in pdf_reader.pages:
115
+ text += page.extract_text() + "\n"
116
+
117
+ self.documents.append(text)
118
+
119
+ def load_documents(self):
120
+ self.load()
121
+ return self.documents
122
+
123
+
124
+ if __name__ == "__main__":
125
+ loader = TextFileLoader("data/KingLear.txt")
126
+ loader.load()
127
+ splitter = CharacterTextSplitter()
128
+ chunks = splitter.split_texts(loader.documents)
129
+ print(len(chunks))
130
+ print(chunks[0])
131
+ print("--------")
132
+ print(chunks[1])
133
+ print("--------")
134
+ print(chunks[-2])
135
+ print("--------")
136
+ 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)
api.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, UploadFile, File, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import List
4
+ import uvicorn
5
+ from aimakerspace.text_utils import CharacterTextSplitter, TextFileLoader, PDFLoader
6
+ from aimakerspace.openai_utils.prompts import (
7
+ UserRolePrompt,
8
+ SystemRolePrompt,
9
+ )
10
+ from aimakerspace.vectordatabase import VectorDatabase
11
+ from aimakerspace.openai_utils.chatmodel import ChatOpenAI
12
+ import os
13
+ import tempfile
14
+ import shutil
15
+ from fastapi.middleware.cors import CORSMiddleware
16
+ from fastapi.responses import StreamingResponse
17
+ import json
18
+
19
+ app = FastAPI(title="RAG API", description="REST API for RAG-based Q&A system")
20
+
21
+ # Move CORS middleware setup to the top, before any routes
22
+ app.add_middleware(
23
+ CORSMiddleware,
24
+ allow_origins=["http://localhost:3000"], # React app's address
25
+ allow_credentials=True,
26
+ allow_methods=["*"],
27
+ allow_headers=["*"],
28
+ )
29
+
30
+ # Keep the same prompt templates
31
+ system_template = """\
32
+ 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."""
33
+ system_role_prompt = SystemRolePrompt(system_template)
34
+
35
+ user_prompt_template = """\
36
+ Context:
37
+ {context}
38
+
39
+ Question:
40
+ {question}
41
+ """
42
+ user_role_prompt = UserRolePrompt(user_prompt_template)
43
+
44
+ # Pydantic models for request/response
45
+ class Question(BaseModel):
46
+ query: str
47
+
48
+ class Answer(BaseModel):
49
+ response: str
50
+ context: List[str]
51
+
52
+ class Config:
53
+ json_schema_extra = {
54
+ "example": {
55
+ "response": "This is a sample response",
56
+ "context": ["Context piece 1", "Context piece 2"]
57
+ }
58
+ }
59
+
60
+ # Add this class near the top of the file, after imports
61
+ class AppState:
62
+ def __init__(self):
63
+ self.text_splitter = CharacterTextSplitter()
64
+ self.vector_db = None
65
+ self.qa_pipeline = None
66
+
67
+ def has_pipeline(self):
68
+ return self.qa_pipeline is not None
69
+
70
+ # Create a global app state
71
+ app_state = AppState()
72
+
73
+ class RetrievalAugmentedQAPipeline:
74
+ def __init__(self, llm: ChatOpenAI(), vector_db_retriever: VectorDatabase) -> None:
75
+ self.llm = llm
76
+ self.vector_db_retriever = vector_db_retriever
77
+
78
+ async def arun_pipeline(self, user_query: str):
79
+ context_list = self.vector_db_retriever.search_by_text(user_query, k=4)
80
+
81
+ context_prompt = ""
82
+ for context in context_list:
83
+ context_prompt += context[0] + "\n"
84
+
85
+ formatted_system_prompt = system_role_prompt.create_message()
86
+ formatted_user_prompt = user_role_prompt.create_message(
87
+ question=user_query, context=context_prompt
88
+ )
89
+
90
+ # Get the full response instead of streaming
91
+ response = ""
92
+ async for chunk in self.llm.astream([formatted_system_prompt, formatted_user_prompt]):
93
+ response += chunk
94
+
95
+ return {
96
+ "response": response,
97
+ "context": [str(context[0]) for context in context_list] # Convert context to strings
98
+ }
99
+
100
+ def process_file(file_path: str, file_name: str):
101
+ if file_name.lower().endswith('.pdf'):
102
+ loader = PDFLoader(file_path)
103
+ else:
104
+ loader = TextFileLoader(file_path)
105
+
106
+ documents = loader.load_documents()
107
+ texts = app_state.text_splitter.split_texts(documents)
108
+ return texts
109
+
110
+ @app.post("/upload")
111
+ async def upload_file(file: UploadFile = File(...)):
112
+ print("Starting file upload process...") # Debug print
113
+
114
+ if not file:
115
+ print("No file provided") # Debug print
116
+ raise HTTPException(400, detail="No file provided")
117
+
118
+ print(f"File received: {file.filename}") # Debug print
119
+
120
+ if not file.filename.lower().endswith(('.txt', '.pdf')):
121
+ print(f"Invalid file type: {file.filename}") # Debug print
122
+ raise HTTPException(400, detail="Only .txt and .pdf files are supported")
123
+
124
+ try:
125
+ suffix = f".{file.filename.split('.')[-1]}"
126
+ with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
127
+ print(f"Created temp file: {temp_file.name}") # Debug print
128
+ content = await file.read()
129
+ temp_file.write(content)
130
+ temp_file.flush()
131
+
132
+ try:
133
+ print("Processing file...") # Debug print
134
+ texts = process_file(temp_file.name, file.filename)
135
+ print(f"Got {len(texts)} text chunks") # Debug print
136
+
137
+ # Initialize vector database
138
+ print("Initializing vector database...") # Debug print
139
+ app_state.vector_db = VectorDatabase()
140
+ app_state.vector_db = await app_state.vector_db.abuild_from_list(texts)
141
+
142
+ # Initialize QA pipeline
143
+ print("Initializing QA pipeline...") # Debug print
144
+ chat_openai = ChatOpenAI()
145
+ app_state.qa_pipeline = RetrievalAugmentedQAPipeline(
146
+ vector_db_retriever=app_state.vector_db,
147
+ llm=chat_openai
148
+ )
149
+ print("QA pipeline initialized successfully") # Debug print
150
+
151
+ return {"message": f"Successfully processed {file.filename}", "chunks": len(texts)}
152
+ finally:
153
+ try:
154
+ os.unlink(temp_file.name)
155
+ print("Temporary file cleaned up") # Debug print
156
+ except Exception as e:
157
+ print(f"Error cleaning up temporary file: {e}")
158
+ except Exception as e:
159
+ print(f"Error during file processing: {str(e)}") # Debug print
160
+ raise HTTPException(
161
+ status_code=500,
162
+ detail=f"Error processing file: {str(e)}"
163
+ )
164
+
165
+ @app.post("/query", response_model=Answer)
166
+ async def query(question: Question):
167
+ print(f"Received query: {question.query}") # Debug print
168
+ print(f"QA Pipeline exists: {app_state.has_pipeline()}") # Debug print
169
+
170
+ if not app_state.has_pipeline():
171
+ print("No QA pipeline available") # Debug print
172
+ raise HTTPException(
173
+ status_code=400,
174
+ detail="Please upload a document first"
175
+ )
176
+
177
+ try:
178
+ print("Starting query pipeline...") # Debug print
179
+ result = await app_state.qa_pipeline.arun_pipeline(question.query)
180
+ print(f"Generated result: {result}") # Debug print
181
+ return result
182
+ except Exception as e:
183
+ print(f"Error in query endpoint: {str(e)}") # Debug print
184
+ raise HTTPException(
185
+ status_code=500,
186
+ detail=f"Error processing query: {str(e)}"
187
+ )
188
+
189
+ @app.get("/status")
190
+ async def get_status():
191
+ return {
192
+ "ready": app_state.has_pipeline(),
193
+ "message": "Document loaded and ready for queries" if app_state.has_pipeline() else "No document loaded"
194
+ }
195
+
196
+ if __name__ == "__main__":
197
+ uvicorn.run(
198
+ "api:app",
199
+ host="0.0.0.0",
200
+ port=8000,
201
+ reload=True, # Enable auto-reload
202
+ reload_dirs=["./"] # Watch current directory for changes
203
+ )
app.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import List
3
+ from chainlit.types import AskFileResponse
4
+ from aimakerspace.text_utils import CharacterTextSplitter, TextFileLoader, PDFLoader
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:
21
+ {context}
22
+
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_file(file: AskFileResponse):
54
+ import tempfile
55
+ import shutil
56
+
57
+ print(f"Processing file: {file.name}")
58
+
59
+ # Create a temporary file with the correct extension
60
+ suffix = f".{file.name.split('.')[-1]}"
61
+ with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
62
+ # Copy the uploaded file content to the temporary file
63
+ shutil.copyfile(file.path, temp_file.name)
64
+ print(f"Created temporary file at: {temp_file.name}")
65
+
66
+ # Create appropriate loader
67
+ if file.name.lower().endswith('.pdf'):
68
+ loader = PDFLoader(temp_file.name)
69
+ else:
70
+ loader = TextFileLoader(temp_file.name)
71
+
72
+ try:
73
+ # Load and process the documents
74
+ documents = loader.load_documents()
75
+ texts = text_splitter.split_texts(documents)
76
+ return texts
77
+ finally:
78
+ # Clean up the temporary file
79
+ try:
80
+ os.unlink(temp_file.name)
81
+ except Exception as e:
82
+ print(f"Error cleaning up temporary file: {e}")
83
+
84
+
85
+ @cl.on_chat_start
86
+ async def on_chat_start():
87
+ files = None
88
+
89
+ # Wait for the user to upload a file
90
+ while files == None:
91
+ files = await cl.AskFileMessage(
92
+ content="Please upload a Text or PDF file to begin!",
93
+ accept=["text/plain", "application/pdf"],
94
+ max_size_mb=2,
95
+ timeout=180,
96
+ ).send()
97
+
98
+ file = files[0]
99
+
100
+ msg = cl.Message(
101
+ content=f"Processing `{file.name}`..."
102
+ )
103
+ await msg.send()
104
+
105
+ # load the file
106
+ texts = process_file(file)
107
+
108
+ print(f"Processing {len(texts)} text chunks")
109
+
110
+ # Create a dict vector store
111
+ vector_db = VectorDatabase()
112
+ vector_db = await vector_db.abuild_from_list(texts)
113
+
114
+ chat_openai = ChatOpenAI()
115
+
116
+ # Create a chain
117
+ retrieval_augmented_qa_pipeline = RetrievalAugmentedQAPipeline(
118
+ vector_db_retriever=vector_db,
119
+ llm=chat_openai
120
+ )
121
+
122
+ # Let the user know that the system is ready
123
+ msg.content = f"Processing `{file.name}` done. You can now ask questions!"
124
+ await msg.update()
125
+
126
+ cl.user_session.set("chain", retrieval_augmented_qa_pipeline)
127
+
128
+
129
+ @cl.on_message
130
+ async def main(message):
131
+ chain = cl.user_session.get("chain")
132
+
133
+ msg = cl.Message(content="")
134
+ result = await chain.arun_pipeline(message.content)
135
+
136
+ async for stream_resp in result["response"]:
137
+ await msg.stream_token(stream_resp)
138
+
139
+ await msg.send()
chainlit.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Welcome to Chat with Your Text File
2
+
3
+ With this application, you can chat with an uploaded text file that is smaller than 2MB!
docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ backend:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile
8
+ volumes:
9
+ - .:/app
10
+ ports:
11
+ - "8000:8000"
12
+ environment:
13
+ - PYTHONUNBUFFERED=1
14
+
15
+ frontend:
16
+ image: node:14
17
+ working_dir: /app
18
+ volumes:
19
+ - ./frontend:/app
20
+ ports:
21
+ - "3000:3000"
22
+ command: npm start
frontend/.gitignore ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+
8
+ # testing
9
+ /coverage
10
+
11
+ # production
12
+ /build
13
+
14
+ # misc
15
+ .DS_Store
16
+ .env.local
17
+ .env.development.local
18
+ .env.test.local
19
+ .env.production.local
20
+
21
+ npm-debug.log*
22
+ yarn-debug.log*
23
+ yarn-error.log*
frontend/README.md ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Getting Started with Create React App
2
+
3
+ This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4
+
5
+ ## Available Scripts
6
+
7
+ In the project directory, you can run:
8
+
9
+ ### `yarn start`
10
+
11
+ Runs the app in the development mode.\
12
+ Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13
+
14
+ The page will reload if you make edits.\
15
+ You will also see any lint errors in the console.
16
+
17
+ ### `yarn test`
18
+
19
+ Launches the test runner in the interactive watch mode.\
20
+ See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21
+
22
+ ### `yarn build`
23
+
24
+ Builds the app for production to the `build` folder.\
25
+ It correctly bundles React in production mode and optimizes the build for the best performance.
26
+
27
+ The build is minified and the filenames include the hashes.\
28
+ Your app is ready to be deployed!
29
+
30
+ See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31
+
32
+ ### `yarn eject`
33
+
34
+ **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35
+
36
+ If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37
+
38
+ Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39
+
40
+ You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41
+
42
+ ## Learn More
43
+
44
+ You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45
+
46
+ To learn React, check out the [React documentation](https://reactjs.org/).
frontend/assets/img.gif ADDED

Git LFS Details

  • SHA256: 89dcfc2525b543282417b7c4e1289d82a91785254ee601c06ab81758c1e9419e
  • Pointer size: 132 Bytes
  • Size of remote file: 3.22 MB
frontend/assets/loader.gif ADDED

Git LFS Details

  • SHA256: 2609a0cf44978bdfcf15ac91a07d3e780c043c05b51736340119eebf0a4e6e0e
  • Pointer size: 131 Bytes
  • Size of remote file: 126 kB
frontend/package.json ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "@testing-library/jest-dom": "^5.14.1",
7
+ "@testing-library/react": "^13.0.0",
8
+ "@testing-library/user-event": "^13.2.1",
9
+ "@types/jest": "^27.0.1",
10
+ "@types/node": "^16.7.13",
11
+ "@types/react": "^18.0.0",
12
+ "@types/react-dom": "^18.0.0",
13
+ "react": "^19.0.0",
14
+ "react-dom": "^19.0.0",
15
+ "react-scripts": "5.0.1",
16
+ "typescript": "^4.4.2",
17
+ "web-vitals": "^2.1.0"
18
+ },
19
+ "scripts": {
20
+ "start": "react-scripts start",
21
+ "build": "react-scripts build",
22
+ "test": "react-scripts test",
23
+ "eject": "react-scripts eject"
24
+ },
25
+ "eslintConfig": {
26
+ "extends": [
27
+ "react-app",
28
+ "react-app/jest"
29
+ ]
30
+ },
31
+ "browserslist": {
32
+ "production": [
33
+ ">0.2%",
34
+ "not dead",
35
+ "not op_mini all"
36
+ ],
37
+ "development": [
38
+ "last 1 chrome version",
39
+ "last 1 firefox version",
40
+ "last 1 safari version"
41
+ ]
42
+ }
43
+ }
frontend/public/favicon.ico ADDED
frontend/public/index.html ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <meta name="theme-color" content="#000000" />
8
+ <meta
9
+ name="description"
10
+ content="Web site created using create-react-app"
11
+ />
12
+ <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13
+ <!--
14
+ manifest.json provides metadata used when your web app is installed on a
15
+ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16
+ -->
17
+ <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
18
+ <!--
19
+ Notice the use of %PUBLIC_URL% in the tags above.
20
+ It will be replaced with the URL of the `public` folder during the build.
21
+ Only files inside the `public` folder can be referenced from the HTML.
22
+
23
+ Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
24
+ work correctly both with client-side routing and a non-root public URL.
25
+ Learn how to configure a non-root public URL by running `npm run build`.
26
+ -->
27
+ <title>React App</title>
28
+ </head>
29
+ <body>
30
+ <noscript>You need to enable JavaScript to run this app.</noscript>
31
+ <div id="root"></div>
32
+ <!--
33
+ This HTML file is a template.
34
+ If you open it directly in the browser, you will see an empty page.
35
+
36
+ You can add webfonts, meta tags, or analytics to this file.
37
+ The build step will place the bundled scripts into the <body> tag.
38
+
39
+ To begin the development, run `npm start` or `yarn start`.
40
+ To create a production bundle, use `npm run build` or `yarn build`.
41
+ -->
42
+ </body>
43
+ </html>
frontend/public/logo192.png ADDED
frontend/public/logo512.png ADDED
frontend/public/manifest.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "short_name": "React App",
3
+ "name": "Create React App Sample",
4
+ "icons": [
5
+ {
6
+ "src": "favicon.ico",
7
+ "sizes": "64x64 32x32 24x24 16x16",
8
+ "type": "image/x-icon"
9
+ },
10
+ {
11
+ "src": "logo192.png",
12
+ "type": "image/png",
13
+ "sizes": "192x192"
14
+ },
15
+ {
16
+ "src": "logo512.png",
17
+ "type": "image/png",
18
+ "sizes": "512x512"
19
+ }
20
+ ],
21
+ "start_url": ".",
22
+ "display": "standalone",
23
+ "theme_color": "#000000",
24
+ "background_color": "#ffffff"
25
+ }
frontend/public/robots.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
frontend/src/App.css ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .App {
2
+ text-align: center;
3
+ max-width: 800px;
4
+ margin: 0 auto;
5
+ padding: 20px;
6
+ position: relative;
7
+ }
8
+
9
+ .App-header {
10
+ background-color: #282c34;
11
+ padding: 20px;
12
+ color: white;
13
+ border-radius: 8px;
14
+ margin-bottom: 20px;
15
+ }
16
+
17
+ .App-header h1 {
18
+ margin: 0;
19
+ }
20
+
21
+ .App-main {
22
+ display: flex;
23
+ flex-direction: column;
24
+ gap: 20px;
25
+ }
26
+
27
+ body {
28
+ background: url('../assets/img.gif');
29
+ background-size: cover;
30
+ background-position: center;
31
+ background-repeat: no-repeat;
32
+ min-height: 100vh;
33
+ }
34
+
35
+ .upload-section,
36
+ .query-section,
37
+ .answer-section {
38
+ background-color: #222;
39
+ padding: 20px;
40
+ border-radius: 8px;
41
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
42
+ color: #fff;
43
+ }
44
+
45
+ input[type="text"] {
46
+ width: 70%;
47
+ padding: 10px;
48
+ margin-right: 10px;
49
+ border: 1px solid #ddd;
50
+ border-radius: 4px;
51
+ }
52
+
53
+ button {
54
+ padding: 10px 20px;
55
+ background: linear-gradient(90deg, rgba(0, 20, 36, 1) 0%, rgba(9, 121, 93, 1) 3%, rgba(22, 255, 0, 1) 100%);
56
+ border: none;
57
+ border-radius: 4px;
58
+ cursor: pointer;
59
+ transition: background-color 0.3s;
60
+ }
61
+
62
+ button:hover {
63
+ background-color: #4fa8d1;
64
+ }
65
+
66
+ button:disabled {
67
+ background-color: #cccccc;
68
+ cursor: not-allowed;
69
+ }
70
+
71
+ .status {
72
+ margin: 10px 0;
73
+ color: #fff;
74
+ }
75
+
76
+ .loader {
77
+ margin: 20px 0;
78
+ color: #666;
79
+ background: url('../assets/loader.gif');
80
+ background-size: cover;
81
+ background-position: center;
82
+ background-repeat: no-repeat;
83
+ min-height: 100vh;
84
+ position: fixed;
85
+ opacity: 0.8;
86
+ top: 0;
87
+ left: 0;
88
+ width: 100%;
89
+ height: 100%;
90
+ }
91
+
92
+ .loader p {
93
+ position: absolute;
94
+ top: 47vh;
95
+ left: calc(50% - 180px);
96
+ background: #666;
97
+ padding: 10px;
98
+ border-radius: 10px;
99
+ border: 5px solid #333;
100
+ color: #fff;
101
+ }
102
+
103
+ .context {
104
+ margin-top: 20px;
105
+ text-align: left;
106
+ }
107
+
108
+ .context-item {
109
+ background-color: #16e261;
110
+ padding: 10px;
111
+ margin: 10px 0;
112
+ border-radius: 4px;
113
+ border: 1px solid #ddd;
114
+ }
115
+
116
+ form {
117
+ display: flex;
118
+ gap: 10px;
119
+ justify-content: center;
120
+ align-items: center;
121
+ }
122
+
123
+ .answer-section p {
124
+ border: 3px inset #666;
125
+ padding: 10px;
126
+ border-radius: 5px;
127
+ box-shadow: 0 1px 3px #666;
128
+ }
frontend/src/App.test.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import App from './App';
4
+
5
+ test('renders learn react link', () => {
6
+ render(<App />);
7
+ const linkElement = screen.getByText(/learn react/i);
8
+ expect(linkElement).toBeInTheDocument();
9
+ });
frontend/src/App.tsx ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import './App.css';
3
+
4
+ function App() {
5
+ const [file, setFile] = useState<File | null>(null);
6
+ const [query, setQuery] = useState('');
7
+ const [answer, setAnswer] = useState('');
8
+ const [context, setContext] = useState<string[]>([]);
9
+ const [loading, setLoading] = useState(false);
10
+ const [uploadStatus, setUploadStatus] = useState('');
11
+
12
+ const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
13
+ if (e.target.files && e.target.files[0]) {
14
+ setFile(e.target.files[0]);
15
+ const formData = new FormData();
16
+ formData.append('file', e.target.files[0]);
17
+
18
+ setLoading(true);
19
+ setUploadStatus('Uploading...');
20
+
21
+ try {
22
+ const response = await fetch('http://localhost:8000/upload', {
23
+ method: 'POST',
24
+ body: formData,
25
+ });
26
+
27
+ if (!response.ok) {
28
+ const errorData = await response.json();
29
+ throw new Error(errorData.detail || 'Failed to upload file');
30
+ }
31
+
32
+ const data = await response.json();
33
+ setUploadStatus(`Success! Processed ${data.chunks} chunks`);
34
+ } catch (error) {
35
+ console.error('Error:', error);
36
+ setUploadStatus(error instanceof Error ? error.message : 'Error uploading file');
37
+ }
38
+
39
+ setLoading(false);
40
+ }
41
+ };
42
+
43
+ const handleQuery = async (e: React.FormEvent) => {
44
+ e.preventDefault();
45
+ if (!query.trim()) return;
46
+
47
+ setLoading(true);
48
+ try {
49
+ const response = await fetch('http://localhost:8000/query', {
50
+ method: 'POST',
51
+ headers: {
52
+ 'Content-Type': 'application/json',
53
+ },
54
+ body: JSON.stringify({ query: query.trim() }),
55
+ });
56
+
57
+ if (!response.ok) {
58
+ const errorData = await response.json();
59
+ throw new Error(errorData.detail || 'Failed to get response');
60
+ }
61
+
62
+ const data = await response.json();
63
+ console.log(data);
64
+ setAnswer(data.response);
65
+ setContext(data.context);
66
+ } catch (error) {
67
+ console.error('Error:', error);
68
+ setAnswer(error instanceof Error ? error.message : 'Error getting response');
69
+ }
70
+ setLoading(false);
71
+ };
72
+
73
+ const checkStatus = async () => {
74
+ try {
75
+ const response = await fetch('http://localhost:8000/status');
76
+ const data = await response.json();
77
+ if (data.ready) {
78
+ setUploadStatus('Session initialized. Ready for document upload.');
79
+ }
80
+ } catch (error) {
81
+ console.error('Error checking status:', error);
82
+ }
83
+ };
84
+
85
+ useEffect(() => {
86
+ checkStatus();
87
+ }, []);
88
+
89
+ return (
90
+ <div className="App">
91
+ {loading && <div className="loader">
92
+ <p>Processing your document...</p>
93
+ </div>}
94
+ <header className="App-header">
95
+ <h1>Custom document Q&A System</h1>
96
+ </header>
97
+
98
+ <main className="App-main">
99
+ <section className="upload-section">
100
+ <h2>Upload Document</h2>
101
+ <input
102
+ type="file"
103
+ accept=".txt,.pdf"
104
+ onChange={handleFileUpload}
105
+ disabled={loading}
106
+ />
107
+ {uploadStatus && <p className="status">{uploadStatus}</p>}
108
+ </section>
109
+
110
+ <section className="query-section">
111
+ <h2>Ask a Question</h2>
112
+ <form onSubmit={handleQuery}>
113
+ <input
114
+ type="text"
115
+ value={query}
116
+ onChange={(e) => setQuery(e.target.value)}
117
+ placeholder="Enter your question..."
118
+ disabled={loading || !uploadStatus.includes('Success')}
119
+ />
120
+ <button
121
+ type="submit"
122
+ disabled={loading || !uploadStatus.includes('Success')}
123
+ >
124
+ Ask
125
+ </button>
126
+ </form>
127
+ </section>
128
+
129
+ {answer && (
130
+ <section className="answer-section">
131
+ <h2>Answer</h2>
132
+ <p>{answer}</p>
133
+
134
+ <h3>Relevant Context</h3>
135
+ <div className="context">
136
+ {context.map((text, index) => (
137
+ <div key={index} className="context-item">
138
+ {text}
139
+ </div>
140
+ ))}
141
+ </div>
142
+ </section>
143
+ )}
144
+ </main>
145
+ </div>
146
+ );
147
+ }
148
+
149
+ export default App;
frontend/src/index.css ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ margin: 0;
3
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5
+ sans-serif;
6
+ -webkit-font-smoothing: antialiased;
7
+ -moz-osx-font-smoothing: grayscale;
8
+ }
9
+
10
+ code {
11
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12
+ monospace;
13
+ }
frontend/src/index.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import './index.css';
4
+ import App from './App';
5
+ import reportWebVitals from './reportWebVitals';
6
+
7
+ const root = ReactDOM.createRoot(
8
+ document.getElementById('root') as HTMLElement
9
+ );
10
+ root.render(
11
+ <React.StrictMode>
12
+ <App />
13
+ </React.StrictMode>
14
+ );
15
+
16
+ // If you want to start measuring performance in your app, pass a function
17
+ // to log results (for example: reportWebVitals(console.log))
18
+ // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19
+ reportWebVitals();
frontend/src/logo.svg ADDED
frontend/src/react-app-env.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ /// <reference types="react-scripts" />
frontend/src/reportWebVitals.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ReportHandler } from 'web-vitals';
2
+
3
+ const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4
+ if (onPerfEntry && onPerfEntry instanceof Function) {
5
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6
+ getCLS(onPerfEntry);
7
+ getFID(onPerfEntry);
8
+ getFCP(onPerfEntry);
9
+ getLCP(onPerfEntry);
10
+ getTTFB(onPerfEntry);
11
+ });
12
+ }
13
+ };
14
+
15
+ export default reportWebVitals;
frontend/src/setupTests.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
+ // allows you to do things like:
3
+ // expect(element).toHaveTextContent(/react/i)
4
+ // learn more: https://github.com/testing-library/jest-dom
5
+ import '@testing-library/jest-dom';
frontend/tsconfig.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "strict": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "module": "esnext",
17
+ "moduleResolution": "node",
18
+ "resolveJsonModule": true,
19
+ "isolatedModules": true,
20
+ "noEmit": true,
21
+ "jsx": "react-jsx"
22
+ },
23
+ "include": [
24
+ "src"
25
+ ]
26
+ }
frontend/yarn.lock ADDED
The diff for this file is too large to render. See raw diff
 
images/docchain_img.png ADDED
nodemon.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "watch": [
3
+ "api.py",
4
+ "*.py"
5
+ ],
6
+ "ext": "py",
7
+ "ignore": [
8
+ ".git",
9
+ "node_modules/**/node_modules",
10
+ "frontend/*"
11
+ ],
12
+ "exec": "python api.py"
13
+ }
package-lock.json ADDED
@@ -0,0 +1,697 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "rag-system",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "rag-system",
9
+ "version": "1.0.0",
10
+ "devDependencies": {
11
+ "concurrently": "^8.2.2",
12
+ "nodemon": "^3.0.3"
13
+ }
14
+ },
15
+ "node_modules/@babel/runtime": {
16
+ "version": "7.26.7",
17
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz",
18
+ "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==",
19
+ "dev": true,
20
+ "dependencies": {
21
+ "regenerator-runtime": "^0.14.0"
22
+ },
23
+ "engines": {
24
+ "node": ">=6.9.0"
25
+ }
26
+ },
27
+ "node_modules/ansi-regex": {
28
+ "version": "5.0.1",
29
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
30
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
31
+ "dev": true,
32
+ "engines": {
33
+ "node": ">=8"
34
+ }
35
+ },
36
+ "node_modules/ansi-styles": {
37
+ "version": "4.3.0",
38
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
39
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
40
+ "dev": true,
41
+ "dependencies": {
42
+ "color-convert": "^2.0.1"
43
+ },
44
+ "engines": {
45
+ "node": ">=8"
46
+ },
47
+ "funding": {
48
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
49
+ }
50
+ },
51
+ "node_modules/anymatch": {
52
+ "version": "3.1.3",
53
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
54
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
55
+ "dev": true,
56
+ "dependencies": {
57
+ "normalize-path": "^3.0.0",
58
+ "picomatch": "^2.0.4"
59
+ },
60
+ "engines": {
61
+ "node": ">= 8"
62
+ }
63
+ },
64
+ "node_modules/balanced-match": {
65
+ "version": "1.0.2",
66
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
67
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
68
+ "dev": true
69
+ },
70
+ "node_modules/binary-extensions": {
71
+ "version": "2.3.0",
72
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
73
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
74
+ "dev": true,
75
+ "engines": {
76
+ "node": ">=8"
77
+ },
78
+ "funding": {
79
+ "url": "https://github.com/sponsors/sindresorhus"
80
+ }
81
+ },
82
+ "node_modules/brace-expansion": {
83
+ "version": "1.1.11",
84
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
85
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
86
+ "dev": true,
87
+ "dependencies": {
88
+ "balanced-match": "^1.0.0",
89
+ "concat-map": "0.0.1"
90
+ }
91
+ },
92
+ "node_modules/braces": {
93
+ "version": "3.0.3",
94
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
95
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
96
+ "dev": true,
97
+ "dependencies": {
98
+ "fill-range": "^7.1.1"
99
+ },
100
+ "engines": {
101
+ "node": ">=8"
102
+ }
103
+ },
104
+ "node_modules/chalk": {
105
+ "version": "4.1.2",
106
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
107
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
108
+ "dev": true,
109
+ "dependencies": {
110
+ "ansi-styles": "^4.1.0",
111
+ "supports-color": "^7.1.0"
112
+ },
113
+ "engines": {
114
+ "node": ">=10"
115
+ },
116
+ "funding": {
117
+ "url": "https://github.com/chalk/chalk?sponsor=1"
118
+ }
119
+ },
120
+ "node_modules/chalk/node_modules/supports-color": {
121
+ "version": "7.2.0",
122
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
123
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
124
+ "dev": true,
125
+ "dependencies": {
126
+ "has-flag": "^4.0.0"
127
+ },
128
+ "engines": {
129
+ "node": ">=8"
130
+ }
131
+ },
132
+ "node_modules/chokidar": {
133
+ "version": "3.6.0",
134
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
135
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
136
+ "dev": true,
137
+ "dependencies": {
138
+ "anymatch": "~3.1.2",
139
+ "braces": "~3.0.2",
140
+ "glob-parent": "~5.1.2",
141
+ "is-binary-path": "~2.1.0",
142
+ "is-glob": "~4.0.1",
143
+ "normalize-path": "~3.0.0",
144
+ "readdirp": "~3.6.0"
145
+ },
146
+ "engines": {
147
+ "node": ">= 8.10.0"
148
+ },
149
+ "funding": {
150
+ "url": "https://paulmillr.com/funding/"
151
+ },
152
+ "optionalDependencies": {
153
+ "fsevents": "~2.3.2"
154
+ }
155
+ },
156
+ "node_modules/cliui": {
157
+ "version": "8.0.1",
158
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
159
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
160
+ "dev": true,
161
+ "dependencies": {
162
+ "string-width": "^4.2.0",
163
+ "strip-ansi": "^6.0.1",
164
+ "wrap-ansi": "^7.0.0"
165
+ },
166
+ "engines": {
167
+ "node": ">=12"
168
+ }
169
+ },
170
+ "node_modules/color-convert": {
171
+ "version": "2.0.1",
172
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
173
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
174
+ "dev": true,
175
+ "dependencies": {
176
+ "color-name": "~1.1.4"
177
+ },
178
+ "engines": {
179
+ "node": ">=7.0.0"
180
+ }
181
+ },
182
+ "node_modules/color-name": {
183
+ "version": "1.1.4",
184
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
185
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
186
+ "dev": true
187
+ },
188
+ "node_modules/concat-map": {
189
+ "version": "0.0.1",
190
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
191
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
192
+ "dev": true
193
+ },
194
+ "node_modules/concurrently": {
195
+ "version": "8.2.2",
196
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz",
197
+ "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==",
198
+ "dev": true,
199
+ "dependencies": {
200
+ "chalk": "^4.1.2",
201
+ "date-fns": "^2.30.0",
202
+ "lodash": "^4.17.21",
203
+ "rxjs": "^7.8.1",
204
+ "shell-quote": "^1.8.1",
205
+ "spawn-command": "0.0.2",
206
+ "supports-color": "^8.1.1",
207
+ "tree-kill": "^1.2.2",
208
+ "yargs": "^17.7.2"
209
+ },
210
+ "bin": {
211
+ "conc": "dist/bin/concurrently.js",
212
+ "concurrently": "dist/bin/concurrently.js"
213
+ },
214
+ "engines": {
215
+ "node": "^14.13.0 || >=16.0.0"
216
+ },
217
+ "funding": {
218
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
219
+ }
220
+ },
221
+ "node_modules/date-fns": {
222
+ "version": "2.30.0",
223
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
224
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
225
+ "dev": true,
226
+ "dependencies": {
227
+ "@babel/runtime": "^7.21.0"
228
+ },
229
+ "engines": {
230
+ "node": ">=0.11"
231
+ },
232
+ "funding": {
233
+ "type": "opencollective",
234
+ "url": "https://opencollective.com/date-fns"
235
+ }
236
+ },
237
+ "node_modules/debug": {
238
+ "version": "4.4.0",
239
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
240
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
241
+ "dev": true,
242
+ "dependencies": {
243
+ "ms": "^2.1.3"
244
+ },
245
+ "engines": {
246
+ "node": ">=6.0"
247
+ },
248
+ "peerDependenciesMeta": {
249
+ "supports-color": {
250
+ "optional": true
251
+ }
252
+ }
253
+ },
254
+ "node_modules/emoji-regex": {
255
+ "version": "8.0.0",
256
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
257
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
258
+ "dev": true
259
+ },
260
+ "node_modules/escalade": {
261
+ "version": "3.2.0",
262
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
263
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
264
+ "dev": true,
265
+ "engines": {
266
+ "node": ">=6"
267
+ }
268
+ },
269
+ "node_modules/fill-range": {
270
+ "version": "7.1.1",
271
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
272
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
273
+ "dev": true,
274
+ "dependencies": {
275
+ "to-regex-range": "^5.0.1"
276
+ },
277
+ "engines": {
278
+ "node": ">=8"
279
+ }
280
+ },
281
+ "node_modules/fsevents": {
282
+ "version": "2.3.3",
283
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
284
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
285
+ "dev": true,
286
+ "hasInstallScript": true,
287
+ "optional": true,
288
+ "os": [
289
+ "darwin"
290
+ ],
291
+ "engines": {
292
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
293
+ }
294
+ },
295
+ "node_modules/get-caller-file": {
296
+ "version": "2.0.5",
297
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
298
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
299
+ "dev": true,
300
+ "engines": {
301
+ "node": "6.* || 8.* || >= 10.*"
302
+ }
303
+ },
304
+ "node_modules/glob-parent": {
305
+ "version": "5.1.2",
306
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
307
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
308
+ "dev": true,
309
+ "dependencies": {
310
+ "is-glob": "^4.0.1"
311
+ },
312
+ "engines": {
313
+ "node": ">= 6"
314
+ }
315
+ },
316
+ "node_modules/has-flag": {
317
+ "version": "4.0.0",
318
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
319
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
320
+ "dev": true,
321
+ "engines": {
322
+ "node": ">=8"
323
+ }
324
+ },
325
+ "node_modules/ignore-by-default": {
326
+ "version": "1.0.1",
327
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
328
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
329
+ "dev": true
330
+ },
331
+ "node_modules/is-binary-path": {
332
+ "version": "2.1.0",
333
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
334
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
335
+ "dev": true,
336
+ "dependencies": {
337
+ "binary-extensions": "^2.0.0"
338
+ },
339
+ "engines": {
340
+ "node": ">=8"
341
+ }
342
+ },
343
+ "node_modules/is-extglob": {
344
+ "version": "2.1.1",
345
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
346
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
347
+ "dev": true,
348
+ "engines": {
349
+ "node": ">=0.10.0"
350
+ }
351
+ },
352
+ "node_modules/is-fullwidth-code-point": {
353
+ "version": "3.0.0",
354
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
355
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
356
+ "dev": true,
357
+ "engines": {
358
+ "node": ">=8"
359
+ }
360
+ },
361
+ "node_modules/is-glob": {
362
+ "version": "4.0.3",
363
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
364
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
365
+ "dev": true,
366
+ "dependencies": {
367
+ "is-extglob": "^2.1.1"
368
+ },
369
+ "engines": {
370
+ "node": ">=0.10.0"
371
+ }
372
+ },
373
+ "node_modules/is-number": {
374
+ "version": "7.0.0",
375
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
376
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
377
+ "dev": true,
378
+ "engines": {
379
+ "node": ">=0.12.0"
380
+ }
381
+ },
382
+ "node_modules/lodash": {
383
+ "version": "4.17.21",
384
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
385
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
386
+ "dev": true
387
+ },
388
+ "node_modules/minimatch": {
389
+ "version": "3.1.2",
390
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
391
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
392
+ "dev": true,
393
+ "dependencies": {
394
+ "brace-expansion": "^1.1.7"
395
+ },
396
+ "engines": {
397
+ "node": "*"
398
+ }
399
+ },
400
+ "node_modules/ms": {
401
+ "version": "2.1.3",
402
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
403
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
404
+ "dev": true
405
+ },
406
+ "node_modules/nodemon": {
407
+ "version": "3.1.9",
408
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz",
409
+ "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==",
410
+ "dev": true,
411
+ "dependencies": {
412
+ "chokidar": "^3.5.2",
413
+ "debug": "^4",
414
+ "ignore-by-default": "^1.0.1",
415
+ "minimatch": "^3.1.2",
416
+ "pstree.remy": "^1.1.8",
417
+ "semver": "^7.5.3",
418
+ "simple-update-notifier": "^2.0.0",
419
+ "supports-color": "^5.5.0",
420
+ "touch": "^3.1.0",
421
+ "undefsafe": "^2.0.5"
422
+ },
423
+ "bin": {
424
+ "nodemon": "bin/nodemon.js"
425
+ },
426
+ "engines": {
427
+ "node": ">=10"
428
+ },
429
+ "funding": {
430
+ "type": "opencollective",
431
+ "url": "https://opencollective.com/nodemon"
432
+ }
433
+ },
434
+ "node_modules/nodemon/node_modules/has-flag": {
435
+ "version": "3.0.0",
436
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
437
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
438
+ "dev": true,
439
+ "engines": {
440
+ "node": ">=4"
441
+ }
442
+ },
443
+ "node_modules/nodemon/node_modules/supports-color": {
444
+ "version": "5.5.0",
445
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
446
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
447
+ "dev": true,
448
+ "dependencies": {
449
+ "has-flag": "^3.0.0"
450
+ },
451
+ "engines": {
452
+ "node": ">=4"
453
+ }
454
+ },
455
+ "node_modules/normalize-path": {
456
+ "version": "3.0.0",
457
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
458
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
459
+ "dev": true,
460
+ "engines": {
461
+ "node": ">=0.10.0"
462
+ }
463
+ },
464
+ "node_modules/picomatch": {
465
+ "version": "2.3.1",
466
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
467
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
468
+ "dev": true,
469
+ "engines": {
470
+ "node": ">=8.6"
471
+ },
472
+ "funding": {
473
+ "url": "https://github.com/sponsors/jonschlinkert"
474
+ }
475
+ },
476
+ "node_modules/pstree.remy": {
477
+ "version": "1.1.8",
478
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
479
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
480
+ "dev": true
481
+ },
482
+ "node_modules/readdirp": {
483
+ "version": "3.6.0",
484
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
485
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
486
+ "dev": true,
487
+ "dependencies": {
488
+ "picomatch": "^2.2.1"
489
+ },
490
+ "engines": {
491
+ "node": ">=8.10.0"
492
+ }
493
+ },
494
+ "node_modules/regenerator-runtime": {
495
+ "version": "0.14.1",
496
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
497
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
498
+ "dev": true
499
+ },
500
+ "node_modules/require-directory": {
501
+ "version": "2.1.1",
502
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
503
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
504
+ "dev": true,
505
+ "engines": {
506
+ "node": ">=0.10.0"
507
+ }
508
+ },
509
+ "node_modules/rxjs": {
510
+ "version": "7.8.1",
511
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
512
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
513
+ "dev": true,
514
+ "dependencies": {
515
+ "tslib": "^2.1.0"
516
+ }
517
+ },
518
+ "node_modules/semver": {
519
+ "version": "7.6.3",
520
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
521
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
522
+ "dev": true,
523
+ "bin": {
524
+ "semver": "bin/semver.js"
525
+ },
526
+ "engines": {
527
+ "node": ">=10"
528
+ }
529
+ },
530
+ "node_modules/shell-quote": {
531
+ "version": "1.8.2",
532
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz",
533
+ "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==",
534
+ "dev": true,
535
+ "engines": {
536
+ "node": ">= 0.4"
537
+ },
538
+ "funding": {
539
+ "url": "https://github.com/sponsors/ljharb"
540
+ }
541
+ },
542
+ "node_modules/simple-update-notifier": {
543
+ "version": "2.0.0",
544
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
545
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
546
+ "dev": true,
547
+ "dependencies": {
548
+ "semver": "^7.5.3"
549
+ },
550
+ "engines": {
551
+ "node": ">=10"
552
+ }
553
+ },
554
+ "node_modules/spawn-command": {
555
+ "version": "0.0.2",
556
+ "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
557
+ "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==",
558
+ "dev": true
559
+ },
560
+ "node_modules/string-width": {
561
+ "version": "4.2.3",
562
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
563
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
564
+ "dev": true,
565
+ "dependencies": {
566
+ "emoji-regex": "^8.0.0",
567
+ "is-fullwidth-code-point": "^3.0.0",
568
+ "strip-ansi": "^6.0.1"
569
+ },
570
+ "engines": {
571
+ "node": ">=8"
572
+ }
573
+ },
574
+ "node_modules/strip-ansi": {
575
+ "version": "6.0.1",
576
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
577
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
578
+ "dev": true,
579
+ "dependencies": {
580
+ "ansi-regex": "^5.0.1"
581
+ },
582
+ "engines": {
583
+ "node": ">=8"
584
+ }
585
+ },
586
+ "node_modules/supports-color": {
587
+ "version": "8.1.1",
588
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
589
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
590
+ "dev": true,
591
+ "dependencies": {
592
+ "has-flag": "^4.0.0"
593
+ },
594
+ "engines": {
595
+ "node": ">=10"
596
+ },
597
+ "funding": {
598
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
599
+ }
600
+ },
601
+ "node_modules/to-regex-range": {
602
+ "version": "5.0.1",
603
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
604
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
605
+ "dev": true,
606
+ "dependencies": {
607
+ "is-number": "^7.0.0"
608
+ },
609
+ "engines": {
610
+ "node": ">=8.0"
611
+ }
612
+ },
613
+ "node_modules/touch": {
614
+ "version": "3.1.1",
615
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
616
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
617
+ "dev": true,
618
+ "bin": {
619
+ "nodetouch": "bin/nodetouch.js"
620
+ }
621
+ },
622
+ "node_modules/tree-kill": {
623
+ "version": "1.2.2",
624
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
625
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
626
+ "dev": true,
627
+ "bin": {
628
+ "tree-kill": "cli.js"
629
+ }
630
+ },
631
+ "node_modules/tslib": {
632
+ "version": "2.8.1",
633
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
634
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
635
+ "dev": true
636
+ },
637
+ "node_modules/undefsafe": {
638
+ "version": "2.0.5",
639
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
640
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
641
+ "dev": true
642
+ },
643
+ "node_modules/wrap-ansi": {
644
+ "version": "7.0.0",
645
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
646
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
647
+ "dev": true,
648
+ "dependencies": {
649
+ "ansi-styles": "^4.0.0",
650
+ "string-width": "^4.1.0",
651
+ "strip-ansi": "^6.0.0"
652
+ },
653
+ "engines": {
654
+ "node": ">=10"
655
+ },
656
+ "funding": {
657
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
658
+ }
659
+ },
660
+ "node_modules/y18n": {
661
+ "version": "5.0.8",
662
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
663
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
664
+ "dev": true,
665
+ "engines": {
666
+ "node": ">=10"
667
+ }
668
+ },
669
+ "node_modules/yargs": {
670
+ "version": "17.7.2",
671
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
672
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
673
+ "dev": true,
674
+ "dependencies": {
675
+ "cliui": "^8.0.1",
676
+ "escalade": "^3.1.1",
677
+ "get-caller-file": "^2.0.5",
678
+ "require-directory": "^2.1.1",
679
+ "string-width": "^4.2.3",
680
+ "y18n": "^5.0.5",
681
+ "yargs-parser": "^21.1.1"
682
+ },
683
+ "engines": {
684
+ "node": ">=12"
685
+ }
686
+ },
687
+ "node_modules/yargs-parser": {
688
+ "version": "21.1.1",
689
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
690
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
691
+ "dev": true,
692
+ "engines": {
693
+ "node": ">=12"
694
+ }
695
+ }
696
+ }
697
+ }
package.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "rag-system",
3
+ "version": "1.0.0",
4
+ "description": "RAG Q&A System",
5
+ "scripts": {
6
+ "start-backend": "nodemon --exec python api.py --ext py",
7
+ "start-frontend": "cd frontend && npm start",
8
+ "dev": "concurrently \"npm run start-backend\" \"npm run start-frontend\""
9
+ },
10
+ "devDependencies": {
11
+ "nodemon": "^3.0.3",
12
+ "concurrently": "^8.2.2"
13
+ }
14
+ }
pyproject.toml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "aie5-deploypythonicrag"
3
+ version = "0.1.0"
4
+ description = "Simple Pythonic RAG App"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "chainlit>=2.0.4",
9
+ "numpy>=2.2.2",
10
+ "openai>=1.59.9",
11
+ "pydantic==2.10.1",
12
+ "pypdf2>=3.0.1",
13
+ "websockets>=14.2",
14
+ "pip>=24.0",
15
+ ]
requirements.txt ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ chainlit>=2.0.4
2
+ numpy>=2.2.2
3
+ openai>=1.59.9
4
+ pip>=24.0
5
+ pydantic==2.10.1
6
+ pypdf2>=3.0.1
7
+ websockets>=14.2
8
+ aiofiles==23.2.1
9
+ annotated-types==0.7.0
10
+ anyio==4.8.0
11
+ asyncer==0.0.7
12
+ bidict==0.23.1
13
+ colorama==0.4.6
14
+ dataclasses-json==0.6.7
15
+ deprecated==1.2.15
16
+ distro==1.9.0
17
+ fastapi==0.115.6
18
+ filetype==1.2.0
19
+ lazify==0.4.0
20
+ literalai==0.1.103
21
+ marshmallow==3.25.1
22
+ mypy-extensions==1.0.0
23
+ nest-asyncio==1.6.0
24
+ protobuf==5.29.3
25
+ python-dotenv==1.0.1
26
+ python-engineio==4.11.2
27
+ python-multipart==0.0.18
28
+ python-socketio==5.12.1
29
+ requests==2.32.3
30
+ simple-websocket==1.1.0
31
+ sniffio==1.3.1
32
+ starlette==0.41.3
33
+ syncer==2.0.3
34
+ tomli==2.2.1
35
+ typing-inspect==0.9.0
36
+ uptrace==1.28.2
37
+ urllib3==2.3.0
38
+ uvicorn==0.34.0
39
+ watchfiles==0.20.0
uv.lock ADDED
@@ -0,0 +1,938 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version = 1
2
+ requires-python = ">=3.13"
3
+
4
+ [[package]]
5
+ name = "aie5-deploypythonicrag"
6
+ version = "0.1.0"
7
+ source = { virtual = "." }
8
+ dependencies = [
9
+ { name = "chainlit" },
10
+ { name = "numpy" },
11
+ { name = "openai" },
12
+ { name = "pip" },
13
+ { name = "pydantic" },
14
+ { name = "pypdf2" },
15
+ { name = "websockets" },
16
+ ]
17
+
18
+ [package.metadata]
19
+ requires-dist = [
20
+ { name = "chainlit", specifier = ">=2.0.4" },
21
+ { name = "numpy", specifier = ">=2.2.2" },
22
+ { name = "openai", specifier = ">=1.59.9" },
23
+ { name = "pip", specifier = ">=24.0" },
24
+ { name = "pydantic", specifier = "==2.10.1" },
25
+ { name = "pypdf2", specifier = ">=3.0.1" },
26
+ { name = "websockets", specifier = ">=14.2" },
27
+ ]
28
+
29
+ [[package]]
30
+ name = "aiofiles"
31
+ version = "23.2.1"
32
+ source = { registry = "https://pypi.org/simple" }
33
+ sdist = { url = "https://files.pythonhosted.org/packages/af/41/cfed10bc64d774f497a86e5ede9248e1d062db675504b41c320954d99641/aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a", size = 32072 }
34
+ wheels = [
35
+ { url = "https://files.pythonhosted.org/packages/c5/19/5af6804c4cc0fed83f47bff6e413a98a36618e7d40185cd36e69737f3b0e/aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107", size = 15727 },
36
+ ]
37
+
38
+ [[package]]
39
+ name = "annotated-types"
40
+ version = "0.7.0"
41
+ source = { registry = "https://pypi.org/simple" }
42
+ sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
43
+ wheels = [
44
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
45
+ ]
46
+
47
+ [[package]]
48
+ name = "anyio"
49
+ version = "4.8.0"
50
+ source = { registry = "https://pypi.org/simple" }
51
+ dependencies = [
52
+ { name = "idna" },
53
+ { name = "sniffio" },
54
+ ]
55
+ sdist = { url = "https://files.pythonhosted.org/packages/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 }
56
+ wheels = [
57
+ { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 },
58
+ ]
59
+
60
+ [[package]]
61
+ name = "asyncer"
62
+ version = "0.0.7"
63
+ source = { registry = "https://pypi.org/simple" }
64
+ dependencies = [
65
+ { name = "anyio" },
66
+ ]
67
+ sdist = { url = "https://files.pythonhosted.org/packages/39/29/245ba9fa5769a1e3226c1157aedb372fe9dab28c4e1dcf6911d84d3a5e04/asyncer-0.0.7.tar.gz", hash = "sha256:d5e563fb0f56eb87b97257984703658a4f5bbdb52ff851b3e8ed864cc200b1d2", size = 14437 }
68
+ wheels = [
69
+ { url = "https://files.pythonhosted.org/packages/3e/4b/40a1dc52fc26695b1e80a9e67dfb0fe7e6ddc57bbc5b61348e40c0045abb/asyncer-0.0.7-py3-none-any.whl", hash = "sha256:f0d579d4f67c4ead52ede3a45c854f462cae569058a8a6a68a4ebccac1c335d8", size = 8476 },
70
+ ]
71
+
72
+ [[package]]
73
+ name = "bidict"
74
+ version = "0.23.1"
75
+ source = { registry = "https://pypi.org/simple" }
76
+ sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093 }
77
+ wheels = [
78
+ { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764 },
79
+ ]
80
+
81
+ [[package]]
82
+ name = "certifi"
83
+ version = "2024.12.14"
84
+ source = { registry = "https://pypi.org/simple" }
85
+ sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 }
86
+ wheels = [
87
+ { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 },
88
+ ]
89
+
90
+ [[package]]
91
+ name = "chainlit"
92
+ version = "2.0.5"
93
+ source = { registry = "https://pypi.org/simple" }
94
+ dependencies = [
95
+ { name = "aiofiles" },
96
+ { name = "asyncer" },
97
+ { name = "click" },
98
+ { name = "dataclasses-json" },
99
+ { name = "fastapi" },
100
+ { name = "filetype" },
101
+ { name = "httpx" },
102
+ { name = "lazify" },
103
+ { name = "literalai" },
104
+ { name = "nest-asyncio" },
105
+ { name = "packaging" },
106
+ { name = "pydantic" },
107
+ { name = "pyjwt" },
108
+ { name = "python-dotenv" },
109
+ { name = "python-multipart" },
110
+ { name = "python-socketio" },
111
+ { name = "starlette" },
112
+ { name = "syncer" },
113
+ { name = "tomli" },
114
+ { name = "uptrace" },
115
+ { name = "uvicorn" },
116
+ { name = "watchfiles" },
117
+ ]
118
+ sdist = { url = "https://files.pythonhosted.org/packages/1e/d8/7173caf3ca0d7480b3614e3126da9c592692d353764326fc0e1702b9eddd/chainlit-2.0.5.tar.gz", hash = "sha256:8af7746999d6641c69c33b67e5325e2d018432dd0b3306926d7435b862b0bfe2", size = 4646512 }
119
+ wheels = [
120
+ { url = "https://files.pythonhosted.org/packages/dc/01/8f02145330355e2802b95f835afb4cf11ea503b779cd6136892d4940abc5/chainlit-2.0.5-py3-none-any.whl", hash = "sha256:30cd2c39a9393de047b4e64b3dcf84ca4f691cb61445d59ae9a29f8e1f1af006", size = 4709971 },
121
+ ]
122
+
123
+ [[package]]
124
+ name = "charset-normalizer"
125
+ version = "3.4.1"
126
+ source = { registry = "https://pypi.org/simple" }
127
+ sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
128
+ wheels = [
129
+ { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 },
130
+ { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 },
131
+ { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 },
132
+ { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 },
133
+ { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 },
134
+ { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 },
135
+ { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 },
136
+ { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 },
137
+ { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 },
138
+ { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 },
139
+ { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 },
140
+ { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 },
141
+ { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 },
142
+ { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 },
143
+ ]
144
+
145
+ [[package]]
146
+ name = "chevron"
147
+ version = "0.14.0"
148
+ source = { registry = "https://pypi.org/simple" }
149
+ sdist = { url = "https://files.pythonhosted.org/packages/15/1f/ca74b65b19798895d63a6e92874162f44233467c9e7c1ed8afd19016ebe9/chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf", size = 11440 }
150
+ wheels = [
151
+ { url = "https://files.pythonhosted.org/packages/52/93/342cc62a70ab727e093ed98e02a725d85b746345f05d2b5e5034649f4ec8/chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443", size = 11595 },
152
+ ]
153
+
154
+ [[package]]
155
+ name = "click"
156
+ version = "8.1.8"
157
+ source = { registry = "https://pypi.org/simple" }
158
+ dependencies = [
159
+ { name = "colorama", marker = "sys_platform == 'win32'" },
160
+ ]
161
+ sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
162
+ wheels = [
163
+ { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 },
164
+ ]
165
+
166
+ [[package]]
167
+ name = "colorama"
168
+ version = "0.4.6"
169
+ source = { registry = "https://pypi.org/simple" }
170
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
171
+ wheels = [
172
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
173
+ ]
174
+
175
+ [[package]]
176
+ name = "dataclasses-json"
177
+ version = "0.6.7"
178
+ source = { registry = "https://pypi.org/simple" }
179
+ dependencies = [
180
+ { name = "marshmallow" },
181
+ { name = "typing-inspect" },
182
+ ]
183
+ sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227 }
184
+ wheels = [
185
+ { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686 },
186
+ ]
187
+
188
+ [[package]]
189
+ name = "deprecated"
190
+ version = "1.2.15"
191
+ source = { registry = "https://pypi.org/simple" }
192
+ dependencies = [
193
+ { name = "wrapt" },
194
+ ]
195
+ sdist = { url = "https://files.pythonhosted.org/packages/2e/a3/53e7d78a6850ffdd394d7048a31a6f14e44900adedf190f9a165f6b69439/deprecated-1.2.15.tar.gz", hash = "sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d", size = 2977612 }
196
+ wheels = [
197
+ { url = "https://files.pythonhosted.org/packages/1d/8f/c7f227eb42cfeaddce3eb0c96c60cbca37797fa7b34f8e1aeadf6c5c0983/Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320", size = 9941 },
198
+ ]
199
+
200
+ [[package]]
201
+ name = "distro"
202
+ version = "1.9.0"
203
+ source = { registry = "https://pypi.org/simple" }
204
+ sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 }
205
+ wheels = [
206
+ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 },
207
+ ]
208
+
209
+ [[package]]
210
+ name = "fastapi"
211
+ version = "0.115.6"
212
+ source = { registry = "https://pypi.org/simple" }
213
+ dependencies = [
214
+ { name = "pydantic" },
215
+ { name = "starlette" },
216
+ { name = "typing-extensions" },
217
+ ]
218
+ sdist = { url = "https://files.pythonhosted.org/packages/93/72/d83b98cd106541e8f5e5bfab8ef2974ab45a62e8a6c5b5e6940f26d2ed4b/fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654", size = 301336 }
219
+ wheels = [
220
+ { url = "https://files.pythonhosted.org/packages/52/b3/7e4df40e585df024fac2f80d1a2d579c854ac37109675db2b0cc22c0bb9e/fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305", size = 94843 },
221
+ ]
222
+
223
+ [[package]]
224
+ name = "filetype"
225
+ version = "1.2.0"
226
+ source = { registry = "https://pypi.org/simple" }
227
+ sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020 }
228
+ wheels = [
229
+ { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 },
230
+ ]
231
+
232
+ [[package]]
233
+ name = "googleapis-common-protos"
234
+ version = "1.66.0"
235
+ source = { registry = "https://pypi.org/simple" }
236
+ dependencies = [
237
+ { name = "protobuf" },
238
+ ]
239
+ sdist = { url = "https://files.pythonhosted.org/packages/ff/a7/8e9cccdb1c49870de6faea2a2764fa23f627dd290633103540209f03524c/googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c", size = 114376 }
240
+ wheels = [
241
+ { url = "https://files.pythonhosted.org/packages/a0/0f/c0713fb2b3d28af4b2fded3291df1c4d4f79a00d15c2374a9e010870016c/googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed", size = 221682 },
242
+ ]
243
+
244
+ [[package]]
245
+ name = "grpcio"
246
+ version = "1.69.0"
247
+ source = { registry = "https://pypi.org/simple" }
248
+ sdist = { url = "https://files.pythonhosted.org/packages/e4/87/06a145284cbe86c91ca517fe6b57be5efbb733c0d6374b407f0992054d18/grpcio-1.69.0.tar.gz", hash = "sha256:936fa44241b5379c5afc344e1260d467bee495747eaf478de825bab2791da6f5", size = 12738244 }
249
+ wheels = [
250
+ { url = "https://files.pythonhosted.org/packages/54/47/3ff4501365f56b7cc16617695dbd4fd838c5e362bc7fa9fee09d592f7d78/grpcio-1.69.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:a78a06911d4081a24a1761d16215a08e9b6d4d29cdbb7e427e6c7e17b06bcc5d", size = 5162928 },
251
+ { url = "https://files.pythonhosted.org/packages/c0/63/437174c5fa951052c9ecc5f373f62af6f3baf25f3f5ef35cbf561806b371/grpcio-1.69.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:dc5a351927d605b2721cbb46158e431dd49ce66ffbacb03e709dc07a491dde35", size = 11103027 },
252
+ { url = "https://files.pythonhosted.org/packages/53/df/53566a6fdc26b6d1f0585896e1cc4825961039bca5a6a314ff29d79b5d5b/grpcio-1.69.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:3629d8a8185f5139869a6a17865d03113a260e311e78fbe313f1a71603617589", size = 5659277 },
253
+ { url = "https://files.pythonhosted.org/packages/e6/4c/b8a0c4f71498b6f9be5ca6d290d576cf2af9d95fd9827c47364f023969ad/grpcio-1.69.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9a281878feeb9ae26db0622a19add03922a028d4db684658f16d546601a4870", size = 6305255 },
254
+ { url = "https://files.pythonhosted.org/packages/ef/55/d9aa05eb3dfcf6aa946aaf986740ec07fc5189f20e2cbeb8c5d278ffd00f/grpcio-1.69.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cc614e895177ab7e4b70f154d1a7c97e152577ea101d76026d132b7aaba003b", size = 5920240 },
255
+ { url = "https://files.pythonhosted.org/packages/ea/eb/774b27c51e3e386dfe6c491a710f6f87ffdb20d88ec6c3581e047d9354a2/grpcio-1.69.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1ee76cd7e2e49cf9264f6812d8c9ac1b85dda0eaea063af07292400f9191750e", size = 6652974 },
256
+ { url = "https://files.pythonhosted.org/packages/59/98/96de14e6e7d89123813d58c246d9b0f1fbd24f9277f5295264e60861d9d6/grpcio-1.69.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:0470fa911c503af59ec8bc4c82b371ee4303ececbbdc055f55ce48e38b20fd67", size = 6215757 },
257
+ { url = "https://files.pythonhosted.org/packages/7d/5b/ce922e0785910b10756fabc51fd294260384a44bea41651dadc4e47ddc82/grpcio-1.69.0-cp313-cp313-win32.whl", hash = "sha256:b650f34aceac8b2d08a4c8d7dc3e8a593f4d9e26d86751ebf74ebf5107d927de", size = 3642488 },
258
+ { url = "https://files.pythonhosted.org/packages/5d/04/11329e6ca1ceeb276df2d9c316b5e170835a687a4d0f778dba8294657e36/grpcio-1.69.0-cp313-cp313-win_amd64.whl", hash = "sha256:028337786f11fecb5d7b7fa660475a06aabf7e5e52b5ac2df47414878c0ce7ea", size = 4399968 },
259
+ ]
260
+
261
+ [[package]]
262
+ name = "h11"
263
+ version = "0.14.0"
264
+ source = { registry = "https://pypi.org/simple" }
265
+ sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
266
+ wheels = [
267
+ { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
268
+ ]
269
+
270
+ [[package]]
271
+ name = "httpcore"
272
+ version = "1.0.7"
273
+ source = { registry = "https://pypi.org/simple" }
274
+ dependencies = [
275
+ { name = "certifi" },
276
+ { name = "h11" },
277
+ ]
278
+ sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 }
279
+ wheels = [
280
+ { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 },
281
+ ]
282
+
283
+ [[package]]
284
+ name = "httpx"
285
+ version = "0.28.1"
286
+ source = { registry = "https://pypi.org/simple" }
287
+ dependencies = [
288
+ { name = "anyio" },
289
+ { name = "certifi" },
290
+ { name = "httpcore" },
291
+ { name = "idna" },
292
+ ]
293
+ sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
294
+ wheels = [
295
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
296
+ ]
297
+
298
+ [[package]]
299
+ name = "idna"
300
+ version = "3.10"
301
+ source = { registry = "https://pypi.org/simple" }
302
+ sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
303
+ wheels = [
304
+ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
305
+ ]
306
+
307
+ [[package]]
308
+ name = "importlib-metadata"
309
+ version = "8.5.0"
310
+ source = { registry = "https://pypi.org/simple" }
311
+ dependencies = [
312
+ { name = "zipp" },
313
+ ]
314
+ sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304 }
315
+ wheels = [
316
+ { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514 },
317
+ ]
318
+
319
+ [[package]]
320
+ name = "jiter"
321
+ version = "0.8.2"
322
+ source = { registry = "https://pypi.org/simple" }
323
+ sdist = { url = "https://files.pythonhosted.org/packages/f8/70/90bc7bd3932e651486861df5c8ffea4ca7c77d28e8532ddefe2abc561a53/jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d", size = 163007 }
324
+ wheels = [
325
+ { url = "https://files.pythonhosted.org/packages/6c/b0/bfa1f6f2c956b948802ef5a021281978bf53b7a6ca54bb126fd88a5d014e/jiter-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca1f08b8e43dc3bd0594c992fb1fd2f7ce87f7bf0d44358198d6da8034afdf84", size = 301190 },
326
+ { url = "https://files.pythonhosted.org/packages/a4/8f/396ddb4e292b5ea57e45ade5dc48229556b9044bad29a3b4b2dddeaedd52/jiter-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5672a86d55416ccd214c778efccf3266b84f87b89063b582167d803246354be4", size = 309334 },
327
+ { url = "https://files.pythonhosted.org/packages/7f/68/805978f2f446fa6362ba0cc2e4489b945695940656edd844e110a61c98f8/jiter-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58dc9bc9767a1101f4e5e22db1b652161a225874d66f0e5cb8e2c7d1c438b587", size = 333918 },
328
+ { url = "https://files.pythonhosted.org/packages/b3/99/0f71f7be667c33403fa9706e5b50583ae5106d96fab997fa7e2f38ee8347/jiter-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b2998606d6dadbb5ccda959a33d6a5e853252d921fec1792fc902351bb4e2c", size = 356057 },
329
+ { url = "https://files.pythonhosted.org/packages/8d/50/a82796e421a22b699ee4d2ce527e5bcb29471a2351cbdc931819d941a167/jiter-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab9a87f3784eb0e098f84a32670cfe4a79cb6512fd8f42ae3d0709f06405d18", size = 379790 },
330
+ { url = "https://files.pythonhosted.org/packages/3c/31/10fb012b00f6d83342ca9e2c9618869ab449f1aa78c8f1b2193a6b49647c/jiter-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79aec8172b9e3c6d05fd4b219d5de1ac616bd8da934107325a6c0d0e866a21b6", size = 388285 },
331
+ { url = "https://files.pythonhosted.org/packages/c8/81/f15ebf7de57be488aa22944bf4274962aca8092e4f7817f92ffa50d3ee46/jiter-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711e408732d4e9a0208008e5892c2966b485c783cd2d9a681f3eb147cf36c7ef", size = 344764 },
332
+ { url = "https://files.pythonhosted.org/packages/b3/e8/0cae550d72b48829ba653eb348cdc25f3f06f8a62363723702ec18e7be9c/jiter-0.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:653cf462db4e8c41995e33d865965e79641ef45369d8a11f54cd30888b7e6ff1", size = 376620 },
333
+ { url = "https://files.pythonhosted.org/packages/b8/50/e5478ff9d82534a944c03b63bc217c5f37019d4a34d288db0f079b13c10b/jiter-0.8.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9c63eaef32b7bebac8ebebf4dabebdbc6769a09c127294db6babee38e9f405b9", size = 510402 },
334
+ { url = "https://files.pythonhosted.org/packages/8e/1e/3de48bbebbc8f7025bd454cedc8c62378c0e32dd483dece5f4a814a5cb55/jiter-0.8.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:eb21aaa9a200d0a80dacc7a81038d2e476ffe473ffdd9c91eb745d623561de05", size = 503018 },
335
+ { url = "https://files.pythonhosted.org/packages/d5/cd/d5a5501d72a11fe3e5fd65c78c884e5164eefe80077680533919be22d3a3/jiter-0.8.2-cp313-cp313-win32.whl", hash = "sha256:789361ed945d8d42850f919342a8665d2dc79e7e44ca1c97cc786966a21f627a", size = 203190 },
336
+ { url = "https://files.pythonhosted.org/packages/51/bf/e5ca301245ba951447e3ad677a02a64a8845b185de2603dabd83e1e4b9c6/jiter-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ab7f43235d71e03b941c1630f4b6e3055d46b6cb8728a17663eaac9d8e83a865", size = 203551 },
337
+ { url = "https://files.pythonhosted.org/packages/2f/3c/71a491952c37b87d127790dd7a0b1ebea0514c6b6ad30085b16bbe00aee6/jiter-0.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b426f72cd77da3fec300ed3bc990895e2dd6b49e3bfe6c438592a3ba660e41ca", size = 308347 },
338
+ { url = "https://files.pythonhosted.org/packages/a0/4c/c02408042e6a7605ec063daed138e07b982fdb98467deaaf1c90950cf2c6/jiter-0.8.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2dd880785088ff2ad21ffee205e58a8c1ddabc63612444ae41e5e4b321b39c0", size = 342875 },
339
+ { url = "https://files.pythonhosted.org/packages/91/61/c80ef80ed8a0a21158e289ef70dac01e351d929a1c30cb0f49be60772547/jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566", size = 202374 },
340
+ ]
341
+
342
+ [[package]]
343
+ name = "lazify"
344
+ version = "0.4.0"
345
+ source = { registry = "https://pypi.org/simple" }
346
+ sdist = { url = "https://files.pythonhosted.org/packages/24/2c/b55c4a27a56dd9a00bb2812c404b57f8b7aec0cdbff9fdc61acdd73359bc/Lazify-0.4.0.tar.gz", hash = "sha256:7102bfe63e56de2ab62b3bc661a7190c4056771a8624f04a8b785275c3dd1f9b", size = 2968 }
347
+ wheels = [
348
+ { url = "https://files.pythonhosted.org/packages/03/a5/866b44697cee47d1cae429ed370281d937ad4439f71af82a6baaa139d26a/Lazify-0.4.0-py2.py3-none-any.whl", hash = "sha256:c2c17a7a33e9406897e3f66fde4cd3f84716218d580330e5af10cfe5a0cd195a", size = 3107 },
349
+ ]
350
+
351
+ [[package]]
352
+ name = "literalai"
353
+ version = "0.1.103"
354
+ source = { registry = "https://pypi.org/simple" }
355
+ dependencies = [
356
+ { name = "chevron" },
357
+ { name = "httpx" },
358
+ { name = "packaging" },
359
+ { name = "pydantic" },
360
+ ]
361
+ sdist = { url = "https://files.pythonhosted.org/packages/fc/fc/628b39e31b368aacbca51721ba7a66a4d140e9be916a0c7396664fdaed7a/literalai-0.1.103.tar.gz", hash = "sha256:060e86e63c0f53041a737b2183354ac092ee8cd9faec817dc95df639bb263a7d", size = 62540 }
362
+
363
+ [[package]]
364
+ name = "marshmallow"
365
+ version = "3.25.1"
366
+ source = { registry = "https://pypi.org/simple" }
367
+ dependencies = [
368
+ { name = "packaging" },
369
+ ]
370
+ sdist = { url = "https://files.pythonhosted.org/packages/b8/85/43b8e95251312e8d0d3389263e87e368a5a015db475e140d5dd8cb8dcb47/marshmallow-3.25.1.tar.gz", hash = "sha256:f4debda3bb11153d81ac34b0d582bf23053055ee11e791b54b4b35493468040a", size = 217295 }
371
+ wheels = [
372
+ { url = "https://files.pythonhosted.org/packages/8e/25/5b300f0400078d9783fbe44d30fedd849a130fc3aff01f18278c12342b6f/marshmallow-3.25.1-py3-none-any.whl", hash = "sha256:ec5d00d873ce473b7f2ffcb7104286a376c354cab0c2fa12f5573dab03e87210", size = 49624 },
373
+ ]
374
+
375
+ [[package]]
376
+ name = "mypy-extensions"
377
+ version = "1.0.0"
378
+ source = { registry = "https://pypi.org/simple" }
379
+ sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 }
380
+ wheels = [
381
+ { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
382
+ ]
383
+
384
+ [[package]]
385
+ name = "nest-asyncio"
386
+ version = "1.6.0"
387
+ source = { registry = "https://pypi.org/simple" }
388
+ sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 }
389
+ wheels = [
390
+ { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 },
391
+ ]
392
+
393
+ [[package]]
394
+ name = "numpy"
395
+ version = "2.2.2"
396
+ source = { registry = "https://pypi.org/simple" }
397
+ sdist = { url = "https://files.pythonhosted.org/packages/ec/d0/c12ddfd3a02274be06ffc71f3efc6d0e457b0409c4481596881e748cb264/numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f", size = 20233295 }
398
+ wheels = [
399
+ { url = "https://files.pythonhosted.org/packages/e1/fe/df5624001f4f5c3e0b78e9017bfab7fdc18a8d3b3d3161da3d64924dd659/numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc", size = 20899188 },
400
+ { url = "https://files.pythonhosted.org/packages/a9/80/d349c3b5ed66bd3cb0214be60c27e32b90a506946857b866838adbe84040/numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369", size = 14113972 },
401
+ { url = "https://files.pythonhosted.org/packages/9d/50/949ec9cbb28c4b751edfa64503f0913cbfa8d795b4a251e7980f13a8a655/numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd", size = 5114294 },
402
+ { url = "https://files.pythonhosted.org/packages/8d/f3/399c15629d5a0c68ef2aa7621d430b2be22034f01dd7f3c65a9c9666c445/numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be", size = 6648426 },
403
+ { url = "https://files.pythonhosted.org/packages/2c/03/c72474c13772e30e1bc2e558cdffd9123c7872b731263d5648b5c49dd459/numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84", size = 14045990 },
404
+ { url = "https://files.pythonhosted.org/packages/83/9c/96a9ab62274ffafb023f8ee08c88d3d31ee74ca58869f859db6845494fa6/numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff", size = 16096614 },
405
+ { url = "https://files.pythonhosted.org/packages/d5/34/cd0a735534c29bec7093544b3a509febc9b0df77718a9b41ffb0809c9f46/numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0", size = 15242123 },
406
+ { url = "https://files.pythonhosted.org/packages/5e/6d/541717a554a8f56fa75e91886d9b79ade2e595918690eb5d0d3dbd3accb9/numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de", size = 17859160 },
407
+ { url = "https://files.pythonhosted.org/packages/b9/a5/fbf1f2b54adab31510728edd06a05c1b30839f37cf8c9747cb85831aaf1b/numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9", size = 6273337 },
408
+ { url = "https://files.pythonhosted.org/packages/56/e5/01106b9291ef1d680f82bc47d0c5b5e26dfed15b0754928e8f856c82c881/numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369", size = 12609010 },
409
+ { url = "https://files.pythonhosted.org/packages/9f/30/f23d9876de0f08dceb707c4dcf7f8dd7588266745029debb12a3cdd40be6/numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391", size = 20924451 },
410
+ { url = "https://files.pythonhosted.org/packages/6a/ec/6ea85b2da9d5dfa1dbb4cb3c76587fc8ddcae580cb1262303ab21c0926c4/numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39", size = 14122390 },
411
+ { url = "https://files.pythonhosted.org/packages/68/05/bfbdf490414a7dbaf65b10c78bc243f312c4553234b6d91c94eb7c4b53c2/numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317", size = 5156590 },
412
+ { url = "https://files.pythonhosted.org/packages/f7/ec/fe2e91b2642b9d6544518388a441bcd65c904cea38d9ff998e2e8ebf808e/numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49", size = 6671958 },
413
+ { url = "https://files.pythonhosted.org/packages/b1/6f/6531a78e182f194d33ee17e59d67d03d0d5a1ce7f6be7343787828d1bd4a/numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2", size = 14019950 },
414
+ { url = "https://files.pythonhosted.org/packages/e1/fb/13c58591d0b6294a08cc40fcc6b9552d239d773d520858ae27f39997f2ae/numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7", size = 16079759 },
415
+ { url = "https://files.pythonhosted.org/packages/2c/f2/f2f8edd62abb4b289f65a7f6d1f3650273af00b91b7267a2431be7f1aec6/numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb", size = 15226139 },
416
+ { url = "https://files.pythonhosted.org/packages/aa/29/14a177f1a90b8ad8a592ca32124ac06af5eff32889874e53a308f850290f/numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648", size = 17856316 },
417
+ { url = "https://files.pythonhosted.org/packages/95/03/242ae8d7b97f4e0e4ab8dd51231465fb23ed5e802680d629149722e3faf1/numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4", size = 6329134 },
418
+ { url = "https://files.pythonhosted.org/packages/80/94/cd9e9b04012c015cb6320ab3bf43bc615e248dddfeb163728e800a5d96f0/numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576", size = 12696208 },
419
+ ]
420
+
421
+ [[package]]
422
+ name = "openai"
423
+ version = "1.59.9"
424
+ source = { registry = "https://pypi.org/simple" }
425
+ dependencies = [
426
+ { name = "anyio" },
427
+ { name = "distro" },
428
+ { name = "httpx" },
429
+ { name = "jiter" },
430
+ { name = "pydantic" },
431
+ { name = "sniffio" },
432
+ { name = "tqdm" },
433
+ { name = "typing-extensions" },
434
+ ]
435
+ sdist = { url = "https://files.pythonhosted.org/packages/ec/2d/04faa92bac0341649223398503db4415d2f658a757d9d32bb68f3378ddd0/openai-1.59.9.tar.gz", hash = "sha256:ec1a20b0351b4c3e65c6292db71d8233515437c6065efd4fd50edeb55df5f5d2", size = 347134 }
436
+ wheels = [
437
+ { url = "https://files.pythonhosted.org/packages/07/b4/57f1954a4560092ad8c45f07ad183eab9c8e093e0a1db829f9b506b2d5d1/openai-1.59.9-py3-none-any.whl", hash = "sha256:61a0608a1313c08ddf92fe793b6dbd1630675a1fe3866b2f96447ce30050c448", size = 455527 },
438
+ ]
439
+
440
+ [[package]]
441
+ name = "opentelemetry-api"
442
+ version = "1.28.2"
443
+ source = { registry = "https://pypi.org/simple" }
444
+ dependencies = [
445
+ { name = "deprecated" },
446
+ { name = "importlib-metadata" },
447
+ ]
448
+ sdist = { url = "https://files.pythonhosted.org/packages/51/34/e4e9245c868c6490a46ffedf6bd5b0f512bbc0a848b19e3a51f6bbad648c/opentelemetry_api-1.28.2.tar.gz", hash = "sha256:ecdc70c7139f17f9b0cf3742d57d7020e3e8315d6cffcdf1a12a905d45b19cc0", size = 62796 }
449
+ wheels = [
450
+ { url = "https://files.pythonhosted.org/packages/4d/58/b17393cdfc149e14ee84c662abf921993dcce8058628359ef1f49e2abb97/opentelemetry_api-1.28.2-py3-none-any.whl", hash = "sha256:6fcec89e265beb258fe6b1acaaa3c8c705a934bd977b9f534a2b7c0d2d4275a6", size = 64302 },
451
+ ]
452
+
453
+ [[package]]
454
+ name = "opentelemetry-exporter-otlp"
455
+ version = "1.28.2"
456
+ source = { registry = "https://pypi.org/simple" }
457
+ dependencies = [
458
+ { name = "opentelemetry-exporter-otlp-proto-grpc" },
459
+ { name = "opentelemetry-exporter-otlp-proto-http" },
460
+ ]
461
+ sdist = { url = "https://files.pythonhosted.org/packages/8a/eb/ad88c61b4e51cdd294ad4ae7c45b35120fb381eb019675954c4fc15b6c4c/opentelemetry_exporter_otlp-1.28.2.tar.gz", hash = "sha256:45f8d7fe4cdd41526464b542ce91b1fd1ae661be92d2c6cba71a3d948b2bdf70", size = 6155 }
462
+ wheels = [
463
+ { url = "https://files.pythonhosted.org/packages/b8/16/65b0f0f9a85e6c0e1ce30e0ea96e0174ca4db85301883d1d6a9702700946/opentelemetry_exporter_otlp-1.28.2-py3-none-any.whl", hash = "sha256:b50f6d4a80e6bcd329e36f360ac486ecfa106ea704d6226ceea05d3a48455f70", size = 7010 },
464
+ ]
465
+
466
+ [[package]]
467
+ name = "opentelemetry-exporter-otlp-proto-common"
468
+ version = "1.28.2"
469
+ source = { registry = "https://pypi.org/simple" }
470
+ dependencies = [
471
+ { name = "opentelemetry-proto" },
472
+ ]
473
+ sdist = { url = "https://files.pythonhosted.org/packages/60/cd/cd990f891b64e7698b8a6b6ab90dfac7f957db5a3d06788acd52f73ad4c0/opentelemetry_exporter_otlp_proto_common-1.28.2.tar.gz", hash = "sha256:7aebaa5fc9ff6029374546df1f3a62616fda07fccd9c6a8b7892ec130dd8baca", size = 19136 }
474
+ wheels = [
475
+ { url = "https://files.pythonhosted.org/packages/2a/4d/769f3b1b1c6af5e603da50349ba31af757897540a75d666de22d39461055/opentelemetry_exporter_otlp_proto_common-1.28.2-py3-none-any.whl", hash = "sha256:545b1943b574f666c35b3d6cc67cb0b111060727e93a1e2866e346b33bff2a12", size = 18460 },
476
+ ]
477
+
478
+ [[package]]
479
+ name = "opentelemetry-exporter-otlp-proto-grpc"
480
+ version = "1.28.2"
481
+ source = { registry = "https://pypi.org/simple" }
482
+ dependencies = [
483
+ { name = "deprecated" },
484
+ { name = "googleapis-common-protos" },
485
+ { name = "grpcio" },
486
+ { name = "opentelemetry-api" },
487
+ { name = "opentelemetry-exporter-otlp-proto-common" },
488
+ { name = "opentelemetry-proto" },
489
+ { name = "opentelemetry-sdk" },
490
+ ]
491
+ sdist = { url = "https://files.pythonhosted.org/packages/f7/4c/b5374467e97f2b290611de746d0e6cab3a07aec865d6b99d11535cd60059/opentelemetry_exporter_otlp_proto_grpc-1.28.2.tar.gz", hash = "sha256:07c10378380bbb01a7f621a5ce833fc1fab816e971140cd3ea1cd587840bc0e6", size = 26227 }
492
+ wheels = [
493
+ { url = "https://files.pythonhosted.org/packages/dd/7e/6af5a7de87988cfc951db86f7fd0ecaabc20bc112fd9cfe06b8a01f11400/opentelemetry_exporter_otlp_proto_grpc-1.28.2-py3-none-any.whl", hash = "sha256:6083d9300863aab35bfce7c172d5fc1007686e6f8dff366eae460cd9a21592e2", size = 18518 },
494
+ ]
495
+
496
+ [[package]]
497
+ name = "opentelemetry-exporter-otlp-proto-http"
498
+ version = "1.28.2"
499
+ source = { registry = "https://pypi.org/simple" }
500
+ dependencies = [
501
+ { name = "deprecated" },
502
+ { name = "googleapis-common-protos" },
503
+ { name = "opentelemetry-api" },
504
+ { name = "opentelemetry-exporter-otlp-proto-common" },
505
+ { name = "opentelemetry-proto" },
506
+ { name = "opentelemetry-sdk" },
507
+ { name = "requests" },
508
+ ]
509
+ sdist = { url = "https://files.pythonhosted.org/packages/b1/91/4e32e52d13dbdf9560bc095dfe66a2c09e0034a886f7725fcda8fe10a052/opentelemetry_exporter_otlp_proto_http-1.28.2.tar.gz", hash = "sha256:d9b353d67217f091aaf4cfe8693c170973bb3e90a558992570d97020618fda79", size = 15043 }
510
+ wheels = [
511
+ { url = "https://files.pythonhosted.org/packages/19/23/802b889cf8bf3e235f30fbcbaa2b3fd484fe8c76b5b4db00f00c0e9af20f/opentelemetry_exporter_otlp_proto_http-1.28.2-py3-none-any.whl", hash = "sha256:af921c18212a56ef4be68458ba475791c0517ebfd8a2ff04669c9cd477d90ff2", size = 17218 },
512
+ ]
513
+
514
+ [[package]]
515
+ name = "opentelemetry-instrumentation"
516
+ version = "0.49b2"
517
+ source = { registry = "https://pypi.org/simple" }
518
+ dependencies = [
519
+ { name = "opentelemetry-api" },
520
+ { name = "opentelemetry-semantic-conventions" },
521
+ { name = "packaging" },
522
+ { name = "wrapt" },
523
+ ]
524
+ sdist = { url = "https://files.pythonhosted.org/packages/6f/1f/9fa51f6f64f4d179f4e3370eb042176ff7717682428552f5e1f4c5efcc09/opentelemetry_instrumentation-0.49b2.tar.gz", hash = "sha256:8cf00cc8d9d479e4b72adb9bd267ec544308c602b7188598db5a687e77b298e2", size = 26480 }
525
+ wheels = [
526
+ { url = "https://files.pythonhosted.org/packages/ef/e3/ad23372525653b0221212d5e2a71bd97aae64cc35f90cbf0c70de57dfa4e/opentelemetry_instrumentation-0.49b2-py3-none-any.whl", hash = "sha256:f6d782b0ef9fef4a4c745298651c65f5c532c34cd4c40d230ab5b9f3b3b4d151", size = 30693 },
527
+ ]
528
+
529
+ [[package]]
530
+ name = "opentelemetry-proto"
531
+ version = "1.28.2"
532
+ source = { registry = "https://pypi.org/simple" }
533
+ dependencies = [
534
+ { name = "protobuf" },
535
+ ]
536
+ sdist = { url = "https://files.pythonhosted.org/packages/d0/45/96c4f34c79fd87dc8a1c0c432f23a5a202729f21e4e63c8b36fc8e57767a/opentelemetry_proto-1.28.2.tar.gz", hash = "sha256:7c0d125a6b71af88bfeeda16bfdd0ff63dc2cf0039baf6f49fa133b203e3f566", size = 34316 }
537
+ wheels = [
538
+ { url = "https://files.pythonhosted.org/packages/1d/12/646f48d6d698a6df0437a22b591387440dc4888c8752d1a1300f730da710/opentelemetry_proto-1.28.2-py3-none-any.whl", hash = "sha256:0837498f59db55086462915e5898d0b1a18c1392f6db4d7e937143072a72370c", size = 55818 },
539
+ ]
540
+
541
+ [[package]]
542
+ name = "opentelemetry-sdk"
543
+ version = "1.28.2"
544
+ source = { registry = "https://pypi.org/simple" }
545
+ dependencies = [
546
+ { name = "opentelemetry-api" },
547
+ { name = "opentelemetry-semantic-conventions" },
548
+ { name = "typing-extensions" },
549
+ ]
550
+ sdist = { url = "https://files.pythonhosted.org/packages/4b/f4/840a5af4efe48d7fb4c456ad60fd624673e871a60d6494f7ff8a934755d4/opentelemetry_sdk-1.28.2.tar.gz", hash = "sha256:5fed24c5497e10df30282456fe2910f83377797511de07d14cec0d3e0a1a3110", size = 157272 }
551
+ wheels = [
552
+ { url = "https://files.pythonhosted.org/packages/da/8b/4f2b418496c08016d4384f9b1c4725a8af7faafa248d624be4bb95993ce1/opentelemetry_sdk-1.28.2-py3-none-any.whl", hash = "sha256:93336c129556f1e3ccd21442b94d3521759541521861b2214c499571b85cb71b", size = 118757 },
553
+ ]
554
+
555
+ [[package]]
556
+ name = "opentelemetry-semantic-conventions"
557
+ version = "0.49b2"
558
+ source = { registry = "https://pypi.org/simple" }
559
+ dependencies = [
560
+ { name = "deprecated" },
561
+ { name = "opentelemetry-api" },
562
+ ]
563
+ sdist = { url = "https://files.pythonhosted.org/packages/7d/0a/e3b93f94aa3223c6fd8e743502a1fefd4fb3a753d8f501ce2a418f7c0bd4/opentelemetry_semantic_conventions-0.49b2.tar.gz", hash = "sha256:44e32ce6a5bb8d7c0c617f84b9dc1c8deda1045a07dc16a688cc7cbeab679997", size = 95213 }
564
+ wheels = [
565
+ { url = "https://files.pythonhosted.org/packages/b1/be/6661c8f76708bb3ba38c90be8fa8d7ffe17ccbc5cbbc229334f5535f6448/opentelemetry_semantic_conventions-0.49b2-py3-none-any.whl", hash = "sha256:51e7e1d0daa958782b6c2a8ed05e5f0e7dd0716fc327ac058777b8659649ee54", size = 159199 },
566
+ ]
567
+
568
+ [[package]]
569
+ name = "packaging"
570
+ version = "24.2"
571
+ source = { registry = "https://pypi.org/simple" }
572
+ sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
573
+ wheels = [
574
+ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
575
+ ]
576
+
577
+ [[package]]
578
+ name = "pip"
579
+ version = "24.3.1"
580
+ source = { registry = "https://pypi.org/simple" }
581
+ sdist = { url = "https://files.pythonhosted.org/packages/f4/b1/b422acd212ad7eedddaf7981eee6e5de085154ff726459cf2da7c5a184c1/pip-24.3.1.tar.gz", hash = "sha256:ebcb60557f2aefabc2e0f918751cd24ea0d56d8ec5445fe1807f1d2109660b99", size = 1931073 }
582
+ wheels = [
583
+ { url = "https://files.pythonhosted.org/packages/ef/7d/500c9ad20238fcfcb4cb9243eede163594d7020ce87bd9610c9e02771876/pip-24.3.1-py3-none-any.whl", hash = "sha256:3790624780082365f47549d032f3770eeb2b1e8bd1f7b2e02dace1afa361b4ed", size = 1822182 },
584
+ ]
585
+
586
+ [[package]]
587
+ name = "protobuf"
588
+ version = "5.29.3"
589
+ source = { registry = "https://pypi.org/simple" }
590
+ sdist = { url = "https://files.pythonhosted.org/packages/f7/d1/e0a911544ca9993e0f17ce6d3cc0932752356c1b0a834397f28e63479344/protobuf-5.29.3.tar.gz", hash = "sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620", size = 424945 }
591
+ wheels = [
592
+ { url = "https://files.pythonhosted.org/packages/dc/7a/1e38f3cafa022f477ca0f57a1f49962f21ad25850c3ca0acd3b9d0091518/protobuf-5.29.3-cp310-abi3-win32.whl", hash = "sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888", size = 422708 },
593
+ { url = "https://files.pythonhosted.org/packages/61/fa/aae8e10512b83de633f2646506a6d835b151edf4b30d18d73afd01447253/protobuf-5.29.3-cp310-abi3-win_amd64.whl", hash = "sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a", size = 434508 },
594
+ { url = "https://files.pythonhosted.org/packages/dd/04/3eaedc2ba17a088961d0e3bd396eac764450f431621b58a04ce898acd126/protobuf-5.29.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e", size = 417825 },
595
+ { url = "https://files.pythonhosted.org/packages/4f/06/7c467744d23c3979ce250397e26d8ad8eeb2bea7b18ca12ad58313c1b8d5/protobuf-5.29.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84", size = 319573 },
596
+ { url = "https://files.pythonhosted.org/packages/a8/45/2ebbde52ad2be18d3675b6bee50e68cd73c9e0654de77d595540b5129df8/protobuf-5.29.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f", size = 319672 },
597
+ { url = "https://files.pythonhosted.org/packages/fd/b2/ab07b09e0f6d143dfb839693aa05765257bceaa13d03bf1a696b78323e7a/protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f", size = 172550 },
598
+ ]
599
+
600
+ [[package]]
601
+ name = "pydantic"
602
+ version = "2.10.1"
603
+ source = { registry = "https://pypi.org/simple" }
604
+ dependencies = [
605
+ { name = "annotated-types" },
606
+ { name = "pydantic-core" },
607
+ { name = "typing-extensions" },
608
+ ]
609
+ sdist = { url = "https://files.pythonhosted.org/packages/c4/bd/7fc610993f616d2398958d0028d15eaf53bde5f80cb2edb7aa4f1feaf3a7/pydantic-2.10.1.tar.gz", hash = "sha256:a4daca2dc0aa429555e0656d6bf94873a7dc5f54ee42b1f5873d666fb3f35560", size = 783717 }
610
+ wheels = [
611
+ { url = "https://files.pythonhosted.org/packages/e0/fc/fda48d347bd50a788dd2a0f318a52160f911b86fc2d8b4c86f4d7c9bceea/pydantic-2.10.1-py3-none-any.whl", hash = "sha256:a8d20db84de64cf4a7d59e899c2caf0fe9d660c7cfc482528e7020d7dd189a7e", size = 455329 },
612
+ ]
613
+
614
+ [[package]]
615
+ name = "pydantic-core"
616
+ version = "2.27.1"
617
+ source = { registry = "https://pypi.org/simple" }
618
+ dependencies = [
619
+ { name = "typing-extensions" },
620
+ ]
621
+ sdist = { url = "https://files.pythonhosted.org/packages/a6/9f/7de1f19b6aea45aeb441838782d68352e71bfa98ee6fa048d5041991b33e/pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235", size = 412785 }
622
+ wheels = [
623
+ { url = "https://files.pythonhosted.org/packages/0f/d6/91cb99a3c59d7b072bded9959fbeab0a9613d5a4935773c0801f1764c156/pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073", size = 1895033 },
624
+ { url = "https://files.pythonhosted.org/packages/07/42/d35033f81a28b27dedcade9e967e8a40981a765795c9ebae2045bcef05d3/pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08", size = 1807542 },
625
+ { url = "https://files.pythonhosted.org/packages/41/c2/491b59e222ec7e72236e512108ecad532c7f4391a14e971c963f624f7569/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf", size = 1827854 },
626
+ { url = "https://files.pythonhosted.org/packages/e3/f3/363652651779113189cefdbbb619b7b07b7a67ebb6840325117cc8cc3460/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737", size = 1857389 },
627
+ { url = "https://files.pythonhosted.org/packages/5f/97/be804aed6b479af5a945daec7538d8bf358d668bdadde4c7888a2506bdfb/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2", size = 2037934 },
628
+ { url = "https://files.pythonhosted.org/packages/42/01/295f0bd4abf58902917e342ddfe5f76cf66ffabfc57c2e23c7681a1a1197/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107", size = 2735176 },
629
+ { url = "https://files.pythonhosted.org/packages/9d/a0/cd8e9c940ead89cc37812a1a9f310fef59ba2f0b22b4e417d84ab09fa970/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51", size = 2160720 },
630
+ { url = "https://files.pythonhosted.org/packages/73/ae/9d0980e286627e0aeca4c352a60bd760331622c12d576e5ea4441ac7e15e/pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a", size = 1992972 },
631
+ { url = "https://files.pythonhosted.org/packages/bf/ba/ae4480bc0292d54b85cfb954e9d6bd226982949f8316338677d56541b85f/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc", size = 2001477 },
632
+ { url = "https://files.pythonhosted.org/packages/55/b7/e26adf48c2f943092ce54ae14c3c08d0d221ad34ce80b18a50de8ed2cba8/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960", size = 2091186 },
633
+ { url = "https://files.pythonhosted.org/packages/ba/cc/8491fff5b608b3862eb36e7d29d36a1af1c945463ca4c5040bf46cc73f40/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23", size = 2154429 },
634
+ { url = "https://files.pythonhosted.org/packages/78/d8/c080592d80edd3441ab7f88f865f51dae94a157fc64283c680e9f32cf6da/pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05", size = 1833713 },
635
+ { url = "https://files.pythonhosted.org/packages/83/84/5ab82a9ee2538ac95a66e51f6838d6aba6e0a03a42aa185ad2fe404a4e8f/pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337", size = 1987897 },
636
+ { url = "https://files.pythonhosted.org/packages/df/c3/b15fb833926d91d982fde29c0624c9f225da743c7af801dace0d4e187e71/pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", size = 1882983 },
637
+ ]
638
+
639
+ [[package]]
640
+ name = "pyjwt"
641
+ version = "2.10.1"
642
+ source = { registry = "https://pypi.org/simple" }
643
+ sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 }
644
+ wheels = [
645
+ { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 },
646
+ ]
647
+
648
+ [[package]]
649
+ name = "pypdf2"
650
+ version = "3.0.1"
651
+ source = { registry = "https://pypi.org/simple" }
652
+ sdist = { url = "https://files.pythonhosted.org/packages/9f/bb/18dc3062d37db6c491392007dfd1a7f524bb95886eb956569ac38a23a784/PyPDF2-3.0.1.tar.gz", hash = "sha256:a74408f69ba6271f71b9352ef4ed03dc53a31aa404d29b5d31f53bfecfee1440", size = 227419 }
653
+ wheels = [
654
+ { url = "https://files.pythonhosted.org/packages/8e/5e/c86a5643653825d3c913719e788e41386bee415c2b87b4f955432f2de6b2/pypdf2-3.0.1-py3-none-any.whl", hash = "sha256:d16e4205cfee272fbdc0568b68d82be796540b1537508cef59388f839c191928", size = 232572 },
655
+ ]
656
+
657
+ [[package]]
658
+ name = "python-dotenv"
659
+ version = "1.0.1"
660
+ source = { registry = "https://pypi.org/simple" }
661
+ sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
662
+ wheels = [
663
+ { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
664
+ ]
665
+
666
+ [[package]]
667
+ name = "python-engineio"
668
+ version = "4.11.2"
669
+ source = { registry = "https://pypi.org/simple" }
670
+ dependencies = [
671
+ { name = "simple-websocket" },
672
+ ]
673
+ sdist = { url = "https://files.pythonhosted.org/packages/52/e0/a9e0fe427ce7f1b7dbf9531fa00ffe4b557c4a7bc8e71891c115af123170/python_engineio-4.11.2.tar.gz", hash = "sha256:145bb0daceb904b4bb2d3eb2d93f7dbb7bb87a6a0c4f20a94cc8654dec977129", size = 91381 }
674
+ wheels = [
675
+ { url = "https://files.pythonhosted.org/packages/07/8f/978a0b913e3f8ad33a9a2fe204d32efe3d1ee34ecb1f2829c1cfbdd92082/python_engineio-4.11.2-py3-none-any.whl", hash = "sha256:f0971ac4c65accc489154fe12efd88f53ca8caf04754c46a66e85f5102ef22ad", size = 59239 },
676
+ ]
677
+
678
+ [[package]]
679
+ name = "python-multipart"
680
+ version = "0.0.18"
681
+ source = { registry = "https://pypi.org/simple" }
682
+ sdist = { url = "https://files.pythonhosted.org/packages/b4/86/b6b38677dec2e2e7898fc5b6f7e42c2d011919a92d25339451892f27b89c/python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe", size = 36622 }
683
+ wheels = [
684
+ { url = "https://files.pythonhosted.org/packages/13/6b/b60f47101ba2cac66b4a83246630e68ae9bbe2e614cbae5f4465f46dee13/python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996", size = 24389 },
685
+ ]
686
+
687
+ [[package]]
688
+ name = "python-socketio"
689
+ version = "5.12.1"
690
+ source = { registry = "https://pypi.org/simple" }
691
+ dependencies = [
692
+ { name = "bidict" },
693
+ { name = "python-engineio" },
694
+ ]
695
+ sdist = { url = "https://files.pythonhosted.org/packages/ce/d0/40ed38076e8aee94785d546d3e3a1cae393da5806a8530be877187e2875f/python_socketio-5.12.1.tar.gz", hash = "sha256:0299ff1f470b676c09c1bfab1dead25405077d227b2c13cf217a34dadc68ba9c", size = 119991 }
696
+ wheels = [
697
+ { url = "https://files.pythonhosted.org/packages/8a/a3/c69806f30dd81df5a99d592e7db4c930c3a9b098555aa97b0eb866b20b11/python_socketio-5.12.1-py3-none-any.whl", hash = "sha256:24a0ea7cfff0e021eb28c68edbf7914ee4111bdf030b95e4d250c4dc9af7a386", size = 76947 },
698
+ ]
699
+
700
+ [[package]]
701
+ name = "requests"
702
+ version = "2.32.3"
703
+ source = { registry = "https://pypi.org/simple" }
704
+ dependencies = [
705
+ { name = "certifi" },
706
+ { name = "charset-normalizer" },
707
+ { name = "idna" },
708
+ { name = "urllib3" },
709
+ ]
710
+ sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
711
+ wheels = [
712
+ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
713
+ ]
714
+
715
+ [[package]]
716
+ name = "simple-websocket"
717
+ version = "1.1.0"
718
+ source = { registry = "https://pypi.org/simple" }
719
+ dependencies = [
720
+ { name = "wsproto" },
721
+ ]
722
+ sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300 }
723
+ wheels = [
724
+ { url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842 },
725
+ ]
726
+
727
+ [[package]]
728
+ name = "sniffio"
729
+ version = "1.3.1"
730
+ source = { registry = "https://pypi.org/simple" }
731
+ sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
732
+ wheels = [
733
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
734
+ ]
735
+
736
+ [[package]]
737
+ name = "starlette"
738
+ version = "0.41.3"
739
+ source = { registry = "https://pypi.org/simple" }
740
+ dependencies = [
741
+ { name = "anyio" },
742
+ ]
743
+ sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159 }
744
+ wheels = [
745
+ { url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225 },
746
+ ]
747
+
748
+ [[package]]
749
+ name = "syncer"
750
+ version = "2.0.3"
751
+ source = { registry = "https://pypi.org/simple" }
752
+ sdist = { url = "https://files.pythonhosted.org/packages/8d/dd/d4dd75843692690d81f0a4b929212a1614b25d4896aa7c72f4c3546c7e3d/syncer-2.0.3.tar.gz", hash = "sha256:4340eb54b54368724a78c5c0763824470201804fe9180129daf3635cb500550f", size = 11512 }
753
+
754
+ [[package]]
755
+ name = "tomli"
756
+ version = "2.2.1"
757
+ source = { registry = "https://pypi.org/simple" }
758
+ sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 }
759
+ wheels = [
760
+ { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 },
761
+ { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 },
762
+ { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 },
763
+ { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 },
764
+ { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 },
765
+ { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 },
766
+ { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 },
767
+ { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 },
768
+ { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 },
769
+ { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 },
770
+ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
771
+ ]
772
+
773
+ [[package]]
774
+ name = "tqdm"
775
+ version = "4.67.1"
776
+ source = { registry = "https://pypi.org/simple" }
777
+ dependencies = [
778
+ { name = "colorama", marker = "sys_platform == 'win32'" },
779
+ ]
780
+ sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 }
781
+ wheels = [
782
+ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 },
783
+ ]
784
+
785
+ [[package]]
786
+ name = "typing-extensions"
787
+ version = "4.12.2"
788
+ source = { registry = "https://pypi.org/simple" }
789
+ sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
790
+ wheels = [
791
+ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
792
+ ]
793
+
794
+ [[package]]
795
+ name = "typing-inspect"
796
+ version = "0.9.0"
797
+ source = { registry = "https://pypi.org/simple" }
798
+ dependencies = [
799
+ { name = "mypy-extensions" },
800
+ { name = "typing-extensions" },
801
+ ]
802
+ sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 }
803
+ wheels = [
804
+ { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 },
805
+ ]
806
+
807
+ [[package]]
808
+ name = "uptrace"
809
+ version = "1.28.2"
810
+ source = { registry = "https://pypi.org/simple" }
811
+ dependencies = [
812
+ { name = "opentelemetry-api" },
813
+ { name = "opentelemetry-exporter-otlp" },
814
+ { name = "opentelemetry-instrumentation" },
815
+ { name = "opentelemetry-sdk" },
816
+ ]
817
+ sdist = { url = "https://files.pythonhosted.org/packages/71/a3/9236f08b4d15a4dbc72d8281ca80642e02aeabf649d15de73d9cafde6e31/uptrace-1.28.2.tar.gz", hash = "sha256:8858345a8eee04482b175ae899ddefc1030e0490588ae486abfe0de27e6f6e8e", size = 7629 }
818
+ wheels = [
819
+ { url = "https://files.pythonhosted.org/packages/d2/d8/1362c475f9bc7709abd55c9914c0dc6d5f636f39a801cf251bc26122c6f5/uptrace-1.28.2-py3-none-any.whl", hash = "sha256:6eba00edd82f42b9f87833f3cf9c9775caf31d8fa892a4ce956ec710eabbe4b0", size = 8620 },
820
+ ]
821
+
822
+ [[package]]
823
+ name = "urllib3"
824
+ version = "2.3.0"
825
+ source = { registry = "https://pypi.org/simple" }
826
+ sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 }
827
+ wheels = [
828
+ { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 },
829
+ ]
830
+
831
+ [[package]]
832
+ name = "uvicorn"
833
+ version = "0.34.0"
834
+ source = { registry = "https://pypi.org/simple" }
835
+ dependencies = [
836
+ { name = "click" },
837
+ { name = "h11" },
838
+ ]
839
+ sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 }
840
+ wheels = [
841
+ { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 },
842
+ ]
843
+
844
+ [[package]]
845
+ name = "watchfiles"
846
+ version = "0.20.0"
847
+ source = { registry = "https://pypi.org/simple" }
848
+ dependencies = [
849
+ { name = "anyio" },
850
+ ]
851
+ sdist = { url = "https://files.pythonhosted.org/packages/ef/48/02d2d2cbf54e134810b2cb40ac79fdb8ce08476184536a4764717a7bc9f4/watchfiles-0.20.0.tar.gz", hash = "sha256:728575b6b94c90dd531514677201e8851708e6e4b5fe7028ac506a200b622019", size = 37041 }
852
+ wheels = [
853
+ { url = "https://files.pythonhosted.org/packages/4d/db/899832e11fef2d468bf8b3c1c13289b1db4cb7c3410bb2a9612a52fc8b22/watchfiles-0.20.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:3796312bd3587e14926013612b23066912cf45a14af71cf2b20db1c12dadf4e9", size = 417357 },
854
+ { url = "https://files.pythonhosted.org/packages/9f/1a/85c914e4db62a3f8197daa98a271ea380a5d200a8d3058bd9f417752bc26/watchfiles-0.20.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:d0002d81c89a662b595645fb684a371b98ff90a9c7d8f8630c82f0fde8310458", size = 407258 },
855
+ { url = "https://files.pythonhosted.org/packages/25/ae/b7bddad421af5e33079a2ce639aa58837b715a2da98df16e25ecd310af52/watchfiles-0.20.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:570848706440373b4cd8017f3e850ae17f76dbdf1e9045fc79023b11e1afe490", size = 1331327 },
856
+ { url = "https://files.pythonhosted.org/packages/21/e5/b080cec4e841b1cf338ccbd958cf3232ad1691a590653b2d124b5c79cf6b/watchfiles-0.20.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a0351d20d03c6f7ad6b2e8a226a5efafb924c7755ee1e34f04c77c3682417fa", size = 1301371 },
857
+ { url = "https://files.pythonhosted.org/packages/05/a0/2fb2c36730995a6b3f060187195dc08ad9ceee67426bdca8a4296024071c/watchfiles-0.20.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:007dcc4a401093010b389c044e81172c8a2520dba257c88f8828b3d460c6bb38", size = 1302438 },
858
+ { url = "https://files.pythonhosted.org/packages/13/ea/d11971958ae703cfe443b21f672169cb8bc12dbec5781b910633fa2186ec/watchfiles-0.20.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d82dbc1832da83e441d112069833eedd4cf583d983fb8dd666fbefbea9d99c0", size = 1410655 },
859
+ { url = "https://files.pythonhosted.org/packages/6b/81/3f922f3ede53ca9c0b4095f63688ffeea19a49592d0ac62db1eb9632b1e3/watchfiles-0.20.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99f4c65fd2fce61a571b2a6fcf747d6868db0bef8a934e8ca235cc8533944d95", size = 1494222 },
860
+ { url = "https://files.pythonhosted.org/packages/e1/46/c9d5ee4871b187d291d62e61c41f9a4d67d4866a89704b0ad16b6949e9bd/watchfiles-0.20.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5392dd327a05f538c56edb1c6ebba6af91afc81b40822452342f6da54907bbdf", size = 1294171 },
861
+ { url = "https://files.pythonhosted.org/packages/59/5e/6b64e3bf9fd4422250f3c716d992dd76dbe55e6fa1e7ebaf2bf88f389707/watchfiles-0.20.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:08dc702529bb06a2b23859110c214db245455532da5eaea602921687cfcd23db", size = 1462256 },
862
+ { url = "https://files.pythonhosted.org/packages/11/c0/75f5a71ac24118ab11bd898e0114cedc72b25924ff2d960d473bddb4ec6e/watchfiles-0.20.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7d4e66a857621584869cfbad87039e65dadd7119f0d9bb9dbc957e089e32c164", size = 1461725 },
863
+ { url = "https://files.pythonhosted.org/packages/91/d4/0c0fdcc4293ad1b73db54896fa0de4b37439ae4f25971b5eb1708dd04f9a/watchfiles-0.20.0-cp37-abi3-win32.whl", hash = "sha256:a03d1e6feb7966b417f43c3e3783188167fd69c2063e86bad31e62c4ea794cc5", size = 268193 },
864
+ { url = "https://files.pythonhosted.org/packages/87/79/098b1b1fcb6de16149d23283a2ab5dadce6a06b864e7a182d231f57a1f9e/watchfiles-0.20.0-cp37-abi3-win_amd64.whl", hash = "sha256:eccc8942bcdc7d638a01435d915b913255bbd66f018f1af051cd8afddb339ea3", size = 276723 },
865
+ { url = "https://files.pythonhosted.org/packages/3f/82/45dddf4f5bf8b73ba27382cebb2bb3c0ee922c7ef77d936b86276aa39dca/watchfiles-0.20.0-cp37-abi3-win_arm64.whl", hash = "sha256:b17d4176c49d207865630da5b59a91779468dd3e08692fe943064da260de2c7c", size = 265344 },
866
+ ]
867
+
868
+ [[package]]
869
+ name = "websockets"
870
+ version = "14.2"
871
+ source = { registry = "https://pypi.org/simple" }
872
+ sdist = { url = "https://files.pythonhosted.org/packages/94/54/8359678c726243d19fae38ca14a334e740782336c9f19700858c4eb64a1e/websockets-14.2.tar.gz", hash = "sha256:5059ed9c54945efb321f097084b4c7e52c246f2c869815876a69d1efc4ad6eb5", size = 164394 }
873
+ wheels = [
874
+ { url = "https://files.pythonhosted.org/packages/82/94/4f9b55099a4603ac53c2912e1f043d6c49d23e94dd82a9ce1eb554a90215/websockets-14.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f1372e511c7409a542291bce92d6c83320e02c9cf392223272287ce55bc224e", size = 163102 },
875
+ { url = "https://files.pythonhosted.org/packages/8e/b7/7484905215627909d9a79ae07070057afe477433fdacb59bf608ce86365a/websockets-14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4da98b72009836179bb596a92297b1a61bb5a830c0e483a7d0766d45070a08ad", size = 160766 },
876
+ { url = "https://files.pythonhosted.org/packages/a3/a4/edb62efc84adb61883c7d2c6ad65181cb087c64252138e12d655989eec05/websockets-14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8a86a269759026d2bde227652b87be79f8a734e582debf64c9d302faa1e9f03", size = 160998 },
877
+ { url = "https://files.pythonhosted.org/packages/f5/79/036d320dc894b96af14eac2529967a6fc8b74f03b83c487e7a0e9043d842/websockets-14.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86cf1aaeca909bf6815ea714d5c5736c8d6dd3a13770e885aafe062ecbd04f1f", size = 170780 },
878
+ { url = "https://files.pythonhosted.org/packages/63/75/5737d21ee4dd7e4b9d487ee044af24a935e36a9ff1e1419d684feedcba71/websockets-14.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b0f6c3ba3b1240f602ebb3971d45b02cc12bd1845466dd783496b3b05783a5", size = 169717 },
879
+ { url = "https://files.pythonhosted.org/packages/2c/3c/bf9b2c396ed86a0b4a92ff4cdaee09753d3ee389be738e92b9bbd0330b64/websockets-14.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669c3e101c246aa85bc8534e495952e2ca208bd87994650b90a23d745902db9a", size = 170155 },
880
+ { url = "https://files.pythonhosted.org/packages/75/2d/83a5aca7247a655b1da5eb0ee73413abd5c3a57fc8b92915805e6033359d/websockets-14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eabdb28b972f3729348e632ab08f2a7b616c7e53d5414c12108c29972e655b20", size = 170495 },
881
+ { url = "https://files.pythonhosted.org/packages/79/dd/699238a92761e2f943885e091486378813ac8f43e3c84990bc394c2be93e/websockets-14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2066dc4cbcc19f32c12a5a0e8cc1b7ac734e5b64ac0a325ff8353451c4b15ef2", size = 169880 },
882
+ { url = "https://files.pythonhosted.org/packages/c8/c9/67a8f08923cf55ce61aadda72089e3ed4353a95a3a4bc8bf42082810e580/websockets-14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ab95d357cd471df61873dadf66dd05dd4709cae001dd6342edafc8dc6382f307", size = 169856 },
883
+ { url = "https://files.pythonhosted.org/packages/17/b1/1ffdb2680c64e9c3921d99db460546194c40d4acbef999a18c37aa4d58a3/websockets-14.2-cp313-cp313-win32.whl", hash = "sha256:a9e72fb63e5f3feacdcf5b4ff53199ec8c18d66e325c34ee4c551ca748623bbc", size = 163974 },
884
+ { url = "https://files.pythonhosted.org/packages/14/13/8b7fc4cb551b9cfd9890f0fd66e53c18a06240319915533b033a56a3d520/websockets-14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b439ea828c4ba99bb3176dc8d9b933392a2413c0f6b149fdcba48393f573377f", size = 164420 },
885
+ { url = "https://files.pythonhosted.org/packages/7b/c8/d529f8a32ce40d98309f4470780631e971a5a842b60aec864833b3615786/websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b", size = 157416 },
886
+ ]
887
+
888
+ [[package]]
889
+ name = "wrapt"
890
+ version = "1.17.2"
891
+ source = { registry = "https://pypi.org/simple" }
892
+ sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531 }
893
+ wheels = [
894
+ { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800 },
895
+ { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824 },
896
+ { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920 },
897
+ { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690 },
898
+ { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861 },
899
+ { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174 },
900
+ { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721 },
901
+ { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763 },
902
+ { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585 },
903
+ { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676 },
904
+ { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871 },
905
+ { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312 },
906
+ { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062 },
907
+ { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155 },
908
+ { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471 },
909
+ { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208 },
910
+ { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339 },
911
+ { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232 },
912
+ { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476 },
913
+ { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377 },
914
+ { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986 },
915
+ { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750 },
916
+ { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594 },
917
+ ]
918
+
919
+ [[package]]
920
+ name = "wsproto"
921
+ version = "1.2.0"
922
+ source = { registry = "https://pypi.org/simple" }
923
+ dependencies = [
924
+ { name = "h11" },
925
+ ]
926
+ sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425 }
927
+ wheels = [
928
+ { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226 },
929
+ ]
930
+
931
+ [[package]]
932
+ name = "zipp"
933
+ version = "3.21.0"
934
+ source = { registry = "https://pypi.org/simple" }
935
+ sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 }
936
+ wheels = [
937
+ { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 },
938
+ ]