From deb77393a20979a1fbc891bfde4eda7826d45781 Mon Sep 17 00:00:00 2001 From: gerard-g Date: Thu, 30 Jan 2025 00:21:17 +0100 Subject: [PATCH 1/2] feat: add IA langchain-ollama article --- _posts/2024-04-24-webrtc.md | 4 - .../2026-01-29-ai-with-llama-and-langchain.md | 369 ++++++++++++++++++ assets/images/chatbot-ollama/app-vue.png | Bin 0 -> 12492 bytes .../chatbot-ollama/interface-client.png | Bin 0 -> 23640 bytes 4 files changed, 369 insertions(+), 4 deletions(-) create mode 100644 _posts/2026-01-29-ai-with-llama-and-langchain.md create mode 100644 assets/images/chatbot-ollama/app-vue.png create mode 100644 assets/images/chatbot-ollama/interface-client.png diff --git a/_posts/2024-04-24-webrtc.md b/_posts/2024-04-24-webrtc.md index 0d4a84c..71d605a 100644 --- a/_posts/2024-04-24-webrtc.md +++ b/_posts/2024-04-24-webrtc.md @@ -4,8 +4,6 @@ title: Introduction au WebRTC categories: webrtc, vue3, node, express, socket.io --- -{% raw %} - Nous partageons ici une introduction au WebRTC avec un exemple d'implémentation utilisant **Node.js** et **Vue 3**. - [Introduction](#introduction) @@ -593,5 +591,3 @@ N'oubliez pas que pour que cela fonctionne autrement qu'avec **localhost**, votr - [simple-signal-server](https://github.com/feross/simple-signal-server) - [Vue 3](https://v3.vuejs.org/) - [Projet github du tutoriel](https://github.com/gerard-g-dm/vue-web-rtc) - -{% endraw %} \ No newline at end of file diff --git a/_posts/2026-01-29-ai-with-llama-and-langchain.md b/_posts/2026-01-29-ai-with-llama-and-langchain.md new file mode 100644 index 0000000..529cd4f --- /dev/null +++ b/_posts/2026-01-29-ai-with-llama-and-langchain.md @@ -0,0 +1,369 @@ +--- +author: Gwenolé +title: Comment faire tourner une IA en local avec langchain, Llama, Node.js et Vue 3 +categories: langchain Vue 3 Node.js llama ollama +--- + +Nous partageons ici un tutoriel pour faire fonctionner simplement une **intelligence artificielle** avec **langchain**, **Llama**/**DeepSeek**, **Node.js** et **Vue 3**. + +- [Introduction](#introduction) +- [Installation](#installation) + - [Création d'un environnement virtuel python](#python) + - [Installation du modèle d'IA](#ia) + - [Installation d'Ollama](#ollama) + - [Installation de Llama](#llama) + - [Installation de DeepSeek](#deepseek) +- [Création du chat](#chat) + - [Mise en place du serveur Node.js](#node) + - [Mise en place du client Vue 3](#vue3) +- [Ressources](#ressources) + +## Introduction + +Ce tutoriel a pour but de voir comment l'on peut assez facilement implémenter une **intelligence artificielle** dans nos projets. On prendra ici **Vue 3** pour la partie client et **Node.js** pour la partie serveur, mais ce tutoriel peut assez simplement être adapté à d'autres outils (surtout pour la partie client si vous utilisez également **JavaScript** ou **TypeScript**). + +Nous n'aborderons pas ici l'enrichissement du contexte ni l'amélioration du **LLM** (Large Language Model, votre modèle d'IA en résumé). + +## Installation + +### Création d'un environnement virtuel python + +Pour pouvoir faire tourner Llama, il nous faudra tout d'abord un environnement avec Python 3.10 d'installé. Pour cela nous allons créer un environnement virtuel. Les étapes qui suivent sont les étapes à suivre si vous êtes sur Ubuntu mais seront assez semblables sur d'autres distributions. + +- Commençons par mettre à jour les packages disponibles pour notre distribution avec `sudo apt update`. +- Vous pouvez lancer `sudo apt upgrade` pour mettre à jour tous les packages ou seulement mettre à jour le package python. +- Vous pouvez vérifier la version de python3 via `python3 --version`. +- Si la version de **python3** est toujours inférieure à 3.10 après la mise à jour (ou si vous avez eu une erreur en voulant faire la mise à jour manuellement pour ce package), alors il vous faudra ajouter le répertoire "Deadsnakes" aux répertoires de packages de votre distribution. Pour cela: + - Il nous faut d'abord pouvoir ajouter un répertoire via la commande **add-apt-repository**. Pour cela une autre installation s'impose: `sudo apt install software-properties-common -y`. + - Ensuite on ajoute le répertoire "Deadsnakes": `sudo add-apt-repository ppa:deadsnakes/ppa`. + - On refait une mise à jour des packages disponibles: `sudo apt update -y`. + - On installe **python3.10**: `sudo apt install python3.10 -y`. +- Naviguez ensuite dans le dossier dans lequel vous souhaitez créer votre environnement virtuel et lancez la commande : `python3.10 -m venv ./`. + +### Installation du modèle d'IA + +#### Installation d'Ollama + +Nous allons installer Ollama qui simplifie le déploiement et la gestion des modèles. +Ollama permet de télécharger et d'exécuter un nombre important de modèles LLM, notamment les LLM Llama et DeepSeek que nous détaillerons dans ce tutoriel. +Ollama permet également de créer son propre modèle mais cette fonctionnalité ne sera pas abordée dans ce tutoriel. + +Nous allons activer notre environnement virtuel puis télécharger le modèle: + +- À la racine du dossier de votre environnement virtuel lancez la commande: `source bin/activate`. +- Installez Ollama: `curl -fsSL https://ollama.com/install.sh | sh`. + +#### Installation de Llama + +Nous utiliserons dans notre cas la version Llama 3.2 lightweight 1B: +- Téléchargez et lancez la version Llama qui correspond à votre demande sur le site de [Llama](https://www.llama.com/): `ollama run llama3.2:1b` dans notre exemple. Le modèle sera lancé par défaut sur `localhost:11434`. + +Nous avons maintenant notre modèle Llama opérationnel dans notre environnement virtuel, nous pouvons désormais créer notre chat et le connecter à notre modèle. + +#### Installation de DeepSeek + +Pour utiliser DeepSeek, c'est encore plus simple ! Pas besoin de faire de demande de téléchargement ici, lancez simplement **ollama** avec la version **DeepSeek** que vous souhaitez. Attention tout de même, la version lightweight de **DeepSeek** est plus conséquente que celle de Llama (pour notre version **deepseek-v2:lite** il vous faudra compter 8,9 Go par exemple) : `ollama run deepseek-v2:lite` + +## Création du chat + +### Mise en place du serveur Node.js + +**Si vous faites ce tutoriel en plusieurs fois, pensez bien à réactiver votre environnement virtuel !** + +On pourrait directement brancher notre client avec Ollama, mais il est intéressant d'avoir un serveur **Node.js** pour implémenter par la suite un contexte côté API pour enrichir les réponses de notre IA. + +Créez un répertoire pour votre serveur **Node.js**. Nous l'appellerons dans ce tutoriel **node-server** tout simplement. + +Initiez un projet dans ce répertoire: +{% include code-header.html %} +```sh +npm init +``` + +Installons ensuite les dépendances Ollama et langchain dont nous aurons besoin: +{% include code-header.html %} +```sh +npm i --save langchain @langchain/core @langchain/ollama +``` + +Nous allons également utiliser express: +{% include code-header.html %} +```sh +npm i --save express +``` + +Pour faciliter la communication, nous allons installer **cors**: +{% include code-header.html %} +```sh +npm i --save cors +``` + +Créons le fichier **ai-model.js** dans lequel nous aurons le fonctionnement pour requêter le service **Ollama**: +{% include code-header.html %} +```js +import { Ollama } from "@langchain/ollama"; + +export class AI { + aiModel; + // remplacer llama3.2:1b par votre modèle, deepseek-v2:lite dans notre exemple avec DeepSeek + constructor(model = "llama3.2:1b", baseUrl = "localhost:11434") { + this.aiModel = new Ollama({ model, baseUrl }); + } + async chatMessage(req, res) { + try { + // On récupère ici le message de l'utilisateur envoyé par le client + const message = req.body.message; + // La réponse complète d'une IA peut être streamée pour éviter un trop long temps d'attente + // On pourra ainsi afficher côté client la réponse de l'IA au fur et à mesure de sa complétion + const responseStream = await this.aiModel.stream(message); + // Pour chaque morceau de réponse reçu, on écrit ce morceau + for await (const chunk of responseStream) { + res.write(chunk); + } + // Lorsque la réponse est complète, on met fin à la requête + res.end(); + } catch (error) { + console.error("error: ", error); + res.status(500).json({ error: error.message }).send(); + } + } +} +``` + +Ensuite dans le fichier **index.js** déjà créé à l'initialisation, nous allons créer une route pour appeler notre fonction **chatMessage**: +{% include code-header.html %} +```js +import { AI } from "./ai-model.js"; +import cors from "cors"; +import express from "express"; + +const app = express(); +app.use(cors()); +const port = 3000; +const aiModel = new AI(); + +app.get("/", (req, res) => { + res.send("Node.js server is running"); +}); + +app.post("/message", (req, res) => { + return aiModel.chatMessage(req, res); +}); + +app.listen(port, () => { + console.log(`Server is running on http://localhost:${port}`); +}); +``` + +Attention cependant à ne pas laisser les **CORS** sans restriction après vos tests ! + +### Mise en place du client Vue 3 + +**Si vous faites ce tutoriel en plusieurs fois, pensez bien à réactiver votre environnement virtuel !** + +Nous allons ici utiliser **Vue 3** avec la **composition API**. + +Tout d'abord commençons par installer le projet **Vue 3** qu'on nommera vue-client (je vous laisse choisir les options que vous souhaitez pour le projet, mais nous continuerons le tutoriel en **TypeScript**, et on utilisera pas le routing puisque l'on aura une seule page): +{% include code-header.html %} +```sh +npm create vue@latest +``` + +Nous allons aussi installer **marked** qui nous permettra de transformer les réponses **markdown** en **html** : +{% include code-header.html %} +```sh +npm i --save marked +``` + +On suit ensuite les instructions pour tester l'installation : +{% include code-header.html %} +```sh +npm i +npm run format +npm run dev +``` + +Allez ensuite sur l'url affiché dans la console pour vérifier que vous avez bien accès au client. + +On supprime les composants existants et on crée notre composant **Chatbot.vue**. + +Ici on ne détaillera pas les styles, si vous souhaitez vous inspirer de ceux présents dans les aperçus, vous pouvez les récupérer dans les [sources du projet GitHub](https://github.com/gerard-g-dm/langchain-ollama-vue-node) + +On commence par créer le template avec une zone pour l'affichage de la discussion et une zone de saisie de texte: +{% include code-header.html %} +```vue + +``` + +On change ensuite le **App.vue** générée lors de la création du projet pour implémenter le composant **Chatbot.vue**: +{% include code-header.html %} +```vue + + + +``` + +Vous devriez obtenir ceci: + +![Visualisation App.vue](/assets/images/chatbot-ollama/app-vue.png) + +On retourne maintenant compléter **Chatbot.vue**. + +On va créer le script avec en variables: + +- **apiUrl**: l'url du serveur **Node.js** +- **apiPort**: le port du serveur **Node.js** +- **userInput**: la saisie utilisateur (variable réactive) +- **currentIAResponse**: la réponse en cours de notre IA (variable réactive) +- **chatHistory**: l'historique de la discussion (variable réactive) + +La variable **chatHistory** sera un tableau d'objet avec deux propriétés: + +- **sender**: permettra de déterminer si le message vient de l'utilisateur ou de l'IA +- **message**: le message associé à cette entrée dans le chat + +Nous allons initialiser **chatHistory** avec un premier message de l'IA également. + +Nous allons également créer une fonction **sendMessage** qui dans un premier temps se contentera d'ajouter le message de l'utilisateur dans **chatHistory** (la fonction est asynchrone pour la suite de l'implémentation). + +On aura donc pour le moment comme script ceci: +{% include code-header.html %} +```vue + +``` + +Nous complètons maintenant le template pour afficher **chatHistory** et **currentIAResponse** (même si ce dernier n'est pas encore modifié pour le moment) et ajouter une classe en fonction de l'expéditeur: +{% include code-header.html %} +```vue + +``` + +Si vous retournez sur l'application et que vous tapez un message, vous devriez obtenir ce résultat: + +![Interface client](/assets/images/chatbot-ollama/interface-client.png) + +Complètons à présent la fonction **sendMessage** pour requêter notre serveur **Node.js** et afficher la réponse de notre IA! +{% include code-header.html %} +```vue + +``` + +Vous devriez maintenant pouvoir poser vos questions dans l'interface et voir votre IA vous répondre ! + +## Ressources + +[Site officiel de Llama](https://www.llama.com/) +[Site officiel d'Ollama](https://ollama.com/) +[Documentation JavaScript pour langchain](https://js.langchain.com/docs/introduction/) +[Documentation Node.js](https://nodejs.org/docs/latest/api/) +[Documentation Vue 3](https://vuejs.org/guide/introduction.html) +[Projet github du tutoriel](https://github.com/gerard-g-dm/langchain-ollama-vue-node) diff --git a/assets/images/chatbot-ollama/app-vue.png b/assets/images/chatbot-ollama/app-vue.png new file mode 100644 index 0000000000000000000000000000000000000000..a5017e92ad7895665cd6b315a50e853bd29c5d43 GIT binary patch literal 12492 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 literal 0 HcmV?d00001 diff --git a/assets/images/chatbot-ollama/interface-client.png b/assets/images/chatbot-ollama/interface-client.png new file mode 100644 index 0000000000000000000000000000000000000000..856a9d7a511374625ea4b8e489121f6bc26b3085 GIT binary patch literal 23640 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 literal 0 HcmV?d00001 From d7d9e9e30392487b66544d68906b2bc83d3bbff7 Mon Sep 17 00:00:00 2001 From: gerard-g Date: Sat, 31 Jan 2026 17:14:03 +0100 Subject: [PATCH 2/2] fix: missing font-size for h4,h5,h6 --- _sass/_post.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/_sass/_post.scss b/_sass/_post.scss index 833d7f8..3ce6aca 100644 --- a/_sass/_post.scss +++ b/_sass/_post.scss @@ -80,6 +80,10 @@ article.post-content { color: $third; } + h4, h5, h6 { + font-size: large; + } + blockquote { padding: 0.75rem; border-left: 5px $gray-200 solid;