story writer backend migration complete, Blog writer SEO and story writer backend migration complete, Blog writer SEO and story writer frontend migration complete
This commit is contained in:
186
backend/services/blog_writer/content/introduction_generator.py
Normal file
186
backend/services/blog_writer/content/introduction_generator.py
Normal file
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
Introduction Generator - Generates varied blog introductions based on content and research.
|
||||
|
||||
Generates 3 different introduction options for the user to choose from.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List
|
||||
from loguru import logger
|
||||
|
||||
from models.blog_models import BlogResearchResponse, BlogOutlineSection
|
||||
|
||||
|
||||
class IntroductionGenerator:
|
||||
"""Generates blog introductions using research and content data."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the introduction generator."""
|
||||
pass
|
||||
|
||||
def build_introduction_prompt(
|
||||
self,
|
||||
blog_title: str,
|
||||
research: BlogResearchResponse,
|
||||
outline: List[BlogOutlineSection],
|
||||
sections_content: Dict[str, str],
|
||||
primary_keywords: List[str],
|
||||
search_intent: str
|
||||
) -> str:
|
||||
"""Build a prompt for generating blog introductions."""
|
||||
|
||||
# Extract key research insights
|
||||
keyword_analysis = research.keyword_analysis or {}
|
||||
content_angles = research.suggested_angles or []
|
||||
|
||||
# Get a summary of the first few sections for context
|
||||
section_summaries = []
|
||||
for i, section in enumerate(outline[:3], 1):
|
||||
section_id = section.id
|
||||
content = sections_content.get(section_id, '')
|
||||
if content:
|
||||
# Take first 200 chars as summary
|
||||
summary = content[:200] + '...' if len(content) > 200 else content
|
||||
section_summaries.append(f"{i}. {section.heading}: {summary}")
|
||||
|
||||
sections_text = '\n'.join(section_summaries) if section_summaries else "Content sections are being generated."
|
||||
|
||||
primary_kw_text = ', '.join(primary_keywords) if primary_keywords else "the topic"
|
||||
content_angle_text = ', '.join(content_angles[:3]) if content_angles else "General insights"
|
||||
|
||||
return f"""Generate exactly 3 varied blog introductions for the following blog post.
|
||||
|
||||
BLOG TITLE: {blog_title}
|
||||
|
||||
PRIMARY KEYWORDS: {primary_kw_text}
|
||||
SEARCH INTENT: {search_intent}
|
||||
CONTENT ANGLES: {content_angle_text}
|
||||
|
||||
BLOG CONTENT SUMMARY:
|
||||
{sections_text}
|
||||
|
||||
REQUIREMENTS FOR EACH INTRODUCTION:
|
||||
- 80-120 words in length
|
||||
- Hook the reader immediately with a compelling opening
|
||||
- Clearly state the value proposition and what readers will learn
|
||||
- Include the primary keyword naturally within the first 2 sentences
|
||||
- Each introduction should have a different angle/approach:
|
||||
1. First: Problem-focused (highlight the challenge readers face)
|
||||
2. Second: Benefit-focused (emphasize the value and outcomes)
|
||||
3. Third: Story/statistic-focused (use a compelling fact or narrative hook)
|
||||
- Maintain a professional yet engaging tone
|
||||
- Avoid generic phrases - be specific and benefit-driven
|
||||
|
||||
Return ONLY a JSON array of exactly 3 introductions:
|
||||
[
|
||||
"First introduction (80-120 words, problem-focused)",
|
||||
"Second introduction (80-120 words, benefit-focused)",
|
||||
"Third introduction (80-120 words, story/statistic-focused)"
|
||||
]"""
|
||||
|
||||
def get_introduction_schema(self) -> Dict[str, Any]:
|
||||
"""Get the JSON schema for introduction generation."""
|
||||
return {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 80,
|
||||
"maxLength": 150
|
||||
},
|
||||
"minItems": 3,
|
||||
"maxItems": 3
|
||||
}
|
||||
|
||||
async def generate_introductions(
|
||||
self,
|
||||
blog_title: str,
|
||||
research: BlogResearchResponse,
|
||||
outline: List[BlogOutlineSection],
|
||||
sections_content: Dict[str, str],
|
||||
primary_keywords: List[str],
|
||||
search_intent: str,
|
||||
user_id: str
|
||||
) -> List[str]:
|
||||
"""Generate 3 varied blog introductions.
|
||||
|
||||
Args:
|
||||
blog_title: The blog post title
|
||||
research: Research data with keywords and insights
|
||||
outline: Blog outline sections
|
||||
sections_content: Dictionary mapping section IDs to their content
|
||||
primary_keywords: Primary keywords for the blog
|
||||
search_intent: Search intent (informational, commercial, etc.)
|
||||
user_id: User ID for API calls
|
||||
|
||||
Returns:
|
||||
List of 3 introduction options
|
||||
"""
|
||||
from services.llm_providers.main_text_generation import llm_text_gen
|
||||
|
||||
if not user_id:
|
||||
raise ValueError("user_id is required for introduction generation")
|
||||
|
||||
# Build prompt
|
||||
prompt = self.build_introduction_prompt(
|
||||
blog_title=blog_title,
|
||||
research=research,
|
||||
outline=outline,
|
||||
sections_content=sections_content,
|
||||
primary_keywords=primary_keywords,
|
||||
search_intent=search_intent
|
||||
)
|
||||
|
||||
# Get schema
|
||||
schema = self.get_introduction_schema()
|
||||
|
||||
logger.info(f"Generating blog introductions for user {user_id}")
|
||||
|
||||
try:
|
||||
# Generate introductions using structured JSON response
|
||||
result = llm_text_gen(
|
||||
prompt=prompt,
|
||||
json_struct=schema,
|
||||
system_prompt="You are an expert content writer specializing in creating compelling blog introductions that hook readers and clearly communicate value.",
|
||||
user_id=user_id
|
||||
)
|
||||
|
||||
# Handle response - could be array directly or wrapped in dict
|
||||
if isinstance(result, list):
|
||||
introductions = result
|
||||
elif isinstance(result, dict):
|
||||
# Try common keys
|
||||
introductions = result.get('introductions', result.get('options', result.get('intros', [])))
|
||||
if not introductions and isinstance(result.get('response'), list):
|
||||
introductions = result['response']
|
||||
else:
|
||||
logger.warning(f"Unexpected introduction generation result type: {type(result)}")
|
||||
introductions = []
|
||||
|
||||
# Validate and clean introductions
|
||||
cleaned_introductions = []
|
||||
for intro in introductions:
|
||||
if isinstance(intro, str) and len(intro.strip()) >= 50: # Minimum reasonable length
|
||||
cleaned = intro.strip()
|
||||
# Ensure it's within reasonable bounds
|
||||
if len(cleaned) <= 200: # Allow slight overflow for quality
|
||||
cleaned_introductions.append(cleaned)
|
||||
|
||||
# Ensure we have exactly 3 introductions
|
||||
if len(cleaned_introductions) < 3:
|
||||
logger.warning(f"Generated only {len(cleaned_introductions)} introductions, expected 3")
|
||||
# Pad with placeholder if needed
|
||||
while len(cleaned_introductions) < 3:
|
||||
cleaned_introductions.append(f"{blog_title} - A comprehensive guide covering essential insights and practical strategies.")
|
||||
|
||||
# Return exactly 3 introductions
|
||||
return cleaned_introductions[:3]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to generate introductions: {e}")
|
||||
# Fallback: generate simple introductions
|
||||
fallback_introductions = [
|
||||
f"In this comprehensive guide, we'll explore {primary_keywords[0] if primary_keywords else 'essential insights'} and provide actionable strategies.",
|
||||
f"Discover everything you need to know about {primary_keywords[0] if primary_keywords else 'this topic'} and how it can transform your approach.",
|
||||
f"Whether you're new to {primary_keywords[0] if primary_keywords else 'this topic'} or looking to deepen your understanding, this guide has you covered."
|
||||
]
|
||||
return fallback_introductions
|
||||
|
||||
Reference in New Issue
Block a user