RAG 을 활용하여 LLM 만들어보기

Streamit 으로 chatbot 만들기

몽자비루 2025. 8. 9. 22:59

1. Streamit 이란

streamit 이란 HTML, CSS, JavaScript 를 몰라도 브라우저에 UI를 만들 수 있다.

왜냐하면 Streamit 이 자체적으로 React 로 component 를 만들고 추상화하여 매핑했기 때문이다.

 

또한 Data 분석가들이 시각화하는 도구로 많이 사용하므로, python과 잘 맞는 library 이다.

 

먼저 아래 링크를 참고하여 streamit 환경을 설정 후 확인했을 때 Welcome to Streamlit 이 나와야 한다.

 

위 상태에서 Enter 을 한번 더 누르면 아래와 같이 웹사이트로 접속할 수 있다.

2. Streamlit 디자인 구성하기.

먼저 아래와 같이 디자인 한 뒤, terminal 이나 powershell 에서 "streamlit run .\chat.py" 를 입력하면

페이지가 열리면서 아래처럼 streamlit 의 타이틀과 아이콘이 변경되는 것을 확인할 수 있다.

이후 아래와 같이 title 과 caption 을 넣음으로서 타이틀과 내용을 입력할 수 있게 된다.

3. 사용자 메시지 입력 레이아웃 만들기

그다음으론 사용자가 챗봇에 물어볼 때 입력할 수 있는 메시지 입력 레이아웃을 만들어 본다.

먼저 사용자가 메시지를 입력한 뒤에 전송하면 위에서부터 차례대로 채팅처럼 로그가 남아야 한다.

 

아래 내용을 보면 session_state를 사용하여 세션을 유지하는데, 사용자가 새로고침하거나

접근할 때마다 세션을 유지하고, 첫 실행인 경우엔 session_state.messages 에 빈 배열을 생성한다.

 

그다음에 세션에 저장된 메시지를 반복하고 각 메시지 역할(user, ai 등) 에 따라 메시지를 출력한다.

 

마지막으로 사용자로부터 받은 질문을 role 이 user 인 위치에 입력하고 Session_state 에 추가함으로써

계속해서 사용자가 입력한 메시지를 출력한 상태로 유지할 수 있도록 레이아웃을 만든다.

위 상태에서 실행한 결과는 아래와 같은데, 소득세 > 안녕하세요 > 세금에 .... > 있나요 순으로 입력했다.

그러나 아직 질문에 대한 답변이 오지 않는데, 이전에 Langchain으로 작성한 코드를 활용해

질문에 대해 LLM 답변을 생성하고 사용자한테 보여주는 부분을 구현해보려고 한다.

4. LangChain으로 작성한 코드를 활용한 LLM 답변.

그렇다면 LangChain으로 작성한 코드를 활용해서 LLM 답변을 출력하는걸 진행해보려고 한다.

먼저 내가 위 코드에서 아래 부분을 추가했는데, 간단하게 설명하자면 AI 응답을 생성하는 중에 로딩을 보여주고

이후 ai role 이 `ai 메시지입니다.` 로 출력되고 메시지가 위로 올라가도 로그로 보이게 해준다.

 

이 상태에서 질문을 보내면 아래와 같에 메시지가 오는것을 확인할 수 있다.

 

여기에서 이전 포스팅을 참고해서 실제 ChatUpstage를 활용해 ai 메시지를 보내보려고 한다. 

먼저 결과를 보여주자면, 아래와 같이 로딩이 돌다가 그다음에 결과를 보여주는 것을 확인할 수 있다.

 

대답이 조금 이상하게 나오지만, 이부분은 유로 AI API를 활용하면 해결될 것으로 생각된다.

코드 내용은 아래와 같고 `# ======` 사이에 있는 내용이 LangChain 을 사용해서 대답 결과를 받아오고

위 코드 내용중 `ai_message = get_ai_message(user_question)`를 사이에 넣고, ai_message를 출력하도록 수정했다.

 

아래는 전체 코드내용이므로 참고하면 좋을것같다.

import streamlit as st


# ======

import os
from langchain_upstage import UpstageEmbeddings
from dotenv import load_dotenv
from langchain_pinecone import PineconeVectorStore
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_upstage import ChatUpstage
from langchain.chains import RetrievalQA
from langchain import hub


def get_ai_message(user_question):
    load_dotenv()
    
    embeddings = UpstageEmbeddings(model="solar-embedding-1-large") 
    index_name = 'tax-index-markdown'
     # 데이터베이스가 이미 존재하는 경우 DB불러오기
    database = PineconeVectorStore.from_existing_index(
        index_name=index_name,
        embedding=embeddings
    )

    llm = ChatUpstage()
    # 미리 정의된 RAG용 프롬포트를 불러오기
    prompt = hub.pull("rlm/rag-prompt")

    retriever = database.as_retriever(search_kwargs={"k": 1})
    # 직장인으로 입력받는 경우, 거주자로 변경하는 chain을 추가한다.

    qa_chain = RetrievalQA.from_chain_type(
        llm, 
        retriever=retriever,
        chain_type_kwargs={"prompt": prompt}
    )

    dictionary = ["사람을 나타내는 표현은 모두 거주자로 변경해주세요"]
    prompt = ChatPromptTemplate.from_template(f"""
                            사용자의 질문을 보고, 우리의 사전을 참고해서 사용자의 질문을 변경해주세요.
                            변경할 필요가 없다고 판단되면, 사용자 질문을 변경하지 않아도 됩니다.
                            그런 경우에는 질문만 return 해주세요.

                            사전: {dictionary}
                            질문 : {{question}}
                            """)

    dictionary_chain = prompt | llm | StrOutputParser()
    tax_chain = {"query": dictionary_chain} |  qa_chain
    ai_message = tax_chain.invoke({
        "question": user_question
    })["result"]
    return ai_message

# ======



st.set_page_config(page_title="Chat Application", page_icon="📈")

st.title("소득세 챗봇")
st.caption("소득세 관련 질문을 해보세요!")

if "messages" not in st.session_state:
    st.session_state.messages = []

# 세션에 저장된 메시지를 반복하고 각 메시지 역할에 따라 메시지 표시
print(f"before: {st.session_state.messages}")
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.write(message["content"])



if user_question := st.chat_input("소득세와 관련된 궁금한 내용들을 말씀해주세요!"):
    # 채팅이 입력되었을 때 상단에 메시지가 노출되도록 함.
    # "user", "assistant", "ai", "human" 가 있는데 그중 user 을 사용함.
    with st.chat_message("user"):
        st.write(user_question)

    # 채팅 메시지를 Session State에 저장
    st.session_state.messages.append({"role": "user", "content": user_question})


    # 로딩중인 부분을 보여주기
    with st.spinner("AI 응답을 생성하는 중..."):
        ai_message = get_ai_message(user_question)
        with st.chat_message("ai"):
            st.write(ai_message)
        # 채팅 메시지를 Session State에 저장
        st.session_state.messages.append({"role": "ai", "content": ai_message})

 

그러나 추가적으로 부족한 부분이 있는데, 체팅 히스토리가 연결되지 않아 문맥을 파악하지 못한다는 것이다.

아래 예시를 살펴보면 문맥상 연봉 1억 직장인 소득세에 대해서 물어봤지만, 문맥을 잡지 못하고 있다.

 

그래서 다음 포스팅에서는 Chat history 와 streaming 을 구현해 보려고 한다.