feat: Add Content Generation Engine v2 with quality scoring

Major improvements to AI content generation:

## New Components (app/services/ai/)
- PromptLibrary: YAML-based prompt templates with inheritance
- ContextEngine: Anti-repetition and best performers tracking
- ContentGeneratorV2: Enhanced generation with dynamic parameters
- PlatformAdapter: Platform-specific content adaptation
- ContentValidator: AI-powered quality scoring (0-100)

## Prompt Library (app/prompts/)
- 3 personalities: default, educational, promotional
- 5 templates: tip_tech, product_post, service_post, thread, response
- 4 platform configs: x, threads, instagram, facebook
- Few-shot examples by category: ia, productividad, seguridad

## Database Changes
- New table: content_memory (tracks generated content)
- New columns in posts: quality_score, score_breakdown, generation_attempts

## New API Endpoints (/api/v2/generate/)
- POST /generate - Generation with quality check
- POST /generate/batch - Batch generation
- POST /quality/evaluate - Evaluate content quality
- GET /templates, /personalities, /platforms - List configs

## Celery Tasks
- update_engagement_scores (every 6h)
- cleanup_old_memory (monthly)
- refresh_best_posts_yaml (weekly)

## Tests
- Comprehensive tests for all AI engine components

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-28 20:55:28 +00:00
parent f458f809ca
commit 11b0ba46fa
36 changed files with 6266 additions and 55 deletions

View File

@@ -16,9 +16,13 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
from app.core.config import settings
from app.core.database import Base
# Import all models for autogenerate support
from app.models import (
User, Product, Service, TipTemplate,
Post, ContentCalendar, ImageTemplate, Interaction
Post, ContentCalendar, ImageTemplate, Interaction,
PostMetrics, AnalyticsReport, Lead, OdooSyncLog,
ABTest, ABTestVariant, RecycledPost, ThreadSeries, ThreadPost,
ContentMemory
)
# this is the Alembic Config object

View File

@@ -0,0 +1,100 @@
"""Add content_memory table and quality columns to posts
Revision ID: 20260128001
Revises:
Create Date: 2026-01-28
This migration adds:
1. New table 'content_memory' for tracking generated content
2. New columns in 'posts' for quality scoring
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '20260128001'
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
# === Create content_memory table ===
op.create_table(
'content_memory',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('post_id', sa.Integer(), nullable=False),
# Content analysis
sa.Column('topics', postgresql.ARRAY(sa.String()), nullable=True),
sa.Column('key_phrases', postgresql.ARRAY(sa.String()), nullable=True),
sa.Column('hook_type', sa.String(50), nullable=True),
sa.Column('content_summary', sa.Text(), nullable=True),
sa.Column('content_embedding', postgresql.JSON(), nullable=True),
# Engagement metrics
sa.Column('engagement_score', sa.Float(), nullable=True),
sa.Column('engagement_breakdown', postgresql.JSON(), nullable=True),
sa.Column('is_top_performer', sa.Boolean(), default=False),
# Quality score
sa.Column('quality_score', sa.Integer(), nullable=True),
sa.Column('quality_breakdown', postgresql.JSON(), nullable=True),
# Example usage tracking
sa.Column('times_used_as_example', sa.Integer(), default=0),
sa.Column('last_used_as_example', sa.DateTime(), nullable=True),
# Metadata
sa.Column('platform', sa.String(20), nullable=True),
sa.Column('content_type', sa.String(50), nullable=True),
sa.Column('personality_used', sa.String(50), nullable=True),
sa.Column('template_used', sa.String(50), nullable=True),
# Timestamps
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.ForeignKeyConstraint(['post_id'], ['posts.id'], ondelete='CASCADE'),
)
# Create indexes for content_memory
op.create_index('ix_content_memory_post_id', 'content_memory', ['post_id'], unique=True)
op.create_index('ix_content_memory_engagement_score', 'content_memory', ['engagement_score'])
op.create_index('ix_content_memory_is_top_performer', 'content_memory', ['is_top_performer'])
op.create_index('ix_content_memory_hook_type', 'content_memory', ['hook_type'])
op.create_index('ix_content_memory_platform', 'content_memory', ['platform'])
op.create_index('ix_content_memory_content_type', 'content_memory', ['content_type'])
op.create_index('ix_content_memory_created_at', 'content_memory', ['created_at'])
# === Add quality columns to posts table ===
op.add_column('posts', sa.Column('quality_score', sa.Integer(), nullable=True))
op.add_column('posts', sa.Column('score_breakdown', postgresql.JSON(), nullable=True))
op.add_column('posts', sa.Column('generation_attempts', sa.Integer(), server_default='1', nullable=True))
# Create index for quality_score
op.create_index('ix_posts_quality_score', 'posts', ['quality_score'])
def downgrade() -> None:
# Remove indexes from posts
op.drop_index('ix_posts_quality_score', table_name='posts')
# Remove columns from posts
op.drop_column('posts', 'generation_attempts')
op.drop_column('posts', 'score_breakdown')
op.drop_column('posts', 'quality_score')
# Remove indexes from content_memory
op.drop_index('ix_content_memory_created_at', table_name='content_memory')
op.drop_index('ix_content_memory_content_type', table_name='content_memory')
op.drop_index('ix_content_memory_platform', table_name='content_memory')
op.drop_index('ix_content_memory_hook_type', table_name='content_memory')
op.drop_index('ix_content_memory_is_top_performer', table_name='content_memory')
op.drop_index('ix_content_memory_engagement_score', table_name='content_memory')
op.drop_index('ix_content_memory_post_id', table_name='content_memory')
# Drop content_memory table
op.drop_table('content_memory')