llms.py
Features

Voice Input

Adds voice-to-text transcription to the chat UI via a microphone button or ALT+D keyboard shortcut.

The voice extension supports four transcription modes tried in order: voxtype, transcribe, api, and voxtral-mini-latest, using the first one that's available.

Local tools are tried first, so a machine with voxtype or your own transcribe script keeps transcribing locally even when API keys are present.

To remove modes or change their priority, override with the LLMS_VOICE environment variable, e.g:

export LLMS_VOICE="api,voxtral-mini-latest"

Usage

🎤 Microphone Button

Click the microphone icon in the chat input area to start recording. Click again to stop and transcribe.

If the voice extension is enabled the microphone button will appear in the chat input area, and the ALT+D keyboard shortcut will be available for voice input.

Keyboard Shortcut

Alt+D toggles voice recording with two modes:

  • Tap (< 500ms): Toggle mode - starts recording, press again to stop
  • Hold (≥ 500ms): Push-to-talk - records while held, stops when released

The transcribed text is appended to the current message input.

Voice input can be disabled by disabling the voice extension or by setting LLMS_VOICE="" to disable all modes.

Available Modes

Voice Input will use the first available mode.

api

Sends the recording to any OpenAI-compatible /v1/audio/transcriptions endpoint. This is the most flexible mode — it covers hosted providers and any local speech-to-text server, and it's the only one that needs nothing installed alongside llms.py.

Requirements:

  • An API key for a supported provider, or a url and model for your own endpoint

With no configuration at all it uses the first provider API key it finds:

Environment variableProviderDefault model
GROQ_API_KEYGroqwhisper-large-v3-turbo
OPENAI_API_KEYOpenAIwhisper-1
MISTRAL_API_KEYMistralvoxtral-mini-latest

llms.py ships with mistral / voxtral-mini-latest configured in defaults.voice. If that provider has no API key it falls back to any other provider that does, so the shipped default never disables voice input for someone using a different provider — --verbose logs [fallback] when that happens.

Audio format. Browsers record webm/opus, which Groq and OpenAI accept but Mistral rejects with "Audio input could not be decoded". The chat UI converts the recording to 16 kHz mono WAV before uploading, so every provider works with no extra software. If the browser can't do the conversion the server falls back to ffmpeg when it's installed, and otherwise sends the original — in which case use groq or openai, which decode webm directly.

Configuring in llms.json

Add a voice section under defaults in llms.json to choose the provider and model:

{
  "defaults": {
    "voice": {
      "provider": "groq",
      "model": "whisper-large-v3",
      "language": "en"
    }
  }
}
SettingPurpose
providergroq, openai or mistral — selects the endpoint and default model
modelModel id, e.g. whisper-large-v3
urlFull endpoint URL. Set this instead of provider to use any other server
api_keyAPI key. Use $SOME_VAR to read an environment variable rather than storing it inline
languageISO-639-1 hint, e.g. en. Omit to auto-detect
promptBiasing prompt to improve recognition of names and jargon

Switching model within a provider is a single key:

{
  "defaults": {
    "voice": {
      "model": "whisper-large-v3"
    }
  }
}

Using a local speech-to-text server

Point url at any OpenAI-compatible server — speaches, faster-whisper-server, or your own. No API key is needed:

{
  "defaults": {
    "voice": {
      "url": "http://localhost:8001/v1/audio/transcriptions",
      "model": "Systran/faster-whisper-small"
    }
  }
}

This keeps audio on your own hardware while still using the api mode, and is the recommended way to run offline transcription in Docker, where the host's localhost is reachable as host.docker.internal.

defaults is sent to the browser by /config, so prefer "api_key": "$SOME_VAR" over a literal key. A literal api_key is stripped from that response, but keeping secrets in the environment is still the safer habit.

Environment variable overrides

Every setting has an environment variable that takes precedence over llms.json, useful for one-off runs or for keeping keys out of config:

Variablellms.json equivalent
LLMS_TRANSCRIBE_PROVIDERprovider
LLMS_TRANSCRIBE_MODELmodel
LLMS_TRANSCRIBE_URLurl
LLMS_TRANSCRIBE_KEYapi_key
LLMS_TRANSCRIBE_LANGlanguage
LLMS_TRANSCRIBE_PROMPTprompt

Run with --verbose to see which provider and model were selected, and where each value came from:

Using api for voice: groq [llms.json] model=whisper-large-v3 [llms.json]

voxtype

Uses the voxtype.io CLI tool for local transcription.

Requirements:

  • voxtype must be installed and on your $PATH
  • ffmpeg must be installed for audio format conversion

Voxtype requires a graphical desktop session, so it isn't available in headless or containerised deployments — use api with a local server instead.

Installation

Voxtype works on GNOME, KDE, Sway, Hyprland, River-Wayland or X11 with native packages for Arch Linux, Debian, Ubuntu, Fedora and support for macOS via their source builds.

transcribe

Use your preferred speech-to-text tool by creating a custom transcribe script or executable.

Requirements:

  • A transcribe executable on your $PATH that accepts an audio wav file and outputs text to stdout
  • ffmpeg must be installed for audio format conversion

Interface:

transcribe recording.wav > transcript.txt

See Creating a transcribe Script for implementation examples.

voxtral-mini-latest

Uses Mistral's Voxtral model for cloud-based transcription, through the configured Mistral provider.

Requirements:

  • Mistral provider must be enabled in your configuration
  • MISTRAL_API_KEY environment variable must be set

Pricing: ~$0.003/minute

Any voxtral* model id may be used, e.g. LLMS_VOICE="voxtral-small-latest".

The api mode reaches the same endpoint and defaults to the same model, so this mode is mainly useful when you want transcription to go through the Mistral provider's own configuration.


Creating a transcribe Script

Make the script executable and add it to your $PATH:

chmod +x ./transcribe
sudo ln -s $(pwd)/transcribe /usr/local/bin/transcribe

Using OpenAI Whisper

Create a script using uvx and openai-whisper:

./transcribe

#!/usr/bin/env bash
uvx --from openai-whisper whisper "$1" --model base.en --output_format txt --output_dir /tmp >/dev/null 2>&1

BASENAME=$(basename "${1%.*}")
cat "/tmp/${BASENAME}.txt"
rm -f "/tmp/${BASENAME}.txt"

Using Whisper.cpp

whisper.cpp provides a faster, dependency-free C++ implementation.

Setup:

git clone https://github.com/ggml-org/whisper.cpp.git
cd whisper.cpp

# Download a model
sh ./models/download-ggml-model.sh base.en

# Build
cmake -B build
cmake --build build -j --config Release

# Test
./build/bin/whisper-cli -f samples/jfk.wav

Create the transcribe script:

./transcribe

#!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
MODEL="$SCRIPT_DIR/models/ggml-base.en.bin"
CLI="$SCRIPT_DIR/build/bin/whisper-cli"
TMPFILE=$(mktemp /tmp/whisper-XXXXXX)

trap 'rm -f "$TMPFILE" "${TMPFILE}.txt"' EXIT

"$CLI" -m "$MODEL" -otxt -f "$1" -of "$TMPFILE" >/dev/null 2>&1

cat "${TMPFILE}.txt"

Troubleshooting

The microphone button doesn't appear

Browsers only expose the microphone API in a secure context: HTTPS, or http://localhost / http://127.0.0.1. Browsing to a plain-HTTP address such as http://192.168.1.10:8000 silently disables it, so no button is shown regardless of your configuration.

Reach the server over an SSH tunnel (ssh -L 8000:localhost:8000 host) or put it behind a TLS-terminating reverse proxy.

Otherwise, no mode was available. Run with --verbose to see why each was skipped:

Cannot use voxtype - voxtype not installed
Cannot use transcribe - transcribe not installed
Cannot use api - no voice provider configured, see defaults.voice in llms.json
Cannot use voxtral-mini-latest - Mistral not enabled