- Streamlit은 맞춤형 AI 챗봇 UI를 빠르게 만들 수 있는 방법이지만, 내장된 채팅 컴포넌트 이상의 유연성이 필요합니다.
- Botpress Chat API는 챗봇의 논리, 검색, 워크플로우를 지원하며, 맞춤형 Python 클라이언트를 통해 사용할 수 있습니다.
- Streamlit 앱은 대화를 관리하고, 응답을 스트리밍하며, 동적인 사용자 세션과 통합됩니다.
제가 알기로 Streamlit은 커스터마이즈 가능한 웹앱을 가장 빠르게 시작할 수 있는 방법입니다. 만약 AI 챗봇을 만들고 직접 프론트엔드에 배포하고 싶다면, 이보다 더 나은 선택지는 없을 것 같습니다.
유일한 아쉬운 점은 채팅 요소 라이브러리입니다. 이 라이브러리는 OpenAI API와 Python 클라이언트에 꽤 특화되어 있습니다.
이 점은 훌륭합니다. 몇 줄의 코드만으로 최고의 기술과 상호작용할 수 있다는 건 정말 멋진 일이죠.
하지만 이게 전부는 아닙니다.
봇을 좀 더 세밀하게 제어하고 싶으신가요? 예를 들어, 여러 단계로 이루어진 워크플로우나 검색 기반 생성(RAG) 기능이 필요할 수 있습니다. 이러한 추가 기능을 구현하려면 보통 다양한 라이브러리와 복잡한 의존성을 다뤄야 합니다.
꼭 그런 것만은 아닙니다.
이 튜토리얼에서는 Streamlit에서 호스팅되는 챗봇 클라이언트를 만들어보겠습니다. 빠르게 반복 개발하고, 자유롭게 커스터마이즈할 수 있는 챗봇 인터페이스를 보여드릴게요. 그리고 OpenAI 스타일의 맞춤형 Python 클라이언트로 챗봇을 통합하는 방법도 배울 수 있습니다.
프로토타입을 만들 때는 의존성이나 기술적인 문제에 얽매일 필요가 없습니다.
빠른 프로토타이핑의 취지에 맞게, 튜토리얼을 건너뛰고 바로 실습하고 싶다면 코드는 GitHub에 있습니다.
시작해봅시다 💣
1단계: 챗봇 논리 만들기
워크플로우 자동화든 예약 챗봇이든, 여기서 할 수 있는 일은 무궁무진합니다.
아이디어가 필요하다면 GenAI 챗봇의 다양한 활용 사례를 꼭 살펴보세요. 예시로는, 이제는 유명해졌을지도 모를 소믈리에 Winona를 다시 소개하겠습니다.
우리의 똑똑하고 친절한 챗봇은 몇 단계만 거치면 완성됩니다. 간단히 설명하겠지만, 자세하고 유용한 튜토리얼도 많으니 참고하세요.
1. 지침 작성하기
스튜디오에서 왼쪽 사이드바의 Home으로 이동합니다.

Instructions 섹션이 중앙에 보일 것입니다. 클릭해서 지침을 추가하거나 수정하세요.

이곳에서 챗봇의 방향성, 성격, 가이드라인을 정할 수 있습니다. 평이한 언어로 원하는 행동을 유도할 수 있습니다. 더 인간적으로 보이게 만들 수도 있고,
2. 플로우 만들기
여기가 챗봇의 성격과 기능이 구현되는 핵심입니다: 특정 정보 접근, 단계별 진행, 코드 실행 등.
단순함의 힘을 과소평가하지 마세요. 하나의 자율 노드만으로도 추론 에이전트와 비슷한 기능을 구현할 수 있습니다. 저는 이를 지식 베이스(KB)와 연결해 두었습니다.

3. 지식 베이스 추가하기
지침이 분위기를 정한다면, KB는 사실에 기반합니다. 제 경우에는 Wine Reviews 데이터셋에 있는 와인 목록, 설명, 가격이 해당됩니다. 이 데이터를 챗봇의 와인 인벤토리로 활용할 예정입니다.
왼쪽 패널에서 Tables를 클릭한 뒤, 페이지 왼쪽 상단의 New Table을 눌러 설명이 담긴 이름을 입력합니다.

오른쪽 상단의 세로 점 3개(⋮)를 클릭하고 Import.를 선택하세요.

팝업되는 모달에 .csv 파일을 드래그해서 올리고, 화면의 안내를 따르세요.
테이블을 챗봇이 사용할 수 있게 하려면, 왼쪽 사이드바에서 Knowledge Bases로 이동하세요.

초록색 테이블 아이콘을 클릭하고, 원하는 소스를 선택한 뒤 Add tables를 누르세요.

플로우가 지식 베이스에 접근할 수 있는지 확인하면 준비가 끝납니다.

2단계: Chat API 통합 추가하기
챗봇과 로컬 클라이언트의 연결점이 바로 Chat API입니다. 이를 추가하려면 Communication Channels로 스크롤해서 … More를 클릭하세요.

원한다면 다양한 통합 항목을 살펴볼 수 있습니다. 우리는 Chat이 필요합니다. 저는 조금 아래로 내려가야 찾을 수 있었습니다.

해당 통합을 클릭하고, 팝업되는 모달에서 Install Integration을 누르세요.

설치가 완료되면, 웹훅 URL 끝에 Chat API ID가 표시됩니다. 이 값은 나중에 필요합니다.
3단계: Python 클라이언트 작성하기
Chat API는 사용자, 대화, 메시지에 대한 CRUD 작업을 할 수 있는 여러 엔드포인트를 제공합니다. 약속대로, 이 기능들을 OpenAI 클라이언트를 대체할 수 있는 Python 클라이언트로 감쌉니다.
1. 인증 정보 추가하기
class BotpressClient:
def __init__(self, api_id=None, user_key=None):
self.api_id = api_id or os.getenv("CHAT_API_ID")
self.user_key = user_key or os.getenv("USER_KEY")
self.base_url = f"{BASE_URI}/{self.api_id}"
self.headers = {
**HEADERS,
"x-user-key": self.user_key,
}
Chat API ID를 .env 파일에 추가해도 됩니다. 디버깅에 도움이 되지만, 필수는 아닙니다. API ID와 사용자 키는 Streamlit 앱을 만들 때 처리할 예정입니다.
저는 BASE_URI와 HEADERS를 constants.py 파일에 따로 보관합니다. 코드가 깔끔해집니다.
# constants.py
BASE_URI = "https://chat.botpress.cloud"
HEADERS = {
"accept": "application/json",
"Content-Type": "application/json",
}
2. 요청 메서드 만들기
def _request(self, method, path, json=None):
url = f"{self.base_url}{path}"
try:
response = requests.request(method, url, headers=self.headers, json=json)
response.raise_for_status()
return response.json()
except requests.HTTPError:
return response.status_code, response.text
# --- Core API Methods ---
def get_user(self):
return self._request("GET", "/users/me")
def create_user(self, name, id):
user_data = {"name": name, "id": id}
return self._request("POST", "/users", json=user_data)
def set_user_key(self, key):
self.user_key = key
self.headers["x-user-key"] = key
def create_and_set_user(self, name, id):
new_user = self.create_user(name, id)
self.set_user_key(new_user["key"])
def create_conversation(self):
return self._request("POST", "/conversations", json={"body": {}})
def list_conversations(self):
return self._request("GET", "/conversations")
def get_conversation(self, conversation_id):
return self._request("GET", f"/conversations/{conversation_id}")
def create_message(self, message, conversation_id):
payload = {
"payload": {"type": "text", "text": message},
"conversationId": conversation_id,
}
return self._request("POST", "/messages", json=payload)
def list_messages(self, conversation_id):
return self._request("GET", f"/conversations/{conversation_id}/messages")앞서 언급했듯, 대부분의 기능이 API 엔드포인트에 매핑됩니다. 저는 이들을 클래스에 감싸서 사용합니다.
3. SSE 리스너 만들기
여기까지가 핵심입니다. 대화 업데이트를 수신하고 Streamlit 프론트엔드로 연결하려면, 클라이언트에 챗봇에서 보내는 서버 전송 이벤트를 수신하고 전달하는 메서드가 필요합니다.
def listen_conversation(self, conversation_id):
url = f"{self.base_url}/conversations/{conversation_id}/listen"
for event in sseclient.SSEClient(url, headers=self.headers):
print(event.data)
if event.data == "ping":
continue
data = json.loads(event.data)["data"]
yield {"id": data["id"], "text": data["payload"]["text"]}이 함수는 conversation_id를 받아(앱 내에서 프로그래밍적으로 접근) 실시간으로 들어오는 데이터를 반환합니다.
4단계: Streamlit 앱 만들기
이제 준비가 끝났으니 챗봇을 만들어봅시다. 참고로 저는 Streamlit의 LLM 챗앱 만들기 가이드를 따르되, 몇 가지 기능을 추가했습니다.
1. 보일러플레이트 코드 수정하기
이론적으로는 Streamlit 예제의 보일러플레이트를 거의 수정하지 않고도 앱을 동작시킬 수 있습니다.
# app.py
from client import BotpressClient
import streamlit as st
from constants import CONVERSATION_ID
st.title("Botpress Front-end for Streamlit")
client = BotpressClient(
api_id=st.secrets["CHAT_API_ID"], user_key=st.secrets["USER_KEY"]
)
if "messages" not in st.session_state:
messages = client.list_messages(CONVERSATION_ID)
next_token = messages["meta"]["nextToken"]
st.session_state.messages = messages["messages"][::-1]
for message in st.session_state.messages:
with st.chat_message(message["userId"]):
st.markdown(message["payload"]["text"])
if prompt := st.chat_input("*wine*-d it up"):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
client.create_message(prompt, conversation_id=CONVERSATION_ID)
with st.chat_message("assistant"):
response_box = st.empty()
last_rendered = ""
for message in client.listen_conversation(CONVERSATION_ID):
message_id = message["id"]
message_text = message["text"]
if message_id != last_rendered:
last_rendered = message_id
response_box.markdown(message_text)
st.session_state.messages.append(
{"role": "assistant", "content": message_text}
)
여기서는 시크릿 변수를 읽어옵니다. .streamlit/secrets.toml 파일을 만들고 변수들을 입력하세요.
CHAT_API_ID = ”YOUR_API_ID”
USER_KEY = ”YOUR_USER_KEY”주요 로직은 다음 부분에서 처리됩니다:
with st.chat_message("assistant"):
response_box = st.empty()
last_rendered = ""
for message in client.listen_conversation(CONVERSATION_ID):
message_id = message["id"]
message_text = message["text"]
if message_id != last_rendered:
last_rendered = message_id
response_box.markdown(message_text)
st.session_state.messages.append(
{"role": "assistant", "content": message_text}
)
여기서 클라이언트가 채팅 요소에 연결되어 메시지를 주고받습니다.
이 방식도 동작하지만, 몇 가지 이유로 최적은 아닙니다:
- 새 대화를 별도로 생성해야 합니다.
- 이전 메시지는 역할 지정(사용자 또는 어시스턴트)이 없어 새 메시지와 포맷이 다릅니다.
- 대화 전환이 불가능합니다.
2. 대화를 동적으로 생성하기
처음부터 시작해서, 새 대화를 자동으로 생성하거나 가장 최근 대화를 엽니다:
# app.py
from client import BotpressClient
import streamlit as st
st.title("Botpress Front-end for Streamlit")
client = BotpressClient(
api_id=st.secrets["CHAT_API_ID"], user_key=st.secrets["users"][0]["key"]
)
# user info
user = client.get_user()
user_id = user["user"]["id"]
conversations = client.list_conversations()["conversations"]
conversation_ids = [conv["id"] for conv in conversations]
# conversation
def create_conversation():
res = client.create_conversation()
print(f"Created new conversation: {res}")
conversation_id = res["conversation"]["id"]
st.session_state.active_conversation = conversation_id
st.session_state.messages = []
st.rerun()
if not conversations:
create_conversation()
if "active_conversation" not in st.session_state:
st.session_state["active_conversation"] = conversations[0]["id"]
여러 사용자를 저장할 수 있도록 시크릿 키를 수정했습니다. .streamlit/secrets.toml 파일도 이에 맞게 수정하세요.
[[users]]
key = "your_user_key"이 블록을 반복해서 여러 사용자를 테이블 배열로 저장할 수 있습니다.
3. 사용자가 대화를 생성하고 전환할 수 있게 하기
제목 그대로, 상단에 드롭다운과 버튼을 만들어 대화를 선택할 수 있게 합니다.
col1, col2 = st.columns([5, 1])
with col1:
conversation_id = st.selectbox(
"Select Conversation",
options=[conv["id"] for conv in conversations],
index=conversation_ids.index(st.session_state.active_conversation),
)
with col2:
st.markdown("<div style='height: 1.9em'></div>", unsafe_allow_html=True)
if st.button("➕"):
create_conversation()
selected_conversation = client.get_conversation(conversation_id)4. 이전 메시지에 올바른 역할 지정하기
위에서 언급한 포맷 문제는, 이전 메시지마다 사용자 또는 어시스턴트 역할을 지정해서 해결합니다:
if (
"messages" not in st.session_state
or st.session_state.get("active_conversation") != conversation_id
):
st.session_state.active_conversation = conversation_id
st.session_state.messages = []
messages = client.list_messages(conversation_id)
next_token = messages["meta"].get("nextToken")
for message in messages["messages"][::-1]:
role = "user" if message["userId"] == user_id else "assistant"
text = message["payload"]["text"]
st.session_state.messages.append({"role": role, "content": text})이렇게 하면 Streamlit이 기대하는 구조에 맞게 코드가 정리됩니다.
5. 메시지 처리 로직 추가하기
이전과 거의 동일하지만, 새로운 구조에 맞게 조정했습니다.
# display chat history
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
if prompt := st.chat_input("*wine*-d it up"):
st.session_state.messages.append({"role": "user", "content": prompt})
client.create_message(prompt, conversation_id=conversation_id)
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
stream = client.listen_conversation(conversation_id=conversation_id)
response = st.write_stream(stream)
st.session_state.messages.append({"role": "assistant", "content": response})5. 사용자 생성하기
로직은 준비됐지만, 앱을 실행하려면 사용자를 생성해야 합니다. 저는 서비스 가입 경험을 시뮬레이션하기 위해 별도의 스크립트로 추가했습니다. 다행히도, 스크립트도 준비해두었습니다:
# create_user.py
import argparse
from pathlib import Path
from client import *
from constants import *
secrets_path = Path(".streamlit") / "secrets.toml"
template = """[[users]]
key="{}"
"""
client = BotpressClient()
def create_user(name, id, add_to_secrets=True):
res = client.create_user(name, id)
if not add_to_secrets:
return res
secrets_path.touch(exist_ok=True)
with open(secrets_path, "a") as f:
f.write(template.format(res["user"]["id"], res["key"]))
return res
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Create a Botpress user and optionally store secrets."
)
parser.add_argument("--name", required=True, help="Display name of the user.")
parser.add_argument(
"--id", required=True, help="User ID. If omitted, one is generated by the API."
)
parser.add_argument("--chat_api_id", help="ID for the Botpress Chat API integration. Taken from `.env` file if not provided.")
parser.add_argument(
"--no-secrets",
action="store_true",
help="Do not append to .streamlit/secrets.toml.",
)
args = parser.parse_args()
print(f"Creating user: {args.name} (ID: {args.id or 'auto-generated'})")
result = create_user(name=args.name, id=args.id, add_to_secrets=not args.no_secrets)
print("✅ User created:")
print(result)
Chat API ID만 있으면 다음 명령어로 실행할 수 있습니다:
python create_user.py –name YOUR_NAME –id SOME_USER_ID –chat_api_id YOUR_CHAT_API_ID
이 명령어가 사용자를 생성하고 시크릿 파일에 추가해줍니다.
5단계: 애플리케이션 실행하기
로직을 완성하고 사용자를 생성했다면, 이제 애플리케이션을 직접 실행해 볼 차례입니다. 의존성 패키지를 설치한 후 다음 명령어를 실행하세요:
streamlit run app.py
이렇게 하면 우리의 봇이 멋지게 동작하는 모습을 확인할 수 있습니다.

오늘 바로 Streamlit 챗봇을 실행해 보세요
Streamlit으로 프로토타입을 만들 때, 편리함을 해치지 않으면서도 원하는 대로 커스터마이즈할 수 있어야 합니다. 챗봇은 문제를 해결하기 위해 존재하는 것이지, 새로운 문제를 만들기 위한 것이 아닙니다.
Botpress는 시각적 드래그 앤 드롭 빌더, 다양한 공식 통합 기능, 그리고 접근하기 쉬운 API 엔드포인트를 제공합니다. 이를 통해 여러 커뮤니케이션 채널에서 손쉽게 챗봇을 만들고, 반복 개발하며, 배포할 수 있습니다.
지금 바로 시작해보세요. 무료입니다.
자주 묻는 질문
챗봇을 만들 때 다른 프론트엔드 프레임워크 대신 Streamlit을 선택하는 이유는 무엇인가요?
Streamlit은 프론트엔드 전문 지식 없이도 최소한의 코드로 채팅 컴포넌트와 상태 관리를 처리해주기 때문에, 빠르고 파이썬 기반으로 인터랙티브 앱을 신속하게 프로토타이핑하고 싶을 때 적합합니다.
Streamlit 챗봇은 프로덕션 환경에서도 사용할 수 있나요, 아니면 프로토타입에만 적합한가요?
Streamlit 챗봇은 프로토타입이나 내부 도구로는 매우 적합하지만, 대규모 트래픽이나 고급 스타일링이 필요한 외부 서비스용 프로덕션 앱에는 리버스 프록시, 보안 강화, 더 견고한 프론트엔드 프레임워크 등 추가적인 구성이 필요할 수 있습니다.
Streamlit으로 만든 챗봇의 디자인과 스타일을 얼마나 자유롭게 변경할 수 있나요?
Streamlit에서는 색상, 글꼴, 레이아웃 등 기본적인 스타일 조정이 가능하지만, 전통적인 웹 프레임워크에 비해 유연성이 떨어집니다. 완전히 맞춤형 디자인을 원한다면 HTML/CSS 또는 JavaScript를 직접 삽입해야 하며, 이는 Streamlit의 기본 위젯을 사용하는 것보다 복잡도가 높아집니다.
Streamlit 챗봇을 기존 웹사이트에 통합할 수 있나요, 아니면 반드시 독립적으로 실행해야 하나요?
Streamlit 챗봇은 일반적으로 자체 URL에서 독립적인 웹 앱으로 실행되지만, iframe을 이용해 기존 웹사이트에 삽입할 수도 있습니다. 이 경우, 사용자 경험을 자연스럽게 만들기 위해 스타일링과 보안 문제를 별도로 신경 써야 합니다.
Streamlit 챗봇을 공개용으로 배포하는 데 드는 비용은 얼마인가요?
Streamlit 챗봇은 로컬이나 Streamlit Community Cloud에서 소규모 앱으로 호스팅하면 무료로 배포할 수 있지만, 대중적으로 사용하거나 대규모로 운영하려면 Heroku, AWS, DigitalOcean 같은 클라우드 플랫폼에서 월 약 5~50달러의 비용이 발생할 수 있습니다(트래픽 및 가동 시간 요구 사항에 따라 다름).





.webp)
