# Detail
- [[Streamlit]]을 사용하여 UI를 제작했고 내부에는 [[Stuff LCEL Chain]]에 [[Memory Modules#Conversation Summary Buffer Memory|Conversation Summary Buffer Memory]]을 연결하여 Chain을 구성하였다.
- UI는 `ai`와 `user`의 Chat 형식으로 구성하였고 Streamlit 특성 상 input widgit을 사용하는 순간 값들이 초기화 되기 때문에 [[Streamlit#session_state|session]]을 이용하여 message data와 memory data을 caching 하였다.
- `ai`는 `user`가 넣은 file의 데이터와 user의 대화 기록(memory)을 바탕으로 답변하도록 [[Prompt#ChatPromptTemplate|ChatPromptTemplate]]을 작성하였다.
- file은 `SideBar`에서 넣을 수 있도록 하였고 file을 넣은 뒤 해당 file을 [[Retrieval#Load|load]], [[Retrieval#CharacterTextSplitter|split]], [[Retrieval#Embed|embedding]] 처리하였다.
- Retrieval 과정에서 `@st.cache_resource(show_spinner="Embedding file...")`을 적용해 file값이 바뀌지 않는 이상 해당 과정을 반복하여 수행하지 않도록 설정하였다.
- `UnstructuredFileLoader` loader을 사용하여 **어떠한 file이 들어와도** load 할 수 있도록 구현하였다.
- [[LCEL(LangChain Expression Language)]]를 사용하여 file Retriever와 massage, memory history를 prompt에 넣고 llm과 결합하여 `Chain`을 완성하였다.
- "ai"가 답변을 하는 과정에서 작성 중인 값들을 실시간으로 화면에 띄우기 위해 `Stream=True And ChatCallbackHandler`을 사용하였다.
- `ChatCallbackHandler` class는 `BaseCallbackHandler` extend 해서 만들었으며 `on_llm_new_token` function으로 실시간 새로운 token이 나올 때마다 값이 화면에 띄울 수 있도록 구현하였다.
- **자세한 내용은 Code을 참조하자**
# Code
```python
import streamlit as st
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.document_loaders import UnstructuredFileLoader
from langchain_community.vectorstores import Chroma
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import CacheBackedEmbeddings
from langchain.storage import LocalFileStore
from langchain.schema.runnable import RunnablePassthrough, RunnableLambda
from langchain.callbacks.base import BaseCallbackHandler
from langchain.memory import ConversationSummaryBufferMemory
st.set_page_config(
page_title="document GPT",
page_icon="🤣",
)
class ChatCallbackHandler(BaseCallbackHandler):
def __init__(self):
self.response = ""
def on_llm_start(self, *arg, **kwargs):
self.message_box = st.empty()
def on_llm_end(self, *arg, **kwargs):
save_message(self.response, "ai")
def on_llm_new_token(self, token, *arg, **kwargs):
self.response += token
self.message_box.markdown(self.response)
llm = ChatOpenAI(
temperature=0.1,
streaming=True,
callbacks=[ChatCallbackHandler()],
)
memory_llm = ChatOpenAI(temperature=0.1)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"""
You are a helpful assistant. Answer questions using only the following context.
and You remember conversations with human.
If you don't know the answer just say you don't know, dont't makt it
------
{context}
""",
),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
]
)
@st.cache_resource(show_spinner="Embedding file...")
def embed_file(file):
file_name = file.name
file_path = f"./.cache/files/{file_name}"
file_context = file.read()
with open(file_path, "wb") as f:
f.write(file_context)
loader = UnstructuredFileLoader(file_path)
splitter = CharacterTextSplitter.from_tiktoken_encoder(
separator="\n\n",
chunk_size=500,
chunk_overlap=60,
)
documents = loader.load_and_split(text_splitter=splitter)
cache_dir = LocalFileStore(f"./.cache/embeddings/{file_name}")
embedder = OpenAIEmbeddings()
cache_embedder = CacheBackedEmbeddings.from_bytes_store(embedder, cache_dir)
vectorStore = Chroma.from_documents(documents, cache_embedder)
retriever = vectorStore.as_retriever()
return retriever
def paint_history():
for dic_message in st.session_state["messages"]:
send_message(dic_message["message"], dic_message["role"], save=False)
def save_message(message, role):
st.session_state["messages"].append({"message": message, "role": role})
def send_message(message, role, save=True):
with st.chat_message(role):
st.markdown(message)
if save:
save_message(message, role)
def format_doc(documents):
return "\n\n".join(doc.page_content for doc in documents)
def memory_load(input):
return memory.load_memory_variables({})["history"]
if "memory" not in st.session_state:
st.session_state["memory"] = ConversationSummaryBufferMemory(
llm=memory_llm,
max_token_limit=150,
memory_key="history",
return_messages=True,
)
memory = st.session_state["memory"]
st.title("Document GPT")
st.markdown(
"""
Welcome!
Use this chatbot to ask questions to an AI about your files!
Upload your files on the sidebar.
"""
)
with st.sidebar:
file = st.file_uploader(
"Upload a .txt .pdf or .docx file",
type=["txt", "pdf", "docx"],
)
if file:
retriever = embed_file(file)
send_message("How can I help you?", "ai", save=False)
paint_history()
answer = st.chat_input("Ask anything about your file....")
if answer:
send_message(answer, "human")
chain = (
{
"context": retriever | RunnableLambda(format_doc),
"history": RunnableLambda(memory_load),
"question": RunnablePassthrough(),
}
| prompt
| llm
)
with st.chat_message("ai"):
response = chain.invoke(answer)
memory.save_context({"input": answer}, {"output": response.content})
else:
st.session_state["messages"] = []
memory.clear()
```