Utiliser l’API d’OpenAI avec Symfony

Quentin Dequippe
4 min readJan 2, 2023

--

Dans le cadre de mon side projet de job board dédié à Symfony (https://jobbsy.dev) j’ai eu le besoin de classifier automatiquement des offres d’emploi afin de faciliter, par la suite, la recherche.

Ce projet est un agrégateur de jobs via des sources externes qui ne classifient pas les offres d’emploi d’un point de vue technique (car génériques), l’idée est donc d’enrichir ces données via la description fournie et si possible automatiquement (on le sait nous les devs on on aime bien automatiser 🙂).

Actuellement il y a une forte tendance du côté de l’intelligence artificielle “IA” ou “AI” en anglais. Une société fait beaucoup parler d’elle à ce sujet : OpenAI dont l’un des fondateurs n’est autre qu’Elon Musk. OpenAI a mis au point une IA (GPT) capable dans une première version d’écrire des articles de presse et des oeuvres de fiction, puis par la suite d’autres permettant par exemple de générer des images (DALL-E) ou encore de converser avec un humain (ChatGPT). C’est même leur IA derrière Github Copilot !

La plupart de ces algorithmes (car disons le ca reste des algorithmes ☺️) sont disponibles via des APIs et nous allons les utiliser.

S’amuser dans le terrain de jeux

Avant de passer à l’intégration découvrons tout d’abord le fonctionnement de ce système.

OpenAI propose de nombreux exemples pour divers besoins (classification, génération de code, conversation etc) ainsi qu’un outil permettant de tester les demandes faites à l’IA le “playground”.

Pour notre besoin nous allons nous baser sur un exemple existant permettant d’extraire des mots clés d’un texte https://beta.openai.com/examples/default-keywords :

Extract keywords from this text:

...

Que nous allons adapter à notre besoin :

Extract maximum 5 tech keywords separated by comma from this text:

...

Ceci va définir ce que nous allons envoyer à l’IA, le “prompt”.

D’autres paramètres peuvent être envoyés comme le model ou encore la température, pour en savoir plus regardez du côté de la documentation.

Mettons les mains dans le cambouis

La requête à faire est plutot simple, un header pour la sécurité (et pour l’usage surtout car l’utilisation est payante), un payload contenant les paramètres vus plus haut le tout en POST et en json :

curl https://api.openai.com/v1/completions \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-d '{
"model": "text-davinci-003",
"prompt": "Extract maximum 5 tech keywords separated by comma from this text: ...",
"max_tokens": 30,
"temperature": 0.8
}'

Pour obtenir et générer une clé d’API tout est la aussi dans la documentation.

Pour implémenter cette requete dans notre projet nous allons utilisé le HTTP Client de Symfony, commencons par un peu de config afin de créer un client HTTP dédié à l’API d’OpenAI :

// config/packages/framework.php

<?php

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Config\FrameworkConfig;
use function Symfony\Component\DependencyInjection\Loader\Configurator\env;

return static function (FrameworkConfig $config, ContainerConfigurator $containerConfigurator): void {
$config->httpClient()
->scopedClient('openai.client')
->baseUri('https://api.openai.com/v1/')
->authBearer(env('OPENAI_API_KEY'));
};

Puis un service permettant de communiquer avec l’API utilisant le client HTTP configuré ci-dessus qui est mis à disposition automatiquement (merci à l’autowiring de Symfony 💖) :

<?php

namespace App\OpenAI;

use App\OpenAI\Model\CompletionRequest;
use Symfony\Contracts\HttpClient\HttpClientInterface;

final readonly class Client
{
public function __construct(private HttpClientInterface $openaiClient)
{
}

public function completions(string $model, string $prompt, float $temperature): array
{
$response = $this->openaiClient->request('POST', 'completions', [
'json' => [
'model' => $model,
'prompt' => $prompt,
'temperature' => $temperature,
],
]);

if (200 !== $response->getStatusCode()) {
return [];
}

return $response->toArray(false);
}
}

Et finalement l’utilisation de ce service (ici dans un handler Messenger par exemple) :

<?php

namespace App\MessageHandler\Job;

use App\Message\Job\ClassifyMessage;
use App\OpenAI\Client;
use App\Repository\JobRepository;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler]
final readonly class ClassifyHandler
{
public function __construct(
private Client $openAIClient,
private JobRepository $jobRepository,
#[Autowire('%env(OPENAI_API_COMPLETION_MODEL)%')]
private string $model
) {
}

public function __invoke(ClassifyMessage $message): void
{
$job = $this->jobRepository->find($message->jobId);

if (null === $job) {
return;
}

$prompt = sprintf(
'Extract maximum 5 tech keywords separated by comma from this text: %s',
$job->getDescription()
);
$result = $this->openAIClient->completions($this->model, $prompt, 0.8);

if (false === isset($result['choices'][0]['text'])) {
return;
}

$keywords = array_filter(array_map('trim', explode(',', $result['choices'][0]['text'])));

$job->setTags($keywords);

$this->jobRepository->save($job, true);
}
}

Le “model” injecté ici est celui qui est le plus puissant : Davinci (mais aussi le plus couteux 🤑). L’API renvoie une liste de choix contenant le texte généré par l’IA en réponse à notre demande.

Ce texte est ensuite traité pour en extraire une liste de catégories (tags ici).

Et voila 🎉

Et au final ca marche ?

Le code est en production, vous pouvez le retrouver ici https://github.com/jobbsy-dev/jobbsy.

Après quelques jours le résultat est plutot concluant, dans 90% des cas cela fonctionne bien, les catégories générées correspondent bien à la description de l’offre. Quelques ajustements restent à faire notamment sur le paramètre “max_token” qui définit le nombre maximum de “caractères” (token) que doit renvoyer l’algorithme, parfois le texte généré est tronqué.

Merci d’avoir lu cet article. Au plaisir d’échanger sur Twitter ou LinkedIn.

P.S : et non cet article n’a pas été généré via une IA 😋

--

--

Quentin Dequippe

Lead developer PHP/Symfony · Freelance · Creator of @jobbsy-dev and open source enthusiast