byteforce

CPN 한국어 자습서 · 러닝패스 2 / 4 — Building with the Claude API

API로 Claude에 접근하기

구조화된 데이터

Structured data

Claude로 JSON·코드 같은 구조화된 데이터를 생성하면, 종종 마크다운 백틱이나 설명 문장이 함께 붙습니다. assistant 메시지 prefill과 stop_sequences를 조합해, 부가 설명 없이 원하는 데이터만 정확히 받아내는 방법입니다.

전체 내레이션영상 나레이션 한국어 번역 (전체)

Stephen Grider · Anthropic 기술 스태프

stop sequence와 assistant message prefill은 아주 강력하게 조합할 수 있습니다. 구조화된 데이터를 생성해야 할 때 자주 쓰게 될 기법입니다. 화면처럼 EventBridge 규칙을 생성하는 웹 앱을 만든다고 해 봅시다. EventBridge 규칙은 AWS에서 쓰는 작은 JSON 조각입니다. 사용자가 프롬프트를 입력하고 생성을 누르면, 생성된 규칙이 위쪽에 나타나고, 사용자는 그걸 바로 선택하거나 복사 버튼으로 가져다 씁니다.

여기서 핵심 사용자 경험은, 생성된 규칙의 JSON만 보여 주고 그 외엔 아무것도 안 보여 주는 것입니다. 만약 위에 헤더가 붙고 아래에 설명 푸터가 붙은 응답을 보여 준다면, "전체 복사" 버튼이 무용지물이 됩니다. 사용자가 JSON만 직접 골라내야 하니까요. 즉 이 경우엔 Claude가 친절하게 설명까지 해 주는 게 오히려 방해입니다. 우리는 딱 그 데이터만 원합니다.

분명히 해 두면, 이건 JSON에만 해당하는 문제가 아닙니다. Claude로 어떤 구조화된 데이터든 생성할 때 — JSON이든 Python 코드든, 심지어 불릿 리스트든 — Claude는 종종 헤더나 푸터, 부가 설명을 끼워 넣으려 합니다. 많은 경우 그 부가 설명은 필요 없고, 요청한 raw 콘텐츠만 원합니다.

Claude가 딴 길로 새지 않고 요청한 raw 콘텐츠만 주도록, stop sequence와 prefill된 assistant message를 함께 씁니다. 노트북으로 가서 새 셀을 만들고, messages 리스트를 만든 뒤 user message를 추가합니다. "generate a very short event bridge rule as json"이라고 하고, 그대로 넘겨 초기 결과를 봅니다.

바로 JSON이 돌아오긴 하는데, 아쉽게도 앞에 백틱 세 개와 json, 뒤에 닫는 백틱 세 개가 붙어 있습니다. 이 백틱들은 마크다운으로 포맷하려고 들어간 것입니다. 마크다운으로 렌더링하면 보기 좋게 나오죠. 하지만 우리는 그 부가 문자 없이 순수 JSON만 원합니다.

그러려면 두 가지를 합니다. assistant message와 stop sequence를 함께 씁니다. 먼저 assistant message를 prefill 합니다. prefill 메시지는 백틱 세 개와 json입니다. 그리고 chat 호출에 stop sequence를 추가해, 백틱 세 개가 보이면 즉시 생성을 멈추게 합니다. 셀을 실행하면 순수 JSON만 돌아옵니다.

줄바꿈 문자가 좀 보이지만 괜찮습니다. JSON으로 파싱하거나 strip을 호출하면 쉽게 정리됩니다. text에 chat 결과를 담고, 다음 셀에서 json을 import해서 json.loads에 text.strip()을 넣습니다. 실행하면 우리가 기대하는 방식으로 접근할 수 있는, 아주 잘 정리된 JSON이 나옵니다.

assistant message와 stop sequence가 정확히 무슨 일을 하는지 다이어그램으로 봅시다. user message, prefill된 assistant, stop sequence를 함께 줍니다. Claude는 요청의 각 부분을 살핍니다. 먼저 user message를 보고 "전체 규칙을 써야겠다, 설명도 곁들이자"고 생각합니다 — 헤더·푸터를 붙이려는 거죠. Claude는 본래 자기 작업을 설명하려는 경향이 있으니까요.

그런데 prefill된 assistant message를 만나면, 지난 영상에서 배웠듯 Claude는 그 내용을 자기가 이미 썼다고 가정합니다. 그래서 "아, JSON 부분을 이미 시작했네, 이제 실제 JSON만 쓰면 되겠다"고 판단하고 JSON을 써 내려갑니다. 그리고 끝에 다다르면, 아까 자기가 열었다고 생각한 마크다운 코드 블록을 닫으려고 자연스럽게 닫는 백틱 세 개를 쓰려 합니다.

바로 그 순간 stop sequence에 걸려 생성이 완전히 멈추고 응답이 즉시 반환됩니다. 결국 "이걸로 시작해서 저걸로 끝내고, 그 사이의 모든 걸 달라"고 말한 셈이고, 그 결과 우리가 정말 원하는 부분 — 순수 JSON만 — 받게 됩니다.

이 기법은 아주 강력해서 자주 쓰게 됩니다. 어떤 구조화된 데이터든 부가 설명 없이 그 데이터만 받고 싶을 때, JSON에 국한되지 않고, assistant message prefill과 stop sequence 조합을 떠올리면 됩니다.

이 장에서 배우는 것What you'll learn

약 6분
1

구조화된 데이터(JSON·코드·리스트)를 요청하면 Claude가 헤더·푸터·마크다운 백틱을 붙이곤 한다

2

웹앱에선 순수 데이터만 필요 — 부가 설명은 복사·파싱을 방해한다

3

assistant 메시지 prefill로 출력의 시작을 강제한다 (예: ```json)

4

stop_sequences로 닫는 ```를 만나면 즉시 생성을 끊는다

5

둘을 합치면 시작과 끝 사이의 raw 데이터만 남는다

6

json.loads(text.strip())로 깔끔하게 파싱한다

먼저 짚고 갈 용어
stop sequence (정지 시퀀스)
stop_sequences에 넣은 문자열이 생성 도중 나타나면 즉시 생성을 멈춘다. create() 인자.
assistant prefill (어시스턴트 프리필)
assistant 역할 메시지를 미리 넣어 Claude가 그 뒤를 이어 쓰게 만든다. Claude는 그 내용을 이미 자기가 썼다고 가정한다.
EventBridge rule
AWS에서 이벤트를 매칭하는 작은 JSON 규칙. 이 레슨의 생성 예시.
json.loads · strip
문자열을 JSON으로 파싱(loads)하고, 앞뒤 공백·줄바꿈을 제거(strip)한다.

왜 순수 데이터가 어려운가

The problem with default responses

화면의 웹 앱은 사용자 입력으로 EventBridge 규칙(AWS의 작은 JSON 조각)을 생성합니다. 사용자는 생성된 JSON을 바로 선택하거나 복사 버튼으로 가져다 씁니다. 그래서 JSON만 보여 주고 그 외엔 아무것도 안 보여 줘야 합니다.

같은 요청, prefill+stop 적용 전/후 · 복사해서 바로 쓸 수 있나?
EventBridge Rule Generator
Generate a very short EventBridge rule as JSON생성

생성 결과 · 복사 영역


"그냥 요청"이면 마크다운 백틱 ```json … ```이 섞여 들어와, 전체 복사 시 JSON이 아닌 텍스트가 됩니다. prefill+stop을 켜면 순수 JSON만 남아 그대로 파싱·복사할 수 있습니다.

실제로 그냥 요청해 보면, JSON이 돌아오긴 하지만 앞뒤에 마크다운 코드펜스(```json```)가 붙어 있습니다.

004_Controlling_Output.ipynb · 그냥 요청
messages = []
add_user_message(messages, "Generate a very short event bridge rule as json")

chat(messages)
출력 · 마크다운 백틱이 섞임
```json   ← 마크다운 코드펜스 (불필요)
{
  "source": ["aws.ec2"],
  "detail-type": ["EC2 Instance State-change Notification"],
  "detail": {
    "state": ["running"]
  }
}
```   ← 닫는 코드펜스 (불필요)

출력은 실제로 한 문자열입니다 — '```json\n{...}\n```'. 백틱은 마크다운 렌더링용일 뿐, 우리에겐 불필요한 문자입니다.

해결: prefill + stop sequence

Assistant prefilling + stop sequences

두 도구를 조합합니다. 먼저 chat 함수가 stop_sequences를 받도록 인자를 하나 더합니다 (앞 장의 system·temperature와 같은 방식).

004_Controlling_Output.ipynb · chat()에 stop_sequences 추가
def chat(messages, system=None, temperature=1.0, stop_sequences=None):
    params = {
        "model": model,
        "max_tokens": 1000,
        "messages": messages,
        "temperature": temperature,
    }
    if system:
        params["system"] = system
    if stop_sequences:                       # ← 이번에 추가
        params["stop_sequences"] = stop_sequences

    message = client.messages.create(**params)
    return message.content[0].text
stop sequence만 먼저

stop_sequences에 넣은 문자열이 생성 중 나타나면 그 직전에 즉시 멈춥니다. 예를 들어 1부터 10까지 세게 하되 "5" 또는 "3, 4"에서 멈추라고 하면:

stop_sequences 맛보기
messages = []
add_user_message(messages, "Count from 1 to 10")

chat(messages, stop_sequences=["5", "3, 4"])
# → '1, 2, '   ("5" 또는 "3, 4"가 나오기 직전에 멈춤)

이제 본론입니다. assistant 메시지를 ```json으로 prefill하고, stop_sequences=["```"]를 줍니다. prefill은 출력의 시작을 강제하고, stop은 닫는 백틱에서 생성을 끊습니다.

004_Controlling_Output.ipynb · prefill + stop
messages = []
add_user_message(messages, "Generate a very short event bridge rule as json")
add_assistant_message(messages, "```json")   # ← prefill

text = chat(messages, stop_sequences=["```"])
text
출력 · 순수 JSON (백틱 없음)
{
  "source": ["aws.ec2"],
  "detail-type": ["EC2 Instance State-change Notification"],
  "detail": {
    "state": ["running"]
  }
}
# 백틱은 사라지고 앞뒤 \n만 남음 → 실제 문자열 '\n{...}\n'

동작 원리를 단계별로 봅시다. Claude가 prefill을 만나 무엇을 가정하고, 어디서 멈추는지가 핵심입니다.

prefill + stop sequence · Claude의 머릿속 단계별로
User messageGenerate an EventBridge rule as JSON
Assistant (prefill)```json
stop_sequences["```"]

prefill로 시작을 강제하고 stop으로 끝을 자르면, "이걸로 시작해 저걸로 끝내고 그 사이를 달라"는 셈 — 사이의 raw JSON만 남습니다.

응답 처리

Processing the response

잘라낸 문자열 앞뒤에 \n이 조금 남지만 문제없습니다. strip()으로 정리하고 json.loads()로 파싱하면, 우리가 기대하는 방식으로 접근할 수 있는 깔끔한 데이터가 됩니다.

004_Controlling_Output.ipynb · 파싱
import json

data = json.loads(text.strip())   # strip()으로 앞뒤 \n 제거 후 파싱
data
출력 · 파싱된 dict
{'source': ['aws.ec2'],
 'detail-type': ['EC2 Instance State-change Notification'],
 'detail': {'state': ['running']}}
결과

이제 data["source"]처럼 평범한 Python 딕셔너리로 접근할 수 있습니다. 부가 설명도, 백틱도 없는 순수 데이터입니다.

JSON 너머

Beyond JSON

이 기법은 JSON 전용이 아닙니다. 특정 형식의 콘텐츠만 받고 부가 설명을 떼고 싶을 때 어디서나 씁니다 — 시작을 prefill로 강제하고, 끝을 stop으로 자르면 됩니다.

같은 패턴, 다른 형식
  • Python 코드```python으로 prefill, ```로 stop.
  • 불릿 리스트 — 첫 항목 기호로 시작을 유도하고, 마무리 문장이 시작되는 패턴으로 stop.
  • CSV·기타 — 헤더 행으로 prefill하고, 설명이 붙기 시작하는 지점에서 stop.
핵심

"시작과 끝 사이의 raw 콘텐츠만" 받고 싶을 때면, assistant prefill + stop sequence 조합을 떠올리세요.

정리 & 점검

Recap & check
핵심 정리
  • 구조화 데이터를 요청하면 Claude가 헤더·푸터·마크다운 백틱을 붙이곤 한다.
  • assistant prefill(예: ```json)로 출력의 시작을 강제한다 — Claude는 그걸 이미 썼다고 가정한다.
  • stop_sequences(예: ["```"])로 닫는 백틱에서 생성을 끊는다.
  • 둘을 합치면 시작과 끝 사이의 raw 데이터만 남는다.
  • json.loads(text.strip())로 파싱한다. JSON뿐 아니라 모든 구조화 데이터에 같은 패턴.

Q1JSON만 원하는데 Claude가 ```json … ```로 감싸 줄 때, 백틱을 없애려면?

Q2assistant 메시지를 prefill하면 Claude는 그 내용을?

Q3잘라낸 문자열 앞뒤에 남은 \n을 정리하는, 영상이 보여 준 방법은?


LAB · 실습 콘솔JSON PARSE

구조화 출력 파서

모델 응답 문자열을 JSON으로 파싱해 보고, 산문이 섞이면 어디서 어긋나는지 확인합니다.

index.js
// 구조화 출력 파서 — 모델이 돌려준 텍스트를 JSON으로 파싱한다.
// 깨끗한 JSON은 그대로 파싱되지만, 앞에 설명 문장이 섞이면 파싱이 깨진다.

function parseModelJson(raw) {
  const text = raw.trim();        // 앞뒤 공백·줄바꿈 제거
  const data = JSON.parse(text);  // 산문이 섞이면 여기서 예외가 난다
  const required = ["title", "tags"];
  const missing = required.filter(function (k) { return !(k in data); });
  if (missing.length) {
    throw new Error("필수 키 누락: " + missing.join(", "));
  }
  return data;
}

// 케이스 A — 깨끗한 JSON만 돌려준 응답
const clean = '\n{ "title": "부산 여행", "tags": ["해운대", "달맞이길"] }\n';

// 케이스 B — 앞에 설명 문장이 새어 나온 응답
const withProse = '요청하신 결과입니다:\n{ "title": "부산 여행", "tags": ["해운대"] }';

[["A · 깨끗한 JSON", clean], ["B · 산문이 섞임", withProse]].forEach(function (pair) {
  const label = pair[0], raw = pair[1];
  try {
    const data = parseModelJson(raw);
    console.log(label + " → 파싱 성공: " + data.title + " · 태그 " + data.tags.length + "개");
  } catch (e) {
    console.log(label + " → 파싱 실패: " + e.message);
  }
});
MEMBER SESSION REQUIRED · REGISTRATION IS FREE

여기부터는 등록한 분에게 열립니다.

전 코스는 계속 무료입니다. 등록하면 이 코스의 남은 76개 레슨을 끝까지 읽을 수 있습니다.

등록하고 이어서 읽기

이미 등록하셨다면 그때 쓰신 이메일을 넣어 주세요.