긴 보고서를 생성시키는 기능을 붙였는데, 가끔 답변이 문장 중간에서 뚝 끊겼다. 처음엔 프롬프트 문제인 줄 알고 한참 헤맸다. 알고 보니 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면 잘린 걸로 취급해서 이어받거나 최소한 사용자에게 알려주자. 이거 하나 체크하는 걸로 "왜 가끔 답이 중간에 끊기죠?" 하는 버그 리포트가 사라졌다.