The voice AI service for the ELDORA ecosystem — a four-layer pipeline that takes raw audio from DoraBot, transcribes it with Faster-Whisper, generates an empathic response via Ollama, classifies safety intents with local rules, analyzes emotion via Ollama, synthesizes speech with Edge-TTS, and returns a cached audio URL with full latency telemetry.
Eldora-AI-Pipeline is the voice intelligence engine of the ELDORA eldercare ecosystem. It runs as a standalone FastAPI microservice, called exclusively by the ELDORA backend whenever DoraBot uploads a voice recording from an elderly user. The pipeline converts raw audio into a meaningful spoken response and a structured safety signal — all within a strict latency budget.
Eldora-AI-Pipeline's role in the ELDORA ecosystem: "The voice — listening to what the elder says, understanding what they need, and speaking back with warmth and clarity."
| Runtime | Python 3.11 |
| Framework | FastAPI + Uvicorn |
| STT | Faster-Whisper (CTranslate2-optimized Whisper) — local, CPU or CUDA |
| LLM | Ollama API — Qwen2.5:1.5b (configurable, optional API key) |
| Intent | Local rule-based classifier — bilingual (English + Bahasa Indonesia) |
| Emotion | Ollama API — runs in parallel with LLM response generation |
| TTS | Microsoft Edge-TTS — MD5-keyed audio file cache |
| Deployment | Docker / Railway |
Eldora-AI-Pipeline is the voice intelligence layer called by the backend:
ELDORA Ecosystem
├── 🛡️ DoraShield — Fall detection wearable (ESP32)
├── 🤖 DoraBot — AI voice companion (ESP32-S3) — uploads raw audio
├── ☁️ Eldora-Backend — API server — calls this pipeline, routes results
├── 🧠 Eldora-AI-Pipeline — Voice AI service (this repo) — STT + LLM + TTS
└── 📱 Eldora-Mobile — Caregiver app — receives FCM alerts triggered by this pipeline
- 🎙️ Local Speech-to-Text — Faster-Whisper transcribes audio on-device; auto-detects CUDA and falls back gracefully to CPU int8 mode
- 💬 LLM Conversational Response — Ollama API generates a warm, concise (≤ 2 sentences) response tailored for elderly users
- 🔍 Bilingual Intent Classification — local rule-based engine detects safety intents in both English and Bahasa Indonesia with no external call
- 😊 Emotion Analysis — Ollama API classifies the elder's emotional state; runs in parallel with response generation to minimize latency
- 🔊 Edge-TTS Speech Synthesis — Microsoft Edge-TTS produces natural MP3 audio with MD5-keyed caching to avoid re-synthesis of identical responses
- 📡 Per-Request Voice Configuration — caller sets language, TTS voice, speech rate, and enabled/disabled via HTTP headers on each request
- 📊 Structured Latency Telemetry — every response includes a per-stage latency breakdown (audio receive, STT, AI+emotion, TTS, total)
- 🔄 Graceful Degradation — if Ollama is unavailable, the service continues with local fallback responses and still performs STT + TTS
- 🐳 Docker-First Deployment — single
Dockerfilehandles all system dependencies includingffmpeg; GPU acceleration supported via NVIDIA Container Toolkit
| Layer | Technology | Purpose |
|---|---|---|
| Framework | FastAPI + Uvicorn | Async HTTP API server |
| Language | Python 3.11 | Pipeline implementation |
| STT | faster-whisper ≥1.0 | CTranslate2-optimized Whisper for local transcription |
| LLM / Emotion | httpx → Ollama API | Async calls to Qwen2.5 for response generation and emotion analysis |
| TTS | edge-tts ≥6.1 | Microsoft Edge neural TTS voices, MP3 output |
| Validation | Pydantic v2 | Request/response models |
| HTTP Client | httpx | Async Ollama API calls |
| Audio | ffmpeg (system) | Audio decoding for Faster-Whisper input |
| Containerization | Docker + Docker Compose | Reproducible deployment with optional GPU passthrough |
| Deployment | Railway (railway.json) |
Cloud deployment via Dockerfile builder |
Eldora-AI-Pipeline/
│
├── 📄 main.py # Complete FastAPI service — all four pipeline layers,
│ # API endpoints, VoiceProcessor class, audio cache server
│
├── 📄 test_suite.py # Evaluation client — Levenshtein STT accuracy,
│ # 2-sentence constraint check, latency SLA tests
├── 📄 test_cases.json # 10 representative elderly voice scenario test cases
│
├── 📄 convert_hf_to_gguf.py # Utility — converts HuggingFace PyTorch weights
│ # to GGUF format for Ollama import (for custom LoRA models)
│
├── 📄 requirements.txt # Python dependencies (fastapi, uvicorn, faster-whisper,
│ # edge-tts, httpx, pydantic, python-multipart)
│
├── 📄 Dockerfile # python:3.11-slim + ffmpeg + git; exposes port 8000
├── 📄 docker-compose.yml # Compose with audio_cache + HF model cache volumes,
│ # optional NVIDIA GPU reservation
├── 📄 railway.json # Railway deployment config — Dockerfile builder,
│ # /health check, ON_FAILURE restart policy
│
├── 📄 .env.example # All configurable environment variables with defaults
└── 📁 test_audio/ # Holds .wav files for test_suite.py (git-ignored)
In plain English:
DoraBot uploads audio → backend forwards raw bytes to POST /api/process-audio
Layer 0 — STT: Faster-Whisper transcribes audio → transcript + language + confidence
Layer 1 — LLM: Ollama generates warm 1–2 sentence response ┐ run in parallel
Layer 2 — Intent: Local rules classify safety intent ├ (Layer 2 is sync, no I/O)
Layer 3 — Emotion: Ollama classifies emotion state ┘
TTS — Synthesis: Edge-TTS synthesizes MP3 → stored in audio_cache/
Return: { transcript, response_message, audio_url, emotion, intent, latency }
Faster-Whisper is a CTranslate2-optimized reimplementation of OpenAI Whisper. It runs entirely locally — no audio ever leaves the server.
| Setting | Behavior |
|---|---|
STT_DEVICE=cuda |
Runs with float16 compute — fastest, requires NVIDIA GPU |
STT_DEVICE=cpu |
Runs with int8, 4 threads — slower but runs everywhere |
STT_DEVICE=auto |
Tries CUDA first, falls back to CPU automatically |
STT_MODEL_SIZE |
tiny / base / small / medium / large — trades speed for accuracy |
Language detection runs automatically. If detected language is not English or Indonesian, it defaults to English. Transcriptions with confidence < 0.7 are flagged in logs.
The pipeline calls the configured Ollama endpoint with a system prompt that constrains responses to exactly 1–2 sentences to prevent cognitive overload for elderly users. Default model: qwen2.5:1.5b (1.5 billion parameters, fast on modest hardware).
System: "You are Eldora, a warm voice companion for elderly users.
Respond in EXACTLY 1 or 2 short sentences."
Temperature: 0.4 | num_predict: 50 | timeout: 15s
If Ollama is unreachable, the service falls back to local keyword-matched responses covering all safety and care scenarios — the pipeline never silently fails.
Runs synchronously with no external call. Detects six safety/care intents in both English and Bahasa Indonesia:
| Intent | Example Triggers | Notifies Caregiver |
|---|---|---|
call_family |
"call my family", "panggil keluarga" | ✅ + call flag |
fall_detected |
"i fell", "fallen", "jatuh" | ✅ |
help_request |
"help", "emergency", "tolong", "sakit" | ✅ |
water_request |
"water", "drink", "minum", "haus" | ✅ |
medicine_request |
"medicine", "pill", "obat" | ✅ |
emotional_support |
"lonely", "scared", "kesepian" | ❌ (comfort response only) |
general |
(catch-all) | ❌ |
Runs concurrently with Layer 1 using asyncio.gather so it adds no serial latency. The same Ollama model classifies the transcript into five states: calm, happy, sad, anxious, distressed. Temperature: 0.1 for deterministic classification.
The emotion result is returned to the backend, which stores it in TrVoiceEmotionLog to power the caregiver's analytics dashboard.
Edge-TTS generates MP3 audio using Microsoft's neural TTS voices. Generated files are stored in audio_cache/ with an MD5-keyed filename (voice + rate + text) — identical responses are served from cache without re-synthesis.
Default voices:
- English:
en-US-JennyNeural - Indonesian:
id-ID-GadisNeural
TTS voice, rate, and language can be overridden per-request via X-Voice-TTS-Voice, X-Voice-Rate, and X-Voice-Language headers — the backend sets these from the per-device voice configuration stored in MsDeviceVoiceConfig.
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/health |
GET | None | Service health — STT model status, Ollama connectivity, active device |
/api/process-audio |
POST | None | Full pipeline — raw audio bytes in, full response out |
/api/test-tts |
POST | None | TTS preview — synthesize text to MP3 without STT or LLM |
/api/audio/{filename} |
GET | None | Serve a cached MP3 audio file from audio_cache/ |
Request: raw audio bytes in the body (any format ffmpeg can decode, minimum 1000 bytes)
Voice config headers (all optional — fall back to server defaults):
| Header | Example | Description |
|---|---|---|
X-Voice-Language |
en |
Target language for STT hint and TTS voice pack selection |
X-Voice-TTS-Voice |
en-US-AriaNeural |
Override TTS voice for this request |
X-Voice-Rate |
-5% |
Speech rate adjustment |
X-Voice-Enabled |
true |
Set false to skip TTS synthesis entirely |
Response:
{
"text": "Eldora, I need some water.",
"message": "Got it, I will let your caregiver know that you need some water.",
"audio_url": "/api/audio/tts_a3f2c1d9e4b8.mp3",
"language": "en",
"confidence": 0.94,
"response_source": "ollama_qwen",
"emotion": { "state": "anxious", "confidence": 0.82 },
"intent": { "name": "water_request", "confidence": 0.78, "notify_caregiver": true, "call_family": false },
"latency_ms": 1243.5,
"latency": {
"audio_ms": 4.1,
"stt_ms": 680.2,
"ai_ms": 490.3,
"tts_ms": 68.9,
"total_ms": 1243.5
}
}Create a .env file in the project root (copy from .env.example):
PORT=8000
LOG_LEVEL=INFO
# Faster-Whisper STT
STT_MODEL_SIZE=base # tiny | base | small | medium | large
STT_DEVICE=auto # auto | cuda | cpu
# Ollama LLM + Emotion
OLLAMA_API_URL=http://localhost:11434/api/chat
OLLAMA_API_KEY= # leave empty if no auth required
OLLAMA_MODEL_NAME=qwen2.5:1.5b
# Default TTS settings (overridable per-request via headers)
VOICE_LANGUAGE=en
VOICE_TTS_VOICE=en-US-JennyNeural
VOICE_TTS_RATE=-10%
VOICE_AUDIO_CACHE_DIR=./audio_cacheℹ️ If
OLLAMA_API_URLis empty or unreachable, the service operates in fallback mode — STT and TTS still work, and local keyword responses are used in place of LLM-generated replies.
- Python 3.11+
- ffmpeg (system dependency for audio decoding)
- Ollama running locally or a hosted Ollama-compatible endpoint
- (Optional) NVIDIA GPU + CUDA for accelerated STT
# 1. Clone the repository
git clone https://github.com/Eldoraaa/eldora-ai-pipeline.git
cd eldora-ai-pipeline
# 2. Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate # Linux / macOS
.\.venv\Scripts\Activate.ps1 # Windows
# 3. Install dependencies
pip install -r requirements.txt
# 4. Copy and fill in environment variables
cp .env.example .env
# Set OLLAMA_API_URL to your Ollama endpoint
# 5. Pull the Ollama model (if running Ollama locally)
ollama pull qwen2.5:1.5b
# 6. Start the service
python main.pyThe service starts at http://localhost:8000.
Swagger UI is available at http://localhost:8000/docs.
On first start, Faster-Whisper downloads the configured model from Hugging Face and caches it locally. Subsequent starts load from cache.
Docker handles all system dependencies (ffmpeg, Python) automatically.
# Build and start
docker compose up --build
# Run in background
docker compose up -d --buildThe compose file maps two volumes for persistence:
./audio_cache— synthesized Edge-TTS MP3 files./.hf_cache— downloaded Faster-Whisper models (avoids re-download on container restart)
GPU acceleration in Docker
The docker-compose.yml includes an NVIDIA GPU reservation block. To use it, install the NVIDIA Container Toolkit on the host. If no GPU or NVIDIA runtime is available, remove the deploy: block from docker-compose.yml and set STT_DEVICE=cpu.
The railway.json configures Railway to build from the Dockerfile and run python main.py as the start command.
# Required env vars on Railway
PORT=8000
STT_MODEL_SIZE=base
STT_DEVICE=cpu
OLLAMA_API_URL=https://your-hosted-ollama.example.com/api/chat
OLLAMA_API_KEY=your-bearer-token-if-required
OLLAMA_MODEL_NAME=qwen2.5:1.5b
VOICE_LANGUAGE=en
VOICE_TTS_VOICE=en-US-JennyNeural
VOICE_TTS_RATE=-10%
VOICE_AUDIO_CACHE_DIR=/app/audio_cacheAfter deploying, set these in the ELDORA backend .env:
VOICE_AUDIO_PROCESSOR_URL=https://your-pipeline.up.railway.app/api/process-audio
VOICE_AUDIO_BASE_URL=https://your-pipeline.up.railway.appThe repository includes an automated evaluation suite (test_suite.py) that tests the running service against test_cases.json (10 representative elderly voice scenarios).
# 1. Place test audio files (1.wav to 10.wav) in test_audio/
# 2. Ensure the service is running at http://localhost:8000
# 3. Run the suite
python test_suite.py| Metric | Description |
|---|---|
| STT Accuracy | Character-level Levenshtein similarity between Faster-Whisper output and ground-truth transcripts |
| Cognitive sentence constraint | Verifies LLM responses contain ≤ 2 sentences (prevents overload for elderly users) |
| Latency SLA | Measures per-stage latency against a 1.5 s total latency target |
The repository includes convert_hf_to_gguf.py to convert a fine-tuned HuggingFace model to GGUF format for Ollama import.
# 1. Convert merged PyTorch weights to GGUF
python convert_hf_to_gguf.py --model-dir ./merged_eldercare_qwen2 --output-file ./eldora_bot.gguf
# 2. Create an Ollama Modelfile
cat > Modelfile << 'EOF'
FROM ./eldora_bot.gguf
SYSTEM "You are Eldora, a supportive voice companion for seniors. Speak warmly, keep answers under 2 sentences."
EOF
# 3. Register in Ollama
ollama create eldora-bot -f Modelfile
# 4. Update OLLAMA_MODEL_NAME in .env
OLLAMA_MODEL_NAME=eldora-botELDORA — BINUS BM Team Passage to ASEAN Hackathon 2026
| Name | Role |
|---|---|
| Stanley Nathanael Wijaya | Team Lead |
| Lutfi Alvaro Pratama | IoT Engineer |
| Andrian Pratama | Mobile Developer |
| Khalisa Amanda Sifa Ghaizani | Backend Developer |
| Devon Nicholas | AI Engineer |
Have questions, want to collaborate, or interested in ELDORA?
| Channel | Details |
|---|---|
| stanley.n.wijaya7@gmail.com | |
| @xstynwx | |
| 💬 Discord | stynw7 |

