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"]
}}
]
}}