The inspiration for this project came from the desire to create a tool that offers proactive mental wellness support. Instead of a user always having to seek out help, what if an application could gently offer a moment of calm during a busy day? This "Digital Companion" is a project that showcases how modern web technologies can be used to build applications with a positive impact on mental well-being.
We will build a chatbot that can:
- Listen: Accept a user's message about their current state of mind.
- Understand: Use spaCy to recognize the user's intent (e.g., feeling stressed or anxious).
- Act: Immediately suggest a relevant mindfulness exercise.
- Remind: Asynchronously schedule a series of gentle reminders for short mindfulness breaks throughout the day using Celery.
This project is perfect for developers interested in the practical application of NLP, asynchronous task scheduling, and modern backend development.
Prerequisites
- Python 3.7+ and an understanding of virtual environments.
- Basic knowledge of building APIs.
- Docker and Docker Compose for easy setup of our message broker.
Understanding the Problem
Many mental wellness apps are reactive; they require the user to open the app and actively seek out a meditation or exercise. The challenge is to create a more proactive system that can intelligently schedule reminders without being intrusive.
This is where our tech stack shines:
- FastAPI: For creating a high-performance API that can handle incoming user messages efficiently.
- spaCy: To perform lightweight yet powerful intent recognition on user messages, allowing our chatbot to understand the user's needs.
- Celery: To manage asynchronous tasks. When a user expresses stress, we don't want our main application to be blocked while scheduling future reminders. Celery, with a message broker like Redis, handles this in the background.
- Celery Beat: A scheduler for Celery that allows us to trigger periodic tasks, like sending daily mindfulness tips or checking in with the user.
Prerequisites
Before we start coding, let's set up our environment.
First, create a project directory and a virtual environment:
mkdir digital-companion && cd digital-companion
python3 -m venv venv
source venv/bin/activate
Next, create a requirements.txt file with the necessary libraries:
fastapi
uvicorn
celery[redis]
spacy
requests
Install these dependencies:
pip install -r requirements.txt
We also need to download a spaCy language model. We'll use a small English model for this project:
python -m spacy download en_core_web_sm
Finally, we'll use Docker to run Redis, our message broker. Create a docker-compose.yml file:
version: '3.8'
services:
redis:
image: redis:6.2-alpine
ports:
- "6379:6379"
Start Redis by running:
docker-compose up -d
Your Redis instance is now running in the background.
Step 1: Setting up the FastAPI Application
Let's create the basic structure for our FastAPI application.
What we're doing
We'll create a main.py file that will house our API. We'll start with a simple endpoint to make sure everything is working correctly.
Implementation
Create a file named main.py:
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Welcome to your Digital Companion"}```
### How it works
This code snippet initializes a FastAPI application and defines a single endpoint for the root URL (`/`). When a GET request is made to this URL, it returns a simple JSON response.
To run the application, use uvicorn:
```bash
uvicorn main:app --reload
You should see output indicating that the server is running. You can now visit http://127.0.0.1:8000 in your browser and see the welcome message.
Step 2: Configuring Celery
Now, let's integrate Celery for our asynchronous tasks.
What we're doing
We will create a Celery instance and define a simple task. This will involve creating a celery_worker.py file to configure Celery and define our tasks.
Implementation
Create a celery_worker.py file:
# celery_worker.py
from celery import Celery
import time
# Configure Celery
celery_app = Celery(
'tasks',
broker='redis://localhost:6379/0',
backend='redis://localhost:6379/0'
)
@celery_app.task
def send_mindfulness_reminder(user_id: int, message: str):
"""
A mock function to simulate sending a mindfulness reminder.
In a real application, this would send an email, a push notification, etc.
"""
print(f"Sending reminder to user {user_id}: '{message}'")
time.sleep(5) # Simulate a delay
return f"Reminder sent to user {user_id}"
celery_app.conf.beat_schedule = {
'send-daily-tip': {
'task': 'celery_worker.send_mindfulness_reminder',
'schedule': 86400.0, # Every 24 hours
'args': (0, "Here is your daily mindfulness tip: Take a moment to notice three things you can see around you.")
},
}
celery_app.conf.timezone = 'UTC'```
### How it works
We create a Celery instance, specifying our Redis instance as both the message broker and the result backend. We then define a task `send_mindfulness_reminder` using the `@celery_app.task` decorator. We've also configured Celery Beat to send a daily tip every 24 hours.
To run the Celery worker, open a new terminal and run:
```bash
celery -A celery_worker.celery_app worker --loglevel=info
To run Celery Beat, open another terminal:
celery -A celery_worker.celery_app beat --loglevel=info
Step 3: Intent Recognition with spaCy
Now for the "smart" part of our chatbot. We'll use spaCy to understand the user's intent.
What we're doing
We'll create a simple intent recognition function that can identify if a user's message indicates stress.
Implementation
Create a new file nlp.py:
# nlp.py
import spacy
# Load the spaCy model
nlp = spacy.load("en_core_web_sm")
# Define keywords for different intents
STRESS_KEYWORDS = ["stress", "anxious", "overwhelmed", "worried", "burnout"]
def recognize_intent(text: str):
doc = nlp(text.lower())
for token in doc:
if token.lemma_ in STRESS_KEYWORDS:
return "stress_intent"
return "unknown_intent"
How it works
This function takes a text string, processes it with spaCy, and checks if any of the lemmatized words match our predefined stress keywords. Lemmatization reduces words to their base form (e.g., "worried" becomes "worry"), making our keyword matching more robust. This is a simple but effective way to perform basic intent recognition.
Putting It All Together
Now let's integrate everything into our FastAPI application.
What we're doing
We'll create a new endpoint in main.py that accepts a user's message, uses our nlp.py module to recognize the intent, and if stress is detected, triggers Celery to schedule a series of mindfulness reminders.
Implementation
Update main.py:
# main.py
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
from celery_worker import send_mindfulness_reminder
from nlp import recognize_intent
from datetime import timedelta
app = FastAPI()
class Message(BaseModel):
user_id: int
text: str
@app.get("/")
def read_root():
return {"message": "Welcome to your Digital Companion"}
@app.post("/chat")
def chat(message: Message, background_tasks: BackgroundTasks):
intent = recognize_intent(message.text)
if intent == "stress_intent":
# Immediate response
response = {
"message": "I understand you're feeling stressed. Here's a quick exercise: Take a deep breath in for 4 seconds, hold for 4, and exhale for 6. Repeat this three times."
}
# Schedule follow-up reminders
reminders = [
("In 1 hour, take a moment to stretch.", timedelta(hours=1)),
("In 3 hours, how about a 5-minute walk?", timedelta(hours=3)),
("Before you end your day, reflect on one positive thing that happened.", timedelta(hours=6))
]
for msg, delay in reminders:
send_mindfulness_reminder.apply_async(args=[message.user_id, msg], countdown=delay.total_seconds())
return response
else:
return {"message": "I'm here to help with mindfulness. Try telling me if you're feeling stressed."}
How it works
- We define a
Messagemodel to structure the incoming request body. - The
/chatendpoint receives a user's message anduser_id. - It calls
recognize_intentto determine the user's state. - If the intent is
stress_intent, it provides an immediate mindfulness exercise. - Crucially, it then uses
send_mindfulness_reminder.apply_asyncto schedule several follow-up reminders at different intervals using thecountdownargument. These tasks are sent to our Celery worker to be executed in the background, so the API response is immediate.
Security Best Practices
- Input Validation: FastAPI, with Pydantic, automatically validates incoming data. This is a great first line of defense.
- Authentication: In a real-world application, you would protect your endpoints with an authentication mechanism like OAuth2 to ensure only authorized users can interact with the chatbot.
- Rate Limiting: To prevent abuse, consider implementing rate limiting on your API endpoints.
Production Deployment Tips
- Containerization: For a production environment, you would containerize the FastAPI app, the Celery worker, and the Celery Beat scheduler using Docker.
- Environment Variables: Use environment variables for sensitive information like database credentials or API keys, rather than hardcoding them.
- Monitoring: Tools like Flower can be used to monitor your Celery workers and tasks.
Alternative Approaches
- More Advanced NLP: For more sophisticated intent recognition, you could train a custom machine learning model using libraries like Rasa or even leverage large language models (LLMs) for more conversational abilities.
- WebSocket for Real-time Communication: Instead of a simple HTTP endpoint, you could use WebSockets for a more interactive, real-time chat experience.
Conclusion
We've successfully built a "Digital Companion" chatbot that combines a FastAPI backend, Celery for asynchronous task scheduling, and spaCy for natural language processing. This project is a great starting point for anyone looking to build intelligent, responsive, and impactful web applications.
The key takeaway is how these powerful tools can be integrated to solve a real-world problem. By offloading tasks to a background worker, our API remains fast and responsive, providing a much better user experience.
Next steps for readers:
- Expand the number of intents our chatbot can recognize.
- Integrate a database to store user preferences and conversation history.
- Implement a more sophisticated method for sending reminders, such as email or push notifications.