Hallo zusammen, hier ist Nina von agntbox.com! Heute möchte ich über etwas sprechen, das in den letzten Wochen in meinem Arbeitsbereich für viel Aufregung gesorgt hat: die neue Version von LangChain.js. Genauer gesagt, habe ich mich intensiv mit dem aktualisierten SDK beschäftigt und wie es mein Leben als Entwickler von KI-Agenten erheblich erleichtert – und manchmal auch ein wenig frustrierend ist, aber auf eine gute, lehrreiche Art und Weise.
Wenn ihr meine Beiträge verfolgt habt, wisst ihr, dass ich ein großer Fan von JavaScript wegen seiner Vielseitigkeit bin, und wenn es darum geht, große Sprachmodelle zu orchestrieren, war LangChain.js mein bevorzugtes Werkzeug. Aber seien wir ehrlich, die Anfangszeiten hatten ihre Macken. Komplexe Ketten einzurichten, den Speicher über verschiedene Aufrufe zu verwalten und mit verschiedenen Tools zu integrieren, fühlte sich manchmal so an, als würde man versuchen, Katzen zu hüten. Mit den neuesten SDK-Updates sehe ich jedoch einen erheblichen Wandel zu intuitiveren Mustern und einer besseren Entwicklererfahrung. Und ehrlich gesagt, es wurde auch höchste Zeit.
Statt eines generischen Beitrags „Was ist LangChain.js?“ (die haben wir schon gemacht!), möchte ich meine praktischen Erfahrungen mit dem neuen SDK teilen, wobei ich mich darauf konzentriere, wie es die Agentenentwicklung vereinfacht, insbesondere wenn man Agenten anstrebt, die tatsächlich Dinge in der realen Welt tun können – nicht nur chatten.
Mein Kopfzerbrechen beim Aufbau von Agenten, vor dem Update
Bevor wir das Gute erkunden, lassen Sie mich ein Bild von meinen bisherigen Schwierigkeiten zeichnen. Ich arbeitete an einem Projekt für einen Kunden – nennen wir ihn „Acme Analytics“ – der einen Agenten benötigte, um Datenanalysen durchzuführen. Dieser Agent musste in der Lage sein:
- Auf eine SQL-Datenbank zuzugreifen, um Rohdaten abzurufen.
- Einfachstatistische Berechnungen durchzuführen (Mittelwert, Median usw.).
- Einfache Diagramme unter Verwendung einer Charting-Bibliothek zu erstellen.
- Ergebnisse zusammenzufassen und dem Benutzer zu präsentieren.
Klingt unkompliziert, oder? Nun, all diese „Werkzeuge“ mit einem LLM zu integrieren, den Konversationsspeicher zu verwalten und sicherzustellen, dass der Agent korrekt entscheiden kann, welches Werkzeug in welchem Schritt zu verwenden ist, war… eine Reise. Ich habe einen großen Teil meiner Zeit damit verbracht, mit der Eingabeaufforderung zu kämpfen, um das LLM zu steuern, benutzerdefinierte Werkzeugdefinitionen zu erstellen, die in die bestehende Struktur von LangChain passten, und Speicherlecks zu debuggen, die aus dem Nichts auftauchten. Es fühlte sich an, als würde ich ständig Dinge zusammenflicken, anstatt elegant zu bauen.
Der „Werkzeugdefinitions“-Tango
Einer der größten Schmerzpunkte war die Definition von Werkzeugen. Man hatte seine Funktion und umschloss sie dann in einem `Tool`-Objekt, wobei man darauf achten musste, dass die Beschreibung absolut perfekt war, damit das LLM sie versteht. Wenn die Beschreibung nur um ein einziges Wort abwich, konnte es passieren, dass das LLM halluzinierte oder dein Werkzeug einfach ignorierte. Es war ein delikater Tanz.
// Alte Methode (vereinfacht für das Beispiel)
import { Tool } from "langchain/tools";
const sqlQueryTool = new Tool({
name: "SQL_Query_Executor",
description: "Verwenden Sie dieses Werkzeug, um SQL-Abfragen auf der Acme Analytics-Datenbank auszuführen. Die Eingabe sollte eine gültige SQL SELECT-Anweisung sein. Gibt das Abfrageergebnis zurück.",
func: async (query: string) => {
// ... Logik zur Verbindung zur DB und Ausführung der Abfrage
return "Abfrageergebnisse...";
},
});
Das funktionierte, war aber langwierig, und jede Änderung an der zugrunde liegenden Funktion bedeutete oft, die Beschreibung ebenfalls anzupassen. Es fühlte sich an, als wäre es eine sehr manuelle Übersetzungsschicht.
Betreten Sie das neue LangChain.js SDK: Ein Hauch frischer Luft?
Als das neue SDK eingeführt wurde, war ich vorsichtig optimistisch. „Bessere Werkzeugdefinitionen“, sagten sie. „Vereinfachte Agentenerstellung“, versprachen sie. Mein Skeptizismus war hoch, aber mein Bedürfnis nach einem reibungsloseren Workflow war noch höher.
Ich beschloss, eine vereinfachte Version des Acme Analytics-Agenten mit den neuen SDK-Mustern neu zu erstellen, wobei ich mich auf die Kerndefinition der Werkzeuge und die Agentenorchestrierung konzentrierte. Und ehrlich gesagt, ich war angenehm überrascht.
Moderne Werkzeuge mit Zod-Schemas
Die größte Verbesserung für mich war die Art, wie Werkzeuge definiert werden. Das neue SDK setzt stark auf die Verwendung von Zod zur Validierung von Eingabeschemas. Das mag wie eine kleine Änderung erscheinen, aber es ist ein großer Schritt nach vorn aus mehreren Gründen:
- Typensicherheit: Sie erhalten eine ordentliche Typprüfung für die Eingaben Ihrer Werkzeuge, was Laufzeitfehler erheblich reduziert.
- Klarere Beschreibungen: Zod ermöglicht es Ihnen, Beschreibungen direkt zu Ihren Schemafeldern hinzuzufügen, die LangChain dann verwenden kann, um eine genauere und maschinenlesbare Werkzeugbeschreibung für das LLM zu erstellen. Das bedeutet weniger manuelle Eingabeaufforderungsgestaltung Ihrerseits.
- Integrierte Validierung: Wenn das LLM versucht, Ihr Werkzeug mit ungültigen Argumenten aufzurufen, erkennt Zod das sofort und gibt Ihnen bessere Debugging-Rückmeldungen.
Lassen Sie uns unser SQL-Abfragewerkzeug mit dem neuen Ansatz wieder besuchen:
// Neue Methode mit Zod
import { DynamicTool } from "@langchain/core/tools";
import { z } from "zod";
const sqlQueryTool = new DynamicTool({
name: "SQL_Query_Executor",
description: "Führt SQL-Abfragen auf der Acme Analytics-Datenbank aus.",
schema: z.object({
query: z.string().describe("Eine gültige SQL SELECT-Anweisung, die auf der Datenbank ausgeführt werden soll."),
}),
func: async ({ query }) => {
try {
// ... Logik zur Verbindung zur DB und Ausführung der Abfrage
console.log(`Ausführen der SQL-Abfrage: ${query}`);
// Simulation eines Datenbankaufrufs
await new Promise(resolve => setTimeout(resolve, 500));
if (query.includes("SELECT * FROM users")) {
return JSON.stringify([{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]);
}
return JSON.stringify([{ result: "Daten erfolgreich für die Abfrage abgerufen: " + query }]);
} catch (error) {
return `Fehler bei der Ausführung der Abfrage: ${error.message}`;
}
},
});
Sehen Sie den Unterschied? Die `schema`-Eigenschaft, die `z.object` und `z.string().describe()` verwendet, bietet eine viel strukturiertere und solidere Möglichkeit zu definieren, was Ihr Werkzeug erwartet. Die `description` für das Werkzeug selbst ist immer noch wichtig, aber die detaillierten Beschreibungen innerhalb des Schemas geben dem LLM einen viel besseren Kontext für jedes Argument. Ich habe festgestellt, dass das LLM erheblich besser darin ist, korrekte Funktionsaufrufe zu generieren, wenn es diese expliziten Schemata zur Verfügung hat.
Vereinfachte Agentenerstellung mit `createOpenAIFunctionsAgent`
Ein weiterer Bereich, in dem das neue SDK glänzt, ist die Agentenerstellung. Für alle, die OpenAI-Modelle verwenden (was, seien wir ehrlich, viele von uns tun), ist die Funktion `createOpenAIFunctionsAgent` ein Segen. Sie kümmert sich um den Großteil des Boilerplates, das mit der Einrichtung eines Agenten verbunden ist, der die Funktionserstellungsfähigkeiten von OpenAI nutzen kann.
Früher habe ich oft manuell `RunnableSequence`-Objekte erstellt, wobei ich sorgfältig ein `ChatPromptTemplate`, das LLM und dann einen `ToolExecutor` verkettet habe. Es funktionierte, aber es fühlte sich ein bisschen an, als würde man IKEA-Möbel ohne alle Anleitungen zusammenbauen.
Jetzt ist es viel einfacher:
import { ChatOpenAI } from "@langchain/openai";
import { createOpenAIFunctionsAgent, AgentExecutor } from "langchain/agents";
import { ChatPromptTemplate } from "@langchain/core/prompts";
// ... (sqlQueryTool-Definition wie oben)
const llm = new ChatOpenAI({
model: "gpt-4-0125-preview", // Oder welches aktuelle Modell Sie bevorzugen
temperature: 0,
});
const prompt = ChatPromptTemplate.fromMessages([
["system", "Du bist ein hilfreicher KI-Assistent, der Daten analysieren kann. Verwende die bereitgestellten Werkzeuge, um Fragen zu den Daten von Acme Analytics zu beantworten."],
["human", "{input}"],
["placeholder", "{agent_scratchpad}"], // Wichtig für den internen Denkprozess des Agenten
]);
const tools = [sqlQueryTool]; // Hier bei Bedarf weitere Werkzeuge hinzufügen
const agent = await createOpenAIFunctionsAgent({
llm,
tools,
prompt,
});
const agentExecutor = new AgentExecutor({
agent,
tools,
verbose: true, // Immer gut zu sehen, was der Agent tut!
});
// Lassen Sie uns das testen!
const result = await agentExecutor.invoke({
input: "Kannst du mir die Namen aller Benutzer aus der Datenbank geben?",
});
console.log("Endgültige Antwort des Agenten:", result.output);
Dieser Code-Schnipsel ist so viel klarer! Die `createOpenAIFunctionsAgent` kümmert sich um die komplexe Logik, um die Funktionsaufrufe des LLM in tatsächliche Werkzeugaussführungen umzuwandeln. Der `AgentExecutor` orchestriert dann den gesamten Prozess, führt den Agenten aus, überprüft, ob er ein Werkzeug verwenden muss, führt das Werkzeug aus und gibt das Ergebnis dem Agenten zur weiteren Verarbeitung zurück. Die Option `verbose: true` ist ein Lebensretter für das Debugging, da Sie den Denkprozess des Agenten Schritt für Schritt sehen können.
Verbessertes Speichermanagement (noch ein Wachstumsbereich, aber besser!)
Speicher war schon immer ein heikles Thema in der conversational AI. Vergangene Interaktionen im Blick zu behalten, ohne das Kontextfenster des LLM zu überladen, ist ein ständiger Balanceakt. Das neue SDK löst nicht alle Speicherprobleme auf magische Weise, bietet jedoch gestraffte Möglichkeiten zur Integration verschiedener Speichertypen.
Für meinen Acme Analytics-Agenten benötigte ich einen einfachen Konversationspuffer. Die Integration mit dem neuen Agenten-Setup ist ziemlich unkompliziert:
import { BufferWindowMemory } from "langchain/memory";
const memory = new BufferWindowMemory({
k: 5, // Behalte die letzten 5 Austausche im Gedächtnis
memoryKey: "chat_history", // Dies wird an die Eingabe übergeben
returnMessages: true,
});
// ... (rest der Agenten-Konfiguration)
// Modifiziere die Eingabeaufforderung, um den Chatverlauf einzuschließen
const promptWithMemory = ChatPromptTemplate.fromMessages([
["system", "Du bist ein hilfreicher KI-Assistent, der Daten analysieren kann. Nutze die bereitgestellten Werkzeuge, um Fragen zu den Daten von Acme Analytics zu beantworten."],
new MessagesPlaceholder("chat_history"), // Platzhalter für den Speicher
["human", "{input}"],
["placeholder", "{agent_scratchpad}"],
]);
const agentWithMemory = await createOpenAIFunctionsAgent({
llm,
tools,
prompt: promptWithMemory,
});
const agentExecutorWithMemory = new AgentExecutor({
agent: agentWithMemory,
tools,
memory, // Speicher hier übergeben
verbose: true,
});
// Beispielinteraktion
await agentExecutorWithMemory.invoke({
input: "Hallo, was kannst du tun?",
});
await agentExecutorWithMemory.invoke({
input: "Kannst du mir die Namen aller Benutzer aus der Datenbank holen?",
});
// Der Speicher wird jetzt den Austausch "Hallo, was kannst du tun?" fortführen
Der `MessagesPlaceholder` in der Eingabeaufforderung ist entscheidend, da er dem `AgentExecutor` ermöglicht, den `chat_history` aus dem `BufferWindowMemory` direkt in die Eingabeaufforderung einzufügen. Auch wenn die Begrenzungen des Kontextfensters weiterhin bestehen, macht diese Integration das Management dieses Verlaufs viel sauberer als zuvor.
Umsetzbare Erkenntnisse für Ihr nächstes KI-Projekt
Nachdem ich viel Zeit mit dem neuen LangChain.js SDK verbracht habe, sind hier meine Erkenntnisse und Empfehlungen, wenn Sie die Entwicklung von Agenten erkunden:
-
Nutze Zod für Werkzeugdefinitionen:
Im Ernst, das ist ein erheblicher Wandel. Es macht Ihre Werkzeuge solider, einfacher für das LLM zu verstehen und bietet Ihnen eine bessere Typensicherheit. Investieren Sie die Zeit im Voraus, um Ihre Werkzeug-Schemas richtig zu definieren.
// Definieren Sie immer ein klares Schema für Ihre Werkzeuge const myNewTool = new DynamicTool({ name: "WeatherDataFetcher", description: "Erhält aktuelle Wetterdaten für eine gegebene Stadt.", schema: z.object({ city: z.string().describe("Der Name der Stadt, für die das Wetter abgerufen werden soll."), unit: z.enum(["celsius", "fahrenheit"]).default("celsius").describe("Die Einheit der zurückgegebenen Temperatur."), }), func: async ({ city, unit }) => { // ... Logik zum Abrufen der Wetter-API return `Das Wetter in ${city} beträgt 25 Grad ${unit}.`; }, }); -
Beginne mit `createOpenAIFunctionsAgent` (wenn du OpenAI verwendest):
Es sei denn, du hast sehr spezifische Gründe, dies nicht zu tun, vereinfacht diese Funktion die Erstellung von Agenten erheblich. Sie sorgt für die Feinheiten der Funktionsaufruf-API von OpenAI, sodass du dich auf deine Werkzeuge und Eingabeaufforderungen konzentrieren kannst.
-
Halte deine Eingabeaufforderungen fokussiert und klar:
Selbst mit besseren Werkzeugdefinitionen ist die Systemaufforderung immer noch der Nordstern für deinen Agenten. Definiere klar seine Rolle, Fähigkeiten und alle Einschränkungen. Nutze den Platzhalter `agent_scratchpad` für den internen Monolog des Agenten.
-
Verwende `verbose: true` zum Debuggen:
Wenn etwas schiefgeht (und das wird es!), ist `verbose: true` bei deinem `AgentExecutor` dein bester Freund. Es gibt den Denkprozess des LLM aus, welches Werkzeug es versucht aufzurufen und die Ergebnisse, was dir hilft, Probleme schnell zu identifizieren.
-
Verwalte den Speicher bedacht:
Obwohl die Integration besser ist, denke daran, dass der Speicher Token kostet. Wähle den richtigen Speicher-Typ für deinen Anwendungsfall (z.B. `BufferWindowMemory` für kurzfristige Chats oder eine ausgeklügelte Zusammenfassung, wenn die Kontextlänge ein großes Anliegen ist). Füge immer den `MessagesPlaceholder` in deine Eingabeaufforderung ein, wenn du Speicher verwendest.
-
Iterativ testen:
Erstelle ein Werkzeug, teste es. Integriere es mit dem Agenten, teste es. Füge ein weiteres Werkzeug hinzu, teste es erneut. Die Entwicklung von KI-Agenten ist von Natur aus iterativ. Kleine, fokussierte Tests sparen dir später Kopfschmerzen.
Das neue LangChain.js SDK fühlt sich wie ein erheblicher Schritt hin zur Zugänglichkeit der Entwicklung von KI-Agenten an und ist weniger anfällig für versteckte Fehler. Es ist nicht perfekt, und es gibt immer eine Lernkurve mit neuen Mustern, aber die Verbesserungen in der Werkzeugdefinition und der Agentenorchestrierung machen meine Projekte wirklich reibungsloser. Wenn Sie bisher unschlüssig waren, ob Sie LangChain.js erkunden möchten oder wenn Sie mit älteren Versionen weniger gute Erfahrungen gemacht haben, ist jetzt eine großartige Zeit, das aktualisierte SDK neu zu betrachten.
Was sind Ihre Erfahrungen mit dem neuen LangChain.js SDK? Gibt es coole Tricks oder Herausforderungen, auf die Sie gestoßen sind? Lassen Sie es mich in den Kommentaren unten wissen! Viel Spaß beim Programmieren!
🕒 Published:
Related Articles
- Sacks si dimette dal ruolo di Czar dell’AI dopo solo quattro mesi
- Beste Terminal-Emulatoren im Jahr 2026: Meine Top-Auswahl
- Il tuo terapeuta AI potrebbe essere il tuo peggior facilitante
- <!--td {border: 1px solid #ccc;}br {mso-data-placement:same-cell;}-->Como construir um pipeline Rag com Langfuse (Passo a passo)