Powrót do bloga

Rozwiązywanie problemówPrzewodnik

Odpowiedź AI ucięta? Napraw finish_reason "length" i max_tokens

Twoja odpowiedź API urywa się w połowie zdania albo zwraca zepsuty JSON? Sprawdź finish_reason, zwiększ max_tokens w granicach limitu i dodaj krok kontynuacji.

Autor: CheapTokens TeamOpublikowano 5 min czytania

Okładka w stylu terminala pokazująca finish_reason length w odpowiedzi API
Na tej stronie
  1. Jak ucięta odpowiedź wygląda na ekranie
  2. Krok 1: odczytaj pole, które Ci to powie
  3. Krok 2: zwiększ max_tokens, ale nie ponad sufit
  4. Krok 3: JSON, który urywa się w połowie
  5. Krok 4: pozwól modelowi kontynuować
  6. Ile kosztuje długa odpowiedź
  7. Zanim wdrożysz: krótka lista kontrolna

Twój skrypt się wykonał, model odpowiedział, a funkcja kończy się w połowie linii. Albo parser JSON wywala się na odpowiedzi, która wygląda zupełnie normalnie. Najczęściej model nie zawiódł: osiągnął ustawiony przez Ciebie limit max_tokens (albo domyślny, którego nigdy nie widziałeś) i API zatrzymało go w połowie słowa.

Szybka poprawka: odczytaj finish_reason w odpowiedzi. Jeśli wynosi "length" (API zgodne z OpenAI) lub stop_reason to "max_tokens" (Anthropic Messages), odpowiedź została ucięta. Zwiększ max_tokens do limitu wyjścia modelu albo poproś model o kontynuację. Reszta tej strony pokazuje jedno i drugie w kodzie, który możesz wkleić.

Jeśli chcesz prostsze wyjaśnienie, dlaczego tak się dzieje, zacznij od dlaczego odpowiedź AI urywa się w połowie zdania. Ta strona to wersja do debugowania.

Jak ucięta odpowiedź wygląda na ekranie#

Objawy różnią się w zależności od tego, o co prosiłeś, ale przyczyna jest ta sama. Spróbuj dopasować je do tego, co widziałeś:

Dopasuj pary

Dopasuj objaw do prawdopodobnej przyczyny

  • Funkcja kończy się na return resuOdpowiedź z kodem trafiła w max_tokens w połowie linii
  • json.loads kończy się błędem "Unterminated string" lub "Expecting ',' delimiter"JSON został ucięty w połowie
  • Tłumaczenie zatrzymuje się po pierwszej połowie tekstuLimit wyjścia jest mniejszy niż tłumaczenie
  • Odpowiedź jest kompletna, a pole mówi stopModel zakończył sam; szukaj gdzie indziej

Ten ostatni wiersz ma znaczenie. Jeśli finish_reason to stop, limit nie jest Twoim problemem, a zwiększanie max_tokens niczego nie zmieni.

Krok 1: odczytaj pole, które Ci to powie#

Każda odpowiedź mówi, dlaczego generowanie się zakończyło. W odpowiedzi zgodnej z OpenAI to choices[0].finish_reason: "stop", gdy model skończył, "length", gdy skończyły mu się tokeny. W Anthropic Messages API polem jest stop_reason, a wartością ucięcia jest "max_tokens". Przewodnik po stop-reason Anthropic (sprawdzony 28 października 2026) zaleca dokładnie dwie poprawki poniżej: zwiększyć max_tokens lub kontynuować odpowiedź.

Oto ta sama kontrola w trzech formach. Bazowy URL to https://api.cheaptokens.dev/v1 dla OpenAI SDK i https://api.cheaptokens.dev (bez /v1) dla Anthropic SDK. max_tokens=200 jest celowo mały, żebyś sam zobaczył ucięcie.

Jedna kontrola, trzy sposoby

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."}]
  }'

Szukaj "finish_reason": "length" w JSON-ie, który wraca.

Gdy zadziała jako diagnoza, zobaczysz tę parę: niski limit daje length, przestronny limit daje stop.

To samo zapytanie z niskim i wysokim max_tokens: finish_reason zmienia się z length na stop
Dwie wartości finish_reason, które spotkasz najczęściej

Krok 2: zwiększ max_tokens, ale nie ponad sufit#

Oczywista poprawka to większa liczba. Haczyk: każdy model ma własny limit odpowiedzi. Nie ustawiaj wartości powyżej niego: limit jest na model, więc najpierw sprawdź tabelę poniżej. To limity na odpowiedź w CheapTokens (fakty sprawdzone 28 października 2026):

Model Maks. wyjście na odpowiedź Kontekst
DeepSeek-V4-Flash 32 768 tokenów 380K
GLM-5.3-Flash 8 192 tokenów 390K
MiniMax-M2.7 8 192 tokenów 180K

Tłumaczenie na 9 000 tokenów wymaga modelu z większym limitem na odpowiedź — DeepSeek-V4-Flash przyjmuje do 32 768 tokenów na odpowiedź. Dla długiego kodu, długiego JSON-a lub całego dokumentu deepseek-ai/DeepSeek-V4-Flash-0731 ma najwięcej miejsca: do 32 768 tokenów na odpowiedź, jedna cena o każdej porze dnia. Weź dokładne identyfikatory modeli z dokumentacji CheapTokens i strony modeli, ponieważ dostawcy nazywają je różnie.

Praktyczna zasada: ustaw max_tokens na najdłuższą odpowiedź, jaką byś zaakceptował, a nie na maksimum. Płacisz za tokeny, które model faktycznie zapisuje, więc wysoki sufit nic nie kosztuje, gdy odpowiedzi są krótkie.

Krok 3: JSON, który urywa się w połowie#

Ucięta odpowiedź JSON to najgorszy przypadek, bo tekst wygląda dobrze, dopóki parser nie dojdzie do końca. Powiedzmy, że wyciągasz pola z 40 faktur w jednym wywołaniu i prosisz o tablicę. Model zapisuje 31 obiektów, osiąga limit, a tablica nigdy się nie zamyka.

Błąd json.loads na tablicy uciętej na limicie tokenów
Jak wygląda obcięta tablica JSON w parserze

Nie próbuj naprawiać ciągu, dodając ]}. Dostaniesz poprawny JSON z brakującymi rekordami, co jest gorsze niż błąd. Najpierw sprawdź finish_reason; jeśli to length, albo zwiększ limit i uruchom wywołanie ponownie, albo podziel dane wejściowe na mniejsze partie. O wymuszaniu samego formatu przeczytasz w poprawny JSON z API LLM.

Krok 4: pozwól modelowi kontynuować#

Czasem odpowiedź naprawdę jest dłuższa niż jakikolwiek limit: rozdział książki, duży refaktor. Wtedy odeślij to, co masz, i poproś model, żeby kontynuował. Pętla poniżej zachowuje dotychczasowy tekst, prosi o resztę i poddaje się po kilku rundach, żeby błąd nie kręcił się w nieskończoność.

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

Dwa nawyki utrzymują to w ryzach. Zwracaj flagę True/False i pokazuj użytkownikowi ostrzeżenie przy False, zamiast przedstawiać połowę dokumentu jako gotową. I pamiętaj, że każda runda wysyła wcześniejszy tekst jako wejście, więc kontynuowana odpowiedź kosztuje więcej niż jedna duża odpowiedź. Dla danych strukturalnych wolij większy limit lub mniejsze partie niż kontynuację.

Ile kosztuje długa odpowiedź#

Powiedzmy, że długa odpowiedź to 2 000 tokenów na wejściu i 8 000 tokenów na wyjściu. W DeepSeek-V4-Flash to zapytanie kosztuje $0.00083 przy dzisiejszej cenie, a w sumie, licząc wejście i wyjście razem, jest taniej niż OpenRouter. Zmień liczby na swoje obciążenie:

Kalkulator kosztów

Dzień z 50 długimi odpowiedziami

Tokeny wejściowe na żądanie: 2000 · Tokeny wyjściowe na żądanie: 8000 · Żądania dziennie: 50

ModelDziennieMiesięcznie
GLM-5.3-Flash$0.018$0.54
DeepSeek-V4-Flash$0.0415$1.25
MiniMax-M2.7$0.065$1.95

Wniosek: w tym scenariuszu dominuje strona wyjścia, więc zapas w max_tokens to tanie ubezpieczenie od ponownego uruchomienia. Ucięta odpowiedź, którą musisz wygenerować ponownie, jest płacona dwa razy. Tło o dwóch stronach rachunku znajdziesz w tokeny wejściowe vs wyjściowe, a pełna lista modeli z cenami jest na stronie cen.

Zanim wdrożysz: krótka lista kontrolna#

Lista kontrolna

Lista kontrolna ucinania dla Twojego kodu