From 38324aedcdd7d9f44c338e37c6257f385679c012 Mon Sep 17 00:00:00 2001 From: Gitea Deploy Date: Mon, 10 Nov 2025 06:28:34 +0000 Subject: [PATCH] Add function_id tracking and enable JSON mode for all AI functions --- backend/igny8_core/ai/ai_core.py | 12 +- backend/igny8_core/ai/engine.py | 5 + .../ai/functions/generate_content.py | 6 +- .../igny8_core/ai/functions/generate_ideas.py | 4 + backend/igny8_core/ai/prompts.py | 241 ++++++++++++++---- backend/igny8_core/ai/settings.py | 18 +- .../modules/system/management/__init__.py | 2 + .../system/management/commands/__init__.py | 2 + .../commands/update_default_prompts.py | 57 +++++ backend/igny8_core/modules/system/utils.py | 241 ++++++++++++++---- backend/igny8_core/utils/ai_processor.py | 18 +- .../src/components/common/ProgressModal.tsx | 31 ++- frontend/src/hooks/useProgressModal.ts | 8 +- frontend/src/pages/Planner/Clusters.tsx | 5 +- frontend/src/pages/Planner/Ideas.tsx | 1 + frontend/src/pages/Planner/Keywords.tsx | 3 +- frontend/src/pages/Writer/Tasks.tsx | 3 +- 17 files changed, 544 insertions(+), 113 deletions(-) create mode 100644 backend/igny8_core/modules/system/management/__init__.py create mode 100644 backend/igny8_core/modules/system/management/commands/__init__.py create mode 100644 backend/igny8_core/modules/system/management/commands/update_default_prompts.py diff --git a/backend/igny8_core/ai/ai_core.py b/backend/igny8_core/ai/ai_core.py index dc070856..f8f17e4f 100644 --- a/backend/igny8_core/ai/ai_core.py +++ b/backend/igny8_core/ai/ai_core.py @@ -103,6 +103,7 @@ class AICore: response_format: Optional[Dict] = None, api_key: Optional[str] = None, function_name: str = 'ai_request', + function_id: Optional[str] = None, tracker: Optional[ConsoleStepTracker] = None ) -> Dict[str, Any]: """ @@ -158,10 +159,17 @@ class AICore: else: tracker.ai_call("Using text response format") - # Step 4: Validate prompt length + # Step 4: Validate prompt length and add function_id prompt_length = len(prompt) tracker.ai_call(f"Prompt length: {prompt_length} characters") + # Add function_id to prompt if provided (for tracking) + final_prompt = prompt + if function_id: + function_id_prefix = f'function_id: "{function_id}"\n\n' + final_prompt = function_id_prefix + prompt + tracker.ai_call(f"Added function_id to prompt: {function_id}") + # Step 5: Build request payload url = 'https://api.openai.com/v1/chat/completions' headers = { @@ -171,7 +179,7 @@ class AICore: body_data = { 'model': active_model, - 'messages': [{'role': 'user', 'content': prompt}], + 'messages': [{'role': 'user', 'content': final_prompt}], 'temperature': temperature, } diff --git a/backend/igny8_core/ai/engine.py b/backend/igny8_core/ai/engine.py index 42f1582d..11ed3925 100644 --- a/backend/igny8_core/ai/engine.py +++ b/backend/igny8_core/ai/engine.py @@ -82,11 +82,15 @@ class AIEngine: ai_core = AICore(account=self.account) function_name = fn.get_name() + # Generate function_id for tracking (ai-{function_name}-01) + function_id = f"ai-{function_name}-01" + # Get model config from settings (Stage 4 requirement) model_config = get_model_config(function_name) model = model_config.get('model') self.console_tracker.ai_call(f"Calling {model or 'default'} model with {len(prompt)} char prompt") + self.console_tracker.ai_call(f"Function ID: {function_id}") # Track AI call start self.step_tracker.add_response_step("AI_CALL", "success", f"Calling {model or 'default'} model...") @@ -102,6 +106,7 @@ class AIEngine: temperature=model_config.get('temperature'), response_format=model_config.get('response_format'), function_name=function_name, + function_id=function_id, # Pass function_id for tracking tracker=self.console_tracker # Pass console tracker for logging ) except Exception as e: diff --git a/backend/igny8_core/ai/functions/generate_content.py b/backend/igny8_core/ai/functions/generate_content.py index a2227429..ee260519 100644 --- a/backend/igny8_core/ai/functions/generate_content.py +++ b/backend/igny8_core/ai/functions/generate_content.py @@ -299,6 +299,9 @@ def generate_content_core(task_ids: List[int], account_id: int = None, progress_ # Get model config from settings model_config = get_model_config('generate_content') + # Generate function_id for tracking (ai-generate-content-02 for legacy path) + function_id = "ai-generate-content-02" + # Call AI using centralized request handler ai_core = AICore(account=account) result = ai_core.run_ai_request( @@ -307,7 +310,8 @@ def generate_content_core(task_ids: List[int], account_id: int = None, progress_ max_tokens=model_config.get('max_tokens'), temperature=model_config.get('temperature'), response_format=model_config.get('response_format'), - function_name='generate_content' + function_name='generate_content', + function_id=function_id # Pass function_id for tracking ) if result.get('error'): diff --git a/backend/igny8_core/ai/functions/generate_ideas.py b/backend/igny8_core/ai/functions/generate_ideas.py index 473fd7b1..43a24898 100644 --- a/backend/igny8_core/ai/functions/generate_ideas.py +++ b/backend/igny8_core/ai/functions/generate_ideas.py @@ -282,6 +282,9 @@ def generate_ideas_core(cluster_id: int, account_id: int = None, progress_callba # Get model config from settings model_config = get_model_config('generate_ideas') + # Generate function_id for tracking (ai-generate-ideas-02 for legacy path) + function_id = "ai-generate-ideas-02" + # Call AI using centralized request handler ai_core = AICore(account=account) result = ai_core.run_ai_request( @@ -291,6 +294,7 @@ def generate_ideas_core(cluster_id: int, account_id: int = None, progress_callba temperature=model_config.get('temperature'), response_format=model_config.get('response_format'), function_name='generate_ideas', + function_id=function_id, # Pass function_id for tracking tracker=tracker ) diff --git a/backend/igny8_core/ai/prompts.py b/backend/igny8_core/ai/prompts.py index ba2f92d9..2df411eb 100644 --- a/backend/igny8_core/ai/prompts.py +++ b/backend/igny8_core/ai/prompts.py @@ -19,54 +19,209 @@ class PromptRegistry: # Default prompts stored in registry DEFAULT_PROMPTS = { - 'clustering': """Analyze the following keywords and group them into topic clusters. + '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. -Each cluster should include: -- "name": A clear, descriptive topic name -- "description": A brief explanation of what the cluster covers -- "keywords": A list of related keywords that belong to this cluster +Return a single JSON object with a "clusters" array. Each cluster must follow this structure: -Format the output as a JSON object with a "clusters" array. +{ + "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", "..."] +} -IMPORTANT: In the "keywords" array, you MUST use the EXACT keyword strings from the input list below. Do not modify, paraphrase, or create variations of the keywords. Only use the exact keywords as they appear in the input list. +CLUSTERING STRATEGY: -Clustering rules: -- Group keywords based on strong semantic or topical relationships (intent, use-case, function, audience, etc.) -- Clusters should reflect how people actually search — problem ➝ solution, general ➝ specific, product ➝ benefit, etc. -- Avoid grouping keywords just because they share similar words — focus on meaning -- Include 3–10 keywords per cluster where appropriate -- Skip unrelated or outlier keywords that don't fit a clear theme -- CRITICAL: Only return keywords that exactly match the input keywords (case-insensitive matching is acceptable) +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. -Keywords to process: -[IGNY8_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 detailed outlines for each of the following keyword clusters. - -Clusters to analyze: -[IGNY8_CLUSTERS] - -Keywords in each cluster: -[IGNY8_CLUSTER_KEYWORDS] - -Return your response as JSON with an "ideas" array. -For each cluster, generate 1-3 content ideas. + '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": compelling blog/article title that naturally includes a primary keyword -- "description": detailed content outline with H2/H3 structure (as plain text or structured JSON) -- "content_type": the type of content (blog_post, article, guide, tutorial) -- "content_structure": the editorial structure (cluster_hub, landing_page, pillar_page, supporting_page) -- "estimated_word_count": estimated total word count (1500-2200 words) -- "target_keywords": comma-separated list of keywords that will be covered (or "covered_keywords") -- "cluster_name": name of the cluster this idea belongs to (REQUIRED) -- "cluster_id": ID of the cluster this idea belongs to (REQUIRED - use the exact cluster ID from the input) +title, description, content_type, content_structure, cluster_id, estimated_word_count (1500–2200), and covered_keywords. -IMPORTANT: You MUST include the exact "cluster_id" from the cluster data provided. Match the cluster name to find the correct cluster_id. +Outline Rules: -Return only valid JSON with an "ideas" array.""", +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": [ + {"content_type": "paragraph", "details": "Overview of organic cotton's rise in bedding industry."}, + {"content_type": "paragraph", "details": "Why consumers prefer organic bedding over synthetic alternatives."} + ] + }, + "H2": [ + { + "heading": "Why Choose Organic Cotton for Bedding?", + "subsections": [ + {"subheading": "Health and Skin Benefits", "content_type": "paragraph", "details": "Discuss hypoallergenic and chemical-free aspects."}, + {"subheading": "Environmental Sustainability", "content_type": "list", "details": "Eco benefits like low water use, no pesticides."}, + {"subheading": "Long-Term Cost Savings", "content_type": "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" + } + ] +}""", - 'content_generation': """You are an editorial content strategist. Generate a complete blog post/article based on the provided content idea. + 'content_generation': """You are an editorial content strategist. Your task is to generate a complete JSON response object that includes all the fields listed below, based on the provided content idea, keyword cluster, and keyword list. + +Only the `content` field should contain HTML inside JSON object. + +================== +Generate a complete JSON response object matching this structure: +================== + +{ + "title": "[Blog title using the primary keyword — full sentence case]", + "meta_title": "[Meta title under 60 characters — natural, optimized, and compelling]", + "meta_description": "[Meta description under 160 characters — clear and enticing summary]", + "content": "[HTML content — full editorial structure with

,

,

,