Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions backends/advanced/setup-requirements.txt

This file was deleted.

5 changes: 5 additions & 0 deletions extras/asr-services/.env.template
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# ASR Services Configuration
# Copy this file to .env and configure as needed

# PyTorch CUDA version for Docker build
# Options: cu121 (CUDA 12.1), cu126 (CUDA 12.6), cu128 (CUDA 12.8)
# Should match your system's CUDA version (check with: nvidia-smi)
PYTORCH_CUDA_VERSION=cu126

# Parakeet ASR Model Selection
PARAKEET_MODEL=nvidia/parakeet-tdt-0.6b-v3

Expand Down
6 changes: 3 additions & 3 deletions extras/asr-services/Dockerfile_Parakeet
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
######################### builder #################################
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder

# Accept CUDA version as build argument
ARG CUDA_VERSION
# Accept PyTorch CUDA version as build argument
ARG PYTORCH_CUDA_VERSION

WORKDIR /app

Expand All @@ -16,7 +16,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \

# Dependency manifest first for cache‑friendly installs
COPY pyproject.toml uv.lock ./
RUN uv sync --no-install-project --group parakeet --extra ${CUDA_VERSION} && \
RUN uv sync --no-install-project --group parakeet --extra ${PYTORCH_CUDA_VERSION} && \
uv cache clean

# Should prepare the .venv for use :)
Expand Down
2 changes: 1 addition & 1 deletion extras/asr-services/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ services:
context: .
dockerfile: Dockerfile_Parakeet
args:
CUDA_VERSION: ${CUDA_VERSION:-cu121}
PYTORCH_CUDA_VERSION: ${PYTORCH_CUDA_VERSION:-cu126}
image: parakeet-asr:latest
ports:
- "${PARAKEET_HOST_PORT:-8767}:${PARAKEET_CONTAINER_PORT:-8765}"
Expand Down
268 changes: 268 additions & 0 deletions extras/asr-services/init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
#!/usr/bin/env python3
"""
Chronicle ASR Services Setup Script
Interactive configuration for offline ASR (Parakeet) service
"""

import argparse
import os
import shutil
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict

from dotenv import set_key
from rich import print as rprint
from rich.console import Console
from rich.panel import Panel
from rich.prompt import Confirm, Prompt
from rich.text import Text


class ASRServicesSetup:
def __init__(self, args=None):
self.console = Console()
self.config: Dict[str, Any] = {}
self.args = args or argparse.Namespace()

def print_header(self, title: str):
"""Print a colorful header"""
self.console.print()
panel = Panel(
Text(title, style="cyan bold"),
style="cyan",
expand=False
)
self.console.print(panel)
self.console.print()

def print_section(self, title: str):
"""Print a section header"""
self.console.print()
self.console.print(f"[magenta]► {title}[/magenta]")
self.console.print("[magenta]" + "─" * len(f"► {title}") + "[/magenta]")

def prompt_value(self, prompt: str, default: str = "") -> str:
"""Prompt for a value with optional default"""
try:
return Prompt.ask(prompt, default=default)
except EOFError:
self.console.print(f"Using default: {default}")
return default

def prompt_choice(self, prompt: str, choices: Dict[str, str], default: str = "1") -> str:
"""Prompt for a choice from options"""
self.console.print(prompt)
for key, desc in choices.items():
self.console.print(f" {key}) {desc}")
self.console.print()

while True:
try:
choice = Prompt.ask("Enter choice", default=default)
if choice in choices:
return choice
self.console.print(f"[red]Invalid choice. Please select from {list(choices.keys())}[/red]")
except EOFError:
self.console.print(f"Using default choice: {default}")
return default

def read_existing_env_value(self, key: str) -> str:
"""Read a value from existing .env file"""
env_path = Path(".env")
if not env_path.exists():
return None

from dotenv import get_key
value = get_key(str(env_path), key)
# get_key returns None if key doesn't exist or value is empty
return value if value else None

def backup_existing_env(self):
"""Backup existing .env file"""
env_path = Path(".env")
if env_path.exists():
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f".env.backup.{timestamp}"
shutil.copy2(env_path, backup_path)
self.console.print(f"[blue][INFO][/blue] Backed up existing .env file to {backup_path}")

def detect_cuda_version(self) -> str:
"""Detect system CUDA version from nvidia-smi"""
try:
result = subprocess.run(
["nvidia-smi", "--query-gpu=driver_version", "--format=csv,noheader"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
# Try to get CUDA version from nvidia-smi
result = subprocess.run(
["nvidia-smi"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
output = result.stdout
# Parse CUDA Version from nvidia-smi output
# Format: "CUDA Version: 12.6"
import re
match = re.search(r'CUDA Version:\s*(\d+)\.(\d+)', output)
if match:
major, minor = match.groups()
cuda_ver = f"{major}.{minor}"

# Map to available PyTorch CUDA versions
if cuda_ver >= "12.8":
return "cu128"
elif cuda_ver >= "12.6":
return "cu126"
elif cuda_ver >= "12.1":
return "cu121"
except (subprocess.SubprocessError, FileNotFoundError):
pass
return "cu126" # Default fallback to cu126

def setup_cuda_version(self):
"""Configure PyTorch CUDA version"""
self.print_section("PyTorch CUDA Version Configuration")

# Check if provided via command line
if hasattr(self.args, 'pytorch_cuda_version') and self.args.pytorch_cuda_version:
cuda_version = self.args.pytorch_cuda_version
self.console.print(f"[green][SUCCESS][/green] PyTorch CUDA version configured from command line: {cuda_version}")
else:
# Detect system CUDA version and suggest as default
detected_cuda = self.detect_cuda_version()

# Map to default choice number
cuda_to_choice = {
"cu121": "1",
"cu126": "2",
"cu128": "3"
}
default_choice = cuda_to_choice.get(detected_cuda, "2")

self.console.print()
self.console.print(f"[blue][INFO][/blue] Detected CUDA version: {detected_cuda}")
self.console.print("[blue][INFO][/blue] This controls which PyTorch version is installed for GPU acceleration")
self.console.print()

cuda_choices = {
"1": "CUDA 12.1 (cu121)",
"2": "CUDA 12.6 (cu126) - Recommended",
"3": "CUDA 12.8 (cu128)"
}
cuda_choice = self.prompt_choice(
"Choose CUDA version for PyTorch:",
cuda_choices,
default_choice
)

choice_to_cuda = {
"1": "cu121",
"2": "cu126",
"3": "cu128"
}
cuda_version = choice_to_cuda[cuda_choice]

self.config["PYTORCH_CUDA_VERSION"] = cuda_version
self.console.print(f"[blue][INFO][/blue] Using PyTorch with CUDA version: {cuda_version}")

def generate_env_file(self):
"""Generate .env file from template and update with configuration"""
env_path = Path(".env")
env_template = Path(".env.template")

# Backup existing .env if it exists
self.backup_existing_env()

# Copy template to .env
if env_template.exists():
shutil.copy2(env_template, env_path)
self.console.print("[blue][INFO][/blue] Copied .env.template to .env")
else:
self.console.print("[yellow][WARNING][/yellow] .env.template not found, creating new .env")
env_path.touch(mode=0o600)

# Update configured values using set_key
env_path_str = str(env_path)
for key, value in self.config.items():
if value: # Only set non-empty values
set_key(env_path_str, key, value)

# Ensure secure permissions
os.chmod(env_path, 0o600)

self.console.print("[green][SUCCESS][/green] .env file configured successfully with secure permissions")

def show_summary(self):
"""Show configuration summary"""
self.print_section("Configuration Summary")
self.console.print()

self.console.print(f"✅ PyTorch CUDA Version: {self.config.get('PYTORCH_CUDA_VERSION', 'Not configured')}")

def show_next_steps(self):
"""Show next steps"""
self.print_section("Next Steps")
self.console.print()

self.console.print("1. Start the Parakeet ASR service:")
self.console.print(" [cyan]docker compose up --build -d parakeet-asr[/cyan]")
self.console.print()
self.console.print("2. Service will be available at:")
self.console.print(" [cyan]http://host.docker.internal:8767[/cyan]")
self.console.print()
self.console.print("3. Configure your backend to use offline ASR:")
self.console.print(" Set PARAKEET_ASR_URL=http://host.docker.internal:8767 in backend .env")

def run(self):
"""Run the complete setup process"""
self.print_header("🎤 ASR Services (Parakeet) Setup")
self.console.print("Configure offline speech-to-text service")
self.console.print()

try:
# Run setup steps
self.setup_cuda_version()

# Generate files
self.print_header("Configuration Complete!")
self.generate_env_file()

# Show results
self.show_summary()
self.show_next_steps()

self.console.print()
self.console.print("[green][SUCCESS][/green] ASR Services setup complete! 🎉")

except KeyboardInterrupt:
self.console.print()
self.console.print("[yellow]Setup cancelled by user[/yellow]")
sys.exit(0)
except Exception as e:
self.console.print(f"[red][ERROR][/red] Setup failed: {e}")
sys.exit(1)


def main():
"""Main entry point"""
parser = argparse.ArgumentParser(description="ASR Services (Parakeet) Setup")
parser.add_argument("--pytorch-cuda-version",
choices=["cu121", "cu126", "cu128"],
help="PyTorch CUDA version (default: auto-detect)")

args = parser.parse_args()

setup = ASRServicesSetup(args)
setup.run()


if __name__ == "__main__":
main()
4 changes: 4 additions & 0 deletions extras/asr-services/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,7 @@ parakeet = [
[build-system]
requires = ["setuptools>=64", "wheel"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
# No packages - this project only contains standalone scripts
packages = []
Loading