Claude 응답이 잘렸는지 stop_reason 으로 확인하기
JSON 답이 중간에 끊겨 json.loads 가 실패하면 stop_reason 부터 봅니다. max_tokens 일 때 잘림을 잡는 파이썬 검사와, max_tokens 를 올리거나 이어 받는 방법입니다.
json.loads 가 Unterminated string starting at 로 실패하는데 프롬프트는 멀쩡하다면, 답이 길어서 끊긴 경우를 먼저 의심합니다. 응답 JSON 의 stop_reason 한 필드로 가릴 수 있습니다.
[1] 응답의 stop_reason 값을 확인합니다. 공식 문서의 표에서 잘림과 관련된 값은 둘입니다.
| 값 | 의미(공식 문서) | 문서의 대처 |
|---|---|---|
end_turn | Claude finished its response naturally. | Use the response. |
max_tokens | The response reached your max_tokens limit. | Raise max_tokens or continue the response. |
model_context_window_exceeded | The 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 호출 결과가 아닙니다.
{"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 을 냅니다.
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] 세 파일에 돌립니다.
$ 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=1cut_valid.json 을 보면 json.loads 가 통과해도 잘린 응답일 수 있습니다. 최상위가 배열이나 숫자인 JSON 은 중간에서 끊겨도 우연히 문법이 맞을 수 있으니, 파싱 성공만 믿지 말고 stop_reason 을 같이 봅니다.
[5] SDK 를 쓴다면 같은 검사를 응답 객체에 합니다. 아래는 문서의 예시 구조 그대로입니다.
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] 사용자에게 보여 주는 글이라면 잘렸다는 표시를 붙여 내보내는 방법도 문서에 있습니다.
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 만 받기.