Cours Python · Module 10: IA et modèles de langage

LangChain - la chaîne qui relie tout

6 min de lecture
Dans cette leçon5

Tu sais déjà appeler un modèle et écrire un bon prompt. Mais une vraie application, c'est bien plus que ça : charger un template, l'envoyer au modèle, analyser la réponse, se souvenir de la conversation. LangChain relie toutes ces étapes en une seule - et il le fait grâce à une idée unique, étonnamment simple, que tu découvriras dès le début.

Retiens tout de suite la définition : LangChain est un framework pour construire des applications avec des LLM. Ce n'est ni une base de données, ni une cryptomonnaie, ni un langage de programmation - tu continues à écrire du Python, tu installes simplement quelques paquets de plus.

1pip install langchain langchain-openai langchain-anthropic

Trois paquets, trois rôles : langchain apporte les briques communes, tandis que langchain-openai et langchain-anthropic branchent chacun un fournisseur de modèles. Tu pourrais n'en installer qu'un seul, mais les avoir tous les deux te permet de changer de modèle sans réécrire ton code - et tu vas voir tout de suite pourquoi c'est aussi facile.

La brique : le modèle

Commençons par la plus petite brique - le modèle lui-même. Dans LangChain, chaque fournisseur (OpenAI, Anthropic) expose exactement la même méthode invoke, si bien que changer de modèle revient à changer une seule ligne.

1from langchain_openai import ChatOpenAI
2from langchain_core.messages import HumanMessage, SystemMessage
3
4llm = ChatOpenAI(model="gpt-5", temperature=0.7)
5
6messages = [
7    SystemMessage(content="Tu es un expert du Safari."),
8    HumanMessage(content="Parle-moi des elephants.")
9]
10print(llm.invoke(messages).content)

Regarde d'abord l'import : il se lit de gauche à droite - from, le paquet langchain_openai, puis import et enfin la classe ChatOpenAI. Regarde ensuite invoke : c'est le "exécute" commun à toutes les briques de LangChain. Dans un instant, tu verras qu'on lance un modèle seul exactement comme on lance une chaîne entière. Cette uniformité est le fondement de la bibliothèque. Pour passer chez Anthropic, une ligne suffit : from langchain_anthropic import ChatAnthropic, puis ChatAnthropic(model="claude-3-sonnet-20240229") - tout le reste du code ne bouge pas.

La brique : le template de prompt

La deuxième brique, c'est un template à trous - ton prompt de la leçon précédente, mais réutilisable. Au lieu de coller des morceaux de texte bout à bout, tu écris des {variables}.

1from langchain_core.prompts import ChatPromptTemplate
2
3prompt = ChatPromptTemplate.from_messages([
4    ("system", "Tu es un expert en {domaine}."),
5    ("human", "Parle-moi de {sujet}.")
6])

Les trous {domaine} et {sujet} ne seront remplis qu'au moment de l'exécution. Un seul template couvre ainsi des centaines de questions. Tu pourrais le rendre à la main avec prompt.format_messages(domaine="Safari", sujet="lions") puis passer le résultat au modèle toi-même, mais ce n'est pas nécessaire : comme toute brique LangChain, un PromptTemplate possède lui aussi invoke et sait donc se brancher directement sur le modèle. Et c'est ici qu'intervient l'idée centrale de toute la bibliothèque.

La grande idée : l'opérateur | (pipe)

Voici le cœur de LangChain. Tu chaînes les briques avec l'opérateur |, exactement comme un tuyau : le résultat de l'une s'écoule dans la suivante. prompt | llm | parser se lit "remplis le prompt, envoie-le au modèle, transforme la réponse".

1from langchain_core.output_parsers import StrOutputParser
2
3chain = prompt | llm | StrOutputParser()
4
5result = chain.invoke({"domaine": "Safari", "sujet": "lions"})
6print(result)

C'est le même invoke que pour le modèle seul - sauf qu'il exécute maintenant toute la chaîne d'un coup. Les données entrent sous forme de dictionnaire qui remplit les trous, traversent le template, le modèle et le parser, et ressortent en texte prêt à l'emploi. Le StrOutputParser final extrait le texte de la réponse pour t'éviter d'écrire .content. Cette écriture porte un nom : LCEL, le LangChain Expression Language. Ce n'est pas un nouveau langage de programmation - tu restes en Python -, ni un format de fichier, ni un protocole réseau : LCEL, c'est simplement une façon de chaîner des composants LangChain en utilisant l'opérateur |. Retiens ce motif prompt | llm | parser - un PromptTemplate, un LLM, un OutputParser - car il revient dans chacun des exemples qui suivent.

La même chaîne, un parser plus puissant : le résultat sous forme d'objet

Puisque le parser est la dernière brique du tuyau, il suffit de le remplacer pour obtenir non plus du texte, mais un objet directement exploitable. Tu décris la structure attendue avec une classe, et LangChain veille au format.

1from langchain_core.pydantic_v1 import BaseModel, Field
2from langchain_core.output_parsers import JsonOutputParser
3
4class AnimalInfo(BaseModel):
5    name: str = Field(description="Nom courant")
6    habitat: str = Field(description="Habitat naturel")
7    population: int = Field(description="Population totale")
8
9parser = JsonOutputParser(pydantic_object=AnimalInfo)
10
11prompt = ChatPromptTemplate.from_messages([
12    ("human", "{format_instructions}\n\nAnimal : {animal}")
13])
14chain = prompt | llm | parser
15
16result = chain.invoke({
17    "animal": "Elephant africain",
18    "format_instructions": parser.get_format_instructions()
19})
20print(result)  # {'name': 'Elephant africain', 'habitat': ..., 'population': ...}

C'est toujours la même chaîne prompt | llm | parser - seule la dernière brique a changé. La classe AnimalInfo décrit les champs que tu attends, et get_format_instructions injecte dans le prompt une consigne automatique du type "renvoie du JSON ayant cette forme" : tu n'as donc pas besoin d'ajouter toi-même un message système qui réclame du JSON. Le résultat est immédiatement un dictionnaire Python, utilisable tel quel dans ton code - aucun parsing manuel. Ajoute un champ diet à la classe et le parser l'exigera lui aussi, sans que tu touches au reste de la chaîne.

Ajouter de la mémoire à la chaîne

Par défaut, le modèle ne se souvient d'aucun message précédent - chaque invoke repart de zéro. Pour qu'il tienne une véritable conversation, tu enveloppes la chaîne dans une brique qui enregistre l'historique.

1from langchain_community.chat_message_histories import ChatMessageHistory
2from langchain_core.runnables.history import RunnableWithMessageHistory
3
4history = ChatMessageHistory()
5
6chain_with_history = RunnableWithMessageHistory(
7    chain,
8    lambda session_id: history,
9    input_messages_key="input",
10    history_messages_key="history"
11)
12
13chain_with_history.invoke(
14    {"input": "Je suis en Safari au Kenya"},
15    config={"configurable": {"session_id": "abc123"}}
16)
17response2 = chain_with_history.invoke(
18    {"input": "Quels animaux puis-je voir ici ?"},
19    config={"configurable": {"session_id": "abc123"}}
20)

RunnableWithMessageHistory enveloppe la chaîne existante et rajoute, à chaque tour, les messages échangés auparavant. C'est grâce à cela que, dans la seconde question, le modèle sait que "ici" désigne le Kenya, alors que tu ne l'as pas répété. Le session_id sépare les conversations des différents utilisateurs, pour que leurs historiques ne se mélangent jamais.

Retiens un seul caractère de cette leçon : |. LangChain, ce sont des briques - prompt, modèle, parser, mémoire - assemblées en tuyau par le même opérateur et lancées par le même invoke. Une fois que tu vois ça, chaque nouvel élément n'est plus qu'une brique de plus à ajouter au tuyau.

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Qu'est-ce que LangChain ?

  2. 2. Qu'est-ce que LCEL (LangChain Expression Language) ?

Tâches pratiques dans le jeu

  • Tri horizontal

    Range l'import de ChatOpenAI depuis LangChain :

  • Éditeur de code

    Connecte un PromptTemplate, un LLM et un OutputParser en utilisant l'opérateur |.

  • Clique dans l'ordre

    Clique sur les boutons pour créer une chaîne LCEL :

Articles utiles