""" Meta Description Generation Service AI-powered SEO meta description generator that creates compelling, optimized descriptions for content creators and digital marketers. """ from typing import Dict, Any, List, Optional from datetime import datetime from loguru import logger from ..llm_providers.main_text_generation import llm_text_gen from middleware.logging_middleware import seo_logger class MetaDescriptionService: """Service for generating AI-powered SEO meta descriptions""" def __init__(self): """Initialize the meta description service""" self.service_name = "meta_description_generator" logger.info(f"Initialized {self.service_name}") async def generate_meta_description( self, keywords: List[str], tone: str = "General", search_intent: str = "Informational Intent", language: str = "English", custom_prompt: Optional[str] = None, user_id: Optional[str] = None ) -> Dict[str, Any]: """ Generate AI-powered meta descriptions based on keywords and parameters Args: keywords: List of target keywords tone: Desired tone (General, Informative, Engaging, etc.) search_intent: Type of search intent language: Target language for generation custom_prompt: Optional custom prompt override Returns: Dictionary containing generated meta descriptions and analysis """ try: start_time = datetime.utcnow() # Input validation if not keywords or len(keywords) == 0: raise ValueError("At least one keyword is required") # Prepare keywords string keywords_str = ", ".join(keywords[:10]) # Limit to 10 keywords # Build the generation prompt if custom_prompt: prompt = custom_prompt else: prompt = self._build_meta_description_prompt( keywords_str, tone, search_intent, language ) # Generate meta descriptions using AI logger.info(f"Generating meta descriptions for keywords: {keywords_str}") ai_response = llm_text_gen( prompt=prompt, system_prompt=self._get_system_prompt(language), user_id=user_id ) # Parse and structure the response meta_descriptions = self._parse_ai_response(ai_response) # Analyze generated descriptions analysis = self._analyze_meta_descriptions(meta_descriptions, keywords) execution_time = (datetime.utcnow() - start_time).total_seconds() result = { "meta_descriptions": meta_descriptions, "analysis": analysis, "generation_params": { "keywords": keywords, "tone": tone, "search_intent": search_intent, "language": language, "keywords_count": len(keywords) }, "ai_model_info": { "provider": "gemini", "model": "gemini-2.0-flash-001", "prompt_length": len(prompt), "response_length": len(ai_response) }, "execution_time": execution_time, "timestamp": datetime.utcnow().isoformat() } # Log the operation await seo_logger.log_tool_usage( tool_name=self.service_name, input_data={ "keywords": keywords, "tone": tone, "search_intent": search_intent, "language": language }, output_data=result, success=True ) await seo_logger.log_ai_analysis( tool_name=self.service_name, prompt=prompt, response=ai_response, model_used="gemini-2.0-flash-001" ) logger.info(f"Successfully generated {len(meta_descriptions)} meta descriptions") return result except Exception as e: logger.error(f"Error generating meta descriptions: {e}") # Log the error await seo_logger.log_tool_usage( tool_name=self.service_name, input_data={ "keywords": keywords, "tone": tone, "search_intent": search_intent, "language": language }, output_data={"error": str(e)}, success=False ) raise def _build_meta_description_prompt( self, keywords: str, tone: str, search_intent: str, language: str ) -> str: """Build the AI prompt for meta description generation""" intent_guidance = { "Informational Intent": "Focus on providing value and answering questions", "Commercial Intent": "Emphasize benefits and competitive advantages", "Transactional Intent": "Include strong calls-to-action and urgency", "Navigational Intent": "Highlight brand recognition and specific page content" } tone_guidance = { "General": "balanced and professional", "Informative": "educational and authoritative", "Engaging": "compelling and conversational", "Humorous": "light-hearted and memorable", "Intriguing": "mysterious and curiosity-driven", "Playful": "fun and energetic" } prompt = f""" Create 5 compelling SEO meta descriptions for content targeting these keywords: {keywords} Requirements: - Length: 150-160 characters (optimal for search results) - Language: {language} - Tone: {tone_guidance.get(tone, tone)} - Search Intent: {search_intent} - {intent_guidance.get(search_intent, "")} - Include primary keywords naturally - Create urgency or curiosity where appropriate - Ensure each description is unique and actionable Guidelines for effective meta descriptions: 1. Start with action words or emotional triggers 2. Include primary keyword in first 120 characters 3. Add value proposition or benefit 4. Use active voice 5. Consider including numbers or specific details 6. End with compelling reason to click Please provide 5 different meta descriptions, each on a new line, numbered 1-5. Focus on creating descriptions that will improve click-through rates for content creators and digital marketers. """ return prompt def _get_system_prompt(self, language: str) -> str: """Get system prompt for meta description generation""" return f"""You are an expert SEO copywriter specializing in meta descriptions that drive high click-through rates. You understand search engine optimization, user psychology, and compelling copywriting. Your goal is to create meta descriptions that: - Accurately represent the content - Entice users to click - Include target keywords naturally - Comply with search engine best practices - Appeal to the target audience Language: {language} Always provide exactly 5 unique meta descriptions as requested, numbered 1-5. """ def _parse_ai_response(self, ai_response: str) -> List[Dict[str, Any]]: """Parse AI response into structured meta descriptions""" descriptions = [] lines = ai_response.strip().split('\n') current_desc = "" for line in lines: line = line.strip() if not line: continue # Check if line starts with a number (1., 2., etc.) if line and (line[0].isdigit() or line.startswith(('1.', '2.', '3.', '4.', '5.'))): if current_desc: # Process previous description cleaned_desc = self._clean_description(current_desc) if cleaned_desc: descriptions.append(self._analyze_single_description(cleaned_desc)) # Start new description current_desc = line else: # Continue current description if current_desc: current_desc += " " + line # Process last description if current_desc: cleaned_desc = self._clean_description(current_desc) if cleaned_desc: descriptions.append(self._analyze_single_description(cleaned_desc)) # If parsing failed, create fallback descriptions if not descriptions: descriptions = self._create_fallback_descriptions(ai_response) return descriptions[:5] # Ensure max 5 descriptions def _clean_description(self, description: str) -> str: """Clean and format a meta description""" # Remove numbering cleaned = description if cleaned and cleaned[0].isdigit(): # Remove "1. ", "2. ", etc. cleaned = cleaned.split('.', 1)[-1].strip() # Remove extra whitespace cleaned = ' '.join(cleaned.split()) # Remove quotes if present if cleaned.startswith('"') and cleaned.endswith('"'): cleaned = cleaned[1:-1] return cleaned def _analyze_single_description(self, description: str) -> Dict[str, Any]: """Analyze a single meta description""" char_count = len(description) word_count = len(description.split()) # Check if length is optimal length_status = "optimal" if 150 <= char_count <= 160 else \ "short" if char_count < 150 else "long" return { "text": description, "character_count": char_count, "word_count": word_count, "length_status": length_status, "seo_score": self._calculate_seo_score(description, char_count), "recommendations": self._generate_recommendations(description, char_count) } def _calculate_seo_score(self, description: str, char_count: int) -> int: """Calculate SEO score for a meta description""" score = 0 # Length scoring (40 points max) if 150 <= char_count <= 160: score += 40 elif 140 <= char_count <= 170: score += 30 elif 130 <= char_count <= 180: score += 20 else: score += 10 # Action words (20 points max) action_words = ['discover', 'learn', 'get', 'find', 'explore', 'unlock', 'master', 'boost', 'improve', 'achieve'] if any(word.lower() in description.lower() for word in action_words): score += 20 # Numbers or specifics (15 points max) if any(char.isdigit() for char in description): score += 15 # Emotional triggers (15 points max) emotional_words = ['amazing', 'incredible', 'proven', 'secret', 'ultimate', 'essential', 'exclusive', 'free'] if any(word.lower() in description.lower() for word in emotional_words): score += 15 # Call to action (10 points max) cta_phrases = ['click', 'read more', 'learn more', 'discover', 'find out', 'see how'] if any(phrase.lower() in description.lower() for phrase in cta_phrases): score += 10 return min(score, 100) # Cap at 100 def _generate_recommendations(self, description: str, char_count: int) -> List[str]: """Generate recommendations for improving meta description""" recommendations = [] if char_count < 150: recommendations.append("Consider adding more detail to reach optimal length (150-160 characters)") elif char_count > 160: recommendations.append("Shorten description to fit within optimal length (150-160 characters)") if not any(char.isdigit() for char in description): recommendations.append("Consider adding specific numbers or statistics for better appeal") action_words = ['discover', 'learn', 'get', 'find', 'explore', 'unlock', 'master', 'boost', 'improve', 'achieve'] if not any(word.lower() in description.lower() for word in action_words): recommendations.append("Add action words to create urgency and encourage clicks") if description.count(',') > 2: recommendations.append("Simplify sentence structure for better readability") return recommendations def _analyze_meta_descriptions(self, descriptions: List[Dict[str, Any]], keywords: List[str]) -> Dict[str, Any]: """Analyze all generated meta descriptions""" if not descriptions: return {"error": "No descriptions generated"} # Calculate overall statistics avg_length = sum(desc["character_count"] for desc in descriptions) / len(descriptions) avg_score = sum(desc["seo_score"] for desc in descriptions) / len(descriptions) # Find best description best_desc = max(descriptions, key=lambda x: x["seo_score"]) # Keyword coverage analysis keyword_coverage = self._analyze_keyword_coverage(descriptions, keywords) return { "total_descriptions": len(descriptions), "average_length": round(avg_length, 1), "average_seo_score": round(avg_score, 1), "best_description": best_desc, "keyword_coverage": keyword_coverage, "length_distribution": { "optimal": len([d for d in descriptions if d["length_status"] == "optimal"]), "short": len([d for d in descriptions if d["length_status"] == "short"]), "long": len([d for d in descriptions if d["length_status"] == "long"]) } } def _analyze_keyword_coverage(self, descriptions: List[Dict[str, Any]], keywords: List[str]) -> Dict[str, Any]: """Analyze how well keywords are covered in descriptions""" coverage_stats = {} for keyword in keywords: coverage_count = sum( 1 for desc in descriptions if keyword.lower() in desc["text"].lower() ) coverage_stats[keyword] = { "covered_count": coverage_count, "coverage_percentage": (coverage_count / len(descriptions)) * 100 } return coverage_stats def _create_fallback_descriptions(self, ai_response: str) -> List[Dict[str, Any]]: """Create fallback descriptions if parsing fails""" # Split response into sentences and use first few as descriptions sentences = ai_response.split('. ') descriptions = [] for i, sentence in enumerate(sentences[:5]): if len(sentence.strip()) > 50: # Minimum length check desc_text = sentence.strip() if not desc_text.endswith('.'): desc_text += '.' descriptions.append(self._analyze_single_description(desc_text)) return descriptions async def health_check(self) -> Dict[str, Any]: """Health check for the meta description service""" try: # Test basic functionality test_result = await self.generate_meta_description( keywords=["test"], tone="General", search_intent="Informational Intent", language="English" ) return { "status": "operational", "service": self.service_name, "test_passed": bool(test_result.get("meta_descriptions")), "last_check": datetime.utcnow().isoformat() } except Exception as e: return { "status": "error", "service": self.service_name, "error": str(e), "last_check": datetime.utcnow().isoformat() }