{"record":{"id":"02861b45712a74e4","repo":"SillyTavern/SillyTavern","slug":"internal-server-error-02861b","errorCode":null,"errorMessage":"Internal Server Error","messagePattern":"Internal Server Error","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"src/endpoints/speech.js","lineNumber":57,"sourceCode":"    return audioData;\n}\n\nrouter.post('/recognize', async (req, res) => {\n    try {\n        const TASK = 'automatic-speech-recognition';\n        const { model, audio, lang } = req.body;\n        const pipe = await getPipeline(TASK, model);\n        const wav = getWaveFile(audio);\n        const start = performance.now();\n        const result = await pipe(wav, { language: lang || null, task: 'transcribe' });\n        const end = performance.now();\n        console.info(`Execution duration: ${(end - start) / 1000} seconds`);\n        console.info('Transcribed audio:', result.text);\n\n        return res.json({ text: result.text });\n    } catch (error) {\n        console.error(error);\n        return res.sendStatus(500);\n    }\n});\n\nrouter.post('/synthesize', async (req, res) => {\n    try {\n        const TASK = 'text-to-speech';\n        const { text, model, speaker } = req.body;\n        const pipe = await getPipeline(TASK, model);\n        const speaker_embeddings = speaker\n            ? new Float32Array(new Uint8Array(Buffer.from(speaker.startsWith('data:') ? speaker.split(',')[1] : speaker, 'base64')).buffer)\n            : null;\n        const start = performance.now();\n        const result = await pipe(text, { speaker_embeddings: speaker_embeddings });\n        const end = performance.now();\n        console.debug(`Execution duration: ${(end - start) / 1000} seconds`);\n\n        const wav = new wavefile.WaveFile();\n        wav.fromScratch(1, result.sampling_rate, '32f', result.audio);","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/SillyTavern/SillyTavern/blob/8172dcd0ee672d3cd9a5e5f7af134f91a45cd2b8/src/endpoints/speech.js#L39-L75","documentation":"The HTTP 500 catch-all for the speech transcription route (POST using Transformers.js pipeline with task 'transcribe'). Any throw from getPipeline (model load), getWaveFile (audio decoding), or pipe(wav, ...) (inference) lands here. The error is console.error'd; only a bare 500 reaches the client, so the transcribe stage that failed must be identified from logs.","triggerScenarios":"Model name is missing or not downloadable; audio base64 is malformed so getWaveFile throws; the WebGPU/Transformers.js runtime hits an out-of-memory or unsupported-dtype error during inference; the language code is invalid; the model cache is corrupt.","commonSituations":"First request after adding a new STT model id that has not been cached; client sends audio/wav bytes that are not valid base64 or not a real WAV; Node/Transformers.js version mismatch causing ONNX runtime failure; low-memory host.","solutions":["Read the server console - the logged error distinguishes model-load vs decode vs inference failures.","Validate the audio payload is valid base64 of a WAV before sending, and that model is a supported automatic-speech-recognition model id.","Pre-warm the pipeline by calling the endpoint once after install so the model downloads/caches succeed.","Update transformers.js and onnxruntime-node to matching versions; clear the model cache if it is corrupt."],"exampleFix":"// before\nconst wav = getWaveFile(audio);\nconst result = await pipe(wav, { language: lang || null, task: 'transcribe' });\n// after - guard the decode step to get a precise error\nlet wav;\ntry { wav = getWaveFile(audio); }\ncatch (e) { return res.status(400).send({ error: 'Invalid audio payload' }); }","handlingStrategy":"validation","validationCode":"function isValidAudioPayload(audio) {\n  if (typeof audio !== 'string') return false;\n  const b = audio.startsWith('data:') ? audio.split(',')[1] : audio;\n  return /^[A-Za-z0-9+/]+={0,2}$/.test(b) && b.length % 4 === 0;\n}","typeGuard":"/** @param {unknown} m */\nconst isSupportedSttModel = (m) => typeof m === 'string' && m.startsWith('Xenova/') || typeof m === 'string' && m.startsWith('onnx-community/') || typeof m === 'string' && m.startsWith('openai/whisper');","tryCatchPattern":"try { const { text } = await transcribe(audio, model, lang); }\ncatch (e) {\n  if (/tensor|dtype|onnx/i.test(e.message)) handleModelVersion();\n  else if (/decode|wav|base64/i.test(e.message)) handleBadAudio();\n  else handleInference(e);\n}","preventionTips":["Warm the STT pipeline once at startup so model-download failures surface early.","Validate the audio is real WAV bytes before posting.","Pin transformers.js and onnxruntime-node to versions known to work with the chosen model."],"tags":["speech","transformers-js","http-500","express","ml-inference"],"backgroundTag":null,"analyzedSha":"8172dcd0ee672d3cd9a5e5f7af134f91a45cd2b8","analyzedAt":"2026-08-13T07:48:40.832Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}