8. Состояние диалога и память
До сих пор каждое взаимодействие с LLM, которое мы создавали, было без сохранения состояния (stateless). Без сохранения состояния означает, что каждый запрос независим — модель не помнит предыдущих диалогов. Это хорошо работает для разовых задач, таких как суммаризация документов или ответы на отдельные вопросы.
Но создание диалогового агента — это совсем другая история. Вам нужно, чтобы агент помнил, что обсуждалось ранее, понимал местоимения вроде "это" или "то" и поддерживал контекст на протяжении всего диалога. Для этого вы должны явно управлять состоянием (state).
(Здесь "состояние" относится к информации, которую программа запоминает. Для диалоговых агентов история предыдущего диалога является состоянием.)
В этой главе мы рассмотрим:
- Почему LLM не "помнят" диалоги
- Как реализовать память диалогов с помощью инструментов истории сообщений LangChain
- Как управлять бюджетами токенов для предотвращения переполнения контекста
8.1) Почему LLM забывают
LLM не имеют памяти
LLM обладают критической характеристикой: они не помнят ничего из предыдущих диалогов.
Когда вы вызываете API LLM, модель обрабатывает ваш ввод и генерирует ответ. Но она не сохраняет эту запись где-либо. Внутри модели нет памяти, которая поддерживает состояние, нет истории диалогов. Каждый вызов API полностью независим. Это как начинать с чистого листа каждый раз.
Это сделано намеренно. LLM работают как функции без состояния: вы предоставляете ввод, они производят вывод, и ничего не сохраняется. Модель, работающая на серверах OpenAI прямо сейчас, не имеет записи о том, что вы только что спросили.
Почему кажется, что LLM помнят
Но подождите — когда вы используете ChatGPT или Claude, кажется, что они помнят ваш диалог. Вы можете сказать "Расскажи мне о Париже", затем продолжить "Какое население?" и модель знает, что вы все еще говорите о Париже. Как это работает?
Вот как: приложение отправляет историю предыдущего диалога вместе с каждым новым сообщением.
Вот что на самом деле происходит:
LLM не "помнит", что вы спрашивали о Париже ранее — она знает только потому, что приложение отправило историю предыдущего диалога вместе с новым сообщением. В конечном счете, именно приложение управляет состоянием, а не модель.
Почему "состояние" должно управляться в вашем приложении, а не в модели
Думайте о LLM как о чистой функции: при заданном вводе она производит вывод. Нет внутреннего состояния, которым управляет LLM, кроме сообщений, которые вы предоставляете. Это сделано намеренно.
Для диалоговых агентов это означает, что состояние должно управляться в вашем приложении.
Состояние — история диалога — находится в коде вашего приложения, а не в модели. Это означает, что вы несете ответственность за:
- Хранение истории диалога
- Отправку релевантной истории с каждым новым запросом
- Управление размером истории (рассматривается в разделе 8.3)
В разделе 8.2 мы реализуем это с помощью инструментов истории сообщений LangChain.
Что происходит, если вы не управляете состоянием?
Если вы не управляете состоянием, ваш агент (ваше приложение) не может поддерживать связный диалог. Вот наиболее распространенные сбои:
1. Не может вспомнить предыдущий диалог
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
# Первый вопрос
response1 = llm.invoke([HumanMessage(content="Меня зовут Алиса")])
print(response1.content) # Вывод: Приятно познакомиться, Алиса!
# Второй вопрос (история не отправлена)
response2 = llm.invoke([HumanMessage(content="Как меня зовут?")])
print(response2.content) # Вывод: Я не знаю вашего имени...Модель понятия не имеет, что вы сказали, что вас зовут Алиса, потому что мы не отправили эту информацию во втором вызове.
2. Не может определить, к чему относятся местоимения
# Пользователь спрашивает о теме
response1 = llm.invoke([HumanMessage(content="Расскажи мне о Python")])
print(response1.content)
# Вывод: Python — это высокоуровневый язык программирования, известный своей читаемостью...
# Пользователь продолжает с местоимением
response2 = llm.invoke([HumanMessage(content="Каковы его основные особенности?")])
print(response2.content)
# Вывод: Я буду рад помочь! Не могли бы вы уточнить, о чем вы спрашиваете?Без предыдущего сообщения модель не может определить, к чему относится "его".
Реальное влияние:
Представьте, что вы создаете агента поддержки клиентов без управления состоянием:
Пользователь: "У меня проблема с заказом #12345"
Агент: "Мне жаль это слышать. В чем проблема?"
Пользователь: "Адрес доставки неправильный"
Агент: "Я могу помочь с этим. Не могли бы вы предоставить номер заказа?"
Пользователь: "Я только что сказал вам..."Этот опыт расстраивает пользователей и подрывает их доверие. Управление состоянием не является опциональным для диалоговых агентов — это необходимо для создания связных, полезных взаимодействий.
В разделе 8.2 мы реализуем управление состоянием с помощью инструментов истории сообщений LangChain и добавим память диалогов в CLI-чат.
8.2) Управление состоянием диалога
Теперь, когда мы понимаем, почему управление состоянием критично, давайте научимся его реализовывать. Мы будем использовать встроенные инструменты истории сообщений LangChain для управления историей диалогов. В конце мы применим то, что узнали, чтобы добавить управление состоянием в CLI-чат из главы 3.
Понимание типов сообщений: HumanMessage, AIMessage и SystemMessage
Прежде чем изучать, как управлять состоянием диалога, вам нужно знать типы сообщений, используемые в состоянии диалога. LangChain использует три типа сообщений для представления диалогов: HumanMessage (ввод пользователя), AIMessage (ответ модели) и SystemMessage (инструкции). Каждое сообщение имеет роль (тип сообщения) и содержимое (фактический текст).
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
# SystemMessage: Инструкции для поведения модели
system_msg = SystemMessage(content="Вы полезный помощник, специализирующийся на программировании на Python.")
# HumanMessage: Ввод пользователя
user_msg = HumanMessage(content="Как прочитать файл в Python?")
# AIMessage: Ответ модели
# (На практике LangChain оборачивает ответ модели в этот объект - показано здесь для иллюстрации)
ai_msg = AIMessage(content="Вы можете использовать функцию `open()` с контекстным менеджером...")Зачем отдельные типы сообщений?
Чтобы модель эффективно понимала историю диалога, ей нужно знать назначение каждого сообщения и кто его сказал. Три типа сообщений служат различным целям:
- SystemMessage: Инструкции, которые определяют, как должна вести себя модель (например, "Будьте лаконичны", "Вы репетитор по Python")
- HumanMessage: Что сказал пользователь
- AIMessage: Что модель ответила ранее
Эта структура позволяет модели различать инструкции, вопросы пользователя и свои собственные прошлые ответы — что необходимо для поддержания связных многоходовых диалогов.
Ручное построение истории диалога
Теперь, когда мы понимаем три типа сообщений, давайте посмотрим, как вручную построить историю диалога:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
llm = ChatOpenAI(model="gpt-4o-mini")
# Построение истории диалога
# SystemMessage устанавливает инструкции (один раз в начале)
# Затем: Ввод пользователя → Ответ AI → Ввод пользователя (естественный поток диалога)
messages = [
SystemMessage(content="Вы лаконичный репетитор по Python."),
HumanMessage(content="Что такое list comprehension?"),
AIMessage(content="List comprehension — это лаконичный способ создания списков: [x*2 for x in range(5)]"),
HumanMessage(content="Можете показать более сложный пример?")
]
# Отправляем всю историю с новым вопросом
response = llm.invoke(messages)
print(response.content)Вывод:
Конечно! Вот list comprehension, который фильтрует и преобразует:
[x**2 for x in range(10) if x % 2 == 0]
Это производит:
[0, 4, 16, 36, 64]Модель понимает, что "более сложный пример" относится к более сложному примеру list comprehension, потому что мы отправили полную историю диалога.
Использование InMemoryChatMessageHistory
Ручное построение списков сообщений становится громоздким по мере роста диалогов. InMemoryChatMessageHistory LangChain упрощает это, предоставляя методы для добавления сообщений и получения полной истории.
Ключевые методы:
add_message(message): Добавляет одно сообщение (HumanMessage, AIMessage, SystemMessage)add_messages(messages): Добавляет несколько сообщений сразуmessages: Свойство, которое возвращает полный список сообщенийclear(): Удаляет все сообщения (полезно для начала с чистого листа)
Пример:
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
# Создаем хранилище истории сообщений
history = InMemoryChatMessageHistory()
# Добавляем несколько сообщений сразу
history.add_messages([
SystemMessage(content="Вы полезный репетитор по Python."),
HumanMessage(content="Что такое декоратор в Python?")
])
# Добавляем сообщения по одному
history.add_message(AIMessage(content="Декоратор — это функция, которая изменяет поведение другой функции..."))
history.add_message(HumanMessage(content="Можете показать пример?"))
# Получаем все сообщения
messages = history.messagesПрактический пример: Управление состоянием с InMemoryChatMessageHistory:
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
history = InMemoryChatMessageHistory()
# Устанавливаем системное сообщение и добавляем в историю (делается один раз в начале)
system_msg = SystemMessage(content="Вы полезный репетитор по Python.")
history.add_message(system_msg)
# Функция чата
def chat(user_input):
"""Обрабатывает ввод пользователя, отправляет в LLM и автоматически управляет историей диалога"""
# Добавляем ввод пользователя в историю
history.add_message(HumanMessage(content=user_input))
# Отправляем ввод пользователя вместе с предыдущей историей диалога
response = llm.invoke(history.messages)
# Добавляем ответ модели в историю
history.add_message(response)
return response.content
# Симулируем диалог
print(chat("Что такое lambda-функция?"))
print(chat("Покажите мне пример")) # Модель помнит контекст
print(chat("В чем разница от обычной функции?")) # Все еще помнитВывод:
Lambda-функция — это анонимная функция, определенная с ключевым словом lambda...
Вот пример: square = lambda x: x**2
Вы можете использовать её так: square(5) # Возвращает 25
Lambda-функции ограничены одним выражением, в то время как обычные функции...Функция chat() автоматически обрабатывает управление состоянием: она отправляет каждый запрос пользователя вместе с предыдущей историей диалога в LLM и добавляет как запрос, так и ответ обратно в историю. Простой вызов chat() поддерживает состояние диалога без дополнительного управления.
Рефакторинг главы 3: Добавление памяти в ваш CLI-чат
Давайте возьмем потоковый CLI-чат из главы 3 и добавим память диалогов. Вот оригинальная версия без сохранения состояния:
# chapter3_cli.py (оригинал - без состояния)
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
def chat_loop():
print("Чат начат. Введите 'quit' для выхода.\n")
while True:
user_input = input("Вы: ")
if user_input.lower() == "quit":
break
# Без состояния - нет истории
response = llm.stream([HumanMessage(content=user_input)])
print("AI: ", end="", flush=True)
for chunk in response:
print(chunk.content, end="", flush=True)
print("\n")
if __name__ == "__main__":
chat_loop()Рефакторенная версия с памятью:
# chapter8_cli.py (с памятью)
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
llm = ChatOpenAI(model="gpt-4o-mini")
def chat_loop():
print("Чат начат. Введите 'quit' для выхода.\n")
# Создаем историю и добавляем системное сообщение
history = InMemoryChatMessageHistory()
history.add_message(SystemMessage(content="Вы полезный помощник."))
while True:
user_input = input("Вы: ")
if user_input.lower() == "quit":
break
# Добавляем сообщение пользователя в историю
history.add_message(HumanMessage(content=user_input))
# Потоковый ответ
print("AI: ", end="", flush=True)
full_response = ""
for chunk in llm.stream(history.messages):
print(chunk.content, end="", flush=True)
full_response += chunk.content
print("\n")
# Добавляем ответ AI в историю
history.add_message(AIMessage(content=full_response))
if __name__ == "__main__":
chat_loop()Что изменилось:
- Добавлено хранилище истории: Создан экземпляр
InMemoryChatMessageHistory()внутриchat_loop() - Системное сообщение: Добавлено в историю один раз в начале
- Отправка ввода пользователя с историей:
llm.stream(history.messages)включает предыдущий диалог - Отслеживание диалога: Добавление как ввода пользователя, так и ответа LLM в историю
Тестирование рефакторенного чата:
Чат начат. Введите 'quit' для выхода.
Вы: Меня зовут Алиса
AI: Приятно познакомиться, Алиса! Чем я могу вам помочь сегодня?
Вы: Как меня зовут?
AI: Вас зовут Алиса.
Вы: О чем я только что вас спросила?
AI: Вы спросили меня, как вас зовут.
Вы: quitМодель теперь поддерживает контекст на протяжении всего диалога. Она помнит ваше имя, предыдущие вопросы и может ссылаться на более ранние части диалога.
Постоянное хранилище: Выход за рамки опций в памяти
InMemoryChatMessageHistory удобен для локальной разработки, но переход в продакшн требует замены его на решение для хранения, которое гарантирует постоянство.
Технические ограничения InMemory:
- Энергозависимая RAM: Когда процесс сервера завершается или перезапускается, вся история диалогов, хранящаяся в памяти, немедленно удаляется. Обновления или восстановление после ошибок приводят к полной потере контекста пользователя.
- Нет горизонтального масштабирования: По мере масштабирования вашего сервиса на несколько экземпляров серверов каждый сервер поддерживает свою собственную изолированную память. Пользователи, подключающиеся к разным серверам, не могут делиться историей диалогов.
- Неэффективность ресурсов: Хранение всей истории диалогов в RAM требует много памяти и угрожает стабильности системы по мере увеличения количества одновременных пользователей.
Профессиональные альтернативы:
PostgresChatMessageHistory(Рекомендуется): Наиболее надежный и широко применяемый выбор. Использует PostgreSQL для постоянного хранения и превосходен в сложных запросах и анализе данных.SQLChatMessageHistory: Использует существующие SQL-базы данных, такие как MySQL. Позволяет использовать вашу текущую инфраструктуру без изменений.RedisChatMessageHistory: Идеален для сервисов, где критична скорость ответа. Основан на памяти с опциями постоянства, специализирован для обработки высокого трафика.
"Хранилище меняется, код остается прежним"
LangChain предоставляет единый интерфейс для всех бэкендов хранения. Те же методы, которые вы использовали с InMemoryChatMessageHistory — такие как add_message() и add_messages() — работают идентично с другими опциями хранения. Это означает, что ваша бизнес-логика (код обработки диалогов) не требует изменений при переключении хранилища.
# [Разработка] Локальное хранение в памяти
# from langchain_core.chat_history import InMemoryChatMessageHistory
# history = InMemoryChatMessageHistory()
# [Продакшн] Постоянное хранилище PostgreSQL
import psycopg
from langchain_postgres import PostgresChatMessageHistory
# Создаем соединение с PostgreSQL
sync_connection = psycopg.connect(
"postgresql://user:password@10.1.1.100:5432/agent_db",
autocommit=True,
)
# Указываем соединение с БД и ID сессии
# session_id: Уникальный ключ для идентификации диалогов
# - Управление по пользователям: session_id = user_id (один диалог на пользователя)
# - Управление по сессиям: session_id = uuid (новый ID для каждого диалога)
history = PostgresChatMessageHistory(
table_name="chat_history",
session_id="user_123",
sync_connection=sync_connection
)
# --- Одинаковый интерфейс независимо от типа хранилища ---
history.add_message(HumanMessage(content="Покажите мой предыдущий заказ."))
print(history.messages)Важно: Это руководство использует InMemory для быстрого прогресса, но продакшн-развертывания должны переключиться на постоянное хранилище, такое как PostgresChatMessageHistory.
8.3) Управление длиной диалога
Функция памяти диалогов, которую мы реализовали в предыдущем разделе, имеет важную проблему: она только добавляет сообщения в историю. Это означает, что история продолжает расти, что создает две основные проблемы:
- Стоимость: Вся история отправляется с каждым запросом, поэтому стоимость за запрос продолжает увеличиваться по мере роста истории
- Ограничения контекстного окна: Модели имеют максимальный размер ввода, который они могут обработать в одном запросе (например, 400K токенов для GPT-5). Когда история диалога превышает этот лимит, модель не может правильно обработать запрос.
Одно из самых простых решений — это паттерн скользящего окна (sliding window).
Паттерн скользящего окна (Сохранение только последних N сообщений)
Паттерн скользящего окна решает две вышеуказанные проблемы, сохраняя только самые последние N сообщений в истории диалога. Он управляет размером истории, отбрасывая старые сообщения, обеспечивая следующие преимущества:
- Контроль стоимости: Сохраняя историю ниже определенного размера независимо от длины диалога, он предотвращает бесконечный рост стоимости за запрос
- Нет переполнения: Сохраняет размер ввода в пределах максимального лимита модели
Концептуальная диаграмма:
При размере окна 4 мы сохраняем только самые последние 4 сообщения (3, 4, 5, 6) и отбрасываем более старые сообщения (1, 2). Когда приходит новое сообщение (7), окно сдвигается к последнему сообщению, отбрасывая самое старое сообщение (3) и сохраняя сообщения 4, 5, 6, 7.
Компромиссы скользящего окна:
- Плюсы: Ограничивает размер истории для поддержания постоянных затрат и предотвращает переполнение контекстного окна
- Минусы: Сообщения за пределами размера окна отбрасываются, поэтому модель не может на них ссылаться
Этот компромисс может быть проблематичным. Решение состоит в использовании скользящего окна для недавнего диалога при одновременном извлечении необходимой прошлой информации из отдельного хранилища при необходимости. Это можно реализовать с помощью RAG (Retrieval-Augmented Generation), который мы рассмотрим в главе 9.
Единицы размера окна: Количество сообщений против количества токенов
Диаграмма выше показывает пример установки размера окна по количеству сообщений. Однако в продакшн-средах более распространено использование размера окна на основе токенов, потому что размеры сообщений варьируются:
Обрезка на основе количества сообщений:
Ограничивает размер окна количеством сообщений (например, сохранять только последние 20 сообщений).
- Характеристики: Фиксированное количество сообщений, но общее количество токенов все еще может варьироваться
- Использовать когда: Размеры сообщений контролируются (SMS, чаты с ограничением символов)
- Риск: Одно длинное сообщение все еще может превысить контекстное окно
Обрезка на основе количества токенов (Рекомендуется для продакшн):
Ограничивает размер окна количеством токенов, единицей ввода, обрабатываемой LLM (например, сохранять только последние 5000 токенов).
- Характеристики: Никогда не превышает контекстное окно независимо от длины сообщения
- Использовать когда: Размеры сообщений варьируются
Реализация обрезки на основе токенов: trim_messages()
LangChain предоставляет утилиту trim_messages(), которая реализует паттерн скользящего окна на основе токенов.
Как работает trim_messages():
Эта функция принимает полный список сообщений и максимальное количество токенов, возвращая только самые последние сообщения, которые помещаются в пределах лимита токенов.
Ключевые параметры:
messages: Список сообщений для обрезкиmax_tokens: Максимальное количество токенов для поддержанияtoken_counter: Функция для подсчета количества токенов для каждого сообщения (использует токенизатор модели для возврата количества токенов сообщения)include_system: Всегда ли сохранять SystemMessage (обычно True)
Настройка счетчика токенов:
import tiktoken
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
from langchain_core.messages.utils import trim_messages
# Получаем токенизатор для вашей модели (разные семейства моделей используют разные токенизаторы)
# Предоставьте имя модели для получения соответствующего токенизатора
# Модели Claude: Используйте токенизатор Anthropic (tiktoken специфичен для OpenAI)
enc = tiktoken.encoding_for_model("gpt-4o")
def token_counter(msg: BaseMessage) -> int:
"""Подсчитывает токены в одном сообщении."""
return len(enc.encode(msg.content or ""))
# Создаем историю диалога
messages = [
SystemMessage(content="Вы полезный помощник."),
HumanMessage(content="Привет!"),
AIMessage(content="Здравствуйте! Чем могу помочь?"),
HumanMessage(content="Сколько будет 2+2?"),
AIMessage(content="2+2 равно 4."),
HumanMessage(content="Сколько будет 3+3?"),
AIMessage(content="3+3 равно 6."),
HumanMessage(content="Сколько будет 4+4?"),
]
# Сохраняем только сообщения в пределах максимального количества токенов
trimmed = trim_messages(
messages,
max_tokens=30,
token_counter=token_counter,
include_system=True,
)
print(f"Оригинал: {len(messages)} сообщений")
print(f"Обрезано: {len(trimmed)} сообщений")
for m in trimmed:
print(f"{type(m).__name__}: {m.content}")Примечание: Количество сохраненных сообщений зависит от значения max_tokens и фактического количества токенов каждого сообщения. В примере выше max_tokens=30 — это очень маленькое значение, выбранное для целей тестирования. В продакшн вы должны установить соответствующее значение, учитывая средний размер сообщения и желаемый диапазон диалога.
Пример: Применение обрезки к функции чата
import tiktoken
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage, BaseMessage
from langchain_core.messages.utils import trim_messages
llm = ChatOpenAI(model="gpt-4o-mini")
history = InMemoryChatMessageHistory()
enc = tiktoken.encoding_for_model("gpt-4o-mini")
def token_counter(msg: BaseMessage) -> int:
"""Подсчитывает токены в одном сообщении."""
return len(enc.encode(msg.content or ""))
def chat_with_trimming(user_input: str, max_tokens: int = 1000) -> str:
"""Чат с автоматической обрезкой истории."""
history.add_message(HumanMessage(content=user_input))
system_msg = SystemMessage(content="Вы полезный помощник.")
all_messages = [system_msg] + history.messages
# Обрезаем до максимального количества токенов
trimmed_messages = trim_messages(
all_messages,
max_tokens=max_tokens,
token_counter=token_counter,
include_system=True,
)
response = llm.invoke(trimmed_messages)
history.add_message(response)
return response.content
# Пример использования
print(chat_with_trimming("Я планирую поездку в Японию"))
print(chat_with_trimming("Что мне посетить в Токио?"))
print(chat_with_trimming("Сколько дней мне следует там провести?"))
# Даже по мере роста истории только недавний диалог в пределах максимального количества токенов отправляется в LLMСледующий шаг: Ограничения управления состоянием диалога и решения (Глава 9: RAG)
Предоставление истории диалога модели помогает поддерживать контекст диалога. Однако этого одного недостаточно в некоторых случаях. Например:
- Когда вам нужно найти информацию в документах компании или руководствах
- Когда вам нужно сослаться на старую историю диалога, которая была вытеснена из скользящего окна
Здесь нужен RAG (Retrieval-Augmented Generation). RAG работает следующим образом:
- Хранение: Сохранение информации в векторной базе данных для семантического поиска
- Извлечение: Запрос информации с похожим значением на то, что вы ищете
Если использовать RAG для дополнения ограничений паттерна скользящего окна:
- Скользящее окно: Сохранять последние 20 сообщений (недавний контекст)
- RAG: Искать и извлекать релевантный контент из сообщений, вытесненных из окна
Вместо того чтобы просто помнить недавний диалог, RAG позволяет создать систему долговременной памяти.
RAG позволяет агентам использовать более широкие знания и более длинный контекст через использование внешних знаний и извлечение прошлых диалогов.
Глава 9 подробно рассмотрит, как реализовать RAG.
Резюме главы:
В этой главе вы узнали:
- Почему LLM забывают: Модели без состояния — память — это иллюзия, созданная повторной отправкой истории диалога
- Типы сообщений: SystemMessage (инструкции), HumanMessage (ввод пользователя), AIMessage (ответы модели)
- Управление состоянием: Управление историей диалога с помощью
InMemoryChatMessageHistory - Управление длиной диалога: Почему неограниченная история вызывает проблемы со стоимостью и контекстным окном
- Скользящие окна: Паттерн, который сохраняет только недавние сообщения для предотвращения бесконечного роста истории
- Обрезка на основе токенов: Реализация скользящих окон с использованием
trim_messages()и подсчета токенов