[AI] RAG์ LangChain์ ํ์ฉํ Chatbot ๊ตฌํ(2)
๐RAG์ LangChain์ ํ์ฉํ Chatbot ๊ตฌํ(1) ๊ฒ์๋ฌผ ๋ณด๋ฌ๊ฐ๊ธฐ
์ง๋ ๊ฒ์๋ฌผ์์ RAG์ ๊ฐ๋ , RAG๋ฅผ ์ฌ์ฉํ์ ๋์ ์ฅ์ , RAG ๊ตฌํ์ ์ํ ํ๋ ์์ํฌ langchain์ ํ์ฉ๋ฒ์ ์์๋ณด๊ณ ์ง๋ฌธ์ ๊ฐ๋จํ๊ฒ ๋ต๋ณํ ์ ์๋ RAG๋ฅผ ๊ตฌ์ถํด๋ณด์๋ค.
์ง๋ ์๊ฐ์ ๊ตฌ์ถํ๋ RAG๋ฅผ ์ข ๋ ๋ณด๊ฐํ์ฌ ๋ต๋ณ ์ ๋ขฐ๋์ ํ์ง์ ์ข ๋ ๋์ฌ๋ณด์.
Chat history ์ถ๊ฐํ๊ธฐ
RAG์์ ๋ํ ๊ธฐ๋ก(chat history)๋ฅผ ์ฌ์ฉํด์ ์ด์ ์ ๋ฌธ๋งฅ(context)๋ฅผ ํ ๋๋ก ๋ ์์ฐ์ค๋ฝ๊ณ ์ผ๊ด๋ ๋ต๋ณ์ ์์ฑํ๋ ๊ฒ์ด ๊ฐ๋ฅํ๋ค. chat history๋ฅผ ์ฌ์ฉํ์ง ์์ ๊ฒฝ์ฐ ์๋์ ๊ฐ์ด ๋น์ทํ ๋งฅ๋ฝ์ ์ง๋ฌธ์์๋ ๋ถ๊ตฌํ๊ณ ๋ต๋ณ์ ์ ๋๋ก ํ์ง ๋ชปํ๋ค๋ ๋จ์ ์ด ์๋ค.

"1๋ฃจ ์ฃผ์์ 2๋ฃจ ์ฃผ์๊ฐ ๋์์ ๋ฒ ์ด์ค๋ฅผ ํ์น๋ ๊ฒ์ ๋ญ๋ผ๊ณ ํ๋์?"์ "๊ทธ๋ผ ํ ๋ช ์ด ํ๋ ๊ฑด?"์ ์ฌ๋์ด ๋ณด๊ธฐ์ ๋น์ทํ ๋ฌธ๋งฅ์ ๋ฌธ์ฅ์์ AI ๋ชจ๋ธ์์ ์ด๋ฅผ ์ธ์งํ์ง ๋ชปํ๋ ๊ฒ์ ์ ์ ์๋ค. chat history๋ฅผ ์ ๊ณตํ์ฌ ์ด์ ์ ๋ํ ๊ธฐ๋ก์ ์ฐธ๊ณ ํ๋๋ก ํ์ฌ AI์๊ฒ ์ถ๋ก ๋ฅ๋ ฅ์ ๋ถ์ฌํ๊ณ ์ ์ ์ ๋ณด๋ก ์ ํํ ๋ต๋ณ์ ์ ๊ณต๋ฐ๊ณ ์์ฐ์ค๋ฌ์ด ๋ํ ๋ํ ์ด์ด๋๊ฐ ์ ์๋ค.
๊ธฐ์กด์ RAG์ chat history๋ฅผ ์ ์ฉํด๋ณด์. ๋จผ์ ์ด์ ์ ๋ํ ๋ด์ฉ์ chat history๋ก ์ ์ฅํ๋ ๊ฒ๋ถํฐ ์์ํด๋ณด์. retriever๋ ์ด์ ์ retriever๋ฅผ ํ์ฉํ์ฌ chat history๋ฅผ ํ ๋๋ก ๋ฌธ๋งฅ ํ๋ฆ์ ๊ณ์ ์ ์งํ ์ ์๋ history_aware_retriever๋ฅผ ์์ฑํ๋ค(create_history_aware_retriever๋ฅผ ์ฌ์ฉํ๋๋ฐ langchain 0.2v์ ๊ณต์๋ฌธ์์์ ์ฌ์ฉ๋ฒ ํ์ธ์ด ๊ฐ๋ฅํ๋ค). history_aware_retriever๋ฅผ ํตํด ์ฑํ ๊ธฐ๋ก์ ๊ธฐ๋ฐ์ผ๋ก ๊ฒ์์ด ๊ฐ๋ฅํ๊ณ , ์ฑํ ๊ธฐ๋ก์ key๊ฐ์ธ session_id์ value ๊ฐ์ธ ์ฑํ ๊ธฐ๋ก(ChatMessageHistory)๋ก ์ด๋ฃจ์ด์ง ํด์ ํ ์ด๋ธ ๊ตฌ์กฐ์ chat_storage๋ก ๊ด๋ฆฌํ๋ค.
from langchain_core.prompts import MessagesPlaceholder
from langchain.chains import create_history_aware_retriever
chat_storage = {}
def get_llm():
llm = ChatUpstage()
return llm
# chat history๋ฅผ ๋ฐ์ํ์ฌ ์ง๋ฌธ์ ์ฌ๊ตฌ์ฑํ๋ ์์คํ
์ค์
def get_history_retriever():
llm = get_llm()
retriever = get_retriever()
# chat history๋ฅผ ๋ฐ์ํ์ฌ ์ง๋ฌธ์ ์ฌ๊ตฌ์ฑํ๋ ์์คํ
ํ๋กฌํํธ
contextualize_q_system_prompt = (
"Given a chat history and the latest user question "
"which might reference context in the chat history, "
"formulate a standalone question which can be understood "
"without the chat history. Do NOT answer the question, "
"just reformulate it if needed and otherwise return it as is."
)
# ์ฑํ
๊ธฐ๋ก์ ๋ฐ์ํ์ฌ QA๋ฅผ ์ํ ํ๋กฌํํธ
contextualize_q_prompt = ChatPromptTemplate.from_messages(
[
("system", contextualize_q_system_prompt),
MessagesPlaceholder("chat_history"), # ๋ฉ์์ง ๋ฆฌ์คํธ๋ฅผ ์ ๋ฌ
("human", "{input}")
]
)
history_aware_retriever = create_history_aware_retriever(llm, retriever, contextualize_q_prompt)
return history_aware_retriever
์ฑํ ๊ธฐ๋ก์ ๋ฐ์ํ๋ history_retriever๋ฅผ ๊ตฌํํ์ผ๋ฉด ์ง๋ต์ ์ํ QA ์ฒด์ธ์ ์ถ๊ฐํ๊ณ RAG ์ฒด์ธ์ ์์ฑํด๋ณด์.
์ฃผ์ํด์ผ ํ ์ ์ ์ฑํ ๊ธฐ๋ก์ ๊ธฐ๋ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ history_retriever๊ฐ ์ถ๊ฐ ๋์๊ธฐ ๋๋ฌธ์ RAG ์ฒด์ธ ๋ด๋ถ์์ chat_storage์์ ์ฑํ ๊ธฐ๋ก์ ๋ถ๋ฌ์ค๊ณ ์ ์ฅํ๋ ๋ก์ง ๋ํ ์ถ๊ฐ๋์ด์ผ ํ๋ค. ํด๋น ๊ธฐ๋ฅ์ ๋ด๋นํ๋ ๋ถ๋ถ์ด get_session_history(์ธ์ ์ ๋ํ ์ฑํ ๊ธฐ๋ก์ด ์์ผ๋ฉด ๊ธฐ๋ก์ ๋ฐํํ๊ณ ์์ผ๋ฉด ์ ์ฅ ํ ๋ฐํ)์ด๋ค.
chat history๋ฅผ ๋ฐ์ํ๋ rag chain์ ๊ตฌํํ๊ธฐ ์ํด์๋ RunnableWithMessageHistory ํด๋์ค๋ฅผ ํ์ฉํ๋ค.
โ RunnableWithMessageHistory:
๋ํ ํ์คํ ๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํ์ฌ Runnable ์ปดํฌ๋ํธ๋ฅผ ์์ฑํ๊ณ ์คํํ ์ ์๋๋ก ๋๋ ํด๋์ค. ๊ตฌํ์ ์ํด ์๋์ ์ด 5๊ฐ์ ์ธ์๋ฅผ ํ์ฉํ๋ค.
1. runnable: ๋ฐ์ ์ธ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์คํํ ์ฒด์ธ
2. get_session_history: ์ธ์ ID ํค ๊ฐ์ ํด๋นํ๋ ์ฑํ ๊ธฐ๋ก ๋ฐ์ดํฐ
3. input_messages_key: ์ ์ ์ ์ ๋ ฅ์ด ํฌํจ๋๋ ํค ๊ฐ
4. history_messages_key: ์ ์ ์ ์ฑํ ๊ธฐ๋ก์ ์ ์ฅํ ํค ๊ฐ
5. output_messages_key: ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ ํค ๊ฐ
# ์ธ์
๊ธฐ๋ก์ ๊ฐ์ ธ์ค๊ธฐ ์ํ ํจ์ ์ค์
def get_session_history(session_id: str) -> BaseChatMessageHistory:
# ์ธ์
์ด ์์ผ๋ฉด ํด๋น ์ธ์
์ ๋ํ ์ฑํ
๊ธฐ๋ก ์์ฑ
if session_id not in chat_storage:
chat_storage[session_id] = ChatMessageHistory()
return chat_storage[session_id]
# RAG ์ฒด์ธ์ ์์ฑ
def get_rag_chain():
llm = get_llm()
# ๋ชจ๋ธ์ ์ญํ ์ ์ ์ํด์ฃผ๊ธฐ ์ํ ์์คํ
ํ๋กฌํํธ
system_prompt = ("""
๋น์ ์ ์ต๊ณ ์ KBO ๋ฆฌ๊ทธ ์ผ๊ตฌ ๊ท์น ์ ๋ฌธ๊ฐ์
๋๋ค.
์๋์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด์ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์ ํด์ฃผ์ธ์.
๋ต๋ณ ์์ ์ถ์ฒ์ ๋ํด์๋ ๋ช
ํํ๊ฒ ๋ฐํ์ฃผ์๊ณ , ์ฌ์ฉ์๊ฐ ์ดํดํ๊ธฐ ์ฝ๊ฒ ์ค๋ช
ํด์ฃผ์ธ์.
๋ต๋ณ์ ๊ธธ์ด๋ 2-3์ค ์ ๋๋ก ์ ํํด์ฃผ์ธ์.
\n\n
๋ฌธ์: {context}
""")
# ์ฑํ
๊ธฐ๋ก ๋ฐ์ํ์ฌ QA๋ฅผ ์ํ ํ๋กฌํํธ
qa_prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
MessagesPlaceholder("chat_history"), # ๋ฉ์์ง ๋ฆฌ์คํธ(์ฑํ
๊ธฐ๋ก)๋ฅผ ์ ๋ฌ
("human", "{input}")
]
)
history_aware_retriever = get_history_retriever() # ๊ธฐ๋ก ๊ธฐ๋ฐ ๊ฒ์์ ์ํ retriever
qa_chain = create_stuff_documents_chain(llm, qa_prompt) # ๋ชจ๋ธ์๊ฒ ๊ฒ์ํ ๋ฌธ์ ๋ฆฌ์คํธ๋ฅผ ์ ๋ฌ
rag_chain = create_retrieval_chain(history_aware_retriever, qa_chain) # RAG ์ฒด์ธ ์์ฑ
conversational_rag_chain = RunnableWithMessageHistory(
rag_chain, # ์คํํ RAG ์ฒด์ธ์ด๋ LLM ์ฒด์ธ
get_session_history, # ์ธ์
_ID ๊ธฐ๋ฐ์ ์ฑํ
๊ธฐ๋ก
input_messages_key="input", # ์ ์ ์ ์
๋ ฅ์ด ๋ด๊ธด ํค
history_messages_key="chat_history", # ์ฑํ
๊ธฐ๋ก์ ์ ๋ฌํ ํค
output_messages_key="answer" # ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ ํค
).pick("answer") # AI์ ์๋ต๋ง ์ ํํด์ ๋ฐํ
# ์ฑํ
history๋ฅผ ํฌํจํ retriever๋ฅผ ํ์ฉํ์ฌ rag_chain ๋ฐํ
return conversational_rag_chain
์ดํ์ rag_chain์ invokeํ๊ณ ์์ ์์ฑํ ๋ชจ๋ ์ฒด์ธ์ ๋์์ํค๊ธฐ ์ํ get_ai_responseํจ์๋ฅผ ์๋์ ์์ฑํด๋ณด์. ์์ ์์ฑํ filter_chain, dictionary_chain, rag_chain์ ์ฒด์ด๋ํด์ ํ์ดํ๋ผ์ธ์ ๋ง๋ค์ด์ฃผ๊ณ ํด๋น ํ์ดํ๋ผ์ธ์ invoke ํ๋ค. ์ด ๋ filter_chain์์ ์ฟผ๋ฆฌ๋ฅผ "question" ํค ๊ฐ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ๋๋ฌธ์ question์ ์ฌ์ฉ์ ์ง๋ฌธ์ ์ ์ฅํด์ ๋๊ฒจ์ฃผ๊ณ conversational_rag_chain ๋์์ ์ํด์๋ session_id๊ฐ ํ์์ด๊ธฐ ๋๋ฌธ์ config์ configurable ๊ฐ์ ํตํด session_id๋ฅผ ๋๊ฒจ์ฃผ๋๋ก ํ์.
def get_ai_response():
filter_chain = get_filter()
dictionary_chain = get_dictionary_chain()
rag_chain = get_rag_chain()
baseball_chain = filter_chain | {"input": dictionary_chain} | rag_chain
ai_response = baseball_chain.invoke(
{"question": "์ผ๊ตฌ์์ ์ฃผ์ ํ๋ช
์ด ๋ฒ ์ด์ค๋ฅผ ํ์น๋ ๊ฒ์ ๋ญ๋ผ๊ณ ํ๋์?"},
config={"configurable": {"session_id": "abc123"}}
)
return ai_response
์ง๊ธ๊น์ง ์์ฑํ ์ฝ๋๋ฅผ ํ๋์ .py ํ์ผ๋ก ์ฎ๊ธฐ๋ฉด ์๋์ ๊ฐ๋ค. ์๋์ llm.py ํ์ผ์ ์คํ์์ผ๋ณด์.
# llm.py
from langchain_upstage import ChatUpstage, UpstageEmbeddings
from langchain_pinecone import PineconeVectorStore
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
chat_storage = {}
def get_llm():
llm = ChatUpstage()
return llm
# ์ง๋ฌธ์ด ์ฃผ์ ์ ๊ด๋ จ์ด ์๋์ง ํ์ธํ๋ ํํฐ ์ค์
def get_filter():
llm = get_llm()
filter_prompt = ChatPromptTemplate.from_template(f"""
์ ์ ์ ์ง๋ฌธ์ด ์ผ๊ตฌ์ ๊ด๋ จ๋ ์ง๋ฌธ์ธ์ง ํ์ธํ๊ณ , ์ผ๊ตฌ์ ๊ด๋ จ๋ ์ง๋ฌธ์ผ ๊ฒฝ์ฐ์๋ "YES" ๋ผ๋ ๋ต๋ณ์, ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ์๋ "NO" ๋ผ๋ ๋ต๋ณ์ ๋ฆฌํดํด์ฃผ์ธ์.
์ง๋ฌธ: {{question}}
""")
filtering_chain = filter_prompt | llm | StrOutputParser()
return filtering_chain
# ์ฌ์ ์ ์ฐธ๊ณ ํ์ฌ ์ ์ ์ ์ง๋ฌธ์ ์ฌ๊ตฌ์ฑํ๋ ์์คํ
์ค์
def get_dictionary_chain():
llm = get_llm()
dictionary = [
"๋ฒ ์ด์ค์ ์๋ ์ ์๋ฅผ ๋ํ๋ด๋ ํํ -> ์ฃผ์",
"ํ์์ ์๋ ์ ์๋ฅผ ๋ํ๋ด๋ ํํ -> ํ์",
"๋ง์ด๋ ์์์ ๊ณต์ ๋์ง๋ ์ ์๋ฅผ ๋ํ๋ด๋ ํํ -> ํฌ์",
"๋ญ๋ผ๊ณ ํ๋์?, ๋ญ๋ผ๊ณ ๋ถ๋ฅด๋์(๋ฌด์์ด๋๊ณ ๋ฌผ์ ๊ฒฝ์ฐ) -> ๋ปํ๋ ์ฉ์ด๊ฐ ๋ฌด์์ธ๊ฐ์?"
]
dictionary_prompt = ChatPromptTemplate.from_template(f"""
์ฌ์ฉ์์ ์ง๋ฌธ์ ํ์ธํ๊ณ ์ฌ์ ์ ์ฐธ๊ณ ํ์ฌ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ณ๊ฒฝํด์ฃผ์ธ์.
๋ง์ฝ ๋ณ๊ฒฝํ ํ์๊ฐ ์๋ค๊ณ ํ๋จ๋๋ฉด ๋ณ๊ฒฝํ์ง ์๊ณ ์ง๋ฌธ์ ๊ทธ๋๋ก ๋ฆฌํดํด์ฃผ์ธ์.
์ฌ์ : {dictionary}
์ง๋ฌธ: {{question}}
""")
dictionary_chain = dictionary_prompt | llm | StrOutputParser()
return dictionary_chain
# ๋ฌธ์๋ฅผ ๋ถ๋ฌ์ค๊ธฐ ์ํ retriever ์ค์
def get_retriever():
index_name = "baseball-rules-index"
# ๋ฐ์ดํฐ๋ฅผ ๋ฒกํฐํํ ์๋ฒ ๋ฉ ๋ชจ๋ธ ์ค์
embedding = UpstageEmbeddings(model="solar-embedding-1-large")
database = PineconeVectorStore.from_existing_index(index_name=index_name, embedding=embedding)
retriever = database.as_retriever(search_kwargs={"k": 4})
return retriever
# chat history๋ฅผ ๋ฐ์ํ์ฌ ์ง๋ฌธ์ ์ฌ๊ตฌ์ฑํ๋ ์์คํ
์ค์
def get_history_retriever():
llm = get_llm()
retriever = get_retriever()
# chat history๋ฅผ ๋ฐ์ํ์ฌ ์ง๋ฌธ์ ์ฌ๊ตฌ์ฑํ๋ ์์คํ
ํ๋กฌํํธ
contextualize_q_system_prompt = (
"Given a chat history and the latest user question "
"which might reference context in the chat history, "
"formulate a standalone question which can be understood "
"without the chat history. Do NOT answer the question, "
"just reformulate it if needed and otherwise return it as is."
)
# ์ฑํ
๊ธฐ๋ก์ ๋ฐ์ํ์ฌ QA๋ฅผ ์ํ ํ๋กฌํํธ
contextualize_q_prompt = ChatPromptTemplate.from_messages(
[
("system", contextualize_q_system_prompt),
MessagesPlaceholder("chat_history"), # ๋ฉ์์ง ๋ฆฌ์คํธ๋ฅผ ์ ๋ฌ
("human", "{input}")
]
)
history_aware_retriever = create_history_aware_retriever(llm, retriever, contextualize_q_prompt)
return history_aware_retriever
# ์ธ์
๊ธฐ๋ก์ ๊ฐ์ ธ์ค๊ธฐ ์ํ ํจ์ ์ค์
def get_session_history(session_id: str) -> BaseChatMessageHistory:
# ์ธ์
์ด ์์ผ๋ฉด ํด๋น ์ธ์
์ ๋ํ ์ฑํ
๊ธฐ๋ก ์์ฑ
if session_id not in chat_storage:
chat_storage[session_id] = ChatMessageHistory()
return chat_storage[session_id]
# RAG ์ฒด์ธ์ ์์ฑ
def get_rag_chain():
llm = get_llm()
# ๋ชจ๋ธ์ ์ญํ ์ ์ ์ํด์ฃผ๊ธฐ ์ํ ์์คํ
ํ๋กฌํํธ
system_prompt = ("""
๋น์ ์ ์ต๊ณ ์ KBO ๋ฆฌ๊ทธ ์ผ๊ตฌ ๊ท์น ์ ๋ฌธ๊ฐ์
๋๋ค.
์๋์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด์ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์ ํด์ฃผ์ธ์.
๋ต๋ณ ์์ ์ถ์ฒ์ ๋ํด์๋ ๋ช
ํํ๊ฒ ๋ฐํ์ฃผ์๊ณ , ์ฌ์ฉ์๊ฐ ์ดํดํ๊ธฐ ์ฝ๊ฒ ์ค๋ช
ํด์ฃผ์ธ์.
๋ต๋ณ์ ๊ธธ์ด๋ 2-3์ค ์ ๋๋ก ์ ํํด์ฃผ์ธ์.
\n\n
๋ฌธ์: {context}
""")
# ์ฑํ
๊ธฐ๋ก ๋ฐ์ํ์ฌ QA๋ฅผ ์ํ ํ๋กฌํํธ
qa_prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
MessagesPlaceholder("chat_history"), # ๋ฉ์์ง ๋ฆฌ์คํธ(์ฑํ
๊ธฐ๋ก)๋ฅผ ์ ๋ฌ
("human", "{input}")
]
)
history_aware_retriever = get_history_retriever() # ๊ธฐ๋ก ๊ธฐ๋ฐ ๊ฒ์์ ์ํ retriever
qa_chain = create_stuff_documents_chain(llm, qa_prompt) # ๋ชจ๋ธ์๊ฒ ๊ฒ์ํ ๋ฌธ์ ๋ฆฌ์คํธ๋ฅผ ์ ๋ฌ
rag_chain = create_retrieval_chain(history_aware_retriever, qa_chain) # RAG ์ฒด์ธ ์์ฑ
conversational_rag_chain = RunnableWithMessageHistory(
rag_chain, # ์คํํ RAG ์ฒด์ธ์ด๋ LLM ์ฒด์ธ
get_session_history, # ์ธ์
_ID ๊ธฐ๋ฐ์ ์ฑํ
๊ธฐ๋ก
input_messages_key="input", # ์ ์ ์ ์
๋ ฅ์ด ๋ด๊ธด ํค
history_messages_key="chat_history", # ์ฑํ
๊ธฐ๋ก์ ์ ๋ฌํ ํค
output_messages_key="answer" # ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ ํค
).pick("answer")
# ์ฑํ
history๋ฅผ ํฌํจํ retriever๋ฅผ ํ์ฉํ์ฌ rag_chain ๋ฐํ
return conversational_rag_chain
def get_ai_response():
filter_chain = get_filter()
dictionary_chain = get_dictionary_chain()
rag_chain = get_rag_chain()
baseball_chain = filter_chain | {"input": dictionary_chain} | rag_chain
ai_response = baseball_chain.invoke(
{"question": "์ผ๊ตฌ์์ ์ฃผ์ ํ๋ช
์ด ๋ฒ ์ด์ค๋ฅผ ํ์น๋ ๊ฒ์ ๋ญ๋ผ๊ณ ํ๋์?"},
config={"configurable": {"session_id": "abc123"}}
)
return ai_response
ai_response = get_ai_response()
print(ai_response)

๋ฌธ์ ๊ฐ ์๊ฒผ๋ค. ๊ธฐ์กด ํ์ดํ๋ผ์ธ์ ํ๋ฆ์ ์ ๋ฆฌํด๋ณด์๋ฉด ์๋์ ๊ฐ์๋ฐ, dictionary_chain์ ๊ฒฐ๊ณผ๊ฐ์ธ input์ ๋ณด๋ฉด ์ง๋ฌธ์ด ์๊ฑฐ๋ ๋ช ํํ์ง ์๋ค๋ ๋ฉ์์ง๋ฅผ ํ์ธํ ์ ์๋ค. filter chain์ ํตํด ์ ๋ ฅ๋ query์ ๊ฒฐ๊ณผ๋ฌผ์ธ YES ๋ NO ๊ฐ dictionary chain์ ์ ๋ ฅ๋๋ค. dictionary chain์์๋ "YES" ๋ "NO" ์ฟผ๋ฆฌ๋ฅผ dictionary๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฌ๊ตฌ์ฑํด์ผ ํ๋๋ฐ, ๋น์ฐํ ๋ถ๊ฐ๋ฅํ๋ค. ๋ฐ๋ผ์ "์ง๋ฌธ์ด ์๊ฑฐ๋ ์๋ชป๋ ํ์์ธ ๊ฒ ๊ฐ์ต๋๋ค...."์ ๊ฐ์ ๋ต๋ณ์ ์ถ๋ ฅํ๊ณ ์ด ๋ต๋ณ์ rag chain์ ๋๊ฒจ์ฃผ๋ ๊ฒ์ด๋ค. ์ฆ filter_chain์์ dictionary_chain์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋๊ธฐ๋ ๊ณผ์ ์ ๋ฌธ์ ๊ฐ ์๋ ๊ฒ์ด๋ค.

์ฌ์ค ์๊ฐํด๋ณด๋ฉด chat history๋ฅผ ํ์ฉํ๋ ์์ ์์ filter chain์ ํ์๊ฐ ์๊ณ ์ฌ์ฉํ์ ๋์ ์คํ๋ ค ๋ฌธ์ ๊ฐ ๋ ์ ์๋ค.
1๏ธโฃ์ฒซ ๋ฒ์งธ ์ง๋ฌธ: "์ผ๊ตฌ์์ ์ฃผ์ ํ๋ช ์ด ๋๋ฃจํ๋ ๊ฒ์ ๋ญ๋ผ๊ณ ํด?"
2๏ธโฃ๋ ๋ฒ์งธ ์ง๋ฌธ: "๊ทธ๋ผ 2๋ช ์ด ํ๋๊ฑด?"
๋ ์ง๋ฌธ์ด ์๋ก ๊ฐ์ ๋ฌธ๋งฅ์ ์ง๋ฌธ์์๋ ๋ถ๊ตฌํ๊ณ filter chain(๊ฐ์ฅ ๋จผ์ ๋์ํ๋ ์ฒด์ธ, history retriever๋ณด๋ค ๋จผ์ ๋์ํ๋ค)์์ ๋ ๋ฒ์งธ ์ง๋ฌธ์ ์ผ๊ตฌ ๊ท์น๊ณผ ์ฐ๊ด์ฑ์ด ์๋ ์ง๋ฌธ์ผ๋ก ํ๋จํ๊ฒ ๋๋ค. ๋ฐ๋ผ์ filter chain์ ์ ๊ฑฐํ๊ณ ํด๋น ๊ธฐ๋ฅ์ rag chain์ ์์ํ๋ ๊ฒ๋ ๋์์ง ์์ ์ ํ์ด๋ค. filter chain์ ์ญ์ ํ๊ณ rag chain์์ ํํฐ๋ง์ด ๊ฐ๋ฅํ๋๋ก ํ๋กฌํํธ๋ฅผ ์์ ํ์.
# RAG ์ฒด์ธ์ ์์ฑ
def get_rag_chain():
llm = get_llm()
# ๋ชจ๋ธ์ ์ญํ ์ ์ ์ํด์ฃผ๊ธฐ ์ํ ์์คํ
ํ๋กฌํํธ
system_prompt = ("""
๋น์ ์ ์ต๊ณ ์ KBO ๋ฆฌ๊ทธ ์ผ๊ตฌ ๊ท์น ์ ๋ฌธ๊ฐ์
๋๋ค.
์๋์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด์ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์ ํด์ฃผ์ธ์.
๋จ์ฝ์ ์ผ๊ตฌ ๊ท์น๊ณผ ๊ด๋ จ์ด ์๋ค๋ฉด "๐ซ์ผ๊ตฌ ๊ท์น์ ๋ํ ์ง๋ฌธ๋ง ๋ต๋ณ์ด ๊ฐ๋ฅํฉ๋๋ค."๋ผ๊ณ ๋ต๋ณํด์ฃผ์ธ์.
๋ง์ฝ์ ๋ต๋ณํ ์ ์๋ค๋ฉด ๋ชจ๋ฅธ๋ค๊ณ ๋ต๋ณํด์ฃผ์ธ์.
๋ต๋ณ ์์ ์ถ์ฒ์ ๋ํด์๋ ๋ช
ํํ๊ฒ ๋ฐํ์ฃผ์๊ณ , ์ฌ์ฉ์๊ฐ ์ดํดํ๊ธฐ ์ฝ๊ฒ ์ค๋ช
ํด์ฃผ์ธ์.
๋ต๋ณ์ ๊ธธ์ด๋ 2-3์ค ์ ๋๋ก ์ ํํด์ฃผ์ธ์.
\n\n
๋ฌธ์: {context}
""")
# ์ฑํ
๊ธฐ๋ก ๋ฐ์ํ์ฌ QA๋ฅผ ์ํ ํ๋กฌํํธ
qa_prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
MessagesPlaceholder("chat_history"), # ๋ฉ์์ง ๋ฆฌ์คํธ(์ฑํ
๊ธฐ๋ก)๋ฅผ ์ ๋ฌ
("human", "{input}")
]
)
history_aware_retriever = get_history_retriever() # ๊ธฐ๋ก ๊ธฐ๋ฐ ๊ฒ์์ ์ํ retriever
qa_chain = create_stuff_documents_chain(llm, qa_prompt) # ๋ชจ๋ธ์๊ฒ ๊ฒ์ํ ๋ฌธ์ ๋ฆฌ์คํธ๋ฅผ ์ ๋ฌ
rag_chain = create_retrieval_chain(history_aware_retriever, qa_chain) # RAG ์ฒด์ธ ์์ฑ
conversational_rag_chain = RunnableWithMessageHistory(
rag_chain, # ์คํํ RAG ์ฒด์ธ์ด๋ LLM ์ฒด์ธ
get_session_history, # ์ธ์
_ID ๊ธฐ๋ฐ์ ์ฑํ
๊ธฐ๋ก
input_messages_key="input", # ์ ์ ์ ์
๋ ฅ์ด ๋ด๊ธด ํค
history_messages_key="chat_history", # ์ฑํ
๊ธฐ๋ก์ ์ ๋ฌํ ํค
output_messages_key="answer" # ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ ํค
).pick("answer")
# ์ฑํ
history๋ฅผ ํฌํจํ retriever๋ฅผ ํ์ฉํ์ฌ rag_chain ๋ฐํ
return conversational_rag_chain
def get_ai_response():
dictionary_chain = get_dictionary_chain()
rag_chain = get_rag_chain()
baseball_chain = {"input": dictionary_chain} | rag_chain
ai_response = baseball_chain.invoke(
{"question": "์ผ๊ตฌ์์ ์ฃผ์ ํ๋ช
์ด ๋ฒ ์ด์ค๋ฅผ ํ์น๋ ๊ฒ์ ๋ญ๋ผ๊ณ ํ๋์?"},
config={"configurable": {"session_id": "abc123"}}
)
return ai_response
๋ ์ง๋ฌธ์ ๋ํด ๋ต๋ณ์ ์ ์ถ๋ ฅํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.


๐จ์ฌ๋ฌ ๋ฒ ํด๋ณด๋ ๋ฌธ์ ๊ฐ ์๊ฒผ๋๋ฐ, ์ฒซ ๋ฒ์งธ ์ง๋ฌธ์์๋ ์ ๋๋ก ๋ต๋ณ์ ํ์ง๋ง ์ด์ ์ฑํ ๊ธฐ๋ก์ ํ ๋๋ก ๋ฌธ๋งฅ์ ์ ์ถํด์ ๋ต๋ณ์ ํด์ผ ํ๋ "๊ทธ๋ผ ๋ ๋ช ์ด ๋์์ ํ๋ ๊ฑด ๋ญ๋ผ๊ณ ํด?" ๊ฐ์ ์ง๋ฌธ์๋ ์๋ฑํ ๋ต๋ณ์ ํ๊ฑฐ๋ ์ ๋๋ก ๋ตํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ๋ ์ข ์ข ์๋ค.


์ด๋ฌํ ๋ฌธ์ ๊ฐ ์๊ธฐ๋ ์ด์ ๋ dictionary chain์์ "๋ ๋ช ์ด ๋์์ ํ๋๊ฑด ๋ญ๋ผ๊ณ ํด?"์ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ๋ณด๊ฐ์ ํ๋ต์๊ณ ํ๋กฌํํธ๋ฅผ ํตํด ๋ณ๊ฒฝ์ํค๊ธฐ ๋๋ฌธ์ด๋ค. ์ฟผ๋ฆฌ๊ฐ ๋ชจํธํ๋๋ผ๋ dictionary chain ์ดํ์ history retriever์์ chat history๋ฅผ ์ฐธ๊ณ ํ์ฌ ๋ฌธ๋งฅ ํ์ ์ด ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ dictionary chain์์ ์ฝ๊ฐ์ ์ฉ์ด ์์ ์ ํตํด ์ฟผ๋ฆฌ๋ฅผ ๋ณด๊ฐํ๋ ์์ ๋ชจํธํ ์ง๋ฌธ์ ๋ํด์๋ ์์ ์ด ๋ฐ์ํ์ง ์๋๋ก ํด์ผํ๋ค.
๋ชจํธํ ์ง๋ฌธ์ ๋ํด์๋ ๋ฌธ๋งฅ ํ์ ์ ํตํด ์ฟผ๋ฆฌ ์์ ์ ํ๋๋ก dictionary chain์ ํ๋กฌํํธ๋ฅผ ์กฐ๊ธ ์์ ํด๋ณด์.
# ์ฌ์ ์ ์ฐธ๊ณ ํ์ฌ ์ ์ ์ ์ง๋ฌธ์ ์ฌ๊ตฌ์ฑํ๋ ์์คํ
์ค์
def get_dictionary_chain():
llm = get_llm()
dictionary = [
"๋ฒ ์ด์ค์ ์๋ ์ ์๋ฅผ ๋ํ๋ด๋ ํํ -> ์ฃผ์",
"ํ์์ ์๋ ์ ์๋ฅผ ๋ํ๋ด๋ ํํ -> ํ์",
"๋ง์ด๋ ์์์ ๊ณต์ ๋์ง๋ ์ ์๋ฅผ ๋ํ๋ด๋ ํํ -> ํฌ์"
]
dictionary_prompt = ChatPromptTemplate.from_template(f"""
์ฌ์ฉ์์ ์ง๋ฌธ์ ํ์ธํ๊ณ ์ฌ์ ์ ์ฐธ๊ณ ํ์ฌ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ณ๊ฒฝํด์ฃผ์ธ์.
๋ง์ฝ ๋ณ๊ฒฝํ ํ์๊ฐ ์๋ค๊ณ ํ๋จ๋๋ฉด ๋ณ๊ฒฝํ์ง ์๊ณ ์ง๋ฌธ์ ๊ทธ๋๋ก ๋ฆฌํดํด์ฃผ์ธ์.
์ง๋ฌธ์ด๋ ์ฃผ์ด๊ฐ ๋ชจํธํ ๊ฒฝ์ฐ history retriever์์ history chat์ ์ฐธ๊ณ ํ์ฌ ์ ์ถํ ์ ์๋๋ก ์ง๋ฌธ์ ๋ณ๊ฒฝํ์ง ์๊ณ ๊ทธ๋๋ก ๋ฆฌํดํด์ฃผ์ธ์.
์ฌ์ : {dictionary}
์ง๋ฌธ: {{question}}
""")
dictionary_chain = dictionary_prompt | llm | StrOutputParser()
return dictionary_chain
๊ฒฐ๊ณผ๊ฐ ์ ๋์ค๋ ๊ฒ์ ํ์ธ ๊ฐ๋ฅํ๋ค.

Few-shot ์ถ๊ฐํ๊ธฐ
์ฝ๋๋ฅผ ์ฌ๋ฌ ๋ฒ ๋๋ ค๋ณด๋ฉด ๋ต๋ณ์ด ์ผ๊ด์ ์ด์ง ์๋ค๋ ๊ฒ์ ํ์ธํ ์ ์๋ค("๋๋ธ ์คํธ", ~~ํ๋ ๊ฒ์ "๋๋ธ์คํธ"์ด๋ผ๊ณ ํฉ๋๋ค... ๋ฑ). ์ฌ์ฉ์ ์ ์ฅ์์๋ ์ผ๊ด์ ์ธ ํํ๋ฅผ ์ ์งํ๋ ๋ต๋ณ์ ๋ณด๋ ๊ฒ์ด ํธํ๋ Few-shot์ ์ถ๊ฐํด๋ณด์(shot์ด ์๋ ๊ฒ์ Zero-shot, ํ๋์ธ ๊ฒ์ One-shot, Single-shot์ผ๋ก ๋ถ๋ฅด๊ธฐ๋ ํ๋ค).
Few-shot์ด๋ ์ฝ๊ฒ ๋งํด ์์ ์ง๋ฌธ๊ณผ ๋ต๋ณ์ ์ฃผ๊ณ ์ด๋ฐ ์์ผ๋ก ๋ต๋ณํ๋ฉด ๋ผ! ๋ผ๊ณ ๋ชจ๋ธ์๊ฒ ๋ฏธ๋ฆฌ ๊ฐ๋ฅด์น๋ ๊ฒ์ด๋ค. ๋ชจ๋ธ์ด ์๋ก์ด ์ง๋ฌธ์ ๋ฐ์ ๋ ๋ฏธ๋ฆฌ ํ์ตํ ์์๋ฅผ ์ฐธ๊ณ ํ๋๋ก ํ๋ ๊ธฐ๋ฒ์ด๋ค.
๋๋ ๋ฐ๋ก๋ ์๋์ ์ฅ์ ์ด ์๋ค.
1. hallucination(ํ๊ฐ) ํ์์ด ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ ๋ฎ์ถ ์ ์๋ค. ์ฃผ์ ๋ฅผ ๋ฒ์ด๋๋ ํค์๋์ ๋ํด์๋ ๋ต๋ณํ์ง ์๋๋ก ์์๋ฅผ ํตํด ํ์ต์ํฌ ์ ์๋ค.
2. ๋ต๋ณ ์์์ ํต์ผ ์ํฌ ์ ์๋ค. ๋ต๋ณ ์์์ ๋ง์ถฐ ์ผ๊ด๋ ๋ต๋ณ ํ์์ ์ ์ ์๊ฒ ์ ๊ณตํ์ฌ ๊ฐ๋ ์ฑ์ ๋์ผ ์ ์๋ค.
3. ๊ทธ ๋ฐ์ ์์ํ ๋ชจ๋ธ์ ์ค์๋ ๋ฐฉ์ง(๋ฌธ๋งฅ ํ์ ์ค๋ฅ ๋ฑ)์ ๋ ์ ํํ ๋ต๋ณ ์์ฑ
์ด์ ์ ์งํํ๋ ํ๋ก์ ํธ Cquis์์๋ OPENAI ๋ชจ๋ธ ํ๋กฌํํธ๋ฅผ ์์ฑํ์๋๋ฐ ๊ทธ ๋๋ Few-shot์ ๊ฐ๋ ๋ ์ ๋ชจ๋ฅด๋ ์ฑ๋ก ๋๋ฆ๋๋ก hallucination์ ์ก๊ฒ ๋ค๊ณ ๋ ธ๋ ฅํ์๋ค.


few shot์ ์ต์ข qa_prompt์์ ์ฐธ๊ณ ํ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ChatPromptTemplate.from_messages์ ์ธ์๋ก ์ถ๊ฐํด์ฃผ๋ฉด ํ๋กฌํํธ ์คํ ์์ ์ถ๊ฐํ few shot ๊ด๋ จ ํ๋กฌํํธ๋ฅผ ์ฐธ๊ณ ํด์ ๋ต๋ณ์ ์์ฑํ๋ค. example_prompt๋ฅผ ํตํด ๋ฏธ๋ฆฌ ๋ํ ํ์์ ํ์ ์ ๊ณตํด์ฃผ๊ณ answer_examples๋ฅผ ํตํด ์ง๋ต ์์๋ฅผ ๋ฏธ๋ฆฌ ํ์ต์ํจ๋ค.
# RAG ์ฒด์ธ์ ์์ฑ
def get_rag_chain():
llm = get_llm()
# few-shot์ ์ํ ํ๋กฌํํธ ์ค์
example_prompt = ChatPromptTemplate.from_messages(
[
("human", "{input}"),
("ai", "{answer}")
]
)
# few-shot์ ์ํ ํ๋กฌํํธ ์ค์
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt,
examples=answer_examples
)
# ๋ชจ๋ธ์ ์ญํ ์ ์ ์ํด์ฃผ๊ธฐ ์ํ ์์คํ
ํ๋กฌํํธ
system_prompt = ("""
๋น์ ์ ์ต๊ณ ์ KBO ๋ฆฌ๊ทธ ์ผ๊ตฌ ๊ท์น ์ ๋ฌธ๊ฐ์
๋๋ค.
์๋์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด์ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์ ํด์ฃผ์ธ์.
๋จ์ฝ์ ์ผ๊ตฌ ๊ท์น๊ณผ ๊ด๋ จ์ด ์๋ค๋ฉด "๐ซ์ผ๊ตฌ ๊ท์น์ ๋ํ ์ง๋ฌธ๋ง ๋ต๋ณ์ด ๊ฐ๋ฅํฉ๋๋ค."๋ผ๊ณ ๋ต๋ณํด์ฃผ์ธ์.
๋ง์ฝ์ ๋ต๋ณํ ์ ์๋ค๋ฉด ๋ชจ๋ฅธ๋ค๊ณ ๋ต๋ณํด์ฃผ์ธ์.
๋ต๋ณ ์์ ์ถ์ฒ์ ๋ํด์๋ ๋ช
ํํ๊ฒ ๋ฐํ์ฃผ์๊ณ , ์ฌ์ฉ์๊ฐ ์ดํดํ๊ธฐ ์ฝ๊ฒ ์ค๋ช
ํด์ฃผ์ธ์.
๋ต๋ณ์ ๊ธธ์ด๋ 2-3์ค ์ ๋๋ก ์ ํํด์ฃผ์ธ์.
\n\n
๋ฌธ์: {context}
""")
# ์ฑํ
๊ธฐ๋ก ๋ฐ์ํ์ฌ QA๋ฅผ ์ํ ํ๋กฌํํธ
qa_prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
few_shot_prompt, # few-shot์ ์ฐธ๊ณ ํ๋๋ก ์ ๋ฌ
MessagesPlaceholder("chat_history"), # ๋ฉ์์ง ๋ฆฌ์คํธ(์ฑํ
๊ธฐ๋ก)๋ฅผ ์ ๋ฌ
("human", "{input}")
]
)
history_aware_retriever = get_history_retriever() # ๊ธฐ๋ก ๊ธฐ๋ฐ ๊ฒ์์ ์ํ retriever
qa_chain = create_stuff_documents_chain(llm, qa_prompt) # ๋ชจ๋ธ์๊ฒ ๊ฒ์ํ ๋ฌธ์ ๋ฆฌ์คํธ๋ฅผ ์ ๋ฌ
rag_chain = create_retrieval_chain(history_aware_retriever, qa_chain) # RAG ์ฒด์ธ ์์ฑ
conversational_rag_chain = RunnableWithMessageHistory(
rag_chain, # ์คํํ RAG ์ฒด์ธ์ด๋ LLM ์ฒด์ธ
get_session_history, # ์ธ์
_ID ๊ธฐ๋ฐ์ ์ฑํ
๊ธฐ๋ก
input_messages_key="input", # ์ ์ ์ ์
๋ ฅ์ด ๋ด๊ธด ํค
history_messages_key="chat_history", # ์ฑํ
๊ธฐ๋ก์ ์ ๋ฌํ ํค
output_messages_key="answer" # ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ ํค
).pick("answer")
# ์ฑํ
history๋ฅผ ํฌํจํ retriever๋ฅผ ํ์ฉํ์ฌ rag_chain ๋ฐํ
return conversational_rag_chain
์๋์ ๊ฐ์ ์ง๋ต ์์ ๋ฌธ์๋ฅผ ๋ฐ๋ก ์์ฑํด์ llm.py์ import ์์ผฐ๋ค.
# fewshot_doc.py
answer_examples = [
{
"input": "์ฃผ์๊ฐ ๋ ๋ช
์ด ๋์์ ๋๋ฃจํ๋ ๊ฒ์ ๋ญ๋ผ๊ณ ํด?",
"answer": "์ฃผ์ ๋ ๋ช
์ด ๋์์ ๋๋ฃจํ๋ ๊ฒ์ '๋๋ธ ์คํธ'์ด๋ผ๊ณ ํฉ๋๋ค!\n\n"
"๋๋ธ ์คํธ(Double Steal)์ ๋ ๋ช
์ด์์ ์ฃผ์๊ฐ ๋์์ ๋ค์ ๋ฒ ์ด์ค๋ก ๋๋ฃจํ๋ ์ ๋ต์ ํ๋ ์ด์
๋๋ค. "
"ํนํ, ์๋น ํ์ด ํ ์ฃผ์์๊ฒ ์ง์คํ ๋ ๋ค๋ฅธ ์ฃผ์๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ง๋ฃจํ ๊ฐ๋ฅ์ฑ์ด ๋์์ง๋ ์ ์ ์
๋๋ค.\n"
"[์ถ์ฒ: KBO ๊ท์น ๋ฌธ์]"
},
{
"input": "ํฌ์๊ฐ ๋ฐ์น ๋์์ ํ๋ฉด ๋ญ๋ผ๊ณ ํด?",
"answer": "ํฌ์๊ฐ ๋ฐ์น ๋์์ ํ๋ฉด '๋ณดํฌ'๋ผ๊ณ ํฉ๋๋ค!\n\n"
"๋ณดํฌ(Balk)๋ ํฌ์๊ฐ ์ฃผ์๊ฐ ์๋ ์ํฉ์์ ๊ท์น์ ์ด๊ธ๋๋ ๋ถ๋ฒ ๋์์ ํ์ ๋ ์ ์ธ๋ฉ๋๋ค. "
"๋ณดํฌ๊ฐ ์ ์ธ๋๋ฉด ๋ชจ๋ ์ฃผ์๋ ํ ๋ฒ ์ด์ค์ฉ ์ง๋ฃจํ๊ฒ ๋๋ฉฐ, ์ด๋ ํฌ์์ ํ์ดํฌ ๋์ ๋ฐฉ์ง๋ฅผ ์ํ ๊ท์ ์
๋๋ค.\n"
"[์ถ์ฒ: KBO ๊ท์น ๋ฌธ์]"
},
{
"input": "๊ฒฝ๊ธฐ ์ค ํ์๊ฐ ์ผ์ง์ ๋นํ์ง๋ง 1๋ฃจ๋ก ๋ฌ๋ฆด ์ ์๋ ์ํฉ์?",
"answer": "ํ์๊ฐ ์ผ์ง์ ๋นํ์ง๋ง 1๋ฃจ๋ก ๋ฌ๋ฆด ์ ์๋ ์ํฉ์ '๋ซ์์ ์ผ์ง'์
๋๋ค!\n\n"
"๋ซ์์ ์ผ์ง(Dropped Third Strike)์ ์คํธ๋ผ์ดํฌ ์์์ด ์ ์ธ๋์์ง๋ง ํฌ์๊ฐ ๊ณต์ ๋์ณ์ ์ก์ง ๋ชปํ ๊ฒฝ์ฐ๋ฅผ ์๋ฏธํฉ๋๋ค. "
"์ด๋, 1๋ฃจ์ ์ฃผ์๊ฐ ์๊ฑฐ๋ 2์์ ์ํฉ์ด๋ผ๋ฉด ํ์๋ 1๋ฃจ๋ก ๋ฌ๋ ค ์ถ๋ฃจ๋ฅผ ์๋ํ ์ ์์ต๋๋ค.\n"
"[์ถ์ฒ: KBO ๊ท์น ๋ฌธ์]"
},
{
"input": "์ฃผ์๊ฐ 1๋ฃจ์ 2๋ฃจ ์ฌ์ด์์ ํ๊ทธ ์์๋์ง ์์ผ๋ ค๊ณ ์๋ค ๊ฐ๋ค ํ๋ ๊ฒ์?",
"answer": "์ฃผ์๊ฐ 1๋ฃจ์ 2๋ฃจ ์ฌ์ด์์ ํ๊ทธ ์์๋์ง ์์ผ๋ ค๊ณ ์๋ค ๊ฐ๋ค ํ๋ ๊ฒ์ '๋ฐ๋ค์ด'์ด๋ผ๊ณ ํฉ๋๋ค!\n\n"
"๋ฐ๋ค์ด(Rundown)์ ์ฃผ์๊ฐ ์ผ์๋ค์ ์ก๊ตฌ ํ๋ ์ด ์ฌ์ด์์ ์์์ ํผํ๊ธฐ ์ํด ์ข์ฐ๋ก ์์ง์ด๋ฉฐ ๋๋ง๊ฐ๋ ์ํฉ์ ์๋ฏธํฉ๋๋ค. "
"์๋นํ์ ์ฃผ์๋ฅผ ํ๋ ฅํ์ฌ ๋น ๋ฅด๊ฒ ํ๊ทธ ์์ํ๋ ๊ฒ์ด ์ค์ํ๋ฉฐ, ์ด๋ฅผ '๋ฐ๋ค์ด ํ๋ ์ด'๋ผ๊ณ ๋ ํฉ๋๋ค.\n"
"[์ถ์ฒ: KBO ๊ท์น ๋ฌธ์]"
},
# ๋ต๋ณํ ์ ์๋ ์ง๋ฌธ ์์
{
"input": "์ค๋ KBO ๋ฆฌ๊ทธ ๊ฒฝ๊ธฐ ๊ฒฐ๊ณผ ์๋ ค์ค.",
"answer": "๐ซ์ผ๊ตฌ ๊ท์น์ ๋ํ ์ง๋ฌธ๋ง ๋ต๋ณ์ด ๊ฐ๋ฅํฉ๋๋ค."
},
{
"input": "2024๋
KBO ๋ฆฌ๊ทธ์์ ๊ฐ์ฅ ๋ง์ ํ๋ฐ์ ์น ์ ์๋ ๋๊ตฌ์ผ?",
"answer": "๐ซ์ผ๊ตฌ ๊ท์น์ ๋ํ ์ง๋ฌธ๋ง ๋ต๋ณ์ด ๊ฐ๋ฅํฉ๋๋ค."
},
{
"input": "KBO ๋ฆฌ๊ทธ์์ ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ํ์ ์ด๋์ผ?",
"answer": "๐ซ์ผ๊ตฌ ๊ท์น์ ๋ํ ์ง๋ฌธ๋ง ๋ต๋ณ์ด ๊ฐ๋ฅํฉ๋๋ค."
},
{
"input": "์ผ๊ตฌ์ฅ์์ ๊ฐ์ฅ ๋ง์๋ ์์ ์ถ์ฒํด์ค.",
"answer": "๐ซ์ผ๊ตฌ ๊ท์น์ ๋ํ ์ง๋ฌธ๋ง ๋ต๋ณ์ด ๊ฐ๋ฅํฉ๋๋ค."
}
]
few shot ์ ์ฉ ํ์ ๋ต๋ณ์ ํ ์คํธ ํ๊ธฐ ์ํด์ ์๋์ ์ง๋ฌธ set์ ์คํ์์ผฐ๋ค. chat history๋ฅผ ์ฐธ๊ณ ํ์ฌ ๋์ํ๋๋ก ์ผ๋ถ๋ฌ ๋ ๋ฒ์งธ ์ง๋ฌธ์ ๋งฅ๋ฝ์ ์ ์ถํด์ผ ๋ต๋ณํ ์ ์๋๋ก ์์ฑํ๋ค.
Question Set 1.
1๏ธโฃ์ฃผ์๊ฐ ๋ฒ ์ด์ค๋ฅผ ํ์น๋ ๊ฒ์ ๋ญ๋ผ๊ณ ํด?
2๏ธโฃ๊ทธ๋ผ ์ฃผ์ ๋ ๋ช ์ด ๋์์ ํ๋ ๊ฑด ๋ญ๋ผ๊ณ ํด?
Question Set 2.
1๏ธโฃ์ธํ๋ ํ๋ผ์ด์ ๋ํด ์ค๋ช ํด์ค.
2๏ธโฃํด๋น ์ํฉ์์ ์ฃผ์๋ ์ด๋ป๊ฒ ํ๋ํ๋์ง ์ค๋ช ํด์ค.

์ง๊ธ๊น์ง ์์ฑํ ์ฝ๋๋ ์๋์ ๊ฐ๋ค(llm.py, fewshot_doc.py)
# llm.py
from langchain_upstage import ChatUpstage, UpstageEmbeddings
from langchain_pinecone import PineconeVectorStore
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, FewShotChatMessagePromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from fewshot_doc import answer_examples
chat_storage = {}
def get_llm():
llm = ChatUpstage()
return llm
# ์ฌ์ ์ ์ฐธ๊ณ ํ์ฌ ์ ์ ์ ์ง๋ฌธ์ ์ฌ๊ตฌ์ฑํ๋ ์์คํ
์ค์
def get_dictionary_chain():
llm = get_llm()
dictionary = [
"๋ฒ ์ด์ค์ ์๋ ์ ์๋ฅผ ๋ํ๋ด๋ ํํ -> ์ฃผ์",
"ํ์์ ์๋ ์ ์๋ฅผ ๋ํ๋ด๋ ํํ -> ํ์",
"๋ง์ด๋ ์์์ ๊ณต์ ๋์ง๋ ์ ์๋ฅผ ๋ํ๋ด๋ ํํ -> ํฌ์"
]
dictionary_prompt = ChatPromptTemplate.from_template(f"""
์ฌ์ฉ์์ ์ง๋ฌธ์ ํ์ธํ๊ณ ์ฌ์ ์ ์ฐธ๊ณ ํ์ฌ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ณ๊ฒฝํด์ฃผ์ธ์.
๋ง์ฝ ๋ณ๊ฒฝํ ํ์๊ฐ ์๋ค๊ณ ํ๋จ๋๋ฉด ๋ณ๊ฒฝํ์ง ์๊ณ ์ง๋ฌธ์ ๊ทธ๋๋ก ๋ฆฌํดํด์ฃผ์ธ์.
์ง๋ฌธ์ด๋ ์ฃผ์ด๊ฐ ๋ชจํธํ ๊ฒฝ์ฐ history retriever์์ history chat์ ์ฐธ๊ณ ํ์ฌ ์ ์ถํ ์ ์๋๋ก ์ง๋ฌธ์ ๋ณ๊ฒฝํ์ง ์๊ณ ๊ทธ๋๋ก ๋ฆฌํดํด์ฃผ์ธ์.
์ฌ์ : {dictionary}
์ง๋ฌธ: {{question}}
""")
dictionary_chain = dictionary_prompt | llm | StrOutputParser()
return dictionary_chain
# ๋ฌธ์๋ฅผ ๋ถ๋ฌ์ค๊ธฐ ์ํ retriever ์ค์
def get_retriever():
index_name = "baseball-rules-index"
# ๋ฐ์ดํฐ๋ฅผ ๋ฒกํฐํํ ์๋ฒ ๋ฉ ๋ชจ๋ธ ์ค์
embedding = UpstageEmbeddings(model="solar-embedding-1-large")
database = PineconeVectorStore.from_existing_index(index_name=index_name, embedding=embedding)
retriever = database.as_retriever(search_kwargs={"k": 4})
return retriever
# chat history๋ฅผ ๋ฐ์ํ์ฌ ์ง๋ฌธ์ ์ฌ๊ตฌ์ฑํ๋ ์์คํ
์ค์
def get_history_retriever():
llm = get_llm()
retriever = get_retriever()
# chat history๋ฅผ ๋ฐ์ํ์ฌ ์ง๋ฌธ์ ์ฌ๊ตฌ์ฑํ๋ ์์คํ
ํ๋กฌํํธ
contextualize_q_system_prompt = (
"Given a chat history and the latest user question "
"which might reference context in the chat history, "
"formulate a standalone question which can be understood "
"without the chat history. Do NOT answer the question, "
"just reformulate it if needed and otherwise return it as is."
)
# ์ฑํ
๊ธฐ๋ก์ ๋ฐ์ํ์ฌ QA๋ฅผ ์ํ ํ๋กฌํํธ
contextualize_q_prompt = ChatPromptTemplate.from_messages(
[
("system", contextualize_q_system_prompt),
MessagesPlaceholder("chat_history"), # ๋ฉ์์ง ๋ฆฌ์คํธ๋ฅผ ์ ๋ฌ
("human", "{input}")
]
)
history_aware_retriever = create_history_aware_retriever(llm, retriever, contextualize_q_prompt)
return history_aware_retriever
# ์ธ์
๊ธฐ๋ก์ ๊ฐ์ ธ์ค๊ธฐ ์ํ ํจ์ ์ค์
def get_session_history(session_id: str) -> BaseChatMessageHistory:
# ์ธ์
์ด ์์ผ๋ฉด ํด๋น ์ธ์
์ ๋ํ ์ฑํ
๊ธฐ๋ก ์์ฑ
if session_id not in chat_storage:
chat_storage[session_id] = ChatMessageHistory()
return chat_storage[session_id]
# RAG ์ฒด์ธ์ ์์ฑ
def get_rag_chain():
llm = get_llm()
# few-shot์ ์ํ ํ๋กฌํํธ ์ค์
example_prompt = ChatPromptTemplate.from_messages(
[
("human", "{input}"),
("ai", "{answer}")
]
)
# few-shot์ ์ํ ํ๋กฌํํธ ์ค์
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt,
examples=answer_examples
)
# ๋ชจ๋ธ์ ์ญํ ์ ์ ์ํด์ฃผ๊ธฐ ์ํ ์์คํ
ํ๋กฌํํธ
system_prompt = ("""
๋น์ ์ ์ต๊ณ ์ KBO ๋ฆฌ๊ทธ ์ผ๊ตฌ ๊ท์น ์ ๋ฌธ๊ฐ์
๋๋ค.
์๋์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด์ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์ ํด์ฃผ์ธ์.
๋จ์ฝ์ ์ผ๊ตฌ ๊ท์น๊ณผ ๊ด๋ จ์ด ์๋ค๋ฉด "๐ซ์ผ๊ตฌ ๊ท์น์ ๋ํ ์ง๋ฌธ๋ง ๋ต๋ณ์ด ๊ฐ๋ฅํฉ๋๋ค."๋ผ๊ณ ๋ต๋ณํด์ฃผ์ธ์.
๋ง์ฝ์ ๋ต๋ณํ ์ ์๋ค๋ฉด ๋ชจ๋ฅธ๋ค๊ณ ๋ต๋ณํด์ฃผ์ธ์.
๋ต๋ณ ์์ ์ถ์ฒ์ ๋ํด์๋ ๋ช
ํํ๊ฒ ๋ฐํ์ฃผ์๊ณ , ์ฌ์ฉ์๊ฐ ์ดํดํ๊ธฐ ์ฝ๊ฒ ์ค๋ช
ํด์ฃผ์ธ์.
๋ต๋ณ์ ๊ธธ์ด๋ 2-3์ค ์ ๋๋ก ์ ํํด์ฃผ์ธ์.
\n\n
๋ฌธ์: {context}
""")
# ์ฑํ
๊ธฐ๋ก ๋ฐ์ํ์ฌ QA๋ฅผ ์ํ ํ๋กฌํํธ
qa_prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
few_shot_prompt, # few-shot์ ์ฐธ๊ณ ํ๋๋ก ์ ๋ฌ
MessagesPlaceholder("chat_history"), # ๋ฉ์์ง ๋ฆฌ์คํธ(์ฑํ
๊ธฐ๋ก)๋ฅผ ์ ๋ฌ
("human", "{input}")
]
)
history_aware_retriever = get_history_retriever() # ๊ธฐ๋ก ๊ธฐ๋ฐ ๊ฒ์์ ์ํ retriever
qa_chain = create_stuff_documents_chain(llm, qa_prompt) # ๋ชจ๋ธ์๊ฒ ๊ฒ์ํ ๋ฌธ์ ๋ฆฌ์คํธ๋ฅผ ์ ๋ฌ
rag_chain = create_retrieval_chain(history_aware_retriever, qa_chain) # RAG ์ฒด์ธ ์์ฑ
conversational_rag_chain = RunnableWithMessageHistory(
rag_chain, # ์คํํ RAG ์ฒด์ธ์ด๋ LLM ์ฒด์ธ
get_session_history, # ์ธ์
_ID ๊ธฐ๋ฐ์ ์ฑํ
๊ธฐ๋ก
input_messages_key="input", # ์ ์ ์ ์
๋ ฅ์ด ๋ด๊ธด ํค
history_messages_key="chat_history", # ์ฑํ
๊ธฐ๋ก์ ์ ๋ฌํ ํค
output_messages_key="answer" # ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ ํค
).pick("answer")
# ์ฑํ
history๋ฅผ ํฌํจํ retriever๋ฅผ ํ์ฉํ์ฌ rag_chain ๋ฐํ
return conversational_rag_chain
def get_ai_response1():
dictionary_chain = get_dictionary_chain()
rag_chain = get_rag_chain()
baseball_chain = {"input": dictionary_chain} | rag_chain
ai_response = baseball_chain.invoke(
{"question": "์ธํ๋ ํ๋ผ์ด์ ๋ํด ์ค๋ช
ํด์ค."},
config={"configurable": {"session_id": "abc123"}}
)
return ai_response
def get_ai_response2():
dictionary_chain = get_dictionary_chain()
rag_chain = get_rag_chain()
baseball_chain = {"input": dictionary_chain} | rag_chain
ai_response = baseball_chain.invoke(
{"question": "ํด๋น ์ํฉ์์ ์ฃผ์๋ ์ด๋ป๊ฒ ํ๋ํ๋์ง ์ค๋ช
ํด์ค."},
config={"configurable": {"session_id": "abc123"}}
)
return ai_response
ai_response = get_ai_response1()
print(f"ai_response1:\n {ai_response}\n")
ai_response = get_ai_response2()
print(f"ai_response2:\n {ai_response}\n")
# fewshot_doc.py
answer_examples = [
{
"input": "์ฃผ์๊ฐ ๋ ๋ช
์ด ๋์์ ๋๋ฃจํ๋ ๊ฒ์ ๋ญ๋ผ๊ณ ํด?",
"answer": "์ฃผ์ ๋ ๋ช
์ด ๋์์ ๋๋ฃจํ๋ ๊ฒ์ '๋๋ธ ์คํธ'์ด๋ผ๊ณ ํฉ๋๋ค!\n\n"
"๋๋ธ ์คํธ(Double Steal)์ ๋ ๋ช
์ด์์ ์ฃผ์๊ฐ ๋์์ ๋ค์ ๋ฒ ์ด์ค๋ก ๋๋ฃจํ๋ ์ ๋ต์ ํ๋ ์ด์
๋๋ค. "
"ํนํ, ์๋น ํ์ด ํ ์ฃผ์์๊ฒ ์ง์คํ ๋ ๋ค๋ฅธ ์ฃผ์๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ง๋ฃจํ ๊ฐ๋ฅ์ฑ์ด ๋์์ง๋ ์ ์ ์
๋๋ค.\n"
"[์ถ์ฒ: KBO ๊ท์น ๋ฌธ์]"
},
{
"input": "ํฌ์๊ฐ ๋ฐ์น ๋์์ ํ๋ฉด ๋ญ๋ผ๊ณ ํด?",
"answer": "ํฌ์๊ฐ ๋ฐ์น ๋์์ ํ๋ฉด '๋ณดํฌ'๋ผ๊ณ ํฉ๋๋ค!\n\n"
"๋ณดํฌ(Balk)๋ ํฌ์๊ฐ ์ฃผ์๊ฐ ์๋ ์ํฉ์์ ๊ท์น์ ์ด๊ธ๋๋ ๋ถ๋ฒ ๋์์ ํ์ ๋ ์ ์ธ๋ฉ๋๋ค. "
"๋ณดํฌ๊ฐ ์ ์ธ๋๋ฉด ๋ชจ๋ ์ฃผ์๋ ํ ๋ฒ ์ด์ค์ฉ ์ง๋ฃจํ๊ฒ ๋๋ฉฐ, ์ด๋ ํฌ์์ ํ์ดํฌ ๋์ ๋ฐฉ์ง๋ฅผ ์ํ ๊ท์ ์
๋๋ค.\n"
"[์ถ์ฒ: KBO ๊ท์น ๋ฌธ์]"
},
{
"input": "๊ฒฝ๊ธฐ ์ค ํ์๊ฐ ์ผ์ง์ ๋นํ์ง๋ง 1๋ฃจ๋ก ๋ฌ๋ฆด ์ ์๋ ์ํฉ์?",
"answer": "ํ์๊ฐ ์ผ์ง์ ๋นํ์ง๋ง 1๋ฃจ๋ก ๋ฌ๋ฆด ์ ์๋ ์ํฉ์ '๋ซ์์ ์ผ์ง'์
๋๋ค!\n\n"
"๋ซ์์ ์ผ์ง(Dropped Third Strike)์ ์คํธ๋ผ์ดํฌ ์์์ด ์ ์ธ๋์์ง๋ง ํฌ์๊ฐ ๊ณต์ ๋์ณ์ ์ก์ง ๋ชปํ ๊ฒฝ์ฐ๋ฅผ ์๋ฏธํฉ๋๋ค. "
"์ด๋, 1๋ฃจ์ ์ฃผ์๊ฐ ์๊ฑฐ๋ 2์์ ์ํฉ์ด๋ผ๋ฉด ํ์๋ 1๋ฃจ๋ก ๋ฌ๋ ค ์ถ๋ฃจ๋ฅผ ์๋ํ ์ ์์ต๋๋ค.\n"
"[์ถ์ฒ: KBO ๊ท์น ๋ฌธ์]"
},
{
"input": "์ฃผ์๊ฐ 1๋ฃจ์ 2๋ฃจ ์ฌ์ด์์ ํ๊ทธ ์์๋์ง ์์ผ๋ ค๊ณ ์๋ค ๊ฐ๋ค ํ๋ ๊ฒ์?",
"answer": "์ฃผ์๊ฐ 1๋ฃจ์ 2๋ฃจ ์ฌ์ด์์ ํ๊ทธ ์์๋์ง ์์ผ๋ ค๊ณ ์๋ค ๊ฐ๋ค ํ๋ ๊ฒ์ '๋ฐ๋ค์ด'์ด๋ผ๊ณ ํฉ๋๋ค!\n\n"
"๋ฐ๋ค์ด(Rundown)์ ์ฃผ์๊ฐ ์ผ์๋ค์ ์ก๊ตฌ ํ๋ ์ด ์ฌ์ด์์ ์์์ ํผํ๊ธฐ ์ํด ์ข์ฐ๋ก ์์ง์ด๋ฉฐ ๋๋ง๊ฐ๋ ์ํฉ์ ์๋ฏธํฉ๋๋ค. "
"์๋นํ์ ์ฃผ์๋ฅผ ํ๋ ฅํ์ฌ ๋น ๋ฅด๊ฒ ํ๊ทธ ์์ํ๋ ๊ฒ์ด ์ค์ํ๋ฉฐ, ์ด๋ฅผ '๋ฐ๋ค์ด ํ๋ ์ด'๋ผ๊ณ ๋ ํฉ๋๋ค.\n"
"[์ถ์ฒ: KBO ๊ท์น ๋ฌธ์]"
},
# ๋ต๋ณํ ์ ์๋ ์ง๋ฌธ ์์
{
"input": "์ค๋ KBO ๋ฆฌ๊ทธ ๊ฒฝ๊ธฐ ๊ฒฐ๊ณผ ์๋ ค์ค.",
"answer": "๐ซ์ผ๊ตฌ ๊ท์น์ ๋ํ ์ง๋ฌธ๋ง ๋ต๋ณ์ด ๊ฐ๋ฅํฉ๋๋ค."
},
{
"input": "2024๋
KBO ๋ฆฌ๊ทธ์์ ๊ฐ์ฅ ๋ง์ ํ๋ฐ์ ์น ์ ์๋ ๋๊ตฌ์ผ?",
"answer": "๐ซ์ผ๊ตฌ ๊ท์น์ ๋ํ ์ง๋ฌธ๋ง ๋ต๋ณ์ด ๊ฐ๋ฅํฉ๋๋ค."
},
{
"input": "KBO ๋ฆฌ๊ทธ์์ ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ํ์ ์ด๋์ผ?",
"answer": "๐ซ์ผ๊ตฌ ๊ท์น์ ๋ํ ์ง๋ฌธ๋ง ๋ต๋ณ์ด ๊ฐ๋ฅํฉ๋๋ค."
},
{
"input": "์ผ๊ตฌ์ฅ์์ ๊ฐ์ฅ ๋ง์๋ ์์ ์ถ์ฒํด์ค.",
"answer": "๐ซ์ผ๊ตฌ ๊ท์น์ ๋ํ ์ง๋ฌธ๋ง ๋ต๋ณ์ด ๊ฐ๋ฅํฉ๋๋ค."
}
]
References
https://python.langchain.com/v0.2/docs/how_to/qa_chat_history_how_to/#chains
https://python.langchain.com/v0.2/docs/how_to/few_shot_examples_chat/#create-prompt-template