Webchatbot / app.py
sesacna's picture
Rename web.py to app.py
ab960ba verified
Raw
History Blame Contribute Delete
7.69 kB
# --- 1. ν•„μˆ˜ 라이브러리 μž„ν¬νŠΈ ---
import streamlit as st
from dotenv import load_dotenv
import streamlit.components.v1 as components
import os
# (λ‹€λ₯Έ import ꡬ문듀은 이전과 동일)
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace
from rag_logic import create_and_store_vector_db
# (API ν‚€ λ‘œλ“œ 뢀뢄은 이전과 동일)
load_dotenv()
HUGGINGFACE_API_KEY = os.getenv("HUGGINGFACE_API_KEY")
# --- 2. νŽ˜μ΄μ§€ μ„€μ • 및 CSS ---
st.set_page_config(page_title="λͺ¨κ΅¬μ±— - My RAG 챗봇", page_icon="avatar.png", layout="centered")
st.markdown("""
<style>
/* ... (λ‹€λ₯Έ CSSλŠ” 이전과 동일) ... */
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap');
html, body, [class*="st-"] { font-family: 'Noto Sans KR', sans-serif; }
.stApp { background: linear-gradient(135deg, #F9F5FF 0%, #E2E1FF 100%); }
.st-emotion-cache-1f1G203 { background-color: white; border-radius: 1.5rem; padding: 1.5rem; margin: 1rem; box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1); border: 1px solid rgba(255, 255, 255, 0.18); height: 85vh; padding-bottom: 5rem; }
[data-testid="stChatMessage"][data-testid-role="assistant"] .st-emotion-cache-124el85 { background-color: #F0F0F5; border-radius: 20px 20px 20px 5px; color: #111; border: 1px solid #E5E7EB; animation: fadeIn 0.5s ease-in-out; }
/* β—€β—€β—€ 아바타 이미지 크기 쑰절 CSS β—€β—€β—€ */
[data-testid="stChatMessage"] img {
width: 5rem; /* λ„ˆλΉ„ μ„€μ • (기본값은 2rem) */
height: 5rem; /* 높이 μ„€μ • (기본값은 2rem) */
border-radius: 50%;
object-fit: cover; /* 이미지가 μ°Œκ·ΈλŸ¬μ§€μ§€ μ•Šλ„λ‘ μ„€μ • */
}
[data-testid="stChatMessage"][data-testid-role="user"] .st-emotion-cache-124el85 { background: linear-gradient(45deg, #7A42E2, #9469F4); border-radius: 20px 20px 5px 20px; color: white; animation: fadeIn 0.5s ease-in-out; }
.faq-card { background-color: rgba(249, 245, 255, 0.8); border: 1px solid rgba(255, 255, 255, 0.3); padding: 1.2rem; border-radius: 1rem; margin-bottom: 1rem; }
.faq-title { font-size: 18px; font-weight: 700; margin-bottom: 1rem; }
.stButton>button { background-color: #FFFFFF; color: #555; border: 1px solid #DDD; border-radius: 20px; padding: 8px 16px; transition: all 0.2s ease-in-out; box-shadow: 0 1px 2px rgba(0,0,0,0.05); width: 100%; text-align: left; }
.stButton>button:hover { background-color: #F0F0F5; color: #7A42E2; border-color: #7A42E2; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
.stChatInput { background-color: #FFFFFF; padding: 1rem; border-top: 1px solid #E5E7EB; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
</style>
""", unsafe_allow_html=True)
# --- 3. RAG 챗봇 둜직 λ‘œλ“œ 및 체인 ꡬ성 (이전과 동일) ---
@st.cache_resource
def get_rag_chain():
# ... (λ‚΄μš© λ³€κ²½ μ—†μŒ)
if not HUGGINGFACE_API_KEY: return None
vector_db = create_and_store_vector_db()
retriever = vector_db.as_retriever(search_kwargs={"k": 10})
llm_endpoint = HuggingFaceEndpoint(repo_id="meta-llama/Meta-Llama-3-8B-Instruct", huggingfacehub_api_token=HUGGINGFACE_API_KEY, temperature=0.3)
llm = ChatHuggingFace(llm=llm_endpoint)
# AI의 역할을 맀우 μ—„κ²©ν•˜κ²Œ μ œν•œν•˜λŠ” μƒˆλ‘œμš΄ μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ
system_prompt = """
당신은 'λͺ¨κ΅¬μ„œλΉ„μŠ€'의 κ·œμΉ™μ„ μ•ˆλ‚΄ν•˜λŠ” AI 상담원 'λͺ¨κ΅¬λ΄‡'μž…λ‹ˆλ‹€. λ‹Ήμ‹ μ˜ μœ μΌν•œ μž„λ¬΄λŠ” μ•„λž˜μ— μ œκ³΅λ˜λŠ” "κ²€μƒ‰λœ λ¬Έμ„œ"의 λ‚΄μš©λ§Œμ„ μ‚¬μš©ν•˜μ—¬ μ‚¬μš©μžμ˜ μ§ˆλ¬Έμ— λ‹΅λ³€ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.
--- [κ·œμΉ™] ---
1. **였직 "κ²€μƒ‰λœ λ¬Έμ„œ"의 λ‚΄μš©λ§Œμ„ μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.** μ ˆλŒ€λ‘œ λ‹Ήμ‹ μ˜ 사전 μ§€μ‹μ΄λ‚˜ μ™ΈλΆ€ 정보λ₯Ό μ‚¬μš©ν•΄μ„œλŠ” μ•ˆ λ©λ‹ˆλ‹€.
2. 닡변은 "κ²€μƒ‰λœ λ¬Έμ„œ"에 λͺ…μ‹œλœ 사싀을 κ·ΈλŒ€λ‘œ 전달해야 ν•˜λ©°, λ‚΄μš©μ„ μΆ”μΈ‘ν•˜κ±°λ‚˜ λ³€ν˜•ν•΄μ„œλŠ” μ•ˆ λ©λ‹ˆλ‹€.
3. λ§Œμ•½ "κ²€μƒ‰λœ λ¬Έμ„œ"의 λ‚΄μš©λ§ŒμœΌλ‘œ μ§ˆλ¬Έμ— λ‹΅λ³€ν•  수 μ—†λ‹€λ©΄, λ‹€λ₯Έ 정보λ₯Ό 찾으렀 ν•˜μ§€ 말고 **λ°˜λ“œμ‹œ** "아직 μ€€λΉ„λ˜μ§€ μ•Šμ€ μ •λ³΄μ˜ˆμš”. κ³§ μ—…λ°μ΄νŠΈν• κ²Œμš” πŸ™‚" 라고만 λ‹΅λ³€ν•΄μ•Ό ν•©λ‹ˆλ‹€. λ‹€λ₯Έ 말을 덧뢙이지 λ§ˆμ„Έμš”.
4. λͺ¨λ“  닡변은 μΉœμ ˆν•˜κ³  λͺ…ν™•ν•œ "μš”"체둜 μž‘μ„±ν•΄μ•Ό ν•©λ‹ˆλ‹€.
--- [κ²€μƒ‰λœ λ¬Έμ„œ] ---
{context}
"""
prompt = ChatPromptTemplate.from_messages([("system", system_prompt), ("human", "{question}")])
rag_chain = ({"context": retriever, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser())
return rag_chain
rag_chain = get_rag_chain()
# --- 4. μžλ™ 슀크둀 ν•¨μˆ˜ (이전과 동일) ---
def auto_scroll():
# ... (λ‚΄μš© λ³€κ²½ μ—†μŒ)
components.html(
"""<script> window.parent.document.querySelector('.st-emotion-cache-1f1G203').scrollTo(0, 99999); </script>""",
height=0)
# --- 5. UI λ Œλ”λ§ ν•¨μˆ˜ (이전과 동일) ---
def render_welcome_elements():
# ... (λ‚΄μš© λ³€κ²½ μ—†μŒ)
if not st.session_state.messages:
with st.chat_message("assistant", avatar="avatar.png"):
st.markdown("κΆκΈˆν•œ λ‚΄μš©μ„ μž…λ ₯ν•΄μ£Όμ‹œλ©΄,\n닡변을 λΉ λ₯΄κ²Œ 챗봇이 λ„μ™€λ“œλ¦΄κ²Œμš”.")
st.markdown('<div class="faq-card">', unsafe_allow_html=True)
st.markdown('<div class="faq-title">많이 μ°ΎλŠ” 질문 TOP 3</div>', unsafe_allow_html=True)
faq_items = {
"λͺ¨κ΅¬ 수수료 μ œν•œμ€ μ–΄λ–»κ²Œ λ˜λ‚˜μš”?": "πŸ’¬ λͺ¨κ΅¬ 수수료 μ œν•œ",
"λͺ¨κ΅¬ 마감 κΈ°ν•œμ€ λ©°μΉ κΉŒμ§€ κ°€λŠ₯ν•œκ°€μš”?": "πŸ’¬ λͺ¨κ΅¬ 마감 κΈ°ν•œ",
"λͺ¨κ΅¬μ—μ„œ νŒ”λ©΄ μ•ˆλ˜λŠ” 물건은 λ¬΄μ—‡μΈκ°€μš”?": "πŸ’¬ λͺ¨κ΅¬ 판맀 κΈˆμ§€ ν’ˆλͺ©"
}
for query, text in faq_items.items():
if st.button(text, key=query):
st.session_state.prompt_from_button = query
st.rerun()
st.markdown('</div>', unsafe_allow_html=True)
# --- 6. 메인 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 둜직 (이전과 동일) ---
st.title("λͺ¨κ΅¬μ±— ✨")
# ... (μ΄ν•˜ λͺ¨λ“  μ½”λ“œ λ³€κ²½ μ—†μŒ)
if "messages" not in st.session_state:
st.session_state.messages = []
for message in st.session_state.messages:
with st.chat_message(message["role"], avatar="avatar.png" if message["role"] == "assistant" else "πŸ‘€"):
st.markdown(message["content"])
render_welcome_elements()
prompt = st.chat_input("κΆκΈˆν•˜μ‹  λ‚΄μš©μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”.")
if "prompt_from_button" in st.session_state and st.session_state.prompt_from_button:
prompt = st.session_state.prompt_from_button
st.session_state.prompt_from_button = None
if prompt:
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user", avatar="πŸ‘€"):
st.markdown(prompt)
with st.chat_message("assistant", avatar="avatar.png"):
if rag_chain:
response_stream = rag_chain.stream(prompt)
full_response = st.write_stream(response_stream)
else:
full_response = "μ£„μ†‘ν•©λ‹ˆλ‹€, 챗봇을 μ΄ˆκΈ°ν™”ν•˜λŠ” 데 λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."
st.write(full_response)
st.session_state.messages.append({"role": "assistant", "content": full_response})
auto_scroll()
st.rerun()
else:
auto_scroll()