데이터 분석용 Chatbot 개발과정
개요
이번 주제는 OpenAI API (Application Programming Interface) 를 활용한 챗봇 개발입니다. 😃Wow~
아래 결과물을 먼저 살펴보겠습니다.
이 글은,
OpenAI API를 이용해 챗봇 GUI를 구성하고 데이터파일(*.xlsx)을 추가, 대화내용에 데이터를 포함한 분석 결과를 챗봇으로부터 얻는 과정을 설명합니다.
개발환경
Windows 11 Pro, Python 3.11
openai 1.63.2, pandas 2.2.3
pyqt6 6.8.1
openAI 회원가입 후 결재
저는 US$ 10불을 작년에 결재해 두고 잊은게 있었습니다. 😅
(참고로 사용기간제한이 있으므로 테스트 시 너무 많은 금액을 결재하지
마세요)
결재 후 API Key, Billing Info, Usage 등을 모니터링 가능합니다.
![]() |
OpenAI 모니터링 페이지 |
분석용 샘플 엑셀파일
-
기상청 기상자료개방포털에서 가져온 엑셀파일
참고로, 샘플 엑셀파일은 기상청에서 받은 제주도의 2개월간 기온, 습도, 풍속 등의 단순데이터 입니다.
![]() |
엑셀 샘플파일 |
단순 채팅을 넘어서 데이터를 챗봇에게 제공하고 그 분석 결과를 시험해보고 싶었습니다.
GUI 화면 구성
아래는 PyQt6로 제작된 화면 구성입니다.
데이터를 추가하는 부분과 채팅을 진행하는 2개의 파트로 이루어져 있으며, 필요시 엑셀 파일을 추가해 챗봇과 상담이 가능합니다.
(다만 테스트에 사용한 'gpt-3.5-turbo' 모델의 토큰 허용량이 적어
데이터가 크면 오류나서 제한해 두었습니다.)
![]() |
실행화면 |
참고로 gpt-3.5-turbo Model의 비용이 가성비가 좋아보여 테스트시 사용하고 있습니다.
아래는 1M(메가) token 당 비용입니다.
![]() |
출처 : openAI Pricing |
OpenAI 토큰이란?
토큰(token)은 일반적인 문자열의 한 글자가 아니라, 텍스트의 작은 단위를 의미합니다.
토큰은 일반적으로 단어, 부분 단어, 공백, 문장 부호 등으로 나뉘며, 언어와 문장 구조에 따라 다르게 계산됩니다.
Model별 차이는 있지만 예를 들면,
- "Hello, world!" → 2 tokens
- "안녕하세요" → 2~5 tokens
- 한글은 영어보다 토큰 개수가 더 많아질 수 있으며, 한글자가 2~3토큰
정도로 이해하면 좋을 것 같습니다.😉
세부적인 내용은
OpenAI Tokenizer 계산기를 사용해 보세요.
소스코드
소스코드 내용을 *.py 파일로 복사하고 같은 위치에 main.ui 파일(아래링크)을 위치한 뒤 실행시키면 예제코드가 동작합니다.
소스코드에 사용된 ui 파일링크 : main.ui (우클릭, 다른이름 저장, 소스코드와 같은 경로에 위치)
참고로 *.ui는 xml형태로 Qt Control 정보를 저장합니다.
from PyQt6.QtWidgets import QApplication, QWidget, QFileDialog
from PyQt6.QtCore import QEvent, Qt
from PyQt6.uic import loadUi
import sys
import pandas as pd
from openai import OpenAI
class Window(QWidget):
OPENAI_KEY = 'input openAI API key here!'
def __init__(self):
super().__init__()
loadUi('main.ui', self)
self.setWindowTitle('Charbot_OpenAI - Ocean Coding School')
self.te_resp.setReadOnly(True)
self.dfs = dict()
self.client = OpenAI(
api_key=Window.OPENAI_KEY,
organization='your org ID',
project='your project name',)
self.history = []
# model combobox
models = ('gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo', 'gpt-4', 'gpt-4-turbo', 'text-embedding-3-small')
self.cmb.addItems(models)
#default 3.5-turbo
self.cmb.setCurrentIndex(2)
#signal
self.pb_add.clicked.connect(self.onAddFile)
self.pb_del.clicked.connect(self.onDelFile)
self.pb_send.clicked.connect(self.onSend)
QApplication.instance().installEventFilter(self)
def eventFilter(self, obj, e):
if obj==self.te_query:
if e.type()== QEvent.Type.KeyPress and e.key()==Qt.Key.Key_Return:
self.onSend()
return super().eventFilter(obj, e)
def getStringFromDF(self):
# get a string from dataframes
all_data = ''
for name, df in self.dfs.items():
all_data += f'File: {name}\n'
all_data += df.to_string(index=False)
# limit 5 rows
#all_data += df.head(5).to_string(index=False)
all_data += '\n\n'
return all_data
def onAddFile(self):
path, ok = QFileDialog.getOpenFileNames(self, '', '', 'Excel Files(*.xlsx)')
if ok:
for file in path:
# add listwidget
name = file.split('/')[-1]
if name not in self.dfs:
self.lw_file.addItem(name)
# add pandas, only single sheet
with pd.ExcelFile(file) as xlsx:
df = pd.read_excel(xlsx, xlsx.sheet_names[0])
self.dfs[name] = df
self.history.append({'user': 'system', 'response': self.getStringFromDF()})
def onDelFile(self):
row = self.lw_file.currentRow()
if row>=0:
try:
item = self.lw_file.takeItem(row)
except Exception as e:
print(e)
else:
name = item.text()
if name in self.dfs:
del self.dfs[name]
#print( 'Number of df:', len(self.dfs) )
def onSend(self):
# get query
query = self.te_query.toPlainText()
if query:
# add query on chatboard
self.te_resp.append(f'User: {query}')
response = self.get_answer_from_chatbot(query, self.history)
self.te_resp.append(f'Chatbot: {response}\n')
# clear query
self.te_query.clear()
def get_answer_from_chatbot(self, query, conversation_history):
# prepare
conversation = [{'role': 'system', 'content': 'You are a helpful assistant.'}]
for turn in conversation_history:
conversation.append({'role': 'user', 'content': turn['user']})
conversation.append({'role': 'assistant', 'content': turn['response']})
# user query
conversation.append({'role': 'user', 'content': query})
try:
sel_model = self.cmb.currentText()
# Chat API 호출
response =self.client.chat.completions.create(
#model='gpt-3.5-turbo',
model = sel_model,
messages=conversation,
#max_tokens=150
)
except Exception as e:
return e
else:
# 챗봇 응답
return response.choices[0].message.content.strip()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec())
그럼 코드를 함수별로 분석해 보겠습니다.
1. __init__(self):
- UI를 초기화하고, 창 제목을 설정
- OpenAI 클라이언트를 설정하고, 대화 기록을 초기화
- 버튼 클릭 이벤트(파일 추가, 삭제, 메시지 전송)에 연결된 슬롯을 설정
- eventFilter를 설치하여 te_query 텍스트 상자에서 엔터키 입력을 감지
2. eventFilter(self, obj, e):
- te_query 텍스트 상자에서 엔터키를 눌렀을 때 onSend() 함수를 호출
3. getStringFromDF(self):
- 현재 열린 Excel 파일들의 내용을 문자열로 반환
- 데이터프레임에서
5개의 행만전체 행을 가져와 문자열로 반환
4. onAddFile(self):
- 파일 선택 대화 상자를 통해 Excel 파일을 추가.
- 선택한 파일의 첫 번째 시트를 읽어 pandas DataFrame으로 저장, 파일 이름을 리스트에 추가
5. onDelFile(self):
- 선택한 파일을 리스트에서 삭제, 해당 DataFrame을 self.dfs에서 제거
6. onSend(self):
- 텍스트 상자에서 사용자의 입력을 가져와 챗봇에게 전송
- 챗봇의 응답을 받아 텍스트 상자에 표시
- 사용자 입력 후 입력란을 초기화
7. get_answer_from_chatbot(self, query, conversation_history):
- 대화 기록에 History를 포함, OpenAI API 로 보낼 대화형식 Query 생성
- 사용자 입력을 포함한 대화 형식을 OpenAI에 전달하여 답변
8. if __name__ == '__main__':
- 애플리케이션을 실행
- sys.exit(app.exec())로 실행하고 리턴시 앱 종료
느낀점
ChatGPT와 대화 시 이전 채팅내용을 현재 채팅내용에 계속 추가 포함시킨다는 사실.😰
그럼 채팅이 길어질수록 사용되는 데이터는 늘어날 수 밖에 없는 구조.🤑
따라서 conversation list에서 오래된 대화를 자르거나 요약해서 관리해야 할 필요성.🤔
그리고 챗봇에게 전송되는 데이터파일 또한 문자열로 채팅처럼 포함되어 전송된다는 사실.👌
이상으로 모든 설명을 마칩니다. 감사합니다.
2025.02.20 Updated
-
다양한 OpenAI LLM Model을 적용하기위해 콤보박스 추가.
토큰 허용량이 큰 모델에 파일을 업로드 후 분석 테스트
예제 코드의 첨부파일 제한 해제 (기존 5행으로 제한)
![]() |
엑셀파일 크기의 토큰 전송테스트 |
제주의 약 2개월치 기온, 풍속, 습도 등을 엑셀로 제공하고 분석을 의뢰.
답변한 1월 9일보다 기온이 낮은 날이 존재 (오류인가?)
흥미로운 점은 더 추운기온(제주 2월 7일)이 있었지만, 풍속, 습도를 고려해
대답한 점. 😮
와 👍 대단하십니다 컨셉으로 멋진 결과물을 도출해 내시는군요! AI chat 에 대해 더 공부 하고픈 마음이 듭니다.
답글삭제네, 아직까진 Chatbot LLM의 입,출력 토큰 허용량이 기업의 특정데이터를 분석할 만큼 크지 않지만 빠르게 개선되지 않을까 생각합니다.
삭제