Das Framework für AI-Integration

LLMs in der Praxis mit LangChain

LLMs in der Praxis mit LangChain

Das Framework für AI-Integration

LLMs in der Praxis mit LangChain


Wahrscheinlich kennen Sie das Meme mit dem bekannten Zitat aus dem Film „The Wolf of Wall Street“: „Sell me this pen“. Die Antwort lautet: „It’s AI powered“. Mittlerweile ist es ja schon fast nicht mehr zum Lachen – alle wollen AI, aber was die praktische Umsetzung angeht, ist sich keiner so richtig einig. Hier kommt das Framework LangChain ins Spiel.

Ein entscheidender Vorteil des AI-Booms für uns Softwareentwickler ist, dass sich Probleme herauskristallisieren, die in der gleichen oder ähnlichen Form überall auftreten. Die Integration eines KI-Modells ist ein typisches Beispiel. Die meisten Plattformen wie das OpenAI API, Google Gemini oder auch das lokale Ollama bieten HTTP-Schnittstellen, über die Sie mit einem LLM interagieren können. Zusätzlich dazu stellt Ihnen jeder Anbieter ein eigenes Paket zur Integration in Ihre Applikation zur Verfügung.

Das Problem an dieser Stelle ist jedoch, dass sich die Anbieter in den Details ihrer APIs unterscheiden. Das macht einen leichtgewichtigen Austausch der Modelle schwierig. Was für die Modelle gilt, gilt auch für andere Komponenten einer KI-Applikation. Wäre es da nicht schön, wenn es ein Framework gäbe, das Ihnen die Arbeit mit dem unübersichtlichen und schnell wachsenden KI-Ökosystem erleichtert? Das Framework, nach dem Sie suchen, heißt LangChain.

LangChain ist modular aufgebaut und adressiert die wichtigsten Aspekte einer AI-Applikation von der Anbindung von Modellen über den Umgang mit Prompts und Templates und der Anbindung von Vektordatenbanken bis hin zur Verarbeitung der Ausgabe. Dabei arbeitet das Framework, wie der Name schon andeutet, mit Ketten. Diese sind vergleichbar mit dem Konzept von Pipes, die Sie beispielsweise aus Unix-Shell oder der Rx-Bibliothek kennen. Die einzelnen Glieder der Kette haben jeweils eine Ein- und Ausgabe und können zu beliebig langen Ketten zusammengefügt werden.

Das LangChain-Framework existiert in zwei verschiedenen Varianten: Python und JavaScript bzw. TypeScript. Die grundlegende Architektur und die Konzepte sind in beiden Versionen die gleichen. Dieser Artikel verwendet für die konkreten Umsetzungsbeispiele die JavaScript-Variante.

Aufbau und Konzepte

Das wichtigste Paket des LangChain-Frameworks ist das Core-Paket. Es enthält die grundlegenden Strukturen für alle weiteren Komponenten. In der JavaScript-Variante trägt dieses Paket den Namen @langchain/core. Es enthält alle Basisklassen, wie beispielsweise BaseLLM, von der die konkreten Model-Klassen für die LLM-Integration ableiten. Außerdem definiert dieses Paket die LangChain Expression Language (LCEL), eine Schnittstelle, mit der Sie auf einfache Art die Ketten für Ihre Applikation definieren können. Das zweite Paket ist das langchain-Paket, das die Strukturen für Ketten, Agents und Retrieval Strategien beisteuert.

Zu diesen beiden Paketen kommt noch eine Vielzahl zusätzlicher Integrationspakete für die verschiedenen Bestandteile der Applikation. So können Sie das @langchain/openai-Paket nutzen, um das OpenAI API in Ihre Applikation einzubinden, oder Sie nutzen @langchain/pinecore, um die Pinecore-Vektordatenbank zu integrieren.

Für beide Pakete gilt, dass Sie sich problemlos sowohl für JavaScript als auch für TypeScript entscheiden können. LangChain liefert immer die aktuellen Typdefinitionen selbst aus, da das gesamte Framework in TypeScript umgesetzt ist. Die wichtigsten Bestandteile des Frameworks lernen Sie im Folgenden am konkreten Beispiel kennen und sehen dabei, wie die einzelnen Komponenten ineinandergreifen.

Die erste Beispielapplikation: ein Chatbot mit LangChain

Wenn Sie von KI-Applikationen hören, kommt Ihnen wahrscheinlich sehr schnell die typische Chatbot-Anwendung in den Sinn. Und genau mit einer solchen wollen wir den Einstieg in LangChain beginnen. Das Beispiel ist vollständig lokal lauffähig und setzt dafür auf Ollama als Plattform, die ein Sprachmodell lokal ausführt. Als konkretes Modell kommt Llama in der Version 3.2 zum Einsatz. Neben den bereits vorgestellten Paketen benötigen Sie dafür noch das @langchain/ollama-Paket, um mit der Plattform zu kommunizieren.

Egal, ob Sie Ollama oder eine andere Plattform wie OpenAI nutzen, die HTTP-Schnittstellen sind in der Regel zustandslos. Das bedeutet, dass das Modell kein Gedächtnis hat, die Konversation nach der Antwort also sofort wieder vergisst. Dieses Problem können Sie lösen, indem Sie eine Nachrichtenhistorie integrieren. Auch für diesen Fall bietet Ihnen LangChain eine passende Struktur. Doch bevor es an die konkrete Umsetzung geht, werfen wir einen Blick auf die Komponenten, die Sie für die Umsetzung eines solchen Chatbots benötigen:

  • Prompt: Der Prompt ist die Nachricht, die Sie an das Sprachmodell senden, um eine Antwort zu erhalten.

  • Prompt-Template: Mit dem Prompt-Template können Sie zum Prompt noch weitere Strukturen hinzufügen, um dem Model zusätzlichen Kontext zu bieten.

  • Model: Das Model steht für das Sprachmodell, mit dem Ihre Applikation interagiert. Dieser Klasse übergeben Sie im Fall von Ollama das konkrete Modell, das Sie verwenden möchten, oder bei OpenAI zusätzlich noch Ihren API-Key.

  • OutputParser: Mit dem OutputParser können Sie die Antwort des Modells manipulieren und den Inhalt der Nachricht extrahieren.

Der Prompt ist die Eingabe eines Menschen. Sie kann in ein Prompt-Template eingefügt werden. Diese Eingabe wird an das Modell weitergegeben, das dann die Antwort erzeugt. Der OutputParser liefert Ihnen schließlich die Antwort des Modells. In Listing 1 sehen Sie den Code des Beispiels.

Listing 1: Chatbot mit Historie in LangChain

import { InMemoryChatMessageHistory } from '@langchain/core/chat_history';
import { AIMessage, HumanMessage } from '@langchain/core/messages';
import { StringOutputParser } from '@langchain/core/output_parsers';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import {
  RunnableSequence,
  RunnableWithMessageHistory,
} from '@langchain/core/runnables';
import { ChatOllama } from '@langchain/ollama';

const messageHistories: Record<string, InMemoryChatMessageHistory> = {};

const prompt = ChatPromptTemplate.fromMessages([
  [
    'system',
    'You are a helpful assistant who remembers all details the user shares with you.',
  ],
  ['placeholder', '{chat_history}'],
  ['human', '{input}'],
]);

const model = new ChatOllama({
  model: 'llama3.2',
});

const parser = new StringOutputParser();

const chain = RunnableSequence.from([prompt, model, parser]);

const withMessageHistory = new RunnableWithMessageHistory({
  runnable: chain,
  getMessageHistory: async (sessionId) => {
    if (messageHistories[sessionId] === undefined) {
      messageHistories[sessionId] = new InMemoryChatMessageHistory();
    }
    return messageHistories[sessionId];
  },
  inputMessagesKey: 'input',
  historyMessagesKey: 'chat_history',
});

const config = {
  configurable: {
    sessionId: 'theSession',
  },
};

const messages = [
  new HumanMessage('Hey AI, I need help with my JavaScript project.'),
  new AIMessage("Sure! What's the project about, and what's the issue?"),
];

const stream = await withMessageHistory.stream(
  {
    chat_history: messages,
    input:
      "I'm building a task list app, but the date filtering isn't working right.",
  },
  config
);
for await (const chunk of stream) {
  console.log(chunk);
}

Die erste Komponente der Implementierung ist das Prompt-Template. Dieses erstellen Sie mit der fromMessages-Methode der ChatPromptTemplate-Klasse. Ihr übergeben Sie ein Array aus Nachrichten, wobei jede Nachricht aus einem Tupel aus Nachrichtentyp und Nachricht gebildet wird. Es gibt drei verschiedene Typen von Nachrichten: system steht für den System-Prompt, mit dem Sie das Verhalten des Systems beeinflussen können. Den Typ ai nutzen Sie, um eine Antwort des Modells zu speichern und human steht schließlich für eine menschliche Eingabe. Eine Sonderrolle nimmt der Typ placeholder ein. Hier platzieren Sie später die Einträge der Chat-Historie.

Als Modell nutzt das Beispiel eine Instanz der ChatOllama. Hier wird nur der Wert für das zu verwendende Modell überschrieben, sodass das Llama-3.2-Modell verwendet wird. Diese Konfiguration sorgt dafür, dass die Applikation mit dem Ollama API auf http://localhost:11434 kommuniziert. Als Output Parser kommt im Beispiel der StringOutputParser zum Einsatz. Dieser nimmt ein Objekt vom Typ AIMessage entgegen und extrahiert den Textinhalt, da das ursprüngliche Objekt neben diesem Inhalt noch eine ganze Reihe von Metainformationen enthält, die für das Beispiel nicht relevant sind.

Prompt-Template, Modell und Output Parser fasst die from-Methode der RunnableSequence-Klasse zu einer Kette zusammen. Diese können Sie bereits verwenden, indem Sie die invoke- oder stream-Methode des zurückgegebenen Objekts verwenden. In diesem Fall hat Ihre Applikation jedoch noch kein Gedächtnis. Dieses erhält sie erst mit dem withMessageHistory-Objekt. Dafür instanziieren...