🔊 fix: Validate language format for OpenAI STT model (#10875)
Some checks are pending
Publish `@librechat/client` to NPM / build-and-publish (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions

* 🔊 fix: Validate language format for OpenAI STT model

* fix: Normalize input language model assignment in STTService

* refactor: Enhance error logging and language validation in STT and TTS services

* fix: Improve language validation in getValidatedLanguageCode function
This commit is contained in:
Danny Avila 2025-12-09 22:25:45 -05:00 committed by GitHub
parent 11923b9b96
commit 5879b3f518
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 49 additions and 17 deletions

View file

@ -3,8 +3,8 @@ const fs = require('fs').promises;
const FormData = require('form-data');
const { Readable } = require('stream');
const { logger } = require('@librechat/data-schemas');
const { genAzureEndpoint } = require('@librechat/api');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { genAzureEndpoint, logAxiosError } = require('@librechat/api');
const { extractEnvVariable, STTProviders } = require('librechat-data-provider');
const { getAppConfig } = require('~/server/services/Config');
@ -35,6 +35,34 @@ const MIME_TO_EXTENSION_MAP = {
'audio/x-flac': 'flac',
};
/**
* Validates and extracts ISO-639-1 language code from a locale string.
* @param {string} language - The language/locale string (e.g., "en-US", "en", "zh-CN")
* @returns {string|null} The ISO-639-1 language code (e.g., "en") or null if invalid
*/
function getValidatedLanguageCode(language) {
try {
if (!language) {
return null;
}
const normalizedLanguage = language.toLowerCase();
const isValidLocaleCode = /^[a-z]{2}(-[a-z]{2})?$/.test(normalizedLanguage);
if (isValidLocaleCode) {
return normalizedLanguage.split('-')[0];
}
logger.warn(
`[STT] Invalid language format "${language}". Expected ISO-639-1 locale code like "en-US" or "en". Skipping language parameter.`,
);
return null;
} catch (error) {
logger.error(`[STT] Error validating language code "${language}":`, error);
return null;
}
}
/**
* Gets the file extension from the MIME type.
* @param {string} mimeType - The MIME type.
@ -173,10 +201,9 @@ class STTService {
model: sttSchema.model,
};
if (language) {
/** Converted locale code (e.g., "en-US") to ISO-639-1 format (e.g., "en") */
const isoLanguage = language.split('-')[0];
data.language = isoLanguage;
const validLanguage = getValidatedLanguageCode(language);
if (validLanguage) {
data.language = validLanguage;
}
const headers = {
@ -221,10 +248,9 @@ class STTService {
contentType: audioFile.mimetype,
});
if (language) {
/** Converted locale code (e.g., "en-US") to ISO-639-1 format (e.g., "en") */
const isoLanguage = language.split('-')[0];
formData.append('language', isoLanguage);
const validLanguage = getValidatedLanguageCode(language);
if (validLanguage) {
formData.append('language', validLanguage);
}
const headers = {
@ -286,7 +312,7 @@ class STTService {
return response.data.text.trim();
} catch (error) {
logger.error(`STT request failed for provider ${provider}:`, error);
logAxiosError({ message: `STT request failed for provider ${provider}:`, error });
throw error;
}
}
@ -316,7 +342,7 @@ class STTService {
const text = await this.sttRequest(provider, sttSchema, { audioBuffer, audioFile, language });
res.json({ text });
} catch (error) {
logger.error('An error occurred while processing the audio:', error);
logAxiosError({ message: 'An error occurred while processing the audio:', error });
res.sendStatus(500);
} finally {
try {

View file

@ -1,7 +1,7 @@
const axios = require('axios');
const { logger } = require('@librechat/data-schemas');
const { genAzureEndpoint } = require('@librechat/api');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { genAzureEndpoint, logAxiosError } = require('@librechat/api');
const { extractEnvVariable, TTSProviders } = require('librechat-data-provider');
const { getRandomVoiceId, createChunkProcessor, splitTextIntoChunks } = require('./streamAudio');
const { getAppConfig } = require('~/server/services/Config');
@ -274,7 +274,7 @@ class TTSService {
try {
return await axios.post(url, data, options);
} catch (error) {
logger.error(`TTS request failed for provider ${provider}:`, error);
logAxiosError({ message: `TTS request failed for provider ${provider}:`, error });
throw error;
}
}
@ -330,7 +330,10 @@ class TTSService {
break;
}
} catch (innerError) {
logger.error('Error processing manual update:', chunk, innerError);
logAxiosError({
message: `[TTS] Error processing manual update for chunk: ${chunk?.text?.substring(0, 50)}...`,
error: innerError,
});
if (!res.headersSent) {
return res.status(500).end();
}
@ -342,7 +345,7 @@ class TTSService {
res.end();
}
} catch (error) {
logger.error('Error creating the audio stream:', error);
logAxiosError({ message: '[TTS] Error creating the audio stream:', error });
if (!res.headersSent) {
return res.status(500).send('An error occurred');
}
@ -412,7 +415,10 @@ class TTSService {
break;
}
} catch (innerError) {
logger.error('Error processing audio stream update:', update, innerError);
logAxiosError({
message: `[TTS] Error processing audio stream update: ${update?.text?.substring(0, 50)}...`,
error: innerError,
});
if (!res.headersSent) {
return res.status(500).end();
}
@ -429,7 +435,7 @@ class TTSService {
res.end();
}
} catch (error) {
logger.error('Failed to fetch audio:', error);
logAxiosError({ message: '[TTS] Failed to fetch audio:', error });
if (!res.headersSent) {
res.status(500).end();
}