Few-Shot, Chain-of-Thought, Tree-of-Thought

모델에게 무엇을 하라고 말하는 것은 프롬프팅(Prompting)입니다. 모델에게 어떻게 생각해야 하는지 보여주는 것은 엔지니어링(Engineering)입니다. 같은 모델, 같은 작업, 같은 데이터에서 정확도가 78%에서 91%로 오르는 차이는 더 나은 모델이 아닙니다. 더 나은 추론 전략입니다.

유형: Build 언어: Python 선수 지식: Lesson 11.01 (프롬프트 엔지니어링(Prompt Engineering)) 예상 시간: 약 45분

학습 목표

  • 작업 정확도를 높이는 예시 데모(Example Demonstration)를 선택하고 형식화해 퓨샷 프롬프팅(Few-Shot Prompting)을 구현합니다.
  • 수학 문장제처럼 여러 단계가 필요한 문제에서 정확도를 높이기 위해 사고 연쇄(Chain-of-Thought, CoT) 추론을 적용합니다.
  • 여러 추론 경로를 탐색하고 가장 좋은 경로를 선택하는 사고 트리(Tree-of-Thought, ToT) 프롬프트를 만듭니다.
  • 표준 벤치마크에서 제로샷(Zero-Shot), 퓨샷, CoT의 정확도 개선 폭을 측정합니다.

문제

수학 튜터링 앱을 만든다고 해봅시다. 프롬프트는 "이 문장제를 풀어줘"입니다. 표준 초등 수학 벤치마크인 GSM8K에서 GPT-5는 94% 정도 정답을 맞힙니다. 이미 한계에 도달했다고 생각할 수 있습니다. 하지만 그렇지 않습니다. 사고 연쇄는 여전히 3-4%p를 더 올릴 수 있습니다.

다섯 단어, "Let's think step by step"을 추가하면 정확도가 91%까지 올라갑니다. 풀이 예시 몇 개를 추가하면 95%에 도달합니다. 같은 모델, 같은 온도(temperature), 같은 API 비용입니다. 유일한 차이는 모델에게 연습장(Scratch Paper)을 주었다는 점입니다.

이것은 해킹이 아닙니다. 추론이 작동하는 방식입니다. 사람은 여러 단계 문제를 한 번의 정신적 도약으로 풀지 않습니다. 트랜스포머(Transformer)도 마찬가지입니다. 모델이 중간 토큰을 생성하도록 강제하면, 그 토큰은 다음 토큰을 위한 컨텍스트가 됩니다. 각 추론 단계가 다음 단계를 먹여 살립니다. 모델은 문자 그대로 답을 향해 계산해 갑니다.

하지만 "단계별로 생각해보자"는 시작일 뿐 끝이 아닙니다. 추론 경로를 다섯 개 샘플링하고 다수결을 취하면 어떨까요? 모델이 가능성의 트리를 탐색하고, 가지를 평가하고, 가지치기하게 하면 어떨까요? 추론과 도구 사용을 번갈아 수행하면 어떨까요? 이것들은 가정이 아닙니다. 측정된 개선 효과가 있는 공개 기법이며, 이 lesson에서 모두 직접 만들어봅니다.

사전 테스트

2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요

1.제로샷(Zero-Shot) 프롬프팅과 퓨샷(Few-Shot) 프롬프팅의 핵심 차이는 무엇인가요?

2.사고 연쇄(Chain-of-Thought, CoT) 프롬프팅은 무엇을 하나요?

0/2 답변 완료

개념

제로샷 vs 퓨샷: 예시가 지시보다 나을 때

제로샷 프롬프팅(Zero-Shot Prompting)은 모델에 작업만 주고 그 외에는 아무것도 주지 않습니다. 퓨샷 프롬프팅(Few-Shot Prompting)은 먼저 예시를 줍니다.

Wei et al. (2022)은 8개 벤치마크에서 이를 측정했습니다. 감성 분류처럼 단순한 작업에서는 제로샷과 퓨샷의 성능 차이가 2% 이내였습니다. 하지만 다단계 산술이나 기호 추론(Symbolic Reasoning)처럼 복잡한 작업에서는 퓨샷이 정확도를 10-25% 개선했습니다.

직관은 이렇습니다. 예시는 압축된 지시입니다. 출력 형식을 설명하는 대신 보여줍니다. 추론 과정을 설명하는 대신 시연합니다. 모델은 추상적 지시를 해석하는 것보다 예시의 패턴을 맞추는 일을 더 안정적으로 수행합니다.

graph TD
    subgraph Comparison["Zero-Shot vs Few-Shot"]
        direction LR
        Z["Zero-Shot\n'이 리뷰를 분류하세요'\n모델이 형식을 추측\nGSM8K 78%"]
        F["Few-Shot\n'예시 3개가 있습니다...\n이제 이 리뷰를 분류하세요'\n모델이 패턴을 맞춤\nGSM8K 85%"]
    end

    Z ~~~ F

    style Z fill:#1a1a2e,stroke:#e94560,color:#fff
    style F fill:#1a1a2e,stroke:#51cf66,color:#fff

퓨샷이 이기는 경우: 형식에 민감한 작업, 분류, 구조화 추출, 도메인 특화 전문 용어, 모델이 특정 패턴을 맞춰야 하는 모든 작업.

제로샷이 이기는 경우: 단순 사실 질문, 예시가 창의성을 제한하는 창의적 작업, 좋은 예시를 찾는 일이 좋은 지시를 쓰는 것보다 어려운 작업.

예시 선택: 무작위보다 유사성이 낫다

모든 예시가 동등하지 않습니다. 대상 입력과 유사한 예시를 고르면 분류 작업에서 무작위 선택보다 5-15% 더 좋은 성능을 냅니다(Liu et al., 2022). 세 가지 원칙이 있습니다.

  1. 의미적 유사성(Semantic Similarity): 임베딩 공간(Embedding Space)에서 입력과 가장 가까운 예시를 고릅니다.
  2. 라벨 다양성(Label Diversity): 출력 범주를 모두 덮도록 예시를 구성합니다.
  3. 난이도 매칭(Difficulty Matching): 대상 문제와 비슷한 복잡도 수준의 예시를 고릅니다.

대부분의 작업에서 최적 예시 수는 3-5개입니다. 3개보다 적으면 모델이 패턴을 추출하기에 신호가 부족합니다. 5개를 넘으면 수익 체감이 시작되고 컨텍스트 창 토큰을 낭비합니다. 라벨이 많은 분류 작업에서는 라벨당 하나의 예시를 사용합니다.

사고 연쇄: 모델에게 연습장을 주기

사고 연쇄(Chain-of-Thought, CoT) 프롬프팅은 Google Brain의 Wei et al. (2022)이 소개했습니다. 아이디어는 단순합니다. 모델에 답만 요청하는 대신, 먼저 추론 단계를 보여달라고 요청합니다.

graph LR
    subgraph Standard["표준 프롬프팅"]
        Q1["Q: Roger는 공 5개가 있습니다.\n공 3개짜리 캔 2개를 삽니다.\n공은 몇 개입니까?"] --> A1["A: 11"]
    end

    subgraph CoT["사고 연쇄 프롬프팅"]
        Q2["Q: Roger는 공 5개가 있습니다.\n공 3개짜리 캔 2개를 삽니다.\n공은 몇 개입니까?"] --> R2["Roger는 5개로 시작합니다.\n3개짜리 캔 2개 = 6개.\n5 + 6 = 11."] --> A2["A: 11"]
    end

    style Q1 fill:#1a1a2e,stroke:#e94560,color:#fff
    style A1 fill:#1a1a2e,stroke:#e94560,color:#fff
    style Q2 fill:#1a1a2e,stroke:#51cf66,color:#fff
    style R2 fill:#1a1a2e,stroke:#ffa500,color:#fff
    style A2 fill:#1a1a2e,stroke:#51cf66,color:#fff

이것은 기계적으로 왜 작동할까요? 트랜스포머가 생성한 각 토큰은 다음 토큰의 컨텍스트가 됩니다. CoT가 없으면 모델은 모든 추론을 단일 순전파(Forward Pass)의 은닉 상태에 압축해야 합니다. CoT가 있으면 모델은 중간 계산을 토큰으로 외부화합니다. 각 추론 토큰은 유효 계산 깊이를 늘립니다.

GSM8K 벤치마크(초등 수학, 8.5K 문제):

모델Zero-ShotZero-Shot CoTFew-Shot CoT
GPT-4o78%91%95%
GPT-594%97%98%
o4-mini (reasoning)97%
Claude Opus 4.793%97%98%
Gemini 3 Pro92%96%98%
Llama 4 70B80%89%94%
DeepSeek-V3.189%94%96%

추론 모델(Reasoning Models)에 대한 참고. OpenAI o-series(o3, o4-mini)와 DeepSeek-R1 같은 모델은 답을 내기 전에 내부적으로 사고 연쇄를 실행합니다. 이런 추론 모델에 "Let's think step by step"을 추가하는 것은 중복이며, 때로는 역효과가 날 수 있습니다. 이미 내부에서 수행했기 때문입니다.

CoT에는 두 가지 변형이 있습니다.

제로샷 CoT(Zero-Shot CoT): 프롬프트 뒤에 "Let's think step by step"을 붙입니다. 예시는 필요 없습니다. Kojima et al. (2022)은 이 한 문장이 산술, 상식, 기호 추론 작업 전반에서 정확도를 개선한다는 것을 보였습니다.

퓨샷 CoT(Few-Shot CoT): 추론 단계가 포함된 예시를 제공합니다. 모델이 기대하는 정확한 추론 형식을 볼 수 있으므로 제로샷 CoT보다 더 효과적입니다.

CoT가 해로운 경우: 단순 사실 회상("프랑스의 수도는?"), 단일 단계 분류, 정확도보다 속도가 더 중요한 작업입니다. CoT는 질의(query)당 50-200 토큰의 추론 오버헤드를 추가합니다. 처리량이 높고 복잡도가 낮은 작업에서는 비용 낭비입니다.

자기 일관성(Self-Consistency): 여러 번 샘플링하고 한 번 투표하기

Wang et al. (2023)은 자기 일관성(Self-Consistency)을 소개했습니다. 핵심 통찰은 이렇습니다. 하나의 CoT 경로에는 추론 오류가 들어갈 수 있습니다. 하지만 N개의 독립적인 추론 경로를 0보다 큰 온도(temperature > 0)로 샘플링하고 최종 답에 대해 다수결을 취하면 오류가 상쇄됩니다.

graph TD
    P["문제: '상점에 사과 48개가 있습니다.\n월요일에 1/3을 팔고\n화요일에 남은 것의 1/4을 팝니다.\n몇 개가 남나요?'"]

    P --> Path1["경로 1: 48 - 16 = 32\n32 - 8 = 24\n답: 24"]
    P --> Path2["경로 2: 48의 1/3 = 16\n남은 것: 32\n32의 1/4 = 8\n32 - 8 = 24\n답: 24"]
    P --> Path3["경로 3: 48/3 = 16 판매\n48 - 16 = 32\n32/4 = 8 판매\n32 - 8 = 24\n답: 24"]
    P --> Path4["경로 4: 1/3 판매: 48 - 12 = 36\n1/4 판매: 36 - 9 = 27\n답: 27"]
    P --> Path5["경로 5: 월요일: 48 * 2/3 = 32\n화요일: 32 * 3/4 = 24\n답: 24"]

    Path1 --> V["다수결\n24: 4표\n27: 1표\n최종: 24"]
    Path2 --> V
    Path3 --> V
    Path4 --> V
    Path5 --> V

    style P fill:#1a1a2e,stroke:#ffa500,color:#fff
    style Path1 fill:#1a1a2e,stroke:#51cf66,color:#fff
    style Path2 fill:#1a1a2e,stroke:#51cf66,color:#fff
    style Path3 fill:#1a1a2e,stroke:#51cf66,color:#fff
    style Path4 fill:#1a1a2e,stroke:#e94560,color:#fff
    style Path5 fill:#1a1a2e,stroke:#51cf66,color:#fff
    style V fill:#1a1a2e,stroke:#51cf66,color:#fff

원래 PaLM 540B 실험에서 자기 일관성은 GSM8K 정확도를 단일 CoT 56.5%에서 N=40 기준 74.4%로 개선했습니다. GPT-5에서는 기본 정확도가 이미 포화 상태라 개선 폭이 작습니다(97%에서 98%). 이 기법은 기본 CoT 정확도가 60-85%인 모델에서 가장 빛납니다. 단일 경로 오류가 자주 나오지만 체계적으로 틀리지는 않는 구간입니다. 추론 모델(o-series, R1)에서는 자기 일관성이 내장된 내부 샘플링에 흡수됩니다.

트레이드오프(trade-off)는 비용과 지연시간입니다. N개의 샘플은 API 비용과 지연시간이 N배라는 뜻입니다. 실무에서는 N=5가 대부분의 이점을 잡아냅니다. 의미 있는 투표를 위한 최소값은 N=3입니다. 대부분의 작업에서 N > 10은 수익 체감이 큽니다.

사고 트리: 가지를 뻗는 탐색

Yao et al. (2023)은 사고 트리(Tree-of-Thought, ToT)를 소개했습니다. CoT가 하나의 선형 추론 경로를 따르는 반면, ToT는 여러 가지를 탐색하고 어떤 가지가 더 유망한지 평가한 뒤 계속 진행합니다.

graph TD
    Root["문제"] --> B1["생각 1a"]
    Root --> B2["생각 1b"]
    Root --> B3["생각 1c"]

    B1 --> E1["평가: 0.8"]
    B2 --> E2["평가: 0.3"]
    B3 --> E3["평가: 0.9"]

    E1 -->|계속| B1a["생각 2a"]
    E1 -->|계속| B1b["생각 2b"]
    E3 -->|계속| B3a["생각 2a"]
    E3 -->|계속| B3b["생각 2b"]

    E2 -->|가지치기| X["X"]

    B1a --> E4["평가: 0.7"]
    B3a --> E5["평가: 0.95"]

    E5 -->|최선 경로| Final["해결책"]

    style Root fill:#1a1a2e,stroke:#ffa500,color:#fff
    style E2 fill:#1a1a2e,stroke:#e94560,color:#fff
    style X fill:#1a1a2e,stroke:#e94560,color:#fff
    style E5 fill:#1a1a2e,stroke:#51cf66,color:#fff
    style Final fill:#1a1a2e,stroke:#51cf66,color:#fff
    style B1 fill:#1a1a2e,stroke:#808080,color:#fff
    style B2 fill:#1a1a2e,stroke:#808080,color:#fff
    style B3 fill:#1a1a2e,stroke:#808080,color:#fff
    style B1a fill:#1a1a2e,stroke:#808080,color:#fff
    style B1b fill:#1a1a2e,stroke:#808080,color:#fff
    style B3a fill:#1a1a2e,stroke:#808080,color:#fff
    style B3b fill:#1a1a2e,stroke:#808080,color:#fff
    style E1 fill:#1a1a2e,stroke:#808080,color:#fff
    style E3 fill:#1a1a2e,stroke:#808080,color:#fff
    style E4 fill:#1a1a2e,stroke:#808080,color:#fff

ToT에는 세 가지 구성 요소가 있습니다.

  1. 생각 생성(Thought Generation): 여러 후보 다음 단계를 생성합니다.
  2. 상태 평가(State Evaluation): 각 후보에 점수를 매깁니다. LLM 자체를 평가자로 사용할 수 있습니다.
  3. 탐색 알고리즘(Search Algorithm): 트리에 대해 너비 우선 탐색(BFS) 또는 깊이 우선 탐색(DFS)을 수행하고 낮은 점수의 가지를 잘라냅니다.

24 게임(Game of 24) 작업에서는 네 숫자를 사칙연산으로 조합해 24를 만들어야 합니다. GPT-4는 표준 프롬프팅으로 7.3%를 해결합니다. CoT는 4.0%입니다. 여기서는 탐색 공간이 넓기 때문에 CoT가 오히려 해롭습니다. ToT를 쓰면 74%까지 올라갑니다.

ToT는 비쌉니다. 트리의 각 노드마다 LLM 호출이 필요합니다. 분기 계수 3, 깊이 3인 트리는 최대 39번의 LLM 호출을 요구합니다. 따라서 탐색 공간이 크지만 평가 가능한 문제에만 사용합니다. 예: 계획, 퍼즐 풀이, 제약이 있는 창의적 문제 해결.

ReAct: 생각하기 + 행동하기

Yao et al. (2022)은 추론 흔적(Reasoning Trace)과 행동(Action)을 결합했습니다. 모델은 생각하기, 즉 추론 생성과 행동하기, 즉 도구 호출, 검색, 계산을 번갈아 수행합니다.

graph LR
    Q["질문:\n에펠탑이 있는\n나라의 인구는\n얼마인가?"]
    T1["Thought: 먼저\n에펠탑이 어느 나라에\n있는지 찾아야 함"]
    A1["Action: search\n'Eiffel Tower location'"]
    O1["Observation:\nParis, France"]
    T2["Thought: 이제\n프랑스 인구가 필요함"]
    A2["Action: search\n'France population 2024'"]
    O2["Observation:\n68.4 million"]
    T3["Thought: 답을 얻었음"]
    F["Answer:\n68.4 million"]

    Q --> T1 --> A1 --> O1 --> T2 --> A2 --> O2 --> T3 --> F

    style Q fill:#1a1a2e,stroke:#ffa500,color:#fff
    style T1 fill:#1a1a2e,stroke:#51cf66,color:#fff
    style A1 fill:#1a1a2e,stroke:#e94560,color:#fff
    style O1 fill:#1a1a2e,stroke:#808080,color:#fff
    style T2 fill:#1a1a2e,stroke:#51cf66,color:#fff
    style A2 fill:#1a1a2e,stroke:#e94560,color:#fff
    style O2 fill:#1a1a2e,stroke:#808080,color:#fff
    style T3 fill:#1a1a2e,stroke:#51cf66,color:#fff
    style F fill:#1a1a2e,stroke:#51cf66,color:#fff

ReAct는 지식 집약적 작업에서 순수 CoT보다 성능이 좋습니다. 외부 데이터에 추론을 접지(Grounding)할 수 있기 때문입니다. 다중 홉 질문 답변 벤치마크인 HotpotQA에서 GPT-4 기반 ReAct는 완전 일치(exact match) 35.1%를 달성했고, CoT만 사용한 경우는 29.4%였습니다. 진짜 힘은 관찰(Observation)이 추론 오류를 수정한다는 데 있습니다. 모델은 실행 중간에 계획을 업데이트할 수 있습니다.

ReAct는 현대 AI 에이전트의 기반입니다. 모든 에이전트 프레임워크(LangChain, CrewAI, AutoGen)는 Thought-Action-Observation 루프의 변형을 구현합니다. Phase 14에서 전체 에이전트를 만들 것입니다. 이 lesson은 그 프롬프팅 패턴을 다룹니다.

구조화 프롬프팅: XML 태그, 구분자, 헤더

프롬프트가 복잡해질수록 구조는 모델이 섹션을 혼동하지 않도록 막아줍니다. 세 가지 접근이 있습니다.

XML 태그(XML Tags): Claude에서 가장 잘 작동하고, 다른 모델에서도 탄탄합니다.

<context>
당신은 pull request를 리뷰하고 있습니다.
이 코드베이스는 TypeScript와 React를 사용합니다.
</context>

<task>
다음 diff에서 버그, 보안 이슈, 스타일 위반을 리뷰하세요.
</task>

<diff>
{diff_content}
</diff>

<output_format>
각 이슈를 file, line, severity(critical/warning/info), description으로 나열하세요.
</output_format>

Markdown 헤더(Markdown Headers): 범용적으로 작동합니다.

## 역할
핀테크 회사의 시니어 보안 엔지니어.

## 작업
이 API endpoint의 취약점을 분석하세요.

## 입력
{api_code}

## 규칙
- OWASP Top 10에 집중하세요.
- 각 발견사항의 등급을 critical, high, medium, low로 매기세요.
- 수정 단계를 포함하세요.

구분자(Delimiters): 작지만 효과적입니다.

---INPUT---
{user_text}
---END INPUT---

---INSTRUCTIONS---
위 내용을 3개의 글머리표로 요약하세요.
---END INSTRUCTIONS---

프롬프트 체이닝(Prompt Chaining): 순차적 분해

일부 작업은 하나의 프롬프트로 처리하기에 너무 복잡합니다. 프롬프트 체이닝은 작업을 단계로 나누고, 한 프롬프트의 출력을 다음 프롬프트의 입력으로 사용합니다.

graph LR
    I["원시 입력"] --> P1["프롬프트 1:\n핵심 사실\n추출"]
    P1 --> O1["사실"]
    O1 --> P2["프롬프트 2:\n사실\n분석"]
    P2 --> O2["분석"]
    O2 --> P3["프롬프트 3:\n추천\n생성"]
    P3 --> F["최종 출력"]

    style I fill:#1a1a2e,stroke:#808080,color:#fff
    style P1 fill:#1a1a2e,stroke:#e94560,color:#fff
    style O1 fill:#1a1a2e,stroke:#ffa500,color:#fff
    style P2 fill:#1a1a2e,stroke:#e94560,color:#fff
    style O2 fill:#1a1a2e,stroke:#ffa500,color:#fff
    style P3 fill:#1a1a2e,stroke:#e94560,color:#fff
    style F fill:#1a1a2e,stroke:#51cf66,color:#fff

체이닝이 단일 프롬프트보다 나은 이유는 세 가지입니다.

  1. 각 단계가 더 단순합니다: 모델이 모든 것을 동시에 처리하는 대신 하나의 집중된 작업을 처리합니다.
  2. 중간 출력이 검사 가능합니다: 단계 사이에서 검증하고 수정할 수 있습니다.
  3. 단계마다 다른 모델을 사용할 수 있습니다: 추출에는 저렴한 모델을, 추론에는 비싼 모델을 사용할 수 있습니다.

성능 비교

기법가장 적합한 경우GSM8K 정확도(GPT-5)API 호출토큰 오버헤드복잡도
Zero-Shot단순 작업94%1없음매우 낮음
Few-Shot형식 맞추기96%1200-500 토큰낮음
Zero-Shot CoT빠른 추론 개선97%150-200 토큰매우 낮음
Few-Shot CoT단일 호출 최대 정확도98%1300-600 토큰낮음
Self-Consistency (N=5)중요한 추론98.5%5토큰 비용 5배중간
Reasoning model (o4-mini)CoT 대체97%1숨겨짐(내부 2-10배)매우 낮음
Tree-of-Thought탐색/계획 문제N/A (Game of 24에서 74%)10-40+토큰 비용 10-40배높음
ReAct지식 기반 추론N/A (HotpotQA에서 35.1%)3-10+가변높음
Prompt Chaining복잡한 다단계 작업96% (파이프라인)2-5토큰 비용 2-5배중간

올바른 기법은 세 가지 요소에 따라 달라집니다. 정확도 요구사항, 지연시간 예산, 비용 허용 범위입니다. 대부분의 프로덕션(production) 시스템에서는 3샘플 자기 일관성 폴백(fallback)을 가진 퓨샷 CoT가 사용 사례의 90%를 덮습니다.

직접 만들기

퓨샷 프롬프팅, 사고 연쇄 추론, 자기 일관성 투표를 하나의 파이프라인으로 결합한 수학 문제 풀이기를 만듭니다. 그다음 어려운 문제를 위해 사고 트리를 추가합니다.

전체 구현은 code/advanced_prompting.py에 있습니다. 핵심 구성 요소는 다음과 같습니다.

Step 1: 퓨샷 예시 저장소

첫 번째 구성 요소는 퓨샷 예시를 관리하고, 주어진 문제에 가장 관련 있는 예시를 선택합니다.

GSM8K_EXAMPLES = [
    {
        "question": "Janet의 오리는 하루에 알 16개를 낳습니다. Janet은 매일 아침 3개를 먹고 친구들을 위해 머핀을 굽는 데 4개를 씁니다. 남은 알을 farmers' market에서 개당 $2에 팝니다. Janet은 매일 farmers' market에서 얼마를 버나요?",
        "reasoning": "Janet의 오리는 하루에 알 16개를 낳습니다. 3개를 먹고 4개로 굽기 때문에 3 + 4 = 7개를 사용합니다. 따라서 16 - 7 = 9개가 남습니다. 각 알을 $2에 팔므로 하루에 9 * 2 = $18를 법니다.",
        "answer": "18"
    },
    ...
]

각 예시는 세 부분을 가집니다. 질문, 추론 체인, 최종 답입니다. 추론 체인이 일반 퓨샷 예시를 CoT 퓨샷 예시로 바꿉니다.

Step 2: 사고 연쇄 프롬프트 빌더

프롬프트 빌더는 시스템 메시지, 추론 체인이 있는 퓨샷 예시, 대상 질문을 하나의 프롬프트로 조립합니다.

def build_cot_prompt(question, examples, num_examples=3):
    system = (
        "당신은 정밀한 수학 문제 풀이기입니다. "
        "각 문제마다 단계별 추론을 명확히 보여준 뒤, "
        "마지막 줄에 최종 숫자 답을 정확히 다음 형식으로 쓰세요. "
        "'The answer is [number]'."
    )

    example_text = ""
    for ex in examples[:num_examples]:
        example_text += f"Q: {ex['question']}\n"
        example_text += f"A: {ex['reasoning']} The answer is {ex['answer']}.\n\n"

    user = f"{example_text}Q: {question}\nA:"
    return system, user

형식 제약("The answer is [number]")은 매우 중요합니다. 이 제약이 없으면 자기 일관성이 여러 샘플의 답을 추출하고 비교할 수 없습니다.

Step 3: 자기 일관성 투표

N개의 추론 경로를 샘플링하고 다수결 답을 선택합니다.

def self_consistency_solve(question, examples, client, model, n_samples=5):
    system, user = build_cot_prompt(question, examples)

    answers = []
    reasonings = []
    for _ in range(n_samples):
        response = client.chat.completions.create(
            model=model,
            messages=[
                {"role": "system", "content": system},
                {"role": "user", "content": user}
            ],
            temperature=0.7
        )
        text = response.choices[0].message.content
        reasonings.append(text)
        answer = extract_answer(text)
        if answer is not None:
            answers.append(answer)

    vote_counts = Counter(answers)
    best_answer = vote_counts.most_common(1)[0][0] if vote_counts else None
    confidence = vote_counts[best_answer] / len(answers) if best_answer else 0

    return best_answer, confidence, reasonings, vote_counts

온도(temperature) 0.7이 중요합니다. 온도 0.0이면 N개의 샘플이 모두 같아져 목적이 사라집니다. 다양한 추론 경로를 만들 만큼의 무작위성은 필요하지만, 모델이 의미 없는 출력을 만들 정도로 높으면 안 됩니다.

Step 4: 사고 트리 풀이기

선형 추론이 실패하는 문제에서는 ToT가 여러 접근을 탐색하고 어떤 방향이 가장 유망한지 평가합니다.

def tree_of_thought_solve(question, client, model, breadth=3, depth=3):
    thoughts = generate_initial_thoughts(question, client, model, breadth)
    scored = [(t, evaluate_thought(t, question, client, model)) for t in thoughts]
    scored.sort(key=lambda x: x[1], reverse=True)

    for current_depth in range(1, depth):
        next_thoughts = []
        for thought, score in scored[:2]:
            extensions = extend_thought(thought, question, client, model, breadth)
            for ext in extensions:
                ext_score = evaluate_thought(ext, question, client, model)
                next_thoughts.append((ext, ext_score))
        scored = sorted(next_thoughts, key=lambda x: x[1], reverse=True)

    best_thought = scored[0][0] if scored else ""
    return extract_answer(best_thought), best_thought

평가자는 그 자체로 LLM 호출입니다. 모델에게 "이 추론 경로가 문제 해결에 얼마나 유망한지 0.0에서 1.0 사이 점수로 평가하세요"라고 묻습니다. 이것이 ToT의 핵심 통찰입니다. 모델이 자신의 부분 해결책을 평가합니다.

Step 5: 전체 파이프라인

파이프라인은 모든 기법을 단계적 확대 전략(Escalation Strategy)과 결합합니다.

def solve_with_escalation(question, examples, client, model):
    system, user = build_cot_prompt(question, examples)
    single_response = call_llm(client, model, system, user, temperature=0.0)
    single_answer = extract_answer(single_response)

    sc_answer, confidence, _, _ = self_consistency_solve(
        question, examples, client, model, n_samples=5
    )

    if confidence >= 0.8:
        return sc_answer, "self_consistency", confidence

    tot_answer, _ = tree_of_thought_solve(question, client, model)
    return tot_answer, "tree_of_thought", None

단계적 확대 로직은 이렇습니다. 먼저 저렴한 단일 CoT를 시도합니다. 자기 일관성 신뢰도가 0.8보다 낮으면, 즉 5개 샘플 중 4개 미만이 동의하면 ToT로 확대합니다. 이렇게 하면 비용과 정확도의 균형을 잡을 수 있습니다. 대부분의 문제는 저렴하게 풀고, 어려운 문제에는 더 많은 계산을 씁니다.

사용해보기

LangChain과 함께 사용하기

LangChain은 퓨샷과 CoT 패턴을 단순화하는 프롬프트 템플릿과 출력 파싱 기능을 기본 제공합니다.

from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
from langchain_openai import ChatOpenAI

example_prompt = PromptTemplate(
    input_variables=["question", "reasoning", "answer"],
    template="Q: {question}\nA: {reasoning} The answer is {answer}."
)

few_shot_prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    suffix="Q: {input}\nA: Let's think step by step.",
    input_variables=["input"]
)

llm = ChatOpenAI(model="gpt-4o", temperature=0.7)
chain = few_shot_prompt | llm
result = chain.invoke({"input": "If a train travels 120 km in 2 hours..."})

LangChain에는 의미적 유사성 선택을 위한 ExampleSelector 클래스도 있습니다.

from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_openai import OpenAIEmbeddings

selector = SemanticSimilarityExampleSelector.from_examples(
    examples,
    OpenAIEmbeddings(),
    k=3
)

DSPy와 함께 사용하기

DSPy는 프롬프팅 전략을 최적화 가능한 모듈로 취급합니다. CoT 프롬프트를 손으로 만드는 대신, 시그니처(Signature)를 정의하고 DSPy가 프롬프트를 최적화하게 합니다.

import dspy

dspy.configure(lm=dspy.LM("openai/gpt-4o", temperature=0.7))

class MathSolver(dspy.Module):
    def __init__(self):
        self.solve = dspy.ChainOfThought("question -> answer")

    def forward(self, question):
        return self.solve(question=question)

solver = MathSolver()
result = solver(question="Janet's ducks lay 16 eggs per day...")

DSPy의 ChainOfThought는 추론 흔적을 자동으로 추가합니다. dspy.majority는 자기 일관성을 구현합니다.

result = dspy.majority(
    [solver(question=q) for _ in range(5)],
    field="answer"
)

비교: 직접 구현 vs 프레임워크

기능직접 구현(이 lesson)LangChainDSPy
프롬프트 형식 제어전체 제어템플릿 기반자동
자기 일관성수동 투표수동내장(dspy.majority)
예시 선택커스텀 로직ExampleSelectordspy.BootstrapFewShot
사고 트리커스텀 트리 탐색커뮤니티 체인내장 아님
프롬프트 최적화수동 반복수동자동 컴파일
가장 적합한 경우학습, 커스텀 파이프라인표준 워크플로연구, 최적화

산출물 만들기

이 lesson은 두 가지 산출물을 만듭니다.

1. 추론 체인 프롬프트 (outputs/prompt-reasoning-chain.md): 자기 일관성을 지원하는 프로덕션(production) 준비 퓨샷 CoT 프롬프트 템플릿입니다. 예시와 문제 도메인을 끼워 넣어 사용합니다.

2. CoT 패턴 선택 스킬(Skill) (outputs/skill-cot-patterns.md): 작업 유형, 정확도 요구사항, 비용 제약에 따라 올바른 추론 기법을 고르는 의사결정 프레임워크(framework)입니다.

연습문제

  1. 차이 측정하기: GSM8K 문제 10개를 가져옵니다. 각 문제를 제로샷, 퓨샷, 제로샷 CoT, 퓨샷 CoT로 풉니다. 각 정확도를 기록하세요. 어떤 기법이 사용하는 모델에서 가장 큰 개선을 주나요?

  2. 예시 선택 실험: 같은 10개 문제에서 무작위 예시 선택과 손으로 고른 유사 예시를 비교하세요. 정확도 차이를 측정합니다. 어느 지점에서 예시 품질이 예시 수보다 더 중요해지나요?

  3. 자기 일관성 비용 곡선: GSM8K 문제 20개에 대해 N=1, 3, 5, 7, 10으로 자기 일관성을 실행하세요. 정확도 대비 비용(총 토큰)을 그래프로 그립니다. 사용하는 모델에서 곡선의 무릎 지점은 어디인가요?

  4. ReAct 루프 만들기: 파이프라인에 계산기 도구를 추가하세요. 모델이 수학식을 생성하면 Python의 eval()을 샌드박스(sandbox)에서 실행하고 결과를 다시 넣습니다. 도구로 접지된 추론이 순수 CoT보다 나은지 측정하세요.

  5. 창의적 작업을 위한 ToT: 사고 트리 풀이기를 창의적 글쓰기 작업에 맞게 바꾸세요. 예: "웃기면서도 슬픈 6단어 이야기를 쓰세요." LLM을 평가자로 사용합니다. 가지 탐색이 단일 생성보다 더 나은 창의적 출력을 만드나요?

핵심 용어

용어흔한 설명실제 의미
퓨샷 프롬프팅(Few-Shot Prompting)"예시 몇 개 주기"모델의 출력 형식과 행동을 고정하기 위해 프롬프트 안에 입력-출력 데모를 포함하는 방식
사고 연쇄(Chain-of-Thought)"단계별로 생각하게 하기"최종 답을 내기 전에 중간 추론 토큰을 유도해 모델의 유효 계산을 확장하는 방식
자기 일관성(Self-Consistency)"여러 번 실행하기"0보다 큰 온도(temperature > 0)에서 N개의 다양한 추론 경로를 샘플링하고 가장 흔한 최종 답을 다수결로 선택하는 방식
사고 트리(Tree-of-Thought)"선택지를 탐색하게 하기"각 부분 해결책을 평가하고 유망한 경로만 확장하는 구조화된 추론 가지 탐색
ReAct"생각 + 도구 사용"Thought-Action-Observation 루프에서 추론 흔적과 외부 행동(검색, 계산, API 호출)을 번갈아 수행하는 방식
프롬프트 체이닝(Prompt Chaining)"단계로 나누기"복잡한 작업을 순차 프롬프트로 분해하고 각 출력이 다음 입력으로 들어가게 하는 방식
제로샷 CoT(Zero-Shot CoT)"'think step by step'만 붙이기"예시 없이 추론 유도 문구를 붙여 모델의 잠재 추론 능력에 기대는 방식

더 읽을거리

실습 코드

이 강의의 실습 코드 1개

advanced prompting
Code

산출물

이 강의에서 생성된 프롬프트, 스킬, 코드 산출물 2개

skill-cot-patterns

Decision framework for choosing the right reasoning technique based on task complexity, accuracy requirements, and cost constraints

Skill
prompt-reasoning-chain

Production-ready few-shot CoT prompt with self-consistency support for multi-step reasoning tasks

Prompt

확인 문제

3문제 · 모두 맞추면 완료 표시가 가능합니다

1.사고 트리(Tree-of-Thought)는 사고 연쇄(Chain-of-Thought)와 어떻게 다른가요?

2.퓨샷 예시를 선택할 때 가장 중요한 것은 무엇인가요?

3.모델이 CoT 유무와 관계없이 같은 지식을 갖고 있는데도 CoT 프롬프팅이 정확도를 높이는 이유는 무엇인가요?

0/3 답변 완료

추가 문제 풀기

AI가 강의 내용을 바탕으로 새로운 문제를 생성합니다