NLP Development/Streamlit

데이터 과학자를 위한 웹 개발 도구 Streamlit - RAG를 활용한 웹 검색 챗봇 데모페이지 시연

patrick610 2025. 7. 13. 22:32
반응형
SMALL

 

 

 

오늘은 그동안 Streamlit 관련 포스팅 한 내용을 토대로 간단한 데모페이지를 만들어 보겠습니다.

데모페이지는 Streamlit를 통해 인터렉티브하게 질의응답을 받을 수 있는 웹 검색 챗봇을 구현해 보았습니다.


 

 

페이징 처리

 

먼저 파일 위치 구성은 Streamlit 애플리케이션을 실행할 메인 파일과 같은 경로에 config.py와 pages에 페이징 처리할 파일을 두었습니다.

home.py은 페이지에 접속하면 기본으로 설정될 메인 페이지이고 demo_page.py는 오늘 알아볼 웹 검색 챗봇 데모 페이지입니다.

 

 

 

 


main.py

import streamlit as st

st.set_page_config(
    page_title="데모 페이지",
    page_icon="🤖",
)

# 페이지 정의
p1 = st.Page("pages/home.py", title="홈 화면", icon="📄", default=True)
p2 = st.Page("pages/demo_page.py", title="데모 페이지", icon="📄")

menu = {
    "🏠 메인" : [p1],
    "🖥️ 데모페이지" : [p2],
}

pg = st.navigation(
    menu,
    position="sidebar", 
    expanded=True,
)
pg.run()

 

st.set_page_config로 페이지 타이틀과 아이콘을 지정합니다.

st.Page로 home.py와 demo_page.py를 각각 페이지 변수로 지정하고 st.navigation으로 페이지를 구성합니다.

 


home.py

import streamlit as st


st.image("/Users/seonghwanpark/Desktop/데스크탑/shpark/Development/Streamlit/src/image.png", width=300)
st.subheader("데모 페이지")


st.markdown(
    """
    ###
    ### 메뉴
    - 메인 홈화면
    - 데모 페이지
"""
)

 

 

main.py가 있는 경로에서 streamlit run main.py으로 메인 파일을 실행하면 main.py에 작성된 대로 페이징 처리가 된 화면이 띄워집니다.

페이징 처리에서 home.py에 설정한 화면을 기본 페이지로 지정했기 때문에 url에 접근하면 해당 페이지를 가장 먼저 확인할 수 있습니다.

streamlit을 실행할 때, nohup같은 프로그램으로 계속 플로팅할 수 있고, --server.port 옵션으로 원하는 포트를 지정할 수도 있습니다. 예) nohup streamlit run main.py --server.port=8925

 


demo_page.py

import os, sys
from PIL import Image

import streamlit as st
from langchain.chat_models import ChatOpenAI
from langchain.utilities import GoogleSearchAPIWrapper
from langchain.agents.initialize import initialize_agent
from langchain_core.tools import Tool
from langchain.agents.agent_types import AgentType
from langchain.agents import AgentExecutor

current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
sys.path.append(parent_dir)

import config as cfg


@st.cache_resource()
def get_model():
    # del chat state
    for key in st.session_state.keys():
        del st.session_state[key]

    model = ChatOpenAI(**cfg.gpt)
    
    return model

def main():
    # cache_resource
    model = get_model()

    # image
    image = Image.open("chat-bot.png")
    col1, col2, col3 = st.columns([1, 2, 1])  # col2가 가운데
    with col2:
        st.image(
            image,
            caption=None,
            width=200,  # 원하는 크기 지정
            use_column_width=None,
            clamp=False,
            channels="RGB",
            output_format="auto",
        )

    # align
    mystyle = """
    <style>
        p {
            text-align: justify;
        }
    </style>
    """
    st.markdown(mystyle, unsafe_allow_html=True)

    con1 = st.container()
    con1.caption(f"🤖 챗봇")
    con2 = st.container()
    con2.info(f"""감사합니다. 챗봇입니다. 무엇을 도와드릴까요?""")

    def ask_flow(user_input):
        search = GoogleSearchAPIWrapper(
            google_api_key=cfg.google_api_key,
            google_cse_id=cfg.google_cse_id,
            k=3
        )
        tools = [
            Tool(
                name="Intermediate Answer",
                func=search.run,
                description="검색 중간 결과를 반환하는 도구"
            )
        ]
        # Option A: initialize_agent 사용
        agent = initialize_agent(
            tools=tools,
            llm=model,
            agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
            verbose=True,
            handle_parsing_errors=True,
        )
        response = agent.run({"input": user_input})
        print("Result:", response)
        print("-" * 100)
        
        return response


    st.header("")
    st.header("")
    # 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:
        with st.chat_message(message["role"]):
            st.markdown(message["content"])

    # React to user input
    if prompt := st.chat_input("말씀해 주세요."):
        # Display user message in chat message container
        with st.chat_message("user"):
            st.markdown(prompt)
        # Add user message to chat history
        st.session_state.messages.append({"role": "user", "content": prompt})

        # Request chat completion
        response = ask_flow(prompt)
        # Display assistant response in chat message container
        with st.chat_message("assistant"):
            st.markdown(response)
        # Add assistant response to chat history
        st.session_state.messages.append({"role": "assistant", "content": response})


if __name__ == "__main__":
    main()

 

위 데모페이지 코드의 역할을 요약하자면 Langchain을 활용하여 OpenAI gpt4.1 api와 Google Custom Search API를 통해 실시간 웹 검색하는 챗봇입니다.  Streamlit과 LangChain을 사용할 때, LLM(ChatOpenAI) 모델을 초기화하고 세션 상태를 정리하는 역할을 합니다.

위에서부터 같이 한 번 살펴보겠습니다.

 

 

get_model()

 

@st.cache_resource() 캐시 데코레이터를 활용하여 함수 실행 결과인 model을 한 번만 실행해서 저장합니다.

이후 재실행 시에도 저장된 model을 재사용하므로, 성능과 응답 속도 개선에 도움 됩니다.

 

 

ask_flow()

데모 페이지 질문
AgentExecutor chain log

 

  • GoogleSearchAPIWrapper
    • LangChain이 제공하는 구글 검색 래퍼를 통해 웹 검색 기능을 사용.
    • cfg에 있는 키(API KEY, CSE ID)를 이용하여 실제 Google Custom Search API를 호출.
    • k=3은 검색 결과 중 상위 3개만 사용한다는 의미입니다.
  • Tool
    • LangChain Tool은 LLM이 사용할 수 있는 도구 정의.
    • "Intermediate Answer"라는 이름으로 검색 기능을 툴 등록.
  • initialize_agent
    • 도구와 LLM을 연결하는 추론 에이전트를 생성합니다.
    • AgentType.ZERO_SHOT_REACT_DESCRIPTION은 질문을 분석해서 툴을 사용할지 말지 LLM이 직접 판단하는 방식.
    • handle_parsing_errors=True는 LLM이 예상치 못한 출력을 내더라도 다시 시도하게 해 줍니다.

 

st.session_state

  • Streamlit은 웹페이지가 새로 고쳐질 때마다 스크립트를 위에서부터 다시 실행합니다.
  • st.session_state는 사용자 세션 상태(예: 대화 기록, 필터 값 등)를 저장하는 공간인데,
    이 줄은 기존에 남아 있는 세션 상태 데이터를 모두 초기화합니다.
  • for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"])  이렇게 처리되면 아래 입력받은 값을 session_state의 messages 키에 담을 수 있고 그 값을 출력하여 이전 대화 목적을 출력하는 형태로 보여줄 수 있습니다.
  • 질문과 답변은 각각 st.session_state messages에 "role":"user", "role":"assistant"로 append 합니다.

 

 

 

 

✅ 정리

이번 데모 페이지를 통해 다음과 같은 내용을 함께 살펴보았습니다.

  • LangChain과 OpenAI 기반 LLM을 어떻게 연동하는지
  • Google Search API를 활용해 외부 검색 기능을 접목하는 방법
  • Streamlit으로 대화형 챗 UI를 구성하는 방식

Streamlit의 상호작용성과 세션 상태 관리 기능을 적절히 활용하면, 챗봇 프로토타입을 빠르게 구축하고 실험할 수 있습니다.
프론트엔드 개발이 여의치 않거나, POC 단계에서 빠르게 기능을 검증해야 할 때 특히 유용하게 활용될 수 있습니다.

반응형
LIST