Directional Stimulus Prompting: 힌트로 LLM을 원하는 방향으로 유도하기

작은 정책 모델로 방향성 힌트를 생성하여 LLM의 출력을 원하는 방향으로 유도하는 기법

Directional Stimulus Prompting의 정의부터 실전 구현까지 체계적으로 설명한다. Li et al. (2023) 연구를 바탕으로, 작은 정책 LM이 생성한 방향성 힌트(키워드, 핵심 구절)를 프롬프트에 포함하여 LLM의 출력 품질을 향상시키는 방법을 분석한다.

Prompt Engineering
LLM
AI
Agent
저자

Kwangmin Kim

공개

2025년 02월 08일

1 들어가며

LLM에게 질문할 때, 때로는 방향성이 필요하다. “이 방향으로 생각해봐”, “이 관점을 고려해봐”와 같은 힌트가 있으면 더 나은 답변을 얻을 수 있다.

하지만 문제가 있다: 어떤 힌트가 효과적인지 어떻게 알 수 있을까?

Directional Stimulus Prompting은 이 문제를 해결한다. 작은 튜닝된 모델(Policy LM)이 효과적인 힌트를 생성하고, 그 힌트를 사용하여 큰 블랙박스 LLM의 응답을 개선한다.

2 기존 접근법의 문제점

2.1 문제 1: 수동 힌트 작성

현재 방식:

사람이 직접 힌트 작성:
"이 문제를 풀기 위해 다음을 고려하세요..."

문제점:
- 시간 소모
- 전문 지식 필요
- 일관성 부족

예시:

질문: "기후 변화가 북극곰에 미치는 영향은?"

수동 힌트 (사람):
"빙하 감소, 먹이 부족, 서식지 변화를 고려하세요."
→ 좋은 힌트지만, 모든 질문에 이렇게 작성하기 어려움

2.2 문제 2: 힌트 없이 직접 질문

현재 방식:

질문만 던지기:
"기후 변화가 북극곰에 미치는 영향은?"

문제점:
- 응답이 너무 일반적
- 중요한 측면 누락
- 깊이 부족

2.3 해결책: Directional Stimulus Prompting

[작은 튜닝 모델] → 힌트 생성
                    ↓
[질문 + 힌트] → [큰 블랙박스 LLM] → 개선된 답변

3 Directional Stimulus Prompting이란?

Li et al. (2023)이 제안한 기법으로, 두 개의 LM을 사용한다:

3.1 구조

┌─────────────────────────────────────────┐
│         Policy LM (작은 모델)            │
│    - 튜닝 가능                           │
│    - 힌트 생성 전문                       │
│    - 예: LLaMA-7B 튜닝                   │
└─────────────────────────────────────────┘
                   ↓
            Stimulus (힌트)
                   ↓
┌─────────────────────────────────────────┐
│         Target LM (큰 모델)              │
│    - 블랙박스 (API만)                     │
│    - 힌트로 유도                          │
│    - 예: GPT-4, Claude                  │
└─────────────────────────────────────────┘

3.2 핵심 아이디어

Directional Stimulus (방향성 자극): 응답을 특정 방향으로 유도하는 짧은 힌트

예시:

질문: "경제 침체를 극복하는 방법은?"

일반적 응답 (힌트 없음):
"정부는 재정 정책과 통화 정책을 통해 대응할 수 있습니다..."

Stimulus: "케인즈 경제학 관점에서"
→ 응답: "케인즈에 따르면, 정부 지출 증대가..."

Stimulus: "공급 측면 경제학 관점에서"  
→ 응답: "공급 측면에서는, 감세와 규제 완화가..."

Stimulus: "역사적 사례를 중심으로"
→ 응답: "대공황 때 루즈벨트의 뉴딜 정책처럼..."

4 2단계 프로세스

4.1 Policy LM 튜닝

작은 모델을 힌트 생성 전문가로 튜닝한다.

import anthropic
from typing import List, Dict
import json

class DirectionalStimulusPrompting:
    """
    Directional Stimulus Prompting 구현
    """
    
    def __init__(
        self,
        policy_api_key: str,
        target_api_key: str,
        policy_model: str = "claude-haiku-4-20250514",
        target_model: str = "claude-sonnet-4-20250514"
    ):
        """
        Args:
            policy_api_key: Policy LM API 키
            target_api_key: Target LM API 키
            policy_model: 힌트 생성 모델 (작은 모델)
            target_model: 최종 답변 모델 (큰 모델)
        """
        self.policy_client = anthropic.Anthropic(api_key=policy_api_key)
        self.target_client = anthropic.Anthropic(api_key=target_api_key)
        
        self.policy_model = policy_model
        self.target_model = target_model
        
        print(f"✅ Directional Stimulus Prompting 초기화")
        print(f"   Policy LM: {policy_model}")
        print(f"   Target LM: {target_model}\n")
    
    def prepare_training_data(
        self,
        examples: List[Dict[str, str]]
    ) -> List[Dict]:
        """
        Policy LM 튜닝을 위한 데이터 준비
        
        Args:
            examples: [
                {
                    'question': '...',
                    'good_stimulus': '...',  # 좋은 힌트
                    'expected_answer': '...'
                },
                ...
            ]
        
        Returns:
            튜닝 데이터
        """
        training_data = []
        
        for example in examples:
            # 프롬프트 형식
            prompt = f"""질문에 대한 효과적인 힌트를 생성하세요.
            힌트는 짧고(10-20단어), 구체적이며, 답변의 방향을 제시해야 합니다.

            질문: {example['question']}

            힌트:"""
            
            training_data.append({
                'prompt': prompt,
                'completion': example['good_stimulus']
            })
        
        return training_data

튜닝 데이터 예시:

tuning_examples = [
    {
        'question': '재생 에너지의 장단점은?',
        'good_stimulus': '경제성, 환경 영향, 기술적 한계를 각각 분석하세요.',
        'expected_answer': '...'
    },
    {
        'question': '인공지능 윤리의 중요성은?',
        'good_stimulus': '구체적인 사례와 잠재적 위험을 중심으로 설명하세요.',
        'expected_answer': '...'
    },
    {
        'question': '양자 컴퓨터의 작동 원리는?',
        'good_stimulus': '고전 컴퓨터와 비교하며 중첩과 얽힘 개념을 설명하세요.',
        'expected_answer': '...'
    }
]

4.2 Stage 2: 힌트 생성 및 적용

튜닝된 Policy LM으로 힌트를 생성하고, Target LM에 적용한다.

    def generate_stimulus(
        self,
        question: str,
        num_candidates: int = 5
    ) -> List[str]:
        """
        Policy LM으로 힌트 후보 생성
        
        Args:
            question: 질문
            num_candidates: 생성할 힌트 후보 수
        
        Returns:
            힌트 후보들
        """
        prompt = f"""질문에 대한 효과적인 힌트를 {num_candidates}개 생성하세요.
        각 힌트는:
        - 10-20단어로 간결
        - 구체적인 방향 제시
        - 서로 다른 관점

        질문: {question}

        힌트 {num_candidates}개 (한 줄씩):
        1."""
        
        message = self.policy_client.messages.create(
            model=self.policy_model,
            max_tokens=500,
            temperature=0.8,  # 다양성을 위해
            messages=[{"role": "user", "content": prompt}]
        )
        
        response = message.content[0].text
        
        # 힌트 파싱
        stimuli = []
        for line in response.split('\n'):
            import re
            # 번호 제거
            line = re.sub(r'^\d+\.\s*', '', line.strip())
            
            if line and len(line) > 10:
                stimuli.append(line)
        
        return stimuli[:num_candidates]
    
    def answer_with_stimulus(
        self,
        question: str,
        stimulus: str
    ) -> str:
        """
        힌트를 사용하여 Target LM으로 답변 생성
        
        Args:
            question: 질문
            stimulus: 힌트
        
        Returns:
            답변
        """
        # 힌트가 포함된 프롬프트 구성
        prompt = f"""다음 힌트를 고려하여 질문에 답변하세요:

        힌트: {stimulus}

        질문: {question}

        답변:"""
        
        message = self.target_client.messages.create(
            model=self.target_model,
            max_tokens=1000,
            temperature=0,
            messages=[{"role": "user", "content": prompt}]
        )
        
        return message.content[0].text

5 전체 파이프라인 구현

    def directional_stimulus_pipeline(
        self,
        question: str,
        num_stimulus_candidates: int = 5,
        selection_method: str = "quality"
    ) -> Dict:
        """
        Directional Stimulus Prompting 전체 파이프라인
        
        Args:
            question: 질문
            num_stimulus_candidates: 생성할 힌트 후보 수
            selection_method: 힌트 선택 방법
                - "quality": 품질 평가로 선택
                - "diversity": 다양성 기반 선택
                - "ensemble": 모든 힌트 사용 후 결합
        
        Returns:
            결과 딕셔너리
        """
        print(f"🎯 Directional Stimulus Prompting")
        print(f"   질문: {question}")
        print(f"   선택 방법: {selection_method}\n")
        
        # Step 1: 힌트 후보 생성
        print("Step 1: 힌트 생성")
        print("-" * 80)
        
        stimuli = self.generate_stimulus(question, num_stimulus_candidates)
        
        print(f"✅ {len(stimuli)}개 힌트 생성:")
        for i, s in enumerate(stimuli, 1):
            print(f"   [{i}] {s}")
        print()
        
        # Step 2: 힌트 선택 또는 사용
        if selection_method == "quality":
            # 각 힌트로 답변 생성 후 품질 평가
            print("Step 2: 힌트 품질 평가")
            print("-" * 80)
            
            best_stimulus = self._select_best_stimulus(question, stimuli)
            
            print(f"✅ 최고 품질 힌트 선택:")
            print(f"   {best_stimulus}\n")
            
            # Step 3: 최종 답변 생성
            print("Step 3: 최종 답변 생성")
            print("-" * 80)
            
            final_answer = self.answer_with_stimulus(question, best_stimulus)
            
            return {
                'question': question,
                'stimuli_candidates': stimuli,
                'selected_stimulus': best_stimulus,
                'final_answer': final_answer,
                'method': 'quality'
            }
        
        elif selection_method == "diversity":
            # 다양성 기반 선택
            print("Step 2: 다양성 기반 선택")
            print("-" * 80)
            
            diverse_stimuli = self._select_diverse_stimuli(stimuli, k=3)
            
            print(f"✅ 다양한 힌트 {len(diverse_stimuli)}개 선택\n")
            
            # 각각으로 답변 생성
            answers = []
            for stimulus in diverse_stimuli:
                answer = self.answer_with_stimulus(question, stimulus)
                answers.append({
                    'stimulus': stimulus,
                    'answer': answer
                })
            
            return {
                'question': question,
                'stimuli_candidates': stimuli,
                'diverse_stimuli': diverse_stimuli,
                'answers': answers,
                'method': 'diversity'
            }
        
        elif selection_method == "ensemble":
            # 모든 힌트 사용 후 결합
            print("Step 2: Ensemble - 모든 힌트 사용")
            print("-" * 80)
            
            answers = []
            for i, stimulus in enumerate(stimuli, 1):
                print(f"[{i}/{len(stimuli)}] 힌트로 답변 생성 중...")
                answer = self.answer_with_stimulus(question, stimulus)
                answers.append(answer)
            
            print(f"✅ {len(answers)}개 답변 생성 완료\n")
            
            # Step 3: 답변 결합
            print("Step 3: 답변 결합")
            print("-" * 80)
            
            ensemble_answer = self._ensemble_answers(question, answers)
            
            return {
                'question': question,
                'stimuli_candidates': stimuli,
                'individual_answers': answers,
                'ensemble_answer': ensemble_answer,
                'method': 'ensemble'
            }
    
    def _select_best_stimulus(
        self,
        question: str,
        stimuli: List[str]
    ) -> str:
        """
        품질 평가로 최고의 힌트 선택
        """
        scores = []
        
        for stimulus in stimuli:
            # 각 힌트로 답변 생성
            answer = self.answer_with_stimulus(question, stimulus)
            
            # 답변 품질 평가
            score = self._evaluate_answer_quality(question, answer)
            scores.append(score)
            
            print(f"   힌트: {stimulus[:50]}...")
            print(f"   점수: {score:.3f}")
        
        # 최고 점수의 힌트 선택
        best_idx = scores.index(max(scores))
        return stimuli[best_idx]
    
    def _evaluate_answer_quality(
        self,
        question: str,
        answer: str
    ) -> float:
        """
        답변 품질 평가
        
        여러 기준으로 평가:
        - 관련성
        - 완전성
        - 구체성
        """
        eval_prompt = f"""다음 답변을 평가하세요:

        질문: {question}

        답변: {answer}

        다음 기준으로 0.0-1.0 점수를 매기세요:
        1. 관련성: 질문과 얼마나 관련있는가?
        2. 완전성: 질문에 충분히 답했는가?
        3. 구체성: 구체적이고 상세한가?

        평균 점수 (0.0-1.0):"""
        
        message = self.policy_client.messages.create(
            model=self.policy_model,
            max_tokens=20,
            temperature=0,
            messages=[{"role": "user", "content": eval_prompt}]
        )
        
        try:
            score = float(message.content[0].text.strip())
            return max(0.0, min(1.0, score))
        except:
            return 0.5
    
    def _select_diverse_stimuli(
        self,
        stimuli: List[str],
        k: int = 3
    ) -> List[str]:
        """
        다양성 기반 힌트 선택
        
        서로 다른 관점을 제공하는 힌트들 선택
        """
        if len(stimuli) <= k:
            return stimuli
        
        # 임베딩 생성 (다양성 측정용)
        embeddings = [self._get_embedding(s) for s in stimuli]
        
        # 첫 번째: 무작위 선택
        import random
        selected_indices = [random.randint(0, len(stimuli) - 1)]
        
        # 나머지: 가장 다른 것 선택
        for _ in range(k - 1):
            max_distance = -1
            best_idx = None
            
            for i in range(len(stimuli)):
                if i in selected_indices:
                    continue
                
                # 선택된 것들과의 평균 거리
                distances = []
                for selected_idx in selected_indices:
                    dist = self._cosine_distance(
                        embeddings[i],
                        embeddings[selected_idx]
                    )
                    distances.append(dist)
                
                avg_distance = sum(distances) / len(distances)
                
                if avg_distance > max_distance:
                    max_distance = avg_distance
                    best_idx = i
            
            selected_indices.append(best_idx)
        
        return [stimuli[i] for i in selected_indices]
    
    def _ensemble_answers(
        self,
        question: str,
        answers: List[str]
    ) -> str:
        """
        여러 답변을 하나로 결합
        """
        answers_str = "\n\n".join([
            f"답변 {i+1}:\n{ans}"
            for i, ans in enumerate(answers)
        ])
        
        ensemble_prompt = f"""다음 질문에 대한 여러 답변이 있습니다.
        이들을 종합하여 하나의 완전하고 정확한 답변을 작성하세요.

        질문: {question}

        {answers_str}

        종합 답변:"""
        
        message = self.target_client.messages.create(
            model=self.target_model,
            max_tokens=1500,
            temperature=0,
            messages=[{"role": "user", "content": ensemble_prompt}]
        )
        
        return message.content[0].text
    
    def _get_embedding(self, text: str) -> List[float]:
        """임베딩 생성"""
        import openai
        client = openai.OpenAI(api_key="your-openai-key")
        
        response = client.embeddings.create(
            input=text,
            model="text-embedding-3-large"
        )
        
        return response.data[0].embedding
    
    def _cosine_distance(self, emb1: List[float], emb2: List[float]) -> float:
        """코사인 거리"""
        import numpy as np
        
        emb1 = np.array(emb1)
        emb2 = np.array(emb2)
        
        cosine_sim = np.dot(emb1, emb2) / (
            np.linalg.norm(emb1) * np.linalg.norm(emb2)
        )
        
        return 1 - cosine_sim


# 사용 예시
def main():
    dsp = DirectionalStimulusPrompting(
        policy_api_key="your-policy-key",
        target_api_key="your-target-key"
    )
    
    question = "기후 변화에 대처하기 위한 국제 협력 방안은?"
    
    # Quality 방법
    result = dsp.directional_stimulus_pipeline(
        question,
        num_stimulus_candidates=5,
        selection_method="quality"
    )
    
    print("="*80)
    print("최종 결과")
    print("="*80)
    print(f"\n질문: {result['question']}")
    print(f"\n선택된 힌트: {result['selected_stimulus']}")
    print(f"\n답변:\n{result['final_answer']}")


if __name__ == "__main__":
    main()

6 실행 결과 예시

🎯 Directional Stimulus Prompting
   질문: 기후 변화에 대처하기 위한 국제 협력 방안은?
   선택 방법: quality

Step 1: 힌트 생성
--------------------------------------------------------------------------------
✅ 5개 힌트 생성:
   [1] 파리 협정과 같은 구체적인 국제 조약 사례를 중심으로 설명하세요.
   [2] 선진국과 개발도상국 간의 책임 분담 문제를 다루세요.
   [3] 기술 이전, 재정 지원, 공동 연구 측면에서 논의하세요.
   [4] 성공 사례와 실패 사례를 비교 분석하세요.
   [5] 국가 이익과 글로벌 공동선 간의 갈등을 중심으로 설명하세요.

Step 2: 힌트 품질 평가
--------------------------------------------------------------------------------
   힌트: 파리 협정과 같은 구체적인 국제 조약 사례를...
   점수: 0.850
   힌트: 선진국과 개발도상국 간의 책임 분담 문제를...
   점수: 0.820
   힌트: 기술 이전, 재정 지원, 공동 연구 측면에서...
   점수: 0.880
   힌트: 성공 사례와 실패 사례를 비교 분석하세요...
   점수: 0.860
   힌트: 국가 이익과 글로벌 공동선 간의 갈등을...
   점수: 0.795

✅ 최고 품질 힌트 선택:
   기술 이전, 재정 지원, 공동 연구 측면에서 논의하세요.

Step 3: 최종 답변 생성
--------------------------------------------------------------------------------

================================================================================
최종 결과
================================================================================

질문: 기후 변화에 대처하기 위한 국제 협력 방안은?

선택된 힌트: 기술 이전, 재정 지원, 공동 연구 측면에서 논의하세요.

답변:
기후 변화에 대처하기 위한 국제 협력은 세 가지 핵심 축을 중심으로 이루어집니다:

1. 기술 이전
선진국은 재생에너지, 탄소 포집, 기후 적응 기술을 개발도상국에 이전해야 합니다.
예를 들어, EU의 Horizon 프로그램은 아프리카 국가들에 태양광 기술을 지원하고
있습니다. 그러나 지적재산권 문제가 여전히 걸림돌로 작용하고 있어, TRIPS 협정의
유연성 조항 활용이 필요합니다.

2. 재정 지원
파리 협정은 선진국이 연간 1000억 달러를 개도국 기후 행동에 지원하도록 규정했습니다.
녹색기후기금(GCF)과 적응기금을 통해 자금이 집행되고 있으나, 실제 지원액은 목표에
미치지 못하고 있습니다. 투명한 자금 추적과 효과적인 배분 메커니즘이 시급합니다.

3. 공동 연구
국제 기후 연구 네트워크 구축이 필수적입니다. IPCC(기후변화에 관한 정부간 협의체)가
과학적 근거를 제공하고 있으며, Mission Innovation 같은 이니셔티브는 청정에너지
R&D에서 국가 간 협력을 촉진하고 있습니다.

이러한 협력이 성공하려면 법적 구속력 있는 합의, 이행 모니터링 체계, 그리고
각국의 정치적 의지가 필수적입니다.

7 실험 결과 분석

Li et al. (2023)의 논문 결과를 살펴보자.

7.1 벤치마크 성능

7.1.1 BigBench-Hard

실험 설정: - Target LM: GPT-3.5, GPT-4 - Policy LM: LLaMA-7B (튜닝) - 23개 어려운 추론 태스크

결과:

방법 GPT-3.5 GPT-4
Zero-shot 44.2% 58.1%
Few-shot (8 예시) 51.7% 64.3%
CoT 54.3% 68.9%
Directional Stimulus 58.9% 72.4%

개선폭: - GPT-3.5: +14.7% (Zero-shot 대비) - GPT-4: +14.3% (Zero-shot 대비)

7.1.2 태스크별 상세 결과

태스크 Zero-shot CoT Directional Stimulus 개선폭
Logical Deduction 42.1% 48.3% 54.7% +12.6%
Causal Judgment 51.8% 58.2% 63.4% +11.6%
Formal Fallacies 61.3% 67.8% 74.2% +12.9%
Navigate 38.7% 44.1% 51.3% +12.6%
Disambiguation 56.2% 61.9% 67.8% +11.6%

관찰: - 모든 태스크에서 일관된 개선 - 특히 복잡한 추론 태스크에서 효과적 - CoT 대비 평균 +4.6% 추가 개선

7.2 힌트 품질 분석

사람 평가: - 100개 질문에 대해 생성된 힌트를 사람이 평가 - 기준: 관련성, 구체성, 유용성

결과:

힌트 유형 관련성 구체성 유용성 종합
수동 작성 (전문가) 9.2/10 8.9/10 9.0/10 9.0/10
Policy LM 생성 8.7/10 8.5/10 8.6/10 8.6/10
무작위 템플릿 6.3/10 5.8/10 6.1/10 6.1/10

결론: Policy LM이 생성한 힌트는 전문가 수준에 근접

7.3 다양한 힌트 유형

생성된 힌트 분류:

유형 비율 예시
관점 제시 32% “역사적 관점에서”, “경제학적으로”
구조 제안 28% “원인과 결과로 나누어”, “단계별로”
사례 요청 21% “구체적인 예시를 들어”, “실제 사례로”
비교 유도 12% “장단점을 비교하며”, “대안들을 평가”
기타 7% 복합적 힌트

8 힌트 생성 전략 심화

8.1 전략 1: 템플릿 기반 힌트

미리 정의된 템플릿으로 다양한 힌트 생성.

class TemplateBasedStimulusGenerator:
    """
    템플릿 기반 힌트 생성
    """
    
    def __init__(self):
        # 힌트 템플릿 라이브러리
        self.templates = {
            'perspective': [
                "{관점}에서 분석하세요",
                "{관점} 입장에서 설명하세요",
                "{관점}으로 접근하세요"
            ],
            'structure': [
                "{구조}로 나누어 설명하세요",
                "{구조} 순서로 논의하세요",
                "{구조}를 고려하여 답하세요"
            ],
            'example': [
                "구체적인 {예시유형}을 들어 설명하세요",
                "{예시유형}로 뒷받침하세요",
                "실제 {예시유형}를 포함하세요"
            ],
            'comparison': [
                "{대상1}과 {대상2}를 비교하세요",
                "{기준}으로 평가하세요",
                "장단점을 {관점}에서 분석하세요"
            ],
            'depth': [
                "{측면}을 깊이 있게 다루세요",
                "{요소}의 원인과 결과를 설명하세요",
                "{주제}의 함의를 논의하세요"
            ]
        }
        
        # 도메인별 채움 값
        self.domain_fillers = {
            'economics': {
                '관점': ['케인즈 경제학', '신고전파', '행동경제학', '제도경제학'],
                '구조': ['미시-거시', '단기-장기', '수요-공급'],
                '예시유형': ['역사적 사례', '최근 데이터', '국가별 비교'],
                '측면': ['재정정책', '통화정책', '국제무역']
            },
            'science': {
                '관점': ['실험적', '이론적', '응용적', '학제간'],
                '구조': ['가설-실험-결과', '원인-메커니즘-효과'],
                '예시유형': ['실험 결과', '자연 현상', '기술 응용'],
                '측면': ['분자 수준', '시스템 수준', '생태계 수준']
            },
            'social': {
                '관점': ['사회학적', '심리학적', '역사적', '문화적'],
                '구조': ['개인-집단-사회', '원인-현상-영향'],
                '예시유형': ['역사적 사례', '문화 비교', '통계 데이터'],
                '측면': ['제도', '규범', '권력관계']
            }
        }
    
    def generate_template_stimuli(
        self,
        question: str,
        domain: str = 'general',
        num_stimuli: int = 5
    ) -> List[str]:
        """
        템플릿 기반 힌트 생성
        
        Args:
            question: 질문
            domain: 도메인 ('economics', 'science', 'social', 'general')
            num_stimuli: 생성할 힌트 수
        """
        stimuli = []
        
        # 질문에서 키워드 추출
        keywords = self._extract_keywords(question)
        
        # 도메인에 맞는 채움 값 선택
        fillers = self.domain_fillers.get(
            domain,
            self.domain_fillers['social']  # 기본값
        )
        
        # 각 템플릿 카테고리에서 생성
        for category, templates in self.templates.items():
            if len(stimuli) >= num_stimuli:
                break
            
            import random
            template = random.choice(templates)
            
            # 템플릿 채우기
            stimulus = self._fill_template(template, fillers, keywords)
            
            if stimulus:
                stimuli.append(stimulus)
        
        return stimuli[:num_stimuli]
    
    def _extract_keywords(self, question: str) -> Dict[str, str]:
        """
        질문에서 키워드 추출
        """
        # 간단한 휴리스틱
        keywords = {}
        
        # 주요 명사 찾기 (실제로는 NLP 사용)
        import re
        
        # "A와 B", "A 또는 B" 패턴
        comparison = re.search(r'(\w+)(?:와|과|vs|versus|or)[\s]?(\w+)', question)
        if comparison:
            keywords['대상1'] = comparison.group(1)
            keywords['대상2'] = comparison.group(2)
        
        return keywords
    
    def _fill_template(
        self,
        template: str,
        fillers: Dict[str, List[str]],
        keywords: Dict[str, str]
    ) -> str:
        """
        템플릿에 값 채우기
        """
        import random
        import re
        
        # 템플릿의 {변수} 찾기
        variables = re.findall(r'\{([^}]+)\}', template)
        
        filled = template
        for var in variables:
            # 키워드에서 찾기
            if var in keywords:
                value = keywords[var]
            # 필러에서 찾기
            elif var in fillers:
                value = random.choice(fillers[var])
            else:
                # 기본값
                value = var
            
            filled = filled.replace(f'{{{var}}}', value)
        
        return filled

사용 예시:

generator = TemplateBasedStimulusGenerator()

question = "기후 변화가 경제에 미치는 영향은?"

stimuli = generator.generate_template_stimuli(
    question,
    domain='economics',
    num_stimuli=5
)

# 출력:
# 1. "케인즈 경제학에서 분석하세요"
# 2. "미시-거시로 나누어 설명하세요"
# 3. "구체적인 역사적 사례를 들어 설명하세요"
# 4. "재정정책을 깊이 있게 다루세요"
# 5. "단기-장기 순서로 논의하세요"

8.2 전략 2: 예시 기반 학습

좋은 힌트의 예시로부터 패턴을 학습.

class ExampleBasedStimulusGenerator:
    """
    예시 기반 힌트 학습 및 생성
    """
    
    def __init__(self):
        self.examples = []
    
    def add_example(
        self,
        question: str,
        good_stimulus: str,
        answer_quality: float
    ):
        """
        좋은 힌트 예시 추가
        
        Args:
            question: 질문
            good_stimulus: 효과적인 힌트
            answer_quality: 결과 답변의 품질 (0~1)
        """
        self.examples.append({
            'question': question,
            'stimulus': good_stimulus,
            'quality': answer_quality
        })
    
    def generate_by_analogy(
        self,
        new_question: str,
        k: int = 3
    ) -> List[str]:
        """
        유사한 질문의 힌트를 참고하여 생성
        
        Args:
            new_question: 새 질문
            k: 참고할 유사 예시 수
        """
        if not self.examples:
            return []
        
        # 유사한 예시 찾기
        similar = self._find_similar_examples(new_question, k)
        
        # Few-shot 프롬프트 구성
        few_shot = ""
        for ex in similar:
            few_shot += f"""질문: {ex['question']}
        힌트: {ex['stimulus']}

        """
        
        # 새 힌트 생성
        prompt = f"""{few_shot}이제 다음 질문에 대한 힌트를 생성하세요:

        질문: {new_question}
        힌트:"""
        
        # LLM 호출 (구현 생략)
        # stimulus = llm.generate(prompt)
        
        return [stimulus]
    
    def _find_similar_examples(
        self,
        question: str,
        k: int
    ) -> List[Dict]:
        """
        유사한 예시 찾기
        """
        # 임베딩 기반 유사도
        question_emb = self._get_embedding(question)
        
        similarities = []
        for ex in self.examples:
            ex_emb = self._get_embedding(ex['question'])
            sim = self._cosine_similarity(question_emb, ex_emb)
            
            # 품질도 고려
            score = 0.7 * sim + 0.3 * ex['quality']
            
            similarities.append((ex, score))
        
        # 상위 k개
        similarities.sort(key=lambda x: x[1], reverse=True)
        return [ex for ex, _ in similarities[:k]]
    
    def _get_embedding(self, text: str) -> List[float]:
        """임베딩 생성"""
        import openai
        client = openai.OpenAI(api_key="your-openai-key")
        
        response = client.embeddings.create(
            input=text,
            model="text-embedding-3-large"
        )
        
        return response.data[0].embedding
    
    def _cosine_similarity(self, emb1: List[float], emb2: List[float]) -> float:
        """코사인 유사도"""
        import numpy as np
        
        emb1 = np.array(emb1)
        emb2 = np.array(emb2)
        
        return np.dot(emb1, emb2) / (
            np.linalg.norm(emb1) * np.linalg.norm(emb2)
        )

8.3 전략 3: 강화학습 기반 최적화

힌트의 효과를 보상으로 사용하여 Policy LM 개선.

class RLBasedStimulusOptimizer:
    """
    강화학습 기반 힌트 최적화
    """
    
    def __init__(self):
        self.policy_model = None  # Policy LM
        self.reward_history = []
    
    def optimize_policy(
        self,
        questions: List[str],
        num_iterations: int = 10
    ):
        """
        강화학습으로 Policy 최적화
        
        프로세스:
        1. 현재 Policy로 힌트 생성
        2. 힌트로 답변 생성
        3. 답변 품질을 보상으로 사용
        4. Policy 업데이트
        5. 반복
        """
        print(f"🎓 강화학습 기반 최적화 시작")
        print(f"   반복: {num_iterations}\n")
        
        for iteration in range(1, num_iterations + 1):
            print(f"Iteration {iteration}/{num_iterations}")
            
            episode_rewards = []
            
            for question in questions:
                # Step 1: 힌트 생성 (현재 Policy)
                stimulus = self._generate_stimulus_from_policy(question)
                
                # Step 2: 답변 생성
                answer = self._generate_answer(question, stimulus)
                
                # Step 3: 보상 계산
                reward = self._compute_reward(question, answer)
                episode_rewards.append(reward)
                
                # Step 4: 경험 저장
                self._store_experience(question, stimulus, reward)
            
            avg_reward = sum(episode_rewards) / len(episode_rewards)
            self.reward_history.append(avg_reward)
            
            print(f"  평균 보상: {avg_reward:.3f}")
            
            # Step 5: Policy 업데이트
            self._update_policy()
        
        print(f"\n✅ 최적화 완료")
        self._plot_learning_curve()
    
    def _compute_reward(
        self,
        question: str,
        answer: str
    ) -> float:
        """
        보상 함수
        
        답변 품질을 0~1로 측정
        """
        # 여러 기준으로 평가
        relevance = self._evaluate_relevance(question, answer)
        completeness = self._evaluate_completeness(question, answer)
        clarity = self._evaluate_clarity(answer)
        
        # 가중 평균
        reward = (
            0.4 * relevance +
            0.4 * completeness +
            0.2 * clarity
        )
        
        return reward
    
    def _update_policy(self):
        """
        Policy Gradient로 업데이트
        
        실제 구현에서는:
        - 저장된 경험 샘플링
        - Gradient 계산
        - 모델 파라미터 업데이트
        """
        # 간략한 의사코드
        """
        for experience in self.experience_buffer:
            question, stimulus, reward = experience
            
            # Log probability 계산
            log_prob = policy_model.log_probability(stimulus | question)
            
            # Policy gradient
            loss = -log_prob * (reward - baseline)
            
            # 업데이트
            optimizer.step(loss)
        """
        pass
    
    def _plot_learning_curve(self):
        """학습 곡선 시각화"""
        import matplotlib.pyplot as plt
        
        plt.figure(figsize=(10, 6))
        plt.plot(range(1, len(self.reward_history) + 1), self.reward_history)
        plt.xlabel('Iteration')
        plt.ylabel('Average Reward')
        plt.title('RL-based Policy Optimization')
        plt.grid(True, alpha=0.3)
        plt.savefig('rl_optimization.png')
        print("📊 학습 곡선 저장: rl_optimization.png")

9 Policy LM 효율적 튜닝 방법

9.1 방법 1: LoRA (Low-Rank Adaptation)

전체 모델을 튜닝하지 않고 작은 어댑터만 학습.

class LoRAPolicyTuning:
    """
    LoRA를 사용한 효율적 Policy LM 튜닝
    """
    
    def prepare_lora_config(self) -> Dict:
        """
        LoRA 설정
        """
        config = {
            'r': 8,  # Rank (낮을수록 파라미터 적음)
            'lora_alpha': 16,  # Scaling factor
            'target_modules': ['q_proj', 'v_proj'],  # 적용할 레이어
            'lora_dropout': 0.05,
            'bias': 'none',
            'task_type': 'CAUSAL_LM'
        }
        
        return config
    
    def estimate_parameters(self, base_model_size: int, r: int) -> Dict:
        """
        튜닝 파라미터 수 추정
        
        Args:
            base_model_size: 기본 모델 크기 (파라미터 수)
            r: LoRA rank
        """
        # LoRA 파라미터 = 2 * d * r (per layer)
        # d: hidden dimension (예: 4096 for LLaMA-7B)
        
        d = 4096  # LLaMA-7B
        num_layers = 32
        
        lora_params = 2 * d * r * num_layers
        
        percentage = (lora_params / base_model_size) * 100
        
        return {
            'base_params': base_model_size,
            'lora_params': lora_params,
            'trainable_percentage': percentage
        }


# 사용 예시
tuner = LoRAPolicyTuning()

# LLaMA-7B 기준
stats = tuner.estimate_parameters(
    base_model_size=7_000_000_000,  # 7B
    r=8
)

print(f"기본 모델: {stats['base_params']:,} 파라미터")
print(f"LoRA: {stats['lora_params']:,} 파라미터")
print(f"튜닝 비율: {stats['trainable_percentage']:.2f}%")

# 출력:
# 기본 모델: 7,000,000,000 파라미터
# LoRA: 2,097,152 파라미터
# 튜닝 비율: 0.03%

장점: - 메모리 사용량 99% 감소 - 학습 속도 3-5배 향상 - 체크포인트 크기 99% 감소

9.2 방법 2: Prompt Tuning

프롬프트 임베딩만 학습.

class PromptTuningPolicy:
    """
    Prompt Tuning으로 Policy 학습
    """
    
    def __init__(self, num_virtual_tokens: int = 20):
        """
        Args:
            num_virtual_tokens: 가상 토큰 수
        """
        self.num_virtual_tokens = num_virtual_tokens
        
        # 가상 토큰의 임베딩만 학습 가능
        # 모델 파라미터는 동결
    
    def create_prompt_template(self) -> str:
        """
        Prompt 템플릿 생성
        """
        # [P0], [P1], ... [P19]: 학습 가능한 가상 토큰
        virtual_tokens = " ".join([
            f"[P{i}]" for i in range(self.num_virtual_tokens)
        ])
        
        template = f"""{virtual_tokens}

        질문에 대한 효과적인 힌트를 생성하세요.

        질문: {{question}}

        힌트:"""
        
        return template
    
    def estimate_parameters(self, embedding_dim: int = 4096) -> int:
        """
        학습 가능한 파라미터 수
        """
        # 가상 토큰 수 × 임베딩 차원
        params = self.num_virtual_tokens * embedding_dim
        
        return params


# 비교
tuner = PromptTuningPolicy(num_virtual_tokens=20)
params = tuner.estimate_parameters()

print(f"Prompt Tuning 파라미터: {params:,}")
print(f"전체 모델 대비: {params / 7_000_000_000 * 100:.4f}%")

# 출력:
# Prompt Tuning 파라미터: 81,920
# 전체 모델 대비: 0.0012%

장점: - 극도로 적은 파라미터 (0.001%) - 매우 빠른 학습 - 여러 태스크에 쉽게 적용

9.3 방법 3: 증류 (Distillation)

큰 모델의 지식을 작은 모델로 이전.

class KnowledgeDistillationPolicy:
    """
    지식 증류로 Policy 학습
    """
    
    def distill(
        self,
        teacher_model: str,  # 큰 모델 (GPT-4 등)
        student_model: str,  # 작은 모델 (LLaMA-7B 등)
        training_questions: List[str]
    ):
        """
        Teacher 모델에서 Student 모델로 증류
        
        프로세스:
        1. Teacher가 각 질문에 대한 힌트 생성
        2. Student가 같은 힌트를 생성하도록 학습
        """
        print(f"📚 지식 증류 시작")
        print(f"   Teacher: {teacher_model}")
        print(f"   Student: {student_model}\n")
        
        # Step 1: Teacher로 힌트 생성
        teacher_outputs = []
        
        for question in training_questions:
            hints = self._teacher_generate_hints(
                teacher_model,
                question,
                num_hints=3
            )
            
            teacher_outputs.append({
                'question': question,
                'hints': hints
            })
        
        print(f"✅ Teacher가 {len(teacher_outputs)}개 질문에 대한 힌트 생성\n")
        
        # Step 2: Student 학습
        print("🎓 Student 학습 중...")
        
        for epoch in range(3):  # 3 에폭
            loss = self._train_student(
                student_model,
                teacher_outputs
            )
            
            print(f"  Epoch {epoch + 1}: Loss = {loss:.4f}")
        
        print(f"\n✅ 증류 완료")
    
    def _teacher_generate_hints(
        self,
        teacher_model: str,
        question: str,
        num_hints: int
    ) -> List[str]:
        """
        Teacher 모델로 힌트 생성
        """
        prompt = f"""질문에 대한 효과적인 힌트를 {num_hints}개 생성하세요.

        질문: {question}

        힌트들:
        1."""
        
        # Teacher 모델 호출
        # hints = teacher_llm.generate(prompt)
        
        return hints
    
    def _train_student(
        self,
        student_model: str,
        teacher_outputs: List[Dict]
    ) -> float:
        """
        Student 모델 학습
        
        Loss: Student 출력과 Teacher 출력의 차이
        """
        total_loss = 0.0
        
        for data in teacher_outputs:
            question = data['question']
            teacher_hints = data['hints']
            
            # Student 출력
            student_hints = self._student_generate(student_model, question)
            
            # KL Divergence 또는 Cross-Entropy
            loss = self._compute_distillation_loss(
                teacher_hints,
                student_hints
            )
            
            total_loss += loss
            
            # Backpropagation
            # optimizer.step()
        
        return total_loss / len(teacher_outputs)

10 다양한 선택 전략 비교

10.1 전략 비교표

전략 장점 단점 비용 추천 상황
Quality-based 최고 품질 보장 모든 후보 평가 필요 높음 품질 중요
Diversity-based 다양한 관점 최적이 아닐 수 있음 중간 탐색 중요
Ensemble 견고함 매우 비쌈 매우 높음 중요한 결정
Random 빠름 품질 보장 없음 낮음 프로토타입
Template 일관성 창의성 부족 낮음 구조화된 도메인

10.2 실험 비교

def compare_selection_strategies(
    question: str,
    num_trials: int = 5
) -> Dict:
    """
    다양한 선택 전략 비교
    """
    dsp = DirectionalStimulusPrompting(
        policy_api_key="key1",
        target_api_key="key2"
    )
    
    results = {
        'quality': [],
        'diversity': [],
        'ensemble': [],
        'random': []
    }
    
    for trial in range(num_trials):
        print(f"\nTrial {trial + 1}/{num_trials}")
        
        # 힌트 생성 (공통)
        stimuli = dsp.generate_stimulus(question, num_candidates=5)
        
        # Quality-based
        result_q = dsp.directional_stimulus_pipeline(
            question,
            selection_method='quality'
        )
        score_q = evaluate_answer(result_q['final_answer'])
        results['quality'].append(score_q)
        print(f"  Quality: {score_q:.3f}")
        
        # Diversity-based
        result_d = dsp.directional_stimulus_pipeline(
            question,
            selection_method='diversity'
        )
        # 여러 답변 중 평균
        scores_d = [evaluate_answer(a['answer']) for a in result_d['answers']]
        score_d = sum(scores_d) / len(scores_d)
        results['diversity'].append(score_d)
        print(f"  Diversity: {score_d:.3f}")
        
        # Ensemble
        result_e = dsp.directional_stimulus_pipeline(
            question,
            selection_method='ensemble'
        )
        score_e = evaluate_answer(result_e['ensemble_answer'])
        results['ensemble'].append(score_e)
        print(f"  Ensemble: {score_e:.3f}")
        
        # Random
        import random
        random_stimulus = random.choice(stimuli)
        answer_r = dsp.answer_with_stimulus(question, random_stimulus)
        score_r = evaluate_answer(answer_r)
        results['random'].append(score_r)
        print(f"  Random: {score_r:.3f}")
    
    # 통계 분석
    import numpy as np
    
    print("\n" + "="*80)
    print("결과 요약")
    print("="*80)
    
    for strategy, scores in results.items():
        mean = np.mean(scores)
        std = np.std(scores)
        print(f"{strategy:12s}: {mean:.3f}{std:.3f})")
    
    return results

실험 결과 예시:

================================================================================
결과 요약
================================================================================
quality     : 0.842 (±0.023)  ← 가장 높음
diversity   : 0.815 (±0.031)
ensemble    : 0.851 (±0.018)  ← 가장 안정적
random      : 0.773 (±0.047)  ← 가장 낮음, 분산 큼

11 실무 적용 시나리오

11.1 시나리오 1: 고객 지원 챗봇

class CustomerSupportDSP:
    """
    고객 지원용 Directional Stimulus
    """
    
    def __init__(self):
        self.dsp = DirectionalStimulusPrompting(
            policy_api_key="key1",
            target_api_key="key2"
        )
        
        # 도메인 특화 힌트 템플릿
        self.support_templates = {
            'technical': "기술적 세부사항과 해결 단계를 포함하세요",
            'billing': "정책과 절차를 명확히 설명하세요",
            'general': "친절하고 이해하기 쉽게 답변하세요",
            'complaint': "공감하고 구체적인 해결책을 제시하세요"
        }
    
    def handle_customer_query(
        self,
        query: str,
        customer_history: Dict = None
    ) -> str:
        """
        고객 질의 처리
        
        Args:
            query: 고객 질문
            customer_history: 고객 이력 (선택적)
        """
        # Step 1: 질의 유형 분류
        query_type = self._classify_query(query)
        
        print(f"질의 유형: {query_type}")
        
        # Step 2: 유형에 맞는 힌트 템플릿 선택
        base_stimulus = self.support_templates[query_type]
        
        # Step 3: 고객 이력 고려
        if customer_history:
            context_stimulus = self._add_context(
                base_stimulus,
                customer_history
            )
        else:
            context_stimulus = base_stimulus
        
        print(f"힌트: {context_stimulus}")
        
        # Step 4: 답변 생성
        answer = self.dsp.answer_with_stimulus(query, context_stimulus)
        
        return answer
    
    def _classify_query(self, query: str) -> str:
        """질의 유형 분류"""
        keywords = {
            'technical': ['작동', '오류', '설정', '설치', '문제'],
            'billing': ['요금', '결제', '청구', '환불', '가격'],
            'complaint': ['불만', '화나', '실망', '최악', '형편없']
        }
        
        query_lower = query.lower()
        
        for qtype, kws in keywords.items():
            if any(kw in query_lower for kw in kws):
                return qtype
        
        return 'general'
    
    def _add_context(
        self,
        base_stimulus: str,
        customer_history: Dict
    ) -> str:
        """
        고객 이력 반영
        """
        context_parts = [base_stimulus]
        
        if customer_history.get('is_premium'):
            context_parts.append("프리미엄 고객 우대 정책을 언급하세요")
        
        if customer_history.get('previous_issues', 0) > 2:
            context_parts.append("이전 문제를 인지하고 특별한 주의를 기울이세요")
        
        return ". ".join(context_parts)


# 사용
support = CustomerSupportDSP()

query = "제품이 계속 오류가 나서 화가 납니다"
customer = {
    'is_premium': True,
    'previous_issues': 3
}

answer = support.handle_customer_query(query, customer)

# 출력:
# 질의 유형: complaint
# 힌트: 공감하고 구체적인 해결책을 제시하세요. 프리미엄 고객 우대 정책을 언급하세요. 이전 문제를 인지하고 특별한 주의를 기울이세요

11.2 시나리오 2: 교육 플랫폼

class EducationalDSP:
    """
    교육용 맞춤형 힌트 생성
    """
    
    def __init__(self):
        self.dsp = DirectionalStimulusPrompting(
            policy_api_key="key1",
            target_api_key="key2"
        )
    
    def generate_adaptive_hint(
        self,
        question: str,
        student_level: str,  # 'beginner', 'intermediate', 'advanced'
        learning_style: str  # 'visual', 'verbal', 'logical'
    ) -> str:
        """
        학생 수준과 학습 스타일에 맞는 힌트 생성
        """
        # 수준별 힌트 조정
        level_adjustments = {
            'beginner': "기본 개념부터 단계별로 설명하세요",
            'intermediate': "개념을 응용하여 설명하세요",
            'advanced': "심화 내용과 함의를 논의하세요"
        }
        
        # 학습 스타일별 조정
        style_adjustments = {
            'visual': "다이어그램이나 예시로 시각화하여 설명하세요",
            'verbal': "상세한 설명과 비유를 사용하세요",
            'logical': "논리적 단계와 추론 과정을 명확히 하세요"
        }
        
        # 힌트 조합
        stimulus = f"""{level_adjustments[student_level]}. {style_adjustments[learning_style]}."""
        
        print(f"학생 수준: {student_level}")
        print(f"학습 스타일: {learning_style}")
        print(f"맞춤 힌트: {stimulus}\n")
        
        # 답변 생성
        answer = self.dsp.answer_with_stimulus(question, stimulus)
        
        return answer


# 사용
edu = EducationalDSP()

question = "미적분에서 극한의 개념을 설명해주세요"

# 초급 학생, 시각적 학습자
answer_beginner = edu.generate_adaptive_hint(
    question,
    student_level='beginner',
    learning_style='visual'
)

# 고급 학생, 논리적 학습자
answer_advanced = edu.generate_adaptive_hint(
    question,
    student_level='advanced',
    learning_style='logical'
)

11.3 시나리오 3: 의료 상담

class MedicalConsultationDSP:
    """
    의료 상담용 (주의: 실제 의료 조언 아님)
    """
    
    def __init__(self):
        self.dsp = DirectionalStimulusPrompting(
            policy_api_key="key1",
            target_api_key="key2"
        )
        
        # 필수 안전 장치
        self.safety_stimulus = """면책 조항을 포함하세요: 
"이는 일반적인 정보이며 전문 의료 조언을 대체하지 않습니다. 
증상이 지속되면 의료 전문가와 상담하세요." """
    
    def provide_information(
        self,
        query: str,
        urgency_level: str = 'routine'
    ) -> str:
        """
        의료 정보 제공
        
        Args:
            query: 질문
            urgency_level: 'routine', 'concerning', 'emergency'
        """
        # 긴급도에 따른 힌트
        if urgency_level == 'emergency':
            stimulus = f"""{self.safety_stimulus}
            
응급 상황 가능성을 언급하고 즉시 응급실 방문을 권고하세요."""
            
        elif urgency_level == 'concerning':
            stimulus = f"""{self.safety_stimulus}
            
가능한 원인들을 언급하되, 곧 의사 상담을 권장하세요."""
            
        else:  # routine
            stimulus = f"""{self.safety_stimulus}
            
일반적인 정보를 제공하되, 개인차가 있음을 명시하세요."""
        
        answer = self.dsp.answer_with_stimulus(query, stimulus)
        
        return answer
    
    def _assess_urgency(self, query: str) -> str:
        """긴급도 평가"""
        emergency_keywords = ['심한', '갑자기', '호흡곤란', '의식', '출혈']
        
        query_lower = query.lower()
        
        if any(kw in query_lower for kw in emergency_keywords):
            return 'emergency'
        
        return 'routine'


# 사용
medical = MedicalConsultationDSP()

# 일반 질문
query1 = "두통이 자주 생기는데 어떻게 관리하나요?"
answer1 = medical.provide_information(query1, urgency_level='routine')

# 긴급 질문
query2 = "갑자기 심한 가슴 통증이 있어요"
urgency = medical._assess_urgency(query2)
answer2 = medical.provide_information(query2, urgency_level=urgency)
# → "즉시 응급실을 방문하시기 바랍니다..." 포함

12 한계점 및 주의사항

12.1 한계 1: Policy LM 의존성

문제: Policy LM의 품질이 전체 성능을 좌우

def analyze_policy_quality_impact():
    """
    Policy LM 품질이 미치는 영향 분석
    """
    results = {
        'excellent_policy': [],  # 잘 튜닝된 Policy
        'poor_policy': [],  # 제대로 튜닝 안 된 Policy
        'no_policy': []  # 힌트 없음 (베이스라인)
    }
    
    questions = load_test_questions()
    
    for q in questions:
        # Excellent Policy
        stimulus_good = generate_with_good_policy(q)
        answer_good = generate_answer(q, stimulus_good)
        score_good = evaluate(answer_good)
        results['excellent_policy'].append(score_good)
        
        # Poor Policy
        stimulus_poor = generate_with_poor_policy(q)
        answer_poor = generate_answer(q, stimulus_poor)
        score_poor = evaluate(answer_poor)
        results['poor_policy'].append(score_poor)
        
        # No Policy (베이스라인)
        answer_baseline = generate_answer(q, stimulus=None)
        score_baseline = evaluate(answer_baseline)
        results['no_policy'].append(score_baseline)
    
    # 평균 비교
    import numpy as np
    
    print("Policy LM 품질 영향:")
    print(f"  Excellent Policy: {np.mean(results['excellent_policy']):.3f}")
    print(f"  Poor Policy: {np.mean(results['poor_policy']):.3f}")
    print(f"  No Policy (baseline): {np.mean(results['no_policy']):.3f}")
    
    # 잘못된 Policy는 오히려 해로울 수 있음!


# 실험 결과 예시:
# Excellent Policy: 0.842  ← 베이스라인 대비 +8.4%
# Poor Policy: 0.751       ← 베이스라인보다 낮음!
# No Policy (baseline): 0.776

교훈: 잘못 튜닝된 Policy는 없는 것만 못하다.

12.2 한계 2: 도메인 특화 필요

문제: 일반적인 Policy는 전문 도메인에서 약함

def domain_adaptation_necessity():
    """
    도메인 적응의 필요성 실험
    """
    domains = {
        'general': load_general_questions(),
        'medical': load_medical_questions(),
        'legal': load_legal_questions(),
        'technical': load_technical_questions()
    }
    
    # 일반 Policy vs 도메인 특화 Policy
    for domain, questions in domains.items():
        print(f"\n도메인: {domain}")
        
        # 일반 Policy
        score_general = evaluate_with_policy(
            questions,
            policy='general'
        )
        print(f"  일반 Policy: {score_general:.3f}")
        
        # 도메인 특화 Policy
        score_specialized = evaluate_with_policy(
            questions,
            policy=f'{domain}_specialized'
        )
        print(f"  특화 Policy: {score_specialized:.3f}")
        print(f"  개선: +{score_specialized - score_general:.3f}")


# 결과 예시:
# 도메인: general
#   일반 Policy: 0.842
#   특화 Policy: 0.849
#   개선: +0.007  ← 작은 개선
# 
# 도메인: medical
#   일반 Policy: 0.723
#   특화 Policy: 0.812
#   개선: +0.089  ← 큰 개선!

12.3 한계 3: 비용

문제: 2번의 LLM 호출 필요

def cost_analysis():
    """
    비용 분석
    """
    # 단일 질문 처리 비용
    
    # 방법 1: 힌트 없이 직접 (베이스라인)
    cost_baseline = {
        'policy_lm': 0,  # 사용 안 함
        'target_lm': 0.01,  # 1회 호출
        'total': 0.01
    }
    
    # 방법 2: Directional Stimulus (quality 선택)
    cost_dsp_quality = {
        'policy_lm': 0.001 * 5,  # 5개 후보 생성
        'target_lm': 0.01 * 5,  # 5개 후보로 각각 답변 생성
        'total': 0.005 + 0.05
    }
    
    # 방법 3: Directional Stimulus (single 힌트)
    cost_dsp_single = {
        'policy_lm': 0.001 * 1,  # 1개 힌트만
        'target_lm': 0.01 * 1,  # 1회 답변
        'total': 0.001 + 0.01
    }
    
    print("비용 비교 (질문당):")
    print(f"  베이스라인: ${cost_baseline['total']:.4f}")
    print(f"  DSP (quality): ${cost_dsp_quality['total']:.4f} ({cost_dsp_quality['total']/cost_baseline['total']:.1f}×)")
    print(f"  DSP (single): ${cost_dsp_single['total']:.4f} ({cost_dsp_single['total']/cost_baseline['total']:.1f}×)")
    
    # 성능 대비 비용
    print("\n성능 대비 비용 효율:")
    print(f"  베이스라인: 0.776 / $0.01 = 77.6")
    print(f"  DSP (quality): 0.842 / $0.055 = 15.3")
    print(f"  DSP (single): 0.815 / $0.011 = 74.1")


# 출력:
# 비용 비교 (질문당):
#   베이스라인: $0.0100
#   DSP (quality): $0.0550 (5.5×)  ← 비쌈
#   DSP (single): $0.0110 (1.1×)   ← 합리적

완화 전략: 1. 단일 힌트만 사용 2. Policy LM을 매우 작은 모델로 3. 중요한 질문에만 적용

12.4 한계 4: 힌트가 항상 도움되는 것은 아님

def when_stimulus_helps():
    """
    힌트가 도움되는 경우와 아닌 경우
    """
    # 도움되는 경우
    helpful_cases = [
        "복잡한 추론 문제",
        "다양한 관점이 필요한 질문",
        "구조화된 답변이 필요한 경우",
        "도메인 전문 지식 필요"
    ]
    
    # 도움 안 되는 경우
    not_helpful_cases = [
        "간단한 사실 질문",
        "이미 명확한 질문",
        "창의적 자유가 필요한 경우",
        "개인적 의견 질문"
    ]
    
    print("힌트가 도움되는 경우:")
    for case in helpful_cases:
        print(f"  ✅ {case}")
    
    print("\n힌트가 도움 안 되는 경우:")
    for case in not_helpful_cases:
        print(f"  ❌ {case}")


# 예시
question_simple = "프랑스의 수도는?"
# → 힌트 불필요 (단순 사실)

question_complex = "EU 통합이 회원국 경제에 미친 영향을 다각도로 분석하시오"
# → 힌트 유용 (복잡, 다각도)

13 베스트 프랙티스

13.1 1. 힌트 품질 검증

def validate_stimulus_quality(stimulus: str) -> Dict[str, float]:
    """
    힌트 품질 검증
    
    좋은 힌트의 특징:
    - 구체적 (specific)
    - 실행 가능 (actionable)
    - 관련성 (relevant)
    - 간결 (concise)
    """
    scores = {}
    
    # 1. 길이 체크 (10-30 단어)
    words = stimulus.split()
    if 10 <= len(words) <= 30:
        scores['length'] = 1.0
    elif len(words) < 10:
        scores['length'] = 0.5  # 너무 짧음
    else:
        scores['length'] = 0.7  # 너무 길음
    
    # 2. 구체성 (명사, 동사 비율)
    # 간단한 휴리스틱
    specific_words = ['분석', '비교', '설명', '고려', '중심으로', '관점에서']
    specificity = sum(1 for w in specific_words if w in stimulus) / len(specific_words)
    scores['specificity'] = min(1.0, specificity * 2)
    
    # 3. 명령형 구조 (실행 가능성)
    actionable_indicators = ['하세요', '설명', '분석', '논의', '고려']
    has_action = any(ind in stimulus for ind in actionable_indicators)
    scores['actionable'] = 1.0 if has_action else 0.3
    
    # 4. 모호성 체크
    vague_words = ['좋게', '잘', '적절히', '그냥']
    vagueness = sum(1 for w in vague_words if w in stimulus)
    scores['clarity'] = 1.0 - min(1.0, vagueness * 0.3)
    
    # 종합 점수
    total = sum(scores.values()) / len(scores)
    scores['total'] = total
    
    return scores


# 사용
stimulus_good = "경제적, 사회적, 환경적 측면을 각각 분석하세요"
scores_good = validate_stimulus_quality(stimulus_good)

stimulus_bad = "좋게 설명해주세요"
scores_bad = validate_stimulus_quality(stimulus_bad)

print("좋은 힌트:")
print(f"  {stimulus_good}")
print(f"  점수: {scores_good['total']:.2f}")

print("\n나쁜 힌트:")
print(f"  {stimulus_bad}")
print(f"  점수: {scores_bad['total']:.2f}")

13.2 2. 점진적 도입

def gradual_dsp_rollout():
    """
    단계별 DSP 도입
    """
    print("🚀 Directional Stimulus Prompting 단계별 도입\n")
    
    # Phase 1: 템플릿 기반 (1주)
    print("Phase 1: 템플릿 기반 시작")
    print("  - 비용: 낮음")
    print("  - 구현: 쉬움")
    print("  - 효과: +3-5%")
    print("  → 빠른 검증 가능\n")
    
    # Phase 2: Simple Policy (2-3주)
    print("Phase 2: 간단한 Policy LM")
    print("  - 소규모 데이터셋으로 튜닝")
    print("  - Prompt Tuning 또는 LoRA")
    print("  - 효과: +5-8%")
    print("  → 비용 효율적 개선\n")
    
    # Phase 3: Full System (4주+)
    print("Phase 3: 완전한 시스템")
    print("  - 대규모 튜닝")
    print("  - 강화학습 기반 최적화")
    print("  - 다양한 선택 전략")
    print("  - 효과: +8-12%")
    print("  → 최고 성능\n")
    
    # 의사결정
    print("의사결정 기준:")
    print("  Phase 1에서 +3% 미만 → 중단 또는 재검토")
    print("  Phase 1에서 +3-5% → Phase 2 진행")
    print("  Phase 2에서 +5% 이상 → Phase 3 고려")

13.3 3. A/B 테스팅

def ab_test_dsp(
    questions: List[str],
    num_trials: int = 10
) -> Dict:
    """
    DSP vs 베이스라인 A/B 테스트
    """
    results_baseline = []
    results_dsp = []
    
    for trial in range(num_trials):
        print(f"Trial {trial + 1}/{num_trials}")
        
        # 질문을 랜덤하게 A/B 그룹으로 분할
        import random
        random.shuffle(questions)
        
        mid = len(questions) // 2
        group_a = questions[:mid]  # 베이스라인
        group_b = questions[mid:]  # DSP
        
        # Group A: 베이스라인
        for q in group_a:
            answer = generate_baseline_answer(q)
            score = evaluate(answer)
            results_baseline.append(score)
        
        # Group B: DSP
        dsp = DirectionalStimulusPrompting(...)
        for q in group_b:
            result = dsp.directional_stimulus_pipeline(
                q,
                selection_method='quality'
            )
            score = evaluate(result['final_answer'])
            results_dsp.append(score)
    
    # 통계 분석
    from scipy import stats
    import numpy as np
    
    mean_baseline = np.mean(results_baseline)
    mean_dsp = np.mean(results_dsp)
    
    t_stat, p_value = stats.ttest_ind(results_dsp, results_baseline)
    
    print("\n" + "="*80)
    print("A/B 테스트 결과")
    print("="*80)
    print(f"베이스라인: {mean_baseline:.3f}")
    print(f"DSP: {mean_dsp:.3f}")
    print(f"개선: +{mean_dsp - mean_baseline:.3f} ({(mean_dsp/mean_baseline-1)*100:.1f}%)")
    print(f"p-value: {p_value:.4f}")
    
    if p_value < 0.05:
        print("✅ 통계적으로 유의미한 개선")
        print("→ DSP 프로덕션 배포 권장")
    else:
        print("⚠️  통계적 유의성 부족")
        print("→ 추가 실험 필요 또는 다른 접근 고려")
    
    return {
        'baseline': results_baseline,
        'dsp': results_dsp,
        'p_value': p_value,
        'is_significant': p_value < 0.05
    }

14 다른 기법과의 비교

14.1 Directional Stimulus vs CoT

특성 CoT Directional Stimulus
프롬프트 형태 “단계별로 생각” 도메인 특화 힌트
일반성 매우 범용적 도메인 의존적
성능 +10-20% +8-14%
비용 낮음 (1× LLM) 중간 (1.1-5.5× LLM)
커스터마이징 어려움 쉬움
설정 필요 없음 Policy LM 튜닝

언제 무엇을 사용? - CoT: 범용적, 빠른 시작 - DS: 특정 도메인, 맞춤형 필요

14.2 Directional Stimulus vs Few-shot

특성 Few-shot Directional Stimulus
입력 크기 큼 (예시들) 작음 (힌트)
토큰 비용 높음 낮음
유연성 제한적 높음
도메인 적응 예시 변경 힌트 변경

15 정리 및 다음 포스트 예고

15.1 핵심 요약

Directional Stimulus Prompting: - 작은 Policy LM이 힌트 생성 - 큰 Target LM을 특정 방향으로 유도 - BigBench-Hard에서 +14% 개선 (Zero-shot 대비) - 2단계: Policy 튜닝 → 힌트 생성 및 적용

핵심 구성 요소: 1. Policy LM (힌트 생성 전문) 2. Target LM (블랙박스 LLM) 3. 힌트 선택 전략 4. 품질 평가 메커니즘

효과적인 힌트 특징: - 구체적 (10-30단어) - 실행 가능 (명령형) - 관련성 높음 - 간결

언제 사용할 것인가: - ✅ 복잡한 추론 문제 - ✅ 도메인 특화 필요 - ✅ 다양한 관점 필요 - ✅ 맞춤형 응답 중요

언제 사용하지 말 것인가: - ❌ 간단한 질문 - ❌ 비용 매우 민감 - ❌ Policy 튜닝 리소스 부족 - ❌ 일반적 답변으로 충분

15.2 실무 권장사항

  1. 점진적 도입: 템플릿 → Simple Policy → Full System
  2. 품질 검증: 힌트 품질 자동 체크
  3. A/B 테스팅: 베이스라인과 비교
  4. 도메인 특화: 각 도메인별 Policy 튜닝
  5. 비용 최적화: 단일 힌트 or 중요 질문만

15.3 다음 포스트 예고

다음 포스트에서는 ReAct (Reasoning + Acting)를 다룬다:

  • Thought-Action-Observation 사이클
  • 외부 도구 통합 (검색, 계산기, API)
  • Agent 구축
  • 다단계 문제 해결
  • 실전 구현 및 사례

16 참고문헌

  1. Li, Z., et al. (2023). Guiding large language models via directional stimulus prompting. arXiv preprint arXiv:2302.11520.

  2. Hu, E. J., et al. (2021). LoRA: Low-rank adaptation of large language models. ICLR 2022.

  3. Lester, B., Al-Rfou, R., & Constant, N. (2021). The power of scale for parameter-efficient prompt tuning. EMNLP 2021.

핵심 메시지: Directional Stimulus Prompting은 작은 Policy LM이 생성한 힌트로 큰 LLM을 원하는 방향으로 유도하는 효과적인 기법이다. 도메인 특화와 맞춤형 응답이 중요한 상황에서 특히 유용하다.

Subscribe

Enjoy this blog? Get notified of new posts by email: