Claude한테 JSON만 달라고 했는데 꼭 "네, 요청하신 JSON은 다음과 같습니다:" 같은 서두를 붙여서 돌려준다. 파싱하는 입장에선 이게 제일 짜증난다. 정규식으로 중괄호를 긁어내거나, 응답에서 코드블록을 벗겨내는 코드를 매번 붙이게 된다.
프롬프트에 "서두 없이 JSON만"이라고 백 번 적는 것보다 확실한 방법이 있다. assistant 턴을 미리 채워 넣는(prefill) 것이다.
messages 마지막에 assistant를 넣는다
Claude API는 messages 배열의 마지막이 assistant 역할이면, 그 내용을 "이미 쓰기 시작한 답변"으로 보고 그 뒤를 이어서 생성한다. 즉 내가 첫 글자를 대신 찍어주는 셈이다.
const res = await client.messages.create({
model: 'claude-sonnet-5-5',
max_tokens: 1024,
messages: [
{ role: 'user', content: '아래 후기를 감정 분석해서 JSON으로 줘: "배송은 빨랐는데 품질이 아쉬워요"' },
{ role: 'assistant', content: '{' }, // ← 여기서부터 이어 쓰게 만든다
],
});
const json = '{' + res.content[0].text; // prefill한 '{'는 응답에 안 들어오니 다시 붙인다
console.log(JSON.parse(json));
// { sentiment: 'mixed', positives: ['배송'], negatives: ['품질'] }
{ 하나만 미리 넣었을 뿐인데 Claude는 그 다음을 JSON으로 이어 쓴다. 서두가 끼어들 자리 자체가 없어진다. 주의할 점은 prefill한 {는 응답 텍스트에 포함되지 않으니, 파싱 전에 직접 앞에 다시 붙여줘야 한다.
stop_sequences로 깔끔하게 끊기
코드블록(```)으로 감싸는 버릇을 막고 싶다면 prefill과 stop_sequences를 같이 쓰면 된다.
messages: [
{ role: 'user', content: '...' },
{ role: 'assistant', content: '```json\n' },
],
stop_sequences: ['```'],
여는 펜스를 내가 찍었으니 닫는 펜스가 나오는 순간 멈춘다. 가운데 알맹이만 남는다.
형식 외에 선택지 강제에도 쓴다
JSON만을 위한 건 아니다. 분류 작업에서 { role: 'assistant', content: '분류 결과:' }처럼 서두를 고정하거나, 객관식에서 '정답은 ('까지 찍어두면 Claude가 A), B) 중 하나로 바로 이어간다. 역할극에서 캐릭터가 설정을 깨지 않게 잡아두는 용도로도 자주 쓴다.
두 가지 함정
- prefill 문자열 끝에 공백을 두면 안 된다.
'{'는 되지만'{ '는 API 에러가 난다. 끝 공백을 trim하자. - 확장된 사고(extended thinking)를 켜면 prefill을 못 쓴다. 둘은 같이 못 간다. 추론이 필요한 작업이라면 prefill 대신 structured outputs 쪽을 봐야 한다.
스키마 보장까지 필요하면 structured outputs가 맞지만, "서두 좀 빼고 바로 본론부터"가 목적이라면 prefill 한 줄이 제일 싸고 빠르다.