Claude 시스템 프롬프트 설계 패턴 7가지: 역할 지정부터 출력 포맷 제어까지

목차

Anthropic의 자체 조사에 따르면 Claude API 사용자 중 시스템 프롬프트를 커스텀으로 작성하는 비율이 약 78%인데, 그중 체계적인 Claude 시스템 프롬프트 설계 패턴을 적용하는 경우는 절반도 안 된다고 한다. 나도 처음엔 "너는 친절한 AI야" 한 줄 넣고 끝냈다. 결과가 들쭉날쭉해서 미칠 뻔했다.

데이터 파이프라인 쪽 작업을 하다 보면 Claude API로 비정형 데이터를 정제하거나, 로그를 분류하거나, 슬랙 알림 메시지를 자동 생성하는 일이 꽤 있다. 그때마다 Claude 시스템 프롬프트 설계 패턴을 제대로 잡느냐에 따라 후처리 코드 양이 확 줄어든다. 이 글은 동료들한테 "시스템 프롬프트 어떻게 짜?"라고 질문받을 때마다 반복 설명하던 내용을 정리한 거다.

시스템 프롬프트가 왜 이렇게 중요한가

"그냥 user 메시지에 다 넣으면 안 돼?"라는 질문을 제일 많이 받는다. 답부터 말하면, 안 된다. 정확히는 되긴 하는데 결과가 다르다.

시스템 프롬프트는 Claude의 응답 전체를 관통하는 기본 행동 규칙이다. user 메시지에 넣은 지시는 대화가 길어지면 묻히지만, system 필드에 넣은 내용은 매 턴마다 참조된다. 특히 멀티턴 대화에서 차이가 크다.

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    system="당신은 서버 로그를 분석하는 SRE 엔지니어입니다. 모든 응답은 JSON 형식으로만 출력하세요.",
    messages=[
        {"role": "user", "content": "nginx 502 에러가 반복됩니다. 원인 분석해주세요."}
    ]
)

이렇게 system 필드에 역할과 출력 포맷을 박아두면, user 메시지가 아무리 바뀌어도 JSON으로 응답이 나온다. user 메시지에 같은 내용을 넣으면? 3~4턴 지나면 슬슬 무시하기 시작한다.

system 필드 vs user 메시지
Claude API에서 system 필드는 모든 턴에 걸쳐 유지되는 전역 지시다. user 메시지 안의 지시는 해당 턴에서만 강하게 작동하고, 대화가 길어질수록 영향력이 약해진다.
## 패턴 1: 역할 지정 — 페르소나를 구체적으로

가장 기본이면서 효과가 큰 패턴이다. "친절한 AI"가 아니라 구체적인 직무와 경력 수준을 명시하는 게 핵심.

나쁜 예 vs 좋은 예

# 나쁜 예
당신은 도움이 되는 AI 어시스턴트입니다.

# 좋은 예
당신은 경력 7년차 데이터 엔지니어입니다. 
Apache Airflow, dbt, BigQuery를 주로 사용합니다.
비정형 로그 데이터를 정제하여 분석 가능한 형태로 변환하는 것이 주 업무입니다.
기술적 정확성을 최우선으로 하며, 모르는 것은 모른다고 답합니다.

차이가 확연하다. 역할을 구체적으로 잡으면 Claude가 해당 도메인의 용어와 관점을 일관되게 유지한다. 데이터 엔지니어로 설정하면 "테이블" 대신 "스키마", "저장" 대신 "적재"라는 표현을 자연스럽게 쓴다.

직무별 설정 팁

사실 역할 지정에서 제일 중요한 건 전문 분야와 사용 도구를 명시하는 거다. "백엔드 개발자"만 쓰면 범위가 너무 넓다. "FastAPI와 PostgreSQL을 쓰는 백엔드 개발자"라고 쓰면 응답이 해당 스택에 맞춰진다.

개인적으로 이게 제일 깔끔한 방식이다. 역할 + 도구 + 행동 원칙. 이 세 가지만 잡아도 응답 품질이 눈에 띄게 달라진다.

패턴 2: 출력 포맷 제어 — JSON, 마크다운, 테이블

데이터 파이프라인에서 Claude를 쓸 때 가장 골치 아픈 게 출력 파싱이다. 자유 형식 텍스트를 후처리하는 코드를 짜다 보면 엣지 케이스가 끝도 없이 나온다. 그래서 시스템 프롬프트에서 출력 포맷을 강제하는 게 시간을 엄청 아껴준다.

모든 응답은 아래 JSON 스키마를 따릅니다. JSON 외의 텍스트는 절대 출력하지 마세요.

{
  "analysis": {
    "severity": "low | medium | high | critical",
    "category": "string",
    "summary": "string (100자 이내)",
    "recommended_action": "string"
  }
}
JSON 출력 안정화 꿀팁
“JSON 외의 텍스트는 절대 출력하지 마세요”를 반드시 추가해라. 이게 없으면 Claude가 “네, 분석 결과는 다음과 같습니다:” 같은 서두를 붙인다. 파서가 바로 깨진다.
근데 JSON만 되는 건 아니다. 마크다운 테이블, CSV, YAML 등 원하는 포맷을 명시하면 대부분 잘 따른다. 다만 복잡한 네스티드 JSON은 가끔 스키마를 무시하는 경우가 있어서, 그럴 때는 few-shot 예제를 같이 넣는 게 낫다.

![시스템 프롬프트 구조 다이어그램]({{image:prompt-structure|Isometric 3D illustration of layered system architecture with role layer, format layer, and constraint layer stacked vertically, modern flat design, blue and teal gradient, dark background}})

Claude 시스템 프롬프트 설계 패턴: 제약 조건 설정

패턴 3번인데, 개인적으로 이게 제일 과소평가된 패턴이다. 역할 지정이나 포맷 제어는 다들 하는데, 하지 말아야 할 것을 명시하는 건 의외로 빠뜨린다.

## 제약 조건
- 학습 데이터에 없는 내용을 추측하지 마세요. 모르면 "확인이 필요합니다"라고 답하세요.
- 코드를 생성할 때 deprecated된 API를 사용하지 마세요.
- 응답 길이는 500토큰을 넘지 마세요.
- 한국어로만 답변하세요. 코드 주석도 한국어로.
- 외부 URL을 추천하지 마세요.

제약 조건이 없으면 Claude는 기본적으로 "최대한 도움이 되려는" 방향으로 움직인다. 할루시네이션이 생기는 것도 이 때문이다. 모른다고 하는 것보다 뭐라도 답하는 게 "도움이 된다"고 판단하니까.

데이터 파이프라인에서 이게 치명적이다. ETL 스크립트를 생성하는데 존재하지 않는 함수명을 만들어내면? 파이프라인이 터진다. 그래서 나는 제약 조건 섹션을 항상 넣는다. 바로 이거다.

패턴 4~5: 컨텍스트 주입과 Few-shot 예제

컨텍스트 주입 패턴

"이 회사의 데이터 스키마는 이렇고, 네이밍 컨벤션은 이렇다"를 시스템 프롬프트에 넣는 패턴이다. RAG로 검색한 문서를 system에 넣는 것도 같은 맥락.

## 회사 데이터베이스 컨텍스트
- 사용 DB: PostgreSQL 16
- 스키마: public.users, public.orders, public.products
- 네이밍: snake_case, 테이블명은 복수형
- 시간대: 모든 timestamp는 UTC 기준
- 민감 컬럼: users.email, users.phone은 마스킹 필수

이렇게 컨텍스트를 박아두면 "주문 테이블 쿼리 짜줘"라고만 해도 public.orders를 기준으로 snake_case 쿼리가 나온다. 매번 스키마를 설명할 필요가 없다.

Few-shot 예제 패턴

솔직히 이건 좀 귀찮다. 하지만 효과는 확실하다. 입출력 예제를 2~3개 넣으면 Claude가 패턴을 파악해서 일관된 출력을 내놓는다.

## 입출력 예제

입력: "2024-01-15 ERROR db_connection timeout after 30s"
출력: {"timestamp": "2024-01-15", "level": "ERROR", "component": "db_connection", "message": "timeout after 30s"}

입력: "2024-01-15 WARN api_gateway rate limit exceeded for user_123"
출력: {"timestamp": "2024-01-15", "level": "WARN", "component": "api_gateway", "message": "rate limit exceeded for user_123"}

few-shot 예제를 넣을 때 주의할 게 있다. 예제가 너무 많으면 토큰을 잡아먹어서 비용이 올라간다. 2~3개면 충분하다. 그리고 엣지 케이스를 포함한 예제가 평범한 예제 5개보다 낫다. 로그에 개행이 포함된 경우, 필드가 빠진 경우 같은 걸 예제로 보여주면 Claude가 예외 처리를 알아서 한다.

토큰 비용 주의
시스템 프롬프트는 매 API 호출마다 전송된다. few-shot 예제를 10개 넣으면 모든 요청에 그 토큰이 붙는다. 월간 API 호출이 10만 건이면 비용 차이가 꽤 크다.
## 프롬프트 설계 패턴별 효과 비교

패턴을 조합할 때 뭘 먼저 넣어야 하는지 질문을 많이 받는다. 아래 표는 내가 실무에서 체감한 효과를 정리한 거다. 정밀한 벤치마크는 아니고 체감 기준이니 참고만.

패턴 적용 난이도 응답 일관성 개선 체감 토큰 비용 증가 추천 우선순위
역할 지정 낮음 중간 거의 없음 1순위
출력 포맷 제어 낮음 높음 거의 없음 1순위
제약 조건 낮음 높음 거의 없음 1순위
컨텍스트 주입 중간 높음 중간 2순위
Few-shot 예제 중간 매우 높음 높음 상황별
단계별 사고 유도 낮음 중간 중간 3순위
멀티 페르소나 높음 상황별 높음 고급

역할 지정, 포맷 제어, 제약 조건. 이 세 개가 비용 대비 효과가 가장 좋다. 토큰도 거의 안 먹고 응답이 확 안정된다.

패턴 6: 단계별 사고 유도 (Chain of Thought)

Claude한테 "생각 과정을 보여줘"라고 하면 복잡한 작업의 정확도가 올라간다. 근데 이걸 시스템 프롬프트에서 구조화하는 방법이 있다.

## 응답 프로세스
모든 질문에 대해 아래 단계를 따르세요:

1단계 - 문제 파악: 사용자의 요청을 한 문장으로 요약
2단계 - 필요 정보 확인: 답변에 필요한 정보가 충분한지 판단. 부족하면 질문
3단계 - 해결 방안 도출: 가능한 접근법을 2~3개 나열
4단계 - 최적안 선택: 이유와 함께 하나를 추천
5단계 - 실행 코드: 선택한 방안의 구현 코드 제공

이 패턴을 쓰면 Claude가 바로 코드를 뱉는 대신, 문제를 분석하고 대안을 비교한 뒤에 코드를 준다. 데이터 파이프라인 설계처럼 정답이 하나가 아닌 작업에서 특히 유용하다.

다만 단점도 있다. 응답이 길어진다. 간단한 질문에도 5단계를 다 밟으니까 토큰이 낭비될 때가 있다. 그래서 나는 "질문이 단순하면 3단계부터 시작해도 됩니다"라는 조건을 추가한다.

Extended Thinking과의 차이
Claude 3.5 이후 모델에서 지원하는 Extended Thinking은 모델 내부에서 자동으로 사고 과정을 확장하는 기능이다. 시스템 프롬프트의 CoT 유도와는 다른 메커니즘이고, 둘을 같이 쓰면 응답이 과도하게 길어질 수 있으니 주의.
![프롬프트 패턴 조합 흐름]({{image:prompt-pattern-flow|Flowchart diagram showing decision tree for selecting prompt patterns: role setting, output format, constraints, context injection, few-shot examples, chain of thought, multi-persona, modern minimal style, white lines on dark navy background}})

Claude 시스템 프롬프트 설계 패턴의 조합: 실전 템플릿

패턴을 하나씩 쓰는 건 누구나 한다. 진짜 차이는 조합에서 나온다. 내가 데이터 파이프라인 프로젝트에서 실제로 쓰는 시스템 프롬프트 템플릿을 공개한다.

## 역할
당신은 경력 5년차 데이터 엔지니어입니다.
Apache Airflow 2.8, dbt 1.7, BigQuery를 주로 사용합니다.
Python 3.12 환경에서 작업합니다.

## 행동 원칙
- 정확성을 최우선으로 합니다. 확실하지 않으면 "확인 필요"라고 명시합니다.
- deprecated된 API나 라이브러리를 사용하지 않습니다.
- 보안 민감 정보(API 키, 비밀번호)를 예제에 포함하지 않습니다.

## 출력 포맷
- 코드: Python 코드블록으로 출력. 타입 힌트 필수.
- 설명: 코드 아래에 간결하게. 3문장 이내.
- SQL: BigQuery Standard SQL 문법만 사용.

## 데이터베이스 컨텍스트
- 프로젝트: my-project.analytics
- 주요 테이블: events, users, sessions
- 파티션: 모든 테이블은 event_date로 파티션됨
- 네이밍: snake_case, 테이블명 복수형

## 예제
사용자: "일별 활성 사용자 수 쿼리 짜줘"
응답:
SELECT
  event_date,
  COUNT(DISTINCT user_id) AS daily_active_users
FROM `my-project.analytics.events`
WHERE event_date BETWEEN '2024-01-01' AND '2024-01-31'
GROUP BY event_date
ORDER BY event_date

이 템플릿의 포인트는 섹션을 마크다운 헤딩으로 구분한다는 거다. Claude는 마크다운 구조를 잘 이해해서, ##으로 나누면 각 섹션의 역할을 명확하게 구분한다. 그냥 줄바꿈으로 나누는 것보다 안정적이다.

섹션 순서가 중요한가

"역할을 먼저 써야 하나, 포맷을 먼저 써야 하나?" 이것도 자주 받는 질문이다. Anthropic 공식 문서에서는 역할 → 지시 → 제약 순서를 권장한다. 근데 실무에서 테스트해보면, 솔직히 순서보다 각 섹션이 명확하게 구분되어 있는지가 더 중요하다. ## 헤딩만 잘 써도 순서 영향은 미미하다.

프롬프트 길이 관리

시스템 프롬프트가 길어지면 두 가지 문제가 생긴다. 토큰 비용이 올라가는 거랑, 지시 간 충돌이 생기는 거. 경험상 시스템 프롬프트는 500~1500토큰 사이가 적당하다. 2000토큰을 넘으면 Claude가 일부 지시를 무시하는 빈도가 높아진다.

# 시스템 프롬프트 토큰 수 확인하는 간단한 방법
import anthropic

client = anthropic.Anthropic()

system_prompt = """여기에 시스템 프롬프트 내용"""

# token counting API 활용
count = client.count_tokens(
    model="claude-sonnet-4-20250514",
    system=system_prompt,
    messages=[{"role": "user", "content": "test"}]
)
print(f"시스템 프롬프트 포함 총 토큰: {count.input_tokens}")

패턴 7: 멀티 페르소나와 조건부 응답

마지막 패턴인데, 고급 사용법이다. 하나의 시스템 프롬프트 안에 상황에 따라 다른 페르소나로 응답하도록 설정하는 거다.

## 응답 모드

사용자 메시지에 [CODE] 태그가 포함되면:
- 코드 리뷰어 모드로 전환
- 버그, 보안 취약점, 성능 이슈를 지적
- 개선된 코드를 함께 제시

사용자 메시지에 [EXPLAIN] 태그가 포함되면:
- 기술 멘토 모드로 전환
- 개념을 비유와 함께 쉽게 설명
- 추가 학습 자료를 추천

사용자 메시지에 [DEBUG] 태그가 포함되면:
- 디버깅 전문가 모드로 전환
- 에러 메시지를 분석하고 원인을 좁혀나감
- 단계별 디버깅 절차를 제시

태그가 없으면 기본 데이터 엔지니어 모드로 응답

이 패턴은 하나의 Claude 인스턴스로 여러 용도를 커버할 때 유용하다. 슬랙 봇 같은 데 붙이면 사용자가 태그 하나로 모드를 전환하니까 편하다.

실무에서 자주 쓰는 조합
데이터 팀에서는 [SQL] 모드와 [PIPELINE] 모드를 나눠서 쓰면 좋다. SQL 모드에서는 쿼리 최적화에 집중하고, PIPELINE 모드에서는 Airflow DAG 설계에 집중하도록 분리하면 응답 품질이 올라간다.
아 그리고 이건 주의해야 하는데, 모드가 3개를 넘으면 Claude가 모드 간 규칙을 혼동하는 경우가 생긴다. 4개 이상이면 차라리 시스템 프롬프트를 분리해서 별도 API 호출로 나누는 게 낫다.

![멀티 페르소나 모드 전환]({{image:multi-persona-mode|Abstract illustration showing three connected hexagons representing different AI personas: code reviewer, mentor, debugger, with switching arrows between them, neon blue accents on dark background, modern tech style}})

Claude 시스템 프롬프트 설계 패턴 실전 트러블슈팅

패턴을 적용하다 보면 예상대로 안 되는 경우가 꽤 있다. 내가 겪은 문제들과 해결법을 정리한다.

포맷 무시 문제

JSON으로 출력하라고 했는데 앞에 "네, 분석 결과입니다:" 같은 텍스트가 붙는 경우. 이건 시스템 프롬프트에 "JSON 외의 텍스트를 절대 출력하지 마세요"를 추가하면 거의 해결된다. 그래도 가끔 뚫리면 response.content[0].text.strip()으로 앞뒤 공백 제거하고, json.loads()에서 에러 나면 정규식으로 JSON 부분만 추출하는 폴백을 넣어둔다.

import json
import re

def parse_claude_json(text: str) -> dict:
    """Claude 응답에서 JSON을 안전하게 추출"""
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        # JSON 부분만 추출 시도
        match = re.search(r'\{[\s\S]*\}', text)
        if match:
            return json.loads(match.group())
        raise ValueError(f"JSON 파싱 실패: {text[:100]}")

제약 조건 충돌

"간결하게 답하세요"와 "모든 단계를 설명하세요"를 동시에 넣으면? Claude가 혼란스러워한다. 당연히. 제약 조건끼리 충돌하는지 확인하는 게 중요하다. Anthropic의 프롬프트 엔지니어링 가이드에서도 이 점을 강조한다.

멀티턴에서 지시 약화

대화가 10턴 이상 이어지면 시스템 프롬프트의 효력이 약해지는 느낌을 받을 때가 있다. 이건 Claude의 구조적 한계라기보다, 대화 컨텍스트가 길어지면서 시스템 프롬프트의 상대적 비중이 줄어드는 거다. 해결법은 간단하다. 중요한 지시는 시스템 프롬프트 맨 위에, 그리고 user 메시지에서도 주기적으로 리마인드.

다음 단계: 시스템 프롬프트 너머

Claude 시스템 프롬프트 설계 패턴을 어느 정도 잡았으면 다음은 Tool Use다. Claude에 외부 API를 직접 연결하는 MCP(Model Context Protocol) 서버를 만들면 프롬프트만으로는 못 하는 것들이 가능해진다. 데이터 파이프라인에서는 Airflow API를 MCP로 연결해서 DAG 상태 조회나 트리거를 Claude가 직접 하게 만들 수 있다.

프롬프트 버전 관리도 한번 고민해볼 만하다. Git으로 시스템 프롬프트를 관리하면 누가 언제 뭘 바꿨는지 추적이 된다. 시스템 프롬프트 변경 → A/B 테스트 → 성능 비교 파이프라인을 자동화하면 프롬프트 최적화가 데이터 문제가 된다. 데이터 엔지니어 입장에서는 오히려 익숙한 영역이다.

Claude Agent SDK를 써서 멀티 에이전트 워크플로우를 구성하는 것도 다음 주제로 괜찮다. 시스템 프롬프트가 단일 에이전트의 행동을 정의한다면, Agent SDK는 여러 에이전트의 협업 구조를 정의하는 거니까 자연스러운 확장이다.

관련 글