TL;DR
LangChain est devenu l'un des outils les plus utilisés par les Bibliothèque Python pour interagir avec LLM en moins d'un an, mais LangChain était surtout une bibliothèque pour les POC car il n'avait pas la capacité de créer des des applications complexes et évolutives.
Tout a changé en août 2023, lorsqu'ils ont publié LangChain Expression Language (LCEL), Une nouvelle syntaxe qui comble le fossé entre le Du POC à la production. Cet article vous guidera à travers les tenants et les aboutissants de la LCEL, en vous montrant comment elle simplifie le processus d'achat. création de chaînes personnalisées et pourquoi vous devez l'apprendre si vous voulez construire Candidatures au LLM!
Prompts, LLM et chaînes, rafraîchissons-nous la mémoire
Avant de plonger dans la syntaxe LCEL, je pense qu'il est utile de se rafraîchir la mémoire sur les concepts LangChain tels que LLM et Prompt ou même une chaîne.
LLM: Dans langchain, llm est une abstraction autour du modèle utilisé pour faire les complétions comme openai gpt3.5, claude, etc...
Prompt: Il s'agit de l'entrée de l'objet LLM, qui posera les questions LLM et donnera ses objectifs.
Chaîne: Il s'agit d'une séquence d'appels à un LLM ou à toute autre étape de traitement data.

Maintenant que les définitions sont terminées, supposons que nous voulions créer une entreprise ! Nous avons besoin d'un nom vraiment cool et accrocheur et d'un modèle d'entreprise pour gagner de l'argent !
Exemple - Nom de l'entreprise et modèle d'entreprise avec les anciennes chaînes
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
USER_INPUT = "chaussettes colorées"
llm = OpenAI(temperature=0)
prompt_template_product = "Quel est un bon nom pour une entreprise qui fabrique ?"
company_name_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
company_name_output = company_name_chain(USER_INPUT)
prompt_template_business = "Donnez moi la meilleure idée de modèle d'entreprise pour mon entreprise nommée : "
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
business_model_output = business_model_chain(company_name_output["text"])
print(nom_de_l'entreprise_sortie)
print(business_model_output)
>>>
>>>
C'est assez facile à suivre, il y a un peu de redondance, mais c'est gérable.
Ajoutons quelques personnalisations en traitant les cas où l'utilisateur n'utilise pas notre chaîne comme prévu.
Peut-être l'utilisateur va-t-il saisir quelque chose qui n'a rien à voir avec l'objectif de notre chaîne ? Dans ce cas, nous voulons le détecter et réagir de manière appropriée.
Exemple - Personnalisation et routage avec d'anciennes chaînes
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
import ast
USER_INPUT = "Harrison Chase"."
llm = OpenAI(temperature=0)
# -- Même code que précédemment
prompt_template_product = "Quel est un bon nom pour une entreprise qui fabrique ?"
company_name_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
prompt_template_business = "Donnez moi la meilleure idée de modèle d'entreprise pour mon entreprise nommée : "
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
# -- Nouveau code
prompt_template_is_product = (
"Votre objectif est de déterminer si l'entrée de l'utilisateur est un nom de produit plausible."
"Les questions, les salutations, les longues phrases, les célébrités et autres entrées non pertinentes ne sont pas considérées comme des produits."
"input : n"
"Répondez uniquement par 'Vrai' ou 'Faux' et rien de plus"."
)
prompt_template_cannot_respond = (
"Vous ne pouvez pas répondre à l'entrée de l'utilisateur : n"
"Demandez à l'utilisateur de saisir le nom d'un produit pour que vous puissiez en faire une entreprise."
)
cannot_respond_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_cannot_respond))
company_name_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
is_a_product_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_is_product))
# Si nous utilisons bool sur une chaîne non vide, ce sera True, nous avons donc besoin de `literal_eval`.
is_a_product = ast.literal_eval(is_a_product_chain(USER_INPUT)["text"])
if is_a_product :
company_name_output = company_name_chain(USER_INPUT)
business_model_output = business_model_chain(company_name_output["text"])
print(business_model_output)
autre :
print(cannot_respond_chain(USER_INPUT))
Cela devient un peu plus difficile à comprendre, mais résumons :
De nombreux problèmes commencent à se poser :
Qu'est-ce que le LangChain Expression Language (LCEL) ?
LCEL est une interface et une syntaxe unifiées permettant d'écrire des chaînes composables prêtes à la production.
Nous allons d'abord essayer de comprendre la nouvelle syntaxe en réécrivant la chaîne de tout à l'heure.
Exemple - Nom de l'entreprise et modèle d'entreprise avec LCEL
from langchain_core.runnables import RunnablePassthrough
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
USER_INPUT = "chaussettes colorées"
llm = OpenAI(temperature=0)
prompt_template_product = "Quel est un bon nom pour une entreprise qui fabrique ?"
prompt_template_business = "Donnez moi la meilleure idée de modèle d'entreprise pour mon entreprise nommée : "
chaîne = (
PromptTemplate.from_template(prompt_template_product)
| llm
|
| PromptTemplate.from_template(prompt_template_business)
| llm
)
business_model_output = chain.invoke()
Beaucoup de code inhabituel, en quelques lignes seulement :
Mais est-il vraiment plus facile de créer des chaînes de cette manière ?
Mettons-le à l'épreuve en ajoutant la fonction is_a_product_chain() et le branchement si la saisie de l'utilisateur n'est pas correcte. Nous pouvons même taper la chaîne avec Python Typing, faisons-le à titre de bonne pratique.
Exemple - Personnalisation et routage avec LCEL
from typing import Dict
from langchain_core.runnables import RunnablePassthrough, RunnableBranch
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.output_parsers import BooleanOutputParser
from langchain_community.llms import OpenAI
USER_INPUT = "Harrrison Chase"."
llm = OpenAI(temperature=0)
prompt_template_product = "Quel est un bon nom pour une entreprise qui fabrique ?"
prompt_template_cannot_respond = (
"Vous ne pouvez pas répondre à l'entrée de l'utilisateur : n"
"Demandez à l'utilisateur de saisir le nom d'un produit pour que vous puissiez en faire une entreprise."
)
prompt_template_business = "Donnez moi la meilleure idée de modèle d'entreprise pour mon entreprise nommée : "
prompt_template_is_product = (
"Votre objectif est de déterminer si l'entrée de l'utilisateur est un nom de produit plausible."
"Les questions, les salutations, les longues phrases, les célébrités et autres entrées non pertinentes ne sont pas considérées comme des produits."
"input : n"
"Répondez uniquement par 'Vrai' ou 'Faux' et rien de plus"."
)
answer_user_chain = (
PromptTemplate.from_template(prompt_template_product)
| llm
|
| PromptTemplate.from_template(prompt_template_business)
| llm
).with_types(input_type=Dict[str, str], output_type=str)
is_product_chain = (
PromptTemplate.from_template(prompt_template_is_product)
| llm
| BooleanOutputParser(true_val='True', false_val='False')
).with_types(input_type=Dict[str, str], output_type=bool)
cannot_respond_chain = (
PromptTemplate.from_template(prompt_template_cannot_respond) | llm
).with_types(input_type=Dict[str, str], output_type=str)
full_chain = RunnableBranch(
(is_product_chain, answer_user_chain),
cannot_respond_chain
).with_types(input_type=Dict[str, str], output_type=str)
print(full_chain.invoke())
Dressons la liste des différences :
Pourquoi la LCEL est-elle meilleure pour l'industrialisation ?
Si je lisais cet article jusqu'à ce point précis, et que quelqu'un me demandait si j'étais convaincu par LCEL, je dirais probablement non. La syntaxe est trop différente, et je peux probablement organiser mon code en fonctions pour obtenir presque exactement le même code. Mais je suis ici, en train d'écrire cet article, alors il doit y avoir quelque chose de plus.
Invoke, stream et batch prêts à l'emploi
En utilisant LCEL, votre chaîne a automatiquement :
ma_chaîne = prompt | llm
# ---invoke--- #
result_with_invoke = my_chain.invoke(“hello world !”)
# ---lot--- #
result_with_batch = my_chain.batch([“hello”, “world”, “ !”])
# ---stream--- #
for chunk in my_chain.stream(“hello world !”) :
print(chunk, flush=True, end=””)
Lorsque vous itérez, vous pouvez utiliser la méthode invoke pour faciliter le processus de développement. Mais lorsque vous affichez la sortie de votre chaîne dans une interface utilisateur, vous souhaitez diffuser la réponse. Vous pouvez maintenant utiliser la méthode stream sans réécrire quoi que ce soit.
Méthodes asynchrones prêtes à l'emploi
La plupart du temps, le frontend et le backend de votre application seront séparés, ce qui signifie que le frontend fera une requête au backend. Si vous avez plusieurs utilisateurs, vous pouvez avoir besoin de gérer plusieurs requêtes en même temps sur votre backend.
Puisque la plupart du code de LangChain ne fait qu'attendre entre les appels à l'API, nous pouvons tirer parti du code asynchrone pour améliorer l'évolutivité de l'API. l'histoire des hamburgers simultanés de la documentation FastAPI.
Il n'est pas nécessaire de se préoccuper de l'implémentation, car les méthodes asynchrones sont déjà disponibles si vous utilisez LCEL :
.ainvoke() / .abatch() / .astream: versions asynchrones de invoke, batch et stream.
Je vous recommande également de lire le Pourquoi utiliser la page LCEL de la documentation LangChain ? avec des exemples pour chaque méthode synchrone / asynchrone.
Langchain a réalisé ces fonctionnalités “prêtes à l'emploi” en créant une interface unifiée appelée “Exécutable”. Maintenant, pour tirer pleinement parti de LCEL, nous devons nous plonger dans cette nouvelle interface Runnable.
L'interface Runnable
Tous les objets que nous avons utilisés dans la syntaxe LCEL jusqu'à présent sont des Runnables. Il s'agit d'un objet python créé par LangChain, cet objet hérite automatiquement de toutes les fonctionnalités dont nous avons parlé précédemment et de bien d'autres encore. En utilisant la syntaxe LCEL, nous composons un nouveau Runnable à chaque étape, ce qui signifie que l'objet final créé sera également un Runnable. Vous pouvez en savoir plus sur l'interface dans la documentation officielle.
Tous les objets du code ci-dessous sont soit des Runnables, soit des dictionnaires qui sont automatiquement convertis en Runnables :
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
numéro_de_chaîne = (
PromptTemplate.from_template(prompt_template_product)
| llm
| # <- CECI VA CHANGER
| PromptTemplate.from_template(prompt_template_business)
| llm
)
numéro_de_chaîne_deux = (
PromptTemplate.from_template(prompt_template_product)
| llm
| RunnableParallel(company=RunnablePassthrough()) # <- CECI A CHANGÉ
| PromptTemplate.from_template(prompt_template_business)
| llm
)
print(numéro_de_chaîne_un == numéro_de_chaîne_deux)
>>> Vrai
Pourquoi utiliser RunnableParallel() et pas simplement Runnable() ?
En effet, chaque Runnable à l'intérieur d'un RunnableParallel est exécuté en parallèle. Cela signifie que si vous avez 3 étapes indépendantes dans votre Runnable, elles s'exécuteront en même temps sur différents threads de votre machine, améliorant ainsi la vitesse de votre chaîne gratuitement !
Inconvénients de la LCEL
Malgré ses avantages, la LCEL présente quelques inconvénients potentiels :
Conclusion
En conclusion, LangChain Expression Language (LCEL) est un outil puissant qui apporte une nouvelle perspective à la construction d'applications Python. Malgré sa syntaxe peu conventionnelle, je vous recommande vivement d'utiliser LCEL pour les raisons suivantes :
Pour aller plus loin...
L'abstraction exécutable
Dans certains cas, je pense qu'il est important de comprendre l'abstraction que LangChain a mise en œuvre pour faire fonctionner la syntaxe LCEL.
Vous pouvez facilement réimplémenter les fonctionnalités de base de Runnable comme suit :
classe Runnable :
def __init__(self, func) :
self.func = func
def __or__(self, other) :
def chained_func(*args, **kwargs) :
# self.func est à gauche, other est à droite
return other(self.func(*args, **kwargs))
return Runnable(chained_func)
def __call__(self, *args, **kwargs) :
return self.func(*args, **kwargs)
def add_ten(x) :
retour x + 10
def divide_by_two(x) :
retour x / 2
runnable_add_ten = Runnable(add_ten)
runnable_divide_by_two = Runnable(divide_by_two)
chain = runnable_add_ten | runnable_divide_by_two
résultat = chain(8) # (8+10) / 2 = 9.0 devrait être la réponse
print(result)
>>> 9.0
Un Runnable est simplement un objet python dans lequel la méthode .__or__() a été écrasée.
En pratique, LangChain a ajouté de nombreuses fonctionnalités telles que la conversion de dictionnaires en Runnable, des capacités de typage, des capacités de configuration, et des méthodes invoke, batch, stream et async !
Alors, pourquoi ne pas essayer LCEL pour votre prochain projet ?
Si vous souhaitez en savoir plus, je vous recommande vivement de consulter le site suivant Livre de cuisine LangChain sur LCEL.

BLOG






