Spaces:
Running
Running
""" | |
Creative Generator using OpenAI | |
Analyzes existing ad performance and generates new ad creatives | |
""" | |
import os | |
import logging | |
from typing import List, Dict, Any, Optional | |
import pandas as pd | |
from openai import OpenAI | |
import json | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
class CreativeGenerator: | |
"""Generates new ad creatives using OpenAI based on existing ad performance data.""" | |
def __init__(self, api_key=None): | |
"""Initialize the OpenAI client.""" | |
# Use provided API key or fall back to environment variable | |
openai_key = api_key or os.getenv('OPENAI_API_KEY') | |
if not openai_key: | |
raise ValueError("OpenAI API key is required either as parameter or OPENAI_API_KEY environment variable") | |
self.client = OpenAI(api_key=openai_key) | |
def analyze_top_performing_ads(self, ads_df: pd.DataFrame, performance_df: pd.DataFrame, | |
top_n: int = 10) -> Dict[str, Any]: | |
""" | |
Analyze top performing ads to identify successful patterns. | |
Args: | |
ads_df: DataFrame with ad creative data | |
performance_df: DataFrame with performance metrics | |
top_n: Number of top performing ads to analyze | |
Returns: | |
Analysis summary with patterns and insights | |
""" | |
try: | |
# Merge ads with performance data | |
merged_df = pd.merge(ads_df, performance_df, on='ad_id', how='inner') | |
if merged_df.empty: | |
logger.warning("No matching ads found between creative and performance data") | |
return {} | |
# Sort by CTR and conversion rate | |
merged_df['performance_score'] = ( | |
merged_df['ctr'] * 0.4 + | |
merged_df['conversion_rate'] * 0.6 | |
) | |
top_ads = merged_df.nlargest(top_n, 'performance_score') | |
# Extract creative elements for analysis | |
creative_elements = [] | |
for _, ad in top_ads.iterrows(): | |
if ad['ad_type'] == 'RESPONSIVE_SEARCH_AD': | |
headlines = ad['headlines'].split('|') if pd.notna(ad['headlines']) else [] | |
descriptions = ad['descriptions'].split('|') if pd.notna(ad['descriptions']) else [] | |
creative_elements.append({ | |
'headlines': headlines, | |
'descriptions': descriptions, | |
'ctr': ad['ctr'], | |
'conversion_rate': ad['conversion_rate'], | |
'campaign_name': ad['campaign_name_x'] | |
}) | |
elif ad['ad_type'] == 'EXPANDED_TEXT_AD': | |
headlines = [ad['headline1'], ad['headline2'], ad['headline3']] | |
descriptions = [ad['description1'], ad['description2']] | |
creative_elements.append({ | |
'headlines': [h for h in headlines if pd.notna(h)], | |
'descriptions': [d for d in descriptions if pd.notna(d)], | |
'ctr': ad['ctr'], | |
'conversion_rate': ad['conversion_rate'], | |
'campaign_name': ad['campaign_name_x'] | |
}) | |
analysis = { | |
'top_performing_ads': creative_elements, | |
'avg_ctr': top_ads['ctr'].mean(), | |
'avg_conversion_rate': top_ads['conversion_rate'].mean(), | |
'total_analyzed': len(top_ads) | |
} | |
logger.info(f"Analyzed {len(top_ads)} top performing ads") | |
return analysis | |
except Exception as e: | |
logger.error(f"Error analyzing top performing ads: {e}") | |
raise | |
def generate_new_creatives(self, analysis_data: Dict[str, Any], | |
business_context: str, num_variations: int = 5) -> List[Dict[str, Any]]: | |
""" | |
Generate new ad creatives based on successful patterns. | |
Args: | |
analysis_data: Analysis of top performing ads | |
business_context: Description of the business/product | |
num_variations: Number of creative variations to generate | |
Returns: | |
List of generated ad creatives | |
""" | |
try: | |
if not analysis_data or not analysis_data.get('top_performing_ads'): | |
logger.warning("No analysis data provided, generating generic creatives") | |
return self._generate_generic_creatives(business_context, num_variations) | |
# Prepare context for OpenAI | |
successful_patterns = self._extract_patterns(analysis_data['top_performing_ads']) | |
prompt = f""" | |
Based on the following successful Google Ads patterns and business context, generate {num_variations} new responsive search ad variations. | |
BUSINESS CONTEXT: | |
{business_context} | |
SUCCESSFUL PATTERNS FROM TOP PERFORMING ADS: | |
Average CTR: {analysis_data.get('avg_ctr', 0):.2%} | |
Average Conversion Rate: {analysis_data.get('avg_conversion_rate', 0):.2%} | |
SUCCESSFUL HEADLINES PATTERNS: | |
{chr(10).join(successful_patterns['headline_patterns'])} | |
SUCCESSFUL DESCRIPTIONS PATTERNS: | |
{chr(10).join(successful_patterns['description_patterns'])} | |
INSTRUCTIONS: | |
1. Create responsive search ads with 10-15 headlines and 4 descriptions each | |
2. Headlines should be under 30 characters | |
3. Descriptions should be under 90 characters | |
4. Incorporate successful patterns while staying relevant to the business context | |
5. Use compelling calls-to-action | |
6. Focus on value propositions that have proven successful | |
7. Vary the messaging approach across variations | |
Please return your response as a valid JSON array of objects, each with: | |
- "headlines": array of 10-15 headline strings | |
- "descriptions": array of 4 description strings | |
- "theme": brief description of the creative theme | |
- "target_audience": suggested target audience for this variation | |
Return only the JSON response, no additional text. | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[ | |
{"role": "system", "content": "You are an expert Google Ads copywriter who creates high-converting ad creatives based on performance data analysis."}, | |
{"role": "user", "content": prompt} | |
], | |
response_format={"type": "json_object"} | |
) | |
# Parse the JSON response | |
generated_creatives = json.loads(response.choices[0].message.content) | |
logger.info(f"Generated {len(generated_creatives)} new ad creative variations") | |
return generated_creatives | |
except json.JSONDecodeError as e: | |
logger.error(f"Failed to parse OpenAI response as JSON: {e}") | |
return self._generate_fallback_creatives(business_context, num_variations) | |
except Exception as e: | |
logger.error(f"Error generating new creatives: {e}") | |
return self._generate_fallback_creatives(business_context, num_variations) | |
def _extract_patterns(self, top_ads: List[Dict[str, Any]]) -> Dict[str, List[str]]: | |
"""Extract successful patterns from top performing ads.""" | |
headline_patterns = [] | |
description_patterns = [] | |
for ad in top_ads: | |
headlines = ad.get('headlines', []) | |
descriptions = ad.get('descriptions', []) | |
headline_patterns.extend(headlines) | |
description_patterns.extend(descriptions) | |
# Remove duplicates and empty strings | |
headline_patterns = list(set([h for h in headline_patterns if h and h.strip()])) | |
description_patterns = list(set([d for d in description_patterns if d and d.strip()])) | |
return { | |
'headline_patterns': headline_patterns[:20], # Limit to top 20 | |
'description_patterns': description_patterns[:15] # Limit to top 15 | |
} | |
def _generate_generic_creatives(self, business_context: str, num_variations: int) -> List[Dict[str, Any]]: | |
"""Generate generic creatives when no analysis data is available.""" | |
prompt = f""" | |
Create {num_variations} Google Ads responsive search ad variations for the following business: | |
BUSINESS CONTEXT: | |
{business_context} | |
INSTRUCTIONS: | |
1. Create responsive search ads with 10-15 headlines and 4 descriptions each | |
2. Headlines should be under 30 characters | |
3. Descriptions should be under 90 characters | |
4. Use compelling calls-to-action | |
5. Focus on key value propositions | |
6. Vary the messaging approach across variations | |
Please return your response as a valid JSON array with objects containing: | |
- "headlines": array of 10-15 headline strings | |
- "descriptions": array of 4 description strings | |
- "theme": brief description of the creative theme | |
- "target_audience": suggested target audience for this variation | |
Return only the JSON response, no additional text. | |
""" | |
try: | |
response = self.client.chat.completions.create( | |
model="gpt-4-turbo-preview", | |
messages=[ | |
{"role": "system", "content": "You are an expert Google Ads copywriter who creates high-converting ad creatives."}, | |
{"role": "user", "content": prompt} | |
], | |
temperature=0.7, | |
max_tokens=4000 | |
) | |
return json.loads(response.choices[0].message.content) | |
except Exception as e: | |
logger.error(f"Error generating generic creatives: {e}") | |
return self._generate_fallback_creatives(business_context, num_variations) | |
def _generate_fallback_creatives(self, business_context: str, num_variations: int) -> List[Dict[str, Any]]: | |
"""Generate fallback creatives when OpenAI fails.""" | |
logger.warning("Using fallback creative generation") | |
fallback_creatives = [] | |
for i in range(num_variations): | |
creative = { | |
"headlines": [ | |
f"Quality Products - Variation {i+1}", | |
"Shop Now & Save", | |
"Limited Time Offer", | |
"Best Deals Available", | |
"Order Today", | |
"Free Shipping", | |
"Expert Service", | |
"Top Rated", | |
"Fast Delivery", | |
"Satisfaction Guaranteed" | |
], | |
"descriptions": [ | |
f"Discover amazing products and services. {business_context[:50]}...", | |
"Join thousands of satisfied customers. Order now!", | |
"Premium quality at unbeatable prices. Shop today.", | |
"Experience the difference. Fast shipping available." | |
], | |
"theme": f"Generic Theme {i+1}", | |
"target_audience": "General audience" | |
} | |
fallback_creatives.append(creative) | |
return fallback_creatives | |
def optimize_creative_for_audience(self, creative: Dict[str, Any], | |
audience_description: str) -> Dict[str, Any]: | |
""" | |
Optimize a creative for a specific audience. | |
Args: | |
creative: Generated creative to optimize | |
audience_description: Description of target audience | |
Returns: | |
Optimized creative | |
""" | |
try: | |
prompt = f""" | |
Optimize the following Google Ads creative for this specific audience: | |
TARGET AUDIENCE: {audience_description} | |
CURRENT CREATIVE: | |
Theme: {creative.get('theme', 'N/A')} | |
Headlines: {', '.join(creative.get('headlines', []))} | |
Descriptions: {', '.join(creative.get('descriptions', []))} | |
INSTRUCTIONS: | |
1. Adjust language, tone, and messaging to resonate with the target audience | |
2. Maintain headline length under 30 characters | |
3. Maintain description length under 90 characters | |
4. Keep the same number of headlines and descriptions | |
5. Focus on benefits most relevant to this audience | |
Please return the optimized creative as a valid JSON object with the same structure: | |
- "headlines": array of headline strings | |
- "descriptions": array of description strings | |
- "theme": updated theme description | |
- "target_audience": confirmed target audience | |
Return only the JSON response, no additional text. | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4-turbo-preview", | |
messages=[ | |
{"role": "system", "content": "You are an expert at tailoring ad copy for specific audiences."}, | |
{"role": "user", "content": prompt} | |
], | |
temperature=0.6, | |
max_tokens=2000 | |
) | |
optimized_creative = json.loads(response.choices[0].message.content) | |
logger.info(f"Optimized creative for audience: {audience_description}") | |
return optimized_creative | |
except Exception as e: | |
logger.error(f"Error optimizing creative: {e}") | |
return creative # Return original if optimization fails | |
def analyze_performance(self, performance_data): | |
"""Analyze performance data and provide insights.""" | |
try: | |
# Calculate key metrics | |
avg_ctr = performance_data['ctr'].mean() | |
avg_conversion_rate = performance_data['conversion_rate'].mean() if 'conversion_rate' in performance_data.columns else 0 | |
avg_cost = performance_data['cost'].mean() | |
# Get top and bottom performers | |
top_performers = performance_data.nlargest(5, 'ctr') | |
bottom_performers = performance_data.nsmallest(5, 'ctr') | |
prompt = f""" | |
As a Google Ads performance analyst, analyze this campaign data and provide insights: | |
**Overall Performance:** | |
- Average CTR: {avg_ctr:.4f} | |
- Average Conversion Rate: {avg_conversion_rate:.4f} | |
- Average Cost: ${avg_cost:.2f} | |
**Top 5 Performers (CTR):** | |
{top_performers[['ctr', 'conversion_rate', 'cost']].to_string() if not top_performers.empty else 'No data'} | |
**Bottom 5 Performers (CTR):** | |
{bottom_performers[['ctr', 'conversion_rate', 'cost']].to_string() if not bottom_performers.empty else 'No data'} | |
Provide actionable insights on: | |
1. What's working well | |
2. What needs improvement | |
3. Specific recommendations for optimization | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[{"role": "user", "content": prompt}] | |
) | |
return response.choices[0].message.content | |
except Exception as e: | |
return f"Error analyzing performance: {str(e)}" | |
def analyze_performance_patterns(self, top_performers, bottom_performers): | |
"""Analyze what works vs what doesn't work in ad performance.""" | |
try: | |
# Extract headlines and descriptions from top performers | |
top_headlines = [] | |
top_descriptions = [] | |
top_metrics = [] | |
for idx, ad in top_performers.iterrows(): | |
if isinstance(ad['headlines'], list): | |
top_headlines.extend(ad['headlines']) | |
else: | |
top_headlines.append(str(ad['headlines'])) | |
if isinstance(ad['descriptions'], list): | |
top_descriptions.extend(ad['descriptions']) | |
else: | |
top_descriptions.append(str(ad['descriptions'])) | |
top_metrics.append(f"CTR: {ad['ctr']*100:.2f}%, Conversions: {ad['conversions']:.0f}, Cost: ${ad['cost']:.2f}") | |
# Extract headlines and descriptions from bottom performers | |
bottom_headlines = [] | |
bottom_descriptions = [] | |
bottom_metrics = [] | |
for idx, ad in bottom_performers.iterrows(): | |
if isinstance(ad['headlines'], list): | |
bottom_headlines.extend(ad['headlines']) | |
else: | |
bottom_headlines.append(str(ad['headlines'])) | |
if isinstance(ad['descriptions'], list): | |
bottom_descriptions.extend(ad['descriptions']) | |
else: | |
bottom_descriptions.append(str(ad['descriptions'])) | |
bottom_metrics.append(f"CTR: {ad['ctr']*100:.2f}%, Conversions: {ad['conversions']:.0f}, Cost: ${ad['cost']:.2f}") | |
prompt = f""" | |
As a Google Ads expert, analyze the performance patterns between top and bottom performing ads. | |
**TOP PERFORMING ADS:** | |
Headlines: {' | '.join(top_headlines[:10])} | |
Descriptions: {' | '.join(top_descriptions[:5])} | |
Metrics: {' | '.join(top_metrics)} | |
**BOTTOM PERFORMING ADS:** | |
Headlines: {' | '.join(bottom_headlines[:10])} | |
Descriptions: {' | '.join(bottom_descriptions[:5])} | |
Metrics: {' | '.join(bottom_metrics)} | |
Please provide your analysis in a valid JSON format with this structure: | |
{{ | |
"what_works": "Clear explanation of successful patterns, messaging, and approaches", | |
"what_doesnt_work": "Clear explanation of unsuccessful patterns and what to avoid", | |
"recommendations": "Specific actionable recommendations for future creatives" | |
}} | |
Focus on: | |
- Language patterns and messaging that drive results | |
- Emotional triggers that work vs don't work | |
- Call-to-action effectiveness | |
- Value proposition clarity | |
- Trust signals and credibility elements | |
Return only the JSON response, no additional text. | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[{"role": "user", "content": prompt}], | |
response_format={"type": "json_object"} | |
) | |
import json | |
try: | |
return json.loads(response.choices[0].message.content) | |
except: | |
# Fallback if JSON parsing fails | |
content = response.choices[0].message.content | |
return { | |
"what_works": content[:len(content)//3], | |
"what_doesnt_work": content[len(content)//3:2*len(content)//3], | |
"recommendations": content[2*len(content)//3:] | |
} | |
except Exception as e: | |
return { | |
"what_works": f"Error analyzing patterns: {str(e)}", | |
"what_doesnt_work": "Unable to analyze poor performers", | |
"recommendations": "Please try again or check your OpenAI API key" | |
} | |
def generate_similar_creatives(self, top_performers, target_audience, additional_context, num_variations=3): | |
"""Generate creatives similar to top performers.""" | |
try: | |
# Extract patterns from top performers | |
successful_headlines = [] | |
successful_descriptions = [] | |
for idx, ad in top_performers.iterrows(): | |
if isinstance(ad['headlines'], list): | |
successful_headlines.extend(ad['headlines'][:3]) # Top 3 headlines | |
else: | |
successful_headlines.append(str(ad['headlines'])) | |
if isinstance(ad['descriptions'], list): | |
successful_descriptions.extend(ad['descriptions'][:2]) # Top 2 descriptions | |
else: | |
successful_descriptions.append(str(ad['descriptions'])) | |
prompt = f""" | |
Create {num_variations} new Google Ads responsive search ad variations based on these TOP PERFORMING ads. | |
**SUCCESSFUL HEADLINES:** | |
{chr(10).join(f"• {h}" for h in successful_headlines[:10])} | |
**SUCCESSFUL DESCRIPTIONS:** | |
{chr(10).join(f"• {d}" for d in successful_descriptions[:5])} | |
**Target Audience:** {target_audience} | |
**Additional Context:** {additional_context} | |
**REQUIREMENTS:** | |
- Create variations that follow the successful patterns but aren't identical copies | |
- Each variation needs 5-10 headlines (max 30 chars each) | |
- Each variation needs 2-4 descriptions (max 90 chars each) | |
- Stay true to what made the originals successful | |
- Keep the same tone and value propositions that work | |
Return as JSON array: | |
[ | |
{{ | |
"headlines": ["headline1", "headline2", ...], | |
"descriptions": ["desc1", "desc2", ...], | |
"reasoning": "Why this follows successful patterns" | |
}} | |
] | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[{"role": "user", "content": prompt}] | |
) | |
import json | |
try: | |
return json.loads(response.choices[0].message.content) | |
except: | |
# Fallback parsing | |
return self._parse_creative_fallback(response.choices[0].message.content, num_variations) | |
except Exception as e: | |
return [{"headlines": [f"Error: {str(e)}"], "descriptions": ["Please check your API key"], "reasoning": "Generation failed"}] | |
def generate_experimental_creatives(self, performance_insights, target_audience, additional_context, num_variations=2): | |
"""Generate experimental creative ideas to test new approaches.""" | |
try: | |
insights_text = "" | |
if performance_insights: | |
insights_text = f""" | |
**What Currently Works:** {performance_insights.get('what_works', 'N/A')} | |
**What Doesn't Work:** {performance_insights.get('what_doesnt_work', 'N/A')} | |
**Recommendations:** {performance_insights.get('recommendations', 'N/A')} | |
""" | |
prompt = f""" | |
Create {num_variations} EXPERIMENTAL Google Ads variations that test new approaches and ideas. | |
**Performance Insights:** | |
{insights_text} | |
**Target Audience:** {target_audience} | |
**Additional Context:** {additional_context} | |
**EXPERIMENTAL GOALS:** | |
- Test completely different messaging angles | |
- Try new emotional triggers or value propositions | |
- Experiment with different call-to-action approaches | |
- Push creative boundaries while staying relevant | |
- Test contrarian or bold approaches | |
**REQUIREMENTS:** | |
- Each variation needs 5-10 headlines (max 30 chars each) | |
- Each variation needs 2-4 descriptions (max 90 chars each) | |
- Be creative and test new hypotheses | |
- Don't just copy what's already working | |
Return as JSON array: | |
[ | |
{{ | |
"headlines": ["headline1", "headline2", ...], | |
"descriptions": ["desc1", "desc2", ...], | |
"reasoning": "Experimental hypothesis and why this approach might work" | |
}} | |
] | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[{"role": "user", "content": prompt}] | |
) | |
import json | |
try: | |
return json.loads(response.choices[0].message.content) | |
except: | |
# Fallback parsing | |
return self._parse_creative_fallback(response.choices[0].message.content, num_variations) | |
except Exception as e: | |
return [{"headlines": [f"Error: {str(e)}"], "descriptions": ["Please check your API key"], "reasoning": "Generation failed"}] | |
def _parse_creative_fallback(self, content, num_variations): | |
"""Fallback parser when JSON parsing fails.""" | |
try: | |
# Simple fallback - create basic structure | |
creatives = [] | |
for i in range(num_variations): | |
creatives.append({ | |
"headlines": [f"New Headline {i+1}", f"Creative {i+1}", f"Test {i+1}"], | |
"descriptions": [f"Description {i+1}", f"Test description {i+1}"], | |
"reasoning": f"Fallback creative {i+1} - check content parsing" | |
}) | |
return creatives | |
except: | |
return [{"headlines": ["Parsing Error"], "descriptions": ["Check response"], "reasoning": "Fallback failed"}] | |
def analyze_creative_patterns(self, top_performers, bottom_performers, best_assets, learning_assets): | |
"""Analyze creative copy patterns to identify what works vs what doesn't.""" | |
try: | |
# Extract creative copy from top performers | |
top_headlines = [] | |
top_descriptions = [] | |
for idx, ad in top_performers.iterrows(): | |
if isinstance(ad['headlines'], list): | |
top_headlines.extend(ad['headlines']) | |
else: | |
top_headlines.append(str(ad['headlines'])) | |
if isinstance(ad['descriptions'], list): | |
top_descriptions.extend(ad['descriptions']) | |
else: | |
top_descriptions.append(str(ad['descriptions'])) | |
# Extract creative copy from bottom performers | |
bottom_headlines = [] | |
bottom_descriptions = [] | |
for idx, ad in bottom_performers.iterrows(): | |
if isinstance(ad['headlines'], list): | |
bottom_headlines.extend(ad['headlines']) | |
else: | |
bottom_headlines.append(str(ad['headlines'])) | |
if isinstance(ad['descriptions'], list): | |
bottom_descriptions.extend(ad['descriptions']) | |
else: | |
bottom_descriptions.append(str(ad['descriptions'])) | |
# Extract asset-level creative | |
best_asset_copy = [] | |
learning_asset_copy = [] | |
if not best_assets.empty: | |
best_asset_copy = best_assets['asset'].tolist() | |
if not learning_assets.empty: | |
learning_asset_copy = learning_assets['asset'].tolist() | |
prompt = f""" | |
As a Google Ads creative expert, analyze the creative copy patterns between high and low performing elements. | |
**HIGH-PERFORMING CREATIVE:** | |
Headlines from high-CTR ads: | |
{chr(10).join(f"• {h}" for h in top_headlines[:15])} | |
Descriptions from high-CTR ads: | |
{chr(10).join(f"• {d}" for d in top_descriptions[:10])} | |
"Best" rated assets: | |
{chr(10).join(f"• {a}" for a in best_asset_copy[:10])} | |
**LOW-PERFORMING CREATIVE:** | |
Headlines from low-CTR ads: | |
{chr(10).join(f"• {h}" for h in bottom_headlines[:15])} | |
Descriptions from low-CTR ads: | |
{chr(10).join(f"• {d}" for d in bottom_descriptions[:10])} | |
"Learning" rated assets: | |
{chr(10).join(f"• {a}" for a in learning_asset_copy[:10])} | |
**ANALYSIS FOCUS:** | |
- Language patterns, words, and phrases that drive clicks | |
- Emotional triggers and psychological approaches that work | |
- Call-to-action effectiveness and messaging style | |
- Value proposition clarity and positioning | |
- Creative themes and angles that resonate | |
Please provide your analysis as a valid JSON response with this format: | |
{{ | |
"winning_elements": "Detailed analysis of successful creative patterns and approaches", | |
"weak_elements": "Analysis of underperforming creative patterns to avoid", | |
"winning_phrases": ["phrase1", "phrase2", "phrase3", "phrase4", "phrase5"], | |
"weak_phrases": ["avoid1", "avoid2", "avoid3", "avoid4", "avoid5"], | |
"creative_recommendations": "Specific actionable recommendations for future creative copy" | |
}} | |
Return only the JSON response, no additional text. | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[{"role": "user", "content": prompt}], | |
response_format={"type": "json_object"} | |
) | |
import json | |
try: | |
return json.loads(response.choices[0].message.content) | |
except: | |
# Fallback if JSON parsing fails | |
content = response.choices[0].message.content | |
return { | |
"winning_elements": content[:len(content)//4], | |
"weak_elements": content[len(content)//4:len(content)//2], | |
"winning_phrases": ["Data analysis", "Quick quote", "Best prices", "Save money", "Compare now"], | |
"weak_phrases": ["Generic offer", "Standard service", "Basic coverage", "Regular rates", "Normal terms"], | |
"creative_recommendations": content[len(content)//2:] | |
} | |
except Exception as e: | |
return { | |
"winning_elements": f"Error analyzing creative patterns: {str(e)}", | |
"weak_elements": "Unable to analyze weak creative patterns", | |
"winning_phrases": [], | |
"weak_phrases": [], | |
"creative_recommendations": "Please try again or check your OpenAI API key" | |
} | |
def generate_best_creative_suggestions(self, creative_analysis, creative_insights, target_audience, additional_context, num_variations=3): | |
"""Generate best creative suggestions based on data-driven insights.""" | |
try: | |
# Extract successful patterns from analysis | |
successful_headlines = [] | |
successful_descriptions = [] | |
for idx, ad in creative_analysis['top_performers'].iterrows(): | |
if isinstance(ad['headlines'], list): | |
successful_headlines.extend(ad['headlines'][:3]) | |
else: | |
successful_headlines.append(str(ad['headlines'])) | |
if isinstance(ad['descriptions'], list): | |
successful_descriptions.extend(ad['descriptions'][:2]) | |
else: | |
successful_descriptions.append(str(ad['descriptions'])) | |
# Get winning phrases if available | |
winning_phrases = [] | |
creative_recs = "" | |
if creative_insights: | |
winning_phrases = creative_insights.get('winning_phrases', []) | |
creative_recs = creative_insights.get('creative_recommendations', '') | |
# Get best asset copy | |
best_asset_copy = [] | |
if not creative_analysis['best_assets'].empty: | |
best_asset_copy = creative_analysis['best_assets']['asset'].tolist()[:5] | |
prompt = f""" | |
Generate {num_variations} BEST CREATIVE SUGGESTIONS for Google Ads based on proven winning patterns. | |
**PROVEN SUCCESSFUL CREATIVE:** | |
Headlines: {' | '.join(successful_headlines[:10])} | |
Descriptions: {' | '.join(successful_descriptions[:8])} | |
Best Assets: {' | '.join(best_asset_copy)} | |
**WINNING PHRASES TO USE:** | |
{', '.join(winning_phrases[:10])} | |
**CREATIVE INSIGHTS:** | |
{creative_recs} | |
**TARGET:** {target_audience} | |
**CONTEXT:** {additional_context} | |
**REQUIREMENTS:** | |
- Follow proven patterns from the successful creative above | |
- Incorporate winning phrases and elements | |
- Each suggestion needs 8-12 headlines (max 30 chars each) | |
- Each suggestion needs 3-4 descriptions (max 90 chars each) | |
- Focus on data-driven recommendations, not speculation | |
- Prioritize approaches that have already shown success | |
Please return your response as a valid JSON array: | |
[ | |
{{ | |
"strategy": "Brief strategy name (e.g. 'Speed + Price Focus')", | |
"headlines": ["headline1", "headline2", ...], | |
"descriptions": ["desc1", "desc2", ...], | |
"reasoning": "Why this approach will work based on the data" | |
}} | |
] | |
Return only the JSON response, no additional text. | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[{"role": "user", "content": prompt}], | |
response_format={"type": "json_object"} | |
) | |
import json | |
try: | |
result = json.loads(response.choices[0].message.content) | |
# Ensure result is a list of dicts | |
if isinstance(result, dict): | |
result = [result] | |
elif isinstance(result, str): | |
result = self._parse_creative_fallback_suggestions(result, num_variations) | |
return result | |
except: | |
return self._parse_creative_fallback_suggestions(response.choices[0].message.content, num_variations) | |
except Exception as e: | |
return [{"strategy": f"Error: {str(e)}", "headlines": ["Check API key"], "descriptions": ["Generation failed"], "reasoning": "Please try again"}] | |
def generate_themed_creative_variants(self, creative_insights, target_audience, additional_context, num_variations=5): | |
"""Generate themed creative variants testing different messaging angles.""" | |
try: | |
insights_text = "" | |
if creative_insights: | |
insights_text = f""" | |
Winning Elements: {creative_insights.get('winning_elements', 'N/A')} | |
Recommendations: {creative_insights.get('creative_recommendations', 'N/A')} | |
""" | |
prompt = f""" | |
Generate {num_variations} THEMED CREATIVE VARIANTS for Google Ads testing different messaging angles. | |
**CREATIVE INSIGHTS:** | |
{insights_text} | |
**TARGET:** {target_audience} | |
**CONTEXT:** {additional_context} | |
**VARIANT THEMES TO EXPLORE:** | |
1. Price/Cost Focus - Emphasize savings, value, competitive pricing | |
2. Speed/Convenience Focus - Quick service, fast quotes, easy process | |
3. Trust/Security Focus - Reliability, protection, peace of mind | |
4. Exclusivity/Premium Focus - Special offers, limited time, premium service | |
5. Problem/Solution Focus - Address pain points, provide solutions | |
**REQUIREMENTS:** | |
- Each variant should explore a different messaging angle/theme | |
- 8-12 headlines per variant (max 30 chars each) | |
- 3-4 descriptions per variant (max 90 chars each) | |
- Be creative and test new hypotheses while staying relevant | |
- Vary emotional triggers and value propositions | |
Please return your response as a valid JSON array: | |
[ | |
{{ | |
"theme": "Theme name (e.g. 'Price Focus', 'Speed Focus')", | |
"headlines": ["headline1", "headline2", ...], | |
"descriptions": ["desc1", "desc2", ...], | |
"reasoning": "Experimental hypothesis for this themed approach" | |
}} | |
] | |
Return only the JSON response, no additional text. | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[{"role": "user", "content": prompt}], | |
response_format={"type": "json_object"} | |
) | |
import json | |
try: | |
result = json.loads(response.choices[0].message.content) | |
# Ensure result is a list of dicts | |
if isinstance(result, dict): | |
result = [result] | |
elif isinstance(result, str): | |
result = self._parse_creative_fallback_variants(result, num_variations) | |
return result | |
except: | |
return self._parse_creative_fallback_variants(response.choices[0].message.content, num_variations) | |
except Exception as e: | |
return [{"theme": f"Error: {str(e)}", "headlines": ["Check API key"], "descriptions": ["Generation failed"], "reasoning": "Please try again"}] | |
def _parse_creative_fallback_suggestions(self, content, num_variations): | |
"""Fallback parser for best suggestions when JSON parsing fails.""" | |
try: | |
suggestions = [] | |
for i in range(num_variations): | |
suggestions.append({ | |
"strategy": f"Data-Driven Strategy {i+1}", | |
"headlines": [f"Best Headline {i+1}-1", f"Proven Copy {i+1}-2", f"Winner {i+1}-3"], | |
"descriptions": [f"Best description {i+1}-1", f"Proven copy {i+1}-2"], | |
"reasoning": f"Based on winning patterns - fallback {i+1}" | |
}) | |
return suggestions | |
except: | |
return [{"strategy": "Parsing Error", "headlines": ["Check response"], "descriptions": ["Check response"], "reasoning": "Fallback failed"}] | |
def _parse_creative_fallback_variants(self, content, num_variations): | |
"""Fallback parser for themed variants when JSON parsing fails.""" | |
try: | |
themes = ["Price Focus", "Speed Focus", "Trust Focus", "Premium Focus", "Solution Focus"] | |
variants = [] | |
for i in range(num_variations): | |
theme = themes[i] if i < len(themes) else f"Theme {i+1}" | |
variants.append({ | |
"theme": theme, | |
"headlines": [f"{theme} Headline {i+1}-1", f"{theme} Copy {i+1}-2", f"{theme} Test {i+1}-3"], | |
"descriptions": [f"{theme} description {i+1}-1", f"{theme} copy {i+1}-2"], | |
"reasoning": f"Testing {theme} approach - fallback {i+1}" | |
}) | |
return variants | |
except: | |
return [{"theme": "Parsing Error", "headlines": ["Check response"], "descriptions": ["Check response"], "reasoning": "Fallback failed"}] | |
def categorize_creative_elements(self, ads_data, asset_data): | |
"""Use LLM to categorize creative elements for better analysis.""" | |
try: | |
# Extract all headlines and descriptions | |
all_headlines = [] | |
all_descriptions = [] | |
for _, ad in ads_data.iterrows(): | |
if isinstance(ad['headlines'], list): | |
all_headlines.extend(ad['headlines']) | |
elif isinstance(ad['headlines'], str) and ad['headlines']: | |
all_headlines.extend(ad['headlines'].split('|')) | |
if isinstance(ad['descriptions'], list): | |
all_descriptions.extend(ad['descriptions']) | |
elif isinstance(ad['descriptions'], str) and ad['descriptions']: | |
all_descriptions.extend(ad['descriptions'].split('|')) | |
# Add asset headlines/descriptions | |
if not asset_data.empty: | |
headlines_assets = asset_data[asset_data['asset_type'] == 'Headline']['asset'].tolist() | |
descriptions_assets = asset_data[asset_data['asset_type'] == 'Description']['asset'].tolist() | |
all_headlines.extend(headlines_assets) | |
all_descriptions.extend(descriptions_assets) | |
# Remove duplicates and empty strings | |
unique_headlines = list(set([h.strip() for h in all_headlines if h and h.strip()]))[:20] | |
unique_descriptions = list(set([d.strip() for d in all_descriptions if d and d.strip()]))[:15] | |
prompt = f""" | |
Analyze and categorize these Google Ads creative elements for van insurance. | |
**HEADLINES:** | |
{chr(10).join(f"• {h}" for h in unique_headlines)} | |
**DESCRIPTIONS:** | |
{chr(10).join(f"• {d}" for d in unique_descriptions)} | |
Categorize each creative element by: | |
1. **Value Proposition**: Price/Cost, Speed/Convenience, Trust/Security, Quality/Premium, Comparison/Choice | |
2. **Emotional Trigger**: Urgency, Fear/Protection, Savings, Convenience, Authority/Expert | |
3. **Call-to-Action Type**: Get Quote, Compare, Save, Apply, Buy, Learn More | |
4. **Messaging Style**: Direct/Simple, Descriptive/Detailed, Question/Curiosity, Benefit-focused, Feature-focused | |
Return as JSON: | |
{{ | |
"headlines_analysis": [ | |
{{ | |
"text": "headline text", | |
"value_proposition": "category", | |
"emotional_trigger": "category", | |
"cta_type": "category", | |
"messaging_style": "category" | |
}} | |
], | |
"descriptions_analysis": [ | |
{{ | |
"text": "description text", | |
"value_proposition": "category", | |
"emotional_trigger": "category", | |
"cta_type": "category", | |
"messaging_style": "category" | |
}} | |
], | |
"patterns_summary": {{ | |
"most_common_value_prop": "category", | |
"most_common_emotional_trigger": "category", | |
"most_common_cta": "category", | |
"messaging_style_distribution": {{"style": "count"}}, | |
"insights": "Key insights about creative patterns" | |
}} | |
}} | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[{"role": "user", "content": prompt}], | |
response_format={"type": "json_object"} | |
) | |
import json | |
try: | |
return json.loads(response.choices[0].message.content) | |
except: | |
return { | |
"headlines_analysis": [], | |
"descriptions_analysis": [], | |
"patterns_summary": { | |
"insights": "Error categorizing creative elements" | |
} | |
} | |
except Exception as e: | |
return { | |
"headlines_analysis": [], | |
"descriptions_analysis": [], | |
"patterns_summary": { | |
"insights": f"Error analyzing creative patterns: {str(e)}" | |
} | |
} | |
def analyze_creative_patterns_enhanced(self, top_performers, bottom_performers, best_assets, learning_assets, ads_data): | |
"""Enhanced creative pattern analysis using categorization.""" | |
try: | |
# First categorize all creative elements | |
creative_categories = self.categorize_creative_elements(ads_data, best_assets) | |
# Extract creative copy from performers for traditional analysis | |
top_headlines = [] | |
top_descriptions = [] | |
for idx, ad in top_performers.iterrows(): | |
if isinstance(ad['headlines'], list): | |
top_headlines.extend(ad['headlines']) | |
elif isinstance(ad['headlines'], str): | |
top_headlines.extend(ad['headlines'].split('|')) | |
if isinstance(ad['descriptions'], list): | |
top_descriptions.extend(ad['descriptions']) | |
elif isinstance(ad['descriptions'], str): | |
top_descriptions.extend(ad['descriptions'].split('|')) | |
bottom_headlines = [] | |
bottom_descriptions = [] | |
for idx, ad in bottom_performers.iterrows(): | |
if isinstance(ad['headlines'], list): | |
bottom_headlines.extend(ad['headlines']) | |
elif isinstance(ad['headlines'], str): | |
bottom_headlines.extend(ad['headlines'].split('|')) | |
if isinstance(ad['descriptions'], list): | |
bottom_descriptions.extend(ad['descriptions']) | |
elif isinstance(ad['descriptions'], str): | |
bottom_descriptions.extend(ad['descriptions'].split('|')) | |
best_asset_copy = [] | |
learning_asset_copy = [] | |
if not best_assets.empty: | |
best_asset_copy = best_assets['asset'].tolist() | |
if not learning_assets.empty: | |
learning_asset_copy = learning_assets['asset'].tolist() | |
prompt = f""" | |
Analyze creative performance patterns using both performance data and creative categorization. | |
**CREATIVE CATEGORIZATION INSIGHTS:** | |
{creative_categories.get('patterns_summary', {}).get('insights', 'No categorization available')} | |
Most common value proposition: {creative_categories.get('patterns_summary', {}).get('most_common_value_prop', 'Unknown')} | |
Most common emotional trigger: {creative_categories.get('patterns_summary', {}).get('most_common_emotional_trigger', 'Unknown')} | |
Most common CTA type: {creative_categories.get('patterns_summary', {}).get('most_common_cta', 'Unknown')} | |
**HIGH-PERFORMING CREATIVE:** | |
Headlines from high-CTR ads: {' | '.join(top_headlines[:10])} | |
Descriptions from high-CTR ads: {' | '.join(top_descriptions[:8])} | |
"Best" rated assets: {' | '.join(best_asset_copy[:8])} | |
**LOW-PERFORMING CREATIVE:** | |
Headlines from low-CTR ads: {' | '.join(bottom_headlines[:10])} | |
Descriptions from low-CTR ads: {' | '.join(bottom_descriptions[:8])} | |
"Learning" rated assets: {' | '.join(learning_asset_copy[:8])} | |
Please provide enhanced analysis combining performance data with creative categorization and return your response as a valid JSON object: | |
{{ | |
"winning_elements": "Analysis of successful creative patterns including categories that work", | |
"weak_elements": "Analysis of underperforming patterns including categories to avoid", | |
"winning_phrases": ["phrase1", "phrase2", "phrase3", "phrase4", "phrase5"], | |
"weak_phrases": ["avoid1", "avoid2", "avoid3", "avoid4", "avoid5"], | |
"winning_categories": {{ | |
"value_propositions": ["category1", "category2"], | |
"emotional_triggers": ["trigger1", "trigger2"], | |
"cta_types": ["cta1", "cta2"], | |
"messaging_styles": ["style1", "style2"] | |
}}, | |
"avoid_categories": {{ | |
"value_propositions": ["category1", "category2"], | |
"emotional_triggers": ["trigger1", "trigger2"], | |
"cta_types": ["cta1", "cta2"], | |
"messaging_styles": ["style1", "style2"] | |
}}, | |
"creative_recommendations": "Specific actionable recommendations based on both performance and categorization analysis" | |
}} | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[{"role": "user", "content": prompt}], | |
response_format={"type": "json_object"} | |
) | |
import json | |
try: | |
result = json.loads(response.choices[0].message.content) | |
# Add the categorization data to the result | |
result['creative_categorization'] = creative_categories | |
return result | |
except: | |
return { | |
"winning_elements": "Error analyzing enhanced patterns", | |
"weak_elements": "Unable to analyze weak patterns", | |
"winning_phrases": [], | |
"weak_phrases": [], | |
"winning_categories": {}, | |
"avoid_categories": {}, | |
"creative_recommendations": "Analysis failed - check API response", | |
"creative_categorization": creative_categories | |
} | |
except Exception as e: | |
return { | |
"winning_elements": f"Error in enhanced analysis: {str(e)}", | |
"weak_elements": "Unable to analyze patterns", | |
"winning_phrases": [], | |
"weak_phrases": [], | |
"winning_categories": {}, | |
"avoid_categories": {}, | |
"creative_recommendations": "Please try again", | |
"creative_categorization": {} | |
} | |
def classify_asset_types(self, asset_data): | |
""" | |
Use LLM to classify each headline/description into creative types. | |
Args: | |
asset_data: DataFrame with asset text and performance data | |
Returns: | |
DataFrame with asset classifications added | |
""" | |
try: | |
# Extract unique assets to avoid duplicate API calls | |
unique_assets = asset_data[['asset', 'asset_type']].drop_duplicates() | |
if unique_assets.empty: | |
return asset_data | |
# Prepare assets for classification | |
headlines = unique_assets[unique_assets['asset_type'] == 'Headline']['asset'].tolist() | |
descriptions = unique_assets[unique_assets['asset_type'] == 'Description']['asset'].tolist() | |
prompt = f""" | |
Classify these Google Ads creative elements into specific types. Focus on van insurance/business insurance context. | |
**HEADLINES TO CLASSIFY:** | |
{chr(10).join(f"{i+1}. {h}" for i, h in enumerate(headlines[:20]))} | |
**DESCRIPTIONS TO CLASSIFY:** | |
{chr(10).join(f"{i+1}. {d}" for i, d in enumerate(descriptions[:15]))} | |
**CLASSIFICATION CATEGORIES:** | |
**Value Proposition Types:** | |
- Price/Cost (savings, competitive pricing, low cost) | |
- Speed/Convenience (fast quotes, quick service, easy process) | |
- Trust/Security (reliable, protected, established, safe) | |
- Quality/Premium (expert, professional, comprehensive) | |
- Comparison/Choice (compare, options, best, top-rated) | |
**Emotional Trigger Types:** | |
- Urgency (limited time, act now, don't wait) | |
- Fear/Protection (peace of mind, protect, secure, coverage) | |
- Savings (save money, discounts, deals) | |
- Convenience (simple, easy, hassle-free) | |
- Authority/Expert (trusted, professional, experienced) | |
**Call-to-Action Types:** | |
- Get Quote (get quote, free quote, quote now) | |
- Compare (compare prices, compare options) | |
- Save (save money, save today) | |
- Apply/Buy (apply now, buy online, get covered) | |
- Learn More (learn more, find out, discover) | |
**Messaging Style:** | |
- Direct (straightforward, clear, simple language) | |
- Descriptive (detailed, explanatory, informative) | |
- Question (asking questions, curiosity-driven) | |
- Benefit-focused (emphasizes outcomes/benefits) | |
- Feature-focused (emphasizes product features) | |
Return as JSON with this structure: | |
{{ | |
"headlines": [ | |
{{ | |
"text": "headline text", | |
"value_proposition": "category", | |
"emotional_trigger": "category", | |
"cta_type": "category", | |
"messaging_style": "category" | |
}} | |
], | |
"descriptions": [ | |
{{ | |
"text": "description text", | |
"value_proposition": "category", | |
"emotional_trigger": "category", | |
"cta_type": "category", | |
"messaging_style": "category" | |
}} | |
], | |
"classification_summary": {{ | |
"most_effective_value_prop": "category based on performance", | |
"most_effective_emotional_trigger": "category", | |
"most_effective_cta": "category", | |
"messaging_insights": "Key insights about what messaging works" | |
}} | |
}} | |
Return only the JSON response, no additional text. | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[{"role": "user", "content": prompt}], | |
response_format={"type": "json_object"} | |
) | |
import json | |
try: | |
classification_result = json.loads(response.choices[0].message.content) | |
# Create classification mapping | |
classification_map = {} | |
for headline in classification_result.get('headlines', []): | |
classification_map[headline['text']] = { | |
'value_proposition': headline['value_proposition'], | |
'emotional_trigger': headline['emotional_trigger'], | |
'cta_type': headline['cta_type'], | |
'messaging_style': headline['messaging_style'] | |
} | |
for description in classification_result.get('descriptions', []): | |
classification_map[description['text']] = { | |
'value_proposition': description['value_proposition'], | |
'emotional_trigger': description['emotional_trigger'], | |
'cta_type': description['cta_type'], | |
'messaging_style': description['messaging_style'] | |
} | |
# Add classifications to asset_data | |
asset_data_with_types = asset_data.copy() | |
def add_classification(row): | |
classifications = classification_map.get(row['asset'], {}) | |
row['value_proposition'] = classifications.get('value_proposition', 'Unknown') | |
row['emotional_trigger'] = classifications.get('emotional_trigger', 'Unknown') | |
row['cta_type'] = classifications.get('cta_type', 'Unknown') | |
row['messaging_style'] = classifications.get('messaging_style', 'Unknown') | |
return row | |
asset_data_with_types = asset_data_with_types.apply(add_classification, axis=1) | |
logger.info(f"Classified {len(unique_assets)} unique assets") | |
return asset_data_with_types, classification_result.get('classification_summary', {}) | |
except json.JSONDecodeError: | |
logger.error("Failed to parse LLM classification response") | |
return asset_data, {} | |
except Exception as e: | |
logger.error(f"Error classifying asset types: {e}") | |
return asset_data, {} | |
def generate_creative_insights(self, asset_data_with_types, performance_categories=['Best', 'Good']): | |
""" | |
Generate insights about what creative types perform best. | |
Args: | |
asset_data_with_types: DataFrame with classified assets | |
performance_categories: Which performance categories to analyze | |
Returns: | |
Dictionary with creative insights | |
""" | |
try: | |
# Filter for high-performing assets | |
high_performers = asset_data_with_types[ | |
asset_data_with_types['performance_category'].isin(performance_categories) | |
] | |
# Filter for low-performing assets | |
low_performers = asset_data_with_types[ | |
asset_data_with_types['performance_category'].isin(['Low', 'Learning']) | |
] | |
if high_performers.empty: | |
return {"insights": "No high-performing assets found for analysis"} | |
# Analyze patterns in high vs low performers | |
prompt = f""" | |
Analyze creative performance patterns based on asset classifications and performance ratings. | |
**HIGH-PERFORMING ASSETS (Best/Good):** | |
Headlines ({len(high_performers[high_performers['asset_type'] == 'Headline'])} assets): | |
{chr(10).join(f"• {row['asset']} [VP: {row.get('value_proposition', 'N/A')}, ET: {row.get('emotional_trigger', 'N/A')}, CTA: {row.get('cta_type', 'N/A')}]" for _, row in high_performers[high_performers['asset_type'] == 'Headline'].head(10).iterrows())} | |
Descriptions ({len(high_performers[high_performers['asset_type'] == 'Description'])} assets): | |
{chr(10).join(f"• {row['asset']} [VP: {row.get('value_proposition', 'N/A')}, ET: {row.get('emotional_trigger', 'N/A')}, CTA: {row.get('cta_type', 'N/A')}]" for _, row in high_performers[high_performers['asset_type'] == 'Description'].head(8).iterrows())} | |
**LOW-PERFORMING ASSETS (Low/Learning):** | |
Headlines ({len(low_performers[low_performers['asset_type'] == 'Headline'])} assets): | |
{chr(10).join(f"• {row['asset']} [VP: {row.get('value_proposition', 'N/A')}, ET: {row.get('emotional_trigger', 'N/A')}, CTA: {row.get('cta_type', 'N/A')}]" for _, row in low_performers[low_performers['asset_type'] == 'Headline'].head(8).iterrows())} | |
Descriptions ({len(low_performers[low_performers['asset_type'] == 'Description'])} assets): | |
{chr(10).join(f"• {row['asset']} [VP: {row.get('value_proposition', 'N/A')}, ET: {row.get('emotional_trigger', 'N/A')}, CTA: {row.get('cta_type', 'N/A')}]" for _, row in low_performers[low_performers['asset_type'] == 'Description'].head(6).iterrows())} | |
**PERFORMANCE METRICS:** | |
High performers - Avg CTR: {high_performers['ctr'].mean():.2%}, Avg Conversions: {high_performers['conversions'].mean():.1f} | |
Low performers - Avg CTR: {low_performers['ctr'].mean():.2%}, Avg Conversions: {low_performers['conversions'].mean():.1f} | |
Provide actionable insights about what creative types and approaches work best. | |
Return as JSON: | |
{{ | |
"winning_creative_types": {{ | |
"value_propositions": ["most effective value prop types"], | |
"emotional_triggers": ["most effective emotional triggers"], | |
"cta_types": ["most effective call-to-action types"], | |
"messaging_styles": ["most effective messaging styles"] | |
}}, | |
"avoid_creative_types": {{ | |
"value_propositions": ["value prop types to avoid"], | |
"emotional_triggers": ["emotional triggers to avoid"], | |
"cta_types": ["cta types to avoid"], | |
"messaging_styles": ["messaging styles to avoid"] | |
}}, | |
"key_insights": "Main insights about what makes creative effective in this campaign", | |
"creative_recommendations": "Specific recommendations for new creative based on this analysis", | |
"top_performing_examples": {{ | |
"headlines": ["best headline examples"], | |
"descriptions": ["best description examples"] | |
}}, | |
"suggested_target_audience": "Suggested target audience description for new creative generation", | |
"suggested_brief": "Suggested additional context/brief for new creative generation" | |
}} | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[{"role": "user", "content": prompt}], | |
response_format={"type": "json_object"} | |
) | |
import json | |
try: | |
insights = json.loads(response.choices[0].message.content) | |
logger.info("Generated creative insights based on asset classifications") | |
return insights | |
except json.JSONDecodeError: | |
logger.error("Failed to parse creative insights response") | |
return {"insights": "Error parsing insights response"} | |
except Exception as e: | |
logger.error(f"Error generating creative insights: {e}") | |
return {"insights": f"Error generating insights: {str(e)}"} | |
def generate_new_creatives_with_insights(self, asset_data, creative_insights, target_audience, additional_context, | |
use_categories=['Best', 'Good'], num_headlines=30, num_descriptions=30): | |
""" | |
Generate new creatives based on insights and high-performing assets. | |
Args: | |
asset_data: DataFrame with asset data and classifications | |
creative_insights: Insights from creative analysis | |
target_audience: Target audience description | |
additional_context: Additional context/brief | |
use_categories: Performance categories to use as input | |
num_headlines: Number of headlines to generate | |
num_descriptions: Number of descriptions to generate | |
Returns: | |
Dictionary with generated creative and reasoning | |
""" | |
try: | |
# Filter for assets in specified categories | |
input_assets = asset_data[asset_data['performance_category'].isin(use_categories)] | |
if input_assets.empty: | |
logger.warning("No assets found in specified categories, using all assets") | |
input_assets = asset_data | |
# Extract successful patterns | |
successful_headlines = input_assets[input_assets['asset_type'] == 'Headline']['asset'].tolist() | |
successful_descriptions = input_assets[input_assets['asset_type'] == 'Description']['asset'].tolist() | |
# Get insights from creative analysis | |
winning_types = creative_insights.get('winning_creative_types', {}) | |
recommendations = creative_insights.get('creative_recommendations', '') | |
key_insights = creative_insights.get('key_insights', '') | |
top_examples = creative_insights.get('top_performing_examples', {}) | |
prompt = f""" | |
Generate {num_headlines} new headlines and {num_descriptions} new descriptions for Google Ads based on proven successful patterns. | |
**TARGET AUDIENCE:** {target_audience} | |
**ADDITIONAL CONTEXT:** {additional_context} | |
**SUCCESSFUL CREATIVE PATTERNS TO FOLLOW:** | |
Winning Value Propositions: {', '.join(winning_types.get('value_propositions', []))} | |
Winning Emotional Triggers: {', '.join(winning_types.get('emotional_triggers', []))} | |
Winning CTA Types: {', '.join(winning_types.get('cta_types', []))} | |
Winning Messaging Styles: {', '.join(winning_types.get('messaging_styles', []))} | |
**TOP PERFORMING EXAMPLES:** | |
Headlines: {' | '.join(top_examples.get('headlines', [])[:5])} | |
Descriptions: {' | '.join(top_examples.get('descriptions', [])[:3])} | |
**SUCCESSFUL ASSET PATTERNS:** | |
Successful Headlines ({len(successful_headlines)} examples): | |
{chr(10).join(f"• {h}" for h in successful_headlines[:15])} | |
Successful Descriptions ({len(successful_descriptions)} examples): | |
{chr(10).join(f"• {d}" for d in successful_descriptions[:10])} | |
**KEY INSIGHTS TO APPLY:** | |
{key_insights} | |
**CREATIVE RECOMMENDATIONS:** | |
{recommendations} | |
**REQUIREMENTS:** | |
- Generate exactly {num_headlines} unique headlines (max 30 characters each) | |
- Generate exactly {num_descriptions} unique descriptions (max 90 characters each) | |
- Follow the winning patterns identified in the analysis | |
- Use successful value propositions, emotional triggers, and CTAs | |
- Maintain relevance to: {target_audience} | |
- Incorporate: {additional_context} | |
- Ensure variety while staying true to what works | |
Return as JSON: | |
{{ | |
"headlines": ["{num_headlines} headlines here"], | |
"descriptions": ["{num_descriptions} descriptions here"], | |
"strategy_summary": "Brief summary of the creative strategy used", | |
"pattern_usage": "How you incorporated the successful patterns", | |
"character_counts": {{ | |
"headlines_avg": "average headline length", | |
"descriptions_avg": "average description length" | |
}} | |
}} | |
Return only the JSON response, no additional text. | |
""" | |
response = self.client.chat.completions.create( | |
model="gpt-4o", | |
messages=[{"role": "user", "content": prompt}], | |
response_format={"type": "json_object"} | |
) | |
import json | |
try: | |
generated_creative = json.loads(response.choices[0].message.content) | |
# Validate we got the right number of items | |
headlines = generated_creative.get('headlines', []) | |
descriptions = generated_creative.get('descriptions', []) | |
logger.info(f"Generated {len(headlines)} headlines and {len(descriptions)} descriptions") | |
return generated_creative | |
except json.JSONDecodeError: | |
logger.error("Failed to parse generated creative response") | |
return self._generate_fallback_creative_with_insights( | |
successful_headlines, successful_descriptions, target_audience, | |
num_headlines, num_descriptions | |
) | |
except Exception as e: | |
logger.error(f"Error generating new creatives with insights: {e}") | |
return self._generate_fallback_creative_with_insights( | |
[], [], target_audience, num_headlines, num_descriptions | |
) | |
def _generate_fallback_creative_with_insights(self, successful_headlines, successful_descriptions, | |
target_audience, num_headlines=30, num_descriptions=30): | |
"""Fallback creative generation when main method fails.""" | |
try: | |
# Create fallback based on successful patterns | |
base_headlines = successful_headlines[:10] if successful_headlines else [ | |
"Get Van Insurance Quote", "Compare Van Insurance", "Save on Van Cover", | |
"Professional Van Insurance", "Quick Van Insurance Quote" | |
] | |
base_descriptions = successful_descriptions[:5] if successful_descriptions else [ | |
"Get competitive van insurance quotes online. Fast and easy.", | |
"Professional van insurance from trusted providers.", | |
"Save money on van insurance. Compare quotes today." | |
] | |
# Generate variations | |
headlines = [] | |
descriptions = [] | |
# Create headline variations | |
for i in range(num_headlines): | |
base_idx = i % len(base_headlines) | |
base = base_headlines[base_idx] | |
variation = f"{base} {i+1}" if i >= len(base_headlines) else base | |
headlines.append(variation[:30]) # Ensure character limit | |
# Create description variations | |
for i in range(num_descriptions): | |
base_idx = i % len(base_descriptions) | |
base = base_descriptions[base_idx] | |
variation = f"{base} Option {i+1}." if i >= len(base_descriptions) else base | |
descriptions.append(variation[:90]) # Ensure character limit | |
return { | |
"headlines": headlines, | |
"descriptions": descriptions, | |
"strategy_summary": "Fallback creative generation based on available patterns", | |
"pattern_usage": "Used successful examples as base for variations", | |
"character_counts": { | |
"headlines_avg": sum(len(h) for h in headlines) / len(headlines), | |
"descriptions_avg": sum(len(d) for d in descriptions) / len(descriptions) | |
} | |
} | |
except Exception as e: | |
logger.error(f"Fallback creative generation failed: {e}") | |
return { | |
"headlines": [f"Van Insurance Quote {i+1}" for i in range(num_headlines)], | |
"descriptions": [f"Get van insurance quote {i+1}. Save money today." for i in range(num_descriptions)], | |
"strategy_summary": "Basic fallback creative", | |
"pattern_usage": "No patterns available", | |
"character_counts": {"headlines_avg": 20, "descriptions_avg": 45} | |
} |