-
LLM 챗봇 개발하기 더 유용한 프레임워크는? (Gradio vs Streamlit)Data & ML & AI/기타 모델, 알고리즘, 툴 2025. 3. 10. 02:01반응형
간단한 AI 데모를 만들려고 할 때,
프론트 개발자가 아닌 이상, 인터페이스 구성까지 일일이 신경쓰기는 부담스러운 법입니다.
그때 주로 사용되는 프레임워크가 바로 Gradio와 Streamlit입니다.
가슴이 웅장해진다 개인적으로 기존 타 프로젝트 진행할 때는 Streamlit을 주로 사용해 왔었는데요,
LLM 챗봇 예제들 중에는 Gradio를 사용한 것들이 꽤 많기도 했고
최근 듣고 있는 강의에서는 Gradio를 위주로 사용하고 있어
적어도 각각의 특징과 최소한의 사용감을 비교해 보고자 했습니다.
챗봇 프로젝트의 유형에 따라 어떨 때 이걸 쓰고 어떨 때 저걸 쓸지, 아니면 아예 갈아탈지...
주로 아래의 자료를 주로 참고했고, 마지막으로 제가 개인적으로 느낀 사용감을 첨언해 보겠습니다.
- 자료1: https://www.youtube.com/watch?v=rJ4_7pnIRmA
- 자료2: https://vladlarichev.com/llm-genai-frameworks-gradio-vs-streamlit/
< 비교 요약 (출처: 자료1, 2) >
Gradio Streamlit 특징 - input-output 형태의 컴포넌트 위주
- input을 위한 요소 + output을 위한 요소가 기본
→ "머신러닝/AI 개발자에게 더 좋다는 의견 많음"- 대시보드, 차트, 위젯 등 구성에 유리
→ "데이터 사이언티스트에게 더 좋다는 의견 많음"작동방식 - 함수의 작동이 중심이 됨
- 유저가 버튼을 클릭할 때 함수 run/re-run
→ 버튼을 클릭하는 경우에만 UI 및 컨텐츠 변경됨- 스크립트 기반으로 작동
- 일단 스크립트에 작성된 대로 run
- 유저가 요소의 상태를 변경하면 re-run
(요소: 필터 선택, 텍스트 입력, 버튼클릭 등)
→ 상태가 변경될 때마다 UI 및 컨텐츠 변경가능커스터마이징 - 비교적 엄격한 구조 및 역할
- 정해진 규칙/역할 내에서 컴포넌트 사용
예) 버튼을 누르기 전까지는 아무것도 바뀌지 않음- 비교적 커스터마이징에 유리
예) 필터를 선택하니 그래프/표 내용이 변경됨
예) 자료를 업로드 하는 것 만으로도 분석을 진행해줌복잡성,
학습곡선비교적 낮음, 쉬움 중간 커뮤니티 비교적 작음 비교적 큼 배포&공유 - 상대적으로 쉬움
- Hugging Face 배포, 링크 공유 등- 상대적으로 복잡하지만 자유도 높음
- Streamlit Cloud, Docker, 직접 서버운영 등챗봇구현
편의- chatbot() 등의 기능 미리 구현하여 제공 - 채팅 UI는 직접 구성해야 함
- 예제는 많음보다
적합한
사례- 대화형 UI 중심의 서비스를 구현하고자 할 때
- 핵심기능을 빠르게 구현해서 테스트 하고자 할 때
- 간단히 배포하고자 할 때- 데이터 분석 등의 시각화 결과 & LLM 결과를 함께 보여줘야 할 때 (ex. 대화형 대시보드)
- 좀 더 자유로운 기능/UI 구성이 필요할 때, 서비스 커스터마이징이 필요할 때
- 좀 더 복잡한 형식의 배포가 필요할 때1. 단순 챗봇 기본 예제 비교
각 공식문서가 제공하는 기본 예제 코드를 비교해보면
Gradio와 Streamlit이 어떤 느낌으로 구현되고 작동하는지 대략 감이 옵니다.
[기본예제]
- 연속적인 대화(멀티턴) 기능 X
- 챗 스트리밍 기능 X
0) 공통작업: LLM 정의
- 저는 편의상 Langchain을 이용해서 정의했지만, 아무거나 상관 없습니다.
- gradio, streamlit 모두에 테스트하기 위해 llm 세팅을 llm.py 파일에 따로 했습니다.
- LLM 정의 등의 공통작업이 이 글의 메인주제는 아니기 때문에 접은글 내에 표기하겠습니다.
더보기a) 세팅
# llm.py import os from langchain.chat_models import ChatOpenAI from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler # 스트리밍 from langchain_core.output_parsers import StrOutputParser os.environ['OPENAI_API_KEY'] = "api key 값" # LLM 객체, 함수 정의 llm = ChatOpenAI( temperature=0.1, max_tokens=2048, model_name="gpt-4o-mini", ) # 함수로 만들 필요까진 없지만, 아래 코드들에서의 가독성을 위해 함수로 만들었습니다. def llm_chat(user_input): response = llm.invoke(user_input) return response.content
b) 테스트
from llm import llm_chat llm_chat("안녕 넌 누구니?")
1) Gradio
- https://www.gradio.app/docs/gradio/chatbot
import gradio as gr from llm import llm_chat # gradio 정의 with gr.Blocks() as demo: chatbot = gr.Chatbot(type="messages") msg = gr.Textbox(label='내용을 입력해주세요') clear = gr.ClearButton([msg, chatbot]) # gradio 내 respond 함수 정의 def respond(user_message, chat_history): # chat 실행 bot_message = llm_chat(user_message) # history에 추가 chat_history.append({"role": "user", "content": user_message}) chat_history.append({"role": "assistant", "content": bot_message}) return "", chat_history # 유저가 submit 하면 작동 msg.submit(respond, [msg, chatbot], [msg, chatbot]) if __name__ == "__main__": demo.launch()
- "함수"의 작동이 gradio 서비스의 중심입니다. (그래서 <비교요약>에서 input/output이 기본이라고 언급함)
- gradio 앱 내에 메인 기능에 대한 함수(respond)를 구현합니다.
- 유저가 msg.submit()하면 작동합니다.
Gradio 예제로 만든 간단 챗봇 기본예제 체험 후 Gradio에 대한 개인적 소감
(저는 stramlit에 더 익숙했음을 먼저 밝힙니다.)
- 구현 방법
- 함수 중심의 체계라는 것이 살짝 낯설다.
- msg.submit({함수명}, {함수input}, {함수output}) 코드가 많이 낯설다.
- "chatbot, msg 등의 UI 요소 = 채팅 내역 데이터" 인 느낌이라 적응하는데 시간이 걸렸다. (취향 차이 있을 듯)
- 구현 결과
- LLM의 응답이 끝나야 유저가 입력한 메세지도 채팅창에 나타난다..
유저입력 → llm결과가 나올 때까지 화면 바뀌지 않음 → 결과가 나와야 유저입력사항도 화면에 표시됨 - 심플한 구성이다.
- 기본 디자인 서식이 별로다. 내 취향이 아니다
- LLM의 응답이 끝나야 유저가 입력한 메세지도 채팅창에 나타난다..
2) Streamlit
- https://docs.streamlit.io/develop/tutorials/chat-and-llm-apps/build-conversational-apps
import streamlit as st from llm import llm_chat st.title("Simple chat") # 세션상태에 채팅 히스토리 추가 if "chat_history" not in st.session_state: st.session_state.chat_history = [] # rerun 될 때마다 메세지 히스토리를 UI 상에 표시 for message in st.session_state.chat_history: with st.chat_message(message["role"]): st.markdown(message["content"]) # 유저가 submit 하면 작동(rerun) if user_message := st.chat_input("내용을 입력하세요"): # 유저 메세지 UI에 표시 st.chat_message("user").markdown(user_message) # chat 실행 bot_message = llm_chat(user_message) # 챗봇 메세지 UI에 표시 st.chat_message("assistant").markdown(bot_message) # history에 추가 st.session_state.chat_history.append({"role": "user", "content": user_message}) st.session_state.chat_history.append({"role": "assistant", "content": bot_message})
- 메인 기능 구현을 "if절"로 합니다. (그래서 <비교요약>에서 상태를 변경하면 "re-run"이라고 언급함)
- st.session_state로 상태를 관리합니다.
- 유저가 st.chat_input() 을 하면 작동합니다.
Streamlit 예제로 만든 간단 챗봇 기본예제 체험 후 Gradio에 대한 개인적 소감
- 구현 방식
- UI 구현 따로, 기능 구현 따로의 느낌이 더 강다.
- 구현되는 방식이 좀 더 코드의 흐름에 가까워 상대적으로 더 직관적인 느낌이다.
- 구현 결과
- 처음 웹서비스를 켤 때, 프론트가 눈 앞에 나타나기까지 더 오래 걸린다.
- 정보가 왔다갔다 하는 과정이 좀 더 느리다.
- 기본 디자인 서식이 조금 더 예쁘다
Gradio(좌) vs Streamlit(우) 저는 streamlit에 더 익숙한 상황이였지만 이렇게 두 코드를 같이 놓고 비교해보니,
gradio의 코드가 더 한 눈에 들어오고 보기 편한 것 같기도 합니다.
2. 기능 몇 개 더 넣어서 추가비교
이대로 마무리하기엔 아쉬워, 기능을 추가해 비교해 봅시다.
[심화예제]
- RAG
- 연속적인 대화(멀티턴) 기능 O
- 챗 스트리밍 기능 O
0) 공통작업: 벡터저장소, LLM 정의
- 공통작업이 이 글의 메인주제는 아니기 때문에 접은글 내에 작성합니다.
- gradio, streamlit 모두에 테스트하기 위해 벡터스토어 세팅을 store.py에, llm 세팅을 llm_stream.py 파일에 따로 했습니다.
더보기a) 벡터저장소 세팅
# store.py from langchain.document_loaders import DirectoryLoader from langchain.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.document_loaders import PyMuPDFLoader from langchain.embeddings import OpenAIEmbeddings from langchain_community.vectorstores import FAISS # 문서 로드(텍스트 문서는 따로 준비) loader = DirectoryLoader('.', glob="*.txt", loader_cls=TextLoader) loaded_documents = loader.load() # 문서 청킹 text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) documents = text_splitter.split_documents(loaded_documents) # 벡터스토어 생성 embeddings = OpenAIEmbeddings() vector_store = FAISS.from_documents(documents, embeddings) vector_store.save_local(folder_path="faiss_db", index_name="faiss_index") # 리트리버 객체 생성 retriever = vector_store.as_retriever(search_kwargs={'k':1}, verbose=True) # 문서검색 테스트 retriever.get_relevant_documents("서울시 신혼부부를 위한 정책을 알려줘")
b) LLM 세팅
# llm_stream.py from store import get_retriever from langchain.chat_models import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser os.environ['OPENAI_API_KEY'] = "api key 값" # LLM 객체, 함수 정의 llm = ChatOpenAI( temperature=0.1, max_tokens=2048, model_name="gpt-4o-mini", ) retriever = get_retriever() def llm_chat(user_input, chat_history): prompt = """ Answer the question based on the Previous Conversations and following Documents. # Documents {documents} Question: {user_input} """ template = ChatPromptTemplate.from_messages( [ ("system", "You are a helpful AI bot."), MessagesPlaceholder("chat_history"), ("human", prompt), ] ) chain = ( {"documents": retriever, "user_input": RunnablePassthrough(), "chat_history": lambda _:chat_history} | template | llm | StrOutputParser() ) return chain.stream(user_input)
from llm_stream import llm_chat chat_history = [ ("human", "서울시 청년 주거정책을 찾아줘"), ("ai", "제공된 정보에는 서울시 청년 주거정책에 대한 내용이 포함되어 있지 않습니다. 대신 서울시 청년수당에 대한 정보가 포함되어 있습니다. 서울시 청년 주거정책에 대한 구체적인 내용을 원하신다면 서울시 공식 웹사이트나 관련 부서에 문의하시는 것이 좋습니다."), ] for token in llm_chat("그럼 그걸 알려줘",chat_history): print(token, end='')
스트리밍 테스트 1) Gradio
import gradio as gr from llm_stream import llm_chat with gr.Blocks() as demo: chatbot = gr.Chatbot(type="messages") msg = gr.Textbox(label='내용을 입력해주세요') clear = gr.Button("Clear") # 유저 def user(user_input, history: list): return "", history + [{"role": "user", "content": user_input}] def bot(history: list): # history에서 가장 마지막 메세지(유저 메세지)를 다시 꺼내옴 user_message = history[-1]["content"] # history에 assistant의 빈 응답을 추가 history.append({"role": "assistant", "content": ""}) # 빈 응답에 스트리밍 방식으로 토큰이 올 때마다 history 내에도 내용을 추가 for token in llm_chat(user_message, history): history[-1]["content"] += token yield history msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then( bot, chatbot, chatbot ) clear.click(lambda: None, None, chatbot, queue=False) if __name__ == "__main__": demo.launch(debug=True)
- 스트리밍 구현을 위해, user함수와 bot함수를 별개로 구현합니다.
- 스트리밍 구현을 위해, 매 토큰을 for문으로 처리해 history를 업데이트 합니다.
- msg.submit() 다음에 then()이 추가로 붙었습니다.
Gradio 챗봇 + 스트리밍 예제 시험 후 Gradio에 대한 개인적 소감
- 코드 구조가 왠지 많이 바뀌었다.
- user, bot 함수로 분할되면서 가독성이 좀 떨어졌다
- 검색하지 않고는 msg.submit().then() 작동 방식을 잘 모르겠다.
gradio 기본예제 대비 변경된 부분 2) Streamlit
import streamlit as st from llm_stream import llm_chat st.title("Simple chat") # 세션상태에 채팅 히스토리 추가 if "chat_history" not in st.session_state: st.session_state.chat_history = [] # rerun 될 때마다 메세지 히스토리를 UI 상에 표시 for message in st.session_state.chat_history: with st.chat_message(message["role"]): st.markdown(message["content"]) # 유저가 submit 하면 작동(rerun) if user_message := st.chat_input("내용을 입력하세요"): # 유저 메세지 UI에 표시 st.chat_message("user").markdown(user_message) # 챗봇 메세지 UI에 표시 with st.chat_message("assistant"): bot_message = st.write_stream(llm_chat(user_message, st.session_state.chat_history)) # history에 추가 st.session_state.chat_history.append({"role": "user", "content": user_message}) st.session_state.chat_history.append({"role": "assistant", "content": bot_message})
- 챗봇 호출을 st.write_stream()으로 감쌌습니다.
- 챗봇 메세지를 with로 감쌌습니다.
Streamlit 챗봇 + 스트리밍 예제 시험 후 Streamlit에 대한 개인적 소감
- 코드 구조가 상대적으로 덜 바뀌어서 이해하기 편하다.
- 느리긴 느리다
streamlit 기본예제 대비 변경된 부분 3. 후기
기본적인 챗봇 형태 구현을 중심으로 Gradio와 Streamlit을 비교해 봤습니다.
제가 애초에 Streamlit에 보다 익숙해서 그런지는 모르겠지만,
Gradio보다는 Streamlit이 더 편하고 직관적인 느낌이였습니다.
좀 더 예쁜건 덤...
하지만 다음의 경우들이라면 저는 Streamlit보다는 Gradio를 택할 것 같습니다.
1. 로컬 컴퓨터가 아니라 구글 Colab으로 개발, 테스트 하는 경우
- 그냥 Gradio 쓰세요
- Streamlit도 불가능한건 아니지만.... 세팅, 검토 및 디버깅 절차가 너무 귀찮습니다.
구글링하면 방법들이 많이 뜨긴 하고, 또 어려운건 아니지만 귀찮...2. 단순 챗봇 뿐 아니라 여러가지 기능을 한 화면에서 제공하는 서비스 MVP를 만드는 경우
- 오디오, 이미지 생성 등의 기능이 함게 있는 MVP를 구현하기에는 Gradio가 더 편합니다.
- streamlit도 가능은 하지만, gradio의 함수, 메소드가 더 간편하다고 합니다.
https://www.analyticsvidhya.com/blog/2023/06/build-a-chatgpt-for-youtube-videos-with-langchain/ - 반대로 streamlit은 그래프, 표 등과 함께 보여줄 때 더 좋습니다.
- 무조건 함수가 돌아가야 화면이 변경되는 gradio와 달리, 필터 선택지만 바꿔도 그래프/표 내용을 볼 수 있으니까요.
https://blog.streamlit.io/chat2vis-ai-driven-visualisations-with-streamlit-and-natural-language/ 결론
- 어처피 어느정도 규모있는 회사라면 gradio, streamlit은 B2B 서비스용 도구가 아니다.
내부 테스트용 or MVP용이다. (전 그렇다고 생각합니다...)
"이런거 만들어봤는데, 어때요? 우리 백엔드팀 프론트엔드팀 붙어서 본격 개발하면 그럴싸 할 것 같지 않나요?"
위에서 스트리밍 기능, 디자인 등을 얘기하긴 했지만, 그런건 내부 테스트, MVP에선 중요하지 않을 수 있다.
메인 서비스, 핵심 기능이 더 중요하지... - 어떤 형태의 챗봇 프로젝트를 개발하느냐에 따라 다르다. 프로젝트 성격에 맞는 도구를 선택해보자.
- 뭘 선택하든 불가능한건 아니다. 본인이 더 익숙한거, 편한거 하는게 더 나을 수도 있다.
스터디에서는 gradio 쓰고 있으니까 gradio 쓰고, 혼자 할 때는 streamlit이 더 편하니까 streamlit 쓸래요
반응형'Data & ML & AI > 기타 모델, 알고리즘, 툴' 카테고리의 다른 글
[UI/UX] AI 디자인툴 5개 비교 (Figma vs Creatie vs Motiff vs Uizard vs Galileo) (0) 2025.01.01 모델 파일 load 속도 비교 (joblib vs Pickle vs cPickle) (0) 2023.06.28