포스트

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. 자주 나오는 실수 / 주의사항
  14. 파라미터 실험을 비교 가능한 기록으로 남기기

1. 문제와 접근

응답 스타일을 조정할 때 여러 파라미터를 동시에 바꾸면 무엇이 영향을 줬는지 판단하기 어렵다. 샘플링 설정, 출력 길이, 대화 기록을 나누어 살펴보고 토큰 사용량과 응답 종료 상태를 함께 확인한다. 스트리밍도 답변 품질과 사용자 대기 경험을 구분해서 본다.

오늘 배우는 파라미터들

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
7
  계산 결과를 소수 셋째 자리까지 반올림:
  T=0.1  → [0.993, 0.007, 0.000, 0.000]   ← 가장 높은 logit에 확률 집중
  T=0.5  → [0.644, 0.237, 0.087, 0.032]
  T=1.0  → [0.455, 0.276, 0.167, 0.102]
  T=2.0  → [0.350, 0.273, 0.212, 0.165]
  T=10.0 → [0.269, 0.256, 0.243, 0.232]
  T=100  → [0.252, 0.251, 0.249, 0.248]   ← 균등 분포에 가까워짐
  • 양수 T가 작아지면 높은 logit의 토큰에 확률이 집중된다. 다른 토큰의 확률이 반드시 0이 되지는 않는다.
  • T가 커지면 이 예제의 네 토큰에 대한 분포가 균등해지는 방향으로 변한다.
  • 위 수식은 T > 0에서 사용한다. 2보다 큰 값은 수학적 분포 비교용이며 API에 그대로 전달하는 설정값은 아니다.

실험: 같은 질문 다른 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를 고정해 응답 변동성을 줄여 비교 (완전한 동일성 보장은 아님)
response = client.chat.completions.create(
    model       = "gpt-4o-mini",
    messages    = [{"role": "user", "content": "색깔 3개 추천해줘"}],
    seed        = 42,
    temperature = 1.0   # 다른 요청 조건도 동일하게 유지해 비교
)
print(response.choices[0].message.content)
# 응답 예시: 파란색, 초록색, 노란색 (실제 결과는 달라질 수 있음)
  • seed와 요청 조건을 고정하면 비교에 도움이 되지만 동일 출력을 보장하지는 않는다.
  • 모델 구성의 변화도 재현성에 영향을 줄 수 있으므로 실제 응답과 실행 조건을 함께 기록한다. OpenAI 공식 설명

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 한도로 잘렸습니다!")

14. 파라미터 실험을 비교 가능한 기록으로 남기기

같은 질문 묶음을 두고 한 번에 하나의 설정만 바꾸는 방식이 해석하기 쉽다. 답변 내용 외에 입력·출력 토큰 수, 잘림 여부, 처리 시간을 함께 남기면 품질과 비용 중 무엇이 달라졌는지 설명할 수 있다.

몇 번 비슷한 응답을 받았다는 사실과 재현성이 보장된다는 주장은 구분한다. seed나 낮은 temperature를 사용한 경우에도 실제 반환값을 확인하고, 모델 및 실행 조건과 함께 결과를 해석한다.

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

인기 태그