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:

  1. 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.
  2. Übergeben Sie den Aufrufparameter {"topic": "Eis"} an das erste Glied der Kette, prompt, das die Aufforderungsvorlage formatiert, um die Aufforderung Erzähl mir einen kleinen Witz über Eis zu erhalten.
  3. Übergeben Sie die Aufforderung Erzähl mir einen kleinen Witz über Eis an das model (gpt4-Modell).
  4. Übergeben Sie das vom model zurückgegebene Ergebnis an den output_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:

  1. Erstellen Sie zunächst ein RunnableParallel-Objekt mit zwei Einträgen. Der erste Eintrag kontext enthält die von der Suche zurückgegebenen Dokumente. Der zweite Eintrag frage enthält die ursprüngliche Frage des Benutzers. Um die Frage zu übergeben, verwenden wir RunnablePassthrough, um diesen Eintrag zu kopieren.
  2. Ü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 einen PromptValue aus.
  3. 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 eine ChatMessage.
  4. Schließlich nimmt die output_parser-Komponente eine ChatMessage entgegen, konvertiert sie in einen Python-String und gibt sie aus der invoke-Methode zurück.