Skip to content

Commit 5fd2d0b

Browse files
committed
feat: Add sample content generation script
- Standalone script to demonstrate full content generation workflow - Uses ContentOrchestrator to generate text, images, and compliance checks - Supports custom briefs and products via JSON files - Includes sample brief and product data for testing
1 parent a888b78 commit 5fd2d0b

1 file changed

Lines changed: 361 additions & 0 deletions

File tree

Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Sample Content Generation Script
4+
5+
This script demonstrates how to use the ContentOrchestrator to generate
6+
complete marketing content packages including text and images.
7+
8+
Prerequisites:
9+
1. Set up environment variables (or use a .env file):
10+
- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint
11+
- AZURE_OPENAI_GPT_MODEL: GPT model deployment name
12+
- AZURE_OPENAI_GPT_IMAGE_ENDPOINT: (Optional) Endpoint for images
13+
- AZURE_OPENAI_IMAGE_MODEL: Image model name (e.g., gpt-image-1)
14+
- AZURE_COSMOS_ENDPOINT: Your CosmosDB endpoint
15+
- AZURE_COSMOS_DATABASE_NAME: content-generation
16+
- AZURE_COSMOS_CONVERSATIONS_CONTAINER: conversations
17+
18+
2. Ensure you have RBAC access:
19+
- "Cognitive Services OpenAI User" role on the Azure OpenAI resource
20+
- "Cosmos DB Built-in Data Contributor" on CosmosDB (if using products)
21+
22+
Usage:
23+
python sample_content_generation.py
24+
python sample_content_generation.py --no-images
25+
python sample_content_generation.py --output results.json
26+
"""
27+
28+
import asyncio
29+
import argparse
30+
import json
31+
import os
32+
import sys
33+
from datetime import datetime
34+
from pathlib import Path
35+
36+
# Add the backend directory to the path
37+
backend_path = Path(__file__).parent.parent / "src" / "backend"
38+
sys.path.insert(0, str(backend_path))
39+
40+
# Now import the orchestrator and models
41+
from orchestrator import ContentOrchestrator
42+
from models import CreativeBrief
43+
44+
45+
def create_sample_brief() -> CreativeBrief:
46+
"""Create a sample creative brief for testing."""
47+
return CreativeBrief(
48+
overview="Spring home refresh campaign promoting interior paint colors",
49+
objectives="Drive awareness and consideration for spring paint collection, increase website traffic by 20%",
50+
target_audience="Homeowners aged 30-55, interested in DIY home improvement and interior design",
51+
key_message="Transform your home this spring with our fresh, on-trend paint colors that bring warmth and style to any room",
52+
tone_and_style="Inspiring, approachable, and aspirational. Use warm, inviting language that speaks to the joy of home improvement",
53+
deliverable="Social media carousel post (3-5 images) with captions for Instagram and Facebook",
54+
timelines="Launch by March 15th for spring campaign",
55+
visual_guidelines="Bright, airy rooms with natural lighting. Show before/after transformations. Feature paint swatches in context. Modern, clean aesthetic",
56+
cta="Shop the Spring Collection - Visit our website for color inspiration",
57+
raw_input="Sample brief for testing content generation"
58+
)
59+
60+
61+
def create_sample_products() -> list:
62+
"""Create sample product data for testing."""
63+
return [
64+
{
65+
"id": "sample-1",
66+
"product_name": "Morning Mist",
67+
"description": "A soft, ethereal blue-gray that evokes early morning calm. Perfect for bedrooms and living spaces.",
68+
"tags": "blue, gray, soft, calming, bedroom, living room",
69+
"price": 45.99,
70+
"image_url": "https://example.com/morning-mist.jpg"
71+
},
72+
{
73+
"id": "sample-2",
74+
"product_name": "Sunlit Meadow",
75+
"description": "A warm, golden yellow that brings sunshine indoors. Ideal for kitchens and breakfast nooks.",
76+
"tags": "yellow, warm, sunny, kitchen, cheerful",
77+
"price": 42.99,
78+
"image_url": "https://example.com/sunlit-meadow.jpg"
79+
},
80+
{
81+
"id": "sample-3",
82+
"product_name": "Forest Haven",
83+
"description": "A rich, deep green inspired by lush forest canopies. Creates a sophisticated, grounding atmosphere.",
84+
"tags": "green, deep, sophisticated, nature, accent wall",
85+
"price": 48.99,
86+
"image_url": "https://example.com/forest-haven.jpg"
87+
}
88+
]
89+
90+
91+
async def generate_content_sample(
92+
brief: CreativeBrief = None,
93+
products: list = None,
94+
generate_images: bool = True,
95+
output_path: str = None
96+
) -> dict:
97+
"""
98+
Generate a complete content package using the orchestrator.
99+
100+
Args:
101+
brief: Creative brief (uses sample if not provided)
102+
products: Products to feature (uses samples if not provided)
103+
generate_images: Whether to generate images
104+
output_path: Path to save results JSON (optional)
105+
106+
Returns:
107+
Dictionary with generation results
108+
"""
109+
# Use defaults if not provided
110+
brief = brief or create_sample_brief()
111+
products = products or create_sample_products()
112+
113+
print(f"\n{'='*70}")
114+
print("CONTENT GENERATION SAMPLE")
115+
print(f"{'='*70}")
116+
print(f"\nCreative Brief Overview: {brief.overview}")
117+
print(f"Target Audience: {brief.target_audience}")
118+
print(f"Deliverable: {brief.deliverable}")
119+
print(f"Products: {len(products)} items")
120+
print(f"Generate Images: {generate_images}")
121+
122+
print(f"\n{'='*70}")
123+
print("Initializing Content Orchestrator...")
124+
print(f"{'='*70}\n")
125+
126+
# Create and initialize the orchestrator
127+
orchestrator = ContentOrchestrator()
128+
orchestrator.initialize()
129+
130+
print("Orchestrator initialized successfully!")
131+
print("\nGenerating content...\n")
132+
133+
# Generate the content
134+
start_time = datetime.now()
135+
136+
results = await orchestrator.generate_content(
137+
brief=brief,
138+
products=products,
139+
generate_images=generate_images
140+
)
141+
142+
elapsed = (datetime.now() - start_time).total_seconds()
143+
144+
# Display results
145+
print(f"\n{'='*70}")
146+
print("GENERATION RESULTS")
147+
print(f"{'='*70}")
148+
print(f"\nTime elapsed: {elapsed:.1f} seconds")
149+
150+
# Text content
151+
if results.get("text_content"):
152+
print(f"\n{'─'*40}")
153+
print("TEXT CONTENT:")
154+
print(f"{'─'*40}")
155+
text_content = results["text_content"]
156+
# Show first 1000 chars
157+
if len(text_content) > 1000:
158+
print(text_content[:1000] + "\n\n[...truncated...]")
159+
else:
160+
print(text_content)
161+
162+
# Image prompt
163+
if results.get("image_prompt"):
164+
print(f"\n{'─'*40}")
165+
print("IMAGE PROMPT:")
166+
print(f"{'─'*40}")
167+
print(results["image_prompt"][:500] + "..." if len(results.get("image_prompt", "")) > 500 else results["image_prompt"])
168+
169+
# Generated image
170+
if results.get("generated_image"):
171+
print(f"\n{'─'*40}")
172+
print("GENERATED IMAGE:")
173+
print(f"{'─'*40}")
174+
img = results["generated_image"]
175+
if img.get("success"):
176+
print(f"✅ Image generated successfully")
177+
print(f" Model: {img.get('model', 'unknown')}")
178+
if img.get("image_base64"):
179+
print(f" Data size: {len(img['image_base64']) / 1024:.1f} KB (base64)")
180+
else:
181+
print(f"❌ Image generation failed: {img.get('error', 'unknown error')}")
182+
183+
# Compliance
184+
if results.get("compliance"):
185+
print(f"\n{'─'*40}")
186+
print("COMPLIANCE CHECK:")
187+
print(f"{'─'*40}")
188+
print(results["compliance"])
189+
190+
if results.get("violations"):
191+
print(f"\n⚠️ Violations found: {len(results['violations'])}")
192+
for v in results["violations"]:
193+
print(f" - {v}")
194+
195+
if results.get("requires_modification"):
196+
print("\n⚠️ Content requires modification before publishing")
197+
198+
# Save results if output path provided
199+
if output_path:
200+
# Prepare results for JSON serialization
201+
output_data = {
202+
"timestamp": datetime.now().isoformat(),
203+
"elapsed_seconds": elapsed,
204+
"brief": brief.model_dump(),
205+
"products": products,
206+
"generate_images": generate_images,
207+
"results": {
208+
"text_content": results.get("text_content"),
209+
"image_prompt": results.get("image_prompt"),
210+
"compliance": results.get("compliance"),
211+
"violations": results.get("violations"),
212+
"requires_modification": results.get("requires_modification"),
213+
}
214+
}
215+
216+
# Handle generated image separately (base64 can be large)
217+
if results.get("generated_image"):
218+
img = results["generated_image"]
219+
output_data["results"]["generated_image"] = {
220+
"success": img.get("success"),
221+
"model": img.get("model"),
222+
"error": img.get("error"),
223+
"revised_prompt": img.get("revised_prompt"),
224+
# Store base64 in separate file to keep JSON readable
225+
"has_image_data": bool(img.get("image_base64"))
226+
}
227+
228+
# Save image to separate file if successful
229+
if img.get("success") and img.get("image_base64"):
230+
import base64
231+
image_path = output_path.replace(".json", "_image.png")
232+
image_data = base64.b64decode(img["image_base64"])
233+
with open(image_path, "wb") as f:
234+
f.write(image_data)
235+
output_data["results"]["generated_image"]["image_file"] = image_path
236+
print(f"\n📁 Image saved to: {image_path}")
237+
238+
with open(output_path, "w") as f:
239+
json.dump(output_data, f, indent=2)
240+
print(f"📁 Results saved to: {output_path}")
241+
242+
return results
243+
244+
245+
async def main():
246+
"""Main entry point for the sample script."""
247+
parser = argparse.ArgumentParser(
248+
description="Generate marketing content using the Content Orchestrator"
249+
)
250+
parser.add_argument(
251+
"--no-images",
252+
action="store_true",
253+
help="Skip image generation"
254+
)
255+
parser.add_argument(
256+
"--output", "-o",
257+
type=str,
258+
default=None,
259+
help="Output file path for results JSON"
260+
)
261+
parser.add_argument(
262+
"--brief-file",
263+
type=str,
264+
default=None,
265+
help="Path to JSON file containing creative brief"
266+
)
267+
parser.add_argument(
268+
"--products-file",
269+
type=str,
270+
default=None,
271+
help="Path to JSON file containing products list"
272+
)
273+
274+
args = parser.parse_args()
275+
276+
# Load brief from file if provided
277+
brief = None
278+
if args.brief_file:
279+
with open(args.brief_file, "r") as f:
280+
brief_data = json.load(f)
281+
brief = CreativeBrief(**brief_data)
282+
283+
# Load products from file if provided
284+
products = None
285+
if args.products_file:
286+
with open(args.products_file, "r") as f:
287+
products = json.load(f)
288+
289+
# Generate content
290+
try:
291+
result = await generate_content_sample(
292+
brief=brief,
293+
products=products,
294+
generate_images=not args.no_images,
295+
output_path=args.output
296+
)
297+
298+
print(f"\n{'='*70}")
299+
print("✅ Content generation completed!")
300+
print(f"{'='*70}\n")
301+
302+
sys.exit(0)
303+
304+
except Exception as e:
305+
print(f"\n❌ Error during content generation: {e}")
306+
import traceback
307+
traceback.print_exc()
308+
sys.exit(1)
309+
310+
311+
# Example: Custom brief and products
312+
async def custom_example():
313+
"""Example with custom brief and products."""
314+
315+
# Custom brief for a summer promotion
316+
brief = CreativeBrief(
317+
overview="Summer outdoor living promotion for patio furniture",
318+
objectives="Increase summer patio sales by 30%, drive foot traffic to showrooms",
319+
target_audience="Suburban homeowners, 35-60, with outdoor entertaining spaces",
320+
key_message="Create your perfect outdoor oasis with our premium patio collection",
321+
tone_and_style="Relaxed, aspirational, lifestyle-focused. Evoke summer gatherings and outdoor relaxation",
322+
deliverable="Email banner and 2 social media posts",
323+
timelines="Launch Memorial Day weekend",
324+
visual_guidelines="Outdoor settings at golden hour, families enjoying patios, lush greenery, modern outdoor furniture",
325+
cta="Explore the Summer Collection - 20% off this weekend only"
326+
)
327+
328+
# Custom products
329+
products = [
330+
{
331+
"id": "patio-1",
332+
"product_name": "Sunset Lounger",
333+
"description": "Premium outdoor chaise lounge with weather-resistant cushions in coastal blue",
334+
"tags": "patio, lounge, outdoor, blue, relaxation",
335+
"price": 599.99
336+
},
337+
{
338+
"id": "patio-2",
339+
"product_name": "Garden Dining Set",
340+
"description": "6-piece aluminum dining set with tempered glass table, perfect for outdoor entertaining",
341+
"tags": "patio, dining, aluminum, entertaining, family",
342+
"price": 1299.99
343+
}
344+
]
345+
346+
results = await generate_content_sample(
347+
brief=brief,
348+
products=products,
349+
generate_images=True,
350+
output_path="custom_content_results.json"
351+
)
352+
353+
return results
354+
355+
356+
if __name__ == "__main__":
357+
# Run the main function
358+
asyncio.run(main())
359+
360+
# Uncomment below to run custom example instead:
361+
# asyncio.run(custom_example())

0 commit comments

Comments
 (0)