LabHub

블로그

프롬프트 엔지니어링의 과학: CoT, Self-consistency, DSPy, 구조화 출력, 자동 최적화까지 (2025)

Season 4 Ep 2 — Ep 1(RAG)이 "무엇을 보여줄까"였다면, Ep 2는 "어떻게 지시할까"다. 검색과 지시, 이 두 축이 날카로워야 제품이 날아오른다.

Prologue — "프롬프트는 예술이 아니다"

2023년의 밈은 "프롬프트 엔지니어 연봉 3억"이었다. 2025년엔 그 직무 공고가 거의 사라졌다. 이유는 간단하다.

하지만 동시에 LLM을 쓰는 제품 수는 수십 배로 늘었다. 프롬프트는 직무가 아니라 모든 소프트웨어 엔지니어의 기본 스킬이 됐고, 버저닝·테스트·CI·롤백·평가는 필수가 됐다.

이 글은 "잘 쓰는 팁"보다 체계적인 과학으로서의 프롬프트 엔지니어링이 어디까지 왔는지 정리한다.


1장 · 프롬프트의 3층 구조

1.1 System · User · Assistant

OpenAI·Anthropic·Google 모두 공통된 메시지 타입:

system:    "너는 법률 전문가다. 답변은 반드시 한국 법령 기준으로..."
user:      "근로기준법 제60조의 연차휴가 계산 방법은?"
assistant: (모델의 답)

1.2 Anthropic의 XML 선호, OpenAI의 Markdown

Anthropic Claude는 XML 태그를 특히 잘 해석한다.

<role>...</role>
<instructions>...</instructions>
<context>...</context>
<example>
  <input>...</input>
  <output>...</output>
</example>

OpenAI GPT는 Markdown 섹션(## Role, ## Instructions)도 잘 먹힌다. 모델별 경험적 선호가 있다는 걸 기억하는 것만으로 프롬프트 이관에서 10–20% 차이가 난다.

1.3 Template의 힘

프롬프트는 템플릿으로 관리해야 한다. 인라인 문자열은 3개월 뒤에 아무도 이해 못 한다.

# prompts/legal_answer.j2
You are a legal assistant specialized in Korean labor law.

<rules>
- Always cite the article number.
- If uncertain, say "추가 확인이 필요합니다".
</rules>

<context>
{{ retrieved_docs }}
</context>

<question>
{{ user_query }}
</question>

Jinja2, Handlebars, 혹은 Python string.Template 중 하나로 프롬프트를 코드 밖으로 빼라.


2장 · Chain-of-Thought (CoT)

2.1 원리

"생각 과정을 출력하게 하면 정답률이 오른다." 2022년 Wei et al. 논문에서 본격화됐다.

Q: 한 가게에 사과 23개가 있다. 10개 팔고 6개 더 사왔다. 남은?
A: 단계별로 생각해보자.
   시작: 23   판매 후: 23 - 10 = 13
   구매 후: 13 + 6 = 19
   따라서 19.

단순한 "Let's think step by step"(Zero-shot CoT)만으로도 수학·추론 벤치가 크게 오른다.

2.2 2025년의 CoT

최신 모델(GPT-4o, Claude 3.5/4, Gemini 2 등)은 내부적으로 추론을 하거나 thinking 토큰을 별도로 둔다. OpenAI o1/o3 계열, Claude "extended thinking"은 아예 추론 전용 모드를 제공한다.

영향:

즉 2025년에 CoT를 다룰 때는 "얼마나 모델이 알아서 하는가"를 먼저 측정하고, 필요한 만큼만 명시한다.

2.3 구조화 CoT 템플릿

복잡한 업무 프로세스는 자유 CoT보다 정형 CoT가 안전하다.

<thinking>
1. 입력 해석:
2. 관련 규정 식별:
3. 적용:
4. 예외 확인:
5. 결론:
</thinking>

<answer>
...
</answer>

이렇게 하면 출력에서 <answer> 부분만 잘라 사용자에게 보이고, <thinking>은 로그로 남긴다.


3장 · Self-consistency, Tree-of-Thoughts, Reflection

3.1 Self-consistency

CoT를 여러 번 샘플링해서 다수결을 취한다(Wang et al., 2022).

answers = [call_llm(prompt, temperature=0.7) for _ in range(5)]
final = majority_vote(answers)

3.2 Tree-of-Thoughts (ToT)

선형 추론이 아니라 트리 탐색. 각 단계에서 여러 분기를 생성하고, 각 분기를 평가해서 가장 유망한 것을 확장.

3.3 Reflection / Self-criticism

모델이 자기 답을 다시 검토하게 한다.

1) 초안 작성
2) "이 답에 어떤 오류/놓친 점이 있는가?"로 자기 비판
3) 비판을 반영해 재작성

작은 태스크에선 비용 대비 이득이 크지만, 큰 태스크에선 루프 무한증식 위험. 최대 1–2회 반복으로 제한.

3.4 선택 가이드

문제 유형추천 기법
수학·논리 퍼즐CoT + Self-consistency
창의적 탐색Tree-of-Thoughts
요약·추출Reflection 1회
단순 분류아무 것도 안 씀 (그냥 프롬프트)
코드 작성CoT + Self-critic(테스트 기반)

4장 · Few-shot의 과학

4.1 왜 Few-shot인가

"예시 2–5개"를 넣으면 출력 포맷·톤·기준을 빠르게 교정할 수 있다. 2024년 이후 모델은 Zero-shot 성능이 매우 높아서 Few-shot 의존도는 줄었지만, 엣지 케이스에 대한 안전장치로는 여전히 필수.

4.2 예시 선택의 함정

Dynamic Few-shot: 실제 질의와 유사도가 높은 예시를 벡터 검색으로 뽑아서 동적으로 프롬프트에 삽입. 이렇게 하면 정적 3개보다 훨씬 안정적이다.

4.3 예시 순서

4.4 Meta-프롬프트

"어떤 스타일의 예시를 쓸지"를 먼저 메타적으로 지시하는 기법.

아래는 당신이 따라야 할 톤/포맷의 대표적 예시들이다.
- 격식 있는 존댓말
- 답변 첫 줄은 요약, 그 다음 근거
- 마지막 줄은 행동 제안
(예시 3개 …)

5장 · Structured Output — JSON mode, Tool use, Schema

5.1 왜 구조화 출력인가

프로덕션 LLM 호출의 대부분은 다른 시스템이 소비한다. 자유 텍스트는 파싱 지옥.

5.2 접근 3가지

(a) JSON mode / Structured Outputs

JSON Schema로 완전히 강제할 수 있고, 제약을 어기면 모델이 실패로 반환하거나 재시도.

(b) Tool use / Function calling

(c) Pydantic/Zod Parser 기반 재시도

2025년 기준 (a) + (b)가 주류. (c)는 모델이 네이티브 구조화를 지원 안 하는 경우에만.

5.3 Schema 설계 원칙

{
  "type": "object",
  "required": ["intent", "entities"],
  "properties": {
    "intent": {
      "type": "string",
      "enum": ["question", "complaint", "request", "other"],
      "description": "사용자 발화의 의도 분류"
    },
    "entities": {
      "type": "array",
      "items": { "type": "string" },
      "description": "언급된 제품명, 사람명 등"
    },
    "confidence": {
      "type": "number",
      "minimum": 0,
      "maximum": 1
    }
  }
}

5.4 주의점


6장 · 자동 최적화 — DSPy, TextGrad, APO

6.1 DSPy

스탠포드에서 나온 "프롬프트를 코드처럼 컴파일하는" 프레임워크.

import dspy

class Summarize(dspy.Signature):
    """주어진 문서를 3문장으로 요약한다."""
    document = dspy.InputField()
    summary = dspy.OutputField(desc="정확히 3문장")

summarize = dspy.ChainOfThought(Summarize)

6.2 TextGrad / DSPy의 진화

"프롬프트를 gradient처럼 업데이트"한다는 아이디어. 자연어 피드백을 "기울기"처럼 써서 반복적으로 프롬프트를 개선.

실무적으로는 DSPy로 시작해서, 매우 고도화된 경우에만 TextGrad 계열로 넘어간다.

6.3 APO (Automatic Prompt Optimization)

6.4 현업에 적용하려면


7장 · 프롬프트 버저닝과 CI

7.1 왜 필수인가

7.2 저장 위치 옵션

중소 팀은 Git + 파일이 가장 생산적. 팀이 커지면 레지스트리 도입 고려.

7.3 평가셋 기반 CI

# .github/workflows/prompt-eval.yml
on: pull_request
jobs:
  eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run eval -- --golden tests/golden/*.json
      - run: npm run eval -- --regression-threshold 0.95

7.4 Shadow mode

새 프롬프트를 프로덕션 트래픽에 응답은 안 쓰되 호출만 병렬로 태운다. 결과를 로그에 저장하고, 기존 응답과 diff 보면서 배포 여부 결정.


8장 · 모델별 프롬프트 이관

8.1 GPT-4o vs Claude vs Gemini

특성GPT-4o / o-seriesClaude 3.5/4Gemini 2
시스템 프롬프트 스타일Markdown 친화XML 친화혼합
지시 따라가기매우 강함매우 강함 (규칙 명시 선호)우수
JSON 구조화Structured Outputs 강력Tool use 경유responseSchema
긴 컨텍스트 활용양호매우 우수최우수(1–2M)
한국어 톤자연스러움매우 자연스러움자연스러움
거절 스타일간결상세한 이유간결

8.2 이관 체크리스트

8.3 A/B 테스트 구성


9장 · 프롬프트 캐싱 전략

9.1 왜 캐싱인가

대부분 실서비스의 프롬프트는 긴 시스템 프롬프트 + 변하는 사용자 입력 구조다. 시스템 부분을 캐시하면:

9.2 주요 옵션

9.3 캐시 친화적 설계

[시스템: 정적 규칙 (캐시됨)]
[Few-shot: 정적 예시 (캐시됨)]
[RAG context: 반정적 (경우에 따라 캐시)]
[User message: 변함]

10장 · 한국어 프롬프트의 특수성

10.1 톤과 존대

10.2 숫자·단위

10.3 번역/혼용

10.4 토큰 비용


11장 · 안전과 견고함 — Prompt Injection, Jailbreak

11.1 Prompt Injection 기본

공격자가 사용자 입력이나 외부 문서에 "이전 지시를 무시하고 …"를 숨겨두는 것. RAG에서는 특히 위험. 검색된 문서에 공격 문자열이 들어가면 바로 당한다.

11.2 방어 레이어

11.3 Jailbreak

11.4 Data exfiltration 방지


12장 · 조직·프로세스 — 프롬프트는 팀 자산

12.1 역할 분담 제안

소규모 팀은 1인 3역, 대규모는 분리.

12.2 문서화 표준

프롬프트 파일마다 헤더에 메타데이터:

- 용도:
- 예상 입력/출력 스키마:
- 평가셋 경로:
- 주의/알려진 실패:
- 마지막 수정:

12.3 변경 절차

1) 이슈/티켓 생성 (문제 or 개선 가설)
2) 평가셋으로 현재 점수 측정
3) 프롬프트 수정 or DSPy 옵티마이즈
4) 평가셋 재측정 → 개선 확인
5) Shadow mode 136) 510% A/B
7) 전체 배포
8) 회귀 대시보드 1주 관찰

13장 · 흔한 함정 10선 (안티패턴)

13.1 "Please", "I beg you" 따위를 남발

모델 성능과 큰 관계 없다. 명확한 지시가 낫다.

13.2 System prompt에 비밀/PII 포함

로그에 남고, 프롬프트 인젝션으로 유출 가능.

13.3 Few-shot을 평균적 케이스로만 채우기

엣지 케이스 예시가 없으면 엣지에서 깨진다.

13.4 모델 바꿨는데 프롬프트 그대로

GPT ↔ Claude ↔ Gemini 간 성향 차이가 있다. 이관 검증 필수.

13.5 Temperature 기본값 방치

결정적 태스크(분류·추출) = 0, 창의 태스크 = 0.5–0.9. 조정 안 하면 흔들림.

13.6 JSON 파싱 의존

네이티브 Structured Output을 쓰지 않으면 5–10% 케이스에서 JSON 깨짐. 재시도·스키마 기반 가드 필수.

13.7 "한 번에 다 해"의 프롬프트

복잡한 태스크를 한 번에 시키면 뭐가 틀렸는지 디버그 불가. 여러 단계로 쪼개라.

13.8 평가 없이 수정

"어제 건드렸더니 더 좋아진 것 같아요"는 과학이 아니다.

13.9 System prompt 비대증

3000토큰 시스템 프롬프트는 대부분 필요 없고 비용만 올린다. 정기적으로 다이어트.

13.10 캐시 친화적 설계 무시

사용자 변수를 앞쪽에 꽂아서 캐시를 깨는 순간 비용·지연이 3–5배.


14장 · 체크리스트 — 프로덕션 프롬프트 런칭 전 12가지


15장 · 다음 글 예고 — Season 4 Ep 3: "LLM 에이전트 실전"

RAG가 재료, 프롬프트가 지시라면, 에이전트는 "반복 행동"이다. Ep 3에서는:

"프롬프트로 한 번에 되는 일 = Ep 2, 여러 번 반복해야 되는 일 = Ep 3." 경계를 잘 그어야 비용과 견고함이 동시에 잡힌다.

다음 글에서 만나자.


요약: 프롬프트는 이제 개인기가 아니라 팀이 버저닝하고 평가하고 CI에 태우는 코드다. CoT·Self-consistency·ToT·Reflection은 태스크 유형별로 선택하고, Few-shot은 동적으로, 출력은 네이티브 Structured Output으로, 최적화는 DSPy·TextGrad로, 캐시는 정적 부분을 앞으로, 인젝션은 태그와 다층 가드로. 그리고 평가셋 없는 프롬프트 개선은 여전히 미신이다.

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다