GrepBoard/매뉴얼
47 편 ·

Claude 응답이 잘렸는지 stop_reason 으로 확인하기

·Python 3.12.3 · Claude Messages API 공식 문서 기준 2026-10 · 샘플 응답 JSON 으로 검사·7 단계

JSON 답이 중간에 끊겨 json.loads 가 실패하면 stop_reason 부터 봅니다. max_tokens 일 때 잘림을 잡는 파이썬 검사와, max_tokens 를 올리거나 이어 받는 방법입니다.

json.loads 가 Unterminated string starting at 로 실패하는데 프롬프트는 멀쩡하다면, 답이 길어서 끊긴 경우를 먼저 의심합니다. 응답 JSON 의 stop_reason 한 필드로 가릴 수 있습니다.

[1] 응답의 stop_reason 값을 확인합니다. 공식 문서의 표에서 잘림과 관련된 값은 둘입니다.

값의미(공식 문서)문서의 대처
end_turnClaude finished its response naturally.Use the response.
max_tokensThe response reached your max_tokens limit.Raise max_tokens or continue the response.
model_context_window_exceededThe response filled the model's context window.Treat the response as truncated.

max_tokens 는 요청에서 정한 생성 상한입니다. 문서는 이 값이 "the absolute maximum number of tokens to generate" 이고 모델이 그 전에 멈출 수도 있다고 적습니다. 상한에 닿으면 문장이나 JSON 중간에서도 그대로 멈춥니다.

[2] 정상 응답과 잘린 응답을 파일로 둡니다. 아래 샘플은 문서의 응답 예시(id·content·stop_reason·usage 구조)를 따라 직접 만든 것이고, 실제 API 호출 결과가 아닙니다.

cut.json
{"id":"msg_01235","type":"message","role":"assistant","content":[{"type":"text","text":"{\"name\": \"A\", \"tags\": [\"x\", \"y"}],"stop_reason":"max_tokens","stop_sequence":null,"usage":{"input_tokens":100,"output_tokens":10}}

ok.json 은 같은 구조에서 text 가 {"name": "A", "tags": ["x", "y"]} 이고 stop_reason 이 end_turn 입니다. cut_valid.json 은 text 가 [1, 2, 3] 인데 stop_reason 이 max_tokens 입니다.

[3] 검사 스크립트를 만듭니다. 텍스트 블록을 이어 붙여 json.loads 를 시도하고, stop_reason 이 잘림 값이면 종료 코드 1 을 냅니다.

check_stop.py
import json, sys TRUNCATED = {"max_tokens", "model_context_window_exceeded"} def check(path):    resp = json.load(open(path))    stop = resp.get("stop_reason")    text = "".join(b["text"] for b in resp["content"] if b["type"] == "text")    try:        json.loads(text)        parsed = "ok"    except json.JSONDecodeError as e:        parsed = f"fail ({e.msg}, char {e.pos})"    cut = stop in TRUNCATED    print(f"{path}: stop_reason={stop} output_tokens={resp['usage']['output_tokens']} json={parsed} -> {'TRUNCATED' if cut else 'complete'}")    return 1 if cut else 0 sys.exit(max(check(p) for p in sys.argv[1:]))

[4] 세 파일에 돌립니다.

console
$ python3 check_stop.py ok.json cut.json cut_valid.json; echo exit=$?ok.json: stop_reason=end_turn output_tokens=18 json=ok -> completecut.json: stop_reason=max_tokens output_tokens=10 json=fail (Unterminated string starting at, char 28) -> TRUNCATEDcut_valid.json: stop_reason=max_tokens output_tokens=10 json=ok -> TRUNCATEDexit=1

cut_valid.json 을 보면 json.loads 가 통과해도 잘린 응답일 수 있습니다. 최상위가 배열이나 숫자인 JSON 은 중간에서 끊겨도 우연히 문법이 맞을 수 있으니, 파싱 성공만 믿지 말고 stop_reason 을 같이 봅니다.

[5] SDK 를 쓴다면 같은 검사를 응답 객체에 합니다. 아래는 문서의 예시 구조 그대로입니다.

app.py
if response.stop_reason == "max_tokens":    # Response was truncated    print("Response was cut off at token limit")    # Consider making another request to continue

[6] 잘렸다면 max_tokens 를 올려 다시 요청하는 것이 가장 단순합니다. 모델마다 max_tokens 의 최대값이 다르다고 문서가 밝히므로, 올릴 때 쓰는 모델의 한도를 모델 개요에서 확인합니다. 도구 호출이 max_tokens 에서 끊겨 마지막 블록이 불완전한 tool_use 라면, 문서는 더 큰 max_tokens 로 요청을 다시 보내라고 합니다.

[7] 사용자에게 보여 주는 글이라면 잘렸다는 표시를 붙여 내보내는 방법도 문서에 있습니다.

app.py
def handle_truncated_response(response):    text = next((block.text for block in response.content if block.type == "text"), "")    if response.stop_reason in ["max_tokens", "model_context_window_exceeded"]:        if response.stop_reason == "max_tokens":            note = "[Response truncated due to max_tokens limit]"        else:            note = "[Response truncated due to context window limit]"        return f"{text}\n\n{note}"    return text

문서: Handling stop reasons · Messages API

관련: JSON 형식 자체가 깨질 때는 Claude 답에서 json.loads 가 실패할 때 JSON 만 받기.

#claude#api#stop_reason#json