domicile
naviguer_suivant
Blog
naviguer_suivant

Comment construire des logiciels avec des LLM (2ème partie)

Comment construire des logiciels avec des LLM (2ème partie)
Définir ce qu'est une unité de calcul et apprendre à construire un système logiciel du monde réel en suivant ce nouveau paradigme de calcul.
Comment construire des logiciels avec des LLM (2ème partie)

par Iulian Serban

Les LLM en tant qu'unités de calcul et le flux d'informations 

Dans le dernier billet de blog, nous avons soutenu que les LLM sont un nouveau type d'unité de calcul, une unité de calcul à usage général. Nous avons discuté de leurs propriétés principales et émergentes et observé à quel point ils sont différents de tous les autres outils du monde de l'ingénierie logicielle. Nous avons conclu qu'ils constituent un paradigme de calcul fondamentalement nouveau, qui nécessite désormais un nouvel ensemble de cadres et de modèles de conception pour la construction de logiciels.

Dans cet article de blog, nous allons approfondir la question et commencer par définir ce qu'est une unité de calcul. Nous verrons ensuite comment construire un système logiciel réel en suivant ce nouveau paradigme de calcul.

Les LLM étant des unités de calcul autonomes, commençons par cette couche d'abstraction. Généralisons le concept d'unité de calcul de manière à ce qu'il englobe également le développement de logiciels traditionnels et les systèmes de type "human-in-the-loop" (HITL).

Supposons qu'une unité de calcul puisse être n'importe quel opérateur autonome et exécutable qui transforme une séquence de bits (données d'entrée) en une autre séquence de bits (données de sortie) :

- LLMs
- Base de données vectorielle (par exemple, traitement des données d'entrée et retour des k exemples les plus similaires dans une base de données)
- Fonctions Python (par exemple, mise en correspondance d'une liste de variables d'entrée avec des objets de retour)
- API de microservices (par exemple, mise en correspondance d'une requête d'entrée avec un objet de retour)
- Humains (par exemple, mise en correspondance des données d'entrée et retour des données de sortie)
- Groupes d'humains (par exemple, mise en correspondance des données d'entrée et retour des données de sortie)
- ...

Il s'agit d'une définition très générale. Selon cette définition, même une chaîne d'unités constituerait une unité de calcul. Par exemple, deux LLM couplés en chaîne constituent également une unité de calcul.

Par souci de simplicité, supposons en outre que chaque unité de calcul est sans état et idempotente. Étant donné la même entrée, l'unité de calcul produira toujours la même sortie. Dans ce cadre, les données avec état peuvent être modélisées comme des entrées dans une unité de calcul donnée (par exemple, la ligne d'une base de données est donnée en entrée à l'unité de calcul, mais si la même ligne est donnée en entrée, la fonction produira la même sortie). De même, le caractère aléatoire peut être modélisé comme une entrée de l'unité de calcul (par exemple, une graine aléatoire peut être donnée en entrée à l'unité de calcul, et avec la même graine aléatoire et la même entrée, on s'attend à ce qu'elle renvoie toujours la même sortie).

Ce cadre nous permet de définir des unités de calcul et d'en parler, mais comment les relier entre elles pour construire des systèmes logiciels ?

Chaque unité de calcul reçoit en entrée une séquence de bits et émet une autre séquence de bits. Par conséquent, la connexion (ou le "lien") entre les unités de calcul représente simplement le flux de bits entre elles (le "flux d'informations" ou le "flux de données").

N'oublions pas que nous opérons dans un cadre probabiliste où les données d'entrée à un moment donné du système peuvent comporter des éléments aléatoires tels que des erreurs aléatoires. En outre, de nouvelles informations sont générées dans l'ensemble du système à mesure que différentes entrées sont traitées par différentes unités de calcul. Plus le nombre de calculs exécutés est élevé, plus le système génère de nouvelles informations.

La meilleure décision possible sur la manière de relier une paire d'unités de calcul ne peut être prise que lorsque le système dispose d'un maximum d'informations. En d'autres termes, la décision de relier deux paires d'unités de calcul doit être prise au dernier moment avec le maximum d'informations disponibles.

Cela conduit à la contrainte supplémentaire que chaque unité de calcul du système doit produire à la fois 1) une séquence de bits (données de sortie) et 2) l'unité de calcul suivante à exécuter. Il s'agit d'une approche très générale et puissante, car elle permet à chaque unité de calcul de modifier le flux d'informations dans le système. En d'autres termes, le système possède des liens probabilistes.

Permettez-moi d'illustrer mon propos par un exemple concret. Supposons que je sois épuisé par les innombrables spams et que je veuille les filtrer. Et disons que je n'aime pas consulter mon courrier électronique plusieurs fois par jour, mais que je reçois parfois des courriels importants et urgents auxquels je dois répondre de toute urgence.

Je peux créer un assistant de messagerie pour résoudre ce problème !

Je réfléchis au problème posé et je puise dans ma boîte à outils d'unités de calcul pour le résoudre !

Tout d'abord, nous recevons un courrier électronique. Comment le système doit-il le traiter ? La question importante est de savoir s'il s'agit d'un courrier indésirable à ignorer, d'un courrier normal (non indésirable) à lire plus tard ou d'un courrier urgent (non indésirable) à lire maintenant. Si nous connaissons la réponse à cette question, le reste du comportement du système sera simple à mettre en œuvre.

Les LLM sont des unités de calcul à usage général et devraient pouvoir répondre à cette question. Nous allons donc prendre le courrier électronique entrant et le donner en entrée à une unité de calcul qui est un LLM. Nous demanderons au LLM de classer le courrier électronique en tant que spam ou non. Il s'agit d'un problème bien défini et nous pouvons facilement vérifier s'il est capable de distinguer les courriels de spam de ceux qui ne le sont pas. Sur la base de ses résultats, ce LLM décidera ensuite de la destination des informations (c'est-à-dire de l'unité de calcul suivante à exécuter).

S'il s'agit d'un courrier indésirable, nous devons demander au client de messagerie de le supprimer. Cette partie est tellement triviale que nous pouvons écrire une petite fonction Python pour le faire à notre place. Étant donné l'identifiant de l'e-mail, il faut supprimer l'e-mail correspondant. Dans ce cas, l'unité de calcul est simplement une fonction Python déterministe.

S'il ne s'agit pas d'un courrier indésirable, la question suivante est de savoir s'il s'agit d'un courrier important à lire maintenant. Il s'agit également d'une tâche bien définie, qui peut être traitée par un LLM en tant qu'unité de calcul. Nous demanderons au LLM de classer l'e-mail comme important ou non important. Bien que bien définie, cette tâche est beaucoup plus complexe que de détecter s'il s'agit d'un spam ou non. Le fait qu'un courriel soit important ou non dépendra de mes préférences personnelles (par exemple, l'identité de l'expéditeur, le sujet traité, etc.) ). Il dépend également du contexte (par exemple, le jour et l'heure, les autres réponses dans le même fil de discussion et le fait qu'il soit lié à d'autres courriels). Nous pouvons appliquer un certain nombre de modèles de conception pour résoudre ce problème, notamment :

- Ingénierie de l'invite LLM pour comprendre mes préférences personnelles et le contexte:
- Par exemple, ajout dans l'invite LLM de mes contacts les plus importants
- Par exemple, ajout dans l'invite LLM des sujets les plus importants pour moi
- Apprentissage à petite échelle basé sur des exemples d'autres courriels urgents
- Affinage du LLM avec des exemples d'autres courriels urgents
- Application du raisonnement de la chaîne de pensée (CoT) pour décider s'il s'agit d'une urgence ou non

Nous aborderons tous ces modèles de conception ultérieurement, mais pour l'instant, considérons qu'il s'agit d'une unité de calcul unique et autonome.

Sur la base de sa classification de l'e-mail, ce deuxième LLM décidera alors de la destination des informations. S'il ne s'agit pas d'un courriel important, le système ne doit rien faire et s'arrêter.

S'il s'agit d'un courriel important, l'information doit être transmise à une autre unité de calcul qui me préviendra d'une manière ou d'une autre. Disons que je veux toujours être notifié sur Slack lorsqu'un courriel important arrive afin de pouvoir y répondre rapidement. Dans ce cas, cette dernière unité de calcul pourrait être une autre petite fonction Python. Cette fonction Python générera une requête pour un client Slack et lui demandera de m'envoyer un message Slack m'informant de l'arrivée d'un courriel important.

Voici l'organigramme d'un tel système :

Comme vous pouvez le voir ci-dessus, les unités de calcul sont représentées par des boîtes, les composants externes par des cercles et le flux d'informations par des flèches.

‍Important, le flux d'informations est lui-même probabiliste. Le premier LLM peut avoir une probabilité de 90 % de classer correctement les courriels entrants en tant que spam ou non-spam. Cela signifie qu'il existe une probabilité de 90 % que l'information soit acheminée vers l'unité de calcul suivante correcte, mais aussi une probabilité de 10 % que l'information soit acheminée vers une unité de calcul incorrecte. Ce point est important, car il rend compte des décisions (probabilistes) prises par les différentes unités de calcul et du fait que les erreurs peuvent se répercuter en cascade dans tout le système.

Je peux maintenant facilement consulter l'organigramme pour déterminer comment construire et améliorer mon assistant e-mail.

Entre autres choses, l'organigramme implique que des cas de test sont nécessaires pour chaque unité de calcul. Je peux rapidement déterminer quels devraient être ces cas de test en fonction de l'impact de chaque unité de calcul sur le reste du système. En particulier, pour les LLM (et autres unités de calcul probabilistes), il s'agit généralement de tests statistiques car nous devons évaluer le système par rapport à un ensemble d'entrées statistiquement représentatif. De la même manière que l'on peut tester un modèle ML par rapport à un ensemble de paires d'entrées-sorties données, on peut également tester une unité de calcul.

Supposons que j'aie créé l'assistant de messagerie et qu'au bout de quelques jours, je constate que de nombreux courriels sont classés à tort comme "importants". Cela m'ennuie et me ralentit, car mon Slack n'arrête pas de m'envoyer des messages à propos d'emails qui ne sont pas importants.

Je peux regarder l'organigramme et identifier rapidement le second LLM comme étant le coupable. Je peux maintenant décider de mettre en œuvre l'une des approches et l'un des modèles de conception présentés précédemment : concevoir l'invite de manière à intégrer des informations sur mes préférences personnelles et le contexte, appliquer l'apprentissage en quelques coups, affiner le ML, appliquer le raisonnement par chaîne de pensée (CoT) et ainsi de suite.

Supposons que je veuille appliquer une approche d'apprentissage par petites touches pour améliorer le système. Dans ce cas, je pourrais commencer à étiqueter des exemples de courriels importants et non importants et les réinjecter dans le système à l'aide de l'apprentissage par petites touches afin d'améliorer sa précision.

Pour ce faire, j'ajouterais une nouvelle unité de calcul sous la forme d'un "annotateur humain", qui prend en entrée un courrier électronique donné et lui attribue une étiquette ("important" ou "pas important"). Le courriel et son étiquette sont ensuite envoyés pour être stockés dans une base de données vectorielle. Lorsqu'un nouvel e-mail arrive, la base de données vectorielle est interrogée pour trouver les e-mails les plus similaires et leurs étiquettes, qui sont ensuite fournies au LLM en tant qu'exemples d'apprentissage ponctuels pour classer les e-mails comme importants ou non importants.

Voici le nouvel organigramme avec l'annotateur humain et la BD vectorielle. L'unité de calcul de la BD vectorielle doit en fait lire/écrire dans une BD, mais pour des raisons de simplicité, j'ai exclu ce point :

Remarquez que j'ai obtenu tout cela en ajoutant simplement deux nouvelles unités de calcul au système.

Nous avons maintenant vu comment appliquer ce nouveau paradigme informatique pour construire des applications logicielles réelles, comment penser aux unités informatiques et au flux d'informations dans le système.

Dans le prochain billet de blog, nous examinerons un ensemble de modèles de conception pour les LLM, que nous pouvons utiliser pour construire des unités de calcul et des systèmes permettant de résoudre des problèmes très complexes.

----------

Korbit construit son AI Mentor avec des LLM depuis plus d'un an. Les LLM sont un nouveau paradigme informatique. Ils l'ont mis en pratique et ont beaucoup appris sur le fonctionnement des LLM, sur la façon de construire des applications réelles avec eux et sur les architectures et les modèles de conception qui les font fonctionner.
Essayez Korbit AI Mentor :
https://www.korbit.ai/get-started
Pour en savoir plus :
Comment construire des logiciels avec des LLM (Partie 1)
Comment construire des logiciels avec des LLM (partie 3)

retour_flèche
Retour au blog