Con StoryPixAI mi objetivo era crear una aplicación web interactiva que permitiera a los usuarios generar historias para niños, enriquecidas con imágenes generadas por modelos de inteligencia artificial. Para lograr esto, utilicé varios servicios de AWS como Lambda, API Gateway, DynamoDB, S3 y Cognito para la autenticación. El código de la infraestructura se gestiona con Terraform, y el despliegue se automatiza mediante GitLab CI. En este artículo, te revelo los entresijos de este apasionante proyecto, desde las decisiones tecnológicas hasta los desafíos encontrados.
Introducción
Como arquitecto de infraestructuras cloud y DevOps con experiencia, siempre me han fascinado las nuevas tecnologías y su potencial para transformar nuestra vida cotidiana. El surgimiento de la IA generativa despertó en mí una curiosidad creciente, y sentí la necesidad de sumergirme en este universo en plena efervescencia.
Así nació StoryPixAI, un proyecto personal que me permitió explorar las posibilidades infinitas de la IA para crear historias personalizadas e ilustraciones mágicas para niños. Este proyecto fue para mí la ocasión de ponerme en la piel de un desarrollador full-stack, de un prompt engineer, de un product owner e incluso de un diseñador UX/UI, todo ello mientras compartía mi pasión por la tecnología con mis allegados.
En esta entrada del blog, compartiré mis elecciones tecnológicas y los desafíos superados a lo largo de esta apasionante aventura.
Pero antes de nada, ¡un aperitivo!
Para darte una idea del potencial de StoryPixAI, aquí tienes algunas historias generadas automáticamente en varios idiomas.
Cada historia va acompañada de ilustraciones, haciendo la narración aún más inmersiva para los niños:
- Francés: Tom, Zoé y el Reino de Argentor
- Inglés: La búsqueda mágica de la princesa Léa y del ladrón de caracoles gigante
- Español: La gran aventura de Roger y Coco el payaso
- Alemán: El leñador loco y la aventura mágica
- Italiano: La niña y el unicornio mágico en el bosque encantado
- Portugués: El viaje encantado de Lucas y sus amigos
La IA al servicio de la creatividad: un recorrido de experimentación
Mi aventura con StoryPixAI comenzó con una prueba de concepto (PoC) sencilla: una función Lambda que interactuaba con OpenAI para generar texto y DALL-E para crear imágenes. Este primer éxito me animó a ir más allá y a explorar otros modelos de IA a través de Bedrock de AWS.
GPT-4 y GPT-4-o: los narradores ágiles
Desde el inicio del proyecto, GPT-4 de OpenAI se impuso como una elección evidente para la generación de texto. Su capacidad para comprender las sutilezas del lenguaje natural y producir relatos coherentes y creativos me permitió crear historias cautivadoras, adaptadas a la edad e intereses de los niños. Pude experimentar con diferentes estilos de escritura, desde cuentos de hadas hasta aventuras espaciales, pasando por historias de animales y relatos fantásticos.
Cuando se lanzó GPT-4-0, integré rápidamente este nuevo modelo en StoryPixAI. Me impresionó su mayor velocidad de generación, que permitió reducir considerablemente el tiempo de espera, y la mejora notable en la calidad de las historias generadas, con relatos aún más fluidos, coherentes e imaginativos. GPT-4-0 se convirtió así en un activo importante para StoryPixAI, ofreciendo una experiencia de usuario más rápida y agradable.
DALL-E 3: el ilustrador de referencia
Si los modelos de generación de texto ofrecían resultados satisfactorios, la elección de la herramienta de generación de imágenes resultó más crucial. Tras numerosos ensayos, DALL-E 3 se impuso como el modelo de referencia para StoryPixAI. Su capacidad para crear ilustraciones originales, detalladas y perfectamente adaptadas a las historias generadas por GPT-4 fue un factor determinante en el éxito del proyecto.
Bedrock de AWS: la puerta abierta a la experimentación
Deseando no limitarme a OpenAI, utilicé Bedrock de AWS para integrar fácilmente otros modelos de IA generativa en StoryPixAI. Esta plataforma me permitió probar Claude de Anthropic y Mistral para la generación de texto, y Stable Diffusion para la creación de imágenes.
Aunque estos modelos ofrecieron resultados interesantes, finalmente decidí centrarme en GPT-4 y GPT-4-0 por su rapidez y calidad en la generación de texto, y en DALL-E 3 por su capacidad para producir ilustraciones perfectamente adaptadas a las historias. Es importante señalar que el prompt utilizado para generar las imágenes es en gran parte elaborado por el propio modelo de texto, lo que asegura coherencia entre el relato y la ilustración.
El desafío de la API asíncrona y de DynamoDB
Una vez validado el PoC, emprendí la creación de una API para hacer StoryPixAI accesible a través de una interfaz web. Fue en esta etapa cuando me encontré con mi primer desafío importante: la limitación de timeout de API Gateway. Para sortear esta restricción y permitir la generación de historias más largas y complejas, tuve que implementar una arquitectura asíncrona.
Amazon DynamoDB entró en juego entonces. Utilicé esta base de datos NoSQL para almacenar las tareas de generación de historias en curso, así como sus resultados una vez finalizadas. Gracias a este enfoque, la API podía devolver una respuesta inmediata al usuario, que luego podía consultar el estado de su solicitud y recuperar la historia generada cuando estuviera lista.
CORS y la interfaz de usuario: obstáculos por superar
La implementación de la interfaz web también trajo sus retos. Tuve que familiarizarme con las sutilezas de CORS (Cross-Origin Resource Sharing) para permitir que mi frontend se comunicara con la API. También dediqué tiempo a mejorar la experiencia de usuario añadiendo funcionalidades como la selección de modelos de IA y estilos de imagen.
El prompting: un arte por dominar
A lo largo del desarrollo de StoryPixAI, perfeccioné mis habilidades en prompting, ese arte de formular las solicitudes correctas para guiar los modelos de IA. Aprendí a adaptar mis prompts según los modelos utilizados, los parámetros de la historia y las expectativas de los usuarios. Esta etapa fue crucial para obtener resultados de calidad y garantizar una experiencia satisfactoria.
Una infraestructura robusta y automatizada en AWS
StoryPixAI se apoya en una infraestructura serverless alojada en Amazon Web Services (AWS), que ofrece una combinación ideal de flexibilidad, escalabilidad y optimización de costes. Esta arquitectura, totalmente automatizada gracias a Terraform y GitLab CI/CD, permite un despliegue rápido y fiable de la aplicación.
Los servicios de AWS en el corazón de StoryPixAI

La arquitectura de StoryPixAI se articula en torno a los siguientes servicios de AWS:
- Amazon S3 (Simple Storage Service): Almacenamiento de los archivos estáticos del sitio web (HTML, CSS, JavaScript) y de las historias generadas, así como de sus ilustraciones asociadas.
- Amazon CloudFront: Una red de distribución de contenido (CDN) que acelera la entrega del contenido de StoryPixAI a usuarios de todo el mundo al cachearlo en ubicaciones geográficas cercanas.
- Amazon API Gateway: La puerta de entrada segura de la aplicación. Gestiona las peticiones de los usuarios, asegura su autenticación vía Amazon Cognito y las encamina hacia las funciones Lambda correspondientes.
- AWS Lambda: Funciones serverless que constituyen el motor de StoryPixAI. Orquestan la generación de historias, la creación de imágenes, la gestión de tareas asíncronas y la interacción con DynamoDB y otros servicios de AWS.
- Amazon DynamoDB: Una base de datos NoSQL flexible y de alto rendimiento utilizada para almacenar información esencial para el funcionamiento de la aplicación.
- Amazon Cognito: Un servicio de gestión de identidades y accesos que asegura la aplicación permitiendo a los usuarios iniciar sesión y controlando sus permisos. Garantiza que solo los usuarios autenticados puedan acceder a las funcionalidades de generación de historias.
- Amazon Bedrock: Una plataforma que simplifica el acceso y uso de modelos de IA generativa de distintos proveedores, como Anthropic (Claude) y Stability AI (Stable Diffusion). Bedrock permite integrar estos modelos en la aplicación sin tener que gestionar su infraestructura subyacente.
- Otros servicios AWS: StoryPixAI también utiliza otros servicios de AWS, como IAM (Identity and Access Management) para la gestión fina de permisos de acceso a los recursos, CloudWatch para la monitorización y los logs (crucial para la depuración y el análisis de rendimiento), y Systems Manager Parameter Store (SSM Parameter Store) para almacenar información sensible como las claves API, garantizando así la seguridad de la aplicación.
Terraform: la automatización al servicio de la infraestructura
Para gestionar esta infraestructura compleja, opté por Terraform, una herramienta de Infrastructure as Code (IaC) que permite describir la infraestructura en forma de código declarativo. Gracias a Terraform, pude automatizar la creación, modificación y destrucción de los recursos AWS, garantizando así un entorno coherente, reproducible y fácil de gestionar. Esto simplifica considerablemente el proceso de despliegue y reduce el riesgo de errores humanos.
GitLab CI/CD: despliegues fluidos y sin contratiempos
Para asegurar un despliegue continuo y fiable de StoryPixAI, configuré un pipeline CI/CD (Integración Continua / Despliegue Continuo) en GitLab. Este pipeline automatiza las pruebas, la construcción y el despliegue de la aplicación en cada cambio del código fuente, permitiendo detectar y corregir rápidamente errores y entregar nuevas funcionalidades con confianza. Este enfoque garantiza que la aplicación esté siempre actualizada y minimiza los tiempos de inactividad.
Esta combinación de AWS, Terraform y GitLab CI/CD me permitió construir una infraestructura robusta, escalable y fácil de mantener, dejándome así más tiempo para concentrarme en el aspecto creativo del proyecto y en la mejora de la experiencia de usuario.
Arquitectura global del proyecto StoryPixAI
Antes de sumergirnos en el código, aquí tienes una vista general de la arquitectura de la aplicación:
- Sitio estático en S3: Un sitio web estático hospedado en un bucket S3, accesible vía CloudFront para una distribución global.
- API Gateway: Expone endpoints para la generación de historias y la verificación del estado.
- Funciones Lambda:
StoryPixAI.py: Genera la historia y las imágenes asociadas.status_checker.py: Verifica el estado de la generación en DynamoDB.
- DynamoDB: Almacena el estado de las tareas de generación.
- S3: Almacena las imágenes generadas y las páginas HTML resultantes.
- Cognito: Gestiona la autenticación de usuarios para asegurar la API.
Función Lambda StoryPixAI.py
Visión general
La función StoryPixAI.py es el corazón de la aplicación. Es responsable de:
- Generar una historia basada en un prompt del usuario.
- Crear instrucciones detalladas para guiar al modelo de IA en la generación de la historia.
- Extraer resúmenes para cada escena o elemento clave de la historia.
- Generar imágenes correspondientes a esos resúmenes.
- Combinar el texto y las imágenes en una página HTML.
- Almacenar el resultado en S3 y actualizar el estado en DynamoDB.
Descomposición del código
Imports y configuración inicial
import json
import boto3
import base64
import os
import re
from datetime import datetime
from openai import OpenAI
import logging
# Configuration du logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)
generate_images = False
region_name = os.getenv("AWS_REGION", "us-east-1")
# Création d'un client SSM
ssm = boto3.client("ssm", region_name=region_name)
# Obtention de la clé API OpenAI depuis le SSM
parameter = ssm.get_parameter(Name="/openaikey", WithDecryption=True)
En esta sección importo los módulos necesarios, configuro el logger para depuración y recupero la clave API de OpenAI almacenada en AWS Systems Manager Parameter Store (SSM). Esto permite asegurar la clave y no almacenarla en texto plano en el código.
Funciones utilitarias
Corrección de las etiquetas
def correct_resume_tags(text):
# Corrige les balises 'résumé', 'resume', 'titre' et leurs variantes en 'resume' et 'titre' respectivement dans le texte généré.
Esta función asegura que las etiquetas utilizadas para delimitar los resúmenes y los títulos sean uniformes. Esto es crucial para la extracción correcta de los resúmenes más adelante.
Extracción de los resúmenes
def extract_summaries(text):
# Extrait les résumés du texte en utilisant des balises spécifiques.
Utiliza expresiones regulares para extraer las secciones de texto delimitadas por [resume] y [end_resume]. Estos resúmenes servirán como prompts para la generación de imágenes.
Generación de instrucciones para las imágenes
def generate_image_instructions(prompt, style, language):
# Génère les instructions pour la création d'images.
Esta función formatea el prompt de manera que guíe al modelo de generación de imágenes, incluyendo el estilo y el idioma.
Actualización de DynamoDB
def update_dynamodb(request_id, status, result_url=None):
# Met à jour une entrée dans la table DynamoDB avec l'ID de la requête, le statut et l'URL du résultat.
Actualiza la tabla TaskStatus para hacer seguimiento del estado de la generación, lo cual es esencial para la función status_checker.py.
Análisis detallado de generate_story_instructions
La función generate_story_instructions es el núcleo del proyecto. Genera un conjunto de instrucciones detalladas que se pasarán al modelo de IA para guiar la generación de la historia.
def generate_story_instructions(prompt, language):
"""
Génère les instructions pour créer une histoire captivante pour enfants.
Args:
prompt (str): Texte source pour inspirer l'histoire.
language (str): Langue de l'histoire.
Returns:
str: Instructions formatées pour la génération de l'histoire.
"""
language_description = get_language_description(language)
return f"""
Crée une histoire unique de 1000 à 1500 mots, captivante et riche en descriptions visuelles pour enfants uniquement dans la langue "{language_description}", inspirée par : "{prompt}". Cette histoire doit mêler aventure, magie, et enseigner des valeurs importantes telles que l'amitié, le courage, la persévérance, l'empathie, et la gentillesse.
L'histoire peut aborder des thèmes comme : l'amitié entre un enfant humain et un animal merveilleux, la découverte d'un monde magique caché, un long voyage vers une contrée enchantée, un enfant qui découvre qu'il/elle possède des pouvoirs magiques spéciaux et doit apprendre à les maîtriser, une quête pour sauver une créature légendaire en danger, un voyage à travers des royaumes féeriques pour briser un sortilège ancien, une aventure sous-marine dans un monde marin peuplé de sirènes et de créatures fantastiques, une mission pour réunir des objets magiques dispersés afin d'empêcher un grand cataclysme, une compétition amicale entre enfants dotés de capacités extraordinaires dans une école de sorcellerie, etc.
L'histoire peut également explorer : l'acceptation de soi à travers un personnage unique comme un enfant métamorphe, la découverte d'une ancienne civilisation perdue et de ses secrets, une épopée pour retrouver des parents disparus dans un monde parallèle, une lutte contre les forces des ténèbres menaçant d'engloutir un royaume enchanté, etc.
N'hésites pas à combiner plusieurs de ces idées pour créer une trame narrative riche et captivante. Tu peux aussi t'inspirer de contes ou légendes traditionnels et leur donner un nouvel éclairage fantastique adapté aux enfants.
Raconte l'histoire au présent pour une immersion maximale.
Instructions spécifiques :
- Utilise des phrases courtes et simples, adaptées pour des enfants de moins de 10 ans.
- Intègre des dialogues dynamiques et réalistes pour rendre l'histoire vivante.
- Choisis des mots simples pour une meilleure compréhension par de jeunes lecteurs.
- Crée des personnages diversifiés en termes d'âge, de genre, d'origine ethnique et de capacités. Assure-toi que l'apparence des personnages (cheveux, yeux, taille, etc.) est précisée au niveau du résumé si jamais ils doivent y apparaître pour être cohérent avec le texte de l'histoire.
- Attribue des traits de personnalité uniques, des intérêts, des peurs et des rêves à chaque personnage pour une caractérisation approfondie.
- Développe les personnages et leurs relations tout au long de l'histoire en montrant leurs interactions, leurs moments de partage et leur évolution.
- Crée des conflits émotionnels et intellectuels, au-delà des défis physiques.
- Décris en détail les défis physiques et les actions des personnages pour les surmonter. Par exemple, lorsqu'ils traversent la forêt, mentionne les branches qui les gênent, les racines sur lesquelles ils trébuchent, la végétation dense qu'ils doivent écarter. Montre leur fatigue, leurs efforts pour avancer, les émotions qu'ils ressentent face à ces difficultés.
- Fais échouer les personnages principaux à un moment donné. Montre comment ils gèrent cet échec et essaient à nouveau. Décris en détail leurs sentiments de doute, de frustration ou de découragement, et comment ils puisent dans leur détermination et leur amitié pour surmonter cet obstacle. Assure-toi que l'échec est significatif et impacte réellement la progression de l'histoire.
- Crée des conflits entre les personnages principaux, ou entre les personnages principaux et les personnages secondaires.
- Ajoute des rebondissements et des défis supplémentaires pour maintenir l'intérêt des jeunes lecteurs. Décris en détail la réaction des personnages face à ces rebondissements, leurs émotions, leurs doutes et leurs efforts pour s'adapter à la nouvelle situation.
- Résous les conflits de manière créative et non violente, en mettant l'accent sur le pouvoir de la communication et de la coopération.
- Développe les antagonistes en leur donnant des motivations claires, des traits de personnalité distincts et des capacités redoutables qui les rendent réellement menaçants pour les héros. Décris en détail leurs actions pour contrecarrer ou mettre en échec les héros à plusieurs reprises au cours de l'histoire. Montre comment leur présence et leurs actions sèment le doute, la peur ou le découragement chez les héros avant qu'ils ne parviennent à les surmonter.
- Assure-toi que le récit comporte une structure narrative claire avec une introduction captivante, de l'action, des conflits, et une résolution.
- Ajoute un objectif clair pour les personnages à atteindre et un accomplissement significatif à la fin de l'histoire.
- Inclue des moments de réflexion ou d'émotion pour permettre aux lecteurs de se connecter aux personnages et à leurs aventures.
- Varie les interactions entre les personnages pour éviter les répétitions et maintenir l'intérêt.
- Maintiens un bon rythme dans l'histoire en alternant des scènes d'action, de réflexion et d'émotion. Ajoute des éléments de suspense pour maintenir l'intérêt des jeunes lecteurs.
- Utilise abondamment des descriptions visuelles riches en couleurs, en textures et en formes pour stimuler l'imagination des enfants et créer un monde immersif.
- Inclue des descriptions sensorielles pour enrichir l'expérience narrative (sons, odeurs, textures).
- Chaque personnage doit avoir une motivation claire et des traits de caractère distincts.
- Assure-toi que chaque chapitre se termine par un cliffhanger ou une question ouverte pour maintenir l'intérêt des lecteurs.
- Ajoute des éléments éducatifs subtils (faits scientifiques, connaissances culturelles) pour enrichir l'histoire sans alourdir le récit.
- Enrichis les descriptions sensorielles pour permettre aux lecteurs de vraiment "voir", "entendre" et "ressentir" l'environnement des personnages.
- Personnalise l'histoire avec des noms ou des éléments familiers pour une connexion émotionnelle plus forte.
- Intègre des questions de réflexion et d'interaction pour engager les enfants.
- Ajoute des éléments d'humour et des jeux de mots pour rendre l'histoire amusante.
- Utilise des illustrations mentales vives et détaillées pour stimuler l'imagination.
- Intègre une leçon morale ou un message éducatif de manière naturelle dans le récit.
- Intègre des messages positifs et encourageants dans tes histoires, comme l'importance de croire en soi, de poursuivre ses rêves et de surmonter les obstacles.
- Ajoute des éléments d'humour et de légèreté dans tes histoires pour les rendre plus amusantes et agréables à lire pour les enfants.
- Intègre des éléments éducatifs dans tes histoires de manière subtile et ludique, comme des métaphores pour enseigner des concepts scientifiques ou des voyages dans différents pays pour enseigner la géographie et les cultures.
- Ajoute des éléments interactifs dans tes histoires, comme des questions aux enfants, des choix qui influencent l'histoire, ou des petits défis ou jeux à réaliser.
Ajoute des difficultés et des obstacles significatifs pour rendre l'histoire plus engageante et permettre aux héros de montrer leur courage et leur ingéniosité :
- Développe les antagonistes en leur donnant des motivations claires, des traits de personnalité distincts et des capacités redoutables qui les rendent réellement menaçants pour les héros. Décris en détail leurs actions pour contrecarrer ou mettre en échec les héros à plusieurs reprises au cours de l'histoire. Montre comment leur présence et leurs actions sèment le doute, la peur ou le découragement chez les héros avant qu'ils ne parviennent à les surmonter.
- Décris chaque affrontement au niveau quasi "temps réel", avec les actions, réactions, émotions, blessures, etc. détaillées pas à pas, presque comme si on y assistait. Intègre des éléments de surprise, de retournements inattendus au cours de ces affrontements pour augmenter le suspense. Montre comment les capacités et l'ingéniosité des antagonistes poussent les héros dans leurs derniers retranchements.
- Lorsque les héros échouent, prends le temps de décrire en détail leurs émotions négatives (déception, frustration, colère, tristesse, etc.) et leurs doutes intérieurs. Montre qu'ils remettent en question leur capacité à poursuivre leur quête à la suite de ces échecs cuisants. Fais en sorte qu'ils aient besoin d'un véritable déclic intérieur, motivé par l'amitié ou leurs valeurs, pour se relever et persévérer. Montre comment cela impacte leurs relations entre eux (reproches, disputes, tensions, ou au contraire un élan de solidarité).
- Décris les affrontements physiques ou psychologiques étape par étape, en montrant les actions, réactions et émotions ressenties de part et d'autre. N'hésite pas à inclure des blessures, de la souffrance ou de la peur pour les héros lors de ces affrontements acharnés. Fais en sorte que la victoire des héros ne soit jamais acquise d'avance et nécessite des sacrifices ou des prises de risque de leur part.
- Crée des situations où les héros doivent collaborer et utiliser leurs compétences spécifiques pour réussir.
- Intègre des moments de doute ou de découragement pour montrer la persévérance des héros. Décris leurs luttes internes et comment ils trouvent la force de continuer. Fais en sorte que les héros aient besoin d'un véritable déclic intérieur, motivé par l'amitié ou leurs valeurs, pour se relever et persévérer.
- Ajoute des moments où l'amitié ou la confiance entre les héros est mise à rude épreuve par les difficultés rencontrées. Montre comment ils doivent surmonter leurs doutes, leur colère ou leur rancune les uns envers les autres pour rester soudés. Décris leurs prises de conscience, leurs excuses et leur cheminement pour renouer des liens forts malgré l'adversité.
- Place les héros dans des situations où ils doivent faire un choix difficile qui aura des conséquences douloureuses (abandonner un compagnon, renoncer à un rêve, etc.). Montre leur dilemme intérieur, leur déchirement avant de faire ce choix douloureux pour un plus grand bien. N'aie pas peur d'inclure des pertes, des renoncements ou des traumatismes marquants issus de ces choix cornéliens.
- Fais en sorte que les personnages apprennent et grandissent à travers les difficultés qu'ils rencontrent.
- Ajoute des rebondissements inattendus qui changent la direction de l'histoire et maintiennent l'intérêt des lecteurs. Décris en détail la réaction des personnages face à ces rebondissements, leurs émotions, leurs doutes et leurs efforts pour s'adapter à la nouvelle situation.
- Fais en sorte que les antagonistes infligent de véritables blessures physiques et/ou psychologiques aux héros au cours des affrontements. Décris ces blessures, la douleur ressentie, l'impact sur leur moral et leurs capacités à avancer. Montre leur résolution, leur courage pour continuer malgré ces handicaps.
- Assure-toi que chaque défi est pertinent pour l'histoire et contribue au développement des personnages.
- Décris en détail chaque énigme ou défi rencontré par les personnages. Par exemple, si les enfants doivent résoudre des énigmes chantées par les vents, précise le contenu de ces énigmes et la manière dont les enfants trouvent les réponses grâce à leur persévérance ou à l'aide de personnages secondaires.
- Lorsque les personnages surmontent un obstacle, montre le processus complet de leurs tentatives, incluant les échecs et les efforts qu'ils font avant de réussir. Par exemple, détaille comment ils essaient plusieurs méthodes pour résoudre une énigme ou surmonter un défi avant de finalement trouver la solution.
- Intègre des dialogues et des interactions entre les personnages et les gardiens ou les antagonistes qui posent des défis. Par exemple, si un enfant des vents protège un objet précieux, décris la conversation où il teste la patience des héros et les réactions des enfants face à ce test.
- Ajoute des descriptions des émotions et des pensées des personnages lorsqu'ils font face à des épreuves difficiles, montrant leur détermination, leurs doutes, et comment ils surmontent ces sentiments pour réussir.
- Assure-toi que chaque défi est clairement expliqué avec des indices et des solutions logiques que les enfants peuvent comprendre et suivre. Par exemple, spécifie les indices que les héros utilisent pour résoudre les énigmes et comment ces indices les mènent à la solution.
IMPORTANT : Ne traduisez ni modifiez pas les balises suivantes :
[titre]Ton titre ici[end_titre] (balises de titre)
[resume] et [end_resume] (balises de résumé)
N'ajoutez aucune autre balise que celles spécifiées ci-dessus.
Voici comment structurer les descriptions visuelles inspirées par : "{prompt}" :
- Commence chaque description avec la balise [resume] et finis avec la balise [end_resume]. Ne traduisez ni modifiez pas ces balises.
- Les descriptions doivent se concentrer exclusivement sur les éléments visuels sans inclure d'actions ou de dialogues des personnages.
- Chaque élément clé mentionné dans le prompt initial doit être décrit de manière unique et détaillée.
- Ne mentionne chaque élément (personnage, animal, lieu, objet clé) qu'une seule fois dans les descriptions visuelles. Une fois qu'un élément a été décrit, ne le mentionne plus dans les descriptions suivantes, même indirectement.
- Utilise des descriptions riches en couleurs, en textures et en formes pour stimuler l'imagination visuelle.
- Inclue des éléments fantastiques, magiques ou surréalistes pour rendre les scènes plus intéressantes et mémorables.
- Veille à ce que chaque description soit suffisamment détaillée pour permettre la création d'une illustration complète.
Exemple de structure de descriptions visuelles (ces exemples sont seulement pour référence, ne les utilisez pas tels quels dans l'histoire) :
[resume]Un koala super sympa avec une fourrure douce et grise, des yeux pétillants et un sourire amical. Il est assis sur une branche d'eucalyptus, grignotant des feuilles et observant son environnement avec curiosité.[end_resume]
[resume]Un escargot très méchant avec une coquille noire et luisante, et des yeux perçants qui semblent voir à travers tout. Il se déplace lentement mais de manière menaçante, laissant derrière lui une traînée de bave visqueuse.[end_resume]
[resume]Un arbre magique avec des feuilles d'un bleu profond qui brillent comme des étoiles. Des oiseaux de toutes les couleurs chantent autour des branches, ajoutant une mélodie enchantée à l'atmosphère mystique.[end_resume]
Assure-toi que chaque description visuelle est riche, détaillée et entièrement nouvelle, sans aucune répétition d'éléments précédents. Évite d'utiliser les exemples fournis ci-dessus et crée des descriptions fraîches pour chaque scène.
La conclusion de l'histoire doit renforcer les thèmes de l'aventure et de l'amitié avec une touche plus percutante, et être accompagnée d'une dernière description visuelle marquante.
[resume]Visualise le chemin de retour à travers un paysage unique et magique, différent pour chaque histoire. Par exemple, un pont arc-en-ciel, un sentier lumineux sous une pluie d'étoiles filantes, des pas dans le sable avec un soleil couchant, etc. Assure-toi que la description finale est riche en détails visuels et évoque une atmosphère enchantée et inoubliable.[end_resume]
Pour varier les débuts d'histoire et éviter la répétition, choisis parmi les exemples suivants, ou laisse libre cours à ton imagination :
- Une classe à l'école, un voyage en famille, une fête d'anniversaire, une visite chez les grands-parents, un jour de pluie où les enfants jouent à l'intérieur, une sortie en nature, etc.
- La découverte d'un livre magique, une rencontre inattendue avec un personnage mystérieux, un rêve étrange qui devient réalité, un message secret trouvé dans une bouteille, un animal parlant qui apparaît soudainement, etc.
- Des personnages principaux différents : une fratrie, des amis, un enfant et son grand-parent, un groupe de camarades de classe, etc.
- Des lieux de départ variés : une maison en ville, une cabane dans les bois, un appartement au bord de la mer, une ferme, une école, etc.
- Déclencheur de l'aventure variés aussi : un portail vers un monde magique, un objet mystérieux trouvé dans le grenier, un événement étrange comme une éclipse ou une étoile filante, un animal parlant qui a besoin d'aide, un visiteur de l'espace, etc.
Cette structure aide à créer un récit harmonieux et visuellement riche, propice à l'illustration et captivant pour les enfants.
Attention, je te rappelle la langue cible de l'histoire : "{language_description}"
"""
Construcción del prompt
El prompt está diseñado para proporcionar al modelo de IA toda la información necesaria para generar una historia coherente, educativa y adaptada a los niños.
-
Idioma : El parámetro
language_descriptionpermite especificar el idioma de la historia, asegurando que el texto generado esté en el idioma deseado. -
Tema : El prompt del usuario se integra en las instrucciones para servir de base a la historia.
-
Longitud : Se especifica un rango de 1000 a 1500 palabras para controlar la longitud de la historia.
-
Elementos clave : Las instrucciones fomentan la inclusión de elementos tales como la aventura, la magia y valores educativos importantes.
Detalles de las instrucciones
Las instrucciones proporcionadas al modelo son extremadamente detalladas para guiar la generación de manera precisa.
Aquí tienes un análisis de las diferentes partes del prompt:
-
Estructura narrativa : Se pide al modelo que estructure la historia con un comienzo cautivador, un desarrollo rico en acontecimientos y una conclusión satisfactoria.
-
Descripciones visuales : La historia debe ser rica en descripciones visuales para estimular la imaginación de los niños.
-
Personajes : Se fomenta el desarrollo de personajes entrañables con personalidades distintas.
-
Etiquetas específicas : Se utilizan etiquetas como
[titre]... [end_titre]y[resume]... [end_resume]para delimitar el título y las descripciones visuales. -
Elementos fantásticos : Se invita al modelo a incluir elementos mágicos o fantásticos para hacer la historia más atractiva.
-
Valores educativos : La historia debe transmitir valores importantes.
Rol de las etiquetas Las etiquetas desempeñan un papel crucial en el procesamiento posterior del texto generado.
-
[titre]… [end_titre] : Enmarca el título de la historia. Esto permite extraerlo fácilmente para mostrarlo adecuadamente en la interfaz de usuario.
-
[resume]… [end_resume] : Enmarca las descripciones visuales detalladas de escenas clave de la historia. Estos resúmenes se usarán como prompts para la generación de imágenes.
Procesamiento Tras la Generación
Una vez que el modelo de IA ha generado la historia siguiendo estas instrucciones, el código realiza los siguientes pasos :
-
Corrección de las Etiquetas : La función
correct_resume_tagsse asegura de que todas las etiquetas estén correctamente formateadas para la extracción. -
Extracción de los Resúmenes : La función
extract_summariesutiliza las etiquetas[resume]y[end_resume]para extraer las descripciones visuales. -
Generación de Imágenes : Cada resumen se pasa a la función
generate_imagepara crear una imagen correspondiente. -
Creación del Contenido HTML : El texto de la historia y las imágenes generadas se combinan para crear una página HTML completa.
Impacto en la Generación
Al proporcionar estas instrucciones detalladas, el modelo se guía para :
-
Respetar el Formato : Al usar las etiquetas especificadas, el modelo produce un texto estructurado que facilita el procesamiento automatizado.
-
Generar Contenido Adecuado : Las restricciones sobre el idioma, el estilo y los temas garantizan que la historia sea apropiada para el público objetivo.
-
Facilitar la Generación de Imágenes : Al extraer descripciones visuales precisas, se obtienen prompts de calidad para la generación de imágenes.
Gestión de las Etiquetas por el Modelo
El modelo está explícitamente instruido para no traducir ni modificar las etiquetas. Esto es esencial para que las etiquetas permanezcan intactas y puedan utilizarse para el postprocesamiento. Las instrucciones insisten en este punto para evitar que el modelo, que podría intentar parafrasear o traducir todo el texto, altere las etiquetas.
Generación de la Historia
Una vez que las instrucciones detalladas son generadas por la función generate_story_instructions, el siguiente paso es pasar estas instrucciones al modelo de IA para que cree la historia.
def generate_story(prompt, model_type, model_id, language, api_key=None, region_name="us-east-1"):
instruction = generate_story_instructions(prompt, language)
if model_type == "openai":
client = OpenAI(api_key=api_key)
try:
response = client.chat.completions.create(
model=model_id,
messages=[
{
"role": "system",
"content": "Vous êtes un assistant AI expert des histoires pour enfant.",
},
{"role": "user", "content": instruction},
],
)
first_choice_message = response.choices[0].message
return first_choice_message.content
except Exception as e:
return f"Une erreur est survenue : {e}"
# Gestion des autres modèles (Mistral, Anthropic, Meta) via Amazon Bedrock
Interacción con el Modelo OpenAI
-
Cliente OpenAI : Instancio un cliente OpenAI usando la clave API obtenida previamente.
-
Prompting : El modelo recibe una serie de mensajes :
- Un mensaje del sistema indicando que el asistente es un experto en historias para niños.
- El mensaje del usuario que contiene las instrucciones detalladas generadas.
-
Respuesta del Modelo : El modelo genera una historia basándose en las instrucciones proporcionadas.
Gestión de Errores
Si ocurre una excepción durante la llamada a la API de OpenAI, esta se captura y se devuelve un mensaje de error.
Extracción de los Resúmenes y Etiquetas
Tras la generación de la historia, el siguiente paso consiste en extraer las descripciones visuales usando las etiquetas especificadas.
def correct_resume_tags(text):
# Corrige les balises 'résumé', 'resume', 'titre' et leurs variantes en 'resume' et 'titre' respectivement dans le texte généré.
def extract_summaries(text):
pattern = r"\[resume\](.*?)\[end_resume\]"
summaries = re.findall(pattern, text, re.DOTALL)
return summaries
Corrección de las Etiquetas
El modelo a veces puede alterar ligeramente las etiquetas (por ejemplo, añadir acentos). La función correct_resume_tags se asegura de que todas las etiquetas sean uniformes y estén correctamente formateadas.
Extracción de los Resúmenes
La función extract_summaries utiliza una expresión regular para encontrar todas las ocurrencias de texto entre las etiquetas [resume] y [end_resume]. Estos resúmenes son las descripciones visuales detalladas que se usarán para generar las imágenes.
Generación de Imágenes
Una vez extraídos los resúmenes, cada resumen se utiliza para generar una imagen correspondiente.
def generate_image_for_each_summary(summaries, model, bucket_name, seed, style, size, quality, language):
images_urls = []
for summary in summaries:
image_data = generate_image(summary, model, seed, style, size, quality, language)
if image_data is not None:
image_url = upload_to_s3(image_data, bucket_name)
images_urls.append(image_url)
else:
images_urls.append("")
return images_urls
Función generate_image
La función generate_image llama a la API del modelo de generación de imágenes (por ejemplo, OpenAI DALL·E) para crear una imagen a partir del resumen.
def generate_image(prompt, model, seed, style, size, quality, language):
width, height = extract_dimensions(size)
if model == "openai":
client = OpenAI(api_key=parameter["Parameter"]["Value"])
adjusted_prompt = generate_image_instructions(prompt, style, language)
try:
response = client.images.generate(
prompt=adjusted_prompt,
model=os.environ.get("OPENAI_IMAGE_MODEL"),
n=1,
size=size,
response_format="b64_json",
quality=quality,
user="user_id",
)
image_data = response.data[0].b64_json
return image_data
except Exception as e:
logger.error(f"Error generating image with OpenAI: {str(e)}", exc_info=True)
return None
# Gestion des autres modèles (Titan, Stable Diffusion) via Amazon Bedrock
Generación de las Instrucciones para las Imágenes
La función generate_image_instructions adapta el resumen para crear un prompt apropiado para la generación de imágenes.
def generate_image_instructions(prompt, style, language):
language_description = get_language_description(language)
return f"""
Génère un dessin pour enfant dans le style "{style}" basé sur cette description en langue "{language_description}" : {prompt}.
La scène doit être purement visuelle, sans aucun texte, et conçue pour éveiller l'émerveillement chez les jeunes spectateurs.
"""
-
Estilo : El estilo especificado por el usuario (por ejemplo, “acuarela”, “caricatura”) se incluye en el prompt para influir en el resultado de la imagen.
-
Idioma : La descripción se adapta al idioma elegido, lo que puede ayudar al modelo a comprender los matices culturales.
-
Instrucciones Claras : Al especificar que la escena debe ser puramente visual, se evita que el modelo añada texto o elementos no deseados en la imagen.
Interacción con la API OpenAI para las Imágenes
-
Llamada a la API : La función
client.images.generatese usa para generar la imagen. -
Parámetros Importantes :
- Prompt : El prompt ajustado se envía a la API.
- Modelo : El modelo de generación de imágenes especificado.
- Tamaño : El tamaño de la imagen (por ejemplo, “1024x1024”).
- Calidad : La calidad de la imagen (estándar, HD).
- Formato de Respuesta : Las imágenes se devuelven en base64 para facilitar el almacenamiento y la manipulación.
Gestión de Errores
Los errores durante la generación de imágenes se capturan y registran, lo que permite diagnosticar los problemas.
Creación del Contenido HTML
Después de generar las imágenes correspondientes a los resúmenes extraídos, el siguiente paso consiste en ensamblar el texto de la historia y las imágenes en un formato presentable para el usuario. Esto se hace creando un contenido HTML estructurado que se mostrará en el sitio web.
def create_html_with_images(text_data, images_urls, generate_images=True):
"""
Crée un contenu HTML en intégrant le texte et les images générées.
"""
# Extraction du titre
title_match = re.search(r"\[titre\](.*?)\[end_titre\]", text_data)
if title_match is not None:
title = title_match.group(1)
text_data = text_data.replace(title_match.group(0), "")
else:
title = "Histoire Générée par l'IA"
# Initialisation du contenu HTML
html_content = """
<html>
<head>
<title>Histoire générée par l'IA</title>
<meta charset='UTF-8'>
<style>
/* Styles CSS pour une présentation agréable */
body { font-family: Arial, sans-serif; margin: 20px; }
.title { text-align: center; font-size: 2em; margin-bottom: 20px; }
.center { text-align: center; }
img { max-width: 100%; height: auto; margin: 20px 0; }
p { text-align: justify; line-height: 1.6; }
</style>
</head>
<body>
"""
html_content += f'<div class="title">{title}</div>\n'
# Séparation du texte en segments basés sur les résumés
summaries = extract_summaries(text_data)
segments = re.split(r"\[resume\].*?\[end_resume\]", text_data, flags=re.DOTALL)
# Assemblage du texte et des images
for i, segment in enumerate(segments):
formatted_segment = segment.strip().replace("\n", "<br>")
html_content += f"<p>{formatted_segment}</p>\n"
if generate_images and i < len(images_urls) and images_urls[i]:
image_url = images_urls[i]
html_content += f'<div class="center"><img src="{image_url}" alt="Image générée"></div>\n'
html_content += "</body></html>"
return html_content
Explicación en Detalle
-
Extracción del Título:
- Utiliza una expresión regular para encontrar el texto entre las etiquetas
[titre]y[end_titre]. - Elimina las etiquetas del texto principal después de la extracción.
- Si no se encuentra ningún título, se utiliza un título por defecto.
- Utiliza una expresión regular para encontrar el texto entre las etiquetas
-
Inicialización del HTML:
- El contenido HTML comienza con las etiquetas
<html>,<head>y<body>. - Se incluyen estilos CSS para mejorar la presentación (tipografía, márgenes, alineaciones).
- El contenido HTML comienza con las etiquetas
-
Separación del Texto:
- El texto se divide en segmentos utilizando las etiquetas
[resume]y[end_resume]. - Los segmentos representan las partes de la historia sin los resúmenes.
- El texto se divide en segmentos utilizando las etiquetas
-
Montaje:
- Cada segmento de texto se inserta en un párrafo
<p>. - Si la generación de imágenes está activada y hay una imagen correspondiente, la imagen se inserta después del párrafo.
- Las imágenes se centran y se adaptan al tamaño de la pantalla para una mejor experiencia de usuario.
- Cada segmento de texto se inserta en un párrafo
-
Finalización:
- Se añaden las etiquetas de cierre
</body>y</html>para completar el documento HTML.
- Se añaden las etiquetas de cierre
¿Por Qué Este Enfoque?
-
Alineación del Texto y las Imágenes: Al insertar las imágenes después de los segmentos de texto correspondientes, la historia se enriquece visualmente, lo que es particularmente importante para los niños.
-
Flexibilidad: Si el usuario elige no generar imágenes, el código gestiona este caso insertando solo el texto.
-
Accesibilidad: Al usar etiquetas semánticas y estilos adecuados, el contenido es accesible en diferentes dispositivos (ordenadores, tabletas, smartphones).
Subida a S3 y Actualización del Estado
Una vez generado el contenido HTML, es necesario ponerlo a disposición del usuario. Esto se hace subiendo el archivo a un bucket S3 configurado para el alojamiento de sitios web estáticos.
def upload_to_s3(content, bucket_name, content_type="image/jpeg"):
"""
Télécharge le contenu sur S3 et retourne l'URL publique.
"""
s3_client = boto3.client("s3")
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
if content_type == "image/jpeg":
file_name = f"generated_images/{timestamp}.jpg"
content_to_upload = base64.b64decode(content)
else:
file_name = f"generated_content/{timestamp}.html"
content_to_upload = content.encode("utf-8")
content_type = "text/html; charset=utf-8"
try:
s3_client.put_object(
Bucket=bucket_name,
Key=file_name,
Body=content_to_upload,
ContentType=content_type,
ACL='public-read'
)
return f"https://{os.environ['CLOUDFRONT_DOMAIN']}/{file_name}"
except Exception as e:
logger.error(f"Error uploading content to S3: {e}", exc_info=True)
raise
Detalles Técnicos
-
Nomenclatura de los Archivos:
- Los archivos se nombran usando una marca temporal para garantizar la unicidad.
- Las imágenes se almacenan en la carpeta
generated_images/y los archivos HTML engenerated_content/.
-
Subida a S3:
- Uso del cliente S3 de
boto3para interactuar con el servicio. - El contenido se codifica o decodifica según el tipo (imagen o texto).
- El parámetro
ACL='public-read'hace que el archivo sea accesible públicamente.
- Uso del cliente S3 de
-
Construcción de la URL:
- La URL pública se construye usando el dominio de CloudFront configurado, lo que permite una distribución rápida y segura del contenido.
-
Gestión de Excepciones:
- En caso de error durante la subida, la excepción se registra y se lanza para ser tratada por
lambda_handler.
- En caso de error durante la subida, la excepción se registra y se lanza para ser tratada por
Función Principal lambda_handler
La función lambda_handler es el punto de entrada de la función Lambda. Orquesta todos los pasos descritos anteriormente.
def lambda_handler(event, context):
"""
Point d'entrée de la fonction Lambda.
"""
try:
# Récupération des données de la requête
request_id = event.get("requestId")
body = json.loads(event.get("body", "{}"))
prompt = body.get("text", "Texte par défaut")
# Récupération des autres paramètres (modèles, langue, etc.)
# Mise à jour du statut dans DynamoDB
update_dynamodb(request_id, "Processing")
# Génération de l'histoire
text_data = generate_story(prompt, story_generation_model, model_id, language, api_key)
# Correction des balises et extraction des résumés
text_data = correct_resume_tags(text_data)
summaries = extract_summaries(text_data)
# Génération des images
images_urls = []
if generate_images and summaries:
images_urls = generate_image_for_each_summary(
summaries, image_generation_model, bucket_name, seed, style_with_spaces, size, quality, language
)
# Création du contenu HTML
html_content = create_html_with_images(text_data, images_urls, generate_images)
# Upload du contenu sur S3
result_url = upload_to_s3(html_content, bucket_name, content_type="text/html")
# Mise à jour du statut avec le lien du résultat
update_dynamodb(request_id, "link", result_url)
# Retour de la réponse HTTP
return {
"isBase64Encoded": False,
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"requestId": request_id, "resultUrl": result_url}),
}
except Exception as e:
logger.error(f"Erreur lors de l'exécution de la fonction lambda: {str(e)}", exc_info=True)
update_dynamodb(request_id, "Failed")
return {
"statusCode": 500,
"body": json.dumps({"message": "Internal server error"}),
"headers": {"Content-Type": "application/json"},
}
Explicación
-
Procesamiento de la Solicitud:
- Recupera la información necesaria desde el evento (event) recibido.
- Los parámetros de la solicitud incluyen el prompt, los modelos seleccionados, el idioma, etc.
-
Actualización del Estado:
- Antes de comenzar el procesamiento, el estado se actualiza a “Processing” en DynamoDB.
-
Generación de la Historia:
- Llamada a
generate_storycon los parámetros apropiados.
- Llamada a
-
Extracción y Procesamiento:
- Se corrigen las etiquetas y se extraen los resúmenes para la generación de imágenes.
-
Generación de Imágenes:
- Si la generación de imágenes está activada, se generan las imágenes correspondientes y se recopilan las URLs.
-
Creación del Contenido HTML:
- El texto y las imágenes se combinan para crear el contenido HTML final.
-
Subida a S3:
- El contenido HTML se sube a S3 y se obtiene la URL del resultado.
-
Actualización del Estado Final:
- El estado se actualiza a “link” con la URL del resultado en DynamoDB.
-
Retorno de la Respuesta:
- La respuesta incluye el
requestIdy la URL del resultado, permitiendo al cliente verificar el estado o acceder directamente al contenido.
- La respuesta incluye el
-
Gestión de Excepciones:
- En caso de error, el estado se actualiza a “Failed” y se devuelve una respuesta HTTP 500.
Función Lambda status_checker.py
Visión General
La función status_checker.py permite a los usuarios verificar el estado de su solicitud de generación de historia. Consulta DynamoDB para recuperar el estado actual y, si está disponible, la URL del resultado.
Análisis del Código
import boto3
import json
from botocore.exceptions import ClientError
def lambda_handler(event, context):
"""
Fonction Lambda pour vérifier le statut d'une demande.
"""
# Initialisation de DynamoDB
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("TaskStatus")
# Définition des en-têtes HTTP
headers = {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,OPTIONS",
"Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
}
# Récupération du requestId depuis les paramètres de la requête
query_params = event.get("queryStringParameters")
if not query_params or "requestId" not in query_params:
return {
"statusCode": 400,
"body": json.dumps({"message": "Missing requestId"}),
"headers": headers,
}
request_id = query_params["requestId"]
try:
# Récupération de l'élément dans DynamoDB
response = table.get_item(Key={"requestId": request_id})
if "Item" in response:
item = response["Item"]
return {
"statusCode": 200,
"body": json.dumps(
{
"status": item.get("status", "Unknown"),
"resultUrl": item.get("resultUrl", ""),
}
),
"headers": headers,
}
else:
return {
"statusCode": 404,
"body": json.dumps({"message": "Request ID not found"}),
"headers": headers,
}
except ClientError as e:
return {
"statusCode": 500,
"body": json.dumps({"message": str(e)}),
"headers": headers,
}
Detalles
-
Recuperación del
requestId:- El
requestIdes esencial para identificar la solicitud específica del usuario.
- El
-
Consulta a DynamoDB:
- La función intenta recuperar el elemento correspondiente a
requestId. - Si el elemento existe, se extraen el estado y
resultUrl.
- La función intenta recuperar el elemento correspondiente a
-
Construcción de la Respuesta:
- Si el estado está disponible, se devuelve junto con la URL del resultado.
- Si el elemento no se encuentra, se devuelve un error 404.
- En caso de error al consultar la base de datos, se devuelve un error 500 con un mensaje apropiado.
-
Cabeceras HTTP:
- Se definen las cabeceras para permitir las solicitudes CORS desde el sitio web.
Integración con API Gateway
Configuración de los Endpoints
API Gateway expone dos endpoints principales para interactuar con las funciones Lambda :
-
/generate-image:- Método :
POST - Descripción : Permite a los usuarios iniciar la generación de una historia y, opcionalmente, de las imágenes asociadas.
- Integración : Conectado a la función Lambda
StoryPixAI.py.
- Método :
-
/check-status:- Método :
GET - Descripción : Permite a los usuarios verificar el estado de su solicitud proporcionando el
requestId. - Integración : Conectado a la función Lambda
status_checker.py.
- Método :
Autenticación con Cognito
Para asegurar la API y controlar el acceso a los recursos, he integrado Amazon Cognito.
-
User Pool:
- Gestiona las credenciales de los usuarios.
- Permite el registro, el inicio de sesión y la gestión de usuarios.
-
Authorizer:
- Configurado en API Gateway para verificar los tokens JWT emitidos por Cognito.
- Asegura que solo las solicitudes autenticadas puedan acceder a los endpoints protegidos.
-
Integración en API Gateway:
- Los endpoints
/generate-imagey/check-statusestán protegidos por el authorizer de Cognito. - Los clientes deben incluir el token de autenticación en las cabeceras de sus solicitudes (
Authorization).
- Los endpoints
Sitio Estático en S3 e Interacción con la API
Estructura del Sitio
El sitio web estático sirve como interfaz de usuario para la aplicación.
-
index.html:- Contiene el formulario que permite a los usuarios introducir el prompt, elegir las opciones de generación y enviar su solicitud.
- Incluye los scripts necesarios para la interacción con la API y la gestión de la autenticación.
-
storypixai.js:- Contiene el código JavaScript para gestionar las interacciones con la API.
- Gestiona la autenticación con Cognito, el envío del formulario, el seguimiento del estado y la visualización de los resultados.
Flujo de Trabajo del Usuario
-
Conexión:
- El usuario se conecta a través del formulario de inicio de sesión integrado.
- La información se verifica mediante Cognito.
-
Envío de la Solicitud:
- El usuario completa el formulario con el prompt y las opciones deseadas.
- Al enviar, se envía una petición
POSTal endpoint/generate-imagecon los datos.
-
Procesamiento Asíncrono:
- La API devuelve inmediatamente un
requestId. - El procesamiento de la generación se realiza en segundo plano.
- La API devuelve inmediatamente un
-
Verificación del Estado:
- El sitio web consulta periódicamente el endpoint
/check-statusproporcionando elrequestId. - Una vez recibido el estado “link”, la URL del resultado se muestra al usuario.
- El sitio web consulta periódicamente el endpoint
-
Visualización del Resultado:
- El usuario puede hacer clic en el enlace para acceder a la historia generada con las imágenes.
Gestión de las Solicitudes y las Respuestas
-
Solicitudes Autenticadas:
- Todas las solicitudes a la API incluyen el token de autenticación.
- El token es gestionado por el SDK de Cognito incluido en el sitio web.
-
Gestión de los Estados:
- Los estados posibles son “Processing”, “link”, “Failed”.
- El sitio adapta su interfaz en función del estado recibido (por ejemplo, muestra un spinner, un mensaje de error, un enlace al resultado).
Interconexiones entre los Componentes
Así es como interactúan los distintos componentes :
-
Sitio Web ↔️ API Gateway:
- El sitio web envía solicitudes a los endpoints expuestos por API Gateway.
- Se incluyen los tokens de autenticación para asegurar las solicitudes.
-
API Gateway ↔️ Funciones Lambda:
- API Gateway invoca las funciones Lambda correspondientes según las solicitudes recibidas.
-
Funciones Lambda ↔️ DynamoDB:
- Las funciones Lambda
StoryPixAI.pyystatus_checker.pyinteractúan con DynamoDB para actualizar y recuperar el estado de las solicitudes.
- Las funciones Lambda
-
Función Lambda ↔️ S3:
- La función
StoryPixAI.pysube las imágenes generadas y el contenido HTML a S3.
- La función
-
CloudFront ↔️ S3:
- CloudFront se utiliza para distribuir el contenido almacenado en S3 de forma rápida y segura.
- Las URLs proporcionadas a los usuarios apuntan al dominio de CloudFront.
-
Usuario ↔️ Sitio Web:
- El usuario interactúa con el sitio web para enviar solicitudes y visualizar los resultados.
Ejemplo de Resultado en los logs CloudWatch tras una Llamada de Solicitud
Aquí hay un ejemplo de un resultado de logs tras una llamada de solicitud para que puedan ver el formato bruto de los datos generados :
[INFO] 2024-07-22T19:13:49.764Z 4ec7d759-2fd2-49ca-b929-4f4d12629c73 Texte généré par l'ia : [titre]Tom et Zoé à l'aventure ![end_titre]
Une belle matinée de printemps, Tom et Zoé se retrouvent chez leur grand-mère pour les vacances. Tom a des cheveux bruns et ébouriffés, des yeux verts pétillants et une tache de rousseur sur le nez. Zoé, elle, a de longs cheveux blonds tressés, des yeux bleus comme le ciel et toujours un sourire aux lèvres.
Ce jour-là, alors qu'ils jouent dans le jardin, ils découvrent quelque chose d'étrange près du vieux puits. "Regarde, Zoé, cette lumière étrange !", s'exclame Tom.
"On dirait un passage secret...", murmure Zoé avec fascination. Ils se regardent, surexcités par la perspective d'une aventure. Ils s'approchent prudemment et tombent sur un escalier en colimaçon menant sous terre. Sans hésiter, ils commencent à descendre.
L'escalier les mène à une forêt lumineuse où les arbres sont couverts de feuilles dorées et où des fleurs scintillent de toutes les couleurs de l'arc-en-ciel. Le sol est tapissé de mousse douce et le chant mélodieux des oiseaux résonne autour d'eux.
[resume]Un arbre gigantesque au centre de la clairière, avec des racines enchevêtrées formant des arches naturelles. Ses feuilles changent de couleur au gré du vent, passant du vert émeraude au violet profond. Autour de ses branches, des lucioles dansent et éclairent l'atmosphère d'une lumière douce et féerique.[end_resume]
Alors qu'ils explorent les environs émerveillés, un petit renard roux avec une touffe blanche sur la queue surgit devant eux. "Bonjour, je m'appelle Félix. Êtes-vous perdus ?"
"Non, pas vraiment. Nous cherchons simplement à explorer !" répondent-ils en chœur.
"Alors, vous êtes au bon endroit. Mais attention, quelque chose de précieux est en danger ici. Un méga escargot vole toutes les salades du jardin magique et il faut l'arrêter ! Voulez-vous m'aider ?" demande Félix.
Tom et Zoé, enthousiastes devant cette mission, acceptent sans hésiter.
Félix les guide à travers des sentiers sinueux, où les branches des arbres semblent former des arches protectrices au-dessus de leurs têtes. La route devient de plus en plus difficile à mesure qu'ils s'enfoncent dans la forêt.
[resume]Une rivière cristalline aux eaux claires comme le verre, dans laquelle nagent des poissons multicolores. Les rives sont bordées de galets ronds et lisses, et des nénuphars aux fleurs roses flottent doucement à la surface.[end_resume]
"
Regardez là-bas, derrière ce buisson," chuchote Félix, en pointant une direction. Derrière les plantes, ils aperçoivent une trace de bave visqueuse brillamment éclairée.
"Ça doit être l'escargot," murmure Tom.
Ils suivent la piste de bave jusqu'à une clairière où ils tombent face à face avec le méga escargot. Il est énorme, avec une coquille noire et luisante et des yeux perçants qui semblent voir à travers tout.
"Je suis le protecteur de ces salades !" s'exclame l'escargot d'une voix grondante. "Elles m'appartiennent toutes !"
[resume]Le méga escargot est si grand que sa coquille ressemble à une petite montagne arrondie. Elle est noire avec des motifs argentés en spirale qui brillent sous le soleil. Ses antennes sont longues et frémissent à chaque mouvement. Il laisse derrière lui une traînée de bave qui scintille comme des cristaux de glace.[end_resume]
"Mais ces salades nourrissent tout le monde ici," réplique Zoé courageusement. "Il faut partager !"
L'escargot se met à rire et glisse vers eux lentement mais de manière menaçante. Tom et Zoé échangent un regard, ils savent qu'ils doivent utiliser leur intelligence et leur courage pour résoudre ce problème.
"Il y a sans doute un moyen de convaincre l'escargot !" dit Félix. "Utilisons la magie de cette forêt pour lui montrer une meilleure voie."
Zoé, qui découvre soudain qu'elle possède un pouvoir magique, ferme les yeux et se concentre. Elle sent une énergie chaude circuler en elle. Elle lève la main et des lianes lumineuses surgissent du sol, s'enroulant doucement autour de l'escargot sans lui faire de mal.
"Je vais créer un jardin immense juste pour toi," annonce Zoé, "mais tu devras promettre de partager avec tout le monde ici."
L'escargot, touché par la bonté de Zoé, hésite puis accepte. "Je ne savais pas que j'avais blessé autant de monde. Merci de me montrer un autre chemin."
Les lianes lumineuses dessinent alors un magnifique jardin rempli de salades et d'autres délices pour l'escargot. Cependant, le jardin ne s’ouvre que s’il appelle les autres créatures pour partager.
[resume]Un jardin magnifique avec des salades immenses, leurs feuilles vert tendre et croquantes. Des carottes orange vif et des courgettes vertes s'y mêlent, baignant dans une lumière dorée. Des papillons aux ailes irisées volent autour, ajoutant une touche de magie à ce lieu merveilleux.[end_resume]
En voyant cela, l'escargot laisse échapper une larme de reconnaissance et appelle instantanément les animaux de la forêt pour voir le miracle. Les habitants de la forêt acclament Tom et Zoé. Un énorme festin est organisé en leur honneur.
"Merci d'avoir sauvé notre jardin et notre amitié !" s'exclame Félix avec émotion.
Puis, ils se disent au revoir et, guidés par Félix, Tom et Zoé retrouvent le chemin de la maison. Au moment de passer le portail magique, ils se retournent une dernière fois pour admirer le spectacle enchanteur.
[resume]Un pont arc-en-ciel scintillant traverse le ciel, connectant la forêt magique à leur monde. Les couleurs brisées de l'arc iridescent se mélangent sous leurs regards émerveillés, illuminant la verdure environnante sous une lumière douce et chaleureuse. Chaque pas sur le pont résonne d'une mélodie cristalline.[end_resume]
Ils reprennent leur place dans le jardin de leur grand-mère, main dans la main, renforcés par cette aventure. "Tom, tu penses qu'on reverra Félix ?" demande Zoé rêveusement.
"J'espère bien ! Et qui sait quelle nouvelle aventure nous attend !" répond Tom en souriant.
La journée se termine sous le ciel étoilé, et leur amitié est plus forte que jamais, une étoile brillante dans l'univers de leurs rêves et de leurs aventures.
Les défis et les épreuves leur ont appris des valeurs précieuses : l'amitié, le partage, la persévérance, et surtout, la gentillesse.
Et c'est ainsi que Tom et Zoé grandissent, un peu plus chaque jour, devenant eux-mêmes des héros dans leurs cœurs d'enfants.
[resume]Des étoiles filantes traversent un ciel de velours noir, chaque trainée lumineuse ajoutant une touche de mystère à la nuit. Sur le chemin du retour, chaque pas dans le sable semble faire briller les grains comme des diamants sous la douce lumière de la lune. Une douce brise apporte l'odeur salée de la mer, ponctuée par le murmure des vagues au loin.[end_resume]
Fin.
Integración Continua con GitLab CI/CD
Para asegurar un desarrollo y despliegue fluidos de StoryPixAI, he implementado una canalización de integración continua (CI) y despliegue continuo (CD) usando GitLab CI/CD. Esta configuración automatiza los procesos de construcción y despliegue, garantizando la calidad y la fiabilidad del código en cada modificación.
Configuración de la Pipeline
La pipeline se define en el archivo .gitlab-ci.yml en la raíz del proyecto. Aquí hay una vista general de su estructura :
stages:
- Pré-requis optionel
- Vérifications
- Déploiements
- Management
- Suppressions
variables:
TERRAFORM_VERSION: '1.5.7-*'
TF_VAR_region: $AWS_DEFAULT_REGION
``` Esta configuración define las diferentes etapas del pipeline y las variables globales utilizadas en el proceso CI/CD.
### Trabajos Principales
El pipeline incluye varios trabajos clave:
1. **Verificación Terraform** :
```yaml
Vérification Terraform:
stage: Vérifications
when: manual
script:
- /bin/bash -c "source export.sh && terraform_plan"
Este job ejecuta terraform plan para verificar los cambios de infraestructura previstos sin aplicarlos.
- Despliegue Terraform :
Déploiement Terraform:
stage: Déploiements
when: manual
dependencies:
- Vérification Terraform
script:
- /bin/bash -c "source export.sh && terraform_apply"
Después de la verificación, este job aplica los cambios de infraestructura ejecutando terraform apply.
- Eliminación Terraform :
Suppression Terraform:
stage: Suppressions
when: manual
script:
- /bin/bash -c "source export.sh && terraform_destroy"
Este job permite destruir la infraestructura si es necesario, ejecutando terraform destroy.
- Gestión de claves OpenAI :
Clé OpenAI - Ajout:
stage: Pré-requis optionel
when: manual
script:
- |
KEYS_FOUND=false
if [ -n "$OPENAI_KEY" ]; then
/bin/bash -c "source export.sh && manage_openai_key put $OPENAI_KEY"
KEYS_FOUND=true
fi
if [ "$KEYS_FOUND" = false ]; then
echo "Aucune clé trouvée."
exit 1
fi
Clé OpenAI - Supression:
stage: Suppressions
when: manual
script:
- /bin/bash -c "source export.sh && manage_openai_key delete"
Estos jobs gestionan la adición y la eliminación segura de las claves API de OpenAI en AWS Parameter Store.
Entorno de ejecución
Cada job se ejecuta en un contenedor Docker basado en Ubuntu 22.04, con Terraform y AWS CLI instalados :
.terraform_template: &terraform_template
image:
name: ubuntu:22.04
before_script:
- apt-get update
- apt-get install -y gnupg software-properties-common curl
- curl -fsSL https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep 'VERSION_CODENAME' /etc/os-release | cut -d'=' -f2) main" | tee /etc/apt/sources.list.d/hashicorp.list
- apt-get update
- apt-get install -y terraform=${TERRAFORM_VERSION} python3-pip bash jq zip
- pip3 install awscli >> /dev/null
Ventajas de este enfoque CI/CD
-
Automatización : Cada modificación del código dispara automáticamente el pipeline, asegurando verificaciones y despliegues coherentes.
-
Control manual : Las etapas críticas como el despliegue y la eliminación están configuradas en modo manual (
when: manual), ofreciendo un control adicional antes de la ejecución. -
Gestión segura de secretos : La integración con AWS Parameter Store para la gestión de las claves API asegura un manejo seguro de la información sensible.
-
Flexibilidad : La estructura en etapas permite una ejecución ordenada y lógica de las diferentes etapas del pipeline.
-
Reproducibilidad : El uso de un entorno Docker estandarizado garantiza que las builds y las pruebas sean reproducibles en distintos sistemas.
Esta configuración CI/CD permite no solo automatizar el despliegue de StoryPixAI, sino también mantener un alto nivel de calidad y fiabilidad a lo largo del ciclo de desarrollo.
Conclusión
StoryPixAI fue mucho más que un simple proyecto técnico. Fue una verdadera aventura en el mundo de la IA generativa, que me permitió combinar mi pasión por la tecnología con el deseo de crear historias mágicas para mis hijos.
Este proyecto me brindó la oportunidad de explorar diversas facetas de la IA, desde el diseño de una interfaz de usuario intuitiva hasta el dominio del prompting, pasando por la implementación de una infraestructura cloud robusta con AWS y Terraform. Cada etapa fue una fuente de aprendizaje, enfrentándome a desafíos técnicos estimulantes y obligándome a ampliar mis competencias en desarrollo full-stack y DevOps.
Espero que este artículo de blog les haya dado una visión tras bambalinas de esta apasionante aventura.
Puntos Clave
-
Instrucciones detalladas :
- Prompts claros y estructurados permiten obtener resultados coherentes y de alta calidad por parte de los modelos de IA.
-
Arquitectura modular :
- Cada componente (sitio web, API Gateway, Lambda, DynamoDB, S3, Cognito) desempeña un papel específico, facilitando el mantenimiento y la evolución del sistema.
-
Seguridad y escalabilidad :
- El uso de los servicios gestionados de AWS asegura una seguridad robusta y la capacidad de adaptarse a una demanda creciente.
Enlace del proyecto : StoryPixAI
Este documento ha sido traducido de la versión fr al idioma es utilizando el modelo gpt-5-mini. Para más información sobre el proceso de traducción, consulte https://gitlab.com/jls42/ai-powered-markdown-translator