Maison >interface Web >js tutoriel >Maîtriser le modèle de générateur : créer une CLI de générateur d'invites d'IA dynamique
Avez-vous déjà rencontré un cas dans votre parcours de développement où vous avez dû gérer des objets complexes ? Peut-être parce qu'ils ont trop de paramètres, qui peuvent même être imbriqués, ou qu'ils nécessitent de nombreuses étapes de construction et une logique complexe pour être construits.
Peut-être souhaitez-vous concevoir un module avec une interface claire et simple sans avoir à vous disperser ou à penser au code de création de vos objets complexes à chaque fois !
C'est là qu'intervient le modèle de conception du constructeur !
Tout au long de ce didacticiel, nous expliquerons tout sur le modèle de conception du constructeur, puis nous créerons une application CLI Node.js pour générer une invite de génération d'image optimisée DALL-E 3 à l'aide du modèle de conception du constructeur .
Le code final est disponible dans ce référentiel Github.
Builder est un modèle de conception créatif, qui est une catégorie de modèles de conception qui traite des différents problèmes liés à la manière native de créer des objets avec le nouveau mot-clé ou opérateur.
Le Builder Design Pattern se concentre sur la résolution des problèmes suivants :
Fournir une interface simple pour créer des objets complexes : Imaginez un objet profondément imbriqué avec de nombreuses étapes d'initialisation requises.
Séparer le code de construction de l'objet lui-même, permettant la création de plusieurs représentations ou configurations à partir du même objet.
Le Builder Design Pattern résout ces deux problèmes en déléguant la responsabilité de la création d'objets à des objets spéciaux appelés builders.
L'objet constructeur compose l'objet original et décompose le processus de création en plusieurs étapes ou étapes.
Chaque étape est définie par une méthode dans l'objet générateur qui initialise un sous-ensemble des attributs de l'objet en fonction d'une logique métier.
class PromptBuilder { private prompt: Prompt constructor() { this.reset() } reset() { this.prompt = new Prompt() } buildStep1() { this.prompt.subject = "A cheese eating a burger" //initialization code... return this } buildStep2() { //initialization code... return this } buildStep3() { //initialization code... return this } build() { const result = structuredClone(this.prompt) // deep clone this.reset() return result } }
Code client : il suffit d'utiliser le constructeur et d'appeler les étapes individuelles
const promptBuilder = new PromptBuilder() const prompt1 = promptBuilder .buildStep1() // optional .buildStep2() // optional .buildStep3() // optional .build() // we've got a prompt const prompt2 = promptBuilder .buildStep1() // optional .buildStep3() // optional .build() // we've got a prompt
Le modèle de conception typique d'un constructeur se compose de 4 classes principales :
Builder : L'interface du constructeur ne doit définir que les méthodes de construction sans la méthode build(), qui est chargée de renvoyer l'entité créée.
Classes de constructeur concret : Chaque constructeur concret fournit sa propre implémentation des méthodes Builder Interface afin qu'il puisse produire sa propre variante de l'objet (instance de Produit1 ou Produit2 ).
Client : Vous pouvez considérer le client comme le consommateur de premier niveau de nos objets, l'utilisateur qui importe des modules de bibliothèque ou le point d'entrée de notre application.
Réalisateur : Même le même objet constructeur peut produire de nombreuses variantes de l'objet.
class PromptBuilder { private prompt: Prompt constructor() { this.reset() } reset() { this.prompt = new Prompt() } buildStep1() { this.prompt.subject = "A cheese eating a burger" //initialization code... return this } buildStep2() { //initialization code... return this } buildStep3() { //initialization code... return this } build() { const result = structuredClone(this.prompt) // deep clone this.reset() return result } }
Comme vous pouvez le voir dans le code ci-dessus, il est nécessaire qu'une entité prenne la responsabilité de diriger ou d'orchestrer les différentes séquences de combinaisons possibles d'appels aux méthodes du constructeur, car chaque séquence peut produire un objet résultant différent.
Pouvons-nous donc encore plus abstraitr le processus et fournir une interface encore plus simple pour le code client ?
C'est là que la classe Directeur entre en jeu. Le directeur prend plus de responsabilités de la part du client et nous permet de prendre en compte tous ces appels de séquence de constructeur et de les réutiliser si nécessaire.
const promptBuilder = new PromptBuilder() const prompt1 = promptBuilder .buildStep1() // optional .buildStep2() // optional .buildStep3() // optional .build() // we've got a prompt const prompt2 = promptBuilder .buildStep1() // optional .buildStep3() // optional .build() // we've got a prompt
Code client
const promptBuilder = new PromptBuilder() const prompt1 = promptBuilder.buildStep1().buildStep2().build() const prompt2 = promptBuilder.buildStep1().buildStep3().build()
Comme vous pouvez le voir dans le code ci-dessus, le code client n'a pas besoin de connaître les détails de création de prompt1 ou prompt2. Il appelle simplement le directeur, définit l'objet générateur correct, puis appelle les méthodes makePrompt.
Pour démontrer davantage l'utilité du modèle de conception du générateur, créons un outil CLI AI de génération d'images d'ingénierie rapide à partir de zéro.
Le code source de cette application CLI est disponible ici.
L'outil CLI fonctionnera comme suit :
L'invite réaliste aura besoin de tous les attributs de configuration suivants pour être construite.
fichier : invites.ts
class Director { private builder: PromptBuilder constructor() {} setBuilder(builder: PromptBuilder) { this.builder = builder } makePrompt1() { return this.builder.buildStep1().buildStep2().build() } makePrompt2() { return this.builder.buildStep1().buildStep3().build() } }
fichier : invites.ts
const director = new Director() const builder = new PromptBuilder() director.setBuilder(builder) const prompt1 = director.makePrompt1() const prompt2 = director.makePrompt2()
Comme vous pouvez le voir ici, chaque type d'invite nécessite la construction de nombreux attributs complexes, comme artStyle , colorPalette , lightingEffect , perspective , Type de caméra , etc.
N'hésitez pas à explorer tous les détails des attributs, qui sont définis dans le fichier enums.ts de notre projet.
enums.ts
class PromptBuilder { private prompt: Prompt constructor() { this.reset() } reset() { this.prompt = new Prompt() } buildStep1() { this.prompt.subject = "A cheese eating a burger" //initialization code... return this } buildStep2() { //initialization code... return this } buildStep3() { //initialization code... return this } build() { const result = structuredClone(this.prompt) // deep clone this.reset() return result } }
L'utilisateur de notre application CLI peut ne pas être au courant de toutes ces configurations ; ils voudront peut-être simplement générer une image basée sur un sujet spécifique comme hamburger mangeur de fromage et un style (art réaliste ou numérique).
Après avoir cloné le dépôt Github, installez les dépendances à l'aide de la commande suivante :
const promptBuilder = new PromptBuilder() const prompt1 = promptBuilder .buildStep1() // optional .buildStep2() // optional .buildStep3() // optional .build() // we've got a prompt const prompt2 = promptBuilder .buildStep1() // optional .buildStep3() // optional .build() // we've got a prompt
Après avoir installé les dépendances, exécutez la commande suivante :
const promptBuilder = new PromptBuilder() const prompt1 = promptBuilder.buildStep1().buildStep2().build() const prompt2 = promptBuilder.buildStep1().buildStep3().build()
Vous serez invité à choisir un type d'invite : Réaliste ou Art numérique.
Ensuite, vous devrez saisir le sujet de votre invite. Restons-en au burger mangeur de fromage.
En fonction de votre choix, vous obtiendrez les invites textuelles suivantes :
Invite de style réaliste :
class Director { private builder: PromptBuilder constructor() {} setBuilder(builder: PromptBuilder) { this.builder = builder } makePrompt1() { return this.builder.buildStep1().buildStep2().build() } makePrompt2() { return this.builder.buildStep1().buildStep3().build() } }
Invite de style d'art numérique :
const director = new Director() const builder = new PromptBuilder() director.setBuilder(builder) const prompt1 = director.makePrompt1() const prompt2 = director.makePrompt2()
Copiez les commandes précédentes puis collez-les dans ChatGPT. ChatGPT utilisera le modèle DALL-E 3 pour générer les images.
Résultat d'invite d'image réaliste
Résultat de l'invite d'image d'art numérique
N'oubliez pas la complexité des paramètres d'invite et l'expertise nécessaire pour construire chaque type d'invite, sans parler des appels de constructeur laids qui sont nécessaires.
class RealisticPhotoPrompt { constructor( public subject: string, public location: string, public timeOfDay: string, public weather: string, public camera: CameraType, public lens: LensType, public focalLength: number, public aperture: string, public iso: number, public shutterSpeed: string, public lighting: LightingCondition, public composition: CompositionRule, public perspective: string, public foregroundElements: string[], public backgroundElements: string[], public colorScheme: ColorScheme, public resolution: ImageResolution, public postProcessing: string[] ) {} }
Avertissement : cet appel de constructeur laid n'est pas un gros problème en JavaScript car nous pouvons transmettre un objet de configuration avec toutes les propriétés pouvant être nullables.
Pour résumer le processus de création de l'invite et rendre notre code ouvert pour extension et fermé pour modification (O dans SOLID), et pour rendre l'utilisation de notre bibliothèque de génération d'invite transparente ou plus facile pour nos clients de bibliothèque, nous choisirons de mettre en œuvre le modèle de conception du constructeur.
Commençons par déclarer l'interface générique du générateur d'invites.
L'interface déclare un tas de méthodes :
builders.ts
class PromptBuilder { private prompt: Prompt constructor() { this.reset() } reset() { this.prompt = new Prompt() } buildStep1() { this.prompt.subject = "A cheese eating a burger" //initialization code... return this } buildStep2() { //initialization code... return this } buildStep3() { //initialization code... return this } build() { const result = structuredClone(this.prompt) // deep clone this.reset() return result } }
builders.ts
const promptBuilder = new PromptBuilder() const prompt1 = promptBuilder .buildStep1() // optional .buildStep2() // optional .buildStep3() // optional .build() // we've got a prompt const prompt2 = promptBuilder .buildStep1() // optional .buildStep3() // optional .build() // we've got a prompt
Comme vous pouvez le voir dans les implémentations ci-dessus, chaque constructeur choisit de créer son propre type d'invite (les formes d'invite finales sont différentes) tout en respectant les mêmes étapes de construction définies par le contrat PromptBuilder !
Passons maintenant à notre définition de classe Directeur.
directeur.ts
const promptBuilder = new PromptBuilder() const prompt1 = promptBuilder.buildStep1().buildStep2().build() const prompt2 = promptBuilder.buildStep1().buildStep3().build()
La classe Director encapsule un PromptBuilder et nous permet de créer une configuration d'invite qui consiste à appeler toutes les méthodes du constructeur à partir de setSubject à buildArtisticElements.
Cela simplifiera notre code client dans le fichier index.ts, que nous verrons dans la section suivante.
sérialiseurs.ts
class Director { private builder: PromptBuilder constructor() {} setBuilder(builder: PromptBuilder) { this.builder = builder } makePrompt1() { return this.builder.buildStep1().buildStep2().build() } makePrompt2() { return this.builder.buildStep1().buildStep3().build() } }
Pour imprimer le texte d'invite final sur la console du terminal, j'ai implémenté certaines fonctions de sérialisation d'utilitaires.
Maintenant, notre code de génération de bibliothèque d'invite est prêt. Utilisons-le dans le fichier index.ts.
index.ts
const director = new Director() const builder = new PromptBuilder() director.setBuilder(builder) const prompt1 = director.makePrompt1() const prompt2 = director.makePrompt2()
Le code ci-dessus effectue les actions suivantes :
Rappelez-vous : il n'est pas possible d'obtenir l'invite du directeur car la forme de l'invite produite par chaque type de constructeur est différente.
Le modèle de conception Builder s'avère être une excellente solution pour créer des objets complexes avec plusieurs configurations, comme le démontre notre application CLI de génération d'invites d'images AI. Voici pourquoi le modèle Builder s'est avéré bénéfique dans ce scénario :
Création d'objets simplifiée : Le modèle nous a permis de créer des objets complexes RealisticPhotoPrompt et DigitalArtPrompt sans exposer leur processus de construction complexe au code client.
Flexibilité : En utilisant des classes de constructeur distinctes pour chaque type d'invite, nous pourrions facilement ajouter de nouveaux types d'invite ou modifier ceux existants sans changer le code client.
Organisation du code : Le modèle a permis de séparer la logique de construction de la représentation, rendant le code plus modulaire et plus facile à maintenir.
Réutilisabilité : La classe PromptDirector nous a permis de réutiliser le même processus de construction pour différents types d'invites, améliorant ainsi la réutilisabilité du code.
Abstraction : Le code client dans index.ts est resté simple et axé sur la logique de haut niveau, tandis que les complexités de la construction rapide ont été abstraites dans les classes de constructeur.
Si vous avez des questions ou souhaitez discuter davantage de quelque chose, n'hésitez pas à me contacter ici.
Bon codage !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!