From 37aad732f5c203bbf6acef723ece4f1da755e494 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Sat, 1 Nov 2025 09:05:10 +0100 Subject: [PATCH] voice route updated --- modules/connectors/connectorVoiceGoogle.py | 22 ++++-- modules/datamodels/datamodelVoice.py | 2 +- modules/interfaces/interfaceVoiceObjects.py | 7 ++ .../renderers/rendererDocx.py | 2 +- .../renderers/rendererPdf.py | 2 +- .../subPromptBuilderGeneration.py | 4 ++ .../processing/adaptive/contentValidator.py | 71 ++++++------------- 7 files changed, 51 insertions(+), 59 deletions(-) diff --git a/modules/connectors/connectorVoiceGoogle.py b/modules/connectors/connectorVoiceGoogle.py index 68d0cd31..715772d0 100644 --- a/modules/connectors/connectorVoiceGoogle.py +++ b/modules/connectors/connectorVoiceGoogle.py @@ -723,15 +723,16 @@ class ConnectorGoogleSpeech: logger.info("🌐 Getting available languages from Google Cloud TTS") # List voices from Google Cloud TTS - voices = self.tts_client.list_voices() + response = self.tts_client.list_voices() # Extract unique language codes + # Note: Google TTS API doesn't provide language descriptions, only codes language_codes = set() - for voice in voices: + for voice in response.voices: if voice.language_codes: language_codes.update(voice.language_codes) - # Convert to sorted list + # Convert to sorted list of language codes available_languages = sorted(list(language_codes)) logger.info(f"✅ Found {len(available_languages)} available languages") @@ -763,11 +764,11 @@ class ConnectorGoogleSpeech: logger.info(f"🎤 Getting available voices from Google Cloud TTS, language filter: {languageCode}") # List voices from Google Cloud TTS - voices = self.tts_client.list_voices() + response = self.tts_client.list_voices() availableVoices = [] - for voice in voices: + for voice in response.voices: # Extract language code from voice name (e.g., 'de-DE-Wavenet-A' -> 'de-DE') voiceLanguage = voice.language_codes[0] if voice.language_codes else None @@ -783,15 +784,24 @@ class ConnectorGoogleSpeech: elif voice.name.endswith(('-B', '-D')): gender = "Female" - # Create voice info + # Create voice info with all available fields from Google API voiceInfo = { "name": voice.name, "language_code": voiceLanguage, + "language_codes": list(voice.language_codes) if voice.language_codes else [], "gender": gender, "ssml_gender": voice.ssml_gender.name if voice.ssml_gender else "NEUTRAL", "natural_sample_rate_hertz": voice.natural_sample_rate_hertz } + # Include any additional fields if available from Google API + # Check for common fields that might exist + for field_name in ['description', 'display_name', 'labels']: + if hasattr(voice, field_name): + field_value = getattr(voice, field_name, None) + if field_value: + voiceInfo[field_name] = field_value + availableVoices.append(voiceInfo) # Sort by language code, then by gender, then by name diff --git a/modules/datamodels/datamodelVoice.py b/modules/datamodels/datamodelVoice.py index 6ecdd857..c6cd5ddd 100644 --- a/modules/datamodels/datamodelVoice.py +++ b/modules/datamodels/datamodelVoice.py @@ -9,7 +9,7 @@ import uuid class VoiceSettings(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key", frontend_type="text", frontend_readonly=True, frontend_required=False) userId: str = Field(description="ID of the user these settings belong to", frontend_type="text", frontend_readonly=True, frontend_required=True) - mandateId: str = Field(description="ID of the mandate these settings belong to", frontend_type="text", frontend_readonly=True, frontend_required=False) + mandateId: str = Field(description="ID of the mandate these settings belong to", frontend_type="text", frontend_readonly=True, frontend_required=True) sttLanguage: str = Field(default="de-DE", description="Speech-to-Text language", frontend_type="select", frontend_readonly=False, frontend_required=True) ttsLanguage: str = Field(default="de-DE", description="Text-to-Speech language", frontend_type="select", frontend_readonly=False, frontend_required=True) ttsVoice: str = Field(default="de-DE-KatjaNeural", description="Text-to-Speech voice", frontend_type="select", frontend_readonly=False, frontend_required=True) diff --git a/modules/interfaces/interfaceVoiceObjects.py b/modules/interfaces/interfaceVoiceObjects.py index e83ef77d..66b51765 100644 --- a/modules/interfaces/interfaceVoiceObjects.py +++ b/modules/interfaces/interfaceVoiceObjects.py @@ -268,6 +268,12 @@ class VoiceObjects: try: logger.info(f"Creating voice settings: {settingsData}") + # Ensure mandateId is set from user context if not provided + if "mandateId" not in settingsData or not settingsData["mandateId"]: + if not self.currentUser or not self.currentUser.mandateId: + raise ValueError("mandateId is required but not provided and user context has no mandateId") + settingsData["mandateId"] = self.currentUser.mandateId + # Add timestamps currentTime = getUtcTimestamp() settingsData["creationDate"] = currentTime @@ -330,6 +336,7 @@ class VoiceObjects: # Create default settings if none exist defaultSettings = { "userId": userId, + "mandateId": self.currentUser.mandateId, "sttLanguage": "de-DE", "ttsLanguage": "de-DE", "ttsVoice": "de-DE-Wavenet-A", diff --git a/modules/services/serviceGeneration/renderers/rendererDocx.py b/modules/services/serviceGeneration/renderers/rendererDocx.py index 6db48c32..2a8a0627 100644 --- a/modules/services/serviceGeneration/renderers/rendererDocx.py +++ b/modules/services/serviceGeneration/renderers/rendererDocx.py @@ -87,7 +87,7 @@ class RendererDocx(BaseRenderer): # Process each section in order sections = json_content.get("sections", []) for section in sections: - self._render_json_section(doc, section, styles) + self._renderJsonSection(doc, section, styles) # Save to buffer buffer = io.BytesIO() diff --git a/modules/services/serviceGeneration/renderers/rendererPdf.py b/modules/services/serviceGeneration/renderers/rendererPdf.py index f2b15e46..9154c810 100644 --- a/modules/services/serviceGeneration/renderers/rendererPdf.py +++ b/modules/services/serviceGeneration/renderers/rendererPdf.py @@ -96,7 +96,7 @@ class RendererPdf(BaseRenderer): title_style = self._createTitleStyle(styles) story.append(Paragraph(document_title, title_style)) story.append(Spacer(1, 50)) # Increased spacing to prevent overlap - story.append(Paragraph(f"Generated: {self._format_timestamp()}", self._createNormalStyle(styles))) + story.append(Paragraph(f"Generated: {self._formatTimestamp()}", self._createNormalStyle(styles))) story.append(Spacer(1, 30)) # Add spacing before page break story.append(PageBreak()) diff --git a/modules/services/serviceGeneration/subPromptBuilderGeneration.py b/modules/services/serviceGeneration/subPromptBuilderGeneration.py index a2a98f66..8c21b4e7 100644 --- a/modules/services/serviceGeneration/subPromptBuilderGeneration.py +++ b/modules/services/serviceGeneration/subPromptBuilderGeneration.py @@ -95,9 +95,12 @@ Instructions: - Continue from where it stopped — add NEW items only; do not repeat existing items. - Generate remaining content to complete the user request. - Fill with actual content (no placeholders or instructional text such as "Add more..."). +- IMPORTANT: Ensure "filename" in each document has meaningful name with appropriate extension matching the content. - When the request is fully satisfied, add "complete_response": true at root level. - Output JSON only; no markdown fences or extra text. +IMPORTANT: Before responding, analyse the remaining data to fully satisfy user request. + Continue generating: """ else: @@ -114,6 +117,7 @@ Instructions: - Return ONLY valid JSON (strict). No comments. No trailing commas. Use double quotes. - Do NOT reuse example section IDs; create your own. - Generate complete content based on the user request. +- IMPORTANT: Set a meaningful "filename" in each document with appropriate file extension (e.g., "prime_numbers.txt", "report.docx", "data.json"). The filename should reflect the content and task objective. - When the request is fully satisfied, add "complete_response": true at root level. - Output JSON only; no markdown fences or extra text. diff --git a/modules/workflows/processing/adaptive/contentValidator.py b/modules/workflows/processing/adaptive/contentValidator.py index d13e0dfc..862e36d2 100644 --- a/modules/workflows/processing/adaptive/contentValidator.py +++ b/modules/workflows/processing/adaptive/contentValidator.py @@ -83,10 +83,12 @@ class ContentValidator: availableModels = modelRegistry.getAvailableModels() # Create options for PLAN operation (what validation uses) + # Use default values for priority and processingMode (will use defaults from AiCallOptions) + from modules.datamodels.datamodelAi import PriorityEnum, ProcessingModeEnum options = AiCallOptions( operationType=OperationTypeEnum.PLAN, - priority=None, - processingMode=None + priority=PriorityEnum.BALANCED, + processingMode=ProcessingModeEnum.BASIC ) # Get failover model list to find the model that will be used @@ -118,60 +120,28 @@ class ContentValidator: return 8 * 1024 def _analyzeDocumentsWithSizeLimit(self, documents: List[Any], maxTotalBytes: int) -> List[Dict[str, Any]]: - """Analyze documents with size limit, dividing available space evenly among documents.""" + """ + Analyze documents for validation - METADATA ONLY (no document content/previews). + For planning/validation, we only need metadata to assess format, type, and size compatibility. + """ if not documents: return [] - # Reserve space for JSON structure overhead (approximately 200 bytes per document) - jsonOverheadPerDoc = 200 - reservedOverhead = len(documents) * jsonOverheadPerDoc - availableForContent = max(0, maxTotalBytes - reservedOverhead) - - # Divide available space evenly among documents - bytesPerDoc = availableForContent // len(documents) if documents else 0 - # Ensure minimum space per document (at least 100 bytes) - bytesPerDoc = max(bytesPerDoc, 100) - - logger.debug(f"Document summary space: total={maxTotalBytes} bytes, available={availableForContent} bytes, perDoc={bytesPerDoc} bytes") - summaries = [] for doc in documents: try: - data = getattr(doc, 'documentData', None) name = getattr(doc, 'documentName', 'Unknown') mimeType = getattr(doc, 'mimeType', 'unknown') formatExt = self._detectFormat(doc) sizeInfo = self._calculateSize(doc) - # Create preview with size limit - preview = None - if data is not None: - if isinstance(data, (dict, list)): - preview = json.dumps(data, indent=2, ensure_ascii=False) - else: - preview = str(data) - - # Truncate preview to fit within bytesPerDoc (accounting for JSON structure) - # Estimate: preview takes ~70% of document summary space - maxPreviewBytes = int(bytesPerDoc * 0.7) - previewBytes = len(preview.encode('utf-8')) - - if previewBytes > maxPreviewBytes: - # Truncate to fit - truncated = preview.encode('utf-8')[:maxPreviewBytes] - # Try to decode safely - try: - preview = truncated.decode('utf-8', errors='ignore') - except: - preview = truncated[:maxPreviewBytes-50].decode('utf-8', errors='ignore') - preview += f"\n\n[Truncated - {self._formatBytes(sizeInfo['bytes'])} total]" - + # Only include metadata - NO document content/previews + # This keeps prompts small and focused on validation criteria summary = { "name": name, "mimeType": mimeType, "format": formatExt, - "size": sizeInfo["readable"], - "preview": preview + "size": sizeInfo["readable"] } summaries.append(summary) except Exception as e: @@ -181,7 +151,6 @@ class ContentValidator: "mimeType": getattr(doc, 'mimeType', 'unknown'), "format": "unknown", "size": "0 B", - "preview": None, "error": str(e) }) @@ -291,12 +260,14 @@ EXPECTED FORMAT: {intent.get('expectedFormat', 'unknown')} SUCCESS CRITERIA ({criteriaCount} items): {successCriteria} VALIDATION RULES: -1. Check if delivered documents match expected data type +IMPORTANT: You only have document METADATA (filename, format, size, mimeType) - NOT document content. +Validate based on metadata only: +1. Check if filenames are meaningful and match approximately the task objective 2. Check if delivered formats are compatible with expected format -3. Verify each success criterion is met based on document content/metadata -4. Check document sizes are reasonable for the task -5. Rate overall quality (0.0-1.0) -6. Identify specific gaps based on what the user requested +3. Check if document sizes are reasonable for the task objective +4. Assess if filename and size combination suggests correct data type +5. Rate overall quality (0.0-1.0) based on metadata indicators +6. Identify specific gaps based on what the user requested (infer from filename, size, format - NOT content) OUTPUT FORMAT - JSON ONLY (no prose): {{ @@ -306,13 +277,13 @@ OUTPUT FORMAT - JSON ONLY (no prose): "formatMatch": false, "documentCount": {len(documents)}, "successCriteriaMet": {[False] * criteriaCount}, - "gapAnalysis": "Describe what is missing or incorrect", + "gapAnalysis": "Describe what is missing or incorrect based on filename, size, format metadata", "improvementSuggestions": ["General action to improve overall result"], "validationDetails": [ {{ "documentName": "document.ext", - "issues": ["Specific problem with this document"], - "suggestions": ["Specific fix for this document's issues"] + "issues": ["Issue inferred from metadata (e.g., filename doesn't match task, size too small for objective)"], + "suggestions": ["Specific fix based on metadata analysis"] }} ] }}