XThomasBU commited on
Commit
60929fd
1 Parent(s): eefbb54
.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/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 ["sh", "-c", "python -m modules.vectorstore.store_manager && uvicorn app:app --host 0.0.0.0 --port 7860"]
 
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/code
 
 
26
 
27
  # Expose the port the app runs on
28
- EXPOSE 8000
29
 
30
  # Default command to run the application
31
- CMD ["sh", "-c", "python -m modules.vectorstore.store_manager && chainlit run main.py --host 0.0.0.0 --port 8000"]
 
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. **Put your data under the `storage/data` directory**
 
 
 
 
 
 
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 code
40
- python -m modules.dataloader.data_loader --links "your_pdf_link"
41
  ```
42
 
43
  4. **Create the Vector Database**
44
  ```bash
45
- cd code
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 code
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 8000:8000 dev
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 modules.config.constants import (
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 modules.chat_processor.helpers import (
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(user_details)
 
 
 
 
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(user_details)
 
 
 
 
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(user_details)
 
 
 
 
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 modules.config.constants import (
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
- get_time,
 
 
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
- with open("config/config.yml", "r") as f:
42
- config = yaml.safe_load(f)
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, config=None):
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(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
- with open("config/config.yml", "r") as f:
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
- # Return Starters only if the chat is new
229
-
230
- try:
231
- thread = cl_data._data_layer.get_thread(
232
- cl.context.session.thread_id
233
- ) # see if the thread has any steps
234
- if thread.steps or len(thread.steps) > 0:
235
- return None
236
- except Exception as e:
237
- print(e)
238
- return [
239
- cl.Starter(
240
- label="recording on CNNs?",
241
- message="Where can I find the recording for the lecture on Transformers?",
242
- icon="/public/adv-screen-recorder-svgrepo-com.svg",
243
- ),
244
- cl.Starter(
245
- label="where's the slides?",
246
- message="When are the lectures? I can't find the schedule.",
247
- icon="/public/alarmy-svgrepo-com.svg",
248
- ),
249
- cl.Starter(
250
- label="Due Date?",
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, config=None):
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
- try:
292
- self.user = {
293
- "user_id": user.identifier,
294
- "session_id": cl.context.session.thread_id,
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 modules.config.prompts import prompts # TODO: MOVE THIS TO APP SPECIFIC DIRECTORY
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 dotenv import load_dotenv
 
2
  import os
 
3
 
4
  load_dotenv()
5
 
6
- TIMEOUT = 60
7
- COOLDOWN_TIME = 60
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("../code/modules/config/config.yml", "r") as f:
434
  config = yaml.safe_load(f)
435
 
436
- with open("../code/modules/config/project_config.yml", "r") as f:
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