Aller au contenu

L'Ère des Chaînes Explicites : Quand LangChain était Verbeux

Imaginez que pour préparer un simple café, vous deviez rédiger un formulaire administratif en trois exemplaires pour chaque étape : un pour moudre le grain, un pour chauffer l’eau, et un dernier pour verser le liquide. Si vous faites une faute de frappe sur le formulaire numéro 2, la machine s’arrête sans vous dire pourquoi.

C’était, en substance, le quotidien des développeurs d’IA générative entre 2022 et 2024.

Avant l’arrivée de la syntaxe moderne et fluide (LCEL), l’orchestration des modèles de langage reposait sur ce qu’on appelle le “Code verbeux avec classes Chain”. C’est le chapitre “Ancien Testament” de l’ingénierie IA : une époque où construire un pipeline de données demandait une rigueur syntaxique absolue, une gestion manuelle des entrées/sorties et une quantité massive de code répétitif (boilerplate).

Cet article explore cette architecture fondatrice, pourquoi elle a existé, et pourquoi sa complexité a fini par devenir son propre goulot d’étranglement.

Le Problème : L’Usine à Gaz Architecturale

Pour comprendre la douleur de cette approche, il faut saisir ce qu’elle tentait de résoudre. Au début de l’ère des LLM, enchaîner une invite (prompt) avec un modèle, puis nettoyer la réponse, n’avait rien de standard.

L’approche “Chain Class” a tenté de structurer cela via la Programmation Orientée Objet (POO). L’idée semblait saine : chaque étape du traitement est un “Objet” (une classe) qui possède ses propres règles.

Cependant, cette rigidité a créé trois frictions majeures :

  1. La Bureaucratie des Données (Input/Output Keys) : Rien n’était implicite. Si l’étape A produisait un résumé, vous deviez explicitement nommer cette sortie (ex: summary_text). Si l’étape B devait traduire ce résumé, vous deviez lui dire explicitement “Prends la variable qui s’appelle summary_text”. Une erreur de nommage, et tout le système s’effondrait silencieusement.
  2. L’Enfer de l’Héritage : Pour créer une logique un peu complexe, il ne suffisait pas d’écrire une fonction. Il fallait créer une nouvelle “Classe” qui héritait de la classe mère Chain, et redéfinir ses méthodes internes. C’est comme devoir reconstruire les fondations de la maison juste pour changer la couleur du papier peint.
  3. L’Opacité du Debugging : Quand une erreur survenait, elle se cachait derrière des couches d’abstractions. Le développeur ne voyait pas le flux de données, mais une pile d’appels de fonctions internes à la librairie.

Comment ça Marche (ou plutôt, comment ça marchait)

Pour les praticiens actuels, revisiter ce code ressemble à de l’archéologie logicielle. Pourtant, comprendre ce mécanisme est crucial pour maintenir les systèmes “legacy” (hérités) qui tournent encore en production aujourd’hui.

Le cœur du système reposait sur la classe Chain et ses dérivées (LLMChain, SequentialChain).

Le Flux de Données Manuel

Contrairement aux flux modernes qui ressemblent à des tuyaux connectés (a | b | c), l’approche verbeuse fonctionnait par accumulation d’état. Imaginez un dossier papier (un dictionnaire Python) qui passe de bureau en bureau. Chaque bureau (Chain) lit une page, fait son travail, ajoute une nouvelle page au dossier, et le passe au suivant.

Voici la visualisation de ce processus laborieux :

flowchart LR
    Input[Entrée Utilisateur] --> DictState{Dictionnaire d'État Global}
    
    subgraph Chain_1 [LLMChain: Résumé]
    direction TB
    Read1[Lire clé: 'text'] --> Process1[Générer Résumé]
    Process1 --> Write1[Écrire clé: 'summary']
    end
    
    subgraph Chain_2 [LLMChain: Traduction]
    direction TB
    Read2[Lire clé: 'summary'] --> Process2[Traduire en EN]
    Process2 --> Write2[Écrire clé: 'translated_text']
    end
    
    DictState -- "{text: ...}" --> Chain_1
    Chain_1 -- "{text: ..., summary: ...}" --> DictState
    DictState -- "{text: ..., summary: ...}" --> Chain_2
    Chain_2 -- "{text: ..., summary: ..., translated_text: ...}" --> DictState
    DictState --> Output[Sortie Finale]

    style DictState fill:#f96,stroke:#333,stroke-width:2px

Anatomie d’une Classe Chain

Pour implémenter ce diagramme, le développeur devait écrire un code d’une verbosité intimidante. Voici les concepts techniques sous-jacents :

  • Instanciation Explicite : Il fallait créer une instance de LLMChain pour chaque petite action.
  • Mapping des Variables : Déclaration obligatoire des input_variables (ce que j’attends) et output_variables (ce que je produis).
  • Orchestration Séquentielle : Utilisation de SequentialChain pour lier les morceaux, en spécifiant manuellement comment les sorties des uns deviennent les entrées des autres.

C’est ici que la charge cognitive explosait. Le développeur ne passait pas son temps à peaufiner le prompt (la valeur ajoutée réelle), mais à vérifier que la variable output_key="summary" de la ligne 40 correspondait bien à input_variables=["summary"] de la ligne 85.

Applications Concrètes : Le Choc de la Complexité

Pour illustrer la lourdeur de cette approche, comparons des cas d’usage réels. Observez comment des tâches simples devenaient des projets d’ingénierie lourds.

Le Besoin : Une startup doit générer un rapport financier : (1) Lire des données brutes, (2) Résumer, (3) Traduire.

L’Approche Verbeuse (Avant) : Le développeur devait créer une classe ReportChain héritant de Chain.

  • Boilerplate : Environ 150 lignes de code.
  • Gestion des clés : Il fallait gérer manuellement 12 clés différentes (raw_data, clean_data, summary_fr, summary_en, etc.).
  • Conséquence : Si le client demandait d’ajouter une étape de “Vérification de conformité” entre l’étape 2 et 3, il fallait réécrire la classe parente, modifier tous les dictionnaires d’entrée/sortie et re-tester l’intégralité de la chaîne. Le coût de maintenance était astronomique.

Les Pièges à Éviter (Héritage Technique)

Si vous tombez sur un tutoriel ou une base de code datant de 2023, soyez vigilant. Ces structures sont encore fonctionnelles mais représentent une dette technique.

Pourquoi c’était “Nécessaire” ?

Il est facile de critiquer le passé avec les lunettes du présent. Cependant, l’approche verbeuse a eu un mérite immense : elle a standardisé le chaos.

Avant les classes Chain, chacun écrivait ses scripts Python “spaghetti” dans son coin. LangChain a imposé une structure. Certes, cette structure était lourde, inspirée des vieux frameworks Java ou des pipelines ETL traditionnels, mais elle a permis aux entreprises de commencer à construire des applications sérieuses.

C’était une étape évolutive indispensable. Comme les gros téléphones portables des années 90 : encombrants, lourds, avec une autonomie ridicule, mais révolutionnaires car ils ont ouvert la voie au smartphone.

À Retenir

L’approche “Code verbeux avec classes Chain” est le fondement historique de l’orchestration des LLM.

  1. Verbosité Extrême : Elle exigeait une définition explicite de chaque étape, des clés d’entrée et de sortie, via l’instanciation de classes Python.
  2. Gestion Manuelle de l’État : Les données transitaient via un dictionnaire global que le développeur devait gérer mentalement, créant une forte charge cognitive.
  3. Rigidité : Modifier une chaîne existante (ajouter/retirer une étape) demandait souvent une refonte complète du code (refactoring).
  4. Héritage vs Composition : Elle privilégiait l’héritage de classes (POO classique) là où l’IA moderne privilégie la composition fonctionnelle.
  5. Obsolescence : Bien que toujours présente dans le code legacy, cette approche est remplacée par le LCEL (LangChain Expression Language), plus déclaratif et lisible.

Notions Liées

Pour comprendre vers quoi nous avons évolué, consultez ces fiches :