Staying consistent with fitness routines is challenging, especially for beginners. Gyms can be intimidating, and personal trainers are not always available or affordable. The Fitness Assistant solves this by providing a conversational AI that gives personalised exercise recommendations, step-by-step instructions, and equipment-aware alternatives — on demand, through natural language.
Interact with the assistant directly on Telegram: @codeflamer_bot
- Project Overview
- Architecture
- Dataset
- Technologies
- Prerequisites
- Environment Setup
- Running Locally
- Deployment
- API Reference
- Evaluation
The Fitness Assistant is a Retrieval-Augmented Generation (RAG) application. When a user sends a fitness question via Telegram, the system retrieves the most relevant exercise records from a vector database and feeds them as context to a Mistral AI language model, which generates a grounded, accurate response.
The main use cases are:
- Exercise Selection — Recommend exercises based on targeted muscle groups, activity type, or available equipment.
- Exercise Replacement — Suggest suitable alternatives to a given exercise.
- Exercise Instructions — Provide step-by-step guidance on how to perform a specific exercise correctly.
- Equipment Queries — Describe what a piece of equipment looks like, including an image where available.
User (Telegram)
│
▼
Telegram Bot (Railway — Dockerfile.telegram)
│
│ HTTP (internal)
▼
FastAPI Application (Railway)
│
├──► Qdrant Cloud ──► Exercise documents (vector embeddings)
│ retrieval
│
├──► Mistral AI ──► Response generation + LLM-as-judge evaluation
│
└──► PostgreSQL ──► Conversation & feedback storage
│
▼
Grafana Cloud (private monitoring)
Request flow:
- User sends a message to the Telegram bot.
- The bot forwards the query to the FastAPI backend.
- The query is embedded and used to retrieve the top-5 most relevant exercise documents from Qdrant (MMR search).
- Retrieved documents and the user query are passed to Mistral AI, which generates a response.
- The response is evaluated for relevance by a second LLM-as-judge call.
- The conversation, token usage, cost, and relevance score are saved to PostgreSQL.
- The bot sends the response back to the user with thumbs-up / thumbs-down feedback buttons.
The dataset contains 207 exercise records generated with ChatGPT. Each record includes:
| Field | Description |
|---|---|
exercise_name |
Name of the exercise (e.g., Push-Ups, Squats) |
type_of_activity |
General category (e.g., Strength, Mobility, Cardio) |
type_of_equipment |
Equipment required (e.g., Bodyweight, Dumbbells, Kettlebell) |
body_part |
Primary body area targeted (e.g., Upper Body, Core, Lower Body) |
type |
Movement type (e.g., Push, Pull, Hold, Stretch) |
muscle_groups_activated |
Specific muscles engaged (e.g., Pectorals, Triceps, Quadriceps) |
instructions |
Step-by-step instructions for correct form |
image_url |
URL of an equipment/exercise image where available |
The dataset is stored at data/data_clean.csv and is loaded into Qdrant on first startup.
| Component | Technology |
|---|---|
| LLM | Mistral AI (mistral-large-latest) |
| RAG Framework | LangChain |
| Vector Database | Qdrant Cloud |
| Embeddings | FastEmbed — BAAI/bge-large-en-v1.5 |
| API | FastAPI + Uvicorn |
| Database | PostgreSQL (Railway managed) |
| Monitoring | Grafana Cloud (private) |
| Telegram Client | python-telegram-bot |
| Rate Limiting | SlowAPI |
| Hosting | Railway |
| Containerisation | Docker |
| Package Manager | uv |
- Docker and Docker Compose (for local development)
- uv (for running without Docker)
- A Mistral AI API key
- A Telegram Bot token
- A HuggingFace token
- A Qdrant Cloud cluster and API key (free tier)
Copy the example environment file and fill in your credentials:
cp .env.example .envEdit .env with your values:
# API Keys
MISTRAL_API_KEY=your_mistral_api_key
TELEGRAM_TOKEN=your_telegram_bot_token
HF_TOKEN=your_huggingface_token
# FastAPI
FASTAPI_URL=http://127.0.0.1:8000
APP_PORT=8000
# PostgreSQL
POSTGRES_HOST=your_postgres_host
POSTGRES_DB=your_database_name
POSTGRES_USER=your_username
POSTGRES_PASSWORD=your_strong_password
POSTGRES_PORT=5432
# Qdrant
QDRANT_URL=https://your-cluster.qdrant.io
QDRANT_API_KEY=your_qdrant_api_key
# Data
DATA_PATH=./data/data_clean.csvNever commit your
.envfile. It is already listed in.gitignore.
pip install uvuv venv
# Windows
source .venv/Scripts/activate
# macOS / Linux
source .venv/bin/activateuv pip install -r requirements.txtdocker-compose up postgres -dUpdate your .env to use localhost:
POSTGRES_HOST=localhostuv run uvicorn api.main:app --reloadThe database tables are created automatically on first startup.
uv run python clients/telegram_bot.pyOpen Telegram, find your bot, and send a fitness question to test it.
uv run python test.pyThe application is hosted on Railway across two services backed by managed infrastructure.
| Service | Platform | Notes |
|---|---|---|
| FastAPI app | Railway | Built from Dockerfile |
| Telegram bot | Railway | Built from Dockerfile.telegram |
| PostgreSQL | Railway managed | Connected via internal variables |
| Qdrant | Qdrant Cloud free | Connected via QDRANT_URL |
| Monitoring | Grafana Cloud | Private, reads from Railway Postgres |
1. Create a Railway project and connect your GitHub repo.
2. Add a managed PostgreSQL database:
- In your project → "+ New Service" → "Database" → "PostgreSQL"
- Railway generates connection variables automatically.
3. Configure the FastAPI app service variables:
MISTRAL_API_KEY=your_mistral_api_key
HF_TOKEN=your_huggingface_token
DATA_PATH=./data/data_clean.csv
QDRANT_URL=https://your-cluster.qdrant.io
QDRANT_API_KEY=your_qdrant_api_key
POSTGRES_HOST=${{Postgres.PGHOST}}
POSTGRES_PORT=${{Postgres.PGPORT}}
POSTGRES_DB=${{Postgres.PGDATABASE}}
POSTGRES_USER=${{Postgres.PGUSER}}
POSTGRES_PASSWORD=${{Postgres.PGPASSWORD}}
4. Add the Telegram bot as a second Railway service:
- "+ New Service" → "GitHub Repo" → same repo
- In Settings → Build → Dockerfile Path set to:
Dockerfile.telegram - Set variables on the bot service:
TELEGRAM_TOKEN=your_telegram_bot_token
FASTAPI_URL=http://${{app.RAILWAY_PRIVATE_DOMAIN}}:8000
The bot communicates with the FastAPI app over Railway's private internal network — the API is never exposed publicly.
5. Set up Qdrant Cloud:
- Create a free cluster at cloud.qdrant.io
- Copy the cluster URL and API key into the Railway app service variables above.
If you need to create a new Telegram bot:
- Open Telegram and search for @BotFather
- Send
/newbotand follow the prompts - BotFather will give you a token — add it as
TELEGRAM_TOKENin your Railway bot service variables - Optionally send
/setdescriptionand/setuserpicto customise the bot's profile
The API is not publicly exposed. All interaction goes through the Telegram bot. The endpoints below are for local development only.
GET http://localhost:8000/api/v1/health{ "status": "ok", "version": "1.0.0" }curl -X POST http://localhost:8000/api/v1/chat \
-H "Content-Type: application/json" \
-d '{"query": "I want core exercises that also help my back", "source": "cmd", "user": "demo"}'Example response:
{
"answer": {
"message": "Based on your goal, here are some core exercises that also strengthen your back:\n\n### Superman Exercise\n- **Muscles:** Lower Back, Glutes, Hamstrings\n- **Instructions:** Lie face down, extend arms forward. Lift arms, chest, and legs simultaneously, hold briefly, then lower.",
"image_urls": [],
"conversation_id": "1f440215-b880-4d4e-88ac-6f21804d1583"
},
"source": "cmd"
}curl -X POST http://localhost:8000/api/v1/feedback \
-H "Content-Type: application/json" \
-d '{
"conversation_id": "1f440215-b880-4d4e-88ac-6f21804d1583",
"feedback": "relevant",
"source": "cmd"
}'Accepted feedback values: "relevant" or "not_relevant".
All endpoints are rate-limited to 2 requests per minute per IP address.
Retrieval quality was measured using Hit Rate and Mean Reciprocal Rank (MRR) on a set of generated ground-truth question–document pairs.
| Approach | Hit Rate | MRR |
|---|---|---|
| Optimised (MMR, bge-large-en-v1.5) | 0.726 | 0.71 |
Each generated response is automatically evaluated for relevance by a second Mistral AI call acting as a judge. Results across the evaluation set:
| Label | Count | Percentage |
|---|---|---|
| RELEVANT | 186 | 0.9 |
| NON_RELEVANT | 21 | 0.1 |
| Total | 207 | 100% |
Feedback collected from real interactions via the thumbs-up / thumbs-down buttons:
| Signal | Count |
|---|---|
| Thumbs up (relevant) | |
| Thumbs down (not relevant) |
