StoryPixAI를 통해 내 목표는 사용자가 인공지능 모델이 생성한 이미지로 채워진 어린이 이야기를 생성할 수 있게 하는 인터랙티브 웹 애플리케이션을 만드는 것이었습니다. 이를 위해 Lambda, API Gateway, DynamoDB, S3, Cognito와 같은 여러 AWS 서비스를 사용했습니다. 인프라 코드는 Terraform을 통해 관리되고, 배포는 GitLab CI를 통해 자동화됩니다. 이 블로그 글에서는 흥미진진한 이 프로젝트의 후기를 기술 선택에서 겪은 도전 과제까지 공개합니다.
소개
클라우드 인프라와 DevOps 경험이 풍부한 아키텍트로서, 저는 항상 새로운 기술과 그것이 우리의 일상을 변화시킬 잠재력에 매료되었습니다. 생성형 인공지능의 출현은 제 호기심을 증폭시켰고, 저는 이 흥미진진한 영역에 뛰어들어야 할 필요성을 느꼈습니다.
그렇게 StoryPixAI가 탄생했습니다. 이 프로젝트는 저에게 맞춤형 이야기와 아이들을 위한 마법 같은 삽화를 만드는 AI의 무한한 가능성을 탐구할 수 있는 기회를 주었습니다. 이 프로젝트에서는 풀스택 개발자, 프롬프트 엔지니어, 제품 관리자, UX/UI 디자이너의 역할까지 수행하며 기술에 대한 열정을 가족과 나누는 계기가 되었습니다.
이 블로그 글에서 저는 이 흥미진진한 모험의 기술 선택과 극복한 도전 과제들을 공유할 것입니다.
하지만 그 전에, 맛보기 하나!
StoryPixAI의 잠재력을 엿볼 수 있도록 여러 언어로 자동 생성된 몇 가지 이야기를 소개합니다.
각 이야기는 삽화와 함께 제공되어 어린이들에게 더욱 몰입감을 줍니다:
- 프랑스어: Tom, Zoé et le Royaume d’Argentor
- 영어: La quête magique de la princesse Léa et du voleur d’escargot géant
- 스페인어: La grande aventura de Roger y Coco el payaso
- 독일어: Der verrückte Holzfäller und das magische Abenteuer
- 이탈리아어: La bambina e il unicorno magico nella foresta incantata
- 포르투갈어: A jornada encantada de Lucas e seus amigos
창의성을 위한 AI: 실험의 여정
StoryPixAI와 함께한 여정은 간단한 개념 증명(Proof of Concept)부터 시작되었습니다: OpenAI와 상호작용하여 텍스트를 생성하고 DALL-E로 이미지를 생성하는 Lambda 함수였습니다. 이 첫 성공은 저를 더욱 나아가게 하여 AWS의 Bedrock을 통해 다른 AI 모델들을 탐구하도록 했습니다.
GPT-4와 GPT-4-0 : 민첩한 이야기꾼들
프로젝트 초기부터, OpenAI의 GPT-4는 텍스트 생성을 위한 당연한 선택이었습니다. 자연어의 뉘앙스를 이해하고 일관성 있으며 창의적인 이야기를 생성할 수 있는 능력 덕분에 어린이의 연령대와 관심사에 맞춘 매력적인 이야기를 만들 수 있었습니다. 저는 동화부터 우주 모험, 동물 이야기, 판타지 이야기까지 다양한 글쓰기 스타일을 실험할 수 있었습니다.
GPT-4-0이 출시되었을 때, 이 새로운 모델을 빠르게 StoryPixAI에 통합했습니다. 저는 생성 속도의 증가에 감명받았는데, 이는 생성 대기 시간을 상당히 단축시켰을 뿐만 아니라, 생성된 이야기의 품질이 현저히 개선되어 더욱 유려하고 일관성 있으며 상상력이 풍부한 이야기가 만들어졌기 때문입니다. GPT-4-0은 이로 인해 StoryPixAI의 주요 자산이 되어, 더 빠르고 즐거운 사용자 경험을 제공하게 되었습니다.
DALL-E 3: 기준이 되는 일러스트레이터
텍스트 생성 모델이 만족스러운 결과를 제공했음에도, 이미지 생성 도구 선택은 더 중요한 것으로 드러났습니다. 여러 차례의 시도 끝에 DALL-E 3가 StoryPixAI의 기준 모델로 자리 잡았습니다. 이 도구는 GPT-4가 생성한 이야기와 완벽하게 어울리는 독창적이고 세밀한 일러스트를 제작할 수 있었던 것이 프로젝트의 성공에 결정적인 요인이 되었습니다.
AWS의 Bedrock: 실험의 문을 열다
OpenAI에만 의존하지 않기 위해, AWS의 Bedrock을 사용하여 StoryPixAI에 다른 생성 AI 모델을 쉽게 통합했습니다. 이 플랫폼은 Anthropic의 Claude와 Mistral을 텍스트 생성에, Stable Diffusion을 이미지 생성에 테스트할 수 있게 해주었습니다.
이 모델들이 흥미로운 결과를 제공했음에도, 최종적으로는 GPT-4와 GPT-4-0을 텍스트 생성의 속도와 품질 때문에, 그리고 DALL-E 3를 이야기와 완벽하게 어울리는 일러스트를 제작할 수 있는 능력 때문에 집중하기로 결정했습니다. 이미지 생성을 위한 프롬프트는 대부분 텍스트 모델 자체에 의해 만들어지므로, 이야기와 일러스트 사이의 일관성이 보장된다는 점이 중요합니다.
비동기 API와 DynamoDB의 도전 과제
PoC가 검증된 후, StoryPixAI를 웹 인터페이스를 통해 접근할 수 있도록 하는 API를 만들기 시작했습니다. 이 단계에서 첫 번째 주요 도전과제인 API Gateway의 타임아웃 제한에 직면했습니다. 이 제약을 극복하고 더 길고 복잡한 이야기 생성을 가능하게 하기 위해 비동기 아키텍처를 구축해야만 했습니다.
이때 Amazon DynamoDB가 등장했습니다. 이 NoSQL 데이터베이스를 사용하여 진행 중인 이야기 생성 작업과 완료된 결과를 저장했습니다. 이 접근 방식을 통해 API는 사용자에게 즉각적인 응답을 제공할 수 있었고, 사용자는 자신의 요청 상태를 확인하고 생성된 이야기를 준비된 단계에서 받아볼 수 있었습니다.
CORS와 사용자 인터페이스: 극복해야 할 장애물
웹 인터페이스 구축 또한 많은 도전을 안겨주었습니다. API와 통신할 수 있도록 하기 위해 CORS(Cross-Origin Resource Sharing)의 미묘한 점들을 익혀야 했습니다. 또한 사용자가 AI 모델과 이미지 스타일을 선택할 수 있는 기능을 추가하여 사용자 경험을 향상시키는 데 시간을 투자했습니다.
프롬프트 작성: 마스터해야 할 예술
StoryPixAI 개발 내내, 저는 올바른 요청을 작성하여 AI 모델을 안내하는 프롬프트 작성 기술을 계속해서 개선해 나갔습니다. 사용된 모델, 이야기의 매개변수, 사용자의 기대치에 따라 프롬프트를 조정하는 법을 배웠습니다. 이 단계는 고품질 결과를 얻고 만족스러운 사용자 경험을 보장하는 데 중요한 역할을 했습니다.
AWS에서의 견고하고 자동화된 인프라
StoryPixAI는 Amazon Web Services (AWS)에서 호스팅되는 서버리스 인프라를 기반으로 하며, 유연성, 확장성 및 비용 최적화의 이상적인 조합을 제공합니다. 이 아키텍처는 Terraform과 GitLab CI/CD를 통해 완전히 자동화되어 애플리케이션의 빠르고 안정적인 배포를 가능하게 합니다.
StoryPixAI의 중심에 있는 AWS 서비스
StoryPixAI의 아키텍처는 다음과 같은 AWS 서비스를 중심으로 구성됩니다: * Amazon S3 (Simple Storage Service) : 웹사이트의 정적 파일(HTML, CSS, JavaScript)과 생성된 이야기 및 관련 삽화를 저장합니다.
- Amazon CloudFront : 콘텐츠 전송 네트워크(CDN)로, 전 세계 StoryPixAI 사용자에게 콘텐츠 배포를 가속화하며, 지리적으로 가까운 위치에 캐시합니다.
- Amazon API Gateway : 애플리케이션의 보안 게이트웨이입니다. 사용자 요청을 관리하고 Amazon Cognito를 통해 인증한 후 적절한 Lambda 함수로 라우팅합니다.
- AWS Lambda : StoryPixAI의 서버리스 기능으로, 이야기 생성, 이미지 생성, 비동기 작업 관리 및 DynamoDB 및 기타 AWS 서비스와의 상호 작용을 조율합니다.
- Amazon DynamoDB : 애플리케이션 운영에 필수적인 정보를 저장하는 유연하고 성능 좋은 NoSQL 데이터베이스입니다.
- Amazon Cognito : 사용자가 로그인하고 권한을 제어할 수 있도록 하여 애플리케이션을 보안하는 신원 및 액세스 관리 서비스입니다. 인증된 사용자만 스토리 생성 기능에 접근할 수 있게 보장합니다.
- Amazon Bedrock : Anthropic (Claude) 및 Stability AI (Stable Diffusion)과 같은 다양한 공급업체의 생성형 AI 모델에 쉽게 접근하고 사용할 수 있게 하는 플랫폼입니다. Bedrock은 이러한 모델을 애플리케이션에 쉽고 인프라 관리 없이 통합할 수 있게 합니다.
- 기타 AWS 서비스 : StoryPixAI는 또한 IAM(Identity and Access Management)과 같은 다른 AWS 서비스를 사용하여 자원 접근 권한을 세밀하게 관리하고, CloudWatch를 사용하여 모니터링 및 로깅을 수행합니다 (디버깅 및 성능 분석에 중요). 민감한 정보(예: API 키)는 **Systems Manager Parameter Store (SSM Parameter Store)**에 저장하여 애플리케이션의 보안을 보장합니다.
Terraform : 인프라 자동화 도구
이 복잡한 인프라를 관리하기 위해, 나는 선언형 코드 형태로 인프라를 설명할 수 있는 Infrastructure as Code (IaC) 도구인 Terraform을 선택했습니다. Terraform을 통해 AWS 리소스의 생성, 수정 및 파괴를 자동화하여 일관성 있고 재현 가능하며 관리하기 쉬운 환경을 보장했습니다. 이는 배포 과정을 크게 단순화하고 인간 오류의 위험을 줄여줍니다.
GitLab CI/CD : 원활한 배포
StoryPixAI의 지속적이고 신뢰할 수 있는 배포를 보장하기 위해, 나는 GitLab의 CI/CD(지속적 통합 / 지속적 배포) 파이프라인을 구축했습니다. 이 파이프라인은 소스 코드가 변경될 때마다 테스트, 빌드 및 배포를 자동화하여 오류를 신속히 감지하고 수정하며 새로운 기능을 자신있게 배포할 수 있게 합니다. 이 접근 방식은 애플리케이션이 항상 최신 상태로 유지되며 가동 중지 시간을 최소화합니다.
AWS, Terraform 및 GitLab CI/CD의 조합을 통해 나는 견고하고 확장 가능하며 유지보수하기 용이한 인프라를 구축할 수 있었으며, 프로젝트의 창의적 측면과 사용자 경험 향상에 더 많은 시간을 할애할 수 있게 되었습니다.
StoryPixAI 프로젝트의 전체 아키텍처
코드로 들어가기 전에, 애플리케이션 아키텍처의 개요를 소개합니다:
- S3에 호스팅된 정적 사이트 : S3 버킷에 호스팅된 정적 웹사이트로, 클라우드프론트를 통해 글로벌 배포가 가능합니다.
- API Gateway : 이야기 생성 및 상태 확인을 위한 엔드포인트를 노출합니다.
- Lambda 함수 :
StoryPixAI.py
: 이야기와 관련 이미지를 생성합니다.status_checker.py
: DynamoDB에서 생성 상태를 확인합니다.
- DynamoDB : 생성 작업의 상태를 저장합니다.
- S3 : 생성된 이미지와 결과 HTML 페이지를 저장합니다.
- Cognito : API를 보호하기 위해 사용자 인증을 관리합니다.
Lambda 함수 StoryPixAI.py
개요
StoryPixAI.py
함수는 애플리케이션의 핵심입니다. 이 함수는 다음과 같은 작업을 담당합니다:
- 사용자 프롬프트를 바탕으로 이야기를 생성합니다.
- 이야기를 생성하기 위한 지침을 자세히 만듭니다.
- 이야기의 각 장면이나 주요 요소에 대한 요약을 추출합니다.
- 이러한 요약에 따른 이미지를 생성합니다.
- 텍스트와 이미지를 HTML 페이지로 결합합니다.
- 결과를 S3에 저장하고 DynamoDB에서 상태를 업데이트합니다.
코드 분해
Imports 및 초기 설정
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)
이 섹션에서는 필요한 모듈을 가져오고, 디버깅을 위해 로거를 설정하며, AWS Systems Manager Parameter Store(SSM)에 저장된 OpenAI API 키를 가져옵니다. 이는 키를 안전하게 관리하고 코드 내에서 명확한 텍스트로 저장하지 않기 위함입니다.
유틸리티 함수들
태그 수정
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):
# Extrait les résumés du texte en utilisant des balises spécifiques.
이 함수는 정규식을 사용하여 [resume]
및 [end_resume]
로 구분된 텍스트 섹션을 추출합니다. 이러한 요약은 이미지 생성 프롬프트로 사용됩니다.
이미지 지침 생성
def generate_image_instructions(prompt, style, language):
# Génère les instructions pour la création d'images.
이 함수는 프롬프트를 스타일과 언어를 포함하여 이미지 생성 모델을 안내할 수 있도록 형식화합니다.
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.
이 함수는 TaskStatus
테이블을 업데이트하여 생성 상태를 추적합니다. 이는 status_checker.py
함수에 필수적입니다.
generate_story_instructions
에 대한 심층 분석
generate_story_instructions
함수는 프로젝트의 핵심입니다. 이 함수는 AI 모델에게 이야기를 생성하도록 유도하는 자세한 지침 세트를 생성합니다.
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}"
"""
프롬프트 구성
이 프롬프트는 AI 모델에게 일관되고, 교육적이고, 어린이에게 적합한 이야기를 생성하는 데 필요한 모든 정보를 제공합니다.
- 언어 :
language_description
매개변수는 이야기의 언어를 지정하여 생성된 텍스트가 원하는 언어로 나오도록 합니다. - 주제 : 사용자 프롬프트가 지침에 통합되어 이야기의 기초가 됩니다.
- 길이 : 이야기가 1000에서 1500단어 사이로 유지되도록 범위를 지정합니다.
- 핵심 요소 : 지침에는 모험, 마법, 중요한 교육적 가치를 포함하도록 권장하고 있습니다.
지침의 세부 사항
모델에게 제공되는 지침은 정확하게 생성하도록 매우 상세합니다.
여기 프롬프트의 다양한 부분에 대한 분석이 있습니다:
- 이야기 구조 : 이야기를 흥미로운 시작, 풍부한 사건 전개, 만족스러운 결말로 구조화하도록 모델에게 요청합니다.
- 시각적 묘사 : 이야기는 어린이의 상상력을 자극하기 위해 시각적 묘사가 풍부해야 합니다.
- 캐릭터 : 개성 있는 매력적인 캐릭터 개발을 권장합니다.
- 특정 태그 :
[titre]... [end_titre]
및[resume]... [end_resume]
와 같은 태그를 사용하여 제목과 시각적 묘사를 구분합니다. - 환상 요소 : 이야기를 더 매력적으로 만들기 위해 마법 또는 환상적인 요소를 포함하도록 모델에게 권장합니다.
- 교육적 가치 : 이야기는 중요한 가치를 가르쳐야 합니다.
태그의 역할
태그는 생성된 텍스트를 후속 처리하는 데 중요한 역할을 합니다.
-
[titre]… [end_titre] : 이야기의 제목을 둘러싸고 있습니다. 이렇게 하면 쉽게 추출하여 사용자 인터페이스에 적절히 표시할 수 있습니다.
-
[resume]… [end_resume]: 이야기의 주요 장면에 대한 시각적 설명을 감쌀 수 있습니다. 이러한 요약은 이미지 생성을 위한 프롬프트로 사용됩니다.
생성 후 처리
AI 모델이 이러한 지침을 따라 이야기를 생성한 후, 코드는 다음 단계를 수행합니다:
-
태그 수정:
correct_resume_tags
함수는 모든 태그가 추출을 위해 올바르게 형식화되었는지 확인합니다. -
요약 추출:
extract_summaries
함수는[resume]
및[end_resume]
태그를 사용하여 시각적 설명을 추출합니다. -
이미지 생성: 각 요약은 이미지 생성을 위해
generate_image
함수에 전달됩니다. -
HTML 콘텐츠 생성: 이야기의 텍스트와 생성된 이미지를 결합하여 전체 HTML 페이지를 생성합니다.
생성에 미치는 영향
이러한 세부 지침을 제공함으로써 모델은 다음을 수행하도록 안내됩니다:
-
형식 준수: 지정된 태그를 사용함으로써 모델은 자동화된 처리를 용이하게 하는 구조화된 텍스트를 생성합니다.
-
적합한 콘텐츠 생성: 언어, 스타일 및 주제에 대한 제약은 이야기가 대상 청중에게 적절하도록 보장합니다.
-
이미지 생성을 용이하게 함: 정확한 시각적 설명을 추출함으로써 고품질의 이미지 생성 프롬프트를 얻을 수 있습니다.
모델의 태그 처리
모델은 태그를 번역하거나 수정하지 않도록 명확히 지시받습니다. 이는 태그가 변경 없이 유지되어 후처리에 사용될 수 있도록 하기 위해 필수적입니다. 지침은 모델이 모든 텍스트를 패러프레이즈하거나 번역하려는 시도를 방지하기 위해 이 점을 강조합니다.
이야기 생성
generate_story_instructions
함수가 세부 지침을 생성한 후, 다음 단계는 AI 모델에 이 지침을 전달하여 이야기를 생성하는 것입니다.
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
OpenAI 모델과의 상호작용
-
OpenAI 클라이언트: 이전에 회수한 API 키를 사용하여 OpenAI 클라이언트를 인스턴스화합니다.
-
프롬팅: 모델은 다음 메시지 시리즈를 수신합니다:
- 어린이 이야기 전문가임을 나타내는 시스템 메시지.
- 생성된 상세 지침을 포함하는 사용자 메시지.
-
모델의 응답: 모델은 제공된 지침에 기반하여 이야기를 생성합니다.
오류 처리
OpenAI API 호출 중 예외가 발생하면 캡처되어 오류 메시지가 반환됩니다.
요약 및 태그 추출
이야기가 생성된 후, 다음 단계는 지정된 태그를 사용하여 시각적 설명을 추출하는 것입니다.
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
태그 수정
모델이 때때로 태그를 약간 변경할 수 있습니다 (예: 악센트 추가). correct_resume_tags
함수는 모든 태그가 일관되게 올바르게 형식화되었는지 확인합니다.
요약 추출
extract_summaries
함수는 정규 표현식을 사용하여 [resume]
및 [end_resume]
태그 사이의 모든 텍스트 발생을 찾습니다. 이러한 요약은 이미지 생성을 위한 상세한 시각적 설명입니다.
이미지 생성
요약이 추출된 후, 각 요약은 해당 이미지를 생성하는 데 사용됩니다.
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
generate_image
함수
generate_image
함수는 모델의 이미지 생성 API(OpenAI DALL·E 등)를 호출하여 요약에서 이미지를 생성합니다.
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
이미지에 대한 지침 생성 generate_image_instructions
함수는 요약을 조정하여 이미지 생성을 위한 적절한 프롬프트를 만듭니다.
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.
"""
-
스타일: 사용자가 지정한 스타일(예: “수채화”, “만화”)이 프롬프트에 포함되어 이미지의 렌더링에 영향을 미칩니다.
-
언어: 설명은 선택한 언어에 맞게 조정되며, 이는 모델이 문화적 뉘앙스를 이해하는 데 도움이 될 수 있습니다.
-
명확한 지침: 장면이 순수하게 시각적이어야 한다고 명시하면, 모델이 이미지에 텍스트나 원치 않는 요소를 추가하는 것을 방지합니다.
OpenAI 이미지 API와의 상호작용
-
API 호출:
client.images.generate
함수가 이미지를 생성하기 위해 사용됩니다. -
중요한 매개변수:
- 프롬프트: 조정된 프롬프트가 API에 전달됩니다.
- 모델: 지정된 이미지 생성 모델.
- 크기: 이미지 크기(예: “1024x1024”).
- 품질: 이미지 품질(표준, HD).
- 응답 형식: 이미지는 저장 및 조작을 용이하게 하기 위해 base64 형식으로 반환됩니다.
오류 관리
이미지 생성 중 발생한 오류는 포착 및 기록되어 문제 진단에 활용됩니다.
HTML 콘텐츠 생성
요약에서 추출한 이미지 생성 후, 다음 단계는 이야기의 텍스트와 이미지를 사용자에게 적합한 형식으로 조립하는 것입니다. 이를 위해 구조화된 HTML 콘텐츠를 생성하여 웹사이트에 표시합니다.
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
상세 설명
-
제목 추출:
- 정규 표현식을 사용하여
[titre]
및[end_titre]
태그 사이의 텍스트를 찾습니다. - 추출 후 기본 텍스트에서 태그를 제거합니다.
- 제목을 찾지 못한 경우, 기본 제목이 사용됩니다.
- 정규 표현식을 사용하여
-
HTML 초기화:
- HTML 콘텐츠는
<html>
,<head>
,<body>
태그로 시작됩니다. - CSS 스타일이 포함되어 프레젠테이션을 개선합니다 (타이포, 여백, 정렬).
- HTML 콘텐츠는
-
텍스트 분할:
- 텍스트는
[resume]
및[end_resume]
태그를 사용하여 여러 세그먼트로 분할됩니다. - 세그먼트는 요약 없는 이야기 부분을 나타냅니다.
- 텍스트는
-
조립:
- 각 텍스트 세그먼트는
<p>
단락에 삽입됩니다. - 이미지 생성을 활성화하고 해당 이미지가 있는 경우, 이미지가 단락 뒤에 삽입됩니다.
- 이미지는 중앙에 배치되며 사용자 경험을 향상시키기 위해 화면 크기에 맞게 조정됩니다.
- 각 텍스트 세그먼트는
-
마무리:
- 문서를 완성하기 위해
</body>
및</html>
태그가 추가됩니다.
- 문서를 완성하기 위해
이 접근 방식의 이유
-
텍스트와 이미지 정렬: 해당 텍스트 세그먼트 뒤에 이미지를 삽입함으로써, 특히 어린이에게 시각적으로 풍부한 이야기를 제공합니다.
-
유연성: 사용자가 이미지를 생성하지 않기로 선택한 경우, 코드는 텍스트만 삽입하여 이 경우를 처리합니다.
-
접근성: 의미있는 태그와 적절한 스타일을 사용하여, 콘텐츠는 다양한 장치(컴퓨터, 태블릿, 스마트폰)에서도 접근할 수 있습니다.
S3 업로드 및 상태 업데이트
HTML 콘텐츠가 생성된 후, 이를 사용자가 접근할 수 있도록 하는 과정이 필요합니다. 이는 정적 웹사이트 호스팅을 위해 설정된 S3 버킷에 파일을 업로드함으로써 이루어집니다.
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
기술적 세부사항
-
파일 명명:
- 타임스탬프를 사용하여 고유성을 보장하는 파일 명명.
- 이미지는
generated_images/
폴더에, HTML 파일은generated_content/
폴더에 저장됩니다.
-
S3에 업로드:
boto3
의 S3 클라이언트를 사용하여 서비스와 상호작용합니다.- 콘텐츠는 유형(이미지 또는 텍스트)에 따라 인코딩 또는 디코딩됩니다.
ACL='public-read'
매개변수는 파일을 공개적으로 접근 가능하게 합니다. - URL 구조:- 공개 URL은 구성된 CloudFront 도메인을 사용하여 구축되며, 이는 콘텐츠의 빠르고 안전한 배포를 가능하게 합니다.
-
예외 처리:
- 다운로드 중 오류가 발생하면 예외가 로깅되고
lambda_handler
에 의해 처리되도록 다시 발생됩니다.
- 다운로드 중 오류가 발생하면 예외가 로깅되고
주요 함수 lambda_handler
lambda_handler
함수는 Lambda 함수의 진입점입니다. 이전에 설명된 모든 단계를 조정합니다.
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"},
}
설명
-
요청 처리:
- 수신된 이벤트에서 필요한 정보를 가져옵니다.
- 요청 매개변수에는 프롬프트, 선택된 모델, 언어 등이 포함됩니다.
-
상태 업데이트:
- 처리를 시작하기 전에 상태가 DynamoDB에서 “Processing"으로 업데이트됩니다.
-
스토리 생성:
- 적절한 매개변수로
generate_story
를 호출합니다.
- 적절한 매개변수로
-
태그 수정 및 처리:
- 태그가 수정되고 이미지 생성을 위한 요약이 추출됩니다.
-
이미지 생성:
- 이미지 생성이 활성화된 경우, 해당 이미지가 생성되고 URL이 수집됩니다.
-
HTML 콘텐츠 생성:
- 텍스트와 이미지를 결합하여 최종 HTML 콘텐츠를 만듭니다.
-
S3에 업로드:
- HTML 콘텐츠가 S3에 업로드되고 결과 URL이 얻어집니다.
-
최종 상태 업데이트:
- 상태가 DynamoDB에서 결과 URL과 함께 “link"로 업데이트됩니다.
-
응답 반환:
- 응답에는
requestId
와 결과 URL이 포함되어 있어 클라이언트가 상태를 확인하거나 콘텐츠에 직접 접근할 수 있습니다.
- 응답에는
-
예외 처리:
- 오류가 발생하면 상태가 “Failed"로 업데이트되고 HTTP 500 응답이 반환됩니다.
Lambda 함수 status_checker.py
개요
status_checker.py
함수는 사용자가 스토리 생성 요청의 상태를 확인할 수 있도록 합니다. 이는 DynamoDB를 쿼리하여 현재 상태와 사용 가능한 경우 결과 URL을 가져옵니다.
코드 분석
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,
}
세부사항
-
requestId
가져오기:requestId
는 사용자의 특정 요청을 식별하는 데 필수적입니다.
-
DynamoDB 쿼리:
- 함수는
requestId
에 해당하는 항목을 가져오려고 시도합니다. - 항목이 존재하는 경우 상태와
resultUrl
이 추출됩니다.
- 함수는
-
응답 생성:
- 상태가 사용 가능한 경우, 이는 결과 URL과 함께 반환됩니다.
- 항목이 발견되지 않으면 404 오류가 반환됩니다.
- 데이터베이스 쿼리 중 오류가 발생하면 적절한 메시지와 함께 500 오류가 반환됩니다.
-
HTTP 헤더:
- 웹사이트에서 CORS 요청을 허용하기 위해 헤더가 정의됩니다.
API Gateway와의 통합
엔드포인트 구성
API Gateway는 Lambda 함수와 상호작용하기 위해 두 개의 주요 엔드포인트를 노출합니다:
-
/generate-image
:- 메서드:
POST
- 설명: 사용자가 스토리 생성과 선택적으로 관련 이미지를 생성할 수 있도록 합니다.
- 통합: Lambda 함수
StoryPixAI.py
에 연결됩니다.
- 메서드:
-
/check-status
:- 메서드:
GET
- 설명: 사용자가
requestId
를 제공하여 요청 상태를 확인할 수 있도록 합니다. - 통합: Lambda 함수
status_checker.py
에 연결됩니다.
- 메서드:
Cognito를 통한 인증
API를 보호하고 리소스 접근을 제어하기 위해 Amazon Cognito를 통합했습니다.
-
User Pool:
- 사용자 자격 증명 정보를 관리합니다.
- 사용자 등록, 로그인 및 관리를 허용합니다.
-
Authorizer:
- API Gateway에서 Cognito가 발행한 JWT 토큰을 확인하도록 구성됩니다.
- 인증된 요청만 보호된 엔드포인트에 접근할 수 있도록 보장합니다. - API Gateway 통합:
- 엔드포인트
/generate-image
및/check-status
는 Cognito authorizer에 의해 보호됩니다. - 클라이언트는 요청 헤더 (
Authorization
)에 인증 토큰을 포함해야 합니다.
S3의 정적 사이트 및 API와의 상호 작용
사이트 구조
정적 웹사이트는 애플리케이션의 사용자 인터페이스 역할을 합니다.
-
index.html
:- 사용자가 프롬프트를 입력하고 생성 옵션을 선택하며 요청을 제출할 수 있는 양식을 포함합니다.
- API와의 상호 작용 및 인증 관리를 위한 스크립트를 포함합니다.
-
storypixai.js
:- API와 상호 작용을 관리하는 JavaScript 코드가 포함되어 있습니다.
- Cognito를 통한 인증, 양식 제출, 상태 추적 및 결과 표시를 관리합니다.
사용자 작업 흐름
-
로그인:
- 사용자는 통합 로그인 양식을 통해 로그인합니다.
- 정보는 Cognito를 통해 확인됩니다.
-
요청 제출:
- 사용자는 프롬프트 및 원하는 옵션으로 양식을 작성합니다.
- 제출 시,
POST
요청이 데이터를 포함하여/generate-image
엔드포인트로 전송됩니다.
-
비동기 처리:
- API는 즉시
requestId
를 반환합니다. - 생성 처리는 백그라운드에서 수행됩니다.
- API는 즉시
-
상태 확인:
- 웹사이트는 주기적으로
requestId
를 제공하여/check-status
엔드포인트를 조회합니다. - “link” 상태를 수신하면 결과 URL이 사용자에게 표시됩니다.
- 웹사이트는 주기적으로
-
결과 표시:
- 사용자는 링크를 클릭하여 생성된 이미지와 함께 이야기를 볼 수 있습니다.
요청 및 응답 관리
-
인증된 요청:
- API로의 모든 요청에는 인증 토큰이 포함됩니다.
- 토큰은 웹사이트에 포함된 Cognito SDK에 의해 관리됩니다.
-
상태 관리:
- 가능한 상태는 “Processing”, “link”, “Failed"입니다.
- 웹사이트는 수신된 상태에 따라 인터페이스를 조정합니다 (예: 스피너 표시, 오류 메시지, 결과 링크).
구성 요소 간의 상호 연결
다음은 다양한 구성 요소들이 상호 작용하는 방법입니다:
-
웹사이트 ↔️ API Gateway:
- 웹사이트는 API Gateway에 의해 노출된 엔드포인트로 요청을 보냅니다.
- 인증 토큰이 포함되어 요청을 보안합니다.
-
API Gateway ↔️ Lambda 함수:
- API Gateway는 수신된 요청에 따라 Lambda 함수를 호출합니다.
-
Lambda 함수 ↔️ DynamoDB:
- Lambda 함수
StoryPixAI.py
및status_checker.py
는 요청 상태를 업데이트하고 검색하기 위해 DynamoDB와 상호 작용합니다.
- Lambda 함수
-
Lambda 함수 ↔️ S3:
StoryPixAI.py
함수는 생성된 이미지와 HTML 콘텐츠를 S3에 업로드합니다.
-
CloudFront ↔️ S3:
- CloudFront는 S3에 저장된 콘텐츠를 빠르고 안전하게 배포하는데 사용됩니다.
- 사용자에게 제공되는 URL은 CloudFront 도메인을 가리킵니다.
-
사용자 ↔️ 웹사이트:
- 사용자는 웹사이트를 통해 요청을 제출하고 결과를 시청합니다.
요청 호출 후 CLoudwatch 로그 예시
다음은 요청 호출 후 생성된 로그의 예시입니다. 생성된 데이터의 원본 형식을 볼 수 있습니다:
[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.
GitLab CI/CD를 사용한 지속적 통합
StoryPixAI의 원활한 개발 및 배포를 위해 GitLab CI/CD를 사용하여 지속적 통합(CI) 및 지속적 배포(CD) 파이프라인을 설정했습니다. 이 설정은 빌드 및 배포 프로세스를 자동화하여 코드의 품질과 안정성을 각 수정마다 보장합니다.
파이프라인 구성
파이프라인은 프로젝트의 루트에 있는 .gitlab-ci.yml
파일에 정의되어 있습니다. 구조는 다음과 같습니다:
stages:
- Pré-requis optionel
- Vérifications
- Déploiements
- Management
- Suppressions
variables:
TERRAFORM_VERSION: "1.5.7-*"
TF_VAR_region: $AWS_DEFAULT_REGION
``` 이 구성은 파이프라인의 다양한 단계와 CI/CD 프로세스에서 사용되는 전역 변수를 정의합니다.
### 주요 작업
파이프라인은 여러 주요 작업을 포함합니다:
1. **Terraform 검증**:
```yaml
Terraform 검증:
stage: 검증
when: manual
script:
- /bin/bash -c "source export.sh && terraform_plan"
```
이 작업은 `terraform plan`을 실행하여 인프라 변경 사항을 적용하지 않고 검증합니다.
2. **Terraform 배포**:
```yaml
Terraform 배포:
stage: 배포
when: manual
dependencies:
- Terraform 검증
script:
- /bin/bash -c "source export.sh && terraform_apply"
```
검증 후, 이 작업은 `terraform apply`를 실행하여 인프라 변경 사항을 적용합니다.
3. **Terraform 삭제**:
```yaml
Terraform 삭제:
stage: 삭제
when: manual
script:
- /bin/bash -c "source export.sh && terraform_destroy"
```
이 작업은 필요시 `terraform destroy`를 실행하여 인프라를 삭제할 수 있습니다.
4. **OpenAI 키 관리**:
```yaml
OpenAI 키 추가:
stage: 사전 요구 사항
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 "키를 찾을 수 없습니다."
exit 1
fi
OpenAI 키 삭제:
stage: 삭제
when: manual
script:
- /bin/bash -c "source export.sh && manage_openai_key delete"
```
이 작업들은 AWS Parameter Store에서 OpenAI API 키를 안전하게 추가 및 삭제합니다.
### 실행 환경
각 작업은 Ubuntu 22.04를 기반으로 한 Docker 컨테이너에서 실행되며, Terraform과 AWS CLI가 설치되어 있습니다:
```yaml
.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
이 CI/CD 접근 방식의 장점
-
자동화: 코드 변경 사항마다 자동으로 파이프라인이 트리거되어 일관된 검증 및 배포를 보장합니다.
-
수동 제어: 배포 및 삭제와 같은 중요한 단계는 수동 모드(
when: manual
)로 구성되어 실행 전에 추가 제어를 제공합니다. -
비밀 관리 보안: AWS Parameter Store와의 통합으로 API 키의 안전한 관리를 보장합니다.
-
유연성: 단계별 구조로 다양한 파이프라인 단계를 논리적 순서대로 실행할 수 있습니다.
-
재현성: 표준화된 Docker 환경을 사용하여 빌드 및 테스트가 다양한 시스템에서 재현 가능함을 보장합니다.
이 CI/CD 구성을 통해 StoryPixAI의 배포를 자동화할 뿐만 아니라 개발 주기 내내 높은 수준의 품질과 신뢰성을 유지할 수 있습니다.
결론
StoryPixAI는 단순한 기술 프로젝트 이상의 것이었습니다. 이는 제가 기술에 대한 열정을 자녀들을 위한 마법 같은 이야기를 만드는 것과 결합한 진정한 AI 생성 모험이었습니다.
이 프로젝트는 직관적인 사용자 인터페이스 설계부터 프롬프트 작성 숙달, AWS 및 Terraform을 사용한 견고한 클라우드 인프라 설정에 이르기까지 AI의 다양한 측면을 탐구할 수 있는 기회를 제공했습니다. 각 단계가 학습의 원천이 되었고, 다양한 기술적 도전에 직면하며 풀스택 개발과 DevOps 분야의 역량을 확장할 수 있었습니다.
이 블로그 글이 이 흥미로운 모험의 비하인드 스토리를 제공하길 바랍니다.
주요 포인트
-
상세한 지침:
- 명확하고 구조화된 프롬프트는 AI 모델로부터 일관되고 높은 품질의 결과를 얻을 수 있게 합니다. - 모듈형 아키텍처:
- 각 구성 요소(웹사이트, API 게이트웨이, Lambda, DynamoDB, S3, Cognito)는 특정 역할을 수행하여 시스템 유지 보수와 발전을 용이하게 합니다.
-
보안 및 확장성:
- AWS 관리형 서비스를 사용하면 강력한 보안과 증가하는 수요에 대응할 수 있는 능력을 보장합니다.
프로젝트 링크: StoryPixAI
이 문서는 gpt-4o 모델을 사용하여 fr 버전에서 ko 언어로 번역되었습니다. 번역 과정에 대한 자세한 내용은 https://gitlab.com/jls42/ai-powered-markdown-translator를 참조하십시오.