Spaces:
Running
Running
Upload 5 files
Browse files- Dockerfile +46 -0
- Makefile +293 -0
- app2.py +414 -0
- prompts.py +329 -0
- requirements.txt +10 -0
Dockerfile
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Use Python 3.11 slim image as base
|
2 |
+
FROM python:3.13-slim
|
3 |
+
|
4 |
+
# Set working directory
|
5 |
+
WORKDIR /app
|
6 |
+
|
7 |
+
# Set environment variables
|
8 |
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
9 |
+
PYTHONUNBUFFERED=1
|
10 |
+
|
11 |
+
# Install system dependencies including curl for health check
|
12 |
+
RUN apt-get update && apt-get install -y \
|
13 |
+
--no-install-recommends \
|
14 |
+
gcc \
|
15 |
+
curl \
|
16 |
+
&& rm -rf /var/lib/apt/lists/*
|
17 |
+
|
18 |
+
# Copy requirements first for better Docker layer caching
|
19 |
+
COPY requirements.txt .
|
20 |
+
|
21 |
+
# Install Python dependencies
|
22 |
+
RUN pip --no-cache-dir install --upgrade pip && \
|
23 |
+
pip --no-cache-dir install -r requirements.txt
|
24 |
+
|
25 |
+
# Copy application files
|
26 |
+
COPY app2.py .
|
27 |
+
COPY prompts.py .
|
28 |
+
|
29 |
+
# Create log directory and set proper permissions
|
30 |
+
RUN mkdir -p /app/logs && \
|
31 |
+
chmod 755 /app/logs
|
32 |
+
|
33 |
+
# Create a non-root user for security
|
34 |
+
RUN useradd --create-home --shell /bin/bash app && \
|
35 |
+
chown -R app:app /app
|
36 |
+
USER app
|
37 |
+
|
38 |
+
# Expose the port the app runs on
|
39 |
+
EXPOSE 7860
|
40 |
+
|
41 |
+
# Health check
|
42 |
+
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
|
43 |
+
CMD curl -f http://localhost:7860/ || exit 1
|
44 |
+
|
45 |
+
# Run the application
|
46 |
+
CMD ["python", "app2.py"]
|
Makefile
ADDED
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Thank you @Earthly https://www.youtube.com/watch?v=w2UeLF7EEwk
|
2 |
+
# Can be adapted to pipenv, and poetry
|
3 |
+
# Other languages coming soon especially R and Julia
|
4 |
+
|
5 |
+
# .ONESHELL tells make to run each recipe line in a single shell
|
6 |
+
.ONESHELL:
|
7 |
+
|
8 |
+
# .DEFAULT_GOAL tells make which target to run when no target is specified
|
9 |
+
.DEFAULT_GOAL := all
|
10 |
+
|
11 |
+
# Specify python location in virtual environment
|
12 |
+
# Specify pip location in virtual environment
|
13 |
+
PYTHON := .venv/bin/python3
|
14 |
+
PIP := .venv/bin/pip3
|
15 |
+
DOCKER_IMAGE_NAME := tima-chatbot
|
16 |
+
DOCKER_IMAGE_VERSION := v0.0.0
|
17 |
+
DOCKER_IMAGE_TAG := $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_VERSION)
|
18 |
+
|
19 |
+
#-----------------------------------------------------------------
|
20 |
+
# Package Manager Detection
|
21 |
+
#-----------------------------------------------------------------
|
22 |
+
|
23 |
+
# Detect available package managers
|
24 |
+
# UV https://app.readytensor.ai/publications/uv-package-1yiSfLXTffSF?utm_id=12
|
25 |
+
DETECT_PIPENV := $(shell which pipenv)
|
26 |
+
DETECT_POETRY := $(shell which poetry)
|
27 |
+
DETECT_UV := $(shell which uv)
|
28 |
+
|
29 |
+
# Set flags (1: available, 0: not available)
|
30 |
+
ifeq ($(DETECT_PIPENV),)
|
31 |
+
HAS_PIPENV := 0
|
32 |
+
else
|
33 |
+
HAS_PIPENV := 1
|
34 |
+
endif
|
35 |
+
|
36 |
+
ifeq ($(DETECT_POETRY),)
|
37 |
+
HAS_POETRY := 0
|
38 |
+
else
|
39 |
+
HAS_POETRY := 1
|
40 |
+
endif
|
41 |
+
|
42 |
+
ifeq ($(DETECT_UV),)
|
43 |
+
HAS_UV := 0
|
44 |
+
else
|
45 |
+
HAS_UV := 1
|
46 |
+
endif
|
47 |
+
|
48 |
+
#-----------------------------------------------------------------
|
49 |
+
# Default Package Manager Selection (priority: uv > pipenv > poetry)
|
50 |
+
#-----------------------------------------------------------------
|
51 |
+
|
52 |
+
ifeq ($(HAS_UV),1)
|
53 |
+
PACKAGE_MANAGER := uv
|
54 |
+
else ifeq ($(HAS_PIPENV),1)
|
55 |
+
PACKAGE_MANAGER := pipenv
|
56 |
+
else ifeq ($(HAS_POETRY),1)
|
57 |
+
PACKAGE_MANAGER := poetry
|
58 |
+
else
|
59 |
+
$(error No supported package manager found (pipenv, poetry, or uv))
|
60 |
+
endif
|
61 |
+
|
62 |
+
#-----------------------------------------------------------------
|
63 |
+
# Set Commands Based on the Package Manager
|
64 |
+
#-----------------------------------------------------------------
|
65 |
+
|
66 |
+
ifeq ($(PACKAGE_MANAGER),pipenv)
|
67 |
+
INSTALL_COMMAND := pipenv install --dev
|
68 |
+
RUN_COMMAND := pipenv run
|
69 |
+
else ifeq ($(PACKAGE_MANAGER),poetry)
|
70 |
+
INSTALL_COMMAND := poetry install
|
71 |
+
RUN_COMMAND := poetry run
|
72 |
+
else ifeq ($(PACKAGE_MANAGER),uv)
|
73 |
+
INSTALL_COMMAND := uv pip install -e . -r requirements-dev.txt
|
74 |
+
RUN_COMMAND := python # or python3, depending on your setup
|
75 |
+
endif
|
76 |
+
|
77 |
+
detect:
|
78 |
+
@echo "Pipenv detected: $(HAS_PIPENV)"
|
79 |
+
@echo "Poetry detected: $(HAS_POETRY)"
|
80 |
+
@echo "UV detected: $(HAS_UV)"
|
81 |
+
@echo "Package manager selected: $(PACKAGE_MANAGER)"
|
82 |
+
|
83 |
+
|
84 |
+
venv/bin/activate: requirements.txt
|
85 |
+
# create virtual environment
|
86 |
+
python3 -m venv .venv
|
87 |
+
# make command executable
|
88 |
+
chmod +x .venv/bin/activate
|
89 |
+
# activate virtual environment
|
90 |
+
. .venv/bin/activate
|
91 |
+
|
92 |
+
activate:
|
93 |
+
# Print instructions for activating the virtual environment
|
94 |
+
@echo "To activate the virtual environment, run the following command in your terminal:"
|
95 |
+
@echo "source .venv/bin/activate"
|
96 |
+
|
97 |
+
install: venv/bin/activate requirements.txt # prerequisite
|
98 |
+
# install commands
|
99 |
+
$(PIP) --no-cache-dir install --upgrade pip &&\
|
100 |
+
$(PIP) --no-cache-dir install -r requirements.txt
|
101 |
+
|
102 |
+
docstring: activate
|
103 |
+
# format docstring
|
104 |
+
pyment -w -o numpydoc *.py
|
105 |
+
|
106 |
+
format: activate
|
107 |
+
# format code
|
108 |
+
black *.py
|
109 |
+
|
110 |
+
clean:
|
111 |
+
# clean directory of cache
|
112 |
+
rm -rf __pycache__ &&\
|
113 |
+
rm -rf utils/__pycache__ &&\
|
114 |
+
rm -rf testing/__pycache__ &&\
|
115 |
+
rm -rf .pytest_cache &&\
|
116 |
+
rm -rf .venv
|
117 |
+
|
118 |
+
lint: activate install format
|
119 |
+
# flake8 or #pylint
|
120 |
+
pylint --disable=R,C --errors-only *.py utils/*.py testing/*.py
|
121 |
+
|
122 |
+
setup_readme: ## Create a README.md
|
123 |
+
@if [ ! -f README.md ]; then \
|
124 |
+
echo "# Project Name\n\
|
125 |
+
Description of the project.\n\n\
|
126 |
+
## Installation\n\
|
127 |
+
- Step 1\n\
|
128 |
+
- Step 2\n\n\
|
129 |
+
## Usage\n\
|
130 |
+
Explain how to use the project here.\n\n\
|
131 |
+
## Contributing\n\
|
132 |
+
Explain how to contribute to the project.\n\n\
|
133 |
+
## License\n\
|
134 |
+
License information." > README.md; \
|
135 |
+
echo "README.md created."; \
|
136 |
+
else \
|
137 |
+
echo "README.md already exists."; \
|
138 |
+
fi
|
139 |
+
|
140 |
+
test: activate install format
|
141 |
+
# test
|
142 |
+
$(PYTHON) -m pytest testing/*.py
|
143 |
+
|
144 |
+
run: activate install format
|
145 |
+
# run application
|
146 |
+
# example $(PYTHON) app.py
|
147 |
+
$(PYTHON) app2.py
|
148 |
+
|
149 |
+
run_test: activate install format
|
150 |
+
# run test application
|
151 |
+
$(PYTHON) enchanced_app.py
|
152 |
+
|
153 |
+
review-code:
|
154 |
+
@echo "#### General Code Review Prompt ####"
|
155 |
+
@echo "1. Code Quality:"
|
156 |
+
@echo " - Architecture patterns"
|
157 |
+
@echo " - Design principles (SOLID, DRY, KISS)"
|
158 |
+
@echo " - Code complexity"
|
159 |
+
@echo " - Documentation quality"
|
160 |
+
|
161 |
+
@echo "2. Reliability:"
|
162 |
+
@echo " - Single and multiple points of failure"
|
163 |
+
@echo " - Failover strategies"
|
164 |
+
@echo " - Resource management"
|
165 |
+
@echo " - Thread safety"
|
166 |
+
|
167 |
+
@echo "3. Performance:"
|
168 |
+
@echo " - Algorithmic efficiency"
|
169 |
+
@echo " - Memory usage"
|
170 |
+
@echo " - I/O operations"
|
171 |
+
@echo " - Caching strategy"
|
172 |
+
|
173 |
+
review-ds:
|
174 |
+
@echo "#### Data Science Review Prompt ####"
|
175 |
+
@echo "1. Data Pipeline:"
|
176 |
+
@echo " - Data validation (schema, types, ranges)"
|
177 |
+
@echo " - Preprocessing steps (scaling, encoding, imputation)"
|
178 |
+
@echo " - Feature engineering (relevance, creation, selection)"
|
179 |
+
@echo " - Data versioning (tracking datasets)"
|
180 |
+
@echo " - Data leakage checks"
|
181 |
+
@echo " - Handling of missing or anomalous data"
|
182 |
+
|
183 |
+
@echo "2. Model Development:"
|
184 |
+
@echo " - Algorithm selection rationale"
|
185 |
+
@echo " - Hyperparameter tuning methodology and tracking"
|
186 |
+
@echo " - Cross-validation strategy (appropriateness, implementation)"
|
187 |
+
@echo " - Choice of evaluation metrics (relevance to business goal)"
|
188 |
+
@echo " - Model interpretability/explainability methods"
|
189 |
+
@echo " - Bias and fairness assessment"
|
190 |
+
@echo " - Model persistence (saving/loading)"
|
191 |
+
@echo " - Code reproducibility (seeds, dependencies)"
|
192 |
+
|
193 |
+
@echo "3. Production Readiness:"
|
194 |
+
@echo " - Scalability of prediction/inference code"
|
195 |
+
@echo " - Monitoring setup (technical and model performance metrics)"
|
196 |
+
@echo " - Logging for model inputs/outputs/errors"
|
197 |
+
@echo " - A/B testing or canary deployment capability"
|
198 |
+
@echo " - Model deployment pipeline (automation)"
|
199 |
+
@echo " - Rollback strategy for model updates"
|
200 |
+
|
201 |
+
@echo "4. Experiment Tracking:"
|
202 |
+
@echo " - Are experiments logged (parameters, code versions, metrics)?"
|
203 |
+
@echo " - Is an experiment tracking tool used (e.g., MLflow, W&B)?"
|
204 |
+
@echo " - Are model artifacts versioned and stored?"
|
205 |
+
|
206 |
+
review-logging:
|
207 |
+
@echo "#### Logging Review Prompt ####"
|
208 |
+
@echo ""
|
209 |
+
@echo "1. Level 1: Print Statements"
|
210 |
+
@echo " - Are there raw print() statements used for debugging?"
|
211 |
+
@echo " - Should these be replaced with proper logging?"
|
212 |
+
@echo " - Are any critical error conditions only visible via print statements?"
|
213 |
+
@echo ""
|
214 |
+
@echo "2. Level 2: Logging Libraries"
|
215 |
+
@echo " - Is a proper logging library being used consistently?"
|
216 |
+
@echo " - Are appropriate log levels (DEBUG, INFO, WARNING, ERROR) used?"
|
217 |
+
@echo " - Is logging properly configured (handlers, formatters)?"
|
218 |
+
@echo " - Are logs structured (JSON/key-value) for better analysis?"
|
219 |
+
@echo " - Is sensitive information protected from being logged?"
|
220 |
+
@echo ""
|
221 |
+
@echo "3. Level 3: Tracing"
|
222 |
+
@echo " - Is function/method entry/exit tracked for performance analysis?"
|
223 |
+
@echo " - Are execution paths through the code captured with timing data?"
|
224 |
+
@echo " - Are trace IDs used to track request flow?"
|
225 |
+
@echo ""
|
226 |
+
@echo "4. Level 4: Distributed Tracing"
|
227 |
+
@echo " - Is context propagated across service boundaries?"
|
228 |
+
@echo " - Are trace IDs maintained throughout the entire request lifecycle?"
|
229 |
+
@echo " - Is sampling strategy appropriate for production load?"
|
230 |
+
@echo ""
|
231 |
+
@echo "5. Level 5: Observability"
|
232 |
+
@echo " - Are logs, metrics, and traces integrated into a unified system?"
|
233 |
+
@echo " - Is there anomaly detection for unexpected behaviors?"
|
234 |
+
@echo " - Does the system provide business-level insights from technical data?"
|
235 |
+
@echo " - Is the observability stack scalable for production use?"
|
236 |
+
|
237 |
+
docker_build: Dockerfile
|
238 |
+
# build container. Feel free to change the platform given your needs
|
239 |
+
# docker build -platform linux/amd64 -t plot-timeseries-app:v0 .
|
240 |
+
# podman build -platform linux/amd64 -t plot-timeseries-app:v0 .
|
241 |
+
sudo docker build --platform linux/amd64 -t $(DOCKER_IMAGE_TAG) .
|
242 |
+
|
243 |
+
docker_run_test: Dockerfile
|
244 |
+
# linting Dockerfile
|
245 |
+
# podman run --rm -i hadolint/hadolint < Dockerfile
|
246 |
+
docker run --rm -i hadolint/hadolint < Dockerfile
|
247 |
+
|
248 |
+
docker_clean: Dockerfile
|
249 |
+
# remove dangling images, containers, volumes and networks
|
250 |
+
# podman system prune -a
|
251 |
+
sudo docker system prune -a
|
252 |
+
|
253 |
+
docker_run: Dockerfile docker_build
|
254 |
+
# run docker
|
255 |
+
# For ARM images (Apple Silicon, Raspberry Pi, etc.), use --platform linux/arm64
|
256 |
+
# Example: sudo docker run --platform linux/arm64 -p 7860:7860 -e TIMA_API_KEY=$TIMA_API_KEY $(DOCKER_IMAGE_TAG)
|
257 |
+
# For x86_64 (most PCs/servers), use --platform linux/amd64
|
258 |
+
sudo docker run --platform linux/amd64 -p 7860:7860 -e TIMA_API_KEY=${TIMA_API_KEY} $(DOCKER_IMAGE_TAG)
|
259 |
+
docker_push: docker_build
|
260 |
+
# push to registry
|
261 |
+
# docker tag <my-image> registry.digitalocean.com/<my-registry>/<my-image>
|
262 |
+
# docker push registry.digitalocean.com/<my-registry>/<my-image>
|
263 |
+
# podman tag <my-image> registry.digitalocean.com/<my-registry>/<my-image>
|
264 |
+
# podman push registry.digitalocean.com/<my-registry>/<my-image>
|
265 |
+
|
266 |
+
help:
|
267 |
+
@echo "Usage: make [target]"
|
268 |
+
@echo ""
|
269 |
+
@echo "Available targets:"
|
270 |
+
@echo " detect Detect package managers"
|
271 |
+
@echo " activate Activate virtual environment"
|
272 |
+
@echo " install Install dependencies"
|
273 |
+
@echo " docstring Format docstrings"
|
274 |
+
@echo " format Format code"
|
275 |
+
@echo " clean Clean directory of cache"
|
276 |
+
@echo " lint Lint code"
|
277 |
+
@echo " test Run tests"
|
278 |
+
@echo " run Run application"
|
279 |
+
@echo " setup_readme Create a README.md"
|
280 |
+
@echo " review-code Code review prompt"
|
281 |
+
@echo " review-ds Data Science review prompt"
|
282 |
+
@echo " review-logging Logging implementation review prompt"
|
283 |
+
@echo " docker-build Build Docker image $(DOCKER_IMAGE_TAG)"
|
284 |
+
@echo " docker_run_test Lint Dockerfile"
|
285 |
+
@echo " docker_clean Remove dangling images, containers, volumes, and networks"
|
286 |
+
@echo " docker_run Run Docker container"
|
287 |
+
@echo " docker_push Push Docker container to registry"
|
288 |
+
@echo " help Show this help message"
|
289 |
+
|
290 |
+
# .PHONY tells make that these targets do not represent actual files
|
291 |
+
.PHONY: activate format clean lint test build run docker_build docker_run docker_push docker_clean docker_run_test
|
292 |
+
|
293 |
+
all: install format lint test run docker_build docker_run docker_push
|
app2.py
ADDED
@@ -0,0 +1,414 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Module for mental health chatbot with improved crisis handling.
|
2 |
+
|
3 |
+
This module provides a Gradio-based chat interface for mental health support
|
4 |
+
with proper crisis intervention protocols and streamlined code organization.
|
5 |
+
|
6 |
+
Example:
|
7 |
+
To run the chat interface:
|
8 |
+
$ python app2.py
|
9 |
+
|
10 |
+
Attributes:
|
11 |
+
TIMA_API_KEY (str): The API token for the AI service, loaded from environment variables.
|
12 |
+
|
13 |
+
Created by Mainye B
|
14 |
+
"""
|
15 |
+
|
16 |
+
import os
|
17 |
+
import time
|
18 |
+
import logging
|
19 |
+
from typing import Generator, List, Dict
|
20 |
+
import gradio as gr
|
21 |
+
import openai
|
22 |
+
import httpx
|
23 |
+
from tenacity import retry, stop_after_attempt, wait_exponential
|
24 |
+
from ratelimit import limits, sleep_and_retry
|
25 |
+
|
26 |
+
from prompts import load_system_prompt
|
27 |
+
|
28 |
+
# Constants for API interaction
|
29 |
+
MAX_RETRIES = 5
|
30 |
+
INITIAL_RETRY_DELAY = 1 # seconds
|
31 |
+
MAX_RETRY_DELAY = 60 # seconds
|
32 |
+
RATE_LIMIT_CALLS = 40 # Cerebras recommended rate limit
|
33 |
+
RATE_LIMIT_PERIOD = 60 # 1 minute period
|
34 |
+
|
35 |
+
# Configure logging
|
36 |
+
logging.basicConfig(
|
37 |
+
filename="chat_interactions.log",
|
38 |
+
level=logging.INFO,
|
39 |
+
format="%(asctime)s - %(levelname)s - %(message)s",
|
40 |
+
)
|
41 |
+
logger = logging.getLogger(__name__)
|
42 |
+
|
43 |
+
# Load environment variables
|
44 |
+
TIMA_API_KEY = os.getenv("TIMA_API_KEY", os.getenv("CEREBRAS_API_KEY", None))
|
45 |
+
if not TIMA_API_KEY:
|
46 |
+
raise ValueError("CEREBRAS_API_KEY environment variable not found in environment")
|
47 |
+
|
48 |
+
# Rate limiting helps to avoid exceeding the API rate limits
|
49 |
+
CALLS_PER_MINUTE = 60
|
50 |
+
PERIOD = 60
|
51 |
+
|
52 |
+
|
53 |
+
class ChatError(Exception):
|
54 |
+
"""Custom exception for chat-related errors"""
|
55 |
+
|
56 |
+
pass
|
57 |
+
|
58 |
+
|
59 |
+
class APIError(Exception):
|
60 |
+
"""Base exception for API errors"""
|
61 |
+
|
62 |
+
pass
|
63 |
+
|
64 |
+
|
65 |
+
class RateLimitError(APIError):
|
66 |
+
"""Exception for rate limit errors"""
|
67 |
+
|
68 |
+
pass
|
69 |
+
|
70 |
+
|
71 |
+
class TokenLimitError(APIError):
|
72 |
+
"""Exception for token limit errors"""
|
73 |
+
|
74 |
+
pass
|
75 |
+
|
76 |
+
|
77 |
+
class InvalidRequestError(APIError):
|
78 |
+
"""Exception for invalid request errors"""
|
79 |
+
|
80 |
+
pass
|
81 |
+
|
82 |
+
|
83 |
+
class AuthenticationError(APIError):
|
84 |
+
"""Exception for authentication errors"""
|
85 |
+
|
86 |
+
pass
|
87 |
+
|
88 |
+
|
89 |
+
class ServerError(APIError):
|
90 |
+
"""Exception for server-side errors"""
|
91 |
+
|
92 |
+
pass
|
93 |
+
|
94 |
+
|
95 |
+
def detect_crisis_situation(messages: List[Dict[str, str]]) -> bool:
|
96 |
+
"""Detect if the conversation indicates a crisis situation.
|
97 |
+
|
98 |
+
Args:
|
99 |
+
messages: List of conversation messages
|
100 |
+
|
101 |
+
Returns:
|
102 |
+
bool: True if crisis situation detected, False otherwise
|
103 |
+
"""
|
104 |
+
if not messages:
|
105 |
+
return False
|
106 |
+
|
107 |
+
# Check for PHQ-9 suicide question with concerning response
|
108 |
+
last_message = messages[-1].get("content", "").lower()
|
109 |
+
prev_message = messages[-2].get("content", "").lower() if len(messages) >= 2 else ""
|
110 |
+
|
111 |
+
# Look for PHQ-9 suicide question and high score response
|
112 |
+
suicide_question_keywords = [
|
113 |
+
"thoughts that you would be better off dead",
|
114 |
+
"hurting yourself",
|
115 |
+
]
|
116 |
+
concerning_responses = ["3", "nearly every day", "more than half"]
|
117 |
+
|
118 |
+
has_suicide_question = any(
|
119 |
+
keyword in prev_message for keyword in suicide_question_keywords
|
120 |
+
)
|
121 |
+
has_concerning_response = any(
|
122 |
+
response in last_message for response in concerning_responses
|
123 |
+
)
|
124 |
+
|
125 |
+
return has_suicide_question and has_concerning_response
|
126 |
+
|
127 |
+
|
128 |
+
def get_crisis_response() -> str:
|
129 |
+
"""Generate an appropriate crisis response with local resources
|
130 |
+
|
131 |
+
Returns:
|
132 |
+
str: A compassionate response with immediate and local resources
|
133 |
+
"""
|
134 |
+
response_parts = []
|
135 |
+
|
136 |
+
# Initial validation and support
|
137 |
+
response_parts.append(
|
138 |
+
"I hear you, and I want you to know that your life has immense value. "
|
139 |
+
"What you're going through sounds incredibly difficult, and it's so important "
|
140 |
+
"that you get the support you need right now."
|
141 |
+
)
|
142 |
+
|
143 |
+
# Immediate crisis resources
|
144 |
+
response_parts.append(
|
145 |
+
"\n\nThere are caring people available 24/7 to support you:"
|
146 |
+
"\n• National Suicide Prevention Lifeline: 1-800-273-TALK (8255)"
|
147 |
+
"\n• Crisis Text Line: Text HOME to 741741"
|
148 |
+
"\n• Emergency Services: 911 (if you're in immediate danger)"
|
149 |
+
)
|
150 |
+
|
151 |
+
# Local mental health professionals
|
152 |
+
response_parts.append(
|
153 |
+
"\n\nI also want to connect you with some local mental health professionals in Nairobi "
|
154 |
+
"who can provide ongoing support:"
|
155 |
+
"\n\n1. Dr Nancy Nyagah at Blossom Out Consultants"
|
156 |
+
"\n • Location: 2nd floor Park View Towers (opposite Parklands police station)"
|
157 |
+
"\n • Cost: 5000 KES (as of 2018)"
|
158 |
+
"\n • Accepts insurance"
|
159 |
+
"\n • Contact: 0722938606 or 0780938606"
|
160 |
+
"\n\n2. Dr. Judy Kamau at Scripture Union, Hurlingham"
|
161 |
+
"\n • Cost: 3,500 KES (as of 2018)"
|
162 |
+
"\n • Contact: +254202712852"
|
163 |
+
"\n\n3. Rhoda Mutiso (specializes in depression)"
|
164 |
+
"\n • Cost: 2000 KES (as of 2018)"
|
165 |
+
"\n • Contact: 0722 333378"
|
166 |
+
)
|
167 |
+
|
168 |
+
# Next steps and validation
|
169 |
+
response_parts.append(
|
170 |
+
"\n\nI know reaching out for help can feel overwhelming, but it's a sign of strength, "
|
171 |
+
"not weakness. Would you like me to:"
|
172 |
+
"\n1. Help you make a safety plan for the next 24 hours?"
|
173 |
+
"\n2. Talk about what you're going through right now?"
|
174 |
+
"\n3. Give you more information about any of these mental health professionals?"
|
175 |
+
"\n\nYou don't have to go through this alone. I'm here to support you in taking the next step, "
|
176 |
+
"whatever that looks like for you."
|
177 |
+
)
|
178 |
+
|
179 |
+
return "".join(response_parts)
|
180 |
+
|
181 |
+
|
182 |
+
def handle_api_error(e: Exception) -> APIError:
|
183 |
+
"""
|
184 |
+
Convert API exceptions to our custom exception types.
|
185 |
+
|
186 |
+
Args:
|
187 |
+
e (Exception): The caught exception
|
188 |
+
|
189 |
+
Returns:
|
190 |
+
APIError: The appropriate custom exception type
|
191 |
+
"""
|
192 |
+
error_msg = str(e).lower()
|
193 |
+
if "rate limit" in error_msg:
|
194 |
+
return RateLimitError("Rate limit exceeded. Please try again later.")
|
195 |
+
elif "token limit" in error_msg:
|
196 |
+
return TokenLimitError(
|
197 |
+
"Input too long. Please reduce the length of your message."
|
198 |
+
)
|
199 |
+
elif "authentication" in error_msg or "api key" in error_msg:
|
200 |
+
return AuthenticationError("Authentication failed. Please check your API key.")
|
201 |
+
elif "invalid request" in error_msg:
|
202 |
+
return InvalidRequestError("Invalid request. Please check your input.")
|
203 |
+
elif any(code in error_msg for code in ["502", "503", "504"]):
|
204 |
+
return ServerError("Server is temporarily unavailable. Please try again later.")
|
205 |
+
return APIError(f"API error occurred: {str(e)}")
|
206 |
+
|
207 |
+
|
208 |
+
@sleep_and_retry
|
209 |
+
@limits(calls=RATE_LIMIT_CALLS, period=RATE_LIMIT_PERIOD)
|
210 |
+
@retry(
|
211 |
+
stop=stop_after_attempt(MAX_RETRIES),
|
212 |
+
wait=wait_exponential(
|
213 |
+
multiplier=INITIAL_RETRY_DELAY, min=INITIAL_RETRY_DELAY, max=MAX_RETRY_DELAY
|
214 |
+
),
|
215 |
+
retry=lambda e: isinstance(e, (ServerError, RateLimitError)),
|
216 |
+
reraise=True,
|
217 |
+
)
|
218 |
+
def create_chat_completion(
|
219 |
+
messages: List[Dict[str, str]],
|
220 |
+
) -> Generator[str, None, None]:
|
221 |
+
"""
|
222 |
+
Create a chat completion with comprehensive error handling and rate limiting
|
223 |
+
|
224 |
+
Args:
|
225 |
+
messages (List[Dict[str, str]]): List of messages in the chat
|
226 |
+
|
227 |
+
Yields:
|
228 |
+
Generator[str, None, None]: A generator of chat completion response chunks
|
229 |
+
|
230 |
+
Raises:
|
231 |
+
APIError: Base class for all API-related errors
|
232 |
+
RateLimitError: When API rate limit is exceeded
|
233 |
+
TokenLimitError: When input tokens exceed model's limit
|
234 |
+
AuthenticationError: When API key is invalid
|
235 |
+
InvalidRequestError: When request is malformed
|
236 |
+
ServerError: When API server has issues
|
237 |
+
"""
|
238 |
+
try:
|
239 |
+
# Initialize the OpenAI client with Cerebras API endpoint
|
240 |
+
client = openai.OpenAI(
|
241 |
+
base_url="https://api.cerebras.ai/v1",
|
242 |
+
api_key=TIMA_API_KEY,
|
243 |
+
timeout=60.0, # 60 second timeout
|
244 |
+
max_retries=0, # We handle retries ourselves
|
245 |
+
)
|
246 |
+
|
247 |
+
logger.info("Starting chat completion request to Cerebras API.")
|
248 |
+
logger.debug("Messages sent to API: %s", messages)
|
249 |
+
|
250 |
+
try:
|
251 |
+
stream = client.chat.completions.create(
|
252 |
+
model="llama-3.3-70b", # Cerebras recommended model
|
253 |
+
messages=messages,
|
254 |
+
temperature=0.8, # Adjust temperature for creativity 0 to 1
|
255 |
+
max_tokens=500, # Limit response length to prevent token limit errors
|
256 |
+
top_p=0.9, # Use top-p sampling for more diverse responses
|
257 |
+
stream=True,
|
258 |
+
)
|
259 |
+
except openai.APIError as e:
|
260 |
+
raise handle_api_error(e)
|
261 |
+
except httpx.TimeoutException:
|
262 |
+
raise ServerError("Request timed out. Please try again.")
|
263 |
+
except httpx.RequestError as e:
|
264 |
+
raise ServerError(f"Network error occurred: {str(e)}")
|
265 |
+
|
266 |
+
try:
|
267 |
+
# Check for crisis situations and provide appropriate response
|
268 |
+
crisis_detected = detect_crisis_situation(messages)
|
269 |
+
if crisis_detected:
|
270 |
+
crisis_response = get_crisis_response()
|
271 |
+
yield crisis_response
|
272 |
+
else:
|
273 |
+
for chunk in stream:
|
274 |
+
if (
|
275 |
+
chunk.choices
|
276 |
+
and chunk.choices[0].delta
|
277 |
+
and chunk.choices[0].delta.content
|
278 |
+
):
|
279 |
+
yield chunk.choices[0].delta.content
|
280 |
+
logger.info("Chat completion stream finished successfully.")
|
281 |
+
except Exception as e:
|
282 |
+
logger.error("Error during stream processing: %s", str(e), exc_info=True)
|
283 |
+
raise handle_api_error(e)
|
284 |
+
|
285 |
+
except APIError as e:
|
286 |
+
logger.error("API Error in chat completion: %s", str(e), exc_info=True)
|
287 |
+
raise
|
288 |
+
except Exception as e:
|
289 |
+
logger.error("Unexpected error in chat completion: %s", str(e), exc_info=True)
|
290 |
+
raise APIError(f"Unexpected error occurred: {str(e)}")
|
291 |
+
|
292 |
+
|
293 |
+
def chat_handler(message: str, history: List[List[str]]) -> Generator[str, None, None]:
|
294 |
+
"""Handle chat interactions with proper history management and error handling
|
295 |
+
|
296 |
+
Args:
|
297 |
+
message (str): The user message
|
298 |
+
history (List[List[str]]): The chat history
|
299 |
+
|
300 |
+
Yields:
|
301 |
+
Generator[str, None, None]: The response message chunks
|
302 |
+
|
303 |
+
Raises:
|
304 |
+
Exception: If an error occurs during chat handling
|
305 |
+
"""
|
306 |
+
if not isinstance(message, str):
|
307 |
+
raise InvalidRequestError("Message must be a string")
|
308 |
+
|
309 |
+
if not message.strip():
|
310 |
+
raise InvalidRequestError("Message cannot be empty")
|
311 |
+
|
312 |
+
try:
|
313 |
+
# Validate and sanitize history format
|
314 |
+
if history:
|
315 |
+
if not isinstance(history, (list, tuple)):
|
316 |
+
logger.warning(
|
317 |
+
"History is not a list or tuple, converting to empty list"
|
318 |
+
)
|
319 |
+
history = []
|
320 |
+
else:
|
321 |
+
# Filter out invalid history entries
|
322 |
+
sanitized_history = []
|
323 |
+
for h in history:
|
324 |
+
if isinstance(h, (list, tuple)) and len(h) == 2:
|
325 |
+
# Ensure both elements are strings or None
|
326 |
+
user_msg = str(h[0]) if h[0] is not None else None
|
327 |
+
assistant_msg = str(h[1]) if h[1] is not None else None
|
328 |
+
if (
|
329 |
+
user_msg or assistant_msg
|
330 |
+
): # At least one message must be non-None
|
331 |
+
sanitized_history.append([user_msg, assistant_msg])
|
332 |
+
else:
|
333 |
+
logger.warning(f"Skipping invalid history entry: {h}")
|
334 |
+
history = sanitized_history
|
335 |
+
|
336 |
+
# Apply rate limiting at the conversation level
|
337 |
+
current_time = time.time()
|
338 |
+
if hasattr(chat_handler, "last_call_time"):
|
339 |
+
time_since_last_call = current_time - chat_handler.last_call_time
|
340 |
+
if time_since_last_call < (
|
341 |
+
60 / RATE_LIMIT_CALLS
|
342 |
+
): # Minimum time between calls
|
343 |
+
sleep_time = (60 / RATE_LIMIT_CALLS) - time_since_last_call
|
344 |
+
time.sleep(sleep_time)
|
345 |
+
chat_handler.last_call_time = current_time
|
346 |
+
|
347 |
+
# Define your system prompt
|
348 |
+
system_prompt = load_system_prompt()
|
349 |
+
|
350 |
+
formatted_messages = [{"role": "system", "content": system_prompt}]
|
351 |
+
|
352 |
+
if history:
|
353 |
+
for user_msg, assistant_msg in history:
|
354 |
+
if user_msg:
|
355 |
+
formatted_messages.append(
|
356 |
+
{"role": "user", "content": str(user_msg)}
|
357 |
+
)
|
358 |
+
if assistant_msg:
|
359 |
+
formatted_messages.append(
|
360 |
+
{"role": "assistant", "content": str(assistant_msg)}
|
361 |
+
)
|
362 |
+
|
363 |
+
formatted_messages.append({"role": "user", "content": message})
|
364 |
+
|
365 |
+
logger.info("User message: %s", message)
|
366 |
+
|
367 |
+
full_response = ""
|
368 |
+
for chunk in create_chat_completion(formatted_messages):
|
369 |
+
full_response += chunk
|
370 |
+
yield full_response # Stream response to Gradio
|
371 |
+
|
372 |
+
logger.info("AI response: %s", full_response)
|
373 |
+
|
374 |
+
except ChatError as e:
|
375 |
+
logger.error(f"ChatError in chat_handler: {e}", exc_info=True)
|
376 |
+
yield f"Error: {e}" # Display error in Gradio UI
|
377 |
+
except Exception as e:
|
378 |
+
logger.error(f"Unexpected error in chat_handler: {e}", exc_info=True)
|
379 |
+
yield "An unexpected error occurred. Please try again later."
|
380 |
+
|
381 |
+
|
382 |
+
def main():
|
383 |
+
"""Main function to launch the Gradio interface"""
|
384 |
+
# Create and launch the Gradio interface
|
385 |
+
chat_interface = gr.ChatInterface(
|
386 |
+
fn=chat_handler,
|
387 |
+
title="Tima - Your Mental Health Companion",
|
388 |
+
description="A safe space to talk. Tima is here to listen, offer support, and provide understanding. This is not a replacement for professional medical advice.",
|
389 |
+
examples=[
|
390 |
+
"I feel like giving up on everything",
|
391 |
+
"I'm feeling really anxious lately and can't stop worrying",
|
392 |
+
"I've been feeling down and hopeless for weeks",
|
393 |
+
"I think people are watching me and I keep hearing voices",
|
394 |
+
"Can you recommend a therapist in Nairobi?",
|
395 |
+
"I need someone to talk to about my depression",
|
396 |
+
],
|
397 |
+
type="messages",
|
398 |
+
)
|
399 |
+
chat_interface.launch(
|
400 |
+
server_name="0.0.0.0", # Server name
|
401 |
+
server_port=7860, # Different port to avoid conflicts
|
402 |
+
share=False, # Share the server publicly
|
403 |
+
max_threads=16, # Increased maximum number of threads to handle more concurrent jobs
|
404 |
+
show_error=True, # Show error messages
|
405 |
+
inbrowser=True, # Open in browser
|
406 |
+
show_api=True, # Show API
|
407 |
+
enable_monitoring=True,
|
408 |
+
state_session_capacity=50,
|
409 |
+
auth=("admin", "tima2025"), # Simple authentication - change password as needed
|
410 |
+
)
|
411 |
+
|
412 |
+
|
413 |
+
if __name__ == "__main__":
|
414 |
+
main()
|
prompts.py
ADDED
@@ -0,0 +1,329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Module containing system prompts and related functions for the mental health chatbot."""
|
2 |
+
|
3 |
+
import logging
|
4 |
+
from typing import Optional
|
5 |
+
|
6 |
+
logger = logging.getLogger(__name__)
|
7 |
+
|
8 |
+
|
9 |
+
def load_system_prompt() -> str:
|
10 |
+
"""Load and return the system prompt for the mental health chatbot."""
|
11 |
+
return SYSTEM_PROMPT
|
12 |
+
|
13 |
+
|
14 |
+
def validate_system_prompt(prompt: str) -> bool:
|
15 |
+
"""
|
16 |
+
Validate that the system prompt contains all required components.
|
17 |
+
|
18 |
+
Args:
|
19 |
+
prompt (str): The system prompt to validate
|
20 |
+
|
21 |
+
Returns:
|
22 |
+
bool: True if the prompt is valid, False otherwise
|
23 |
+
"""
|
24 |
+
required_sections = [
|
25 |
+
"INITIAL INTERACTION GUIDELINES",
|
26 |
+
"CRITICAL RESPONSE RULES",
|
27 |
+
"Special Response Scenarios",
|
28 |
+
"KEYWORD DETECTION AND SCREENING ACTIVATION",
|
29 |
+
"Depression Keywords",
|
30 |
+
"Anxiety Keywords",
|
31 |
+
"Psychosis Keywords",
|
32 |
+
"SCREENING PROCESS",
|
33 |
+
]
|
34 |
+
|
35 |
+
for section in required_sections:
|
36 |
+
if section not in prompt:
|
37 |
+
logger.error(f"Missing required section in system prompt: {section}")
|
38 |
+
return False
|
39 |
+
return True
|
40 |
+
|
41 |
+
|
42 |
+
# The main system prompt
|
43 |
+
SYSTEM_PROMPT = """You are Tima, a compassionate and empathetic AI assistant specializing in mental health support. Your goal is to provide a safe, non-judgmental, and supportive space for users to express themselves and support their emotional well-being. You are not a medical provider and must never offer therapy, diagnosis, or medical advice.
|
44 |
+
|
45 |
+
Respond like a caring, understanding friend who truly listens. Use warm, conversational language and show genuine curiosity about the user's experiences.
|
46 |
+
|
47 |
+
**INITIAL INTERACTION GUIDELINES:**
|
48 |
+
- Keep first responses shorter and warmer to ease the user in
|
49 |
+
- Use casual, friendly language initially: "Hi there! How are you doing today?"
|
50 |
+
- Let them share at their own pace before diving deeper
|
51 |
+
- Build rapport before asking more detailed questions
|
52 |
+
|
53 |
+
**CRITICAL RESPONSE RULES:**
|
54 |
+
- Ask only ONE question at a time, maximum 2 questions per response
|
55 |
+
- ALWAYS ask permission before sharing coping techniques: "Would you like me to share..."
|
56 |
+
- NEVER provide solutions, techniques, or advice unless the user explicitly says yes
|
57 |
+
- Wait for user consent before offering any coping strategies
|
58 |
+
- Keep responses focused and avoid overwhelming the user with too much information
|
59 |
+
|
60 |
+
## Special Response Scenarios:
|
61 |
+
|
62 |
+
**When users express thoughts of giving up or feel hopeless:**
|
63 |
+
- Validate their pain and acknowledge how difficult things must be
|
64 |
+
- Ask ONE gentle question to understand more: "What's been weighing on you lately?" OR "Can you tell me more about what's been going on?"
|
65 |
+
- Listen to their response completely before offering any help
|
66 |
+
- Only after they share, ask permission: "Would you like me to share some techniques that might help you feel a bit more grounded right now?"
|
67 |
+
- If they say yes, offer only 1-2 techniques initially:
|
68 |
+
* Grounding exercises (5-4-3-2-1 technique)
|
69 |
+
* Deep breathing exercises (4-7-8 technique)
|
70 |
+
- Wait for their response before offering additional techniques
|
71 |
+
- Emphasize that these feelings are temporary and that seeking professional help is a sign of strength
|
72 |
+
- Remind them of their worth and that their life has value
|
73 |
+
|
74 |
+
**KEYWORD DETECTION AND SCREENING ACTIVATION:**
|
75 |
+
|
76 |
+
**IMPORTANT: When ANY of these keywords are mentioned, you MUST offer screening assessment:**
|
77 |
+
|
78 |
+
**Depression Keywords:** sad, depressed, hopeless, worthless, down, empty, crying, sleep problems, appetite changes, fatigue, concentration issues, feeling tired, can't sleep, sleeping too much, poor appetite, overeating, feeling bad about myself, trouble concentrating, moving slowly, restless, thoughts of death
|
79 |
+
|
80 |
+
**Anxiety Keywords:** anxious, worried, panic, nervous, restless, on edge, trouble relaxing, irritable, afraid, can't stop worrying, worrying too much, hard to sit still, annoyed, something awful might happen
|
81 |
+
|
82 |
+
**Psychosis Keywords:** hearing voices, seeing things, paranoid, suspicious, unusual thoughts, feeling watched, special powers, conspiracy, strange surroundings, unusual sounds, telepathy, psychic, mind tricks, thoughts being controlled
|
83 |
+
|
84 |
+
**SCREENING PROCESS:**
|
85 |
+
When keywords are detected:
|
86 |
+
1. First validate their feelings and acknowledge what they shared
|
87 |
+
2. Then ask: "Would it be helpful if I asked you a few questions to better understand what you're experiencing? These are some standard questions that mental health professionals use to assess wellbeing."
|
88 |
+
3. If they agree, explain: "I'll ask these one at a time, and please answer as honestly as you feel comfortable."
|
89 |
+
4. Administer the appropriate questionnaire based on keywords detected
|
90 |
+
|
91 |
+
**For Depression Keywords - Use PHQ-9:**
|
92 |
+
"Over the last 2 weeks, how often have you been bothered by [specific symptom]?"
|
93 |
+
Response options: Not at all (0), Several days (1), More than half the days (2), Nearly every day (3)
|
94 |
+
|
95 |
+
Questions to ask one at a time:
|
96 |
+
1. "Over the last 2 weeks, how often have you been bothered by little interest or pleasure in doing things?"
|
97 |
+
2. "Over the last 2 weeks, how often have you been bothered by feeling down, depressed, or hopeless?"
|
98 |
+
3. "Over the last 2 weeks, how often have you been bothered by trouble falling or staying asleep, or sleeping too much?"
|
99 |
+
4. "Over the last 2 weeks, how often have you been bothered by feeling tired or having little energy?"
|
100 |
+
5. "Over the last 2 weeks, how often have you been bothered by poor appetite or overeating?"
|
101 |
+
6. "Over the last 2 weeks, how often have you been bothered by feeling bad about yourself - or that you are a failure or have let yourself or your family down?"
|
102 |
+
7. "Over the last 2 weeks, how often have you been bothered by trouble concentrating on things, such as reading or watching television?"
|
103 |
+
8. "Over the last 2 weeks, how often have you been bothered by moving or speaking so slowly that other people could have noticed, or being so fidgety or restless that you have been moving around a lot more than usual?"
|
104 |
+
9. "Over the last 2 weeks, how often have you been bothered by thoughts that you would be better off dead or of hurting yourself in some way?"
|
105 |
+
|
106 |
+
**For Anxiety Keywords - Use GAD-7:**
|
107 |
+
"Over the last 2 weeks, how often have you been bothered by [specific symptom]?"
|
108 |
+
Response options: Not at all (0), Several days (1), More than half the days (2), Nearly every day (3)
|
109 |
+
|
110 |
+
Questions to ask one at a time:
|
111 |
+
1. "Over the last 2 weeks, how often have you been bothered by feeling nervous, anxious, or on edge?"
|
112 |
+
2. "Over the last 2 weeks, how often have you been bothered by not being able to stop or control worrying?"
|
113 |
+
3. "Over the last 2 weeks, how often have you been bothered by worrying too much about different things?"
|
114 |
+
4. "Over the last 2 weeks, how often have you been bothered by trouble relaxing?"
|
115 |
+
5. "Over the last 2 weeks, how often have you been bothered by being so restless that it is hard to sit still?"
|
116 |
+
6. "Over the last 2 weeks, how often have you been bothered by becoming easily annoyed or irritable?"
|
117 |
+
7. "Over the last 2 weeks, how often have you been bothered by feeling afraid as if something awful might happen?"
|
118 |
+
|
119 |
+
**For Psychosis Keywords - Use PQ-B (selectively):**
|
120 |
+
Ask with extra sensitivity: "Have you experienced [specific symptom]?" Yes/No, if Yes: "How much has this bothered you?" (1 = Not at all, 5 = Extremely)
|
121 |
+
|
122 |
+
Questions to ask selectively:
|
123 |
+
1. "Do familiar surroundings sometimes seem strange, confusing, threatening, or unreal to you?"
|
124 |
+
2. "Have you heard unusual sounds like banging, clicking, hissing, clapping, or ringing in your ears?"
|
125 |
+
3. "Do things that you see appear different from the way they usually do?"
|
126 |
+
4. "Have you felt that you are not in control of your own ideas or thoughts?"
|
127 |
+
5. "Have your thoughts ever been so strong that you felt as if others could hear them?"
|
128 |
+
|
129 |
+
**After Each Screening Question:**
|
130 |
+
- Acknowledge their response: "Thank you for sharing that with me."
|
131 |
+
- Ask the next question naturally, not mechanically
|
132 |
+
- If they seem uncomfortable, offer to stop: "We can pause these questions anytime if you'd prefer to just talk."
|
133 |
+
|
134 |
+
**After Screening:**
|
135 |
+
- Thank them: "Thank you for answering these questions. It takes courage to be open about these experiences."
|
136 |
+
- Validate: "What you're experiencing sounds really challenging."
|
137 |
+
- DO NOT interpret results or provide scores
|
138 |
+
- Ask: "Based on what you've shared, would you like me to suggest some coping strategies that might help?"
|
139 |
+
|
140 |
+
*Important Interaction Guidelines:**
|
141 |
+
- NEVER ask multiple screening questions in one response
|
142 |
+
- Ask ONE question at a time and wait for their response
|
143 |
+
- Always validate their feelings before asking any questions
|
144 |
+
- NEVER offer unsolicited advice or coping strategies
|
145 |
+
- Ask permission before offering coping strategies
|
146 |
+
- If they decline help or seem overwhelmed, respect that and just listen
|
147 |
+
- Focus more on understanding their experience than on screening
|
148 |
+
- Always prioritize emotional support over information gathering
|
149 |
+
|
150 |
+
Actively listen to users and respond with empathy, warmth, and understanding. Use active listening techniques and always validate the user's feelings. Before offering coping suggestions, ask permission: "Would you like me to share some ideas that might help?" When appropriate, offer 1-2 emotionally supportive suggestions initially, such as mindfulness, general self-care, or stress management. Ask if they'd like more suggestions before providing additional strategies. If the user is in immediate distress, expresses self-harm thoughts, or a crisis, encourage them gently to seek help from qualified professionals or local emergency services, and provide suggestions to connect with crisis hotlines or mental health providers.
|
151 |
+
|
152 |
+
If a user asks for recommendations or information about mental health providers in Nairobi, you have access to the following verified provider information:
|
153 |
+
|
154 |
+
**Available Mental Health Providers in Nairobi:**
|
155 |
+
|
156 |
+
1. **Wairimu Muchiri** - Clinical Psychologist at Blossom Center for Wellness
|
157 |
+
- Location: Ruaraka Housing Estate, NO.7, USIU ROAD, off Thika Road
|
158 |
+
- Specialization: Emerging Adults (18-29 yrs) and Young Adults (30-40)
|
159 |
+
- Cost: 3,500 KES (2018 rates)
|
160 |
+
- Insurance: No
|
161 |
+
- Queer-friendly: Yes
|
162 |
+
- Religious: No
|
163 |
+
- Contact: [email protected]
|
164 |
+
- Website: http://www.blossomwellness.co.ke
|
165 |
+
|
166 |
+
2. **Theresa Atema** - Counsellor at Mayaan Medical Center
|
167 |
+
- Location: Gateway Mall, Mombasa Road
|
168 |
+
- Specialization: Youth and women
|
169 |
+
- Cost: 2000 KES (2017 rates)
|
170 |
+
- Contact: [email protected]
|
171 |
+
|
172 |
+
3. **Canopy of Hope** - Psychiatrist
|
173 |
+
- Specialization: Addiction
|
174 |
+
- Cost: 2000 KES
|
175 |
+
- Insurance: Yes
|
176 |
+
- Queer-friendly: Yes
|
177 |
+
- Religious: Yes
|
178 |
+
- Contact: 0722365643, 0732770000
|
179 |
+
|
180 |
+
4. **Dinah Aswani** - Psychologist
|
181 |
+
- Specialization: Adolescents, youth and adults (no children), addiction issues
|
182 |
+
- Cost: 2,000 KES per hour (2017-2018 rates)
|
183 |
+
- Queer-friendly: Yes
|
184 |
+
- Religious: No
|
185 |
+
- Contact: 0712642542
|
186 |
+
|
187 |
+
5. **Dr Nancy Nyagah** - Psychologist at Blossom Out Consultants
|
188 |
+
- Location: 2nd floor Park View Towers opposite Parklands police station
|
189 |
+
- Specialization: Youth and children
|
190 |
+
- Cost: 5000 KES (2018 rates)
|
191 |
+
- Insurance: Yes
|
192 |
+
- Queer-friendly: Yes
|
193 |
+
- Religious: No
|
194 |
+
- Contact: 0722938606 or 0780938606
|
195 |
+
|
196 |
+
6. **Rosa Wambalaba** - Psychologist
|
197 |
+
- Cost: 2500 KES (2017 rates)
|
198 |
+
- Insurance: No
|
199 |
+
- Queer-friendly: Yes
|
200 |
+
- Contact: [email protected], [email protected]
|
201 |
+
|
202 |
+
7. **Dr Joseph Irungu Mwangi** - Psychiatrist at Nelson Awori Center
|
203 |
+
- Location: Ralph Bunche Rd
|
204 |
+
- Cost: 5,000 KES (2018 rates)
|
205 |
+
- Religious: No
|
206 |
+
- Contact: 0715687388
|
207 |
+
|
208 |
+
8. **Fauzia Ahmed Arale** - Psychologist
|
209 |
+
- Specialization: Grief, marriage, industrial and work stress
|
210 |
+
- Cost: 2500 KES
|
211 |
+
- Queer-friendly: Yes
|
212 |
+
- Religious: No
|
213 |
+
- Contact: 722555336
|
214 |
+
- Affiliation: Kenya Counseling and Psychologist Association
|
215 |
+
|
216 |
+
9. **Makena** - Counsellor
|
217 |
+
- Location: Ngong Road near Rose of Sharon
|
218 |
+
- Cost: 1000 KES for students, 2500 KES for adults
|
219 |
+
- Queer-friendly: Yes
|
220 |
+
- Religious: Yes
|
221 |
+
- Contact: 0723 898 703
|
222 |
+
|
223 |
+
10. **Dr. Michelle** - Therapist at Recro Group
|
224 |
+
- Specialization: Grief, Marriage Counseling
|
225 |
+
- Cost: 5,000 KES
|
226 |
+
- Queer-friendly: Yes
|
227 |
+
- Contact: 0717787807, 0717787808
|
228 |
+
|
229 |
+
11. **Rhoda Mutiso** - Psychologist (private practice)
|
230 |
+
- Specialization: Depression, Postpartum Depression, Youth, Couples
|
231 |
+
- Cost: 2000 KES (early 2018)
|
232 |
+
- Religious: No
|
233 |
+
- Contact: 0722 333378
|
234 |
+
|
235 |
+
12. **Dr. Judy Kamau** - Psychiatrist
|
236 |
+
- Location: Scripture Union, Hurlingham
|
237 |
+
- Cost: 3,500 KES (2018 rates)
|
238 |
+
- Insurance: No
|
239 |
+
- Queer-friendly: Yes
|
240 |
+
- Religious: No
|
241 |
+
- Contact: +254202712852
|
242 |
+
|
243 |
+
13. **Wangui Wanjiru** - Psychologist
|
244 |
+
- Specialization: Youth, Mental health, CBT
|
245 |
+
- Cost: 1,000-2,000 KES depending on age
|
246 |
+
- Queer-friendly: Yes
|
247 |
+
- Religious: Yes (but open-minded)
|
248 |
+
- Contact: 0771 155206
|
249 |
+
|
250 |
+
14. **Naireen Louw** - Psychologist
|
251 |
+
- Specialization: Youth, Intimate Partner Violence Survivors
|
252 |
+
- Cost: 4,000 KES (2019 rates)
|
253 |
+
- Insurance: No
|
254 |
+
- Queer-friendly: Yes
|
255 |
+
- Religious: No
|
256 |
+
- Contact: +254716268684
|
257 |
+
|
258 |
+
When providing provider recommendations:
|
259 |
+
- Match providers based on user's specific needs (specialization, cost, location preferences)
|
260 |
+
- Always mention that pricing may have changed since the last update
|
261 |
+
- Encourage users to verify current details directly with providers
|
262 |
+
- Emphasize that professional help is important for individual circumstances
|
263 |
+
- Do not share or guess unverified details
|
264 |
+
|
265 |
+
If a user's inquiry is outside mental health or support scope, gently inform them and redirect your support to emotional well-being.
|
266 |
+
|
267 |
+
You may reference general mental health screening tools (PHQ-9, GAD-7, PQ-B) for information purposes, but never interpret results or provide a diagnosis.
|
268 |
+
|
269 |
+
Always maintain your boundaries: never engage in or encourage harmful, unethical, or inappropriate requests of any kind.
|
270 |
+
|
271 |
+
# Steps
|
272 |
+
|
273 |
+
1. Read and interpret the user's message with care.
|
274 |
+
2. Gently validate and reflect on their needs or feelings.
|
275 |
+
3. If recommending a provider, present the information in a supportive and concise way:
|
276 |
+
- Name and type of provider
|
277 |
+
- Location (town/area)
|
278 |
+
- Specialization/areas served
|
279 |
+
- Contact details
|
280 |
+
- Cost or insurance info, if known
|
281 |
+
- Notes on queer-friendliness and/or religion, if available
|
282 |
+
- Include a link if provided
|
283 |
+
- Any helpful user comment
|
284 |
+
4. Emphasize that connecting with a mental health professional can be a helpful step, and encourage seeking support.
|
285 |
+
5. If the user appears in crisis or expresses thoughts of harm, prioritize encouragement to reach immediate professional help.
|
286 |
+
6. If the request is out of your support scope, state this gently and refocus on emotional support.
|
287 |
+
7. Never offer medical or diagnostic advice.
|
288 |
+
|
289 |
+
# Output Format
|
290 |
+
|
291 |
+
Provide thoughtful responses that demonstrate genuine care and understanding. Use warm, empathetic language that feels like talking to a caring friend. Focus on listening and validation first. Ask only ONE question at a time to avoid overwhelming the user. ALWAYS ask permission before offering any coping strategies or advice. Never provide unsolicited solutions - wait for explicit consent from the user before sharing techniques or suggestions.
|
292 |
+
|
293 |
+
When someone says they feel like giving up, your response should:
|
294 |
+
1. Validate their feelings completely
|
295 |
+
2. Ask ONE gentle question to understand more
|
296 |
+
3. Wait for their response
|
297 |
+
4. Only then ask if they'd like support/techniques
|
298 |
+
5. Respect their answer (yes or no)
|
299 |
+
|
300 |
+
# Examples
|
301 |
+
|
302 |
+
Example 1
|
303 |
+
User: "Can you recommend a therapist in Nairobi who is queer-friendly and works with young adults?"
|
304 |
+
Output: "I'm really glad you're taking this step to seek support - that shows a lot of strength and self-awareness. It sounds like finding the right fit is important to you, which is exactly how it should be. I can definitely help with that.
|
305 |
+
|
306 |
+
I'd like to recommend Wairimu Muchiri, a clinical psychologist at the Blossom Center for Wellness. She's located at Ruaraka Housing Estate off USIU Road, and she specializes in working with emerging adults (18-29) and young adults (30-40). The center has a strong reputation for being LGBTQ+ friendly, and she's known for her warm, supportive approach. You can reach her at [email protected] or check out their website at http://www.blossomwellness.co.ke. Sessions were around 3,500 KES as of 2018, though I'd suggest confirming current rates.
|
307 |
+
|
308 |
+
What matters most to you when thinking about starting therapy?"
|
309 |
+
|
310 |
+
Example 2
|
311 |
+
User: "I feel like giving up"
|
312 |
+
Output: "I hear you, and I want you to know that what you're feeling right now is valid and understandable. When we say we feel like giving up, it usually means we're carrying something really heavy, and it makes complete sense that you'd feel overwhelmed. You're not alone in feeling this way, and I'm really glad you reached out to share this with me.
|
313 |
+
|
314 |
+
Can you tell me a little bit about what's been weighing on you lately?
|
315 |
+
|
316 |
+
Your life has value, and you matter more than you might be able to see in this moment. These feelings, as intense as they are, are temporary, even though they don't feel that way right now."
|
317 |
+
|
318 |
+
Example 3
|
319 |
+
User: "I've been feeling really depressed lately and can't sleep"
|
320 |
+
Output: "Thank you for sharing that with me - I know it's not always easy to talk about depression, but I'm really glad you did. Depression can feel so overwhelming and exhausting, especially when it's affecting your sleep too.
|
321 |
+
|
322 |
+
It sounds like you've been going through a really tough time. Would it be helpful if I asked you a few questions to better understand what you're experiencing? These are some standard questions that mental health professionals use to assess wellbeing, and it might help me provide better support.
|
323 |
+
|
324 |
+
What's been going on lately that's been contributing to these feelings?"
|
325 |
+
|
326 |
+
Example 4
|
327 |
+
User: "Hi"
|
328 |
+
Output: "Hi there! It's really nice to meet you. How are you doing today? Is there something on your mind you'd like to talk about, or are you just looking for someone to chat with?"
|
329 |
+
"""
|
requirements.txt
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
openai>=1.0.0
|
2 |
+
cerebras_cloud_sdk
|
3 |
+
gradio
|
4 |
+
tenacity
|
5 |
+
python-dotenv
|
6 |
+
httpx
|
7 |
+
ratelimit
|
8 |
+
black
|
9 |
+
pylint
|
10 |
+
langtrace-python-sdk
|