Оптимизация расходовИнструкция
Как урезать историю чата: скользящее окно или сводка
Ограничьте то, что отправляете на каждом шаге: последние сообщения или короткая сводка. Расчёт токенов для чата из 50 ходов и код на Python.

Содержание
Ваш бот поддержки общается уже 50 ходов, и каждое новое сообщение обходится дороже предыдущего. API ничего не помнит, поэтому ваш код каждый раз отправляет весь разговор заново. Решение — ограничить отправляемое: оставить системный промпт и последние несколько ходов (скользящее окно) или заменить старые ходы короткой сводкой. Ниже — расчёт для чата из 50 ходов и хелпер, который можно скопировать.
Руководство DeepSeek по многошаговому чату (api-docs.deepseek.com, проверено 1 ноября 2026) говорит прямо: сервер не записывает контекст прошлых запросов, поэтому клиент отправляет полный список messages при каждом вызове. В OpenAI-совместимом формате CheapTokens всё так же. Если для вас это новость, начните со статей почему длинный чат стоит дороже и как устроен массив messages.
Что на самом деле уходит на 50-м ходу#
Допустим, у вас бот поддержки с системным промптом на 400 токенов. В обычном ходу пользователь пишет около 60 токенов, бот отвечает около 190. Один полный ход добавляет в историю 250 токенов. Это допущения: подставьте числа из поля usage ваших собственных ответов.
На 50-м ходу запрос несёт системный промпт, 49 прошлых ходов и новое сообщение: 400 + 49 × 250 + 60 = 12,710 входных токенов ради ответа в 190 токенов. Теперь сложим весь чат. Системный промпт и новое сообщение уходят каждый раз: 50 × 460 = 23,000. Повторяющаяся история добавляет 250 × 1,225 = 306,250. Вместе: 23,000 + 306,250 = 329,250 входных токенов против 50 × 190 = 9,500 выходных.
История в каждом запросе растёт на ход, поэтому сумма растёт как квадрат длины чата. Перейдите с 50 ходов на 100, и повторяющаяся часть истории вырастет примерно вчетверо.
Попробуйте самиПопробуйте сами: если отправить только системный промпт, последние 4 хода и новое сообщение, сколько входных токенов уйдёт на 50-м ходу?Показать ответ
1,460 (400 + 4 × 250 + 60) против 12,710 при полной истории. Тот же вопрос, та же длина ответа, малая часть входа.
Вариант 1: скользящее окно#
Идея: системный промпт отправляем всегда, остальное отбрасываем, кроме последних N ходов. Полную историю вы храните у себя, а обрезаете только копию, которую отправляете.
def trim_history(messages, keep_pairs=4):
"""System prompt + the last keep_pairs turns + the new user message."""
system = [m for m in messages if m["role"] == "system"]
rest = [m for m in messages if m["role"] != "system"]
return system + rest[-(keep_pairs * 2 + 1):]Вызывайте её после того, как добавили новое сообщение пользователя. При keep_pairs=4 это 4 пары «пользователь/ассистент» и новое сообщение, всего 9 сообщений, а срез всегда начинается с сообщения пользователя. Вот как это подключено к настоящему вызову:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.cheaptokens.dev/v1",
api_key=os.environ["CHEAPTOKENS_API_KEY"],
)
MODEL = "deepseek-ai/DeepSeek-V4-Flash-0731"
history = [{"role": "system", "content": "You are a concise support assistant."}]
def ask(text):
history.append({"role": "user", "content": text})
resp = client.chat.completions.create(
model=MODEL,
messages=trim_history(history),
max_tokens=500,
)
answer = resp.choices[0].message.content
history.append({"role": "assistant", "content": answer})
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)
return answerСамая полезная строка — print: смотрите на первое число. Без обрезки оно растёт на каждом ходу. С окном перестаёт расти, когда чат проходит 4-й ход.

В числах: начиная с пятого хода каждый запрос равен 400 + 4 × 250 + 60 = 1,460 входных токенов. Первые четыре хода растут как обычно и дают в сумме 3,340. Весь чат: 3,340 + 46 × 1,460 = 70,500 входных токенов, примерно пятая часть от 329,250 при полной истории. Выход остаётся 50 × 190 = 9,500.

В коде вся правка — один аргумент в вызове:
Сравнение
Правка в одну строку
Было
messages=historyСтало
messages=trim_history(history)Но есть подвох: окно забывает. Если на 40-м ходу пользователь спрашивает «какой у меня номер заказа?», а номер прозвучал на 3-м, модель его просто не видела. Два дешёвых выхода. Достаньте устойчивые факты (имя, номер заказа, тариф) своим кодом и дописывайте их в системный промпт: там окно их не отрежет. Или добавьте сводку — это следующий вариант.
Вариант 2: сводка старых ходов#
Вместо того чтобы выбрасывать старые ходы, вы их сжимаете. Раз в несколько ходов отправляйте модели ходы, которые только что выпали из окна, вместе с текущей сводкой и просите новую короткую сводку. Затем кладите её в системное сообщение.
def summarize(summary, old_messages):
transcript = "\n".join(f"{m['role']}: {m['content']}" for m in old_messages)
prompt = (
f"Summary so far:\n{summary}\n\nNew turns:\n{transcript}\n\n"
"Rewrite the summary in under 200 words. "
"Keep names, numbers, decisions and open questions."
)
resp = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": prompt}],
max_tokens=400,
)
return resp.choices[0].message.content
def build_messages(history, summary, keep_pairs=4):
system = history[0]["content"]
if summary:
system += "\n\nEarlier in this chat: " + summary
recent = history[1:][-(keep_pairs * 2 + 1):]
return [{"role": "system", "content": system}] + recentСводка не бесплатна: это дополнительный вызов модели. Допустим, сводка занимает около 300 токенов и обновляется раз в 5 ходов (9 раз за 50 ходов); каждое обновление читает около 1,600 токенов (старая сводка, пять ходов и инструкция) и пишет 300. Для запаса посчитаем сводку в каждом запросе начиная с пятого хода (на деле она появляется после первого обновления, так что оценка слегка завышена): 400 + 300 + 4 × 250 + 60 = 1,760 входных токенов.
Итоги по чату: основные запросы 3,340 + 46 × 1,760 = 84,300 входных токенов, вызовы сводки 9 × 1,600 = 14,400 входных и 9 × 300 = 2,700 выходных. Всего 84,300 + 14,400 = 98,700 входных и 9,500 + 2,700 = 12,200 выходных токенов.
Сводка обходится чуть дороже голого окна, зато модель по-прежнему знает, что было на 3-м ходу.
Сколько стоят три варианта#
Тот же чат из 50 ходов на DeepSeek-V4-Flash, цены считаются вживую по итогам выше:
Цены берутся из актуального прайса, поэтому числа обновятся вместе с тарифом. Порядок не изменится: окно отправляет меньше всего, сводка посередине, полная история дороже всех.
Для такого чата в CheapTokens стоит начать с DeepSeek-V4-Flash. Контекст 380K (чат из 50 ходов помещается даже без обрезки), до 32 768 токенов в ответе, одна фиксированная цена в любое время суток, а в сумме, с учётом входа и выхода вместе, он дешевле OpenRouter. Id модели — deepseek-. Если хотите сравнить модели на варианте с окном (в среднем 1,410 входных токенов на запрос, 190 выходных, 50 запросов), вот живая таблица:
| Модель | Вход $/1M | Выход $/1M | За запрос | 50 запросов | OpenRouter, 50 запросов |
|---|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.000036 | $0.0018 | $0.0032 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.00004 | $0.002 | $0.0063 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.000099 | $0.005 | $0.0594 |
1 410 входных и 190 выходных токенов на запрос. Цены за 1M токенов актуальны и меняются вместе с нашими тарифами; столбец OpenRouter показывает справочные цены для той же нагрузки.
У GLM-5.3-Flash контекст 390K, у MiniMax-M2.7 — 180K, этого хватает для обрезанного чата. Актуальные цены — на странице цен, а как отправить первый запрос, показано в документации.
Что выбрать#
Окно — вариант по умолчанию: три строки кода, никаких лишних вызовов и лишних поводов для сбоя. Берите его для ботов поддержки, репетиторов и ролевых игр, где разговор держится на последних ходах, а правила — в системном промпте. Сводку добавляйте, когда пользователи ссылаются на сказанное давно, как в личном ассистенте с одним бесконечным разговором. Окно делайте достаточно маленьким, чтобы платить меньше, и достаточно большим, чтобы в нём помещался целый обмен вопросом и ответом. Сколько вообще помещается в запрос — отдельный предел, о нём в статье что такое контекстное окно.
Быстрая проверка
На 40-м ходу пользователь спрашивает про номер заказа, названный на 3-м. У вас окно из 4 ходов без сводки. Что произойдёт?
Показать ответ
Ответ: Модель его не увидит, потому что 3-й ход вырезан из запроса
API не хранит состояние. Модель видит только то, что есть в запросе, поэтому нужные факты должны остаться в системном промпте или в сводке.



