LCEL Einführung
LCEL (LangChain Expression Language) ist ein leistungsstarkes Workflow-Orchestrierungstool, mit dem Sie komplexe Aufgabenketten aus grundlegenden Komponenten erstellen können. Es unterstützt out-of-the-box-Funktionen wie Streaming-Verarbeitung, parallele Verarbeitung und Logging.
Grundbeispiel: Prompt + Model + Output-Parsing
In diesem Beispiel zeigen wir, wie man LCEL (LangChain Expression Language) verwendet, um drei Komponenten - Prompt-Vorlage, Modell und Output-Parser - miteinander zu verbinden, um einen vollständigen Workflow für die Umsetzung der Aufgabe "Witze erzählen" zu erstellen. Der Code zeigt, wie man Ketten erstellt, das Pipe-Symbol |
verwendet, um verschiedene Komponenten zu verbinden, und stellt die Rolle jeder Komponente zusammen mit den Ausgabenergebnissen vor.
Zunächst sehen wir, wie man die Prompt-Vorlage und das Modell verbindet, um einen Witz zu einem bestimmten Thema zu generieren:
Abhängigkeiten installieren
%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("Erzähl mir einen Witz über {topic}")
model = ChatOpenAI(model="gpt-4")
output_parser = StrOutputParser()
chain = prompt | model | output_parser
chain.invoke({"topic": "Eiscreme"})
Ausgabe
"Wieso lädt man keine Eiscreme zu Partys ein? Weil sie schmilzt, wenn es heiß ist!"
In diesem Code verwenden wir LCEL, um verschiedene Komponenten zu einer Kette zu verbinden:
chain = prompt | model | output_parser
Das |
-Symbol hier ähnelt dem Unix Pipe-Operator, der verschiedene Komponenten miteinander verbindet und die Ausgabe einer Komponente als Eingabe an die nächste Komponente übergibt.
In dieser Kette wird die Benutzereingabe an die Prompt-Vorlage übergeben, dann wird die Ausgabe der Prompt-Vorlage an das Modell übergeben und schließlich wird die Ausgabe des Modells an den Output-Parser übergeben. Werfen wir einen Blick auf jede Komponente separat, um besser zu verstehen, was passiert.
1. Prompt
prompt
ist eine BasePromptTemplate
, die ein Vorlagenvariablen-Dictionary akzeptiert und einen PromptValue
generiert. PromptValue
ist ein umschlossenes Objekt, das die Aufforderung enthält, die an LLM
(als Eingabe in Form eines Strings) oder ChatModel
(als Eingabe in Form von Nachrichtensequenzen) übergeben werden kann. Es kann mit jedem Typ von Sprachmodell verwendet werden, da es die Logik zum Generieren von BaseMessage
definiert, die Zeichenfolgen generiert.
prompt_value = prompt.invoke({"topic": "Eiscreme"})
prompt_value
Ausgabe
ChatPromptValue(messages=[HumanMessage(content='Erzähl mir einen Witz über Eiscreme')])
Im Folgenden wandeln wir das erhaltene Prompt-Format in das Nachrichtenformat um, das von Chat-Modellen verwendet wird:
prompt_value.to_messages()
Ausgabe
[HumanMessage(content='Erzähl mir einen Witz über Eiscreme')]
Es kann auch direkt in eine Zeichenfolge umgewandelt werden:
prompt_value.to_string()
Ausgabe
'Mensch: Erzähl mir einen Witz über Eiscreme.'
2. Modell
Als nächstes wird das PromptValue
an das Modell
übergeben. In diesem Beispiel ist unser Modell
ein ChatModel
, was bedeutet, dass es eine BaseMessage
ausgibt.
Versuchen Sie, das Modell
direkt aufzurufen:
message = model.invoke(prompt_value)
message
Ergebnis:
AIMessage(content="Wieso lädt man keine Eiscreme zu Partys ein? Weil sie schmilzt, wenn es heiß ist!")
Wenn unser Modell
als Typ LLM
definiert ist, gibt es eine Zeichenfolge aus.
from langchain_openai.llms import OpenAI
llm = OpenAI(model="gpt-3.5-turbo-instruct")
llm.invoke(prompt_value)
Modell liefert:
'\n\nBot: Warum ist der Eiscreme-Wagen liegengeblieben? Weil er einen Schmelzpunkt erreicht hat!'
3. Output-Parser
Schließlich wird die Ausgabe unseres Modells
an den Output-Parser
weitergeleitet, der ein BaseOutputParser
ist und eine Zeichenfolge oder BaseMessage
als Eingabe akzeptiert. Der StrOutputParser
wandelt speziell jede Eingabe in eine einfache Zeichenfolge um.
output_parser.invoke(message)
Wieso lädt man keine Eiscreme zu Partys ein? Weil sie schmilzt, wenn es heiß ist!
4. Der gesamte Prozess
Der Ausführungsprozess erfolgt wie folgt:
- Rufen Sie
chain.invoke({"topic": "Eis"})
auf. Dies entspricht der Initiierung des von uns definierten Workflows und der Übergabe des Parameters{"topic": "Eis"}
, um einen Witz über "Eis" zu generieren. - Übergeben Sie den Aufrufparameter
{"topic": "Eis"}
an das erste Glied der Kette,prompt
, das die Aufforderungsvorlage formatiert, um die AufforderungErzähl mir einen kleinen Witz über Eis
zu erhalten. - Übergeben Sie die Aufforderung
Erzähl mir einen kleinen Witz über Eis
an dasmodel
(gpt4-Modell). - Übergeben Sie das vom
model
zurückgegebene Ergebnis an denoutput_parser
, der das Modellergebnis formatiert und den endgültigen Inhalt zurückgibt.
Wenn Sie am Output eines bestimmten Komponenten interessiert sind, können Sie jederzeit eine kleinere Version der Kette testen, z.B. prompt
oder prompt | model
, um die Zwischenergebnisse zu sehen:
eingabe = {"topic": "Eis"}
prompt.invoke(eingabe)
(prompt | model).invoke(eingabe)
RAG-Suchbeispiel
Als nächstes erklären wir ein etwas komplexeres LCEL-Beispiel. Wir werden ein Beispiel für eine verbesserte Abfrage ausführen, um Ketten zu generieren, um Hintergrundinformationen hinzuzufügen, wenn Fragen beantwortet werden.
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 arbeitete bei Kensho", "Bären essen gerne Honig"],
einbettung=OpenAIEmbeddings(),
)
retriever = vectorstore.as_retriever()
vorlage = """Beantworten Sie die Frage nur auf Grundlage des folgenden Kontexts:
{kontext}
Frage: {frage}
"""
prompt = ChatPromptTemplate.from_template(vorlage)
model = ChatOpenAI()
output_parser = StrOutputParser()
setup_and_retrieval = RunnableParallel(
{"kontext": retriever, "frage": RunnablePassthrough()}
)
kette = setup_and_retrieval | prompt | model | output_parser
kette.invoke("Wo hat Harrison gearbeitet?")
In diesem Fall besteht die zusammengesetzte Kette aus:
kette = setup_and_retrieval | prompt | model | output_parser
Einfach ausgedrückt akzeptiert die obige Aufforderungsvorlage kontext
und frage
als Werte, die in der Aufforderung ersetzt werden sollen. Bevor die Aufforderungsvorlage erstellt wird, möchten wir relevante Dokumente abrufen, um sie als Teil des Kontexts zu verwenden.
Als Test verwenden wir DocArrayInMemorySearch
, um eine speicherbasierte Vektordatenbank zu simulieren und einen Retriever zu definieren, der ähnliche Dokumente anhand von Abfragen abrufen kann. Dies ist ebenfalls ein zusammenfügbarer ausführbarer Bestandteil, den Sie jedoch auch separat ausführen können:
retriever.invoke("Wo hat Harrison gearbeitet?")
Dann verwenden wir RunnableParallel
, um die Eingabe für die Aufforderung vorzubereiten, Dokumente mit dem Retriever zu suchen und die Frage des Benutzers mit RunnablePassthrough
zu übergeben:
setup_and_retrieval = RunnableParallel(
{"kontext": retriever, "frage": RunnablePassthrough()}
)
Zusammenfassend besteht die vollständige Kette aus:
setup_and_retrieval = RunnableParallel(
{"kontext": retriever, "frage": RunnablePassthrough()}
)
kette = setup_and_retrieval | prompt | model | output_parser
Der Prozess erfolgt wie folgt:
- Erstellen Sie zunächst ein
RunnableParallel
-Objekt mit zwei Einträgen. Der erste Eintragkontext
enthält die von der Suche zurückgegebenen Dokumente. Der zweite Eintragfrage
enthält die ursprüngliche Frage des Benutzers. Um die Frage zu übergeben, verwenden wirRunnablePassthrough
, um diesen Eintrag zu kopieren. - Übergeben Sie das Wörterbuch aus dem vorherigen Schritt an die
prompt
-Komponente. Sie akzeptiert die Benutzereingabe (d.h.frage
) sowie die abgerufenen Dokumente (d.h.kontext
), erstellt eine Aufforderung und gibt einenPromptValue
aus. - Die
model
-Komponente nimmt die erstellte Aufforderung entgegen und gibt sie an das LLM-Modell von OpenAI zur Auswertung weiter. Die Ausgabe des Modells ist eineChatMessage
. - Schließlich nimmt die
output_parser
-Komponente eineChatMessage
entgegen, konvertiert sie in einen Python-String und gibt sie aus derinvoke
-Methode zurück.