Met StoryPixAI was het mijn doel een interactieve webapplicatie te bouwen waarmee gebruikers kinderverhalen konden genereren, verrijkt met afbeeldingen die door modellen voor kunstmatige intelligentie zijn gemaakt. Om dit te realiseren heb ik verschillende AWS-diensten gebruikt zoals Lambda, API Gateway, DynamoDB, S3 en Cognito voor authenticatie. De infrastructuurcode wordt beheerd met Terraform en de uitrol is geautomatiseerd via GitLab CI. In dit artikel neem ik je mee achter de schermen van dit boeiende project, van technologische keuzes tot de uitdagingen die ik tegenkwam.
Introductie
Als ervaren cloudinfrastructuur- en DevOps-architect ben ik altijd gefascineerd geweest door nieuwe technologieën en hun potentieel om ons dagelijks leven te veranderen. De opkomst van generatieve AI wekte mijn groeiende nieuwsgierigheid en ik voelde de behoefte om me in dit snel ontwikkelende domein te verdiepen.
Zo ontstond StoryPixAI, een persoonlijk project dat me in staat stelde de eindeloze mogelijkheden van AI te verkennen om gepersonaliseerde verhalen en magische illustraties voor kinderen te creëren. Dit project gaf me de kans om me te verplaatsen in de rol van full-stackontwikkelaar, prompt engineer, product owner en zelfs UX/UI-designer, terwijl ik mijn passie voor technologie deelde met mijn omgeving.
In dit blogbericht deel ik mijn technologische keuzes en de uitdagingen die ik tijdens dit spannende avontuur ben tegengekomen.
Maar eerst een voorsmaakje!
Om je een voorproefje te geven van het potentieel van StoryPixAI, volgen hier enkele automatisch gegenereerde verhalen in verschillende talen.
Elk verhaal wordt vergezeld van illustraties, wat het verhaal nog meeslepender maakt voor kinderen:
- Frans: Tom, Zoé et le Royaume d’Argentor
- Engels: De magische queeste van prinses Léa en de dief van de reuzen-slak
- Spaans: De grote avonturen van Roger en Coco de clown
- Duits: De gekke houthakker en het magische avontuur
- Italiaans: Het meisje en de magische eenhoorn in het betoverde bos
- Portugees: De betoverde reis van Lucas en zijn vrienden
AI in dienst van creativiteit: een traject van experimentatie
Mijn avontuur met StoryPixAI begon met een eenvoudige Proof of Concept (PoC): een Lambda-functie die met OpenAI communiceerde om tekst te genereren en DALL-E om afbeeldingen te maken. Dit eerste succes moedigde me aan om verder te gaan en andere AI-modellen via AWS Bedrock te verkennen.
GPT-4 en GPT-4-o: de behendige vertellers
Vanaf het begin was GPT-4 van OpenAI een voor de hand liggende keuze voor tekstgeneratie. Het vermogen om de nuances van natuurlijke taal te begrijpen en samenhangende, creatieve verhalen te produceren stelde me in staat boeiende verhalen te maken die aangepast zijn aan de leeftijd en interesses van kinderen. Ik kon experimenteren met verschillende schrijfstijlen, van sprookjes tot ruimtereizen, en van dierenverhalen tot fantastische vertellingen.
Toen GPT-4-o werd gelanceerd, heb ik dit nieuwe model snel in StoryPixAI geïntegreerd. Ik was onder de indruk van de verhoogde generatiesnelheid, wat de wachttijd voor gebruikers sterk verminderde, en van de merkbare verbetering in de kwaliteit van de gegenereerde verhalen, met nog vloeiender, coherenter en fantasierijker narratief. GPT-4-o werd daardoor een belangrijke troef voor StoryPixAI en bood een snellere en prettigere gebruikerservaring.
DALL-E 3: de illustrateur bij uitstek
Als de tekstmodellen al bevredigende resultaten opleverden, bleek de keuze van het beeldgeneratiemodel cruciaal. Na veel experimenten bleek DALL-E 3 de referentie te zijn voor StoryPixAI. Het vermogen om originele, gedetailleerde illustraties te maken die perfect aansluiten op de door GPT-4 gegenereerde verhalen, was een beslissende factor voor het succes van het project.
AWS Bedrock: de deur naar experimentatie
Om me niet te beperken tot OpenAI, gebruikte ik AWS Bedrock om gemakkelijk andere generatieve AI-modellen in StoryPixAI te integreren. Dit platform stelde me in staat Claude van Anthropic en Mistral voor tekstgeneratie te testen, en Stable Diffusion voor beeldcreatie.
Hoewel deze modellen interessante resultaten opleverden, besloot ik me uiteindelijk te concentreren op GPT-4 en GPT-4-o vanwege hun snelheid en tekstgeneratiekwaliteit, en op DALL-E 3 voor het produceren van illustraties die perfect bij de verhalen passen. Belangrijk om op te merken is dat de prompt die voor het genereren van afbeeldingen wordt gebruikt grotendeels door het tekstmodel zelf wordt opgesteld, wat zorgt voor coherentie tussen verhaal en illustratie.
De uitdaging van asynchrone API’s en DynamoDB
Nadat de PoC was gevalideerd, begon ik met het bouwen van een API om StoryPixAI via een webinterface toegankelijk te maken. Hier stuitte ik op mijn eerste grote uitdaging: de timeoutlimiet van API Gateway. Om deze beperking te omzeilen en langere, complexere verhalen te kunnen genereren, moest ik een asynchrone architectuur implementeren.
Amazon DynamoDB kwam toen in beeld. Ik gebruikte deze NoSQL-database om de lopende generatietaken en hun eindresultaten op te slaan. Dankzij deze aanpak kon de API een onmiddellijke respons aan de gebruiker teruggeven, die daarna de status van zijn aanvraag kon controleren en het gegenereerde verhaal kon ophalen zodra het klaar was.
CORS en de gebruikersinterface: obstakels om te overwinnen
Het opzetten van de webinterface bracht ook uitdagingen met zich mee. Ik moest me verdiepen in de subtiliteiten van CORS (Cross-Origin Resource Sharing) zodat mijn frontend met de API kon communiceren. Daarnaast heb ik tijd geïnvesteerd in het verbeteren van de gebruikerservaring door functies toe te voegen zoals het selecteren van AI-modellen en afbeeldingsstijlen.
Prompting: een kunst om te beheersen
Gedurende de ontwikkeling van StoryPixAI heb ik mijn prompting-skills verfijnd — de kunst om de juiste opdrachten te formuleren om AI-modellen te sturen. Ik leerde prompts aan te passen op basis van het gebruikte model, de parameters van het verhaal en de verwachtingen van de gebruiker. Deze stap was cruciaal om kwaliteitsresultaten te krijgen en een bevredigende gebruikerservaring te waarborgen.
Een robuuste, geautomatiseerde infrastructuur op AWS
StoryPixAI draait op een serverless infrastructuur gehost op Amazon Web Services (AWS), wat een ideale combinatie biedt van flexibiliteit, schaalbaarheid en kostenoptimalisatie. Deze architectuur, volledig geautomatiseerd met Terraform en GitLab CI/CD, maakt snelle en betrouwbare uitrol van de applicatie mogelijk.
De AWS-diensten in het hart van StoryPixAI

De architectuur van StoryPixAI draait rond de volgende AWS-diensten:
- Amazon S3 (Simple Storage Service): Opslag van statische sitebestanden (HTML, CSS, JavaScript) en van de gegenereerde verhalen en bijbehorende illustraties.
- Amazon CloudFront: Een content delivery network (CDN) dat de distributie van StoryPixAI-content wereldwijd versnelt door caching in geografisch nabijgelegen locaties.
- Amazon API Gateway: De veilige toegangspoort van de applicatie. Het verwerkt gebruikersaanvragen, zorgt voor authenticatie via Amazon Cognito en routeert ze naar de juiste Lambda-functies.
- AWS Lambda: Serverless functies die de motor van StoryPixAI vormen. Ze orkestreren de generatie van verhalen, de creatie van afbeeldingen, het beheer van asynchrone taken en de interactie met DynamoDB en andere AWS-diensten.
- Amazon DynamoDB: Een flexibele en krachtige NoSQL-database die wordt gebruikt om essentiële informatie voor de applicatie op te slaan.
- Amazon Cognito: Een identiteit- en toegangsbeheerdienst die de applicatie beveiligt door gebruikers in staat te stellen in te loggen en door hun autorisaties te controleren. Dit garandeert dat alleen geauthenticeerde gebruikers toegang hebben tot de generatiefuncties.
- Amazon Bedrock: Een platform dat de toegang tot en het gebruik van generatieve AI-modellen van verschillende aanbieders vereenvoudigt, zoals Anthropic (Claude) en Stability AI (Stable Diffusion). Bedrock maakt het gemakkelijk om deze modellen in de applicatie te integreren zonder de onderliggende infrastructuur te beheren.
- Andere AWS-diensten: StoryPixAI gebruikt ook andere AWS-diensten zoals IAM (Identity and Access Management) voor fijnmazig toegangsbeheer, CloudWatch voor monitoring en logs (cruciaal voor debugging en prestatieanalyse), en Systems Manager Parameter Store (SSM Parameter Store) voor het veilig opslaan van gevoelige gegevens zoals API-sleutels.
Terraform: automatisering voor de infrastructuur
Om deze complexe infrastructuur te beheren koos ik voor Terraform, een Infrastructure as Code (IaC)-tool waarmee je infrastructuur in declaratieve code kunt beschrijven. Met Terraform kon ik het aanmaken, wijzigen en verwijderen van AWS-resources automatiseren, wat zorgde voor een consistente, reproduceerbare en makkelijk te beheren omgeving. Dit vereenvoudigt het uitrolproces aanzienlijk en verkleint de kans op menselijke fouten.
GitLab CI/CD: soepele, betrouwbare uitrol
Om een continue en betrouwbare uitrol van StoryPixAI te garanderen, heb ik een CI/CD-pijplijn opgezet in GitLab. Deze pijplijn automatiseert tests, builds en deploys van de applicatie bij elke wijziging in de broncode, waardoor fouten snel worden opgespoord en verholpen en nieuwe functionaliteiten met vertrouwen kunnen worden uitgerold. Deze aanpak zorgt ervoor dat de applicatie altijd up-to-date is en minimaliseert downtime.
De combinatie van AWS, Terraform en GitLab CI/CD stelde me in staat een robuuste, schaalbare en onderhoudsvriendelijke infrastructuur neer te zetten, waardoor ik meer tijd kon besteden aan het creatieve gedeelte van het project en het verbeteren van de gebruikerservaring.
Globale architectuur van het StoryPixAI-project
Voordat we in de code duiken, volgt hier een overzicht van de applicatiearchitectuur:
- Statische site op S3: Een statische website gehost in een S3-bucket, toegankelijk via CloudFront voor wereldwijde distributie.
- API Gateway: Blootst endpoints voor het genereren van verhalen en het controleren van de status.
- Lambda-functies:
StoryPixAI.py: Genereert het verhaal en de bijbehorende afbeeldingen.status_checker.py: Controleert de voortgang van de generatie in DynamoDB.
- DynamoDB: Slaat de status van generatietaken op.
- S3: Slaat de gegenereerde afbeeldingen en resulterende HTML-pagina’s op.
- Cognito: Beheert gebruikersauthenticatie om de API te beveiligen.
Lambda-functie StoryPixAI.py
Algemeen overzicht
De functie StoryPixAI.py is het hart van de applicatie. Ze is verantwoordelijk voor:
- Het genereren van een verhaal op basis van een gebruikersprompt.
- Het creëren van gedetailleerde instructies om het AI-model te sturen bij het genereren van het verhaal.
- Het extraheren van samenvattingen voor elke scène of belangrijk element van het verhaal.
- Het genereren van bijpassende afbeeldingen voor deze samenvattingen.
- Het combineren van tekst en afbeeldingen in een HTML-pagina.
- Het opslaan van het resultaat in S3 en het bijwerken van de status in DynamoDB.
Uiteenrafeling van de code
Imports en initiële configuratie
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 deze sectie importeer ik de benodigde modules, configureer de logger voor debugging en haal de OpenAI API-sleutel op die is opgeslagen in AWS Systems Manager Parameter Store (SSM). Dit zorgt ervoor dat de sleutel veilig is en niet in platte tekst in de code staat.
Hulpfuncties
Correctie van 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é.
Deze functie zorgt ervoor dat de tags die gebruikt worden om samenvattingen en titels af te bakenen uniform zijn. Dat is cruciaal voor het correct extraheren van de samenvattingen later.
Extractie van samenvattingen
def extract_summaries(text):
# Extrait les résumés du texte en utilisant des balises spécifiques.
Deze functie gebruikt reguliere expressies om tekstsecties te extraheren die worden afgebakend door [resume] en [end_resume]. Deze samenvattingen dienen als prompts voor de beeldgeneratie.
Generatie van instructies voor afbeeldingen
def generate_image_instructions(prompt, style, language):
# Génère les instructions pour la création d'images.
Deze functie formatteert de prompt om het beeldgeneratiemodel te sturen, inclusief stijl en taal.
Bijwerken van 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.
Deze functie werkt de tabel TaskStatus bij om de status van de generatie te volgen, wat essentieel is voor de functie status_checker.py.
Diepere analyse van generate_story_instructions
De functie generate_story_instructions is de kern van het project. Ze genereert een set gedetailleerde instructies die aan het AI-model worden doorgegeven om de productie van het verhaal te sturen.
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}"
"""
Constructie van de prompt
De prompt is ontworpen om het AI-model alle noodzakelijke informatie te geven om een samenhangend, educatief en kindvriendelijk verhaal te genereren.
-
Taal: De parameter
language_descriptionmaakt het mogelijk de taal van het verhaal te specificeren, zodat de gegenereerde tekst in de gewenste taal verschijnt. -
Thema: De gebruikersprompt wordt in de instructies geïntegreerd als basis voor het verhaal.
-
Lengte: Een bereik van 1000 tot 1500 woorden is opgegeven om de lengte van het verhaal te beheersen.
-
Kernpunten: De instructies moedigen het opnemen van elementen zoals avontuur, magie en belangrijke opvoedkundige waarden aan.
Details van de instructies
De instructies die aan het model worden gegeven zijn zeer gedetailleerd om de generatie nauwkeurig te sturen.
Hier is een analyse van de verschillende onderdelen van de prompt:
- Narratieve structuur: Het model wordt gevraagd het verhaal te structureren met een pakkend begin, een ontwikkeling rijk aan gebeurtenissen en een bevredigende conclusie.
- Visuele beschrijvingen: Het verhaal moet rijk zijn aan visuele omschrijvingen om de verbeelding van kinderen te prikkelen.
- Personages: Er wordt aangemoedigd personages te ontwikkelen die aandoenlijk zijn en duidelijke persoonlijkheden hebben.
- Specifieke tags: Tags zoals
[titre]... [end_titre]en[resume]... [end_resume]worden gebruikt om titel en visuele beschrijvingen af te bakenen. - Fantastische elementen: Het model wordt uitgenodigd magische of fantastische elementen op te nemen om het verhaal aantrekkelijker te maken.
- Opvoedkundige waarden: Het verhaal moet belangrijke waarden overbrengen.
Rol van de tags De labels spelen een cruciale rol bij de verdere verwerking van de gegenereerde tekst.
-
[titre]… [end_titre] : Omringt de titel van het verhaal. Dit maakt het gemakkelijk om die te extraheren en op de juiste manier in de gebruikersinterface weer te geven.
-
[resume]… [end_resume] : Omringt de gedetailleerde visuele beschrijvingen van belangrijke scènes van het verhaal. Deze samenvattingen worden gebruikt als prompts voor het genereren van afbeeldingen.
Verwerking na Generatie
Zodra het AI-model het verhaal heeft gegenereerd volgens deze instructies, voert de code de volgende stappen uit:
-
Correctie van de Labels : De functie
correct_resume_tagszorgt ervoor dat alle labels correct zijn geformatteerd voor extractie. -
Extractie van de Samenvattingen : De functie
extract_summariesgebruikt de labels[resume]en[end_resume]om de visuele beschrijvingen te extraheren. -
Generatie van de Afbeeldingen : Elke samenvatting wordt doorgegeven aan de functie
generate_imageom een bijpassende afbeelding te maken. -
Aanmaken van de HTML-inhoud : De tekst van het verhaal en de gegenereerde afbeeldingen worden gecombineerd om een volledige HTML-pagina te creëren.
Impact op de Generatie
Door deze gedetailleerde instructies te geven, wordt het model gestuurd om:
-
Het Formaat te Respecteren : Door de gespecificeerde labels te gebruiken, produceert het model een gestructureerde tekst die geautomatiseerde verwerking vergemakkelijkt.
-
Aangepaste Inhoud te Genereren : De beperkingen ten aanzien van taal, stijl en thema’s zorgen ervoor dat het verhaal geschikt is voor het beoogde publiek.
-
Het Genereren van Afbeeldingen te Vergemakkelijken : Door precieze visuele beschrijvingen te extraheren, krijgt men kwalitatieve prompts voor het genereren van afbeeldingen.
Beheer van de Labels door het Model
Het model krijgt expliciet de instructie om de labels niet te vertalen of te wijzigen. Dit is essentieel zodat de labels intact blijven en gebruikt kunnen worden voor nabewerking. De instructies leggen de nadruk op dit punt om te voorkomen dat het model, dat zou kunnen proberen de tekst te parafraseren of te vertalen, de labels aantast.
Generatie van het Verhaal
Zodra de gedetailleerde instructies door de functie generate_story_instructions zijn gegenereerd, is de volgende stap om deze instructies naar het AI-model te sturen zodat het het verhaal kan creëren.
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
Interactie met het OpenAI-model
-
Client OpenAI : Ik instantieer een OpenAI-client met de eerder opgehaalde API-sleutel.
-
Prompting : Het model ontvangt een reeks berichten:
- Een systeembericht dat aangeeft dat de assistent een expert is in kinderfverhalen.
- Het gebruikersbericht met de gegenereerde gedetailleerde instructies.
-
Antwoord van het Model : Het model genereert een verhaal op basis van de verstrekte instructies.
Foutafhandeling
Als er een uitzondering optreedt tijdens de oproep naar de OpenAI API, wordt deze opgevangen en wordt een foutmelding geretourneerd.
Extractie van Samenvattingen en Labels
Na de generatie van het verhaal bestaat de volgende stap uit het extraheren van de visuele beschrijvingen met behulp van de gespecificeerde labels.
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
Correctie van de Labels
Het model kan soms de labels lichtjes wijzigen (bijvoorbeeld accenten toevoegen). De functie correct_resume_tags zorgt ervoor dat alle labels uniform en correct geformatteerd zijn.
Extractie van de Samenvattingen
De functie extract_summaries gebruikt een reguliere expressie om alle voorkomens van tekst tussen de labels [resume] en [end_resume] te vinden. Deze samenvattingen zijn de gedetailleerde visuele beschrijvingen die gebruikt zullen worden om de afbeeldingen te genereren.
Generatie van de Afbeeldingen
Zodra de samenvattingen zijn geëxtraheerd, wordt elke samenvatting gebruikt om een bijbehorende afbeelding te genereren.
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
Functie generate_image
De functie generate_image roept de API van het afbeeldingsgeneratiemodel aan (bijv. OpenAI DALL·E) om een afbeelding te maken op basis van de samenvatting.
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
Generatie van de Instructies voor de Afbeeldingen
De functie generate_image_instructions past de samenvatting aan om een geschikte prompt voor de afbeeldingsgeneratie te creëren.
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.
"""
-
Stijl : De door de gebruiker opgegeven stijl (bijv. “aquarel”, “cartoon”) wordt opgenomen in de prompt om het uiterlijk van de afbeelding te beïnvloeden.
-
Taal : De beschrijving wordt aangepast aan de gekozen taal, wat het model kan helpen om culturele nuances te begrijpen.
-
Duidelijke Instructies : Door te specificeren dat de scène puur visueel moet zijn, voorkomen we dat het model tekst of ongewenste elementen in de afbeelding toevoegt.
Interactie met de OpenAI API voor Afbeeldingen
-
API-oproep : De functie
client.images.generatewordt gebruikt om de afbeelding te genereren. -
Belangrijke Parameters :
- Prompt : De aangepaste prompt wordt aan de API doorgegeven.
- Model : Het opgegeven afbeeldingsgeneratiemodel.
- Resolutie : De grootte van de afbeelding (bijv. “1024x1024”).
- Kwaliteit : De kwaliteit van de afbeelding (standaard, HD).
- Responsformaat : De afbeeldingen worden in base64 geretourneerd om opslag en verwerking te vergemakkelijken.
Foutafhandeling
Fouten tijdens de afbeeldingsgeneratie worden vastgelegd en gelogd, waardoor het mogelijk is problemen te diagnosticeren.
Aanmaken van de HTML-inhoud
Nadat de bij de samenvattingen behorende afbeeldingen zijn gegenereerd, bestaat de volgende stap uit het samenstellen van de tekst van het verhaal en de afbeeldingen in een presentabel formaat voor de gebruiker. Dit gebeurt door het creëren van gestructureerde HTML-inhoud die op de website wordt weergegeven.
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
Gedetailleerde Uitleg
-
Extractie van de Titel:
- Er wordt een reguliere expressie gebruikt om de tekst tussen de labels
[titre]en[end_titre]te vinden. - De labels worden uit de hoofdtekst verwijderd na extractie.
- Als er geen titel wordt gevonden, wordt een standaardtitel gebruikt.
- Er wordt een reguliere expressie gebruikt om de tekst tussen de labels
-
Initialisatie van de HTML:
- De HTML-inhoud begint met de labels
<html>,<head>en<body>. - CSS-stijlen worden opgenomen om de presentatie te verbeteren (typografie, marges, uitlijningen).
- De HTML-inhoud begint met de labels
-
Opsplitsing van de Tekst:
- De tekst wordt in segmenten verdeeld met behulp van de labels
[resume]en[end_resume]. - De segmenten vertegenwoordigen de delen van het verhaal zonder de samenvattingen.
- De tekst wordt in segmenten verdeeld met behulp van de labels
-
Samenvoeging:
- Elk tekstsegment wordt in een paragraaf
<p>geplaatst. - Als het genereren van afbeeldingen is ingeschakeld en er een bijbehorende afbeelding is, wordt de afbeelding na de paragraaf ingevoegd.
- De afbeeldingen worden gecentreerd en aangepast aan de schermgrootte voor een betere gebruikerservaring.
- Elk tekstsegment wordt in een paragraaf
-
Finalisatie:
- De afsluitende labels
</body>en</html>worden toegevoegd om het HTML-document te voltooien.
- De afsluitende labels
Waarom deze Benadering?
-
Afstemming van Tekst en Afbeeldingen: Door de afbeeldingen na de overeenkomstige tekstsegmenten in te voegen, wordt het verhaal visueel verrijkt, wat vooral belangrijk is voor kinderen.
-
Flexibiliteit: Als de gebruiker ervoor kiest geen afbeeldingen te genereren, behandelt de code dit geval door alleen de tekst in te voegen.
-
Toegankelijkheid: Door semantische labels en aangepaste stijlen te gebruiken, is de inhoud toegankelijk op verschillende apparaten (computers, tablets, smartphones).
Upload naar S3 en Statusupdate
Zodra de HTML-inhoud is gegenereerd, moet deze toegankelijk worden gemaakt voor de gebruiker. Dit gebeurt door het bestand te uploaden naar een S3-bucket die is geconfigureerd voor het hosten van statische websites.
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
Technische Details
-
Namen van de Bestanden:
- Bestanden worden benoemd met behulp van een timestamp om uniciteit te garanderen.
- De afbeeldingen worden opgeslagen in de map
generated_images/en de HTML-bestanden ingenerated_content/.
-
Upload naar S3:
- Gebruik van de S3-client van
boto3om met de service te communiceren. - De inhoud wordt gecodeerd of gedecodeerd afhankelijk van het type (afbeelding of tekst).
- De parameter
ACL='public-read'maakt het bestand openbaar toegankelijk.
- Gebruik van de S3-client van
-
Opbouw van de URL:
- De publieke URL wordt opgebouwd met het geconfigureerde CloudFront-domein, wat snelle en veilige distributie van de inhoud mogelijk maakt.
-
Foutafhandeling:
- Bij een fout tijdens het uploaden wordt de uitzondering gelogd en opgegooid om door
lambda_handlerte worden afgehandeld.
- Bij een fout tijdens het uploaden wordt de uitzondering gelogd en opgegooid om door
Hoofdfunctie lambda_handler
De functie lambda_handler is het toegangspunt van de Lambda-functie. Zij orkestreert alle hierboven beschreven stappen.
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"},
}
Uitleg
-
Verwerking van het Verzoek:
- Haalt de benodigde informatie op uit het ontvangen event.
- De parameters van het verzoek omvatten de prompt, de geselecteerde modellen, de taal, enz.
-
Statusupdate:
- Voordat de verwerking begint, wordt de status in DynamoDB bijgewerkt naar “Processing”.
-
Generatie van het Verhaal:
- Oproep naar
generate_storymet de juiste parameters.
- Oproep naar
-
Extractie en Verwerking:
- De labels worden gecorrigeerd en de samenvattingen worden geëxtraheerd voor het genereren van afbeeldingen.
-
Generatie van Afbeeldingen:
- Als het genereren van afbeeldingen is ingeschakeld, worden de bijpassende afbeeldingen gemaakt en worden de URL’s verzameld.
-
Aanmaken van de HTML-inhoud:
- De tekst en afbeeldingen worden gecombineerd om de uiteindelijke HTML-inhoud te creëren.
-
Upload naar S3:
- De HTML-inhoud wordt naar S3 geüpload en de resulterende URL wordt verkregen.
-
Bijwerken van de Eindstatus:
- De status wordt bijgewerkt naar “link” met de URL van het resultaat in DynamoDB.
-
Teruggeven van de Respons:
- De respons bevat de
requestIden de URL van het resultaat, zodat de client de status kan controleren of rechtstreeks toegang kan krijgen tot de inhoud.
- De respons bevat de
-
Foutafhandeling:
- In geval van een fout wordt de status bijgewerkt naar “Failed” en wordt er een HTTP 500-respons geretourneerd.
Lambda-functie status_checker.py
Algemeen Overzicht
De functie status_checker.py stelt gebruikers in staat de status van hun aanvraag voor verhaalgeneratie te controleren. Deze functie vraagt DynamoDB aan om de huidige status en, indien beschikbaar, de URL van het resultaat op te halen.
Code-analyse
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
-
Ophalen van de
requestId:- De
requestIdis essentieel om de specifieke aanvraag van de gebruiker te identificeren.
- De
-
Query naar DynamoDB:
- De functie probeert het item op te halen dat overeenkomt met de
requestId. - Als het item bestaat, worden de status en de
resultUrlgeëxtraheerd.
- De functie probeert het item op te halen dat overeenkomt met de
-
Opbouw van de Respons:
- Als de status beschikbaar is, wordt deze samen met de URL van het resultaat geretourneerd.
- Als het item niet wordt gevonden, wordt een 404-fout geretourneerd.
- Bij een fout tijdens de databasequery wordt een 500-foutgeretourneerd met een geschikte foutmelding.
-
HTTP-headers:
- De headers zijn ingesteld om CORS-verzoeken vanaf de website toe te staan.
Integratie met API Gateway
Configuratie van de Endpoints
De API Gateway biedt twee hoofdendpoints voor interactie met de Lambda-functies:
-
/generate-image:- Methode :
POST - Beschrijving : Stelt gebruikers in staat om de generatie van een verhaal te starten en eventueel bijbehorende afbeeldingen.
- Integratie : Verbonden met de Lambda-functie
StoryPixAI.py.
- Methode :
-
/check-status:- Methode :
GET - Beschrijving : Stelt gebruikers in staat de status van hun aanvraag te controleren door de
requestIdte verstrekken. - Integratie : Verbonden met de Lambda-functie
status_checker.py.
- Methode :
Authenticatie met Cognito
Om de API te beveiligen en de toegang tot resources te beheren, heb ik Amazon Cognito geïntegreerd.
-
User Pool:
- Beheert de gebruikersreferenties.
- Stelt registratie, aanmelding en gebruikersbeheer in staat.
-
Authorizer:
- Geconfigureerd in API Gateway om de JWT-tokens van Cognito te verifiëren.
- Zorgt ervoor dat alleen geauthenticeerde verzoeken toegang hebben tot beschermde endpoints.
-
Integratie in API Gateway:
- De endpoints
/generate-imageen/check-statuszijn beschermd door de Cognito-authorizer. - Clients moeten het authenticatietoken in de headers van hun verzoeken opnemen (
Authorization).
- De endpoints
Statische Site op S3 en Interactie met de API
Structuur van de Site
De statische website dient als gebruikersinterface voor de applicatie.
-
index.html:- Bevat het formulier waarmee gebruikers de prompt kunnen invoeren, de generatie-opties kunnen kiezen en hun aanvraag kunnen indienen.
- Bevat de scripts die nodig zijn voor interactie met de API en het beheer van authenticatie.
-
storypixai.js:- Bevat de JavaScript-code voor het beheren van de interacties met de API.
- Beheert authenticatie met Cognito, het indienen van het formulier, het volgen van de status en het weergeven van de resultaten.
Gebruikersworkflow
-
Inloggen:
- De gebruiker logt in via het ingebouwde inlogformulier.
- De gegevens worden gecontroleerd via Cognito.
-
Indienen van de Aanvraag:
- De gebruiker vult het formulier in met de prompt en gewenste opties.
- Bij verzending wordt een verzoek
POSTnaar het endpoint/generate-imagegestuurd met de gegevens.
-
Asynchrone Verwerking:
- De API retourneert onmiddellijk een
requestId. - De generatie wordt op de achtergrond verwerkt.
- De API retourneert onmiddellijk een
-
Statuscontrole:
- De website bevraagt periodiek het endpoint
/check-statusdoor derequestIdte verstrekken. - Zodra de status “link” is ontvangen, wordt de resultaat-URL aan de gebruiker weergegeven.
- De website bevraagt periodiek het endpoint
-
Weergave van het Resultaat:
- De gebruiker kan op de link klikken om het gegenereerde verhaal met afbeeldingen te bekijken.
Beheer van Verzoeken en Responses
-
Geauthenticeerde Verzoeken:
- Alle verzoeken naar de API bevatten het authenticatietoken.
- Het token wordt beheerd door de Cognito SDK die in de website is opgenomen.
-
Statusbeheer:
- De mogelijke statussen zijn “Processing”, “link”, “Failed”.
- De site past de interface aan op basis van de ontvangen status (bijv. weergave van een spinner, foutmelding, link naar het resultaat).
Interconnecties tussen de Componenten
Dit is hoe de verschillende componenten met elkaar interageren:
-
Website ↔️ API Gateway:
- De website stuurt verzoeken naar de endpoints die door de API Gateway worden aangeboden.
- Authenticatietokens worden meegestuurd om de verzoeken te beveiligen.
-
API Gateway ↔️ Lambda-functies:
- De API Gateway roept de betreffende Lambda-functies aan op basis van de ontvangen verzoeken.
-
Lambda-functies ↔️ DynamoDB:
- De Lambda-functies
StoryPixAI.pyenstatus_checker.pyinterageren met DynamoDB om de status van aanvragen bij te werken en op te halen.
- De Lambda-functies
-
Lambda-functie ↔️ S3:
- De functie
StoryPixAI.pyuploadt de gegenereerde afbeeldingen en de HTML-inhoud naar S3.
- De functie
-
CloudFront ↔️ S3:
- CloudFront wordt gebruikt om de op S3 opgeslagen inhoud snel en veilig te distribueren.
- De aan gebruikers verstrekte URL’s verwijzen naar het CloudFront-domein.
-
Gebruiker ↔️ Website:
- De gebruiker interacteert met de website om aanvragen in te dienen en de resultaten te bekijken.
Voorbeeldresultaat in de CloudWatch-logs na een Verzoek
Hier is een voorbeeld van een logresultaat na een verzoek, zodat je het ruwe formaat van de gegenereerde gegevens kunt zien:
[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.
Continue Integratie met GitLab CI/CD
Om een soepel ontwikkelings- en uitrolproces voor StoryPixAI te garanderen, heb ik een CI/CD-pijplijn opgezet met GitLab CI/CD. Deze configuratie automatiseert het build- en deploymentproces en waarborgt zo de kwaliteit en betrouwbaarheid van de code bij elke wijziging.
Configuratie van de Pipeline
De pijplijn is gedefinieerd in het bestand .gitlab-ci.yml in de root van het project. Hier is een overzicht van de structuur:
stages:
- Pré-requis optionel
- Vérifications
- Déploiements
- Management
- Suppressions
variables:
TERRAFORM_VERSION: '1.5.7-*'
TF_VAR_region: $AWS_DEFAULT_REGION
``` Deze configuratie definieert de verschillende stappen van de pipeline en de globale variabelen die in het CI/CD-proces worden gebruikt.
### Belangrijke jobs
De pipeline bevat meerdere kernjobs:
1. **Vérification Terraform** :
```yaml
Vérification Terraform:
stage: Vérifications
when: manual
script:
- /bin/bash -c "source export.sh && terraform_plan"
Deze job voert terraform plan uit om de verwachte infrastructuurwijzigingen te controleren zonder ze toe te passen.
-
Déploiement Terraform :
Déploiement Terraform: stage: Déploiements when: manual dependencies: - Vérification Terraform script: - /bin/bash -c "source export.sh && terraform_apply"Na verificatie past deze job de infrastructuurwijzigingen toe door
terraform applyuit te voeren. -
Suppression Terraform :
Suppression Terraform: stage: Suppressions when: manual script: - /bin/bash -c "source export.sh && terraform_destroy"Deze job maakt het mogelijk om de infrastructuur te verwijderen indien nodig, door
terraform destroyuit te voeren. -
Gestion des Clés 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"Deze jobs beheren het veilig toevoegen en verwijderen van OpenAI API-sleutels in AWS Parameter Store.
Uitvoeringsomgeving
Elke job draait in een Docker-container gebaseerd op Ubuntu 22.04, met Terraform en de AWS CLI geïnstalleerd:
.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
Voordelen van deze CI/CD-aanpak
-
Automatisering : Elke codewijziging triggert automatisch de pipeline, wat zorgt voor consistente controles en deploys.
-
Handmatige controle : Kritieke stappen zoals deployment en verwijdering zijn ingesteld als handmatig (
when: manual), wat extra controle biedt vóór uitvoering. -
Beveiligd beheer van geheimen : Integratie met AWS Parameter Store voor het beheer van API-sleutels zorgt voor een veilige omgang met gevoelige informatie.
-
Flexibiliteit : De structuur in stages maakt een geordende en logische uitvoering van de verschillende stappen van de pipeline mogelijk.
-
Reproduceerbaarheid : Het gebruik van een gestandaardiseerde Docker-omgeving garandeert dat builds en tests reproduceerbaar zijn op verschillende systemen.
Deze CI/CD-configuratie maakt het niet alleen mogelijk om StoryPixAI te automatiseren, maar helpt ook een hoog niveau van kwaliteit en betrouwbaarheid gedurende de hele ontwikkelingscyclus te behouden.
Conclusie
StoryPixAI was veel meer dan alleen een technisch project. Het was een echte avontuur in de wereld van generatieve AI, waarin ik mijn passie voor technologie kon combineren met het verlangen om magische verhaaltjes voor mijn kinderen te creëren.
Dit project gaf me de kans om verschillende aspecten van AI te verkennen, van het ontwerpen van een intuïtieve gebruikersinterface tot het beheersen van prompting, en het opzetten van een robuuste cloudinfrastructuur met AWS en Terraform. Elke stap was een leermoment, confronteerde me met uitdagende technische problemen en dwong me mijn vaardigheden in full-stack ontwikkeling en DevOps uit te breiden.
Ik hoop dat deze blogpost je een kijkje achter de schermen heeft gegeven van dit spannende avontuur.
Belangrijke punten
-
Gedetailleerde instructies :
- Duidelijke en gestructureerde prompts zorgen voor consistente en hoogwaardige resultaten van de AI-modellen.
-
Modulaire architectuur :
- Elk component (website, API Gateway, Lambda, DynamoDB, S3, Cognito) speelt een specifieke rol, wat onderhoud en evolutie van het systeem vergemakkelijkt.
-
Beveiliging en schaalbaarheid :
- Het gebruik van beheerde AWS-diensten zorgt voor robuuste beveiliging en het vermogen om zich aan te passen aan groeiende vraag.
Projectlink : StoryPixAI
Dit document is vertaald van de fr-versie naar de nl-taal met behulp van het model gpt-5-mini. Voor meer informatie over het vertaalproces, raadpleeg https://gitlab.com/jls42/ai-powered-markdown-translator