3 Commits

Author SHA1 Message Date
IGNY8 VPS (Salman)
826ad89a3e Remove aws-admin pattern completely - use account + GlobalIntegrationSettings
ARCHITECTURE FIX:
- aws-admin IntegrationSettings will NEVER exist (it's a legacy pattern)
- Only user's own account IntegrationSettings can exist (if they override defaults)
- Otherwise GlobalIntegrationSettings is used directly
- API keys are ALWAYS from GlobalIntegrationSettings (accounts cannot override API keys)

REMOVED:
- All aws-admin Account lookups
- All aws-admin IntegrationSettings fallback attempts
- Confusing nested try/except chains

CORRECT FLOW NOW:
1. Try account's IntegrationSettings for config overrides
2. Use GlobalIntegrationSettings for missing values and ALL API keys
3. No intermediate aws-admin lookups
2025-12-25 02:11:21 +00:00
IGNY8 VPS (Salman)
504d0174f7 Fix image generation: escape JSON in prompt template + GlobalIntegrationSettings fallback
ROOT CAUSES IDENTIFIED:
1. GlobalAIPrompt template had unescaped JSON braces that broke Python's .format()
   - Python treats {...} as placeholders, causing KeyError when rendering
   - Escaped JSON braces to {{...}} while preserving {title}, {content}, {max_images}

2. Image functions hardcoded aws-admin IntegrationSettings which didn't exist
   - Functions failed when aws-admin account had no IntegrationSettings
   - Added GlobalIntegrationSettings fallback for all missing values

CHANGES:
- Fixed GlobalAIPrompt.image_prompt_extraction template in database (escaped JSON)
- Updated generate_image_prompts._get_max_in_article_images() with fallback
- Updated generate_images.prepare_data() with fallback for all image settings
- Updated tasks.process_image_generation_queue() with fallback for config + API keys

TESTED: Template rendering now works, GlobalIntegrationSettings.max_in_article_images=4
2025-12-25 02:09:29 +00:00
IGNY8 VPS (Salman)
5299fd82eb Revert image prompt changes - investigate original issue 2025-12-25 01:59:23 +00:00
8 changed files with 93 additions and 200 deletions

View File

@@ -99,7 +99,6 @@ class GenerateImagePromptsFunction(BaseAIFunction):
content_text = self._format_content_for_prompt(extracted)
# Get prompt from PromptRegistry - same as other functions
# Provide multiple context key variations for compatibility with different prompt templates
prompt = PromptRegistry.get_prompt(
function_name='generate_image_prompts',
account=account,
@@ -107,11 +106,6 @@ class GenerateImagePromptsFunction(BaseAIFunction):
'title': extracted['title'],
'content': content_text,
'max_images': max_images,
'count': max_images, # Alias for backward compatibility
'TITLE': extracted['title'], # Uppercase variants
'CONTENT': content_text,
'MAX_IMAGES': max_images,
'COUNT': max_images,
}
)
@@ -224,17 +218,34 @@ class GenerateImagePromptsFunction(BaseAIFunction):
# Helper methods
def _get_max_in_article_images(self, account) -> int:
"""Get max_in_article_images from global settings with optional account override"""
from igny8_core.ai.settings import get_image_generation_config
"""
Get max_in_article_images from settings.
Uses account's IntegrationSettings override, or GlobalIntegrationSettings.
"""
from igny8_core.modules.system.models import IntegrationSettings
from igny8_core.modules.system.global_settings_models import GlobalIntegrationSettings
# Try account-specific override first
try:
config = get_image_generation_config(account)
max_images = config.get('max_in_article_images', 2) # Default to 2 if not set
logger.info(f"Using max_in_article_images={max_images} for account {account.id}")
return max_images
except Exception as e:
logger.warning(f"Failed to get max_in_article_images from settings: {e}. Using default of 2")
return 2 # Fallback default
settings = IntegrationSettings.objects.get(
account=account,
integration_type='image_generation',
is_active=True
)
max_images = settings.config.get('max_in_article_images')
if max_images is not None:
max_images = int(max_images)
logger.info(f"Using max_in_article_images={max_images} from account {account.id} IntegrationSettings override")
return max_images
except IntegrationSettings.DoesNotExist:
logger.debug(f"No IntegrationSettings override for account {account.id}, using GlobalIntegrationSettings")
# Use GlobalIntegrationSettings default
global_settings = GlobalIntegrationSettings.get_instance()
max_images = global_settings.max_in_article_images
logger.info(f"Using max_in_article_images={max_images} from GlobalIntegrationSettings (account {account.id})")
return max_images
def _extract_content_elements(self, content: Content, max_images: int) -> Dict:
"""Extract title, intro paragraphs, and H2 headings from content HTML"""

View File

@@ -67,23 +67,42 @@ class GenerateImagesFunction(BaseAIFunction):
if not tasks:
raise ValueError("No tasks found")
# Get image generation settings from global settings with optional account override
from igny8_core.ai.settings import get_image_generation_config
# Get image generation settings
# Try account-specific override, otherwise use GlobalIntegrationSettings
from igny8_core.modules.system.models import IntegrationSettings
from igny8_core.modules.system.global_settings_models import GlobalIntegrationSettings
image_config = get_image_generation_config(account)
provider = image_config.get('provider', 'openai')
model = image_config.get('model', 'dall-e-3')
max_in_article_images = image_config.get('max_in_article_images', 2)
image_settings = {}
try:
integration = IntegrationSettings.objects.get(
account=account,
integration_type='image_generation',
is_active=True
)
image_settings = integration.config or {}
logger.info(f"Using image settings from account {account.id} IntegrationSettings override")
except IntegrationSettings.DoesNotExist:
logger.info(f"No IntegrationSettings override for account {account.id}, using GlobalIntegrationSettings")
# Use GlobalIntegrationSettings for missing values
global_settings = GlobalIntegrationSettings.get_instance()
# Extract settings with defaults from global settings
provider = image_settings.get('provider') or image_settings.get('service') or global_settings.default_image_service
if provider == 'runware':
model = image_settings.get('model') or image_settings.get('runwareModel') or global_settings.runware_model
else:
model = image_settings.get('model') or global_settings.dalle_model
return {
'tasks': tasks,
'account': account,
'provider': provider,
'model': model,
'image_type': image_config.get('style', 'realistic'),
'max_in_article_images': max_in_article_images,
'desktop_enabled': True, # Always enabled
'mobile_enabled': True, # Always enabled
'image_type': image_settings.get('image_type') or global_settings.image_style,
'max_in_article_images': int(image_settings.get('max_in_article_images') or global_settings.max_in_article_images),
'desktop_enabled': image_settings.get('desktop_enabled', True),
'mobile_enabled': image_settings.get('mobile_enabled', True),
}
def build_prompt(self, data: Dict, account=None) -> Dict:

View File

@@ -130,24 +130,20 @@ class PromptRegistry:
logger.debug(f"Replaced placeholder {placeholder} with {len(str(value))} characters")
# Step 2: Try .format() style for {variable} placeholders (if any remain)
# Normalize context keys - provide both original case, lowercase, and uppercase
# Normalize context keys - convert UPPER to lowercase for .format()
normalized_context = {}
for key, value in context.items():
# Add original key
# Try both original key and lowercase version
normalized_context[key] = value
# Add lowercase version
normalized_context[key.lower()] = value
# Add uppercase version
normalized_context[key.upper()] = value
# Only try .format() if there are {variable} placeholders
if '{' in rendered and '}' in rendered:
try:
rendered = rendered.format(**normalized_context)
logger.debug(f"Successfully formatted prompt with context keys: {list(context.keys())}")
except (KeyError, ValueError, IndexError) as e:
# If .format() fails, log warning but keep the [IGNY8_*] replacements
logger.warning(f"Failed to format prompt with .format(): {e}. Context keys: {list(context.keys())}. Using [IGNY8_*] replacements only.")
logger.warning(f"Failed to format prompt with .format(): {e}. Using [IGNY8_*] replacements only.")
return rendered

View File

@@ -123,120 +123,3 @@ def get_model_config(function_name: str, account) -> Dict[str, Any]:
'response_format': response_format,
}
def get_image_generation_config(account) -> Dict[str, Any]:
"""
Get image generation configuration for AI functions.
Architecture:
- API keys: ALWAYS from GlobalIntegrationSettings (platform-wide)
- Model/params: From IntegrationSettings if account has override, else from global
- Supports both OpenAI (DALL-E) and Runware providers
Args:
account: Account instance (required)
Returns:
dict: Image generation configuration with 'provider', 'model', 'api_key',
'size', 'quality', 'style', 'max_in_article_images'
Raises:
ValueError: If account not provided or settings not configured
"""
if not account:
raise ValueError("Account is required for image generation configuration")
try:
from igny8_core.modules.system.global_settings_models import GlobalIntegrationSettings
from igny8_core.modules.system.models import IntegrationSettings
# Get global settings (for API keys and defaults)
global_settings = GlobalIntegrationSettings.get_instance()
# Start with global defaults
provider = global_settings.default_image_service # 'openai' or 'runware'
max_in_article_images = global_settings.max_in_article_images
if provider == 'runware':
api_key = global_settings.runware_api_key
model = global_settings.runware_model
size = global_settings.desktop_image_size
quality = global_settings.image_quality
style = global_settings.image_style
if not api_key:
raise ValueError(
"Platform Runware API key not configured. "
"Please configure GlobalIntegrationSettings in Django admin."
)
else: # openai/dalle
api_key = global_settings.dalle_api_key or global_settings.openai_api_key
model = global_settings.dalle_model
size = global_settings.dalle_size
quality = global_settings.image_quality
style = global_settings.image_style
if not api_key:
raise ValueError(
"Platform OpenAI/DALL-E API key not configured. "
"Please configure GlobalIntegrationSettings in Django admin."
)
# Check if account has overrides
# Try both 'image_generation' and provider-specific types for backward compatibility
account_settings = None
for integration_type in ['image_generation', provider, 'dalle', 'runware']:
try:
account_settings = IntegrationSettings.objects.get(
account=account,
integration_type=integration_type,
is_active=True
)
break
except IntegrationSettings.DoesNotExist:
continue
if account_settings:
config = account_settings.config or {}
# Override provider if specified
if config.get('provider') or config.get('service'):
provider = config.get('provider') or config.get('service')
# Override model if specified
if config.get('model'):
model = config['model']
# Override size if specified
if config.get('size') or config.get('image_size'):
size = config.get('size') or config.get('image_size')
# Override quality if specified
if config.get('quality') or config.get('image_quality'):
quality = config.get('quality') or config.get('image_quality')
# Override style if specified
if config.get('style') or config.get('image_style'):
style = config.get('style') or config.get('image_style')
# Override max_in_article_images if specified
if config.get('max_in_article_images'):
max_in_article_images = int(config['max_in_article_images'])
return {
'provider': provider,
'model': model,
'api_key': api_key, # ALWAYS from global
'size': size,
'quality': quality,
'style': style,
'max_in_article_images': max_in_article_images,
}
except Exception as e:
logger.error(f"Could not load image generation settings for account {account.id}: {e}")
raise ValueError(
f"Could not load image generation configuration for account {account.id}. "
f"Please configure GlobalIntegrationSettings."
)

View File

@@ -181,41 +181,47 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
failed = 0
results = []
# Get image generation settings from IntegrationSettings
# Always use system account settings (aws-admin) for global configuration
logger.info("[process_image_generation_queue] Step 1: Loading image generation settings from aws-admin")
from igny8_core.auth.models import Account
# Get image generation settings
# Try account-specific override, otherwise use GlobalIntegrationSettings
logger.info("[process_image_generation_queue] Step 1: Loading image generation settings")
from igny8_core.modules.system.global_settings_models import GlobalIntegrationSettings
config = {}
try:
system_account = Account.objects.get(slug='aws-admin')
image_settings = IntegrationSettings.objects.get(
account=system_account,
account=account,
integration_type='image_generation',
is_active=True
)
logger.info(f"[process_image_generation_queue] Using system account (aws-admin) settings")
logger.info(f"[process_image_generation_queue] Using account {account.id} IntegrationSettings override")
config = image_settings.config or {}
except (Account.DoesNotExist, IntegrationSettings.DoesNotExist):
logger.error("[process_image_generation_queue] ERROR: Image generation settings not found in aws-admin account")
return {'success': False, 'error': 'Image generation settings not found in aws-admin account'}
except IntegrationSettings.DoesNotExist:
logger.info(f"[process_image_generation_queue] No IntegrationSettings override for account {account.id}, using GlobalIntegrationSettings")
except Exception as e:
logger.error(f"[process_image_generation_queue] ERROR loading image generation settings: {e}", exc_info=True)
return {'success': False, 'error': f'Error loading image generation settings: {str(e)}'}
# Use GlobalIntegrationSettings for missing values
global_settings = GlobalIntegrationSettings.get_instance()
logger.info(f"[process_image_generation_queue] Image generation settings loaded. Config keys: {list(config.keys())}")
logger.info(f"[process_image_generation_queue] Full config: {config}")
# Get provider and model from config (respect user settings)
provider = config.get('provider', 'openai')
# Get model - try 'model' first, then 'imageModel' as fallback
model = config.get('model') or config.get('imageModel') or 'dall-e-3'
# Get provider and model from config with global fallbacks
provider = config.get('provider') or global_settings.default_image_service
if provider == 'runware':
model = config.get('model') or config.get('imageModel') or global_settings.runware_model
else:
model = config.get('model') or config.get('imageModel') or global_settings.dalle_model
logger.info(f"[process_image_generation_queue] Using PROVIDER: {provider}, MODEL: {model} from settings")
image_type = config.get('image_type', 'realistic')
image_type = config.get('image_type') or global_settings.image_style
image_format = config.get('image_format', 'webp')
desktop_enabled = config.get('desktop_enabled', True)
mobile_enabled = config.get('mobile_enabled', True)
# Get image sizes from config, with fallback defaults
featured_image_size = config.get('featured_image_size') or ('1280x832' if provider == 'runware' else '1024x1024')
desktop_image_size = config.get('desktop_image_size') or '1024x1024'
desktop_image_size = config.get('desktop_image_size') or global_settings.desktop_image_size
in_article_image_size = config.get('in_article_image_size') or '512x512' # Default to 512x512
logger.info(f"[process_image_generation_queue] Settings loaded:")
@@ -226,44 +232,22 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
logger.info(f" - Desktop enabled: {desktop_enabled}")
logger.info(f" - Mobile enabled: {mobile_enabled}")
# Get provider API key (using same approach as test image generation)
# Note: API key is stored as 'apiKey' (camelCase) in IntegrationSettings.config
# Normal users use system account settings (aws-admin) via fallback
logger.info(f"[process_image_generation_queue] Step 2: Loading {provider.upper()} API key")
try:
provider_settings = IntegrationSettings.objects.get(
account=account,
integration_type=provider, # Use the provider from settings
is_active=True
)
logger.info(f"[process_image_generation_queue] {provider.upper()} integration settings found for account {account.id}")
except IntegrationSettings.DoesNotExist:
# Fallback to system account (aws-admin) settings
logger.info(f"[process_image_generation_queue] No {provider.upper()} settings for account {account.id}, falling back to system account")
from igny8_core.auth.models import Account
try:
system_account = Account.objects.get(slug='aws-admin')
provider_settings = IntegrationSettings.objects.get(
account=system_account,
integration_type=provider,
is_active=True
)
logger.info(f"[process_image_generation_queue] Using system account (aws-admin) {provider.upper()} settings")
except (Account.DoesNotExist, IntegrationSettings.DoesNotExist):
logger.error(f"[process_image_generation_queue] ERROR: {provider.upper()} integration settings not found in system account either")
return {'success': False, 'error': f'{provider.upper()} integration not found or not active'}
except Exception as e:
logger.error(f"[process_image_generation_queue] ERROR getting {provider.upper()} API key: {e}", exc_info=True)
return {'success': False, 'error': f'Error retrieving {provider.upper()} API key: {str(e)}'}
# Get provider API key
# API keys are ALWAYS from GlobalIntegrationSettings (accounts cannot override API keys)
# Account IntegrationSettings only store provider preference, NOT API keys
logger.info(f"[process_image_generation_queue] Step 2: Loading {provider.upper()} API key from GlobalIntegrationSettings")
# Extract API key from provider settings
logger.info(f"[process_image_generation_queue] {provider.upper()} config keys: {list(provider_settings.config.keys()) if provider_settings.config else 'None'}")
# Get API key from GlobalIntegrationSettings
if provider == 'runware':
api_key = global_settings.runware_api_key
elif provider == 'openai':
api_key = global_settings.dalle_api_key or global_settings.openai_api_key
else:
api_key = None
api_key = provider_settings.config.get('apiKey') if provider_settings.config else None
if not api_key:
logger.error(f"[process_image_generation_queue] {provider.upper()} API key not found in config")
logger.error(f"[process_image_generation_queue] {provider.upper()} config: {provider_settings.config}")
return {'success': False, 'error': f'{provider.upper()} API key not configured'}
logger.error(f"[process_image_generation_queue] {provider.upper()} API key not configured in GlobalIntegrationSettings")
return {'success': False, 'error': f'{provider.upper()} API key not configured in GlobalIntegrationSettings'}
# Log API key presence (but not the actual key for security)
api_key_preview = f"{api_key[:10]}...{api_key[-4:]}" if len(api_key) > 14 else "***"