Головна / Статті / Історія діалогу для чат-бота на LangChain за допомогою повідомлень

Історія діалогу для чат-бота на LangChain за допомогою повідомлень

Збираємо простий чат-бот, передаючи моделі попередні повідомлення через MessagesPlaceholder, і з'ясовуємо, чому історія — це не те саме, що вбудована пам'ять моделі.

1268 слів

Досі кожен виклик моделі був сам по собі. Поставили запитання, отримали відповідь, і наступний виклик починається з нуля. Щоб отримати щось схоже на справжній чат-бот, спершу треба розібратися з цим.

Ось два виклики поспіль:

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

Другий гадки не має, що йшлося про Python. Модель бачить лише те, що є в надісланих їй повідомленнях, тому попередній обмін треба включити.

Основна ідея

Розмова — це список реплік. Перша:

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

І наступна:

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

Щоб модель зрозуміла друге запитання, разом із ним по черзі надсилаються попередні репліки:

SystemMessage
HumanMessage
AIMessage
HumanMessage

Ось чому AIMessage такий важливий. Саме так власні попередні відповіді моделі повертаються в промпт.

Спершу розмова вручну

Варто зробити це руками, перш ніж додавати будь-які помічники. Приклад нижче використовує Ollama, тож він працює з локальною моделлю й не потребує квоти API.

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?"))

Придивіться до цього рядка:

messages.append(response)

Він повертає відповідь моделі назад у розмову. Після нього в списку лежать системне повідомлення, перше запитання, перша відповідь і уточнювальне запитання:

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

Тож під час такого виклику модель бачить усю гілку:

model.invoke(messages)

Чому це працює

Уявіть, що ви надіслали лише це:

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

Ніщо не підказує моделі, до чого стосується "it". З доданою історією вона читає ось що:

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

Тепер вона може зрозуміти займенник:

"it" = Python

Історія — це просто контекст, переданий моделі.

LangChain не дає моделі пам'яті

Цей момент легко пропустити. Використання LangChain не змушує модель щось пам'ятати. Як правило, модель отримує лише ті повідомлення, які ви передали для конкретної взаємодії. Грубо кажучи:

Your application
      ↓
Conversation history
      ↓
Messages
      ↓
Model

Станом розмови володіє ваш застосунок. Тримайте це в голові: це стане дуже важливим, коли дійдете до керування станом і LangGraph.

Додаємо шаблон промпту

Історія добре поєднується з шаблонами промптів із попередніх уроків. Почніть з імпортів:

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

Потім визначте промпт:

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

Усю роботу робить ось цей рядок:

MessagesPlaceholder(variable_name="history")

Читайте його як "встав сюди попередні повідомлення".

Збираємо ланцюжок

Пропустіть промпт через модель:

chain = prompt | model

Почніть із порожнього списку історії:

history = []

Зробіть перший виклик і виведіть відповідь:

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

Потім збережіть обидва боки обміну: спершу повідомлення користувача

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

а потім відповідь моделі:

history.append(response)

Тепер поставте уточнювальне запитання:

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

Те, що модель реально отримує, виглядає приблизно так — спершу системна інструкція:

System:
You are a helpful Python teacher.

а за нею розмова на поточний момент:

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

Обгортаємо в чат-бот

Усе це вміщується в одну функцію:

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?"))

Тепер у вас є найпростіший діалоговий застосунок.

Деталь про порядок

Повідомлення користувача потрапляє в історію після виклику моделі, а не до нього:

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

Причина в тому, що промпт уже приймає поточне запитання як окремий вхід:

"question": question

тож додавання його ще й до history призвело б до дублювання. Можна зробити й навпаки: покласти все в історію й передавати моделі лише її. Обидва варіанти робочі. Головне — стежте, щоб поточне повідомлення не з'являлося двічі.

Історія і пам'ять

У старих туторіалах із LangChain багато говорять про «пам'ять» ("memory."). Не думайте, що це завжди одне й те саме:

history = memory

На тому рівні, що розібрано тут, історія означає просто повідомлення з попередніх реплік:

History = messages from previous turns

Просунутіша робота зі станом буде пізніше, коли застосунок зберігає й оновлює стан розмови між кроками. А поки запам'ятайте такий потік:

Conversation history
        ↓
Previous messages
        ↓
Model gets context

Архітектура, що вийшла

Зібраний чат-бот працює так: застосунок зберігає історію, подає її в промпт, а модель відповідає на її основі.

Чому це важливо в реальних застосунках

На цій самій ідеї побудовано безліч продуктів:

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

Візьмімо простий обмін, де користувач представляється:

User: My name is Wasim.

Якщо наступне запитання — як його звати, модель відповість правильно, бо попередні репліки передано як контекст:

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

Ваша мисленнєва модель на цей момент

Ви пройшли такі елементи — від голого виклику моделі до ланцюжка з історією:

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

Що далі

Наступна тема — інструменти та їхній виклик. Саме там LLM починає робити щось поза межами самої моделі, ось таким шляхом:

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

Це фундамент для розуміння агентів.