mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-08-19 09:40:23 +00:00
fix(stt/google): honor googleOptions.recognizerId for Speech v2
`rOpts.sgoogleOptions?.recognizerId` (note the stray `s`) is always
undefined, so GOOGLE_SPEECH_RECOGNIZER_ID was never set on the channel.
The media server therefore fell back to the wildcard recognizer "_" with a
full inline config, and every caller passing a recognizerId silently got the
stock v2 model instead of their custom recognizer. Present since 8999c85, so
in v0.9.7-rc1 and v0.9.8.
Also in the same v2 path:
- Accept a full resource path in `recognizerId`, not just a bare id. The
media server builds <parent>/recognizers/<id>, so a full path would be
concatenated onto the parent into a nonsense recognizer name. A parent
embedded in the path now also wins over `parentPath` and feeds the
regional-endpoint derivation.
- Add the four v2-only vars to google's stickyVars. Without them a v2 gather
leaks its recognizer, endpoint and service version into a later v1 (or
wildcard) gather on the same call. Clearing a sticky var sends "", which
FreeSWITCH treats as a delete (switch_channel.c: zstr(value) ->
switch_event_del_header), so the media server sees the var as absent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f35b694877
commit
024d658200
@@ -11,7 +11,13 @@ const stickyVars = {
|
||||
'GOOGLE_SPEECH_USE_ENHANCED',
|
||||
'GOOGLE_SPEECH_ALTERNATIVE_LANGUAGE_CODES',
|
||||
'GOOGLE_SPEECH_METADATA_INTERACTION_TYPE',
|
||||
'GOOGLE_SPEECH_METADATA_INDUSTRY_NAICS_CODE'
|
||||
'GOOGLE_SPEECH_METADATA_INDUSTRY_NAICS_CODE',
|
||||
/* v2-only vars; without these a v2 gather leaks its recognizer and
|
||||
endpoint into a later v1 (or wildcard-recognizer) gather on the same call */
|
||||
'GOOGLE_SPEECH_CLOUD_SERVICES_VERSION',
|
||||
'GOOGLE_SPEECH_RECOGNIZER_PARENT',
|
||||
'GOOGLE_SPEECH_RECOGNIZER_ID',
|
||||
'GOOGLE_SPEECH_TO_TEXT_URI'
|
||||
],
|
||||
microsoft: [
|
||||
'AZURE_SPEECH_HINTS',
|
||||
@@ -212,6 +218,25 @@ const selectDefaultGoogleModel = (task, language, version) => {
|
||||
(useV2 ? 'telephony_short' : 'command_and_search') :
|
||||
(useV2 ? 'long' : 'latest_long');
|
||||
};
|
||||
|
||||
/**
|
||||
* Google STT v2 addresses a recognizer as
|
||||
* `projects/{project}/locations/{location}/recognizers/{id}`, which the media
|
||||
* server assembles from GOOGLE_SPEECH_RECOGNIZER_PARENT + GOOGLE_SPEECH_RECOGNIZER_ID.
|
||||
* Callers supply `recognizerId` either as the bare id or as the full resource
|
||||
* path, so split a full path back into its parts before handing them over --
|
||||
* otherwise the media server concatenates the path onto the parent and Google
|
||||
* gets a nonsense recognizer name. A parent embedded in the path wins over
|
||||
* `parentPath`, being the more specific of the two.
|
||||
*/
|
||||
const parseGoogleRecognizer = (recognizerId, parentPath) => {
|
||||
if (typeof recognizerId !== 'string' || !recognizerId.trim()) return {parent: parentPath};
|
||||
const id = recognizerId.trim();
|
||||
const m = /^(projects\/[^/]+\/locations\/[^/]+)\/recognizers\/(.+)$/.exec(id);
|
||||
if (m) return {parent: m[1], recognizerId: m[2]};
|
||||
return {parent: parentPath, recognizerId: id};
|
||||
};
|
||||
|
||||
const consolidateTranscripts = (bufferedTranscripts, channel, language, vendor) => {
|
||||
if (bufferedTranscripts.length === 1) {
|
||||
bufferedTranscripts[0].is_final = true;
|
||||
@@ -822,20 +847,21 @@ module.exports = (logger) => {
|
||||
|
||||
if ('google' === vendor) {
|
||||
const useV2 = rOpts.googleOptions?.serviceVersion === 'v2';
|
||||
const {parentPath} = rOpts.googleOptions || {};
|
||||
const {parent: recognizerParent, recognizerId} =
|
||||
parseGoogleRecognizer(rOpts.googleOptions?.recognizerId, rOpts.googleOptions?.parentPath);
|
||||
const version = useV2 ? 'v2' : 'v1';
|
||||
let {model} = rOpts;
|
||||
model = model || selectDefaultGoogleModel(task, language, version);
|
||||
|
||||
/**
|
||||
* For STT v2, Google routes regional resources only via regional endpoints.
|
||||
* A parentPath ending in `/locations/<region>` (anything other than `global`)
|
||||
* A parent ending in `/locations/<region>` (anything other than `global`)
|
||||
* requires the request to go to `<region>-speech.googleapis.com` instead of
|
||||
* the default `speech.googleapis.com`.
|
||||
*/
|
||||
let derivedSttUri;
|
||||
if (useV2 && parentPath) {
|
||||
const m = parentPath.match(/\/locations\/([^/]+)/);
|
||||
if (useV2 && recognizerParent) {
|
||||
const m = recognizerParent.match(/\/locations\/([^/]+)/);
|
||||
if (m && m[1] && m[1] !== 'global') derivedSttUri = `${m[1]}-speech.googleapis.com`;
|
||||
}
|
||||
opts = {
|
||||
@@ -871,7 +897,7 @@ module.exports = (logger) => {
|
||||
...(rOpts.naicsCode > 0 && {GOOGLE_SPEECH_METADATA_INDUSTRY_NAICS_CODE: rOpts.naicsCode}),
|
||||
GOOGLE_SPEECH_METADATA_RECORDING_DEVICE_TYPE: 'phone_line',
|
||||
...(useV2 && {
|
||||
GOOGLE_SPEECH_RECOGNIZER_PARENT: parentPath ||
|
||||
GOOGLE_SPEECH_RECOGNIZER_PARENT: recognizerParent ||
|
||||
`projects/${sttCredentials.credentials.project_id}/locations/global`,
|
||||
GOOGLE_SPEECH_CLOUD_SERVICES_VERSION: 'v2',
|
||||
...(derivedSttUri && {GOOGLE_SPEECH_TO_TEXT_URI: derivedSttUri}),
|
||||
@@ -887,10 +913,7 @@ module.exports = (logger) => {
|
||||
...(rOpts.googleOptions?.enableVoiceActivityEvents && {
|
||||
GOOGLE_SPEECH_ENABLE_VOICE_ACTIVITY_EVENTS: rOpts.googleOptions.enableVoiceActivityEvents
|
||||
}),
|
||||
...(rOpts.sgoogleOptions?.recognizerId) && {GOOGLE_SPEECH_RECOGNIZER_ID: rOpts.googleOptions.recognizerId},
|
||||
...(rOpts.googleOptions?.enableVoiceActivityEvents && {
|
||||
GOOGLE_SPEECH_ENABLE_VOICE_ACTIVITY_EVENTS: rOpts.googleOptions.enableVoiceActivityEvents
|
||||
}),
|
||||
...(recognizerId && {GOOGLE_SPEECH_RECOGNIZER_ID: recognizerId}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user