Alsacreations.com - Apprendre - Archives (octobre 2024)

Les dernières ressources publiées sur Alsacreations.com

Le: 02 10 2024 à 10:30 Auteur: Rodolphe

Nous n'avons pas fini d'entendre parler de les LLM (grands modèles de langages) et de l'Intelligence Artificielle. Outre les sytèmes en ligne (souvent payants) il est possible d'interroger un modèle local (gratuitement) par l'intermédiaire d'un peu de JavaScript.

Pour ceci nous utiliserons :

Ollama

Ollama

Ollama est une application disponible pour Linux, macOS, Windows qui sert d'interface de gestion de LLM. Voyons la comme une sorte de Docker qui ira piocher dans un catalogue d'images disponibles en ligne, faciles à télécharger et à exécuter en une seule instruction ou presque en précisant bien le nom du modèle souhaité.

Les commandes essentielles après avoir téléchargé et installé Ollama :

  • ollama list liste les modèles déjà téléchargés
  • ollama pull <modèle> télécharge un nouveau LLM
  • ollama run <modèle> exécute
  • ollama stop <modèle> met fin à l'exécution
  • ollama rm <modèle> supprime

Pour l'occasion, nous utiliserons llama 3.2

ollama pull llama3.2

Pour préciser une autre version du modèle avec nombre de paramètres (comprenez complexité et poids) différent, on pourra par exemple indiquer ollama pull llama3.2:1b pour 1B soit un milliard de paramètres.

Jusque-là si tout va bien, nous pouvons d'ores et déjà discuter en mode texte brut par un ollama run llama3.2.

Package JavaScript Ollama

Cette bibliothèque nous permet d'aller interroger Ollama installé localement en définissant le modèle, le message à lui envoyer et en traitant la réponse. De manière très basique on peut se servir de console.log mais ce n'est pas très intéressant car bloquant jusqu'à obtenir la totalité de la réponse ; la promptitude du modèle dépendra aussi de la puissance de votre machine et de votre mémoire vive disponible.

À l'aide d'un environnement Node.js (déjà installé n'est-ce pas ?), nous pouvons poursuivre.

  1. Créer un dossier quelconque 📁 pour notre projet.

  2. Installer la dépendnace avec npm install --save ollama (ou avec pnpm)

npm install ollama

  1. Écrire un petit script kiwi.mjs pour importer ollama et appeler chat() afin de lancer la discussion
import ollama from 'ollama'

const response = await ollama.chat({
  model: 'llama3.2',
  messages: [{ role: 'user', content: 'Quelle est la recette du cake au kiwi ?' }],
})
// ⚠️ Ceci peut prendre beaucoup de temps car on attend la réponse complète
console.log(response.message.content)

Il suffira de l'exécuter en ligne de commande avec node kiwi.mjs.

Exécution d'ollama dans le terminal

Pour streamer la réponse, c'est-à-dire la restituer au fur et à mesure de l'ajout de mots par le LLM, on peut se servir de l'alternative en activant l'option stream.

import ollama from 'ollama'

const message = { role: 'user', content: 'Quelle est la recette du cake au kiwi ?' }
const response = await ollama.chat({ model: 'llama3.2', messages: [message], stream: true })
for await (const part of response) {
  process.stdout.write(part.message.content)
}

Un bon nombre d'autres paramètres et méthodes existent dans cette interface, il suffira de consulter la documentation pour les découvrir.

Et en application web ?

C'est possible ! Une page interrogera le modèle via ollama :

Architecture d'application web utilisant l'IA

Pour transformer le tout en une petite application web utilisable dans le navigateur...

  1. Nous ajoutons express
npm install --save express

Ce qui va permettre de construire un couple client/serveur minimaliste avec deux fichiers :

  • server.mjs, qui sera à l'écoute des messages
  • public/index.html, qui contiendra un formulaire et affichera le résultat

Les explications sont fournies par des commentaires dans le code source suivant, à vous de jouer en vous l'appropriant.

  1. Fichier server.mjs
import express from 'express';
import ollama from 'ollama';

const app = express(); // Instance d'Express
const port = 3000; // Port à l'écoute

app.use(express.json());

// On sert le dossier public en statique, dans lequel on place notre page index.html
app.use(express.static('public'));

// On accepte les requêtes POST vers /chat
app.post('/chat', async (req, res) => {
  // Notre message sera envoyé dans le corps de la requête (body)
  const message = { role: 'user', content: req.body.content };

  // La réponse provenant du LLM est une promesse
  const response = await ollama.chat({ model: 'llama3.2', messages: [message], stream: true });

  // La réponse envoyée à la page web dispose d'en-têtes HTTP
  // ... permettant de faire persister la connexion
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });

  // Pour toute portion de réponse reçue, on la stream
  for await (const part of response) {
    res.write(`data: ${JSON.stringify(part.message)}\n\n`);
  }

  res.end();
});

// On écoute sur le port configuré
app.listen(port, () => {
  console.log(`Serveur en écoute : http://localhost:${port}`);
});
  1. Page public/index.html correspondante :
<!doctype html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>KiwIA</title>
    <style>
    /* à personnaliser selon vos envies */
    body { font-family: system-ui; background: #222; color: #fff; padding: 2rem; }
    #chat, [type=submit] { padding: 1rem; border-radius: 0.5rem; border: 1px solid #ccc; margin: 1rem 0; background: inherit; color: inherit; }
    #chat { min-width: 20rem; font-size: inherit; }
    #reponse { text-align: left; line-height: 2; }
    </style>
</head>
<body>
<h1>Chat KiwIA 🥝</h1>

<form>
    <input type="text" id="chat" placeholder="Votre message...">
    <button type="submit">Envoyer</button>
</form>
<div id="reponse"></div>

<script>
    const form = document.querySelector('form');
    const input = document.querySelector('#chat');
    const resultat = document.querySelector('#reponse');

    // À la validation du formulaire
    form.addEventListener('submit', async (e) => {
        e.preventDefault();

        // On récupère le contenu du message
        const content = input.value.trim();
        if (!content) return; // Si c'est vide on arrête là

        // Petit message d'attente
        resultat.textContent = 'Un instant et je suis à vous...';
        // On vide le champ d'entrée
        input.value = '';

        try {
            // Requête asynchrone en POST vers /chat
            const response = await fetch('/chat', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                // Corps de la requête en JSON
                body: JSON.stringify({ content }),
            });

            resultat.textContent = '';

            // On instancie une interface ReadableStream
            const reader = response.body.getReader();
            const decoder = new TextDecoder();

            // Tant qu'on a du contenu...
            while (true) {
                const { done, value } = await reader.read(); // On lit
                if (done) break;

                const lines = decoder.decode(value).split('\n');

                // On itère sur la réponse reçue pour alimenter le résultat
                for (const line of lines) {
                    if (line.startsWith('data: ')) {
                        const data = JSON.parse(line.slice(6));
                        resultat.textContent += data.content;
                    }
                }
            }
        } catch (error) {
            console.error('Error:', error);
            resultat.textContent = 'Une erreur est survenue.';
        }
    });
</script>

</body>
</html>
  1. On lance le tout grâce à node server.mjs et on consulte l'adresse indiquée dans le navigateur pour atteindre la page HTML qui affiche le formulaire.

On peut perfectionner le rendu en mettant en page le message renvoyé sous forme de markdown (mais cela dépend du modèle interrogé) plutôt que de l'afficher en texte brut.

Perspectives

On peut voir que construire une application web sollicitant un LLM est tout à fait envisageable avec les technologies d'aujourd'hui et les standards déjà en place (HTML/CSS, JavaScript, fetch, Streams API, etc). À partir de là tout est possible pour imaginer élaborer des interfaces qui vont dialoguer en texte clair ou par d'autres moyens plus subtiles, et réagir en conséquence. Vous pouvez aussi briefer votre LLM en amont et lui donner des instructions ou un contexte de réponse, voire en construire un nouveau (avec l'instruction FROM de Ollama).

Pour pousser l'horizon encore plus loin, faire tourner des LLM dans le navigateur lui-même est possible grâce à WebGPU (voir une démo ici : Qwen-2.5 on WebGPU sur Huggingface) avec une performance tout à fait honorable.

Les concepteurs de navigateurs préparent des interfaces aisées d'accès en JavaScript pour interroger un modèle local, tels que Google avec l'IA Gemini intégrée dans Chrome. Nous n'avons pas fini d'en entendre parler.

Retrouvez l'intégralité de ce tutoriel en ligne sur Alsacreations.com