LangChain Expression Language (LCEL)
Vous construisez une chaîne LLM complexe. Pourquoi celle-ci reste-t-elle lisible ?
Imaginez la ligne de commande UNIX. Vous enchaînez des commandes via le symbole | : cat fichier.txt | grep "erreur" | sort | uniq. Chaque programme reçoit la sortie du précédent, la transforme, la transmet au suivant. Pas d’imbrication verbale, pas d’état partagé—juste un flux de données transparent.
LCEL (LangChain Expression Language) transpose cette élégance UNIX au monde des grands modèles de langage. Au lieu de nester des appels de fonction ou de gérer manuellement le plumbing entre composants, vous écrivez : prompt | llm | parser. La syntaxe reste déclarative, lisible, modularisable.
C’est ce passage du verbeux au fluide qui explique pourquoi LCEL a devenu le standard de facto en 2023-2024 chez les équipes LangChain. Moins de friction cognitive, plus de temps pour peaufiner la logique métier.
Pourquoi LCEL a remplacé LLMChain (et comment)
Avant 2023, les développeurs LangChain enchaînaient les opérations via la classe LLMChain. Voici le paradigme ancien :
# Ancien style (LLMChain) - verbeux et peu composablechain1 = LLMChain(prompt=prompt_template, llm=llm)chain2 = LLMChain(prompt=follow_up_prompt, llm=llm)result1 = chain1.run(input="test")result2 = chain2.run(input=result1)Trois problèmes majeurs :
- Verbosité : chaque composant requiert une instanciation explicite et un appel
.run(). - Manque de composabilité : assembler trois chaînes demandait du code glue ad hoc pour passer les résultats.
- Absence de streaming natif : afficher les tokens en temps réel nécessitait une refactorisation complète.
LCEL émerge en 2023 comme syntaxe minimaliste exposant cette abstraction sous-jacente :
# Nouveau style (LCEL) - déclaratif et composablechain = prompt | llm | parser
# Invocation simpleresult = chain.invoke({"input": "test"})
# Streaming natiffor token in chain.stream({"input": "test"}): print(token, end="", flush=True)Le code se réduit de 50%, et le streaming devient une ligne au lieu d’une refactorisation.
Comment LCEL transforme les données (sous le capot)
Tous les composants LangChain—ChatPromptTemplate, ChatOpenAI, JsonOutputParser—sont des Runnables. L’opérateur | n’est pas magique : c’est la méthode Python __or__ surchargée pour créer une chaîne d’appels.
Quand vous écrivez prompt | llm | parser, vous créez une séquence implicite :
prompt(Runnable) reçoit l’input utilisateur, génère un prompt formaté.llm(Runnable) reçoit le prompt, appelle l’API OpenAI, retourne la génération brute.parser(Runnable) reçoit la génération, l’extrait en JSON, retourne un dictionnaire structuré.
Chaque étape expose invoke(), stream(), batch(). LCEL propage automatiquement ces interfaces à travers la chaîne. Appeler chain.stream() décompose l’exécution en tokens partiels traversant tout le pipeline.
Patterns avancés : branchements et parallélisme
Les chaînes linéaires simples ne suffisent pas. LCEL propose des composants spécialisés :
RunnableBranch : conditionnel déclaratif.
from langchain.runnables import RunnableBranch
# Créer une chaîne de classificationis_spam = prompt_spam | llm_spam | bool_parser
# Brancher sur le résultatchain = RunnableBranch( [(is_spam, spam_handler_chain), normal_handler_chain])# Si is_spam retourne True, exécute spam_handler_chain.# Sinon, exécute normal_handler_chain.RunnableParallel : exécution concurrente de plusieurs branches.
from langchain.runnables import RunnableParallel
parallel_chain = RunnableParallel({ "summary": summarize_chain, "entities": ner_chain, "sentiment": sentiment_chain})
result = parallel_chain.invoke({"text": "..."})# result = {"summary": "...", "entities": [...], "sentiment": "positif"}RunnablePassthrough : préserver le contexte à travers la chaîne.
from langchain.runnables import RunnablePassthrough
# Pattern RAG classiquerag_chain = ( {"context": retriever, "question": RunnablePassthrough()} | prompt_template | llm | parser)
# La sortie du retriever et la question originale sont tous deux# disponibles pour le prompt template.Cas d’usage réels : de la théorie à la production
E-commerce : chatbot de recommandation
Une startup doit deployer un chatbot produit qui (1) récupère les fiches articles pertinentes, (2) les synthétise, (3) formule des recommandations personnalisées avec streaming.
Ancien approche : 5 classes LLMChain imbriquées, gestion manuelle du contexte, refactorisation complète pour ajouter le streaming.
Approche LCEL :
from langchain.runnables import RunnableParallel, RunnablePassthrough
rag_chain = ( {"products": retriever, "user_profile": profile_loader} | RunnablePassthrough.assign(enriched_prompt=enrich_prompt) | recommendation_prompt | llm | json_parser)
# Deploy via LangServe (3 lignes supplémentaires)for token in rag_chain.stream({"query": "téléphones sous 500€"}): print(token, end="", flush=True)Résultat : code compact, lisible, streaming natif, déploiement trivial.
Audit légal : classification multi-étapes de documents
Une équipe compliance doit classifier des documents (risque faible/moyen/élevé) et déclencher des workflows différents.
classifier_chain = document | risk_classifier | risk_parser
escalation_chain = doc_summary | escalation_prompt | llm | notify_slack
audit_pipeline = RunnableBranch( [(high_risk_check, escalation_chain), standard_workflow_chain])RunnableBranch encode le décisionnel sans if/else éparpillés. LangSmith trace chaque branche exécutée pour audit trail automatique.
Déploiement en production via LangServe
LCEL brille particulièrement au déploiement. Une chaîne LCEL peut être exposée comme API REST en trois lignes :
from langserve import add_routes
# Votre chaîne LCEL existantechain = prompt | llm | parser
# Exposition automatiqueadd_routes(app, chain, path="/chain")
# LangServe génère automatiquement :# POST /chain/invoke# POST /chain/stream# POST /chain/batchPas d’adaptation FastAPI manuelle, pas de sérialisation JSON à coder. LangServe infère les schémas via les type hints de votre chaîne et génère swagger/docs automatiquement.
Observabilité intégrée : LangSmith
Chaque appel à chain.invoke() ou chain.stream() génère des événements structurés (début, fin, erreur, token). LangSmith consomme ces événements pour tracer, versionner, évaluer les chaînes en production.
Configuration (3 variables d’environnement) :
export LANGCHAIN_API_KEY="your_key"export LANGCHAIN_PROJECT="production"export LANGCHAIN_ENDPOINT="https://api.smith.langchain.com"Bénéfice : dashboard centralisé pour monitoring, A/B testing de prompts, debugging visuel des chaînes complexes.
Les limites critiques
Courbe d’apprentissage : LCEL suppose une familiarité avec la programmation fonctionnelle (composition, pipes, immutabilité). Les juniors venant de paradigmes impératifs trouvent cette transition abrupte.
Debugging cryptique : quand une chaîne nested échoue, la stack trace est opaque. Le recours à LangSmith devient quasi obligatoire pour clarifier les points de rupture.
Performance des petites chaînes : LCEL ajoute un overhead d’abstraction (Runnable protocol, type inference). Pour des chaînes triviales (1-2 composants), ce coût peut être observable.
Pérennité : LCEL est étroitement lié à LangChain. Si l’écosystème diverge, vous êtes bloqué.
Notions liées
- LangChain
- Programmation fonctionnelle
- Retrieval-Augmented Generation
- Streaming et AsyncIO
- Type Hints Python
Sources & Références
- LangChain Official Documentation - Expression Language Guide
- Wild Code School - LCEL: Qu’est-ce que le LCEL
- Pinecone - LangChain Expression Language Explained
- AWS - Qu’est-ce que LangChain
- DataCamp - Développement d’applications LLM avec LangChain
- Aurelio AI Learn - LangChain Expression Language (LCEL)