Files
majordomo/docs/adr/0017-audio-interfaces.md
T
steve 434d721b99
CI / Tidy (pull_request) Successful in 10m9s
CI / Build & Test (pull_request) Successful in 11m16s
Adversarial Review (Gadfly) / review (pull_request) Successful in 14m51s
feat: audio surfaces (TTS + transcription), imagegen.Editor, llamaswap health probe
- New leaf package `audio` (ADR-0017): SpeechModel/SpeechProvider and
  TranscriptionModel/TranscriptionProvider with imagegen conventions
  (zero value = backend default, functional options + Apply, bytes
  in/out, never URLs). Root re-exports added.
- imagegen.Editor (ADR-0018): optional image-to-image interface —
  EditRequest carries the generation knobs plus Init image and
  denoising Strength; separate interface so existing Models keep
  compiling.
- provider/llamaswap implements all of it: POST /v1/audio/speech (JSON,
  raw-audio response, MIME from Content-Type with format fallback),
  POST /v1/audio/transcriptions (multipart, response_format=json),
  ListVoices (GET /v1/audio/voices?model=, tolerant of string-list and
  object-list shapes), POST /sdapi/v1/img2img (txt2img wire +
  init_images/denoising_strength, shared image decode), and Health(ctx)
  (GET /health) — a cheap liveness probe for often-offline hosts.
- Hermetic httptest coverage for every new wire shape and validation
  path; README sections + support-matrix footnote updated in the same
  commit (also corrects the stale /v1/images/generations claim — the
  image path has been SDAPI since the seed fix).

First consumer: mort's llamaswap media tool cluster (status / image /
TTS / STT agent tools against the netherstorm host).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj
2026-07-11 23:24:14 -04:00

2.8 KiB

ADR-0017: audio — canonical speech synthesis + transcription interfaces

Status: Accepted — 2026-07-11

Context

mort is growing agent tools that speak (TTS) and transcribe audio through a llama-swap host whose upstreams expose the OpenAI /v1/audio/speech and /v1/audio/transcriptions endpoints (kokoro, chatterbox, whisper.cpp). Like image generation before it (ADR-0016), speech shares none of the chat contract's message/tool/stream machinery, and majordomo had no speech surface — an earlier migration doc explicitly scoped transcription out of the llm package. The same reasoning that produced imagegen applies.

Decision

  • One new canonical leaf package audio holding both directions — synthesis and transcription — rather than two packages; they are one modality and will share future types (voice metadata, audio formats). Root re-exports mirror imagegen (SpeechModel, SpeechProvider, SpeechRequest, SpeechResult, TranscriptionModel, ...).
  • Minimal v1 surface, imagegen conventions throughout (zero value = backend default, functional options + Apply, Raw any escape hatch):
    • SpeechRequest{ Input; Voice; Format; Speed }SpeechResult{ Audio []byte; MIME string; Raw }; SpeechModel.Speak(ctx, req, ...opts); SpeechProvider.SpeechModel(id, ...).
    • TranscriptionRequest{ Audio []byte; MIME; Filename; Language; Prompt }TranscriptionResult{ Text string; Raw }; TranscriptionModel.Transcribe(ctx, req, ...opts); TranscriptionProvider.TranscriptionModel(id, ...).
  • Bytes in/out, never URLs — mirrors llm.ImagePart's bytes-only contract; fetching is the caller's concern.
  • Providers are split (SpeechProvider vs TranscriptionProvider) so a backend can implement either half; llamaswap implements both.
  • First implementation: provider/llamaswap/v1/audio/speech (JSON body, raw audio response; MIME from Content-Type with a format-based fallback), /v1/audio/transcriptions (multipart, response_format=json), plus ListVoices(ctx, model) (GET /v1/audio/voices?model=, tolerant of the string-list and object-list shapes upstreams use) as a llamaswap management method, not part of the canonical contract.
  • Out of scope for v1 (designed-for, deferred): streaming synthesis, word-level timestamps/segments, translation, voice cloning inputs, and registry-level DSN resolution for audio models.

Consequences

  • Speech is provider-agnostic from day one; an OpenAI or Google speech backend implements the same interfaces.
  • SpeechResult.Audio is a plain byte slice, so results flow into any file store or attachment pipeline without a majordomo dependency.
  • The audio package name is the modality, not the direction; if music/sfx generation ever lands it has a home.