Cu StoryPixAI, obiectivul meu a fost să creez o aplicație web interactivă care să permită utilizatorilor să genereze povești pentru copii, îmbogățite cu imagini generate de modele de inteligență artificială. Pentru a realiza aceasta, am folosit mai multe servicii AWS precum Lambda, API Gateway, DynamoDB, S3 și Cognito pentru autentificare. Codul infrastructurii este gestionat cu Terraform, iar implementarea este automatizată prin GitLab CI. În acest articol, vă dezvălui culisele acestui proiect palpitant, de la alegerile tehnologice până la provocările întâmpinate.

Introducere

În calitate de arhitect de infrastructuri cloud și expert în DevOps, am fost întotdeauna fascinat de noile tehnologii și de potențialul lor de a ne transforma viața de zi cu zi. Emergența IA generative a stârnit în mine o curiozitate crescândă și am simțit nevoia de a mă cufunda în această lume în plină efervescență.

Astfel a luat naștere StoryPixAI, un proiect personal care mi-a permis să explorez posibilitățile infinite ale IA pentru a crea povești personalizate și ilustrații magice pentru copii. Acest proiect a fost pentru mine ocazia de a mă pune în pielea unui dezvoltator full-stack, a unui prompt engineer, a unui product owner și chiar a unui designer UX/UI, împărtășindu-mi în același timp pasiunea pentru tehnologie cu cei apropiați.

În acest articol de blog, vă voi împărtăși alegerile mele tehnologice și provocările întâlnite pe parcursul acestei aventuri palpitante.

Dar înainte de toate, un pic de aperitiv!

Pentru a vă oferi un pre-gust al potențialului StoryPixAI, iată câteva povești generate automat, în mai multe limbi.
Fiecare poveste este însoțită de ilustrații, făcând relatarea și mai imersivă pentru copii:

IA în slujba creativității: un parcurs de experimentare

Aventura mea cu StoryPixAI a început cu un simplu Proof of Concept (PoC): o funcție Lambda care interacționa cu OpenAI pentru a genera text și DALL-E pentru a crea imagini. Acest prim succes m-a încurajat să merg mai departe și să explorez alte modele de IA prin Bedrock de la AWS.

GPT-4 și GPT-4-o: naratorii agili

De la începutul proiectului, GPT-4 de la OpenAI s-a impus ca o alegere evidentă pentru generarea de text. Capacitatea sa de a înțelege nuanțele limbajului natural și de a produce relatări coerente și creative mi-a permis să creez povești captivante, adaptate vârstei și intereselor copiilor. Am putut experimenta cu diferite stiluri de scriere, de la basm la aventuri spațiale, trecând prin povești cu animale și narațiuni fantastice.

Când a fost lansat GPT-4-0, am integrat rapid acest nou model în StoryPixAI. Am fost impresionat de viteza sa de generare crescută, care a permis reducerea considerabilă a timpului de așteptare pentru generare, și de îmbunătățirea notabilă a calității poveștilor generate, cu narațiuni și mai fluide, coerente și imaginative. GPT-4-0 a devenit astfel un atu major pentru StoryPixAI, oferind o experiență de utilizare mai rapidă și mai plăcută.

DALL-E 3: Ilustratorul de referință

Dacă modelele de generare de texte ofereau rezultate satisfăcătoare, alegerea instrumentului de generare de imagini s-a dovedit mai crucială. După multe încercări, DALL-E 3 s-a impus ca modelul de referință pentru StoryPixAI. Capacitatea sa de a crea ilustrații originale, detaliate și perfect adaptate poveștilor generate de GPT-4 a fost un factor determinant în succesul proiectului.

Bedrock de AWS: Poarta deschisă către experimentare

Dorința de a nu mă limita la OpenAI m-a făcut să folosesc Bedrock de AWS pentru a integra ușor alte modele de AI generativă în StoryPixAI. Această platformă mi-a permis să testez Claude de Anthropic și Mistral pentru generarea de texte și Stable Diffusion pentru crearea de imagini.

Deși aceste modele au dat rezultate interesante, am ales în cele din urmă să mă concentrez pe GPT-4 și GPT-4-0 pentru rapiditatea și calitatea lor de generare a textului, și pe DALL-E 3 pentru capacitatea sa de a produce ilustrații perfect adaptate poveștilor. Este important de notat că promptul folosit pentru generarea imaginilor este în mare parte elaborat de modelul de text însuși, ceea ce asigură o coerență între narațiune și ilustrație.

Provocarea API asincron și a DynamoDB

Odată ce PoC a fost validat, am întreprins crearea unui API pentru a face StoryPixAI accesibil printr-o interfață web. În acest stadiu am întâlnit prima provocare majoră: limita de timeout a API Gateway. Pentru a depăși această constrângere și a permite generarea de povești mai lungi și complexe, a trebuit să pun în aplicare o arhitectură asincronă.

Amazon DynamoDB a intrat atunci în joc. Am folosit această bază de date NoSQL pentru a stoca sarcinile de generare a poveștilor în curs, precum și rezultatele lor odată terminate. Datorită acestei abordări, API-ul putea trimite un răspuns imediat utilizatorului, care putea apoi consulta starea cererii sale și recupera povestea generată odată ce era gata.

CORS și interfața de utilizator: obstacole de depășit

Implementarea interfeței web a fost de asemenea sursa unor provocări. A trebuit să mă familiarizez cu subtilitățile CORS (Cross-Origin Resource Sharing) pentru a permite frontend-ului meu să comunice cu API-ul. De asemenea, am dedicat timp îmbunătățirii experienței utilizatorului adăugând funcționalități precum selectarea modelelor de AI și a stilurilor de imagini.

Prompting-ul: o artă de stăpânit

Pe parcursul dezvoltării StoryPixAI, mi-am perfecționat abilitățile de prompting, această artă de a formula solicitările potrivite pentru a ghida modelele de AI. Am învățat să adaptez prompturile în funcție de modelele utilizate, parametrii poveștii și așteptările utilizatorilor. Această etapă a fost crucială pentru obținerea unor rezultate de calitate și garantarea unei experiențe de utilizator satisfăcătoare.

O infrastructură robustă și automatizată pe AWS

StoryPixAI se bazează pe o infrastructură serverless găzduită pe Amazon Web Services (AWS), oferind o combinație ideală de flexibilitate, scalabilitate și optimizare a costurilor. Această arhitectură, complet automatizată datorită Terraform și GitLab CI/CD, permite o implementare rapidă și fiabilă a aplicației.

Serviciile AWS în centrul StoryPixAI

alt text

Arhitectura StoryPixAI se articulează în jurul următoarelor servicii AWS: * Amazon S3 (Simple Storage Service): Stocarea fișierelor statice ale site-ului web (HTML, CSS, JavaScript) și a poveștilor generate, precum și a ilustrațiilor asociate.

  • Amazon CloudFront: O rețea de distribuție a conținutului (CDN) care accelerează distribuția conținutului StoryPixAI către utilizatorii din întreaga lume prin cache-ul în locații geografice apropiate de aceștia.
  • Amazon API Gateway: Poarta de acces securizată a aplicației. Gestionează solicitările utilizatorilor, le autentifică prin Amazon Cognito și le direcționează către funcțiile Lambda corespunzătoare.
  • AWS Lambda: Funcții serverless care constituie motorul StoryPixAI. Ele orchestrează generarea de povești, crearea de imagini, gestionarea sarcinilor asincrone și interacțiunea cu DynamoDB și alte servicii AWS.
  • Amazon DynamoDB: O bază de date NoSQL flexibilă și performantă utilizată pentru a stoca informații esențiale pentru funcționarea aplicației.
  • Amazon Cognito: Un serviciu de gestionare a identităților și acceselor care securizează aplicația, permițând utilizatorilor să se conecteze și controlând permisiunile acestora. Asigură că doar utilizatorii autentificați pot accesa funcționalitățile de generare de povești.
  • Amazon Bedrock: O platformă care simplifică accesul și utilizarea modelelor de IA generativă de la diferiți furnizori, cum ar fi Anthropic (Claude) și Stability AI (Stable Diffusion). Bedrock permite integrarea ușoară a acestor modele în aplicație fără a fi necesară gestionarea infrastructurii lor subiacente.
  • Alte servicii AWS: StoryPixAI utilizează, de asemenea, alte servicii AWS, cum ar fi IAM (Identity and Access Management) pentru gestionarea fină a permisiunilor de acces la resurse, CloudWatch pentru monitorizare și jurnale (crucial pentru depanare și analiza performanței), și Systems Manager Parameter Store (SSM Parameter Store) pentru stocarea informațiilor sensibile, cum ar fi cheile API, garantând astfel securitatea aplicației.

Terraform: automatizarea în slujba infrastructurii

Pentru a gestiona această infrastructură complexă, am ales Terraform, un instrument de Infrastructure as Code (IaC) care permite descrierea infrastructurii sub formă de cod declarativ. Datorită Terraform, am putut automatiza crearea, modificarea și distrugerea resurselor AWS, garantând astfel un mediu coerent, reproductibil și ușor de gestionat. Acest lucru simplifică considerabil procesul de desfășurare și reduce riscul erorilor umane.

GitLab CI/CD: desfășurări fluide și fără probleme

Pentru a asigura o desfășurare continuă și de încredere a StoryPixAI, am implementat un pipeline CI/CD (Continuous Integration / Continuous Deployment) pe GitLab. Acest pipeline automatizează testele, construirea și desfășurarea aplicației la fiecare modificare a codului sursă, permițând astfel detectarea și corectarea rapidă a erorilor și livrarea de noi funcționalități cu încredere. Această abordare garantează că aplicația este întotdeauna actualizată și minimizează timpii de nefuncționare.

Această combinație de AWS, Terraform și GitLab CI/CD mi-a permis să construiesc o infrastructură robustă, scalabilă și ușor de întreținut, lăsându-mi astfel mai mult timp pentru a mă concentra pe aspectul creativ al proiectului și pe îmbunătățirea experienței utilizatorului.

Arhitectura generală a proiectului StoryPixAI

Înainte de a întra în cod, iată o privire de ansamblu asupra arhitecturii aplicației:

  1. Site static pe S3: Un site web static găzduit pe un bucket S3, accesibil prin CloudFront pentru o distribuire globală.
  2. API Gateway: Expune endpoint-uri pentru generarea de povești și verificarea statusului.
  3. Funcții Lambda:
    • StoryPixAI.py: Generează povestea și imaginile asociate.
    • status_checker.py: Verifică statusul generării în DynamoDB.
  4. DynamoDB: Stochează statusul sarcinilor de generare.
  5. S3 : Stochează imaginile generate și paginile HTML rezultate.
  6. Cognito : Gestionează autentificarea utilizatorilor pentru a securiza API-ul.

Funcție Lambda StoryPixAI.py

Prezentare Generală

Funcția StoryPixAI.py este inima aplicației. Ea este responsabilă de:

  • Generarea unei povești bazate pe un prompt al utilizatorului.
  • Crearea unor instrucțiuni detaliate pentru a ghida modelul de IA în generarea poveștii.
  • Extracția de rezumate pentru fiecare scenă sau element cheie al poveștii.
  • Generarea de imagini corespunzătoare acestor rezumate.
  • Combinarea textului și imaginilor într-o pagină HTML.
  • Stocarea rezultatului în S3 și actualizarea statusului în DynamoDB.

Decompunerea Codului

Imports și Configurație Inițială

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)

În această secțiune, import modulele necesare, configurez logger-ul pentru debug și recuperez cheia API OpenAI stocată în AWS Systems Manager Parameter Store (SSM). Acest lucru permite securizarea cheii și evitarea stocării acesteia în clar în cod.

Funcții Utilitare

Corectarea Balizelor
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é.

Această funcție asigură că balizele utilizate pentru delimitarea rezumatelor și titlurilor sunt uniforme. Acest lucru este crucial pentru extragerea corectă a rezumatelor ulterior.

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

Utilizează expresii regulate pentru a extrage secțiunile de text delimitate de [resume] și [end_resume]. Aceste rezumate vor servi ca prompturi pentru generarea imaginilor.

Generarea Instrucțiunilor pentru Imagini
def generate_image_instructions(prompt, style, language):
    # Génère les instructions pour la création d'images.

Această funcție formatează promptul pentru a ghida modelul de generare a imaginilor incluzând stilul și limba.

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

Actualizează tabelul TaskStatus pentru a urmări starea generării, ceea ce este esențial pentru funcția status_checker.py.

Analiză Aprofundată a generate_story_instructions

Funcția generate_story_instructions este inima proiectului. Ea generează un set de instrucțiuni detaliate care vor fi transmise modelului de IA pentru a ghida generarea poveștii.

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

Construcția Promptului

Promptul este conceput pentru a furniza modelului de IA toate informațiile necesare pentru a genera o poveste coerentă, educativă și adecvată copiilor.

  • Limbă : Parametrul language_description permite specificarea limbii poveștii, asigurând astfel că textul generat va fi în limba dorită.

  • Temă : Promptul utilizatorului este integrat în instrucțiuni pentru a servi ca bază pentru poveste.

  • Lungime : Este specificată o plajă de 1000 până la 1500 de cuvinte pentru a controla lungimea poveștii.

  • Elemente Cheie : Instrucțiunile încurajează includerea unor elemente precum aventura, magia și valori educative importante.

Detalii ale Instrucțiunilor

Instrucțiunile furnizate modelului sunt extrem de detaliate pentru a ghida generarea de manieră precisă.

Iată o analiză a diferitelor părți ale promptului:

  1. Structura Narativă : Se cere modelului să structureze povestea cu un început captivant, un dezvoltare plină de evenimente și o concluzie satisfăcătoare.

  2. Descrieri Vizuale : Povestea trebuie să fie bogată în descrieri vizuale pentru a stimula imaginația copiilor.

  3. Personaje : Se încurajează dezvoltarea de personaje atrăgătoare cu personalități distincte.

  4. Balize Specifice : Balize precum [titre]... [end_titre] și [resume]... [end_resume] sunt utilizate pentru a delimita titlul și descrierile vizuale.

  5. Elemente Fantastice : Se invită modelul să includă elemente magice sau fantastice pentru a face povestea mai atrăgătoare.

  6. Valori Educative : Povestea trebuie să învețe valori importante.

Rolul Balizelor

Balizele joacă un rol crucial în procesarea ulterioară a textului generat.

  • [titre]… [end_titre] : Înconjoară titlul poveștii. Acest lucru permite extragerea acestuia ușor pentru a fi afișat corespunzător în interfața utilizatorului.

  • [resume]… [end_resume] : Encapsulează descrierile vizuale detaliate ale scenelor cheie din poveste. Aceste rezumate vor fi utilizate ca prompturi pentru generarea imaginilor.

Prelucrarea După Generare

Odată ce modelul IA a generat povestea urmând aceste instrucțiuni, codul efectuează următorii pași:

  1. Corectarea Etichetelor: Funcția correct_resume_tags se asigură că toate etichetele sunt formatate corect pentru extragere.

  2. Extragerea Rezumatelor: Funcția extract_summaries utilizează etichetele [resume] și [end_resume] pentru a extrage descrierile vizuale.

  3. Generarea Imaginilor: Fiecare rezumat este trecut la funcția generate_image pentru a crea o imagine corespunzătoare.

  4. Crearea Conținutului HTML: Textul poveștii și imaginile generate sunt combinate pentru a crea o pagină HTML completă.

Impact asupra Generării

Prin furnizarea acestor instrucțiuni detaliate, modelul este ghidat pentru:

  • Respectarea Formatului: Prin utilizarea etichetelor specificate, modelul produce un text structurat care facilitează procesarea automată.

  • Generarea de Conținut Potrivit: Constrângerile asupra limbii, stilului și temelor garantează că povestea este adecvată pentru publicul țintă.

  • Facilitarea Generării de Imagini: Prin extragerea unor descrieri vizuale precise, se obțin prompturi de calitate pentru generarea de imagini.

Gestionarea Etichetelor de către Model

Modelul este explicit instruit să nu traducă sau să modifice etichetele. Acest lucru este esențial pentru ca etichetele să rămână intacte și să poată fi utilizate pentru post-procesare. Instrucțiunile insistă asupra acestui punct pentru a evita ca modelul, care ar putea încerca să parafrazeze sau să traducă tot textul, să modifice etichetele.

Generarea Poveștii

Odată ce instrucțiunile detaliate sunt generate de funcția generate_story_instructions, următorul pas este de a transmite aceste instrucțiuni modelului de IA pentru a crea povestea.

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

Interacțiunea cu Modelul OpenAI

  • Client OpenAI: Instanțiez un client OpenAI utilizând cheia API obținută anterior.

  • Prompting: Modelul primește o serie de mesaje:

    • Un mesaj sistem indicând că asistentul este un expert în povești pentru copii.
    • Mesajul utilizatorului conținând instrucțiunile detaliate generate.
  • Răspunsul Modelului: Modelul generează o poveste pe baza instrucțiunilor furnizate.

Gestionarea Erorilor

Dacă survine o excepție în timpul apelului la API-ul OpenAI, aceasta este capturată și este returnat un mesaj de eroare.

Extragerea Rezumatelor și Etichetelor

După generarea poveștii, următorul pas este de a extrage descrierile vizuale utilizând etichetele specificate.

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 are re.findall(pattern, text, re.DOTALL)
    return summaries

Corectarea Etichetelor

Modelul poate altera ușor etichetele (de exemplu, adăugând accente). Funcția correct_resume_tags se asigură că toate etichetele sunt uniforme și formatate corect.

Extragerea Rezumatelor

Funcția extract_summaries utilizează o expresie regulată pentru a găsi toate ocurențele de text între etichetele [resume] și [end_resume]. Aceste rezumate sunt descrierile vizuale detaliate care vor fi utilizate pentru a genera imaginile.

Generarea Imaginilor

Odată ce rezumatele sunt extrase, fiecare rezumat este utilizat pentru a genera o imagine corespunzătoare.

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

Funcția generate_image

Funcția generate_image apelează API-ul modelului de generare a imaginilor (de exemplu, OpenAI DALL·E) pentru a crea o imagine din rezumat.

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

Generarea Instrucțiunilor pentru Imagini Funcția generate_image_instructions adaptează rezumatul pentru a crea un prompt adecvat pentru generarea imaginilor.

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.
    """
  • Stil: Stilul specificat de utilizator (de exemplu, “aquarelă”, “desen animat”) este inclus în prompt pentru a influența redarea imaginii.

  • Limbă: Descrierea este adaptată la limba aleasă, ceea ce poate ajuta modelul să înțeleagă nuanțele culturale.

  • Instrucțiuni Clare: Precizând că scena trebuie să fie pur vizuală, se evită ca modelul să adauge text sau elemente nedorite în imagine.

Interacțiunea cu API-ul OpenAI pentru Imagini

  • Apel la API: Funcția client.images.generate este utilizată pentru a genera imaginea.

  • Parametri Importanți:

    • Prompt: Promptul ajustat este transmis la API.
    • Model: Modelul de generare a imaginilor specificat.
    • Dimensiune: Dimensiunea imaginii (de exemplu, “1024x1024”).
    • Calitate: Calitatea imaginii (standard, HD).
    • Format de Răspuns: Imaginile sunt returnate în base64 pentru a facilita stocarea și manipularea.

Gestionarea Erorilor

Erorile în timpul generării imaginilor sunt capturate și înregistrate, permițând diagnosticarea problemelor.

Crearea Conținutului HTML

După generarea imaginilor corespunzătoare rezumatelor extrase, pasul următor constă în asamblarea textului poveștii și a imaginilor într-un format prezentabil pentru utilizator. Acest lucru se face prin crearea unui conținut HTML structurat care va fi afișat pe site-ul web.

def create_html_with_images(text_data, images_urls, generate_images=True):
    """
    Crée un contenu HTML en intégrant le texte et les images générées.
    """
    # Extraction du titre
    title_match = re.search(r"\[titre\](.*?)\[end_titre\]", text_data)
    if title_match is not None:
        title = title_match.group(1)
        text_data = text_data.replace(title_match.group(0), "")
    else:
        title = "Histoire Générée par l'IA"

    # Initialisation du contenu HTML
    html_content = """
    <html>
    <head>
        <title>Histoire générée par l'IA</title>
        <meta charset='UTF-8'>
        <style>
            /* Styles CSS pour une présentation agréable */
            body { font-family: Arial, sans-serif; margin: 20px; }
            .title { text-align: center; font-size: 2em; margin-bottom: 20px; }
            .center { text-align: center; }
            img { max-width: 100%; height: auto; margin: 20px 0; }
            p { text-align: justify; line-height: 1.6; }
        </style>
    </head>
    <body>
    """
    html_content += f'<div class="title">{title}</div>\n'

    # Séparation du texte en segments basés sur les résumés
    summaries = extract_summaries(text_data)
    segments = re.split(r"\[resume\].*?\[end_resume\]", text_data, flags=re.DOTALL)

    # Assemblage du texte et des images
    for i, segment in enumerate(segments):
        formatted_segment = segment.strip().replace("\n", "<br>")
        html_content += f"<p>{formatted_segment}</p>\n"
        if generate_images and i < len(images_urls) and images_urls[i]:
            image_url = images_urls[i]
            html_content += f'<div class="center"><img src="{image_url}" alt="Image générée"></div>\n'

    html_content += "</body></html>"
    return html_content

Explicație Detaliată

  1. Extracția Titlului:

    • Folosește o expresie regulată pentru a găsi textul între etichetele [titre] și [end_titre].
    • Elimină etichetele din textul principal după extracție.
    • Dacă nu se găsește niciun titlu, se folosește un titlu implicit.
  2. Inițializarea HTML-ului:

    • Conținutul HTML începe cu etichetele <html>, <head> și <body>.
    • Stilurile CSS sunt incluse pentru a îmbunătăți prezentarea (tipografie, margini, aliniamente).
  3. Separarea Textului:

    • Textul este împărțit în segmente folosind etichetele [resume] și [end_resume].
    • Segmentele reprezintă părțile poveștii fără rezumate.
  4. Asamblare:

    • Fiecare segment de text este inserat într-un paragraf <p>.
    • Dacă generarea imaginilor este activată și există o imagine corespunzătoare, imaginea este inserată după paragraf.
    • Imaginile sunt centrate și adaptate la dimensiunea ecranului pentru o experiență mai bună a utilizatorului.
  5. Finalizare:

    • Etichetele de închidere </body> și </html> sunt adăugate pentru a completa documentul HTML.

De Ce Această Abordare?

  • Alinierea Textului și Imaginilor: Inserând imaginile după segmentele de text corespunzătoare, povestea este îmbogățită vizual, ceea ce este deosebit de important pentru copii.

  • Flexibilitate: Dacă utilizatorul alege să nu genereze imagini, codul gestionează acest caz inserând doar textul.

  • Accesibilitate: Folosind etichete semantice și stiluri adecvate, conținutul este accesibil pe diferite dispozitive (calculatoare, tablete, smartphone-uri).

Upload pe S3 și Actualizarea Statusului

Odată ce conținutul HTML este generat, este necesar să fie accesibil utilizatorului. Acest lucru se face prin încărcarea fișierului într-un bucket S3 configurat pentru găzduirea site-urilor web statice.

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

Detalii Tehnice

  • Denumirea Fișierelor:

    • Fișierele sunt denumite utilizând un timestamp pentru a garanta unicitatea.
    • Imaginile sunt stocate în folderul generated_images/ și fișierele HTML în generated_content/.
  • Încărcare pe S3:

    • Utilizarea clientului S3 din boto3 pentru a interacționa cu serviciul.
    • Conținutul este encodat sau decodat în funcție de tipul (imagine sau text).
    • Parametrul ACL='public-read' face fișierul accesibil public. - Construcția URL-ului:
    • URL-ul public este construit utilizând domeniul CloudFront configurat, ceea ce permite o distribuție rapidă și securizată a conținutului.
  • Gestionarea Excepțiilor:

    • În cazul unei erori în timpul descărcării, excepția este jurnalizată și ridicată pentru a fi gestionată de lambda_handler.

Funcție Principală lambda_handler

Funcția lambda_handler este punctul de intrare pentru funcția Lambda. Ea orchestrează toate etapele descrise anterior.

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

Explicație

  • Procesarea Cererii:

    • Recuperează informațiile necesare din evenimentul (event) primit.
    • Parametrii cererii includ promptul, modelele selectate, limba, etc.
  • Actualizarea Statusului:

    • Înainte de a începe procesarea, statusul este actualizat la “Processing” în DynamoDB.
  • Generarea Poveștii:

    • Apel la generate_story cu parametrii corespunzători.
  • Extragere și Procesare:

    • Tag-urile sunt corectate și rezumatele extrase pentru generarea imaginilor.
  • Generarea Imaginilor:

    • Dacă generarea imaginilor este activată, imaginile corespunzătoare sunt generate și URL-urile colectate.
  • Crearea Conținutului HTML:

    • Textul și imaginile sunt combinate pentru a crea conținutul HTML final.
  • Upload pe S3:

    • Conținutul HTML este uploadat pe S3 și URL-ul rezultatului este obținut.
  • Actualizarea Statusului Final:

    • Statusul este actualizat la “link” cu URL-ul rezultatului în DynamoDB.
  • Returnarea Răspunsului:

    • Răspunsul include requestId și URL-ul rezultatului, permițând clientului să verifice statusul sau să acceseze direct conținutul.
  • Gestionarea Excepțiilor:

    • În cazul unei erori, statusul este actualizat la “Failed” și un răspuns HTTP 500 este returnat.

Funcția Lambda status_checker.py

Prezentare Generală

Funcția status_checker.py permite utilizatorilor să verifice statusul cererii lor de generare a poveștilor. Ea interoghează DynamoDB pentru a recupera statusul actual și, dacă este disponibil, URL-ul rezultatului.

Analiza Codului

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

Detalii

  • Recuperarea requestId:

    • requestId este esențial pentru identificarea cererii specifice a utilizatorului.
  • Interogarea DynamoDB:

    • Funcția încearcă să recupereze elementul corespunzător requestId.
    • Dacă elementul există, statusul și resultUrl sunt extrase.
  • Construcția Răspunsului:

    • Dacă statusul este disponibil, acesta este returnat împreună cu URL-ul rezultatului.
    • Dacă elementul nu este găsit, este returnată o eroare 404.
    • În cazul unei erori în timpul interogării bazei de date, este returnată o eroare 500 cu un mesaj corespunzător.
  • Antete HTTP:

    • Antetele sunt definite pentru a permite cererile CORS de pe site-ul web.

Integrare cu API Gateway

Configurarea Endpoint-urilor

API Gateway expune două endpoint-uri principale pentru a interacționa cu funcțiile Lambda:

  1. /generate-image:

    • Metoda: POST
    • Descriere: Permite utilizatorilor să lanseze generarea unei povești și, eventual, a imaginilor asociate.
    • Integrare: Conectat la funcția Lambda StoryPixAI.py.
  2. /check-status:

    • Metoda: GET
    • Descriere: Permite utilizatorilor să verifice statusul cererii lor, furnizând requestId.
    • Integrare: Conectat la funcția Lambda status_checker.py.

Autentificare cu Cognito

Pentru a securiza API-ul și a controla accesul la resurse, am integrat Amazon Cognito.

  • User Pool:

    • Gestionează informațiile de autentificare ale utilizatorilor.
    • Permite înregistrarea, conectarea și gestionarea utilizatorilor.
  • Authorizer:

    • Configurat în API Gateway pentru a verifica token-urile JWT emise de Cognito.
    • Asigură că doar cererile autentificate pot accesa endpoint-urile protejate. - Integrare pe API Gateway:
    • Endpoints-urile /generate-image și /check-status sunt protejate de authorizer-ul Cognito.
    • Clienții trebuie să includă token-ul de autentificare în anteturile cererilor lor (Authorization).

Site Static pe S3 și Interacțiune cu API-ul

Structura Site-ului

Site-ul web static servește drept interfață utilizator pentru aplicație.

  • index.html:

    • Conține formularul care permite utilizatorilor să introducă prompt-ul, să aleagă opțiunile de generare și să trimită cererea.
    • Include scripturile necesare pentru interacțiunea cu API-ul și gestionarea autentificării.
  • storypixai.js:

    • Conține codul JavaScript pentru gestionarea interacțiunilor cu API-ul.
    • Gestionează autentificarea cu Cognito, trimiterea formularului, urmărirea statutului și afișarea rezultatelor.

Flux de Lucru Utilizator

  1. Conectare:

    • Utilizatorul se conectează via formularul de autentificare integrat.
    • Informațiile sunt verificate prin Cognito.
  2. Trimiterea Cererii:

    • Utilizatorul completează formularul cu prompt-ul și opțiunile dorite.
    • La trimitere, o cerere POST este trimisă către endpoint-ul /generate-image cu datele.
  3. Procesare Asincronă:

    • API-ul returnează imediat un requestId.
    • Procesarea generării se face în fundal.
  4. Verificarea Statutului:

    • Site-ul web interoghează periodic endpoint-ul /check-status furnizând requestId.
    • Odată ce statutul “link” este primit, URL-ul rezultatului este afișat utilizatorului.
  5. Afișarea Rezultatului:

    • Utilizatorul poate face clic pe link pentru a accesa povestea generată cu imaginile.

Gestionarea Cererilor și Răspunsurilor

  • Cererile Autentificate:

    • Toate cererile către API includ token-ul de autentificare.
    • Token-ul este gestionat de SDK-ul Cognito inclus în site-ul web.
  • Gestionarea Statutelor:

    • Statuturile posibile sunt “Processing”, “link”, “Failed”.
    • Site-ul își adaptează interfața în funcție de statutul primit (de exemplu, afișarea unui spinner, mesaj de eroare, link către rezultat).

Interconexiuni între Componente

Iată cum interacționează diferitele componente:

  • Site Web ↔️ API Gateway:

    • Site-ul web trimite cereri către endpoints-urile expuse de API Gateway.
    • Token-urile de autentificare sunt incluse pentru securizarea cererilor.
  • API Gateway ↔️ Funcții Lambda:

    • API Gateway invocă funcțiile Lambda corespunzătoare în funcție de cererile primite.
  • Funcții Lambda ↔️ DynamoDB:

    • Funcțiile Lambda StoryPixAI.py și status_checker.py interacționează cu DynamoDB pentru a actualiza și recupera statutul cererilor.
  • Funcție Lambda ↔️ S3:

    • Funcția StoryPixAI.py încarcă imaginile generate și conținutul HTML pe S3.
  • CloudFront ↔️ S3:

    • CloudFront este utilizat pentru a distribui conținutul stocat pe S3 rapid și în siguranță.
    • URL-urile furnizate utilizatorilor indică spre domeniul CloudFront.
  • Utilizator ↔️ Site Web:

    • Utilizatorul interacționează cu site-ul web pentru a trimite cereri și a vizualiza rezultatele.

Exemplu de Rezultat în logurile Cloudwatch după un Apel de Cerere

Iată un exemplu de rezultat al logurilor după un apel de cerere pentru a vedea formatul brut al datelor generate:

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

Integrare Continuă cu GitLab CI/CD

Pentru a asigura o dezvoltare și o implementare fluentă a StoryPixAI, am implementat un pipeline de integrare continuă (CI) și de implementare continuă (CD) folosind GitLab CI/CD. Această configurare automatizează procesele de construire și implementare, garantând astfel calitatea și fiabilitatea codului la fiecare modificare.

Configurarea Pipeline-ului

Pipeline-ul este definit în fișierul .gitlab-ci.yml la rădăcina proiectului. Iată o privire de ansamblu asupra structurii sale:

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

variables:
  TERRAFORM_VERSION: "1.5.7-*"
  TF_VAR_region: $AWS_DEFAULT_REGION
``` Această configurație definește diferitele etape ale canalului și variabilele globale utilizate în procesul CI/CD.

### Joburi Principale

Canalul include mai multe joburi cheie:

1. **Verificare Terraform**:
   ```yaml
   Verificare Terraform:
     stage: Verificări
     when: manual
     script:
       - /bin/bash -c "source export.sh && terraform_plan"

Acest job execută terraform plan pentru a verifica schimbările planificate ale infrastructurii fără a le aplica.

  1. Deployed Terraform:

    Depunere Terraform:
      stage: Depuneri
      when: manual
      dependencies:
        - Verificare Terraform
      script:
        - /bin/bash -c "source export.sh && terraform_apply"
    

    După verificare, acest job aplică schimbările infrastructurii prin executarea terraform apply.

  2. Ștergere Terraform:

    Ștergere Terraform:
      stage: Ștergeri
      when: manual
      script:
        - /bin/bash -c "source export.sh && terraform_destroy"
    

    Acest job permite ștergerea infrastructurii dacă este necesară, prin executarea terraform destroy.

  3. Gestionarea Cheilor OpenAI:

    Cheie OpenAI - Adăugare:
      stage: Pre-rechizite opțional
      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 "Nicio cheie găsită."
            exit 1
          fi      
    
    Cheie OpenAI - Ștergere:
      stage: Ștergeri
      when: manual
      script:
        - /bin/bash -c "source export.sh && manage_openai_key delete"
    

    Aceste joburi gestionează adăugarea și ștergerea securizată a cheilor API OpenAI în AWS Parameter Store.

Mediu de Execuție

Fiecare job se execută într-un container Docker bazat pe Ubuntu 22.04, cu Terraform și AWS CLI instalate:

.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

Avantajele acestei abordări CI/CD

  1. Automatizare: Fiecare modificare a codului declanșează automat canalul, asigurând verificări și depuneri consistente.

  2. Control Manual: Etapele critice, cum ar fi depunerea și ștergerea, sunt configurate în mod manual (when: manual), oferind un control suplimentar înainte de execuție.

  3. Gestionare Securizată a Secretelor: Integrarea cu AWS Parameter Store pentru gestionarea cheilor API asigură manipularea securizată a informațiilor sensibile.

  4. Flexibilitate: Structura în etape permite o execuție ordonată și logică a diferitelor etape ale canalului.

  5. Reproducibilitate: Utilizarea unui mediu Docker standardizat garantează că build-urile și testele sunt reproductibile pe diferite sisteme.

Această configurație CI/CD nu numai că automatizează depunerea StoryPixAI, dar și menține un nivel înalt de calitate și fiabilitate pe întregul ciclu de dezvoltare.

Concluzie

StoryPixAI a fost mult mai mult decât un simplu proiect tehnic. A fost o adevărată aventură în lumea IA generativă, permițându-mi să combin pasiunea mea pentru tehnologie cu dorința de a crea povești magice pentru copiii mei.

Acest proiect mi-a oferit oportunitatea de a explora diverse fațete ale IA, de la conceperea unei interfețe de utilizator intuitive la stăpânirea prompting-ului, trecând prin realizarea unei infrastructuri cloud robuste cu AWS și Terraform. Fiecare etapă a fost o sursă de învățare, confruntându-mă cu provocări tehnice stimulante și obligându-mă să-mi extind competențele în dezvoltare full-stack și DevOps.

Sper că această postare pe blog v-a oferit un instantaneu al culiselor acestei aventuri pasionante.

Puncte Cheie

  • Instrucțiuni Detaliate:

    • Prompturi clare și structurate permit obținerea de rezultate consistente și de înaltă calitate din partea modelelor IA. - Arhitectură Modulară :
    • Fiecare componentă (site web, API Gateway, Lambda, DynamoDB, S3, Cognito) joacă un rol specific, facilitând întreținerea și evoluția sistemului.
  • Securitate și Scalabilitate :

    • Utilizarea serviciilor gestionate de AWS asigură o securitate robustă și o capacitate de a se adapta la cererea în creștere.

Link-ul proiectului: StoryPixAI

Acest document a fost tradus din versiunea fr în limba ro folosind modelul gpt-4o. Pentru mai multe informații despre procesul de traducere, consultați https://gitlab.com/jls42/ai-powered-markdown-translator.