Startseite / Artikel / Einem LangChain-Chatbot mit Messages einen Gesprächsverlauf geben

Einem LangChain-Chatbot mit Messages einen Gesprächsverlauf geben

Bauen Sie einen einfachen Chatbot, indem Sie frühere Nachrichten per MessagesPlaceholder an das Modell zurückgeben, und sehen Sie, warum Verlauf nicht dasselbe ist wie eingebautes Modellgedächtnis.

1268 Wörter

Bisher stand jeder Modellaufruf für sich. Frage stellen, Antwort bekommen, und der nächste Aufruf beginnt bei null. Damit etwas entsteht, das sich wie ein echter Chatbot verhält, muss man sich zuerst darum kümmern.

Hier sind zwei Aufrufe direkt hintereinander:

model.invoke("What is Python?")
model.invoke("What is it used for?")

Der zweite hat keine Ahnung, dass es um Python ging. Das Modell sieht nur, was in den Nachrichten steht, die Sie ihm schicken, also muss der vorherige Austausch mitgeschickt werden.

Die Grundidee

Ein Gespräch ist eine Liste von Beiträgen. Der erste:

Human → What is Python?
AI    → Python is a programming language.

Und die Nachfrage:

Human → What is it used for?
AI    → Python is used for web development, AI, automation...

Damit das Modell die zweite Frage versteht, schicken Sie die früheren Beiträge in der richtigen Reihenfolge mit:

SystemMessage
HumanMessage
AIMessage
HumanMessage

Deshalb ist AIMessage so wichtig. Auf diesem Weg gelangen die früheren Antworten des Modells zurück in den Prompt.

Zuerst ein Gespräch von Hand

Es lohnt sich, das erst manuell zu machen, bevor man Hilfsmittel hinzufügt. Das Beispiel unten nutzt Ollama, läuft also gegen ein lokales Modell und braucht kein API-Kontingent.

from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

model = ChatOllama(model="llama3.2")

prompt = ChatPromptTemplate.from_messages([
    (
        "system",
        "You are a helpful teacher."
    ),
    MessagesPlaceholder(variable_name="history"),
    (
        "human",
        "{question}"
    )
])

chain = prompt | model

history = []


def chat(question):
    response = chain.invoke({
        "history": history,
        "question": question
    })

    history.append(HumanMessage(content=question))
    history.append(AIMessage(content=response.content))

    return response.content


print(chat("What is Python?"))
print(chat("What is it used for?"))
print(chat("Can you give me three examples?"))

Sehen Sie sich diese Zeile genau an:

messages.append(response)

Sie legt die Antwort des Modells zurück ins Gespräch. Danach enthält die Liste die Systemnachricht, die erste Frage, die erste Antwort und die Nachfrage:

SystemMessage
HumanMessage("What is Python?")
AIMessage("Python is...")
HumanMessage("What is it used for?")

Bei diesem Aufruf sieht das Modell also den ganzen Verlauf:

model.invoke(messages)

Warum das funktioniert

Stellen Sie sich vor, Sie würden nur das hier senden:

HumanMessage(content="What is it used for?")

Nichts sagt dem Modell, worauf sich "it" bezieht. Mit dem angehängten Verlauf liest es stattdessen Folgendes:

Human: What is Python?
AI: Python is a programming language.
Human: What is it used for?

Jetzt kann es das Pronomen auflösen:

"it" = Python

Verlauf ist schlicht Kontext, den man dem Modell übergibt.

LangChain gibt dem Modell kein Gedächtnis

Dieser Punkt wird leicht übersehen. LangChain zu verwenden bringt ein Modell nicht dazu, sich an irgendetwas zu erinnern. Das Modell erhält in der Regel nur die Nachrichten, die Sie für diese eine Interaktion übergeben. Grob gesagt:

Your application
      ↓
Conversation history
      ↓
Messages
      ↓
Model

Der Gesprächszustand gehört Ihrer Anwendung. Behalten Sie das im Kopf, denn es wird sehr wichtig, sobald Sie bei State-Management und LangGraph ankommen.

Ein Prompt-Template hinzufügen

Der Verlauf lässt sich gut mit den Prompt-Templates aus früheren Lektionen kombinieren. Beginnen Sie mit den Imports:

from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

Dann definieren Sie den Prompt:

prompt = ChatPromptTemplate.from_messages([
    (
        "system",
        "You are a helpful Python teacher."
    ),
    MessagesPlaceholder(variable_name="history"),
    (
        "human",
        "{question}"
    )
])

Die entscheidende Zeile ist diese:

MessagesPlaceholder(variable_name="history")

Lesen Sie sie als "hier die früheren Nachrichten einfügen".

Die Chain bauen

Leiten Sie den Prompt per Pipe in das Modell:

chain = prompt | model

Beginnen Sie mit einer leeren Verlaufsliste:

history = []

Machen Sie den ersten Aufruf und geben Sie die Antwort aus:

response = chain.invoke({
    "history": history,
    "question": "What is Python?"
})print(response.content)

Speichern Sie dann beide Seiten des Austauschs, zuerst die Nachricht des Nutzers

history.append(
    HumanMessage(content="What is Python?")
)

und dann die Antwort des Modells:

history.append(response)

Stellen Sie nun die Nachfrage:

response = chain.invoke({
    "history": history,
    "question": "What is it used for?"
})
print(response.content)

Was das Modell tatsächlich erhält, sieht ungefähr so aus, beginnend mit der Systemanweisung:

System:
You are a helpful Python teacher.

gefolgt vom bisherigen Gespräch:

Human:
What is Python?AI:
Python is a programming language.Human:
What is it used for?

Als Chatbot verpacken

All das passt in eine einzige Funktion:

from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

model = ChatOllama(model="llama3.2")

prompt = ChatPromptTemplate.from_messages([
    (
        "system",
        "You are a helpful teacher."
    ),
    MessagesPlaceholder(variable_name="history"),
    (
        "human",
        "{question}"
    )
])

chain = prompt | model

history = []


def chat(question):
    response = chain.invoke({
        "history": history,
        "question": question
    })

    history.append(HumanMessage(content=question))
    history.append(AIMessage(content=response.content))

    return response.content


print(chat("What is Python?"))
print(chat("What is it used for?"))
print(chat("Can you give me three examples?"))

Damit haben Sie eine minimalistische Konversations-App.

Ein Detail zur Reihenfolge

Die Nachricht des Nutzers kommt nach dem Modellaufruf in den Verlauf, nicht davor:

response = chain.invoke(...)
history.append(HumanMessage(content=question))
history.append(AIMessage(content=response.content))

Der Grund: Der Prompt nimmt die aktuelle Frage bereits als eigene Eingabe entgegen:

"question": question

also würde sie doppelt auftauchen, wenn man sie zusätzlich zu history hinzufügt. Man kann es auch andersherum machen, alles in den Verlauf legen und dem Modell nur den Verlauf übergeben. Beide Designs funktionieren. Achten Sie nur darauf, dass die aktuelle Nachricht nicht zweimal erscheint.

Verlauf versus Gedächtnis

Ältere LangChain-Tutorials reden viel von „Gedächtnis“ ("memory."). Gehen Sie nicht davon aus, dass beides immer dasselbe ist:

history = memory

Auf der hier behandelten Ebene bedeutet Verlauf einfach die Nachrichten aus früheren Beiträgen:

History = messages from previous turns

Leistungsfähigerer Umgang mit Zustand kommt später, wenn eine Anwendung den Gesprächszustand über mehrere Schritte hinweg speichert und aktualisiert. Merken Sie sich vorerst diesen Ablauf:

Conversation history
        ↓
Previous messages
        ↓
Model gets context

Die resultierende Architektur

Der gebaute Chatbot funktioniert so: Die Anwendung speichert den Verlauf, speist ihn in den Prompt ein, und das Modell antwortet auf dieser Grundlage.

Warum das in echten Apps zählt

Dieselbe Idee steckt hinter vielen Produkten:

Customer support chatbot
AI tutor
Personal assistant
HR assistant
Coding assistant
Sales assistant

Nehmen wir einen einfachen Austausch, in dem sich der Nutzer vorstellt:

User: My name is Wasim.

Fragt die nächste Frage nach seinem Namen, kann das Modell korrekt antworten, weil die früheren Beiträge als Kontext mitgegeben wurden:

AI: Nice to meet you, Wasim.User: What is my name?AI: Your name is Wasim.

Ihr mentales Modell bis hierhin

Sie haben diese Bausteine abgedeckt, vom nackten Modellaufruf bis zur Chain mit Verlauf:

1. Model
       ↓
   invoke()2. Messages
       ↓
   System / Human / AI
3. Prompt Templates
       ↓
   {variables}
4. Structured Output
       ↓
   predictable data
5. Chains
       ↓
   prompt | model | parser
6. Conversation History
       ↓
   previous messages → context

Wie es weitergeht

Das nächste Thema sind Tools und Tool Calling. Dort beginnt ein LLM, Dinge außerhalb des Modells selbst zu tun, auf diesem Weg:

User
 ↓
LLM
 ↓
"I need a calculation"
 ↓
Python tool
 ↓
Result
 ↓
LLM
 ↓
Final answer

Das ist die Grundlage, um Agenten zu verstehen.