Introduction à LCEL
LCEL (LangChain Expression Language) est un puissant outil d'orchestration de flux de travail qui vous permet de construire des chaînes de tâches complexes à partir de composants de base et prend en charge des fonctionnalités prêtes à l'emploi telles que le traitement en continu, le traitement parallèle et la journalisation.
Exemple de base : Invite + Modèle + Analyseur de sortie
Dans cet exemple, nous allons démontrer comment utiliser LCEL (LangChain Expression Language) pour relier trois composants - un modèle d'invite, un modèle et un analyseur de sortie - afin de former un flux de travail complet pour implémenter la tâche de "raconter des blagues". Le code montre comment créer des chaînes, utiliser le symbole de pipeline |
pour connecter différents composants, et présente le rôle de chaque composant ainsi que les résultats de sortie.
Tout d'abord, voyons comment relier le modèle d'invite et le modèle pour générer une blague sur un sujet spécifique :
Installer les dépendances
%pip install --upgrade --quiet langchain-core langchain-community langchain-openai
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
invite = ChatPromptTemplate.from_template("Dis-moi une blague à propos de {topic}")
modèle = ChatOpenAI(model="gpt-4")
analyseur_sortie = StrOutputParser()
chaîne = invite | modèle | analyseur_sortie
chaîne.invoke({"topic": "glaces"})
Sortie
"Pourquoi les fêtes n'invitent-elles pas les glaces ? Parce qu'elles fondent quand il fait chaud !"
Dans ce code, nous utilisons LCEL pour relier différents composants en une chaîne :
chaîne = invite | modèle | analyseur_sortie
Le symbole |
ici est similaire à l'opérateur de tube Unix pipe operator, qui connecte différents composants et transmet la sortie d'un composant en entrée au composant suivant.
Dans cette chaîne, l'entrée de l'utilisateur est transmise au modèle d'invite, puis la sortie du modèle d'invite est transmise au modèle, et enfin la sortie du modèle est transmise à l'analyseur de sortie. Jetons un coup d'oeil à chaque composant séparément pour mieux comprendre ce qui se passe.
1. Invite
L'invite
est un BasePromptTemplate
qui accepte un dictionnaire de variables de modèle et génère une PromptValue
. PromptValue
est un objet encapsulé contenant l'invite, qui peut être transmis à LLM
(en entrée sous forme de chaîne) ou à ChatModel
(en entrée sous forme de séquences de messages). Il peut être utilisé avec n'importe quel type de modèle de langue car il définit la logique pour générer BaseMessage
et générer des chaînes.
invite_value = invite.invoke({"topic": "glaces"})
invite_value
Sortie
ChatPromptValue(messages=[HumanMessage(content="Dis-moi une blague à propos de glaces")])
En dessous, nous convertissons le résultat formaté de l'invite en format message utilisé par les modèles de conversation :
invite_value.to_messages()
Sortie
[HumanMessage(content="Dis-moi une blague à propos de glaces")]
Il peut également être directement converti en chaîne :
invite_value.to_string()
Sortie
'Humain : Dis-moi une blague à propos de glaces.'
2. Modèle
Ensuite, transmettre la PromptValue
au modèle
. Dans cet exemple, notre modèle
est un ChatModel
, ce qui signifie qu'il produira un BaseMessage
.
Essayons d'appeler le modèle
directement :
message = modèle.invoke(invite_value)
message
Retourne :
AIMessage(content="Pourquoi les fêtes n'invitent-elles pas les glaces ? Parce qu'elles fondent quand il fait chaud !")
Si notre modèle
est défini comme un type LLM
, il produira une chaîne.
from langchain_openai.llms import OpenAI
llm = OpenAI(model="gpt-3.5-turbo-instruct")
llm.invoke(invite_value)
Le modèle retourne :
'\n\nBot: Pourquoi le camion de glaces est-il tombé en panne ? Parce qu'il a fondu de l'intérieur !'
3. Analyseur de sortie
Enfin, transmettre la sortie de notre modèle
à l'analyseur_sortie
, qui est un BaseOutputParser
, ce qui signifie qu'il accepte une chaîne ou un BaseMessage
en entrée. StrOutputParser
convertit spécifiquement n'importe quelle entrée en une simple chaîne.
analyseur_sortie.invoke(message)
Pourquoi les fêtes n'invitent-elles pas les glaces ? \n\nParce qu'elles fondent quand il fait chaud !
4. L'ensemble du processus
Le processus d'exécution est le suivant :
- Appeler
chain.invoke({"topic": "glace"})
, ce qui revient à initialiser le workflow que nous avons défini et à passer le paramètre{"topic": "glace"}
pour générer une blague sur "la glace." - Passer le paramètre d'appel
{"topic": "glace"}
au premier composant de la chaîne,prompt
, qui formate le modèle de prompt pour obtenir le promptDis-moi une petite blague sur la glace
. - Passer le prompt
Dis-moi une petite blague sur la glace
aumodel
(modèle gpt4). - Passer le résultat renvoyé par le
model
au parseur de sortieoutput_parser
, qui formate le résultat du modèle et renvoie le contenu final.
Si vous êtes intéressé par la sortie de n'importe quel composant, vous pouvez tester une version plus petite de la chaîne à tout moment, comme prompt
ou prompt | model
, pour voir les résultats intermédiaires :
input = {"topic": "glace"}
prompt.invoke(input)
(prompt | model).invoke(input)
Exemple de recherche RAG
Ensuite, expliquons un exemple LCEL légèrement plus complexe. Nous allons exécuter un exemple de recherche améliorée pour générer des chaînes, afin d'ajouter des informations de contexte lors de la réponse aux questions.
from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_openai.chat_models import ChatOpenAI
from langchain_openai.embeddings import OpenAIEmbeddings
vectorstore = DocArrayInMemorySearch.from_texts(
["harrison a travaillé chez kensho", "les ours aiment manger du miel"],
embedding=OpenAIEmbeddings(),
)
retriever = vectorstore.as_retriever()
template = """Répondez à la question uniquement sur la base du contexte suivant :
{context}
Question : {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()
output_parser = StrOutputParser()
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser
chain.invoke("où a travaillé harrison ?")
Dans ce cas, la chaîne composée est :
chain = setup_and_retrieval | prompt | model | output_parser
En résumé, le modèle de prompt ci-dessus accepte context
et question
comme valeurs à remplacer dans le prompt. Avant de construire le modèle de prompt, nous voulons récupérer les documents pertinents à utiliser comme partie du contexte.
En guise de test, nous utilisons DocArrayInMemorySearch
pour simuler une base de données vectorielle en mémoire, en définissant un récupérateur qui peut récupérer des documents similaires en fonction des requêtes. Il s'agit également d'un composant exécutable chainable, mais vous pouvez également essayer de l'exécuter séparément :
retriever.invoke("où a travaillé harrison ?")
Ensuite, nous utilisons RunnableParallel
pour préparer l'entrée pour le prompt, rechercher des documents à l'aide du récupérateur, et passer la question de l'utilisateur en utilisant RunnablePassthrough
:
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
En résumé, la chaîne complète est :
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser
Le processus est le suivant :
- Tout d'abord, créer un objet
RunnableParallel
contenant deux entrées. La première entréecontext
inclura les résultats de document extraits par le récupérateur. La deuxième entréequestion
contiendra la question originale de l'utilisateur. Pour passer la question, nous utilisonsRunnablePassthrough
pour copier cette entrée. - Passer le dictionnaire de l'étape précédente au composant
prompt
. Il accepte l'entrée de l'utilisateur (c'est-à-direquestion
) ainsi que les documents récupérés (c'est-à-direcontext
), construit un prompt, et renvoie unePromptValue
. - Le composant
model
prend le prompt généré et le passe au modèle LLM d'OpenAI pour évaluation. La sortie générée par le modèle est un objetChatMessage
. - Enfin, le composant
output_parser
prend unChatMessage
, le convertit en une chaîne Python, et le renvoie à partir de la méthodeinvoke
.