포스트

2026-03-12 강의 정리

환경설정 / LLM 생성 원리 + OpenAI Chat Completion API 활용

2026-03-12 강의 정리

목차

  1. 오늘 강의 개요
  2. API 기본 복습 - 응답 객체 구조
  3. temperature - 창의성 조절 (수학적 원리 포함)
  4. 주요 파라미터 전체 정리
  5. max_tokens - 응답 길이 제한
  6. frequency_penalty / presence_penalty
  7. stop - 특정 토큰에서 중단
  8. seed - 재현 가능한 응답
  9. 토크나이제이션 (BPE) + 비용 계산
  10. 멀티턴 대화 (Multi-turn Conversation)
  11. 스트리밍 (Streaming)
  12. 핵심 개념 총정리
  13. 자주 나오는 실수 / 주의사항

1. 오늘 강의 개요

오늘은 OpenAI API의 다양한 파라미터를 실험하면서 원하는 스타일의 응답을 만드는 방법을 배운다.

오늘 배우는 파라미터들

1
2
3
4
5
6
7
8
  temperature     → 창의성 (얼마나 다양하게?)
  max_tokens      → 응답 길이 제한 (얼마나 짧게?)
  frequency/presence_penalty → 반복 억제, 새 주제 장려
  stop            → 특정 단어에서 응답 중단
  seed            → 동일한 응답 재현

  + 멀티턴 대화  → 이전 대화를 기억하는 챗봇 만들기
  + 스트리밍     → 실시간으로 토큰 단위 응답 받기

2. API 기본 복습 - 응답 객체 구조

기본 호출 복습

1
2
3
4
5
6
7
8
9
10
11
response = client.chat.completions.create(
    model    = "gpt-4o-mini",
    messages = [{"role": "user", "content": "안녕하세요"}]
)

# 응답 구조
response.id                                # 'chatcmpl-xxx' (요청 ID)
response.model                             # 'gpt-4o-mini'
response.choices[0].message.content       # 실제 응답 텍스트
response.choices[0].finish_reason         # 'stop' or 'length'
response.usage.total_tokens               # 사용된 전체 토큰 수
  • finish_reason 중요:
    • ‘stop’ : 정상 완료 (LLM이 스스로 종료)
    • ‘length’ : max_tokens에 잘린 것 → 응답이 불완전할 수 있음

3. temperature - 창의성 조절 (수학적 원리 포함)

temperature란?

  • LLM이 다음 토큰을 선택할 때 “얼마나 다양하게 선택할지” 조절
  • 범위: 0.0 ~ 2.0 (OpenAI 기준)

수학적 원리 - Softmax + Temperature

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np

def calculate_softmax(logits, T):
    z     = np.array(logits)
    e_z   = np.exp(z / T - np.max(z / T))
    return e_z / e_z.sum()

logits      = [2.0, 1.5, 1.0, 0.5]   # 각 토큰의 점수(logit)
temperature = [0.1, 0.5, 1.0, 2.0, 10.0, 100.0]

for T in temperature:
    probs = calculate_softmax(logits, T)
    print(f"T={T:5}: {probs}")

결과 해석

1
2
3
4
5
6
  T=0.1  → [0.994, 0.006, 0.0,   0.0  ]   ← 1등 토큰에 거의 100% 집중
  T=0.5  → [0.706, 0.209, 0.062, 0.019]
  T=1.0  → [0.467, 0.284, 0.172, 0.104]   ← 어느정도 분산
  T=2.0  → [0.371, 0.281, 0.212, 0.160]
  T=10.0 → [0.271, 0.261, 0.250, 0.240]   ← 거의 균등 (무작위)
  T=100  → [0.251, 0.250, 0.250, 0.249]   ← 완전 랜덤
  • T가 작으면 (→ 0) : 가장 확률 높은 단어만 선택 → 항상 같은 응답
  • T가 크면 (→ ∞) : 모든 단어가 비슷한 확률 → 예측 불가능한 응답

실험: 같은 질문 다른 temperature

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
question = "인공지능의 미래에 대해 한 문장으로 말해주세요"

# temperature=0 → 3번 실행해도 동일한 응답
for i in range(3):
    response = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': question}],
        temperature=0.0
    )
    print(f"  T=0, 시도{i+1}: {response.choices[0].message.content[:50]}")

# temperature=1.5 → 매번 다른 응답
for i in range(3):
    response = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': question}],
        temperature=1.5
    )
    print(f"  T=1.5, 시도{i+1}: {response.choices[0].message.content[:50]}")

실용적인 temperature 설정

사용 사례권장 temperature
챗봇 (일반 대화)0.7
데이터 추출 / 분류0.0 ~ 0.3
창작 (시, 소설, 아이디어)1.0 ~ 1.5
코드 생성0.0 ~ 0.3

4. 주요 파라미터 전체 정리

파라미터범위설명
temperature0.0~2.0낮음=일관성, 높음=창의성
max_tokens양의 정수응답 최대 길이 제한 (비용 절감)
top_p0.0~1.0nucleus sampling (낮음=제한, 높음=다양)
frequency_penalty-2.0~2.0이미 나온 단어 반복 억제
presence_penalty-2.0~2.0새로운 주제/단어 장려
stop문자열/리스트해당 토큰 등장 시 응답 중단
seed정수동일 seed → 재현 가능한 동일 응답
streamTrue/FalseTrue 시 실시간 청크 단위 응답
n양의 정수응답 개수 (choices 리스트 크기)
  • temperature와 top_p는 둘 다 조절하면 예측이 어려워지므로 실무에서는 하나만 조절하는 것을 권장

5. max_tokens - 응답 길이 제한

사용 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# max_tokens로 응답 길이 제한
response = client.chat.completions.create(
    model      = "gpt-4o-mini",
    messages   = [{"role": "user", "content": "파이썬 설명해줘"}],
    max_tokens = 50   # 50 토큰으로 제한 → 응답이 중간에 잘릴 수 있음
)

# finish_reason 확인!
print(response.choices[0].finish_reason)
# 'length' → max_tokens에 도달하여 잘렸음

# max_tokens, temperature 조합 실험
for temp in [0, 0.5, 1.0]:
    for token in [50, 100, 200]:
        response = client.chat.completions.create(
            model      = 'gpt-4o-mini',
            messages   = [{"role": "user", "content": "파이썬 설명해줘"}],
            temperature = temp,
            max_tokens  = token
        )
        print(f"temp={temp}, tokens={token}: {response.choices[0].message.content[:40]}")
  • max_tokens는 출력 토큰에만 적용 (입력 토큰은 별도)
  • finish_reason이 ‘length’면 응답이 잘린 것 → 중요한 정보가 빠질 수 있음

6. frequency_penalty / presence_penalty

frequency_penalty - 반복 억제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# frequency_penalty = 0 (기본): 반복 허용
question = "봄에 대한 짧은 시를 써주세요"
response = client.chat.completions.create(
    model             = 'gpt-4o-mini',
    messages          = [{"role": "user", "content": question}],
    frequency_penalty = 0
)

# frequency_penalty = 2.0: 강한 반복 억제
response = client.chat.completions.create(
    model             = 'gpt-4o-mini',
    messages          = [{"role": "user", "content": question}],
    frequency_penalty = 2.0   # 이미 나온 단어를 다시 쓰기 어려워짐
)
  • frequency_penalty 양수 → 이미 많이 나온 토큰 억제 (반복 줄임)
  • frequency_penalty 음수 → 오히려 반복 선호 (특수 용도)

presence_penalty - 새 주제 장려

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
question = "사과에 대해 5문장으로 설명해주세요"

# presence_penalty 낮음 → 같은 주제 유지
response = client.chat.completions.create(
    model            = 'gpt-4o-mini',
    messages         = [{"role": "user", "content": question}],
    presence_penalty = 0
)

# presence_penalty 높음 → 새로운 주제로 이탈 가능성 높음
response = client.chat.completions.create(
    model            = 'gpt-4o-mini',
    messages         = [{"role": "user", "content": question}],
    presence_penalty = 2.0
)
  • presence_penalty 양수 → 새로운 단어/주제 장려
  • 창의적인 글쓰기에서 주제 다양성을 높이고 싶을 때 사용

7. stop - 특정 토큰에서 중단

사용 예시

1
2
3
4
5
6
7
8
9
10
11
12
# "4." 이 등장하면 응답 중단 → 1~3까지만 출력
response = client.chat.completions.create(
    model       = "gpt-4o-mini",
    messages    = [{"role": "user", "content": "과일 10개를 번호로 나열해줘"}],
    stop        = ["4."],   # "4." 등장 시 중단
    temperature = 0
)
print(response.choices[0].message.content)
# 1. 사과
# 2. 바나나
# 3. 오렌지
# (4. 이후는 잘림)
  • stop에 여러 개 지정 가능: stop=[“4.”, “5.”, “\n\n”]
  • 특정 형식의 출력을 강제할 때 유용 (ex: 번호 3개까지만)

8. seed - 재현 가능한 응답

사용 예시

1
2
3
4
5
6
7
8
9
# seed 설정 → 동일 seed이면 항상 같은 응답
response = client.chat.completions.create(
    model       = "gpt-4o-mini",
    messages    = [{"role": "user", "content": "색깔 3개 추천해줘"}],
    seed        = 42,
    temperature = 1.0   # temperature 높아도 seed 있으면 재현됨
)
print(response.choices[0].message.content)
# 파란색, 초록색, 노란색 (seed=42 이면 항상 이 응답)
  • seed는 테스트/디버깅 시 유용 (항상 같은 결과로 비교 가능)
  • 동일 seed + 동일 입력 → 동일 출력 (재현성)

9. 토크나이제이션 (BPE) + 비용 계산

BPE (Byte Pair Encoding) 이란?

  • 텍스트를 “토큰” 단위로 분리하는 방식
  • 단어를 통째로가 아니라 자주 나오는 조합으로 분리
  • 모델은 이 토큰 ID 숫자를 처리함

토큰 예시

1
2
3
4
5
# "대한민국의 수도는 어디인가요"
# → 영어: I have an apple  →  4개 토큰 (1 토큰 ≈ 1 단어)
# → 한글: 대한민국의        →  4-6개 토큰 (1 글자 ≈ 1~2 토큰)

# 한글이 영어보다 토큰 소모 많음 → API 비용 더 높음!

토큰 사용량 확인

1
2
3
4
5
response.usage.prompt_tokens      # 입력에 사용된 토큰 수
response.usage.completion_tokens  # 출력에 사용된 토큰 수
response.usage.total_tokens       # 전체 (입력 + 출력)

print(f"{response.usage.total_tokens} 토큰 사용")

API 비용 계산 예시

1
2
3
4
5
6
7
8
9
10
# gpt-4o-mini 기준 (2026년 기준 대략)
# input:  $0.05 / 1M tokens
# output: $0.20 / 1M tokens

total_tokens   = response.usage.total_tokens
input_tokens   = response.usage.prompt_tokens
output_tokens  = response.usage.completion_tokens

cost = (input_tokens * 0.05 + output_tokens * 0.20) / 1_000_000
print(f"예상 비용: ${cost:.6f}")
  • 멀티턴 대화는 매 요청마다 전체 히스토리를 보내므로 토큰 누적!
  • 긴 대화일수록 비용이 기하급수적으로 증가

10. 멀티턴 대화 (Multi-turn Conversation)

핵심 원리 - OpenAI API는 stateless

1
2
  OpenAI API는 이전 대화를 기억하지 않는다!
  → 매번 "지금까지의 대화 전체"를 messages에 담아서 보내야 함

멀티턴 대화 구현

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
31
32
from openai import OpenAI
client = OpenAI()

# 시스템 메시지로 시작
messages = [
    {"role": "system", "content": "당신은 친절한 AI입니다. 짧게 답변합니다."}
]

questions = [
    "내 이름은 홍길동이야",
    "내 취미는 코딩이야",
    "내 이름과 취미가 뭐야?"   # ← 앞에서 말한 걸 기억하나?
]

for q in questions:
    # 1. 사용자 메시지 추가
    messages.append({"role": "user", "content": q})

    # 2. API 호출 (전체 messages 전달)
    response = client.chat.completions.create(
        model    = "gpt-4o-mini",
        messages = messages
    )
    answer = response.choices[0].message.content

    # 3. AI 응답도 messages에 추가 (다음 턴을 위해)
    messages.append({"role": "assistant", "content": answer})

    print(f"나   : {q}")
    print(f"GPT  : {answer}")
    print(f"토큰 : {response.usage.total_tokens}")
    print()

대화 흐름 시각화

1
2
3
4
5
6
7
8
9
10
11
12
13
  첫 번째 요청 messages:
    [system, user("내 이름은 홍길동이야")]

  두 번째 요청 messages:
    [system, user("내 이름은..."), assistant("홍길동이시군요!"),
     user("내 취미는 코딩이야")]

  세 번째 요청 messages:
    [system, user(...), assistant(...), user(...), assistant(...),
     user("내 이름과 취미가 뭐야?")]

  → 매번 전체 대화 기록을 포함!
  → 토큰 수가 계속 증가함

주의사항

1
2
3
4
5
6
7
8
# 대화가 길어질수록 토큰이 기하급수적으로 증가
# context window 초과 시 오래된 메시지 제거 필요

# 예: 최근 10개 메시지만 유지하는 방법
MAX_HISTORY = 10
if len(messages) > MAX_HISTORY + 1:  # +1은 system 메시지
    # system 메시지는 유지하고 오래된 대화만 제거
    messages = [messages[0]] + messages[-(MAX_HISTORY):]
  • 3번째 질문 “내 이름과 취미가 뭐야?” → 앞 대화를 기억해서 “홍길동 / 코딩” 답변
  • API는 stateless이지만 messages에 히스토리를 담으니까 기억처럼 보이는 것!

11. 스트리밍 (Streaming)

왜 스트리밍이 필요한가?

1
2
3
4
5
  일반 호출 (.invoke):
    전체 응답 완성 → 한번에 반환 (사용자는 기다려야 함)

  스트리밍 (stream=True):
    토큰 생성 즉시 → 실시간 전달 (ChatGPT처럼 타이핑 효과)

스트리밍 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
response = client.chat.completions.create(
    model    = "gpt-4o-mini",
    messages = [{"role": "user", "content": "파이썬의 장점 3가지를 설명해줘"}],
    stream   = True   # ← 스트리밍 ON
)

full_answer = ""
for chunk in response:
    text = chunk.choices[0].delta.content   # delta! (message가 아님)
    if text:
        print(text, end="", flush=True)     # 실시간 출력
        full_answer += text

print()  # 마지막 줄바꿈
print(f"\n전체 응답: {full_answer}")
  • stream=True 일 때:
    • 반환값이 iterator (for loop으로 순회)
    • 각 청크(chunk)에서 delta.content 로 텍스트 추출
    • delta : 이전 chunk와의 “차이(delta)” → 새로 추가된 텍스트
  • 일반 호출과의 차이:
    • 일반: response.choices[0].message.content
    • 스트리밍: chunk.choices[0].delta.content
  • end=”” : 출력 후 줄바꿈 없이
  • flush=True : 버퍼를 즉시 비워 실시간 출력

12. 핵심 개념 총정리

파라미터/개념핵심 내용
temperature낮음=일관성, 높음=창의성 (0~2)
max_tokens응답 최대 길이 제한
frequency_penalty이미 나온 단어 반복 억제
presence_penalty새로운 주제/단어 장려
stop특정 토큰에서 응답 중단
seed재현 가능한 응답 생성
usage.total_tokens토큰 수 확인 → 비용 계산
finish_reason‘stop’=정상, ‘length’=잘림
멀티턴 대화messages에 대화 기록 누적
스트리밍stream=True + delta.content

전체 흐름

1
2
3
4
5
6
7
8
9
  API 파라미터 조절
      ↓
  temperature / max_tokens / penalty 설정
      ↓
  멀티턴: messages에 user/assistant 번갈아 추가
      ↓
  스트리밍: stream=True로 실시간 응답
      ↓
  usage.total_tokens 로 비용 모니터링

13. 자주 나오는 실수 / 주의사항

실수 1: 스트리밍에서 .message 대신 .delta 사용 안 하기

1
2
3
4
5
6
7
8
9
# 틀린 코드
for chunk in response:
    text = chunk.choices[0].message.content   # AttributeError!

# 올바른 코드
for chunk in response:
    text = chunk.choices[0].delta.content     # delta 사용!
    if text:   # None인 경우도 있으므로 체크 필요
        print(text, end="")

실수 2: 멀티턴에서 assistant 응답 안 넣기

1
2
3
4
5
6
7
8
9
10
# 틀린 코드 → AI가 이전 대화 기억 못함
messages.append({"role": "user", "content": q})
response = client.chat.completions.create(model=..., messages=messages)
# assistant 응답을 messages에 추가하지 않으면 다음 턴에 기억 안 됨!

# 올바른 코드
messages.append({"role": "user", "content": q})
response = client.chat.completions.create(model=..., messages=messages)
answer   = response.choices[0].message.content
messages.append({"role": "assistant", "content": answer})   # ← 반드시!

실수 3: temperature와 top_p 동시 조절

1
2
# 두 파라미터를 동시에 변경하면 효과 예측이 어려워짐
# 실무에서는 하나만 변경할 것 (보통 temperature만)

실수 4: finish_reason 확인 안 하기

1
2
3
# max_tokens 설정 시 응답이 잘렸는지 반드시 확인
if response.choices[0].finish_reason == 'length':
    print("경고: 응답이 max_tokens 한도로 잘렸습니다!")

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

인기 태그