Grundlagen von LangChain: Was es löst und wann man es komplett überspringen sollte.
Erfahren Sie, was LangChain tatsächlich bietet, warum das Aufrufen mehrerer Anbieter-Tools Teams dazu bringt, es zu nutzen, welche Pakete installiert werden müssen und wann ein einfacher Wrapper die bessere Wahl ist.
Quellen: [Day 11/100] LangChain 101: Why It Exists, and When You Should (and Should Not) Use It
Der fünfzigzeilige Agent hört auf, fünfzig Zeilen zu sein
Das Hinzufügen eines neuen Tools zu einem kleinen, manuell erstellten Agenten ist kostengünstig. Man schreibt eine Funktion, registriert sie in einem Dictionary, beschreibt sie in einem Schema – und der Loop selbst ändert sich nie. Dieser Teil der Agentenentwicklung wirkt angenehm, und das führt dazu, dass Menschen glauben, die gesamte Arbeit werde weiterhin so unkompliziert bleiben.
Dann treten die wirklichen Anforderungen zutage. Der OpenAI-Endpunkt gibt gelegentlich einen 503-Fehler aus, weshalb Wiederholungsversuche mit Zeitverzögerung notwendig sind. Manchmal ist der Anbieter zwanzig Minuten lang nicht erreichbar, daher sollte ein zweiter Anbieter als Ersatz vorhanden sein. Ein Benutzer, der acht Sekunden auf das Abschließen von vier aufeinanderfolgenden Aufrufen wartet, wird annehmen, dass die App eingefroren ist, weshalb ein Token-Streaming hinzugefügt wird. Ein nachgelagerter Dienst lehnt eine Antwort ab, weil ein Feld als Zeichenkette statt als Zahl zurückgegeben wurde, wodurch nun eine Validierung der strukturierten Ausgabe erforderlich ist. Und sobald zehn Personen gleichzeitig davon Gebrauch machen, sind blockierende Aufrufe nicht mehr akzeptabel, weshalb alles asynchron ablaufen muss.
Alles wird auf der Grundlage der Rohbibliotheken openai und requests entwickelt, wodurch der Agent schrittweise auf etwa fünfhundert Zeilen anwächst. Nur ein sehr kleiner Teil dieses Codes bezieht sich auf das eigentliche Verhalten des Agents – es handelt sich dabei um JSON-Parsing, exponentielles Backoff-Verhalten sowie um die Umwandlung zwischen den von den jeweiligen Anbietern eigenständig entwickelten Nachrichtenformaten. Jedes Team muss diese grundlegenden Strukturen selbst schreiben, und niemand möchte sie ein zweites Mal entwickeln.
Diese grundlegenden Strukturen sind es, die das Produkt LangChain bietet. Im Folgenden wird erklärt, was die Bibliothek tatsächlich ist, welche Probleme sie lösen soll – sowie, genauso wichtig, in welchen Fällen es ein Fehler ist, sie zu verwenden.
Was die Bibliothek ist (und nicht ist)
LangChain führt Ihren Agenten nicht für Sie aus, es hostet nichts und es verbessert nicht die Denkfähigkeit eines Modells. Betrachten Sie es als eine Reihe gemeinsamer Schnittstellen sowie vorgefertigte Implementierungen für die Komponenten einer LLM-Anwendung, die Sie sonst selbst programmieren müssten.
Falls Sie bereits einen grundlegenden Agenten entwickelt haben, haben Sie den Großteil dieser Komponenten bereits unter anderen Namen erstellt. Die Liste der übertragenen Nachrichten entspricht ChatMessageHistory. Ein Tool-Schema wird zu einem Tool oder zu einer einfachen Funktion, die mit @tool dekoriert ist. Die while-Schleife, die das Modell so lange aufruft, bis es keine Tools mehr anfordert, war genau das, was AgentExecutor tat. Der direkte Aufruf von client.chat.completions.create wird zu einem ChatModel. Konzeptionell gibt es hier nichts Neues. Was sich ändert, ist, dass diese Komponenten nun eine gemeinsame Struktur haben, die bereits von Tausenden weiterer Projekte genutzt wird, und dass die gängigen Kombinationen bereits vorgefertigt sind.
Die Probleme, für die er entwickelt wurde
Beginnen Sie mit dem Aufruf der Tools, denn dadurch wird das Problem deutlich sichtbar. OpenAI fügt tool_calls zur Nachricht des Assistenten hinzu und erwartet das Ergebnis als Nachricht mit role: „tool“. Anthropic sendet Inhaltsteile von tool_use und erwartet tool_result-Teile im nächsten Schritt des Benutzers. Gemini verwendet die Teile function_call und function_response. Derselbe Ansatz – drei APIs, drei Formate, die nicht zusammenpassen.
Bei einem einzigen Anbieter stellt das kein Problem dar. Wenn Sie eines Tages einen zweiten Anbieter benötigen – vielleicht weil der erste ausfällt oder teurer wird – müssen Sie entweder drei Adapter schreiben und warten oder auf Code zurückgreifen, der das bereits handhabt. Das war ursprünglich der Grund für LangChain: eine ChatModel-Schnittstelle, bei der .invoke() und .bind_tools() unabhängig davon, welche Firma die Token bereitstellt, gleich funktionieren. Der Wechsel von ChatOpenAI zu ChatAnthropic bedeutet lediglich einen Änderung in der Importierung, nicht eine Neuimplementierung der Nachrichtenverarbeitung.
Die Komposition ist das zweite Problem. Wenn man einen Prompt in ein Modell gibt, dieses Modell anschließend in einen Parser und den Parser wieder in ein anderes Modell, bedeutet das Code zur Verbindung an jeder Grenze – und dieser Verbindungskod muss jedes Mal neu geschrieben werden, wenn sich der Ablauf ändert. LangChain macht diese Grenzen einheitlich. Das dafür verwendete Mechanismus ist LCEL, die LangChain Expression Language, die eine eigene Erklärung verdient.
Das dritte Problem ist die einfache Wiederverwendung. Ein Agent, der mehr tut als nur ein Modell aufrufen, benötigt Dinge, die bereits von anderen entwickelt wurden: Lader für etwa vierzig Dokumentformate, Clients für ein Dutzend Vektordatenbanken, Wiederholungspolitiken, Streaming-, Batch-Verarbeitungsfunktionen sowie Tracing-Hooks. Eine gut gepflegte Integration ist fast immer besser als ein PDF-Parser, den man am Freitag geschrieben hat und den man nun noch debuggen muss.
Was man ohne eigenes Schreiben erhält
Eine einheitliche Modell-Schnittstelle. ChatOpenAI, ChatAnthropic, ChatGoogleGenerativeAI sowie ChatOllama für lokale Modelle bieten alle die Methoden .invoke(), .stream() und .bind_tools() bereit. Ihr Code muss sich nicht mehr darum kümmern, wer das Modell hinter der Aufrufoperation trainiert hat.
Komponierbare Bausteine. Mit dem Pipe-Operator von LCEL schreiben Sie prompt | model | parser und erhalten ein Objekt, das sich wie jede seiner Komponenten verhält. Es kann ohne zusätzliche Arbeit streamen, in Batches verarbeiten und asynchron ausführen.
Resilienz als Methodenaufruf. Jeder Runnable verfügt über .with_retry() und .with_fallbacks(), sodass Sie keinen eigenen Backoff-Loop oder Provider-Wechsler manuell schreiben müssen.
Streaming und Asynchronität überall. Jeder Runnable verfügt über .stream(), .ainvoke(), .astream() und .batch(), selbst dann, wenn das ursprüngliche Objekt diese Funktionen ursprünglich nicht unterstützte.
Ein umfangreiches Integrationskatalog. Dokumentlader, Vektorlagereinrichtungen, Suchwerkzeuge, Codeausführung, Datenbankwerkzeuge – viele davon werden von den Herstellern selbst gewartet, und alle nutzen dieselbe Runnable-Schnittstelle.
Ein direkter Weg zu LangGraph. Wenn Ihr Agent Schleifen, gespeicherten Zustand oder einen menschlichen Genehmigungsprozess benötigt, den eine lineare Kette nicht abbilden kann, wechselt der Steuerungslauf in LangGraph. LangGraph basiert auf denselben Kernabstraktionen, sodass das, was Sie in LangChain gelernt haben, direkt übertragbar ist.
Wann man darauf verzichten sollte
Die meisten Einführungen überspringen diesen Teil – doch gerade er vermeidet später die meisten Probleme.
Ein Anbieter, ein Aufruf – für immer. Wenn das gesamte Produkt darin besteht, „diese Anfrage an GPT-4o zu senden und den Text zurückzugeben“, ist ein kleiner eigener Wrapper leichter zu lesen und zu debuggen als eine Abhängigkeit, die Hunderte von übergeordneten Paketen mit sich bringt.
Man muss jede Anfrage sehen können. Abstraktionen sind gerade deshalb nützlich, weil sie Details verbergen – doch dieses Verbergen kostet Zeit, wenn etwas schiefgeht. Wenn man einem Stack Trace über sechs Ebenen von Runnable-Wrappern folgen muss, bevor man den eigentlichen Fehler findet, geht echte Zeit verloren. Teams, die jede Zeile zwischen ihrem Code und dem HTTP-Aufruf lesen möchten, bleiben oft näher am rohen SDK.
Bei latenzkritischen Abläufen. Jede Ebene fügt etwas Overhead hinzu. Neben einem Modellaufruf, der 500 Millisekunden oder länger dauert, ist dieser Overhead in der Regel unbedeutend. Bei einem kritischen Ablauf, bei dem jedes Millisekunde zählt, ist das manchmal nicht der Fall.
Nicht festgelegte Updates. LangChain bringt regelmäßig brisante Änderungen mit sich. Ein Projekt, das auf der neuesten Version läuft, wird irgendwann mit einem umbenannten Import oder geänderten Standardwerten konfrontiert. Sperren Sie die Version wie jede andere Abhängigkeit ein und aktualisieren Sie sie, wenn Sie es für richtig halten – nicht, wenn pip es für Sie entscheidet.
Das bedeutet keineswegs, dass Sie LangChain niemals verwenden sollten. Es bedeutet vielmehr, dass die Entscheidung projektbezogen und bewusst getroffen werden sollte – und nicht aus Gewohnheit.
Welches Paket ist welches
LangChain ist in mehrere Pakete unterteilt, und es ist sinnvoll, vor dem Installieren zu wissen, was jedes Paket leistet.
langchain-coreenthält die grundlegenden Abstraktionen:Runnable,BaseChatModel,BaseTool, Prompt-Vorlagen sowie Ausgabeparser. Es hat nur wenige Abhängigkeiten, und fast alles Weitere basiert darauf.langchainfügt auf der Basis des Kernsystems Ketten, Agenten sowie Logik zur Informationsabrufung hinzu, ohne Sie an einen bestimmten Anbieter zu binden.langchain-communitysammelt eine Vielzahl an Integrationen von Drittanbietern. Einige davon sind hervorragend, andere wiederum veraltet, da jeder beitragen kann. Prüfen Sie, wann eine Integration zuletzt aktualisiert wurde, bevor Sie sie in der Produktion einsetzen.- Partnerpakete wie
langchain-openai,langchain-anthropicundlangchain-google-genaiwerden zusammen mit den eigenen SDKs der jeweiligen Anbieter gepflegt. Installieren Sie nur diejenigen, die Sie tatsächlich verwenden. langchain-classicist der Ort, an den die Agentenkonstruktoren vor der Version 1.0 in v1 verschoben wurden:AgentExecutor,create_tool_calling_agentsowie der altecreate_react_agent. Bestehender Code funktioniert weiterhin, aber neue Agenten sollten nicht hier erstellt werden.langgraphist ein eigenständiges Paket, das auflangchain-coreaufbaut und die Steuerungsschleife für alles bereitstellt, was keine gerade Linie ist. Seitlangchainv1 ist der empfohlene Agentenbauerlangchain.agents.create_agentdirekt darauf implementiert.
Um loszulegen, benötigen Sie nur drei Pakete:
uv add langchain langchain-openai python-dotenv
Austausch des einfachen Clients gegen ChatOpenAI
Erwarten Sie in diesem Schritt nichts Dramatisches. Ziel ist es, den einfachen OpenAI-Client durch LangChain’s Chat-Modell-Wrapper zu ersetzen und zu überprüfen, ob Sie dieselbe Antwort erhalten.
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
load_dotenv()
llm = ChatOpenAI(model="gpt-4o", temperature=0)
response = llm.invoke("What is the weather in Paris?")
print(response.content)
llm.invoke(...) gibt Ihnen einen AIMessage statt eines rohen Wörterbuchs zurück, doch .content liefert Ihnen den Text genauso wie .choices[0].message.content es tat. Im Grunde ruft ChatOpenAI weiterhin denselben OpenAI-Endpunkt auf, den man auch manuell aufrufen würde, und das Modell hat sich überhaupt nicht geändert. Der Unterschied besteht darin, dass llm nun ein Runnable ist, wodurch .stream(), .batch() und .ainvoke() bereits vorhanden sind – ohne dass Sie etwas davon selbst geschrieben hätten.
for chunk in llm.stream("Tell me one fact about Paris."):
print(chunk.content, end="", flush=True)
Führen Sie es aus, und die Tokens kommen nacheinander in Blöcken an. Sie haben nie einen Streaming-Handler geschrieben, und diese kleine Schleife ist im Grunde der gesamte Grund für die Verwendung dieser Bibliothek.
Ein kurzer Hinweis zu den Modellnamen: In den Beispielen wird "gpt-4o" als Ersatz verwendet. Die Angebote der Anbieter ändern sich schneller, als jedes Tutorial folgen kann, daher sollten Sie jede model="..."-Zeichenkette als Platzhalter betrachten und bei der Ausführung des Codes den jeweiligen Flaggschiff-Modell Ihres Anbieters einsetzen. Die Funktionsweise bleibt unverändert.
Probieren Sie es mit Ihrem eigenen Agenten aus
- Fügen Sie
langchainundlangchain-openaimituv addzu Ihrem Projekt hinzu. - Ersetzen Sie in der Modell-Schritt-Logik Ihres Agenten den ursprünglichen
OpenAI()-Aufruf durchChatOpenAIund lassen Sie den Rest der Schleife unverändert. Überprüfen Sie, ob sich der Agent genauso verhält wie zuvor. - Rufen Sie
llm.stream(...)bei einer einfachen Frage ohne zusätzliche Tools auf und geben Sie jeden eingehenden Teil aus. Nun verfügen Sie über Streaming für eine Methode, die Sie nie implementiert haben.
Ihr Agent wird durch diese Änderung nicht intelligenter. Was Sie gewonnen haben, ist eine Grundlage, bei der Wiederholungsversuche, Fallback-Mechanismen, Streaming sowie der Wechsel des Anbieters lediglich eine Methodeaufrufkosten verursachen – anstelle von weiteren hundert Zeilen an Code.