Startseite / Artikel

Erstellen von LangChain-Pipelines mit LCEL: Linear, parallel und verzweigt.

Ermitteln, wie man Prompts, Modelle und Parser in lineare, mehrstufige, parallele sowie bedingte LangChain-Pipelines mithilfe des Pipe-Operators, RunnableParallel und RunnableBranch integriert.

Verlag: REACTAPP.TOP41 Min. Lesezeit9455 Wörter

Quellen: Chains in LangChain: Simple, Sequential, Parallel, and Conditional — Part 7

Reale LLM-Funktionen enden fast nie nach nur einem Modellaufruf. Man formt einen Prompt, ruft ein Modell auf, bearbeitet das Ergebnis, gibt dieses in einen weiteren Prompt ein, führt manchmal mehrere Anfragen gleichzeitig aus und wählt gelegentlich je nach Eingabe einen anderen Weg. Jeder manuelle Übergang zwischen Schritten verwandelt sich schnell in unübersichtlichen „Glue Code“, der schwer zu lesen und noch schwieriger zu ändern ist. Dieser Leitfaden zeigt vier Kompositionsmuster in LangChain, die mit der LangChain Expression Language (LCEL) erstellt wurden: einfache Pipelines, mehrstufige Pipelines, Ausbreitung mit RunnableParallel sowie Routing mit RunnableBranch. Jedes Muster wird zweimal vorgestellt – einmal mit Google Gemini und einmal mit OpenAI – damit Sie genau erkennen können, welche Teile sich beim Wechsel des Anbieters ändern und welche unverändert bleiben.

Was eine Kette eigentlich ist

Eine Kette ist ein Pipeline aus LangChain-Komponenten, bei dem jeder Schritt das erhält, was der vorherige Schritt erzeugt hat. Die typischen Bausteine sind Prompt-Vorlagen, Chat- oder Kompletionsmodelle sowie Ausgabeparser, doch alles, was die Runnable-Schnittstelle von LangChain implementiert, kann daran teilnehmen. Das kleinste nützliche Pipeline sieht so aus:

Prompt → LLM → Output Parser → Output

Jeder Schritt hat eine Aufgabe. Die Prompt-Vorlage nimmt Ihre Eingabevariablen entgegen und wandelt sie in Anweisungen für das Modell um. Das Modell liest diese Anweisungen und erzeugt eine Antwort. Der Ausgabeparser formt diese Antwort in die vom Anwendungsprogramm gewünschte Form um – beispielsweise als einfache Zeichenkette oder als validiertes Objekt. Das, was den Parser verlässt, ist das Ergebnis des gesamten Pipelines.

Der Vorteil der Abstraktion besteht darin, dass man die Daten nicht mehr selbst zwischen diesen Schritten hin- und herschicken muss. Man deklariert die Reihenfolge einmal, und LangChain kümmert sich darum, jedes Komponente mit der richtigen Eingabe anzurufen. Konzeptionell setzt man kleine Blöcke zusammen zu einer größeren Einheit:

Input
  ↓
Component 1
  ↓
Component 2
  ↓
Component 3
  ↓
Output

Das spielt für ein zweistufiges Skript kaum eine Rolle, wird aber unerlässlich, sobald eine Anwendung auf ein Dutzend Schritte anwächst. Eine deklarierte Pipeline hält den Datenfluss an einem Ort sichtbar, anstatt ihn über Hilfsfunktionen zu verteilen. Das mentale Modell besteht aus einem wiederholten Rhythmus von Eingabe, Verarbeitung, Ausgabe und anschließender erneuter Verarbeitung dieser Ausgabe, bis der endgültige Wert erreicht ist.

Warum Komposition besser ist als manuelles Verbinden

Wenn jeder Zwischenwert explizit weitergegeben wird, wird der Code unübersichtlich und anfällig. Ketten lösen drei praktische Probleme.

Datenflüsse ohne „Klebstoff-Code“

Das grundlegende Versprechen ist, dass das Ergebnis eines Schritts automatisch zum Argument des nächsten wird. Nehmen wir wieder die vertraute Abfolge:

Prompt → LLM → Output Parser → Output

Wenn man es von Hand macht, würde man den Prompt formatieren, das Modell mit dem formatierten Wert aufrufen, den Text aus dem Antwortobjekt extrahieren und ihn an den Parser weitergeben, wobei für jeden Schritt eine Variable verwendet wird. Mit LCEL kollabiert dieser Ablauf zu einer einzigen Ausdrucksformel, die mit dem Pipe-Operator erstellt wird:

chain = prompt | llm | parser

Der |-Operator funktioniert, weil Prompte, Modelle und Parser alle die gleiche Runnable-Schnittstelle implementieren, sodass jedes davon weiß, wie es die Ausgabe des vorherigen Komponenten entgegennehmen soll. Weniger temporäre Variablen bedeuten weniger Möglichkeiten, falsche Werte weiterzugeben, und die Pipeline wird in derselben Reihenfolge von links nach rechts ausgeführt.

Mehrschrittige Logik bleibt lesbar

Produktionsfunktionen erfordern oft mehrere Modellaufrufe mit unterschiedlichen Prompts. Eine gängige Vorgehensweise ist es, zunächst etwas auf Basis eines Themas zu generieren und anschließend das Zwischenergebnis mit einem zweiten Prompt nachzubearbeiten:

Topic
  ↓
Prompt 1
  ↓
LLM
  ↓
Output Parser
  ↓
Prompt 2
  ↓
LLM
  ↓
Output Parser
  ↓
Final Output

Wird dies manuell gesteuert, dehnt sich dieser Ablauf mit der Hinzufügung weiterer Schritte aus. Wenn er als Kette dargestellt wird, bleiben die einzelnen Phasen in einer einzigen Deklaration zusammen, und die Regel, dass jede Phase das nächste Schritt versorgt, wird durch das Framework statt durch Disziplin durchgesetzt.

Komponenten können ausgetauscht werden

Weil jede Phase eine eigenständige Komponente ist, kann man eine ersetzen, ohne die anderen zu berühren. Angenommen, ein Pipeline läuft derzeit mit Gemini:

chain = prompt | gemini | parser

Der Wechsel zu OpenAI bedeutet lediglich den Austausch der Modulkomponente:

chain = prompt | openai | parser

Die Anfragen sowie die Parser bleiben unverändert. Dies ist nützlich, wenn sich die Anforderungen ändern, ein Team Anbieter hinsichtlich Kosten oder Qualität vergleicht, oder man in der Entwicklung ein günstigeres Modell und in der Produktion ein leistungsstärkeres haben möchte.

Die Vorteile auf einen Blick

  • Die Ausgaben fließen automatisch in den nächsten Schritt.
  • Mehrere Anfragen, Modelle und Parser befinden sich in einem einzigen deklarierten Pipeline.
  • Komponenten können wiederverwendet oder ersetzt werden, einschließlich des Modellanbieters, ohne den Ablauf neu entwerfen zu müssen.

Vier Kompositionsmuster

Es hilft, LangChain-Pipelines in vier Formen einzuteilen. Streng genommen handelt es sich dabei in modernem LangChain nicht um separate Klassen, sondern um Muster, die man aus denselben Runnable-Primitiven aufbaut. Die Namen bilden dennoch ein nützliches Vokabular:

  • Einfache Kette: eine einzige geradlinige Verbindung vom Prompt bis zum Parser.
  • Sequenzielle Kette: mehrere solcher Verbindungen, die aneinandergereiht sind.
  • Parallele Kette: mehrere unabhängige Unterketten, die gleichzeitig mit demselben Eingang arbeiten.
  • Konditionale Kette: ein Entscheidungspunkt, der auswählt, welche Unterkette den Eingang verarbeitet.

Zusammen umfassen sie alles von einmaligen Prompts bis hin zu Workflows, die sich verzweigen und Entscheidungen treffen. Jede dieser Formen wird im Folgenden anhand eines funktionierenden Beispiels mit zwei Anbietern erläutert.

Einfache Ketten: eine einzige Verarbeitungsschleife

Der einfachste Pipeline verbindet seine Komponenten in einer geraden Linie. Die Ausgabe jeder Komponente ist die Eingabe der nächsten, und es gibt unterwegs keine Verzweigungen oder Entscheidungen:

Input
  ↓
Prompt Template
  ↓
LLM
  ↓
Output Parser
  ↓
Output

Das laufende Beispiel dient als Lernhilfe: Für ein gegebenes Thema soll das Modell einige leicht verständliche Fakten liefern, ohne in komplexe technische Details einzutauchen. Die Kette dafür ist so kurz wie möglich:

prompt → llm → parser

Mit anderen Worten: Ein topic-Wert wird in das Prompt-Muster eingegeben, das formatierte Prompt gelangt zum Modell, und die Antwort des Modells durchläuft vor der Rückgabe einen Ausgabeparser. Der Parser ist technisch gesehen optional, aber seine Einbeziehung sorgt für einen vorhersehbaren Rückgabetyp.

Konkret gibt das Beispiel Cricket als Thema an und bittet um fünf interessante Fakten. Die Daten fließen wie folgt:

{'topic': 'cricket'}
        ↓
PromptTemplate
        ↓
LLM
        ↓
StrOutputParser
        ↓
Five interesting facts about cricket

Die Gemini-Version

Hier ist das vollständige Programm, das Google’s Gemini über die langchain_google_genai-Integration verwendet:

from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv

load_dotenv()

llm = GoogleGenerativeAI(model="gemini-2.5-flash")

prompt = PromptTemplate(
    template='Generate 5 interesting facts about {topic}',
    input_variables=['topic']
)

parser = StrOutputParser()

chain = prompt | llm | parser

result = chain.invoke({'topic':'cricket'})
# print(result)

chain.get_graph().print_ascii()

Es erstellt ein GoogleGenerativeAI-Modell auf Basis von gemini-2.5-flash, einen Eingabeprompt mit einer Variablen sowie einen StrOutputParser, und kombiniert diese anschließend mit prompt | llm | parser. Die letzte Zeile gibt ein ASCII-Diagramm der Kette aus, das weiter unten erläutert wird. Beachten Sie, dass die Zeile print(result) in dieser Version kommentiert ist, sodass das Skript nur das Diagramm anzeigt, es sei denn, Sie aktivieren sie erneut.

Durch den Gemini-Code gehen

Die Importe bringen die vier Komponenten mit, die das Skript benötigt:

from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv

GoogleGenerativeAI ist der Wrapper um das Gemini-Modell. PromptTemplate definiert einen Prompt mit einem {topic}-Slot, der zur Laufzeit ausgefüllt werden soll. StrOutputParser wandelt die Ausgabe des Modells in einen gewöhnlichen Python-String um. load_dotenv liest eine lokale .env-Datei in Umgebungsvariablen ein.

Durch Aufruf von load_dotenv werden der API-Schlüssel sowie alle anderen Einstellungen aus .env bereits vor der Erstellung eines Clients dem Prozess zur Verfügung gestellt:

load_dotenv()

Die Integration mit Gemini lädt ihren Schlüssel aus der Umgebung, weshalb dieser Aufruf vor der Instanziierung des Modells erfolgen muss. Es ist üblich, Schlüssel in .env zu speichern (und außerhalb des Versionskontrollsystems zu halten).

Anschließend wird das Modell selbst erstellt und auf gemini-2.5-flash festgelegt:

llm = GoogleGenerativeAI(model="gemini-2.5-flash")

Das Prompt-Muster deklariert sowohl den Text als auch die Variablen, die er erwartet:

prompt = PromptTemplate(
    template='Generate 5 interesting facts about {topic}',
    input_variables=['topic']
)

Zur Aufrufzeit wird der Platzhalter {topic} aus dem Eingabedictionary gefüllt. Wenn dieser Eingang übergeben wird:

{'topic': 'cricket'}

entsteht der folgende konkrete Prompt:

Generate 5 interesting facts about cricket

Der Parser wird ohne Argumente erstellt:

parser = StrOutputParser()

Ihre Aufgabe besteht darin, die Antwort des Modells als Zeichenkette zurückzugeben. Bei einem Kompletterungsmodell wie GoogleGenerativeAI, das bereits Text zurückgibt, handelt es sich dabei fast um eine direkte Weiterleitung. Bei einem Chat-Modell, das ein Nachrichtenobjekt zurückgibt, wird der Textinhalt extrahiert. In jedem Fall erhält der nächste Verarbeiter eine einfache str.

Dann kommt die Zeile, die den gesamten Pipeline-Prozess definiert:

chain = prompt | llm | parser

Der Pipe-Operator verbindet die drei Komponenten, sodass der formatierte Prompt dem Modell zugeführt wird und die Antwort des Modells wiederum dem Parser zugeführt wird:

PromptTemplate → Gemini 2.5 Flash → StrOutputParser

Schließlich wird die Kette mit einem Dictionary ausgeführt, das das Thema enthält:

result = chain.invoke({'topic':'cricket'})

Diese einzige Aufrufsequenz triggert alle Schritte nacheinander, ohne dass in Ihrem Code Zwischenvariablen verwendet werden:

cricket
   ↓
PromptTemplate
   ↓
Gemini 2.5 Flash
   ↓
StrOutputParser
   ↓
Final Result

Was zurückkommt

Der Rückgabewert ist ein String, der fünf Fakten über Cricket enthält, etwa in dieser Form:

1. Cricket originated in England and has been played for several centuries.
2. A cricket team consists of 11 players.
3. Test cricket matches can last up to five days.
4. The Cricket World Cup is one of the major international cricket tournaments.
5. Cricket is especially popular in countries such as India, Australia, England, and New Zeakand.

Da der Text generiert wird, unterscheiden sich die Formulierungen und spezifischen Fakten von Ausführung zu Ausführung, und die Beispielausgabe enthält sogar einen Tippfehler ("Zeakand"). Betrachten Sie Beispielausgaben als Veranschaulichungen der Struktur, nicht als feste Ergebnisse. Wenn Sie eine deterministische Formatierung benötigen, ist das Aufgabe eines strengeren Parsers oder einer strukturierten Ausgabe – nicht davon auszugehen, dass sich das Modell immer wieder gleich verhält.

Derselbe Pipeline bei OpenAI

Nun der identische Lernhilfe-Tool, der auf OpenAI läuft. Bemerkenswert ist, wie gering die Änderungen sind:

from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv

load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini")
prompt = PromptTemplate(
    template='Generate 5 interesting facts about {topic}',
    input_variables=['topic']
)
parser = StrOutputParser()
chain = prompt | llm | parser
result = chain.invoke({'topic':'cricket'})
print(result)
chain.get_graph().print_ascii()

Das Modell ist nun ChatOpenAI mit gpt-4o-mini. Der Prompt, der Parser sowie die Komposition sind genauso wie zuvor, und diese Version gibt sowohl das Ergebnis als auch den Graphen aus.

Der einzige neue Bestandteil ist das Modellobjekt:

llm = ChatOpenAI(model="gpt-4o-mini")

ChatOpenAI aus langchain_openai ersetzt GoogleGenerativeAI. Es handelt sich um ein Chat-Modell, das daher ein Nachrichtenobjekt statt einer rohen Zeichenkette zurückgibt – genau deshalb lohnt es sich, den Parser in der Kette beizubehalten.

Das Prompt-Muster bleibt unverändert:

prompt = PromptTemplate(
    template='Generate 5 interesting facts about {topic}',
    input_variables=['topic']
)

Auch der Parser ist unverändert:

parser = StrOutputParser()

Ebenso die Komposition:

chain = prompt | llm | parser

Die Struktur ist dieselbe dreistufige Linie, wobei OpenAI in der Mitte steht:

PromptTemplate → OpenAI → StrOutputParser

Die Aufrufung der Kette und das Ausgeben des Ergebnisses sieht so aus:

result = chain.invoke({'topic':'cricket'})
print(result)

Auch der OpenAI-Aufruf gibt eine Zeichenkette mit fünf Cricket-Fakten zurück, zum Beispiel:

1. Cricket is believed to have originated in England.
2. Each cricket team has 11 players.
3. A Test match can be played over a maximum of five days.
4. The Cricket World Cup is one of the most important international cricket tournaments.
5. Cricket has a particularly large following in India and several other countries.

Auch hier variiert die genaue Formulierung je nach Aufruf und Modell.

Ausgabe der Struktur der Kette

Sowohl die Skripte enden mit einem Aufruf, der den Pipeline-Prozess als Textdiagramm darstellt:

chain.get_graph().print_ascii()

get_graph() gibt eine grafische Darstellung des Runnable-Objekts zurück, und print_ascii() zeichnet sie in der Konsole auf. Für die Gemini-Kette erhält man etwas in dieser Art:

PromptInput
     *
     *
PromptTemplate
     *
     *
GoogleGenerativeAI
     *
     *
StrOutputParser
     *
     *
StrOutputParserOutput

Die Knoten PromptInput und StrOutputParserOutput sind die aus dem Schema abgeleiteten Eingangs- und Ausgangspunkte; die drei Knoten dazwischen sind Ihre Komponenten. Dies ist eine schnelle Überprüfung, ob die Kette so verbunden ist, wie Sie es erwarten. In den meisten Umgebungen hängt print_ascii() vom optionalen grandalf-Paket ab, daher installieren Sie es, wenn der Aufruf einen Importfehler verursacht.

Nur das Modell hat sich geändert

Platzieren Sie die beiden Pipelines nebeneinander – der Unterschied besteht in einem einzigen Knoten. Bei Gemini:

Input
  ↓
PromptTemplate
  ↓
GoogleGenerativeAI
  ↓
StrOutputParser
  ↓
Output

Bei OpenAI:

Input
  ↓
PromptTemplate
  ↓
ChatOpenAI
  ↓
StrOutputParser
  ↓
Output

In der Codestruktur kommt der gesamte Unterschied darauf heraus, welches Modellobjekt Sie erstellen:

# Google Gemini
llm = GoogleGenerativeAI(model="gemini-2.5-flash")

# OpenAI
llm = ChatOpenAI(model="gpt-4o-mini")

Alles drumherum kann unverändert bleiben. Das ist der praktische Vorteil des Aufbaus auf standardisierten Komponenten: Die Wahl des Anbieters wird zu einer Konfigurationsentscheidung statt zu einem Neuschreiben. In der Praxis gibt es zwar noch kleine Unterschiede, die beachtet werden sollten – wie Chat-Modelle gegenüber Kompletterungsmodellen, Parameternamen sowie die Art und Weise, wie jeder Anbieter Systemanweisungen verarbeitet – doch der gesamte Ablauf ist portabel.

Der vollständige Datenpfad

Im Allgemeinen sieht eine einfache Kette so aus:

Input
  ↓
PromptTemplate
  ↓
LLM
  ↓
StrOutputParser
  ↓
Output

Mit dem Cricket-Eingabedaten wird jeder Zwischenwert sichtbar – vom Wörterbuch über die dargestellte Anfrage bis hin zur rohen Modellantwort und dem endgültigen String:

{'topic': 'cricket'}
        ↓
PromptTemplate
        ↓
"Generate 5 interesting facts about cricket"
        ↓
LLM
        ↓
Raw LLM Response
        ↓
StrOutputParser
        ↓
Final String Output

Das ist die ganze Idee. Jede Komponente erledigt eine Aufgabe, und ihr Ergebnis wird als Eingabe für die nächste Komponente verwendet – in einem einzigen, durchgehenden Ablauf.

Sequenzielle Ketten: Ein Modellaufruf wird in den nächsten eingespeist

Eine sequenzielle Kette erweitert dieselbe Idee über mehrere Phasen hinweg. Eine einfache Kette enthält in der Regel eine einzige Abfolge dieser Struktur:

Prompt → LLM → Output Parser

Eine sequenzielle Kette kann mehrere Prompt-Vorlagen, mehrere Modellaufrufe sowie mehrere Parser enthalten, die alle miteinander verbunden sind. Die geltende Regel bleibt unverändert: Was in der aktuellen Phase erzeugt wird, wird von der nächsten Phase verarbeitet. Es gibt weiterhin keine Verzweigungen; die Phasen laufen stets in derselben festgelegten Reihenfolge ab. Eine zweistufige Version sieht so aus:

Input
  ↓
Prompt Template 1
  ↓
LLM 1
  ↓
Output Parser
  ↓
Prompt Template 2
  ↓
LLM 2
  ↓
Output Parser
  ↓
Output

Beispiel: ein detaillierter Bericht, anschließend eine Zusammenfassung

Die Beispielaufgabe umfasst zwei Phasen. Zunächst wird ein detaillierter Bericht zu einem bestimmten Thema erstellt. Anschließend wird dieser Bericht in eine fünfpunkte Umfassung zusammengefasst. In der zweiten Phase wird das ursprüngliche Thema niemals direkt betrachtet; sie arbeitet ausschließlich mit dem Bericht, den die erste Phase erzeugt hat:

Input
  ↓
Explanation Prompt
  ↓
LLM
  ↓
StrOutputParser
  ↓
Detailed Report
  ↓
Summary Prompt
  ↓
LLM
  ↓
StrOutputParser
  ↓
5-Point Summary

Zwei Prompt-Vorlagen übernehmen die Aufgabe. explaination_prompt (die Schreibfehler befinden sich im Code, daher beibehalten Sie sie, wenn Sie die Auszüge kopieren) bittet um einen detaillierten Bericht. summary_prompt nimmt diesen Bericht als Eingabe und bittet um fünf Zusammenfassungspunkte.

Die Gemini-Version

Hier ist das vollständige Programm für Gemini:

from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser

from dotenv import load_dotenv
load_dotenv()
llm = GoogleGenerativeAI(model="gemini-2.5-flash")
# Prompt 1 : Explanation
explaination_prompt = PromptTemplate(
    template='Generate a detailed report on {topic}',
    input_variables=['topic']
)
# Prompt 2 : Summary
summary_prompt = PromptTemplate(
    template='Generate a 5 pointer summary from the following text \n {text}',
    input_variables=['text']
)
parser = StrOutputParser()
chain = explaination_prompt | llm | parser | summary_prompt | llm | parser
result = chain.invoke({"topic": "Generative AI"})
# print(result)
chain.get_graph().print_ascii()

Eine Instanz von GoogleGenerativeAI bedient beide Phasen, und der Pipeline ist eine einzige Ausdrucksstruktur mit sechs Komponenten: zwei Prompts, das Modell zweimal sowie der Parser zweimal.

Durch den Gemini-Code gehen

Die Importe entsprechen der einfachen Kette:

from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from dotenv import load_dotenv

GoogleGenerativeAI für das Modell, PromptTemplate für die beiden wiederverwendbaren Prompts, StrOutputParser zur Umwandlung jeder Antwort in einen String sowie load_dotenv für die Konfiguration.

Umgebungsvariablen werden wie zuvor zuerst geladen:

load_dotenv()

Das Modell wird nur einmal erstellt:

llm = GoogleGenerativeAI(model="gemini-2.5-flash")

Es ist völlig in Ordnung, ein einziges Modellobjekt für beide Phasen zu wiederverwenden. Die Model Wrapper in LangChain sind bezüglich einzelner Aufrufe zustandslos, sodass dieselbe Instanz beliebig oft in einer Kette referenziert werden kann.

Die erste Anfrage nimmt einen topic entgegen und bittet um einen detaillierten Bericht:

explaination_prompt = PromptTemplate(
    template='Generate a detailed report on {topic}',
    input_variables=['topic']
)

Angesichts dieser Eingabe:

{"topic": "Generative AI"}

wird folgendes angezeigt:

Generate a detailed report on Generative AI

Die zweite Anfrage ist der Punkt, an dem die interessante Übergabe stattfindet:

summary_prompt = PromptTemplate(
    template='Generate a 5 pointer summary from the following text \n {text}',
    input_variables=['text']
)

Es wird eine Variable mit dem Namen text erwartet, doch im Eingabedictionary gibt es nichts mit diesem Namen. Der Wert stammt aus der ersten Phase: Der durch den ersten Modellaufruf erzeugte Bericht füllt diesen Platz ein.

Das funktioniert dank des Parsers, der zwischen den Schritten steht:

parser = StrOutputParser()

Nach dem ersten Modellaufruf erzeugt der Parser einen einfachen String. Wenn ein PromptTemplate mit genau einer Eingabevariablen einen einfachen String statt eines Dictionaries erhält, mappt LCEL diesen String auf die einzige Variable. Dadurch wird der Bericht ohne jegliche explizite Zuordnung als {text} erzeugt. Hätte der zweite Prompt zwei Variablen, müssten Sie das Dictionary selbst erstellen – beispielsweise mit einem kleinen Dict aus Runnables oder RunnablePassthrough.assign.

Der gesamte Ablauf erfolgt in einer Zeile:

chain = explaination_prompt | llm | parser | summary_prompt | llm | parser

Wenn man ihn von links nach rechts liest, erkennt man die Ausführungsreihenfolge:

Explanation Prompt
        ↓
      Gemini
        ↓
 StrOutputParser
        ↓
  Summary Prompt
        ↓
      Gemini
        ↓
 StrOutputParser
        ↓
     Final Output

Jedes Komponente erhält die Ausgabe der vorherigen Komponente. In Ihrem Code wird der Zwischenbericht nicht berührt.

Sein Ausführen erfordert nur ein einziges Thema:

result = chain.invoke({"topic": "Generative AI"})

Hinter diesem Aufruf werden beide Phasen nacheinander ausgeführt:

"Generative AI"
       ↓
Explanation Prompt
       ↓
Gemini
       ↓
StrOutputParser
       ↓
Detailed Report
       ↓
Summary Prompt
       ↓
Gemini
       ↓
StrOutputParser
       ↓
Final 5-Point Summary

Was zurückkommt

Der endgültige Wert ist eine fünfpunktierte Zusammenfassung von Generative AI, zum Beispiel:

1. Generative AI is a branch of artificial intelligence that creates new content.
2. It can generate text, images, audio, video, and code.
3. Foundation models are an important part of modern Generative AI systems.
4. Generative AI is used across areas such as education, software development, and customer support.
5. It is enabling applications that can automate and simplify many creative and knowledge-based tasks.

Es ist wichtig zu beachten, dass diese Zusammenfassung nicht direkt aus dem Thema selbst erstellt wird. Gemini schreibt zunächst einen vollständigen Bericht, und nur dieser Bericht wird an den Prompt für die Zusammenfassung gesendet. Dieser zweistufige Ansatz liefert in der Regel reichhaltigeres Material zum Komprimieren – auf Kosten eines zusätzlichen Modellaufrufs sowie der damit verbundenen Latenz und Token. Wie immer variiert die genaue Formulierung von Lauf zu Lauf.

Derselbe Ablauf bei OpenAI

Die OpenAI-Variante behält den Arbeitsablauf bei:

from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser

from dotenv import load_dotenv
load_dotenv()
llm = ChatOpenAI()
explaination_prompt = PromptTemplate(
    template="Generate a detailed report on {topic}",
    input_variables=["topic"]
)
summary_prompt = PromptTemplate(
    template="Generate a 5 pointer summary from the following text \n {text}",
    input_variables=["text"],
)
parser = StrOutputParser()
chain = explaination_prompt | llm | parser | summary_prompt | llm | parser
result = chain.invoke({"topic": "Generative AI"})
print(result)
chain.get_graph().print_ascii()

Es handelt sich um dieselbe zweistufige Struktur: Berichtsanfrage, Modell, Parser, Zusammenfassungsanfrage, Modell, Parser. Das Skript importiert außerdem load_dotenv zweimal, was zwar harmlos ist, aber optimiert werden kann.

Dieses Mal wird das Modell ohne Argumente erstellt:

llm = ChatOpenAI()

Da kein model-Argument vorhanden ist, greift ChatOpenAI auf das Standardmodell der Bibliothek zurück, welches sich in den verschiedenen langchain_openai-Versionen geändert hat. In echten Projekten sollte der Modellname explizit festgelegt werden, damit ein Upgrade nicht heimlich das Verhalten oder die Kosten verändert.

Die Berichtsanfrage ist identisch mit der Version von Gemini:

explaination_prompt = PromptTemplate(
    template="Generate a detailed report on {topic}",
    input_variables=["topic"]
)

Ebenso verhält es sich mit der Zusammenfassungsanfrage, die erneut einen Bericht in {text} erwartet:

summary_prompt = PromptTemplate(
    template="Generate a 5 pointer summary from the following text \n {text}",
    input_variables=["text"],
)

Hier zeigt sich der Vorteil des sequenziellen Musters: Die zweite Anfrage wird automatisch aus dem Ausgabeergebnis der ersten Stufe gefüllt.

Der Parser ist derselbe StrOutputParser:

parser = StrOutputParser()

Und die Zusammensetzung erfolgt Zeichen für Zeichen auf dieselbe Weise:

chain = explaination_prompt | llm | parser | summary_prompt | llm | parser

Im Ablauf sieht es so aus:

Prompt 1
   ↓
OpenAI
   ↓
StrOutputParser
   ↓
Prompt 2
   ↓
OpenAI
   ↓
StrOutputParser
   ↓
Final Output

Nur der Modellknoten unterscheidet sich von der Gemini-Version.

Die Aufrufung übermittelt das Thema erneut. Beachten Sie, dass der gezeigte Auszug seinen schließenden Klammern fehlt; das vollständige Programm enthält die korrekte Zeile:

result = chain.invoke({"topic": "Generative AI"}

Die Kette führt anschließend beide Phasen eigenständig aus.

Was zurückkommt

Das Ergebnis ist erneut eine fünfpunkte Umfrage, die aus dem Zwischenbericht abgeleitet wird:

1. Generative AI creates new content by learning patterns from existing data.
2. It is powered by large foundation models trained on diverse datasets.
3. Generative AI can produce text, images, audio, video, and code.
4. It is being used in many industries and applications.
5. It is becoming an important part of modern AI-powered software.

Die Details unterscheiden sich bei verschiedenen Ausführungen.

Ausgabe der Struktur der Kette

Der Graph-Hilfsprogramm funktioniert auch bei längeren Ketten:

chain.get_graph().print_ascii()

Bei der sequenziellen Gemini-Kette zeigt das Diagramm beide Phasen vertikal übereinander:

PromptInput
     *
     *
PromptTemplate
     *
     *
GoogleGenerativeAI
     *
     *
StrOutputParser
     *
     *
PromptTemplate
     *
     *
GoogleGenerativeAI
     *
     *
StrOutputParser
     *
     *
StrOutputParserOutput

Der Modellknoten erscheint zweimal, weil derselbe Modellobjekt an zwei Stellen in der Pipeline verwendet wird, und das Diagramm spiegelt die Positionen wider, nicht die eindeutigen Objekte.

Vergleich der beiden Implementierungen

Sowohl Versionen folgen derselben sequentiellen Struktur; der Anbieter ist der einzige wirkliche Unterschied. Die Gemini-Pipeline:

Input
  ↓
Explanation Prompt
  ↓
GoogleGenerativeAI
  ↓
StrOutputParser
  ↓
Summary Prompt
  ↓
GoogleGenerativeAI
  ↓
StrOutputParser
  ↓
Output

Die OpenAI-Pipeline:

Input
  ↓
Explanation Prompt
  ↓
ChatOpenAI
  ↓
StrOutputParser
  ↓
Summary Prompt
  ↓
ChatOpenAI
  ↓
StrOutputParser
  ↓
Output

Prompts, Parser und Aufbau der Kette werden gemeinsam genutzt. Nur die Erstellung des Modells ändert sich:

# Google Gemini
llm = GoogleGenerativeAI(model="gemini-2.5-flash")

# OpenAI
llm = ChatOpenAI()

Die zusammengesetzte Kette bleibt bei allen Anbietern identisch:

chain = explaination_prompt | llm | parser | summary_prompt | llm | parser

Abstrakt ausgedrückt sieht eine sequentielle Kette so aus:

Prompt 1
   ↓
LLM 1
   ↓
Parser
   ↓
Prompt 2
   ↓
LLM 2
   ↓
Parser
   ↓
Output

Der Unterschied zu einer einfachen Kette besteht darin, dass es mehrere Stufen gibt, und jede Stufe nach der ersten von der vorherigen abhängt.

Der vollständige Datenpfad

In allgemeiner Form bewegt sich die sequenzielle Kette Daten wie folgt:

Input
  ↓
PromptTemplate 1
  ↓
LLM 1
  ↓
StrOutputParser
  ↓
Detailed Report
  ↓
PromptTemplate 2
  ↓
LLM 2
  ↓
StrOutputParser
  ↓
Final Output

Überwacht mit der Eingabe der generativen KI, einschließlich der generierten Anfragen:

{"topic": "Generative AI"}
            ↓
    Explanation Prompt
            ↓
      "Generate a detailed
       report on Generative AI"
            ↓
           LLM
            ↓
    StrOutputParser
            ↓
      Detailed Report
            ↓
      Summary Prompt
            ↓
    "Generate a 5 pointer
     summary from the following text"
            ↓
           LLM
            ↓
    StrOutputParser
            ↓
     5-Point Summary

Der erste Modellaufruf ist ein Zwischenschritt und nicht die Endlösung. Sein Ausgabe wird zur Rohmaterialie für die nächste Operation. Sie können weitere Schritte hinzufügen, wenn ein Workflow mehr davon benötigt:

Prompt
  ↓
LLM
  ↓
Parser
  ↓
Prompt
  ↓
LLM
  ↓
Parser
  ↓
Prompt
  ↓
LLM
  ↓
Output

Egal wie lang die Pipeline wird, bleibt die Reihenfolge konstant, und jede Stufe wartet auf die vorherige. Das bedeutet auch, dass sich die Latenz ansammelt: Drei aufeinanderfolgende Modellaufrufe kosten in etwa die Summe ihrer Antwortzeiten. Es ist außerdem sinnvoll, einen Schutz gegen unkontrolliert wachsende Zwischenergebnisse einzubauen, denn ein sehr langer Bericht wird direkt in die nächste Anfrage eingefügt und erhöht deren Tokenanzahl.

Parallelisierte Ketten: Aufteilung auf dieselbe Eingabe

Eine parallele Kette führt mehrere Unterketten nebeneinander aus. Während eine sequentielle Kette darauf wartet, dass jede Phase abgeschlossen ist, bevor sie mit der nächsten beginnt, startet eine parallele Kette die unabhängigen Unterketten gleichzeitig:

                    ┌──→ Sub-Chain 1 ──→ Output 1
                    │
Input ──────────────┼──→ Sub-Chain 2 ──→ Output 2
                    │
                    └──→ Sub-Chain 3 ──→ Output 3
                                      ↓
                              Combined Output

Da die Unterketten unabhängig sind, sieht keine von ihnen die Ausgabe einer anderen. Die Mechanik ist einfach:

  1. Jede Unterkette erhält dieselbe Eingabe und erzeugt ihre eigene Ausgabe.
  2. Sämtliche Unterketten werden gleichzeitig gestartet.
  3. Der parallele Schritt wartet, bis jede Unterkette abgeschlossen ist.
  4. Ihre Ergebnisse werden zu einem kombinierten Wert zusammengefasst.

Der kombinierte Wert ist ein Dictionary. Jeder Schlüssel ist der Name, den Sie einer Unterkette gegeben haben, und jeder Wert ist die Ausgabe dieser Unterkette:

{
    "facts": "...",
    "definition": "...",
    "use_case": "..."
}

Beispiel: drei Ansichten zu einem Thema

Für ein Thema wie Künstliche Intelligenz besteht das Ziel darin, aus derselben Eingabe drei Dinge zu erzeugen: fünf interessante Fakten, eine prägnante Definition sowie einen Anwendungsfall aus der Praxis. Keines davon hängt von den anderen ab, weshalb deren nacheinanderige Ausführung nur Zeit verschwenden würde. Stattdessen werden sie parallel ausgeführt:

                         Input
                           ↓
                    RunnableParallel
                     /      |      \
                    /       |       \
                   ↓        ↓        ↓
               Facts    Definition  Use Case
                 ↓         ↓          ↓
                LLM       LLM        LLM
                 ↓         ↓          ↓
               Parser    Parser     Parser
                    \       |       /
                     \      |      /
                      ↓     ↓     ↓
                     Combined Result

Drei Unterprozesse übernehmen die Arbeit: facts für die fünf Fakten, definition für die kurze Definition und use_case für die praktische Anwendung. Sie werden mithilfe von RunnableParallel kombiniert. Die erste Implementierung verwendet Gemini für alle drei Aufgaben; die zweite mischt OpenAI und Anthropic ein und fügt einen Zusammenführungsschritt hinzu.

Warum Unterprozesse parallel ausführen

Es gibt zwei Gründe, dieses Muster anzuwenden.

Der erste Grund ist die Geschwindigkeit. Anrufe bei Modellen werden hauptsächlich durch Netzwerkzeiten und Generierungszeit bestimmt, wodurch unabhängige Anrufe stark von Konkurrenznutzung profitieren. Stellen Sie sich drei Unterprozesse vor, die jeweils etwa zwei Sekunden dauern. Wenn sie nacheinander ausgeführt werden, addieren sich die Zeiten:

Chain 1 → 2 seconds
Chain 2 → 2 seconds
Chain 3 → 2 seconds

Total ≈ 6 seconds

Wenn sie gleichzeitig ausgeführt werden, beträgt die Gesamtzeit in etwa die Dauer des langsamsten Prozesses:

Chain 1 ─┐
Chain 2 ─┼→ execute together → ≈ 2 seconds
Chain 3 ─┘

Die tatsächliche Einsparung hängt von den Rate Limits des Anbieters sowie davon ab, wie gleichmäßig die Anrufe abgeschlossen werden, doch bei unabhängigen Aufgaben ist die Verbesserung in der Regel erheblich. Der zweite Grund ist strukturell: Die Aufgaben teilen sich eine Eingabe, benötigen aber nicht die Ergebnisse der anderen. Aus einem einzigen Dokument möchten Sie beispielsweise eine Zusammenfassung, eine Liste der Schlüsselthemen und eine Reihe von Lernfragen erhalten. Jede Aufgabe kann das Dokument direkt verwenden:

                    Document
                       ↓
               ┌───────┼───────┐
               ↓       ↓       ↓
            Summary  Topics  Questions

Die Zusammenfassung muss nicht in die Themenextraktion oder den Fragengenerator übergehen. Wenn Aufgaben auf diese Weise miteinander verbunden sind, führt das Erzwingen einer Sequenz zu Verzögerungen und verknüpft Schritte, die keine wirkliche Abhängigkeit aufweisen. Wenn sie tatsächlich voneinander abhängen, ist Parallelität das falsche Werkzeug – eine sequentielle Abfolge ist die richtige Darstellung.

Die Gemini-Version

Hier ist die dreiseitige Aufteilung bei Gemini:

from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel

from dotenv import load_dotenv
load_dotenv()

llm = GoogleGenerativeAI(model="gemini-2.5-flash")
parser = StrOutputParser()

# Prompt 1: Facts
fact_prompt = PromptTemplate(
    input_variables=["topic"],
    template="Give five interesting short facts about: {topic}"
)

# Prompt 2: Definition
definition_prompt = PromptTemplate(
    input_variables=["topic"],
    template="Give a concise definition of: {topic}"
)

# Prompt 3: Use Case
usecase_prompt = PromptTemplate(
    input_variables=["topic"],
    template="What is a real-world use case of: {topic}?"
)

# Run all chains in parallel
parallel_chain = RunnableParallel({
    "facts": fact_prompt | llm | parser,
    "definition": definition_prompt | llm | parser,
    "use_case": usecase_prompt | llm | parser
})

# Invoke parallel chain
result = parallel_chain.invoke({"topic": "Artificial Intelligence"})

# print(type(result))
# print(result)
# print("\n📌 Facts:\n", result["facts"])
# print("\n📘 Definition:\n", result["definition"])
# print("\n🛠️ Use Case:\n", result["use_case"])

parallel_chain.get_graph().print_ascii()

Drei Prompt-Vorlagen, ein Modell und ein Parser werden zu drei kleinen Ketten zusammengefasst, die anschließend von RunnableParallel gemeinsam mit dem Eingabewert Künstliche Intelligenz ausgeführt werden. Mehrere print-Zeilen sind kommentiert; ihre Aktivierung zeigt den Ergebnistyp sowie jedes Feld an.

Durch den Gemini-Code gehen

Die Importe fügen ein neues Komponenten hinzu:

from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel

Neben dem Modell-Wrapper, PromptTemplate und StrOutputParser importiert das Skript außerdem RunnableParallel aus langchain_core.runnables, welches für den gleichzeitigen Ausführung der Unterketten zuständig ist.

Es wird ein einzelnes Gemini 2.5 Flash-Modell erstellt:

llm = GoogleGenerativeAI(model="gemini-2.5-flash")

Sämtliche drei Unterketten teilen sich dieses Modell. Der gemeinsame Einsatz eines Modellobjekts in parallelen Verarbeitungsschritten ist sicher, da jeder Aufruf unabhängig erfolgt.

Auch ein Parser wird gemeinsam genutzt:

parser = StrOutputParser()

Die erste Anfrage fordert fünf kurze Fakten an:

fact_prompt = PromptTemplate(
    input_variables=["topic"],
    template="Give five interesting short facts about: {topic}"
)

Die zweite Anfrage verlangt eine prägnante Definition:

definition_prompt = PromptTemplate(
    input_variables=["topic"],
    template="Give a concise definition of: {topic}"
)

Die dritte Anfrage bittet um eine Anwendung in der Praxis:

usecase_prompt = PromptTemplate(
    input_variables=["topic"],
    template="What is a real-world use case of: {topic}?"
)

Sämtliche drei Anfragen verwenden denselben Variablennamen:

{topic}

Der gemeinsame Name ermöglicht es, dass ein einziger Eingabedictionary alle Zweige versorgt. Die Aufgaben selbst bleiben völlig getrennt.

Nun der zentrale Bestandteil: die parallele Kette:

parallel_chain = RunnableParallel({
    "facts": fact_prompt | llm | parser,
    "definition": definition_prompt | llm | parser,
    "use_case": usecase_prompt | llm | parser
})

RunnableParallel nimmt ein Dictionary entgegen, dessen Werte Runnables sind. Die Schlüssel werden zu den Schlüsseln der Ausgabe:

facts
definition
use_case

Jeder Wert ist eine eigenständige, vollständige kleine Kette:

fact_prompt → LLM → parser

definition_prompt → LLM → parser

usecase_prompt → LLM → parser

Da keine von ihnen auf eine andere angewiesen ist, können sie alle gleichzeitig ausgeführt werden. Mit einer synchronen invoke-Methode führt LangChain die Zweige in einem Threadpool aus; mit ainvoke laufen sie als konkurrierende asynchrone Aufgaben.

Die Kette wird einmal aufgerufen:

result = parallel_chain.invoke({"topic": "Artificial Intelligence"})

mit einer einzigen Eingabe:

{"topic": "Artificial Intelligence"}

Dieses Dictionary wird an jeden Zweig weitergegeben. Konzeptionell gesehen:

Artificial Intelligence
          ↓
    RunnableParallel
     /      |       \
    ↓       ↓        ↓
  Facts  Definition  Use Case
    ↓       ↓        ↓
   LLM     LLM      LLM
    ↓       ↓        ↓
 Parser   Parser   Parser
     \      |       /
      \     |      /
       Combined Result

Was kommt zurück

Das Ergebnis ist ein Wörterbuch mit einem Eintrag pro Unterkette:

{
    "facts": "1. Artificial Intelligence enables machines to perform tasks that normally require human intelligence.\n2. ...",
    "definition": "Artificial Intelligence is the field of creating systems that can perform tasks requiring human-like intelligence.",
    "use_case": "A real-world use case of AI is customer support chatbots that can understand and respond to user queries."
}

Der Text in jedem Feld unterscheidet sich von Ausführung zu Ausführung, doch die Struktur bleibt konstant – darauf stützt sich Ihr Code:

result["facts"]
result["definition"]
result["use_case"]

Jeder Schlüssel gibt die Ausgabe der entsprechenden Abzweigung zurück, sodass der nachfolgende Code genau den benötigten Teil auswählen kann.

Mischen von Anbietern und Zusammenführen der Ergebnisse

Die zweite Implementierung zeigt zwei weitere Ideen: Verschiedene Abzweigungen können unterschiedliche Anbieter verwenden, und ein paralleler Schritt kann selbst eine Stufe eines größeren sequenziellen Pipelines sein. Hier verwendet eine Abzweigung OpenAI, die andere Anthropic, und ein abschließender Aufruf bei OpenAI fügt beide Ergebnisse zu einem einzigen Dokument zusammen:

from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from dotenv import load_dotenv
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.schema.runnable import RunnableParallel

load_dotenv()

model1 = ChatOpenAI()

model2 = ChatAnthropic(model_name='claude-3-7-sonnet-20250219')

prompt1 = PromptTemplate(
    template='Generate short and simple notes from the following text \n {text}',
    input_variables=['text']
)

prompt2 = PromptTemplate(
    template='Generate 5 short question answers from the following text \n {text}',
    input_variables=['text']
)

prompt3 = PromptTemplate(
    template='Merge the provided notes and quiz into a single document \n notes -> {notes} and quiz -> {quiz}',
    input_variables=['notes', 'quiz']
)

parser = StrOutputParser()

parallel_chain = RunnableParallel({
    'notes': prompt1 | model1 | parser,
    'quiz': prompt2 | model2 | parser
})

merge_chain = prompt3 | model1 | parser

chain = parallel_chain | merge_chain

text = """
Support vector machines (SVMs) are a set of supervised learning methods used for classification, regression and outliers detection.

The advantages of support vector machines are:

Effective in high dimensional spaces.

Still effective in cases where number of dimensions is greater than the number of samples.

Uses a subset of training points in the decision function (called support vectors), so it is also memory efficient.

Versatile: different Kernel functions can be specified for the decision function. Common kernels are provided, but it is also possible to specify custom kernels.

The disadvantages of support vector machines include:

If the number of features is much greater than the number of samples, avoid over-fitting in choosing Kernel functions and regularization term is crucial.

SVMs do not directly provide probability estimates, these are calculated using an expensive five-fold cross-validation (see Scores and probabilities, below).

The support vector machines in scikit-learn support both dense (numpy.ndarray and convertible to that by numpy.asarray) and sparse (any scipy.sparse) sample vectors as input. However, to use an SVM to make predictions for sparse data, it must have been fit on such data. For optimal performance, use C-ordered numpy.ndarray (dense) or scipy.sparse.csr_matrix (sparse) with dtype=float64.
"""

result = chain.invoke({'text': text})

print(result)

chain.get_graph().print_ascii()

Die Eingabe ist ein Textabschnitt über Support-Vektor-Maschinen. Eine Abteilung erstellt daraus Lernnotizen, die andere einen Quiz mit fünf Fragen, und die Kombinationsstufe fügt die Notizen sowie das Quiz zu einem Dokument zusammen.

Zwei Aspekte in dieser Auflistung erfordern eine Einschränkung. Es wird RunnableParallel aus langchain.schema.runnable importiert, einem älteren Pfad aus der Zeit vor der Trennung in langchain_core; aktuelle Versionen stellen es unter langchain_core.runnables zur Verfügung, wie im Gemini-Beispiel, und der veraltete Pfad existiert möglicherweise nicht in neueren Versionen. Zudem wird claude-3-7-sonnet-20250219 festgelegt, ein veralteter Modellidentifikator; Anbieter deaktivieren im Laufe der Zeit alte Snapshots, daher sollte man vor der Wiederverwendung die aktuelle Liste der Anthropic-Modelle überprüfen.

Zwei Modellobjekte werden erstellt:

model1 = ChatOpenAI()

model2 = ChatAnthropic(model_name='claude-3-7-sonnet-20250219')

ChatOpenAI schreibt die Notizen und führt anschließend den Merge durch. ChatAnthropic erstellt das Quiz. In RunnableParallel ist es nicht erforderlich, dass jede Abteilung denselben Modell verwendet – was praktisch ist, wenn ein Anbieter für eine bestimmte Unteraufgabe besser oder günstiger ist.

Der Prompt für die Notizen wandelt den Eingabetext in kurze, einfache Notizen um:

prompt1 = PromptTemplate(
    template='Generate short and simple notes from the following text \n {text}',
    input_variables=['text']
)

Der Prompt für das Quiz erzeugt aus demselben Text fünf kurze Frage-Antwort-Paare:

prompt2 = PromptTemplate(
    template='Generate 5 short question answers from the following text \n {text}',
    input_variables=['text']
)

Keine der Aufgaben benötigt die Ausgabe der anderen, weshalb es sich um natürliche parallele Abteilungen handelt.

Der dritte Prompt ist anders. Er wird nach der Aufteilung ausgeführt und benötigt beide Ergebnisse:

prompt3 = PromptTemplate(
    template='Merge the provided notes and quiz into a single document \n notes -> {notes} and quiz -> {quiz}',
    input_variables=['notes', 'quiz']
)

Er deklariert zwei Eingabevariablen:

notes
quiz

Diese Namen sind nicht willkürlich gewählt. Sie müssen den Schlüsseln des parallelen Schritts entsprechen, denn das von der Fan-Out-Operation erzeugte Dictionary ist genau das, was dieser Prompt erhält.

Der parallele Schritt definiert die beiden Zweige:

parallel_chain = RunnableParallel({
    'notes': prompt1 | model1 | parser,
    'quiz': prompt2 | model2 | parser
})

Seine Struktur besteht aus einer zweigleisigen Aufteilung, die zu einem Dictionary zusammengeführt wird:

                Input Text
                    ↓
             RunnableParallel
                /        \
               ↓          ↓
          Notes Chain   Quiz Chain
               ↓          ↓
          OpenAI        Anthropic
               ↓          ↓
            Parser      Parser
                \        /
                 ↓      ↓
                Dictionary

Konzeptionell sieht der Zwischenwert so aus:

{
    "notes": "...",
    "quiz": "..."
}

Die Verschmelzungskette ist eine gewöhnliche, einfache Kette:

merge_chain = prompt3 | model1 | parser

Es verbraucht das Dictionary aus dem parallelen Schritt, füllt {notes} und {quiz} auf und bittet OpenAI, ein Dokument zusammenzustellen.

Schließlich werden die beiden Phasen mit einem weiteren Pipe miteinander verbunden:

chain = parallel_chain | merge_chain

Das Ergebnis ist ein hybrider Arbeitsablauf: parallel zu Beginn, sequenziell am Ende:

                    Input Text
                        ↓
                 RunnableParallel
                  /            \
                 ↓              ↓
              Notes            Quiz
                 ↓              ↓
              OpenAI         Anthropic
                 ↓              ↓
              Parser          Parser
                  \            /
                   ↓          ↓
                 Combined Dictionary
                        ↓
                  Merge Prompt
                        ↓
                      OpenAI
                        ↓
                      Parser
                        ↓
                  Final Output

Der entscheidende Verhaltensaspekt ist, dass die beiden Zweige unabhängig voneinander ausgeführt werden, doch die Mischphase kann erst beginnen, wenn beide zurückgekehrt sind. Der langsamste Zweig bestimmt daher das Tempo für den gesamten Ausbreitungsprozess. Fällt ein Zweig aus, wird der parallele Schritt abgebrochen und die Mischung findet nie statt; daher sollten instabile Anbieter bei Bedeutung mit Wiederholungsversuchen oder Fallback-Mechanismen umhüllt werden.

Der gesamte Pipeline wird mit folgendem Text aufgerufen:

result = chain.invoke({'text': text})

Die Notizen- und Quiz-Zweige verarbeiten den Textabschnitt gleichzeitig. Sobald beide fertig sind, fließen ihre Ausgaben in die Mischkette ein, und das Ergebnis als Zeichenkette ist es, was invoke zurückgibt.

Ausgabe der Struktur der Kette

Der Graph-Hilfsprogramm visualisiert die Ausbreitung ebenfalls:

parallel_chain.get_graph().print_ascii()

Bei der dreizügigen Gemini-Kette zeigt das Diagramm die Trennung sowie die Vereinigung der Zweige:

                 Parallel<...>Input
                   /      |      \
                  /       |       \
          PromptTemplate PromptTemplate PromptTemplate
               ↓              ↓              ↓
              LLM            LLM            LLM
               ↓              ↓              ↓
          StrOutputParser StrOutputParser StrOutputParser
                  \          |          /
                   \         |         /
                    Parallel<...>Output

Die Knoten Parallel<...>Input und Parallel<...>Output kennzeichnen den Punkt, an dem die Eingaben verteilt werden und wo die Ergebnisse wieder in ein Dictionary zusammengefasst werden.

Vergleich der beiden Implementierungen

Sowohl Versionen veranschaulichen dieselbe Idee: unabhängige Unterketten, die gleichzeitig laufen. Die Gemini-Version ist eine reine Fan-Out-Struktur mit drei Zweigen auf einem Modell:

Input
  ↓
RunnableParallel
  ├── Facts → Gemini → Parser
  ├── Definition → Gemini → Parser
  └── Use Case → Gemini → Parser
              ↓
       Dictionary Output

Die Version mit gemischten Anbietern teilt sich in zwei Zweige auf unterschiedlichen Anbietern auf und fügt anschließend eine Verschmelzungsphase hinzu:

Input
  ↓
RunnableParallel
  ├── Notes → OpenAI → Parser
  └── Quiz → Anthropic → Parser
              ↓
       Dictionary Output
              ↓
         Merge Chain
              ↓
            OpenAI
              ↓
            Output

Unter beiden liegt dieselbe Konstruktion zugrunde:

RunnableParallel({
    "chain_1": chain_1,
    "chain_2": chain_2,
    "chain_3": chain_3
})

Die Schlüssel geben die Unterketten an; die Werte sind die Unterketten selbst. Als Abkürzung wandelt LCEL ein einfaches Dictionary von Runnables, das innerhalb einer Pipe-Expression erscheint, automatisch in ein RunnableParallel um, sodass man das Dictionary oft ohne die explizite Klasse sehen wird.

Der vollständige Datenpfad

In allgemeiner Form verteilt eine parallele Kette einen Eingang und sammelt ein Dictionary zusammen:

                         Input
                           ↓
                    RunnableParallel
                    /       |       \
                   ↓        ↓        ↓
               Sub-chain Sub-chain Sub-chain
                   ↓        ↓        ↓
                 Output   Output   Output
                    \       |       /
                     \      |      /
                      ↓     ↓     ↓
                        Dictionary

Zum Beispiel für Künstliche Intelligenz bei Gemini:

{"topic": "Artificial Intelligence"}
                ↓
        RunnableParallel
          /      |      \
         ↓       ↓       ↓
      Facts  Definition  Use Case
         ↓       ↓       ↓
       Gemini  Gemini   Gemini
         ↓       ↓       ↓
      Parser  Parser   Parser
          \      |      /
           \     |     /
             Dictionary

Der endgültige Wert enthält einen Schlüssel pro Zweig:

{
    "facts": "...",
    "definition": "...",
    "use_case": "..."
}

Das ist der wesentliche Unterschied zu einer sequentiellen Kette. In einer sequentiellen Kette verbraucht jede Stufe die Ausgabe der vorherigen Stufe:

A → B → C → D

In einer parallelen Kette laufen unabhängige Stufen nebeneinander mit demselben Eingang und ihre Ergebnisse werden zusammengeführt:

       ┌→ A ─┐
Input ─┼→ B ─┼→ Combined Output
       └→ C ─┘

Verwenden Sie Parallelität für Aufgaben, die Eingaben teilen, aber keine Ergebnisse, und halten Sie sich an sequenzielle Strukturen für Aufgaben, die tatsächlich auf früheren Schritten aufbauen. Die Mischung beider Ansätze, wie im Beispiel mit Notizen und Quiz der Fall ist, ist üblich und völlig idiomatisch.

Bedingte Ketten: Zuweisung der Eingabe an die richtige Unterkette

Eine bedingte Kette wählt ihren nächsten Schritt zur Laufzeit aus. Eine sequenzielle Kette führt immer jede Stufe in einer festgelegten Reihenfolge aus, während eine parallele Kette alle Zweige gleichzeitig ausführt. Eine bedingte Kette bewertet eine Bedingung anhand der Eingabe und führt nur den passenden Zweig aus:

Input
  ↓
Condition Check
  ↓
 ┌───────────────┐
 │               │
Yes             No
 ↓               ↓
Chain 1        Chain 2
 ↓               ↓
Output 1       Output 2
  \             /
   \           /
    Final Output

In LangChain wird dies mit RunnableBranch dargestellt. Man gibt ihm eine Liste von Paaren an, wobei jedes Paar eine Bedingung mit einem Ausführbaren Objekt verbindet, sowie ein Standard-Ausführbares Objekt am Ende. Die Bedingungen werden nacheinander überprüft; das erste, das True zurückgibt, bestimmt, welches Ausführbare Objekt ausgeführt wird. Falls keine Bedingung zutrifft, kümmert sich das Standard-Objekt um die Eingabe.

Beispiel: Routen nach Textlänge

Das Beispiel behandelt den Text je nach seiner Länge unterschiedlich:

  • Falls der Text mehr als 50 Wörter enthält, wird eine kurze Zusammenfassung erstellt.
  • Andernfalls wird die Idee im Text ausführlicher erläutert.

Das resultierende Workflow:

Input Text
    ↓
Condition Check
    ↓
Is word count > 50?
    ↓
 ┌───────┴───────┐
Yes              No
 ↓                ↓
Summary          Extend
Chain            Chain
 ↓                ↓
LLM              LLM
 ↓                ↓
Parser           Parser
  \                /
   \              /
    Final Output

Zwei Anfragen steuern die Branches: summary_prompt bittet um eine kurze Zusammenfassung, und extend_prompt bittet das Modell, auf dieser Idee aufzubauen. Eine kleine Funktion is_long_text() zählt die Wörter, und RunnableBranch verwendet ihr Ergebnis, um die zu verwendende Kette auszuwählen. Wie bei den anderen Mustern gibt es eine Gemini-Version sowie eine OpenAI-Version.

In allgemeiner Form sieht ein zweistufiger bedingter Pipeline so aus:

User Input
    ↓
Condition Check
    ↓
 ┌───────┴───────┐
Yes              No
 ↓                ↓
Prompt 1        Prompt 2
 ↓                ↓
LLM              LLM
 ↓                ↓
Parser           Parser
  \                /
   └──────┬───────┘
           ↓
      Final Output

Für jede gegebene Eingabe wird genau eine Branch ausgeführt.

Aufschluss über eine konkrete Eingabe

Die Beispielaufgabe ist eine einzeilige Definition des maschinellen Lernens, die es als einen Zweig der KI beschreibt, bei dem statistische Methoden es Computern ermöglichen, aus Daten zu lernen.

Diese Zeile besteht aus 22 Wörtern, was deutlich unter der 50-Wörter-Grenze liegt; daher gibt die Bedingung False zurück und es wird die Erweiterungskette gewählt:

Input Text
    ↓
Condition Check
    ↓
22 > 50?
    ↓
   No
    ↓
Extend Chain
    ↓
Gemini 2.5 Flash
    ↓
StrOutputParser
    ↓
Extended Text

Wenn ein Abschnitt länger als 50 Wörter ist, ändert sich die Bedingung auf True, wodurch der Eingang anstelle dessen an die Zusammenfassungskette gesendet wird.

Wann bedingte Routing-Strategien hilfreich sind

Routing ist nützlich, wenn derselbe Eingangstyp je nach bestimmter Eigenschaft unterschiedlich behandelt werden muss:

  • Länge: Lange Texte kürzen, kurze Texte ausführlicher darstellen.
  • Sentiment: Positive und negative Rückmeldungen an unterschiedliche Antwortketten senden.
  • Anfragetyp: Jede Anfragenkategorie an eine spezialisierte Kette leiten.

Die wichtige Eigenschaft ist, dass man keine Kosten für nicht benötigte Verzweigungen trägt. Nur der ausgewählte Pfad wird ausgeführt:

Input
  ↓
Condition
  ↓
Choose one branch
  ↓
Execute selected chain
  ↓
Output

Dadurch wird der Arbeitsablauf auf eine Weise anpassungsfähig, wie es eine feste Abfolge nicht kann, und es werden im Vergleich zum Ausführen aller Verzweigungen sowie Zurückwerfen der Ergebnisse weniger Modellaufrufe benötigt.

Die Gemini-Version

Hier ist das vollständige Routing-Programm für Gemini:

from langchain_google_genai import GoogleGenerativeAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableBranch
from dotenv import load_dotenv

load_dotenv()

llm = GoogleGenerativeAI(model="gemini-2.5-flash")

parser = StrOutputParser()

# Prompt 1 : Summary
summary_prompt = PromptTemplate(
    input_variables=["text"],
    template="Summarize the following text briefly:\n\n{text}"
)

# Prompt 2 : Extend
extend_prompt = PromptTemplate(
    input_variables=["text"],
    template="Extend the idea in the following long text:\n\n{text}"
)

# Chain 1 : Summary
summary_chain = summary_prompt | llm | parser

# Chain 2 : Extend
extend_chain = extend_prompt | llm | parser

def is_long_text(input: dict) -> bool:
    return len(input["text"].split()) > 50

conditional_chain = RunnableBranch(
    (lambda x: is_long_text(x), summary_chain),
    extend_chain
)

text_input = {
    "text": "Machine learning is a field of artificial intelligence that uses statistical techniques to give computer systems the ability to learn from data."
}

result = conditional_chain.invoke(text_input)

# print("\nOutput:\n")
# print(result)

conditional_chain.get_graph().print_ascii()

Es definiert zwei kleine Ketten, summary_chain und extend_chain, sowie ein Prädikat zur Wortzählung, und lässt anschließend RunnableBranch je nach diesem Prädikat zwischen den Ketten wählen.

Durch den Gemini-Code gehen

Das Modell ist erneut Gemini 2.5 Flash:

llm = GoogleGenerativeAI(model="gemini-2.5-flash")

Ein StrOutputParser wandelt jede Antwort in einen String um:

parser = StrOutputParser()

Die Zusammenfassungsanfrage bittet um eine kurze Zusammenfassung:

summary_prompt = PromptTemplate(
    input_variables=["text"],
    template="Summarize the following text briefly:\n\n{text}"
)

Die Erweiterungsanfrage wird auf dem anderen Pfad verwendet:

extend_prompt = PromptTemplate(
    input_variables=["text"],
    template="Extend the idea in the following long text:\n\n{text}"
)

Betrachten Sie genau die Formulierung: Es wird von „dem folgenden langen Text“ gesprochen, doch dieser Zweig läuft nur bei kurzen Eingaben. Dieser Widerspruch stört zwar nichts, gibt dem Modell aber eine irreführende Anweisung. Eine Überarbeitung in etwas wie „Erweitern Sie die Idee im folgenden Text“ ist eine kleine Korrektur, die in Ihrer eigenen Version vorgenommen werden sollte.

Jeder Prompt wird zusammen mit dem Modell und dem Parser in eine eigene Kette kombiniert:

summary_chain = summary_prompt | llm | parser

extend_chain = extend_prompt | llm | parser

Dadurch entstehen zwei unabhängige Pfade:

Summary Prompt → Gemini → Parser

Extend Prompt  → Gemini → Parser

Der bedingte Schritt entscheidet, welcher Pfad ausgeführt wird.

Die Bedingung ist eine gewöhnliche Python-Funktion:

def is_long_text(input: dict) -> bool:
    return len(input["text"].split()) > 50

Sie teilt input[„text“] anhand von Leerzeichen auf und zählt die Teile. Ihr Vertrag ist einfach:

True  → if word count > 50
False → otherwise

Das Trennen nach Leerzeichen ergibt eine grobe Wortanzahl; Satzzeichen bleiben an den Wörtern haften und durch Bindestriche verbundene Begriffe zählen als ein Wort. Das ist in Ordnung, um nach Länge zu routen, aber wenn es wirklich um die Grenzen des Modells geht, sollte man Tokens statt Wörter zählen. Beachten Sie außerdem, dass der Parametername input einen eingebauten Python-Begriff überschreibt; das funktioniert zwar, aber ein Name wie data ist sauberer.

Sobald alle Bestandteile vorhanden sind, stellt RunnableBranch den Router zusammen:

conditional_chain = RunnableBranch(
    (lambda x: is_long_text(x), summary_chain),
    extend_chain
)

Das erste Argument ist ein Tupel, das als Regel interpretiert wird:

If is_long_text(x) is True
        ↓
Execute summary_chain

Das letzte Argument, das nicht in einem Tupel eingebettet ist, ist die Standardwert:

Otherwise
        ↓
Execute extend_chain

Ein Lambda-Wrapper ist nicht erforderlich. RunnableBranch akzeptiert jede aufrufbare Funktion als Bedingung, sodass auch die direkte Übermittlung von is_long_text funktioniert. Insgesamt sieht die Routing-Logik so aus:

                    Input
                      ↓
              is_long_text()
                 /        \
              True        False
               ↓            ↓
        summary_chain   extend_chain
               ↓            ↓
             Gemini       Gemini
               ↓            ↓
             Parser       Parser
                \          /
                 \        /
                  Output

Beim Ausführen wird das Eingabedictionary verwendet:

result = conditional_chain.invoke(text_input)

Zuerst wird die Bedingung überprüft, und nur der ausgewählte Pfad ruft das Modell auf.

Was zurückkommt

Mit dem 22-Wörter-einzelnen Eingabemuster gibt das Prädikat zurück:

is_long_text() → False

Daher handhabt extend_chain die Eingabe. Konzeptionell:

Input Text
    ↓
Word Count Check
    ↓
False
    ↓
extend_chain
    ↓
Gemini 2.5 Flash
    ↓
StrOutputParser
    ↓
Extended Text

Wenn man ihm einen Text mit mehr als 50 Wörtern zur Verfügung stellt, gibt das Prädikat zurück:

is_long_text() → True

was den Pfad in folgenden ändert:

Input Text
    ↓
Word Count Check
    ↓
True
    ↓
summary_chain
    ↓
Gemini 2.5 Flash
    ↓
StrOutputParser
    ↓
Short Summary

Ein Pipeline, zwei mögliche Routen, bestimmt durch die Eingabe selbst.

Derselbe Router bei OpenAI

Die OpenAI-Version behält die Routing-Logik bei und wechselt nur das Modell aus:

from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableBranch
from dotenv import load_dotenv

load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini")
parser = StrOutputParser()
# Prompt 1 : Summary
summary_prompt = PromptTemplate(
    input_variables=["text"],
    template="Summarize the following text briefly:\n\n{text}"
)
# Prompt 2 : Extend
extend_prompt = PromptTemplate(
    input_variables=["text"],
    template="Extend the idea in the following long text:\n\n{text}"
)
# Chain 1 : Summary
summary_chain = summary_prompt | llm | parser
# Chain 2 : Extend
extend_chain = extend_prompt | llm | parser
def is_long_text(input: dict) -> bool:
    return len(input["text"].split()) > 50
conditional_chain = RunnableBranch(
    (lambda x: is_long_text(x), summary_chain),
    extend_chain
)
text_input = {
    "text": "Machine learning is a field of artificial intelligence that uses statistical techniques to give computer systems the ability to learn from data."
}
result = conditional_chain.invoke(text_input)
print(result)
conditional_chain.get_graph().print_ascii()

Zwei Ketten, ein Prädikat, eine RunnableBranch – genauso wie bei Gemini.

Das Modell ist gpt-4o-mini:

llm = ChatOpenAI(model="gpt-4o-mini")

Die beiden Prompt-Vorlagen bleiben unverändert, eine zum Zusammenfassen und eine zum Erweitern:

summary_prompt = PromptTemplate(
    input_variables=["text"],
    template="Summarize the following text briefly:\n\n{text}"
)

extend_prompt = PromptTemplate(
    input_variables=["text"],
    template="Extend the idea in the following long text:\n\n{text}"
)

Die Ketten sind genauso aufgebaut wie zuvor:

summary_chain = summary_prompt | llm | parser

extend_chain = extend_prompt | llm | parser

Nun laufen beide Pfade über OpenAI:

Summary Prompt → OpenAI → StrOutputParser

Extend Prompt → OpenAI → StrOutputParser

Das Prädikat ist die gleiche 50-Wort-Prüfung:

def is_long_text(input: dict) -> bool:
    return len(input["text"].split()) > 50

Der Router wird auf dieselbe Weise erstellt:

conditional_chain = RunnableBranch(
    (lambda x: is_long_text(x), summary_chain),
    extend_chain
)

Ein True-Ergebnis sendet die Eingabe an summary_chain; alles andere geht an extend_chain.

Die Kette wird aufgerufen und das Ergebnis ausgegeben:

result = conditional_chain.invoke(text_input)
print(result)

Für den Beispielsatz ergibt das Prädikat erneut:

is_long_text() → False

damit die Erweiterungskette ausgeführt wird und eine erweiterte Erklärung der Eingabe zurückgibt, ähnlich wie folgt:

Machine learning enables computer systems to learn patterns
from data and improve their performance without being explicitly
programmed for every task. It is widely used in applications
such as recommendation systems, image recognition, and predictive
analytics.

Der genaue Text hängt vom Modell ab und ändert sich bei verschiedenen Ausführungen.

Ausgabe der Struktur der Kette

Derselbe Graph-Hilfsfunktion dient auch für Router:

conditional_chain.get_graph().print_ascii()

Das auffällige Element des dargestellten Diagramms ist der Branch-Node. Er stellt den Entscheidungspunkt dar, an dem LangChain die Bedingungen prüft und die Eingabe an eine der möglichen Ketten weiterleitet.

Wie sich die beiden Zweige unterscheiden

Jeder Zweig hat seine eigene Aufgabe. Der Zusammenfassungszweig:

summary_prompt
      ↓
Gemini
      ↓
StrOutputParser
      ↓
Short Summary

Der Erweiterungszweig:

extend_prompt
      ↓
Gemini
      ↓
StrOutputParser
      ↓
Extended Text

Der Prädikat wählt zwischen ihnen aus:

                 Input
                   ↓
            is_long_text()
              /         \
           True         False
            ↓             ↓
       Summary Chain   Extend Chain

Das ist der entscheidende Unterschied zu einer parallelen Kette: Beide Zweige laufen nicht gleichzeitig. Nur der Pfad, dessen Bedingung erfüllt ist, wird ausgeführt, wodurch Kosten und Latenz proportional zur tatsächlich benötigten Arbeit bleiben.

Routing auf der eigenen Klassifizierung des Modells

Die Bedingungen beschränken sich nicht auf einfache Kriterien wie die Wortanzahl. Ein gängiges und leistungsfähigeres Muster besteht darin, eine erste Kette dazu zu nutzen, die Eingabe zu klassifizieren, und anschließend auf dieser Klassifizierung weiterzuarbeiten. Im Beispiel zur Stimmungsanalyse wird die eingehende Rückmeldung mit einem von zwei Werten versehen:

positive
negative

und RunnableBranch leitet sie an die Antwortkette für diesen Wert weiter:

Feedback
   ↓
Sentiment Classifier
   ↓
 ┌───────────────┐
 │               │
Positive       Negative
 │               │
 ↓               ↓
Positive       Negative
Response       Response
 │               │
 └───────┬───────┘
         ↓
      Output

Eine zuverlässige Methode zur Erstellung des Klassifikators besteht darin, das Modell dazu zu bringen, eine von einem Pydantic-Modell validierte, strukturierte Ausgabe zurückzugeben – beispielsweise ein Feld, das auf positive oder negative beschränkt ist. Die Zweigbedingungen prüfen dann dieses Feld anstelle der Analyse freier Texte, wodurch Überraschungen vermieden werden, wenn das Modell beispielsweise „Positive.“ mit Großbuchstabe und Punkt antwortet. Halten Sie stets einen sinnvollen Standardzweig bereit, denn ein Klassifikator wird letztendlich oft etwas zurückgeben, was man nicht erwartet hat.

Dies zeigt, dass eine Bedingung von Informationen abhängen kann, die von einer früheren Kette erzeugt wurden, und nicht nur vom rohen Eingang. Für komplexeres Routing beschreibt die aktuelle LangChain-Dokumentation außerdem ein Routing mit einer benutzerdefinierten Funktion, die in RunnableLambda eingebettet ist und die Ausführung der Kette zurückgibt; es lohnt sich, zu prüfen, welchen Ansatz die Dokumentation für Ihre Version empfiehlt.

Der vollständige Datenpfad

Das allgemeine bedingte Muster ist:

                       Input
                         ↓
                  Condition Check
                         ↓
                 ┌───────┴───────┐
                 ↓               ↓
              Condition       Condition
               True            False
                 ↓               ↓
             Chain 1          Chain 2
                 ↓               ↓
                LLM             LLM
                 ↓               ↓
              Parser          Parser
                 \               /
                  \             /
                   └─────┬───────┘
                         ↓
                       Output

Beim Beispiel zur Textlänge:

Machine Learning Text
          ↓
   is_long_text()
          ↓
     Word Count > 50?
        /       \
      Yes       No
       ↓         ↓
   Summary     Extend
    Chain       Chain
       ↓         ↓
    Gemini     Gemini
       ↓         ↓
    Parser     Parser
        \       /
         \     /
          Output

Kurze Eingaben folgen einem Pfad:

Word Count ≤ 50
       ↓
Extend Chain

Lange Eingaben folgen dem anderen Pfad:

Word Count > 50
       ↓
Summary Chain

Im Rückblick: Hier sind die vier Formen nebeneinander dargestellt:

Simple Chain
A → B → C

Sequential Chain
A → B → C → D → E

Parallel Chain
      ┌→ B ─┐
A ────┼→ C ─┼→ Output
      └→ D ─┘

Conditional Chain
       ↓
      A
       ↓
   Condition
    /     \
   B       C

Eine bedingte Kette verleiht einem LLM-Workflow die Fähigkeit, Entscheidungen zu treffen und je nach Eingabe oder einem früher im Pipeline berechneten Wert unterschiedliche Logiken auszuführen.

Praktische Hinweise vor dem Versand

Mehrere häufig auftretende Probleme sollten Sie im Hinterkopf behalten, wenn Sie von Beispielen zu echtem Code übergehen:

  • Speichern Sie die Modellnamen. Konstruktoren ohne Modulargument erben den Standardwert, der von der installierten Integration verwendet wird, und datierte Identifikatoren werden schließlich nicht mehr genutzt.
  • Behalten Sie den Parser bei. Chat-Modelle liefern Nachrichtenobjekte; StrOutputParser stellt jedem nachfolgenden Schritt einen vorhersehbaren str-Wert zur Verfügung.
  • Achten Sie auf die Übertragungen. Ein einfacher String füllt einen Prompt nur automatisch aus, wenn dieser genau eine Variable enthält. Für mehr Fälle erstellen Sie das Dictionary explizit.
  • Passen Sie Schlüssel an Variablen an. Bei einer Aufteilung, gefolgt von einem Zusammenführen, müssen die parallelen Schlüssel den Variablennamen des Zusammenführungsprompts entsprechen.
  • Planen Sie für Fehlschläge. Ein fehlerhafter Branch in einem parallelen Schritt führt zum Scheitern des gesamten Schritts; fügen Sie Wiederholungsversuche oder Alternativen für unzuverlässige Aufrufe hinzu.
  • Nutzen Sie moderne Importe. Verwenden Sie Runnables aus langchain_core.runnables, nicht die veralteten Pfade von langchain.schema.
  • Verschönern Sie die Darstellung. get_graph().print_ascii() (mit installiertem grandalf) ist eine einfache Methode, um zu überprüfen, ob eine komplexe Kette wie vorgesehen konfiguriert ist.
  • Für eine umfassendere Übersicht, die diese Ketten neben der Informationsabrufung sowie Tools und Agenten, die auf lokalen Modellen laufen, zeigt, sehen Sie LangChain 1.x in der Praxis: Ketten, RAG, Tools und Agenten lokal.

    Haupterkenntnisse

    • Eine Kette ist ein deklariertes Pipeline, bei dem die Ausgabe jedes Komponenten der nächsten zugeführt wird; der |-Operator erstellt sie aus beliebigen Runnables.
    • Einfache Ketten umfassen einen einzigen Durchlauf; sequenzielle Ketten stapeln mehrere Durchläufe, wodurch mit jeder Stufe Verzögerungen entstehen.
    • RunnableParallel führt unabhängige Unterketten gleichzeitig aus und gibt ein Dictionary zurück, das nach dem Namen der Abzweigung sortiert ist; spätere Stufen können dieses über den Variablennamen abrufen.
    • RunnableBranch bewertet Bedingungen nacheinander und führt nur die erste passende Abzweigung aus, wobei bei Nichtübereinstimmung auf einen Standardwert zurückgegriffen wird.
    • Bedingungen können günstige Prüfungen am Eingabedaten sein oder Entscheidungen auf der Grundlage der strukturierten Klassifizierung eines früheren Modells.
    • Weil das Modell nur eine Komponente ist, betrifft der Wechsel zwischen Gemini, OpenAI oder Anthropic in der Regel nur eine einzige Zeile, während Prompte, Parser und Struktur unverändert bleiben.

    Sämtliche vier Muster basieren auf derselben Grundlage, der Runnable-Schnittstelle – sie ist es, die erst einmal ermöglicht, Prompte, Modelle, Parser sowie eigene Funktionen miteinander zu kombinieren. Sobald diese Strukturen vertraut geworden sind, ist das direkte Verständnis dieser Schnittstelle der natürliche nächste Schritt.

    Verwandte Artikel

  • Einen Python LangChain Agent mit sieben integrierten Middleware-Komponenten sichern — Erfahren Sie, wie die Middleware von LangChain 1.0 Funktionen wie Zusammenfassung, Aufrufbeschränkungen, Wiederholungsversuche, Modell-Backup, Entfernung sensibler Daten sowie menschliche Freigabe zu einem Gemini-Agent hinzufügt, ohne die Kernlogik zu verändern.
  • Umgekehrte Prompting-Technik: Eine gute LLM-Sitzung in einen wiederverwendbaren Prompt umwandeln — Erfahren Sie, wie man einen einmaligen Prompt aus einem erfolgreichen mehrteiligen LLM-Gespräch extrahiert und wie die Prompt-Inversion dazu dient, Prompts wiederherzustellen, wenn man nur die Ausgaben vorliegen hat.
  • Sechs LangGraph-Primitiven und der in jeder verborgene Ausfallmodus — Erlernen Sie den Zustand von LangGraph, Knoten, Kanten, bedingte Routenfindung, Checkpointing und Unterbrechungen durch die spezifischen Fehler, die jede dieser Funktionen verursacht, sowie wie man ihnen ausweichen kann.