Introduzione a LCEL
LCEL (LangChain Expression Language) è un potente strumento di orchestrare flussi di lavoro che ti consente di costruire catene di attività complesse da componenti di base e supporta funzionalità preconfigurate come l'elaborazione in streaming, l'elaborazione parallela e la registrazione.
Esempio di base: Prompt + Modello + Analisi Output
In questo esempio, mostreremo come utilizzare LCEL (LangChain Expression Language) per collegare tre componenti - modello di prompt, modello e analisi degli output - per formare un flusso di lavoro completo per implementare il compito di "raccontare barzellette". Il codice illustra come creare catene, utilizzare il simbolo pipe |
per collegare componenti diversi e introduce il ruolo di ciascun componente insieme ai risultati dell'output.
Innanzitutto, vediamo come collegare il modello di prompt e il modello per generare una barzelletta su un argomento specifico:
Installare le dipendenze
%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
prompt = ChatPromptTemplate.from_template("Raccontami una barzelletta sull'{argomento}")
model = ChatOpenAI(model="gpt-4")
output_parser = StrOutputParser()
chain = prompt | model | output_parser
chain.invoke({"argomento": "gelato"})
Output
"Perché non invitano mai il gelato alle feste? Perché si scioglie quando fa caldo!"
In questo codice, utilizziamo LCEL per collegare diversi componenti in una catena:
chain = prompt | model | output_parser
Il simbolo |
qui è simile all'operatore di pipe Unix, che collega insieme diversi componenti e passa l'output di un componente come input al componente successivo.
In questa catena, l'input dell'utente viene passato al modello di prompt, quindi l'output del modello di prompt viene passato al modello e infine l'output del modello viene passato all'analisi degli output. Diamo un'occhiata a ciascun componente separatamente per capire meglio cosa sta succedendo.
1. Prompt
Il prompt
è un BasePromptTemplate
che accetta un dizionario di variabili di modello e genera un PromptValue
. PromptValue
è un oggetto incapsulato contenente il prompt, che può essere passato a LLM
(come input sotto forma di stringa) o a ChatModel
(come input sotto forma di sequenze di messaggi). Può essere utilizzato con qualsiasi tipo di modello linguistico perché definisce la logica per generare BaseMessage
e la generazione di stringhe.
prompt_value = prompt.invoke({"argomento": "gelato"})
prompt_value
Output
ChatPromptValue(messaggi=[MessaggioUmano(contenuto='Raccontami una barzelletta sull'argomento gelato')])
Di seguito, convertiamo il risultato formattato del prompt nel formato di messaggio usato dai modelli di chat:
prompt_value.to_messages()
Output
[MessaggioUmano(contenuto='Raccontami una barzelletta sull'argomento gelato')]
Può anche essere convertito direttamente in una stringa:
prompt_value.to_string()
Output
'Umano: Raccontami una barzelletta sull'argomento gelato.'
2. Modello
Successivamente, passiamo il PromptValue
al modello
. In questo esempio, il nostro modello
è un ChatModel
, il che significa che produrrà un BaseMessage
.
Proviamo a chiamare direttamente il modello
:
messaggio = model.invoke(prompt_value)
messaggio
Restituisce:
AIMessage(contenuto="Perché il gelato non viene mai invitato alle feste?\n\nPerché si scioglie sempre quando le cose si riscaldano!")
Se il nostro modello
è definito come tipo LLM
, produrrà una stringa.
from langchain_openai.llms import OpenAI
llm = OpenAI(model="gpt-3.5-turbo-instruct")
llm.invoke(prompt_value)
Il modello restituisce:
'\n\nBot: Perché il furgone del gelato si è rotto? Perché ha avuto un crollo!'
3. Analisi degli Output
Infine, passiamo l'output del nostro modello
all'output_parser
, che è un BaseOutputParser
, il che significa che accetta una stringa o BaseMessage
come input. StrOutputParser
converte specificamente qualsiasi input in una stringa semplice.
output_parser.invoke(messaggio)
Perché il gelato non viene mai invitato alle feste? \n\nPerché si scioglie sempre quando le cose si riscaldano!
4. L'intero processo
Il processo di esecuzione è il seguente:
- Chiamare
chain.invoke({"topic": "gelato"})
, che equivale a iniziare il workflow che abbiamo definito e passare il parametro{"topic": "gelato"}
per generare una battuta sul "gelato". - Passare il parametro chiamato
{"topic": "gelato"}
al primo componente della catena,prompt
, che formatta il modello di prompt per ottenere il promptDimmi una battuta sul gelato
. - Passare il prompt
Dimmi una battuta sul gelato
almodello
(modello gpt4). - Passare il risultato restituito dal
modello
all'analizzatore di outputoutput_parser
, che formatta il risultato del modello e restituisce il contenuto finale.
Se sei interessato all'output di un qualsiasi componente, puoi testare in qualsiasi momento una versione più piccola della catena, come prompt
o prompt | model
, per vedere i risultati intermedi:
input = {"topic": "gelato"}
prompt.invoke(input)
(prompt | model).invoke(input)
Esempio di Ricerca RAG
Ora, spieghiamo un esempio LCEL leggermente più complesso. Eseguiremo un esempio di retrieval avanzato per generare catene, al fine di aggiungere alcune informazioni di background quando si rispondono alle domande.
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 worked at kensho", "bears like to eat honey"],
embedding=OpenAIEmbeddings(),
)
retriever = vectorstore.as_retriever()
template = """Rispondi alla domanda basandoti solo sul contesto seguente:
{context}
Domanda: {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("dove ha lavorato harrison?")
In questo caso, la catena composta è:
chain = setup_and_retrieval | prompt | model | output_parser
In poche parole, il modello di prompt sopra accetta context
e question
come valori da sostituire nel prompt. Prima di costruire il modello di prompt, vogliamo recuperare documenti pertinenti da utilizzare come parte del contesto.
Come test, utilizziamo DocArrayInMemorySearch
per simulare un database di vettori basato su memoria, definendo un retriever che può recuperare documenti simili in base alle query. Anche questo è un componente runnable concatenabile, ma è possibile provare ad eseguirlo separatamente:
retriever.invoke("dove ha lavorato harrison?")
Poi, utilizziamo RunnableParallel
per preparare l'input per il prompt, cercare documenti utilizzando il retriever, e passare la domanda dell'utente utilizzando RunnablePassthrough
:
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
In sintesi, la catena completa è:
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser
Il processo è il seguente:
- Innanzitutto, creare un oggetto
RunnableParallel
contenente due voci. La prima vocecontext
includerà i risultati dei documenti estratti dal retriever. La seconda vocequestion
conterrà la domanda originale dell'utente. Per passare la domanda, utilizziamoRunnablePassthrough
per copiare questa voce. - Passare il dizionario dal passaggio precedente al componente
prompt
. Accetta l'input dell'utente (ovveroquestion
) così come i documenti recuperati (ovverocontext
), costruisce un prompt e restituisce unPromptValue
. - Il componente
modello
prende il prompt generato e lo passa al modello LLM di OpenAI per la valutazione. L'output generato dal modello è un oggettoChatMessage
. - Infine, il componente
output_parser
prende unChatMessage
, lo converte in una stringa Python e lo restituisce dal metodoinvoke
.