Kurz gesagt
LangChain hat sich in weniger als einem Jahr zu einer der meistgenutzten Python-Bibliotheken für die Interaktion mit LLMs entwickelt, doch LangChain diente bislang vor allem als Bibliothek für Proof-of-Concepts (POCs), da es an der Fähigkeit mangelte, komplexe und skalierbare Anwendungen zu erstellen.
Das änderte sich im August 2023, als LangChain Expression Language (LCEL) veröffentlicht wurde – eine neue Syntax, die die Lücke zwischen Proof-of-Concept und Produktion schließt. Dieser Artikel führt Sie durch die Besonderheiten von LCEL und zeigt Ihnen, wie es die Erstellung benutzerdefinierter Ketten vereinfacht und warum Sie es lernen müssen, wenn Sie LLM-Anwendungen entwickeln!
Prompts, LLM und Chains – frischen wir unser Gedächtnis auf
Bevor wir uns mit der LCEL-Syntax befassen, halte ich es für sinnvoll, unser Wissen über LangChain-Konzepte wie LLM, Prompt oder auch die Chain aufzufrischen.
LLM: In Langchain ist ein LLM eine Abstraktion des Modells, das für die Textvervollständigung verwendet wird, wie beispielsweise OpenAI GPT-3.5, Claude usw.
Eingabeaufforderung: Dies ist die Eingabe für das LLM-Objekt, das dem LLM Fragen stellt und dessen Ziele vorgibt.
Kette: Damit ist eine Abfolge von Aufrufen eines LLM oder beliebiger data gemeint.

Nachdem wir nun die Definitionen geklärt haben, nehmen wir einmal an, wir wollen ein Unternehmen gründen! Wir brauchen einen richtig coolen und einprägsamen Namen und ein Geschäftsmodell, um Geld zu verdienen!
Beispiel – Firmenname und Geschäftsmodell mit „Old Chains“
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
USER_INPUT = "bunte Socken"
llm = OpenAI(temperature=0)
prompt_template_product = "Wie lautet ein guter Name für ein Unternehmen, das ... herstellt?"
company_name_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
company_name_output = company_name_chain(USER_INPUT)
prompt_template_business = "Gib mir die beste Idee für ein Geschäftsmodell für mein Unternehmen mit dem Namen: "
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
business_model_output = business_model_chain(company_name_output["text"])
print(company_name_output)
print(business_model_output)
>>>
>>>
Das ist recht leicht nachzuvollziehen; es gibt zwar ein paar Wiederholungen, aber das ist zu verkraften.
Lassen Sie uns einige Anpassungen vornehmen, indem wir Fälle behandeln, in denen der Benutzer unsere Kette nicht wie erwartet nutzt.
Vielleicht gibt der Benutzer etwas ein, das überhaupt nichts mit dem Ziel unserer Kette zu tun hat? In diesem Fall möchten wir dies erkennen und entsprechend reagieren.
Beispiel – Anpassung und Weiterleitung mit alten Ketten
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
import ast
USER_INPUT = "Harrison Chase"
llm = OpenAI(temperature=0)
# —- Gleicher Code wie zuvor
prompt_template_product = "Wie lautet ein guter Name für ein Unternehmen, das ? herstellt?"
company_name_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
prompt_template_business = "Gib mir die beste Idee für ein Geschäftsmodell für mein Unternehmen mit dem Namen: "
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
# —- Neuer Code
prompt_template_is_product = (
"Deine Aufgabe ist es, festzustellen, ob die Eingabe des Benutzers ein plausibler Produktname ist"
"Fragen, Begrüßungen, lange Sätze, Prominente oder andere irrelevante Eingaben gelten nicht als Produktnamen"
"Eingabe: n"
"Antworte nur mit 'True' oder 'False' und nichts weiter"
)
prompt_template_cannot_respond = (
"Du kannst auf die Benutzereingabe nicht reagieren: n"
"Bitte den Benutzer, den Namen eines Produkts einzugeben, damit du daraus ein Unternehmen bilden kannst.n"
)
cannot_respond_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_cannot_respond))
company_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 eine nicht leere Zeichenkette anwenden, ergibt dies „True“; daher benötigen wir „literal_eval“
is_a_product = ast.literal_eval(is_a_product_chain(USER_INPUT)["text"])
if is_a_product:
company_name_output = company_name_chain(USER_INPUT)
business_model_output = business_model_chain(company_name_output["text"])
print(business_model_output)
else:
print(cannot_respond_chain(USER_INPUT))
Das wird etwas schwieriger zu verstehen, fassen wir also zusammen:
Es treten nach und nach mehrere Probleme auf:
Was ist die LangChain Expression Language (LCEL)?
LCEL ist eine einheitliche Schnittstelle und Syntax zum Erstellen von kombinierbaren, produktionsreifen Ketten; es gibt viel zu erklären, 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 und 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(temperature=0)
prompt_template_product = "Was wäre ein guter Name für ein Unternehmen, das … herstellt?"
prompt_template_business = "Schlag mir die beste Geschäftsmodellidee für mein Unternehmen mit dem Namen … vor:"
chain = (
PromptTemplate.from_template(prompt_template_product)
| llm
|
| PromptTemplate.from_template(prompt_template_business)
| llm
)
business_model_output = chain.invoke()
Viel ungewöhnlicher Code, in nur wenigen Zeilen:
Aber lässt sich auf diese Weise wirklich eine Kette besser zusammenstellen?
Probieren wir es aus, indem wir die Funktion `is_a_product_chain()` hinzufügen und eine Verzweigung einbauen, falls die Benutzereingabe nicht korrekt ist. Wir können die Kette sogar mit Python Typing typisieren – machen wir das als bewährte Vorgehensweise.
Beispiel – Anpassung und Weiterleitung 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(temperature=0)
prompt_template_product = "Wie lautet ein guter Name für ein Unternehmen, das ? herstellt?"
prompt_template_cannot_respond = (
"Du kannst auf die Benutzereingabe nicht antworten: n"
"Bitte den Benutzer, den Namen eines Produkts einzugeben, damit du daraus ein Unternehmen machen kannst.n"
)
prompt_template_business = "Gib mir die beste Geschäftsmodell-Idee für mein Unternehmen mit dem Namen: "
prompt_template_is_product = (
"Dein Ziel ist es, herauszufinden, ob die Eingabe des Benutzers ein plausibler Produktname ist"
"Fragen, Begrüßungen, lange Sätze, Prominente oder andere irrelevante Eingaben gelten nicht als Produktnamen"
"Eingabe: n"
"Antworte nur mit 'True' oder 'False' 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),
cannot_respond_chain
).with_types(input_type=Dict[str, str], output_type=str)
print(full_chain.invoke())
Zählen wir die Unterschiede auf:
Warum eignet sich LCEL besser für die industrielle Fertigung?
Hätte ich diesen Artikel bis genau zu dieser Stelle gelesen und mich jemand gefragt, ob ich von LCEL überzeugt sei, hätte ich wahrscheinlich mit Nein geantwortet. Die Syntax ist zu anders, und ich könnte meinen Code wahrscheinlich in Funktionen gliedern, um fast genau denselben Code zu erhalten. Aber ich sitze hier und schreibe diesen Artikel, also muss es noch mehr geben.
Sofort einsatzbereit: Aufrufen, Streamen und Batch-Verarbeitung
Durch den Einsatz von LCEL verfügt Ihre Kette automatisch über:
my_chain = Eingabeaufforderung | 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=„“)
Bei der Iteration können Sie die `invoke`-Methode nutzen, um den Entwicklungsprozess zu vereinfachen. Wenn Sie jedoch die Ausgabe Ihrer Kette in einer Benutzeroberfläche anzeigen möchten, sollten Sie die Antwort streamen. Sie können nun die `stream`-Methode verwenden, ohne etwas umschreiben zu müssen.
Vorkonfigurierte asynchrone Methoden
In den meisten Fällen sind Frontend und Backend Ihrer Anwendung voneinander getrennt, was bedeutet, dass das Frontend eine Anfrage an das Backend sendet. Wenn Sie mehrere Benutzer haben, müssen Sie möglicherweise mehrere Anfragen gleichzeitig in Ihrem Backend bearbeiten.
Da der Großteil des Codes in LangChain lediglich aus Wartezeiten zwischen API-Aufrufen besteht, können wir asynchrone Code-Strukturen nutzen, um die Skalierbarkeit der API zu verbessern. Wenn Sie verstehen möchten, warum dies wichtig ist, empfehle ich Ihnen, die „Concurrent Burgers“-Geschichte in der FastAPI-Dokumentation zu lesen.
Sie müssen sich keine Gedanken über die Umsetzung machen, da asynchrone Methoden bereits verfügbar sind, wenn Sie LCEL verwenden:
.ainvoke() / .abatch() / .astream: Asynchrone Versionen von invoke, batch und stream.
Ich empfehle außerdem, die Seite „Why use LCEL“ aus der LangChain-Dokumentation zu lesen, die Beispiele für jede synchrone bzw. asynchrone Methode enthält.
Langchain hat diese „Out-of-the-Box“-Funktionen durch die Entwicklung einer einheitlichen Schnittstelle namens „Runnable“ realisiert . Um LCEL nun voll auszuschöpfen, müssen wir uns näher 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 dabei um ein von LangChain erstelltes Python-Objekt, das automatisch alle zuvor besprochenen Funktionen und vieles mehr übernimmt. Durch die Verwendung der LCEL-Syntax setzen wir in jedem Schritt ein neues „Runnable“ zusammen, was bedeutet, dass das am Ende erstellte Objekt ebenfalls ein „Runnable“ ist. Weitere Informationen zur Schnittstelle finden Sie in der offiziellen Dokumentation.
Alle Objekte aus dem folgenden Code sind entweder „Runnable“ oder Wörterbücher, 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
| # <— 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(chain_number_one == chain_number_two)
>>> True
Warum verwenden wir „RunnableParallel()“ und nicht einfach „Runnable()“?
Denn jedes `Runnable` innerhalb eines `RunnableParallel` wird parallel ausgeführt. Das bedeutet: Wenn Ihr `Runnable` drei unabhängige Schritte enthält, werden diese gleichzeitig auf verschiedenen Threads Ihres Computers ausgeführt, wodurch sich die Geschwindigkeit Ihrer Kette ganz von selbst erhöht!
Nachteile von LCEL
Trotz seiner Vorteile weist LCEL jedoch auch einige potenzielle Nachteile auf:
Fazit
Zusammenfassend lässt sich sagen, dass die LangChain Expression Language (LCEL) ein leistungsstarkes Werkzeug ist, das neue Perspektiven für die Entwicklung von Python-Anwendungen eröffnet. Trotz ihrer unkonventionellen Syntax empfehle ich den Einsatz von LCEL aus folgenden Gründen wärmstens:
Weiterführende Informationen…
Die ausführbare Abstraktion
Meiner Meinung nach ist es in manchen Fällen 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 nachimplementieren:
Klasse Runnable:
def __init__(self, func):
self.func = func
def __or__(self, other):
def chained_func(*args, **kwargs):
# self.func steht links, other steht 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)
chain = runnable_add_ten | runnable_divide_by_two
result = chain(8) # (8+10) / 2 = 9.0 sollte das Ergebnis sein
print(result)
>>> 9.0
Ein „Runnable“ ist einfach ein Python-Objekt, bei dem die Methode .__or__() überschrieben wurde.
In der Praxis hat LangChain zahlreiche Funktionen hinzugefügt, darunter die Konvertierung von Wörterbüchern in „Runnables“, Typisierungsfunktionen, Konfigurationsmöglichkeiten sowie die Methoden „invoke“, „batch“, „stream“ und „async“!
Warum probieren Sie LCEL nicht einfach bei Ihrem nächsten Projekt aus?
Wenn Sie mehr erfahren möchten, empfehle ich Ihnen wärmstens, das LangChain-Cookbook auf LCEL durchzusehen.

BLOG






