|
| 1 | +// Copyright (c) 2026 Xiaomi Corporation |
| 2 | +const fs = require('fs'); |
| 3 | +const sherpa_onnx = require('sherpa-onnx-node'); |
| 4 | + |
| 5 | +// This example shows how to use per-stream hotwords (contextual biasing) |
| 6 | +// with a NeMo transducer model. Hotwords require |
| 7 | +// decodingMethod 'modified_beam_search'. |
| 8 | +// |
| 9 | +// The test wave contains the name "Phoebe", which the model transcribes |
| 10 | +// with the spelling "Phebe" by default. Passing the hotword "Phoebe" to |
| 11 | +// createStream() biases this stream towards the expected spelling. |
| 12 | +// |
| 13 | +// Please download test files from |
| 14 | +// https://github.com/k2-fsa/sherpa-onnx/releases/tag/asr-models |
| 15 | + |
| 16 | +const modelDir = './sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8'; |
| 17 | + |
| 18 | +// To pass hotwords as normal words, the model config needs a bpe vocab. |
| 19 | +// This model's release does not ship bpe.vocab, but an equivalent one for |
| 20 | +// hotword encoding can be derived from tokens.txt (equal scores make the |
| 21 | +// encoder behave as longest-match). |
| 22 | +const bpeVocab = `${modelDir}/bpe.vocab`; |
| 23 | +if (!fs.existsSync(bpeVocab)) { |
| 24 | + const tokens = fs.readFileSync(`${modelDir}/tokens.txt`, 'utf8'); |
| 25 | + const vocab = tokens.split('\n') |
| 26 | + .filter(line => line.trim() !== '') |
| 27 | + .map(line => `${line.split(' ')[0]}\t-1.0`) |
| 28 | + .join('\n'); |
| 29 | + fs.writeFileSync(bpeVocab, vocab + '\n'); |
| 30 | +} |
| 31 | + |
| 32 | +const config = { |
| 33 | + 'featConfig': { |
| 34 | + 'sampleRate': 16000, |
| 35 | + 'featureDim': 80, |
| 36 | + }, |
| 37 | + 'modelConfig': { |
| 38 | + 'transducer': { |
| 39 | + 'encoder': |
| 40 | + './sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8/encoder.int8.onnx', |
| 41 | + 'decoder': |
| 42 | + './sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8/decoder.int8.onnx', |
| 43 | + 'joiner': './sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8/joiner.int8.onnx', |
| 44 | + }, |
| 45 | + 'tokens': './sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8/tokens.txt', |
| 46 | + 'numThreads': 2, |
| 47 | + 'provider': 'cpu', |
| 48 | + 'debug': 1, |
| 49 | + 'modelType': 'nemo_transducer', |
| 50 | + 'modelingUnit': 'bpe', |
| 51 | + 'bpeVocab': bpeVocab, |
| 52 | + }, |
| 53 | + 'decodingMethod': 'modified_beam_search', |
| 54 | + 'hotwordsScore': 2.0, |
| 55 | +}; |
| 56 | + |
| 57 | +const waveFilename = |
| 58 | + './sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8/test_wavs/0.wav'; |
| 59 | + |
| 60 | +const recognizer = new sherpa_onnx.OfflineRecognizer(config); |
| 61 | +console.log('Started'); |
| 62 | +const wave = sherpa_onnx.readWave(waveFilename); |
| 63 | + |
| 64 | +function decode(hotwords) { |
| 65 | + const stream = recognizer.createStream(hotwords); |
| 66 | + stream.acceptWaveform({sampleRate: wave.sampleRate, samples: wave.samples}); |
| 67 | + recognizer.decode(stream); |
| 68 | + return recognizer.getResult(stream).text; |
| 69 | +} |
| 70 | + |
| 71 | +console.log('Without hotwords:', decode()); |
| 72 | + |
| 73 | +// Multiple phrases are separated by '/'; a per-phrase boosting score can be |
| 74 | +// appended, e.g. 'PHOEBE :3.0/DON QUIXOTE'. |
| 75 | +console.log('With hotwords :', decode('Phoebe')); |
0 commit comments