Comment utiliser l’API de GPT-4 en PHP ?

Guide pas à pas pour installer et utiliser l’API de GPT 4 avec PHP : création d’une clé, package, code de base et avancé.

Découvre dans ce premier article comment installer et utiliser les API d’OpenAI avec PHP. On verra ensuite comment utiliser les premières fonctionnalités de l’API pour exécuter un prompt avec GPT-4. D’autres articles suivront pour présenter les fonctionnalités (encore) plus avancées…

Au programme pour avancer pas à pas :

Package et prérequis technique pour utiliser les API d’OpenAI

Pour utiliser l’API d’OpenAI et pouvoir executer des prompts, nous utiliserons le package https://github.com/openai-php/client

Ce package est agnostique de tout framework. Dans la rubrique « aller + loin » de l’article, tu pourras retrouver les liens vers les packages dédiés à Symfony ou Laravel.

Pour pouvoir utiliser ce package, au moment où j’écris cet article, il faut un environnement permettant d’executer à minima PHP 8.1.

Comme tout package PHP qui se respecte, l’installation se fait au travers de Composer, via la commande :

composer require openai-php/client

Récupérer une clé API OpenAI

Utiliser l’API d’OpenAI n’est pas gratuit. Il te faudra créer un compte sur https://platform.openai.com/ La création du compte est gratuite. Il faudra par contre précharger son compte avec des crédits ($) pour pouvoir utiliser l’API.

Le coût d’un prompt est variable selon le modèle utilisé, la quantité de données envoyées, la quantité de données reçues, etc. Il est donc compliqué de donner une idée de coût. Mais pour en donner une quand même, on peut évaluer le coût d’un prompt moyen à environ 1ct de $. Ce qui fait qu’avec 1$, tu pourras exécuter une centaine de prompts. Donc si tu démarres, en déposant 5 à 10$ de crédits, tu as devant toi plusieurs centaines d’exécutions possibles pour tester l’API.

Les interfaces d’OpenAI évoluent. Au moment où j’écris cet article, l’achat de crédits se trouve dans : Settings > Billing > Add to credit balance

Une fois que c’est fait, tu peux créer une clé API via : API Keys > Create new secret key

Je t’invite à créer une clé par projet, cela te permettra de suivre son usage et de la désactiver facilement si nécessaire.

Cette clé API ne doit pas être partagée, ni versionnée. L’usage d’un fichier d’environnement (.env) est conseillé.

Premier appel à l’API de GPT4 en PHP

Comme expliqué au début, nous allons travailler avec GPT-4.

Le 13 mai 2024 est sortie la dernière version de GPT-4, GPT-4o. C’est actuellement le modèle le plus rapide et… le moins cher !

Pour bien organiser ton code, je t’invite à créer une classe dédiée à l’exécution de tes prompts. Cela te permettra de ranger au même endroit tout ce qui est lié à leur construction, leur exécution et la récupération des données.

Appelons cette classe “IA” :

class IA
{
    // Modèles disponibles
    public const MODEL_GPT4_TURBO = 'gpt-4-turbo';
    public const MODEL_GPT4_O = 'gpt-4o';
    
    // Mon premier prompt
    public static function myFirstPrompt(): string
    {
        // Définition du prompt
	      $prompt = 'Explique moi pourquoi PHP est le meilleur langage pour faire du développement web ?';
	      
	      // Initialisation du client
	      $client = OpenAI::client(getenv('OPENAI_API_KEY')); // Clé disponible dans le fichier .env
	      
	      // Appel à l'API
	      $response = $client->chat()->create([
            'model' => self::MODEL_GPT4_O,
            'messages' => [
                [
                    'role' => 'user',
                    'content' => $prompt,
                ],
            ],
        ]);
        
        // Récupération de la réponse
        return $response->choices[0]->message->content;
    }
}

Un peu d’explications :

  • J’ai décidé de mettre les modèles dans des constantes. De cette manière je pourrais les appeler au travers des constantes, de façon efficace, sans risquer une faute de frappe ou autre.
  • Ma méthode “myFirstPrompt” est statique, je pourrais donc l’appeler sans instancier mon objet IA.
  • Cette méthode retourne donc la réponse à mon prompt et juste ça. C’est le cas d’usage le plus simple, je passe un prompt statique (aucune variation entre chaque appel) et je récupère une chaine de caractères en sortie. On va voir qu’on peut faire des choses plus sophistiquées.

Appel avancé à l’API de GPT4 avec PHP

Le cas d’un prompt statique n’est pas un cas très intéressant. Dans le contexte d’une application web complexe, le prompt sera sans doute différent à chaque appel, pour plusieurs raisons :

  • Utilisation des informations de l’utilisateur
  • Utilisation d’informations métier (par exemple des informations contenues en base de données)
  • Utilisation d’informations de contexte externe (données d’actualité, données météo, etc.)

Tout ça va nous pousser à créer une logique de création du prompt plus avancée.

Ensuite, il y a la notion d’instructions. Si tu es familier de ChatGPT, tu sais que tu peux définir via l’interface des instructions personnalisées. C’est un ensemble de règles et informations qui seront utilisées à chaque prompt pour donner un contexte complémentaire ainsi que des instructions sur la façon de répondre aux différents prompts. Dans ChatGPT, on les écrit une fois et ces instructions s’appliquent pour tous les prompts. Avec l’API, on peut définir les instructions à chaque exécution. Comme pour le prompt, ces instructions peuvent être créées de façon dynamique avec les mêmes typologies d’informations.

Enfin, récupérer la réponse au prompt c’est très bien, mais l’API renvoie d’autres informations dont certaines très intéressantes pour bien comprendre comment le prompt s’est exécuté, et, par exemple, combien il a coûté.

Dans ce nouveau code, nous allons donc :

  • Créer un prompt de façon dynamique
  • Créer des instructions
  • Retourner la totalité de la réponse

Je repars de mon exemple précédent, sauf que cette fois-ci je vais passer en paramètres un utilisateur. On considèrera que cet utilisateur correspond à une classe User qui a certaines propriétés publiques (public readonly).

class IA
{
    // Modèles disponibles
    public const MODEL_GPT4_TURBO = 'gpt-4-turbo';
    public const MODEL_GPT4_O = 'gpt-4o';
    
    /**
     * @param User $user un utilisateur de mon application
     * @return OpenAI\Responses\Chat\CreateResponse une réponse de l'API OpenAI
     */
    public static function newsAboutALanguage(User $user): CreateResponse
    {
        // Construction du prompt
        $prompt = 'Présente moi 4 nouveautés du language : ' . $user->favoriteLanguage;
        
        // Construction des instructions
        $instructions = 'Pour répondre au prompt, tu prendras en compte que je suis ' . $user->seniorityLevel . '.' .
								        'Pour chaque nouveauté, tu donneras son nom, une présentation en 1 phrase et un exemple de code pour illustrer';
								        
				// Initialisation du client
	      $client = OpenAI::client(getenv('OPENAI_API_KEY')); // Clé disponible dans le fichier .env
	      
	      // Appel à l'API
	      $response = $client->chat()->create([
            'model' => self::MODEL_GPT4_O,
            'messages' => [
                [
                    'role' => 'system',
                    'content' => $instructions
                ],
                [
                    'role' => 'user',
                    'content' => $prompt
                ],
            ],
        ]);
        
        // Renvoi de la réponse
        return $response;			        
    }

Un peu d’explications :

  • Cette fois-ci ma méthode a un paramètre, il s’agit d’une instance de User
  • J’utilise $user pour créer de façon dynamique le prompt et les instructions
  • Lors de l’appel à l’API j’envoie désormais 2 messages en même temps.
    • Un correspondant au “system” avec les instructions
    • Un correspondant au “user” (comme dans l’exemple précédent) avec le prompt
  • Je retourne $reponse dans sa totalité. C’est donc le reste de mon code qui sera chargé de traiter la réponse.

Pourquoi et comment traiter la réponse ?

Dans $reponse, tu ne récupères pas que la réponse au prompt, tu récupères d’autres informations intéressantes, notamment :

  • Le coût en tokens de ton message
  • Le coût en tokens de la réponse fournie

Il y a des chances que dans ton application, tu aies besoin d’enregistrer ces informations, par exemple pour faire un suivi de qui fait quel prompt, et combien coûte les prompts réalisés. Si la méthode de ta classe IA ne renvoie que la réponse au prompt, alors toutes ces précieuses informations sont perdues à chaque fois.

Avec cette version du code, ce n’est donc pas à la classe IA d’enregistrer ces informations. Mais on aurait pu faire autrement, avec une classe IA + complexe et une méthode dédiée à l’enregistrement des données du prompt, quelque chose comme ça :

class IA
{
    // Modèles disponibles
    public const MODEL_GPT4_TURBO = 'gpt-4-turbo';
    public const MODEL_GPT4_O = 'gpt-4o';
    
    // Mon premier prompt
    public static function mySavedPrompt(): string
    {
        // Définition du prompt
	      $prompt = '...';
	      
	      // Initialisation du client
	      $client = OpenAI::client(getenv('OPENAI_API_KEY')); // Clé disponible dans le fichier .env
	      
	      // Appel à l'API
	      $response = $client->chat()->create([
            'model' => self::MODEL_GPT4_O,
            'messages' => [
                [
                    'role' => 'user',
                    'content' => $prompt,
                ],
            ],
        ]);
        
        // Sauvegarde des informations du prompt
        $this->save($prompt, $instructions, $response);
        
        // Renvoi seulement de la réponse
        return $response->choices[0]->message->content;
    }
    
    private static function save(string $prompt, string $instructions, CreateResponse $response): void
    {
        // Logique de sauvegarde (propre à ton application, ton SGBD, ton framework, etc.)
    }
}

Si tes poils se hérissent en regardant ce code, ne t’inquiète pas c’est normal ! En effet, ici on ne respecte pas vraiment le principe de Single Responsability (responsabilité unique). Ce n’est pas à la classe IA, à travers une méthode privée de se charger de la sauvegarde. D’ailleurs, les méthodes privées sont souvent un bon indicateur qui montre que le principe de Single Responsability n’est pas respecté (Pas toujours non plus).

On aurait donc plutôt quelque chose du genre :

class IA
{
    // Modèles disponibles ...
    
    // Mon premier prompt
    public static function mySavedPrompt(): string
    {
        // Définition du prompt ...
	      // Initialisation du client ...
	      // Appel à l'API ...
        
        // Sauvegarde des informations du prompt
        PromptInformations::save($prompt, $instructions, $response);
        // Ou : 
        $prompt = new Prompt($prompt, $instructions, $response);
        $prompt->save();
        
        // Encore une fois à adapter selon ton application, ton framework, etc.
        
        // Renvoi seulement de la réponse ...
    }
}

Aller + loin avec GPT4

GPT-4-turbo ou GPT-4o ?

Après quelques essais réalisés dès la sortie de GPT-4o, on a remarqué que les résultats des prompts pouvaient être différents et de moins bonne qualité avec GPT-4o. On t’invite donc à comparer les modèles pour voir lequel est le + adéquat pour ton usage.

Par contre, d’un point de vue rapidité, GPT-4o est bien beaucoup + rapide à répondre.

Les packages dédiés aux frameworks PHP

Quelles influences des autres paramètres ?

Il est possible de gérer d’autres paramètres lors des appels à l’API :

  • temperature : ce paramètre contrôle la créativité ou la variabilité des réponses générées. Sa valeur est comprise entre 0 et 1. Avec une faible valeur, proche de 0, les réponses seront plus déterministes et cohérentes en choisissant les mots les plus probables. Cela rend les réponses plus prévisibles et conservatrices. Avec une valeur élevée, proche de 1, les réponses seront plus variées et créatives, en introduisant plus de diversité dans le choix des mots. Cela peut conduire à des réponses moins prévisibles et plus originales, voir à des hallucinations (donc attention…).
  • top_p : ce paramètre contrôle la diversité des réponses générées en limitant la sélection des mots. Sa valeur est comprise entre 0 et 1. Avec la valeur 1, l’échantillonnage est effectué parmi tous les mots possibles, ce qui peut donner des réponses très variées. Avec une valeur inférieure à 1, par exemple 0.9, seuls les mots dont la probabilité cumulée est inférieure à ce pourcentage (ici 90%) sont pris en compte. Cela peut rendre les réponses plus cohérentes et moins aléatoires.
  • frequency_penalty : ce paramètre est utilisé pour ajuster la tendance du modèle à répéter les mêmes phrases ou mots. Sa valeur est comprise entre -2 et 2 :
    • -2.0 : Encourage fortement la répétition des mots.
    • 0 : Pas de pénalité ni de bonus pour la fréquence des mots (comportement neutre).
    • 2.0 : Pénalise fortement la répétition des mots.
  • presence_penalty : ce paramètre controle la probabilité que de nouveaux sujets ou mots soient introduits dans le texte généré. Sa valeur est aussi comprise entre – 2 et 2.
    • -2.0 : Décourage fortement l’introduction de nouveaux mots ou sujets, favorisant la répétition des mots déjà utilisés.
    • 0 : Aucun ajustement n’est fait sur la présence de nouveaux mots, permettant une génération de texte plus naturelle.
    • 2.0 : Encourage fortement l’introduction de nouveaux mots ou sujets qui n’ont pas encore été mentionnés dans le texte généré.

Exemple d’utilisation avec l’API :

$response = $client->chat()->create([
    'model' => self::MODEL_GPT4_O,
    'messages' => [
        [
            'role' => 'system',
            'content' => $instructions
        ],
        [
            'role' => 'user',
            'content' => $prompt
        ]
    ],
    'temperature' => 0.5,
    'top_p' => 0.9,
]);

On pourrait aussi définir des constantes pour gérer ces paramètres. Au lieu d’utiliser des valeurs numériques, on aurait des noms plus explicites liées au paramétrage, par exemple :

// Constantes définies dans la classe IAClientOpenAI
public const PARAM_TEMPERATURE_CREATIVITY_HIGH = 1;
public const PARAM_TEMPERATURE_CREATIVITY_MEDIUM = 0.5;
public const PARAM_TEMPERATURE_CREATIVITY_LOWH = 0.1;

// Puis l'appel
$response = $client->chat()->create([
    'model' => self::MODEL_GPT4_O,
    'messages' => [
        [
            'role' => 'system',
            'content' => $instructions
        ],
        [
            'role' => 'user',
            'content' => $prompt
        ]
    ],
    'temperature' => self::PARAM_TEMPERATURE_CREATIVITY_MEDIUM,
]);

Avec ces éléments, tu as tout ce qu’il faut pour executer tes premiers prompts avec l’API de GPT4 en PHP. Si tu comptes utiliser plusieurs modèles différents, alors je t’invite à poursuivre la lecture de l’article pour organiser ton code manière efficace.

Bonus : Organiser le code pour utiliser d’autres API, interfaces et DTO

OpenAI et GPT4 c’est très bien, mais il y a d’autres modèles disponibles. Quelques un en vrac :

  • Mistral AI
  • Claude
  • Gemini
  • Etc.

Je vais volontairement commencer par la fin, pour montrer ce qu’on cherche à obtenir, à savoir pouvoir réaliser un prompt, avec n’importe quel modèle, sans avoir à dupliquer la logique liée au prompt. Ma classe IA va légèrement changer, avec notamment un constructeur qui permet de définir le modèle utilisé. Je vais aussi renommer cette classe pour l’appeler désormais Prompts puisqu’elle sert à réaliser des prompts :

class Prompts
{
    // Promotion de propriété
    public function __construct(
        private IAClientInterface $client
    ) { }
    
    /**
     * @param User $user un utilisateur de mon application
     * @return IAResponseDTO une réponse d'une API d'IA
     */
    public static function newsAboutALanguage(User $user): IAResponseDTO
    {
        // Construction du prompt
        $prompt = 'Présente moi 4 nouveautés du language : ' . $user->favoriteLanguage;
        
        // Construction des instructions
        $instructions = 'Pour répondre au prompt, tu prendras en compte que je suis ' . $user->seniorityLevel . '.' .
								        'Pour chaque nouveauté, tu donneras son nom, une présentation en 1 phrase et un exemple de code pour illustrer';
								        
	      // Appel à l'API
	      $response = $this->client->response([
            'messages' => [
                [
                    'role' => 'system',
                    'content' => $instructions
                ],
                [
                    'role' => 'user',
                    'content' => $prompt
                ],
            ],
        ]);
        
        // Renvoi de la réponse
        return $response;			        
    }
}

Qu’est ce qui a changé ?

  • Mon constructeur instancie une propriété $client qui contient une classe qui implémente l’interface IAClientInterface.
  • Je n’ai plus de “new client()”, c’est une autre classe qui sera chargée de ça
  • Ma méthode newsAboutALanguage renvoie une instance de IAResponseDTO qui sera mon DTO qui contient les informations retournées par l’API.
  • Dans la méthode newsAboutALanguage, je fais appel à $this→client→response alors qu’avant j’appelais $client->chat()->create(…) propre à OpenAI.

Je dois donc créer ces éléments :

  • L’interface IAClientInterface
  • Le DTO IAResponseDTO
  • Le client OpenAI
  • Le client MistralAI

L’interface IAClientInterface :

interface IAClientInterface
{
    // Chaque client doit avoir cette méthode
    public function response(array $params): IAResponseDTO;
}

Le DTO IAResponseDTO :

class IAResponseDTO
{
    // Toutes les propriétés sont en public readonly
    // On pourrait les mettre toutes en nullable au cas où une des API ne renverrait pas une donnée
    // Exemple avec la temperature
    public function __construct(
        public readonly string $prompt,
        public readonly string $instructions,
        public readonly string $response,
        public readonly int $nbInputTokens,
        public readonly int $nbOutputTokens,
        public readonly ?float $temperature = null,
        // Etc.
    ) {}
}

Le Client OpenAI :

class IAClientOpenAI implements IAClientInterface
{
    // Les constantes des models sont désormais ici
    public const MODEL_GPT4_TURBO = 'gpt-4-turbo';
    public const MODEL_GPT4_O = 'gpt-4o';

    private Client $client;

    public function __construct(
        private string $model = self::MODEL_GPT4_O
    ) {
        $this->client = OpenAI::client(getenv('OPENAI_API_KEY')); // Clé disponible dans le fichier .env
    }
    
    public function response(array $params): IAResponseDTO
    {
        $response = $this->client->chat()->create([
	          'model' => $this->model,
	          ...$params, // Opérateur de décomposition
        ]);

        // Récupération de prompt et des instructions
        $prompt = '';
        $instructions = '';

        foreach ($params['messages'] as $message) {

            if ($message['role'] == 'system') {
                $instructions = $message['content'];
                continue;
            }

            if ($message['role'] == 'user') {
                $prompt = $message['content'];
                continue;
            }
        }
    
        // Construction du DTO
        return new IAResponseDTO(
            prompt: $prompt,
            instructions: $instructions,
            response: $response->choices[0]->message->content,

            nbInputTokens: $response->usage->promptTokens,
            nbOutputTokens: $response->usage->completionTokens,
        );
    }
}

Le client Mistral AI (code complètement inventé, aucun lien avec un quelconque package existant) :

class IAClientMistralAI implements IAClientInterface
{
    // Modèles
    public const MODEL_7B = 'open-mistral-7b';
    public const MODEL_8X7B = 'open-mixtral-8x7b';

    private Client $client;

    public function __construct(
        private string $model = self::MODEL_7B
    ) {
        $this->client = MistralAI::client(getenv('MISTRALAI_API_KEY')); // Clé disponible dans le fichier .env
    }
    
    public function response(array $params): IAResponseDTO
    {
        $response = $client->chat()->prompt([
	          'model' => $this->model,
	          ...$params // Opérateur de décomposition
        ]);
        
        // Récupération de $prompt et $instructions comme précédemment
    
        // Construction du DTO
        return new IAResponseDTO(
            prompt: $prompt,
            instructions: $instructions,
            response: $response->result,

            // Etc. mapping à faire selon le retour de l'API
            nbInputTokens: $response->tokens->input,
            nbOutputTokens: $response->tokens->output,
        );
    }
}

Rappel : le code ci-dessus est complètement inventé.

Et enfin dans mon programme principal :

// Appel à OpenAI
$ia = new IA(new IAClientOpenAI);
$ia->newsAboutALanguage($user);

// Appel à MistralAI
$ia = new IA(new IAClientMistralAI);
$ia->newsAboutALanguage($user);

Cette organisation du code permet de ne pas avoir à répliquer la ou les logiques de gestion des prompts, il y a à chaque fois une classe intermédiaire qui sera chargée à la fois d’appeler l’API de la bonne façon et également de traiter la réponse reçue pour avoir une réponse homogène peu importe l’API appelée.

Si besoin de réviser quelques concepts techniques :

Et n’hésite pas à nous suivre pour être au courant du prochain article 😉


Qui a codé ce superbe contenu ?

Keep learning

Autres contenus à découvrir


Ta newsletter chaque mois

Corrigés, challenges, actualités, veille technique... aucun spam.