StoryPixAIの目標は、ユーザーが生成AIモデルによって生成された画像で強化された子供のための物語を生成することを可能にするインタラクティブなWebアプリケーションを作成することでした。これを実現するために、Lambda、API Gateway、DynamoDB、S3、および認証のためにCognitoといった複数のAWSサービスを使用しました。インフラストラクチャのコードはTerraformで管理され、デプロイはGitLab CI経由で自動化されています。このブログ記事では、このエキサイティングなプロジェクトの裏側、技術的選択、および直面した課題について紹介します。

はじめに

クラウドインフラストラクチャとDevOpsの経験豊富なアーキテクトとして、常に新しい技術とそれが日常生活を変える可能性について魅了されてきました。生成AIの出現は私の好奇心を刺激し、この活気ある世界に飛び込む必要性を感じました。

こうしてStoryPixAIが誕生し、個人的なプロジェクトとして、子供のためのパーソナライズされた物語や魔法のようなイラストを作成するための無限の可能性を探求できました。このプロジェクトを通じて、フルスタック開発者、プロンプトエンジニア、プロダクトオーナー、さらにはUX/UIデザイナーの役割を体験し、技術への情熱を近しい人々と共有する機会となりました。

このブログ記事では、このエキサイティングな冒険の中で行った技術的な選択と直面した課題を共有します。

まずは、前菜から!

StoryPixAIの可能性の一端を感じていただくために、いくつかの自動生成された物語を複数の言語でご紹介します。
各物語にはイラストが添えられており、子供たちにとって物語をさらに没入感のあるものにしています:

創造性に奉仕するAI:実験の旅

StoryPixAIとの冒険は、テキストを生成するためにOpenAIと画像を生成するためにDALL-Eと対話するLambda関数というシンプルな概念実証(PoC)から始まりました。この最初の成功により、さらに進んでAWSのBedrock経由で他のAIモデルを探求することに踏み切りました。

GPT-4とGPT-4-o:俊敏なストーリーテラー

プロジェクトの開始当初から、テキスト生成のために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をWebインターフェースでアクセス可能にするAPIの構築に取り組みました。この段階で最初の大きな課題に直面しました:API Gatewayのタイムアウト制限です。この制約を回避し、より長く複雑なストーリーの生成を可能にするため、非同期アーキテクチャを導入しました。

ここでAmazon DynamoDBが登場します。このNoSQLデータベースを使用して、生成中のストーリータスクや、完了後の結果を保存しました。このアプローチのおかげで、APIはユーザーに即時の応答を返し、その後ユーザーはリクエストの状態を確認し、生成されたストーリーを準備ができ次第取得できるようになります。

CORSとユーザーインターフェース:克服すべき障害

Webインターフェースの構築もまた課題を生みました。フロントエンドがAPIと通信するためにCORS(クロスオリジンリソース共有)の微妙な点に慣れる必要がありました。また、ユーザー体験を改善するために、AIモデルや画像スタイルの選択などの機能追加に時間を費やしました。

プロンプト作成:重要な技術をマスターする

StoryPixAIの開発を通じて、AIモデルを導く最適なリクエストを作成する技術、すなわちプロンプト作成のスキルを磨きました。使用するモデル、ストーリーパラメータ、ユーザーの期待に応じてプロンプトを適応させる方法を学びました。このステップは高品質な結果を得るために重要であり、満足のいくユーザー体験を保証するためのものでした。

AWS上の堅牢かつ自動化されたインフラストラクチャ

StoryPixAIはAmazon Web Services(AWS)上にホストされたサーバーレスインフラストラクチャを基盤としており、柔軟性、スケーラビリティ、コスト最適化の理想的な組み合わせを提供しています。このアーキテクチャはTerraformとGitLab CI/CDによって完全に自動化されており、迅速かつ信頼性の高いアプリケーションデプロイを実現します。

StoryPixAIの中心にあるAWSサービス

alt text

StoryPixAIのアーキテクチャは以下のAWSサービスで構成されています: * Amazon S3 (Simple Storage Service) : Webサイトの静的ファイル(HTML、CSS、JavaScript)および生成されたストーリーとそれに関連するイラストのストレージ。

  • Amazon CloudFront : StoryPixAIのコンテンツを世界中のユーザーに配信するためのCDN(Content Delivery Network)。ユーザーに近い地理的な位置にキャッシュされることで、コンテンツの配信を高速化。
  • 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サービスも使用しています。IAMはリソースへのアクセス権限の詳細な管理、CloudWatchは監視とログ(デバッグおよびパフォーマンス解析に不可欠)、**Systems Manager Parameter Store (SSM Parameter Store)**はAPIキーなどの機密情報を保存し、アプリケーションのセキュリティを確保。

Terraform : インフラの自動化サービス

この複雑なインフラを管理するために、私はTerraformというInfrastructure as Code(IaC)ツールを選択しました。Terraformを使用して、AWSリソースの作成、変更、および削除を自動化することができ、一貫性があり、再現性があり、管理が容易な環境を保証します。これにより、デプロイメントプロセスが大幅に簡素化され、人為的なミスのリスクが減少しました。

GitLab CI/CD : スムーズでトラブルのないデプロイメント

StoryPixAIの継続的かつ信頼性のあるデプロイメントを確保するために、私はGitLab上でCI/CD(継続的インテグレーション/デプロイメント)パイプラインを設定しました。このパイプラインはソースコードの変更ごとにテスト、ビルド、およびデプロイメントを自動化し、エラーを迅速に検出および修正し、自信を持って新しい機能を提供することを可能にします。このアプローチにより、アプリケーションが常に最新であり、ダウンタイムが最小限に抑えられます。

AWS、Terraform、およびGitLab CI/CDの組み合わせにより、堅牢で拡張性があり、維持が容易なインフラを構築でき、プロジェクトのクリエイティブな側面やユーザー体験の向上に集中するための時間をより多く取れました。

StoryPixAIプロジェクトの全体アーキテクチャ

コードを詳しく見る前に、アプリケーションのアーキテクチャ全体像を以下に示します:

  1. S3上の静的サイト:S3バケット上にホストされ、CloudFrontを介してグローバルに配信される静的ウェブサイト。
  2. API Gateway:ストーリー生成およびステータス確認のためのエンドポイントを公開。
  3. Lambda関数
    • StoryPixAI.py:ストーリーおよび関連する画像を生成。
    • status_checker.py:DynamoDBでの生成ステータスを確認。
  4. DynamoDB:生成タスクのステータスを保存。 S3: 生成された画像と結果のHTMLページを保存します。
  5. Cognito: APIを保護するためにユーザー認証を管理します。

Lambda関数 StoryPixAI.py

全体概要

StoryPixAI.py 関数はアプリケーションの中心です。以下の役割を担っています:

  • ユーザーからのプロンプトに基づいて物語を生成します。
  • モデルを指示するための詳細なインストラクションを作成します。
  • 物語の各シーンや重要な要素の要約を抽出します。
  • これらの要約に対応する画像を生成します。
  • テキストと画像を組み合わせてHTMLページにします。
  • 結果をS3に保存し、DynamoDBにステータスを更新します。

コードの分解

インポートと初期設定

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 はプロジェクトの中心です。これは物語生成のためにモデルに渡される詳細なインストラクションを生成します。

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

プロンプトの構築

このプロンプトは、物語が一貫性があり、教育的で、子供に適したものであるためにモデルにすべての必要な情報を提供するために設計されています。

  • 言語language_description パラメータを使用して物語の言語を指定し、生成されるテキストが希望の言語になるようにします。

  • テーマ: ユーザープロンプトがインストラクションに組み込まれ、物語の基盤として機能します。

  • 長さ: 物語の長さを制御するために1000〜1500語の範囲を指定しています。

  • 重要要素: 冒険、魔法、重要な教育的価値などの要素を含めることを推奨しています。

インストラクションの詳細

モデルに提供されるインストラクションは非常に詳細で、生成を正確に指示するためのものです。

以下はプロンプトの各部分の解析です:

  1. 物語の構造: モデルに、魅力的な始まり、豊かな展開、満足のいく結末を持つ物語を構築するよう要求します。

  2. 視覚的な説明: 物語は子供の想像力を刺激するために視覚的な説明が豊かであるべきです。

  3. キャラクター: 個性が明確な親しみやすいキャラクターの発展を推奨します。

  4. 特定のタグ[titre]... [end_titre][resume]... [end_resume] のようなタグを使用して、タイトルや視覚的な説明を区切ります。

  5. ファンタスティックな要素: 物語をより魅力的にするために魔法やファンタスティックな要素を含めることを勧めます。

  6. 教育的価値: 物語は重要な価値を教えるものであるべきです。

タグの役割

タグは生成されたテキストの後処理で重要な役割を果たします。

  • [titre]… [end_titre]: 物語のタイトルを囲みます。 これにより、それを簡単に抽出してユーザーインターフェースに適切に表示することができます。

  • [resume]… [end_resume] : キーシーンの詳細な視覚的な説明を囲みます。これらのレジュメは、画像生成のプロンプトとして使用されます。

生成後の処理

AIモデルがこれらの指示に従ってストーリーを生成した後、コードは以下のステップを実行します:

  1. タグの修正: correct_resume_tags関数は、すべてのタグが抽出のために正しくフォーマットされていることを確認します。

  2. レジュメの抽出: extract_summaries関数は、[resume][end_resume]タグを使用して視覚的な説明を抽出します。

  3. 画像の生成: 各レジュメはgenerate_image関数に渡され、対応する画像を生成します。

  4. 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関数は、レジュメから画像を生成するために(例えば、OpenAI DALL·Eなどの)画像生成モデルのAPIを呼び出します。

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コンテンツを組み立てることです。これは、ウェブサイトに表示される構造化された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

詳細な説明

  1. タイトルの抽出:

    • 正規表現を使用して [titre][end_titre] タグの間のテキストを見つけます。
    • 抽出後、メインテキストからタグを削除します。
    • タイトルが見つからない場合は、デフォルトのタイトルが使用されます。
  2. HTMLの初期化:

    • HTMLコンテンツは <html>, <head>, および <body> タグで始まります。
    • スタイルシート(CSS)を含めて、プレゼンテーションを改善します(タイポグラフィ、マージン、アライメント)。
  3. テキストの分割:

    • テキストは [resume][end_resume] タグを使用してセグメントに分割されます。
    • セグメントは要約部分を除いたストーリーの一部を表します。
  4. 組み立て:

    • 各テキストセグメントは <p> パラグラフに挿入されます。
    • 画像生成が有効で対応する画像がある場合、パラグラフの後に画像が挿入されます。
    • 画像は中央揃えされ、スクリーンサイズに適応するよう調整されます。
  5. 最終化:

    • ドキュメントを完結させるために、閉じタグ </body></html> が追加されます。

このアプローチの理由

  • テキストと画像の整合: 画像を対応するテキストセグメントの後に挿入することで、視覚的に豊かなストーリーが作成され、特に子供たちに重要です。

  • 柔軟性: ユーザーが画像生成を選択しない場合でも、コードはこの状況を処理し、テキストのみを挿入します。

  • アクセシビリティ: セマンティックなタグと適切なスタイルを使用することで、コンテンツは異なるデバイス(PC、タブレット、スマートフォン)でアクセス可能となります。

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

説明

  • リクエストの処理:

    • 受信されたイベント(event)から必要な情報を取得します。
    • リクエストのパラメータにはプロンプト、選択されたモデル、言語などが含まれます。
  • ステータスの更新:

    • 処理を開始する前に、ステータスは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関数と連携するための2つの主要なエンドポイントを公開します:

  1. /generate-image:

    • メソッド: POST
    • 説明: ユーザーがストーリーの生成と、必要に応じて関連するイメージの生成を開始できるようにします。
    • 統合: Lambda関数StoryPixAI.pyに接続。
  2. /check-status:

    • メソッド: GET
    • 説明: ユーザーがrequestIdを提供することでリクエストのステータスを確認できるようにします。
    • 統合: Lambda関数status_checker.pyに接続。

Cognitoによる認証

APIを保護し、リソースへのアクセスを制御するために、Amazon Cognitoを統合しました。

  • ユーザープール:

    • ユーザー認証情報を管理します。
    • 登録、ログイン、およびユーザー管理を可能にします。
  • オーソライザー:

    • API Gatewayで設定されており、Cognitoが発行するJWTトークンを検証します。
    • 認証されたリクエストのみが保護されたエンドポイントにアクセスできるようにします。 - APIゲートウェイでの統合
    • エンドポイント /generate-image/check-status はCognito認証を使用して保護されています。
    • クライアントはリクエストヘッダー (Authorization) に認証トークンを含める必要があります。

S3の静的サイトとAPIとの連携

サイトの構造

静的ウェブサイトはアプリケーションのユーザーインターフェースとして機能します。

  • index.html

    • プロンプトを入力したり、生成オプションを選択したり、リクエストを送信するためのフォームを含みます。
    • APIとの連携や認証の管理に必要なスクリプトを含みます。
  • storypixai.js

    • APIとのやり取りを管理するJavaScriptコードを含みます。
    • Cognitoでの認証、フォームの送信、ステータスの追跡、結果の表示を管理します。

ユーザーワークフロー

  1. ログイン

    • 組み込みのログインフォームを使用してユーザーがログインします。
    • 情報はCognitoによって検証されます。
  2. リクエストの送信

    • ユーザーはプロンプトと希望するオプションを入力してフォームを送信します。
    • 送信時に、データとともにPOSTリクエストがエンドポイント /generate-image に送信されます。
  3. 非同期処理

    • APIは直ちに requestId を返します。
    • 生成の処理はバックグラウンドで行われます。
  4. ステータスの確認

    • ウェブサイトは定期的にエンドポイント /check-status に対して requestId を提供して問い合わせます。
    • ステータスが「link」になったら、ユーザーに結果のURLが表示されます。
  5. 結果の表示

    • ユーザーはリンクをクリックして生成された画像付きのストーリーにアクセスできます。

リクエストとレスポンスの管理

  • 認証済みリクエスト

    • APIへのすべてのリクエストには認証トークンが含まれます。
    • トークンはウェブサイトに含まれるCognito SDKによって管理されます。
  • ステータスの管理

    • 可能なステータスは “Processing”、“link”、“Failed” です。
    • サイトは受け取ったステータスに応じてインターフェースを適応させます(例:スピナーの表示、エラーメッセージ、結果へのリンク)。

コンポーネント間の相互接続

各コンポーネントの相互作用は次のとおりです:

  • ウェブサイト ↔️ APIゲートウェイ

    • ウェブサイトはAPIゲートウェイによって公開されているエンドポイントにリクエストを送信します。
    • 認証トークンはリクエストのセキュリティを確保するために含まれます。
  • APIゲートウェイ ↔️ Lambda関数

    • APIゲートウェイは受信したリクエストに応じて対応するLambda関数を起動します。
  • Lambda関数 ↔️ DynamoDB

    • Lambda関数 StoryPixAI.pystatus_checker.py は、リクエストのステータスを更新および取得するためにDynamoDBと連携します。
  • 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

### 主要なジョブ

パイプラインにはいくつかの重要なジョブが含まれています:

1. **Terraformの確認**:
   ```yaml
   Terraformの確認:
     stage: 確認
     when: manual
     script:
       - /bin/bash -c "source export.sh && terraform_plan"

このジョブは terraform plan を実行して、予期されるインフラストラクチャの変更を確認しますが、適用はしません。

  1. Terraformの展開

    Terraformの展開:
      stage: 展開
      when: manual
      dependencies:
        - Terraformの確認
      script:
        - /bin/bash -c "source export.sh && terraform_apply"
    

    確認後、このジョブは terraform apply を実行してインフラストラクチャの変更を適用します。

  2. Terraformの削除

    Terraformの削除:
      stage: 削除
      when: manual
      script:
        - /bin/bash -c "source export.sh && terraform_destroy"
    

    このジョブは terraform destroy を実行して、必要に応じてインフラストラクチャを削除します。

  3. OpenAIキーの管理

    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パラメータストアでのOpenAI APIキーの安全な追加と削除を管理します。

実行環境

各ジョブは、Ubuntu 22.04をベースにしたDockerコンテナで実行され、TerraformとAWS CLIがインストールされています:

.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アプローチの利点

  1. 自動化:コードの変更ごとにパイプラインが自動的にトリガーされ、一貫した確認と展開が確保されます。

  2. 手動制御:展開や削除などの重要なステップは手動モード(when: manual)で設定されており、実行前に追加の制御が提供されます。

  3. セキュアなシークレット管理:AWSパラメータストアとの統合により、APIキーの安全な取り扱いが保証されます。

  4. 柔軟性:ステージによる構造により、パイプラインのさまざまなステップを順序立てて論理的に実行できます。

  5. 再現性:標準化されたDocker環境の使用により、ビルドとテストが異なるシステムで再現可能となります。

このCI/CD設定により、StoryPixAIの展開が自動化されるだけでなく、開発サイクル全体で高い品質と信頼性が維持されます。

結論

StoryPixAIは単なる技術プロジェクト以上のものでした。これは生成AIの世界への真の冒険であり、テクノロジーへの情熱を、子供たちのために魔法のような物語を作成するという願いと結び付ける機会でした。

このプロジェクトは、直感的なユーザーインターフェースの設計からプロンプティングの習得、さらにはAWSとTerraformを使用した堅牢なクラウドインフラの構築に至るまで、AIのさまざまな側面を探求する機会を提供しました。それぞれのステップは学習の源であり、技術的な課題に直面し、フルスタック開発とDevOpsのスキルを広げるのを余儀なくされました。

このブログポストが、このエキサイティングな冒険の舞台裏を垣間見るきっかけになれば幸いです。

重要なポイント

  • 詳細な指示

    • 明確で構造化されたプロンプトにより、AIモデルから一貫した高品質の結果を得ることができます。 - モジュラーアーキテクチャ
    • 各コンポーネント(ウェブサイト、APIゲートウェイ、Lambda、DynamoDB、S3、Cognito)は特定の役割を果たし、システムの保守と進化を容易にします。
  • セキュリティとスケーラビリティ

    • AWSのマネージドサービスを使用することで、強固なセキュリティと増大する需要に対応する能力を保証します。

プロジェクトリンク:StoryPixAI

このドキュメントは、gpt-4oモデルを使用してfrバージョンからja言語に翻訳されました。翻訳プロセスの詳細については、https://gitlab.com/jls42/ai-powered-markdown-translator を参照してください。