Streamlit • A faster way to build and share data apps
Streamlit is an open-source Python framework for data scientists and AI/ML engineers to deliver interactive data apps – in only a few lines of code.
streamlit.io
요즘 핫한 Streamlit. logo 부터 깔끔

Streamlit 이 뭐지?
Streamlit 은 python 으로 chat bot app 이나 data dashboard 를 쉽게 구현할 수 있는 open-source web app framework 다.
Steamlit 원칙 3가지
- Embrace scripting: magically simple API 로 몇줄의 코드로 앱을 만든다. 그리고 앱에서 변경사항 바로 확인 가능 !!
- Weave in interacton: widget 추가는 variable 선언처럼
- Deploy instantly: streamlit cloud 를 통해서 앱 배포 for free
Streamlit 기본 구조
https://docs.streamlit.io/develop/concepts/architecture/architecture
Server-Client Model
- Python backend (server): 설치한 Streamlit 이 backgroud 로 실행되고 script 의 실행과 front-end 와 통신
- Browser frontend (client): web app 은 사용자의 browser 에서 실행되고 사용자의 interaction 을 server 로 전달
- WebSockets / session
- server-client 통신은 WebSocket 을 통해서 연결 유지 -> real-time update 가능
- session context 도 각 user 마다 유지. 각 browser tab/window 는 개별 WebSocket / session 으로 처리
- ⚠️ 대규모 서비스 개발 시, Session affinity / stickness 고민 필요 e.g.) LB + multiple server replicas 환경에서 요청이 기존과 다른 서버로 전달되는 경우 필요한 정보 얻지 못할수 있음
┌─────────────────────────┐
│ Your Python Script │
└────────────┬────────────┘
│
Streamlit Runtime
│
┌──────────┴──────────┐
│ Widget Handler │
│ Session State │
└──────────┬──────────┘
│
WebSocket / HTTP
│
┌───────┴────────┐
│ Browser Client │
│ (React UI) │
└────────────────┘
API Referece
https://docs.streamlit.io/develop/api-reference
직관적인 API 설명으로 이해하기 쉽다.

설치
pip install streamlit
App 실행
✅ local 에 있는 script 실행
$ streamlit run your_script.py
✅ remote 에 있는 script 실행
$ streamlit run https://raw.githubusercontent.com/streamlit/demo-uber-nyc-pickups/master/streamlit_app.py
✅ python module 로 실행
$ python -m streamlit run your_script.py
일반 LLMs App 기본 구조
https://blog.streamlit.io/best-practices-for-building-genai-apps-with-streamlit/
Best Practices for Building GenAI Apps with Streamlit
Key strategies for building robust, scalable, and responsible GenAI apps with Streamlit
blog.streamlit.io
my_genai_app/
├── .streamlit/
│ └── config.toml # App configuration and styling
├── assets/ # Reusable UI assets (images, custom CSS, etc.)
├── utils/
│ └── llm.py # LLM utility functions
│ └── prompt.py # Prompt utility functions
├── pages/
│ ├── a_page.py
│ └── another_page.py
├── README.md # Describes the repo and its usage
├── requirements.txt # Python dependencies
├── streamlit_app.py # Main Streamlit app logic
재밌는건 pages 의 파일 이름이 그대로 sidebar 에 보인다는것. 정말 심플하고 편리한 기능이다.
일단 앱을 하나 만들고 하나씩 추가해보자. streamlit 의 장점이 바로바로 확인 가능한것이니~!
Streamlit 앱 만들기
1. 기본 Single Page 앱 구조
my-first-app
├── requirements.txt
└── streamlit_app.py
import streamlit as st
st.set_page_config( # 0️⃣
page_title="My Streemlit App",
page_icon="🤖",
layout="wide",
initial_sidebar_state="expanded"
)
st.title("Welcom to My Streamlit App! 🤖") # 1️⃣


basic LLM Chat app
streamlit 사이트에는 다양한 예제가 있어서 하나씩 따라하기 좋다. 그중 하나가 Chat and LLM app 예제이다.
streamlit 은 chat elemant - st.chat_message, st.chat_input 을 제공한다.
- st.chat_message: chat message 를 추가하는 element. single chat message container 에 chart, table, text 들을 추가
- st.chat_input: 사용자의 입력을 받는 widget
- Customize chat elements: st.chat_message 에 parameter 를 넘겨서 persona 별로 표시 가능
my-first-app
├── requirements.txt
├── streamlit_app.py
└── utils
└── prompt.py 👈🏻 prompt utililty functions
from utils.prompt import get_bot_response
...
# Chat message
# Name using AI
with st.expander("See Sample Chat Messages"): ✅ name parameter 로 여러 avatar 설정
with st.chat_message("AI", avatar=":material/thumb_up:"):
st.write("How can I help you?")
# Name using assistant
with st.chat_message("assistant", avatar="🦖"):
st.write("How can I help you?")
# Name using human
with st.chat_message("human"):
st.write("What is Streamlit?")
# Initialize chat history
if "messages" not in st.session_state:
st.session_state.messages = []
# Display chat messages from history on app rerun
for message in st.session_state.messages:
print(message)
with st.chat_message(message["role"], avatar=message["avatar"]):
st.markdown(message["content"])
# Chat input
if prompt := st.chat_input("Say something"): ✅ user 와 assistant 간 대화 표시
# Add user message to chat history
st.session_state.messages.append({"role": "user", "content": prompt, "avatar": "🩷"})
# Name using user
with st.chat_message("user", avatar="🩷"):
st.markdown(prompt)
response = get_bot_response(prompt) ✅ 사전 정의한 prompt 사용
# Display assistant response in chat message container
with st.chat_message("assistant", avatar="🦖"):
st.markdown(response)
# Add assistant response to chat history
st.session_state.messages.append({"role": "assistant", "content": response, "avatar":"🦖"})

2. Multiple Page 앱
/pages 디렉토리만 써도 해당 파일 명으로 sidebar 에 navigation 생성
- 앞에 숫자를 붙이면 순서 명시도 가능
- 물론, navigation 들을 customize 하려면 코드가 필요
- 파일명 규칙 : number (non-negative integer) + seperator ("_", "-", " ") + idetifier (".py" 이외).py
- e.g. "Awesome page.py", "33 - Awesome page.py"
- 기본적으로 pages 간 navigating 제공
my-first-app
├── pages
│ ├── 1_UI_Sample.py 👈🏻 파일명이 그대로 Navigation 에 표시
│ ├── 2_DeepSeek_Chatbot.py
│ └── 3_Llama_Chatbot.py
├── requirements.txt
└── streamlit_app.py

1. UI_Sample.py 에 각종 UI Element 를 넣어보자
- st.dataframe, st.table 로 chart 를 그리고 .add_rows() 로 data 를 추가하면서 업데이트
- st.progress 는 .progress() 호출로 업데이트
import streamlit as st
import pandas as pd
import numpy as np
import time
st.set_page_config(
page_title="UI Sample",
page_icon="🎨",
layout="wide",
initial_sidebar_state="expanded"
)
st.title("🎨 Various UI Componeents")
col1, col2 = st.columns(2)
with col1:
# Data를 차트에 시간에 따라 업데이트하는 샘플
st.subheader("📊 Time series data")
df = pd.DataFrame(np.random.randn(15, 3), columns=(["A", "B", "C"]))
my_data_element = st.line_chart(df) # Chart 컴포넌트 생성
data_progress = st.progress(0, "Loading...")
for tick in range(10):
time.sleep(.5)
add_df = pd.DataFrame(np.random.randn(1, 3), columns=(["A", "B", "C"]))
my_data_element.add_rows(add_df) # Chart 컴포넌트에 데이터 추가 👈🏻 애니메이션 효과
data_progress.progress((tick+1)/10, "Loading...") 👈🏻 progress 표시
data_progress.progress(1.0, "Done!")
st.button("Regenerate") 👈🏻 버튼
with col2:
# Data를 Pie 차트에 표시
cost_df = generate_cost_data()
st.subheader("💸 Cost by Service")
service_costs = cost_df.groupby('service')['cost'].sum().sort_values(ascending=False)
fig_services = px.pie( 👈🏻 파이차트
values=service_costs.values,
names=service_costs.index,
title="Cost Distribution by Service",
color_discrete_sequence=px.colors.qualitative.Set3
)
fig_services.update_layout(height=400)
st.plotly_chart(fig_services, use_container_width=True)
Streamlit 에서 기본으로 제공하는 메뉴 "Record a screencat" 으로 쉽게 화면 기록도 가능하다.
2. DeepSeek Chatbot 앱
아직 무엇을 할지 모르기때문에 섣불리 비용을 내기 싫으면 local LLM 을 사용할수 있고 그중 하나가 Ollama 이다.
Ollama 는 오픈소스 프로젝트로 llama.cpp (LLM 핵심 고성능 라이브러리) 를 쉽게 사용할 수 있도록 패키징하여 제공한다.
여러 모델을 제공하지만 일반적으로 16GB macOS 에서 사용하기에는 7B/8B 모델이 적당하고, 일반 채팅으로 llama3.2 를 많이 사용한다.
일단 사이트에서 ollama 를 설치하고 원하는 모델을 다운로드하면 된다.
$ ollama pull deepseek-r1:8b
$ ollama pull llama3.2
$ ollama list 👈🏻 설치한 모델 확인
Ollama 설명은 "Ollama를 이용한 로컬 LLM 완전 초보 가이드"에 친절하게 설명되어 있어서 참고하면 된다.
Streamlit 으로 Chatbot 을 구현하는게 목적이라 Ollama API 를 활용해야한다.
# Ollama API 설정
OLLAMA_BASE_URL = "http://localhost:11434" ✅ Local에 설치한 Ollama 서버 주소
DEEPSEEK_MODEL = "deepseek-r1:8b"
class OllamaClient:
def __init__(self, base_url: str = OLLAMA_BASE_URL):
self.base_url = base_url
def chat_stream(self, model: str, messages: List[Dict], **kwargs) -> Generator[str, None, None]:
"""스트리밍 채팅 응답"""
payload = {
"model": model,
"messages": messages,
"stream": True,
**kwargs
}
try:
response = requests.post( ✅ Ollama Rest API 로 응답 요청
f"{self.base_url}/api/chat",
json=payload,
stream=True,
timeout=120 # DeepSeek-R1은 추론 시간이 길 수 있음
)
if response.status_code == 200:
for line in response.iter_lines():
if line:
try:
data = json.loads(line.decode('utf-8'))
if 'message' in data and 'content' in data['message']:
yield data['message']['content']
if data.get('done', False):
break
except json.JSONDecodeError:
continue
else:
yield f"오류 발생: HTTP {response.status_code}"
except requests.exceptions.RequestException as e:
yield f"연결 오류: {str(e)}"
# 메인 채팅 영역
chat_container = st.container()
with chat_container:
# 대화 기록 표시
for message in st.session_state.messages:
with st.chat_message(message["role"]):
if message["role"] == "assistant" and "thinking" in message:
# 추론 과정이 있는 경우
if st.session_state.show_thinking and message["thinking"]:
with st.expander("🤔 추론 과정 보기", expanded=False):
st.markdown(f"```\n{message['thinking']}\n```")
st.markdown(message["content"])
else:
st.markdown(message["content"])
# 사용자 입력
prompt_input = st.chat_input("메시지를 입력하세요...")
if prompt_input:
# 템플릿 적용
template_prefix = template_options[selected_template]
full_prompt = template_prefix + prompt_input if template_prefix else prompt_input
# 사용자 메시지 추가
st.session_state.messages.append({"role": "user", "content": full_prompt})
# 사용자 메시지 표시
with st.chat_message("user"):
st.markdown(full_prompt)
# AI 응답 생성
with st.chat_message("assistant"):
# 진행 상황 표시
progress_placeholder = st.empty()
progress_placeholder.info("🧠 DeepSeek-R1이 생각하고 있습니다...")
message_placeholder = st.empty()
thinking_placeholder = st.empty()
full_response = ""
# 스트리밍 응답
try:
start_time = time.time()
response_stream = st.session_state.ollama_client.chat_stream(
model=DEEPSEEK_MODEL,
messages=st.session_state.messages,
options={
"temperature": temperature,
"num_predict": max_tokens,
"top_p": top_p,
}
)
first_chunk_received = False
for chunk in response_stream:
if not first_chunk_received:
progress_placeholder.empty() # 진행 상황 메시지 제거
first_chunk_received = True
full_response += chunk
# 실시간으로 추론 과정과 최종 답변 분리
thinking, final_answer = parse_deepseek_response(full_response)
# 추론 과정 표시 (옵션에 따라)
if st.session_state.show_thinking and thinking:
with thinking_placeholder.expander("🤔 추론 과정", expanded=True):
st.markdown(f"```\n{thinking}\n```")
# 최종 답변 표시
if final_answer:
message_placeholder.markdown(final_answer + "▌")
# 최종 정리
thinking, final_answer = parse_deepseek_response(full_response)
if st.session_state.show_thinking and thinking:
with thinking_placeholder.expander("🤔 추론 과정", expanded=False):
st.markdown(f"```\n{thinking}\n```")
message_placeholder.markdown(final_answer if final_answer else full_response)
# 응답 시간 표시
end_time = time.time()
response_time = end_time - start_time
st.caption(f"⏱️ 응답 시간: {response_time:.1f}초")
except Exception as e:
progress_placeholder.empty()
error_msg = f"오류가 발생했습니다: {str(e)}"
message_placeholder.error(error_msg)
full_response = error_msg
thinking = ""
final_answer = error_msg
# 응답을 대화 기록에 추가 (추론 과정 포함)
thinking, final_answer = parse_deepseek_response(full_response) if not full_response.startswith("오류") else ("", full_response)
st.session_state.messages.append({
"role": "assistant",
"content": final_answer if final_answer else full_response,
"thinking": thinking
})

3. Llama2 Chatbot
local 연동은 알았으니 이제 cloud 연동을 해보자. cloud server 연동이라 돈💰💰💰을 써야 한다.
https://blog.streamlit.io/how-to-build-a-llama-2-chatbot/ 여기 예제를 따라하다보니 replicate 로 진행했다.
사전 준비
- replicate api key 발급 및 결제 (최소 credit $10 지불...)
- openai 나 replicate 모두 library 를 제공하기 때문에 python library 를 설치
- API 사용 가이드 참고: https://replicate.com/docs/get-started/python
st.secrets 로 API Key 관리
- api key 와 같이 중요한 값들은 $CWD/.streamlit/secrets.toml 내에 저장하면 st.secrets 로 접근 가능
if 'REPLICATE_API_TOKEN' in st.secrets:
st.success('API key already provided!', icon='✅')
replicate_api = st.secrets['REPLICATE_API_TOKEN']
# Function for generating LLaMA2 response. Refactored from https://github.com/a16z-infra/llama2-chatbot
def generate_llama2_response(prompt_input):
string_dialogue = "You are a helpful assistant. You do not respond as 'User' or pretend to be 'User'. You only respond once as 'Assistant'."
for dict_message in st.session_state.llm_messages:
if dict_message["role"] == "user":
string_dialogue += "User: " + dict_message["content"] + "\n\n"
else:
string_dialogue += "Assistant: " + dict_message["content"] + "\n\n"
output = replicate.run(llm,input={"prompt": f"{string_dialogue} {prompt_input} Assistant: ", ✅ replicate 서버로 요청
"temperature":temperature, "top_p":top_p, "max_length":max_length, "repetition_penalty":1})
return output
# User-provided prompt
if prompt := st.chat_input(disabled=not replicate_api): 👈🏻 사용자 Input
st.session_state.llm_messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.write(prompt)
# Generate a new response if last message is not from assistant
if st.session_state.llm_messages[-1]["role"] != "assistant":
with st.chat_message("assistant"): 👈🏻 Llama2 응답
with st.spinner("Thinking..."):
response = generate_llama2_response(prompt)
placeholder = st.empty()
full_response = ''
for item in response:
full_response += item
placeholder.markdown(full_response)
placeholder.markdown(full_response)
message = {"role": "assistant", "content": full_response}
st.session_state.llm_messages.append(message)

LangChain 이 뭐지?
LangChain
LangChain’s suite of products supports developers along each step of their development journey.
www.langchain.com
LangcChain 은 LLM 을 더 똑똑하게 활용할수 있도록 도와주는 Framework 이다.
즉, LLM을 단순한 대화모델이 아니라 앱처럼 활용할수 있게 해주는 개발 키트이다.
주요 기능
- Prompt 관리: 질문을 구조적으로 관리. 복잡한 입력을 템플릿화
- Chain: 여러 단계의 추론이다 도구 호출을 Chain 처럼 연결가능
- Memory: 이전 대화 저장해서 맥락을 이해한채로 대화 가능
- Tools & Agent: DB 검색, API 호출, 계산기 같은 외부 도구 연결 가능
- Integration: OpenAPI, Anthropic 과 같은 모델이나 벡터DB와 연결해서 RAG 시스템 쉽게 구성
4. LangChain 활용 Chatbot
LLM 모델에 따라 interface 가 다르기 때문에 모델 별 코드가 달랐다면, LangChain 의 기능을 사용하면 다양한 모델을 쉽게 바꿀수 있다. https://python.langchain.com/docs/integrations/chat/
☝🏻 OpenAI 모델 사용시
from langchain_openai import ChatOpenAI
os.environ['OPENAI_API_KEY'] = st.secrets['OPENAI_API_KEY']
def generate_response(input_text) -> str:
chat_model = ChatOpenAI(
model="gpt-4o-mini",
temperature=temperature,
top_p=top_p,
max_tokens=max_length
)
response = chat_model.invoke(input_text)
return response.content
}
☝🏻 Local 설치 Ollama 모델 사용시
from langchain_ollama import ChatOllama
def generate_response(input_text) -> str:
chat_model = ChatOllama(
model="llama3.2:latest",
temperature=temperature,
top_p=top_p,
max_tokens=max_length
)
response = chat_model.invoke(input_text)
return response.content
}
이외 나머지 코드는 동일
5 Retrieval Augmented Generation (RAG)
RAG는 LLM 을 강화시키기 위해 외부의 knowledge base 와 통합하는 기술을 얘기한다. 이러한 RAG를 사용하는 장점은
- Up-to-date information: 최신 데이터를 연동해서 최신 정보 제공 가능
- Domain-specific experties: 특정 domain 과 관련된 정보 제공 가능
- Reduces hallucations: 주어진 knowledge 가 허위정보를 줄일 가능성 큼
- Cost-effective knowledge integrations: LLM 자체를 튜닝하기 보다는 RAG를 통해 새로운 데이터 도입이 비용 효율적
RAG 는 두개의 main component 로 구성
1) Indexing: 외부 데이터를 가져와서 처리하여 embedding 으로 저장 (Load -> Split -> Embed -> Store)

2) Retrieval and generation: 사용자가 질문하면 저장된 정보를 가져와서 질문에 대한 응답 생성 (Retrieve -> Generate)

간단하게 PDF 파일을 올려서 질문하는 예제를 따라해보자.
1) Load
uploaded_file = st.file_uploader(
"Choose a PDF file", type=["pdf"], accept_multiple_files=False
)
if uploaded_file:
texts = process_pdf(uploaded_file)
----
from langchain_community.document_loaders import PyPDFLoader ✅ langchain 에서 제공하는 PDFLoader 활용
def process_pdf(uploaded_file):
with tempfile.TemporaryDirectory() as tmp_dir:
filepath = os.path.join(tmp_dir, uploaded_file.name)
with open(filepath, "wb") as f:
f.write(uploaded_file.getvalue())
st.success(f"File {uploaded_file.name} uploaded successfully!")
loader = PyPDFLoader(filepath)
pages = loader.load_and_split()
2) Split
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=20,
length_function=len,
is_separator_regex=False
)
texts = text_splitter.split_documents(pages)
3) Embed
embeddings_model = OllamaEmbeddings(model="nomic-embed-text") ✅ ollama 의 embedding 모델 선택
4) Store: open source vector db 인 chroma db 를 설치해서 local 에서 사용하자.
embeddings_model = OllamaEmbeddings(model="nomic-embed-text")
vectordb = Chroma.from_documents(
documents=texts,
embedding=embeddings_model
)
5) Retrive: multiquery 로 가져오기
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
def format_docs(docs):
return "\n\n".join([doc.page_content for doc in docs])
def get_llm_chain(vectordb):
template = """Use the given context to answer the question.
If you don't know the answer, say you don't know.
Use three sentence maximum and keep the answer concise.
Always say "thanks for asking!" at the end of your answer.
{context}
Question: {question}
Helpful Answer: """
prompt = PromptTemplate.from_template(template) ✅ prompt template
llm = ChatOllama(model="llama3.2:latest", temperature=0) ✅ llm 은 llama3.2
chain = (
{"context": vectordb.as_retriever() | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
)
return chain
이렇게 chain 이 만들어지면. invoke 호출해서 응답을 얻는다.
result = chain.invoke(question)
'자습' 카테고리의 다른 글
| Flutter PWA + Firebase (Cloud Messaging) (0) | 2025.04.12 |
|---|---|
| AWS ASG lifecycle Hook (remotely run shell command on EC2) (0) | 2024.03.29 |
| AWS EC2 X-Ray Enable (0) | 2024.02.24 |
| Go Thread-Safety : sync.Mutex, sync.Map (0) | 2023.08.26 |
| Spring Native (0) | 2022.05.14 |