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 Eiszu erhalten. - Übergeben Sie die Aufforderung
Erzähl mir einen kleinen Witz über Eisan dasmodel(gpt4-Modell). - Übergeben Sie das vom
modelzurü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 Eintragkontextenthält die von der Suche zurückgegebenen Dokumente. Der zweite Eintragfrageenthä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 einenPromptValueaus. - 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 eineChatMessageentgegen, konvertiert sie in einen Python-String und gibt sie aus derinvoke-Methode zurück.