핵심 요약
- 프롬프트 엔지니어링은 "마법 주문"이 아니라 모델의 동작 원리에 기반한 입력 구조화다. 재현 가능한 패턴으로 접근해야 한다.
- 핵심 패턴: System/User 역할 분리, Few-shot 예시, Chain of Thought, XML/구분자 구조화, 출력 형식 강제.
- 모델별 최적 패턴이 다르다. Claude는 XML 태그, GPT는 Markdown/JSON, Gemini는 명시적 구조에 각각 잘 반응한다.
- Prompt Caching을 활용하면 반복되는 긴 컨텍스트(시스템 프롬프트, 문서)의 비용을 최대 90% 절감할 수 있다.
- 프로덕션에서는 프롬프트를 코드처럼 버전 관리 + 평가(eval) + A/B 테스트해야 한다.
- 본 글은 API 사용 개발자 관점에서 실전 패턴과 안티패턴을 정리한다.
사전 지식: 시리즈 #1, LLM API 호출 경험, 토큰/컨텍스트 윈도우 개념
작성 시점: 2026년 5월 기준
1. 프롬프트 엔지니어링을 보는 관점
프롬프트 엔지니어링은 종종 "매직 워드 찾기"로 오해된다. 실제로는 모델이 학습한 패턴에 입력을 정렬시키는 작업이다.
LLM은 다음 토큰을 예측하는 모델이다. 따라서:
- 명확한 구조 → 모델이 패턴을 인식하기 쉬움
- 모호한 지시 → 모델이 확률적으로 추측 (일관성 하락)
- 예시 제공 → 원하는 출력 분포로 유도
핵심 원칙은 "모델이 추측하게 만들지 말고, 명시하라" 다. 이 원칙에서 모든 패턴이 파생된다.
2. 역할 분리 - System / User / Assistant
2.1 메시지 역할의 의미
대부분의 LLM API는 세 가지 역할을 가진다:
- System: 모델의 행동 지침, 페르소나, 제약 조건 (대화 전체에 적용)
- User: 사용자 입력
- Assistant: 모델 응답 (멀티턴에서 이전 응답 포함)
System 프롬프트는 모델 행동의 헌법 역할을 한다. 일관된 동작이 필요한 모든 지침은 여기에 둔다.
2.2 잘못된 패턴 vs 권장 패턴
안티패턴 - 모든 것을 User 메시지에 욱여넣기:
User: 너는 친절한 고객 지원 봇이야. 항상 존댓말 쓰고 3문장 이내로 답해.
환불 정책은 7일 이내야. 자, 고객이 "환불하고 싶어요"라고 하면 어떻게 답할래?
권장 패턴 - 역할 분리:
System: 당신은 친절한 고객 지원 봇입니다.
- 항상 존댓말을 사용합니다.
- 3문장 이내로 간결하게 답합니다.
- 환불 정책: 구매 후 7일 이내 가능.
User: 환불하고 싶어요.
역할을 분리하면 멀티턴 대화에서 지침이 일관되게 유지되고, User 입력만 교체하면 되므로 재사용성이 높아진다.
2.3 System 프롬프트 설계 원칙
효과적인 System 프롬프트의 구성 요소:
- 역할 정의: "당신은 X 전문가입니다"
- 행동 지침: 해야 할 것 / 하지 말아야 할 것
- 출력 형식: 길이, 구조, 톤
- 제약 조건: 도메인 경계, 금지 사항
- 컨텍스트: 작업에 필요한 배경 정보
3. Few-shot 프롬프팅
3.1 Zero-shot vs Few-shot
Zero-shot은 예시 없이 지시만 주는 것이고, Few-shot은 입출력 예시를 함께 제공하는 것이다.
Zero-shot 예시:
다음 문장의 감정을 positive/negative/neutral로 분류하세요.
문장: 이 제품 정말 최악이에요.
Few-shot 예시:
다음 문장의 감정을 분류하세요.
문장: 배송이 빨라서 좋았어요.
감정: positive
문장: 그냥 평범한 제품입니다.
감정: neutral
문장: 두 번 다시 안 삽니다.
감정: negative
문장: 이 제품 정말 최악이에요.
감정:
Few-shot의 장점:
- 출력 형식을 예시로 고정 (파싱 안정성 향상)
- 엣지 케이스 처리 방향 제시
- 도메인 특화 용어/스타일 학습
3.2 예시 선택 전략
Few-shot 예시는 아무거나 넣으면 안 된다. 다음 원칙을 따른다:
- 대표성: 실제 입력 분포를 반영하는 예시
- 다양성: 각 클래스/케이스를 골고루 커버
- 엣지 케이스 포함: 모델이 자주 틀리는 경계 사례
- 순서 주의: 마지막 예시가 출력에 더 큰 영향 (recency bias)
예시 개수는 보통 3~5개가 적절하다. 너무 많으면 컨텍스트 비용이 증가하고, 특정 패턴에 과적합될 수 있다.
3.3 동적 Few-shot (RAG 결합)
고정 예시 대신, 입력과 유사한 예시를 벡터 검색으로 동적 선택하는 패턴이 프로덕션에서 효과적이다.
1. 예시 풀을 임베딩하여 벡터 DB에 저장
2. 사용자 입력을 임베딩
3. 코사인 유사도로 top-k 예시 검색
4. 검색된 예시를 Few-shot으로 프롬프트에 삽입
이 방식은 입력별로 가장 관련성 높은 예시를 제공하므로 정확도가 향상된다.
4. Chain of Thought (CoT)
4.1 원리
Chain of Thought는 모델이 최종 답 전에 추론 과정을 단계적으로 출력하게 유도하는 기법이다. 복잡한 추론, 수학, 논리 문제에서 정확도를 크게 높인다.
기본 CoT 트리거:
다음 문제를 단계별로 차근차근 풀어주세요.
(문제)
"단계별로 생각하라(think step by step)"는 단순한 문구가 정확도를 유의미하게 올린다는 것이 여러 연구에서 입증되었다.
4.2 구조화된 CoT
프로덕션에서는 추론과 최종 답을 명확히 분리한다:
다음 문제를 풀어주세요.
먼저 <thinking> 태그 안에서 단계별로 추론하고,
그 다음 <answer> 태그 안에 최종 답만 작성하세요.
문제: 한 상점에서 사과를 개당 1,200원에 팝니다.
3개 사면 10% 할인일 때, 5개 가격은?
이렇게 하면 <answer> 태그만 파싱하여 최종 결과를 추출할 수 있고, <thinking> 부분은 디버깅/로깅에 활용한다.
4.3 Reasoning 모델과 CoT
o3, Claude의 extended thinking, Gemini의 thinking 모드 같은 추론 특화 모델은 내부적으로 CoT를 수행한다. 이 경우 명시적 "단계별로 생각하라" 지시가 불필요하거나 오히려 역효과일 수 있다.
| 일반 모델 (GPT-4o, Claude Sonnet) | 명시하면 효과적 |
| 추론 모델 (o3, extended thinking) | 불필요, 모델이 자동 수행 |
추론 모델에는 무엇을 추론할지(목표)만 주고, 어떻게 추론할지(과정)는 모델에 맡기는 것이 권장된다.
5. 구조화 - XML, 구분자, 출력 형식
5.1 입력 구조화
긴 프롬프트에서 지시/컨텍스트/데이터를 구분하지 않으면 모델이 혼동한다. 구분자로 명확히 분리한다.
XML 태그 방식:
<instructions>
아래 문서를 3문장으로 요약하세요.
</instructions>
<document>
(긴 문서 내용)
</document>
<constraints>
- 전문 용어는 그대로 유지
- 숫자는 정확히 인용
</constraints>
구분자가 명확하면 "문서 안의 지시처럼 보이는 문장"을 실제 지시로 오인하는 프롬프트 인젝션 위험도 줄어든다.
5.2 출력 형식 강제
파싱 가능한 출력이 필요하면 형식을 명시적으로 지정한다.
JSON 출력 요청:
다음 정보를 추출하여 JSON으로만 응답하세요.
설명이나 마크다운 코드 펜스 없이 순수 JSON만 출력하세요.
출력 형식:
{
"name": "이름",
"age": 숫자,
"email": "이메일"
}
입력 텍스트: 김철수입니다. 30살이고 chulsoo@example.com으로 연락주세요.
5.3 Structured Output / Function Calling
프롬프트로 JSON을 요청하는 것보다, API의 structured output 기능을 쓰는 것이 안정적이다.
- OpenAI: response_format={"type": "json_schema", ...}
- Anthropic: tool use로 스키마 강제
- Gemini: response_schema 파라미터
이 기능들은 스키마 준수를 API 레벨에서 보장하므로, 프롬프트만으로 JSON을 요청할 때 발생하는 형식 오류를 제거한다.
6. 모델별 최적 패턴 차이
각 모델은 학습 데이터와 RLHF 방식이 다르므로 선호하는 입력 형식이 다르다.
6.1 Claude - XML 태그 선호
Anthropic은 학습 과정에서 XML 태그를 광범위하게 사용했다. 따라서 Claude는 XML 구조에 특히 잘 반응한다.
<role>시니어 백엔드 개발자</role>
<task>
아래 코드를 리뷰하고 개선점을 제시하세요.
</task>
<code>
def get_user(id):
return db.query(f"SELECT * FROM users WHERE id = {id}")
</code>
<output_format>
1. 보안 이슈
2. 성능 이슈
3. 개선 코드
</output_format>
6.2 GPT - Markdown / 명확한 섹션
GPT 계열은 Markdown 헤더와 번호 목록 구조에 잘 반응한다.
# 역할
시니어 백엔드 개발자
# 작업
아래 코드를 리뷰하고 개선점을 제시하세요.
# 코드
(코드)
# 출력 형식
1. 보안 이슈
2. 성능 이슈
3. 개선 코드
6.3 Gemini - 명시적 + 긴 컨텍스트 활용
Gemini는 매우 긴 컨텍스트를 처리할 수 있으므로, 관련 자료를 충분히 제공하고 명시적으로 지시하는 패턴이 효과적이다. 단, 긴 컨텍스트에서 핵심 지시는 맨 앞 또는 맨 뒤에 배치한다.
6.4 공통 원칙
모델별 차이는 있지만, 다음은 모든 모델에 공통으로 효과적이다:
- 명확한 역할/작업/형식 분리
- 구체적인 출력 형식 명시
- 모호함 제거
- 예시 제공
모델 이식성을 위해서는 XML과 Markdown을 혼용하기보다 한 가지로 일관되게 쓰는 것이 좋다.
7. Prompt Caching - 비용 최적화
7.1 원리
시스템 프롬프트, Few-shot 예시, 참조 문서처럼 반복 호출에서 변하지 않는 부분을 캐싱하면 비용과 지연을 크게 줄일 수 있다.
지원 현황 (2026년 5월 기준):
| Anthropic | 명시적 cache_control | 캐시 읽기 90% 할인 |
| OpenAI | 자동 (1024토큰 이상) | 캐시 읽기 50% 할인 |
| 명시적 context caching | 캐시 저장분 할인 |
7.2 Anthropic 캐싱 예시
긴 시스템 프롬프트와 문서를 캐싱하는 구조:
messages = [
{
"role": "system",
"content": [
{
"type": "text",
"text": "(긴 시스템 프롬프트 + 참조 문서)",
"cache_control": {"type": "ephemeral"}
}
]
},
{
"role": "user",
"content": "(매번 바뀌는 질문)"
}
]
캐시는 기본 5분간 유지되며, 그 안에 동일 prefix로 재요청하면 캐시된 부분은 90% 할인된 가격으로 처리된다.
7.3 캐싱 설계 전략
캐싱 효과를 극대화하려면 프롬프트를 고정부 + 가변부 순서로 구성한다:
[고정] 시스템 프롬프트
[고정] Few-shot 예시
[고정] 참조 문서
───────────────── ← 캐시 경계
[가변] 사용자 질문
가변부가 앞에 오면 캐시 적중률이 떨어진다. 변하지 않는 부분을 항상 앞에 배치한다.
8. 프로덕션 프롬프트 관리
8.1 프롬프트는 코드다
프로덕션 프롬프트는 코드처럼 관리한다:
- 버전 관리: Git에 프롬프트 텍스트 저장, 변경 이력 추적
- 외부화: 코드에 하드코딩하지 않고 별도 파일/DB로 분리
- 템플릿화: 변수 치환 가능한 템플릿 엔진 사용
- 환경 분리: dev/staging/prod 프롬프트 버전 분리
8.2 평가(Eval) 체계
프롬프트 변경의 효과를 측정하려면 평가 데이터셋이 필요하다.
1. 대표 입력 + 기대 출력 쌍을 데이터셋으로 구축 (50~200개)
2. 프롬프트 변경 시 전체 데이터셋에 대해 실행
3. 자동 채점 (정확도, 형식 준수율, LLM-as-judge 등)
4. 변경 전후 점수 비교
평가 없이 "느낌상 더 좋아진 것 같다"로 프롬프트를 변경하면 회귀(regression)를 놓친다.
8.3 A/B 테스트
중요한 프롬프트는 프로덕션에서 A/B 테스트한다. 트래픽을 분할하여 두 버전의 실제 지표(사용자 만족도, 작업 완료율, 비용)를 비교한다.
8.4 모델 버전 핀
프롬프트는 특정 모델 버전에 최적화된다. 모델이 업데이트되면 동일 프롬프트의 동작이 변할 수 있으므로, 프로덕션은 풀 모델 버전을 명시적으로 고정한다(예: claude-sonnet-4-6 같은 정확한 버전 문자열).
9. 흔한 안티패턴
안티패턴 1: 모순되는 지시
"간결하게 쓰되 모든 세부사항을 포함하라" 같은 충돌 지시는 모델을 혼란시킨다. 우선순위를 명확히 하거나 한쪽을 선택한다.
안티패턴 2: 부정 지시 남발
"X하지 마라"보다 "Y하라"가 효과적이다. 모델은 긍정 지시를 더 잘 따른다. "전문용어 쓰지 마"보다 "중학생도 이해할 수준으로 써"가 낫다.
안티패턴 3: 과도한 예의 표현
"부탁드립니다", "감사합니다" 같은 표현은 출력 품질에 영향을 주지 않으면서 토큰만 소비한다. 프로덕션 프롬프트에서는 제거한다.
안티패턴 4: 한 프롬프트에 너무 많은 작업
요약 + 번역 + 분류 + 포맷팅을 한 번에 요청하면 각 작업의 품질이 떨어진다. 파이프라인으로 분리하는 것이 안정적이다.
안티패턴 5: 매직 워드 의존
"당신은 천재입니다", "이건 매우 중요합니다" 같은 표현의 효과는 제한적이고 모델 버전에 따라 달라진다. 구조와 명확성에 투자하는 것이 재현 가능하다.
안티패턴 6: 출력 형식 미지정
형식을 지정하지 않으면 매번 다른 구조로 출력되어 파싱이 불가능하다. 프로그래밍적으로 처리할 출력은 반드시 형식을 강제한다.
10. 결론 및 다음 글
핵심 정리
- 프롬프트 엔지니어링은 매직 워드가 아니라 모델 동작 원리에 기반한 입력 구조화다.
- 핵심 패턴: 역할 분리, Few-shot, CoT, 구조화(XML/구분자), 출력 형식 강제.
- 모델별 최적 패턴이 다르다. Claude는 XML, GPT는 Markdown, Gemini는 명시적 구조에 잘 반응한다.
- Prompt Caching으로 반복 컨텍스트 비용을 최대 90% 절감할 수 있다. 고정부를 앞에 배치하는 것이 핵심.
- 프로덕션 프롬프트는 코드처럼 버전 관리 + 평가 + A/B 테스트 + 모델 버전 핀이 필요하다.
다음 글(#3 예정): Cursor + Claude Code 워크플로 — Agentic coding의 실제, MCP(Model Context Protocol) 활용, 코드베이스 컨텍스트 관리, 실전 페어 프로그래밍 패턴.
참고 자료
- Anthropic Prompt Engineering 가이드
- OpenAI Prompt Engineering 가이드
- Google Gemini Prompting 가이드
- Anthropic Prompt Caching 문서
- Chain-of-Thought Prompting 논문 (Wei et al.)
- OpenAI Structured Outputs
카테고리: AI / LLM
태그: llm prompt-engineering chain-of-thought few-shot claude gpt gemini prompt-caching ai-tools developer-tools
'개발 프로젝트 > AI 실습 일지' 카테고리의 다른 글
| LLM 도구 선택 가이드 #1 — GPT/Claude/Gemini/Cursor 실무 비교 (0) | 2026.06.08 |
|---|