TL;DR
LangChain hat sich in weniger als einem Jahr zu einer der meistgenutzten Python-Bibliotheken für die Interaktion mit LLMs entwickelt, aber LangChain war hauptsächlich eine Bibliothek für POCs, da ihr die Fähigkeit fehlte, komplexe und skalierbare Anwendungen zu erstellen.
Das änderte sich im August 2023 mit der Veröffentlichung der LangChain Expression Language (LCEL), einer neuen Syntax, die die Lücke zwischen POC und Produktion schließt. Dieser Artikel führt Sie durch die Besonderheiten von LCEL und zeigt Ihnen, wie es die Erstellung von benutzerdefinierten Ketten vereinfacht und warum Sie es unbedingt lernen müssen, wenn Sie LLM-Anwendungen erstellen!
Prompts, LLM und Ketten, zur Auffrischung unseres Gedächtnisses
Bevor wir in die LCEL-Syntax eintauchen, sollten wir unser Gedächtnis über LangChain-Konzepte wie LLM und Prompt oder sogar eine Kette auffrischen.
LLM: In langchain, llm ist eine Abstraktion um das Modell verwendet, um die Vervollständigungen wie openai gpt3.5, Claude, etc.
Aufforderung: Dies ist die Eingabe des LLM-Objekts, das dem 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 importiere PromptTemplate
from langchain_community.llms importieren OpenAI
USER_INPUT = "bunte Socken"
llm = OpenAI(Temperatur=0)
prompt_template_product = "Was ist ein guter Name für ein Unternehmen, das {Produkt} herstellt?"
firmen_name_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
firmenname_output = firmenname_chain(USER_INPUT)
prompt_template_business = “Give me the best business model idea for my company named: {company}”
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
business_model_output = business_model_chain(company_name_output[“text”])
print(Firmenname_Output)
print(business_model_output)
>>> {‘product’: ‘colorful socks’, ‘text’: ‘Socktastic!’}
>>> {‘company’: ‘Socktastic!’,’text’: “A subscription-based service offering a monthly delivery…”}
Das ist recht einfach nachzuvollziehen, es gibt zwar ein wenig Redundanz, aber es ist überschaubar.
Fügen wir einige Anpassungen hinzu, indem wir die Fälle 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 wollen wir dies erkennen und entsprechend reagieren.
Beispiel - Anpassung & Routing mit alten Ketten
from langchain.chains import LLMChain
from langchain.prompts importiere PromptTemplate
von langchain_community.llms importieren 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 {Produkt} herstellt?"
company_name_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
prompt_template_business = “Give me the best business model idea for my company named: {company}”
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
# -- Neuer Code
prompt_template_is_product = (
“Your goal is to find if the input of the user is a plausible product namen”
“Questions, greetings, long sentences, celebrities or other non relevant inputs are not considered productsn”
“input: {product}n”
“Answer only by ‘True’ or ‘False’ and nothing moren”
)
prompt_template_cannot_respond = (
“You cannot respond to the user input: {product}n”
“Ask the user to input the name of a product in order for you to make a company out of it.n”
)
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 es 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(firmen_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 zum Schreiben von zusammensetzbaren, produktionsbereiten Ketten. 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 umschreiben.
Beispiel - Firmenname & Geschäftsmodell mit LCEL
from langchain_core.runnables import RunnablePassthrough
from langchain.prompts importieren PromptTemplate
from langchain_community.llms importieren OpenAI
USER_INPUT = "bunte Socken"
llm = OpenAI(Temperatur=0)
prompt_template_product = “What is a good name for a company that makes {product}?”
prompt_template_business = “Give me the best business model idea for my company named: {company}”
chain = (
PromptTemplate.from_template(prompt_template_product)
| llm
| {‘company’: RunnablePassthrough()}
| PromptTemplate.from_template(prompt_template_business)
| llm
)
business_model_output = chain.invoke({‘product’: ‘colorful socks’})
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 tippen, lassen Sie uns dies als gute Übung tun.
Beispiel - Anpassung & Routing mit LCEL
from typing import Dict
from langchain_core.runnables import RunnablePassthrough, RunnableBranch
from langchain.prompts importiere PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.output_parsers import BooleanOutputParser
from langchain_community.llms importieren OpenAI
USER_INPUT = "Harrrison Chase"
llm = OpenAI(Temperatur=0)
prompt_template_product = “What is a good name for a company that makes {product}?”
prompt_template_cannot_respond = (
“You cannot respond to the user input: {product}n”
“Ask the user to input the name of a product in order for you to make a company out of it.n”
)
prompt_template_business = “Give me the best business model idea for my company named: {company}”
prompt_template_is_product = (
“Your goal is to find if the input of the user is a plausible product namen”
“Questions, greetings, long sentences, celebrities or other non relevant inputs are not considered productsn”
“input: {product}n”
“Answer only by ‘True’ or ‘False’ and nothing moren”
)
answer_user_chain = (
PromptTemplate.from_template(prompt_template_product)
| llm
| {‘company’: RunnablePassthrough()}
| 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),
cannot_respond_chain
).with_types(input_type=Dict[str, str], output_type=str)
print(full_chain.invoke({‘product’: USER_INPUT}))
Wir wollen 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 anderes 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("hello world!")
# ---batch--- #
result_with_batch = my_chain.batch(["hello", "world", "!"])
# ---stream--- #
for chunk in my_chain.stream("hello world!"):
print(chunk, flush=True, end="")
Wenn Sie iterieren, können Sie die invoke-Methode verwenden, um den Entwicklungsprozess zu vereinfachen. Wenn Sie jedoch 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.
Standardmäßige asynchrone Methoden
In den meisten Fällen werden Frontend und Backend Ihrer Anwendung getrennt sein, d.h. das Frontend stellt eine Anfrage an das Backend. 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 Concurrent-Burger-Geschichte in der FastAPI-Dokumentation zu lesen.
Um die Implementierung brauchen Sie sich nicht 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 das Lesen der Seite Why use LCEL aus der LangChain-Dokumentation mit Beispielen für jede sync / async Methode.
Langchain hat diese "out of the box"-Funktionen durch die Schaffung einer einheitlichen Schnittstelle namens "Runnable" erreicht . Um LCEL in vollem Umfang nutzen zu können, müssen wir uns nun mit dieser neuen Runnable-Schnittstelle befassen.
Die Schnittstelle Runnable
Alle Objekte, die wir bisher in der LCEL-Syntax verwendet haben, sind Runnables. Es handelt 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 wird bei jedem Schritt ein neues Runnable erstellt, was bedeutet, dass das endgültige Objekt ebenfalls ein Runnable ist. Weitere Informationen über die Schnittstelle finden Sie in der offiziellen Dokumentation.
Alle Objekte des folgenden Codes sind entweder Runnable oder Dictionaries, die automatisch in Runnable umgewandelt werden:
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain.prompts importiere PromptTemplate
from langchain_community.llms importieren OpenAI
chain_number_one = (
PromptTemplate.from_template(prompt_template_product)
| llm
| {‘company’: RunnablePassthrough()} # <— THIS WILL CHANGE
| PromptTemplate.from_template(prompt_template_business)
| llm
)
chain_number_two = (
PromptTemplate.from_template(prompt_template_product)
| llm
| RunnableParallel(company=RunnablePassthrough()) # <— THIS CHANGED
| 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 Ihrer Maschine ausgeführt, was die Geschwindigkeit Ihrer Kette kostenlos erhöht!
Nachteile von LCEL
Trotz seiner Vorteile hat LCEL auch einige potenzielle Nachteile:
Schlussfolgerung
Zusammenfassend lässt sich sagen, dass die LangChain Expression Language (LCEL) ein leistungsfähiges Werkzeug 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:
Weiter gehen...
Die lauffä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 wie folgt einfach neu implementieren:
class Runnable:
def __init__(self, func):
self.func = func
def __or__(self, other):
def chained_func(*args, **kwargs):
# self.func ist links, other ist rechts
return other(self.func(*args, **kwargs))
return Runnable(chained_func)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
def add_ten(x):
return x + 10
def divide_by_two(x):
return x / 2
runnable_add_ten = Runnable(add_ten)
runnable_divide_by_two = Runnable(divide_by_two)
Kette = runnable_add_ten | runnable_divide_by_two
result = chain(8) # (8+10) / 2 = 9.0 sollte die Antwort sein
print(ergebnis)
>>> 9.0
Ein Runnable ist einfach ein Python-Objekt, in dem die Methode .__or__() überschrieben wurde.
In der Praxis hat LangChain viele Funktionalitäten hinzugefügt, wie z.B. die Konvertierung von Dictionaries in Runnable, Typisierungsfähigkeiten, Konfigurationsfähigkeiten und Invoke-, Batch-, Stream- und Async-Methoden!
Warum probieren Sie LCEL also nicht bei Ihrem nächsten Projekt aus?
Wenn Sie mehr darüber erfahren möchten, empfehle ich Ihnen, das LangChain-Kochbuch auf LCEL zu lesen.