Skip to content

feat(tts): serve all TTS from the RunPod Orpheus-3B deployment#242

Merged
PatrickCmd merged 6 commits into
mainfrom
tts-runpod-orpheus
Jul 25, 2026
Merged

feat(tts): serve all TTS from the RunPod Orpheus-3B deployment#242
PatrickCmd merged 6 commits into
mainfrom
tts-runpod-orpheus

Conversation

@PatrickCmd

@PatrickCmd PatrickCmd commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes the RunPod Orpheus-3B deployment the only TTS inference serving point. POST /tasks/audio/speech (and its batch/speakers/url siblings) now route exclusively to RunPod Orpheus; spark-tts, Modal, and the legacy per-provider TTS routes are removed.

Net −4,576 lines (mostly deletions). Design spec: docs/superpowers/specs/2026-07-25-tts-runpod-orpheus-only-design.md.

Key finding

The RunPod server.py is a port of the former Modal app with the identical route table (/tts, /tts/batch, /speakers, /health), so this is a repoint + teardown, not a new client. The Modal client's retry/backoff logic was generalized into OrpheusClient, pointed at RunPod with Bearer auth and long cold-start timeouts.

What changed

New

  • app/integrations/orpheus_runpod.pyOrpheusClient (Bearer auth, long timeouts, stream_speech() for /v1/audio/speech).
  • app/tests/test_orpheus_runpod_client.py — client unit tests (MockTransport).

Core

  • speech_service.py — Orpheus-only; adds stream(); surfaces audio_bytes for response_mode='both'.
  • routers/audio.pycreate_speech: url (signed GCS URL) · stream (RunPod byte passthrough) · both (bytes + X-Audio-Url header). Batch capped at 16.
  • schemas/speech.py — drop model, platform, max_new_audio_tokens; batch max 128→16.
  • message_processor.py — WhatsApp voice replies synthesize via the shared Orpheus SpeechService (uses returned bytes directly).
  • config.py / .env.exampleTTS_API_URL (default https://gfg8z4dvexiz85.api.runpod.ai/v1, /v1 optional) is the TTS base URL; ORPHEUS_MODEL_NAME (default sunbird-orpheus-tts) is the /v1/audio/speech model id; bearer reuses RUNPOD_API_KEY.

Removedtts.py, runpod_tts.py, orpheus_tts.py routers, legacy /tasks/tts, tts_service.py, runpod_tts_service.py, orpheus_modal.py, and their tests.

Docsdocs.py, docs/tutorial.md, frontend/src/pages/Tutorial.tsx.

Breaking changes

  • /tasks/audio/speech rejects model / platform / max_new_audio_tokens and no longer offers spark-tts.
  • Legacy routes removed: /tasks/modal/tts*, /tasks/runpod/tts, /tasks/modal/orpheus/*, /tasks/tts.
  • response_mode='both' now returns raw audio bytes with the signed URL in the X-Audio-Url header (was SSE).

Config

  • TTS_API_URL is the RunPod endpoint (default https://gfg8z4dvexiz85.api.runpod.ai/v1; the /v1 suffix is optional). The client derives both the root (custom routes) and the /v1 base (/v1/audio/speech). If set to empty, TTS 503s at request time.
  • ORPHEUS_MODEL_NAME = sunbird-orpheus-tts (model id for /v1/audio/speech).
  • Bearer auth reuses RUNPOD_API_KEY.

Test plan

  • pytest app/tests/1126 passed; 6 failures are pre-existing .env-leak failures (ga_*, qwen_endpoint_id) unrelated to TTS.
  • black/isort/flake8 on app/ → clean.
  • frontend/ npm run build (tsc + vite) → clean.

Not verified here

  • Live synthesis against the RunPod endpoint (no key locally). The client relies on the RunPod port keeping Modal's exact /tts headers and /tts/batch base64 shape — if the port diverged, client parsing is where to adjust.
  • frontend npm run lint — the repo's ESLint config predates ESLint 10 and errors before linting (pre-existing). Type coverage via tsc.

🤖 Generated with Claude Code

PatrickCmd and others added 6 commits July 25, 2026 12:09
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the RunPod Orpheus-3B endpoint the only TTS inference serving point.
POST /tasks/audio/speech (+ batch, speakers, url) now route exclusively to
RunPod Orpheus; spark-tts, Modal, and the legacy per-provider TTS routes are
removed.

- integrations: add OrpheusClient (repoint of the retired Modal client) with
  Bearer auth, long cold-start timeouts, and a stream_speech() method for
  /v1/audio/speech; delete orpheus_modal.py.
- services: Orpheus-only SpeechService (drop model/platform matrix + spark);
  add stream() and surface synthesized audio_bytes for response_mode='both'.
- router: create_speech supports response_mode url | stream | both (both streams
  bytes with the signed URL in X-Audio-Url); batch capped at 16 (RunPod limit).
- schema: drop model, platform, max_new_audio_tokens; batch max 128 -> 16.
- whatsapp: message_processor voice replies synthesize via the shared Orpheus
  SpeechService using the returned bytes directly.
- config: ORPHEUS_BASE_URL (+ timeouts) replace orpheus_modal_* and
  whatsapp_tts_backend; bearer reuses RUNPOD_API_KEY.
- remove: tts.py, runpod_tts.py, orpheus_tts.py routers, legacy /tasks/tts,
  tts_service.py, runpod_tts_service.py, and their tests/wiring.
- tests: new client unit tests; rewrite speech/batch/voice/tags/deps tests for
  the Orpheus-only contract.
- docs: docs.py, tutorial.md, Tutorial.tsx updated (breaking change).

BREAKING CHANGE: /tasks/audio/speech rejects model/platform/max_new_audio_tokens
and no longer offers spark-tts; the legacy /tasks/modal/tts, /tasks/runpod/tts,
/tasks/modal/orpheus/*, /tasks/tts routes are removed. ORPHEUS_BASE_URL must be
set for TTS to function.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reuse the existing TTS_API_URL setting (default the RunPod Orpheus endpoint
https://gfg8z4dvexiz85.api.runpod.ai/v1) as the single TTS base URL instead of
a separate ORPHEUS_BASE_URL. The client accepts the URL with or without a '/v1'
suffix and derives both the root (custom /tts, /tts/batch, /speakers, /health)
and the /v1 base (/v1/audio/speech).

Add ORPHEUS_MODEL_NAME (default 'sunbird-orpheus-tts'), the model identifier
sent to /v1/audio/speech, replacing the hardcoded string.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removing the (module-unused) settings import broke the pre-existing dedup test
that monkeypatches mp.settings.whatsapp_dedup_backend. Restore it at module
scope with a noqa so tests can target mp.settings.*.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@PatrickCmd
PatrickCmd merged commit 2fd1b13 into main Jul 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant