Назад до блогу

Вирішення проблемПосібник

Відповідь AI обрізано? Виправте finish_reason "length" і max_tokens

Ваша відповідь API зупиняється посеред речення або повертає зламаний JSON? Перевірте finish_reason, збільште max_tokens у межах ліміту й додайте крок продовження.

Автор: CheapTokens TeamОпубліковано 5 хв читання

Обкладинка в стилі термінала, що показує finish_reason length у відповіді API
На цій сторінці
  1. Як обрізана відповідь виглядає на екрані
  2. Крок 1: прочитайте поле, яке все скаже
  3. Крок 2: збільште max_tokens, але не вище стелі
  4. Крок 3: JSON, що обривається на півдорозі
  5. Крок 4: дайте моделі продовжити
  6. Скільки коштує довга відповідь
  7. Перед релізом: короткий чекліст

Ваш скрипт виконався, модель відповіла, а функція обривається посеред рядка. Або парсер JSON падає на цілком нормальній на вигляд відповіді. Найчастіше модель не зламалася: вона досягла ліміту max_tokens, який ви задали (або типового значення, якого ви не бачили), і API зупинив її посеред слова.

Швидке виправлення: прочитайте finish_reason у відповіді. Якщо там "length" (API, сумісний з OpenAI) або stop_reason дорівнює "max_tokens" (Anthropic Messages), відповідь було обрізано. Збільште max_tokens до ліміту виводу моделі або попросіть модель продовжити. Решта цієї сторінки показує обидва варіанти в коді, який можна скопіювати.

Якщо хочете просте пояснення, чому це відбувається, почніть з чому відповідь AI зупиняється посеред речення. Ця сторінка — версія для налагодження.

Як обрізана відповідь виглядає на екрані#

Симптоми різняться залежно від того, що ви просили, але причина та сама. Спробуйте зіставити ці пункти з тим, що ви бачили:

З'єднайте пари

Зіставте симптом із ймовірною причиною

  • Функція закінчується на return resuВідповідь із кодом досягла max_tokens посеред рядка
  • json.loads падає з "Unterminated string" або "Expecting ',' delimiter"JSON обрізано на півдорозі
  • Переклад зупиняється після першої половини текстуЛіміт виводу менший за переклад
  • Відповідь повна, а поле каже stopМодель завершила сама; шукайте в іншому місці

Останній рядок важливий. Якщо finish_reason дорівнює stop, ліміт не є вашою проблемою, і збільшення max_tokens нічого не змінить.

Крок 1: прочитайте поле, яке все скаже#

Кожна відповідь каже, чому генерація завершилася. У відповіді, сумісній з OpenAI, це choices[0].finish_reason: "stop", коли модель завершила, "length", коли в неї закінчилися токени. В Anthropic Messages API поле — stop_reason, а значення обрізання — "max_tokens". Посібник Anthropic щодо причин зупинки (перевірено 28 жовтня 2026) рекомендує саме два виправлення нижче: збільшити max_tokens або продовжити відповідь.

Ось та сама перевірка у трьох формах. Базова URL-адреса — https://api.cheaptokens.dev/v1 для OpenAI SDK і https://api.cheaptokens.dev (без /v1) для Anthropic SDK. max_tokens=200 навмисно малий, щоб ви могли побачити обрізання самі.

Одна перевірка, три способи

Python (OpenAI SDK)

python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.cheaptokens.dev/v1",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
)

resp = client.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    max_tokens=200,
    messages=[{"role": "user", "content": "Write a Python class for a todo list with tests."}],
)

choice = resp.choices[0]
if choice.finish_reason == "length":
    print("Cut off at the token limit")
print(choice.message.content)
print(resp.usage.completion_tokens)

Python (Anthropic SDK)

python
import os
import anthropic

client = anthropic.Anthropic(
    base_url="https://api.cheaptokens.dev",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
)

msg = client.messages.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    max_tokens=200,
    messages=[{"role": "user", "content": "Write a Python class for a todo list with tests."}],
)

if msg.stop_reason == "max_tokens":
    print("Cut off at the token limit")
print(msg.content[0].text)

curl

bash
curl https://api.cheaptokens.dev/v1/chat/completions \
  -H "Authorization: Bearer $CHEAPTOKENS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-ai/DeepSeek-V4-Flash-0731",
    "max_tokens": 200,
    "messages": [{"role": "user", "content": "Write a Python class for a todo list with tests."}]
  }'

Шукайте "finish_reason": "length" у JSON, що повертається.

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

Той самий запит із низьким і високим max_tokens: finish_reason змінюється з length на stop
Два значення finish_reason, які ви зустрінете найчастіше

Крок 2: збільште max_tokens, але не вище стелі#

Очевидне виправлення — більше число. Підступ: кожна модель має власну межу на відповідь. Не задавайте значення вище за неї: ліміт залежить від моделі, тож спершу перегляньте таблицю нижче. Це ліміти на одну відповідь у CheapTokens (факти перевірено 28 жовтня 2026):

Модель Макс. вивід на відповідь Контекст
DeepSeek-V4-Flash 32 768 токенів 380K
GLM-5.3-Flash 8 192 токени 390K
MiniMax-M2.7 8 192 токени 180K

Переклад на 9 000 токенів потребує моделі з більшим лімітом на відповідь — DeepSeek-V4-Flash приймає до 32 768 токенів на відповідь. Для довгого коду, довгого JSON або цілого документа deepseek-ai/DeepSeek-V4-Flash-0731 має найбільше місця: до 32 768 токенів на відповідь, одна ціна будь-якої пори доби. Беріть точні ідентифікатори моделей з документації CheapTokens і сторінки моделей, оскільки провайдери називають їх по-різному.

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

Крок 3: JSON, що обривається на півдорозі#

Обрізана відповідь у JSON — найгірший випадок, бо текст виглядає правильно, доки парсер не дійде до кінця. Скажімо, ви витягуєте поля з 40 рахунків-фактур за один виклик і просите масив. Модель пише 31 об'єкт, досягає ліміту, і масив так і не закривається.

Помилка json.loads на масиві, обрізаному на ліміті токенів
Як виглядає обрізаний масив JSON у парсері

Не намагайтеся виправити рядок, додаючи ]}. Ви отримаєте валідний JSON із відсутніми записами, що гірше за помилку. Спершу перевірте finish_reason; якщо там length, або збільште ліміт і повторіть виклик, або розбийте вхідні дані на менші пакети. Щодо примусового задання самого формату дивіться валідний JSON від LLM API.

Крок 4: дайте моделі продовжити#

Іноді відповідь справді довша за будь-який ліміт: розділ книги, великий рефакторинг. Тоді поверніть те, що маєте, і попросіть модель продовжити. Цикл нижче зберігає текст, який уже є, просить решту й зупиняється після кількох раундів, щоб помилка не крутилася вічно.

python
def complete(base_messages, model, max_tokens=4096, rounds=4):
    text = ""
    for _ in range(rounds):
        messages = list(base_messages)
        if text:
            messages += [
                {"role": "assistant", "content": text},
                {"role": "user", "content": "Continue exactly where you stopped. Do not repeat anything."},
            ]
        r = client.chat.completions.create(model=model, max_tokens=max_tokens, messages=messages)
        choice = r.choices[0]
        text += choice.message.content or ""
        if choice.finish_reason != "length":
            return text, True   # finished on its own
    return text, False          # still cut after all rounds

Дві звички допомагають зберегти чесність. Повертайте прапорець True/False і показуйте користувачеві попередження на False, а не подавайте половину документа як готову. І пам'ятайте, що кожен раунд надсилає попередній текст як вхідні дані, тож продовжена відповідь коштує дорожче за одну велику відповідь. Для структурованих даних віддавайте перевагу більшому ліміту або меншим пакетам замість продовження.

Скільки коштує довга відповідь#

Скажімо, довга відповідь — це 2 000 токенів на вході та 8 000 токенів на виході. На DeepSeek-V4-Flash цей запит коштує $0.00083 за сьогоднішньою ціною, а загалом, з урахуванням входу й виходу разом, це дешевше за OpenRouter. Змініть числа на своє навантаження:

Калькулятор вартості

День із 50 довгими відповідями

Вхідні токени на запит: 2 000 · Вихідні токени на запит: 8 000 · Запитів на день: 50

МодельЗа деньЗа місяць
GLM-5.3-Flash$0.018$0.54
DeepSeek-V4-Flash$0.0415$1.25
MiniMax-M2.7$0.065$1.95

Висновок: у цьому сценарії домінує сторона виводу, тож запас у max_tokens — дешева страховка від повторного запуску. За обрізану відповідь, яку доводиться генерувати заново, ви платите двічі. Докладніше про дві сторони рахунку — у вхідні проти вихідних токенів, а повний список моделей із цінами — на сторінці цін.

Перед релізом: короткий чекліст#

Чек-лист

Чекліст обрізання для вашого коду