자율 학습/스터디

[LangChain] Streamlit 으로 ChatGPT 클론코딩

2024. 6. 12. 23:47

Streamlit

데이터를 시각화하고 웹 애플리케이션을 구축하기 위한 Python 라이브러리

 

📚 강의 https://youtu.be/ZVmLe3odQvc?feature=shared

 

📌 메시지 입력

1. 페이지 설정

  • set_page_config
st.set_page_config(page_title=None, page_icon=None, layout="centered", initial_sidebar_state="auto", menu_items=None)

 

2. 채팅 기능

📑 공식 문서 Chat elements https://docs.streamlit.io/develop/api-reference/chat

  • chat_input : 사용자로부터 메시지를 입력받는다.
st.chat_input(placeholder="Your message", *, key=None, max_chars=None, disabled=False, on_submit=None, args=None, kwargs=None)
  • chat_message : 사용자에게 메시지 표시한다.
st.chat_message(name, *, avatar=None)
    • name "user" → user 아이콘으로 메시지 출력

 

코드

import streamlit as st

st.set_page_config(page_title="테스트 봇", page_icon="👨‍💻")
st.title("👨‍💻 테스트")

if user_input := st.chat_input("메시지를 입력해주세요."):
    st.chat_message("user").write(f"{user_input}")

 

실행

streamlit run app.py

 

화면

 

사용자가 메시지를 입력하면 user_input으로 들어오게 되고, if 조건문을 타서 user 아이콘으로 그 메시지를 뿌려준다.

기본적으로 streamlit은 메시지를 입력할 때마다 전체 새로고침(재실행) 되기 때문에 메시지 내용이 교체된다.

데이터 보관이 되지 않기 때문에 메시지가 누적될 수 있도록 수동으로 구현해야 한다.

 

📌 답변 구현

3. 답변 메시지 추가

사용자가 메시지를 입력할 때, user 아이콘으로 사용자 메시지를 출력하고,

그 다음 이어서 설정한 메시지 내용을 assistant 아이콘으로 출력해서 답변 메시지처럼 만들었다.

 

코드

import streamlit as st

st.set_page_config(page_title="테스트 봇", page_icon="👨‍💻")
st.title("👨‍💻 테스트")

if user_input := st.chat_input("메시지를 입력해주세요."):
    st.chat_message("user").write(f"{user_input}")

    with st.chat_message("assistant"):
        st.write(f"당신이 입력한 내용: {user_input}")

 

화면

 

 

📌 데이터 보관 (메시지 누적)

메시지를 입력할 때마다 특정 변수에 저장하고, 새로운 메시지가 입력될 때마다 변수에 저장한 메시지를 먼저 출력하고 그 다음 새 메시지를 출력하게 한다.

 

4. 캐싱

  • session_state

messages라는 키 값이 session_state에 없으면 새로운 키로 리스트 형식으로 만들어주고,

이 messages에는 사용자가 입력한 메시지를 계속해서 추가해준다.

 

그냥 append 하지 않고 ("user", user_input) 튜플 형식으로 넣어준다.

 

코드

import streamlit as st

st.set_page_config(page_title="테스트 봇", page_icon="👨‍💻")
st.title("👨‍💻 테스트")

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

if user_input := st.chat_input("메시지를 입력해주세요."):
    st.chat_message("user").write(f"{user_input}")
    # 사용자 메시지 저장
    st.session_state["messages"].append(("user", user_input))
    with st.chat_message("assistant"):
        msg = f"당신이 입력한 내용: {user_input}"
        st.write(msg)
        # 답변 메시지 저장
        st.session_state["messages"].append(("assistant", msg))

 

 

5. 저장된 메시지 먼저 출력

session_state에 "messages" 키 값이 저장되어 있고 메시지가 들어 있다면, 새 메시지를 저장하기 전에 저장된 메시지 먼저 출력한다.

각 role에 맞춰서 user 메시지는 user가, assistant 메시지는 assistant가 출력하도록 반복 돌려준다.

 

코드

import streamlit as st

st.set_page_config(page_title="테스트 봇", page_icon="👨‍💻")
st.title("👨‍💻 테스트")

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

# 이전 대화 기록 출력
# messages 키 값이 저장되어 있고, 메시지가 들어 있다면 먼저 출력
if "messages" in st.session_state and len(st.session_state["messages"]) > 0:
    # user, assistant 각 role에 맞게 출력 
    for role, message in st.session_state["messages"]:
        st.chat_message(role).write(message)

if user_input := st.chat_input("메시지를 입력해주세요."):
    st.chat_message("user").write(f"{user_input}")
    st.session_state["messages"].append(("user", user_input))
    with st.chat_message("assistant"):
        msg = f"당신이 입력한 내용: {user_input}"
        st.write(msg)
        st.session_state["messages"].append(("assistant", msg))

 

화면

 

 

6. 메시지 출력 함수 분리

메시지 출력하는 코드를 함수로 분리하고 utils.py 파일로 옮겨서 import 하면 코드가 깔끔!

 

코드

import streamlit as st
from utils import print_messages

st.set_page_config(page_title="테스트 봇", page_icon="👨‍💻")
st.title("👨‍💻 테스트")

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

# 이전 대화 기록 출력
print_messages()

if user_input := st.chat_input("메시지를 입력해주세요."):
    st.chat_message("user").write(f"{user_input}")
    st.session_state["messages"].append(("user", user_input))
    with st.chat_message("assistant"):
        msg = f"당신이 입력한 내용: {user_input}"
        st.write(msg)
        st.session_state["messages"].append(("assistant", msg))
import streamlit as st


# 이전 대화 기록 출력
def print_messages():
    if "messages" in st.session_state and len(st.session_state["messages"]) > 0:
        for role, message in st.session_state["messages"]:
            st.chat_message(role).write(message)

 

 

7. ChatMessage로 메시지 객체화

메시지를 튜플로 넣지 않고, ChatMessage를 이용해서 role과 content로 메시지를 넣을 수 있다.

채팅 메시지임을 알려주므로 가독성이 좋다.

(아래부터는 import, 나머지 코드 생략)

if user_input := st.chat_input("메시지를 입력해주세요."):
    st.chat_message("user").write(f"{user_input}")
    st.session_state["messages"].append(ChatMessage(role="user", content=user_input))
    with st.chat_message("assistant"):
        msg = f"당신이 입력한 내용: {user_input}"
        st.write(msg)
        st.session_state["messages"].append(ChatMessage(role="assistant", content=msg))

 

객체화 시켰으니 마찬가지로 utils.py에서도 수정해줘야 한다.

def print_messages():
    if "messages" in st.session_state and len(st.session_state["messages"]) > 0:
        for chat_message in st.session_state["messages"]:
            st.chat_message(chat_message.role).write(chat_message.content)

 

 

📌 LLM 적용

8. secrets.toml 파일 생성

Streamlit 앱에서 비밀 정보를 안전하게 관리하기 위한 파일이다.

  • .gitignore에 추가
  • API 키 정의
OPENAI_API_KEY="{API키}"

 

9. API 키 적용

import os
os.environ["OPENAI_API_KEY"] = st.secrets["OPENAI_API_KEY"]

 

10. 프롬프트 만들기

    # LLM을 사용하여 AI의 답변을 생성
    prompt = ChatPromptTemplate.from_template(
        """질문에 간결하게 답변해주세요.
{question}
"""
    )

 

11. 체인 만들기

    chain = prompt | ChatOpenAI()

 

12. 결과 만들기

    response = chain.invoke({"question": user_input})
    msg = response.content
    
    # AI의 답변
    with st.chat_message("assistant"):
        #msg = f"당신이 입력한 내용: {user_input}"
        st.write(msg)
        st.session_state["messages"].append(ChatMessage(role="assistant", content=msg))

 

화면

 

13. StrOutputParser 사용

StrOutputParser를 사용하면 content를 꺼낼 필요가 없다.

    chain = prompt | ChatOpenAI() | StrOutputParser()
    msg = chain.invoke({"question": user_input})

 

 

📌 메모리 기능 구현

눈으로는 누적된 메시지가 확인이 되지만 시스템적으로는 대화 기록을 보관하고 있지 않아서 AI가 이전의 답변을 조회하지 못한다.

 

RunnableWithMessageHistory

 

작성중