Merge pull request #82 from valueonag/feat/foundation-customer-use-cases
fixed issues in workflow with use cases
This commit is contained in:
commit
3f53369b2f
6 changed files with 580 additions and 671 deletions
|
|
@ -335,23 +335,13 @@ class AiOpenai(BaseConnectorAi):
|
||||||
"response_format": "b64_json" # Get base64 data directly instead of URLs
|
"response_format": "b64_json" # Get base64 data directly instead of URLs
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a separate client for DALL-E API calls
|
# Use existing httpClient to benefit from connection pooling
|
||||||
# Timeout set to 600 seconds (10 minutes) for complex image generation requests
|
# This avoids TLS connection issues that can occur with fresh clients
|
||||||
dalle_client = httpx.AsyncClient(
|
response = await self.httpClient.post(
|
||||||
timeout=600.0,
|
|
||||||
headers={
|
|
||||||
"Authorization": f"Bearer {self.apiKey}",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await dalle_client.post(
|
|
||||||
dalle_url,
|
dalle_url,
|
||||||
json=payload
|
json=payload
|
||||||
)
|
)
|
||||||
|
|
||||||
await dalle_client.aclose()
|
|
||||||
|
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
logger.error(f"DALL-E API error: {response.status_code} - {response.text}")
|
logger.error(f"DALL-E API error: {response.status_code} - {response.text}")
|
||||||
return AiModelResponse(
|
return AiModelResponse(
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -288,14 +288,26 @@ class ContentExtractor:
|
||||||
intent = getIntentForDocument(document.id, documentIntents)
|
intent = getIntentForDocument(document.id, documentIntents)
|
||||||
|
|
||||||
if not intent:
|
if not intent:
|
||||||
# Default: extract für alle Dokumente ohne Intent
|
# Try to find intent by similar UUID (fix for AI UUID hallucination)
|
||||||
logger.warning(f"No intent found for document {document.id}, using default 'extract'")
|
correctedIntent = self._findIntentBySimilarId(document.id, documentIntents)
|
||||||
intent = DocumentIntent(
|
if correctedIntent:
|
||||||
documentId=document.id,
|
logger.warning(f"Found intent for document {document.id} using UUID correction (original: {correctedIntent.documentId})")
|
||||||
intents=["extract"],
|
# Create new intent with correct document ID
|
||||||
extractionPrompt="Extract all content from the document",
|
intent = DocumentIntent(
|
||||||
reasoning="Default intent: no specific intent found"
|
documentId=document.id,
|
||||||
)
|
intents=correctedIntent.intents,
|
||||||
|
extractionPrompt=correctedIntent.extractionPrompt,
|
||||||
|
reasoning=f"Intent matched by UUID similarity (original: {correctedIntent.documentId})"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Default: extract für alle Dokumente ohne Intent
|
||||||
|
logger.warning(f"No intent found for document {document.id}, using default 'extract'")
|
||||||
|
intent = DocumentIntent(
|
||||||
|
documentId=document.id,
|
||||||
|
intents=["extract"],
|
||||||
|
extractionPrompt="Extract all content from the document",
|
||||||
|
reasoning="Default intent: no specific intent found"
|
||||||
|
)
|
||||||
|
|
||||||
# WICHTIG: Prüfe alle Intents - ein Dokument kann mehrere ContentParts erzeugen
|
# WICHTIG: Prüfe alle Intents - ein Dokument kann mehrere ContentParts erzeugen
|
||||||
|
|
||||||
|
|
@ -656,4 +668,39 @@ class ContentExtractor:
|
||||||
logger.error(f"Error extracting nested parts from structure part {structurePart.id}: {str(e)}")
|
logger.error(f"Error extracting nested parts from structure part {structurePart.id}: {str(e)}")
|
||||||
|
|
||||||
return nestedParts
|
return nestedParts
|
||||||
|
|
||||||
|
def _findIntentBySimilarId(self, documentId: str, documentIntents: List[DocumentIntent]) -> Optional[DocumentIntent]:
|
||||||
|
"""
|
||||||
|
Versucht ein Intent zu finden, dessen UUID ähnlich zur angegebenen Dokument-ID ist.
|
||||||
|
Dies hilft bei AI UUID-Halluzinationen (z.B. 4451 -> 4551).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
documentId: Die Dokument-ID für die ein Intent gesucht wird
|
||||||
|
documentIntents: Liste aller verfügbaren DocumentIntents
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DocumentIntent mit ähnlicher UUID falls gefunden, sonst None
|
||||||
|
"""
|
||||||
|
if not documentId or len(documentId) != 36: # UUID Format: 8-4-4-4-12
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Prüfe ob es eine UUID ist (Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
||||||
|
if documentId.count('-') != 4:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for intent in documentIntents:
|
||||||
|
intentId = intent.documentId
|
||||||
|
if len(intentId) != 36:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Zähle unterschiedliche Zeichen
|
||||||
|
differences = sum(c1 != c2 for c1, c2 in zip(documentId, intentId))
|
||||||
|
|
||||||
|
# Wenn nur 1-2 Zeichen unterschiedlich sind, ist es wahrscheinlich ein Typo
|
||||||
|
if differences <= 2:
|
||||||
|
# Prüfe ob die Struktur ähnlich ist (gleiche Positionen der Bindestriche)
|
||||||
|
if documentId.count('-') == intentId.count('-'):
|
||||||
|
return intent
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,8 +116,14 @@ class DocumentIntentAnalyzer:
|
||||||
for intent in documentIntents:
|
for intent in documentIntents:
|
||||||
# Validation 1.2: Skip intents for unknown documents
|
# Validation 1.2: Skip intents for unknown documents
|
||||||
if intent.documentId not in documentIds:
|
if intent.documentId not in documentIds:
|
||||||
logger.warning(f"Skipping intent for unknown document: {intent.documentId}")
|
# Try to find similar UUID (fix AI hallucination/typo)
|
||||||
continue
|
correctedDocId = self._findSimilarDocumentId(intent.documentId, documentIds)
|
||||||
|
if correctedDocId:
|
||||||
|
logger.warning(f"Corrected UUID typo in AI response: {intent.documentId} -> {correctedDocId}")
|
||||||
|
intent.documentId = correctedDocId
|
||||||
|
else:
|
||||||
|
logger.warning(f"Skipping intent for unknown document: {intent.documentId}")
|
||||||
|
continue
|
||||||
validatedIntents.append(intent)
|
validatedIntents.append(intent)
|
||||||
|
|
||||||
# Validation 1.1: Documents without intents are OK (not needed)
|
# Validation 1.1: Documents without intents are OK (not needed)
|
||||||
|
|
@ -326,4 +332,39 @@ CRITICAL RULES:
|
||||||
Return ONLY valid JSON following the structure above.
|
Return ONLY valid JSON following the structure above.
|
||||||
"""
|
"""
|
||||||
return prompt
|
return prompt
|
||||||
|
|
||||||
|
def _findSimilarDocumentId(self, incorrectId: str, validIds: set) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Versucht eine ähnliche Dokument-ID zu finden, falls die AI die UUID geändert hat.
|
||||||
|
Prüft auf UUID-Typo (z.B. 4451 -> 4551).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
incorrectId: Die falsche UUID aus der AI-Response
|
||||||
|
validIds: Set von gültigen Dokument-IDs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Korrigierte UUID falls gefunden, sonst None
|
||||||
|
"""
|
||||||
|
if not incorrectId or len(incorrectId) != 36: # UUID Format: 8-4-4-4-12
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Prüfe ob es eine UUID ist (Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
||||||
|
if incorrectId.count('-') != 4:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Versuche Levenshtein-ähnliche Suche: Prüfe ob nur 1-2 Zeichen unterschiedlich sind
|
||||||
|
for validId in validIds:
|
||||||
|
if len(validId) != 36:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Zähle unterschiedliche Zeichen
|
||||||
|
differences = sum(c1 != c2 for c1, c2 in zip(incorrectId, validId))
|
||||||
|
|
||||||
|
# Wenn nur 1-2 Zeichen unterschiedlich sind, ist es wahrscheinlich ein Typo
|
||||||
|
if differences <= 2:
|
||||||
|
# Prüfe ob die Struktur ähnlich ist (gleiche Positionen der Bindestriche)
|
||||||
|
if incorrectId.count('-') == validId.count('-'):
|
||||||
|
return validId
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -386,9 +386,11 @@ CRITICAL:
|
||||||
}}]
|
}}]
|
||||||
}}"""
|
}}"""
|
||||||
|
|
||||||
prompt = f"""# TASK: Generate Chapter Structure
|
prompt = f"""# TASK: Plan Document Structure (Documents + Chapters)
|
||||||
|
|
||||||
This is a PLANNING task. Return EXACTLY ONE complete JSON object. Do not generate multiple JSON objects, alternatives, or variations. Do not use separators like "---" between JSON objects.
|
This is a STRUCTURE PLANNING task. You define which documents to create and which chapters each document will have.
|
||||||
|
Chapter CONTENT will be generated in a later step - here you only plan the STRUCTURE and assign content references.
|
||||||
|
Return EXACTLY ONE complete JSON object. Do not generate multiple JSON objects, alternatives, or variations. Do not use separators like "---" between JSON objects.
|
||||||
|
|
||||||
## USER REQUEST (for context)
|
## USER REQUEST (for context)
|
||||||
```
|
```
|
||||||
|
|
@ -399,6 +401,8 @@ This is a PLANNING task. Return EXACTLY ONE complete JSON object. Do not generat
|
||||||
{contentPartsIndex}
|
{contentPartsIndex}
|
||||||
|
|
||||||
## CONTENT ASSIGNMENT RULE
|
## CONTENT ASSIGNMENT RULE
|
||||||
|
|
||||||
|
CRITICAL: Every chapter MUST have contentParts assigned if it relates to documents/images/data from the user request.
|
||||||
If the user request mentions documents/images/data, then EVERY chapter that generates content related to those references MUST assign the relevant ContentParts explicitly.
|
If the user request mentions documents/images/data, then EVERY chapter that generates content related to those references MUST assign the relevant ContentParts explicitly.
|
||||||
|
|
||||||
Assignment logic:
|
Assignment logic:
|
||||||
|
|
@ -422,8 +426,8 @@ Then chapters that generate those generic content types MUST assign the relevant
|
||||||
- id: Unique identifier (e.g., "chapter_1")
|
- id: Unique identifier (e.g., "chapter_1")
|
||||||
- level: Heading level (1, 2, 3, etc.)
|
- level: Heading level (1, 2, 3, etc.)
|
||||||
- title: Chapter title
|
- title: Chapter title
|
||||||
- contentParts: Object mapping ContentPart IDs to usage instructions
|
- contentParts: Object mapping ContentPart IDs to usage instructions (MUST assign if chapter relates to documents/data from user request)
|
||||||
- generationHint: Description of what content to generate
|
- generationHint: Description of what content to generate (including formatting/styling requirements)
|
||||||
- sections: Empty array [] (REQUIRED - sections are generated in next phase)
|
- sections: Empty array [] (REQUIRED - sections are generated in next phase)
|
||||||
- contentParts: {{"partId": {{"instruction": "..."}} or {{"caption": "..."}} or both}} - Assign ContentParts as required by CONTENT ASSIGNMENT RULE above
|
- contentParts: {{"partId": {{"instruction": "..."}} or {{"caption": "..."}} or both}} - Assign ContentParts as required by CONTENT ASSIGNMENT RULE above
|
||||||
- The "instruction" field for each ContentPart MUST contain ALL relevant details from the USER REQUEST that apply to content extraction for this specific chapter. Include all formatting rules, data requirements, constraints, and specifications mentioned in the user request that are relevant for processing this ContentPart in this chapter.
|
- The "instruction" field for each ContentPart MUST contain ALL relevant details from the USER REQUEST that apply to content extraction for this specific chapter. Include all formatting rules, data requirements, constraints, and specifications mentioned in the user request that are relevant for processing this ContentPart in this chapter.
|
||||||
|
|
@ -431,50 +435,19 @@ Then chapters that generate those generic content types MUST assign the relevant
|
||||||
The generationHint MUST contain ALL relevant details from the USER REQUEST that apply to this specific chapter. Include all formatting rules, data requirements, constraints, column specifications, validation rules, and any other specifications mentioned in the user request that are relevant for generating content for this chapter. Do NOT use generic descriptions - include specific details from the user request.
|
The generationHint MUST contain ALL relevant details from the USER REQUEST that apply to this specific chapter. Include all formatting rules, data requirements, constraints, column specifications, validation rules, and any other specifications mentioned in the user request that are relevant for generating content for this chapter. Do NOT use generic descriptions - include specific details from the user request.
|
||||||
- The number of chapters depends on the user request - create only what is requested
|
- The number of chapters depends on the user request - create only what is requested
|
||||||
|
|
||||||
## WHAT IS A CHAPTER vs WHAT IS FORMATTING
|
CRITICAL: Only create chapters for CONTENT sections, not for formatting/styling requirements. Formatting/styling requirements to be included in each generationHint if needed.
|
||||||
- A CHAPTER contains CONTENT (text, tables, lists, images, etc.)
|
|
||||||
- FORMATTING INSTRUCTIONS (CSS styling, spacing, typography, colors, borders) are NOT separate chapters
|
|
||||||
- If user mentions formatting topics, apply these to ALL chapters via generationHint, do NOT create a separate "Formatting" chapter
|
|
||||||
|
|
||||||
## DOCUMENT OUTPUT FORMAT
|
## DOCUMENT STRUCTURE
|
||||||
For each document, determine the output format by analyzing the USER REQUEST:
|
|
||||||
- Look for explicit format mentions
|
|
||||||
- Infer from document purpose
|
|
||||||
- Infer from content type
|
|
||||||
- If format cannot be determined from the prompt, use: "{outputFormat}"
|
|
||||||
- Include "outputFormat" field in each document in the JSON structure
|
|
||||||
- Multiple documents can have different formats
|
|
||||||
|
|
||||||
## FORMAT-APPROPRIATE CHAPTER STRUCTURE
|
For each document, determine:
|
||||||
When determining the chapter structure, consider the document's output format and ensure chapters are structured appropriately for that format:
|
- outputFormat: From USER REQUEST (explicit mention or infer from purpose/content type). Default: "{outputFormat}". Multiple documents can have different formats.
|
||||||
- Different formats have different capabilities and constraints
|
- language: From USER REQUEST (map to ISO 639-1: de, en, fr, it...). Default: "{language}". Multiple documents can have different languages.
|
||||||
- Structure chapters to match what the format can effectively represent
|
- chapters: Structure appropriately for the format (e.g., pptx=slides, docx=sections, xlsx=worksheets). Match format capabilities and constraints.
|
||||||
- Consider what content types work best for each format
|
|
||||||
- Ensure the chapter structure aligns with the format's strengths and limitations
|
|
||||||
|
|
||||||
## DOCUMENT LANGUAGE
|
Required JSON fields:
|
||||||
For each document, determine the language by analyzing the USER REQUEST:
|
|
||||||
- Look for explicit language mentions
|
|
||||||
- Map language names to ISO 639-1 codes
|
|
||||||
- If language cannot be determined from the prompt, use: "{language}"
|
|
||||||
- Include "language" field in each document in the JSON structure
|
|
||||||
- Multiple documents can have different languages
|
|
||||||
|
|
||||||
## JSON STRUCTURE REQUIREMENTS
|
|
||||||
- metadata: {{"title": "...", "language": "..."}}
|
- metadata: {{"title": "...", "language": "..."}}
|
||||||
- documents: Array of document objects, each with:
|
- documents: Array with id, title, filename, outputFormat, language, chapters[]
|
||||||
- id: Unique document identifier (e.g., "doc_1")
|
- chapters: Array with id, level, title, contentParts, generationHint, sections[]
|
||||||
- title: Document title
|
|
||||||
- filename: Output filename with extension (e.g., "document.docx")
|
|
||||||
- outputFormat: Format code (e.g., "docx", "pdf", "html", "xlsx", "pptx", "txt")
|
|
||||||
- language: ISO 639-1 language code (e.g., "de", "en", "fr", "it")
|
|
||||||
- chapters: Array of chapter objects, each with:
|
|
||||||
- id: Unique chapter identifier (e.g., "chapter_1")
|
|
||||||
- level: Heading level (1, 2, 3, etc.)
|
|
||||||
- title: Chapter title
|
|
||||||
- contentParts: Object mapping ContentPart IDs to usage instructions {{"partId": {{"instruction": "..."}} or {{"caption": "..."}}}}
|
|
||||||
- generationHint: Description of what content to generate
|
|
||||||
- sections: Empty array [] (MANDATORY - always include this field)
|
|
||||||
|
|
||||||
EXAMPLE STRUCTURE (for reference only - adapt to user request):
|
EXAMPLE STRUCTURE (for reference only - adapt to user request):
|
||||||
{{
|
{{
|
||||||
|
|
|
||||||
|
|
@ -139,9 +139,15 @@ class ProgressLogger:
|
||||||
logger.warning(f"Cannot log progress: no workflow available")
|
logger.warning(f"Cannot log progress: no workflow available")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Validate parentOperationId exists in activeOperations (for debugging)
|
# Validate parentOperationId exists in activeOperations or finishedOperations
|
||||||
if parentOperationId and parentOperationId not in self.activeOperations:
|
if parentOperationId:
|
||||||
logger.debug(f"WARNING: Parent operation '{parentOperationId}' not found in activeOperations when creating log for '{operationId}'. Available operations: {list(self.activeOperations.keys())}. Child operation may appear at root level.")
|
if parentOperationId not in self.activeOperations:
|
||||||
|
if parentOperationId in self.finishedOperations:
|
||||||
|
# Parent operation was finished - this is OK, child can still reference it
|
||||||
|
logger.debug(f"Parent operation '{parentOperationId}' is finished but child '{operationId}' can still reference it")
|
||||||
|
else:
|
||||||
|
# Parent operation never existed - log warning
|
||||||
|
logger.debug(f"WARNING: Parent operation '{parentOperationId}' not found in activeOperations when creating log for '{operationId}'. Available operations: {list(self.activeOperations.keys())}. Child operation may appear at root level.")
|
||||||
|
|
||||||
# parentId in ChatLog should be the operationId of the parent operation, not the log entry ID
|
# parentId in ChatLog should be the operationId of the parent operation, not the log entry ID
|
||||||
logData = {
|
logData = {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue