TL;DR
LangChain hat sich zu einem der meistgenutzten Python-Bibliothek zur Interaktion mit LLMs in weniger als einem Jahr, aber LangChain war hauptsächlich eine Bibliothek für POCs da ihm die Fähigkeit fehlte, eine komplexe und skalierbare Anwendungen.
Alles änderte sich im August 2023, als sie die LangChain-Ausdruckssprache (LCEL), eine neue Syntax, die die Lücke zwischen POC zur Produktion. Dieser Artikel führt Sie durch die Besonderheiten von LCEL und zeigt Ihnen, wie es die Erstellung von benutzerdefinierten Ketten und warum Sie es lernen müssen, wenn Sie bauen LLM-Bewerbungen!
Prompts, LLM und Ketten, lassen Sie uns unser Gedächtnis auffrischen
Bevor wir in die LCEL-Syntax eintauchen, sollten wir unsere Erinnerung an LangChain-Konzepte wie LLM und Prompt oder sogar eine Kette auffrischen.
LLM: In langchain ist llm eine Abstraktion des Modells, das zur Erstellung von Vervollständigungen wie openai gpt3.5, claude, etc. verwendet wird.
Eingabeaufforderung: Dies ist die Eingabe des LLM-Objekts, das die LLM-Fragen stellt und seine Ziele angibt.
Kette: Dies bezieht sich auf eine Folge von Aufrufen eines LLM oder eines beliebigen data-Verarbeitungsschritts.

Nun, da die Definitionen aus dem Weg geräumt sind, nehmen wir an, wir wollen ein Unternehmen gründen! Wir brauchen einen wirklich coolen und einprägsamen Namen und ein Geschäftsmodell, um Geld zu verdienen!
Beispiel - Firmenname & Geschäftsmodell mit alten Ketten
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
USER_INPUT = "bunte Socken"
llm = OpenAI(Temperatur=0)
prompt_template_product = "Was ist ein guter Name für ein Unternehmen, das ?"
firmen_name_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
firmenname_ausgabe = firmenname_kette(BENUTZER_EINGABE)
prompt_template_business = "Nennen Sie mir die beste Geschäftsmodellidee für mein Unternehmen namens: "
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
business_model_output = business_model_chain(unternehmen_name_output["text"])
print(Firmenname_Ausgabe)
print(business_model_output)
>>>
>>>
Das ist recht einfach nachzuvollziehen, wir sehen zwar ein wenig Redundanz, aber es ist überschaubar.
Lassen Sie uns einige Anpassungen vornehmen, um die Fälle zu behandeln, in denen der Benutzer unsere Kette nicht wie erwartet verwendet.
Vielleicht gibt der Benutzer etwas ein, das nichts mit dem Ziel unserer Kette zu tun hat? In diesem Fall möchten wir dies erkennen und entsprechend reagieren.
Beispiel - Anpassung & Routing mit alten Ketten
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
importieren ast
USER_INPUT = "Harrison Chase"
llm = OpenAI(Temperatur=0)
# -- Gleicher Code wie zuvor
prompt_template_product = "Was ist ein guter Name für ein Unternehmen, das ?"
firmen_name_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
prompt_template_business = "Nennen Sie mir die beste Geschäftsmodellidee für mein Unternehmen namens: "
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
# -- Neuer Code
prompt_template_is_product = (
"Ihr Ziel ist es, herauszufinden, ob die Eingabe des Benutzers ein plausibler Produktname ist"
"Fragen, Begrüßungen, lange Sätze, Prominente oder andere nicht relevante Eingaben werden nicht als Produkte angesehen."
"Eingabe: n"
"Antworten Sie nur mit 'Richtig' oder 'Falsch' und nichts weiter"
)
prompt_template_cannot_respond = (
"Sie können nicht auf die Benutzereingabe reagieren: n"
"Fordern Sie den Benutzer auf, den Namen eines Produkts einzugeben, damit Sie daraus ein Unternehmen machen können."
)
cannot_respond_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_cannot_respond))
firmen_name_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
is_a_product_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_is_product))
# Wenn wir bool auf einen nicht leeren str anwenden, wird er True sein, also brauchen wir `literal_eval`.
is_a_product = ast.literal_eval(is_a_product_chain(USER_INPUT)["text"])
if is_a_product:
firmenname_ausgabe = firmenname_kette(BENUTZER_EINGABE)
business_model_output = business_model_chain(unternehmen_name_output["text"])
print(business_model_output)
sonst:
print(cannot_respond_chain(USER_INPUT))
Das wird etwas schwieriger zu verstehen, fassen wir es zusammen:
Es gibt eine Reihe von Problemen, die sich daraus ergeben:
Was ist die LangChain Expression Language (LCEL)?
LCEL ist eine einheitliche Schnittstelle und Syntax, um zusammensetzbare produktionsbereite Ketten zu schreiben. Es gibt viel zu entpacken, um zu verstehen, was das bedeutet.
Wir werden zunächst versuchen, die neue Syntax zu verstehen, indem wir die Kette von vorhin neu schreiben.
Beispiel - Firmenname & Geschäftsmodell mit LCEL
from langchain_core.runnables import RunnablePassthrough
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
USER_INPUT = "bunte Socken"
llm = OpenAI(Temperatur=0)
prompt_template_product = "Was ist ein guter Name für ein Unternehmen, das ?"
prompt_template_business = "Nennen Sie mir die beste Geschäftsmodellidee für mein Unternehmen namens: "
Kette = (
PromptTemplate.from_template(prompt_template_product)
| llm
|
| PromptTemplate.from_template(prompt_template_business)
| llm
)
business_model_output = chain.invoke()
Eine Menge ungewöhnlicher Code, in nur wenigen Zeilen:
Aber ist es wirklich kompatibler, Ketten auf diese Weise zu erstellen?
Probieren wir es aus, indem wir die Funktion is_a_product_chain() und die Verzweigung hinzufügen, wenn die Benutzereingabe nicht korrekt ist. Wir können die Kette sogar mit Python Typing eintippen, lassen Sie uns das als gute Übung tun.
Beispiel - Anpassung & Routing mit LCEL
from typing import Dict
from langchain_core.runnables import RunnablePassthrough, RunnableBranch
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.output_parsers import BooleanOutputParser
from langchain_community.llms import OpenAI
USER_INPUT = "Harrrison Chase"
llm = OpenAI(Temperatur=0)
prompt_template_product = "Was ist ein guter Name für ein Unternehmen, das ?"
prompt_template_cannot_respond = (
"Sie können nicht auf die Benutzereingabe reagieren: n"
"Fordern Sie den Benutzer auf, den Namen eines Produkts einzugeben, damit Sie daraus ein Unternehmen machen können."
)
prompt_template_business = "Nennen Sie mir die beste Geschäftsmodellidee für mein Unternehmen namens: "
prompt_template_is_product = (
"Ihr Ziel ist es, herauszufinden, ob die Eingabe des Benutzers ein plausibler Produktname ist"
"Fragen, Begrüßungen, lange Sätze, Prominente oder andere nicht relevante Eingaben werden nicht als Produkte angesehen."
"Eingabe: n"
"Antworten Sie nur mit 'Richtig' oder 'Falsch' und nichts weiter"
)
answer_user_chain = (
PromptTemplate.from_template(prompt_template_product)
| llm
|
| PromptTemplate.from_template(prompt_template_business)
| llm
).with_types(input_type=Dict[str, str], output_type=str)
is_product_chain = (
PromptTemplate.from_template(prompt_template_is_product)
| llm
| BooleanOutputParser(true_val='True', false_val='False')
).with_types(input_type=Dict[str, str], output_type=bool)
cannot_respond_chain = (
PromptTemplate.from_template(prompt_template_cannot_respond) | llm
).with_types(input_type=Dict[str, str], output_type=str)
full_chain = RunnableBranch(
(is_product_chain, answer_user_chain),
kann_nicht_antworten_Kette
).with_types(input_type=Dict[str, str], output_type=str)
print(full_chain.invoke())
Lassen Sie uns die Unterschiede auflisten:
Warum ist LCEL besser für die Industrialisierung?
Wenn ich diesen Artikel bis genau zu diesem Punkt lesen würde und mich jemand fragen würde, ob ich von LCEL überzeugt bin, würde ich wahrscheinlich nein sagen. Die Syntax ist zu unterschiedlich und ich kann meinen Code wahrscheinlich in Funktionen organisieren, um fast genau den gleichen Code zu erhalten. Aber ich bin hier und schreibe diesen Artikel, also muss da noch etwas mehr sein.
Sofortiger Aufruf, Stream und Batch
Durch die Verwendung von LCEL hat Ihre Kette automatisch:
my_chain = prompt | llm
# ---invoke--- #
result_with_invoke = my_chain.invoke(“Hallo Welt!”)
# ---batch--- #
result_with_batch = my_chain.batch([“hallo”, “welt”, “!”])
# ---strom--- #
for chunk in my_chain.stream(“Hallo Welt!”):
print(chunk, flush=True, end=””)
Wenn Sie iterieren, können Sie die Methode invoke verwenden, um den Entwicklungsprozess zu vereinfachen. Aber wenn Sie die Ausgabe Ihrer Kette in einer Benutzeroberfläche anzeigen, möchten Sie die Antwort streamen. Sie können jetzt die stream-Methode verwenden, ohne etwas umzuschreiben.
Sofort einsatzbereite asynchrone Methoden
In den meisten Fällen werden Frontend und Backend Ihrer Anwendung getrennt sein, d.h. das Frontend wird eine Anfrage an das Backend stellen. Wenn Sie mehrere Benutzer haben, müssen Sie möglicherweise mehrere Anfragen an Ihr Backend gleichzeitig bearbeiten.
Da der meiste Code in LangChain nur zwischen den API-Aufrufen wartet, können wir asynchronen Code nutzen, um die Skalierbarkeit der API zu verbessern. Wenn Sie verstehen wollen, warum das wichtig ist, empfehle ich Ihnen die Lektüre des gleichzeitige Burger-Geschichte der FastAPI-Dokumentation.
Sie brauchen sich nicht um die Implementierung zu kümmern, denn asynchrone Methoden sind bereits verfügbar, wenn Sie LCEL verwenden:
.ainvoke() / .abatch() / .astream: Asynchrone Versionen von invoke, batch und stream.
Ich empfehle auch die Lektüre der Warum die LCEL-Seite aus der LangChain-Dokumentation verwenden? mit Beispielen für jede sync / async Methode.
Langchain hat diese “out of the box”-Funktionen durch die Entwicklung einer einheitlichen Schnittstelle namens “Ausführbar”. Um LCEL in vollem Umfang nutzen zu können, müssen wir uns mit dieser neuen Runnable-Schnittstelle befassen.
Die Schnittstelle Runnable
Alle Objekte, die wir bisher in der LCEL-Syntax verwendet haben, sind Runnables. Dabei handelt es sich um ein Python-Objekt, das von LangChain erstellt wird. Dieses Objekt erbt automatisch jede Funktion, über die wir zuvor gesprochen haben, und noch viel mehr. Durch die Verwendung der LCEL-Syntax komponieren wir bei jedem Schritt ein neues Runnable, was bedeutet, dass das letztendlich erstellte Objekt ebenfalls ein Runnable ist. Mehr über die Schnittstelle erfahren Sie in der offiziellen Dokumentation.
Alle Objekte aus dem folgenden Code sind entweder Runnable oder Dictionaries, die automatisch in ein Runnable konvertiert werden:
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
chain_number_one = (
PromptTemplate.from_template(prompt_template_product)
| llm
| # <- DAS WIRD SICH ÄNDERN
| PromptTemplate.from_template(prompt_template_business)
| llm
)
chain_number_two = (
PromptTemplate.from_template(prompt_template_product)
| llm
| RunnableParallel(Firma=RunnablePassthrough()) # <- DIES HAT SICH GEÄNDERT
| PromptTemplate.from_template(prompt_template_business)
| llm
)
print(ketten_nummer_eins == ketten_nummer_zwei)
>>> Wahr
Warum verwenden wir RunnableParallel() und nicht einfach Runnable()?
Denn jede Runnable innerhalb einer RunnableParallel wird parallel ausgeführt. Das heißt, wenn Sie 3 unabhängige Schritte in Ihrer Runnable haben, werden diese gleichzeitig auf verschiedenen Threads Ihres Rechners ausgeführt, was die Geschwindigkeit Ihrer Kette kostenlos erhöht!
Nachteile von LCEL
Trotz seiner Vorteile hat LCEL auch einige potenzielle Nachteile:
Fazit
Zusammenfassend lässt sich sagen, dass die LangChain Expression Language (LCEL) ein leistungsfähiges Tool ist, das der Entwicklung von Python-Anwendungen eine neue Perspektive verleiht. Trotz seiner unkonventionellen Syntax kann ich die Verwendung von LCEL aus folgenden Gründen nur empfehlen:
Um weiter zu gehen...
Die ablauffähige Abstraktion
In einigen Fällen halte ich es für wichtig, die Abstraktion zu verstehen, die LangChain implementiert hat, damit die LCEL-Syntax funktioniert.
Sie können die grundlegenden Funktionen von Runnable ganz einfach wie folgt neu implementieren:
class Runnable:
def __init__(self, func):
self.func = func
def __or__(self, other):
def chained_func(*args, **kwargs):
# self.func ist auf der linken Seite, andere ist auf der rechten Seite
return other(self.func(*args, **kwargs))
return Runnable(verkettete_Funktion)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
def add_ten(x):
Rückgabe x + 10
def divide_by_two(x):
Rückgabe x / 2
runnable_add_ten = Runnable(add_ten)
runnable_divide_by_two = Runnable(divide_by_two)
Kette = runnable_add_ten | runnable_divide_by_two
Ergebnis = Kette(8) # (8+10) / 2 = 9.0 sollte die Antwort sein
print(Ergebnis)
>>> 9.0
Ein Runnable ist einfach ein Python-Objekt, bei dem die Methode .__or__() überschrieben wurde.
In der Praxis hat LangChain viele Funktionen hinzugefügt, wie z.B. die Konvertierung von Dictionaries in Runnable, Typisierungsmöglichkeiten, Konfigurierbarkeit sowie Invoke-, Batch-, Stream- und Async-Methoden!
Warum probieren Sie LCEL also nicht bei Ihrem nächsten Projekt aus?
Wenn Sie mehr erfahren möchten, empfehle ich Ihnen einen Blick auf LangChain Kochbuch auf LCEL.

BLOG






