Com StoryPixAI meu objetivo era criar um aplicativo web interativo que permitisse aos usuários gerar histórias para crianças, enriquecidas com imagens geradas por modelos de inteligência artificial. Para realizar isso, utilizei vários serviços AWS, como Lambda, API Gateway, DynamoDB, S3 e Cognito para autenticação. O código da infraestrutura é gerido com Terraform, e a implantação é automatizada via GitLab CI. Neste post, revelo os bastidores deste projeto empolgante, desde as escolhas tecnológicas até os desafios enfrentados.

Introdução

Como arquiteto de infraestruturas cloud e DevOps experiente, sempre fui fascinado por novas tecnologias e seu potencial para transformar nosso cotidiano. O surgimento da IA generativa despertou em mim uma curiosidade crescente, e senti a necessidade de mergulhar neste universo em plena efervescência.

Assim nasceu StoryPixAI, um projeto pessoal que me permitiu explorar as possibilidades infinitas da IA para criar histórias personalizadas e ilustrações mágicas para crianças. Este projeto foi uma oportunidade para me colocar na pele de um desenvolvedor full-stack, um engenheiro de prompt, um product owner e até mesmo um designer UX/UI, enquanto compartilhava minha paixão pela tecnologia com meus entes queridos.

Neste post do blog, compartilharei minhas escolhas tecnológicas e os desafios superados ao longo desta aventura empolgante.

Mas antes de mais nada, um gostinho do que está por vir!

Para dar uma prévia do potencial do StoryPixAI, aqui estão algumas histórias geradas automaticamente, em várias línguas.
Cada história é acompanhada de ilustrações, tornando o conto ainda mais imersivo para as crianças:

A IA a serviço da criatividade: um percurso de experimentação

Minha aventura com StoryPixAI começou com um Proof of Concept (PoC) simples: uma função Lambda que interagia com a OpenAI para gerar texto e com DALL-E para criar imagens. Esse primeiro sucesso me encorajou a ir mais longe e explorar outros modelos de IA via Bedrock da AWS.

GPT-4 e GPT-4-o: os contadores ágeis

Desde o início do projeto, o GPT-4 da OpenAI se impôs como uma escolha óbvia para a geração de texto. Sua capacidade de compreender as nuances da linguagem natural e de produzir narrativas coerentes e criativas me permitiu criar histórias cativantes, adaptadas à idade e aos interesses das crianças. Pude experimentar com diferentes estilos de escrita, desde contos de fadas até aventuras espaciais, passando por histórias de animais e relatos fantásticos.

Quando o GPT-4-0 foi lançado, rapidamente integrei esse novo modelo ao StoryPixAI. Fiquei impressionado com sua velocidade de geração aumentada, que permitiu reduzir significativamente o tempo de espera para a geração, e com a notável melhoria na qualidade das histórias geradas, com narrativas ainda mais fluídas, coerentes e imaginativas. GPT-4-0 tornou-se assim um grande trunfo para StoryPixAI, proporcionando uma experiência de usuário mais rápida e agradável.

DALL-E 3: o ilustrador de referência

Se os modelos de geração de texto ofereciam resultados satisfatórios, a escolha da ferramenta de geração de imagens mostrou-se mais crucial. Após muitos testes, o DALL-E 3 se estabeleceu como o modelo de referência para o StoryPixAI. Sua capacidade de criar ilustrações originais, detalhadas e perfeitamente adaptadas às histórias geradas pelo GPT-4 foi um fator decisivo no sucesso do projeto.

Bedrock da AWS: a porta aberta para a experimentação

Desejando não me limitar à OpenAI, usei o Bedrock da AWS para integrar facilmente outros modelos de IA generativa ao StoryPixAI. Esta plataforma me permitiu testar Claude da Anthropic e Mistral para geração de texto, e Stable Diffusion para criação de imagens.

Embora esses modelos tenham fornecido resultados interessantes, eu acabei optando por me concentrar no GPT-4 e GPT-4-0 pela sua rapidez e qualidade na geração de texto, e no DALL-E 3 por sua capacidade de produzir ilustrações perfeitamente adaptadas às histórias. É importante notar que o prompt usado para gerar as imagens é em grande parte elaborado pelo próprio modelo de texto, o que assegura uma coerência entre a narrativa e a ilustração.

O desafio da API assíncrona e do DynamoDB

Uma vez validado o PoC, empreendi a criação de uma API para tornar o StoryPixAI acessível via uma interface web. Foi nesse estágio que enfrentei meu primeiro grande desafio: a limitação de timeout do API Gateway. Para contornar essa restrição e permitir a geração de histórias mais longas e complexas, tive que implementar uma arquitetura assíncrona.

O Amazon DynamoDB então entrou em jogo. Usei esse banco de dados NoSQL para armazenar as tarefas de geração de histórias em andamento, bem como seus resultados uma vez finalizados. Graças a esta abordagem, a API poderia retornar uma resposta imediata ao usuário, que podia então consultar o estado da sua requisição e recuperar a história gerada uma vez pronta.

CORS e a interface do usuário: obstáculos a serem superados

A implementação da interface web também foi fonte de desafios. Precisei me familiarizar com as sutilezas do CORS (Cross-Origin Resource Sharing) para permitir que meu frontend se comunicasse com a API. Também dediquei tempo para melhorar a experiência do usuário, adicionando funcionalidades como a seleção de modelos de IA e estilos de imagens.

O prompting: uma arte a ser dominada

Ao longo do desenvolvimento do StoryPixAI, refinei minhas habilidades em prompting, essa arte de formular as requisições certas para guiar os modelos de IA. Aprendi a adaptar meus prompts de acordo com os modelos utilizados, parâmetros da história e expectativas dos usuários. Esta etapa foi crucial para obter resultados de qualidade e garantir uma experiência satisfatória ao usuário.

Uma infraestrutura robusta e automatizada na AWS

StoryPixAI baseia-se em uma infraestrutura serverless hospedada na Amazon Web Services (AWS), oferecendo uma combinação ideal de flexibilidade, escalabilidade e otimização de custos. Esta arquitetura, totalmente automatizada graças ao Terraform e GitLab CI/CD, permite um rápido e confiável deployment da aplicação.

Os serviços da AWS no coração do StoryPixAI

alt text

A arquitetura do StoryPixAI organiza-se ao redor dos seguintes serviços da AWS: * Amazon S3 (Simple Storage Service): Armazenamento dos arquivos estáticos do site web (HTML, CSS, JavaScript) e das histórias geradas, bem como suas ilustrações associadas.

  • Amazon CloudFront: Uma rede de distribuição de conteúdo (CDN) que acelera a distribuição do conteúdo do StoryPixAI para usuários ao redor do mundo, armazenando-os em cache em locais geograficamente próximos a eles.
  • Amazon API Gateway: O portão de entrada seguro da aplicação. Ele gerencia as requisições dos usuários, assegura a autenticação via Amazon Cognito, e as direciona para as funções Lambda apropriadas.
  • AWS Lambda: Funções serverless que constituem o motor do StoryPixAI. Elas orquestram a geração de histórias, a criação de imagens, a gestão de tarefas assíncronas e a interação com DynamoDB e outros serviços AWS.
  • Amazon DynamoDB: Um banco de dados NoSQL flexível e de alta performance usado para armazenar informações essenciais ao funcionamento da aplicação.
  • Amazon Cognito: Um serviço de gestão de identidades e acessos que protege a aplicação permitindo aos usuários conectarem-se e controlando suas autorizações. Garante que apenas os usuários autenticados possam acessar as funcionalidades de geração de histórias.
  • Amazon Bedrock: Uma plataforma que simplifica o acesso e uso de modelos de IA generativa de diferentes fornecedores, tais como Anthropic (Claude) e Stability AI (Stable Diffusion). Bedrock permite integrar facilmente esses modelos na aplicação sem precisar gerenciar a infraestrutura subjacente.
  • Outros serviços AWS: StoryPixAI também utiliza outros serviços AWS, como IAM (Identity and Access Management) para a gestão precisa de autorizações de acesso a recursos, CloudWatch para monitoramento e logs (crucial para depuração e análise de desempenho), e Systems Manager Parameter Store (SSM Parameter Store) para armazenar informações sensíveis como chaves de API, garantindo assim a segurança da aplicação.

Terraform: a automatização a serviço da infraestrutura

Para gerenciar essa infraestrutura complexa, escolhi o Terraform, uma ferramenta de Infrastructure as Code (IaC) que permite descrever a infraestrutura na forma de código declarativo. Graças ao Terraform, consegui automatizar a criação, modificação e destruição dos recursos AWS, garantindo um ambiente consistente, reproduzível e fácil de gerenciar. Isso simplifica consideravelmente o processo de implantação e reduz o risco de erros humanos.

GitLab CI/CD: implantações fluidas e sem falhas

Para assegurar uma implantação contínua e confiável do StoryPixAI, configurei um pipeline CI/CD (Integração Contínua / Implantação Contínua) no GitLab. Esse pipeline automatiza os testes, a construção e o despliegue da aplicação a cada modificação do código-fonte, permitindo detectar e corrigir rapidamente erros e entregar novas funcionalidades com confiança. Essa abordagem garante que a aplicação esteja sempre atualizada e minimiza o tempo de inatividade.

Essa combinação de AWS, Terraform e GitLab CI/CD permitiu-me construir uma infraestrutura robusta, escalável e fácil de manter, me deixando mais tempo para focar no aspecto criativo do projeto e na melhoria da experiência do usuário.

Arquitetura Global do Projeto StoryPixAI

Antes de mergulhar no código, aqui está uma visão geral da arquitetura da aplicação:

  1. Site Estático no S3: Um site web estático hospedado em um bucket S3, acessível via CloudFront para distribuição global.
  2. API Gateway: Exposições de endpoints para geração de histórias e verificação de status.
  3. Funções Lambda:
    • StoryPixAI.py: Gera a história e as imagens associadas.
    • status_checker.py: Verifica o status da geração no DynamoDB.
  4. DynamoDB: Armazena o status das tarefas de geração.
  5. S3: Armazena as imagens geradas e as páginas HTML resultantes.
  6. Cognito: Gerencia a autenticação dos usuários para proteger a API.

Função Lambda StoryPixAI.py

Visão Geral

A função StoryPixAI.py é o coração do aplicativo. Ela é responsável por:

  • Gerar uma história baseada em um prompt do usuário.
  • Criar instruções detalhadas para guiar o modelo de IA na geração da história.
  • Extrair resumos para cada cena ou elemento-chave da história.
  • Gerar imagens correspondentes a esses resumos.
  • Combinar o texto e as imagens em uma página HTML.
  • Armazenar o resultado no S3 e atualizar o status no DynamoDB.

Decomposição do Código

Imports e Configuração Inicial

import json
import boto3
import base64
import os
import re
from datetime import datetime
from openai import OpenAI
import logging

# Configuration du logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)

generate_images = False
region_name = os.getenv("AWS_REGION", "us-east-1")

# Création d'un client SSM
ssm = boto3.client("ssm", region_name=region_name)

# Obtention de la clé API OpenAI depuis le SSM
parameter = ssm.get_parameter(Name="/openaikey", WithDecryption=True)

Nesta seção, importo os módulos necessários, configuro o logger para depuração, e recupero a chave API OpenAI armazenada no AWS Systems Manager Parameter Store (SSM). Isso permite proteger a chave e não armazená-la explicitamente no código.

Funções Utilitárias

Correção de 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é.

Esta função garante que as tags usadas para delimitar os resumos e títulos sejam uniformes. Isso é crucial para a correta extração dos resumos mais tarde.

Extração dos Resumos
def extract_summaries(text):
    # Extrait les résumés du texte en utilisant des balises spécifiques.

Ela usa expressões regulares para extrair as seções de texto delimitadas por [resume] e [end_resume]. Esses resumos servirão como prompts para a geração de imagens.

Geração de Instruções para as Imagens
def generate_image_instructions(prompt, style, language):
    # Génère les instructions pour la création d'images.

Esta função formata o prompt de maneira a guiar o modelo de geração de imagens, incluindo o estilo e o idioma.

Atualização do 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.

Ela atualiza a tabela TaskStatus para acompanhar o estado da geração, o que é essencial para a função status_checker.py.

Análise Aprofundada de generate_story_instructions

A função generate_story_instructions é o coração do projeto. Ela gera um conjunto de instruções detalhadas que serão passadas ao modelo de IA para guiar a geração da história.

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

Construção do Prompt

O prompt é projetado para fornecer ao modelo de IA todas as informações necessárias para gerar uma história coerente, educativa e adequada para crianças.

  • Idioma: O parâmetro language_description permite especificar o idioma da história, garantindo que o texto gerado seja no idioma desejado.

  • Tema: O prompt do usuário é integrado nas instruções para servir de base à história.

  • Duração: Um intervalo de 1000 a 1500 palavras é especificado para controlar a duração da história.

  • Elementos-chave: As instruções incentivam a inclusão de elementos como aventura, magia e valores educativos importantes.

Detalhes das Instruções

As instruções fornecidas ao modelo são extremamente detalhadas para guiar a geração de maneira precisa.

Aqui está uma análise das diferentes partes do prompt:

  1. Estrutura Narrativa: Pede-se ao modelo que estruture a história com um começo cativante, um desenvolvimento rico em eventos e uma conclusão satisfatória.

  2. Descrições Visuais: A história deve ser rica em descrições visuais para estimular a imaginação das crianças.

  3. Personagens: Incentiva-se o desenvolvimento de personagens cativantes com personalidades distintas.

  4. Tags Específicas: Tags como [titre]... [end_titre] e [resume]... [end_resume] são usadas para delimitar o título e as descrições visuais.

  5. Elementos Fantásticos: Incentiva-se o modelo a incluir elementos mágicos ou fantásticos para tornar a história mais atraente.

  6. Valores Educativos: A história deve ensinar valores importantes.

Papel das Tags

As tags desempenham um papel crucial no processamento subsequente do texto gerado.

  • [titre]… [end_titre]: Enquadra o título da história. Isso permite extraí-lo facilmente para exibi-lo de maneira apropriada na interface do usuário.

  • [resume]… [end_resume]: Enquadra as descrições visuais detalhadas de cenas principais da história. Esses resumos serão usados como prompts para a geração de imagens.

Processamento após a Geração

Uma vez que o modelo de IA tenha gerado a história seguindo essas instruções, o código executa as seguintes etapas:

  1. Correção das Tags: A função correct_resume_tags assegura que todas as tags estão corretamente formatadas para a extração.

  2. Extração dos Resumos: A função extract_summaries usa as tags [resume] e [end_resume] para extrair as descrições visuais.

  3. Geração das Imagens: Cada resumo é passado para a função generate_image para criar uma imagem correspondente.

  4. Criação do Conteúdo HTML: O texto da história e as imagens geradas são combinados para criar uma página HTML completa.

Impacto na Geração

Ao fornecer essas instruções detalhadas, o modelo é orientado para:

  • Respeitar o Formato: Usando as tags especificadas, o modelo produz um texto estruturado que facilita o processamento automatizado.
  • Gerar Conteúdo Adequado: As restrições quanto à linguagem, estilo e temas garantem que a história é apropriada para o público-alvo.
  • Facilitar a Geração de Imagens: Extraindo descrições visuais precisas, obtém-se prompts de qualidade para a geração de imagens.

Gerenciamento das Tags pelo Modelo

O modelo é explicitamente instruído a não traduzir ou modificar as tags. Isso é essencial para que as tags permaneçam intactas e possam ser usadas para o pós-processamento. As instruções insistem neste ponto para evitar que o modelo, que poderia tentar parafrasear ou traduzir todo o texto, altere as tags.

Geração da História

Uma vez que as instruções detalhadas foram geradas pela função generate_story_instructions, a próxima etapa é passar essas instruções para o modelo de IA para que ele crie a história.

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

Interação com o Modelo OpenAI

  • Cliente OpenAI: Eu instancio um cliente OpenAI usando a chave de API recuperada anteriormente.
  • Prompting: O modelo recebe uma série de mensagens:
    • Uma mensagem de sistema indicando que o assistente é um especialista em histórias para crianças.
    • A mensagem do usuário contendo as instruções detalhadas geradas.
  • Resposta do Modelo: O modelo gera uma história com base nas instruções fornecidas.

Gerenciamento de Erros

Se uma exceção ocorrer durante a chamada para a API OpenAI, ela é capturada e uma mensagem de erro é retornada.

Extração dos Resumos e Tags

Após a geração da história, a próxima etapa é extrair as descrições visuais usando as tags especificadas.

def correct_resume_tags(text):
    # Corrige les balises 'résumé', 'resume', 'titre' et leurs variantes en 'resume' et 'titre' respectivement dans le texte généré.

def extract_summaries(text):
    pattern = r"\[resume\](.*?)\[end_resume\]"
    summaries = re.findall(pattern, text, re.DOTALL)
    return summaries

Correção das Tags

O modelo pode algumas vezes alterar ligeiramente as tags (por exemplo, adicionar acentos). A função correct_resume_tags assegura que todas as tags são uniformes e corretamente formatadas.

Extração dos Resumos

A função extract_summaries usa uma expressão regular para encontrar todas as ocorrências de texto entre as tags [resume] e [end_resume]. Esses resumos são as descrições visuais detalhadas que serão usadas para gerar as imagens.

Geração das Imagens

Uma vez que os resumos sejam extraídos, cada resumo é usado para gerar uma imagem correspondente.

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

Função generate_image

A função generate_image chama a API do modelo de geração de imagens (por exemplo, OpenAI DALL·E) para criar uma imagem a partir do resumo.

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

Geração das Instruções para as Imagens A função generate_image_instructions adapta o resumo para criar um prompt apropriado para a geração de imagens.

def generate_image_instructions(prompt, style, language):
    language_description = get_language_description(language)
    return f"""
    Génère un dessin pour enfant dans le style "{style}" basé sur cette description en langue "{language_description}" : {prompt}.
    La scène doit être purement visuelle, sans aucun texte, et conçue pour éveiller l'émerveillement chez les jeunes spectateurs.
    """
  • Estilo: O estilo especificado pelo usuário (por exemplo, “aquarela”, “desenho animado”) é incluído no prompt para influenciar o resultado da imagem.

  • Língua: A descrição é adaptada à língua escolhida, o que pode ajudar o modelo a entender as nuances culturais.

  • Instruções Claras: Ao especificar que a cena deve ser puramente visual, evita-se que o modelo adicione texto ou elementos indesejáveis na imagem.

Interação com a API OpenAI para Imagens

  • Chamada à API: A função client.images.generate é utilizada para gerar a imagem.

  • Parâmetros Importantes:

    • Prompt: O prompt ajustado é passado para a API.
    • Modelo: O modelo de geração de imagens especificado.
    • Tamanho: O tamanho da imagem (por exemplo, “1024x1024”).
    • Qualidade: A qualidade da imagem (padrão, HD).
    • Formato de Resposta: As imagens são retornadas em base64 para facilitar o armazenamento e a manipulação.

Gestão de Erros

Os erros durante a geração de imagens são capturados e registrados, permitindo diagnosticar os problemas.

Criação do Conteúdo HTML

Depois de gerar as imagens correspondentes aos resumos extraídos, a etapa seguinte consiste em montar o texto da história e as imagens em um formato apresentável para o usuário. Isso é feito criando um conteúdo HTML estruturado que será exibido no site.

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ção em Detalhe

  1. Extração do Título:

    • Usa uma expressão regular para encontrar o texto entre as tags [titulo] e [end_titulo].
    • Remove as tags do texto principal após a extração.
    • Se nenhum título for encontrado, um título padrão é usado.
  2. Inicialização do HTML:

    • O conteúdo HTML começa com as tags <html>, <head> e <body>.
    • Os estilos CSS são incluídos para melhorar a apresentação (tipografia, margens, alinhamentos).
  3. Separação do Texto:

    • O texto é dividido em segmentos utilizando as tags [resumo] e [end_resumo].
    • Os segmentos representam as partes da história sem os resumos.
  4. Montagem:

    • Cada segmento de texto é inserido em um parágrafo <p>.
    • Se a geração de imagens estiver ativada e houver uma imagem correspondente, a imagem é inserida após o parágrafo.
    • As imagens são centralizadas e adaptadas ao tamanho da tela para uma melhor experiência do usuário.
  5. Finalização:

    • As tags de fechamento </body> e </html> são adicionadas para completar o documento HTML.

Por que Esta Abordagem?

  • Alinhamento do Texto e das Imagens: Ao inserir as imagens após os segmentos de texto correspondentes, a história é enriquecida visualmente, o que é particularmente importante para as crianças.

  • Flexibilidade: Se o usuário optar por não gerar imagens, o código lida com isso inserindo apenas o texto.

  • Acessibilidade: Usando tags semânticas e estilos adaptados, o conteúdo é acessível em diferentes dispositivos (computadores, tablets, smartphones).

Upload no S3 e Atualização do Status

Depois de gerar o conteúdo HTML, é necessário torná-lo acessível ao usuário. Isso é feito enviando o arquivo para um bucket S3 configurado para hospedagem de sites web estáticos.

def upload_to_s3(content, bucket_name, content_type="image/jpeg"):
    """
    Télécharge le contenu sur S3 et retourne l'URL publique.
    """
    s3_client = boto3.client("s3")
    timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
    if content_type == "image/jpeg":
        file_name = f"generated_images/{timestamp}.jpg"
        content_to_upload = base64.b64decode(content)
    else:
        file_name = f"generated_content/{timestamp}.html"
        content_to_upload = content.encode("utf-8")
        content_type = "text/html; charset=utf-8"

    try:
        s3_client.put_object(
            Bucket=bucket_name,
            Key=file_name,
            Body=content_to_upload,
            ContentType=content_type,
            ACL='public-read'
        )
        return f"https://{os.environ['CLOUDFRONT_DOMAIN']}/{file_name}"
    except Exception as e:
        logger.error(f"Error uploading content to S3: {e}", exc_info=True)
        raise

Detalhes Técnicos

  • Nomeação dos Arquivos:

    • Os arquivos são nomeados usando um timestamp para garantir a unicidade.
    • As imagens são armazenadas na pasta generated_images/ e os arquivos HTML em generated_content/.
  • Upload para o S3:

    • Utilização do cliente S3 do boto3 para interagir com o serviço.
    • O conteúdo é codificado ou decodificado conforme o tipo (imagem ou texto).
    • O parâmetro ACL='public-read' torna o arquivo acessível publicamente. - Construção do URL:
    • O URL público é construído utilizando o domínio CloudFront configurado, o que permite uma distribuição rápida e segura do conteúdo.
  • Gestão de Exceções:

    • Em caso de erro durante o download, a exceção é registrada e lançada para ser tratada pelo lambda_handler.

Função Principal lambda_handler

A função lambda_handler é o ponto de entrada da função Lambda. Ela orquestra todas as etapas descritas anteriormente.

def lambda_handler(event, context):
    """
    Point d'entrée de la fonction Lambda.
    """
    try:
        # Récupération des données de la requête
        request_id = event.get("requestId")
        body = json.loads(event.get("body", "{}"))
        prompt = body.get("text", "Texte par défaut")
        # Récupération des autres paramètres (modèles, langue, etc.)

        # Mise à jour du statut dans DynamoDB
        update_dynamodb(request_id, "Processing")

        # Génération de l'histoire
        text_data = generate_story(prompt, story_generation_model, model_id, language, api_key)

        # Correction des balises et extraction des résumés
        text_data = correct_resume_tags(text_data)
        summaries = extract_summaries(text_data)

        # Génération des images
        images_urls = []
        if generate_images and summaries:
            images_urls = generate_image_for_each_summary(
                summaries, image_generation_model, bucket_name, seed, style_with_spaces, size, quality, language
            )

        # Création du contenu HTML
        html_content = create_html_with_images(text_data, images_urls, generate_images)

        # Upload du contenu sur S3
        result_url = upload_to_s3(html_content, bucket_name, content_type="text/html")

        # Mise à jour du statut avec le lien du résultat
        update_dynamodb(request_id, "link", result_url)

        # Retour de la réponse HTTP
        return {
            "isBase64Encoded": False,
            "statusCode": 200,
            "headers": {"Content-Type": "application/json"},
            "body": json.dumps({"requestId": request_id, "resultUrl": result_url}),
        }

    except Exception as e:
        logger.error(f"Erreur lors de l'exécution de la fonction lambda: {str(e)}", exc_info=True)
        update_dynamodb(request_id, "Failed")
        return {
            "statusCode": 500,
            "body": json.dumps({"message": "Internal server error"}),
            "headers": {"Content-Type": "application/json"},
        }

Explicação

  • Processamento da Requisição:

    • Recupera as informações necessárias do evento (event) recebido.
    • Os parâmetros da requisição incluem o prompt, os modelos selecionados, o idioma, etc.
  • Atualização do Status:

    • Antes de começar o processamento, o status é atualizado para “Processing” no DynamoDB.
  • Geração da História:

    • Chamada de generate_story com os parâmetros apropriados.
  • Extração e Processamento:

    • As tags são corrigidas e os resumos extraídos para a geração de imagens.
  • Geração de Imagens:

    • Se a geração de imagens estiver ativada, as imagens correspondentes são geradas e os URLs coletados.
  • Criação do Conteúdo HTML:

    • O texto e as imagens são combinados para criar o conteúdo HTML final.
  • Upload para S3:

    • O conteúdo HTML é carregado no S3 e o URL do resultado é obtido.
  • Atualização do Status Final:

    • O status é atualizado para “link” com o URL do resultado no DynamoDB.
  • Retorno da Resposta:

    • A resposta inclui o requestId e o URL do resultado, permitindo ao cliente verificar o status ou acessar diretamente o conteúdo.
  • Gestão de Exceções:

    • Em caso de erro, o status é atualizado para “Failed” e uma resposta HTTP 500 é retornada.

Função Lambda status_checker.py

Visão Geral

A função status_checker.py permite aos usuários verificar o status de sua solicitação de geração de história. Ela interroga o DynamoDB para recuperar o status atual e, se disponível, o URL do resultado.

Análise do Código

import boto3
import json
from botocore.exceptions import ClientError

def lambda_handler(event, context):
    """
    Fonction Lambda pour vérifier le statut d'une demande.
    """
    # Initialisation de DynamoDB
    dynamodb = boto3.resource("dynamodb")
    table = dynamodb.Table("TaskStatus")

    # Définition des en-têtes HTTP
    headers = {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET,OPTIONS",
        "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
    }

    # Récupération du requestId depuis les paramètres de la requête
    query_params = event.get("queryStringParameters")
    if not query_params or "requestId" not in query_params:
        return {
            "statusCode": 400,
            "body": json.dumps({"message": "Missing requestId"}),
            "headers": headers,
        }

    request_id = query_params["requestId"]
    try:
        # Récupération de l'élément dans DynamoDB
        response = table.get_item(Key={"requestId": request_id})
        if "Item" in response:
            item = response["Item"]
            return {
                "statusCode": 200,
                "body": json.dumps(
                    {
                        "status": item.get("status", "Unknown"),
                        "resultUrl": item.get("resultUrl", ""),
                    }
                ),
                "headers": headers,
            }
        else:
            return {
                "statusCode": 404,
                "body": json.dumps({"message": "Request ID not found"}),
                "headers": headers,
            }
    except ClientError as e:
        return {
            "statusCode": 500,
            "body": json.dumps({"message": str(e)}),
            "headers": headers,
        }

Detalhes

  • Recuperação do requestId:

    • O requestId é essencial para identificar a solicitação específica do usuário.
  • Interrogação do DynamoDB:

    • A função tenta recuperar o item correspondente ao requestId.
    • Se o item existir, o status e o resultUrl são extraídos.
  • Construção da Resposta:

    • Se o status estiver disponível, ele é retornado com o URL do resultado.
    • Se o item não for encontrado, um erro 404 é retornado.
    • Em caso de erro durante a interrogação do banco de dados, um erro 500 é retornado com uma mensagem apropriada.
  • Cabeçalhos HTTP:

    • Os cabeçalhos são definidos para permitir requisições CORS a partir do site.

Integração com API Gateway

Configuração dos Endpoints

O API Gateway expõe dois endpoints principais para interagir com as funções Lambda:

  1. /generate-image:

    • Método: POST
    • Descrição: Permite aos usuários iniciar a geração de uma história e, eventualmente, imagens associadas.
    • Integração: Conectado à função Lambda StoryPixAI.py.
  2. /check-status:

    • Método: GET
    • Descrição: Permite aos usuários verificar o status de sua solicitação fornecendo o requestId.
    • Integração: Conectado à função Lambda status_checker.py.

Autenticação com Cognito

Para proteger a API e controlar o acesso aos recursos, integrei o Amazon Cognito.

  • User Pool:

    • Gerencia as informações de credenciais dos usuários.
    • Permite inscrição, login e gerenciamento de usuários.
  • Authorizer:

    • Configurado no API Gateway para verificar os tokens JWT emitidos pelo Cognito.
    • Garante que apenas requisições autenticadas possam acessar os endpoints protegidos. - Integração com API Gateway:
    • Os endpoints /generate-image e /check-status são protegidos pelo autorizador Cognito.
    • Os clientes devem incluir o token de autenticação nos cabeçalhos de suas requisições (Authorization).

Site Estático no S3 e Interação com a API

Estrutura do Site

O site web estático serve como interface de usuário para o aplicativo.

  • index.html:

    • Contém o formulário permitindo aos usuários inserir o prompt, escolher as opções de geração e enviar sua demanda.
    • Inclui os scripts necessários para a interação com a API e a gestão da autenticação.
  • storypixai.js:

    • Contém o código JavaScript para gerenciar as interações com a API.
    • Gerencia a autenticação com Cognito, o envio do formulário, o acompanhamento do status e a exibição dos resultados.

Fluxo de Trabalho do Usuário

  1. Conexão:

    • O usuário se conecta via o formulário de login integrado.
    • As informações são verificadas via Cognito.
  2. Envio do Pedido:

    • O usuário preenche o formulário com o prompt e as opções desejadas.
    • Durante o envio, uma requisição POST é enviada ao endpoint /generate-image com os dados.
  3. Processamento Assíncrono:

    • A API retorna imediatamente um requestId.
    • O processamento da geração é feito em segundo plano.
  4. Verificação do Status:

    • O site web consulta periodicamente o endpoint /check-status fornecendo o requestId.
    • Uma vez que o status “link” é recebido, a URL do resultado é exibida ao usuário.
  5. Exibição do Resultado:

    • O usuário pode clicar no link para acessar a história gerada com as imagens.

Gestão das Requisições e Respostas

  • Requisições Autenticadas:

    • Todas as requisições para a API incluem o token de autenticação.
    • O token é gerenciado pelo SDK Cognito incluído no site web.
  • Gestão dos Status:

    • Os status possíveis são “Processing”, “link”, “Failed”.
    • O site adapta sua interface de acordo com o status recebido (por exemplo, exibição de um spinner, mensagem de erro, link para o resultado).

Interconexões entre os Componentes

Aqui está como os diferentes componentes interagem:

  • Site Web ↔️ API Gateway:

    • O site web envia requisições aos endpoints expostos pelo API Gateway.
    • Os tokens de autenticação são incluídos para proteger as requisições.
  • API Gateway ↔️ Funções Lambda:

    • O API Gateway invoca as funções Lambda correspondentes de acordo com as requisições recebidas.
  • Funções Lambda ↔️ DynamoDB:

    • As funções Lambda StoryPixAI.py e status_checker.py interagem com o DynamoDB para atualizar e recuperar o status dos pedidos.
  • Função Lambda ↔️ S3:

    • A função StoryPixAI.py faz upload das imagens geradas e do conteúdo HTML no S3.
  • CloudFront ↔️ S3:

    • CloudFront é usado para distribuir o conteúdo armazenado no S3 de maneira rápida e segura.
    • As URLs fornecidas aos usuários apontam para o domínio CloudFront.
  • Usuário ↔️ Site Web:

    • O usuário interage com o site web para enviar pedidos e visualizar os resultados.

Exemplo de Resultado nos logs do CloudWatch após uma Requisição

Aqui está um exemplo de resultado de logs após uma requisição para que você possa ver o formato bruto dos dados gerados:

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

Integração Contínua com GitLab CI/CD

Para assegurar um desenvolvimento e um lançamento fluidos do StoryPixAI, eu configurei um pipeline de integração contínua (CI) e de deploy contínuo (CD) usando GitLab CI/CD. Esta configuração automatiza os processos de construção e deploy, garantindo a qualidade e a confiabilidade do código a cada modificação.

Configuração do Pipeline

O pipeline é definido no arquivo .gitlab-ci.yml na raiz do projeto. Aqui está uma visão geral da sua estrutura:

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

variables:
  TERRAFORM_VERSION: "1.5.7-*"
  TF_VAR_region: $AWS_DEFAULT_REGION
``` Esta configuração define as diferentes etapas do pipeline e as variáveis globais utilizadas no processo de CI/CD.

### Principais Jobs

O pipeline inclui vários jobs chave:

1. **Verificação do Terraform**:
   ```yaml
   Verificação Terraform:
     stage: Verificações
     when: manual
     script:
       - /bin/bash -c "source export.sh && terraform_plan"

Este job executa terraform plan para verificar as mudanças de infraestrutura previstas sem aplicá-las.

  1. Deploy do Terraform:

    Deploy do Terraform:
      stage: Deploys
      when: manual
      dependencies:
        - Verificação do Terraform
      script:
        - /bin/bash -c "source export.sh && terraform_apply"
    

    Após a verificação, este job aplica as mudanças de infraestrutura executando terraform apply.

  2. Destruir Terraform:

    Destruir Terraform:
      stage: Destruições
      when: manual
      script:
        - /bin/bash -c "source export.sh && terraform_destroy"
    

    Este job permite destruir a infraestrutura, se necessário, executando terraform destroy.

  3. Gerenciamento de Chaves OpenAI:

    Chave OpenAI - Adicionar:
      stage: Pré-requisitos opcionais
      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 ]; então
            echo "Nenhuma chave encontrada."
            exit 1
          fi      
    
    Chave OpenAI - Remover:
      stage: Destruições
      when: manual
      script:
        - /bin/bash -c "source export.sh && manage_openai_key delete"
    

    Estes jobs gerenciam a adição e remoção segura das chaves da API OpenAI no AWS Parameter Store.

Ambiente de Execução

Cada job é executado em um container Docker baseado no Ubuntu 22.04, com o Terraform e o AWS CLI instalados:

.terraform_template: &terraform_template
  image:
    name: ubuntu:22.04
  before_script:
    - apt-get update
    - apt-get install -y gnupg software-properties-common curl
    - curl -fsSL https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
    - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep 'VERSION_CODENAME' /etc/os-release | cut -d'=' -f2) main" | tee /etc/apt/sources.list.d/hashicorp.list
    - apt-get update
    - apt-get install -y terraform=${TERRAFORM_VERSION} python3-pip bash jq zip
    - pip3 install awscli >> /dev/null

Vantagens deste Abordagem CI/CD

  1. Automatização: Cada modificação no código aciona automaticamente o pipeline, garantindo verificações e deploys consistentes.

  2. Controle Manual: As etapas críticas, como deploy e destruição, estão configuradas em modo manual (when: manual), fornecendo controle adicional antes da execução.

  3. Gerenciamento Seguro de Segredos: A integração com o AWS Parameter Store para o gerenciamento das chaves API garante uma manipulação segura das informações sensíveis.

  4. Flexibilidade: A estrutura em estágios permite uma execução ordenada e lógica das diferentes etapas do pipeline.

  5. Reprodutibilidade: A utilização de um ambiente Docker padrão garante que os builds e testes sejam reprodutíveis em diferentes sistemas.

Esta configuração de CI/CD permite não só automatizar o deploy do StoryPixAI, mas também manter um alto nível de qualidade e confiabilidade ao longo de todo o ciclo de desenvolvimento.

Conclusão

StoryPixAI foi muito mais do que um simples projeto técnico. Foi uma verdadeira aventura no mundo da IA generativa, permitindo-me combinar minha paixão pela tecnologia com o desejo de criar histórias mágicas para meus filhos.

Este projeto me deu a oportunidade de explorar várias facetas da IA, desde a concepção de uma interface de usuário intuitiva até o domínio do prompting, passando pela configuração de uma infraestrutura robusta na nuvem com AWS e Terraform. Cada etapa foi uma fonte de aprendizado, confrontando-me com desafios técnicos estimulantes e obrigando-me a expandir minhas competências em desenvolvimento full-stack e DevOps.

Espero que este post no blog tenha oferecido uma visão dos bastidores desta aventura emocionante.

Pontos Chave

  • Instruções Detalhadas:

    • Prompts claros e estruturados permitem obter resultados consistentes e de alta qualidade dos modelos de IA. - Arquitetura Modular :
    • Cada componente (site web, API Gateway, Lambda, DynamoDB, S3, Cognito) desempenha um papel específico, facilitando a manutenção e a evolução do sistema.
  • Segurança e Escalabilidade :

    • O uso dos serviços gerenciados da AWS garante uma segurança robusta e uma capacidade de se adaptar a uma demanda crescente.

Link do projeto: StoryPixAI

Este documento foi traduzido da versão fr para a língua pt usando o modelo gpt-4o. Para mais informações sobre o processo de tradução, consulte https://gitlab.com/jls42/ai-powered-markdown-translator