SKILL · Whisper Base (App Builder Pack)
Injected when user selects whisper-base Pack in App Builder and starts a chat.
Teaches how to interpret Whisper-base output, not how to call it.
Pack runs on-device. You MAY call appbuilder_run to verify I/O shape;
user's WebUI calls it over HTTP API. A Run result may already be in context.
1. What this Pack does
Whisper-base: 74M-param encoder/decoder ASR (OpenAI), on Snapdragon QNN HTP, INT8.
Accepts one audio clip (WAV/MP3/FLAC/WEBM, mono 16 kHz, ≤120 s) → structured JSON with detected language, task, full text, and time-aligned segments.
Input: one audio file. Mic capture allowed (allowMic=true; front end records WAV 16 kHz mono).
Output: JSON with language, task, fullText, segments[] (schema in §3).
Two modes via task parameter:
transcribe (default) — text in source language. Model auto-detects language (or uses params.language if forced). Chinese clip → Chinese text, English → English.
translate — English output regardless of source. One-way only: any of ~99 languages → English. Reverse direction not supported; user needs separate text-translation.
2. Parameters
| Param |
Type |
Default |
Meaning |
language |
select auto/zh/en/ja/ko/fr/de/es/ru |
auto |
Forces source language. auto uses Whisper's LID head — best for unknown clips but can mis-fire on short/noisy audio. Forcing skips LID, is faster + more reliable for task=translate. |
task |
select transcribe/translate |
transcribe |
See §1. translate → English only. For English input, translate is a no-op. |
vad |
boolean |
true |
Splits long audio into ≤30 s speech windows, skips silence. Disable only for clips ≤30 s of continuous speech. For ≥30 s audio, VAD is mandatory — without it only the first 30 s is transcribed. |
beam_size |
number 1–10 |
5 |
Decoder beam width. 1=greedy (faster, more hallucinations/loops). 8–10 reduces hallucinations, slightly better WER, linearly slower. 5 is standard default. |
Troubleshooting:
- "Transcript repeats same phrase" → increase
beam_size to 8–10 (repetition = greedy-decode failure).
- "Result in wrong language" →
language=auto mis-detected (common on <5 s or music-heavy clips); force language=<actual>.
3. Output JSON Schema
{
"language": "zh", // ISO 639-1 code of the source audio
"task": "transcribe" | "translate", // mirrors params.task
"fullText": "segment0 text segment1 text ...",
"segments": [
{
"start": 0.00, // seconds, from start of input audio
"end": 3.42, // seconds, from start of input audio
"text": "...", // recognized / translated text for this segment
"conf": 0.91 // average decoder log-prob mapped to [0,1]
},
...
]
}
3.1 language field
- 2-letter ISO 639-1 code. Mandarin =
zh (no zh-CN/zh-TW distinction).
- When
params.language != "auto", equals params.language. When auto, reflects Whisper's LID prediction — single guess for the whole clip, not per-segment. Code-switched audio shows dominant language; segments may contain words from others.
- Treat as a hint, not a hard guarantee.
3.2 task field
Always equals params.task. Included so downstream consumers can branch without re-reading the request.
3.3 segments[] timestamp precision
Timestamps are second-level, not frame-level. Decoder emits at 20 ms grid but end-to-end accuracy is ±0.3–0.5 s due to:
- 30 s encoder window quantization (±0.5 s shift at boundaries after stitching)
- VAD chunk boundaries snapping to silence, not word edges
- Decoder hallucinations causing timestamp drift on noisy audio
Key facts:
start/end are absolute seconds from input start (runner already added chunk offset)
- Sorted ascending, non-overlapping normally. Tiny overlaps (<50 ms) possible at chunk boundaries — clip client-side if needed.
- Typical segment: 2–10 s. No per-word timestamps in this Pack — word-level alignment requires a separate forced-aligner (e.g. WhisperX).
For SRT/VTT output, round to nearest 100 ms:
1
00:00:00,000 --> 00:00:03,420
First segment text
2
00:00:03,420 --> 00:00:07,800
Second segment text
The ,XXX millisecond slot is format-required, not model-accurate.
3.4 text field — language and content
task=transcribe: text in source language. Punctuation/casing are model-generated:
- Chinese: full-width (
, 。 ? !), no spaces between characters.
- English: standard ASCII punctuation, sentence-cased.
- Japanese: mixed kana/kanji, no spaces.
task=translate: English regardless of language, standard English punctuation.
- Non-speech annotations (
[Music], (applause), (笑い)) are real model outputs. Strip with regex for pure speech.
- Empty segments filtered by runner; you won't see them.
3.5 conf field
- Range
[0,1]. Computed as exp(avg_logprob) clipped, averaged over segment tokens.
- Good reads: 0.85–0.97. Below 0.6 = likely hallucination on silence/music/unclear speech.
- Not calibrated probability; use as relative quality signal within a single Run only.
3.6 fullText field
All segment texts joined with a single space (not \n). Use for prose output; for time-aligned use cases iterate segments[].
4. Typical user requests
4.1 "Meeting summary"
- Read
fullText. Use language to pick summary language (match source unless user says otherwise).
- Use
segments[] for topic shifts: gaps segments[i+1].start − segments[i].end > 5 s = section breaks.
- Structure:
- Participants — Whisper does NOT do diarization. State: "I can identify topics/timestamps but not speakers."
- Key points — bullets with
[mm:ss] timestamps from source segments.
- Open questions — unresolved items ("??", "TBD", "回头讨论", etc.).
- Keep original language unless asked to translate.
4.2 "SRT / VTT subtitles"
Walk segments[] per §3.3 format. Notes:
- VTT: separator
. (00:00:00.000 --> 00:00:03.420), header WEBVTT.
- SRT: 1-based index, separator
,.
- If
task=="translate", subtitles are English even if language is zh/ja — inform user.
- Long segments (>7 s): for "broadcast-quality" mention splitting at sentence boundaries (
。/./!/?) with estimated timestamps (linear interpolation).
4.3 "Key decision points"
- Search
segments[] for decision cues:
- en:
decide(d), agree(d), we'll, let's, action item, commit to, by Friday
- zh:
决定, 同意, 我们就, 下周之前, 负责, 跟进
- Return
[mm:ss] text (use HH:MM:SS for clips ≥1 h, else MM:SS).
- Group by topic if >5 matches; otherwise flat list.
- Always quote
text verbatim — user wants verifiable timestamp pointers.
4.4 "Translate to English" (non-English source)
- If Run was
task=transcribe, you have source-language fullText; downstream text-translation works.
- For best quality, suggest re-Run with
task=translate — Whisper's joint audio-translation path beats two-step (transcribe→translate), especially for proper nouns/idioms.
- If source is already English (
language=="en"), point that out instead of re-Running.
4.5 "Translate to Japanese / Chinese / German"
- Whisper-base supports translate-to-English only. State clearly. Workflow:
- Run
task=translate → English text, OR
- Run
task=transcribe → source-language text.
- Pipe through a separate text-translation tool (not included in this Pack).
- Don't pretend non-English translation targets work via post-processing.
5. Known limitations
Whisper-base (74M params) quality drops on:
- Noisy/far-field audio — background music, simultaneous speakers, >2 m from mic. Failures: hallucinated text on silence, repetition loops, wrong LID. Fix: re-record closer or denoise first.
- Audio >120 s — input capped at 120 s. Split client-side before uploading; concatenating
fullText across runs works but timestamps reset per chunk (user must re-offset).
- Heavy code-switching — language changes every few seconds confuse LID. Force
language=<dominant>; expect some transliteration of minority-language words.
- Specialized vocabulary — medical/legal/technical jargon poorly covered; base model guesses phonetically similar common words. Check
conf for suspicious lines.
- Speaker diarization — not supported. No
speaker field. Say so honestly.
- Word-level timestamps — not supported. Segment-level only.
- Singing/lyrics — unreliable; treat as best-effort.
- Languages outside Whisper's 99 — model falls back to related language (often English), produces garbage. Pack limits
params.language to 8 common languages + auto; less common may work via auto but is untested.
"Wrong text" reports → check conf and segment duration: short segments (<1 s) + conf <0.6 are usual culprits.
6. Whisper-base vs. zipformer-zh
| Dimension |
whisper-base |
zipformer-zh |
| Languages |
~99 (multilingual) |
Mandarin Chinese only |
| Translate-to-English |
✅ via task=translate |
❌ |
| Speed |
~6 s for 30 s audio (HTP, beam=5) |
~3 s for 30 s audio |
| Quality on clean Mandarin |
Good |
Slightly better, plus better with hotwords |
| Quality on code-switched zh+en |
OK (multilingual) |
Poor (English words butchered) |
| Hotword bias |
❌ |
✅ |
| Long-audio support |
Same (VAD chunking ≤120 s) |
Same |
Recommendation:
- Clean Mandarin + speed matters → zipformer-zh
- Non-Chinese languages present → whisper-base
- English translation needed → whisper-base with
task=translate
- Domain hotwords (terms, names) → zipformer-zh with
hotwords param
- Unsure → whisper-base (safer default)
Don't volunteer this comparison unless user is choosing between the two.
7. What you (the LLM) should NOT do
- Don't re-run to interpret an existing result. If a Run result is in context, interpret it. You MAY call
appbuilder_run to verify I/O when building a WebUI, but re-running to change params is the user's job.
- Do NOT MODIFY developer-maintained files. You MAY
read runner.py READ-ONLY for I/O understanding. Run inference via HTTP API / appbuilder_run — never execute runner.py inside generated app.
- ❌ Don't invent fields not in schema (no
speaker, words, confidence_avg, language_per_segment). Only: language, task, fullText, segments[].{start,end,text,conf}.
- ❌ Don't silently "fix" text. If transcript says "我们用 React" and you suspect "react.js", suggest correction but flag as guess and quote original
text + [start–end].
- ❌ Don't promise word-level timestamps or speaker labels — not in output.
- ❌ Don't translate when user asked for transcription (or vice versa). Check
task field; if user wants the other, tell them to re-Run with flipped task (prefer re-Run over post-hoc translation for quality).
8. Quick reference — example output
30 s Chinese news clip, task=transcribe, language=auto:
{
"language": "zh",
"task": "transcribe",
"fullText": "今天的天气非常好。 我们去公园散步吧。 顺便买点水果回来。",
"segments": [
{ "start": 0.00, "end": 3.42, "text": "今天的天气非常好。", "conf": 0.94 },
{ "start": 3.42, "end": 7.80, "text": "我们去公园散步吧。", "conf": 0.91 },
{ "start": 7.80, "end": 12.10, "text": "顺便买点水果回来。", "conf": 0.89 }
]
}
Same audio with task=translate:
{
"language": "zh",
"task": "translate",
"fullText": "The weather is great today. Let's go for a walk in the park. We can pick up some fruit on the way back.",
"segments": [
{ "start": 0.00, "end": 3.42, "text": "The weather is great today.", "conf": 0.92 },
{ "start": 3.42, "end": 7.80, "text": "Let's go for a walk in the park.", "conf": 0.90 },
{ "start": 7.80, "end": 12.10, "text": "We can pick up some fruit on the way back.", "conf": 0.88 }
]
}
language stays zh in translate — it describes source audio, not output text. Output language is implied: transcribe ⇒ source language, translate ⇒ English.
1---2name: whisper-base3description: SKILL · Whisper Base (App Builder Pack)4---5# SKILL · Whisper Base (App Builder Pack)67> Injected when user selects `whisper-base` Pack in App Builder and starts a chat.8> Teaches how to *interpret* Whisper-base output, not how to *call* it.9> Pack runs on-device. You MAY call `appbuilder_run` to verify I/O shape;10> user's WebUI calls it over HTTP API. A Run result may already be in context.1112---1314## 1. What this Pack does1516Whisper-base: 74M-param encoder/decoder ASR (OpenAI), on Snapdragon QNN HTP, INT8.17Accepts one audio clip (WAV/MP3/FLAC/WEBM, mono 16 kHz, ≤120 s) → structured JSON with detected language, task, full text, and time-aligned segments.1819**Input:** one audio file. Mic capture allowed (`allowMic=true`; front end records WAV 16 kHz mono).2021**Output:** JSON with `language`, `task`, `fullText`, `segments[]` (schema in §3).2223Two modes via `task` parameter:24- `transcribe` (default) — text in **source** language. Model auto-detects language (or uses `params.language` if forced). Chinese clip → Chinese text, English → English.25- `translate` — **English** output regardless of source. One-way only: any of ~99 languages **→ English**. Reverse direction not supported; user needs separate text-translation.2627---2829## 2. Parameters3031| Param | Type | Default | Meaning |32|-------|------|---------|---------|33| `language` | select `auto`/`zh`/`en`/`ja`/`ko`/`fr`/`de`/`es`/`ru` | `auto` | Forces source language. `auto` uses Whisper's LID head — best for unknown clips but can mis-fire on short/noisy audio. Forcing skips LID, is faster + more reliable for `task=translate`. |34| `task` | select `transcribe`/`translate` | `transcribe` | See §1. `translate` → English only. For English input, `translate` is a no-op. |35| `vad` | boolean | `true` | Splits long audio into ≤30 s speech windows, skips silence. Disable only for clips ≤30 s of continuous speech. For ≥30 s audio, VAD is mandatory — without it only the first 30 s is transcribed. |36| `beam_size` | number 1–10 | `5` | Decoder beam width. 1=greedy (faster, more hallucinations/loops). 8–10 reduces hallucinations, slightly better WER, linearly slower. 5 is standard default. |3738**Troubleshooting:**39- "Transcript repeats same phrase" → increase `beam_size` to 8–10 (repetition = greedy-decode failure).40- "Result in wrong language" → `language=auto` mis-detected (common on <5 s or music-heavy clips); force `language=<actual>`.4142---4344## 3. Output JSON Schema4546```jsonc47{48 "language": "zh", // ISO 639-1 code of the source audio49 "task": "transcribe" | "translate", // mirrors params.task50 "fullText": "segment0 text segment1 text ...",51 "segments": [52 {53 "start": 0.00, // seconds, from start of input audio54 "end": 3.42, // seconds, from start of input audio55 "text": "...", // recognized / translated text for this segment56 "conf": 0.91 // average decoder log-prob mapped to [0,1]57 },58 ...59 ]60}61```6263### 3.1 `language` field6465- 2-letter ISO 639-1 code. Mandarin = `zh` (no `zh-CN`/`zh-TW` distinction).66- When `params.language != "auto"`, equals `params.language`. When `auto`, reflects Whisper's LID prediction — single guess for the **whole** clip, not per-segment. Code-switched audio shows **dominant** language; segments may contain words from others.67- Treat as a hint, not a hard guarantee.6869### 3.2 `task` field7071Always equals `params.task`. Included so downstream consumers can branch without re-reading the request.7273### 3.3 `segments[]` timestamp precision7475Timestamps are **second-level**, not frame-level. Decoder emits at 20 ms grid but end-to-end accuracy is **±0.3–0.5 s** due to:76- 30 s encoder window quantization (±0.5 s shift at boundaries after stitching)77- VAD chunk boundaries snapping to silence, not word edges78- Decoder hallucinations causing timestamp drift on noisy audio7980Key facts:81- `start`/`end` are **absolute seconds from input start** (runner already added chunk offset)82- Sorted ascending, **non-overlapping** normally. Tiny overlaps (<50 ms) possible at chunk boundaries — clip client-side if needed.83- Typical segment: 2–10 s. No per-word timestamps in this Pack — word-level alignment requires a separate forced-aligner (e.g. WhisperX).8485For SRT/VTT output, round to nearest 100 ms:8687```8818900:00:00,000 --> 00:00:03,42090First segment text919229300:00:03,420 --> 00:00:07,80094Second segment text95```9697The `,XXX` millisecond slot is format-required, not model-accurate.9899### 3.4 `text` field — language and content100101- `task=transcribe`: text in source language. Punctuation/casing are model-generated:102 - Chinese: full-width (`,` `。` `?` `!`), no spaces between characters.103 - English: standard ASCII punctuation, sentence-cased.104 - Japanese: mixed kana/kanji, no spaces.105- `task=translate`: **English** regardless of `language`, standard English punctuation.106- Non-speech annotations (`[Music]`, `(applause)`, `(笑い)`) are real model outputs. Strip with regex for pure speech.107- Empty segments filtered by runner; you won't see them.108109### 3.5 `conf` field110111- Range `[0,1]`. Computed as `exp(avg_logprob)` clipped, averaged over segment tokens.112- Good reads: 0.85–0.97. Below 0.6 = likely hallucination on silence/music/unclear speech.113- **Not** calibrated probability; use as **relative** quality signal within a single Run only.114115### 3.6 `fullText` field116117All segment `text`s joined with **a single space** (not `\n`). Use for prose output; for time-aligned use cases iterate `segments[]`.118119---120121## 4. Typical user requests122123### 4.1 "Meeting summary"1241251. Read `fullText`. Use `language` to pick summary language (match source unless user says otherwise).1262. Use `segments[]` for topic shifts: gaps `segments[i+1].start − segments[i].end > 5 s` = section breaks.1273. Structure:128 - **Participants** — Whisper does NOT do diarization. State: "I can identify topics/timestamps but not speakers."129 - **Key points** — bullets with `[mm:ss]` timestamps from source segments.130 - **Open questions** — unresolved items ("??", "TBD", "回头讨论", etc.).1314. Keep original language unless asked to translate.132133### 4.2 "SRT / VTT subtitles"134135Walk `segments[]` per §3.3 format. Notes:136- **VTT**: separator `.` (`00:00:00.000 --> 00:00:03.420`), header `WEBVTT`.137- **SRT**: 1-based index, separator `,`.138- If `task=="translate"`, subtitles are English even if `language` is `zh`/`ja` — inform user.139- Long segments (>7 s): for "broadcast-quality" mention splitting at sentence boundaries (`。`/`.`/`!`/`?`) with **estimated** timestamps (linear interpolation).140141### 4.3 "Key decision points"142143- Search `segments[]` for decision cues:144 - en: `decide(d)`, `agree(d)`, `we'll`, `let's`, `action item`, `commit to`, `by Friday`145 - zh: `决定`, `同意`, `我们就`, `下周之前`, `负责`, `跟进`146- Return `[mm:ss] text` (use `HH:MM:SS` for clips ≥1 h, else `MM:SS`).147- Group by topic if >5 matches; otherwise flat list.148- Always quote `text` verbatim — user wants verifiable timestamp pointers.149150### 4.4 "Translate to English" (non-English source)151152- If Run was `task=transcribe`, you have source-language `fullText`; downstream text-translation works.153- For best quality, suggest **re-Run** with `task=translate` — Whisper's joint audio-translation path beats two-step (transcribe→translate), especially for proper nouns/idioms.154- If source is already English (`language=="en"`), point that out instead of re-Running.155156### 4.5 "Translate to Japanese / Chinese / German"157158- Whisper-base supports translate-to-English **only**. State clearly. Workflow:159 1. Run `task=translate` → English text, OR160 2. Run `task=transcribe` → source-language text.161 3. Pipe through a separate text-translation tool (not included in this Pack).162- Don't pretend non-English translation targets work via post-processing.163164---165166## 5. Known limitations167168Whisper-base (74M params) quality drops on:169170- **Noisy/far-field audio** — background music, simultaneous speakers, >2 m from mic. Failures: hallucinated text on silence, repetition loops, wrong LID. Fix: re-record closer or denoise first.171- **Audio >120 s** — input capped at 120 s. Split client-side before uploading; concatenating `fullText` across runs works but timestamps reset per chunk (user must re-offset).172- **Heavy code-switching** — language changes every few seconds confuse LID. Force `language=<dominant>`; expect some transliteration of minority-language words.173- **Specialized vocabulary** — medical/legal/technical jargon poorly covered; base model guesses phonetically similar common words. Check `conf` for suspicious lines.174- **Speaker diarization** — **not supported.** No `speaker` field. Say so honestly.175- **Word-level timestamps** — **not supported.** Segment-level only.176- **Singing/lyrics** — unreliable; treat as best-effort.177- **Languages outside Whisper's 99** — model falls back to related language (often English), produces garbage. Pack limits `params.language` to 8 common languages + `auto`; less common may work via `auto` but is untested.178179"Wrong text" reports → check `conf` and segment duration: short segments (<1 s) + conf <0.6 are usual culprits.180181---182183## 6. Whisper-base vs. zipformer-zh184185| Dimension | `whisper-base` | `zipformer-zh` |186|-----------|----------------|-----------------|187| Languages | ~99 (multilingual) | Mandarin Chinese only |188| Translate-to-English | ✅ via `task=translate` | ❌ |189| Speed | ~6 s for 30 s audio (HTP, beam=5) | ~3 s for 30 s audio |190| Quality on clean Mandarin | Good | Slightly better, plus better with hotwords |191| Quality on code-switched zh+en | OK (multilingual) | Poor (English words butchered) |192| Hotword bias | ❌ | ✅ |193| Long-audio support | Same (VAD chunking ≤120 s) | Same |194195Recommendation:196- Clean Mandarin + speed matters → **zipformer-zh**197- Non-Chinese languages present → **whisper-base**198- English translation needed → **whisper-base** with `task=translate`199- Domain hotwords (terms, names) → **zipformer-zh** with `hotwords` param200- Unsure → **whisper-base** (safer default)201202Don't volunteer this comparison unless user is choosing between the two.203204---205206## 7. What you (the LLM) should NOT do207208- **Don't re-run to interpret an existing result.** If a Run result is in context, interpret it. You MAY call `appbuilder_run` to verify I/O when building a WebUI, but re-running to change params is the user's job.209- **Do NOT MODIFY** developer-maintained files. You MAY `read` `runner.py` READ-ONLY for I/O understanding. Run inference via HTTP API / `appbuilder_run` — never execute `runner.py` inside generated app.210- ❌ **Don't invent fields** not in schema (no `speaker`, `words`, `confidence_avg`, `language_per_segment`). Only: `language`, `task`, `fullText`, `segments[].{start,end,text,conf}`.211- ❌ **Don't silently "fix" text.** If transcript says "我们用 React" and you suspect "react.js", **suggest** correction but flag as guess and quote original `text` + `[start–end]`.212- ❌ **Don't promise word-level timestamps or speaker labels** — not in output.213- ❌ **Don't translate when user asked for transcription** (or vice versa). Check `task` field; if user wants the other, tell them to re-Run with flipped `task` (prefer re-Run over post-hoc translation for quality).214215---216217## 8. Quick reference — example output21821930 s Chinese news clip, `task=transcribe`, `language=auto`:220221```json222{223 "language": "zh",224 "task": "transcribe",225 "fullText": "今天的天气非常好。 我们去公园散步吧。 顺便买点水果回来。",226 "segments": [227 { "start": 0.00, "end": 3.42, "text": "今天的天气非常好。", "conf": 0.94 },228 { "start": 3.42, "end": 7.80, "text": "我们去公园散步吧。", "conf": 0.91 },229 { "start": 7.80, "end": 12.10, "text": "顺便买点水果回来。", "conf": 0.89 }230 ]231}232```233234Same audio with `task=translate`:235236```json237{238 "language": "zh",239 "task": "translate",240 "fullText": "The weather is great today. Let's go for a walk in the park. We can pick up some fruit on the way back.",241 "segments": [242 { "start": 0.00, "end": 3.42, "text": "The weather is great today.", "conf": 0.92 },243 { "start": 3.42, "end": 7.80, "text": "Let's go for a walk in the park.", "conf": 0.90 },244 { "start": 7.80, "end": 12.10, "text": "We can pick up some fruit on the way back.", "conf": 0.88 }245 ]246}247```248249`language` stays `zh` in `translate` — it describes **source** audio, not output text. Output language is implied: `transcribe` ⇒ source language, `translate` ⇒ English.