블로그 목록
AI

Claude 응답이 중간에 잘릴 때 — stop_reason 보고 max_tokens 끊김 이어받기

긴 보고서를 생성시키는 기능을 붙였는데, 가끔 답변이 문장 중간에서 뚝 끊겼다. 처음엔 프롬프트 문제인 줄 알고 한참 헤맸다. 알고 보니 max_tokens에 걸려서 출력이 잘린 거였고, 코드는 그걸 모른 채 멀쩡한 답변처럼 화면에 뿌리고 있었다.

텍스트만 보면 안 되고 stop_reason을 봐야 한다

Claude 응답에는 왜 생성이 멈췄는지 알려주는 stop_reason이 같이 온다. 종류는 네 개다.

  • end_turn — 모델이 할 말을 다 하고 자연스럽게 끝남. 정상.
  • max_tokens — max_tokens 한도에 걸려서 강제로 끊김. 응답이 불완전하다.
  • stop_sequence — 내가 지정한 중단 문자열을 만나서 멈춤.
  • tool_use — 툴을 호출하려고 멈춤.

문제는 max_tokens로 잘린 응답도 텍스트 필드에는 멀쩡히 (하지만 중간까지만) 들어있다는 거다. stop_reason을 확인하지 않으면 잘린 걸 알 방법이 없다.

const res = await client.messages.create({
  model: 'claude-opus-4-8',
  max_tokens: 1024,
  messages,
});

if (res.stop_reason === 'max_tokens') {
  console.warn('응답이 잘렸습니다. 이어받기가 필요합니다.');
}

잘린 응답 이어받기

한도를 그냥 늘리는 게 1차 해법이지만, 길이를 예측 못 하는 작업이라면 잘린 지점부터 이어서 생성시키는 게 깔끔하다. 방법은 간단하다. 잘린 응답을 assistant 턴으로 다시 넣고 호출하면 Claude가 그 뒤부터 이어 쓴다. prefill과 똑같은 원리다.

async function completeMessage(messages, maxTokens = 1024) {
  let res = await client.messages.create({
    model: 'claude-opus-4-8',
    max_tokens: maxTokens,
    messages,
  });
  let full = res.content[0].text;

  // 잘려 있으면 지금까지 쓴 걸 assistant 턴으로 넣고 이어받는다
  while (res.stop_reason === 'max_tokens') {
    res = await client.messages.create({
      model: 'claude-opus-4-8',
      max_tokens: maxTokens,
      messages: [...messages, { role: 'assistant', content: full }],
    });
    full += res.content[0].text;
  }
  return full;
}

이어받은 조각은 이전 텍스트 뒤에 그대로 붙이면 된다. prefill 특성상 앞뒤 공백 처리만 한 번 확인하면 자연스럽게 이어진다. 무한 루프가 걱정되면 반복 횟수 상한을 하나 걸어두는 걸 추천한다.


핵심은 하나다. 응답 텍스트가 왔다고 완성된 게 아니다. stop_reason을 매번 확인하고, max_tokens면 잘린 걸로 취급해서 이어받거나 최소한 사용자에게 알려주자. 이거 하나 체크하는 걸로 "왜 가끔 답이 중간에 끊기죠?" 하는 버그 리포트가 사라졌다.