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
|
||||
}
|
||||
|
||||
# Create a separate client for DALL-E API calls
|
||||
# Timeout set to 600 seconds (10 minutes) for complex image generation requests
|
||||
dalle_client = httpx.AsyncClient(
|
||||
timeout=600.0,
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.apiKey}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
)
|
||||
|
||||
response = await dalle_client.post(
|
||||
# Use existing httpClient to benefit from connection pooling
|
||||
# This avoids TLS connection issues that can occur with fresh clients
|
||||
response = await self.httpClient.post(
|
||||
dalle_url,
|
||||
json=payload
|
||||
)
|
||||
|
||||
await dalle_client.aclose()
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(f"DALL-E API error: {response.status_code} - {response.text}")
|
||||
return AiModelResponse(
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -288,14 +288,26 @@ class ContentExtractor:
|
|||
intent = getIntentForDocument(document.id, documentIntents)
|
||||
|
||||
if not intent:
|
||||
# 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"
|
||||
)
|
||||
# Try to find intent by similar UUID (fix for AI UUID hallucination)
|
||||
correctedIntent = self._findIntentBySimilarId(document.id, documentIntents)
|
||||
if correctedIntent:
|
||||
logger.warning(f"Found intent for document {document.id} using UUID correction (original: {correctedIntent.documentId})")
|
||||
# Create new intent with correct document ID
|
||||
intent = DocumentIntent(
|
||||
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
|
||||
|
||||
|
|
@ -656,4 +668,39 @@ class ContentExtractor:
|
|||
logger.error(f"Error extracting nested parts from structure part {structurePart.id}: {str(e)}")
|
||||
|
||||
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:
|
||||
# Validation 1.2: Skip intents for unknown documents
|
||||
if intent.documentId not in documentIds:
|
||||
logger.warning(f"Skipping intent for unknown document: {intent.documentId}")
|
||||
continue
|
||||
# Try to find similar UUID (fix AI hallucination/typo)
|
||||
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)
|
||||
|
||||
# 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 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)
|
||||
```
|
||||
|
|
@ -399,6 +401,8 @@ This is a PLANNING task. Return EXACTLY ONE complete JSON object. Do not generat
|
|||
{contentPartsIndex}
|
||||
|
||||
## 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.
|
||||
|
||||
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")
|
||||
- level: Heading level (1, 2, 3, etc.)
|
||||
- title: Chapter title
|
||||
- contentParts: Object mapping ContentPart IDs to usage instructions
|
||||
- generationHint: Description of what content to generate
|
||||
- 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 (including formatting/styling requirements)
|
||||
- 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
|
||||
- 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 number of chapters depends on the user request - create only what is requested
|
||||
|
||||
## WHAT IS A CHAPTER vs WHAT IS FORMATTING
|
||||
- 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
|
||||
CRITICAL: Only create chapters for CONTENT sections, not for formatting/styling requirements. Formatting/styling requirements to be included in each generationHint if needed.
|
||||
|
||||
## DOCUMENT OUTPUT FORMAT
|
||||
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
|
||||
## DOCUMENT STRUCTURE
|
||||
|
||||
## FORMAT-APPROPRIATE CHAPTER STRUCTURE
|
||||
When determining the chapter structure, consider the document's output format and ensure chapters are structured appropriately for that format:
|
||||
- Different formats have different capabilities and constraints
|
||||
- Structure chapters to match what the format can effectively represent
|
||||
- Consider what content types work best for each format
|
||||
- Ensure the chapter structure aligns with the format's strengths and limitations
|
||||
For each document, determine:
|
||||
- outputFormat: From USER REQUEST (explicit mention or infer from purpose/content type). Default: "{outputFormat}". Multiple documents can have different formats.
|
||||
- language: From USER REQUEST (map to ISO 639-1: de, en, fr, it...). Default: "{language}". Multiple documents can have different languages.
|
||||
- chapters: Structure appropriately for the format (e.g., pptx=slides, docx=sections, xlsx=worksheets). Match format capabilities and constraints.
|
||||
|
||||
## DOCUMENT LANGUAGE
|
||||
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
|
||||
Required JSON fields:
|
||||
- metadata: {{"title": "...", "language": "..."}}
|
||||
- documents: Array of document objects, each with:
|
||||
- id: Unique document identifier (e.g., "doc_1")
|
||||
- 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)
|
||||
- documents: Array with id, title, filename, outputFormat, language, chapters[]
|
||||
- chapters: Array with id, level, title, contentParts, generationHint, sections[]
|
||||
|
||||
EXAMPLE STRUCTURE (for reference only - adapt to user request):
|
||||
{{
|
||||
|
|
|
|||
|
|
@ -139,9 +139,15 @@ class ProgressLogger:
|
|||
logger.warning(f"Cannot log progress: no workflow available")
|
||||
return None
|
||||
|
||||
# Validate parentOperationId exists in activeOperations (for debugging)
|
||||
if parentOperationId and parentOperationId not in self.activeOperations:
|
||||
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.")
|
||||
# Validate parentOperationId exists in activeOperations or finishedOperations
|
||||
if parentOperationId:
|
||||
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
|
||||
logData = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue