Search

bloginfrastructureia

Creating Children's Stories with Generative AI: The StoryPixAI Adventure

Creating Children's Stories with Generative AI: The StoryPixAI Adventure

With StoryPixAI my goal was to create an interactive web application allowing users to generate children’s stories, enriched by images produced by artificial intelligence models. To achieve this, I used several AWS services such as Lambda, API Gateway, DynamoDB, S3 and Cognito for authentication. The infrastructure code is managed with Terraform, and deployment is automated via GitLab CI. In this post, I reveal the behind-the-scenes of this exciting project, from technology choices to the challenges encountered.

Introduction

As a cloud infrastructure and DevOps architect with experience, I have always been fascinated by new technologies and their potential to transform our daily lives. The emergence of generative AI sparked a growing curiosity in me, and I felt the need to dive into this booming field.

Thus StoryPixAI was born, a personal project that allowed me to explore the endless possibilities of AI to create personalized stories and magical illustrations for children. This project gave me the opportunity to step into the shoes of a full-stack developer, a prompt engineer, a product owner and even a UX/UI designer, while sharing my passion for technology with my loved ones.

In this blog post, I will share my technological choices and the challenges I tackled during this exciting journey.

But first, a little taste!

To give you a preview of StoryPixAI’s potential, here are some automatically generated stories in several languages.
Each story is accompanied by illustrations, making the narrative even more immersive for children:

AI Serving Creativity: an Experimental Journey

My journey with StoryPixAI began with a simple Proof of Concept (PoC): a Lambda function that interacted with OpenAI to generate text and DALL-E to create images. This first success encouraged me to go further and explore other AI models via AWS Bedrock.

GPT-4 and GPT-4-o: the agile storytellers

From the start of the project, OpenAI’s GPT-4 stood out as an obvious choice for text generation. Its ability to understand the nuances of natural language and produce coherent, creative narratives allowed me to craft captivating stories tailored to the children’s age and interests. I experimented with different writing styles, from fairy tales to space adventures, as well as animal stories and fantasy tales.

When GPT-4-o was launched, I quickly integrated this new model into StoryPixAI. I was impressed by its increased generation speed, which significantly reduced waiting time, and by the notable improvement in the quality of generated stories, producing narratives that were even more fluid, coherent and imaginative. GPT-4-o thus became a major asset for StoryPixAI, offering a faster and more pleasant user experience.

DALL-E 3: the go-to illustrator

While the text generation models delivered satisfactory results, choosing the image generation tool proved more crucial. After many trials, DALL-E 3 established itself as the reference model for StoryPixAI. Its ability to create original, detailed illustrations perfectly matched to the stories generated by GPT-4 was a determining factor in the project’s success.

AWS Bedrock: the gateway to experimentation

Wishing not to limit myself to OpenAI, I used AWS Bedrock to easily integrate other generative AI models into StoryPixAI. This platform allowed me to test Anthropic’s Claude and Mistral for text generation, and Stable Diffusion for image creation.

Although these models produced interesting results, I ultimately chose to focus on GPT-4 and GPT-4-o for their speed and text generation quality, and on DALL-E 3 for its ability to produce illustrations that fit the stories perfectly. It’s important to note that the prompt used to generate the images is largely crafted by the text model itself, ensuring coherence between the narrative and the illustration.

The challenge of the asynchronous API and DynamoDB

Once the PoC was validated, I set out to create an API to make StoryPixAI accessible via a web interface. This is where I encountered my first major challenge: the API Gateway timeout limitation. To work around this constraint and allow the generation of longer and more complex stories, I had to implement an asynchronous architecture.

Amazon DynamoDB then came into play. I used this NoSQL database to store ongoing story generation tasks and their results once completed. Thanks to this approach, the API could return an immediate response to the user, who could then check the status of their request and retrieve the generated story when ready.

CORS and the user interface: hurdles to overcome

Setting up the web interface also posed challenges. I had to become familiar with the subtleties of CORS (Cross-Origin Resource Sharing) to allow my frontend to communicate with the API. I also spent time improving the user experience by adding features such as selecting AI models and image styles.

Prompting: an art to master

Throughout the development of StoryPixAI, I refined my prompting skills—the art of crafting the right requests to guide AI models. I learned to adapt prompts depending on the models used, the story parameters and user expectations. This step was crucial to obtain high-quality results and ensure a satisfactory user experience.

A Robust, Automated Infrastructure on AWS

StoryPixAI is built on a serverless infrastructure hosted on Amazon Web Services (AWS), offering an ideal combination of flexibility, scalability and cost optimization. This architecture, fully automated thanks to Terraform and GitLab CI/CD, enables fast and reliable deployment of the application.

AWS services at the heart of StoryPixAI

alt text

The StoryPixAI architecture revolves around the following AWS services:

  • Amazon S3 (Simple Storage Service): Storage of static website files (HTML, CSS, JavaScript) and the generated stories as well as their associated illustrations.
  • Amazon CloudFront: A content delivery network (CDN) that accelerates the distribution of StoryPixAI content to users worldwide by caching it in locations geographically close to them.
  • Amazon API Gateway: The secure entry point of the application. It handles user requests, ensures authentication via Amazon Cognito, and routes them to the appropriate Lambda functions.
  • AWS Lambda: Serverless functions that form the engine of StoryPixAI. They orchestrate story generation, image creation, management of asynchronous tasks and interaction with DynamoDB and other AWS services.
  • Amazon DynamoDB: A flexible and high-performance NoSQL database used to store essential information required for the application’s operation.
  • Amazon Cognito: An identity and access management service that secures the application by allowing users to sign in and controlling their permissions. It ensures that only authenticated users can access story generation features.
  • Amazon Bedrock: A platform that simplifies access to and use of generative AI models from different providers, such as Anthropic (Claude) and Stability AI (Stable Diffusion). Bedrock makes it easy to integrate these models into the application without managing their underlying infrastructure.
  • Other AWS services: StoryPixAI also uses other AWS services such as IAM (Identity and Access Management) for fine-grained permission control, CloudWatch for monitoring and logs (crucial for debugging and performance analysis), and Systems Manager Parameter Store (SSM Parameter Store) to store sensitive information like API keys, thus ensuring application security.

Terraform: automation serving the infrastructure

To manage this complex infrastructure, I chose Terraform, an Infrastructure as Code (IaC) tool that allows describing infrastructure in a declarative manner. Thanks to Terraform, I was able to automate the creation, modification and destruction of AWS resources, ensuring a consistent, reproducible and easy-to-manage environment. This significantly simplifies the deployment process and reduces the risk of human error.

GitLab CI/CD: smooth, reliable deployments

To ensure continuous and reliable deployment of StoryPixAI, I set up a CI/CD pipeline (Continuous Integration / Continuous Deployment) on GitLab. This pipeline automates testing, building and deploying the application with each change to the source code, allowing quick detection and correction of errors and confident delivery of new features. This approach ensures the application is always up to date and minimizes downtime.

This combination of AWS, Terraform and GitLab CI/CD allowed me to build a robust, scalable and maintainable infrastructure, leaving me more time to focus on the creative aspect of the project and improving the user experience.

Overall Architecture of the StoryPixAI Project

Before diving into the code, here is an overview of the application’s architecture:

  1. Static Site on S3: A static website hosted on an S3 bucket, accessible via CloudFront for global distribution.
  2. API Gateway: Exposes endpoints for story generation and status checking.
  3. Lambda Functions:
    • StoryPixAI.py : Generates the story and associated images.
    • status_checker.py : Checks the generation status in DynamoDB.
  4. DynamoDB: Stores the status of generation tasks.
  5. S3: Stores generated images and resulting HTML pages.
  6. Cognito: Manages user authentication to secure the API.

Lambda Function StoryPixAI.py

General Overview

The StoryPixAI.py function is the heart of the application. It is responsible for:

  • Generating a story based on a user prompt.
  • Creating detailed instructions to guide the AI model in story generation.
  • Extracting summaries for each scene or key element of the story.
  • Generating images corresponding to these summaries.
  • Combining text and images into an HTML page.
  • Storing the result in S3 and updating the status in DynamoDB.

Code Breakdown

Imports and Initial Configuration

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)

In this section, I import the necessary modules, configure the logger for debugging, and retrieve the OpenAI API key stored in AWS Systems Manager Parameter Store (SSM). This secures the key and avoids storing it in plain text in the code.

Utility Functions

Tag Correction
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é.

This function ensures that the tags used to delimit summaries and titles are consistent. This is crucial for the correct extraction of summaries later.

Summary Extraction
def extract_summaries(text):
    # Extrait les résumés du texte en utilisant des balises spécifiques.

It uses regular expressions to extract sections of text delimited by [resume] and [end_resume]. These summaries will serve as prompts for image generation.

Generating Image Instructions
def generate_image_instructions(prompt, style, language):
    # Génère les instructions pour la création d'images.

This function formats the prompt to guide the image generation model by including style and language.

Updating 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.

It updates the TaskStatus table to track the generation state, which is essential for the status_checker.py function.

In-depth Analysis of generate_story_instructions

The generate_story_instructions function is the core of the project. It generates a set of detailed instructions that will be passed to the AI model to guide story generation.

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}"
    """

Prompt Construction

The prompt is designed to provide the AI model with all the necessary information to generate a coherent, educational and child-appropriate story.

  • Language: The language_description parameter allows specifying the story language, ensuring that the generated text will be in the desired language.

  • Theme: The user prompt is incorporated into the instructions to serve as the basis for the story.

  • Length: A range of 1000 to 1500 words is specified to control the story length.

  • Key Elements: The instructions encourage the inclusion of elements such as adventure, magic, and important educational values.

Instruction Details

The instructions provided to the model are extremely detailed to guide generation precisely.

Here is an analysis of the different parts of the prompt:

  1. Narrative Structure: The model is asked to structure the story with a captivating beginning, a development rich in events, and a satisfying conclusion.

  2. Visual Descriptions: The story must be rich in visual descriptions to stimulate children’s imagination.

  3. Characters: The development of endearing characters with distinct personalities is encouraged.

  4. Specific Tags: Tags such as [titre]... [end_titre] and [resume]... [end_resume] are used to delimit the title and visual descriptions.

  5. Fantastic Elements: The model is invited to include magical or fantastical elements to make the story more appealing.

  6. Educational Values: The story should teach important values.

Role of the Tags Tags play a crucial role in the downstream processing of the generated text.

  • [titre]… [end_titre] : Encloses the story title. This allows it to be easily extracted for appropriate display in the user interface.

  • [resume]… [end_resume] : Encloses the detailed visual descriptions of key scenes of the story. These summaries will be used as prompts for image generation.

Post-Generation Processing

Once the AI model has generated the story following these instructions, the code performs the following steps:

  1. Tag Correction : The function correct_resume_tags ensures that all tags are correctly formatted for extraction.

  2. Summary Extraction : The function extract_summaries uses the tags [resume] and [end_resume] to extract the visual descriptions.

  3. Image Generation : Each summary is passed to function generate_image to create a corresponding image.

  4. HTML Content Creation : The story text and the generated images are combined to create a complete HTML page.

Impact on Generation

By providing these detailed instructions, the model is guided to:

  • Respect the Format : By using the specified tags, the model produces a structured text that facilitates automated processing.

  • Generate Appropriate Content : Constraints on language, style, and themes ensure the story is appropriate for the target audience.

  • Facilitate Image Generation : By extracting precise visual descriptions, we obtain quality prompts for image generation.

Handling of Tags by the Model

The model is explicitly instructed not to translate or modify the tags. This is essential so that the tags remain intact and can be used for post-processing. The instructions emphasize this point to prevent the model, which might try to paraphrase or translate the entire text, from altering the tags.

Story Generation

Once the detailed instructions generated by function generate_story_instructions are ready, the next step is to pass these instructions to the AI model so it can create the story.

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

Interaction with the OpenAI Model

  • OpenAI Client : I instantiate an OpenAI client using the API key retrieved earlier.

  • Prompting : The model receives a series of messages :

    • A system message stating that the assistant is an expert in children’s stories.
    • The user message containing the detailed instructions generated.
  • Model Response : The model generates a story based on the provided instructions.

Error Handling

If an exception occurs during the call to the OpenAI API, it is caught and an error message is returned.

Extraction of Summaries and Tags

After generating the story, the next step is to extract the visual descriptions using the specified tags.

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

Tag Correction

The model can sometimes slightly alter the tags (for example, by adding accents). Function correct_resume_tags ensures that all tags are uniform and correctly formatted.

Summary Extraction

Function extract_summaries uses a regular expression to find all occurrences of text between the tags [resume] and [end_resume]. These summaries are the detailed visual descriptions that will be used to generate the images.

Image Generation

Once the summaries are extracted, each summary is used to generate a corresponding image.

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

Function generate_image

Function generate_image calls the image generation model API (for example, OpenAI DALL·E) to create an image from the summary.

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

Generating Instructions for Images

Function generate_image_instructions adapts the summary to create an appropriate prompt for image generation.

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.
    """
  • Style : The style specified by the user (for example, “watercolor”, “cartoon”) is included in the prompt to influence the image rendering.

  • Language : The description is adapted to the chosen language, which can help the model understand cultural nuances.

  • Clear Instructions : By specifying that the scene should be purely visual, the model is prevented from adding text or unwanted elements to the image.

Interaction with the OpenAI API for Images

  • API Call : Function client.images.generate is used to generate the image.

  • Important Parameters :

    • Prompt : The adjusted prompt is passed to the API.
    • Model : The specified image generation model.
    • Size : The image size (for example, “1024x1024”).
    • Quality : The image quality (standard, HD).
    • Response Format : The images are returned in base64 to facilitate storage and handling.

Error Handling

Errors during image generation are caught and logged, allowing issues to be diagnosed.

HTML Content Creation

After generating the images corresponding to the extracted summaries, the next step is to assemble the story text and the images into a presentable format for the user. This is done by creating structured HTML content that will be displayed on the website.

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

Detailed Explanation

  1. Title Extraction:

    • Uses a regular expression to find the text between the tags [titre] and [end_titre].
    • Removes the tags from the main text after extraction.
    • If no title is found, a default title is used.
  2. HTML Initialization:

    • The HTML content begins with the tags <html>, <head>, and <body>.
    • CSS styles are included to improve presentation (typography, margins, alignment).
  3. Text Separation:

    • The text is split into segments using the tags [resume] and [end_resume].
    • The segments represent the parts of the story without the summaries.
  4. Assembly:

    • Each text segment is inserted into a paragraph <p>.
    • If image generation is enabled and there is a corresponding image, the image is inserted after the paragraph.
    • Images are centered and adapted to the screen size for a better user experience.
  5. Finalization:

    • The closing tags </body> and </html> are added to complete the HTML document.

Why This Approach?

  • Alignment of Text and Images: By inserting images after the corresponding text segments, the story is visually enriched, which is especially important for children.

  • Flexibility: If the user chooses not to generate images, the code handles this case by inserting only the text.

  • Accessibility: By using semantic tags and appropriate styles, the content is accessible on different devices (computers, tablets, smartphones).

Upload to S3 and Status Update

Once the HTML content is generated, it needs to be made accessible to the user. This is done by uploading the file to an S3 bucket configured for static website hosting.

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

Technical Details

  • File Naming:

    • Files are named using a timestamp to ensure uniqueness.
    • Images are stored in the folder generated_images/ and HTML files in generated_content/.
  • Upload to S3:

    • Uses the S3 client from boto3 to interact with the service.
    • Content is encoded or decoded depending on the type (image or text).
    • The ACL='public-read' parameter makes the file publicly accessible.
  • URL Construction:

    • The public URL is constructed using the configured CloudFront domain, which enables fast and secure content distribution.
  • Exception Handling:

    • In case of an error during upload, the exception is logged and raised to be handled by lambda_handler.

Main Function lambda_handler

Function lambda_handler is the entry point of the Lambda function. It orchestrates all the steps described above.

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"},
        }

Explanation

  • Request Processing:

    • Retrieves the necessary information from the received event.
    • Request parameters include the prompt, selected models, language, etc.
  • Status Update:

    • Before starting processing, the status is set to “Processing” in DynamoDB.
  • Story Generation:

    • Call to generate_story with the appropriate parameters.
  • Extraction and Processing:

    • Tags are corrected and summaries extracted for image generation.
  • Image Generation:

    • If image generation is enabled, the corresponding images are generated and the URLs collected.
  • HTML Content Creation:

    • The text and images are combined to create the final HTML content.
  • Upload to S3:

    • The HTML content is uploaded to S3 and the resulting URL is obtained.
  • Final Status Update:

    • The status is updated to “link” with the resulting URL in DynamoDB.
  • Response Return:

    • The response includes requestId and the result URL, allowing the client to check the status or directly access the content.
  • Exception Handling:

    • In case of an error, the status is updated to “Failed” and an HTTP 500 response is returned.

Lambda Function status_checker.py

Overview

Function status_checker.py allows users to check the status of their story generation request. It queries DynamoDB to retrieve the current status and, if available, the result URL.

Code Analysis

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,
        }

Details

  • Retrieval of requestId:

    • The requestId is essential to identify the user’s specific request.
  • Querying DynamoDB:

    • The function attempts to retrieve the item corresponding to requestId.
    • If the item exists, the status and resultUrl are extracted.
  • Response Construction:

    • If the status is available, it is returned with the result URL.
    • If the item is not found, a 404 error is returned.
    • In case of an error while querying the database, a 500 error is returned with an appropriate message.
  • HTTP Headers:

    • Headers are set to allow CORS requests from the website.

Integration with API Gateway

Endpoint Configuration

The API Gateway exposes two main endpoints to interact with the Lambda functions:

  1. /generate-image:

    • Method: POST
    • Description: Allows users to start the generation of a story and, optionally, associated images.
    • Integration: Connected to the Lambda function StoryPixAI.py.
  2. /check-status:

    • Method: GET
    • Description: Allows users to check the status of their request by providing the requestId.
    • Integration: Connected to the Lambda function status_checker.py.

Authentication with Cognito

To secure the API and control access to resources, I integrated Amazon Cognito.

  • User Pool:

    • Manages user credentials.
    • Allows registration, sign-in, and user management.
  • Authorizer:

    • Configured in API Gateway to verify JWT tokens issued by Cognito.
    • Ensures that only authenticated requests can access protected endpoints.
  • API Gateway Integration:

    • The endpoints /generate-image and /check-status are protected by the Cognito authorizer.
    • Clients must include the authentication token in the headers of their requests (Authorization).

Static Site on S3 and Interaction with the API

Site Structure

The static website serves as the user interface for the application.

  • index.html:

    • Contains the form allowing users to enter the prompt, choose generation options, and submit their request.
    • Includes the scripts necessary for interacting with the API and managing authentication.
  • storypixai.js:

    • Contains the JavaScript code to handle interactions with the API.
    • Manages Cognito authentication, form submission, status tracking, and displaying results.

User Workflow

  1. Sign-in:

    • The user signs in via the integrated sign-in form.
    • Credentials are verified via Cognito.
  2. Request Submission:

    • The user fills out the form with the prompt and desired options.
    • Upon submission, a POST request is sent to endpoint /generate-image with the data.
  3. Asynchronous Processing:

    • The API immediately returns a requestId.
    • The generation processing is done in the background.
  4. Status Checking:

    • The website periodically queries endpoint /check-status providing requestId.
    • Once the status “link” is received, the result URL is shown to the user.
  5. Result Display:

    • The user can click the link to access the generated story with images.

Request and Response Handling

  • Authenticated Requests:

    • All requests to the API include the authentication token.
    • The token is managed by the Cognito SDK included in the website.
  • Status Management:

    • Possible statuses are “Processing”, “link”, “Failed”.
    • The site adapts its interface according to the received status (for example, showing a spinner, error message, or link to the result).

Interconnections between Components

Here is how the different components interact:

  • Website ↔️ API Gateway:

    • The website sends requests to the endpoints exposed by the API Gateway.
    • Authentication tokens are included to secure the requests.
  • API Gateway ↔️ Lambda Functions:

    • The API Gateway invokes the corresponding Lambda functions based on the received requests.
  • Lambda Functions ↔️ DynamoDB:

    • The Lambda functions StoryPixAI.py and status_checker.py interact with DynamoDB to update and retrieve the status of requests.
  • Lambda Function ↔️ S3:

    • Function StoryPixAI.py uploads the generated images and the HTML content to S3.
  • CloudFront ↔️ S3:

    • CloudFront is used to distribute the content stored on S3 quickly and securely.
    • The URLs provided to users point to the CloudFront domain.
  • User ↔️ Website:

    • The user interacts with the website to submit requests and view results.

Example of Result in CloudWatch logs after a Request Call

Here is an example of a log result after a request call so you can see the raw format of the generated data:

[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.

Continuous Integration with GitLab CI/CD

To ensure smooth development and deployment of StoryPixAI, I set up a continuous integration (CI) and continuous deployment (CD) pipeline using GitLab CI/CD. This configuration automates build and deployment processes, thus ensuring code quality and reliability with each change.

Pipeline Configuration

The pipeline is defined in the file .gitlab-ci.yml at the root of the project. Here is an overview of its structure:

stages:
  - Pré-requis optionel
  - Vérifications
  - Déploiements
  - Management
  - Suppressions

variables:
  TERRAFORM_VERSION: '1.5.7-*'
  TF_VAR_region: $AWS_DEFAULT_REGION
``` This configuration defines the different stages of the pipeline and the global variables used in the CI/CD process.

### Main Jobs

The pipeline includes several key jobs:

1. **Terraform Verification** :

```yaml
Vérification Terraform:
  stage: Vérifications
  when: manual
  script:
    - /bin/bash -c "source export.sh && terraform_plan"

This job runs terraform plan to check the planned infrastructure changes without applying them.

  1. Terraform Deployment :
Déploiement Terraform:
  stage: Déploiements
  when: manual
  dependencies:
    - Vérification Terraform
  script:
    - /bin/bash -c "source export.sh && terraform_apply"

After verification, this job applies the infrastructure changes by running terraform apply.

  1. Terraform Destruction :
Suppression Terraform:
  stage: Suppressions
  when: manual
  script:
    - /bin/bash -c "source export.sh && terraform_destroy"

This job allows destroying the infrastructure if necessary, by running terraform destroy.

  1. OpenAI Key Management :
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"

These jobs handle the secure addition and removal of OpenAI API keys in AWS Parameter Store.

Execution Environment

Each job runs in a Docker container based on Ubuntu 22.04, with Terraform and the AWS CLI installed:

.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

Benefits of this CI/CD Approach

  1. Automation: Every code change automatically triggers the pipeline, ensuring consistent checks and deployments.

  2. Manual Control: Critical steps such as deployment and destruction are configured as manual (when: manual), providing extra control before execution.

  3. Secure Secrets Management: Integration with AWS Parameter Store for API key management ensures secure handling of sensitive information.

  4. Flexibility: The staged structure allows for an ordered and logical execution of the pipeline steps.

  5. Reproducibility: Using a standardized Docker environment guarantees that builds and tests are reproducible across different systems.

This CI/CD configuration not only automates the deployment of StoryPixAI, but also helps maintain a high level of quality and reliability throughout the development lifecycle.

Conclusion

StoryPixAI was much more than a simple technical project. It was a true adventure into the world of generative AI, allowing me to combine my passion for technology with the desire to create magical stories for my children.

This project gave me the opportunity to explore different facets of AI, from designing an intuitive user interface to mastering prompting, and setting up a robust cloud infrastructure with AWS and Terraform. Each step was a source of learning, presenting stimulating technical challenges and forcing me to expand my skills in full-stack development and DevOps.

I hope this blog post has given you an insight into the behind-the-scenes of this exciting journey.

Key Points

  • Detailed Instructions:

    • Clear and structured prompts make it possible to obtain consistent, high-quality results from AI models.
  • Modular Architecture:

    • Each component (website, API Gateway, Lambda, DynamoDB, S3, Cognito) plays a specific role, making the system easier to maintain and evolve.
  • Security and Scalability:

    • Using AWS managed services ensures strong security and the ability to scale to meet growing demand.

Project link: StoryPixAI

This document was translated from the French version into English using the gpt-5-mini model. For more information about the translation process, see https://gitlab.com/jls42/ai-powered-markdown-translator