Commit
Β·
dd2695a
1
Parent(s):
0fbc3f9
feat: Convert image generation and specific service calls to asynchronous operations with timeouts using `AsyncOpenAI` and `asyncio.to_thread`.
Browse files- services/generator.py +4 -2
- services/image.py +31 -16
services/generator.py
CHANGED
|
@@ -2058,7 +2058,8 @@ CONCEPT: {concept.get('name', 'Custom Concept')}
|
|
| 2058 |
|
| 2059 |
# Step 1: Researcher
|
| 2060 |
print("π Step 1: Researching psychology triggers, angles, and concepts...")
|
| 2061 |
-
researcher_output =
|
|
|
|
| 2062 |
target_audience=target_audience,
|
| 2063 |
offer=offer,
|
| 2064 |
niche=niche_display
|
|
@@ -2085,7 +2086,8 @@ CONCEPT: {concept.get('name', 'Custom Concept')}
|
|
| 2085 |
# Step 3: Creative Director
|
| 2086 |
print(f"π¨ Step 3: Creating {num_strategies} creative strategy/strategies...")
|
| 2087 |
print(f"π Parameters: num_strategies={num_strategies}, num_images={num_images}")
|
| 2088 |
-
creative_strategies =
|
|
|
|
| 2089 |
researcher_output=researcher_output,
|
| 2090 |
book_knowledge=book_knowledge,
|
| 2091 |
ads_knowledge=ads_knowledge,
|
|
|
|
| 2058 |
|
| 2059 |
# Step 1: Researcher
|
| 2060 |
print("π Step 1: Researching psychology triggers, angles, and concepts...")
|
| 2061 |
+
researcher_output = await asyncio.to_thread(
|
| 2062 |
+
third_flow_service.researcher,
|
| 2063 |
target_audience=target_audience,
|
| 2064 |
offer=offer,
|
| 2065 |
niche=niche_display
|
|
|
|
| 2086 |
# Step 3: Creative Director
|
| 2087 |
print(f"π¨ Step 3: Creating {num_strategies} creative strategy/strategies...")
|
| 2088 |
print(f"π Parameters: num_strategies={num_strategies}, num_images={num_images}")
|
| 2089 |
+
creative_strategies = await asyncio.to_thread(
|
| 2090 |
+
third_flow_service.creative_director,
|
| 2091 |
researcher_output=researcher_output,
|
| 2092 |
book_knowledge=book_knowledge,
|
| 2093 |
ads_knowledge=ads_knowledge,
|
services/image.py
CHANGED
|
@@ -15,7 +15,8 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
| 15 |
|
| 16 |
import replicate
|
| 17 |
import httpx
|
| 18 |
-
from openai import
|
|
|
|
| 19 |
from config import settings
|
| 20 |
|
| 21 |
|
|
@@ -141,7 +142,7 @@ class ImageService:
|
|
| 141 |
# Initialize OpenAI client for gpt-image-1.5 support
|
| 142 |
self.openai_client = None
|
| 143 |
if hasattr(settings, 'openai_api_key') and settings.openai_api_key:
|
| 144 |
-
self.openai_client =
|
| 145 |
|
| 146 |
def _fetch_image(self, url: str) -> Optional[bytes]:
|
| 147 |
"""Fetch image from URL with retry logic."""
|
|
@@ -161,7 +162,7 @@ class ImageService:
|
|
| 161 |
if attempt == RETRY_ATTEMPTS - 1:
|
| 162 |
print(f"Failed to fetch image from {url}: {e}")
|
| 163 |
return None
|
| 164 |
-
time.sleep(1)
|
| 165 |
return None
|
| 166 |
|
| 167 |
async def load_image(
|
|
@@ -294,15 +295,19 @@ class ImageService:
|
|
| 294 |
print("Generating image with gpt-image-1.5")
|
| 295 |
size_str = f"{width}x{height}"
|
| 296 |
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
)
|
| 307 |
|
| 308 |
if result.data and len(result.data) > 0:
|
|
@@ -313,6 +318,9 @@ class ImageService:
|
|
| 313 |
return image_bytes, "gpt-image-1.5", None
|
| 314 |
|
| 315 |
raise Exception("No image data returned from OpenAI API")
|
|
|
|
|
|
|
|
|
|
| 316 |
|
| 317 |
except Exception as e:
|
| 318 |
print(f"OpenAI image generation failed: {e}")
|
|
@@ -358,8 +366,16 @@ class ImageService:
|
|
| 358 |
try:
|
| 359 |
print(f"Generating image with {current_model} (attempt {attempt + 1})")
|
| 360 |
|
| 361 |
-
# Use official Replicate client
|
| 362 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
|
| 364 |
# Extract image bytes and URL
|
| 365 |
image_bytes, image_url = self._extract_image_from_output(output)
|
|
@@ -372,7 +388,7 @@ class ImageService:
|
|
| 372 |
last_error = e
|
| 373 |
if attempt < RETRY_ATTEMPTS - 1:
|
| 374 |
print(f"Attempt {attempt + 1} failed: {e}, retrying...")
|
| 375 |
-
|
| 376 |
continue
|
| 377 |
|
| 378 |
# Model failed, try next in fallback chain
|
|
@@ -409,7 +425,6 @@ class ImageService:
|
|
| 409 |
except Exception as e:
|
| 410 |
last_error = e
|
| 411 |
if attempt < max_retries:
|
| 412 |
-
import asyncio
|
| 413 |
await asyncio.sleep(2)
|
| 414 |
continue
|
| 415 |
|
|
|
|
| 15 |
|
| 16 |
import replicate
|
| 17 |
import httpx
|
| 18 |
+
from openai import AsyncOpenAI
|
| 19 |
+
import asyncio
|
| 20 |
from config import settings
|
| 21 |
|
| 22 |
|
|
|
|
| 142 |
# Initialize OpenAI client for gpt-image-1.5 support
|
| 143 |
self.openai_client = None
|
| 144 |
if hasattr(settings, 'openai_api_key') and settings.openai_api_key:
|
| 145 |
+
self.openai_client = AsyncOpenAI(api_key=settings.openai_api_key)
|
| 146 |
|
| 147 |
def _fetch_image(self, url: str) -> Optional[bytes]:
|
| 148 |
"""Fetch image from URL with retry logic."""
|
|
|
|
| 162 |
if attempt == RETRY_ATTEMPTS - 1:
|
| 163 |
print(f"Failed to fetch image from {url}: {e}")
|
| 164 |
return None
|
| 165 |
+
time.sleep(1) # Sync fetch so sync sleep is fine here or use asyncio.sleep
|
| 166 |
return None
|
| 167 |
|
| 168 |
async def load_image(
|
|
|
|
| 295 |
print("Generating image with gpt-image-1.5")
|
| 296 |
size_str = f"{width}x{height}"
|
| 297 |
|
| 298 |
+
# Use a timeout for OpenAI image generation
|
| 299 |
+
result = await asyncio.wait_for(
|
| 300 |
+
self.openai_client.images.generate(
|
| 301 |
+
model="gpt-image-1.5",
|
| 302 |
+
prompt=prompt,
|
| 303 |
+
quality="auto",
|
| 304 |
+
background="auto",
|
| 305 |
+
moderation="auto",
|
| 306 |
+
size=size_str,
|
| 307 |
+
output_format="jpeg",
|
| 308 |
+
output_compression=90,
|
| 309 |
+
),
|
| 310 |
+
timeout=120.0
|
| 311 |
)
|
| 312 |
|
| 313 |
if result.data and len(result.data) > 0:
|
|
|
|
| 318 |
return image_bytes, "gpt-image-1.5", None
|
| 319 |
|
| 320 |
raise Exception("No image data returned from OpenAI API")
|
| 321 |
+
except asyncio.TimeoutError:
|
| 322 |
+
print("Timed out generating image with gpt-image-1.5")
|
| 323 |
+
raise Exception("Timeout: Image generation with gpt-image-1.5 took too long")
|
| 324 |
|
| 325 |
except Exception as e:
|
| 326 |
print(f"OpenAI image generation failed: {e}")
|
|
|
|
| 366 |
try:
|
| 367 |
print(f"Generating image with {current_model} (attempt {attempt + 1})")
|
| 368 |
|
| 369 |
+
# Use official Replicate client - offload blocking call to thread and add timeout
|
| 370 |
+
try:
|
| 371 |
+
# Wrap the blocking call in a thread and add a 3-minute timeout
|
| 372 |
+
output = await asyncio.wait_for(
|
| 373 |
+
asyncio.to_thread(self.client.run, cfg["id"], input=input_data),
|
| 374 |
+
timeout=180.0 # 3 minutes timeout
|
| 375 |
+
)
|
| 376 |
+
except asyncio.TimeoutError:
|
| 377 |
+
print(f"Timed out generating image with {current_model} after 180 seconds")
|
| 378 |
+
raise Exception(f"Timeout: Image generation with {current_model} took too long")
|
| 379 |
|
| 380 |
# Extract image bytes and URL
|
| 381 |
image_bytes, image_url = self._extract_image_from_output(output)
|
|
|
|
| 388 |
last_error = e
|
| 389 |
if attempt < RETRY_ATTEMPTS - 1:
|
| 390 |
print(f"Attempt {attempt + 1} failed: {e}, retrying...")
|
| 391 |
+
await asyncio.sleep(2 ** attempt) # Exponential backoff (use await for async sleep)
|
| 392 |
continue
|
| 393 |
|
| 394 |
# Model failed, try next in fallback chain
|
|
|
|
| 425 |
except Exception as e:
|
| 426 |
last_error = e
|
| 427 |
if attempt < max_retries:
|
|
|
|
| 428 |
await asyncio.sleep(2)
|
| 429 |
continue
|
| 430 |
|