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.

Na tej stronie
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
Dotknij element po lewej, a następnie jego parę
- Funkcja kończy się na
return resuOdpowiedź z kodem trafiła w max_tokens w połowie linii json.loadskoń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].: "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:/ dla OpenAI SDK i https:/ (bez /v1) dla Anthropic SDK. max_tokens=200 jest celowo mały, żebyś sam zobaczył ucięcie.
Jedna kontrola, trzy sposoby
Python (OpenAI SDK)
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)
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
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_ w JSON-ie, który wraca.
Gdy zadziała jako diagnoza, zobaczysz tę parę: niski limit daje length, przestronny limit daje stop.

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):
Przewiń tabelę w prawo →
| 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- 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.

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ść.
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 roundsDwa 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
| Model | Dziennie | Miesięcznie |
|---|---|---|
| GLM-5.3-Flash$0.02$0.04 | $0.018 | $0.54 |
| DeepSeek-V4-Flash$0.015$0.10 | $0.0415 | $1.25 |
| MiniMax-M2.7$0.05$0.15 | $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



