File size: 35,990 Bytes
026f283 f201243 026f283 f201243 2c9d10c f201243 2c9d10c f201243 2c9d10c f201243 2c9d10c f201243 026f283 8ffe335 026f283 8ffe335 f201243 8ffe335 f201243 8ffe335 f201243 026f283 f201243 8ffe335 f201243 8ffe335 026f283 f201243 2c9d10c f201243 2c9d10c f201243 026f283 f201243 2c9d10c f201243 2c9d10c f201243 026f283 f201243 2c9d10c d4a4da7 026f283 f201243 026f283 f201243 2c9d10c f201243 026f283 f201243 026f283 f201243 026f283 f201243 026f283 f201243 8ffe335 026f283 2c9d10c 8ffe335 d4a4da7 f201243 d4a4da7 f201243 026f283 f201243 2c9d10c 9474b94 2c9d10c 026f283 533051b f201243 026f283 f201243 26e8ad9 f201243 26e8ad9 2c9d10c 26e8ad9 8ffe335 2c9d10c 8ffe335 f201243 d4a4da7 f201243 026f283 f201243 2c9d10c d4a4da7 f201243 026f283 f201243 026f283 f201243 1afa519 f201243 1afa519 2c9d10c 1afa519 f201243 9474b94 2c9d10c f201243 2c9d10c 8ffe335 f201243 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 |
"""Extensive ad generation: researcher → creative director → designer → copywriter (OpenAI + file search).
Supports optional Creative Inventor to generate new angles, concepts, visuals, and triggers by itself."""
import os
import sys
import time
from typing import List, Optional
from pydantic import BaseModel
# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from openai import OpenAI
from config import settings
# Pydantic models for structured outputs
class ImageAdEssentials(BaseModel):
phsychologyTriggers: str
angles: list[str]
concepts: list[str]
class ImageAdEssentialsOutput(BaseModel):
output: list[ImageAdEssentials]
class Text(BaseModel):
textToBeWrittern: str
color: str
placement: str
class CreativeStrategies(BaseModel):
phsychologyTrigger: str
angle: str
concept: str
text: Text
cta: str
visualDirection: str
titleIdeas: str
captionIdeas: str
bodyIdeas: str
class CreativeStrategiesOutput(BaseModel):
output: list[CreativeStrategies]
class AdImagePrompt(BaseModel):
prompt: str
class CopyWriterOutput(BaseModel):
title: str
body: str
description: str
class ThirdFlowService:
"""Extensive ad generation (researcher → creative director → designer → copywriter)."""
def __init__(self):
"""Set up OpenAI client and vector store IDs."""
self.client = OpenAI(api_key=settings.openai_api_key)
self.search_vector_store_id = "vs_691afcc4f8688191b01487b4a8439607"
self.ads_vector_store_id = "vs_69609db487048191a1e6b7ba0997ee39"
self.gpt_model = getattr(settings, 'third_flow_model', 'gpt-4o')
def researcher(
self,
target_audience: str,
offer: str,
niche: str = ""
) -> List[ImageAdEssentials]:
"""Return psychology triggers, angles, and concepts for niche/offer/audience."""
messages = [
{
"role": "system",
"content": [
{
"type": "text",
"text": """You are the researcher with 20 years of experience for the affiliate marketing company which does research on trending angles, concepts and psychology triggers based on the user input.
Affiliate marketing is a performance-based model where you promote someone else's product or service and earn a commission for each qualified action (click, lead, or sale).
A psychology trigger is an emotional or cognitive stimulus that pushes someone toward action—clicking, signing up, or buying—before logic kicks in.
An ad angle is the reason someone should care right now. Same product → different reasons to click → different angles.
An ad concept is the creative execution style or storyline you use to deliver an angle.
In affiliate marketing 'Low-production, realistic often outperform studio creatives' runs most.
Invent psychology triggers, angles, and concepts without limiting to the given niche. Suggest diverse and hyperrealistic audiences including outside the niche. Prioritize novelty and variety. Provide different angles and concepts we can try based on psychology triggers for the image ads; use the user input as a springboard, not a constraint.
User will provide you the category on which he needs to run the ads, what is the offer he is providing and what is target audience."""
}
]
},
{
"role": "user",
"content": [
{
"type": "text",
"text": f"""Following are the inputs:
Niche: {niche}
Offer to run: {offer}
Target Audience: {target_audience}
Provide the different psychology triggers, angles and concept based on the given input."""
}
]
}
]
try:
completion = self.client.beta.chat.completions.parse(
model=self.gpt_model,
messages=messages,
response_format=ImageAdEssentialsOutput,
)
response = completion.choices[0].message
if response.parsed:
return response.parsed.output
else:
print(f"Warning: Researcher refusal: {response.refusal}")
# Return empty list if refused
return []
except Exception as e:
print(f"Error in researcher: {e}")
return []
def get_essentials_via_inventor(
self,
niche: str,
offer: str,
n: int = 5,
*,
target_audience_hint: Optional[str] = None,
existing_reference: Optional[str] = None,
trend_context: Optional[str] = None,
competitor_insights: Optional[str] = None,
) -> tuple[List[ImageAdEssentials], List[str]]:
"""
Use the Creative Inventor to generate new angles, concepts, visuals,
psychological triggers, and hyper-specific target audiences.
Returns (essentials for creative_director, list of target_audience per essential).
"""
try:
from services.creative_inventor import creative_inventor_service
except ImportError:
from creative_inventor import creative_inventor_service # noqa: F401
invented = creative_inventor_service.invent(
niche=niche,
offer=offer,
n=n,
target_audience_hint=target_audience_hint,
existing_reference=existing_reference,
trend_context=trend_context,
competitor_insights=competitor_insights,
)
essentials = self._invented_to_essentials(invented)
target_audiences = [getattr(e, "target_audience", "") or f"Audience {i+1}" for i, e in enumerate(invented)]
return (essentials, target_audiences)
def _invented_to_essentials(self, invented: list) -> List[ImageAdEssentials]:
"""Convert InventedEssential list to ImageAdEssentials for creative_director."""
out: List[ImageAdEssentials] = []
for e in invented:
# Fold visual_directions into concepts so creative_director gets visual hints
concepts = list(getattr(e, "concepts", [])) + list(getattr(e, "visual_directions", []))
out.append(ImageAdEssentials(
phsychologyTriggers=getattr(e, "psychology_trigger", ""),
angles=list(getattr(e, "angles", [])),
concepts=concepts,
))
return out
def retrieve_search(
self,
target_audience: str,
offer: str,
niche: str = ""
) -> str:
"""Retrieve marketing knowledge from vector store (file search)."""
try:
# Method 1: Try using responses.create
if hasattr(self.client, 'responses') and hasattr(self.client.responses, 'create'):
try:
search = self.client.responses.create(
model="gpt-4o",
input=f"Find {niche} creative strategies relevant to ad images related to target audience: {target_audience} and offer: {offer}. The image ads are associated with performance marketing and affiliate marketing ads. If there is nothing related to this category then take reference from everything and make strategies. Make sure you go through each and every document present and from each file you should give results.",
tools=[
{
"type": "file_search",
"vector_store_ids": [self.search_vector_store_id]
}
]
)
result = search.output[1].content[0].text
print("✓ Used responses.create API for search retrieval")
return result
except Exception as custom_api_error:
error_str = str(custom_api_error)
print(f"⚠️ responses.create API error: {error_str[:100]}...")
raise Exception(f"Failed to retrieve search knowledge via responses.create: {error_str}") from custom_api_error
# Method 2: Try using Assistants API (official OpenAI method)
query = f"Find {niche} creative strategies relevant to ad images related to target audience: {target_audience} and offer: {offer}. The image ads are associated with performance marketing and affiliate marketing ads. If there is nothing related to this category then take reference from everything and make strategies. Make sure you go through each and every document present and from each file you should give results."
try:
# Create assistant with vector store
assistant = self.client.beta.assistants.create(
name="Marketing Knowledge Assistant",
instructions="You are a marketing research assistant with 20 years of experience. Search through the provided documents and extract relevant creative strategies and knowledge.",
model="gpt-4o",
tools=[{"type": "file_search"}],
tool_resources={
"file_search": {
"vector_store_ids": [self.search_vector_store_id]
}
}
)
# Create a thread and run
thread = self.client.beta.threads.create()
message = self.client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content=query
)
# Run the assistant
run = self.client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id
)
# Wait for completion
import time
while run.status in ['queued', 'in_progress']:
time.sleep(1)
run = self.client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id
)
if run.status == 'completed':
# Get the messages
messages = self.client.beta.threads.messages.list(
thread_id=thread.id
)
# Get the assistant's response
for msg in messages.data:
if msg.role == 'assistant':
if msg.content[0].type == 'text':
result = msg.content[0].text.value
# Clean up
self.client.beta.assistants.delete(assistant.id)
return result
# Clean up
self.client.beta.assistants.delete(assistant.id)
except Exception as api_error:
error_str = str(api_error)
print(f"⚠️ Assistants API error: {error_str[:100]}...")
raise Exception(f"Failed to retrieve search knowledge: {error_str}") from api_error
# If we reach here, both methods failed
raise Exception("Failed to retrieve search knowledge: Both API methods failed")
except Exception as e:
print(f"Error in retrieve_search: {e}")
raise
def retrieve_ads(
self,
target_audience: str,
offer: str,
niche: str = ""
) -> str:
"""Retrieve ads knowledge from vector store (file search)."""
try:
# Method 1: Try using responses.create
if hasattr(self.client, 'responses') and hasattr(self.client.responses, 'create'):
try:
search = self.client.responses.create(
model="gpt-4o",
input=f"Find {niche} creative ad ideas relevant to ad images related to target audience: {target_audience} and offer: {offer}. The image ads are associated with performance marketing and affiliate marketing ads.",
tools=[
{
"type": "file_search",
"vector_store_ids": [self.ads_vector_store_id]
}
]
)
result = search.output[1].content[0].text
print("✓ Used responses.create API for ads retrieval")
return result
except Exception as custom_api_error:
error_str = str(custom_api_error)
print(f"⚠️ responses.create API error: {error_str[:100]}...")
raise Exception(f"Failed to retrieve search knowledge via responses.create: {error_str}") from custom_api_error
# Method 2: Try using Assistants API (official OpenAI method)
query = f"Find {niche} creative ad ideas relevant to ad images related to target audience: {target_audience} and offer: {offer}. The image ads are associated with performance marketing and affiliate marketing ads."
try:
# Create assistant with vector store
assistant = self.client.beta.assistants.create(
name="Ads Knowledge Assistant",
instructions="You are a marketing research assistant with 20 years of experience. Search through the provided ad examples and extract relevant creative ideas and patterns.",
model="gpt-4o",
tools=[{"type": "file_search"}],
tool_resources={
"file_search": {
"vector_store_ids": [self.ads_vector_store_id]
}
}
)
# Create a thread and run
thread = self.client.beta.threads.create()
message = self.client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content=query
)
# Run the assistant
run = self.client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id
)
# Wait for completion
import time
while run.status in ['queued', 'in_progress']:
time.sleep(1)
run = self.client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id
)
if run.status == 'completed':
# Get the messages
messages = self.client.beta.threads.messages.list(
thread_id=thread.id
)
# Get the assistant's response
for msg in messages.data:
if msg.role == 'assistant':
if msg.content[0].type == 'text':
result = msg.content[0].text.value
# Clean up
self.client.beta.assistants.delete(assistant.id)
return result
# Clean up
self.client.beta.assistants.delete(assistant.id)
except Exception as api_error:
error_str = str(api_error)
print(f"⚠️ Assistants API error: {error_str[:100]}...")
raise Exception(f"Failed to retrieve ads knowledge: {error_str}") from api_error
# If we reach here, both methods failed
raise Exception("Failed to retrieve ads knowledge: Both API methods failed")
except Exception as e:
print(f"Error in retrieve_ads: {e}")
raise
def creative_director(
self,
researcher_output: List[ImageAdEssentials],
book_knowledge: str,
ads_knowledge: str,
target_audience: str,
offer: str,
niche: str = "",
n: int = 5,
target_audiences: Optional[List[str]] = None,
) -> List[CreativeStrategies]:
"""Create creative strategies from research, book knowledge, and ads knowledge.
When target_audiences is provided (one per strategy), each strategy is tailored to that hyper-specific audience."""
# Convert researcher_output to string for prompt
researcher_str = "\n".join([
f"Psychology Triggers: {item.phsychologyTriggers}\n"
f"Angles: {', '.join(item.angles)}\n"
f"Concepts: {', '.join(item.concepts)}"
for item in researcher_output
])
messages = [
{
"role": "system",
"content": [
{
"type": "text",
"text": f"""You are the Creative Director with 20 years of experience for the affiliate marketing company which make creative strategies for the image ads on the basis of the research given and user's input.
The research work includes the psychology triggers, angles and different concepts. Your work is to finalise the {n} strategies based on the research.
There will also be researched content from the different marketing books. Along with these there will information about the old ads information which are winner.
Make the strongest patterns for the image ads, which should include about what types of visual should be their, colors, what should be the text, what should be the tone of the text with it's placement and CTA.
Strategies can target any audience and use any visual or concept—research is a springboard, not a constraint.
When the user provides per-strategy target audiences, you MUST tailor each strategy (angle, concept, visual, title, body) to that specific audience.
Along with this provide the title ideas and description/caption which should be added with the image ad. It will complete the full ad copy.
If the image should include only visuals then text field must return None or NA.
What information you should give make sure you give in brief and well defined.
Affiliate marketing is a performance-based model where you promote someone else's product or service and earn a commission for each qualified action (click, lead, or sale).
In affiliate marketing 'Low-production, realistic images often outperform studio creatives' runs most.
Role of the Title: Stop the scroll and trigger emotion.
Role of Body: The body is the main paragraph text which should Explain just enough, Reduce anxiety, and Push to the next step.
Role of Description: Reduce friction and justify the click.
Keeping in mind all this, make sure you provide different creative strategies for the image ads for the given input based on affiliate marketing.
User will provide you the category on which he needs to run the ads, what is the offer he is providing and what is target audience, along with the research."""
}
]
},
{
"role": "user",
"content": [
{
"type": "text",
"text": self._creative_director_user_message(
researcher_str, book_knowledge, ads_knowledge,
niche, offer, target_audience, n, target_audiences,
)
}
]
}
]
try:
completion = self.client.beta.chat.completions.parse(
model=self.gpt_model,
messages=messages,
response_format=CreativeStrategiesOutput,
)
response = completion.choices[0].message
if response.parsed:
return response.parsed.output
else:
print(f"Warning: Creative director refusal: {response.refusal}")
return []
except Exception as e:
print(f"Error in creative_director: {e}")
return []
def _creative_director_user_message(
self,
researcher_str: str,
book_knowledge: str,
ads_knowledge: str,
niche: str,
offer: str,
target_audience: str,
n: int,
target_audiences: Optional[List[str]] = None,
) -> str:
"""Build user message for creative_director; inject per-strategy audiences when provided."""
audience_block = ""
if target_audiences and len(target_audiences) >= n:
per = "\n".join([f"Strategy {i+1} must target: {aud}" for i, aud in enumerate(target_audiences[:n])])
audience_block = f"\n\nCRITICAL - Each strategy must speak to this hyper-specific audience:\n{per}\n\nTailor angle, concept, visual, title, and body to that audience."
return f"""Following are the inputs:
Researched Content: {researcher_str}
Researched Content from marketing books: {book_knowledge}
Old Ads Data: {ads_knowledge}
Niche: {niche}
Offer to run: {offer}
Target Audience (overall): {target_audience}
{audience_block}
Provide the different creative strategies based on the given input."""
def creative_designer(self, creative_strategy: CreativeStrategies, niche: str = "") -> str:
"""Generate image prompt from a creative strategy."""
niche_lower = niche.lower().replace(" ", "_").replace("-", "_") if niche else ""
niche_guidance = f"\nThe image must be appropriate for the {niche} niche." if niche else ""
text_overlay = creative_strategy.text.textToBeWrittern if creative_strategy.text and creative_strategy.text.textToBeWrittern not in (None, "None", "NA", "") else "No text overlay"
strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
Angle: {creative_strategy.angle}
Concept: {creative_strategy.concept}
Text: {text_overlay}
CTA: {creative_strategy.cta}
Visual Direction: {creative_strategy.visualDirection}
"""
messages = [
{
"role": "system",
"content": [
{
"type": "text",
"text": f"""You are the Creative Designer with 20 years of experience for the affiliate marketing company which makes the prompt from creative strategy given for the ad images in the affiliate marketing.
Nano Banana image model will be used to generate the images
Affiliate marketing is a performance-based model where you promote someone else's product or service and earn a commission for each qualified action (click, lead, or sale).
In affiliate marketing 'Low-production, realistic images often outperform studio creatives' runs most.
If the image looks like it belongs on a stock website, it has failed.
The psychology trigger and angle from the strategy are the emotional driver. Structure the prompt so the image directly conveys that emotional truth—the scene, expressions, props, and composition must all serve it. A generic scene is not enough; the strategy must differentiate this creative.
For image model here's structure for the prompt: [The Hook - emotion from psychology trigger/angle] + [The Subject] + [The Context/Setting] + [The Technical Polish]
{niche_guidance}
CRITICAL - TEXT IN IMAGE: The strategy includes "Text" (title/caption to show). Your prompt MUST describe where and how this text appears in the image as visible, readable copy—e.g. on a sign, document, phone screen, poster, note, or surface in the scene. The image must contain that exact text (or the main phrase) so viewers can read it. Do not omit text from the prompt.
CRITICAL: If the image includes people or faces, ensure they look like real, original people with:
- Photorealistic faces with natural skin texture, visible pores, and realistic skin imperfections
- Natural facial asymmetry (no perfectly symmetrical faces)
- Unique, individual facial features (not generic or model-like)
- Natural expressions with authentic micro-expressions
- Realistic skin tones with natural variations
- Faces that look like real photographs of real people, NOT AI-generated portraits
- Avoid any faces that look synthetic, fake, or obviously computer-generated
- Avoid overly smooth or plastic-looking skin
- Avoid perfectly symmetrical faces or generic model-like features"""
}
]
},
{
"role": "user",
"content": [
{
"type": "text",
"text": f"""Following is the creative strategy:
{strategy_str}
Provide the image prompt. The prompt MUST lead with the strategy's emotional hook (psychology trigger and angle)—describe a scene that makes that feeling unmistakable. Then add subject, setting, and technical polish. You MUST include in the prompt a clear description of where the Text from the strategy appears in the scene (e.g. "with the text '[exact phrase]' visible on a sign/phone/document") so the generated image contains readable copy."""
}
]
}
]
try:
completion = self.client.beta.chat.completions.parse(
model=self.gpt_model,
messages=messages,
response_format=AdImagePrompt,
)
response = completion.choices[0].message
if response.parsed:
# Refine the prompt for affiliate marketing
raw_prompt = response.parsed.prompt
refined_prompt = self._refine_prompt_for_affiliate(raw_prompt, niche_lower)
return refined_prompt
else:
print(f"Warning: Creative designer refusal: {response.refusal}")
return ""
except Exception as e:
print(f"Error in creative_designer: {e}")
return ""
def _refine_prompt_for_affiliate(self, prompt: str, niche: str) -> str:
"""Refine prompt for affiliate creatives: fix stock/corporate wording, ensure authenticity."""
import re
if not prompt:
return prompt
prompt_lower = prompt.lower()
# =================================================================
# REMOVE STOCK PHOTO / CORPORATE AESTHETICS
# =================================================================
stock_replacements = [
(r'\bstock photo\b', 'authentic photo'),
(r'\bprofessional studio shot\b', 'natural candid shot'),
(r'\bcorporate headshot\b', 'casual portrait'),
(r'\bgeneric model\b', 'real person'),
(r'\bperfect lighting\b', 'natural lighting'),
(r'\bshutterstock\b', 'authentic'),
(r'\bistock\b', 'documentary style'),
]
for pattern, replacement in stock_replacements:
prompt = re.sub(pattern, replacement, prompt, flags=re.IGNORECASE)
# =================================================================
# FIX UNREALISTIC BODY DESCRIPTIONS
# =================================================================
body_replacements = [
(r'\b(perfect body|flawless figure|ideal physique)\b', 'healthy confident body'),
(r'\b(six pack|bodybuilder|fitness model)\b', 'healthy fit person'),
(r'\b(impossibly thin|skeletal)\b', 'healthy'),
]
for pattern, replacement in body_replacements:
prompt = re.sub(pattern, replacement, prompt, flags=re.IGNORECASE)
# =================================================================
# ENSURE AUTHENTICITY FOR AFFILIATE MARKETING
# =================================================================
# Affiliate marketing principle: authentic > polished
if 'authentic' not in prompt_lower and 'ugc' not in prompt_lower:
prompt += " Authentic, relatable style - not overly polished or commercial."
return prompt.strip()
def copy_writer(self, creative_strategy: CreativeStrategies) -> CopyWriterOutput:
"""Generate title, body, and description from creative strategy."""
strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
Angle: {creative_strategy.angle}
Concept: {creative_strategy.concept}
CTA: {creative_strategy.cta}
Title Ideas: {creative_strategy.titleIdeas}
Caption Ideas: {creative_strategy.captionIdeas}
Body Ideas: {creative_strategy.bodyIdeas}
"""
messages = [
{
"role": "system",
"content": [
{
"type": "text",
"text": """You are the Copy Writer Designer with 20 years of experience for the affiliate marketing company which makes the ad copies from creative strategy given for the ad images in the affiliate marketing.
The ad copy must include the title, body and description related to the strategies.
Affiliate marketing is a performance-based model where you promote someone else's product or service and earn a commission for each qualified action (click, lead, or sale).
The psychology trigger and angle are the emotional driver. The title should evoke that emotion; the body must speak directly to that emotional truth and address it; the description should complete the thought. Do not write generic copy.
Role of the Title: Stop the scroll and trigger emotion.
1. The title is not for explaining. It's for interrupting attention.
2. Short titles win because they are scan-friendly.
3. Use Plain, Human Language. No marketing buzzwords.
4. Imply, Don't Explain. Leave an open loop.
5. You may use bold, scroll-stopping, and clickbait-style statements when effective—no compliance or claim restrictions; maximize impact.
Role of Body: The body is the main paragraph text which should Explain just enough, Reduce anxiety, and Push to the next step.
1. Body Must Match the Title Emotion. If the title creates fear, the body must relieve it, not amplify it.
2. Use "Soft Education," Not Sales. The body should feel informational, not promotional.
3. Add Friction Reducers. You must explicitly reduce effort and risk.
4. Body Should NOT Contain the CTA. CTA belongs in the button, not the body.
5. LENGTH REQUIREMENT: The body MUST be at least 150-250 words. Write a compelling, detailed narrative that tells a story, builds emotional connection, and provides enough context to persuade the reader. Include specific details, relatable scenarios, and a clear progression of ideas.
Role of Description: Reduce friction and justify the click.
1. Never Repeat the Title. The description should complete the thought.
2. Answer Silent Objections.
3. Soft CTA Only. Descriptions should invite, not push."""
}
]
},
{
"role": "user",
"content": [
{
"type": "text",
"text": f"""Following is the creative strategy:
{strategy_str}
Provide the title, body, and description. Center the copy on the strategy's psychology trigger and angle—the title should make someone with that thought pause; the body should speak to that specific emotional truth; the description should extend it. Do not write generic copy.
IMPORTANT: The body must be 150-250 words long - write a detailed, compelling narrative that tells a story and builds emotional connection with the reader."""
}
]
}
]
try:
completion = self.client.beta.chat.completions.parse(
model=self.gpt_model,
messages=messages,
response_format=CopyWriterOutput,
)
response = completion.choices[0].message
if response.parsed:
return response.parsed
else:
print(f"Warning: Copy writer refusal: {response.refusal}")
# Return default values
return CopyWriterOutput(
title="",
body="",
description=""
)
except Exception as e:
print(f"Error in copy_writer: {e}")
return CopyWriterOutput(
title="",
body="",
description=""
)
def process_strategy(
self,
creative_strategy: CreativeStrategies,
niche: str = "",
) -> tuple[str, str, str, str]:
"""Run designer + copywriter on one strategy; return (prompt, title, body, description)."""
prompt = self.creative_designer(creative_strategy, niche=niche)
ad_copy = self.copy_writer(creative_strategy)
return (
prompt,
ad_copy.title,
ad_copy.body,
ad_copy.description
)
# Global service instance
third_flow_service = ThirdFlowService()
|