164 lines
No EOL
7.8 KiB
Python
164 lines
No EOL
7.8 KiB
Python
"""
|
|
Image Description Service
|
|
This service generates detailed AI-powered descriptions of creative assets for focus group research.
|
|
It helps distinguish between multiple images in the conversation context.
|
|
"""
|
|
|
|
import os
|
|
import logging
|
|
from typing import Optional
|
|
from PIL import Image
|
|
from app.services.llm_service import LLMService, LLMServiceError
|
|
from app.services.conversation_context_service import ConversationContextService
|
|
from app.utils.prompt_loader import load_prompt, PromptLoaderError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class ImageDescriptionError(Exception):
|
|
"""Exception raised for errors in image description generation."""
|
|
pass
|
|
|
|
class ImageDescriptionService:
|
|
"""Service for generating AI-powered descriptions of creative assets."""
|
|
|
|
@staticmethod
|
|
def generate_description(focus_group_id: str, asset_filename: str) -> str:
|
|
"""
|
|
Generate a detailed AI description of a creative asset image.
|
|
|
|
Args:
|
|
focus_group_id: The focus group ID containing the asset
|
|
asset_filename: The filename of the asset to describe
|
|
|
|
Returns:
|
|
A detailed description of the image
|
|
|
|
Raises:
|
|
ImageDescriptionError: If description generation fails
|
|
"""
|
|
try:
|
|
print(f"🎨 DESCRIPTION: Generating AI description for {asset_filename}")
|
|
|
|
# Resolve the full path to the asset
|
|
asset_path = ConversationContextService._resolve_asset_path(focus_group_id, asset_filename)
|
|
print(f"🔍 DESCRIPTION: Resolved asset path: {asset_path}")
|
|
|
|
# Check if file exists
|
|
if not os.path.exists(asset_path):
|
|
print(f"❌ DESCRIPTION: File does not exist at path: {asset_path}")
|
|
# List files in the directory to help debug
|
|
asset_dir = os.path.dirname(asset_path)
|
|
if os.path.exists(asset_dir):
|
|
files_in_dir = os.listdir(asset_dir)
|
|
print(f"🔍 DESCRIPTION: Files in directory {asset_dir}: {files_in_dir}")
|
|
else:
|
|
print(f"❌ DESCRIPTION: Directory does not exist: {asset_dir}")
|
|
raise ImageDescriptionError(f"Asset file not found: {asset_path}")
|
|
|
|
# Verify the image can be loaded (optional validation)
|
|
try:
|
|
image = Image.open(asset_path)
|
|
print(f"🖼️ DESCRIPTION: Validated image {asset_filename} ({image.size[0]}x{image.size[1]})")
|
|
image.close() # Close the image since we're passing the path to LLM
|
|
except Exception as e:
|
|
raise ImageDescriptionError(f"Failed to validate image {asset_filename}: {str(e)}")
|
|
|
|
# Load the description prompt
|
|
try:
|
|
prompt = load_prompt('image-description', {})
|
|
print(f"📝 DESCRIPTION: Loaded description prompt ({len(prompt)} chars)")
|
|
except PromptLoaderError as e:
|
|
raise ImageDescriptionError(f"Failed to load description prompt: {str(e)}")
|
|
|
|
# Generate description using multimodal LLM
|
|
try:
|
|
print(f"🚀 DESCRIPTION: Calling LLM service with image: {asset_path}")
|
|
description = LLMService.generate_multimodal_content(
|
|
prompt=prompt,
|
|
image_paths=[asset_path],
|
|
temperature=0.7
|
|
)
|
|
print(f"✅ DESCRIPTION: LLM returned description ({len(description)} chars): {description[:100]}...")
|
|
return description.strip()
|
|
|
|
except LLMServiceError as e:
|
|
raise ImageDescriptionError(f"LLM description generation failed: {str(e)}")
|
|
|
|
except Exception as e:
|
|
print(f"⚠️ DESCRIPTION: LLM service failed, generating fallback description: {str(e)}")
|
|
|
|
# Generate fallback description based on image info
|
|
try:
|
|
# We already validated the image exists earlier, so we can safely open it
|
|
image = Image.open(asset_path)
|
|
width, height = image.size
|
|
image.close()
|
|
|
|
# Create a simple but useful fallback description
|
|
fallback_description = f"a {width}x{height} pixel marketing advertisement image"
|
|
|
|
print(f"✅ DESCRIPTION: Generated fallback description: '{fallback_description}'")
|
|
return fallback_description
|
|
|
|
except Exception as fallback_error:
|
|
error_msg = f"Failed to generate description for {asset_filename}: LLM failed ({str(e)}) and fallback failed ({str(fallback_error)})"
|
|
print(f"❌ DESCRIPTION: {error_msg}")
|
|
raise ImageDescriptionError(error_msg)
|
|
|
|
@staticmethod
|
|
def enhance_creative_review_question(original_question: str, asset_filename: str, description: str) -> str:
|
|
"""
|
|
Enhance a creative review question by incorporating AI-generated image description.
|
|
|
|
Args:
|
|
original_question: The original question text
|
|
asset_filename: The asset filename being referenced
|
|
description: The AI-generated description of the image
|
|
|
|
Returns:
|
|
Enhanced question text with detailed visual description
|
|
"""
|
|
try:
|
|
print(f"🔧 ENHANCEMENT: Enhancing question for {asset_filename}")
|
|
print(f"🔧 Original: {original_question[:100]}...")
|
|
print(f"🔧 Description: {description[:100]}...")
|
|
|
|
# Look for the asset filename reference in the question
|
|
if asset_filename in original_question:
|
|
# Find the filename reference and enhance it
|
|
filename_pattern = f"'{asset_filename}'"
|
|
enhanced_reference = f"'{asset_filename}' - {description}"
|
|
|
|
enhanced_question = original_question.replace(filename_pattern, enhanced_reference)
|
|
|
|
print(f"✅ ENHANCEMENT: Enhanced question: {enhanced_question[:150]}...")
|
|
return enhanced_question
|
|
else:
|
|
# If filename not found in expected format, try other patterns
|
|
import re
|
|
|
|
# Try to find quoted filename patterns
|
|
patterns = [
|
|
(f'"{asset_filename}"', f'"{asset_filename}" - {description}'),
|
|
(f"titled '{asset_filename}'", f"titled '{asset_filename}' - {description}"),
|
|
(f'titled "{asset_filename}"', f'titled "{asset_filename}" - {description}'),
|
|
(asset_filename, f"{asset_filename} - {description}")
|
|
]
|
|
|
|
enhanced_question = original_question
|
|
for old_pattern, new_pattern in patterns:
|
|
if old_pattern in enhanced_question:
|
|
enhanced_question = enhanced_question.replace(old_pattern, new_pattern)
|
|
print(f"✅ ENHANCEMENT: Enhanced with pattern '{old_pattern}': {enhanced_question[:150]}...")
|
|
return enhanced_question
|
|
|
|
# If no patterns match, append description at the end
|
|
enhanced_question = f"{original_question} The image shows {description}."
|
|
print(f"⚠️ ENHANCEMENT: Appended description to end: {enhanced_question[:150]}...")
|
|
return enhanced_question
|
|
|
|
except Exception as e:
|
|
error_msg = f"Failed to enhance question for {asset_filename}: {str(e)}"
|
|
print(f"❌ ENHANCEMENT: {error_msg}")
|
|
# Return original question if enhancement fails
|
|
return original_question |