2026-03-12 강의 정리
환경설정 / LLM 생성 원리 + OpenAI Chat Completion API 활용
2026-03-12 강의 정리
목차
- 오늘 강의 개요
- API 기본 복습 - 응답 객체 구조
- temperature - 창의성 조절 (수학적 원리 포함)
- 주요 파라미터 전체 정리
- max_tokens - 응답 길이 제한
- frequency_penalty / presence_penalty
- stop - 특정 토큰에서 중단
- seed - 재현 가능한 응답
- 토크나이제이션 (BPE) + 비용 계산
- 멀티턴 대화 (Multi-turn Conversation)
- 스트리밍 (Streaming)
- 핵심 개념 총정리
- 자주 나오는 실수 / 주의사항
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. 주요 파라미터 전체 정리
| 파라미터 | 범위 | 설명 |
|---|---|---|
| temperature | 0.0~2.0 | 낮음=일관성, 높음=창의성 |
| max_tokens | 양의 정수 | 응답 최대 길이 제한 (비용 절감) |
| top_p | 0.0~1.0 | nucleus sampling (낮음=제한, 높음=다양) |
| frequency_penalty | -2.0~2.0 | 이미 나온 단어 반복 억제 |
| presence_penalty | -2.0~2.0 | 새로운 주제/단어 장려 |
| stop | 문자열/리스트 | 해당 토큰 등장 시 응답 중단 |
| seed | 정수 | 동일 seed → 재현 가능한 동일 응답 |
| stream | True/False | True 시 실시간 청크 단위 응답 |
| 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 라이센스를 따릅니다.