Історія діалогу для чат-бота на LangChain за допомогою повідомлень
Збираємо простий чат-бот, передаючи моделі попередні повідомлення через MessagesPlaceholder, і з'ясовуємо, чому історія — це не те саме, що вбудована пам'ять моделі.
Досі кожен виклик моделі був сам по собі. Поставили запитання, отримали відповідь, і наступний виклик починається з нуля. Щоб отримати щось схоже на справжній чат-бот, спершу треба розібратися з цим.
Ось два виклики поспіль:
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
Це фундамент для розуміння агентів.