Skip to content

Instantly share code, notes, and snippets.

@yagop
Created June 28, 2026 18:32
Show Gist options
  • Select an option

  • Save yagop/d277e39392ca8a4129b49bc5c322f880 to your computer and use it in GitHub Desktop.

Select an option

Save yagop/d277e39392ca8a4129b49bc5c322f880 to your computer and use it in GitHub Desktop.
Z.ai GLM-ASR speech-recognition
name speech-recognition
description Transcribe audio files to text (speech recognition / ASR) using the Z.ai GLM-ASR model. Use whenever the user wants to transcribe, caption, or convert spoken audio (mp3, wav, m4a, flac, ogg, etc.) into text, or asks "what does this audio say".

Speech recognition (Z.ai GLM-ASR)

Claude has no native audio input, so use the Z.ai glm-asr-2512 model to turn an audio file into text. It's one curl call — inlined below, no helper script. Needs curl and jq.

Auth

The API key is ZAI_TOKEN, exported by ~/.zsh_aliases. Source it once per shell:

source ~/.zsh_aliases

Usage

source ~/.zsh_aliases
curl -sS --request POST \
  --url https://api.z.ai/api/paas/v4/audio/transcriptions \
  --header "Authorization: Bearer $ZAI_TOKEN" \
  --form model=glm-asr-2512 \
  --form stream=false \
  --form file=@<audio-file> | jq -r '.text'

Optional --form fields:

  • prompt=<text> — context to bias style/language. GLM-ASR has no language parameter (it auto-detects); pass a short prompt in the target language to nudge detection, e.g. --form prompt="Transcripción en español.".
  • hotwords=<json-array> — domain vocabulary (names, jargon) to improve accuracy, max 100, as a JSON array string: --form hotwords='["Anthropic","Claude"]'.

Full example:

source ~/.zsh_aliases
curl -sS --request POST \
  --url https://api.z.ai/api/paas/v4/audio/transcriptions \
  --header "Authorization: Bearer $ZAI_TOKEN" \
  --form model=glm-asr-2512 \
  --form stream=false \
  --form file=@<audio-file> \
  --form prompt="Transcripción en español." \
  --form hotwords='["Anthropic","Claude"]' | jq -r '.text'

On failure the API returns a JSON error body (e.g. {"error":{"code":"1214",...}}); drop the | jq -r '.text' to see it raw. For incremental output set --form stream=true and parse the data: SSE lines instead of piping to jq.

Limits: .wav/.mp3 only, max 25 MB and 30 seconds per file. Split longer audio before sending.

Preparing input (ffmpeg)

The API accepts only .wav/.mp3 and returns {"code":"1214","message":"...file format is not supported..."} for anything else. Telegram voice notes are Ogg/Opus (document_* with no extension); convert first. macOS afconvert can't decode Opus and ffmpeg/brew are often absent — if so, convert inside a container (OrbStack/Docker is usually available):

docker run --rm -v "$PWD":/work -w /work mcr.microsoft.com/devcontainers/base:ubuntu26.04 bash -c '
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -qq && apt-get install -y -qq ffmpeg
  ffmpeg -y -i in.ogg -ar 16000 -ac 1 out.wav'   # ffprobe -show_entries format=duration to check <30s

Troubleshooting: wrong language on short clips

GLM-ASR has no language override, and on short/noisy clips its auto-detect can lock onto the wrong language (e.g. a 4s Spanish voice note transcribed as Chinese) — prompt and hotwords alone do not fix this. What reliably unblocks detection is padding silence at both the start and end plus loudness normalization, then re-running:

ffmpeg -y -i in.ogg -af "apad=pad_dur=1,adelay=1000|1000,loudnorm" -ar 16000 -ac 1 out.wav

Run 2–3 passes and check the result is stable. If it still won't converge, the clip is genuinely hard — try splitting it in half. Normalize/highpass/tempo tweaks alone did not help; the start+end padding is the key lever.

Docs: https://docs.z.ai/guides/audio/glm-asr-2512

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment