블로그로 돌아가기

문제 해결가이드

AI 답변이 잘렸나요? finish_reason "length"와 max_tokens 수정

API 답변이 문장 중간에서 멈추거나 깨진 JSON을 반환하나요? finish_reason을 확인하고, 한도 내에서 max_tokens를 올리고, 이어쓰기 단계를 추가하세요.

작성자: CheapTokens Team 게시됨읽는 시간 6분

API 응답에서 finish_reason length를 보여주는 터미널 스타일 커버
이 페이지의 내용
  1. 잘린 답변이 화면에 보이는 모습
  2. 1단계: 알려주는 필드를 읽기
  3. 2단계: max_tokens 올리기, 단 상한을 넘지 않게
  4. 3단계: 중간에서 멈추는 JSON
  5. 4단계: 모델이 이어서 쓰게 하기
  6. 긴 답변의 비용
  7. 출시 전: 짧은 체크리스트

스크립트가 실행되고 모델이 답했는데, 함수가 줄 중간에서 끝납니다. 아니면 완벽히 정상으로 보이는 응답에서 JSON 파서가 예외를 던집니다. 대부분의 경우 모델이 실패한 게 아닙니다. 설정한 max_tokens 한도(또는 미처 보지 못한 기본값)에 도달해서 API가 단어 중간에서 생성을 멈춘 것입니다.

빠른 해결책: 응답에서 finish_reason을 읽으세요. 값이 "length"(OpenAI 호환 API)이거나 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의 stop-reason 가이드(2026년 10월 28일 확인)는 정확히 아래 두 가지 해결책을 권장합니다: max_tokens를 올리거나 응답을 이어가기.

같은 확인을 세 가지 형태로 보여드립니다. base URL은 OpenAI SDK의 경우 https://api.cheaptokens.dev/v1, Anthropic SDK의 경우 https://api.cheaptokens.dev(/v1 없음)입니다. 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."}]
  }'

돌아온 JSON에서 "finish_reason": "length"를 찾으세요.

진단으로 제대로 작동하면 이런 쌍을 보게 됩니다: 낮은 한도는 length을, 넉넉한 한도는 stop을 줍니다.

낮은 max_tokens와 높은 max_tokens로 같은 요청: finish_reason이 length에서 stop으로 바뀜
가장 자주 만나게 될 finish_reason의 두 값

2단계: max_tokens 올리기, 단 상한을 넘지 않게#

당연한 해결책은 더 큰 숫자입니다. 주의할 점: 모든 모델은 답변에 자체 상한이 있습니다. 그 위로 값을 설정하지 마세요. 한도는 모델별이므로 먼저 아래 표를 확인하세요. CheapTokens의 답변별 한도입니다(사실 확인 2026년 10월 28일):

모델 답변당 최대 출력 컨텍스트
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토큰, 하루 어느 시간이든 단일 가격. 제공사마다 이름이 다르므로 정확한 모델 id는 CheapTokens 문서와 모델 페이지에서 가져오세요.

실용적인 규칙: max_tokens은 최댓값이 아니라 받아들일 수 있는 가장 긴 답변으로 설정하세요. 모델이 실제로 쓴 토큰에 대해서만 비용을 내므로, 답변이 짧을 때 높은 상한은 비용이 들지 않습니다.

3단계: 중간에서 멈추는 JSON#

잘린 JSON 응답은 가장 골치 아픈 경우입니다. 파서가 끝에 도달할 때까지 텍스트는 멀쩡해 보이기 때문입니다. 한 번의 호출로 40개 인보이스에서 필드를 추출하고 배열을 요청한다고 해봅시다. 모델이 31개 객체를 쓰고 한도에 도달하면 배열은 닫히지 않습니다.

토큰 한도에서 잘린 배열에 대한 json.loads 오류
파서에서 잘린 JSON 배열이 보이는 모습

]}를 추가해서 문자열을 복구하려 하지 마세요. 레코드가 빠진 유효한 JSON을 얻게 되는데, 이는 오류보다 나쁩니다. 먼저 finish_reason을 확인하고, length이라면 한도를 올려 호출을 다시 실행하거나 입력을 더 작은 배치로 나누세요. 형식 자체를 강제하는 방법은 LLM API에서 유효한 JSON 받기를 참고하세요.

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의 여유분은 재실행에 대비한 저렴한 보험입니다. 다시 생성해야 하는 잘린 답변은 두 번 지불하게 됩니다. 청구서 양쪽에 대한 배경은 입력 vs 출력 토큰에, 가격이 포함된 전체 모델 목록은 가격 페이지에 있습니다.

출시 전: 짧은 체크리스트#

체크리스트

코드용 잘림 체크리스트