본문으로 건너뛰기
Koding
Claude Certified Architect – Foundations (CCA-F) 자격시험 교재
무료

Claude Certified Architect – Foundations (CCA-F) 자격시험 교재

한국 SI·컨설팅 실무자와 AI 엔지니어를 위한 CCA-F 합격 교재예요. 원문 61회 연재 원고를 내용 손실 없이 32개 장으로 재구성했어요. 각 세션의 이론·연습문제·해설은 한 장에 묶어 학습 흐름이 끊기지 않게 했고, 프로젝트와 모의고사는 문제편과 해설편을 분리해 실전처럼 풀 수 있게 했어요.

클로드claudeanthropicCCACCAF

7.제6장. 세션 4: 구조화 출력과 JSON 스키마 (이론·연습문제·해설)

출제 도메인: 도메인 4 (20%) · 작성자: 김재우 (jaewoo@claudcode.to)


① Claude 구조화 출력·JSON 스키마 완벽 가이드 (CCA-F 세션 4): Tool Use·검증·리트라이

지난 장 돌아보기

지난 장에서는 프롬프트 설계의 기본 테크닉을 배웠어요. 이번 장에서는 Claude의 출력을 프로그램에서 다룰 수 있는 형식(JSON 등)으로 받기 위한 구조화 출력을 다뤄요. CCA-F 도메인 4의 후반에서 자주 나오는 주제예요.

주제 개요

LLM의 출력은 자유 텍스트가 기본이지만, 실무에서는 '프로그램이 다룰 수 있는 형식'이 요구돼요. 비유하자면 자유 텍스트는 '손으로 쓴 편지지'이고, 구조화 출력은 '빈칸을 채우는 신청서'예요. 후자가 프로그램에서 처리하기 쉽고, 에러도 줄일 수 있어요.

CCA-F에서는 '어떻게 안정적인 JSON 출력을 얻는가', '검증·리트라이를 어떻게 설계하는가'를 물어요.

습득 포인트

  • 구조화 출력의 세 가지 접근(프롬프트 지시 / XML / Tool Use)을 설명할 수 있다
  • JSON 스키마 기반 Tool Use로 구조화 출력을 받을 수 있다
  • 검증 + 리트라이 루프의 설계 패턴을 구현할 수 있다
  • (참고) 스트리밍으로 구조화 출력을 다룰 때의 주의점을 이해하고 있다(스트리밍은 시험 범위 밖)
  • Pydantic 같은 타입 검증 라이브러리와의 연동 방법을 파악하고 있다
  • 자주 나는 실패(포맷 깨짐, 인코딩, 코드 블록 부착)에 대처하는 법을 이해하고 있다
  • 멀티패스 리뷰(draft→review→refine, 생성과 비평의 분리)의 의의와 비용/품질 트레이드오프를 설명할 수 있다

1. 구조화 출력의 세 가지 접근

1.1 접근 비교

접근신뢰성구현 난이도
프롬프트 지시'JSON으로 출력해 주세요'라고 쓴다낮음(쉬움)
XML 태그<result>...</result>로 감싸게 함중~높음낮음(쉬움)
Tool Use 기반JSON 스키마를 따르는 툴 정의높음

1.2 추천: Tool Use 기반

Claude에서는 'Tool Use를 구조화 출력 수단으로 쓰는' 것이 가장 신뢰성 높은 방법이에요. 이건 툴 호출 기능(세션 5에서 상세히)을 JSON 출력 강제 장치로 전용하는 기법이죠.

[최신 정보] 원래는 Tool Use가 사실상 유일한 표준 수단이었지만, 이후 Anthropic이 네이티브 구조화 출력(Structured Outputs) 기능을 추가해 2026년 현재 GA 상태입니다. 스키마를 문법으로 컴파일해 토큰 생성을 제약(constrained decoding)하므로 스키마 준수 JSON이 보장돼요. Tool Use 방식도 여전히 유효하지만, 이제는 유일한 방법이 아닙니다.

tool_use_structured_output.py

import anthropic

client = anthropic.Anthropic()

# JSON 스키마로 툴 정의
tools = [
    {
        "name": "record_user_info",
        "description": "사용자 정보를 구조화 형식으로 기록한다",
        "input_schema": {
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "성명"},
                "age": {"type": "integer", "description": "나이"},
                "email": {"type": "string", "format": "email"},
                "interests": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "관심 분야 리스트",
                },
            },
            "required": ["name", "age", "email"],
        },
    }
]

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "tool", "name": "record_user_info"},  # 반드시 이 툴을 쓰게 함
    messages=[
        {
            "role": "user",
            "content": "저는 김민준, 35세, minjun@example.com입니다. AI와 장기에 관심이 있습니다.",
        }
    ],
)

# 툴 호출 블록에서 구조화 데이터 취득
for block in response.content:
    if block.type == "tool_use":
        print(block.input)
        # {"name": "김민준", "age": 35, "email": "minjun@example.com", "interests": ["AI", "장기"]}

2. JSON 스키마 작성법

JSON 스키마는 '데이터의 타입과 제약을 정의하는 사양'이에요.

2.1 주요 스키마 요소

schema_elements.py

schema = {
    "type": "object",
    "properties": {
        # 기본형
        "name": {"type": "string"},
        "age": {"type": "integer", "minimum": 0, "maximum": 150},
        "score": {"type": "number"},
        "is_active": {"type": "boolean"},
        # Enum
        "status": {
            "type": "string",
            "enum": ["active", "inactive", "pending"],
        },
        # 배열
        "tags": {
            "type": "array",
            "items": {"type": "string"},
            "minItems": 1,
            "maxItems": 10,
        },
        # 중첩 오브젝트
        "address": {
            "type": "object",
            "properties": {
                "country": {"type": "string"},
                "city": {"type": "string"},
            },
            "required": ["country"],
        },
    },
    "required": ["name", "age"],
}

2.2 description의 중요성

각 프로퍼티의 description은 Claude에게 주는 지시로 작동해요.

Bad(모호한 description)

bad_schema.py

"price": {"type": "number"}  # 단위는? 세금 포함?

Good(명확한 description)

good_schema.py

"price": {
    "type": "number",
    "description": "상품 판매가(세금 포함·원(KRW))",
    "minimum": 0,
}

왜 Good일까요: Claude는 description에서 값의 의도를 이해하고, 더 정확한 출력을 해요.

3. 검증 + 리트라이 루프

LLM의 출력은 이따금 스키마 위반을 일으켜요. 이걸 감지해 재시도하는 검증 + 리트라이 루프가 정석이죠.

3.1 Pydantic을 사용한 구현

validation_retry.py

import anthropic
from pydantic import BaseModel, Field, ValidationError
from typing import Literal

class UserInfo(BaseModel):
    name: str = Field(..., description="성명")
    age: int = Field(..., ge=0, le=150)
    email: str
    status: Literal["active", "inactive", "pending"]

def extract_user_info(text: str, max_attempts: int = 3) -> UserInfo:
    client = anthropic.Anthropic()
    last_error = None
    for attempt in range(max_attempts):
        prompt_suffix = ""
        if last_error:
            prompt_suffix = f"\n\n이전 에러: {last_error}\n위 내용을 수정해 다시 출력해 주세요."

        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=512,
            temperature=0,
            tools=[
                {
                    "name": "record_user",
                    "description": "사용자 정보를 기록",
                    "input_schema": UserInfo.model_json_schema(),  # Pydantic에서 자동 생성
                }
            ],
            tool_choice={"type": "tool", "name": "record_user"},
            messages=[{"role": "user", "content": text + prompt_suffix}],
        )

        try:
            for block in response.content:
                if block.type == "tool_use":
                    return UserInfo(**block.input)
        except ValidationError as e:
            last_error = str(e)
            continue

    raise RuntimeError(f"max retries exceeded: {last_error}")

3.2 리트라이 루프의 설계 원칙

사용법과 주의점

  • 에러 내용을 Claude에게 전달하는 게 중요해요(맹목적인 리트라이는 무의미)
  • 최대 시도 횟수를 반드시 둔다(무한 루프 방지)
  • 리트라이해도 안 되면 HITL(사람 검토)로 에스컬레이션한다

4. 스트리밍 × 구조화 출력 (참고·시험 범위 밖)

참고: 스트리밍은 CCA-F 출제 범위 밖이에요. 이 절은 구현상의 함정을 참고로 보여줄 뿐이라, 시험 대비로 외울 필요는 없어요.

스트리밍과 JSON 출력의 조합에는 '중간의 불완전한 JSON을 파싱하면 안 된다'는 함정이 있어요.

Bad(어중간한 JSON을 파싱하려고 함)

bad_streaming.py

import json

# ❌ 스트리밍 중의 불완전 JSON을 파싱
for chunk in stream:
    try:
        partial = json.loads(chunk)  # 거의 확실히 JSONDecodeError
    except:
        pass

Good(스트리밍은 표시용, 완료 후에 파싱)

good_streaming.py

with client.messages.stream(...) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)  # UI 표시만
    final = stream.get_final_message()
    # 툴 호출 블록에서 구조화 데이터 취득
    for block in final.content:
        if block.type == "tool_use":
            data = block.input

왜 Good일까요: Tool Use의 input은 완전한 JSON이 확정된 뒤 Anthropic 쪽에서 파싱을 마쳐 돌려줘요. 중간 스트림에서 수동으로 파싱하는 건 불필요하고 위험해요.

5. 자주 나는 실패 패턴

5.1 실패 패턴 일람

증상원인대처
JSON 앞뒤에 불필요한 텍스트프롬프트 지시만으로 강제Tool Use로 전환
출력이 코드 펜스(json 코드 블록)로 감싸짐코드 블록 버릇프롬프트에서 '코드 블록 없이'라고 명시 or Tool Use
'"의 혼재고온도temperature=0
키 이름의 흔들림(name vs Name)스키마 미지정스키마로 명시
배열의 마지막 콤마LLM의 버릇Tool Use(Anthropic이 파싱)
값이 번역돼 버림(enum 'active'가 '액티브'가 됨)description 부족description에 '영어 소문자 그대로'라고 명기

5.2 enum 값의 번역 문제

enum_problem.py

# Bad
"status": {"type": "string", "enum": ["pending", "approved", "rejected"]}
# → Claude가 '보류 중', '승인 완료'처럼 번역해서 출력할 가능성

# Good
"status": {
    "type": "string",
    "enum": ["pending", "approved", "rejected"],
    "description": "상태 값. **이 3개의 영단어 중 하나를 반드시 사용**할 것. 번역하지 말 것.",
}

6. Tool Use 기반 구조화 출력의 이점 정리

관점프롬프트 지시Tool Use 기반
출력 포맷흔들림 있음엄격하게 JSON 보장
스키마 강제약함강함
파싱 처리직접 구현Anthropic 쪽에서 파싱 완료
에러 감지자체 검증Pydantic + 검증
다국어 대응번역되기 쉬움description으로 억제 가능

7. 멀티패스 리뷰(draft→review→refine)

여기까지는 '한 번의 요청으로 어떻게 올바른 구조를 뽑아낼까'에 집중했어요. 한편 계약서 드래프트나 중요 리포트처럼 출력 자체의 품질을 극한까지 높이고 싶은 장면에서는, 한 번의 생성으로 완벽을 노리는 대신 여러 패스(공정)로 나눠 품질을 쌓아 올리는 방법이 효과적이에요. 이걸 멀티패스 리뷰라고 불러요.

비유하자면 글을 쓸 때 '쓰고 그대로 제출'하는 게 아니라, '초고 → 스스로 빨간 펜 → 정서'로 공정을 나누는 이미지예요. 후자가 빠뜨린 부분이나 논리 비약을 알아채기 쉬워요.

7.1 draft → review → refine의 3패스

멀티패스 리뷰는 전형적으로 다음 3개 패스로 구성해요.

  • draft(생성): 우선 초고를 생성한다
  • review(비평): 초고를 다른 역할로 읽고, 문제점·개선점을 찾아낸다
  • refine(수정): 비평 결과를 반영해 정서한다

여기서 중요한 건 생성 역할과 비평 역할을 분리하는 거예요. 같은 모델을 쓰더라도 역할과 프롬프트를 바꾸면(draft용 프롬프트 / review용 프롬프트), '내가 쓴 걸 무조건 옳다고 여기는' 편향을 피하고 빠뜨린 부분을 줍기 쉬워져요.

multi_pass_review.py

import anthropic

client = anthropic.Anthropic()

def multi_pass_draft(topic: str) -> str:
    # 패스1: draft(생성 역할)
    draft = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2048,
        temperature=0,
        messages=[
            {"role": "user", "content": f"다음 주제로 계약서 드래프트를 작성해 주세요: {topic}"},
        ],
    ).content[0].text

    # 패스2: review(비평 역할 — 역할과 프롬프트를 바꾼다)
    review = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        temperature=0,
        messages=[
            {
                "role": "user",
                "content": (
                    "당신은 계약서를 검토하는 법무 담당입니다. "
                    "다음 드래프트의 누락·모호한 조항·리스크를 개조식으로 지적해 주세요.\n\n"
                    f"{draft}"
                ),
            },
        ],
    ).content[0].text

    # 패스3: refine(비평을 반영해 정서)
    final = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2048,
        temperature=0,
        messages=[
            {
                "role": "user",
                "content": (
                    "다음 드래프트의 리뷰 지적을 모두 반영해, 정서해 주세요.\n\n"
                    f"【드래프트】\n{draft}\n\n【리뷰 지적】\n{review}"
                ),
            },
        ],
    ).content[0].text

    return final

7.2 1회 생성 vs 멀티패스

Bad(품질의 문제):

bad_single_pass.py

# 1회 생성 결과를 그대로 채택한다
final = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=2048,
    messages=[{"role": "user", "content": "계약서 드래프트를 작성해 주세요"}],
).content[0].text
# → 누락이나 모호한 조항을 알아채지 못한 채 확정해 버린다

Good(품질의 개선):

good_multi_pass.py

# draft → review → refine으로 생성과 비평을 분리해 반복한다
final = multi_pass_draft("업무 위탁 계약")
# → 비평 패스에서 누락·리스크를 찾아내고, refine에서 없앤 뒤 확정한다

왜 Good일까요: 생성과 비평을 별개의 공정·별개의 역할로 나누면, 생성할 때는 못 봤던 문제를 독립된 시점으로 주울 수 있어요. 중요 문서에서는 이 한 수고가 품질을 크게 좌우해요.

7.3 비용/품질의 트레이드오프

멀티패스가 만능은 아니에요. 패스를 늘릴수록 품질은 오르지만, 그만큼 API 호출 횟수(비용)와 레이턴시도 늘어나요.

관점1회 생성멀티패스(draft→review→refine)
품질표준높음(비평으로 누락 보정)
비용낮음(1회)높음(최소 3회)
레이턴시낮음높음(패스 수에 비례)
맞는 경우대량·경량 출력계약서·중요 리포트 등 중요 출력

주의: 멀티패스 리뷰는 중요한 출력에만 적용해요. 대량의 경량 처리에까지 적용하면 비용·레이턴시가 수지에 안 맞아요. '품질을 최대화하고 싶은 중요 문서인가?'를 판단 축으로 삼으세요.

7.4 Best of N과의 차이(혼동 주의)

멀티패스 리뷰와 헷갈리기 쉬운 게 Best of N이에요. 둘 다 '품질을 올린다'는 점은 같지만, 접근이 달라요.

기법방식이미지
멀티패스 리뷰1개 안을 draft→review→refine으로 반복 개선한다한 장의 초고에 빨간 펜을 넣어 정서
Best of N여러 안을 독립적으로 생성하고, 그중에서 최선을 고른다여러 안을 써 보고 가장 좋은 것을 채택
  • 멀티패스는 '1개 안을 반복적으로 비평·개선한다'
  • Best of N은 '여러 안을 독립 생성해 최선을 고른다'

시험에서는 '중요 문서·구조화 출력의 품질을 최대화하고 싶다' → '생성과 비평을 분리해 반복하는 멀티패스'라는 판단 축을 잡아 두세요.

자주 하는 오해와 개념 정리

❌ 자주 하는 오해: 'JSON 모드가 있으니 JSON 출력은 확실하다'

  • 오해: 다른 회사의 LLM API에 있는 'JSON 모드'처럼, 설정 하나로 JSON이 확실해진다고 여기기 쉬움
  • 실제: Anthropic API에는 전용 response_format=json이 없다. Tool Use를 구조화 출력의 표준적 수단으로 쓴다
  • 확인 방법: 공식 문서의 "Structured outputs" 섹션을 참조

❌ 자주 하는 오해: 'temperature가 높아도 JSON은 안 깨진다'

  • 오해: Tool Use를 쓰고 있으면 temperature는 상관없다
  • 실제: 온도가 높으면 키의 순서·enum 값·문자열 내용의 흔들림이 늘어난다
  • 확인 방법: 구조화 출력에서는 기본 temperature=0

❌ 자주 하는 오해: '리트라이하면 언젠가는 성공한다'

  • 오해: 몇 번이고 리트라이하면 결국 올바른 출력을 얻는다
  • 실제: 프롬프트/스키마 쪽에 문제가 있으면 몇 번을 시도해도 실패한다. 최대 시도 횟수를 두고, 초과하면 설계를 재검토한다
  • 확인 방법: 실패율이 높으면 스키마와 description을 개선

자주 나는 에러와 대처법

  • 에러 예시: tool_choice를 지정해도 다른 툴이 호출됨
    • 원인: tool_choice의 형식 불량
    • 대처법: {"type": "tool", "name": "<tool_name>"} 형태로 특정 툴 강제를 지정
  • 에러 예시: Pydantic의 ValidationError
    • 원인: Claude의 출력이 스키마 위반
    • 대처법: 에러 내용을 프롬프트에 담아 리트라이. 그래도 계속 실패하면 description을 개선
  • 에러 예시: additionalProperties가 거부됨
    • 원인: JSON 스키마의 엄격성 지정
    • 대처법: 스키마에서 "additionalProperties": false를 명시하거나, 툴 안에서 필터링

정리와 다음 한 걸음

  • 구조화 출력에는 Tool Use 기반 접근이 가장 신뢰성이 높다
  • JSON 스키마의 description은 Claude에게 주는 지시로 작동한다
  • 검증 + 리트라이 루프 + 에러 내용 피드백이 정석
  • 스트리밍은 표시용으로 쓰고, 구조화 데이터는 완료 후에 취득한다
  • 온도는 0으로 고정하는 게 원칙
  • enum 값의 번역 문제에는 description으로 대책

다음 세션에서는 구조화 출력의 기반이기도 한 Tool Use(Function Calling)를 도메인 2의 맥락에서 자세히 다룹니다.


② CCA-F 세션 4 연습문제 9선: Claude 구조화 출력·JSON 스키마·Tool Use 완전 대비

습득 포인트와 문제 매칭

습득 포인트해당 문제
구조화 출력의 3가지 접근문제 1
JSON 스키마 작성법문제 2
Tool Use 기반 구조화 출력문제 3
검증 + 리트라이 루프문제 4, 문제 5
스트리밍 × 구조화 출력문제 6
자주 나는 실패 패턴문제 7
멀티패스 리뷰문제 8

문제 1: 구조화 출력에서 가장 신뢰성 높은 접근

  • 카테고리: 기초
  • 난이도: ★☆☆☆☆
  • 예상 소요 시간: 5분

Anthropic API에서 가장 신뢰성 높은 구조화 출력 수단은 다음 중 무엇인가요?

  • (A) 프롬프트로 'JSON으로 출력해 주세요'라고 지시한다
  • (B) XML 태그로 결과를 감싸게 한다
  • (C) Tool Use(JSON 스키마)를 써서 tool_choice로 강제한다
  • (D) 스트리밍 응답을 문자열로 파싱한다

문제 2: 스키마 description의 역할

  • 카테고리: 기초
  • 난이도: ★★☆☆☆
  • 예상 소요 시간: 5분

JSON 스키마의 description 필드가 Claude의 구조화 출력에 주는 효과로 가장 적절한 것은 무엇인가요?

  • (A) Claude에게 주는 지시로 작동해, 필드의 의미·단위·형식 이해를 돕는다
  • (B) 단순한 문서일 뿐. Claude의 동작에는 영향을 주지 않는다
  • (C) 스키마 검증 때만 사용된다
  • (D) description을 쓰면 레이턴시가 크게 늘어난다

문제 3: 특정 툴을 강제하는 지정

  • 카테고리: 응용
  • 난이도: ★★☆☆☆
  • 예상 소요 시간: 5분

messages.create에서 record_user_info라는 툴을 반드시 쓰게 하기 위한 tool_choice 지정으로 올바른 것은 무엇인가요?

  • (A) tool_choice="record_user_info"
  • (B) tool_choice={"type": "tool", "name": "record_user_info"}
  • (C) tool_choice={"force": True, "tool": "record_user_info"}
  • (D) tool_choice={"required": "record_user_info"}

문제 4: 리트라이 루프의 설계

  • 카테고리: 응용
  • 난이도: ★★★☆☆
  • 예상 소요 시간: 10분

스키마 위반으로 리트라이할 때, 가장 효과적인 방법은 무엇인가요?

  • (A) 같은 프롬프트로 재시도를 10회 반복한다
  • (B) 자동 리트라이는 하지 않고, 에러가 나면 사용자에게 알린다
  • (C) 시도할 때마다 temperature를 올린다
  • (D) 직전 에러 내용을 프롬프트에 담아 재시도한다. 최대 시도 횟수를 설정한다

문제 5: 리트라이 상한 설계

  • 카테고리: 응용
  • 난이도: ★★★☆☆
  • 예상 소요 시간: 5분

리트라이해도 계속 스키마 위반이 날 때, 가장 적절한 다음 대응은 무엇인가요?

  • (A) 리트라이 상한을 100회로 늘린다
  • (B) 프롬프트 / 스키마 자체를 재검토해 개선한다. 그래도 실패하는 경우는 HITL로 에스컬레이션
  • (C) 스키마를 포기하고 자유 텍스트 출력으로 바꾼다
  • (D) 더 고성능인 모델(Opus)로 바꾸기만 해서 대처한다

문제 6: 스트리밍 × 구조화 출력

  • 카테고리: 응용
  • 난이도: ★★★☆☆
  • 예상 소요 시간: 10분

스트리밍 모드에서 JSON 출력을 다룰 때 가장 적절한 접근은 무엇인가요?

  • (A) 스트림의 각 청크마다 JSON 파싱을 시도한다
  • (B) 스트리밍 중의 어중간한 JSON을 try/except로 감싸 강제로 파싱한다
  • (C) 스트림은 표시 전용으로 두고, 구조화 데이터는 완료 후의 tool_use 블록에서 취득한다
  • (D) 스트리밍은 구조화 출력에 못 쓰니 포기한다

문제 7: enum 값의 번역 문제

  • 카테고리: 응용
  • 난이도: ★★★☆☆
  • 예상 소요 시간: 10분

status 필드의 enum 값 ["pending", "approved", "rejected"]을 Claude가 이따금 '보류 중', '승인 완료'로 번역해 버립니다. 가장 효과적인 대책은 무엇인가요?

  • (A) temperature를 1.0으로 올린다
  • (B) 리트라이 횟수를 100회로 늘린다
  • (C) enum을 포기하고 자유 문자열로 바꾼다
  • (D) enum 값의 description에 '영어 소문자 그대로 사용하고, 번역하지 말 것'이라고 명시한다

문제 8: 중요 문서의 품질을 최대화하는 접근

  • 카테고리: 응용
  • 난이도: ★★★☆☆
  • 예상 소요 시간: 10분

중요한 계약서 드래프트의 품질을 최대화하고 싶을 때, 가장 적절한 접근은 무엇인가요?

  • (A) draft→review→refine의 여러 패스로, 생성 역할과 비평 역할을 분리해 반복한다
  • (B) 1회 생성하고, 그 결과를 그대로 채택한다
  • (C) temperature를 올려 출력의 다양성을 낸다
  • (D) max_tokens를 키워 긴 출력을 얻는다

문제 9 (고급): 스키마 설계의 개선

  • 카테고리: 고급
  • 난이도: ★★★★☆
  • 예상 소요 시간: 15분

다음 스키마에는 여러 문제가 있습니다. 개선안을 서술하세요.

schema = {
    "type": "object",
    "properties": {
        "price": {"type": "number"},
        "status": {"type": "string"},
        "items": {"type": "array"},
        "createdAt": {"type": "string"},
    },
}

정답은 이 장 후반의 정답·해설편에서 확인하세요.

[보완] 최신 정보는 문제 1은 주어진 네 선택지 안에서 Tool Use(C)가 가장 신뢰성 높다는 게 정답입니다. 다만 이 내용을 정리한 이후 Anthropic이 네이티브 구조화 출력(Structured Outputs)을 GA로 제공하기 시작했어요. output_config.format(JSON 스키마)이나 툴 정의의 "strict": True를 쓰면 문법 제약 디코딩으로 스키마 준수가 보장되므로, 실무에서는 이 기능이 Tool Use 우회보다 더 견고한 경우가 많습니다. 시험 답안 자체는 (C)로 두되, 실제 구현 시에는 네이티브 구조화 출력도 함께 검토해 보세요.


③ CCA-F 세션 4 연습문제 정답·해설: Claude 구조화 출력·Tool Use·스키마 설계 완전 풀이

정답 일람

문제정답
문제 1(C)
문제 2(A)
문제 3(B)
문제 4(D)
문제 5(B)
문제 6(C)
문제 7(D)
문제 8(A)
문제 9후술

문제 1: 구조화 출력에서 가장 신뢰성 높은 접근 — 정답 (C)

해설

Tool Use를 tool_choice로 강제하는 방법이, 스키마 준수와 JSON 구조 보장 양면에서 가장 신뢰성 높은 접근이에요. Anthropic 쪽에서 JSON 파싱까지 마쳐 돌려주니, 호스트 쪽의 파싱 에러 리스크도 줄어들어요.

◆ 설계와 운용 포인트

  • 설계 사상: Anthropic은 OpenAI 같은 response_format=json을 제공하지 않고, Tool Use를 구조화 출력의 정규 루트로 자리매김하고 있어요.
  • 운용 시 주의점: 툴 이름·description은 '구조화 출력의 의도'가 전해지는 이름으로 지어요.

[최신 정보] 'Anthropic은 response_format=json을 제공하지 않는다'는 서술은 이 문제를 정리하는 시점 기준입니다. 현재는 Anthropic도 **네이티브 구조화 출력(Structured Outputs)**을 GA로 제공해요. 응답 텍스트를 스키마에 맞추는 output_config.format(type: "json_schema")과, 툴 입력을 스키마와 정확히 맞추는 "strict": True(엄격 툴 사용) 두 모드가 있고, 둘 다 문법 제약 디코딩으로 작동합니다. 시험 답안은 주어진 선택지 기준으로 (C) Tool Use가 맞지만, 실무에서는 네이티브 구조화 출력이 더 견고한 경우가 많으니 함께 검토해 보세요.

문제 2: 스키마 description의 역할 — 정답 (A)

해설

스키마의 description은 Claude에게 주는 필드 단위 지시로 작동해, 의미·단위·형식 이해를 도와요. description의 질에 따라 출력 품질이 크게 달라져요.

◆ 설계와 운용 포인트

  • 설계 사상: description은 '주석'이 아니라 '프롬프트'로 여겨요.
  • 운용 시 주의점: '단위', '형식', '언어', '허용되는 값의 범위' 같은 걸 구체적으로 써요.

문제 3: 특정 툴을 강제하는 지정 — 정답 (B)

해설

올바른 형식은 {"type": "tool", "name": "<tool_name>"}이에요. 이렇게 하면 Claude가 반드시 그 툴을 호출하게 돼요.

◆ 설계와 운용 포인트

tool_choice의 3가지 형식:

  • {"type": "auto"}: Claude가 판단(기본값)
  • {"type": "any"}: 반드시 어떤 툴이든 하나를 쓴다
  • {"type": "tool", "name": "..."}: 특정 툴을 반드시 쓴다

문제 4: 리트라이 루프의 설계 — 정답 (D)

해설

에러 내용을 피드백해서 리트라이하는 게 가장 효과적이에요. 맹목적인 재시도는 비용만 늘 뿐 성공률은 오르지 않아요.

◆ 설계와 운용 포인트

  • 설계 사상: '자기 수정형 루프'는 LLM 운용의 기본 패턴이에요.
  • 운용 시 주의점: 재시도할 때마다 온도를 바꾸는 궁리도 있지만, 우선은 '에러 정보 전달'이 가장 중요해요.

문제 5: 리트라이 상한 설계 — 정답 (B)

해설

계속 실패한다면 프롬프트·스키마 쪽 문제일 가능성이 높아, 리트라이 횟수를 늘리는 것만으로는 해결되지 않아요. 설계 재검토 + HITL이 정답이죠.

◆ 설계와 운용 포인트

  • 설계 사상: '시스템으로 감당할 수 있는 범위를 넘어선 이상은 사람에게 넘긴다'가 신뢰성 설계의 원칙이에요. 자세한 내용은 세션 11(신뢰성과 HITL)에서 다뤄요.

문제 6: 스트리밍 × 구조화 출력 — 정답 (C)

해설

스트리밍은 UX 개선을 위한 표시 전용으로 두고, 구조화 데이터는 완료 후의 tool_use 블록에서 취득해요. Anthropic 쪽에서 완전한 JSON이 확정된 뒤 돌아오니, 호스트 쪽의 파싱 처리는 필요 없어요.

◆ 설계와 운용 포인트

  • 계산량과 리소스 사용: 스트리밍 중에 매 청크마다 JSON 파싱을 시도하면 CPU 부하가 늘고, 거의 전부 실패하니 낭비예요.

문제 7: enum 값의 번역 문제 — 정답 (D)

해설

enum 값의 description에 '번역 금지·원문 사용'을 명시하는 게 가장 효과적이에요. Claude는 description의 지시를 필드 단위로 존중해요.

"status": {
    "type": "string",
    "enum": ["pending", "approved", "rejected"],
    "description": "상태 값. 이 3개의 영어 소문자 키워드 중 하나를 반드시 사용하고, 번역하지 말 것.",
}

문제 8: 중요 문서의 품질을 최대화하는 접근 — 정답 (A)

해설

중요한 계약서 드래프트처럼 출력 자체의 품질을 최대화하고 싶은 경우에는, 한 번의 생성으로 완벽을 노리는 대신 draft(생성) → review(비평) → refine(수정)의 여러 패스로 나눠, 생성 역할과 비평 역할을 분리해 반복하는 멀티패스 리뷰가 가장 적절해요. 생성할 때는 못 봤던 누락이나 모호한 조항을, 독립된 비평 시점으로 주울 수 있어요.

(B)의 1회 생성은 손쉽지만 빠뜨린 부분을 알아채지 못해요. (C)의 temperature 올리기, (D)의 max_tokens 늘리기는 각각 출력의 다양성·길이를 바꿀 뿐, 품질을 체계적으로 높이는 수단이 아니에요.

◆ 설계와 운용 포인트

  • 설계 사상: '생성'과 '비평'을 별개의 공정·역할로 나누면, 자기 편향을 피하고 품질을 쌓아 올릴 수 있어요. 같은 모델이라도 역할과 프롬프트를 바꾸는 게 핵심이에요.
  • 비용/품질의 트레이드오프: 패스를 늘릴수록 고품질이지만, API 호출 횟수(비용)와 레이턴시도 늘어요. 계약서·중요 리포트처럼 중요한 출력에만 적용해요.
  • Best of N과의 차이: 멀티패스는 '1개 안을 반복적으로 비평·개선', Best of N은 '여러 안을 독립 생성해 최선을 고른다'예요. 둘을 혼동하지 말아요.

문제 9 (고급): 스키마 설계의 개선 — 개선 예시

원래 스키마의 문제점

필드문제
price단위·통화·최솟값이 불명확
statusenum이 지정되지 않음·취할 수 있는 값이 불명확
items요소의 타입이 items로 지정되지 않음
createdAt포맷(ISO 8601 등)이 불명확
공통required가 지정되지 않음
공통description이 전혀 없음

개선판

improved_schema.py

schema = {
    "type": "object",
    "properties": {
        "price": {
            "type": "number",
            "description": "상품의 판매가(세금 포함·원(KRW))",
            "minimum": 0,
        },
        "status": {
            "type": "string",
            "enum": ["pending", "approved", "rejected"],
            "description": "주문 상태. 이 3개의 영어 소문자 키워드 중 하나를 사용, 번역하지 말 것.",
        },
        "items": {
            "type": "array",
            "description": "주문 상품 리스트",
            "items": {
                "type": "object",
                "properties": {
                    "sku": {"type": "string", "description": "상품 코드"},
                    "quantity": {"type": "integer", "minimum": 1},
                },
                "required": ["sku", "quantity"],
            },
            "minItems": 1,
        },
        "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "주문 생성 일시. ISO 8601 형식(예: 2025-05-28T10:30:00+09:00)",
        },
    },
    "required": ["price", "status", "items", "createdAt"],
}

◆ 설계와 운용 포인트

  • 설계 사상: 스키마는 데이터 계약이에요. 모호함을 남기지 않으면 Claude의 출력 품질과 검증이 둘 다 좋아져요.
  • 운용 시 주의점: 프로덕션 운용에서는 JSON Schema를 OpenAPI 문서 등과 일치시키면, 프런트엔드나 다른 시스템과의 정합성도 지키기 쉬워요.

이 세션 돌아보기

  • 구조화 출력은 Tool Use 기반이 가장 신뢰성이 높다
  • JSON 스키마의 description은 Claude에게 주는 지시로 작동한다
  • 검증 + 리트라이 + 에러 전달이 정석
  • 스트리밍은 표시 전용, 구조화 데이터는 완료 후에 취득
  • 온도는 기본 0, enum 값에는 번역 금지 description

다음 세션(제7장)에서는 구조화 출력의 기반인 Tool Use(Function Calling)를, 에이전트 설계 관점에서 자세히 다룹니다.


©2024-2026 ClaudeCode.to, Hand-crafted & made with Jaewoo Kim.

  • 이메일문의: jaewoo@claudecode.to
  • AI 에이전트 만드는 사람
  • 기업 AI 강의 · 에이전트 개발 · 기술자문
  • "AI한테 일 시키는 법, 제대로 알려드립니다"