À quoi ça sert

Appeler un LLM en Python, c'est trois lignes : on envoie un prompt, on récupère du texte. Construire une application autour, c'est beaucoup plus : gérer la mémoire d'une conversation, aller chercher de l'info dans des documents, appeler des outils externes, enchaîner plusieurs étapes, gérer les erreurs, switcher entre LLMs sans tout réécrire.

LangChain fournit les briques standardisées pour tout ça :

  • Wrappers de LLMs — API uniforme pour OpenAI, Anthropic, Ollama, Mistral, HuggingFace… Tu changes une ligne pour switcher de modèle.
  • Chains — enchaîner prompt → LLM → parser → traitement, le tout déclaratif avec la syntaxe LCEL (LangChain Expression Language).
  • Retrievers et RAG — charger des documents, les découper en chunks, les indexer dans un vector store, et les retrouver par similarité au moment de répondre.
  • Tools et Agents — donner au LLM la capacité d'utiliser des outils (calculatrice, recherche web, API maison) et de décider lequel appeler.
  • Memory — garder le fil d'une conversation multi-tours sans tout renvoyer manuellement à chaque appel.
  • Intégrations — connecteurs prêts à l'emploi pour des centaines de services (Notion, GitHub, Slack, S3, PostgreSQL…).
LangChain vs LlamaIndex ?

Les deux frameworks dominent l'écosystème, mais avec des angles différents. LangChain est généraliste : RAG + agents + chains + dizaines d'intégrations. Plus large, plus puissant, plus complexe. LlamaIndex est focalisé sur le RAG : indexer et requêter des documents, c'est sa raison d'être. Plus simple à démarrer si c'est ton seul besoin. Pour un pur RAG, prends LlamaIndex. Pour des agents, des pipelines multi-étapes ou plusieurs sources mélangées, prends LangChain. Et les deux peuvent cohabiter — beaucoup d'applis utilisent LlamaIndex pour le RAG dans une chaîne LangChain plus large.

Un exemple d'usage

Construire un mini-RAG sur quelques fichiers texte : charger les documents, indexer, poser une question, obtenir une réponse avec les sources. Avec Ollama en local (pas besoin de clé OpenAI) :

python
from langchain_community.document_loaders import DirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_ollama import OllamaEmbeddings, ChatOllama
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate

# 1. Charger des fichiers .txt depuis un dossier
docs = DirectoryLoader("./notes", glob="*.txt").load()

# 2. Découper en chunks (les LLMs ont une fenêtre limitée)
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)

# 3. Embeddings + vector store en mémoire
embeddings = OllamaEmbeddings(model="nomic-embed-text")
store = FAISS.from_documents(chunks, embeddings)

# 4. Prompt + LLM + chaîne RAG
prompt = ChatPromptTemplate.from_template(
    "Réponds à la question en t'appuyant sur le contexte.\n"
    "Contexte: {context}\nQuestion: {input}"
)
llm = ChatOllama(model="llama3.2")
chain = create_retrieval_chain(
    store.as_retriever(),
    create_stuff_documents_chain(llm, prompt),
)

# 5. Question → réponse contextuelle
result = chain.invoke({"input": "Quelle est la date du dernier sprint ?"})
print(result["answer"])

En ~25 lignes, un RAG complet : ingestion → indexation → recherche → réponse contextualisée. Changer Ollama pour OpenAI = 2 imports à modifier. Changer le format des documents (PDF, HTML, Notion…) = changer le loader, tout le reste reste identique.

How-to : installer et utiliser LangChain

  1. Installer le bon set de paquets

    LangChain est modulaire : un paquet « core » + un paquet par intégration. On installe au minimum :

    bash
    uv add langchain langchain-community
    
    # + au moins un fournisseur de LLM :
    uv add langchain-ollama        # local (recommandé pour démarrer)
    uv add langchain-openai        # API OpenAI
    uv add langchain-anthropic     # API Claude
    
    # + utilitaires courants
    uv add langchain-text-splitters faiss-cpu
    Pourquoi autant de paquets ?

    LangChain a éclaté son monorepo en 2024 pour découpler les releases. Du coup, tu n'installes que les intégrations dont tu as besoin. Le revers : faut savoir où trouver quoi — la doc indique pour chaque import le paquet à installer.

  2. Connecter un LLM

    Tous les LLMs ont la même interface : .invoke() pour une requête simple, .stream() pour le mot par mot, .batch() pour plusieurs en parallèle.

    python
    from langchain_ollama import ChatOllama
    llm = ChatOllama(model="llama3.2", temperature=0.7)
    
    print(llm.invoke("Capitale du Pérou ?").content)
    # > "La capitale du Pérou est Lima."
    
    for chunk in llm.stream("Liste 5 fruits."):
        print(chunk.content, end="", flush=True)
  3. Composer une chaîne avec LCEL

    LCEL (LangChain Expression Language) compose les étapes avec l'opérateur | à la manière d'un pipe Unix :

    python
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", "Tu es un assistant concis."),
        ("user", "Explique {sujet} en 2 phrases."),
    ])
    
    chain = prompt | llm | StrOutputParser()
    print(chain.invoke({"sujet": "l'overfitting"}))

    Cette chaîne se lit : un prompt structuré, envoyé au LLM, dont la sortie est parsée en string brute. Chaque étape est interchangeable.

  4. Charger et découper des documents

    Pour le RAG, on commence toujours par charger des sources :

    python
    from langchain_community.document_loaders import (
        PyPDFLoader, WebBaseLoader, TextLoader, DirectoryLoader,
    )
    from langchain_text_splitters import RecursiveCharacterTextSplitter
    
    docs = PyPDFLoader("manuel.pdf").load()
    docs += WebBaseLoader(["https://example.com/doc"]).load()
    
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=100,
    )
    chunks = splitter.split_documents(docs)
    Le bon chunk_size ?

    Règle empirique : 500-1500 tokens (≈ 300-1000 mots). Trop petit, on perd le contexte ; trop grand, le LLM se noie ou dépasse sa fenêtre. chunk_overlap évite de couper une phrase en deux entre deux chunks.

  5. Vector store et retriever

    Le vector store stocke les embeddings des chunks et sait retrouver les plus proches d'une question :

    python
    from langchain_community.vectorstores import FAISS
    from langchain_ollama import OllamaEmbeddings
    
    embeddings = OllamaEmbeddings(model="nomic-embed-text")
    store = FAISS.from_documents(chunks, embeddings)
    
    # Sauver / recharger
    store.save_local("./faiss_index")
    store = FAISS.load_local("./faiss_index", embeddings,
                              allow_dangerous_deserialization=True)
    
    retriever = store.as_retriever(search_kwargs={"k": 4})
    docs_pertinents = retriever.invoke("Sujet de ma question")

    FAISS est l'option locale la plus simple. En production on utilise plutôt Qdrant, Chroma, Weaviate ou Postgres + pgvector (cf. PostgreSQL).

  6. Chaîne RAG complète

    python
    from langchain.chains import create_retrieval_chain
    from langchain.chains.combine_documents import create_stuff_documents_chain
    
    prompt = ChatPromptTemplate.from_template(
        "Contexte : {context}\n\nQuestion : {input}\n\n"
        "Si l'info n'est pas dans le contexte, dis-le."
    )
    doc_chain = create_stuff_documents_chain(llm, prompt)
    rag_chain = create_retrieval_chain(retriever, doc_chain)
    
    ans = rag_chain.invoke({"input": "Quelle est la procédure pour …"})
    print(ans["answer"])
    print([d.metadata for d in ans["context"]])     # sources
  7. Tools et agents (aperçu)

    Un tool est une fonction Python qu'on expose au LLM. Un agent décide lui-même quel tool appeler.

    python
    from langchain_core.tools import tool
    from langgraph.prebuilt import create_react_agent
    
    @tool
    def meteo(ville: str) -> str:
        """Renvoie la météo d'une ville."""
        return f"À {ville} il fait 18°C."
    
    agent = create_react_agent(llm, tools=[meteo])
    result = agent.invoke({"messages": [("user", "Quel temps à Liège ?")]})
    print(result["messages"][-1].content)

    Le LLM lit la docstring du tool, décide qu'il en a besoin, l'appelle avec les bons paramètres, intègre le résultat dans sa réponse finale. Sous-projet LangGraph du même éditeur pour les agents complexes.

  8. Debugger avec LangSmith ou des callbacks

    Une chaîne LangChain de 5 étapes devient opaque à debugger. Activer les logs verbose ou brancher LangSmith (service payant de l'éditeur) donne la trace complète.

    python
    import langchain
    langchain.debug = True                        # logs locaux
    
    # LangSmith via variable d'environnement
    # LANGCHAIN_TRACING_V2=true
    # LANGCHAIN_API_KEY=…

Aide-mémoire

python (LLM de base)
from langchain_ollama import ChatOllama
llm = ChatOllama(model="llama3.2")
llm.invoke("…"); llm.stream("…"); llm.batch(["…", "…"])
python (chaîne LCEL)
chain = prompt | llm | StrOutputParser()
chain.invoke({"var": "…"})
python (RAG minimal)
docs = PyPDFLoader("…").load()
chunks = RecursiveCharacterTextSplitter(...).split_documents(docs)
store = FAISS.from_documents(chunks, embeddings)
chain = create_retrieval_chain(store.as_retriever(), doc_chain)
chain.invoke({"input": "…"})
python (tools / agent)
@tool
def my_tool(x: str) -> str: """docstring vue par le LLM"""; return ...
agent = create_react_agent(llm, tools=[my_tool])
agent.invoke({"messages": [("user", "…")]})

LangChain et le reste de l'écosystème

  • LlamaIndex — l'autre framework majeur. Plus simple pour un pur cas RAG. LangChain est plus complet (agents, tools, multi-step). Les deux peuvent coexister.
  • Ollama / Hugging Face — fournisseurs de LLMs locaux. langchain-ollama et langchain-huggingface exposent la même API que les intégrations cloud.
  • FastAPI — pattern classique : ton API FastAPI expose un endpoint /chat qui appelle une chaîne LangChain. LangChain gère même nativement le streaming pour FastAPI.
  • Pydantic — LangChain utilise Pydantic en interne pour valider les inputs/outputs des chaînes. Tu déclares un BaseModel et un parser le force comme structure de sortie du LLM.
  • PostgreSQL — avec l'extension pgvector, Postgres devient un vector store production intégrable à LangChain via langchain-postgres. Alternative aux vector stores spécialisés (FAISS, Qdrant…).
  • Streamlit — prototyper un chatbot LangChain en quelques dizaines de lignes : st.chat_input + appel à la chaîne + st.write_stream.
  • MLflow — sait logger et servir une chaîne LangChain comme un modèle classique (mlflow.langchain.log_model), ce qui permet de versionner tes RAG comme tu versionnes tes modèles ML.
Le piège LangChain : surdimensionner

LangChain encourage la composition de chaînes complexes. Pour un besoin simple (« appelle Ollama avec ce prompt »), un simple httpx.post(...) sur l'API Ollama suffit. Avant d'ajouter LangChain, demande-toi si tu as vraiment besoin de RAG, d'agents ou de multi-LLM. Si non, reste sur httpx direct — moins de magie, moins de dépendances, plus facile à debugger.

Pour aller plus loin