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
8 changes: 6 additions & 2 deletions .github/workflows/robot-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,14 @@ jobs:
python-version: "3.12"
cache: 'pip'

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"

- name: Install Robot Framework and dependencies
run: |
pip install --upgrade pip
pip install robotframework robotframework-requests python-dotenv websockets
uv pip install --system robotframework robotframework-requests python-dotenv websockets
- name: Create test environment file
working-directory: tests/setup
Expand Down
22 changes: 21 additions & 1 deletion Docs/init-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,33 @@ uv run --with-requirements setup-requirements.txt python services.py start backe
# Check service status
uv run --with-requirements setup-requirements.txt python services.py status

# Restart all services
uv run --with-requirements setup-requirements.txt python services.py restart --all

# Restart specific services
uv run --with-requirements setup-requirements.txt python services.py restart backend

# Stop all services
uv run --with-requirements setup-requirements.txt python services.py stop --all

# Stop specific services
# Stop specific services
uv run --with-requirements setup-requirements.txt python services.py stop asr-services openmemory-mcp
```

**Convenience Scripts:**
```bash
# Quick start (from project root)
./start.sh

# Quick restart (from project root)
./restart.sh
```

**Important Notes:**
- **Restart** restarts containers without rebuilding - use for configuration changes (.env updates)
- **For code changes**, use `stop` + `start --build` to rebuild images
- Example: `uv run --with-requirements setup-requirements.txt python services.py stop --all && uv run --with-requirements setup-requirements.txt python services.py start --all --build`

### Manual Service Management
You can also manage services individually:

Expand Down
16 changes: 15 additions & 1 deletion Docs/ports-and-access.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,24 @@ uv run --with-requirements setup-requirements.txt python services.py start --all
# Start only specific services
uv run --with-requirements setup-requirements.txt python services.py start backend speaker-recognition

# Restart all services
uv run --with-requirements setup-requirements.txt python services.py restart --all

# Restart specific services
uv run --with-requirements setup-requirements.txt python services.py restart backend

# Stop all services
uv run --with-requirements setup-requirements.txt python services.py stop --all
```

**Convenience Scripts:**
```bash
./start.sh # Quick start all configured services
./restart.sh # Quick restart all configured services
```

**Important:** Use `restart` for configuration changes (.env updates). For code changes, use `stop` + `start --build` to rebuild images.

---

## Microphone Access Requirements
Expand All @@ -128,7 +142,7 @@ If you encounter port conflicts:
1. **Check running services**: `uv run --with-requirements setup-requirements.txt python services.py status`
2. **Stop conflicting services**: `uv run --with-requirements setup-requirements.txt python services.py stop --all`
3. **Change ports in .env files** if needed
4. **Restart services**: `uv run --with-requirements setup-requirements.txt python services.py start --all`
4. **Restart services**: `uv run --with-requirements setup-requirements.txt python services.py restart --all` or `./restart.sh`

---

Expand Down
1 change: 0 additions & 1 deletion backends/advanced/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ services:
- MISTRAL_MODEL=${MISTRAL_MODEL}
- TRANSCRIPTION_PROVIDER=${TRANSCRIPTION_PROVIDER}
- PARAKEET_ASR_URL=${PARAKEET_ASR_URL}
- OFFLINE_ASR_TCP_URI=${OFFLINE_ASR_TCP_URI}
- OLLAMA_BASE_URL=${OLLAMA_BASE_URL}
- HF_TOKEN=${HF_TOKEN}
- SPEAKER_SERVICE_URL=${SPEAKER_SERVICE_URL}
Expand Down
2 changes: 1 addition & 1 deletion backends/advanced/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ def setup_transcription(self):
self.console.print("[yellow][WARNING][/yellow] No API key provided - transcription will not work")

elif choice == "3":
self.config["TRANSCRIPTION_PROVIDER"] = "offline"
self.config["TRANSCRIPTION_PROVIDER"] = "parakeet"
self.console.print("[blue][INFO][/blue] Offline Parakeet ASR selected")
parakeet_url = self.prompt_value("Parakeet ASR URL", "http://host.docker.internal:8767")
self.config["PARAKEET_ASR_URL"] = parakeet_url
Expand Down
93 changes: 72 additions & 21 deletions backends/advanced/run-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,74 @@ print_info "Advanced Backend Integration Test Runner"
print_info "========================================"

# Load environment variables (CI or local)
if [ -f ".env" ] && [ -z "$DEEPGRAM_API_KEY" ]; then
# Priority: CI environment > .env.test > .env
if [ -n "$DEEPGRAM_API_KEY" ]; then
print_info "Using environment variables from CI/environment..."
elif [ -f ".env.test" ]; then
print_info "Loading environment variables from .env.test..."
set -a
source .env.test
set +a
elif [ -f ".env" ]; then
print_info "Loading environment variables from .env..."
set -a
source .env
set +a
elif [ -n "$DEEPGRAM_API_KEY" ]; then
print_info "Using environment variables from CI..."
else
print_error "Neither .env file nor CI environment variables found!"
print_info "For local development: cp .env.template .env and configure API keys"
print_info "For CI: ensure DEEPGRAM_API_KEY and OPENAI_API_KEY secrets are set"
print_error "Neither .env.test nor .env file found, and no environment variables set!"
print_info "For local development: cp .env.template .env and configure required API keys"
print_info "For CI: ensure required API keys are set based on configured providers"
exit 1
fi

# Verify required environment variables
if [ -z "$DEEPGRAM_API_KEY" ]; then
print_error "DEEPGRAM_API_KEY not set"
exit 1
fi

if [ -z "$OPENAI_API_KEY" ]; then
print_error "OPENAI_API_KEY not set"
exit 1
fi

print_info "DEEPGRAM_API_KEY length: ${#DEEPGRAM_API_KEY}"
print_info "OPENAI_API_KEY length: ${#OPENAI_API_KEY}"
# Verify required environment variables based on configured providers
TRANSCRIPTION_PROVIDER=${TRANSCRIPTION_PROVIDER:-deepgram}
LLM_PROVIDER=${LLM_PROVIDER:-openai}

print_info "Configured providers:"
print_info " TRANSCRIPTION_PROVIDER: $TRANSCRIPTION_PROVIDER"
print_info " LLM_PROVIDER: $LLM_PROVIDER"

# Check transcription provider API key
case "$TRANSCRIPTION_PROVIDER" in
deepgram)
if [ -z "$DEEPGRAM_API_KEY" ]; then
print_error "DEEPGRAM_API_KEY not set (required for TRANSCRIPTION_PROVIDER=deepgram)"
exit 1
fi
print_info "DEEPGRAM_API_KEY length: ${#DEEPGRAM_API_KEY}"
;;
mistral)
if [ -z "$MISTRAL_API_KEY" ]; then
print_error "MISTRAL_API_KEY not set (required for TRANSCRIPTION_PROVIDER=mistral)"
exit 1
fi
print_info "MISTRAL_API_KEY length: ${#MISTRAL_API_KEY}"
;;
offline|parakeet)
print_info "Using offline/local transcription - no API key required"
;;
*)
print_warning "Unknown TRANSCRIPTION_PROVIDER: $TRANSCRIPTION_PROVIDER"
;;
esac

# Check LLM provider API key (for memory extraction)
case "$LLM_PROVIDER" in
openai)
if [ -z "$OPENAI_API_KEY" ]; then
print_error "OPENAI_API_KEY not set (required for LLM_PROVIDER=openai)"
exit 1
fi
print_info "OPENAI_API_KEY length: ${#OPENAI_API_KEY}"
;;
ollama)
print_info "Using Ollama for LLM - no API key required"
;;
*)
print_warning "Unknown LLM_PROVIDER: $LLM_PROVIDER"
;;
esac

# Ensure memory_config.yaml exists
if [ ! -f "memory_config.yaml" ] && [ -f "memory_config.yaml.template" ]; then
Expand Down Expand Up @@ -119,9 +160,19 @@ export DOCKER_BUILDKIT=0

# Run the integration test with extended timeout (mem0 needs time for comprehensive extraction)
print_info "Starting integration test (timeout: 15 minutes)..."
timeout 900 uv run pytest tests/test_integration.py::test_full_pipeline_integration -v -s --tb=short --log-cli-level=INFO
if timeout 900 uv run pytest tests/test_integration.py::test_full_pipeline_integration -v -s --tb=short --log-cli-level=INFO; then
print_success "Integration tests completed successfully!"
else
TEST_EXIT_CODE=$?
print_error "Integration tests FAILED with exit code: $TEST_EXIT_CODE"

print_success "Integration tests completed successfully!"
# Clean up test containers before exiting
print_info "Cleaning up test containers after failure..."
docker compose -f docker-compose-test.yml down -v || true
docker system prune -f || true

exit $TEST_EXIT_CODE
fi

# Clean up test containers
print_info "Cleaning up test containers..."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,13 +304,13 @@ async def _initialize_streaming_session(

# Determine transcription provider from environment
transcription_provider = os.getenv("TRANSCRIPTION_PROVIDER", "").lower()
if transcription_provider in ["offline", "parakeet"]:
if transcription_provider == "parakeet":
provider = "parakeet"
elif transcription_provider == "deepgram":
provider = "deepgram"
else:
# Auto-detect: prefer Parakeet if URL is set, otherwise Deepgram
parakeet_url = os.getenv("PARAKEET_ASR_URL") or os.getenv("OFFLINE_ASR_TCP_URI")
parakeet_url = os.getenv("PARAKEET_ASR_URL")
deepgram_key = os.getenv("DEEPGRAM_API_KEY")
if parakeet_url:
provider = "parakeet"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,6 @@ def get_transcription_provider(
else:
return ParakeetProvider(parakeet_url)

elif provider_name == "offline":
# "offline" is an alias for Parakeet ASR
if not parakeet_url:
raise RuntimeError(
"Offline transcription provider requested but PARAKEET_ASR_URL not configured"
)
logger.info(f"Using offline Parakeet transcription provider in {mode} mode")
if mode == "streaming":
return ParakeetStreamingProvider(parakeet_url)
else:
return ParakeetProvider(parakeet_url)

# Auto-select provider based on available configuration (when provider_name is None)
if provider_name is None:
# Check TRANSCRIPTION_PROVIDER environment variable first
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ def __init__(self, redis_client, service_url: str = None, buffer_chunks: int = 3
service_url: Parakeet service URL (defaults to PARAKEET_ASR_URL env var)
buffer_chunks: Number of chunks to buffer before transcribing (default: 30 = ~7.5s)
"""
self.service_url = service_url or os.getenv("PARAKEET_ASR_URL") or os.getenv("OFFLINE_ASR_TCP_URI")
self.service_url = service_url or os.getenv("PARAKEET_ASR_URL")
if not self.service_url:
raise ValueError("PARAKEET_ASR_URL or OFFLINE_ASR_TCP_URI is required")
raise ValueError("PARAKEET_ASR_URL is required")

# Initialize Parakeet provider
self.provider = ParakeetProvider(service_url=self.service_url)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ async def main():
logger.info("🚀 Starting Parakeet audio stream worker")

# Get configuration from environment
service_url = os.getenv("PARAKEET_ASR_URL") or os.getenv("OFFLINE_ASR_TCP_URI")
service_url = os.getenv("PARAKEET_ASR_URL")
if not service_url:
logger.warning("PARAKEET_ASR_URL or OFFLINE_ASR_TCP_URI environment variable not set - Parakeet audio stream worker will not start")
logger.warning("PARAKEET_ASR_URL environment variable not set - Parakeet audio stream worker will not start")
logger.warning("Audio transcription will use alternative providers if configured")
return

Expand Down
12 changes: 7 additions & 5 deletions backends/advanced/start-workers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ start_workers() {
AUDIO_STREAM_DEEPGRAM_WORKER_PID=""
fi

# Only start Parakeet worker if PARAKEET_ASR_URL or OFFLINE_ASR_TCP_URI is set
if [ -n "$PARAKEET_ASR_URL" ] || [ -n "$OFFLINE_ASR_TCP_URI" ]; then

# Only start Parakeet worker if PARAKEET_ASR_URL is set
if [ -n "$PARAKEET_ASR_URL" ]; then

echo "🎵 Starting audio stream Parakeet worker (1 worker for sequential processing)..."
uv run python -m advanced_omi_backend.workers.audio_stream_parakeet_worker &
AUDIO_STREAM_PARAKEET_WORKER_PID=$!
else
echo "⏭️ Skipping Parakeet stream worker (PARAKEET_ASR_URL/OFFLINE_ASR_TCP_URI not set)"
echo "⏭️ Skipping Parakeet stream worker (PARAKEET_ASR_URL not set)"
AUDIO_STREAM_PARAKEET_WORKER_PID=""
fi

Expand All @@ -80,8 +82,8 @@ start_workers() {
echo " - RQ worker 5: PID $RQ_WORKER_5_PID (transcription, memory, default)"
echo " - RQ worker 6: PID $RQ_WORKER_6_PID (transcription, memory, default)"
echo " - Audio persistence worker: PID $AUDIO_PERSISTENCE_WORKER_PID (audio queue - file rotation)"
[ -n "$AUDIO_STREAM_DEEPGRAM_WORKER_PID" ] && echo " - Deepgram stream worker: PID $AUDIO_STREAM_DEEPGRAM_WORKER_PID (real-time transcription)" || true
[ -n "$AUDIO_STREAM_PARAKEET_WORKER_PID" ] && echo " - Parakeet stream worker: PID $AUDIO_STREAM_PARAKEET_WORKER_PID (real-time transcription)" || true
[ -n "$AUDIO_STREAM_DEEPGRAM_WORKER_PID" ] && echo " - Audio stream Deepgram worker: PID $AUDIO_STREAM_DEEPGRAM_WORKER_PID (Redis Streams consumer)" || true
[ -n "$AUDIO_STREAM_PARAKEET_WORKER_PID" ] && echo " - Audio stream Parakeet worker: PID $AUDIO_STREAM_PARAKEET_WORKER_PID (Redis Streams consumer)" || true
}

# Function to check worker registration health
Expand Down
14 changes: 7 additions & 7 deletions backends/advanced/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
# Test constants
BACKEND_URL = "http://localhost:8001" # Test backend port
TEST_AUDIO_PATH = tests_dir.parent.parent.parent / "extras/test-audios/DIY Experts Glass Blowing_16khz_mono_4min.wav"
TEST_AUDIO_PATH_OFFLINE = tests_dir / "assets" / "test_clip_10s.wav" # Shorter clip for offline testing
TEST_AUDIO_PATH_PARAKEET = tests_dir / "assets" / "test_clip_10s.wav" # Shorter clip for parakeet testing
MAX_STARTUP_WAIT = 60 # seconds
PROCESSING_TIMEOUT = 300 # seconds for audio processing (5 minutes)

Expand Down Expand Up @@ -715,8 +715,8 @@ def authenticate(self):

def upload_test_audio(self):
"""Upload test audio file and monitor processing."""
# Use different audio file for offline provider
audio_path = TEST_AUDIO_PATH_OFFLINE if self.provider == "offline" else TEST_AUDIO_PATH
# Use different audio file for parakeet provider (shorter for faster testing)
audio_path = TEST_AUDIO_PATH_PARAKEET if self.provider == "parakeet" else TEST_AUDIO_PATH

logger.info(f"📤 Uploading test audio: {audio_path.name}")

Expand Down Expand Up @@ -1425,21 +1425,21 @@ def test_full_pipeline_integration(test_runner):
phase_times['service_startup'] = time.time() - phase_start
logger.info(f"✅ Service startup completed in {phase_times['service_startup']:.2f}s")

# Phase 2b: ASR service startup (offline only)
# Phase 2b: ASR service startup (parakeet only)
phase_start = time.time()
logger.info(f"🎤 Phase 2b: Starting ASR services ({TRANSCRIPTION_PROVIDER} provider)...")
test_runner.start_asr_services()
phase_times['asr_startup'] = time.time() - phase_start
logger.info(f"✅ ASR service startup completed in {phase_times['asr_startup']:.2f}s")

# Phase 3: Wait for services
phase_start = time.time()
logger.info("⏳ Phase 3: Waiting for services to be ready...")
test_runner.wait_for_services()
phase_times['service_readiness'] = time.time() - phase_start
logger.info(f"✅ Service readiness check completed in {phase_times['service_readiness']:.2f}s")
# Phase 3b: Wait for ASR services (offline only)

# Phase 3b: Wait for ASR services (parakeet only)
phase_start = time.time()
logger.info("⏳ Phase 3b: Waiting for ASR services to be ready...")
test_runner.wait_for_asr_ready()
Expand Down
Loading
Loading