T: / Articles techniques / 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 :
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
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é.
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 :
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 :
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 :
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 :
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 :
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 ...
}
}
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 :
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.
OpenAI et GPT4 c’est très bien, mais il y a d’autres modèles disponibles. Quelques un en vrac :
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é ?
Je dois donc créer ces éléments :
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 😉
Other content to discover