Berta AI Scribe is an advanced medical documentation assistant designed to help healthcare providers efficiently create clinical notes from audio recordings of patient encounters. The system uses state-of-the-art AI transcription services and language models to transform medical conversations into well-structured clinical documentation.
Berta AI Scribe aims to reduce the documentation burden on healthcare providers by:
The project consists of two main components:
Backend (web-api): A FastAPI-based service that handles:
Frontend (ai-scribe-app): A Next.js-based web application that provides:
The system follows a modern web application architecture with several layers:
Berta Scribe supports four transcription services:
The transcription service is configurable via the TRANSCRIPTION_SERVICE environment variable.
The application supports six language model providers:
ollama list)The system will automatically use the best available model based on your configuration. For the local deployment we will be using gpt-4o via OpenAI API and for the AWS deployment we will be using Llama3.3 70b.
[!NOTE] The main note generation uses the model specified in your environment configuration. Additionally, the application provides custom settings where you can test different note instructions against various models:
- Local Development:
- Ollama: All models from your
ollama listappear as testing options in custom settings- LM Studio: Only currently loaded models in LM Studio appear as testing options (unlike Ollama which shows all downloaded models)
- AWS Deployment: A fixed set of Bedrock models (Meta Llama 3.3 70B, Llama 3.1 405B/70B, Claude 3.7 Sonnet) are available for testing custom note instructions
Berta Scribe supports two storage options:
Local Storage (Development):
.data/recordingsS3 Storage (AWS Production):
The storage provider is automatically selected based on environment variables.
The application supports three database options:
SQLite (Development):
.data/database.dbAurora PostgreSQL (AWS Production):
USE_AURORA=true[!IMPORTANT] Before you begin: Local development requires Google OAuth credentials for authentication. You'll need to set up a Google Cloud project and create OAuth credentials before the application will work. See the Setting up Google OAuth section below.
The fastest way to get started is using Docker Compose. This handles all dependencies (Python, Node.js, FFmpeg, audiowaveform) automatically.
Prerequisites:
Steps:
Set up Google OAuth first (see instructions below)
Create environment file:
cp .env.example .env
Edit .env with your credentials:
ACCESS_TOKEN_SECRET=your_generated_secret # Run: openssl rand -base64 32
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
OPENAI_API_KEY=your_openai_api_key # If using OpenAI (default)
Start the application:
docker compose up
[!NOTE] First build takes 5-10 minutes (downloading ML dependencies and compiling audiowaveform). Subsequent starts are fast since Docker caches the build layers.
Access the app at http://localhost:4000
Stopping the application:
docker compose down
[!NOTE] Apple Silicon users: The containers run
linux/amd64via Rosetta emulation to match AWS production. This works correctly but may be slightly slower than native builds. Ensure "Use Rosetta for x86/amd64 emulation" is enabled in Docker Desktop → Settings → General.
If you prefer not to use Docker, or need more control over the setup, follow the manual installation instructions below.
macOS users: Most dependencies can be installed via Homebrew. If you don't have Homebrew installed:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Intel Mac users: Note that the default transcription service (Parakeet MLX) only works on Apple Silicon (M1/M2/M3/M4). Intel Mac users should use OpenAI Whisper (Option 1) or WhisperX (Option 2 with WhisperX configuration). See the setup options below for specific instructions.
macOS/Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Alternative (using pip):
pip install uv
Verify installation:
uv --version
# Should show uv version information
Ubuntu/Debian:
sudo apt update
sudo apt install ffmpeg
macOS:
# Using Homebrew
brew install ffmpeg
Windows:
# Using Chocolatey
choco install ffmpeg
Verify installation:
ffmpeg -version
# Should show FFmpeg version information
Ubuntu/Debian:
sudo add-apt-repository ppa:chris-needham/ppa
sudo apt-get update
sudo apt-get install audiowaveform
macOS:
brew install audiowaveform
Windows: Download from BBC audiowaveform releases or use WSL with Ubuntu instructions.
Verify installation:
audiowaveform --version
# Should show version 1.10 or higher
For local development, you'll need Google OAuth credentials:
Go to the Google Cloud Console
Create a new project (or select existing one):
Navigate to "APIs & Services" → "Credentials"
Configure OAuth consent screen:
Create OAuth credentials:
http://localhost:4000http://localhost:4000/loginNote your credentials:
[!IMPORTANT] The redirect URIs must match exactly. If you change the frontend port, update the redirect URIs accordingly.
Before configuring specific AI services, set up the Python backend environment:
Navigate to backend directory:
cd web-api
Create Python virtual environment with uv:
uv venv --python 3.11
Activate the virtual environment:
# macOS/Linux
source .venv/bin/activate
# Windows
.venv\Scripts\activate
Install dependencies:
# BEFORE installing, check if you need to uncomment any dependencies:
# - VLLM users: Uncomment vllm, torch, torchaudio lines
# - Apple Silicon users: Uncomment mlx, parakeet-mlx lines
# - Everyone else: No changes needed
uv pip install -r requirements.txt
For WhisperX with GPU Support (Optional): If you plan to use WhisperX with an NVIDIA GPU (Option 2 or 3), upgrade PyTorch to CUDA version for faster transcription:
# Only if you have NVIDIA GPU and want faster WhisperX transcription
uv pip install torch==2.5.0 torchaudio==2.5.0 --index-url https://download.pytorch.org/whl/cu121 --reinstall --no-deps
# Install cuDNN libraries (Ubuntu/Debian)
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get install -y libcudnn8=8.9.7.29-1+cuda12.2
# Set environment variable
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
Then set WHISPERX_DEVICE=cuda in your .env file when configuring WhisperX.
Note: Skip this step if you're using OpenAI (Option 1), Apple Silicon with Parakeet MLX, or don't have an NVIDIA GPU.
[!NOTE] Keep this terminal open with the virtual environment activated for the remaining setup steps.
All local setups use SQLite database, local file storage, and Google OAuth authentication. Choose based on your AI service preference:
[!IMPORTANT] If you're switching between different AI models or services, delete the
.datafolder in theweb-apidirectory to clear any cached model data and ensure a clean start with your new configuration.
Create the environment file:
web-api directory.env (note the dot at the beginning).env file:# Core Settings
ENVIRONMENT=development
COOKIE_SECURE=false
LOGGING_LEVEL=DEBUG
# JWT Configuration
# ACCESS_TOKEN_SECRET: A random string used to sign JWT tokens for security
# Generate one with: openssl rand -base64 32
ACCESS_TOKEN_SECRET=your_secure_random_string_here
ACCESS_TOKEN_EXPIRE_MINUTES=1440
# Authentication (Google OAuth)
USE_COGNITO=false
USE_GOOGLE_AUTH=true
GOOGLE_CLIENT_ID=your_google_client_id_from_oauth_setup
GOOGLE_CLIENT_SECRET=your_google_client_secret_from_oauth_setup
GOOGLE_REDIRECT_URI=http://localhost:4000/login
# Database (Local SQLite)
USE_AURORA=false
Then, add the AI service-specific variables based on your chosen option below:
Best for: Quick start, highest quality AI models, minimal setup Uses: OpenAI Whisper transcription + GPT-4o models
Requirements:
Setup Steps:
Get OpenAI API Key:
sk-...)Add OpenAI settings to your .env file:
web-api/.env file you created earlier# AI Services (OpenAI)
TRANSCRIPTION_SERVICE=OpenAI Whisper
GENERATIVE_AI_SERVICE=OpenAI
DEFAULT_NOTE_GENERATION_MODEL=gpt-4o
LABEL_MODEL=gpt-4o
# OpenAI API Key (replace with your actual API key from step 1)
OPENAI_API_KEY=your_openai_api_key_here
That's it! No additional software to install or configure.
[!NOTE] Costs: OpenAI charges approximately $0.36 per hour of audio transcribed. GPT-4o usage is additional but typically minimal for note generation.
Best for: First-time users, completely offline setup Uses: Parakeet MLX transcription + Ollama models
[!WARNING] Parakeet MLX requires Apple Silicon (M1/M2/M3 Macs). If you're on Intel Mac, Linux, or Windows, you must change the transcription service in step 5 below.
Requirements:
Setup Steps:
Install Ollama:
# macOS
brew install ollama
# Linux
curl -fsSL https://ollama.ai/install.sh | sh
# Windows - Download from https://ollama.ai/download
Start Ollama service:
# Start Ollama service (required for the application to work)
ollama serve
# The service will run on http://localhost:11434
# Keep this terminal open or run as a background service
Pull Ollama models (in a new terminal):
ollama pull llama3.1:8b
# Optional: For better quality (requires more RAM)
# ollama pull llama3.3:70b
Verify Ollama is working:
ollama list
# Should show your downloaded models
curl http://localhost:11434/api/tags
# Should return JSON with available models
[!IMPORTANT] Apple Silicon users: Make sure you uncommented the
mlxandparakeet-mlxlines inrequirements.txtbefore installing dependencies (as mentioned in Backend Environment Setup step 4).
[!NOTE] Any models you have already downloaded with Ollama (visible in
ollama list) will automatically appear as options in the application's custom settings, allowing you to test different note instructions with various models.
Append these lines to your web-api/.env file (below the common settings):
For Apple Silicon Mac:
# AI Services (Ollama)
TRANSCRIPTION_SERVICE=Parakeet MLX
GENERATIVE_AI_SERVICE=Ollama
DEFAULT_NOTE_GENERATION_MODEL=llama3.1:8b
LABEL_MODEL=llama3.1:8b
For Intel/Linux/Windows with NVIDIA GPU:
# AI Services (Ollama + WhisperX GPU)
TRANSCRIPTION_SERVICE=WhisperX
WHISPERX_DEVICE=cuda # Fast GPU transcription
GENERATIVE_AI_SERVICE=Ollama
DEFAULT_NOTE_GENERATION_MODEL=llama3.1:8b
LABEL_MODEL=llama3.1:8b
For Intel/Linux/Windows CPU-only:
# AI Services (Ollama + WhisperX CPU)
TRANSCRIPTION_SERVICE=WhisperX
WHISPERX_DEVICE=cpu # Warning: Slow transcription (consider Option 1 instead)
GENERATIVE_AI_SERVICE=Ollama
DEFAULT_NOTE_GENERATION_MODEL=llama3.1:8b
LABEL_MODEL=llama3.1:8b
[!WARNING] Performance Notes:
- Parakeet MLX (Apple Silicon): Fast, efficient transcription
- WhisperX GPU (NVIDIA): Fast transcription, comparable to Parakeet
- WhisperX CPU: Very slow (5-20x slower than real-time). Consider using Option 1 (OpenAI) for better performance if you don't have Apple Silicon or NVIDIA GPU.
[!NOTE] Any models you have already downloaded with Ollama (visible in
ollama list) will automatically appear as options in the application's custom settings, allowing you to test different note instructions with various models.
Best for: Users with powerful GPUs, maximum performance and privacy Uses: VLLM inference + WhisperX transcription
Requirements:
Setup Steps:
Install CUDA toolkit (if not installed):
# Check if CUDA is installed
nvidia-smi
# Ubuntu/Debian installation
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get install cuda-toolkit-12-4
Modify requirements.txt and install VLLM (in the Python virtual environment):
cd web-api
# First, uncomment these lines in requirements.txt:
# vllm>=0.3.0
# torch>=2.5.0,<3.0.0
# torchaudio>=2.5.0,<3.0.0
# nvidia-cudnn-cu12>=9.0.0 # Optional but recommended
# Then install:
uv pip install -r requirements.txt
Get Hugging Face token:
Append these lines to your web-api/.env file (below the common settings):
# AI Services (VLLM)
TRANSCRIPTION_SERVICE=WhisperX
GENERATIVE_AI_SERVICE=VLLM
# VLLM Configuration
VLLM_SERVER_NAME=localhost
VLLM_SERVER_PORT=8080
VLLM_MODEL_NAME=meta-llama/Meta-Llama-3.1-70B-Instruct
DEFAULT_NOTE_GENERATION_MODEL=meta-llama/Meta-Llama-3.1-70B-Instruct
LABEL_MODEL=meta-llama/Meta-Llama-3.1-70B-Instruct
# Hugging Face token (required for model downloads)
HUGGINGFACE_TOKEN=your_huggingface_token
# WhisperX device (if using WhisperX): cuda, cpu, or cuda:0
WHISPERX_DEVICE=cuda
[!IMPORTANT] All three model variables must have the same value:
VLLM_MODEL_NAME- Specifies which model to download from Hugging FaceDEFAULT_NOTE_GENERATION_MODEL- Model used for generating clinical notesLABEL_MODEL- Model used for note labeling and classification These must match exactly for VLLM to work properly.
# Start VLLM server in separate terminal
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3.1-70B-Instruct \
--host localhost \
--port 8080 \
--gpu-memory-utilization 0.95
Best for: Users who want a GUI for model management and high-quality local inference Uses: Parakeet MLX transcription + LM Studio models
Requirements:
[!IMPORTANT] Apple Silicon users: Make sure you uncommented the
mlxandparakeet-mlxlines inrequirements.txtbefore installing dependencies. Non-Apple Silicon users: ChangeTRANSCRIPTION_SERVICEtoWhisperXorOpenAI Whisperin step 5, as Parakeet MLX only works on Apple Silicon.
Setup Steps:
Install LM Studio:
Download models in LM Studio:
llama-3.1-8b-instruct (faster, 8GB RAM)llama-3.3-70b-instruct (higher quality, 64GB+ RAM)mistral-7b-instruct-v0.3 (good balance)Load a model:
Start LM Studio server:
http://localhost:1234)[!NOTE] Unlike Ollama which shows all downloaded models in custom settings, LM Studio only shows the currently loaded model as an option for testing different note instructions. You must load the desired model in LM Studio's interface before it becomes available in the application.
web-api/.env file (below the common settings):
# AI Services (LM Studio)
TRANSCRIPTION_SERVICE=Parakeet MLX
GENERATIVE_AI_SERVICE=LM Studio
# Model Selection (use the name of the loaded model in LM Studio)
DEFAULT_NOTE_GENERATION_MODEL=llama-3.1-8b-instruct
LABEL_MODEL=llama-3.1-8b-instruct
[!IMPORTANT] Make sure LM Studio server is running and a model is loaded before starting the backend. The model name in your environment file should match the loaded model in LM Studio.
Best for: NVIDIA DGX Spark workstations with GB10 chip (Project DIGITS) Uses: WhisperX GPU transcription + Ollama with MedGemma or other medical LLMs
[!IMPORTANT] The NVIDIA GB10 uses ARM64 architecture with CUDA 13.0, which requires building some dependencies from source due to limited pre-built wheel availability.
Requirements:
Setup Steps:
Install system dependencies:
sudo apt update
sudo apt install -y ffmpeg libboost-all-dev libmad0-dev libid3tag0-dev \
libsndfile1-dev libgd-dev cmake git build-essential
Build audiowaveform from source (no ARM64 binaries available):
cd /tmp
git clone https://github.com/bbc/audiowaveform.git
cd audiowaveform
mkdir build && cd build
cmake .. -DENABLE_TESTS=OFF
make -j$(nproc)
sudo make install
Set up Python environment:
cd web-api
uv venv --python 3.11
source .venv/bin/activate
uv pip install -r requirements.txt
Install PyTorch with CUDA 13 support:
uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu130
Build CTranslate2 from source with CUDA 13 (no pre-built ARM64 CUDA wheels):
# Install pybind11
uv pip install pybind11
# Clone and build CTranslate2
cd /tmp
git clone --recursive https://github.com/OpenNMT/CTranslate2.git
cd CTranslate2
mkdir build && cd build
cmake .. -DWITH_CUDA=ON -DWITH_CUDNN=OFF -DWITH_MKL=OFF -DWITH_OPENBLAS=OFF \
-DCMAKE_BUILD_TYPE=Release -DOPENMP_RUNTIME=NONE
make -j$(nproc)
cmake --install . --prefix /tmp/ctranslate2_install
# Install Python bindings
cd /tmp/CTranslate2/python
CTranslate2_ROOT=/tmp/ctranslate2_install \
CMAKE_PREFIX_PATH=/tmp/ctranslate2_install \
CPLUS_INCLUDE_PATH=/tmp/ctranslate2_install/include \
LIBRARY_PATH=/tmp/ctranslate2_install/lib \
uv pip install . --no-build-isolation
Install and configure Ollama:
curl -fsSL https://ollama.ai/install.sh | sh
ollama serve &
# Pull a medical LLM (example: MedGemma)
ollama pull MedAIBase/MedGemma1.5:4b
Configure environment - Append to your web-api/.env file:
# AI Services (WhisperX GPU + Ollama)
TRANSCRIPTION_SERVICE=WhisperX
WHISPERX_DEVICE=cuda
GENERATIVE_AI_SERVICE=Ollama
# Model names must include the tag (e.g., :4b)
DEFAULT_NOTE_GENERATION_MODEL=MedAIBase/MedGemma1.5:4b
LABEL_MODEL=MedAIBase/MedGemma1.5:4b
Start the backend (requires environment variables):
cd web-api
source .venv/bin/activate
LD_LIBRARY_PATH=/tmp/ctranslate2_install/lib TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 \
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
[!TIP] Create a startup script
start-backend.shfor convenience:#!/bin/bash export LD_LIBRARY_PATH=/tmp/ctranslate2_install/lib:$LD_LIBRARY_PATH export TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 cd ~/projects/berta-ai-scribe/web-api source .venv/bin/activate uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
For better performance on DGX Spark, you can use llama.cpp instead of Ollama. llama.cpp is ~35% faster and supports Blackwell-native optimizations.
Build llama.cpp with CUDA 13 and Blackwell support:
cd ~
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
mkdir build-gpu && cd build-gpu
cmake .. -DCMAKE_BUILD_TYPE=Release -DGGML_CUDA=ON -DGGML_CUDA_F16=ON -DCMAKE_CUDA_ARCHITECTURES=121
make -j$(nproc)
Download a GGUF model (example: Llama 3.3 70B Q4):
mkdir -p ~/models
cd ~/models
# Download from Hugging Face (one-time, runs 100% locally after)
wget https://huggingface.co/bartowski/Llama-3.3-70B-Instruct-GGUF/resolve/main/Llama-3.3-70B-Instruct-Q4_K_M.gguf
Start llama-server:
cd ~/llama.cpp/build-gpu
LD_LIBRARY_PATH=./bin:$LD_LIBRARY_PATH ./bin/llama-server \
-m ~/models/Llama-3.3-70B-Instruct-Q4_K_M.gguf \
-ngl 99 -c 4096 --host 0.0.0.0 --port 8080
Configure environment - Use these settings in your web-api/.env file:
# AI Services (WhisperX GPU + LlamaCpp)
TRANSCRIPTION_SERVICE=WhisperX
WHISPERX_DEVICE=cuda
GENERATIVE_AI_SERVICE=LlamaCpp
# LLAMA_CPP_SERVER_URL=http://localhost:8080 # Optional, defaults to localhost:8080
# Model name must match the loaded GGUF file
DEFAULT_NOTE_GENERATION_MODEL=Llama-3.3-70B-Instruct-Q4_K_M.gguf
LABEL_MODEL=Llama-3.3-70B-Instruct-Q4_K_M.gguf
Start the backend (in a separate terminal):
cd web-api
source .venv/bin/activate
LD_LIBRARY_PATH=~/ctranslate2_install/lib:$LD_LIBRARY_PATH TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 \
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
[!TIP] Startup Order: Start llama-server first (wait ~60 seconds for model to load), then start the backend.
[!WARNING] Known Warnings (can be safely ignored):
- PyTorch may warn about CUDA capability 12.1 vs supported 12.0 - this generally works fine
- pyannote.audio version mismatch warnings - models still function correctly
For production deployments and better scaling, use NVIDIA's optimized vLLM Docker container. vLLM offers continuous batching, PagedAttention for efficient memory use, and tensor parallelism for multi-GPU setups.
[!IMPORTANT] GPU Sharing: Docker containers take exclusive GPU access. Start the backend (WhisperX) BEFORE launching the vLLM Docker container to allow both to coexist on unified memory.
Pull the NVIDIA-optimized vLLM container:
docker pull nvcr.io/nvidia/vllm:26.01-py3
Choose your model based on available memory:
| Model | Memory Required | Command |
|---|---|---|
| Llama 3.1 8B (recommended for GPU sharing) | ~16GB | See below |
| Llama 3.3 70B NVFP4 (Blackwell-optimized 4-bit) | ~40GB | See below |
Start vLLM Docker (choose one):
For Llama 3.1 8B (leaves ~50GB for WhisperX):
docker run --gpus all -p 8080:8080 \
-e HUGGING_FACE_HUB_TOKEN=your_hf_token \
nvcr.io/nvidia/vllm:26.01-py3 \
--model meta-llama/Llama-3.1-8B-Instruct \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.65 \
--port 8080
For Llama 3.3 70B with NVFP4 (Blackwell 4-bit quantization, ~3.3x memory reduction):
docker run --gpus all -p 8080:8080 \
-e HUGGING_FACE_HUB_TOKEN=your_hf_token \
nvcr.io/nvidia/vllm:26.01-py3 \
--model neuralmagic/Meta-Llama-3.3-70B-Instruct-nvfp4 \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.65 \
--port 8080
Configure environment - Use these settings in your web-api/.env file:
# AI Services (WhisperX GPU + vLLM Docker)
TRANSCRIPTION_SERVICE=WhisperX
WHISPERX_DEVICE=cuda
GENERATIVE_AI_SERVICE=VLLM
# vLLM Configuration
VLLM_SERVER_NAME=localhost
VLLM_SERVER_PORT=8080
# Model name must match exactly what vLLM loads
VLLM_MODEL_NAME=meta-llama/Llama-3.1-8B-Instruct
DEFAULT_NOTE_GENERATION_MODEL=meta-llama/Llama-3.1-8B-Instruct
LABEL_MODEL=meta-llama/Llama-3.1-8B-Instruct
Startup order (critical for GPU sharing):
# Terminal 1: Start backend FIRST (initializes WhisperX on GPU)
cd web-api && source .venv/bin/activate
LD_LIBRARY_PATH=~/ctranslate2_install/lib TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 \
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# Terminal 2: Start vLLM Docker AFTER backend is running
docker run --gpus all -p 8080:8080 ...
[!TIP] Scaling vLLM:
- Multi-GPU: Use
--tensor-parallel-size 2(or higher) to split model across GPUs- Multiple instances: Run several vLLM containers behind a load balancer
- Continuous batching: vLLM automatically batches concurrent requests for 2-4x throughput
- Check models:
curl http://localhost:8080/v1/modelsto verify loaded model name
[!NOTE] Switching models: If you previously used a different model, delete the
.datafolder to reset the database:rm -rf .data/The database will be recreated with the correct model names from your
.envfile on next startup.```
web-api/.env (backend) file.data folder in web-api directorylocalhost:4000 (F12 → Application tab → Clear storage)Start Ollama service FIRST:
ollama serve
# Keep this terminal open, then start backend in new terminal
Before starting backend:
.env must match exactly what's loaded in LM Studiouvicorn app.main:app --reload --port 8000)npm run dev)After completing your chosen AI service setup above:
Ensure your virtual environment is activated:
# If not already activated from the Backend Environment Setup
cd web-api
source .venv/bin/activate # macOS/Linux
# or .venv\Scripts\activate # Windows
Start the backend server:
uvicorn app.main:app --reload --port 8000
Create frontend environment file:
ai-scribe-app directory.env (note the dot at the beginning).env file:# Backend API URL
NEXT_PUBLIC_BACKEND_URL=http://localhost:8000
# Authentication Configuration
NEXT_PUBLIC_USE_COGNITO=false
NEXT_PUBLIC_USE_GOOGLE_AUTH=true
# Google OAuth Configuration (use same Client ID from backend setup)
GOOGLE_CLIENT_ID=your_google_client_id_from_step_above
GOOGLE_REDIRECT_URI=http://localhost:4000/login
Navigate to frontend directory:
cd ai-scribe-app
Install dependencies:
npm install
Start the frontend development server:
npm run dev
The frontend will be available at http://localhost:4000
[!NOTE] The
GOOGLE_CLIENT_IDshould be the same in both frontend and backend environment files.
http://localhost:4000Ollama Connection Issues:
ollama serve is running in a separate terminalhttp://localhost:11434ollama listPython Environment Issues:
python --versionpip install uvAuthentication Issues:
Google OAuth Errors:
http://localhost:4000/login is in your authorized redirect URIs.env filesPort Conflicts:
lsof -i :8000 (macOS/Linux) or netstat -ano | findstr :8000 (Windows). Kill the process or use a different port with --port 8001lsof -i :4000. If you change the port, remember to update your Google OAuth redirect URIs accordinglyps aux | grep ollamaService Startup Order:
Transcription Issues (Intel Mac / Windows / Linux):
nvidia-smiCreate AWS Account: If you don't have one, sign up at aws.amazon.com
Log into AWS Console: After creating your account, log into the AWS Management Console
Enable Bedrock Model Access:
us.meta.llama3-3-70b-instruct-v1:0)meta.llama3-1-405b-instruct-v1:0)meta.llama3-1-70b-instruct-v1:0)anthropic.claude-3-7-sonnet-20250219-v1:0)Register a Domain:
Option 1: Register through Route53 Console (Recommended):
Option 2: Use existing domain with Route53:
# If you have a domain registered elsewhere, create a hosted zone
aws route53 create-hosted-zone \
--name yourdomain.com \
--caller-reference $(date +%s) \
--hosted-zone-config Comment="Berta Scribe hosted zone"
# Note: You'll need to update your domain's nameservers to point to Route53
Find your Hosted Zone ID:
Method 1 (AWS Console - Recommended):
Z1D633PJN98FT9) - you'll need this for deploymentMethod 2 (AWS CLI):
aws route53 list-hosted-zones --query "HostedZones[?Name=='yourdomain.com.'].Id" --output text
If you already have a VPC set up:
Note Your VPC Details:
Verify your subnets (run this command to check):
aws ec2 describe-subnets --filters "Name=vpc-id,Values=<YOUR_VPC_ID>" --region us-west-2 \
--query 'Subnets[*].{ID:SubnetId,AZ:AvailabilityZone,CIDR:CidrBlock,Public:MapPublicIpOnLaunch}' --output table
Verify you have a NAT Gateway:
aws ec2 describe-nat-gateways --filter "Name=vpc-id,Values=<YOUR_VPC_ID>" --region us-west-2 \
--query 'NatGateways[*].{ID:NatGatewayId,State:State,SubnetId:SubnetId}' --output table
Skip to Step 3b to add security hardening, then proceed to Step 4
Use AWS VPC Wizard
Go to VPC Console:
VPC Settings - Choose "VPC and more":
| Setting | Value |
|---|---|
| Resources to create | VPC and more |
| Name tag auto-generation | berta |
| IPv4 CIDR block | 10.0.0.0/16 |
| IPv6 CIDR block | No IPv6 CIDR block |
| Tenancy | Default |
| Number of AZs | 2 |
| Number of public subnets | 2 |
| Number of private subnets | 2 |
| NAT gateways | In 1 AZ |
| VPC endpoints | S3 Gateway |
| DNS hostnames | Enabled |
| DNS resolution | Enabled |
Review the Preview - You should see:
Click "Create VPC" - AWS creates everything automatically!
Note Your Resource IDs (you'll need these for deployment):
| Resource | Where to Find |
|---|---|
| VPC ID | VPC Details tab |
| Public Subnets | Subnets with "public" in name (typically 10.0.0.0/20, 10.0.16.0/20) |
| Private Subnets | Subnets with "private" in name (typically 10.0.128.0/20, 10.0.144.0/20) |
After creating your VPC, add these security measures:
VPC Flow Logs help you monitor network traffic and detect suspicious activity:
# Create CloudWatch log group
aws logs create-log-group --log-group-name /vpc/berta-flow-logs --region us-west-2
# Create IAM role for flow logs
echo '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"vpc-flow-logs.amazonaws.com"},"Action":"sts:AssumeRole"}]}' > /tmp/trust-policy.json
aws iam create-role --role-name VPCFlowLogsRole --assume-role-policy-document file:///tmp/trust-policy.json
echo '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["logs:CreateLogStream","logs:PutLogEvents","logs:DescribeLogGroups","logs:DescribeLogStreams"],"Resource":"*"}]}' > /tmp/flow-logs-policy.json
aws iam put-role-policy --role-name VPCFlowLogsRole --policy-name FlowLogsPolicy --policy-document file:///tmp/flow-logs-policy.json
# Enable flow logs on your VPC (replace <YOUR_VPC_ID> and <YOUR_ACCOUNT_ID>)
aws ec2 create-flow-logs --resource-type VPC --resource-ids <YOUR_VPC_ID> --traffic-type ALL \
--log-destination-type cloud-watch-logs --log-group-name /vpc/berta-flow-logs \
--deliver-logs-permission-arn arn:aws:iam::<YOUR_ACCOUNT_ID>:role/VPCFlowLogsRole --region us-west-2
Add DENY rules to block common malicious ports on your private subnet Network ACL:
Go to VPC Console → Network ACLs
Select the NACL associated with your private subnets
Edit Outbound Rules - Add these DENY rules (lower rule numbers = higher priority):
| Rule # | Type | Port | Destination | Action |
|---|---|---|---|---|
| 50 | TCP | 23 | 0.0.0.0/0 | DENY |
| 51 | TCP | 445 | 0.0.0.0/0 | DENY |
| 52 | TCP | 2323 | 0.0.0.0/0 | DENY |
| 53 | TCP | 3389 | 0.0.0.0/0 | DENY |
| 54 | TCP | 3306 | 0.0.0.0/0 | DENY |
| 100 | ALL | ALL | 0.0.0.0/0 | ALLOW |
[!NOTE] These rules block common ports used by malware for scanning (Telnet, SMB, RDP, MySQL). The CloudFormation template already includes restrictive security group rules, but Network ACLs provide an additional layer of protection.
Now you'll deploy Berta Scribe application using AWS CloudFormation:
Option A: One-click Deployment (Recommended)
Click the deployment button:
You'll be taken to the AWS CloudFormation console where you'll see a form to fill out
Option B: Custom Deployment
If you need to modify the CloudFormation template (e.g., change instance sizes, add custom configurations), you can use the template.yaml file included in this repository. Download the template, make your modifications, and deploy it manually through the AWS CloudFormation console or AWS CLI instead of using the one-click deployment above.
[!IMPORTANT] If you modify the
template.yamlfile and deploy it manually, you cannot use the one-click deployment button. You must deploy your custom template through the AWS CloudFormation console or CLI.
Fill in the required parameters:
| Parameter | Description | Example |
|---|---|---|
| Environment | Deployment environment | production |
| HostedZoneId | Route53 Hosted Zone ID | Z1D633PJN98FT9 |
| VpcId | VPC ID from Step 3 | vpc-12345678 |
| PublicSubnets | Public subnet IDs (comma-separated) | subnet-12345,subnet-67890 |
| PrivateSubnets | Private subnet IDs (comma-separated) | subnet-abcde,subnet-fghij |
| DomainName | Your domain name | yourdomain.com |
| AuthDomainPrefix | Prefix part of the domain name | yourdomain |
| AccessTokenSecret | JWT signing secret | Generate with openssl rand -base64 32 |
| DBName | Database name | berta |
| DBUser | Database username | berta_admin |
| DBPassword | Database password | Generate secure password |
Deploy the stack:
Monitor the deployment:
Get your application URLs:
Test the application:
Docker Images: The CloudFormation template uses pre-built Docker images hosted on AWS Public ECR:
public.ecr.aws/s9f8j1d3/berta-frontend:latestpublic.ecr.aws/s9f8j1d3/berta-backend:latestThese images are automatically pulled during deployment and contain the latest stable versions of the application components.
Updates: When new releases are available, we update the images at the same URLs. To get the latest version, simply restart your ECS services:
aws ecs update-service --cluster berta-cluster-production --service berta-frontend-production --force-new-deployment
aws ecs update-service --cluster berta-cluster-production --service berta-backend-production --force-new-deployment
Berta Scribe currently supports AWS for cloud production deployments. Support for Azure, GCP, and Databricks is under consideration based on community interest. If you need support for a specific platform, please open an issue on GitHub.
You can view all available services and models by running:
cd web-api
python -m app.cli.list_services
This will show:
ollama list outputus.meta.llama3-3-70b-instruct-v1:0, meta.llama3-1-405b-instruct-v1:0, meta.llama3-1-70b-instruct-v1:0, anthropic.claude-3-7-sonnet-20250219-v1:0gpt-4o, gpt-3.5-turboBerta Scribe implements robust security measures:
This project uses third-party libraries and models, including:
For the full text of these licenses, please see the THIRD_PARTY_LICENSES file in this repository.
This project uses Meta Llama 3.3. As per the Llama 3.3 license requirements:
For the complete Meta Llama 3.3 Community License Agreement, refer to the THIRD_PARTY_LICENSES file.
This project integrates with external services that users install and manage separately:
[!IMPORTANT] The Licensed Work is provided as a support tool only and is not intended as a substitute for the guidance or care of a health professional.
[!CAUTION] The authors disclaim all warranties, expressed or implied. In particular, but without limitation, the Licensed Work is provided WITHOUT WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, EITHER EXPRESSED OR IMPLIED. The user assumes all responsibility for losses, costs, claims, damages or liability of any kind whatsoever which may arise from use of the Licensed Work.
79 commits
Python
48.0%
TypeScript
45.5%
PLSQL
3.0%
JavaScript
1.7%
TeX
1.1%
Berta AI Scribe is an advanced medical documentation assistant designed to help healthcare providers efficiently create clinical notes from audio recordings of patient encounters. The system uses state-of-the-art AI transcription services and language models to transform medical conversations into well-structured clinical documentation.
Berta AI Scribe aims to reduce the documentation burden on healthcare providers by:
The project consists of two main components:
Backend (web-api): A FastAPI-based service that handles:
Frontend (ai-scribe-app): A Next.js-based web application that provides:
The system follows a modern web application architecture with several layers:
Berta Scribe supports four transcription services:
The transcription service is configurable via the TRANSCRIPTION_SERVICE environment variable.
The application supports six language model providers:
ollama list)The system will automatically use the best available model based on your configuration. For the local deployment we will be using gpt-4o via OpenAI API and for the AWS deployment we will be using Llama3.3 70b.
[!NOTE] The main note generation uses the model specified in your environment configuration. Additionally, the application provides custom settings where you can test different note instructions against various models:
- Local Development:
- Ollama: All models from your
ollama listappear as testing options in custom settings- LM Studio: Only currently loaded models in LM Studio appear as testing options (unlike Ollama which shows all downloaded models)
- AWS Deployment: A fixed set of Bedrock models (Meta Llama 3.3 70B, Llama 3.1 405B/70B, Claude 3.7 Sonnet) are available for testing custom note instructions
Berta Scribe supports two storage options:
Local Storage (Development):
.data/recordingsS3 Storage (AWS Production):
The storage provider is automatically selected based on environment variables.
The application supports three database options:
SQLite (Development):
.data/database.dbAurora PostgreSQL (AWS Production):
USE_AURORA=true[!IMPORTANT] Before you begin: Local development requires Google OAuth credentials for authentication. You'll need to set up a Google Cloud project and create OAuth credentials before the application will work. See the Setting up Google OAuth section below.
The fastest way to get started is using Docker Compose. This handles all dependencies (Python, Node.js, FFmpeg, audiowaveform) automatically.
Prerequisites:
Steps:
Set up Google OAuth first (see instructions below)
Create environment file:
cp .env.example .env
Edit .env with your credentials:
ACCESS_TOKEN_SECRET=your_generated_secret # Run: openssl rand -base64 32
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
OPENAI_API_KEY=your_openai_api_key # If using OpenAI (default)
Start the application:
docker compose up
[!NOTE] First build takes 5-10 minutes (downloading ML dependencies and compiling audiowaveform). Subsequent starts are fast since Docker caches the build layers.
Access the app at http://localhost:4000
Stopping the application:
docker compose down
[!NOTE] Apple Silicon users: The containers run
linux/amd64via Rosetta emulation to match AWS production. This works correctly but may be slightly slower than native builds. Ensure "Use Rosetta for x86/amd64 emulation" is enabled in Docker Desktop → Settings → General.
If you prefer not to use Docker, or need more control over the setup, follow the manual installation instructions below.
macOS users: Most dependencies can be installed via Homebrew. If you don't have Homebrew installed:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Intel Mac users: Note that the default transcription service (Parakeet MLX) only works on Apple Silicon (M1/M2/M3/M4). Intel Mac users should use OpenAI Whisper (Option 1) or WhisperX (Option 2 with WhisperX configuration). See the setup options below for specific instructions.
macOS/Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Alternative (using pip):
pip install uv
Verify installation:
uv --version
# Should show uv version information
Ubuntu/Debian:
sudo apt update
sudo apt install ffmpeg
macOS:
# Using Homebrew
brew install ffmpeg
Windows:
# Using Chocolatey
choco install ffmpeg
Verify installation:
ffmpeg -version
# Should show FFmpeg version information
Ubuntu/Debian:
sudo add-apt-repository ppa:chris-needham/ppa
sudo apt-get update
sudo apt-get install audiowaveform
macOS:
brew install audiowaveform
Windows: Download from BBC audiowaveform releases or use WSL with Ubuntu instructions.
Verify installation:
audiowaveform --version
# Should show version 1.10 or higher
For local development, you'll need Google OAuth credentials:
Go to the Google Cloud Console
Create a new project (or select existing one):
Navigate to "APIs & Services" → "Credentials"
Configure OAuth consent screen:
Create OAuth credentials:
http://localhost:4000http://localhost:4000/loginNote your credentials:
[!IMPORTANT] The redirect URIs must match exactly. If you change the frontend port, update the redirect URIs accordingly.
Before configuring specific AI services, set up the Python backend environment:
Navigate to backend directory:
cd web-api
Create Python virtual environment with uv:
uv venv --python 3.11
Activate the virtual environment:
# macOS/Linux
source .venv/bin/activate
# Windows
.venv\Scripts\activate
Install dependencies:
# BEFORE installing, check if you need to uncomment any dependencies:
# - VLLM users: Uncomment vllm, torch, torchaudio lines
# - Apple Silicon users: Uncomment mlx, parakeet-mlx lines
# - Everyone else: No changes needed
uv pip install -r requirements.txt
For WhisperX with GPU Support (Optional): If you plan to use WhisperX with an NVIDIA GPU (Option 2 or 3), upgrade PyTorch to CUDA version for faster transcription:
# Only if you have NVIDIA GPU and want faster WhisperX transcription
uv pip install torch==2.5.0 torchaudio==2.5.0 --index-url https://download.pytorch.org/whl/cu121 --reinstall --no-deps
# Install cuDNN libraries (Ubuntu/Debian)
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get install -y libcudnn8=8.9.7.29-1+cuda12.2
# Set environment variable
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
Then set WHISPERX_DEVICE=cuda in your .env file when configuring WhisperX.
Note: Skip this step if you're using OpenAI (Option 1), Apple Silicon with Parakeet MLX, or don't have an NVIDIA GPU.
[!NOTE] Keep this terminal open with the virtual environment activated for the remaining setup steps.
All local setups use SQLite database, local file storage, and Google OAuth authentication. Choose based on your AI service preference:
[!IMPORTANT] If you're switching between different AI models or services, delete the
.datafolder in theweb-apidirectory to clear any cached model data and ensure a clean start with your new configuration.
Create the environment file:
web-api directory.env (note the dot at the beginning).env file:# Core Settings
ENVIRONMENT=development
COOKIE_SECURE=false
LOGGING_LEVEL=DEBUG
# JWT Configuration
# ACCESS_TOKEN_SECRET: A random string used to sign JWT tokens for security
# Generate one with: openssl rand -base64 32
ACCESS_TOKEN_SECRET=your_secure_random_string_here
ACCESS_TOKEN_EXPIRE_MINUTES=1440
# Authentication (Google OAuth)
USE_COGNITO=false
USE_GOOGLE_AUTH=true
GOOGLE_CLIENT_ID=your_google_client_id_from_oauth_setup
GOOGLE_CLIENT_SECRET=your_google_client_secret_from_oauth_setup
GOOGLE_REDIRECT_URI=http://localhost:4000/login
# Database (Local SQLite)
USE_AURORA=false
Then, add the AI service-specific variables based on your chosen option below:
Best for: Quick start, highest quality AI models, minimal setup Uses: OpenAI Whisper transcription + GPT-4o models
Requirements:
Setup Steps:
Get OpenAI API Key:
sk-...)Add OpenAI settings to your .env file:
web-api/.env file you created earlier# AI Services (OpenAI)
TRANSCRIPTION_SERVICE=OpenAI Whisper
GENERATIVE_AI_SERVICE=OpenAI
DEFAULT_NOTE_GENERATION_MODEL=gpt-4o
LABEL_MODEL=gpt-4o
# OpenAI API Key (replace with your actual API key from step 1)
OPENAI_API_KEY=your_openai_api_key_here
That's it! No additional software to install or configure.
[!NOTE] Costs: OpenAI charges approximately $0.36 per hour of audio transcribed. GPT-4o usage is additional but typically minimal for note generation.
Best for: First-time users, completely offline setup Uses: Parakeet MLX transcription + Ollama models
[!WARNING] Parakeet MLX requires Apple Silicon (M1/M2/M3 Macs). If you're on Intel Mac, Linux, or Windows, you must change the transcription service in step 5 below.
Requirements:
Setup Steps:
Install Ollama:
# macOS
brew install ollama
# Linux
curl -fsSL https://ollama.ai/install.sh | sh
# Windows - Download from https://ollama.ai/download
Start Ollama service:
# Start Ollama service (required for the application to work)
ollama serve
# The service will run on http://localhost:11434
# Keep this terminal open or run as a background service
Pull Ollama models (in a new terminal):
ollama pull llama3.1:8b
# Optional: For better quality (requires more RAM)
# ollama pull llama3.3:70b
Verify Ollama is working:
ollama list
# Should show your downloaded models
curl http://localhost:11434/api/tags
# Should return JSON with available models
[!IMPORTANT] Apple Silicon users: Make sure you uncommented the
mlxandparakeet-mlxlines inrequirements.txtbefore installing dependencies (as mentioned in Backend Environment Setup step 4).
[!NOTE] Any models you have already downloaded with Ollama (visible in
ollama list) will automatically appear as options in the application's custom settings, allowing you to test different note instructions with various models.
Append these lines to your web-api/.env file (below the common settings):
For Apple Silicon Mac:
# AI Services (Ollama)
TRANSCRIPTION_SERVICE=Parakeet MLX
GENERATIVE_AI_SERVICE=Ollama
DEFAULT_NOTE_GENERATION_MODEL=llama3.1:8b
LABEL_MODEL=llama3.1:8b
For Intel/Linux/Windows with NVIDIA GPU:
# AI Services (Ollama + WhisperX GPU)
TRANSCRIPTION_SERVICE=WhisperX
WHISPERX_DEVICE=cuda # Fast GPU transcription
GENERATIVE_AI_SERVICE=Ollama
DEFAULT_NOTE_GENERATION_MODEL=llama3.1:8b
LABEL_MODEL=llama3.1:8b
For Intel/Linux/Windows CPU-only:
# AI Services (Ollama + WhisperX CPU)
TRANSCRIPTION_SERVICE=WhisperX
WHISPERX_DEVICE=cpu # Warning: Slow transcription (consider Option 1 instead)
GENERATIVE_AI_SERVICE=Ollama
DEFAULT_NOTE_GENERATION_MODEL=llama3.1:8b
LABEL_MODEL=llama3.1:8b
[!WARNING] Performance Notes:
- Parakeet MLX (Apple Silicon): Fast, efficient transcription
- WhisperX GPU (NVIDIA): Fast transcription, comparable to Parakeet
- WhisperX CPU: Very slow (5-20x slower than real-time). Consider using Option 1 (OpenAI) for better performance if you don't have Apple Silicon or NVIDIA GPU.
[!NOTE] Any models you have already downloaded with Ollama (visible in
ollama list) will automatically appear as options in the application's custom settings, allowing you to test different note instructions with various models.
Best for: Users with powerful GPUs, maximum performance and privacy Uses: VLLM inference + WhisperX transcription
Requirements:
Setup Steps:
Install CUDA toolkit (if not installed):
# Check if CUDA is installed
nvidia-smi
# Ubuntu/Debian installation
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get install cuda-toolkit-12-4
Modify requirements.txt and install VLLM (in the Python virtual environment):
cd web-api
# First, uncomment these lines in requirements.txt:
# vllm>=0.3.0
# torch>=2.5.0,<3.0.0
# torchaudio>=2.5.0,<3.0.0
# nvidia-cudnn-cu12>=9.0.0 # Optional but recommended
# Then install:
uv pip install -r requirements.txt
Get Hugging Face token:
Append these lines to your web-api/.env file (below the common settings):
# AI Services (VLLM)
TRANSCRIPTION_SERVICE=WhisperX
GENERATIVE_AI_SERVICE=VLLM
# VLLM Configuration
VLLM_SERVER_NAME=localhost
VLLM_SERVER_PORT=8080
VLLM_MODEL_NAME=meta-llama/Meta-Llama-3.1-70B-Instruct
DEFAULT_NOTE_GENERATION_MODEL=meta-llama/Meta-Llama-3.1-70B-Instruct
LABEL_MODEL=meta-llama/Meta-Llama-3.1-70B-Instruct
# Hugging Face token (required for model downloads)
HUGGINGFACE_TOKEN=your_huggingface_token
# WhisperX device (if using WhisperX): cuda, cpu, or cuda:0
WHISPERX_DEVICE=cuda
[!IMPORTANT] All three model variables must have the same value:
VLLM_MODEL_NAME- Specifies which model to download from Hugging FaceDEFAULT_NOTE_GENERATION_MODEL- Model used for generating clinical notesLABEL_MODEL- Model used for note labeling and classification These must match exactly for VLLM to work properly.
# Start VLLM server in separate terminal
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3.1-70B-Instruct \
--host localhost \
--port 8080 \
--gpu-memory-utilization 0.95
Best for: Users who want a GUI for model management and high-quality local inference Uses: Parakeet MLX transcription + LM Studio models
Requirements:
[!IMPORTANT] Apple Silicon users: Make sure you uncommented the
mlxandparakeet-mlxlines inrequirements.txtbefore installing dependencies. Non-Apple Silicon users: ChangeTRANSCRIPTION_SERVICEtoWhisperXorOpenAI Whisperin step 5, as Parakeet MLX only works on Apple Silicon.
Setup Steps:
Install LM Studio:
Download models in LM Studio:
llama-3.1-8b-instruct (faster, 8GB RAM)llama-3.3-70b-instruct (higher quality, 64GB+ RAM)mistral-7b-instruct-v0.3 (good balance)Load a model:
Start LM Studio server:
http://localhost:1234)[!NOTE] Unlike Ollama which shows all downloaded models in custom settings, LM Studio only shows the currently loaded model as an option for testing different note instructions. You must load the desired model in LM Studio's interface before it becomes available in the application.
web-api/.env file (below the common settings):
# AI Services (LM Studio)
TRANSCRIPTION_SERVICE=Parakeet MLX
GENERATIVE_AI_SERVICE=LM Studio
# Model Selection (use the name of the loaded model in LM Studio)
DEFAULT_NOTE_GENERATION_MODEL=llama-3.1-8b-instruct
LABEL_MODEL=llama-3.1-8b-instruct
[!IMPORTANT] Make sure LM Studio server is running and a model is loaded before starting the backend. The model name in your environment file should match the loaded model in LM Studio.
Best for: NVIDIA DGX Spark workstations with GB10 chip (Project DIGITS) Uses: WhisperX GPU transcription + Ollama with MedGemma or other medical LLMs
[!IMPORTANT] The NVIDIA GB10 uses ARM64 architecture with CUDA 13.0, which requires building some dependencies from source due to limited pre-built wheel availability.
Requirements:
Setup Steps:
Install system dependencies:
sudo apt update
sudo apt install -y ffmpeg libboost-all-dev libmad0-dev libid3tag0-dev \
libsndfile1-dev libgd-dev cmake git build-essential
Build audiowaveform from source (no ARM64 binaries available):
cd /tmp
git clone https://github.com/bbc/audiowaveform.git
cd audiowaveform
mkdir build && cd build
cmake .. -DENABLE_TESTS=OFF
make -j$(nproc)
sudo make install
Set up Python environment:
cd web-api
uv venv --python 3.11
source .venv/bin/activate
uv pip install -r requirements.txt
Install PyTorch with CUDA 13 support:
uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu130
Build CTranslate2 from source with CUDA 13 (no pre-built ARM64 CUDA wheels):
# Install pybind11
uv pip install pybind11
# Clone and build CTranslate2
cd /tmp
git clone --recursive https://github.com/OpenNMT/CTranslate2.git
cd CTranslate2
mkdir build && cd build
cmake .. -DWITH_CUDA=ON -DWITH_CUDNN=OFF -DWITH_MKL=OFF -DWITH_OPENBLAS=OFF \
-DCMAKE_BUILD_TYPE=Release -DOPENMP_RUNTIME=NONE
make -j$(nproc)
cmake --install . --prefix /tmp/ctranslate2_install
# Install Python bindings
cd /tmp/CTranslate2/python
CTranslate2_ROOT=/tmp/ctranslate2_install \
CMAKE_PREFIX_PATH=/tmp/ctranslate2_install \
CPLUS_INCLUDE_PATH=/tmp/ctranslate2_install/include \
LIBRARY_PATH=/tmp/ctranslate2_install/lib \
uv pip install . --no-build-isolation
Install and configure Ollama:
curl -fsSL https://ollama.ai/install.sh | sh
ollama serve &
# Pull a medical LLM (example: MedGemma)
ollama pull MedAIBase/MedGemma1.5:4b
Configure environment - Append to your web-api/.env file:
# AI Services (WhisperX GPU + Ollama)
TRANSCRIPTION_SERVICE=WhisperX
WHISPERX_DEVICE=cuda
GENERATIVE_AI_SERVICE=Ollama
# Model names must include the tag (e.g., :4b)
DEFAULT_NOTE_GENERATION_MODEL=MedAIBase/MedGemma1.5:4b
LABEL_MODEL=MedAIBase/MedGemma1.5:4b
Start the backend (requires environment variables):
cd web-api
source .venv/bin/activate
LD_LIBRARY_PATH=/tmp/ctranslate2_install/lib TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 \
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
[!TIP] Create a startup script
start-backend.shfor convenience:#!/bin/bash export LD_LIBRARY_PATH=/tmp/ctranslate2_install/lib:$LD_LIBRARY_PATH export TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 cd ~/projects/berta-ai-scribe/web-api source .venv/bin/activate uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
For better performance on DGX Spark, you can use llama.cpp instead of Ollama. llama.cpp is ~35% faster and supports Blackwell-native optimizations.
Build llama.cpp with CUDA 13 and Blackwell support:
cd ~
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
mkdir build-gpu && cd build-gpu
cmake .. -DCMAKE_BUILD_TYPE=Release -DGGML_CUDA=ON -DGGML_CUDA_F16=ON -DCMAKE_CUDA_ARCHITECTURES=121
make -j$(nproc)
Download a GGUF model (example: Llama 3.3 70B Q4):
mkdir -p ~/models
cd ~/models
# Download from Hugging Face (one-time, runs 100% locally after)
wget https://huggingface.co/bartowski/Llama-3.3-70B-Instruct-GGUF/resolve/main/Llama-3.3-70B-Instruct-Q4_K_M.gguf
Start llama-server:
cd ~/llama.cpp/build-gpu
LD_LIBRARY_PATH=./bin:$LD_LIBRARY_PATH ./bin/llama-server \
-m ~/models/Llama-3.3-70B-Instruct-Q4_K_M.gguf \
-ngl 99 -c 4096 --host 0.0.0.0 --port 8080
Configure environment - Use these settings in your web-api/.env file:
# AI Services (WhisperX GPU + LlamaCpp)
TRANSCRIPTION_SERVICE=WhisperX
WHISPERX_DEVICE=cuda
GENERATIVE_AI_SERVICE=LlamaCpp
# LLAMA_CPP_SERVER_URL=http://localhost:8080 # Optional, defaults to localhost:8080
# Model name must match the loaded GGUF file
DEFAULT_NOTE_GENERATION_MODEL=Llama-3.3-70B-Instruct-Q4_K_M.gguf
LABEL_MODEL=Llama-3.3-70B-Instruct-Q4_K_M.gguf
Start the backend (in a separate terminal):
cd web-api
source .venv/bin/activate
LD_LIBRARY_PATH=~/ctranslate2_install/lib:$LD_LIBRARY_PATH TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 \
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
[!TIP] Startup Order: Start llama-server first (wait ~60 seconds for model to load), then start the backend.
[!WARNING] Known Warnings (can be safely ignored):
- PyTorch may warn about CUDA capability 12.1 vs supported 12.0 - this generally works fine
- pyannote.audio version mismatch warnings - models still function correctly
For production deployments and better scaling, use NVIDIA's optimized vLLM Docker container. vLLM offers continuous batching, PagedAttention for efficient memory use, and tensor parallelism for multi-GPU setups.
[!IMPORTANT] GPU Sharing: Docker containers take exclusive GPU access. Start the backend (WhisperX) BEFORE launching the vLLM Docker container to allow both to coexist on unified memory.
Pull the NVIDIA-optimized vLLM container:
docker pull nvcr.io/nvidia/vllm:26.01-py3
Choose your model based on available memory:
| Model | Memory Required | Command |
|---|---|---|
| Llama 3.1 8B (recommended for GPU sharing) | ~16GB | See below |
| Llama 3.3 70B NVFP4 (Blackwell-optimized 4-bit) | ~40GB | See below |
Start vLLM Docker (choose one):
For Llama 3.1 8B (leaves ~50GB for WhisperX):
docker run --gpus all -p 8080:8080 \
-e HUGGING_FACE_HUB_TOKEN=your_hf_token \
nvcr.io/nvidia/vllm:26.01-py3 \
--model meta-llama/Llama-3.1-8B-Instruct \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.65 \
--port 8080
For Llama 3.3 70B with NVFP4 (Blackwell 4-bit quantization, ~3.3x memory reduction):
docker run --gpus all -p 8080:8080 \
-e HUGGING_FACE_HUB_TOKEN=your_hf_token \
nvcr.io/nvidia/vllm:26.01-py3 \
--model neuralmagic/Meta-Llama-3.3-70B-Instruct-nvfp4 \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.65 \
--port 8080
Configure environment - Use these settings in your web-api/.env file:
# AI Services (WhisperX GPU + vLLM Docker)
TRANSCRIPTION_SERVICE=WhisperX
WHISPERX_DEVICE=cuda
GENERATIVE_AI_SERVICE=VLLM
# vLLM Configuration
VLLM_SERVER_NAME=localhost
VLLM_SERVER_PORT=8080
# Model name must match exactly what vLLM loads
VLLM_MODEL_NAME=meta-llama/Llama-3.1-8B-Instruct
DEFAULT_NOTE_GENERATION_MODEL=meta-llama/Llama-3.1-8B-Instruct
LABEL_MODEL=meta-llama/Llama-3.1-8B-Instruct
Startup order (critical for GPU sharing):
# Terminal 1: Start backend FIRST (initializes WhisperX on GPU)
cd web-api && source .venv/bin/activate
LD_LIBRARY_PATH=~/ctranslate2_install/lib TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 \
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# Terminal 2: Start vLLM Docker AFTER backend is running
docker run --gpus all -p 8080:8080 ...
[!TIP] Scaling vLLM:
- Multi-GPU: Use
--tensor-parallel-size 2(or higher) to split model across GPUs- Multiple instances: Run several vLLM containers behind a load balancer
- Continuous batching: vLLM automatically batches concurrent requests for 2-4x throughput
- Check models:
curl http://localhost:8080/v1/modelsto verify loaded model name
[!NOTE] Switching models: If you previously used a different model, delete the
.datafolder to reset the database:rm -rf .data/The database will be recreated with the correct model names from your
.envfile on next startup.```
web-api/.env (backend) file.data folder in web-api directorylocalhost:4000 (F12 → Application tab → Clear storage)Start Ollama service FIRST:
ollama serve
# Keep this terminal open, then start backend in new terminal
Before starting backend:
.env must match exactly what's loaded in LM Studiouvicorn app.main:app --reload --port 8000)npm run dev)After completing your chosen AI service setup above:
Ensure your virtual environment is activated:
# If not already activated from the Backend Environment Setup
cd web-api
source .venv/bin/activate # macOS/Linux
# or .venv\Scripts\activate # Windows
Start the backend server:
uvicorn app.main:app --reload --port 8000
Create frontend environment file:
ai-scribe-app directory.env (note the dot at the beginning).env file:# Backend API URL
NEXT_PUBLIC_BACKEND_URL=http://localhost:8000
# Authentication Configuration
NEXT_PUBLIC_USE_COGNITO=false
NEXT_PUBLIC_USE_GOOGLE_AUTH=true
# Google OAuth Configuration (use same Client ID from backend setup)
GOOGLE_CLIENT_ID=your_google_client_id_from_step_above
GOOGLE_REDIRECT_URI=http://localhost:4000/login
Navigate to frontend directory:
cd ai-scribe-app
Install dependencies:
npm install
Start the frontend development server:
npm run dev
The frontend will be available at http://localhost:4000
[!NOTE] The
GOOGLE_CLIENT_IDshould be the same in both frontend and backend environment files.
http://localhost:4000Ollama Connection Issues:
ollama serve is running in a separate terminalhttp://localhost:11434ollama listPython Environment Issues:
python --versionpip install uvAuthentication Issues:
Google OAuth Errors:
http://localhost:4000/login is in your authorized redirect URIs.env filesPort Conflicts:
lsof -i :8000 (macOS/Linux) or netstat -ano | findstr :8000 (Windows). Kill the process or use a different port with --port 8001lsof -i :4000. If you change the port, remember to update your Google OAuth redirect URIs accordinglyps aux | grep ollamaService Startup Order:
Transcription Issues (Intel Mac / Windows / Linux):
nvidia-smiCreate AWS Account: If you don't have one, sign up at aws.amazon.com
Log into AWS Console: After creating your account, log into the AWS Management Console
Enable Bedrock Model Access:
us.meta.llama3-3-70b-instruct-v1:0)meta.llama3-1-405b-instruct-v1:0)meta.llama3-1-70b-instruct-v1:0)anthropic.claude-3-7-sonnet-20250219-v1:0)Register a Domain:
Option 1: Register through Route53 Console (Recommended):
Option 2: Use existing domain with Route53:
# If you have a domain registered elsewhere, create a hosted zone
aws route53 create-hosted-zone \
--name yourdomain.com \
--caller-reference $(date +%s) \
--hosted-zone-config Comment="Berta Scribe hosted zone"
# Note: You'll need to update your domain's nameservers to point to Route53
Find your Hosted Zone ID:
Method 1 (AWS Console - Recommended):
Z1D633PJN98FT9) - you'll need this for deploymentMethod 2 (AWS CLI):
aws route53 list-hosted-zones --query "HostedZones[?Name=='yourdomain.com.'].Id" --output text
If you already have a VPC set up:
Note Your VPC Details:
Verify your subnets (run this command to check):
aws ec2 describe-subnets --filters "Name=vpc-id,Values=<YOUR_VPC_ID>" --region us-west-2 \
--query 'Subnets[*].{ID:SubnetId,AZ:AvailabilityZone,CIDR:CidrBlock,Public:MapPublicIpOnLaunch}' --output table
Verify you have a NAT Gateway:
aws ec2 describe-nat-gateways --filter "Name=vpc-id,Values=<YOUR_VPC_ID>" --region us-west-2 \
--query 'NatGateways[*].{ID:NatGatewayId,State:State,SubnetId:SubnetId}' --output table
Skip to Step 3b to add security hardening, then proceed to Step 4
Use AWS VPC Wizard
Go to VPC Console:
VPC Settings - Choose "VPC and more":
| Setting | Value |
|---|---|
| Resources to create | VPC and more |
| Name tag auto-generation | berta |
| IPv4 CIDR block | 10.0.0.0/16 |
| IPv6 CIDR block | No IPv6 CIDR block |
| Tenancy | Default |
| Number of AZs | 2 |
| Number of public subnets | 2 |
| Number of private subnets | 2 |
| NAT gateways | In 1 AZ |
| VPC endpoints | S3 Gateway |
| DNS hostnames | Enabled |
| DNS resolution | Enabled |
Review the Preview - You should see:
Click "Create VPC" - AWS creates everything automatically!
Note Your Resource IDs (you'll need these for deployment):
| Resource | Where to Find |
|---|---|
| VPC ID | VPC Details tab |
| Public Subnets | Subnets with "public" in name (typically 10.0.0.0/20, 10.0.16.0/20) |
| Private Subnets | Subnets with "private" in name (typically 10.0.128.0/20, 10.0.144.0/20) |
After creating your VPC, add these security measures:
VPC Flow Logs help you monitor network traffic and detect suspicious activity:
# Create CloudWatch log group
aws logs create-log-group --log-group-name /vpc/berta-flow-logs --region us-west-2
# Create IAM role for flow logs
echo '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"vpc-flow-logs.amazonaws.com"},"Action":"sts:AssumeRole"}]}' > /tmp/trust-policy.json
aws iam create-role --role-name VPCFlowLogsRole --assume-role-policy-document file:///tmp/trust-policy.json
echo '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["logs:CreateLogStream","logs:PutLogEvents","logs:DescribeLogGroups","logs:DescribeLogStreams"],"Resource":"*"}]}' > /tmp/flow-logs-policy.json
aws iam put-role-policy --role-name VPCFlowLogsRole --policy-name FlowLogsPolicy --policy-document file:///tmp/flow-logs-policy.json
# Enable flow logs on your VPC (replace <YOUR_VPC_ID> and <YOUR_ACCOUNT_ID>)
aws ec2 create-flow-logs --resource-type VPC --resource-ids <YOUR_VPC_ID> --traffic-type ALL \
--log-destination-type cloud-watch-logs --log-group-name /vpc/berta-flow-logs \
--deliver-logs-permission-arn arn:aws:iam::<YOUR_ACCOUNT_ID>:role/VPCFlowLogsRole --region us-west-2
Add DENY rules to block common malicious ports on your private subnet Network ACL:
Go to VPC Console → Network ACLs
Select the NACL associated with your private subnets
Edit Outbound Rules - Add these DENY rules (lower rule numbers = higher priority):
| Rule # | Type | Port | Destination | Action |
|---|---|---|---|---|
| 50 | TCP | 23 | 0.0.0.0/0 | DENY |
| 51 | TCP | 445 | 0.0.0.0/0 | DENY |
| 52 | TCP | 2323 | 0.0.0.0/0 | DENY |
| 53 | TCP | 3389 | 0.0.0.0/0 | DENY |
| 54 | TCP | 3306 | 0.0.0.0/0 | DENY |
| 100 | ALL | ALL | 0.0.0.0/0 | ALLOW |
[!NOTE] These rules block common ports used by malware for scanning (Telnet, SMB, RDP, MySQL). The CloudFormation template already includes restrictive security group rules, but Network ACLs provide an additional layer of protection.
Now you'll deploy Berta Scribe application using AWS CloudFormation:
Option A: One-click Deployment (Recommended)
Click the deployment button:
You'll be taken to the AWS CloudFormation console where you'll see a form to fill out
Option B: Custom Deployment
If you need to modify the CloudFormation template (e.g., change instance sizes, add custom configurations), you can use the template.yaml file included in this repository. Download the template, make your modifications, and deploy it manually through the AWS CloudFormation console or AWS CLI instead of using the one-click deployment above.
[!IMPORTANT] If you modify the
template.yamlfile and deploy it manually, you cannot use the one-click deployment button. You must deploy your custom template through the AWS CloudFormation console or CLI.
Fill in the required parameters:
| Parameter | Description | Example |
|---|---|---|
| Environment | Deployment environment | production |
| HostedZoneId | Route53 Hosted Zone ID | Z1D633PJN98FT9 |
| VpcId | VPC ID from Step 3 | vpc-12345678 |
| PublicSubnets | Public subnet IDs (comma-separated) | subnet-12345,subnet-67890 |
| PrivateSubnets | Private subnet IDs (comma-separated) | subnet-abcde,subnet-fghij |
| DomainName | Your domain name | yourdomain.com |
| AuthDomainPrefix | Prefix part of the domain name | yourdomain |
| AccessTokenSecret | JWT signing secret | Generate with openssl rand -base64 32 |
| DBName | Database name | berta |
| DBUser | Database username | berta_admin |
| DBPassword | Database password | Generate secure password |
Deploy the stack:
Monitor the deployment:
Get your application URLs:
Test the application:
Docker Images: The CloudFormation template uses pre-built Docker images hosted on AWS Public ECR:
public.ecr.aws/s9f8j1d3/berta-frontend:latestpublic.ecr.aws/s9f8j1d3/berta-backend:latestThese images are automatically pulled during deployment and contain the latest stable versions of the application components.
Updates: When new releases are available, we update the images at the same URLs. To get the latest version, simply restart your ECS services:
aws ecs update-service --cluster berta-cluster-production --service berta-frontend-production --force-new-deployment
aws ecs update-service --cluster berta-cluster-production --service berta-backend-production --force-new-deployment
Berta Scribe currently supports AWS for cloud production deployments. Support for Azure, GCP, and Databricks is under consideration based on community interest. If you need support for a specific platform, please open an issue on GitHub.
You can view all available services and models by running:
cd web-api
python -m app.cli.list_services
This will show:
ollama list outputus.meta.llama3-3-70b-instruct-v1:0, meta.llama3-1-405b-instruct-v1:0, meta.llama3-1-70b-instruct-v1:0, anthropic.claude-3-7-sonnet-20250219-v1:0gpt-4o, gpt-3.5-turboBerta Scribe implements robust security measures:
This project uses third-party libraries and models, including:
For the full text of these licenses, please see the THIRD_PARTY_LICENSES file in this repository.
This project uses Meta Llama 3.3. As per the Llama 3.3 license requirements:
For the complete Meta Llama 3.3 Community License Agreement, refer to the THIRD_PARTY_LICENSES file.
This project integrates with external services that users install and manage separately:
[!IMPORTANT] The Licensed Work is provided as a support tool only and is not intended as a substitute for the guidance or care of a health professional.
[!CAUTION] The authors disclaim all warranties, expressed or implied. In particular, but without limitation, the Licensed Work is provided WITHOUT WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, EITHER EXPRESSED OR IMPLIED. The user assumes all responsibility for losses, costs, claims, damages or liability of any kind whatsoever which may arise from use of the Licensed Work.
79 commits
Python
48.0%
TypeScript
45.5%
PLSQL
3.0%
JavaScript
1.7%
TeX
1.1%