Recent Posts
Recent Comments
반응형
«   2025/11   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
Archives
Today
Total
관리 메뉴

오늘도 공부

LLM Playground 구축 - 완벽 핸즈온 가이드 #1 본문

AI

LLM Playground 구축 - 완벽 핸즈온 가이드 #1

행복한 수지아빠 2025. 10. 31. 16:12
반응형

🎯 학습 목표

  • LLM API 기본 사용법 이해
  • 프롬프트 엔지니어링 기법 실습
  • 토크나이제이션과 파라미터 조정
  • 인터랙티브 웹 인터페이스 구축

📋 사전 준비

1. 개발 환경 설정

# Python 가상환경 생성
python -m venv llm-playground
source llm-playground/bin/activate  # Windows: llm-playground\Scripts\activate

# 필수 패키지 설치
pip install openai anthropic streamlit python-dotenv tiktoken
pip install pandas numpy matplotlib

2. API 키 발급

3. 프로젝트 구조 생성

mkdir llm-playground
cd llm-playground
mkdir -p src utils data
touch .env app.py

🚀 Step 1: 기본 LLM 호출 (15분)

src/basic_llm.py

import os
from openai import OpenAI
from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv()

class LLMClient:
    def __init__(self):
        self.openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
        self.anthropic_client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
    
    def call_gpt(self, prompt, model="gpt-4o-mini", temperature=0.7, max_tokens=500):
        """OpenAI GPT 호출"""
        try:
            response = self.openai_client.chat.completions.create(
                model=model,
                messages=[{"role": "user", "content": prompt}],
                temperature=temperature,
                max_tokens=max_tokens
            )
            return {
                "response": response.choices[0].message.content,
                "tokens": response.usage.total_tokens,
                "model": model
            }
        except Exception as e:
            return {"error": str(e)}
    
    def call_claude(self, prompt, model="claude-3-5-sonnet-20241022", temperature=0.7, max_tokens=500):
        """Anthropic Claude 호출"""
        try:
            response = self.anthropic_client.messages.create(
                model=model,
                max_tokens=max_tokens,
                temperature=temperature,
                messages=[{"role": "user", "content": prompt}]
            )
            return {
                "response": response.content[0].text,
                "tokens": response.usage.input_tokens + response.usage.output_tokens,
                "model": model
            }
        except Exception as e:
            return {"error": str(e)}

# 테스트
if __name__ == "__main__":
    client = LLMClient()
    
    prompt = "안녕하세요! AI에 대해 한 문장으로 설명해주세요."
    
    print("=== GPT-4 ===")
    gpt_result = client.call_gpt(prompt)
    print(gpt_result["response"])
    print(f"토큰: {gpt_result['tokens']}\n")
    
    print("=== Claude ===")
    claude_result = client.call_claude(prompt)
    print(claude_result["response"])
    print(f"토큰: {claude_result['tokens']}")

.env 파일 생성

OPENAI_API_KEY=your_openai_key_here
ANTHROPIC_API_KEY=your_anthropic_key_here

실행 및 확인

python src/basic_llm.py

✅ 체크포인트: 두 모델 모두 응답이 나오면 성공!


🎨 Step 2: 프롬프트 엔지니어링 템플릿 (20분)

src/prompt_templates.py

class PromptTemplates:
    """다양한 프롬프트 엔지니어링 기법"""
    
    @staticmethod
    def zero_shot(task):
        """Zero-shot: 예시 없이 바로 질문"""
        return f"{task}"
    
    @staticmethod
    def few_shot(task, examples):
        """Few-shot: 예시를 포함한 프롬프트"""
        example_text = "\n\n".join([
            f"입력: {ex['input']}\n출력: {ex['output']}" 
            for ex in examples
        ])
        return f"""다음 예시를 참고하여 작업을 수행하세요.

{example_text}

입력: {task}
출력:"""
    
    @staticmethod
    def chain_of_thought(task):
        """CoT: 단계별 사고 유도"""
        return f"""{task}

단계별로 생각해봅시다:
1. 먼저,
2. 그 다음,
3. 따라서,"""
    
    @staticmethod
    def react_prompt(task, tools):
        """ReAct: 추론과 행동을 결합"""
        tools_desc = "\n".join([f"- {tool}" for tool in tools])
        
        return f"""당신은 다음 도구들을 사용할 수 있습니다:
{tools_desc}

작업: {task}

다음 형식으로 생각하고 행동하세요:
Thought: [현재 상황 분석]
Action: [사용할 도구]
Observation: [도구 결과]
... (필요시 반복)
Final Answer: [최종 답변]

시작:"""
    
    @staticmethod
    def role_specific(role, task):
        """역할 기반 프롬프트"""
        return f"""당신은 {role}입니다.

{task}

{role}의 관점에서 전문적으로 답변해주세요."""

# 테스트
if __name__ == "__main__":
    from basic_llm import LLMClient
    
    client = LLMClient()
    templates = PromptTemplates()
    
    task = "한국의 수도는 어디인가요?"
    
    # 1. Zero-shot
    print("=== Zero-shot ===")
    prompt = templates.zero_shot(task)
    result = client.call_gpt(prompt, max_tokens=100)
    print(result["response"], "\n")
    
    # 2. Few-shot
    print("=== Few-shot ===")
    examples = [
        {"input": "일본의 수도는?", "output": "일본의 수도는 도쿄입니다."},
        {"input": "프랑스의 수도는?", "output": "프랑스의 수도는 파리입니다."}
    ]
    prompt = templates.few_shot(task, examples)
    result = client.call_gpt(prompt, max_tokens=100)
    print(result["response"], "\n")
    
    # 3. Chain of Thought
    print("=== Chain of Thought ===")
    complex_task = "서울에서 부산까지 가는 가장 빠른 방법은?"
    prompt = templates.chain_of_thought(complex_task)
    result = client.call_gpt(prompt, max_tokens=200)
    print(result["response"], "\n")
    
    # 4. ReAct
    print("=== ReAct ===")
    tools = ["검색", "계산기", "날씨 조회"]
    prompt = templates.react_prompt("오늘 서울 날씨는 어때?", tools)
    result = client.call_gpt(prompt, max_tokens=200)
    print(result["response"])

🔤 Step 3: 토크나이제이션 분석 (15분)

src/tokenizer_analyzer.py

import tiktoken
from anthropic import Anthropic

class TokenizerAnalyzer:
    def __init__(self):
        self.gpt_tokenizer = tiktoken.get_encoding("cl100k_base")  # GPT-4, GPT-3.5-turbo
        self.anthropic_client = Anthropic()
    
    def analyze_gpt_tokens(self, text):
        """GPT 토크나이저 분석"""
        tokens = self.gpt_tokenizer.encode(text)
        token_texts = [self.gpt_tokenizer.decode([token]) for token in tokens]
        
        return {
            "token_count": len(tokens),
            "tokens": tokens,
            "token_texts": token_texts,
            "char_count": len(text),
            "tokens_per_char": len(tokens) / len(text) if len(text) > 0 else 0
        }
    
    def analyze_claude_tokens(self, text):
        """Claude 토큰 카운팅"""
        # Anthropic의 count_tokens API 사용
        token_count = self.anthropic_client.count_tokens(text)
        
        return {
            "token_count": token_count,
            "char_count": len(text),
            "tokens_per_char": token_count / len(text) if len(text) > 0 else 0
        }
    
    def compare_tokenizers(self, text):
        """두 토크나이저 비교"""
        gpt_analysis = self.analyze_gpt_tokens(text)
        claude_analysis = self.analyze_claude_tokens(text)
        
        return {
            "text": text,
            "gpt": gpt_analysis,
            "claude": claude_analysis,
            "difference": abs(gpt_analysis["token_count"] - claude_analysis["token_count"])
        }

# 테스트
if __name__ == "__main__":
    analyzer = TokenizerAnalyzer()
    
    test_texts = [
        "Hello, world!",
        "안녕하세요, 세계!",
        "This is a longer sentence with more complex tokenization patterns.",
        "한국어는 영어보다 토큰화가 어떻게 다를까요? 🤔"
    ]
    
    for text in test_texts:
        print(f"\n텍스트: {text}")
        print("=" * 60)
        
        gpt_result = analyzer.analyze_gpt_tokens(text)
        print(f"GPT 토큰 수: {gpt_result['token_count']}")
        print(f"토큰들: {gpt_result['token_texts'][:10]}...")  # 처음 10개만
        
        claude_result = analyzer.analyze_claude_tokens(text)
        print(f"Claude 토큰 수: {claude_result['token_count']}")
        print(f"문자당 토큰 비율: {claude_result['tokens_per_char']:.2f}")

🎛️ Step 4: 파라미터 실험실 (20분)

src/parameter_lab.py

from basic_llm import LLMClient
import json

class ParameterLab:
    def __init__(self):
        self.client = LLMClient()
    
    def experiment_temperature(self, prompt, temps=[0.0, 0.5, 1.0, 1.5]):
        """Temperature 효과 실험"""
        results = []
        
        for temp in temps:
            result = self.client.call_gpt(prompt, temperature=temp, max_tokens=100)
            results.append({
                "temperature": temp,
                "response": result.get("response", "Error"),
                "tokens": result.get("tokens", 0)
            })
        
        return results
    
    def experiment_top_p(self, prompt, top_ps=[0.1, 0.5, 0.9, 1.0]):
        """Top-p (nucleus sampling) 실험"""
        results = []
        
        for top_p in top_ps:
            # OpenAI API에 top_p 파라미터 추가
            response = self.client.openai_client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[{"role": "user", "content": prompt}],
                top_p=top_p,
                max_tokens=100
            )
            
            results.append({
                "top_p": top_p,
                "response": response.choices[0].message.content,
                "tokens": response.usage.total_tokens
            })
        
        return results
    
    def experiment_max_tokens(self, prompt, max_tokens_list=[50, 100, 200, 500]):
        """Max tokens 효과 실험"""
        results = []
        
        for max_tok in max_tokens_list:
            result = self.client.call_gpt(prompt, max_tokens=max_tok)
            results.append({
                "max_tokens": max_tok,
                "response": result.get("response", "Error"),
                "actual_tokens": result.get("tokens", 0)
            })
        
        return results

# 테스트
if __name__ == "__main__":
    lab = ParameterLab()
    
    # 창의적 글쓰기 프롬프트
    creative_prompt = "우주 여행에 대한 짧은 시를 써주세요."
    
    print("=== Temperature 실험 ===")
    temp_results = lab.experiment_temperature(creative_prompt)
    for r in temp_results:
        print(f"\nTemperature: {r['temperature']}")
        print(f"응답: {r['response'][:100]}...")
    
    print("\n\n=== Top-p 실험 ===")
    topp_results = lab.experiment_top_p(creative_prompt)
    for r in topp_results:
        print(f"\nTop-p: {r['top_p']}")
        print(f"응답: {r['response'][:100]}...")

🖥️ Step 5: Streamlit 웹 인터페이스 (30분)

app.py

import streamlit as st
import sys
sys.path.append('src')

from basic_llm import LLMClient
from prompt_templates import PromptTemplates
from tokenizer_analyzer import TokenizerAnalyzer
from parameter_lab import ParameterLab

# 페이지 설정
st.set_page_config(
    page_title="LLM Playground",
    page_icon="🤖",
    layout="wide"
)

# 사이드바 - 모델 선택 및 파라미터
st.sidebar.title("⚙️ 설정")

model_type = st.sidebar.selectbox(
    "모델 선택",
    ["GPT-4o-mini", "GPT-4o", "Claude Sonnet", "Claude Opus"]
)

temperature = st.sidebar.slider("Temperature", 0.0, 2.0, 0.7, 0.1)
max_tokens = st.sidebar.slider("Max Tokens", 50, 2000, 500, 50)
top_p = st.sidebar.slider("Top-p", 0.0, 1.0, 1.0, 0.1)

# 메인 탭
tab1, tab2, tab3, tab4 = st.tabs([
    "💬 기본 채팅", 
    "📝 프롬프트 템플릿", 
    "🔤 토크나이저", 
    "🎛️ 파라미터 실험"
])

# 클라이언트 초기화
@st.cache_resource
def get_clients():
    return LLMClient(), PromptTemplates(), TokenizerAnalyzer(), ParameterLab()

client, templates, analyzer, lab = get_clients()

# Tab 1: 기본 채팅
with tab1:
    st.header("💬 LLM과 대화하기")
    
    user_input = st.text_area(
        "메시지를 입력하세요:",
        height=150,
        placeholder="예: 인공지능의 미래에 대해 설명해주세요."
    )
    
    col1, col2 = st.columns([1, 5])
    with col1:
        if st.button("🚀 전송", type="primary"):
            if user_input:
                with st.spinner("생각하는 중..."):
                    if "GPT" in model_type:
                        model_map = {
                            "GPT-4o-mini": "gpt-4o-mini",
                            "GPT-4o": "gpt-4o"
                        }
                        result = client.call_gpt(
                            user_input, 
                            model=model_map[model_type],
                            temperature=temperature,
                            max_tokens=max_tokens
                        )
                    else:
                        model_map = {
                            "Claude Sonnet": "claude-3-5-sonnet-20241022",
                            "Claude Opus": "claude-opus-4-20250514"
                        }
                        result = client.call_claude(
                            user_input,
                            model=model_map[model_type],
                            temperature=temperature,
                            max_tokens=max_tokens
                        )
                    
                    if "error" not in result:
                        st.success("✅ 응답 완료!")
                        st.markdown("### 📄 응답")
                        st.markdown(result["response"])
                        
                        col_a, col_b, col_c = st.columns(3)
                        col_a.metric("모델", result["model"])
                        col_b.metric("토큰 사용", result["tokens"])
                        col_c.metric("Temperature", temperature)
                    else:
                        st.error(f"❌ 오류: {result['error']}")
            else:
                st.warning("메시지를 입력해주세요!")

# Tab 2: 프롬프트 템플릿
with tab2:
    st.header("📝 프롬프트 엔지니어링 템플릿")
    
    template_type = st.selectbox(
        "템플릿 선택",
        ["Zero-shot", "Few-shot", "Chain of Thought", "ReAct", "Role-specific"]
    )
    
    task_input = st.text_input(
        "작업/질문 입력:",
        placeholder="예: 기후 변화의 주요 원인은 무엇인가요?"
    )
    
    if template_type == "Few-shot":
        st.subheader("예시 입력 (Few-shot)")
        num_examples = st.number_input("예시 개수", 1, 5, 2)
        examples = []
        
        for i in range(num_examples):
            col1, col2 = st.columns(2)
            with col1:
                ex_input = st.text_input(f"예시 {i+1} - 입력", key=f"ex_in_{i}")
            with col2:
                ex_output = st.text_input(f"예시 {i+1} - 출력", key=f"ex_out_{i}")
            
            if ex_input and ex_output:
                examples.append({"input": ex_input, "output": ex_output})
    
    elif template_type == "Role-specific":
        role = st.text_input(
            "역할 입력:",
            placeholder="예: 경제학 교수"
        )
    
    if st.button("🎯 프롬프트 생성 및 실행"):
        if task_input:
            # 프롬프트 생성
            if template_type == "Zero-shot":
                prompt = templates.zero_shot(task_input)
            elif template_type == "Few-shot":
                prompt = templates.few_shot(task_input, examples)
            elif template_type == "Chain of Thought":
                prompt = templates.chain_of_thought(task_input)
            elif template_type == "ReAct":
                tools = ["검색", "계산기", "날씨 API"]
                prompt = templates.react_prompt(task_input, tools)
            elif template_type == "Role-specific":
                prompt = templates.role_specific(role, task_input)
            
            # 프롬프트 표시
            st.markdown("### 생성된 프롬프트")
            st.code(prompt, language="text")
            
            # LLM 실행
            with st.spinner("실행 중..."):
                result = client.call_gpt(prompt, temperature=temperature, max_tokens=max_tokens)
                
                if "error" not in result:
                    st.markdown("### 📄 결과")
                    st.markdown(result["response"])

# Tab 3: 토크나이저 분석
with tab3:
    st.header("🔤 토크나이저 분석")
    
    analyze_text = st.text_area(
        "분석할 텍스트:",
        height=150,
        placeholder="예: Hello, world! 안녕하세요!"
    )
    
    if st.button("🔍 분석 시작"):
        if analyze_text:
            gpt_result = analyzer.analyze_gpt_tokens(analyze_text)
            claude_result = analyzer.analyze_claude_tokens(analyze_text)
            
            col1, col2 = st.columns(2)
            
            with col1:
                st.subheader("GPT 토크나이저")
                st.metric("토큰 수", gpt_result["token_count"])
                st.metric("문자 수", gpt_result["char_count"])
                st.metric("문자당 토큰", f"{gpt_result['tokens_per_char']:.2f}")
                
                with st.expander("개별 토큰 보기"):
                    for i, token_text in enumerate(gpt_result["token_texts"][:50]):
                        st.text(f"{i+1}. '{token_text}' (ID: {gpt_result['tokens'][i]})")
            
            with col2:
                st.subheader("Claude 토크나이저")
                st.metric("토큰 수", claude_result["token_count"])
                st.metric("문자 수", claude_result["char_count"])
                st.metric("문자당 토큰", f"{claude_result['tokens_per_char']:.2f}")
            
            st.info(f"ℹ️ 토큰 차이: {abs(gpt_result['token_count'] - claude_result['token_count'])} 토큰")

# Tab 4: 파라미터 실험
with tab4:
    st.header("🎛️ 파라미터 효과 실험")
    
    experiment_type = st.selectbox(
        "실험 유형",
        ["Temperature", "Top-p", "Max Tokens"]
    )
    
    experiment_prompt = st.text_area(
        "실험용 프롬프트:",
        height=100,
        value="우주 여행에 대한 짧은 시를 써주세요."
    )
    
    if st.button("🧪 실험 시작"):
        if experiment_prompt:
            with st.spinner("실험 진행 중..."):
                if experiment_type == "Temperature":
                    results = lab.experiment_temperature(experiment_prompt)
                    
                    for r in results:
                        with st.expander(f"Temperature: {r['temperature']}"):
                            st.markdown(r['response'])
                            st.caption(f"토큰 사용: {r['tokens']}")
                
                elif experiment_type == "Top-p":
                    results = lab.experiment_top_p(experiment_prompt)
                    
                    for r in results:
                        with st.expander(f"Top-p: {r['top_p']}"):
                            st.markdown(r['response'])
                            st.caption(f"토큰 사용: {r['tokens']}")
                
                elif experiment_type == "Max Tokens":
                    results = lab.experiment_max_tokens(experiment_prompt)
                    
                    for r in results:
                        with st.expander(f"Max Tokens: {r['max_tokens']}"):
                            st.markdown(r['response'])
                            st.caption(f"실제 토큰: {r['actual_tokens']}")

# Footer
st.sidebar.markdown("---")
st.sidebar.markdown("### 📚 학습 리소스")
st.sidebar.markdown("""
- [OpenAI 문서](https://platform.openai.com/docs)
- [Anthropic 문서](https://docs.anthropic.com)
- [프롬프트 엔지니어링 가이드](https://www.promptingguide.ai)
""")

실행

streamlit run app.py

🎓 Step 6: 고급 실습 과제

과제 1: 데이터 정제 (Data Cleaning)

# src/data_cleaning.py
# Common Crawl 데이터를 정제하는 파이프라인 구축
# - HTML 태그 제거
# - 중복 제거
# - 품질 필터링 (문장 길이, 특수문자 비율)

과제 2: SFT (Supervised Fine-Tuning) 데모

# src/sft_demo.py
# OpenAI Fine-tuning API를 사용한 간단한 fine-tuning
# - 데이터셋 준비 (JSONL 포맷)
# - Fine-tuning job 생성
# - 모델 평가

과제 3: RLHF 시뮬레이션

# src/rlhf_simulator.py
# 간단한 RLHF 프로세스 시뮬레이션
# - 여러 응답 생성
# - 사용자 선호도 수집 (thumbs up/down)
# - Reward model 학습 (간단한 분류기)

📊 평가 및 다음 단계

체크리스트

  • [ ] 기본 LLM API 호출 성공
  • [ ] 5가지 프롬프트 템플릿 모두 테스트
  • [ ] 토크나이저 차이 이해
  • [ ] Temperature/Top-p 효과 확인
  • [ ] Streamlit 앱 실행 성공

다음 프로젝트 준비

Project 2를 위해 필요한 것:

  1. Vector Database 설치 (ChromaDB 또는 Pinecone)
  2. 고객 지원 데이터셋 수집
  3. Embeddings 개념 학습

 

반응형