Многоходовой разговор

Диалог из нескольких реплик и что делать, когда история становится слишком длинной.

Многоходовой разговор — диалог, где каждый новый запрос несёт всю предыдущую переписку, чтобы модель сохраняла контекст.

Почему память — это ваша забота

Раз API без состояния (раздел 1), «память» диалога целиком на вашей стороне: это просто список сообщений, который вы ведёте и отправляете заново при каждом запросе. Модель ничего не запоминает между вызовами — иллюзия непрерывного разговора возникает только потому, что вы каждый раз передаёте всю предысторию. Если приложение перезапустилось и список потерян — для модели диалога как не бывало.

Цикл диалога в коде

Менеджер диалога обычно хранит список сообщений и при каждой реплике пользователя: добавляет её, отправляет весь список, получает ответ, дописывает ответ в список.

import anthropic

client = anthropic.Anthropic()
history = []

def send(user_text):
    history.append({"role": "user", "content": user_text})
    resp = client.messages.create(
        model="claude-opus-4-8",
        max_tokens=1024,
        system="Ты дружелюбный ассистент.",
        messages=history,
    )
    answer = "".join(b.text for b in resp.content if b.type == "text")
    history.append({"role": "assistant", "content": answer})
    return answer

print(send("Меня зовут Алиса."))
print(send("Как меня зовут?"))   # модель помнит благодаря history

Проблема: контекст растёт и стоит денег

Каждое сообщение в истории — это токены, за которые вы платите при каждом запросе. Длинный диалог быстро раздувает входные токены и может упереться в контекстное окно модели. Решения два: обрезка (сохранять только последние N сообщений) и суммаризация (сжать старое в краткое содержание).

Окно истории — обрезка (запускаемо)

Простейшая стратегия: держать только последние N сообщений. Системную инструкцию хранят отдельно и не обрезают.

def trim_history(messages, max_messages=4):
    # Системную инструкцию храним отдельно (не в messages).
    # Оставляем только последние max_messages сообщений.
    if len(messages) <= max_messages:
        return messages
    return messages[-max_messages:]

history = [
    {"role": "user", "content": "сообщение 1"},
    {"role": "assistant", "content": "ответ 1"},
    {"role": "user", "content": "сообщение 2"},
    {"role": "assistant", "content": "ответ 2"},
    {"role": "user", "content": "сообщение 3"},
]
trimmed = trim_history(history, max_messages=3)
for m in trimmed:
    print(m["role"], "->", m["content"])
print("Было:", len(history), "Стало:", len(trimmed))

Вывод:

user -> сообщение 2
assistant -> ответ 2
user -> сообщение 3
Было: 5 Стало: 3

Грубая обрезка по числу сообщений может разрезать пару user/assistant — в проде обрезают аккуратнее (по токенам, парами) или суммаризируют. Для длинных диалогов провайдеры предлагают и серверные механизмы сжатия контекста.

Итог

  • Многоходовой диалог = передача всей истории в каждом запросе.
  • История растёт и стоит токенов; контролируйте её размер.
  • Стратегии: обрезка по последним N (или по токенам) и суммаризация старого.
Проверьте себя
1. Почему в длинном диалоге растут расходы?
AМодель дорожает со временем
BВся история отправляется в каждом запросе, увеличивая число входных токенов
CКаждый новый запрос требует нового ключа
DРастёт max_tokens
2. Какие две стратегии помогают сдержать рост истории диалога?
AУвеличить max_tokens и temperature
BОбрезка (последние N сообщений / по токенам) и суммаризация старого контекста
CСменить модель и ключ
DОтключить роль system
3. Что нужно делать с системной инструкцией при обрезке истории?
AОбрезать её первой
BХранить отдельно и не обрезать — она задаёт поведение на весь диалог
CДублировать в каждом сообщении user
DУдалять после первого ответа
Поддержать проект