搜索

bloginfrastructureia

使用生成式人工智能创作儿童故事:StoryPixAI 的冒险

使用生成式人工智能创作儿童故事:StoryPixAI 的冒险

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

介绍

作为一名有经验的云基础设施与 DevOps 架构师,我一直对新技术及其改变日常生活的潜力充满兴趣。生成式人工智能的兴起激发了我不断增长的好奇心,我感到有必要深入探索这个快速发展的领域。

于是 StoryPixAI 应运而生,这是一个个人项目,让我得以探索人工智能在为儿童创作个性化故事和魔幻插图方面的无限可能。这个项目也让我以全栈开发者、prompt 工程师、产品负责人甚至 UX/UI 设计师的身份投入实践,同时与亲友分享我对技术的热情。

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

但首先,先来一段预览!

为了让您先感受一下 StoryPixAI 的潜力,下面是一些自动生成的故事,涵盖多种语言。
每个故事都配有插图,使叙述对儿童更具沉浸感:

将人工智能用于创意:一个实验之旅

我的 StoryPixAI 之旅始于一个简单的概念验证(PoC):一个与 OpenAI 交互以生成文本并使用 DALL-E 来创建图像的 Lambda 函数。这个初步的成功鼓励我继续深入,并通过 AWS Bedrock 探索其他人工智能模型。

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 中。借助 Bedrock,我测试了 Anthropic 的 Claude、Mistral(用于文本生成),以及 Stable Diffusion(用于图像生成)。

尽管这些模型也给出了有趣的结果,但我最终选择专注于 GPT-4 与 GPT-4-0(因为它们的生成速度与质量)以及 DALL-E 3(因为其生成的插图更贴合故事)。需要注意的是,为生成图像所使用的 prompt 很大程度上由文本模型本身生成,从而确保叙事与插图之间的一致性。

异步 API 与 DynamoDB 的挑战

在 PoC 验证之后,我开始构建一个 API,使 StoryPixAI 能通过 Web 界面被访问。在此阶段遇到的第一个重大挑战是 API Gateway 的超时限制。为了绕过该限制并允许生成更长、更复杂的故事,我不得不设计并实现一个异步架构。

Amazon DynamoDB 在此发挥了关键作用。我使用这款 NoSQL 数据库来存储正在进行的生成任务以及它们完成后的结果。通过这种方式,API 可以立即向用户返回响应,用户随后可以查询其请求的状态,并在生成完成后检索故事。

CORS 与前端:需要克服的障碍

前端界面的搭建也带来不少挑战。我需要熟悉 CORS(跨域资源共享)的细节,以便前端能够与 API 通信。我还花时间改进用户体验,添加了诸如选择 AI 模型与图像风格等功能。

Prompting:需要掌握的艺术

在开发 StoryPixAI 的整个过程中,我不断打磨我的 prompting 技能,即如何构造恰当的请求以引导 AI 模型。我学会了根据所用模型、故事参数和用户期望来调整 prompt。这个步骤对获得高质量结果并确保良好的用户体验至关重要。

在 AWS 上构建稳健且自动化的基础设施

StoryPixAI 运行在 Amazon Web Services (AWS) 的 serverless 基础设施之上,在灵活性、可扩展性和成本优化之间提供了理想的平衡。通过 Terraform 与 GitLab CI/CD 的完全自动化,这一架构支持快速可靠的应用部署。

StoryPixAI 的核心 AWS 服务

架构图

StoryPixAI 的架构围绕以下 AWS 服务展开:

  • Amazon S3 (Simple Storage Service): 存储网站静态文件(HTML、CSS、JavaScript)以及生成的故事和对应插图。
  • Amazon CloudFront: 内容分发网络(CDN),通过将内容缓存到地理位置更接近用户的节点来加速 StoryPixAI 的全球分发。
  • Amazon API Gateway: 应用的安全入口。它处理用户请求,通过 Amazon Cognito 进行身份验证,并将请求路由到相应的 Lambda 函数。
  • AWS Lambda: 构成 StoryPixAI 引擎的 serverless 函数。它们负责编排故事生成、图像创建、异步任务管理以及与 DynamoDB 和其他 AWS 服务的交互。
  • Amazon DynamoDB: 用于存储应用运行所需关键信息的灵活且高性能的 NoSQL 数据库。
  • Amazon Cognito: 身份与访问管理服务,通过允许用户登录并控制其权限来保护应用,确保只有经过身份验证的用户能使用故事生成功能。
  • Amazon Bedrock: 简化对不同供应商生成式 AI 模型(例如 Anthropic 的 Claude 和 Stability AI 的 Stable Diffusion)的访问与使用的平台。Bedrock 让在应用中集成这些模型变得更简单,而无需管理其底层基础设施。
  • 其他 AWS 服务: StoryPixAI 还使用 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:存储生成任务的状态。
  5. S3:存储生成的图像和生成结果的 HTML 页面。
  6. Cognito:管理用户身份验证以保护 API。

Lambda 函数 StoryPixAI.py

概览

函数 StoryPixAI.py 是应用的核心。它负责:

  • 根据用户的 prompt 生成故事。
  • 创建详尽的指令以引导 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)

在本节中,我导入所需模块,配置用于调试的 logger,并从 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] 分隔的文本段。这些摘要将作为生成图像的 prompt。

生成图像指令
def generate_image_instructions(prompt, style, language):
    # Génère les instructions pour la création d'images.

此函数格式化 prompt,以包括风格与语言,从而引导图像生成模型。

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

构建 Prompt

Prompt 的设计旨在为 AI 模型提供生成一个连贯、具教育意义且适合儿童的故事所需的全部信息。

  • 语言:参数 language_description 用于指定故事语言,确保生成的文本为所需语言。
  • 主题:用户的提示被整合进指令,作为故事的基础。
  • 长度:指定了 1000 到 1500 字的范围以控制故事长度。
  • 关键要素:指令鼓励包含诸如冒险、魔法和重要教育价值观等元素。

指令细节

提供给模型的指令极为详细,以便精确引导生成过程。

下面是对 prompt 各部分的分析:

  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 模型的交互

  • Client OpenAI:我使用之前获取的 API 密钥实例化 OpenAI 客户端。

  • Prompting:模型接收一系列消息:

    • 一个系统消息,说明助理是儿童故事方面的专家。
    • 包含生成的详细说明的用户消息。
  • 模型响应:模型基于提供的说明生成故事。

错误处理

如果在调用 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.
    """
  • Style:用户指定的风格(例如 “aquarelle”, “cartoon”)会被包含在提示中以影响图像的呈现。

  • Langue:描述会根据所选语言进行调整,这有助于模型理解文化细微差别。

  • 指令清晰:通过指明场景应为纯视觉内容,可以避免模型在图像中添加文字或其他不需要的元素。

与 OpenAI 图像 API 的交互

  • API 调用:使用函数 client.images.generate 来生成图像。

  • 重要参数

    • Prompt:将调整后的提示传递给 API。
    • Modèle:指定的图像生成模型。
    • Taille:图像尺寸(例如 “1024x1024”)。
    • Qualité:图像质量(标准、HD)。
    • Format de Réponse:图像以 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

    • 公共 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)中检索所需信息。
    • 请求参数包括 prompt、所选模型、语言等。
  • 状态更新

    • 在开始处理之前,将状态在 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。

  • User Pool

    • 管理用户凭证信息。
    • 支持注册、登录和用户管理。
  • Authorizer

    • 在 API Gateway 中配置以验证 Cognito 签发的 JWT 令牌。
    • 确保只有经过身份验证的请求才能访问受保护的端点。
  • 在 API Gateway 上的集成

    • 端点 /generate-image/check-status 受 Cognito authorizer 保护。
    • 客户端必须在请求头中包含身份验证令牌(Authorization)。

静态站点在 S3 上与 API 的交互

站点结构

静态网站作为应用的用户界面。

  • index.html

    • 包含允许用户输入 prompt、选择生成选项并提交请求的表单。
    • 包含与 API 交互和身份验证管理所需的脚本。
  • storypixai.js

    • 包含用于与 API 交互的 JavaScript 代码。
    • 处理与 Cognito 的身份验证、表单提交、状态跟踪和结果显示。

用户工作流

  1. 登录

    • 用户通过内嵌的登录表单登录。
    • 信息通过 Cognito 验证。
  2. 提交请求

    • 用户填写包含 prompt 和期望选项的表单。
    • 提交时,会向端点 /generate-image 发送一个 POST 请求并携带数据。
  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

    • 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 的开发和部署流畅,我设置了一个 CI/CD 管道,使用 GitLab CI/CD。该配置自动化构建和部署流程,从而确保每次代码变更的质量和可靠性。

管道配置

管道在项目根目录的文件 .gitlab-ci.yml 中定义。以下是其结构概览:

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

variables:
  TERRAFORM_VERSION: '1.5.7-*'
  TF_VAR_region: $AWS_DEFAULT_REGION
``` 此配置定义了管道的不同阶段以及在 CI/CD 过程中使用的全局变量。

### 主要作业

管道包括多个关键作业:

1. **Terraform 验证**:

   ```yaml
   Vérification Terraform:
     stage: Vérifications
     when: manual
     script:
       - /bin/bash -c "source export.sh && terraform_plan"

该作业执行 terraform plan 来检查预期的基础设施更改,但不应用这些更改。

  1. Terraform 部署

    Déploiement Terraform:
      stage: Déploiements
      when: manual
      dependencies:
        - Vérification Terraform
      script:
        - /bin/bash -c "source export.sh && terraform_apply"

    在验证之后,该作业通过执行 terraform apply 来应用基础设施更改。

  2. Terraform 删除

    Suppression Terraform:
      stage: Suppressions
      when: manual
      script:
        - /bin/bash -c "source export.sh && terraform_destroy"

    如果需要,该作业可销毁基础设施,执行 terraform destroy

  3. OpenAI 密钥管理

    Clé OpenAI - Ajout:
      stage: Pré-requis optionel
      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 "Aucune clé trouvée."
            exit 1
          fi
    
    Clé OpenAI - Supression:
      stage: Suppressions
      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. 灵活性:使用 stages 的结构可对管道的不同步骤实现有序且逻辑的执行。

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

此 CI/CD 配置不仅能够自动化 StoryPixAI 的部署,还能在整个开发周期中保持高水平的质量和可靠性。

结论

StoryPixAI 不仅仅是一个技术项目。这是一次在生成式人工智能领域的真正冒险,使我能够将对技术的热情与为我的孩子创造魔法故事的愿望结合起来。

该项目让我有机会探索人工智能的各个方面,从设计直观的用户界面到掌握 提示工程(prompting),再到使用 AWS 和 Terraform 搭建稳健的云基础设施。每一步都是学习的来源,令我面对具有挑战性的技术问题,并迫使我扩展全栈开发和 DevOps 的技能。

我希望这篇博客文章能让你对这段激动人心的冒险的幕后有一些了解。

关键要点

  • 详细说明

    • 清晰且结构化的提示可以让 AI 模型产生一致且高质量的结果。
  • 模块化架构

    • 每个组件(网站、API Gateway、Lambda、DynamoDB、S3、Cognito)都扮演特定角色,便于系统的维护和演进。
  • 安全性与可扩展性

    • 使用 AWS 托管服务可确保强健的安全性并具备应对增长需求的能力。

项目链接: StoryPixAI 项目

本文件已使用模型 gpt-5-mini 从法语(fr)版本翻译为中文(zh)。有关翻译过程的更多信息,请参见 https://gitlab.com/jls42/ai-powered-markdown-translator