764 lines
26 KiB
Python
764 lines
26 KiB
Python
"""
|
||
Prompt Registry - Centralized prompt management with override hierarchy
|
||
Supports: task-level overrides → DB prompts → default fallbacks
|
||
"""
|
||
import logging
|
||
from typing import Dict, Any, Optional
|
||
from django.db import models
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class PromptRegistry:
|
||
"""
|
||
Centralized prompt registry with hierarchical resolution:
|
||
1. Task-level prompt_override (if exists)
|
||
2. DB prompt for (account, function)
|
||
3. Default fallback from registry
|
||
"""
|
||
|
||
# Default prompts stored in registry
|
||
DEFAULT_PROMPTS = {
|
||
'clustering': """You are a semantic strategist and SEO architecture engine. Your task is to analyze the provided keyword list and group them into meaningful, intent-driven topic clusters that reflect how real users search, think, and act online.
|
||
|
||
Return a single JSON object with a "clusters" array. Each cluster must follow this structure:
|
||
|
||
{
|
||
"name": "[Descriptive cluster name — natural, SEO-relevant, clearly expressing the topic]",
|
||
"description": "[1–2 concise sentences explaining what this cluster covers and why these keywords belong together]",
|
||
"keywords": ["keyword 1", "keyword 2", "keyword 3", "..."]
|
||
}
|
||
|
||
CLUSTERING STRATEGY:
|
||
|
||
1. Keyword-first, structure-follows:
|
||
- Do NOT rely on assumed categories or existing content structures.
|
||
- Begin purely from the meaning, intent, and behavioral connection between keywords.
|
||
|
||
2. Use multi-dimensional grouping logic:
|
||
- Group keywords by these behavioral dimensions:
|
||
• Search Intent → informational, commercial, transactional, navigational
|
||
• Use-Case or Problem → what the user is trying to achieve or solve
|
||
• Function or Feature → how something works or what it does
|
||
• Persona or Audience → who the content or product serves
|
||
• Context → location, time, season, platform, or device
|
||
- Combine 2–3 dimensions naturally where they make sense.
|
||
|
||
3. Model real search behavior:
|
||
- Favor clusters that form natural user journeys such as:
|
||
• Problem ➝ Solution
|
||
• General ➝ Specific
|
||
• Product ➝ Use-case
|
||
• Buyer ➝ Benefit
|
||
• Tool ➝ Function
|
||
• Task ➝ Method
|
||
- Each cluster should feel like a real topic hub users would explore in depth.
|
||
|
||
4. Avoid superficial groupings:
|
||
- Do not cluster keywords just because they share words.
|
||
- Do not force-fit outliers or unrelated keywords.
|
||
- Exclude keywords that don't logically connect to any cluster.
|
||
|
||
5. Quality rules:
|
||
- Each cluster should include between 3–10 strongly related keywords.
|
||
- Never duplicate a keyword across multiple clusters.
|
||
- Prioritize semantic strength, search intent, and usefulness for SEO-driven content structure.
|
||
- It's better to output fewer, high-quality clusters than many weak or shallow ones.
|
||
|
||
INPUT FORMAT:
|
||
{
|
||
"keywords": [IGNY8_KEYWORDS]
|
||
}
|
||
|
||
OUTPUT FORMAT:
|
||
Return ONLY the final JSON object in this format:
|
||
{
|
||
"clusters": [
|
||
{
|
||
"name": "...",
|
||
"description": "...",
|
||
"keywords": ["...", "...", "..."]
|
||
}
|
||
]
|
||
}
|
||
|
||
Do not include any explanations, text, or commentary outside the JSON output.
|
||
""",
|
||
|
||
'ideas': """Generate SEO-optimized, high-quality content ideas and outlines for each keyword cluster.
|
||
Input:
|
||
Clusters: [IGNY8_CLUSTERS]
|
||
Keywords: [IGNY8_CLUSTER_KEYWORDS]
|
||
|
||
Output: JSON with "ideas" array.
|
||
Each cluster → 1 cluster_hub + 2–4 supporting ideas.
|
||
Each idea must include:
|
||
title, description, content_type, content_structure, cluster_id, estimated_word_count (1500–2200), and covered_keywords.
|
||
|
||
Outline Rules:
|
||
|
||
Intro: 1 hook (30–40 words) + 2 intro paragraphs (50–60 words each).
|
||
|
||
5–8 H2 sections, each with 2–3 H3s.
|
||
|
||
Each H2 ≈ 250–300 words, mixed content (paragraphs, lists, tables, blockquotes).
|
||
|
||
Vary section format and tone; no bullets or lists at start.
|
||
|
||
Tables have columns; blockquotes = expert POV or data insight.
|
||
|
||
Use depth, examples, and real context.
|
||
|
||
Avoid repetitive structure.
|
||
|
||
Tone: Professional editorial flow. No generic phrasing. Use varied sentence openings and realistic examples.
|
||
|
||
Output JSON Example:
|
||
|
||
{
|
||
"ideas": [
|
||
{
|
||
"title": "Best Organic Cotton Duvet Covers for All Seasons",
|
||
"description": {
|
||
"introduction": {
|
||
"hook": "Transform your sleep with organic cotton that blends comfort and sustainability.",
|
||
"paragraphs": [
|
||
{"format": "paragraph", "details": "Overview of organic cotton's rise in bedding industry."},
|
||
{"format": "paragraph", "details": "Why consumers prefer organic bedding over synthetic alternatives."}
|
||
]
|
||
},
|
||
"H2": [
|
||
{
|
||
"heading": "Why Choose Organic Cotton for Bedding?",
|
||
"subsections": [
|
||
{"subheading": "Health and Skin Benefits", "format": "paragraph", "details": "Discuss hypoallergenic and chemical-free aspects."},
|
||
{"subheading": "Environmental Sustainability", "format": "list", "details": "Eco benefits like low water use, no pesticides."},
|
||
{"subheading": "Long-Term Cost Savings", "format": "table", "details": "Compare durability and pricing over time."}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
"content_type": "post",
|
||
"content_structure": "review",
|
||
"cluster_id": 12,
|
||
"estimated_word_count": 1800,
|
||
"covered_keywords": "organic duvet covers, eco-friendly bedding, sustainable sheets"
|
||
}
|
||
]
|
||
}
|
||
|
||
Valid content_type values: post, page, product, taxonomy
|
||
|
||
Valid content_structure by type:
|
||
- post: article, guide, comparison, review, listicle
|
||
- page: landing_page, business_page, service_page, general, cluster_hub
|
||
- product: product_page
|
||
- taxonomy: category_archive, tag_archive, attribute_archive""",
|
||
|
||
'content_generation': """You are an editorial content strategist. Your task is to generate a complete JSON response object based on the provided content idea, keyword cluster, keyword list, and metadata context.
|
||
|
||
==================
|
||
Generate a complete JSON response object matching this structure:
|
||
==================
|
||
|
||
{
|
||
"title": "[Article title using target keywords — full sentence case]",
|
||
"content": "[HTML content — full editorial structure with <p>, <h2>, <h3>, <ul>, <ol>, <table>]"
|
||
}
|
||
|
||
===========================
|
||
CONTENT FLOW RULES
|
||
===========================
|
||
|
||
**INTRODUCTION:**
|
||
- Start with 1 italicized hook (30–40 words)
|
||
- Follow with 2 narrative paragraphs (each 50–60 words; 2–3 sentences max)
|
||
- No headings allowed in intro
|
||
|
||
**H2 SECTIONS (5–8 total):**
|
||
Each section should be 250–300 words and follow this format:
|
||
1. Two narrative paragraphs (80–120 words each, 2–3 sentences)
|
||
2. One list or table (must come *after* a paragraph)
|
||
3. Optional closing paragraph (40–60 words)
|
||
4. Insert 2–3 subsections naturally after main paragraphs
|
||
|
||
**Formatting Rules:**
|
||
- Vary use of unordered lists, ordered lists, and tables across sections
|
||
- Never begin any section or sub-section with a list or table
|
||
|
||
===========================
|
||
STYLE & QUALITY RULES
|
||
===========================
|
||
|
||
- **Keyword Usage:**
|
||
- Use keywords naturally in title, introduction, and headings
|
||
- Prioritize readability over keyword density
|
||
|
||
- **Tone & style guidelines:**
|
||
- No robotic or passive voice
|
||
- Avoid generic intros like "In today's world…"
|
||
- Don't repeat heading in opening sentence
|
||
- Vary sentence structure and length
|
||
|
||
===========================
|
||
STAGE 3: METADATA CONTEXT (NEW)
|
||
===========================
|
||
|
||
**Content Structure:**
|
||
[IGNY8_CONTENT_STRUCTURE]
|
||
- If structure is "cluster_hub": Create comprehensive, authoritative content that serves as the main resource for this topic cluster. Include overview sections, key concepts, and links to related topics.
|
||
- If structure is "article" or "guide": Create detailed, focused content that dives deep into the topic with actionable insights.
|
||
- Other structures: Follow the appropriate format (listicle, comparison, review, landing_page, service_page, product_page, category_archive, tag_archive, attribute_archive).
|
||
|
||
**Taxonomy Context:**
|
||
[IGNY8_TAXONOMY]
|
||
- Use taxonomy information to structure categories and tags appropriately.
|
||
- Align content with taxonomy hierarchy and relationships.
|
||
- Ensure content fits within the defined taxonomy structure.
|
||
|
||
**Product/Service Attributes:**
|
||
[IGNY8_ATTRIBUTES]
|
||
- If attributes are provided (e.g., product specs, service modifiers), incorporate them naturally into the content.
|
||
- For product content: Include specifications, features, dimensions, materials, etc. as relevant.
|
||
- For service content: Include service tiers, pricing modifiers, availability, etc. as relevant.
|
||
- Present attributes in a user-friendly format (tables, lists, or integrated into narrative).
|
||
|
||
===========================
|
||
INPUT VARIABLES
|
||
===========================
|
||
|
||
CONTENT IDEA DETAILS:
|
||
[IGNY8_IDEA]
|
||
|
||
KEYWORD CLUSTER:
|
||
[IGNY8_CLUSTER]
|
||
|
||
ASSOCIATED KEYWORDS:
|
||
[IGNY8_KEYWORDS]
|
||
|
||
===========================
|
||
OUTPUT FORMAT
|
||
===========================
|
||
|
||
Return ONLY the final JSON object.
|
||
Do NOT include any comments, formatting, or explanations.""",
|
||
|
||
'site_structure_generation': """You are a senior UX architect and information designer. Use the business brief, objectives, style references, and existing site info to propose a complete multi-page marketing website structure.
|
||
|
||
INPUT CONTEXT
|
||
==============
|
||
BUSINESS BRIEF:
|
||
[IGNY8_BUSINESS_BRIEF]
|
||
|
||
PRIMARY OBJECTIVES:
|
||
[IGNY8_OBJECTIVES]
|
||
|
||
STYLE & BRAND NOTES:
|
||
[IGNY8_STYLE]
|
||
|
||
SITE INFO / CURRENT STRUCTURE:
|
||
[IGNY8_SITE_INFO]
|
||
|
||
OUTPUT REQUIREMENTS
|
||
====================
|
||
Return ONE JSON object with the following keys:
|
||
|
||
{
|
||
"site": {
|
||
"name": "...",
|
||
"primary_navigation": ["home", "services", "about", "contact"],
|
||
"secondary_navigation": ["blog", "faq"],
|
||
"hero_message": "High level value statement",
|
||
"tone": "voice + tone summary"
|
||
},
|
||
"pages": [
|
||
{
|
||
"slug": "home",
|
||
"title": "Home",
|
||
"type": "home | about | services | products | blog | contact | custom",
|
||
"status": "draft",
|
||
"objective": "Explain the core brand promise and primary CTA",
|
||
"primary_cta": "Book a strategy call",
|
||
"seo": {
|
||
"meta_title": "...",
|
||
"meta_description": "..."
|
||
},
|
||
"blocks": [
|
||
{
|
||
"type": "hero | features | services | stats | testimonials | faq | contact | custom",
|
||
"heading": "Section headline",
|
||
"subheading": "Support copy",
|
||
"layout": "full-width | two-column | cards | carousel",
|
||
"content": [
|
||
"Bullet or short paragraph describing what to render in this block"
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
|
||
RULES
|
||
=====
|
||
- Include 5–8 pages covering the complete buyer journey (awareness → evaluation → conversion → trust).
|
||
- Every page must have at least 3 blocks with concrete guidance (no placeholders like "Lorem ipsum").
|
||
- Use consistent slug naming, all lowercase with hyphens.
|
||
- Type must match the allowed enum and reflect page intent.
|
||
- Ensure the navigation arrays align with the page list.
|
||
- Focus on practical descriptions that an engineering team can hand off directly to the Site Builder.
|
||
|
||
Return ONLY valid JSON. No commentary, explanations, or Markdown.
|
||
""",
|
||
|
||
'image_prompt_extraction': """Extract image prompts from the following article content.
|
||
|
||
ARTICLE TITLE: {title}
|
||
|
||
ARTICLE CONTENT:
|
||
{content}
|
||
|
||
Extract image prompts for:
|
||
1. Featured Image: One main image that represents the article topic
|
||
2. In-Article Images: Up to {max_images} images that would be useful within the article content
|
||
|
||
Return a JSON object with this structure:
|
||
{{
|
||
"featured_prompt": "Detailed description of the featured image",
|
||
"in_article_prompts": [
|
||
"Description of first in-article image",
|
||
"Description of second in-article image",
|
||
...
|
||
]
|
||
}}
|
||
|
||
Make sure each prompt is detailed enough for image generation, describing the visual elements, style, mood, and composition.""",
|
||
|
||
'image_prompt_template': '{image_type} image for blog post titled "{post_title}": {image_prompt}',
|
||
|
||
'negative_prompt': 'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title',
|
||
|
||
'optimize_content': """You are an expert content optimizer specializing in SEO, readability, and engagement.
|
||
|
||
Your task is to optimize the provided content to improve its SEO score, readability, and engagement metrics.
|
||
|
||
CURRENT CONTENT:
|
||
Title: {CONTENT_TITLE}
|
||
Word Count: {WORD_COUNT}
|
||
Source: {SOURCE}
|
||
Primary Keyword: {PRIMARY_KEYWORD}
|
||
Internal Links: {INTERNAL_LINKS_COUNT}
|
||
|
||
CURRENT META DATA:
|
||
Meta Title: {META_TITLE}
|
||
Meta Description: {META_DESCRIPTION}
|
||
|
||
CURRENT SCORES:
|
||
{CURRENT_SCORES}
|
||
|
||
HTML CONTENT:
|
||
{HTML_CONTENT}
|
||
|
||
OPTIMIZATION REQUIREMENTS:
|
||
|
||
1. SEO Optimization:
|
||
- Ensure meta title is 30-60 characters (if provided)
|
||
- Ensure meta description is 120-160 characters (if provided)
|
||
- Optimize primary keyword usage (natural, not keyword stuffing)
|
||
- Improve heading structure (H1, H2, H3 hierarchy)
|
||
- Add internal links where relevant (maintain existing links)
|
||
|
||
2. Readability:
|
||
- Average sentence length: 15-20 words
|
||
- Use clear, concise language
|
||
- Break up long paragraphs
|
||
- Use bullet points and lists where appropriate
|
||
- Ensure proper paragraph structure
|
||
|
||
3. Engagement:
|
||
- Add compelling headings
|
||
- Include relevant images placeholders (alt text)
|
||
- Use engaging language
|
||
- Create clear call-to-action sections
|
||
- Improve content flow and structure
|
||
|
||
OUTPUT FORMAT:
|
||
Return ONLY a JSON object in this format:
|
||
{{
|
||
"html_content": "[Optimized HTML content]",
|
||
"meta_title": "[Optimized meta title, 30-60 chars]",
|
||
"meta_description": "[Optimized meta description, 120-160 chars]",
|
||
"optimization_notes": "[Brief notes on what was optimized]"
|
||
}}
|
||
|
||
Do not include any explanations, text, or commentary outside the JSON output.
|
||
""",
|
||
|
||
# Phase 8: Universal Content Types
|
||
'product_generation': """You are a product content specialist. Generate comprehensive product content that includes detailed descriptions, features, specifications, pricing, and benefits.
|
||
|
||
INPUT:
|
||
Product Name: [IGNY8_PRODUCT_NAME]
|
||
Product Description: [IGNY8_PRODUCT_DESCRIPTION]
|
||
Product Features: [IGNY8_PRODUCT_FEATURES]
|
||
Target Audience: [IGNY8_TARGET_AUDIENCE]
|
||
Primary Keyword: [IGNY8_PRIMARY_KEYWORD]
|
||
|
||
OUTPUT FORMAT:
|
||
Return ONLY a JSON object in this format:
|
||
{
|
||
"title": "[Product name and key benefit]",
|
||
"meta_title": "[SEO-optimized meta title, 30-60 chars]",
|
||
"meta_description": "[Compelling meta description, 120-160 chars]",
|
||
"html_content": "[Complete HTML product page content]",
|
||
"word_count": [Integer word count],
|
||
"primary_keyword": "[Primary keyword]",
|
||
"secondary_keywords": ["keyword1", "keyword2", "keyword3"],
|
||
"tags": ["tag1", "tag2", "tag3"],
|
||
"categories": ["Category > Subcategory"],
|
||
"json_blocks": [
|
||
{
|
||
"type": "product_overview",
|
||
"heading": "Product Overview",
|
||
"content": "Detailed product description"
|
||
},
|
||
{
|
||
"type": "features",
|
||
"heading": "Key Features",
|
||
"items": ["Feature 1", "Feature 2", "Feature 3"]
|
||
},
|
||
{
|
||
"type": "specifications",
|
||
"heading": "Specifications",
|
||
"data": {"Spec 1": "Value 1", "Spec 2": "Value 2"}
|
||
},
|
||
{
|
||
"type": "pricing",
|
||
"heading": "Pricing",
|
||
"content": "Pricing information"
|
||
},
|
||
{
|
||
"type": "benefits",
|
||
"heading": "Benefits",
|
||
"items": ["Benefit 1", "Benefit 2", "Benefit 3"]
|
||
}
|
||
],
|
||
"structure_data": {
|
||
"product_type": "[Product type]",
|
||
"price_range": "[Price range]",
|
||
"target_market": "[Target market]"
|
||
}
|
||
}
|
||
|
||
CONTENT REQUIREMENTS:
|
||
- Include compelling product overview
|
||
- List key features with benefits
|
||
- Provide detailed specifications
|
||
- Include pricing information (if available)
|
||
- Highlight unique selling points
|
||
- Use SEO-optimized headings
|
||
- Include call-to-action sections
|
||
- Ensure natural keyword usage
|
||
""",
|
||
|
||
'service_generation': """You are a service page content specialist. Generate comprehensive service page content that explains services, benefits, process, and pricing.
|
||
|
||
INPUT:
|
||
Service Name: [IGNY8_SERVICE_NAME]
|
||
Service Description: [IGNY8_SERVICE_DESCRIPTION]
|
||
Service Benefits: [IGNY8_SERVICE_BENEFITS]
|
||
Target Audience: [IGNY8_TARGET_AUDIENCE]
|
||
Primary Keyword: [IGNY8_PRIMARY_KEYWORD]
|
||
|
||
OUTPUT FORMAT:
|
||
Return ONLY a JSON object in this format:
|
||
{
|
||
"title": "[Service name and value proposition]",
|
||
"meta_title": "[SEO-optimized meta title, 30-60 chars]",
|
||
"meta_description": "[Compelling meta description, 120-160 chars]",
|
||
"html_content": "[Complete HTML service page content]",
|
||
"word_count": [Integer word count],
|
||
"primary_keyword": "[Primary keyword]",
|
||
"secondary_keywords": ["keyword1", "keyword2", "keyword3"],
|
||
"tags": ["tag1", "tag2", "tag3"],
|
||
"categories": ["Category > Subcategory"],
|
||
"json_blocks": [
|
||
{
|
||
"type": "service_overview",
|
||
"heading": "Service Overview",
|
||
"content": "Detailed service description"
|
||
},
|
||
{
|
||
"type": "benefits",
|
||
"heading": "Benefits",
|
||
"items": ["Benefit 1", "Benefit 2", "Benefit 3"]
|
||
},
|
||
{
|
||
"type": "process",
|
||
"heading": "Our Process",
|
||
"steps": ["Step 1", "Step 2", "Step 3"]
|
||
},
|
||
{
|
||
"type": "pricing",
|
||
"heading": "Pricing",
|
||
"content": "Pricing information"
|
||
},
|
||
{
|
||
"type": "faq",
|
||
"heading": "Frequently Asked Questions",
|
||
"items": [{"question": "Q1", "answer": "A1"}]
|
||
}
|
||
],
|
||
"structure_data": {
|
||
"service_type": "[Service type]",
|
||
"duration": "[Service duration]",
|
||
"target_market": "[Target market]"
|
||
}
|
||
}
|
||
|
||
CONTENT REQUIREMENTS:
|
||
- Clear service overview and value proposition
|
||
- Detailed benefits and outcomes
|
||
- Step-by-step process explanation
|
||
- Pricing information (if available)
|
||
- FAQ section addressing common questions
|
||
- Include testimonials or case studies (if applicable)
|
||
- Use SEO-optimized headings
|
||
- Include call-to-action sections
|
||
""",
|
||
|
||
'taxonomy_generation': """You are a taxonomy and categorization specialist. Generate comprehensive taxonomy page content that organizes and explains categories, tags, and hierarchical structures.
|
||
|
||
INPUT:
|
||
Taxonomy Name: [IGNY8_TAXONOMY_NAME]
|
||
Taxonomy Description: [IGNY8_TAXONOMY_DESCRIPTION]
|
||
Taxonomy Items: [IGNY8_TAXONOMY_ITEMS]
|
||
Primary Keyword: [IGNY8_PRIMARY_KEYWORD]
|
||
|
||
OUTPUT FORMAT:
|
||
Return ONLY a JSON object in this format:
|
||
{{
|
||
"title": "[Taxonomy name and purpose]",
|
||
"meta_title": "[SEO-optimized meta title, 30-60 chars]",
|
||
"meta_description": "[Compelling meta description, 120-160 chars]",
|
||
"html_content": "[Complete HTML taxonomy page content]",
|
||
"word_count": [Integer word count],
|
||
"primary_keyword": "[Primary keyword]",
|
||
"secondary_keywords": ["keyword1", "keyword2", "keyword3"],
|
||
"tags": ["tag1", "tag2", "tag3"],
|
||
"categories": ["Category > Subcategory"],
|
||
"json_blocks": [
|
||
{{
|
||
"type": "taxonomy_overview",
|
||
"heading": "Taxonomy Overview",
|
||
"content": "Detailed taxonomy description"
|
||
}},
|
||
{{
|
||
"type": "categories",
|
||
"heading": "Categories",
|
||
"items": [
|
||
{{
|
||
"name": "Category 1",
|
||
"description": "Category description",
|
||
"subcategories": ["Subcat 1", "Subcat 2"]
|
||
}}
|
||
]
|
||
}},
|
||
{{
|
||
"type": "tags",
|
||
"heading": "Tags",
|
||
"items": ["Tag 1", "Tag 2", "Tag 3"]
|
||
}},
|
||
{{
|
||
"type": "hierarchy",
|
||
"heading": "Taxonomy Hierarchy",
|
||
"structure": {{"Level 1": {{"Level 2": ["Level 3"]}}}}
|
||
}}
|
||
],
|
||
"structure_data": {{
|
||
"taxonomy_type": "[Taxonomy type]",
|
||
"item_count": [Integer],
|
||
"hierarchy_levels": [Integer]
|
||
}}
|
||
}}
|
||
|
||
CONTENT REQUIREMENTS:
|
||
- Clear taxonomy overview and purpose
|
||
- Organized category structure
|
||
- Tag organization and relationships
|
||
- Hierarchical structure visualization
|
||
- SEO-optimized headings
|
||
- Include navigation and organization benefits
|
||
- Use clear, descriptive language
|
||
""",
|
||
}
|
||
|
||
# Mapping from function names to prompt types
|
||
FUNCTION_TO_PROMPT_TYPE = {
|
||
'auto_cluster': 'clustering',
|
||
'generate_ideas': 'ideas',
|
||
'generate_content': 'content_generation',
|
||
'generate_images': 'image_prompt_extraction',
|
||
'extract_image_prompts': 'image_prompt_extraction',
|
||
'generate_image_prompts': 'image_prompt_extraction',
|
||
'generate_site_structure': 'site_structure_generation',
|
||
'optimize_content': 'optimize_content',
|
||
# Phase 8: Universal Content Types
|
||
'generate_product_content': 'product_generation',
|
||
'generate_service_page': 'service_generation',
|
||
'generate_taxonomy': 'taxonomy_generation',
|
||
}
|
||
|
||
@classmethod
|
||
def get_prompt(
|
||
cls,
|
||
function_name: str,
|
||
account: Optional[Any] = None,
|
||
task: Optional[Any] = None,
|
||
context: Optional[Dict[str, Any]] = None
|
||
) -> str:
|
||
"""
|
||
Get prompt for a function with hierarchical resolution.
|
||
|
||
Priority:
|
||
1. task.prompt_override (if task provided and has override)
|
||
2. DB prompt for (account, function)
|
||
3. Default fallback from registry
|
||
|
||
Args:
|
||
function_name: AI function name (e.g., 'auto_cluster', 'generate_ideas')
|
||
account: Account object (optional)
|
||
task: Task object with optional prompt_override (optional)
|
||
context: Additional context for prompt rendering (optional)
|
||
|
||
Returns:
|
||
Prompt string ready for formatting
|
||
"""
|
||
# Step 1: Check task-level override
|
||
if task and hasattr(task, 'prompt_override') and task.prompt_override:
|
||
logger.info(f"Using task-level prompt override for {function_name}")
|
||
prompt = task.prompt_override
|
||
return cls._render_prompt(prompt, context or {})
|
||
|
||
# Step 2: Get prompt type
|
||
prompt_type = cls.FUNCTION_TO_PROMPT_TYPE.get(function_name, function_name)
|
||
|
||
# Step 3: Try DB prompt
|
||
if account:
|
||
try:
|
||
from igny8_core.modules.system.models import AIPrompt
|
||
db_prompt = AIPrompt.objects.get(
|
||
account=account,
|
||
prompt_type=prompt_type,
|
||
is_active=True
|
||
)
|
||
logger.info(f"Using DB prompt for {function_name} (account {account.id})")
|
||
prompt = db_prompt.prompt_value
|
||
return cls._render_prompt(prompt, context or {})
|
||
except Exception as e:
|
||
logger.debug(f"No DB prompt found for {function_name}: {e}")
|
||
|
||
# Step 4: Use default fallback
|
||
prompt = cls.DEFAULT_PROMPTS.get(prompt_type, '')
|
||
if not prompt:
|
||
logger.warning(f"No default prompt found for {prompt_type}, using empty string")
|
||
|
||
return cls._render_prompt(prompt, context or {})
|
||
|
||
@classmethod
|
||
def _render_prompt(cls, prompt_template: str, context: Dict[str, Any]) -> str:
|
||
"""
|
||
Render prompt template with context variables.
|
||
Supports both .format() style ({variable}) and placeholder replacement ([IGNY8_*]).
|
||
|
||
Args:
|
||
prompt_template: Prompt template string
|
||
context: Context variables for rendering
|
||
|
||
Returns:
|
||
Rendered prompt string
|
||
"""
|
||
if not context:
|
||
return prompt_template
|
||
|
||
rendered = prompt_template
|
||
|
||
# Step 1: Replace [IGNY8_*] placeholders first (always do this)
|
||
for key, value in context.items():
|
||
placeholder = f'[IGNY8_{key.upper()}]'
|
||
if placeholder in rendered:
|
||
rendered = rendered.replace(placeholder, str(value))
|
||
logger.debug(f"Replaced placeholder {placeholder} with {len(str(value))} characters")
|
||
|
||
# Step 2: Try .format() style for {variable} placeholders (if any remain)
|
||
# Normalize context keys - convert UPPER to lowercase for .format()
|
||
normalized_context = {}
|
||
for key, value in context.items():
|
||
# Try both original key and lowercase version
|
||
normalized_context[key] = value
|
||
normalized_context[key.lower()] = value
|
||
|
||
# Only try .format() if there are {variable} placeholders
|
||
if '{' in rendered and '}' in rendered:
|
||
try:
|
||
rendered = rendered.format(**normalized_context)
|
||
except (KeyError, ValueError, IndexError) as e:
|
||
# If .format() fails, log warning but keep the [IGNY8_*] replacements
|
||
logger.warning(f"Failed to format prompt with .format(): {e}. Using [IGNY8_*] replacements only.")
|
||
|
||
return rendered
|
||
|
||
@classmethod
|
||
def get_image_prompt_template(cls, account: Optional[Any] = None) -> str:
|
||
"""
|
||
Get image prompt template.
|
||
Returns template string (not rendered) - caller should format with .format()
|
||
"""
|
||
prompt_type = 'image_prompt_template'
|
||
|
||
# Try DB prompt
|
||
if account:
|
||
try:
|
||
from igny8_core.modules.system.models import AIPrompt
|
||
db_prompt = AIPrompt.objects.get(
|
||
account=account,
|
||
prompt_type=prompt_type,
|
||
is_active=True
|
||
)
|
||
return db_prompt.prompt_value
|
||
except Exception:
|
||
pass
|
||
|
||
# Use default
|
||
return cls.DEFAULT_PROMPTS.get(prompt_type, '')
|
||
|
||
@classmethod
|
||
def get_negative_prompt(cls, account: Optional[Any] = None) -> str:
|
||
"""
|
||
Get negative prompt.
|
||
Returns template string (not rendered).
|
||
"""
|
||
prompt_type = 'negative_prompt'
|
||
|
||
# Try DB prompt
|
||
if account:
|
||
try:
|
||
from igny8_core.modules.system.models import AIPrompt
|
||
db_prompt = AIPrompt.objects.get(
|
||
account=account,
|
||
prompt_type=prompt_type,
|
||
is_active=True
|
||
)
|
||
return db_prompt.prompt_value
|
||
except Exception:
|
||
pass
|
||
|
||
# Use default
|
||
return cls.DEFAULT_PROMPTS.get(prompt_type, '')
|
||
|
||
|
||
# Convenience function for backward compatibility
|
||
def get_prompt(function_name: str, account=None, task=None, context=None) -> str:
|
||
"""Get prompt using registry"""
|
||
return PromptRegistry.get_prompt(function_name, account=account, task=task, context=context)
|
||
|