From 9b741a0a286ec68d1a3d4624479cce8ad070c88b Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Wed, 18 Mar 2026 00:33:38 +0100 Subject: [PATCH] vector fixes --- modules/connectors/connectorDbPostgre.py | 15 ++++- modules/interfaces/interfaceDbManagement.py | 10 +++- .../services/serviceAgent/mainServiceAgent.py | 60 +++++++++++++++---- 3 files changed, 66 insertions(+), 19 deletions(-) diff --git a/modules/connectors/connectorDbPostgre.py b/modules/connectors/connectorDbPostgre.py index 77689ae5..83517f31 100644 --- a/modules/connectors/connectorDbPostgre.py +++ b/modules/connectors/connectorDbPostgre.py @@ -177,7 +177,7 @@ def _get_cached_connector( oldest_key = _connector_cache_order.pop(0) if oldest_key in _connector_cache: try: - _connector_cache[oldest_key].close() + _connector_cache[oldest_key].close(forceClose=True) except Exception as e: logger.warning(f"Error closing evicted connector: {e}") del _connector_cache[oldest_key] @@ -189,6 +189,7 @@ def _get_cached_connector( dbPort=dbPort, userId=userId, ) + _connector_cache[key]._isCachedShared = True _connector_cache_order.append(key) conn = _connector_cache[key] # Set request-scoped userId via contextvar (avoids mutating shared connector) @@ -225,6 +226,7 @@ class DatabaseConnector: # Initialize database system first (creates database if needed) self.connection = None + self._isCachedShared = False self.initDbSystem() # No caching needed with proper database - PostgreSQL handles performance @@ -1159,8 +1161,15 @@ class DatabaseConnector: logger.error(f"Error in semantic search on {table}: {e}") return [] - def close(self): - """Close the database connection.""" + def close(self, forceClose: bool = False): + """Close the database connection. + + Shared cached connectors are intentionally kept open unless forceClose=True. + This prevents accidental shutdown from interface __del__ methods while + other requests are still using the same cached connector instance. + """ + if self._isCachedShared and not forceClose: + return if ( hasattr(self, "connection") and self.connection diff --git a/modules/interfaces/interfaceDbManagement.py b/modules/interfaces/interfaceDbManagement.py index 4ddcb972..9c266aac 100644 --- a/modules/interfaces/interfaceDbManagement.py +++ b/modules/interfaces/interfaceDbManagement.py @@ -1742,12 +1742,16 @@ class ComponentObjects: if not targetUserId: logger.error("No user ID provided for voice settings") return None - - # Get voice settings for the user, filtered by RBAC + + recordFilter: Dict[str, Any] = {"userId": targetUserId} + if self.featureInstanceId: + recordFilter["featureInstanceId"] = self.featureInstanceId + + # Get voice settings for the user (scoped to current feature instance if available), filtered by RBAC filteredSettings = getRecordsetWithRBAC(self.db, VoiceSettings, self.currentUser, - recordFilter={"userId": targetUserId}, + recordFilter=recordFilter, mandateId=self.mandateId ) diff --git a/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py b/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py index c642a2fa..2de2ebd4 100644 --- a/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py +++ b/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py @@ -2239,21 +2239,55 @@ def _registerCoreTools(registry: ToolRegistry, services): if not voiceName: try: + from modules.interfaces import interfaceDbManagement featureInstanceId = context.get("featureInstanceId", "") userId = context.get("userId", "") - if featureInstanceId and userId: - dbMgmt = services.chat.interfaceDbApp if hasattr(services.chat, "interfaceDbApp") else None - if dbMgmt and hasattr(dbMgmt, "getVoiceSettings"): - vs = dbMgmt.getVoiceSettings(userId) - if vs: - voiceMap = {} - if hasattr(vs, "ttsVoiceMap") and vs.ttsVoiceMap: - voiceMap = vs.ttsVoiceMap if isinstance(vs.ttsVoiceMap, dict) else {} - if language in voiceMap: - voiceName = voiceMap[language].get("voiceName") if isinstance(voiceMap[language], dict) else voiceMap[language] - logger.info(f"textToSpeech: using configured voice '{voiceName}' for {language}") - elif hasattr(vs, "ttsVoice") and vs.ttsVoice and hasattr(vs, "ttsLanguage") and vs.ttsLanguage == language: - voiceName = vs.ttsVoice + if userId: + dbMgmt = interfaceDbManagement.getInterface( + services.user, + mandateId=mandateId or None, + featureInstanceId=featureInstanceId or None, + ) + vs = dbMgmt.getVoiceSettings(userId) if dbMgmt and hasattr(dbMgmt, "getVoiceSettings") else None + if vs: + voiceMap = {} + if hasattr(vs, "ttsVoiceMap") and vs.ttsVoiceMap: + voiceMap = vs.ttsVoiceMap if isinstance(vs.ttsVoiceMap, dict) else {} + + selectedKey = None + selectedVoiceEntry = None + baseLanguage = language.split("-")[0].lower() if isinstance(language, str) and language else "" + + # 1) Exact match first (e.g. de-DE) + if isinstance(language, str) and language in voiceMap: + selectedKey = language + selectedVoiceEntry = voiceMap[language] + + # 2) Match short language key (e.g. de) + if selectedVoiceEntry is None and baseLanguage and baseLanguage in voiceMap: + selectedKey = baseLanguage + selectedVoiceEntry = voiceMap[baseLanguage] + + # 3) Match by same language family (e.g. de-CH -> de-DE mapping) + if selectedVoiceEntry is None and baseLanguage: + for mapKey, mapValue in voiceMap.items(): + mapKeyNorm = str(mapKey).lower() + if mapKeyNorm == baseLanguage or mapKeyNorm.startswith(f"{baseLanguage}-"): + selectedKey = str(mapKey) + selectedVoiceEntry = mapValue + break + + if selectedVoiceEntry is not None: + voiceName = ( + selectedVoiceEntry.get("voiceName") + if isinstance(selectedVoiceEntry, dict) + else selectedVoiceEntry + ) + logger.info( + f"textToSpeech: using configured voice '{voiceName}' for requested language '{language}' (matched key '{selectedKey}')" + ) + elif hasattr(vs, "ttsVoice") and vs.ttsVoice and hasattr(vs, "ttsLanguage") and vs.ttsLanguage == language: + voiceName = vs.ttsVoice except Exception as prefErr: logger.debug(f"textToSpeech: could not load voice preferences: {prefErr}")