KT 에이블스쿨 9기 DX컨설턴트

[KT 에이블스쿨 9기 기자단] DX컨설턴트 5주차 후기

juhyo 2026. 5. 1. 10:07

안녕하세요 KT 에이블스쿨 9기 기자단입니다.
오늘은 DX 컨설턴트 과정 5주차 후기를 작성하려고 합니다.
이번주는 OpenAI API  를 사용한 LLM 모델 활용법에 대해 배우고
3차 미니프로젝트 1일차 진행했습니다.
3차 미프 관련해서는 6주차 후기에 작성하겠습니다.
 

월요일에는 LLM 소개, LLM Application, 고급 대화 관리에 대해 배웠습니다.
가장 인상깊었던 점은 OpenAI API 키를 사용해 코랩에서 생성형 AI를 사용할 수 있던 점이었습니다.
코드로 프롬프트를 다양한 방식으로 작성하였고,
AI가 도출한 결과물이 코드 실행 결과로 바로 출력되는 점이 인상깊었습니다.
아래는 월요일에 배운 내용 중 중요한 내용을 정리해보았습니다.

1. OpenAI API 기본 사용법

환경 설정

import os
from openai import OpenAI

client = OpenAI()

기본 호출

response = client.responses.create(
    model="gpt-4o-mini",
    input=[{"role": "user", "content": "오늘 점심 뭐 먹을까?"}]
)

print(response.output_text)
  • role 에 들어갈 수 있는 값은 3가지
    • user : 사용자 메시지
    • assistant : 모델 응답
    • system : 모델에게 역할/지침을 주는 메시지

2. System Message로 역할 부여하기

모델한테 어떤 역할을 줄지 설정하는 것. system 메시지에 넣으면 대화 전체에 걸쳐 적용됨

sys_role = """
당신은 친절한 요리사입니다.
누구나 쉽게 따라할 수 있도록 간단한 언어로 요리 방법을 설명하세요.
"""

response = client.responses.create(
    model="gpt-4o-mini",
    input=[
        {"role": "system", "content": sys_role},
        {"role": "user", "content": "계란볶음밥 만드는 법 알려줘"}
    ]
)

print(response.output_text)

역할을 안 줬을 때랑 줬을 때 답변 톤이나 수준이 확 달라짐

3 temperature로 창의성 조절하기

temperature 는 모델 답변의 무작위성을 조절하는 파라미터. 0~2 사이 값을 씀

# 논리 모드 (항상 비슷한 답변)
response = client.responses.create(
    model="gpt-4o-mini",
    input=[{"role": "user", "content": "커피 마시면 좋은 점 알려줘"}],
    temperature=0,
    top_p=1.0
)

# 창의 모드 (매번 다른 답변)
response = client.responses.create(
    model="gpt-4o-mini",
    input=[{"role": "user", "content": "커피 마시면 좋은 점 알려줘"}],
    temperature=1.5,
    top_p=1.0
)
  • temperature=0 : 가장 확률 높은 단어만 선택. 코딩, 데이터 추출같은 정확한 답이 필요할 때 씀
  • temperature=1.5 : 다양한 단어 후보 중에서 랜덤하게 선택. 광고 문구, 아이디어 생성할 때 씀
  • top_p : 단어 후보군의 범위를 확률 기준으로 제한하는 것. temperature랑 동시에 건드리는 건 권장하지 않음

4. 프롬프트 템플릿

매번 프롬프트를 하드코딩하면 관리가 힘들어짐. 변하는 부분만 {} 로 비워두고 .format() 으로 채우는 방식

prompt_template = """
당신은 전문 작가입니다.
다음 주제로 {style} 스타일의 글을 {length} 분량으로 작성하세요.

주제: {topic}
"""

prompt = prompt_template.format(
    style="유머러스한",
    length="3문장",
    topic="월요일 아침의 고통"
)

response = client.responses.create(
    model="gpt-4o-mini",
    input=[{"role": "user", "content": prompt}]
)

print(response.output_text)

5. 대화 히스토리 관리

API는 이전 대화를 기억 못함. 그래서 대화 내용을 직접 누적해서 매번 같이 넘겨줘야 함

history = []

# 1번 대화
history.append({"role": "user", "content": "나 오늘 치킨 먹었어"})
response = client.responses.create(model="gpt-4o-mini", input=history)
history.append({"role": "assistant", "content": response.output_text})

# 2번 대화 (이전 내용이 같이 전달되니까 치킨 얘기를 기억함)
history.append({"role": "user", "content": "내가 오늘 뭐 먹었다고 했지?"})
response = client.responses.create(model="gpt-4o-mini", input=history)

print(response.output_text)

근데 대화가 길어질수록 토큰이 계속 쌓여서 비용이 올라감. 그래서 히스토리 관리 전략이 필요함

  • Sliding Window : 최근 N개 메시지만 유지하고 오래된 건 삭제
  • Context Distillation : 이전 대화를 요약해서 한 줄로 압축해서 넘기는 것

 
 
화요일 수요일에는 streamlit, huggingface에 대해 배웠습니다. 
이번에 처음 들었는데요. 간단한 코드로 그럴듯한 ui를 만들 수 있는게 신기했습니다.
 
Streamlit은 Python 코드만으로 웹 UI를 구성할 수 있는 프레임워크입니다.
일반적인 웹 개발에는 HTML, CSS, JavaScript가 필요하지만,
Streamlit을 사용하면 버튼, 입력창, 슬라이더 같은 UI 요소를 Python만으로 구현할 수 있습니다.
데이터 시각화나 머신러닝 결과를 빠르게 보여주는 데 적합하며, 프로토타입 제작에 특히 많이 활용됩니다.
 
Hugging Face는 다양한 사전 학습 AI 모델을 제공하는 플랫폼입니다.
자연어 처리 모델이 대표적이지만, 이미지나 음성 관련 모델도 함께 제공합니다. 
transformers 라이브러리를 통해 복잡한 모델을 직접 학습시키지 않고도
비교적 간단하게 불러와 사용할 수 있습니다.
 
아래는 배운 내용을 요약하여 정리했습니다.

출력 함수

st.write("문자열, 숫자, 딕셔너리 등 무엇이든 출력")
st.markdown("### 마크다운 제목")
st.text("줄바꿈·공백 그대로 출력")
st.code("print('Hello')")

입력 위젯

# 텍스트
name = st.text_input("이름", placeholder="홍길동")
passwd = st.text_input("암호", type="password")
comment = st.text_area("의견")

# 숫자 / 선택
age = st.number_input("나이", min_value=0, max_value=100, step=1)
agree = st.checkbox("동의합니다")
choice = st.selectbox("선택", ["A", "B", "C"])
score = st.slider("점수", 0, 100, 50)
date = st.date_input("날짜", value=datetime.date.today())

입력 즉시 반영 vs 버튼 후 반영

# 패턴 A: 입력 즉시 반영 (가벼운 로직)
name = st.text_input("이름")
st.write("안녕하세요,", name)

# 패턴 B: 버튼 클릭 시에만 반영 (API 호출 등 무거운 로직)
name = st.text_input("이름")
if st.button("인사"):
    st.write("안녕하세요,", name)

 

레이아웃

컬럼 분할

col1, col2 = st.columns([1, 2])  # 1:2 비율, 숫자는 상대 비율

with col1:
    st.text_input("왼쪽")
with col2:
    st.text_input("오른쪽")

사이드바 — 입력/설정은 사이드바, 결과는 메인 화면

st.sidebar.header("설정")
mode = st.sidebar.selectbox("모드", ["A", "B"])
size = st.sidebar.slider("크기", 10, 100, 50)

기타

st.container()            # 코드 구조 정돈용 그룹
st.expander("고급 설정")   # 접었다 펼 수 있는 영역
st.markdown("---")        # 구분선

상태 관리 (session_state)

Streamlit은 이벤트 발생 시 스크립트 전체를 재실행한다. 일반 변수는 재실행 시 초기화되므로 값을 유지하려면 st.session_state를 사용해야 한다.

if "count" not in st.session_state:
    st.session_state.count = 0

if st.button("증가"):
    st.session_state.count += 1

if st.button("초기화"):
    st.session_state.count = 0

st.write("count =", st.session_state.count)

리스트로 히스토리 관리

if "logs" not in st.session_state:
    st.session_state.logs = []

if st.button("추가"):
    st.session_state.logs.append(text)

for i, msg in enumerate(st.session_state.logs, 1):
    st.write(i, msg)

폼으로 일괄 제출 — 제출 버튼 클릭 시에만 처리

with st.form("my_form"):
    name = st.text_input("이름")
    age = st.number_input("나이")
    submitted = st.form_submit_button("제출")

if submitted:
    st.write("이름:", name)
    st.write("나이:", age)

 

파일 업로드

# 텍스트 파일
uploaded = st.file_uploader("파일 업로드", type=["txt"])
if uploaded:
    content = uploaded.read().decode("utf-8")
    st.text_area("내용", content, height=300)
else:
    st.info("파일을 업로드하세요.")

# CSV 파일
uploaded = st.file_uploader("CSV 업로드", type=["csv"])
if uploaded:
    df = pd.read_csv(uploaded)
    st.dataframe(df)
    st.write(df.describe())

# 이미지 파일
uploaded = st.file_uploader("이미지 업로드", type=["png", "jpg"])
if uploaded:
    image = Image.open(uploaded)
    st.image(image, use_container_width=True)

rerun 시 중복 처리 방지

if "df" not in st.session_state:
    st.session_state.df = None

if uploaded and st.session_state.df is None:
    st.session_state.df = pd.read_csv(uploaded)

 

멀티모달

이미지 → Base64 변환 함수

import base64, mimetypes

def to_data_url(path: str) -> str:
    mime, _ = mimetypes.guess_type(path)
    with open(path, "rb") as f:
        b64 = base64.b64encode(f.read()).decode("utf-8")
    return f"data:{mime};base64,{b64}"

 
이미지 + 텍스트 전달

data_url = to_data_url("image.png")

response = client.responses.create(
    model="gpt-4o-mini",
    input=[{
        "role": "user",
        "content": [
            {"type": "input_text", "text": "이 이미지를 설명해주세요."},
            {"type": "input_image", "image_url": data_url}
        ]
    }]
)
print(response.output_text)

STT — 음성 → 텍스트 (Whisper)

audio_file = open("sample.m4a", "rb")

transcript = client.audio.transcriptions.create(  # 음성 인식
    model="whisper-1", file=audio_file
)
transcript = client.audio.translations.create(    # 영어로 번역
    model="whisper-1", file=audio_file
)

print(transcript.text)

TTS — 텍스트 → 음성

JSON 형태로 답변 구조화

방법 1 — 역할 부여 (파싱 필요)

sys_role = "항상 '이름', '나이', '도시' Key를 갖는 JSON으로 대답합니다."

match = re.search(r"\{.*\}", response.output_text, re.DOTALL)
data = json.loads(match.group())

방법 2 — json_schema 지정 (권장)

response = client.responses.create(
    model="gpt-4o-mini",
    input=[{"role": "user", "content": prompt}],
    text={
        "format": {
            "type": "json_schema",
            "name": "student",
            "schema": {
                "type": "object",
                "properties": {
                    "이름": {"type": "string"},
                    "나이": {"type": ["number", "null"]},  # null 허용
                    "도시": {"type": "string"}
                },
                "required": ["이름", "나이", "도시"],
                "additionalProperties": False
            }
        }
    }
)

data = json.loads(response.output_text)

값이 없을 수 있는 필드는 "number" 대신 ["number", "null"]로 지정해야 None으로 처리된다. "number"만 지정하면 0이 반환된다.

도구 활용 (Function Calling)

모델이 직접 코드를 실행하는 게 아니라 어떤 함수를 어떤 인자로 호출할지 JSON으로 반환한다. 이를 받아 실제 함수를 실행하고 결과를 다시 모델에 전달하는 3단계 구조다.

# 1단계: 도구 정의 + 질문 전달
tools = [{
    "type": "function",
    "name": "get_exchange_rate",
    "description": "통화 환율 정보를 조회한다.",
    "parameters": {
        "type": "object",
        "properties": {
            "base": {"type": "string"},
            "target": {"type": "string"}
        },
        "required": ["base", "target"],
        "additionalProperties": False
    },
    "strict": True
}]

response = client.responses.create(
    model="gpt-4o-mini",
    input="현재 USD/KRW 환율을 알려주세요.",
    tools=tools,
    tool_choice="auto"  # auto / required / none
)

# 2단계: 함수 실행
tool_outputs = []
for item in response.output:
    if item.type == "function_call":
        args = json.loads(item.arguments)
        result = get_exchange_rate(**args)
        tool_outputs.append({
            "type": "function_call_output",
            "call_id": item.call_id,
            "output": json.dumps(result, ensure_ascii=False)
        })

# 3단계: 결과 재전달
final = client.responses.create(
    model="gpt-4o-mini",
    previous_response_id=response.id,
    input=tool_outputs
)
print(final.output_text)

서비스 안정화

토큰 수 사전 계산 — API 비용은 토큰 단위로 부과, 한국어는 영어보다 2~3배 소모

import tiktoken

def count_tokens(text, model="gpt-4o-mini"):
    encoding = tiktoken.encoding_for_model(model)
    return len(encoding.encode(text))

자동 재시도 (지수 백오프) — 실패 시 1초 → 2초 → 4초... 간격으로 재시도

from tenacity import retry, wait_random_exponential, stop_after_attempt

@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
def call_api():
    ...

유해 콘텐츠 필터링 — 모델 호출 전 프롬프트 사전 검사

def check_safety(text):
    result = client.moderations.create(input=text)
    return not result.results[0].flagged

if check_safety(prompt):
    response = client.responses.create(...)

 

멀티턴 챗봇 (종합)

session_state로 대화 히스토리를 유지하고, 매 요청마다 전체 대화를 모델에 전달한다.

import streamlit as st
from openai import OpenAI

client = OpenAI()
st.title("🤖 멀티턴 챗봇")

if "messages" not in st.session_state:
    st.session_state.messages = [
        {"role": "assistant", "content": "안녕하세요. 무엇을 도와드릴까요?"}
    ]

for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

user_input = st.chat_input("메시지를 입력하세요...")

if user_input:
    st.session_state.messages.append({"role": "user", "content": user_input})
    with st.chat_message("user"):
        st.markdown(user_input)

    try:
        response = client.responses.create(
            model="gpt-4.1-mini",
            input=st.session_state.messages
        )
        reply = response.output_text
    except Exception as e:
        reply = f"오류가 발생했습니다: {e}"

    st.session_state.messages.append({"role": "assistant", "content": reply})
    with st.chat_message("assistant"):
        st.markdown(reply)

 

Hugging Face Space 배포

from huggingface_hub import HfApi

api = HfApi()
commit_info = api.upload_file(
    path_or_fileobj="app.py",
    path_in_repo="src/app.py",
    repo_id="유저ID/Space이름",
    repo_type="space",
    token=os.environ["HF_TOKEN"]
)
print(commit_info.repo_url)
 

여기까지 읽어주셔서 감사합니다.
5월도 화이팅!