오늘은 OpenAI의 Fine-tuning 기능과 LangChain, Streamlit을 활용하여 일론 머스크 스타일의 트윗을 생성하는 챗봇을 개발하는 과정을 정리해 보려고 한다. 주요 목표는 Fine-tuning된 모델과 일반 Prompt 기반 모델의 성능을 비교하고, 사용자 피드백을 통해 모델을 개선해 보려는 것이다.
크게 두 가지 주요 단계로 구성하였는데
- train_model.py: 일론 머스크의 트윗 데이터를 사용하여 OpenAI의 GPT-3.5-turbo 모델을 Fine-tuning 하고
- app.py: Fine-tuning된 모델과 일반 Prompt 기반 모델을 비교하고, Streamlit을 통해 사용자 인터페이스를 제공하여 트윗을 생성하며, LangSmith를 활용하여 피드백을 수집하였다.
train_model.py
OpenAI 모델을 일론 머스크의 트윗 데이터로 Fine-tuning하는 데 초점을 맞춘다.
# pip install openai langchain streamlit streamlit_feedback
import json
import time
from langchain.schema import AIMessage
from langchain.adapters.openai import convert_message_to_dict
import openai
from openai import OpenAI
import os
os.environ["OPENAI_API_KEY"] = ""
if __name__=="__main__":
# prepare the data into fine-tune format
with open('dataset_elon_tweets.json') as f:
data = json.load(f)
tweets = [ d["full_text"] for d in data if "t.co" not in d["full_text"]]
# print(tweets)
messages = [AIMessage(content=t) for t in tweets]
# print(messages)
system_message = {"role": "system", "content": "write a tweet"}
data = [[system_message, convert_message_to_dict(m)] for m in messages]
# print(data)
# prepare a fine-tuning file
my_file = "dataset_elon_tweets_training.json"
with open(my_file, 'w', encoding='utf-8') as file:
for m in data:
file.write(json.dumps({"messages": m}) + "\n")
# upload the fine-tune file
client = OpenAI()
training_file = client.files.create(
file=open(my_file, "rb"),
purpose="fine-tune"
)
# create a fine-tune model job
job = client.fine_tuning.jobs.create(
training_file = training_file.id,
model = "gpt-3.5-turbo",
suffix="elon-twitter"
)
ftj = client.fine_tuning.jobs.retrieve(job.id)
Step 1: 트윗 데이터 준비
- dataset_elon_tweets.json 파일에서 일론 머스크의 기존 트윗 데이터를 불러온 후에
- 선별된 각 트윗을 OpenAI Fine-tuning API가 요구하는 형식에 맞게 변환한다. 기본적으로 시스템 메시지("role": "system", "content": "write a tweet")와 실제 트윗 내용(모델의 응답 역할로 "role": "assistant")으로 구성된 메시지 쌍을 만든다.
Step 2: Fine-tuning 학습 파일 생성
- Step 1에서 변환된 메시지 쌍들을 JSONL(JSON Lines) 형식으로 dataset_elon_tweets_training.json이라는 파일로 저장한 후에 OpenAI Fine-tuning 학습에 직접 사용 되도록 한다.
Step 3: 학습 파일 OpenAI에 업로드
- OpenAI API 클라이언트를 초기화 후에 생성된 dataset_elon_tweets_training.json 파일을 OpenAI 서버에 업로드한다. 이때 파일의 목적(purpose)을 "fine-tune"으로 명시하여 OpenAI가 이 파일을 Fine-tuning 학습에 사용할 수 있도록 한다.
Step 4: Fine-tuning 작업 시작
- 업로드된 학습 파일의 ID를 사용하여 OpenAI의 gpt-3.5-turbo 모델을 기반으로 새로운 Fine-tuning 작업을 생성한다.
Step 4 이후 Fine tuning 작업이 시작이 되면 platform opanai 에 작업의 과정을 확인할 수 있다.

이번엔 일론 머스크의 스타일의 데이터셋을 학습한 친구로서 일론의 도발적이면서도 과감한 톤을 학습 시켰다.
app.py
app.py 에서는 크게 3가지의 툴/라이브러리를 사용하였는데
- LangChain: 복잡한 LLM 애플리케이션을 모듈화하고 체인화한다. Prompt 관리, 모델 호출, 출력 파싱 등을 효율적으로 처리할 수 있다.
- LangSmith: LLM 애플리케이션 개발의 생명주기를 지원하는 플랫폼이다. 모델의 실행을 추적하고, 성능을 분석하며, 사용자 피드백을 수집하여 지속적인 개선을 가능하게 한다. 이는 특히 프로덕션 환경에서 LLM 애플리케이션의 성능을 모니터링하고 디버깅하는 데 필수적이다.
- Streamlit: 빠르고 쉽게 데이터 애플리케이션을 만들 수 있는 강력한 도구로, LLM 기반 서비스의 프로토타입을 만들거나 데모를 시연하는 데 매우 유용하다.
# pip install openai langchain streamlit streamlit_feedback langsmith langchain-openai
import os
import langchain
import streamlit as st
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser
from langchain.callbacks.tracers.langchain import wait_for_all_tracers
from langchain.callbacks.tracers.run_collector import RunCollectorCallbackHandler
from langchain.schema.runnable import RunnableConfig
from langsmith import Client
from streamlit_feedback import streamlit_feedback
from langchain_openai import ChatOpenAI
client = Client(api_key='')
os.environ["OPENAI_API_KEY"] = ""
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ["LANGSMITH_API_KEY"] = ""
os.environ["LANGSMITH_PROJECT"] = "elon-langsmith"
if "run_id" not in st.session_state:
st.session_state.run_id = None
run_collector = RunCollectorCallbackHandler()
runnable_config = RunnableConfig(
callbacks=[run_collector],
tags=["Streamlit Chat"],
)
normal_chain = (
ChatPromptTemplate.from_messages([("system", "write a tweet about {topic} in the style of Elon Musk") ])
| ChatOpenAI()
| StrOutputParser()
)
chain = (
ChatPromptTemplate.from_messages([("system", "write a tweet about {topic}") ])
| ChatOpenAI(model="ft:gpt-3.5-turbo-0125:wondam:elon-twitter:BmXgxxT1")
| StrOutputParser()
)
def generate_tweet_normal(topic):
result = normal_chain.invoke({"topic": topic})
wait_for_all_tracers()
return result
def generate_tweet(topic):
result = chain.invoke({"topic": topic}, config=runnable_config)
run = run_collector.traced_runs[0]
run_collector.traced_runs = []
st.session_state.run_id = run.id
wait_for_all_tracers()
return result
col1, col2 = st.columns([1, 6])
col2.title("Elon Musk Tweet Generator")
st.info("\n\nTwo tweets will be generated: one using a finetuned model, one using a prompted model. Afterwards, you can provide feedback about whether the finetuned model performed better!")
topic = st.text_input("Enter a topic:")
if 'show_tweets' not in st.session_state:
st.session_state.show_tweets = None
if st.button("Generate Tweets"):
if topic:
col3, col4 = st.columns([6, 6])
tweet = generate_tweet(topic)
col3.markdown("### Finetuned Tweet:")
col3.write(f"🐦: {tweet}")
col3.markdown("---")
feedback = streamlit_feedback(
feedback_type="thumbs",
key=f"feedback_{st.session_state.run_id}",
align="flex-start"
)
scores = {"👍": 1, "👎": 0}
if feedback:
score = scores[feedback["score"]]
feedback = client.create_feedback(st.session_state.run_id, "user_score", score=score)
st.session_state.feedback = {"feedback_id": str(feedback.id), "score": score}
tweet = generate_tweet_normal(topic)
col4.markdown("### Prompted Tweet:")
col4.write(f"🐦: {tweet}")
col4.markdown("---")
else:
st.warning("Please enter a topic before generating a tweet.")
Streamlit을 사용하여 사용자 인터페이스를 구축하고, Fine-tuning된 모델과 일반 Prompt 기반 모델의 트윗 생성 결과를 비교하며, LangSmith를 통해 사용자 피드백을 수집하였다.
Step 1: 환경 설정 및 LangSmith 연동
- OpenAI API 키와 LangSmith API 키를 환경 변수로 설정
- LangSmith 추적을 활성화하고, 이 프로젝트의 모든 실행 기록이 "elon-langsmith"라는 LangSmith 프로젝트에 기록되도록 설정
- LangChain 실행 시 발생하는 추적 정보를 수집할 콜백 핸들러를 준비
Step 2: 트윗 생성 체인 정의 (Fine-tuned vs. Prompt)
- Fine-tuned 모델 체인 정의
- 사용자로부터 입력받은 주제({topic})만으로 트윗을 생성하도록 간단한 프롬프트 템플릿을 정의
- 이전에 train_model.py 를 통해 Fine-tuning하여 얻은 특정 모델 ID(ft:gpt-3.5-turbo-0125:wondam:elon-twitter:BmXgxxT1와 같은 형태)를 지정하여 OpenAI의 Fine-tuned 모델을 호출하는 체인을 구성
- Prompt 기반 모델 체인 정의
- 사용자로부터 입력받은 주제와 함께 "일론 머스크 스타일로 트윗을 작성해달라"는 명시적인 지시가 포함된 프롬프트 템플릿을 정의
- 기본 gpt-3.5-turbo 모델을 호출하는 체인을 구성
Step 3: Streamlit 사용자 인터페이스 구축
Step 4: 트윗 생성 및 결과 비교
- 사용자가 주제를 입력하고 "Generate Tweets" 버튼을 클릭하면
- Fine-tuned 모델 체인을 호출하여 트윗을 생성하고, 이때 LangSmith 추적을 위한 run_id를 수집
- Prompt 기반 모델 체인을 호출하여 트윗을 생성
- 생성된 두 트윗을 웹 페이지의 별도 열에 나란히 표시하여 사용자가 쉽게 비교
Step 5: 사용자 피드백 수집 및 LangSmith 연동
- Fine-tuned 모델의 트윗 아래에 "좋아요" 또는 "싫어요"를 선택할 수 있는 피드백 컴포넌트(streamlit_feedback)를 추가
- 사용자가 피드백을 제출하면, 해당 피드백(예: 긍정/부정)과 연결된 run_id를 사용하여 LangSmith에 피드백을 기록한 후에 이 피드백은 LangSmith 대시보드에서 모델의 성능을 평가하고 개선 방향을 모색하는 데 활용되도록 한다

local에서 app.py 를 실행하고 topic 에 donal trump 를 입력하였을때 파인튜닝된 모델이 좀 더 장황하지 않고 단도직입적인 톤으로 생성된다는것을 확인할 수 있었다. Langsmith에서도 chatPromptTemplate, LLM 호출, 그리고 StrOutputParser를 체인으로 연결한 각 단계 사이에서 어떤 과정이 진행되는지 상세하게 확인할 수 있었다.

monitoring 탭에 들어가면 파인튜닝 모델의 전체적인 성능 지표와 사용 현황을 실시간으로 확인할 수도 있다. 여기서는 응답 속도, 에러율, 토큰 사용량, 그리고 모델의 정확도 변화 추이를 시각화된 차트로 모니터링하여 모델 운영 상태를 종합적으로 파악할 수 있다. 처음보는 지표중에 P50, P99가 있었는데 P50은 '보통 사용자는 얼마나 빨리 답변을 받나', P99는 '가장 오래 기다리는 사용자는 얼마나 기다리나'를 보여준다고 한다. 이 두 숫자가 비슷하면 모든 사용자가 고르게 빠른 서비스를 받고 있다는 뜻이고, P99가 훨씬 크면 일부 사용자는 너무 오래 기다리고 있다는 신호이다.

실무에서 이 지표가 어떻게 사용되고 있는지는 모르겠으나 이번 테스트에서는 P99가 높게 나왔어서 성능상 문제가 있어 보였다. 이유는 여러가지가 있을 수 있지만 혼자 테스트하는 간단한 파인튜닝 모델이라서 GPU 메모리가 부족하거나 그런 이슈로는 보이진 않아서 조금 찾아 보았는데 OpenAI 특성상 가능한 원인은 아래의 이유가 있을 수 있다고 한다.
- 콜드 스타트 (Cold Start)
- 파인튜닝 모델이 일정 시간 사용되지 않으면 "잠들어" 있다가 첫 요청이 오면 모델을 다시 로딩하는 시간이 필요 이때 3-4초 정도 지연 발생 가능
- 모델 스케일링 지연
- OpenAI가 내부적으로 리소스를 할당하는 과정
- 트래픽이 적을 때는 최소 리소스로 운영하다가 요청이 오면 확장
- OpenAI 내부 큐잉
- 파인튜닝 모델은 일반 모델보다 우선순위가 낮을 수 있음
- 시스템 부하가 높을 때 대기시간 발생
- 네트워크 지연
- 특정 시점에 네트워크 레이턴시 급증
- API 게이트웨이에서의 일시적 지연
혼자 테스트하는 상황에서 이런 패턴이라면(특정 요청들만 3초 이상 걸림) OpenAI API의 일시적 성능 이슈로 보였다. 파인튜닝 모델 자체의 문제라기보다는 OpenAI 인프라의 순간적인 부하나 리소스 할당 지연이 원인이 아닌가 싶다^^

오늘은 OpenAI Fine-tuning 으로 특정 스타일에 최적화된 모델을 만들고, LangChain 으로 챗봇 체인을 구성해 보았다. Streamlit으로 인터페이스를 구현하고 LangSmith를 통해 모델 성능 추적 및 사용자 피드백을 수집도 해 보았는데, 비록 맛보기 정도의 보딩이였지만 챗봇이나 파인튜닝의 개발 플로우를 알 수 있어서 매우 재미있었다. LLM 파인튜닝의 가능성도 무궁무진하다는것도 다시한번 깨달으면서!
'인공지능 > 직접 해보기' 카테고리의 다른 글
| 🦥 Unsloth로 "영화 리뷰 특화" LLM으로 파인튜닝해주기 (4) | 2025.07.04 |
|---|---|
| 프롬프팅 구체적으로 작성하는 방법 (0) | 2025.07.03 |
| 대규모 언어 모델(LLM) 양자화해보기 (0) | 2025.06.27 |
| 데이터 추출 시스템 만들어보기 (0) | 2025.06.26 |
| GPT turbo 3 모델로 파인튜닝 해본썰 (0) | 2025.06.25 |