Spaces:
Build error
Build error
XThomasBU
commited on
Commit
•
60929fd
1
Parent(s):
eefbb54
updates
Browse files- .gitignore +3 -1
- Dockerfile +11 -3
- Dockerfile.dev +11 -4
- README.md +14 -9
- apps/ai_tutor/app.py +28 -11
- apps/ai_tutor/config/config_manager.py +188 -0
- apps/ai_tutor/config/constants.py +26 -0
- apps/ai_tutor/config/project_config.yml +14 -1
- {modules → apps/ai_tutor}/config/prompts.py +0 -0
- apps/ai_tutor/helpers.py +94 -0
- apps/ai_tutor/main.py +15 -20
- apps/chainlit_base/chainlit_base.py +30 -90
- apps/chainlit_base/config/config_manager.py +174 -0
- apps/chainlit_base/config/project_config.yml +4 -1
- apps/chainlit_base/config/prompts.py +97 -0
- modules/chat/helpers.py +1 -1
- modules/chat_processor/helpers.py +0 -92
- modules/config/config_manager.py +0 -0
- modules/config/constants.py +5 -28
- modules/dataloader/data_loader.py +11 -2
.gitignore
CHANGED
@@ -178,4 +178,6 @@ code/storage/models/
|
|
178 |
|
179 |
**/vectorstores/*
|
180 |
|
181 |
-
**/private/students.json
|
|
|
|
|
|
178 |
|
179 |
**/vectorstores/*
|
180 |
|
181 |
+
**/private/students.json
|
182 |
+
|
183 |
+
**/apps/*/storage/logs/*
|
Dockerfile
CHANGED
@@ -3,13 +3,18 @@ FROM python:3.11
|
|
3 |
WORKDIR /code
|
4 |
|
5 |
COPY ./requirements.txt /code/requirements.txt
|
|
|
6 |
|
7 |
RUN pip install --upgrade pip
|
8 |
|
9 |
RUN pip install --no-cache-dir -r /code/requirements.txt
|
|
|
10 |
|
11 |
COPY . /code
|
12 |
|
|
|
|
|
|
|
13 |
# List the contents of the /code directory to verify files are copied correctly
|
14 |
RUN ls -R /code
|
15 |
|
@@ -17,12 +22,15 @@ RUN ls -R /code
|
|
17 |
RUN chmod -R 777 /code
|
18 |
|
19 |
# Create a logs directory and set permissions
|
20 |
-
RUN mkdir /code/logs && chmod 777 /code/logs
|
21 |
|
22 |
# Create a cache directory within the application's working directory
|
23 |
RUN mkdir /.cache && chmod -R 777 /.cache
|
24 |
|
25 |
-
WORKDIR /code/
|
|
|
|
|
|
|
26 |
|
27 |
RUN --mount=type=secret,id=HUGGINGFACEHUB_API_TOKEN,mode=0444,required=true
|
28 |
RUN --mount=type=secret,id=OPENAI_API_KEY,mode=0444,required=true
|
@@ -35,4 +43,4 @@ RUN --mount=type=secret,id=LITERAL_API_KEY_LOGGING,mode=0444,required=true
|
|
35 |
RUN --mount=type=secret,id=CHAINLIT_AUTH_SECRET,mode=0444,required=true
|
36 |
|
37 |
# Default command to run the application
|
38 |
-
CMD
|
|
|
3 |
WORKDIR /code
|
4 |
|
5 |
COPY ./requirements.txt /code/requirements.txt
|
6 |
+
COPY ./setup.py /code/setup.py
|
7 |
|
8 |
RUN pip install --upgrade pip
|
9 |
|
10 |
RUN pip install --no-cache-dir -r /code/requirements.txt
|
11 |
+
RUN pip install -e .
|
12 |
|
13 |
COPY . /code
|
14 |
|
15 |
+
# Copy .env file to the application directory
|
16 |
+
COPY .env /code/apps/ai_tutor/.env
|
17 |
+
|
18 |
# List the contents of the /code directory to verify files are copied correctly
|
19 |
RUN ls -R /code
|
20 |
|
|
|
22 |
RUN chmod -R 777 /code
|
23 |
|
24 |
# Create a logs directory and set permissions
|
25 |
+
RUN mkdir /code/apps/ai_tutor/logs && chmod 777 /code/apps/ai_tutor/logs
|
26 |
|
27 |
# Create a cache directory within the application's working directory
|
28 |
RUN mkdir /.cache && chmod -R 777 /.cache
|
29 |
|
30 |
+
WORKDIR /code/apps/ai_tutor
|
31 |
+
|
32 |
+
# Expose the port the app runs on
|
33 |
+
EXPOSE 7860
|
34 |
|
35 |
RUN --mount=type=secret,id=HUGGINGFACEHUB_API_TOKEN,mode=0444,required=true
|
36 |
RUN --mount=type=secret,id=OPENAI_API_KEY,mode=0444,required=true
|
|
|
43 |
RUN --mount=type=secret,id=CHAINLIT_AUTH_SECRET,mode=0444,required=true
|
44 |
|
45 |
# Default command to run the application
|
46 |
+
CMD python -m modules.vectorstore.store_manager --config_file config/config.yml --project_config_file config/project_config.yml && python -m uvicorn app:app --host 0.0.0.0 --port 7860
|
Dockerfile.dev
CHANGED
@@ -3,13 +3,18 @@ FROM python:3.11
|
|
3 |
WORKDIR /code
|
4 |
|
5 |
COPY ./requirements.txt /code/requirements.txt
|
|
|
6 |
|
7 |
RUN pip install --upgrade pip
|
8 |
|
9 |
RUN pip install --no-cache-dir -r /code/requirements.txt
|
|
|
10 |
|
11 |
COPY . /code
|
12 |
|
|
|
|
|
|
|
13 |
# List the contents of the /code directory to verify files are copied correctly
|
14 |
RUN ls -R /code
|
15 |
|
@@ -17,15 +22,17 @@ RUN ls -R /code
|
|
17 |
RUN chmod -R 777 /code
|
18 |
|
19 |
# Create a logs directory and set permissions
|
20 |
-
RUN mkdir /code/logs && chmod 777 /code/logs
|
21 |
|
22 |
# Create a cache directory within the application's working directory
|
23 |
RUN mkdir /.cache && chmod -R 777 /.cache
|
24 |
|
25 |
-
WORKDIR /code/
|
|
|
|
|
26 |
|
27 |
# Expose the port the app runs on
|
28 |
-
EXPOSE
|
29 |
|
30 |
# Default command to run the application
|
31 |
-
CMD
|
|
|
3 |
WORKDIR /code
|
4 |
|
5 |
COPY ./requirements.txt /code/requirements.txt
|
6 |
+
COPY ./setup.py /code/setup.py
|
7 |
|
8 |
RUN pip install --upgrade pip
|
9 |
|
10 |
RUN pip install --no-cache-dir -r /code/requirements.txt
|
11 |
+
RUN pip install -e .
|
12 |
|
13 |
COPY . /code
|
14 |
|
15 |
+
# Copy .env file to the application directory
|
16 |
+
COPY .env /code/apps/ai_tutor/.env
|
17 |
+
|
18 |
# List the contents of the /code directory to verify files are copied correctly
|
19 |
RUN ls -R /code
|
20 |
|
|
|
22 |
RUN chmod -R 777 /code
|
23 |
|
24 |
# Create a logs directory and set permissions
|
25 |
+
RUN mkdir /code/apps/ai_tutor/logs && chmod 777 /code/apps/ai_tutor/logs
|
26 |
|
27 |
# Create a cache directory within the application's working directory
|
28 |
RUN mkdir /.cache && chmod -R 777 /.cache
|
29 |
|
30 |
+
WORKDIR /code/apps/ai_tutor
|
31 |
+
|
32 |
+
RUN ls -R /code
|
33 |
|
34 |
# Expose the port the app runs on
|
35 |
+
EXPOSE 7860
|
36 |
|
37 |
# Default command to run the application
|
38 |
+
CMD python -m modules.vectorstore.store_manager --config_file config/config.yml --project_config_file config/project_config.yml && python -m uvicorn app:app --host 0.0.0.0 --port 7860
|
README.md
CHANGED
@@ -30,26 +30,31 @@ Please visit [setup](https://dl4ds.github.io/dl4ds_tutor/guide/setup/) for more
|
|
30 |
git clone https://github.com/DL4DS/dl4ds_tutor
|
31 |
```
|
32 |
|
33 |
-
2.
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
- Add URLs in the `urls.txt` file.
|
35 |
-
- Add other PDF files in the `storage/data` directory.
|
36 |
|
37 |
3. **To test Data Loading (Optional)**
|
38 |
```bash
|
39 |
-
cd
|
40 |
-
python -m modules.dataloader.data_loader --links "your_pdf_link"
|
41 |
```
|
42 |
|
43 |
4. **Create the Vector Database**
|
44 |
```bash
|
45 |
-
cd
|
46 |
-
python -m modules.vectorstore.store_manager
|
47 |
```
|
48 |
-
- Note: You need to run the above command when you add new data to the `storage/data` directory, or if the `storage/data/urls.txt` file is updated.
|
49 |
|
50 |
6. **Run the FastAPI App**
|
51 |
```bash
|
52 |
-
cd
|
53 |
uvicorn app:app --port 7860
|
54 |
```
|
55 |
|
@@ -64,7 +69,7 @@ The HuggingFace Space is built using the `Dockerfile` in the repository. To run
|
|
64 |
|
65 |
```bash
|
66 |
docker build --tag dev -f Dockerfile.dev .
|
67 |
-
docker run -it --rm -p
|
68 |
```
|
69 |
|
70 |
## Contributing
|
|
|
30 |
git clone https://github.com/DL4DS/dl4ds_tutor
|
31 |
```
|
32 |
|
33 |
+
2. Create your app in the apps folder. (An example is the `apps/ai_tutor` app)
|
34 |
+
```
|
35 |
+
cd apps
|
36 |
+
mkdir your_app
|
37 |
+
```
|
38 |
+
|
39 |
+
2. **Put your data under the `apps/your_app/storage/data` directory**
|
40 |
- Add URLs in the `urls.txt` file.
|
41 |
+
- Add other PDF files in the `apps/your_app/storage/data` directory.
|
42 |
|
43 |
3. **To test Data Loading (Optional)**
|
44 |
```bash
|
45 |
+
cd apps/your_app
|
46 |
+
python -m modules.dataloader.data_loader --links "your_pdf_link" --config_file config/config.yml --project_config_file config/project_config.yml
|
47 |
```
|
48 |
|
49 |
4. **Create the Vector Database**
|
50 |
```bash
|
51 |
+
cd apps/your_app
|
52 |
+
python -m modules.vectorstore.store_manager --config_file config/config.yml --project_config_file config/project_config.yml
|
53 |
```
|
|
|
54 |
|
55 |
6. **Run the FastAPI App**
|
56 |
```bash
|
57 |
+
cd apps/your_app
|
58 |
uvicorn app:app --port 7860
|
59 |
```
|
60 |
|
|
|
69 |
|
70 |
```bash
|
71 |
docker build --tag dev -f Dockerfile.dev .
|
72 |
+
docker run -it --rm -p 7860:7860 dev
|
73 |
```
|
74 |
|
75 |
## Contributing
|
apps/ai_tutor/app.py
CHANGED
@@ -8,27 +8,32 @@ from chainlit.utils import mount_chainlit
|
|
8 |
import secrets
|
9 |
import json
|
10 |
import base64
|
11 |
-
from
|
12 |
OAUTH_GOOGLE_CLIENT_ID,
|
13 |
OAUTH_GOOGLE_CLIENT_SECRET,
|
14 |
CHAINLIT_URL,
|
15 |
-
GITHUB_REPO,
|
16 |
-
DOCS_WEBSITE,
|
17 |
-
ALL_TIME_TOKENS_ALLOCATED,
|
18 |
-
TOKENS_LEFT,
|
19 |
EMAIL_ENCRYPTION_KEY,
|
20 |
)
|
21 |
from fastapi.middleware.cors import CORSMiddleware
|
22 |
from fastapi.staticfiles import StaticFiles
|
23 |
-
from
|
24 |
-
get_user_details,
|
25 |
get_time,
|
26 |
reset_tokens_for_user,
|
27 |
check_user_cooldown,
|
28 |
-
update_user_info,
|
29 |
)
|
|
|
|
|
30 |
import hashlib
|
31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
GOOGLE_CLIENT_ID = OAUTH_GOOGLE_CLIENT_ID
|
33 |
GOOGLE_CLIENT_SECRET = OAUTH_GOOGLE_CLIENT_SECRET
|
34 |
GOOGLE_REDIRECT_URI = f"{CHAINLIT_URL}/auth/oauth/google/callback"
|
@@ -246,7 +251,11 @@ async def cooldown(request: Request):
|
|
246 |
else:
|
247 |
user_details.metadata["in_cooldown"] = False
|
248 |
await update_user_info(user_details)
|
249 |
-
await reset_tokens_for_user(
|
|
|
|
|
|
|
|
|
250 |
return RedirectResponse("/post-signin")
|
251 |
|
252 |
|
@@ -280,7 +289,11 @@ async def post_signin(request: Request):
|
|
280 |
return RedirectResponse("/cooldown")
|
281 |
else:
|
282 |
user_details.metadata["in_cooldown"] = False
|
283 |
-
await reset_tokens_for_user(
|
|
|
|
|
|
|
|
|
284 |
|
285 |
if user_info:
|
286 |
username = user_info["email"]
|
@@ -353,7 +366,11 @@ async def get_tokens_left(request: Request):
|
|
353 |
try:
|
354 |
user_info = await get_user_info_from_cookie(request)
|
355 |
user_details = await get_user_details(user_info["email"])
|
356 |
-
await reset_tokens_for_user(
|
|
|
|
|
|
|
|
|
357 |
tokens_left = user_details.metadata["tokens_left"]
|
358 |
return {"tokens_left": tokens_left}
|
359 |
except Exception as e:
|
|
|
8 |
import secrets
|
9 |
import json
|
10 |
import base64
|
11 |
+
from config.constants import (
|
12 |
OAUTH_GOOGLE_CLIENT_ID,
|
13 |
OAUTH_GOOGLE_CLIENT_SECRET,
|
14 |
CHAINLIT_URL,
|
|
|
|
|
|
|
|
|
15 |
EMAIL_ENCRYPTION_KEY,
|
16 |
)
|
17 |
from fastapi.middleware.cors import CORSMiddleware
|
18 |
from fastapi.staticfiles import StaticFiles
|
19 |
+
from helpers import (
|
|
|
20 |
get_time,
|
21 |
reset_tokens_for_user,
|
22 |
check_user_cooldown,
|
|
|
23 |
)
|
24 |
+
from modules.chat_processor.helpers import get_user_details, update_user_info
|
25 |
+
from config.config_manager import config_manager
|
26 |
import hashlib
|
27 |
|
28 |
+
# set config
|
29 |
+
config = config_manager.get_config().dict()
|
30 |
+
|
31 |
+
# set constants
|
32 |
+
GITHUB_REPO = config["misc"]["github_repo"]
|
33 |
+
DOCS_WEBSITE = config["misc"]["docs_website"]
|
34 |
+
ALL_TIME_TOKENS_ALLOCATED = config["token_config"]["all_time_tokens_allocated"]
|
35 |
+
TOKENS_LEFT = config["token_config"]["tokens_left"]
|
36 |
+
|
37 |
GOOGLE_CLIENT_ID = OAUTH_GOOGLE_CLIENT_ID
|
38 |
GOOGLE_CLIENT_SECRET = OAUTH_GOOGLE_CLIENT_SECRET
|
39 |
GOOGLE_REDIRECT_URI = f"{CHAINLIT_URL}/auth/oauth/google/callback"
|
|
|
251 |
else:
|
252 |
user_details.metadata["in_cooldown"] = False
|
253 |
await update_user_info(user_details)
|
254 |
+
await reset_tokens_for_user(
|
255 |
+
user_details,
|
256 |
+
config["token_config"]["tokens_left"],
|
257 |
+
config["token_config"]["regen_time"],
|
258 |
+
)
|
259 |
return RedirectResponse("/post-signin")
|
260 |
|
261 |
|
|
|
289 |
return RedirectResponse("/cooldown")
|
290 |
else:
|
291 |
user_details.metadata["in_cooldown"] = False
|
292 |
+
await reset_tokens_for_user(
|
293 |
+
user_details,
|
294 |
+
config["token_config"]["tokens_left"],
|
295 |
+
config["token_config"]["regen_time"],
|
296 |
+
)
|
297 |
|
298 |
if user_info:
|
299 |
username = user_info["email"]
|
|
|
366 |
try:
|
367 |
user_info = await get_user_info_from_cookie(request)
|
368 |
user_details = await get_user_details(user_info["email"])
|
369 |
+
await reset_tokens_for_user(
|
370 |
+
user_details,
|
371 |
+
config["token_config"]["tokens_left"],
|
372 |
+
config["token_config"]["regen_time"],
|
373 |
+
)
|
374 |
tokens_left = user_details.metadata["tokens_left"]
|
375 |
return {"tokens_left": tokens_left}
|
376 |
except Exception as e:
|
apps/ai_tutor/config/config_manager.py
ADDED
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel, Field, conint, confloat, conlist, HttpUrl
|
2 |
+
from typing import Optional, List
|
3 |
+
import yaml
|
4 |
+
|
5 |
+
|
6 |
+
class FaissParams(BaseModel):
|
7 |
+
index_path: str = "vectorstores/faiss.index"
|
8 |
+
index_type: str = "Flat" # Options: [Flat, HNSW, IVF]
|
9 |
+
index_dimension: conint(gt=0) = 384
|
10 |
+
index_nlist: conint(gt=0) = 100
|
11 |
+
index_nprobe: conint(gt=0) = 10
|
12 |
+
|
13 |
+
|
14 |
+
class ColbertParams(BaseModel):
|
15 |
+
index_name: str = "new_idx"
|
16 |
+
|
17 |
+
|
18 |
+
class VectorStoreConfig(BaseModel):
|
19 |
+
load_from_HF: bool = True
|
20 |
+
reparse_files: bool = True
|
21 |
+
data_path: str = "storage/data"
|
22 |
+
url_file_path: str = "storage/data/urls.txt"
|
23 |
+
expand_urls: bool = True
|
24 |
+
db_option: str = "RAGatouille" # Options: [FAISS, Chroma, RAGatouille, RAPTOR]
|
25 |
+
db_path: str = "vectorstores"
|
26 |
+
model: str = (
|
27 |
+
"sentence-transformers/all-MiniLM-L6-v2" # Options: [sentence-transformers/all-MiniLM-L6-v2, text-embedding-ada-002]
|
28 |
+
)
|
29 |
+
search_top_k: conint(gt=0) = 3
|
30 |
+
score_threshold: confloat(ge=0.0, le=1.0) = 0.2
|
31 |
+
|
32 |
+
faiss_params: Optional[FaissParams] = None
|
33 |
+
colbert_params: Optional[ColbertParams] = None
|
34 |
+
|
35 |
+
|
36 |
+
class OpenAIParams(BaseModel):
|
37 |
+
temperature: confloat(ge=0.0, le=1.0) = 0.7
|
38 |
+
|
39 |
+
|
40 |
+
class LocalLLMParams(BaseModel):
|
41 |
+
temperature: confloat(ge=0.0, le=1.0) = 0.7
|
42 |
+
repo_id: str = "TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF" # HuggingFace repo id
|
43 |
+
filename: str = (
|
44 |
+
"tinyllama-1.1b-chat-v1.0.Q5_0.gguf" # Specific name of gguf file in the repo
|
45 |
+
)
|
46 |
+
model_path: str = (
|
47 |
+
"storage/models/tinyllama-1.1b-chat-v1.0.Q5_0.gguf" # Path to the model file
|
48 |
+
)
|
49 |
+
|
50 |
+
|
51 |
+
class LLMParams(BaseModel):
|
52 |
+
llm_arch: str = "langchain" # Options: [langchain]
|
53 |
+
use_history: bool = True
|
54 |
+
generate_follow_up: bool = False
|
55 |
+
memory_window: conint(ge=1) = 3
|
56 |
+
llm_style: str = "Normal" # Options: [Normal, ELI5]
|
57 |
+
llm_loader: str = (
|
58 |
+
"gpt-4o-mini" # Options: [local_llm, gpt-3.5-turbo-1106, gpt-4, gpt-4o-mini]
|
59 |
+
)
|
60 |
+
openai_params: Optional[OpenAIParams] = None
|
61 |
+
local_llm_params: Optional[LocalLLMParams] = None
|
62 |
+
stream: bool = False
|
63 |
+
pdf_reader: str = "gpt" # Options: [llama, pymupdf, gpt]
|
64 |
+
|
65 |
+
|
66 |
+
class ChatLoggingConfig(BaseModel):
|
67 |
+
log_chat: bool = True
|
68 |
+
platform: str = "literalai"
|
69 |
+
callbacks: bool = True
|
70 |
+
|
71 |
+
|
72 |
+
class SplitterOptions(BaseModel):
|
73 |
+
use_splitter: bool = True
|
74 |
+
split_by_token: bool = True
|
75 |
+
remove_leftover_delimiters: bool = True
|
76 |
+
remove_chunks: bool = False
|
77 |
+
chunking_mode: str = "semantic" # Options: [fixed, semantic]
|
78 |
+
chunk_size: conint(gt=0) = 300
|
79 |
+
chunk_overlap: conint(ge=0) = 30
|
80 |
+
chunk_separators: List[str] = ["\n\n", "\n", " ", ""]
|
81 |
+
front_chunks_to_remove: Optional[conint(ge=0)] = None
|
82 |
+
last_chunks_to_remove: Optional[conint(ge=0)] = None
|
83 |
+
delimiters_to_remove: List[str] = ["\t", "\n", " ", " "]
|
84 |
+
|
85 |
+
|
86 |
+
class RetrieverConfig(BaseModel):
|
87 |
+
retriever_hf_paths: dict[str, str] = {"RAGatouille": "XThomasBU/Colbert_Index"}
|
88 |
+
|
89 |
+
|
90 |
+
class MetadataConfig(BaseModel):
|
91 |
+
metadata_links: List[HttpUrl] = [
|
92 |
+
"https://dl4ds.github.io/sp2024/lectures/",
|
93 |
+
"https://dl4ds.github.io/sp2024/schedule/",
|
94 |
+
]
|
95 |
+
slide_base_link: HttpUrl = "https://dl4ds.github.io"
|
96 |
+
|
97 |
+
|
98 |
+
class TokenConfig(BaseModel):
|
99 |
+
cooldown_time: conint(gt=0) = 60
|
100 |
+
regen_time: conint(gt=0) = 180
|
101 |
+
tokens_left: conint(gt=0) = 2000
|
102 |
+
all_time_tokens_allocated: conint(gt=0) = 1000000
|
103 |
+
|
104 |
+
|
105 |
+
class MiscConfig(BaseModel):
|
106 |
+
github_repo: HttpUrl = "https://github.com/DL4DS/dl4ds_tutor"
|
107 |
+
docs_website: HttpUrl = "https://dl4ds.github.io/dl4ds_tutor/"
|
108 |
+
|
109 |
+
|
110 |
+
class APIConfig(BaseModel):
|
111 |
+
timeout: conint(gt=0) = 60
|
112 |
+
|
113 |
+
|
114 |
+
class Config(BaseModel):
|
115 |
+
log_dir: str = "storage/logs"
|
116 |
+
log_chunk_dir: str = "storage/logs/chunks"
|
117 |
+
device: str = "cpu" # Options: ['cuda', 'cpu']
|
118 |
+
|
119 |
+
vectorstore: VectorStoreConfig
|
120 |
+
llm_params: LLMParams
|
121 |
+
chat_logging: ChatLoggingConfig
|
122 |
+
splitter_options: SplitterOptions
|
123 |
+
retriever: RetrieverConfig
|
124 |
+
metadata: MetadataConfig
|
125 |
+
token_config: TokenConfig
|
126 |
+
misc: MiscConfig
|
127 |
+
api_config: APIConfig
|
128 |
+
|
129 |
+
|
130 |
+
class ConfigManager:
|
131 |
+
def __init__(self, config_path: str, project_config_path: str):
|
132 |
+
self.config_path = config_path
|
133 |
+
self.project_config_path = project_config_path
|
134 |
+
self.config = self.load_config()
|
135 |
+
self.validate_config()
|
136 |
+
|
137 |
+
def load_config(self) -> Config:
|
138 |
+
with open(self.config_path, "r") as f:
|
139 |
+
config_data = yaml.safe_load(f)
|
140 |
+
|
141 |
+
with open(self.project_config_path, "r") as f:
|
142 |
+
project_config_data = yaml.safe_load(f)
|
143 |
+
|
144 |
+
# Merge the two configurations
|
145 |
+
merged_config = {**config_data, **project_config_data}
|
146 |
+
|
147 |
+
return Config(**merged_config)
|
148 |
+
|
149 |
+
def get_config(self) -> Config:
|
150 |
+
return ConfigWrapper(self.config)
|
151 |
+
|
152 |
+
def validate_config(self):
|
153 |
+
# If any required fields are missing, raise an error
|
154 |
+
# required_fields = [
|
155 |
+
# "vectorstore", "llm_params", "chat_logging", "splitter_options",
|
156 |
+
# "retriever", "metadata", "token_config", "misc", "api_config"
|
157 |
+
# ]
|
158 |
+
# for field in required_fields:
|
159 |
+
# if not hasattr(self.config, field):
|
160 |
+
# raise ValueError(f"Missing required configuration field: {field}")
|
161 |
+
|
162 |
+
# # Validate types of specific fields
|
163 |
+
# if not isinstance(self.config.vectorstore, VectorStoreConfig):
|
164 |
+
# raise TypeError("vectorstore must be an instance of VectorStoreConfig")
|
165 |
+
# if not isinstance(self.config.llm_params, LLMParams):
|
166 |
+
# raise TypeError("llm_params must be an instance of LLMParams")
|
167 |
+
pass
|
168 |
+
|
169 |
+
|
170 |
+
class ConfigWrapper:
|
171 |
+
def __init__(self, config: Config):
|
172 |
+
self._config = config
|
173 |
+
|
174 |
+
def __getitem__(self, key):
|
175 |
+
return getattr(self._config, key)
|
176 |
+
|
177 |
+
def __getattr__(self, name):
|
178 |
+
return getattr(self._config, name)
|
179 |
+
|
180 |
+
def dict(self):
|
181 |
+
return self._config.dict()
|
182 |
+
|
183 |
+
|
184 |
+
# Usage
|
185 |
+
config_manager = ConfigManager(
|
186 |
+
config_path="config/config.yml", project_config_path="config/project_config.yml"
|
187 |
+
)
|
188 |
+
# config = config_manager.get_config().dict()
|
apps/ai_tutor/config/constants.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from dotenv import load_dotenv
|
2 |
+
import os
|
3 |
+
|
4 |
+
load_dotenv()
|
5 |
+
|
6 |
+
# API Keys - Loaded from the .env file
|
7 |
+
|
8 |
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
9 |
+
LLAMA_CLOUD_API_KEY = os.getenv("LLAMA_CLOUD_API_KEY")
|
10 |
+
HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_TOKEN")
|
11 |
+
LITERAL_API_KEY_LOGGING = os.getenv("LITERAL_API_KEY_LOGGING")
|
12 |
+
LITERAL_API_URL = os.getenv("LITERAL_API_URL")
|
13 |
+
CHAINLIT_URL = os.getenv("CHAINLIT_URL")
|
14 |
+
EMAIL_ENCRYPTION_KEY = os.getenv("EMAIL_ENCRYPTION_KEY")
|
15 |
+
|
16 |
+
OAUTH_GOOGLE_CLIENT_ID = os.getenv("OAUTH_GOOGLE_CLIENT_ID")
|
17 |
+
OAUTH_GOOGLE_CLIENT_SECRET = os.getenv("OAUTH_GOOGLE_CLIENT_SECRET")
|
18 |
+
|
19 |
+
opening_message = "Hey, What Can I Help You With?\n\nYou can me ask me questions about the course logistics, course content, about the final project, or anything else!"
|
20 |
+
chat_end_message = (
|
21 |
+
"I hope I was able to help you. If you have any more questions, feel free to ask!"
|
22 |
+
)
|
23 |
+
|
24 |
+
# Model Paths
|
25 |
+
|
26 |
+
LLAMA_PATH = "../storage/models/tinyllama"
|
apps/ai_tutor/config/project_config.yml
CHANGED
@@ -4,4 +4,17 @@ retriever:
|
|
4 |
|
5 |
metadata:
|
6 |
metadata_links: ["https://dl4ds.github.io/sp2024/lectures/", "https://dl4ds.github.io/sp2024/schedule/"]
|
7 |
-
slide_base_link: "https://dl4ds.github.io"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
metadata:
|
6 |
metadata_links: ["https://dl4ds.github.io/sp2024/lectures/", "https://dl4ds.github.io/sp2024/schedule/"]
|
7 |
+
slide_base_link: "https://dl4ds.github.io"
|
8 |
+
|
9 |
+
token_config:
|
10 |
+
cooldown_time: 60
|
11 |
+
regen_time: 180
|
12 |
+
tokens_left: 2000
|
13 |
+
all_time_tokens_allocated: 1000000
|
14 |
+
|
15 |
+
misc:
|
16 |
+
github_repo: "https://github.com/DL4DS/dl4ds_tutor"
|
17 |
+
docs_website: "https://dl4ds.github.io/dl4ds_tutor/"
|
18 |
+
|
19 |
+
api_config:
|
20 |
+
timeout: 60
|
{modules → apps/ai_tutor}/config/prompts.py
RENAMED
File without changes
|
apps/ai_tutor/helpers.py
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime, timedelta, timezone
|
2 |
+
import tiktoken
|
3 |
+
from modules.chat_processor.helpers import update_user_info, convert_to_dict
|
4 |
+
|
5 |
+
|
6 |
+
def get_time():
|
7 |
+
return datetime.now(timezone.utc).isoformat()
|
8 |
+
|
9 |
+
|
10 |
+
async def check_user_cooldown(
|
11 |
+
user_info, current_time, COOLDOWN_TIME, TOKENS_LEFT, REGEN_TIME
|
12 |
+
):
|
13 |
+
# # Check if no tokens left
|
14 |
+
tokens_left = user_info.metadata.get("tokens_left", 0)
|
15 |
+
if tokens_left > 0 and not user_info.metadata.get("in_cooldown", False):
|
16 |
+
return False, None
|
17 |
+
|
18 |
+
user_info = convert_to_dict(user_info)
|
19 |
+
last_message_time_str = user_info["metadata"].get("last_message_time")
|
20 |
+
|
21 |
+
# Convert from ISO format string to datetime object and ensure UTC timezone
|
22 |
+
last_message_time = datetime.fromisoformat(last_message_time_str).replace(
|
23 |
+
tzinfo=timezone.utc
|
24 |
+
)
|
25 |
+
current_time = datetime.fromisoformat(current_time).replace(tzinfo=timezone.utc)
|
26 |
+
|
27 |
+
# Calculate the elapsed time
|
28 |
+
elapsed_time = current_time - last_message_time
|
29 |
+
elapsed_time_in_seconds = elapsed_time.total_seconds()
|
30 |
+
|
31 |
+
# Calculate when the cooldown period ends
|
32 |
+
cooldown_end_time = last_message_time + timedelta(seconds=COOLDOWN_TIME)
|
33 |
+
cooldown_end_time_iso = cooldown_end_time.isoformat()
|
34 |
+
|
35 |
+
# Debug: Print the cooldown end time
|
36 |
+
print(f"Cooldown end time (ISO): {cooldown_end_time_iso}")
|
37 |
+
|
38 |
+
# Check if the user is still in cooldown
|
39 |
+
if elapsed_time_in_seconds < COOLDOWN_TIME:
|
40 |
+
return True, cooldown_end_time_iso # Return in ISO 8601 format
|
41 |
+
|
42 |
+
user_info["metadata"]["in_cooldown"] = False
|
43 |
+
# If not in cooldown, regenerate tokens
|
44 |
+
await reset_tokens_for_user(user_info, TOKENS_LEFT, REGEN_TIME)
|
45 |
+
|
46 |
+
return False, None
|
47 |
+
|
48 |
+
|
49 |
+
async def reset_tokens_for_user(user_info, TOKENS_LEFT, REGEN_TIME):
|
50 |
+
user_info = convert_to_dict(user_info)
|
51 |
+
last_message_time_str = user_info["metadata"].get("last_message_time")
|
52 |
+
|
53 |
+
last_message_time = datetime.fromisoformat(last_message_time_str).replace(
|
54 |
+
tzinfo=timezone.utc
|
55 |
+
)
|
56 |
+
current_time = datetime.fromisoformat(get_time()).replace(tzinfo=timezone.utc)
|
57 |
+
|
58 |
+
# Calculate the elapsed time since the last message
|
59 |
+
elapsed_time_in_seconds = (current_time - last_message_time).total_seconds()
|
60 |
+
|
61 |
+
# Current token count (can be negative)
|
62 |
+
current_tokens = user_info["metadata"].get("tokens_left_at_last_message", 0)
|
63 |
+
current_tokens = min(current_tokens, TOKENS_LEFT)
|
64 |
+
|
65 |
+
# Maximum tokens that can be regenerated
|
66 |
+
max_tokens = user_info["metadata"].get("max_tokens", TOKENS_LEFT)
|
67 |
+
|
68 |
+
# Calculate how many tokens should have been regenerated proportionally
|
69 |
+
if current_tokens < max_tokens:
|
70 |
+
# Calculate the regeneration rate per second based on REGEN_TIME for full regeneration
|
71 |
+
regeneration_rate_per_second = max_tokens / REGEN_TIME
|
72 |
+
|
73 |
+
# Calculate how many tokens should have been regenerated based on the elapsed time
|
74 |
+
tokens_to_regenerate = int(
|
75 |
+
elapsed_time_in_seconds * regeneration_rate_per_second
|
76 |
+
)
|
77 |
+
|
78 |
+
# Ensure the new token count does not exceed max_tokens
|
79 |
+
new_token_count = min(current_tokens + tokens_to_regenerate, max_tokens)
|
80 |
+
|
81 |
+
print(
|
82 |
+
f"\n\n Adding {tokens_to_regenerate} tokens to the user, Time elapsed: {elapsed_time_in_seconds} seconds, Tokens after regeneration: {new_token_count}, Tokens before: {current_tokens} \n\n"
|
83 |
+
)
|
84 |
+
|
85 |
+
# Update the user's token count
|
86 |
+
user_info["metadata"]["tokens_left"] = new_token_count
|
87 |
+
|
88 |
+
await update_user_info(user_info)
|
89 |
+
|
90 |
+
|
91 |
+
def get_num_tokens(text, model):
|
92 |
+
encoding = tiktoken.encoding_for_model(model)
|
93 |
+
tokens = encoding.encode(text)
|
94 |
+
return len(tokens)
|
apps/ai_tutor/main.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
import chainlit.data as cl_data
|
2 |
import asyncio
|
3 |
-
from
|
4 |
LITERAL_API_KEY_LOGGING,
|
5 |
LITERAL_API_URL,
|
6 |
)
|
@@ -18,11 +18,13 @@ from modules.chat.helpers import (
|
|
18 |
)
|
19 |
from modules.chat_processor.helpers import (
|
20 |
update_user_info,
|
21 |
-
|
|
|
|
|
22 |
check_user_cooldown,
|
23 |
reset_tokens_for_user,
|
24 |
-
get_user_details,
|
25 |
)
|
|
|
26 |
import copy
|
27 |
from typing import Optional
|
28 |
from chainlit.types import ThreadDict
|
@@ -30,6 +32,7 @@ import time
|
|
30 |
import base64
|
31 |
from langchain_community.callbacks import get_openai_callback
|
32 |
from datetime import datetime, timezone
|
|
|
33 |
|
34 |
USER_TIMEOUT = 60_000
|
35 |
SYSTEM = "System"
|
@@ -38,8 +41,8 @@ AGENT = "Agent"
|
|
38 |
YOU = "User"
|
39 |
ERROR = "Error"
|
40 |
|
41 |
-
|
42 |
-
|
43 |
|
44 |
|
45 |
async def setup_data_layer():
|
@@ -81,13 +84,6 @@ class Chatbot:
|
|
81 |
"""
|
82 |
self.config = config
|
83 |
|
84 |
-
async def _load_config(self):
|
85 |
-
"""
|
86 |
-
Load the configuration from a YAML file.
|
87 |
-
"""
|
88 |
-
with open("config/config.yml", "r") as f:
|
89 |
-
return yaml.safe_load(f)
|
90 |
-
|
91 |
@no_type_check
|
92 |
async def setup_llm(self):
|
93 |
"""
|
@@ -305,7 +301,7 @@ class Chatbot:
|
|
305 |
rename_dict = {"Chatbot": LLM}
|
306 |
return rename_dict.get(orig_author, orig_author)
|
307 |
|
308 |
-
async def start(self
|
309 |
"""
|
310 |
Start the chatbot, initialize settings widgets,
|
311 |
and display and load previous conversation if chat logging is enabled.
|
@@ -313,10 +309,6 @@ class Chatbot:
|
|
313 |
|
314 |
start_time = time.time()
|
315 |
|
316 |
-
self.config = (
|
317 |
-
await self._load_config() if config is None else config
|
318 |
-
) # Reload the configuration on chat resume
|
319 |
-
|
320 |
await self.make_llm_settings_widgets(self.config) # Reload the settings widgets
|
321 |
|
322 |
user = cl.user_session.get("user")
|
@@ -386,7 +378,11 @@ class Chatbot:
|
|
386 |
|
387 |
# update user info with last message time
|
388 |
user = cl.user_session.get("user")
|
389 |
-
await reset_tokens_for_user(
|
|
|
|
|
|
|
|
|
390 |
updated_user = await get_user_details(user.identifier)
|
391 |
user.metadata = updated_user.metadata
|
392 |
cl.user_session.set("user", user)
|
@@ -524,13 +520,12 @@ class Chatbot:
|
|
524 |
+ str(tokens_left)
|
525 |
+ "</span></footer>\n"
|
526 |
)
|
527 |
-
|
528 |
await cl.Message(
|
529 |
content=answer_with_sources,
|
530 |
elements=source_elements,
|
531 |
author=LLM,
|
532 |
actions=actions,
|
533 |
-
metadata=self.config,
|
534 |
).send()
|
535 |
|
536 |
async def on_chat_resume(self, thread: ThreadDict):
|
|
|
1 |
import chainlit.data as cl_data
|
2 |
import asyncio
|
3 |
+
from config.constants import (
|
4 |
LITERAL_API_KEY_LOGGING,
|
5 |
LITERAL_API_URL,
|
6 |
)
|
|
|
18 |
)
|
19 |
from modules.chat_processor.helpers import (
|
20 |
update_user_info,
|
21 |
+
get_user_details,
|
22 |
+
)
|
23 |
+
from helpers import (
|
24 |
check_user_cooldown,
|
25 |
reset_tokens_for_user,
|
|
|
26 |
)
|
27 |
+
from helpers import get_time
|
28 |
import copy
|
29 |
from typing import Optional
|
30 |
from chainlit.types import ThreadDict
|
|
|
32 |
import base64
|
33 |
from langchain_community.callbacks import get_openai_callback
|
34 |
from datetime import datetime, timezone
|
35 |
+
from config.config_manager import config_manager
|
36 |
|
37 |
USER_TIMEOUT = 60_000
|
38 |
SYSTEM = "System"
|
|
|
41 |
YOU = "User"
|
42 |
ERROR = "Error"
|
43 |
|
44 |
+
# set config
|
45 |
+
config = config_manager.get_config().dict()
|
46 |
|
47 |
|
48 |
async def setup_data_layer():
|
|
|
84 |
"""
|
85 |
self.config = config
|
86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
@no_type_check
|
88 |
async def setup_llm(self):
|
89 |
"""
|
|
|
301 |
rename_dict = {"Chatbot": LLM}
|
302 |
return rename_dict.get(orig_author, orig_author)
|
303 |
|
304 |
+
async def start(self):
|
305 |
"""
|
306 |
Start the chatbot, initialize settings widgets,
|
307 |
and display and load previous conversation if chat logging is enabled.
|
|
|
309 |
|
310 |
start_time = time.time()
|
311 |
|
|
|
|
|
|
|
|
|
312 |
await self.make_llm_settings_widgets(self.config) # Reload the settings widgets
|
313 |
|
314 |
user = cl.user_session.get("user")
|
|
|
378 |
|
379 |
# update user info with last message time
|
380 |
user = cl.user_session.get("user")
|
381 |
+
await reset_tokens_for_user(
|
382 |
+
user,
|
383 |
+
self.config["token_config"]["tokens_left"],
|
384 |
+
self.config["token_config"]["regen_time"],
|
385 |
+
)
|
386 |
updated_user = await get_user_details(user.identifier)
|
387 |
user.metadata = updated_user.metadata
|
388 |
cl.user_session.set("user", user)
|
|
|
520 |
+ str(tokens_left)
|
521 |
+ "</span></footer>\n"
|
522 |
)
|
523 |
+
|
524 |
await cl.Message(
|
525 |
content=answer_with_sources,
|
526 |
elements=source_elements,
|
527 |
author=LLM,
|
528 |
actions=actions,
|
|
|
529 |
).send()
|
530 |
|
531 |
async def on_chat_resume(self, thread: ThreadDict):
|
apps/chainlit_base/chainlit_base.py
CHANGED
@@ -11,9 +11,9 @@ from modules.chat.helpers import (
|
|
11 |
get_last_config,
|
12 |
)
|
13 |
import copy
|
14 |
-
from chainlit.types import ThreadDict
|
15 |
import time
|
16 |
from langchain_community.callbacks import get_openai_callback
|
|
|
17 |
|
18 |
USER_TIMEOUT = 60_000
|
19 |
SYSTEM = "System"
|
@@ -22,23 +22,7 @@ AGENT = "Agent"
|
|
22 |
YOU = "User"
|
23 |
ERROR = "Error"
|
24 |
|
25 |
-
|
26 |
-
config = yaml.safe_load(f)
|
27 |
-
|
28 |
-
|
29 |
-
# async def setup_data_layer():
|
30 |
-
# """
|
31 |
-
# Set up the data layer for chat logging.
|
32 |
-
# """
|
33 |
-
# if config["chat_logging"]["log_chat"]:
|
34 |
-
# data_layer = CustomLiteralDataLayer(
|
35 |
-
# api_key=LITERAL_API_KEY_LOGGING, server=LITERAL_API_URL
|
36 |
-
# )
|
37 |
-
# else:
|
38 |
-
# data_layer = None
|
39 |
-
|
40 |
-
# return data_layer
|
41 |
-
|
42 |
|
43 |
class Chatbot:
|
44 |
def __init__(self, config):
|
@@ -47,13 +31,6 @@ class Chatbot:
|
|
47 |
"""
|
48 |
self.config = config
|
49 |
|
50 |
-
async def _load_config(self):
|
51 |
-
"""
|
52 |
-
Load the configuration from a YAML file.
|
53 |
-
"""
|
54 |
-
with open("config/config.yml", "r") as f:
|
55 |
-
return yaml.safe_load(f)
|
56 |
-
|
57 |
@no_type_check
|
58 |
async def setup_llm(self):
|
59 |
"""
|
@@ -225,38 +202,29 @@ class Chatbot:
|
|
225 |
"""
|
226 |
Set starter messages for the chatbot.
|
227 |
"""
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
message="When is the final project due?",
|
252 |
-
icon="/public/calendar-samsung-17-svgrepo-com.svg",
|
253 |
-
),
|
254 |
-
cl.Starter(
|
255 |
-
label="Explain backprop.",
|
256 |
-
message="I didn't understand the math behind backprop, could you explain it?",
|
257 |
-
icon="/public/acastusphoton-svgrepo-com.svg",
|
258 |
-
),
|
259 |
-
]
|
260 |
|
261 |
def rename(self, orig_author: str):
|
262 |
"""
|
@@ -271,7 +239,7 @@ class Chatbot:
|
|
271 |
rename_dict = {"Chatbot": LLM}
|
272 |
return rename_dict.get(orig_author, orig_author)
|
273 |
|
274 |
-
async def start(self
|
275 |
"""
|
276 |
Start the chatbot, initialize settings widgets,
|
277 |
and display and load previous conversation if chat logging is enabled.
|
@@ -279,26 +247,15 @@ class Chatbot:
|
|
279 |
|
280 |
start_time = time.time()
|
281 |
|
282 |
-
self.config = (
|
283 |
-
await self._load_config() if config is None else config
|
284 |
-
) # Reload the configuration on chat resume
|
285 |
-
|
286 |
await self.make_llm_settings_widgets(self.config) # Reload the settings widgets
|
287 |
|
288 |
user = cl.user_session.get("user")
|
289 |
|
290 |
# TODO: remove self.user with cl.user_session.get("user")
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
}
|
296 |
-
except Exception as e:
|
297 |
-
print(e)
|
298 |
-
self.user = {
|
299 |
-
"user_id": "guest",
|
300 |
-
"session_id": cl.context.session.thread_id,
|
301 |
-
}
|
302 |
|
303 |
memory = cl.user_session.get("memory", [])
|
304 |
self.llm_tutor = LLMTutor(self.config, user=self.user)
|
@@ -432,22 +389,8 @@ class Chatbot:
|
|
432 |
elements=source_elements,
|
433 |
author=LLM,
|
434 |
actions=actions,
|
435 |
-
metadata=self.config,
|
436 |
).send()
|
437 |
|
438 |
-
async def on_chat_resume(self, thread: ThreadDict):
|
439 |
-
thread_config = None
|
440 |
-
steps = thread["steps"]
|
441 |
-
k = self.config["llm_params"][
|
442 |
-
"memory_window"
|
443 |
-
] # on resume, alwyas use the default memory window
|
444 |
-
conversation_list = get_history_chat_resume(steps, k, SYSTEM, LLM)
|
445 |
-
thread_config = get_last_config(
|
446 |
-
steps
|
447 |
-
) # TODO: Returns None for now - which causes config to be reloaded with default values
|
448 |
-
cl.user_session.set("memory", conversation_list)
|
449 |
-
await self.start(config=thread_config)
|
450 |
-
|
451 |
async def on_follow_up(self, action: cl.Action):
|
452 |
user = cl.user_session.get("user")
|
453 |
message = await cl.Message(
|
@@ -466,12 +409,9 @@ chatbot = Chatbot(config=config)
|
|
466 |
|
467 |
|
468 |
async def start_app():
|
469 |
-
# cl_data._data_layer = await setup_data_layer()
|
470 |
-
# chatbot.literal_client = cl_data._data_layer.client if cl_data._data_layer else None
|
471 |
cl.set_starters(chatbot.set_starters)
|
472 |
cl.author_rename(chatbot.rename)
|
473 |
cl.on_chat_start(chatbot.start)
|
474 |
-
cl.on_chat_resume(chatbot.on_chat_resume)
|
475 |
cl.on_message(chatbot.main)
|
476 |
cl.on_settings_update(chatbot.update_llm)
|
477 |
cl.action_callback("follow up question")(chatbot.on_follow_up)
|
|
|
11 |
get_last_config,
|
12 |
)
|
13 |
import copy
|
|
|
14 |
import time
|
15 |
from langchain_community.callbacks import get_openai_callback
|
16 |
+
from config.config_manager import config_manager
|
17 |
|
18 |
USER_TIMEOUT = 60_000
|
19 |
SYSTEM = "System"
|
|
|
22 |
YOU = "User"
|
23 |
ERROR = "Error"
|
24 |
|
25 |
+
config = config_manager.get_config().dict()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
|
27 |
class Chatbot:
|
28 |
def __init__(self, config):
|
|
|
31 |
"""
|
32 |
self.config = config
|
33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
@no_type_check
|
35 |
async def setup_llm(self):
|
36 |
"""
|
|
|
202 |
"""
|
203 |
Set starter messages for the chatbot.
|
204 |
"""
|
205 |
+
|
206 |
+
return [
|
207 |
+
cl.Starter(
|
208 |
+
label="recording on CNNs?",
|
209 |
+
message="Where can I find the recording for the lecture on Transformers?",
|
210 |
+
icon="/public/adv-screen-recorder-svgrepo-com.svg",
|
211 |
+
),
|
212 |
+
cl.Starter(
|
213 |
+
label="where's the slides?",
|
214 |
+
message="When are the lectures? I can't find the schedule.",
|
215 |
+
icon="/public/alarmy-svgrepo-com.svg",
|
216 |
+
),
|
217 |
+
cl.Starter(
|
218 |
+
label="Due Date?",
|
219 |
+
message="When is the final project due?",
|
220 |
+
icon="/public/calendar-samsung-17-svgrepo-com.svg",
|
221 |
+
),
|
222 |
+
cl.Starter(
|
223 |
+
label="Explain backprop.",
|
224 |
+
message="I didn't understand the math behind backprop, could you explain it?",
|
225 |
+
icon="/public/acastusphoton-svgrepo-com.svg",
|
226 |
+
),
|
227 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
228 |
|
229 |
def rename(self, orig_author: str):
|
230 |
"""
|
|
|
239 |
rename_dict = {"Chatbot": LLM}
|
240 |
return rename_dict.get(orig_author, orig_author)
|
241 |
|
242 |
+
async def start(self):
|
243 |
"""
|
244 |
Start the chatbot, initialize settings widgets,
|
245 |
and display and load previous conversation if chat logging is enabled.
|
|
|
247 |
|
248 |
start_time = time.time()
|
249 |
|
|
|
|
|
|
|
|
|
250 |
await self.make_llm_settings_widgets(self.config) # Reload the settings widgets
|
251 |
|
252 |
user = cl.user_session.get("user")
|
253 |
|
254 |
# TODO: remove self.user with cl.user_session.get("user")
|
255 |
+
self.user = {
|
256 |
+
"user_id": "guest",
|
257 |
+
"session_id": cl.context.session.thread_id,
|
258 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
259 |
|
260 |
memory = cl.user_session.get("memory", [])
|
261 |
self.llm_tutor = LLMTutor(self.config, user=self.user)
|
|
|
389 |
elements=source_elements,
|
390 |
author=LLM,
|
391 |
actions=actions,
|
|
|
392 |
).send()
|
393 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
394 |
async def on_follow_up(self, action: cl.Action):
|
395 |
user = cl.user_session.get("user")
|
396 |
message = await cl.Message(
|
|
|
409 |
|
410 |
|
411 |
async def start_app():
|
|
|
|
|
412 |
cl.set_starters(chatbot.set_starters)
|
413 |
cl.author_rename(chatbot.rename)
|
414 |
cl.on_chat_start(chatbot.start)
|
|
|
415 |
cl.on_message(chatbot.main)
|
416 |
cl.on_settings_update(chatbot.update_llm)
|
417 |
cl.action_callback("follow up question")(chatbot.on_follow_up)
|
apps/chainlit_base/config/config_manager.py
ADDED
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel, Field, conint, confloat, conlist, HttpUrl
|
2 |
+
from typing import Optional, List
|
3 |
+
import yaml
|
4 |
+
|
5 |
+
|
6 |
+
class FaissParams(BaseModel):
|
7 |
+
index_path: str = "vectorstores/faiss.index"
|
8 |
+
index_type: str = "Flat" # Options: [Flat, HNSW, IVF]
|
9 |
+
index_dimension: conint(gt=0) = 384
|
10 |
+
index_nlist: conint(gt=0) = 100
|
11 |
+
index_nprobe: conint(gt=0) = 10
|
12 |
+
|
13 |
+
|
14 |
+
class ColbertParams(BaseModel):
|
15 |
+
index_name: str = "new_idx"
|
16 |
+
|
17 |
+
|
18 |
+
class VectorStoreConfig(BaseModel):
|
19 |
+
load_from_HF: bool = True
|
20 |
+
reparse_files: bool = True
|
21 |
+
data_path: str = "storage/data"
|
22 |
+
url_file_path: str = "storage/data/urls.txt"
|
23 |
+
expand_urls: bool = True
|
24 |
+
db_option: str = "RAGatouille" # Options: [FAISS, Chroma, RAGatouille, RAPTOR]
|
25 |
+
db_path: str = "vectorstores"
|
26 |
+
model: str = (
|
27 |
+
"sentence-transformers/all-MiniLM-L6-v2" # Options: [sentence-transformers/all-MiniLM-L6-v2, text-embedding-ada-002]
|
28 |
+
)
|
29 |
+
search_top_k: conint(gt=0) = 3
|
30 |
+
score_threshold: confloat(ge=0.0, le=1.0) = 0.2
|
31 |
+
|
32 |
+
faiss_params: Optional[FaissParams] = None
|
33 |
+
colbert_params: Optional[ColbertParams] = None
|
34 |
+
|
35 |
+
|
36 |
+
class OpenAIParams(BaseModel):
|
37 |
+
temperature: confloat(ge=0.0, le=1.0) = 0.7
|
38 |
+
|
39 |
+
|
40 |
+
class LocalLLMParams(BaseModel):
|
41 |
+
temperature: confloat(ge=0.0, le=1.0) = 0.7
|
42 |
+
repo_id: str = "TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF" # HuggingFace repo id
|
43 |
+
filename: str = (
|
44 |
+
"tinyllama-1.1b-chat-v1.0.Q5_0.gguf" # Specific name of gguf file in the repo
|
45 |
+
)
|
46 |
+
model_path: str = (
|
47 |
+
"storage/models/tinyllama-1.1b-chat-v1.0.Q5_0.gguf" # Path to the model file
|
48 |
+
)
|
49 |
+
|
50 |
+
|
51 |
+
class LLMParams(BaseModel):
|
52 |
+
llm_arch: str = "langchain" # Options: [langchain]
|
53 |
+
use_history: bool = True
|
54 |
+
generate_follow_up: bool = False
|
55 |
+
memory_window: conint(ge=1) = 3
|
56 |
+
llm_style: str = "Normal" # Options: [Normal, ELI5]
|
57 |
+
llm_loader: str = (
|
58 |
+
"gpt-4o-mini" # Options: [local_llm, gpt-3.5-turbo-1106, gpt-4, gpt-4o-mini]
|
59 |
+
)
|
60 |
+
openai_params: Optional[OpenAIParams] = None
|
61 |
+
local_llm_params: Optional[LocalLLMParams] = None
|
62 |
+
stream: bool = False
|
63 |
+
pdf_reader: str = "gpt" # Options: [llama, pymupdf, gpt]
|
64 |
+
|
65 |
+
|
66 |
+
class ChatLoggingConfig(BaseModel):
|
67 |
+
log_chat: bool = True
|
68 |
+
platform: str = "literalai"
|
69 |
+
callbacks: bool = True
|
70 |
+
|
71 |
+
|
72 |
+
class SplitterOptions(BaseModel):
|
73 |
+
use_splitter: bool = True
|
74 |
+
split_by_token: bool = True
|
75 |
+
remove_leftover_delimiters: bool = True
|
76 |
+
remove_chunks: bool = False
|
77 |
+
chunking_mode: str = "semantic" # Options: [fixed, semantic]
|
78 |
+
chunk_size: conint(gt=0) = 300
|
79 |
+
chunk_overlap: conint(ge=0) = 30
|
80 |
+
chunk_separators: List[str] = ["\n\n", "\n", " ", ""]
|
81 |
+
front_chunks_to_remove: Optional[conint(ge=0)] = None
|
82 |
+
last_chunks_to_remove: Optional[conint(ge=0)] = None
|
83 |
+
delimiters_to_remove: List[str] = ["\t", "\n", " ", " "]
|
84 |
+
|
85 |
+
|
86 |
+
class RetrieverConfig(BaseModel):
|
87 |
+
retriever_hf_paths: dict[str, str] = {"RAGatouille": "XThomasBU/Colbert_Index"}
|
88 |
+
|
89 |
+
|
90 |
+
class MetadataConfig(BaseModel):
|
91 |
+
metadata_links: List[HttpUrl] = [
|
92 |
+
"https://dl4ds.github.io/sp2024/lectures/",
|
93 |
+
"https://dl4ds.github.io/sp2024/schedule/",
|
94 |
+
]
|
95 |
+
slide_base_link: HttpUrl = "https://dl4ds.github.io"
|
96 |
+
|
97 |
+
|
98 |
+
class APIConfig(BaseModel):
|
99 |
+
timeout: conint(gt=0) = 60
|
100 |
+
|
101 |
+
|
102 |
+
class Config(BaseModel):
|
103 |
+
log_dir: str = "storage/logs"
|
104 |
+
log_chunk_dir: str = "storage/logs/chunks"
|
105 |
+
device: str = "cpu" # Options: ['cuda', 'cpu']
|
106 |
+
|
107 |
+
vectorstore: VectorStoreConfig
|
108 |
+
llm_params: LLMParams
|
109 |
+
chat_logging: ChatLoggingConfig
|
110 |
+
splitter_options: SplitterOptions
|
111 |
+
retriever: RetrieverConfig
|
112 |
+
metadata: MetadataConfig
|
113 |
+
api_config: APIConfig
|
114 |
+
|
115 |
+
|
116 |
+
class ConfigManager:
|
117 |
+
def __init__(self, config_path: str, project_config_path: str):
|
118 |
+
self.config_path = config_path
|
119 |
+
self.project_config_path = project_config_path
|
120 |
+
self.config = self.load_config()
|
121 |
+
self.validate_config()
|
122 |
+
|
123 |
+
def load_config(self) -> Config:
|
124 |
+
with open(self.config_path, "r") as f:
|
125 |
+
config_data = yaml.safe_load(f)
|
126 |
+
|
127 |
+
with open(self.project_config_path, "r") as f:
|
128 |
+
project_config_data = yaml.safe_load(f)
|
129 |
+
|
130 |
+
# Merge the two configurations
|
131 |
+
merged_config = {**config_data, **project_config_data}
|
132 |
+
|
133 |
+
return Config(**merged_config)
|
134 |
+
|
135 |
+
def get_config(self) -> Config:
|
136 |
+
return ConfigWrapper(self.config)
|
137 |
+
|
138 |
+
def validate_config(self):
|
139 |
+
# If any required fields are missing, raise an error
|
140 |
+
# required_fields = [
|
141 |
+
# "vectorstore", "llm_params", "chat_logging", "splitter_options",
|
142 |
+
# "retriever", "metadata", "token_config", "misc", "api_config"
|
143 |
+
# ]
|
144 |
+
# for field in required_fields:
|
145 |
+
# if not hasattr(self.config, field):
|
146 |
+
# raise ValueError(f"Missing required configuration field: {field}")
|
147 |
+
|
148 |
+
# # Validate types of specific fields
|
149 |
+
# if not isinstance(self.config.vectorstore, VectorStoreConfig):
|
150 |
+
# raise TypeError("vectorstore must be an instance of VectorStoreConfig")
|
151 |
+
# if not isinstance(self.config.llm_params, LLMParams):
|
152 |
+
# raise TypeError("llm_params must be an instance of LLMParams")
|
153 |
+
pass
|
154 |
+
|
155 |
+
|
156 |
+
class ConfigWrapper:
|
157 |
+
def __init__(self, config: Config):
|
158 |
+
self._config = config
|
159 |
+
|
160 |
+
def __getitem__(self, key):
|
161 |
+
return getattr(self._config, key)
|
162 |
+
|
163 |
+
def __getattr__(self, name):
|
164 |
+
return getattr(self._config, name)
|
165 |
+
|
166 |
+
def dict(self):
|
167 |
+
return self._config.dict()
|
168 |
+
|
169 |
+
|
170 |
+
# Usage
|
171 |
+
config_manager = ConfigManager(
|
172 |
+
config_path="config/config.yml", project_config_path="config/project_config.yml"
|
173 |
+
)
|
174 |
+
# config = config_manager.get_config().dict()
|
apps/chainlit_base/config/project_config.yml
CHANGED
@@ -4,4 +4,7 @@ retriever:
|
|
4 |
|
5 |
metadata:
|
6 |
metadata_links: ["https://dl4ds.github.io/sp2024/lectures/", "https://dl4ds.github.io/sp2024/schedule/"]
|
7 |
-
slide_base_link: "https://dl4ds.github.io"
|
|
|
|
|
|
|
|
4 |
|
5 |
metadata:
|
6 |
metadata_links: ["https://dl4ds.github.io/sp2024/lectures/", "https://dl4ds.github.io/sp2024/schedule/"]
|
7 |
+
slide_base_link: "https://dl4ds.github.io"
|
8 |
+
|
9 |
+
api_config:
|
10 |
+
timeout: 60
|
apps/chainlit_base/config/prompts.py
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
prompts = {
|
2 |
+
"openai": {
|
3 |
+
"rephrase_prompt": (
|
4 |
+
"You are someone that rephrases statements. Rephrase the student's question to add context from their chat history if relevant, ensuring it remains from the student's point of view. "
|
5 |
+
"Incorporate relevant details from the chat history to make the question clearer and more specific. "
|
6 |
+
"Do not change the meaning of the original statement, and maintain the student's tone and perspective. "
|
7 |
+
"If the question is conversational and doesn't require context, do not rephrase it. "
|
8 |
+
"Example: If the student previously asked about backpropagation in the context of deep learning and now asks 'what is it', rephrase to 'What is backpropagation.'. "
|
9 |
+
"Example: Do not rephrase if the user is asking something specific like 'cool, suggest a project with transformers to use as my final project' "
|
10 |
+
"Chat history: \n{chat_history}\n"
|
11 |
+
"Rephrase the following question only if necessary: '{input}'"
|
12 |
+
"Rephrased Question:'"
|
13 |
+
),
|
14 |
+
"prompt_with_history": {
|
15 |
+
"normal": (
|
16 |
+
"You are an AI Tutor for the course DS598, taught by Prof. Thomas Gardos. Answer the user's question using the provided context. Only use the context if it is relevant. The context is ordered by relevance. "
|
17 |
+
"If you don't know the answer, do your best without making things up. Keep the conversation flowing naturally. "
|
18 |
+
"Use chat history and context as guides but avoid repeating past responses. Provide links from the source_file metadata. Use the source context that is most relevant. "
|
19 |
+
"Render math equations in LaTeX format between $ or $$ signs, stick to the parameter and variable icons found in your context. Be sure to explain the parameters and variables in the equations."
|
20 |
+
"Speak in a friendly and engaging manner, like talking to a friend. Avoid sounding repetitive or robotic.\n\n"
|
21 |
+
"Do not get influenced by the style of conversation in the chat history. Follow the instructions given here."
|
22 |
+
"Chat History:\n{chat_history}\n\n"
|
23 |
+
"Context:\n{context}\n\n"
|
24 |
+
"Answer the student's question below in a friendly, concise, and engaging manner. Use the context and history only if relevant, otherwise, engage in a free-flowing conversation.\n"
|
25 |
+
"Student: {input}\n"
|
26 |
+
"AI Tutor:"
|
27 |
+
),
|
28 |
+
"eli5": (
|
29 |
+
"You are an AI Tutor for the course DS598, taught by Prof. Thomas Gardos. Your job is to explain things in the simplest and most engaging way possible, just like the 'Explain Like I'm 5' (ELI5) concept."
|
30 |
+
"If you don't know the answer, do your best without making things up. Keep your explanations straightforward and very easy to understand."
|
31 |
+
"Use the chat history and context to help you, but avoid repeating past responses. Provide links from the source_file metadata when they're helpful."
|
32 |
+
"Use very simple language and examples to explain any math equations, and put the equations in LaTeX format between $ or $$ signs."
|
33 |
+
"Be friendly and engaging, like you're chatting with a young child who's curious and eager to learn. Avoid complex terms and jargon."
|
34 |
+
"Include simple and clear examples wherever you can to make things easier to understand."
|
35 |
+
"Do not get influenced by the style of conversation in the chat history. Follow the instructions given here."
|
36 |
+
"Chat History:\n{chat_history}\n\n"
|
37 |
+
"Context:\n{context}\n\n"
|
38 |
+
"Answer the student's question below in a friendly, simple, and engaging way, just like the ELI5 concept. Use the context and history only if they're relevant, otherwise, just have a natural conversation."
|
39 |
+
"Give a clear and detailed explanation with simple examples to make it easier to understand. Remember, your goal is to break down complex topics into very simple terms, just like ELI5."
|
40 |
+
"Student: {input}\n"
|
41 |
+
"AI Tutor:"
|
42 |
+
),
|
43 |
+
"socratic": (
|
44 |
+
"You are an AI Tutor for the course DS598, taught by Prof. Thomas Gardos. Engage the student in a Socratic dialogue to help them discover answers on their own. Use the provided context to guide your questioning."
|
45 |
+
"If you don't know the answer, do your best without making things up. Keep the conversation engaging and inquisitive."
|
46 |
+
"Use chat history and context as guides but avoid repeating past responses. Provide links from the source_file metadata when relevant. Use the source context that is most relevant."
|
47 |
+
"Speak in a friendly and engaging manner, encouraging critical thinking and self-discovery."
|
48 |
+
"Use questions to lead the student to explore the topic and uncover answers."
|
49 |
+
"Chat History:\n{chat_history}\n\n"
|
50 |
+
"Context:\n{context}\n\n"
|
51 |
+
"Answer the student's question below by guiding them through a series of questions and insights that lead to deeper understanding. Use the context and history only if relevant, otherwise, engage in a free-flowing conversation."
|
52 |
+
"Foster an inquisitive mindset and help the student discover answers through dialogue."
|
53 |
+
"Student: {input}\n"
|
54 |
+
"AI Tutor:"
|
55 |
+
),
|
56 |
+
},
|
57 |
+
"prompt_no_history": (
|
58 |
+
"You are an AI Tutor for the course DS598, taught by Prof. Thomas Gardos. Answer the user's question using the provided context. Only use the context if it is relevant. The context is ordered by relevance. "
|
59 |
+
"If you don't know the answer, do your best without making things up. Keep the conversation flowing naturally. "
|
60 |
+
"Provide links from the source_file metadata. Use the source context that is most relevant. "
|
61 |
+
"Speak in a friendly and engaging manner, like talking to a friend. Avoid sounding repetitive or robotic.\n\n"
|
62 |
+
"Context:\n{context}\n\n"
|
63 |
+
"Answer the student's question below in a friendly, concise, and engaging manner. Use the context and history only if relevant, otherwise, engage in a free-flowing conversation.\n"
|
64 |
+
"Student: {input}\n"
|
65 |
+
"AI Tutor:"
|
66 |
+
),
|
67 |
+
},
|
68 |
+
"tiny_llama": {
|
69 |
+
"prompt_no_history": (
|
70 |
+
"system\n"
|
71 |
+
"Assistant is an intelligent chatbot designed to help students with questions regarding the course DS598, taught by Prof. Thomas Gardos. Answer the user's question using the provided context. Only use the context if it is relevant. The context is ordered by relevance.\n"
|
72 |
+
"If you don't know the answer, do your best without making things up. Keep the conversation flowing naturally.\n"
|
73 |
+
"Provide links from the source_file metadata. Use the source context that is most relevant.\n"
|
74 |
+
"Speak in a friendly and engaging manner, like talking to a friend. Avoid sounding repetitive or robotic.\n"
|
75 |
+
"\n\n"
|
76 |
+
"user\n"
|
77 |
+
"Context:\n{context}\n\n"
|
78 |
+
"Question: {input}\n"
|
79 |
+
"\n\n"
|
80 |
+
"assistant"
|
81 |
+
),
|
82 |
+
"prompt_with_history": (
|
83 |
+
"system\n"
|
84 |
+
"You are an AI Tutor for the course DS598, taught by Prof. Thomas Gardos. Answer the user's question using the provided context. Only use the context if it is relevant. The context is ordered by relevance. "
|
85 |
+
"If you don't know the answer, do your best without making things up. Keep the conversation flowing naturally. "
|
86 |
+
"Use chat history and context as guides but avoid repeating past responses. Provide links from the source_file metadata. Use the source context that is most relevant. "
|
87 |
+
"Speak in a friendly and engaging manner, like talking to a friend. Avoid sounding repetitive or robotic.\n"
|
88 |
+
"\n\n"
|
89 |
+
"user\n"
|
90 |
+
"Chat History:\n{chat_history}\n\n"
|
91 |
+
"Context:\n{context}\n\n"
|
92 |
+
"Question: {input}\n"
|
93 |
+
"\n\n"
|
94 |
+
"assistant"
|
95 |
+
),
|
96 |
+
},
|
97 |
+
}
|
modules/chat/helpers.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
from
|
2 |
import chainlit as cl
|
3 |
|
4 |
|
|
|
1 |
+
from config.prompts import prompts
|
2 |
import chainlit as cl
|
3 |
|
4 |
|
modules/chat_processor/helpers.py
CHANGED
@@ -1,9 +1,6 @@
|
|
1 |
import os
|
2 |
from literalai import AsyncLiteralClient
|
3 |
-
from datetime import datetime, timedelta, timezone
|
4 |
-
from modules.config.constants import COOLDOWN_TIME, TOKENS_LEFT, REGEN_TIME
|
5 |
from typing_extensions import TypedDict
|
6 |
-
import tiktoken
|
7 |
from typing import Any, Generic, List, Literal, Optional, TypeVar, Union
|
8 |
|
9 |
Field = TypeVar("Field")
|
@@ -136,10 +133,6 @@ def convert_to_dict(user_info):
|
|
136 |
return user_info
|
137 |
|
138 |
|
139 |
-
def get_time():
|
140 |
-
return datetime.now(timezone.utc).isoformat()
|
141 |
-
|
142 |
-
|
143 |
async def get_user_details(user_email_id):
|
144 |
user_info = await literal_client.api.get_or_create_user(identifier=user_email_id)
|
145 |
return user_info
|
@@ -155,91 +148,6 @@ async def update_user_info(user_info):
|
|
155 |
)
|
156 |
|
157 |
|
158 |
-
async def check_user_cooldown(user_info, current_time):
|
159 |
-
# # Check if no tokens left
|
160 |
-
tokens_left = user_info.metadata.get("tokens_left", 0)
|
161 |
-
if tokens_left > 0 and not user_info.metadata.get("in_cooldown", False):
|
162 |
-
return False, None
|
163 |
-
|
164 |
-
user_info = convert_to_dict(user_info)
|
165 |
-
last_message_time_str = user_info["metadata"].get("last_message_time")
|
166 |
-
|
167 |
-
# Convert from ISO format string to datetime object and ensure UTC timezone
|
168 |
-
last_message_time = datetime.fromisoformat(last_message_time_str).replace(
|
169 |
-
tzinfo=timezone.utc
|
170 |
-
)
|
171 |
-
current_time = datetime.fromisoformat(current_time).replace(tzinfo=timezone.utc)
|
172 |
-
|
173 |
-
# Calculate the elapsed time
|
174 |
-
elapsed_time = current_time - last_message_time
|
175 |
-
elapsed_time_in_seconds = elapsed_time.total_seconds()
|
176 |
-
|
177 |
-
# Calculate when the cooldown period ends
|
178 |
-
cooldown_end_time = last_message_time + timedelta(seconds=COOLDOWN_TIME)
|
179 |
-
cooldown_end_time_iso = cooldown_end_time.isoformat()
|
180 |
-
|
181 |
-
# Debug: Print the cooldown end time
|
182 |
-
print(f"Cooldown end time (ISO): {cooldown_end_time_iso}")
|
183 |
-
|
184 |
-
# Check if the user is still in cooldown
|
185 |
-
if elapsed_time_in_seconds < COOLDOWN_TIME:
|
186 |
-
return True, cooldown_end_time_iso # Return in ISO 8601 format
|
187 |
-
|
188 |
-
user_info["metadata"]["in_cooldown"] = False
|
189 |
-
# If not in cooldown, regenerate tokens
|
190 |
-
await reset_tokens_for_user(user_info)
|
191 |
-
|
192 |
-
return False, None
|
193 |
-
|
194 |
-
|
195 |
-
async def reset_tokens_for_user(user_info):
|
196 |
-
user_info = convert_to_dict(user_info)
|
197 |
-
last_message_time_str = user_info["metadata"].get("last_message_time")
|
198 |
-
|
199 |
-
last_message_time = datetime.fromisoformat(last_message_time_str).replace(
|
200 |
-
tzinfo=timezone.utc
|
201 |
-
)
|
202 |
-
current_time = datetime.fromisoformat(get_time()).replace(tzinfo=timezone.utc)
|
203 |
-
|
204 |
-
# Calculate the elapsed time since the last message
|
205 |
-
elapsed_time_in_seconds = (current_time - last_message_time).total_seconds()
|
206 |
-
|
207 |
-
# Current token count (can be negative)
|
208 |
-
current_tokens = user_info["metadata"].get("tokens_left_at_last_message", 0)
|
209 |
-
current_tokens = min(current_tokens, TOKENS_LEFT)
|
210 |
-
|
211 |
-
# Maximum tokens that can be regenerated
|
212 |
-
max_tokens = user_info["metadata"].get("max_tokens", TOKENS_LEFT)
|
213 |
-
|
214 |
-
# Calculate how many tokens should have been regenerated proportionally
|
215 |
-
if current_tokens < max_tokens:
|
216 |
-
# Calculate the regeneration rate per second based on REGEN_TIME for full regeneration
|
217 |
-
regeneration_rate_per_second = max_tokens / REGEN_TIME
|
218 |
-
|
219 |
-
# Calculate how many tokens should have been regenerated based on the elapsed time
|
220 |
-
tokens_to_regenerate = int(
|
221 |
-
elapsed_time_in_seconds * regeneration_rate_per_second
|
222 |
-
)
|
223 |
-
|
224 |
-
# Ensure the new token count does not exceed max_tokens
|
225 |
-
new_token_count = min(current_tokens + tokens_to_regenerate, max_tokens)
|
226 |
-
|
227 |
-
print(
|
228 |
-
f"\n\n Adding {tokens_to_regenerate} tokens to the user, Time elapsed: {elapsed_time_in_seconds} seconds, Tokens after regeneration: {new_token_count}, Tokens before: {current_tokens} \n\n"
|
229 |
-
)
|
230 |
-
|
231 |
-
# Update the user's token count
|
232 |
-
user_info["metadata"]["tokens_left"] = new_token_count
|
233 |
-
|
234 |
-
await update_user_info(user_info)
|
235 |
-
|
236 |
-
|
237 |
async def get_thread_step_info(thread_id):
|
238 |
step = await literal_client.api.get_step(thread_id)
|
239 |
return step
|
240 |
-
|
241 |
-
|
242 |
-
def get_num_tokens(text, model):
|
243 |
-
encoding = tiktoken.encoding_for_model(model)
|
244 |
-
tokens = encoding.encode(text)
|
245 |
-
return len(tokens)
|
|
|
1 |
import os
|
2 |
from literalai import AsyncLiteralClient
|
|
|
|
|
3 |
from typing_extensions import TypedDict
|
|
|
4 |
from typing import Any, Generic, List, Literal, Optional, TypeVar, Union
|
5 |
|
6 |
Field = TypeVar("Field")
|
|
|
133 |
return user_info
|
134 |
|
135 |
|
|
|
|
|
|
|
|
|
136 |
async def get_user_details(user_email_id):
|
137 |
user_info = await literal_client.api.get_or_create_user(identifier=user_email_id)
|
138 |
return user_info
|
|
|
148 |
)
|
149 |
|
150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
async def get_thread_step_info(thread_id):
|
152 |
step = await literal_client.api.get_step(thread_id)
|
153 |
return step
|
|
|
|
|
|
|
|
|
|
|
|
modules/config/config_manager.py
DELETED
File without changes
|
modules/config/constants.py
CHANGED
@@ -1,35 +1,12 @@
|
|
1 |
-
from
|
|
|
2 |
import os
|
|
|
3 |
|
4 |
load_dotenv()
|
5 |
|
6 |
-
|
7 |
-
|
8 |
-
REGEN_TIME = 180
|
9 |
-
TOKENS_LEFT = 2000
|
10 |
-
ALL_TIME_TOKENS_ALLOCATED = 1000000
|
11 |
-
|
12 |
-
GITHUB_REPO = "https://github.com/DL4DS/dl4ds_tutor"
|
13 |
-
DOCS_WEBSITE = "https://dl4ds.github.io/dl4ds_tutor/"
|
14 |
-
|
15 |
-
# API Keys - Loaded from the .env file
|
16 |
-
|
17 |
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
18 |
LLAMA_CLOUD_API_KEY = os.getenv("LLAMA_CLOUD_API_KEY")
|
19 |
HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_TOKEN")
|
20 |
-
LITERAL_API_KEY_LOGGING = os.getenv("LITERAL_API_KEY_LOGGING")
|
21 |
-
LITERAL_API_URL = os.getenv("LITERAL_API_URL")
|
22 |
-
CHAINLIT_URL = os.getenv("CHAINLIT_URL")
|
23 |
-
EMAIL_ENCRYPTION_KEY = os.getenv("EMAIL_ENCRYPTION_KEY")
|
24 |
-
|
25 |
-
OAUTH_GOOGLE_CLIENT_ID = os.getenv("OAUTH_GOOGLE_CLIENT_ID")
|
26 |
-
OAUTH_GOOGLE_CLIENT_SECRET = os.getenv("OAUTH_GOOGLE_CLIENT_SECRET")
|
27 |
-
|
28 |
-
opening_message = "Hey, What Can I Help You With?\n\nYou can me ask me questions about the course logistics, course content, about the final project, or anything else!"
|
29 |
-
chat_end_message = (
|
30 |
-
"I hope I was able to help you. If you have any more questions, feel free to ask!"
|
31 |
-
)
|
32 |
-
|
33 |
-
# Model Paths
|
34 |
-
|
35 |
-
LLAMA_PATH = "../storage/models/tinyllama"
|
|
|
1 |
+
# from .env setup all constants here
|
2 |
+
|
3 |
import os
|
4 |
+
from dotenv import load_dotenv
|
5 |
|
6 |
load_dotenv()
|
7 |
|
8 |
+
# Required Constants # TODO: MOVE THIS TO APP SPECIFIC DIRECTORY
|
9 |
+
TIMEOUT = os.getenv("TIMEOUT", 60)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
11 |
LLAMA_CLOUD_API_KEY = os.getenv("LLAMA_CLOUD_API_KEY")
|
12 |
HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_TOKEN")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/dataloader/data_loader.py
CHANGED
@@ -423,6 +423,15 @@ if __name__ == "__main__":
|
|
423 |
parser.add_argument(
|
424 |
"--links", nargs="+", required=True, help="List of links to process."
|
425 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
426 |
|
427 |
args = parser.parse_args()
|
428 |
links_to_process = args.links
|
@@ -430,10 +439,10 @@ if __name__ == "__main__":
|
|
430 |
logger = logging.getLogger(__name__)
|
431 |
logger.setLevel(logging.INFO)
|
432 |
|
433 |
-
with open(
|
434 |
config = yaml.safe_load(f)
|
435 |
|
436 |
-
with open(
|
437 |
project_config = yaml.safe_load(f)
|
438 |
|
439 |
# Combine project config with the main config
|
|
|
423 |
parser.add_argument(
|
424 |
"--links", nargs="+", required=True, help="List of links to process."
|
425 |
)
|
426 |
+
parser.add_argument(
|
427 |
+
"--config_file", type=str, help="Path to the main config file", required=True
|
428 |
+
)
|
429 |
+
parser.add_argument(
|
430 |
+
"--project_config_file",
|
431 |
+
type=str,
|
432 |
+
help="Path to the project config file",
|
433 |
+
required=True,
|
434 |
+
)
|
435 |
|
436 |
args = parser.parse_args()
|
437 |
links_to_process = args.links
|
|
|
439 |
logger = logging.getLogger(__name__)
|
440 |
logger.setLevel(logging.INFO)
|
441 |
|
442 |
+
with open(args.config_file, "r") as f:
|
443 |
config = yaml.safe_load(f)
|
444 |
|
445 |
+
with open(args.project_config_file, "r") as f:
|
446 |
project_config = yaml.safe_load(f)
|
447 |
|
448 |
# Combine project config with the main config
|