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:

  1. 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".
  2. Passare il parametro chiamato {"topic": "gelato"} al primo componente della catena, prompt, che formatta il modello di prompt per ottenere il prompt Dimmi una battuta sul gelato.
  3. Passare il prompt Dimmi una battuta sul gelato al modello (modello gpt4).
  4. Passare il risultato restituito dal modello all'analizzatore di output output_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:

  1. Innanzitutto, creare un oggetto RunnableParallel contenente due voci. La prima voce context includerà i risultati dei documenti estratti dal retriever. La seconda voce question conterrà la domanda originale dell'utente. Per passare la domanda, utilizziamo RunnablePassthrough per copiare questa voce.
  2. Passare il dizionario dal passaggio precedente al componente prompt. Accetta l'input dell'utente (ovvero question) così come i documenti recuperati (ovvero context), costruisce un prompt e restituisce un PromptValue.
  3. Il componente modello prende il prompt generato e lo passa al modello LLM di OpenAI per la valutazione. L'output generato dal modello è un oggetto ChatMessage.
  4. Infine, il componente output_parser prende un ChatMessage, lo converte in una stringa Python e lo restituisce dal metodo invoke.