通过 StoryPixAI,我的目标是创建一个交互式网页应用,允许用户生成儿童故事,并由人工智能模型生成的图片进行丰富。为了实现这一目标,我使用了多个AWS服务,如Lambda、API Gateway、DynamoDB、S3和Cognito来进行认证。基础设施代码由Terraform管理,部署通过GitLab CI自动化。在这篇文章中,我将揭示这个令人兴奋的项目背后的故事,包括技术选择和遇到的挑战。

介绍

作为一名经验丰富的云基础设施和DevOps架构师,我一直对新技术及其改变我们日常生活的潜力充满兴趣。生成式AI的兴起激发了我的好奇心,我感到有必要深入探讨这个充满活力的领域。

于是StoryPixAI诞生了,这是一个个人项目,使我能够探索AI的无限可能性,以便为儿童创造个性化的故事和魔幻插图。这个项目让我有机会扮演全栈开发人员、提示工程师、产品负责人,甚至是UX/UI设计师的角色,同时与我的亲人分享我对技术的热情。

在这篇博客文章中,我将分享我的技术选择和在这次激动人心的冒险中克服的挑战。

但首先给你们一些开胃菜!

为了让你们初步了解StoryPixAI的潜力,下面是一些自动生成的故事,涵盖多种语言。每个故事都配有插图,使叙述对孩子们来说更加身临其境:

AI在创造力中的应用:一段实验之旅

我与StoryPixAI的冒险开始于一个简单的概念验证(PoC):一个与OpenAI交互以生成文本并使用DALL-E创建图像的Lambda函数。这个初步成功鼓励我进一步探索通过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 来轻松整合其他生成式 AI 模型到 StoryPixAI 中。该平台让我能够测试 Anthropic 的 Claude 和 Mistral 进行文本生成,以及用于图像创作的 Stable Diffusion。

虽然这些模型给出了有趣的结果,我最终选择专注于 GPT-4 和 GPT-4-0,因为它们的文本生成速度和质量,以及 DALL-E 3,因为它能够生成与故事完美适配的插图。值得注意的是,用于生成图像的提示在很大程度上是由文本模型本身设计的,这确保了叙述和插图之间的一致性。

异步 API 和 DynamoDB 的挑战

在 PoC 验证后,我着手创建一个 API 以使 StoryPixAI 通过网页界面访问。在此阶段,我遇到了第一个重大挑战:API Gateway 的超时限制。为了绕过这个限制并允许生成更长和更复杂的故事,我需要建立一个异步架构。

这时 Amazon DynamoDB 就出场了。我使用这个 NoSQL 数据库来存储正在进行的故事生成任务及其完成后的结果。通过这种方法,API 可以立即向用户返回响应,用户随后可以查看其请求的状态并在准备就绪后取回生成的故事。

CORS 和用户界面:需要克服的障碍

设置网页版界面同样充满挑战。我必须熟悉 CORS(跨域资源共享)的种种细节,以便我的前端能够与 API 进行通信。我还花时间通过添加选择 AI 模型和图像风格的功能来改善用户体验。

提示词:一种需要掌握的艺术

在开发 StoryPixAI 的整个过程中,我提高了我的提示词技能,这是一种构建正确请求以引导 AI 模型的艺术。我学会了根据使用的模型、故事的参数和用户期望来调整提示词。这一步对于获得高质量的结果并确保满意的用户体验至关重要。

基于 AWS 的稳健自动化基础设施

StoryPixAI 基于 Amazon Web Services (AWS) 托管的无服务器基础架构,提供了灵活性、可扩展性和成本优化的完美结合。完全通过 Terraform 和 GitLab CI/CD 自动化的这种架构,确保了应用的快速且可靠的部署。

StoryPixAI 核心的 AWS 服务

alt text

StoryPixAI 的架构围绕以下 AWS 服务展开: * Amazon S3 (简单存储服务) : 存储网站的静态文件(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 : 一个简化不同供应商生成式AI模型访问和使用的平台,例如Anthropic(Claude)和Stability AI(Stable Diffusion)。Bedrock允许在无需管理其基础设施的情况下轻松集成这些模型到应用程序中。
  • 其他AWS服务 : StoryPixAI还使用其他AWS服务,如IAM(身份和访问管理)用于精细的资源访问权限管理,CloudWatch用于监控和日志记录(对调试和性能分析至关重要),以及Systems Manager Parameter Store (SSM Parameter Store) 用于存储敏感信息如API密钥,从而确保应用程序的安全。

Terraform:自动化基础设施管理

为了管理这复杂的基础设施,我选择了Terraform,一个基础设施即代码(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函数是应用程序的核心。它负责:

  • 基于用户提示生成故事。
  • 创建详细说明以指导AI模型生成故事。
  • 提取故事中每个场景或关键元素的摘要。
  • 生成与这些摘要相对应的图像。
  • 将文本和图像结合到一个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 函数是项目的核心。它生成一组详细说明,将传递给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字的范围,以控制故事长度。

  • 关键元素 : 说明鼓励包含冒险、魔法和重要教育价值等元素。

说明的细节

提供给模型的说明非常详细,以精准指导生成。

以下是提示各部分的分析:

  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调用生成图像的模型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”)。
    • 质量:图像的质量 (标准, 高清)。
    • 响应格式:图像以 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

详细解释

  1. 标题提取:

    • 使用正则表达式找到[titre][end_titre]标签之间的文本。
    • 在提取后从主文本中删除标签。
    • 如果没有找到标题,将使用默认标题。
  2. HTML 初始化:

    • HTML 内容从<html><head><body>标签开始。
    • 包含 CSS 样式来改善呈现效果 (字体、边距、对齐)。
  3. 文本分割:

    • 使用[resume][end_resume]标签将文本分成多个片段。
    • 这些片段代表没有摘要的故事部分。
  4. 组装:

    • 每个文本片段被插入到一个<p>段落中。
    • 如果启用了图像生成并且有相应的图像,图像将在段落后插入。
    • 为了更好的用户体验,图像被居中并适应屏幕大小。
  5. 完成:

    • 添加关闭标签</body></html>来完成 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构建
    • 使用配置的CloudFront域构建公共URL,从而实现快速且安全的内容分发。
  • 异常管理

    • 在下载过程中出现错误时,异常会被记录并抛出以由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中将状态更新为“link”并包含结果的URL。
  • 返回响应

    • 响应包括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函数进行交互:

  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 Gateway 集成
    • /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 Gateway

    • 网站向 API Gateway 暴露的端点发送请求。
    • 包含身份验证令牌以确保请求安全。
  • API Gateway ↔️ Lambda 函数

    • API Gateway 根据接收到的请求调用相应的 Lambda 函数。
  • Lambda 函数 ↔️ DynamoDB

    • 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 Parameter Store中安全地管理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 Parameter Store集成管理API密钥,确保敏感信息的安全处理。

  4. 灵活性:分阶段结构允许有序和逻辑地执行流水线的不同步骤。

  5. 可重复性:使用标准化的Docker环境确保构建和测试在不同系统上可重复。

这种CI/CD配置不仅自动化了StoryPixAI的部署,还通过开发周期中的每个阶段维护高质量和可靠性。

结论

StoryPixAI不仅仅是一个技术项目。这是一场在生成式AI领域的真正冒险,让我能够把对技术的热爱与为我的孩子创造神奇故事的愿望结合起来。

该项目使我有机会探索AI的各个方面,从设计直观的用户界面到掌握prompting,再到与AWS和Terraform一起建立强大的云基础设施。每一步都是一个学习的来源,面对刺激的技术挑战,并迫使我扩展全栈开发和DevOps的技能。

希望这篇博客能给你们一个这场令人激动的冒险的幕后花絮。

关键点

  • 详细说明

    • 清晰结构化的prompts使得AI模型能够产生一致且高质量的结果。 - 模块化架构
    • 每个组件(网站、API Gateway、Lambda、DynamoDB、S3、Cognito)扮演特定角色,有助于系统的维护和演进。
  • 安全性和可扩展性

    • 使用AWS的托管服务确保了强大的安全性,并能够适应不断增长的需求。

项目链接:StoryPixAI

此文件已使用 gpt-4o 模型从 fr 版本翻译成 zh 语言。有关翻译过程的更多信息,请参阅 https://gitlab.com/jls42/ai-powered-markdown-translator