Ver3.0 ๐ค LangChain์ผ๋ก ๋ฌธ์ ๊ธฐ๋ฐ FAQ ์ฑ๋ด ๊ตฌํํ๊ธฐ: ๋น์ ์ ์ง์์ ๋๋ํ AI๋ก ๋ณ์ ์ํค๋ ๋ง๋ฒ

๐ค LangChain์ผ๋ก ๋ฌธ์ ๊ธฐ๋ฐ FAQ ์ฑ๋ด ๊ตฌํํ๊ธฐ: ๋น์ ์ ์ง์์ ๋๋ํ AI๋ก ๋ณ์ ์ํค๋ ๋ง๋ฒ
์ค์ ์์ ๋ก ๋ฐฐ์ฐ๋ ๋ฌธ์ ๊ฒ์ ์ฑ๋ด ๊ฐ๋ฐ ์๋ฒฝ ๊ฐ์ด๋
๐ฌ "๊ณ ๊ฐ ๋ฌธ์๊ฐ ๋๋ฌด ๋ง์์ ๋ต๋ณํ๊ธฐ ํ๋ค์ด์!", "์ฐ๋ฆฌ ํ์ฌ ๋งค๋ด์ผ์ด ๋๋ฌด ๋ฐฉ๋ํด์ ์ง์๋ค์ด ์ฐพ๊ธฐ ์ด๋ ค์ํด์!"
์ด๋ฐ ๊ณ ๋ฏผ ํ ๋ฒ์ฏค ํด๋ณด์
จ์ฃ ? ์ค๋์ ์ด ๋ชจ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด์ค LangChain ๊ธฐ๋ฐ ๋ฌธ์ FAQ ์ฑ๋ด์ ํจ๊ป ๋ง๋ค์ด๋ณผ ๊ฑฐ์์. ๋ณต์กํด ๋ณด์ด์ง๋ง ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฐ๋ผ์ค์๋ฉด ์ฌ๋ฌ๋ถ๋ ์ถฉ๋ถํ ๋ง๋ค ์ ์๋ต๋๋ค! ๐
๐ฏ LangChain๊ณผ ๋ฌธ์ ๊ธฐ๋ฐ ์ฑ๋ด์ด ๋ญ๊ฐ์?
๋จผ์ ๊ธฐ๋ณธ ๊ฐ๋
๋ถํฐ ์น๊ตฌ์ฒ๋ผ ํธํ๊ฒ ์ค๋ช
ํด๋๋ฆด๊ฒ์!
LangChain์ ๋๊ท๋ชจ ์ธ์ด ๋ชจ๋ธ(LLM)์ ํ์ฉํ ์ ํ๋ฆฌ์ผ์ด์
์ ์ฝ๊ฒ ๋ง๋ค ์ ์๊ฒ ๋์์ฃผ๋ ํ๋ ์์ํฌ์์. ๋ง์น ๋ ๊ณ ๋ธ๋ก์ฒ๋ผ ํ์ํ ๊ธฐ๋ฅ๋ค์ ์กฐ๋ฆฝํด์ AI ์ ํ๋ฆฌ์ผ์ด์
์ ๋ง๋ค ์ ์์ฃ . ๐งฑ
๋ฌธ์ ๊ธฐ๋ฐ FAQ ์ฑ๋ด์ ์ฌ๋ฌ๋ถ์ด ๊ฐ์ง ๋ฌธ์๋ค(PDF, ์๋, ํ
์คํธ ํ์ผ ๋ฑ)์ ํ์ตํด์, ์ฌ์ฉ์์ ์ง๋ฌธ์ ํด๋น ๋ฌธ์ ๋ด์ฉ์ ๊ธฐ๋ฐ์ผ๋ก ๋ต๋ณํด์ฃผ๋ ๋๋ํ ๋น์ ๊ฐ์ ์กด์ฌ์์. ์ผ๋ฐ ์ฑ๋ด๊ณผ ๋ค๋ฅธ ์ ์ "ํ๊ฐ(Hallucination)" ํ่ฑก์ด ์ ๋ค๋ ๊ฑฐ์ฃ . ์ฆ, ์์ง๋ ์์ ๋ด์ฉ์ ์ง์ด๋ด์ง ์๊ณ ์ค์ ๋ฌธ์์ ์๋ ๋ด์ฉ๋ง ๋ต๋ณํ๋ค๋ ๋ป์ด์์! โจ
๐ก ์ ๋ฌธ์ ๊ธฐ๋ฐ ์ฑ๋ด์ด ํ์ํ ๊น์?
1. ๊ณ ๊ฐ ์ง์ ์๋ํ: ๋ฐ๋ณต์ ์ธ ์ง๋ฌธ์ 24์๊ฐ ์ฆ์ ๋ต๋ณ ๊ฐ๋ฅ
2. ์ฌ๋ด ์ง์ ๊ด๋ฆฌ: ๋ฐฉ๋ํ ๋งค๋ด์ผ์ด๋ ๊ท์ ์ ์ฝ๊ฒ ๊ฒ์
3. ๊ต์ก ๋ฐ ์จ๋ณด๋ฉ: ์ ์
์ง์์ด๋ ํ์๋ค์ ํ์ต ๋์ฐ๋ฏธ
4. ๋น์ฉ ์ ๊ฐ: ์ธ๋ ฅ ํฌ์
์์ด๋ ํจ์จ์ ์ธ ์ ๋ณด ์ ๊ณต
5. ์ ํ์ฑ ํฅ์: ๊ฒ์ฆ๋ ๋ฌธ์ ๊ธฐ๋ฐ์ผ๋ก ์ ๋ขฐํ ์ ์๋ ๋ต๋ณ ์ ๊ณต
๐ ๏ธ ๊ฐ๋ฐ ํ๊ฒฝ ์ค๋นํ๊ธฐ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์์ํด๋ณผ๊น์? ๋จผ์ ํ์ํ ๋๊ตฌ๋ค์ ์ค๋นํด์ผ ํด์. ์๋ฆฌ๋ฅผ ์์ํ๊ธฐ ์ ์ ์ฌ๋ฃ๋ฅผ ์ค๋นํ๋ ๊ฒ์ฒ๋ผ์! ๐จโ๐ณ
๐ฆ ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
Python ํ๊ฒฝ์์ ๋ค์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ์ค์นํด์ฃผ์ธ์. ํฐ๋ฏธ๋์ด๋ ๋ช ๋ น ํ๋กฌํํธ์์ ์คํํ๋ฉด ๋ฉ๋๋ค:
pip install langchain
pip install openai
pip install chromadb
pip install pypdf
pip install python-dotenv
pip install tiktoken
pip install langchain-community
pip install langchain-openai
๐ ์ด๋ณด์ ํ: ๊ฐ์ํ๊ฒฝ(venv)์ ๋ง๋ค์ด์ ์์
ํ๋ ๊ฑธ ์ถ์ฒํด์! ํ๋ก์ ํธ๋ณ๋ก ๋
๋ฆฝ์ ์ธ ํ๊ฒฝ์ ์ ์งํ ์ ์์ด์ ๋์ค์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฒ์ ์ถฉ๋ ๊ฐ์ ๋ฌธ์ ๋ฅผ ์๋ฐฉํ ์ ์๋ต๋๋ค.
python -m venv chatbot_env
source chatbot_env/bin/activate (Mac/Linux)
chatbot_env\Scripts\activate (Windows)
๐ API ํค ์ค๋นํ๊ธฐ
OpenAI์ GPT ๋ชจ๋ธ์ ์ฌ์ฉํ๋ ค๋ฉด API ํค๊ฐ ํ์ํด์. OpenAI ์น์ฌ์ดํธ(platform.openai.com)์์ ๊ณ์ ์ ๋ง๋ค๊ณ API ํค๋ฅผ ๋ฐ๊ธ๋ฐ์ผ์ธ์. ํ๋ก์ ํธ ๋ฃจํธ ํด๋์ .env ํ์ผ์ ๋ง๋ค์ด์ ๋ค์๊ณผ ๊ฐ์ด ์ ์ฅํ๋ฉด ๋ฉ๋๋ค:
OPENAI_API_KEY=your-api-key-here
โ ๏ธ ๋ณด์ ์ฃผ์์ฌํญ: API ํค๋ ์ ๋ GitHub ๊ฐ์ ๊ณต๊ฐ ์ ์ฅ์์ ์ฌ๋ฆฌ๋ฉด ์ ๋ผ์! .gitignore ํ์ผ์ .env๋ฅผ ์ถ๊ฐํด์ ์ค์๋ก ์
๋ก๋๋๋ ๊ฑธ ๋ฐฉ์งํ์ธ์. API ํค๊ฐ ๋
ธ์ถ๋๋ฉด ์๊ธ ํญํ์ ๋ง์ ์ ์์ด์! ๐ธ
๐ ๋ฌธ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ ๊ตฌ์ถํ๊ธฐ
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํด๋ณผ ๊ฑฐ์์. ๋ฌธ์ ๊ธฐ๋ฐ ์ฑ๋ด์ ํต์ฌ์ ๋ฌธ์๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๊ณ ์ ์ฅํ๋๋์์. ์ด ๊ณผ์ ์ ๋จ๊ณ๋ณ๋ก ์ดํด๋ณผ๊ฒ์! ๐
1๏ธโฃ ๋ฌธ์ ๋ก๋ ๊ตฌํํ๊ธฐ
์ฒซ ๋ฒ์งธ ๋จ๊ณ๋ ๋ฌธ์๋ฅผ ์ฝ์ด์ค๋ ๊ฑฐ์์. LangChain์ ๋ค์ํ ๋ฌธ์ ํ์์ ์ง์ํ๋ ๋ก๋๋ค์ ์ ๊ณตํด์. ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ PDF์ ํ ์คํธ ํ์ผ์ ์๋ก ๋ค์ด๋ณผ๊ฒ์:
from langchain_community.document_loaders import PyPDFLoader, TextLoader
from langchain_community.document_loaders import DirectoryLoader
import os
from dotenv import load_dotenv
# ํ๊ฒฝ ๋ณ์ ๋ก๋
load_dotenv()
# PDF ํ์ผ ๋ก๋
def load_pdf_documents(file_path):
"""PDF ํ์ผ์ ๋ก๋ํ๋ ํจ์"""
loader = PyPDFLoader(file_path)
documents = loader.load()
return documents
# ํ
์คํธ ํ์ผ ๋ก๋
def load_text_documents(file_path):
"""ํ
์คํธ ํ์ผ์ ๋ก๋ํ๋ ํจ์"""
loader = TextLoader(file_path, encoding='utf-8')
documents = loader.load()
return documents
# ๋๋ ํ ๋ฆฌ ๋ด ๋ชจ๋ PDF ํ์ผ ๋ก๋
def load_documents_from_directory(directory_path):
"""ํน์ ๋๋ ํ ๋ฆฌ์ ๋ชจ๋ PDF๋ฅผ ๋ก๋"""
loader = DirectoryLoader(
directory_path,
glob="**/*.pdf",
loader_cls=PyPDFLoader
)
documents = loader.load()
print(f"์ด {len(documents)}๊ฐ์ ๋ฌธ์ ํ์ด์ง๋ฅผ ๋ก๋ํ์ต๋๋ค.")
return documents
๐ก ์ค์ ํ: ๋ฌธ์๊ฐ ๋ง๋ค๋ฉด DirectoryLoader๋ฅผ ์ฌ์ฉํ๋ฉด ํธํด์. ํด๋ ์์ ๋ชจ๋ ํ์ผ์ ํ ๋ฒ์ ์ฒ๋ฆฌํ ์ ์๊ฑฐ๋ ์. ์ ๋ ๋ณดํต documents๋ผ๋ ํด๋๋ฅผ ๋ง๋ค์ด์ ๊ฑฐ๊ธฐ์ ๋ชจ๋ FAQ ๋ฌธ์๋ฅผ ๋ฃ์ด๋๊ณ ํ ๋ฒ์ ๋ก๋ํด์! ๐
2๏ธโฃ ํ ์คํธ ๋ถํ ํ๊ธฐ (Chunking)
๋ฌธ์๋ฅผ ๋ก๋ํ๋ค๋ฉด ์ด์ ์ ์ ํ ํฌ๊ธฐ๋ก ๋๋ ์ผ ํด์. ์๋๊ณ ์? LLM์๋ ์
๋ ฅ ํ ํฐ ์ ํ์ด ์๊ณ , ๋๋ฌด ๊ธด ํ
์คํธ๋ ๊ฒ์ ์ ํ๋๊ฐ ๋จ์ด์ง๊ธฐ ๋๋ฌธ์ด์์.
์ ์ ํ ์ฒญํฌ ํฌ๊ธฐ๋ ๋ณดํต 500~1000์ ์ ๋๊ฐ ์ข์์. ๋๋ฌด ์์ผ๋ฉด ๋งฅ๋ฝ์ด ๋ถ์กฑํ๊ณ , ๋๋ฌด ํฌ๋ฉด ๊ด๋ จ ์๋ ์ ๋ณด๊น์ง ํฌํจ๋ ์ ์๊ฑฐ๋ ์. ๐ฏ
from langchain.text_splitter import RecursiveCharacterTextSplitter
def split_documents(documents, chunk_size=1000, chunk_overlap=200):
"""
๋ฌธ์๋ฅผ ์ ์ ํ ํฌ๊ธฐ์ ์ฒญํฌ๋ก ๋ถํ
Args:
documents: ๋ก๋๋ ๋ฌธ์ ๋ฆฌ์คํธ
chunk_size: ๊ฐ ์ฒญํฌ์ ์ต๋ ํฌ๊ธฐ (๊ธฐ๋ณธ 1000์)
chunk_overlap: ์ฒญํฌ ๊ฐ ๊ฒน์น๋ ๋ถ๋ถ (๊ธฐ๋ณธ 200์)
Returns:
๋ถํ ๋ ๋ฌธ์ ์ฒญํฌ ๋ฆฌ์คํธ
"""
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len,
separators=["\n\n", "\n", " ", ""]
)
chunks = text_splitter.split_documents(documents)
print(f"๋ฌธ์๋ฅผ {len(chunks)}๊ฐ์ ์ฒญํฌ๋ก ๋ถํ ํ์ต๋๋ค.")
return chunks
๐ค chunk_overlap์ด ๋ญ๊ฐ์?
chunk_overlap์ ์ฒญํฌ๋ค์ด ์๋ก ๊ฒน์น๋ ๋ถ๋ถ์ด์์. ์๋ฅผ ๋ค์ด "์ค๋ ๋ ์จ๊ฐ ์ ๋ง ์ข๋ค์. ์ฐ์ฑ
ํ๊ธฐ ๋ฑ ์ข์ ๋ ์ด์์."๋ผ๋ ๋ฌธ์ฅ์ด ์์ ๋, ์ฒซ ๋ฒ์งธ ์ฒญํฌ๊ฐ "์ค๋ ๋ ์จ๊ฐ ์ ๋ง ์ข๋ค์."๋ก ๋๋๊ณ ๋ ๋ฒ์งธ ์ฒญํฌ๊ฐ "์ฐ์ฑ
ํ๊ธฐ ๋ฑ ์ข์ ๋ ์ด์์."๋ก ์์ํ๋ฉด ๋ฌธ๋งฅ์ด ๋๊ธฐ์์์?
๊ทธ๋์ overlap์ ์ฃผ๋ฉด "์ ๋ง ์ข๋ค์. ์ฐ์ฑ
ํ๊ธฐ"์ฒ๋ผ ์ผ๋ถ๊ฐ ๊ฒน์น๊ฒ ๋์ด ๋ฌธ๋งฅ์ด ์์ฐ์ค๋ฝ๊ฒ ์ด์ด์ ธ์. ๋ณดํต ์ฒญํฌ ํฌ๊ธฐ์ 10~20% ์ ๋๋ก ์ค์ ํ๋ฉด ์ข๋ต๋๋ค! ๐
3๏ธโฃ ์๋ฒ ๋ฉ ์์ฑ ๋ฐ ๋ฒกํฐ ์ ์ฅ์ ๊ตฌ์ถ
์ด์ ๊ฐ์ฅ ์ค์ํ ๋จ๊ณ์์! ํ
์คํธ๋ฅผ ์ปดํจํฐ๊ฐ ์ดํดํ ์ ์๋ ์ซ์ ๋ฒกํฐ๋ก ๋ณํํ๋ ๊ณผ์ ์ด์ฃ . ์ด๊ฑธ ์๋ฒ ๋ฉ(Embedding)์ด๋ผ๊ณ ํด์.
๋น์ ํ์๋ฉด, ์ฑ
์ ๋ด์ฉ์ DNA์ฒ๋ผ ์ซ์ ์ฝ๋๋ก ๋ฐ๊พธ๋ ๊ฑฐ์์. ๊ทธ๋ฌ๋ฉด ๋์ค์ ์ง๋ฌธ์ด ๋ค์ด์์ ๋ "์ด ์ง๋ฌธ์ DNA์ ๊ฐ์ฅ ๋น์ทํ ๋ฌธ์ DNA๋ฅผ ์ฐพ์์ค!"๋ผ๊ณ ํ ์ ์๋ ๊ฑฐ์ฃ . ๐งฌ
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
def create_vector_store(chunks, persist_directory="./chroma_db"):
"""
์ฒญํฌ๋ค์ ์๋ฒ ๋ฉํ๊ณ ๋ฒกํฐ ์ ์ฅ์์ ์ ์ฅ
Args:
chunks: ๋ถํ ๋ ๋ฌธ์ ์ฒญํฌ
persist_directory: ๋ฒกํฐ DB๋ฅผ ์ ์ฅํ ๋๋ ํ ๋ฆฌ
Returns:
Chroma ๋ฒกํฐ ์ ์ฅ์ ๊ฐ์ฒด
"""
# OpenAI ์๋ฒ ๋ฉ ๋ชจ๋ธ ์ด๊ธฐํ
embeddings = OpenAIEmbeddings(
model="text-embedding-ada-002"
)
# Chroma ๋ฒกํฐ ์ ์ฅ์ ์์ฑ
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory=persist_directory
)
print(f"๋ฒกํฐ ์ ์ฅ์๊ฐ {persist_directory}์ ์์ฑ๋์์ต๋๋ค.")
return vectorstore
# ๊ธฐ์กด ๋ฒกํฐ ์ ์ฅ์ ๋ก๋ํ๊ธฐ
def load_vector_store(persist_directory="./chroma_db"):
"""์ ์ฅ๋ ๋ฒกํฐ ์ ์ฅ์๋ฅผ ๋ก๋"""
embeddings = OpenAIEmbeddings(
model="text-embedding-ada-002"
)
vectorstore = Chroma(
persist_directory=persist_directory,
embedding_function=embeddings
)
return vectorstore
๐พ ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ํํ๊ธฐ
์ฌ๊ธฐ์๋ Chroma๋ฅผ ์ฌ์ฉํ์ง๋ง, ๋ค๋ฅธ ์ต์
๋ค๋ ๋ง์์:
โข Chroma: ๊ฐ๋ณ๊ณ ์ค์น๊ฐ ์ฌ์์ ํ๋กํ ํ์
์ด๋ ์๊ท๋ชจ ํ๋ก์ ํธ์ ์ ํฉ
โข Pinecone: ํด๋ผ์ฐ๋ ๊ธฐ๋ฐ, ๋๊ท๋ชจ ํ๋ก๋์
ํ๊ฒฝ์ ์ต์ ํ
โข FAISS: Facebook์ด ๋ง๋ ์คํ์์ค, ๋น ๋ฅธ ๊ฒ์ ์๋
โข Weaviate: ์คํ์์ค, ํ๋ถํ ๊ธฐ๋ฅ๊ณผ ํ์ฅ์ฑ
์ด๋ณด์๋ผ๋ฉด Chroma๋ก ์์ํ๋ ๊ฑธ ์ถ์ฒํด์. ๋ณ๋ ์๋ฒ ์ค์น ์์ด ๋ก์ปฌ์์ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๊ฑฐ๋ ์! ๐
๐ค ์ฑ๋ด ๊ตฌํํ๊ธฐ
๋๋์ด ์ฑ๋ด์ ๋ง๋ค ์ฐจ๋ก์์! ์ด์ ๊น์ง ์ค๋นํ ์ฌ๋ฃ๋ค๋ก ๋ง์๋ ์๋ฆฌ๋ฅผ ์์ฑํ๋ ๋จ๊ณ์ฃ . ๐
LangChain์ RetrievalQA ์ฒด์ธ์ ์ฌ์ฉํ๋ฉด ๊ฒ์๊ณผ ๋ต๋ณ ์์ฑ์ ํ ๋ฒ์ ์ฒ๋ฆฌํ ์ ์์ด์.
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
def create_qa_chain(vectorstore):
"""
์ง์์๋ต ์ฒด์ธ ์์ฑ
Args:
vectorstore: ๋ฒกํฐ ์ ์ฅ์ ๊ฐ์ฒด
Returns:
RetrievalQA ์ฒด์ธ
"""
# GPT ๋ชจ๋ธ ์ด๊ธฐํ
llm = ChatOpenAI(
model_name="gpt-3.5-turbo",
temperature=0.3 # ๋ฎ์์๋ก ์ผ๊ด๋ ๋ต๋ณ, ๋์์๋ก ์ฐฝ์์
)
# ํ๋กฌํํธ ํ
ํ๋ฆฟ ์ ์
template = """๋น์ ์ ์น์ ํ FAQ ์ฑ๋ด์
๋๋ค.
์ฃผ์ด์ง ๋ฌธ์ ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์ฌ์ฉ์์ ์ง๋ฌธ์ ์ ํํ๊ฒ ๋ต๋ณํด์ฃผ์ธ์.
๋ฌธ์์ ์๋ ๋ด์ฉ์ "์ฃ์กํ์ง๋ง ํด๋น ์ ๋ณด๋ ์ ๊ณต๋ ๋ฌธ์์ ์์ต๋๋ค"๋ผ๊ณ ๋ต๋ณํ์ธ์.
๋ฌธ์ ๋ด์ฉ:
{context}
์ง๋ฌธ: {question}
๋ต๋ณ:"""
PROMPT = PromptTemplate(
template=template,
input_variables=["context", "question"]
)
# RetrievalQA ์ฒด์ธ ์์ฑ
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 3} # ์์ 3๊ฐ ๋ฌธ์ ๊ฒ์
),
return_source_documents=True,
chain_type_kwargs={"prompt": PROMPT}
)
return qa_chain
def ask_question(qa_chain, question):
"""
์ฑ๋ด์๊ฒ ์ง๋ฌธํ๊ธฐ
Args:
qa_chain: QA ์ฒด์ธ ๊ฐ์ฒด
question: ์ฌ์ฉ์ ์ง๋ฌธ
Returns:
๋ต๋ณ ๋ฐ ์ถ์ฒ ๋ฌธ์
"""
result = qa_chain({"query": question})
answer = result['result']
source_docs = result['source_documents']
print(f"\n์ง๋ฌธ: {question}")
print(f"\n๋ต๋ณ: {answer}")
print(f"\n์ฐธ๊ณ ํ ๋ฌธ์ ์: {len(source_docs)}")
return answer, source_docs
๐จ Temperature ํ๋ผ๋ฏธํฐ ์กฐ์ ํ๊ธฐ:
temperature๋ AI์ ์ฐฝ์์ฑ์ ์กฐ์ ํ๋ ๊ฐ์ด์์.
โข 0.0~0.3: ๋งค์ฐ ์ผ๊ด๋๊ณ ์ ํํ ๋ต๋ณ (FAQ ์ฑ๋ด์ ์ ํฉ) โ
โข 0.4~0.7: ๊ท ํ์กํ ๋ต๋ณ
โข 0.8~1.0: ์ฐฝ์์ ์ด๊ณ ๋ค์ํ ๋ต๋ณ (์คํ ๋ฆฌ ์์ฑ์ ์ ํฉ)
FAQ ์ฑ๋ด์ ์ ํ์ฑ์ด ์ค์ํ๋๊น ๋ฎ์ ๊ฐ์ ์ฌ์ฉํ๋ ๊ฒ ์ข์์!
๐ ๊ฒ์ ๋ฐฉ์ ์ดํดํ๊ธฐ
์ ์ฝ๋์์ search_type="similarity"์ search_kwargs={"k": 3}๋ฅผ ์ค์ ํ๋๋ฐ, ์ด๊ฒ ๋ญ ์๋ฏธํ๋์ง ์์๋ณผ๊น์?
| ๊ฒ์ ๋ฐฉ์ | ์ค๋ช | ์ฅ์ | ๋จ์ |
|---|---|---|---|
| similarity | ์ฝ์ฌ์ธ ์ ์ฌ๋ ๊ธฐ๋ฐ ๊ฒ์ | ๊ฐ์ฅ ๊ด๋ จ์ฑ ๋์ ๋ฌธ์ ์ฐพ๊ธฐ | ๋ค์์ฑ ๋ถ์กฑ ๊ฐ๋ฅ |
| mmr | ์ต๋ ํ๊ณ ๊ด๋ จ์ฑ ๊ฒ์ | ๊ด๋ จ์ฑ๊ณผ ๋ค์์ฑ ๊ท ํ | ๊ณ์ฐ ๋น์ฉ ๋์ |
| similarity_score_threshold | ์๊ณ๊ฐ ๊ธฐ๋ฐ ํํฐ๋ง | ๋ฎ์ ๊ด๋ จ์ฑ ๋ฌธ์ ์ ์ธ | ์๊ณ๊ฐ ์ค์ ์ด๋ ค์ |
k=3์ ์์ 3๊ฐ ๋ฌธ์๋ฅผ ๊ฐ์ ธ์จ๋ค๋ ๋ป์ด์์. ์ด ๊ฐ์ ๋๋ฆฌ๋ฉด ๋ ๋ง์ ๋งฅ๋ฝ์ ์ ๊ณตํ ์ ์์ง๋ง, ๊ด๋ จ ์๋ ์ ๋ณด๊ฐ ์์ผ ์๋ ์์ด์. ๋ณดํต 3~5๊ฐ๊ฐ ์ ๋นํ๋ต๋๋ค! ๐ฏ
๐ฌ ์ ์ฒด ์์คํ ํตํฉํ๊ธฐ
์ด์ ์ง๊ธ๊น์ง ๋ง๋ ๋ชจ๋ ํจ์๋ค์ ํ๋๋ก ํฉ์ณ์ ์์ ํ ์ฑ๋ด ์์คํ ์ ๋ง๋ค์ด๋ณผ๊ฒ์! ๋ง์น ํผ์ฆ ์กฐ๊ฐ๋ค์ ๋ง์ถ๋ ๊ฒ์ฒ๋ผ์. ๐งฉ
import os
from dotenv import load_dotenv
class DocumentFAQChatbot:
"""๋ฌธ์ ๊ธฐ๋ฐ FAQ ์ฑ๋ด ํด๋์ค"""
def __init__(self, documents_path, persist_directory="./chroma_db"):
"""
์ฑ๋ด ์ด๊ธฐํ
Args:
documents_path: ๋ฌธ์๊ฐ ์๋ ๋๋ ํ ๋ฆฌ ๊ฒฝ๋ก
persist_directory: ๋ฒกํฐ DB ์ ์ฅ ๊ฒฝ๋ก
"""
load_dotenv()
self.documents_path = documents_path
self.persist_directory = persist_directory
self.vectorstore = None
self.qa_chain = None
def setup(self, force_reload=False):
"""์ฑ๋ด ์ค์ ๋ฐ ์ด๊ธฐํ"""
# ๊ธฐ์กด ๋ฒกํฐ ์ ์ฅ์๊ฐ ์๊ณ ์ฌ๋ก๋๊ฐ ํ์์์ผ๋ฉด ๋ก๋
if os.path.exists(self.persist_directory) and not force_reload:
print("๊ธฐ์กด ๋ฒกํฐ ์ ์ฅ์๋ฅผ ๋ก๋ํฉ๋๋ค...")
self.vectorstore = load_vector_store(self.persist_directory)
else:
print("์๋ก์ด ๋ฒกํฐ ์ ์ฅ์๋ฅผ ์์ฑํฉ๋๋ค...")
# 1. ๋ฌธ์ ๋ก๋
documents = load_documents_from_directory(self.documents_path)
# 2. ๋ฌธ์ ๋ถํ
chunks = split_documents(documents)
# 3. ๋ฒกํฐ ์ ์ฅ์ ์์ฑ
self.vectorstore = create_vector_store(
chunks,
self.persist_directory
)
# 4. QA ์ฒด์ธ ์์ฑ
self.qa_chain = create_qa_chain(self.vectorstore)
print("์ฑ๋ด์ด ์ค๋น๋์์ต๋๋ค! ๐")
def ask(self, question):
"""์ง๋ฌธํ๊ธฐ"""
if self.qa_chain is None:
raise Exception("๋จผ์ setup() ๋ฉ์๋๋ฅผ ํธ์ถํด์ฃผ์ธ์!")
return ask_question(self.qa_chain, question)
def chat_loop(self):
"""๋ํํ ์ธํฐํ์ด์ค"""
print("\n" + "="*50)
print("๐ ๋ฌธ์ ๊ธฐ๋ฐ FAQ ์ฑ๋ด์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค!")
print("์ข
๋ฃํ๋ ค๋ฉด 'quit' ๋๋ 'exit'๋ฅผ ์
๋ ฅํ์ธ์.")
print("="*50 + "\n")
while True:
question = input("\n๐ฌ ์ง๋ฌธ์ ์
๋ ฅํ์ธ์: ").strip()
if question.lower() in ['quit', 'exit', '์ข
๋ฃ']:
print("\n๐ ์ฑ๋ด์ ์ข
๋ฃํฉ๋๋ค. ์ข์ ํ๋ฃจ ๋์ธ์!")
break
if not question:
print("โ ๏ธ ์ง๋ฌธ์ ์
๋ ฅํด์ฃผ์ธ์.")
continue
try:
answer, sources = self.ask(question)
# ์ถ์ฒ ๋ฌธ์ ์ ๋ณด ํ์
print("\n๐ ์ฐธ๊ณ ๋ฌธ์:")
for i, doc in enumerate(sources, 1):
source = doc.metadata.get('source', '์ ์ ์์')
print(f" {i}. {source}")
except Exception as e:
print(f"\nโ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}")
# ์ฌ์ฉ ์์
if __name__ == "__main__":
# ์ฑ๋ด ์ธ์คํด์ค ์์ฑ
chatbot = DocumentFAQChatbot(
documents_path="./documents", # ๋ฌธ์ ํด๋ ๊ฒฝ๋ก
persist_directory="./chroma_db"
)
# ์ฑ๋ด ์ค์
chatbot.setup(force_reload=False)
# ๋ํ ์์
chatbot.chat_loop()
๐ฎ ์คํ ๋ฐฉ๋ฒ
1. ํ๋ก์ ํธ ํด๋์ documents ๋๋ ํ ๋ฆฌ๋ฅผ ๋ง๋ค๊ณ FAQ ๋ฌธ์๋ค์ ๋ฃ์ผ์ธ์.
2. ์ ์ฝ๋๋ฅผ chatbot.py๋ก ์ ์ฅํ์ธ์.
3. ํฐ๋ฏธ๋์์ python chatbot.py๋ฅผ ์คํํ์ธ์.
4. ์ง๋ฌธ์ ์
๋ ฅํ๊ณ ๋ต๋ณ์ ๋ฐ์ผ์ธ์! ๐
์ฒ์ ์คํํ ๋๋ ๋ฌธ์๋ฅผ ์ฒ๋ฆฌํ๋๋ผ ์๊ฐ์ด ์ข ๊ฑธ๋ฆด ์ ์์ด์. ํ์ง๋ง ๋ ๋ฒ์งธ๋ถํฐ๋ ์ ์ฅ๋ ๋ฒกํฐ DB๋ฅผ ์ฌ์ฉํ๋๊น ๋น ๋ฅด๊ฒ ์์๋๋ต๋๋ค!
๐ ์ฑ๋ฅ ์ต์ ํ ๋ฐ ๊ณ ๊ธ ๊ธฐ๋ฅ
๊ธฐ๋ณธ ์ฑ๋ด์ ์์ฑํ์ง๋ง, ์ค์ ์๋น์ค์ ์ฌ์ฉํ๋ ค๋ฉด ๋ช ๊ฐ์ง ๊ฐ์ ์ด ํ์ํด์. ํ๋ก์ฒ๋ผ ์ฑ๋ด์ ์ ๊ทธ๋ ์ด๋ํด๋ณผ๊น์? ๐ช
โก ์๋ต ์๋ ๊ฐ์ ํ๊ธฐ
์ฌ์ฉ์๋ ๋น ๋ฅธ ๋ต๋ณ์ ์ํด์. ๋๋ฆฐ ์ฑ๋ด์ ์๋ฌด๋ ์ฌ์ฉํ์ง ์์ฃ ! ์๋๋ฅผ ๊ฐ์ ํ๋ ๋ช ๊ฐ์ง ๋ฐฉ๋ฒ์ ์๋ ค๋๋ฆด๊ฒ์.
1. ์บ์ฑ ํ์ฉํ๊ธฐ
๊ฐ์ ์ง๋ฌธ์ด ๋ฐ๋ณต๋๋ฉด ๋งค๋ฒ ๊ณ์ฐํ์ง ๋ง๊ณ ์ด์ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฌ์ฉํ์ธ์:
from functools import lru_cache
import hashlib
class CachedChatbot(DocumentFAQChatbot):
"""์บ์ฑ ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋ ์ฑ๋ด"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.cache = {}
def _get_cache_key(self, question):
"""์ง๋ฌธ์ ํด์๊ฐ์ผ๋ก ๋ณํ"""
return hashlib.md5(question.encode()).hexdigest()
def ask(self, question):
"""์บ์๋ฅผ ํ์ธํ๊ณ ์์ผ๋ฉด ์๋ก ์ง๋ฌธ"""
cache_key = self._get_cache_key(question)
if cache_key in self.cache:
print("๐พ ์บ์๋ ๋ต๋ณ์ ์ฌ์ฉํฉ๋๋ค.")
return self.cache[cache_key]
result = super().ask(question)
self.cache[cache_key] = result
return result
2. ์คํธ๋ฆฌ๋ฐ ์๋ต ๊ตฌํํ๊ธฐ
ChatGPT์ฒ๋ผ ๋ต๋ณ์ด ์ค์๊ฐ์ผ๋ก ์์ฑ๋๋ ๊ฑธ ๋ณด์ฌ์ฃผ๋ฉด ์ฌ์ฉ์ ๊ฒฝํ์ด ํจ์ฌ ์ข์์ ธ์:
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
def create_streaming_qa_chain(vectorstore):
"""์คํธ๋ฆฌ๋ฐ ๊ธฐ๋ฅ์ด ์๋ QA ์ฒด์ธ"""
llm = ChatOpenAI(
model_name="gpt-3.5-turbo",
temperature=0.3,
streaming=True,
callbacks=[StreamingStdOutCallbackHandler()]
)
# ๋๋จธ์ง๋ ๋์ผ...
๐ฏ ๋ต๋ณ ํ์ง ํฅ์์ํค๊ธฐ
๋ ์ ํํ๊ณ ์ ์ฉํ ๋ต๋ณ์ ์ ๊ณตํ๋ ค๋ฉด ํ๋กฌํํธ ์์ง๋์ด๋ง์ด ์ค์ํด์. ๋ช ๊ฐ์ง ํ์ ๊ณต์ ํ ๊ฒ์! โจ
๐ ํ๋กฌํํธ ๊ฐ์ ์์
๊ธฐ๋ณธ ํ๋กฌํํธ๋ฅผ ๋ ๊ตฌ์ฒด์ ์ผ๋ก ๋ง๋ค์ด๋ณด์ธ์:
advanced_template = """๋น์ ์ ์ ๋ฌธ์ ์ด๊ณ ์น์ ํ ๊ณ ๊ฐ ์ง์ AI์
๋๋ค.
์ญํ ๊ณผ ๋ชฉํ:
- ์ ๊ณต๋ ๋ฌธ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ํํ๊ณ ๋์์ด ๋๋ ๋ต๋ณ ์ ๊ณต
- ์ฌ์ฉ์๊ฐ ์ดํดํ๊ธฐ ์ฝ๊ฒ ์ค๋ช
- ํ์์ ๋จ๊ณ๋ณ๋ก ์๋ด
๋ต๋ณ ๊ท์น:
1. ๋ฌธ์์ ๋ช
ํํ ๋ต์ด ์์ผ๋ฉด ๊ทธ๋๋ก ์ธ์ฉํ๊ณ ์ถ์ฒ ํ์
2. ๋ฌธ์์ ๋ถ๋ถ์ ์ ๋ณด๋ง ์์ผ๋ฉด "๋ฌธ์์ ๋ฐ๋ฅด๋ฉด..."์ผ๋ก ์์
3. ๋ฌธ์์ ์๋ ๋ด์ฉ์ ์์งํ๊ฒ "์ ๊ณต๋ ๋ฌธ์์๋ ํด๋น ์ ๋ณด๊ฐ ์์ต๋๋ค"๋ผ๊ณ ๋ต๋ณ
4. ๊ด๋ จ ์ ๋ณด๊ฐ ์๋ค๋ฉด ์ถ๊ฐ๋ก ์ ๊ณต
5. ๋ต๋ณ์ 3-5๋ฌธ์ฅ์ผ๋ก ๊ฐ๊ฒฐํ๊ฒ
๋ฌธ์ ๋ด์ฉ:
{context}
์ฌ์ฉ์ ์ง๋ฌธ: {question}
๋ต๋ณ (์น์ ํ๊ณ ์ ๋ฌธ์ ์ผ๋ก):"""
๐ ๋ํ ํ์คํ ๋ฆฌ ๊ด๋ฆฌํ๊ธฐ
์ด์ ๋ํ ๋ด์ฉ์ ๊ธฐ์ตํ๋ฉด ๋ ์์ฐ์ค๋ฌ์ด ๋ํ๊ฐ ๊ฐ๋ฅํด์. ์๋ฅผ ๋ค์ด "๊ทธ๊ฑฐ ๋ง๊ณ ๋ค๋ฅธ ๋ฐฉ๋ฒ์?"์ด๋ผ๊ณ ๋ฌผ์ด๋ดค์ ๋ "๊ทธ๊ฑฐ"๊ฐ ๋ญ์ง ์ ์ ์์ฃ ! ๐ง
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
def create_conversational_chain(vectorstore):
"""๋ํ ๊ธฐ๋ก์ ์ ์งํ๋ ์ฒด์ธ"""
llm = ChatOpenAI(
model_name="gpt-3.5-turbo",
temperature=0.3
)
# ๋ํ ๋ฉ๋ชจ๋ฆฌ ์์ฑ
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True,
output_key="answer"
)
# ๋ํํ ๊ฒ์ ์ฒด์ธ
qa_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
memory=memory,
return_source_documents=True
)
return qa_chain
๐ ์น ์ธํฐํ์ด์ค ์ถ๊ฐํ๊ธฐ
ํฐ๋ฏธ๋์์๋ง ์ฌ์ฉํ๋ ๊ฑด ์ข ๋ถํธํ์ฃ ? Streamlit์ ์ฌ์ฉํ๋ฉด ๋ฉ์ง ์น UI๋ฅผ ์ฝ๊ฒ ๋ง๋ค ์ ์์ด์! ๐จ
# streamlit_app.py
import streamlit as st
from chatbot import DocumentFAQChatbot
# ํ์ด์ง ์ค์
st.set_page_config(
page_title="FAQ ์ฑ๋ด",
page_icon="๐ค",
layout="wide"
)
# ์ ๋ชฉ
st.title("๐ค ๋ฌธ์ ๊ธฐ๋ฐ FAQ ์ฑ๋ด")
st.markdown("---")
# ์ฌ์ด๋๋ฐ
with st.sidebar:
st.header("โ๏ธ ์ค์ ")
documents_path = st.text_input(
"๋ฌธ์ ํด๋ ๊ฒฝ๋ก",
value="./documents"
)
if st.button("์ฑ๋ด ์ด๊ธฐํ"):
with st.spinner("์ฑ๋ด์ ์ค๋นํ๋ ์ค..."):
st.session_state.chatbot = DocumentFAQChatbot(documents_path)
st.session_state.chatbot.setup()
st.success("์ค๋น ์๋ฃ! โ
")
# ์ธ์
์ํ ์ด๊ธฐํ
if 'messages' not in st.session_state:
st.session_state.messages = []
if 'chatbot' not in st.session_state:
st.info("๐ ์ฌ์ด๋๋ฐ์์ ์ฑ๋ด์ ์ด๊ธฐํํด์ฃผ์ธ์!")
st.stop()
# ๋ํ ๊ธฐ๋ก ํ์
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# ์ฌ์ฉ์ ์
๋ ฅ
if prompt := st.chat_input("์ง๋ฌธ์ ์
๋ ฅํ์ธ์..."):
# ์ฌ์ฉ์ ๋ฉ์์ง ์ถ๊ฐ
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
# ์ฑ๋ด ์๋ต
with st.chat_message("assistant"):
with st.spinner("์๊ฐํ๋ ์ค..."):
answer, sources = st.session_state.chatbot.ask(prompt)
st.markdown(answer)
# ์ถ์ฒ ํ์
with st.expander("๐ ์ฐธ๊ณ ๋ฌธ์"):
for i, doc in enumerate(sources, 1):
st.text(f"{i}. {doc.metadata.get('source', '์ ์ ์์')}")
st.session_state.messages.append({"role": "assistant", "content": answer})
๐จ Streamlit ์คํํ๊ธฐ:
pip install streamlit๋ก ์ค์นํ ํ,
streamlit run streamlit_app.py๋ก ์คํํ๋ฉด ๋ธ๋ผ์ฐ์ ์์ ๋ฉ์ง ์ฑ๋ด UI๋ฅผ ๋ณผ ์ ์์ด์! ๐
๐ก๏ธ ๋ณด์ ๋ฐ ์๋ฌ ์ฒ๋ฆฌ
์ค์ ์๋น์ค์์๋ ๋ณด์๊ณผ ์์ ์ฑ์ด ๋งค์ฐ ์ค์ํด์. ๋ช ๊ฐ์ง ํ์ ์ฌํญ๋ค์ ์ฒดํฌํด๋ณผ๊ฒ์! ๐
โ ๏ธ ์ฃผ์ํด์ผ ํ ๋ณด์ ์ฌํญ
1. API ํค ๋ณดํธ: ์ ๋ ์ฝ๋์ ํ๋์ฝ๋ฉํ์ง ๋ง์ธ์. ํ๊ฒฝ ๋ณ์ ์ฌ์ฉ ํ์!
2. ์
๋ ฅ ๊ฒ์ฆ: ์
์์ ์ธ ์
๋ ฅ์ ํํฐ๋งํ์ธ์.
3. ๋น์ฉ ์ ํ: API ํธ์ถ ํ์์ ํ ํฐ ์ฌ์ฉ๋์ ๋ชจ๋ํฐ๋งํ์ธ์.
4. ๋ฏผ๊ฐ ์ ๋ณด: ๋ฌธ์์ ๊ฐ์ธ์ ๋ณด๊ฐ ํฌํจ๋์ง ์๋๋ก ์ฃผ์ํ์ธ์.
5. ์ ๊ทผ ์ ์ด: ์ธ์ฆ๋ ์ฌ์ฉ์๋ง ์ฑ๋ด์ ์ฌ์ฉํ ์ ์๊ฒ ํ์ธ์.
import re
from typing import Optional
class SecureChatbot(DocumentFAQChatbot):
"""๋ณด์ ๊ธฐ๋ฅ์ด ๊ฐํ๋ ์ฑ๋ด"""
def __init__(self, *args, max_query_length=500, **kwargs):
super().__init__(*args, **kwargs)
self.max_query_length = max_query_length
self.query_count = 0
self.max_queries_per_session = 100
def validate_input(self, question: str) -> Optional[str]:
"""์
๋ ฅ ๊ฒ์ฆ"""
# ๊ธธ์ด ์ฒดํฌ
if len(question) > self.max_query_length:
return f"์ง๋ฌธ์ด ๋๋ฌด ๊น๋๋ค. {self.max_query_length}์ ์ด๋ด๋ก ์
๋ ฅํด์ฃผ์ธ์."
# ๋น ์
๋ ฅ ์ฒดํฌ
if not question.strip():
return "์ง๋ฌธ์ ์
๋ ฅํด์ฃผ์ธ์."
# ์
์์ ํจํด ์ฒดํฌ (์์)
dangerous_patterns = [
r'<script',
r'javascript:',
r'onerror=',
]
for pattern in dangerous_patterns:
if re.search(pattern, question, re.IGNORECASE):
return "์ ํจํ์ง ์์ ์
๋ ฅ์
๋๋ค."
return None
def ask(self, question: str):
"""๊ฒ์ฆ๋ ์ง๋ฌธ๋ง ์ฒ๋ฆฌ"""
# ์ฟผ๋ฆฌ ํ์ ์ฒดํฌ
if self.query_count >= self.max_queries_per_session:
raise Exception("์ธ์
๋น ์ต๋ ์ง๋ฌธ ํ์๋ฅผ ์ด๊ณผํ์ต๋๋ค.")
# ์
๋ ฅ ๊ฒ์ฆ
error = self.validate_input(question)
if error:
raise ValueError(error)
self.query_count += 1
try:
return super().ask(question)
except Exception as e:
print(f"์ค๋ฅ ๋ฐ์: {str(e)}")
return "์ฃ์กํฉ๋๋ค. ์ผ์์ ์ธ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.", []
๐ ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง ๋ฐ ๊ฐ์
์ฑ๋ด์ ๋ฐฐํฌํ ํ์๋ ์ฑ๋ฅ์ ์ง์์ ์ผ๋ก ๋ชจ๋ํฐ๋งํ๊ณ ๊ฐ์ ํด์ผ ํด์. ์ด๋ค ์งํ๋ค์ ๋ด์ผ ํ ๊น์? ๐
๐ฏ ์ฃผ์ ์ฑ๋ฅ ์งํ (KPI)
1. ์๋ต ์๊ฐ: ํ๊ท ๋ช ์ด ๋ง์ ๋ต๋ณํ๋๊ฐ?
2. ๋ต๋ณ ์ ํ๋: ์ฌ์ฉ์๊ฐ ๋ง์กฑํ๋ ๋ต๋ณ ๋น์จ
3. ๋ฌธ์ ๊ฒ์ ์ ํ๋: ๊ด๋ จ ๋ฌธ์๋ฅผ ์ ์ฐพ๋๊ฐ?
4. ์ฌ์ฉ์ ๋ง์กฑ๋: ํผ๋๋ฐฑ ์ ์
5. ๋น์ฉ ํจ์จ์ฑ: API ํธ์ถ๋น ๋น์ฉ
6. ์ค๋ฅ์จ: ์คํจํ ์์ฒญ ๋น์จ
import time
import logging
from datetime import datetime
class MonitoredChatbot(DocumentFAQChatbot):
"""๋ชจ๋ํฐ๋ง ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋ ์ฑ๋ด"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setup_logging()
self.metrics = {
'total_queries': 0,
'successful_queries': 0,
'failed_queries': 0,
'total_response_time': 0,
'average_response_time': 0
}
def setup_logging(self):
"""๋ก๊น
์ค์ """
logging.basicConfig(
filename='chatbot.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
self.logger = logging.getLogger(__name__)
def ask(self, question: str):
"""๋ชจ๋ํฐ๋ง๊ณผ ํจ๊ป ์ง๋ฌธ ์ฒ๋ฆฌ"""
start_time = time.time()
self.metrics['total_queries'] += 1
try:
self.logger.info(f"์ง๋ฌธ ์์ : {question}")
result = super().ask(question)
response_time = time.time() - start_time
self.metrics['successful_queries'] += 1
self.metrics['total_response_time'] += response_time
self.metrics['average_response_time'] = (
self.metrics['total_response_time'] /
self.metrics['successful_queries']
)
self.logger.info(
f"๋ต๋ณ ์๋ฃ (์๋ต์๊ฐ: {response_time:.2f}์ด)"
)
return result
except Exception as e:
self.metrics['failed_queries'] += 1
self.logger.error(f"์ค๋ฅ ๋ฐ์: {str(e)}")
raise
def get_metrics(self):
"""์ฑ๋ฅ ์งํ ์กฐํ"""
return self.metrics
๐ ์ค์ ํ์ฉ ์ฌ๋ก
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ค์ ๋ก ์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์ง ๊ตฌ์ฒด์ ์ธ ์์๋ฅผ ๋ณผ๊น์? ๐ผ
๐ผ ์ฌ๋ก 1: ์คํํธ์ ์ ๊ณ ๊ฐ ์ง์ ์๋ํ
ํ ์คํํธ์
์ด ์ ํ ๋งค๋ด์ผ๊ณผ FAQ ๋ฌธ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฑ๋ด์ ๊ตฌ์ถํ์ด์. ๊ฒฐ๊ณผ๋?
โข ๊ณ ๊ฐ ๋ฌธ์ ์๋ต ์๊ฐ: ํ๊ท 2์๊ฐ โ ์ฆ์ ์๋ต
โข ๊ณ ๊ฐ ์ง์ ๋น์ฉ: ์ 500๋ง์ โ ์ 50๋ง์ (90% ์ ๊ฐ)
โข ๊ณ ๊ฐ ๋ง์กฑ๋: 3.5์ โ 4.2์ (5์ ๋ง์ )
โข ์ง์ ์
๋ฌด ํจ์จ: ๋ฐ๋ณต ์ง๋ฌธ ์ฒ๋ฆฌ ์๊ฐ 80% ๊ฐ์
ํนํ ์ผ๊ฐ์ด๋ ์ฃผ๋ง์๋ ์ฆ์ ๋ต๋ณ์ ๋ฐ์ ์ ์์ด์ ๊ณ ๊ฐ๋ค์ ๋ง์กฑ๋๊ฐ ํฌ๊ฒ ์ฌ๋๋ค๊ณ ํด์! ๐
๐ ์ฌ๋ก 2: ๋ํ๊ต ํ์ต ์ง์ ์์คํ
ํ ๋ํ์์ ๊ฐ์ ์๋ฃ์ ๊ต์ฌ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ต ๋์ฐ๋ฏธ ์ฑ๋ด์ ๋ง๋ค์์ด์:
โข ํ์ ์ง๋ฌธ ์ฒ๋ฆฌ: ๊ต์ 1๋ช
๋น ํ๋ฃจ 50๊ฑด โ ์ฑ๋ด์ด 70% ์๋ ์ฒ๋ฆฌ
โข ํ์ต ํจ์จ: ๊ถ๊ธํ ์ ์ ์ฆ์ ํด๊ฒฐํ์ฌ ํ์ต ์๋ ํฅ์
โข ๊ต์ ๋ง์กฑ๋: ๋ฐ๋ณต ์ง๋ฌธ ๊ฐ์๋ก ์ฌํ ํ์ต ์ง๋์ ์ง์ค ๊ฐ๋ฅ
โข 24์๊ฐ ํ์ต ์ง์: ์๋ฒฝ์ ๊ณต๋ถํ๋ ํ์๋ค๋ ๋์ ๋ฐ์ ์ ์์
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์๋ ์ด๋ฐ ์ฑ๋ด์ ํ์ฉํ๋ฉด ์ฌ์ฉ์๋ค์ด ์๋น์ค๋ฅผ ๋ ์ฝ๊ฒ ์ด์ฉํ ์ ์๊ฒ ์ฃ ? ๐
๐ง ๋ฌธ์ ํด๊ฒฐ ๊ฐ์ด๋
๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ์ฌ๋ฌ ๋ฌธ์ ์ ๋ถ๋ชํ๊ฒ ๋ผ์. ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํด๋ดค์ด์! ๐ ๏ธ
โ ์์ฃผ ๋ฌป๋ ์ง๋ฌธ (FAQ)
Q1: "๋ต๋ณ์ด ๋๋ฌด ๋๋ ค์!"
A: ์ฌ๋ฌ ์์ธ์ด ์์ ์ ์์ด์:
โข ๋ฌธ์๊ฐ ๋๋ฌด ๋ง๊ฑฐ๋ ์ฒญํฌ ํฌ๊ธฐ๊ฐ ํผ โ ์ฒญํฌ ํฌ๊ธฐ๋ฅผ ์ค์ด๊ณ ๊ฒ์ ๊ฐ์(k) ์กฐ์
โข ์๋ฒ ๋ฉ ๋ชจ๋ธ์ด ๋๋ฆผ โ ๋ ๋น ๋ฅธ ๋ชจ๋ธ๋ก ๋ณ๊ฒฝ
โข ๋คํธ์ํฌ ์ง์ฐ โ ๋ก์ปฌ ๋ชจ๋ธ ์ฌ์ฉ ๊ณ ๋ ค
Q2: "๋ต๋ณ์ด ๋ถ์ ํํด์!"
A: ๋ค์์ ์ฒดํฌํด๋ณด์ธ์:
โข ๋ฌธ์ ํ์ง์ด ๋ฎ์ โ ๋ฌธ์๋ฅผ ๋ ๋ช
ํํ๊ฒ ์์ฑ
โข ์ฒญํฌ ํฌ๊ธฐ๊ฐ ๋ถ์ ์ โ 500-1000์๋ก ์กฐ์
โข ๊ฒ์ ๊ฐ์๊ฐ ๋ถ์กฑ โ k ๊ฐ์ 3-5๋ก ์ค์
โข ํ๋กฌํํธ๊ฐ ๋ถ๋ช
ํ โ ๋ ๊ตฌ์ฒด์ ์ธ ์ง์์ฌํญ ์ถ๊ฐ
Q3: "๋น์ฉ์ด ๋๋ฌด ๋ง์ด ๋์์!"
A: ๋น์ฉ ์ ๊ฐ ๋ฐฉ๋ฒ:
โข ์บ์ฑ ํ์ฉ์ผ๋ก ์ค๋ณต ์์ฒญ ๋ฐฉ์ง
โข ๋ ์ ๋ ดํ ๋ชจ๋ธ ์ฌ์ฉ (gpt-3.5-turbo)
โข ํ ํฐ ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง ๋ฐ ์ ํ
โข ๋ถํ์ํ ๋ฌธ์ ์ ๊ฑฐ
Q4: "ํ๊ธ ๋ฌธ์๊ฐ ์ ๋๋ก ์ฒ๋ฆฌ๋์ง ์์์!"
A: ์ธ์ฝ๋ฉ ๋ฌธ์ ์ผ ๊ฐ๋ฅ์ฑ์ด ๋์์:
โข TextLoader์ encoding='utf-8' ๋ช
์
โข PDF์ ๊ฒฝ์ฐ ํ๊ธ ํฐํธ๊ฐ ์๋ฒ ๋ฉ๋์ด ์๋์ง ํ์ธ
โข ํ์์ OCR ๋๊ตฌ ์ฌ์ฉ ๊ณ ๋ ค
๐ ๋๋ฒ๊น ํ
# ๋๋ฒ๊น
๋ชจ๋ ํ์ฑํ
import logging
logging.basicConfig(level=logging.DEBUG)
# ๊ฒ์๋ ๋ฌธ์ ํ์ธํ๊ธฐ
def debug_retrieval(vectorstore, query, k=3):
"""๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์์ธํ ํ์ธ"""
retriever = vectorstore.as_retriever(search_kwargs={"k": k})
docs = retriever.get_relevant_documents(query)
print(f"\n์ง๋ฌธ: {query}")
print(f"๊ฒ์๋ ๋ฌธ์ ์: {len(docs)}\n")
for i, doc in enumerate(docs, 1):
print(f"--- ๋ฌธ์ {i} ---")
print(f"์ถ์ฒ: {doc.metadata.get('source', '์ ์ ์์')}")
print(f"๋ด์ฉ ๋ฏธ๋ฆฌ๋ณด๊ธฐ: {doc.page_content[:200]}...")
print()
# ์ฌ์ฉ ์์
debug_retrieval(vectorstore, "ํ๋ถ ์ ์ฑ
์ด ์ด๋ป๊ฒ ๋๋์?")
๐ ๋ฐฐํฌ ๋ฐ ์ด์
์ฑ๋ด์ ๋ง๋ค์์ผ๋ฉด ์ด์ ์ค์ ๋ก ์ฌ์ฉ์๋ค์๊ฒ ์ ๊ณตํด์ผ๊ฒ ์ฃ ? ๋ฐฐํฌ ๋ฐฉ๋ฒ์ ์์๋ณผ๊ฒ์! ๐
โ๏ธ ํด๋ผ์ฐ๋ ๋ฐฐํฌ ์ต์
| ํ๋ซํผ | ์ฅ์ | ๋จ์ | ์ถ์ฒ ๋์ |
|---|---|---|---|
| Streamlit Cloud | ๋ฌด๋ฃ, ์ฌ์ด ๋ฐฐํฌ | ์ ํ๋ ๋ฆฌ์์ค | ํ๋กํ ํ์ , ์๊ท๋ชจ |
| Heroku | ๊ฐ๋จํ ์ค์ | ์ ๋ฃ, ์๋ ์ ํ | ์ค์๊ท๋ชจ ์๋น์ค |
| AWS EC2 | ์์ ํ ์ ์ด | ๋ณต์กํ ์ค์ | ๋๊ท๋ชจ ์ํฐํ๋ผ์ด์ฆ |
| Google Cloud Run | ์๋ ์ค์ผ์ผ๋ง | ๋น์ฉ ๊ด๋ฆฌ ํ์ | ํธ๋ํฝ ๋ณ๋ ํฐ ์๋น์ค |
| Azure App Service | MS ์ํ๊ณ ํตํฉ | ํ์ต ๊ณก์ | ๊ธฐ์ ํ๊ฒฝ |
๐ณ Docker๋ก ์ปจํ ์ด๋ํํ๊ธฐ
Docker๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋์๋ ๋์ผํ ํ๊ฒฝ์์ ์ฑ๋ด์ ์คํํ ์ ์์ด์:
# Dockerfile
FROM python:3.9-slim
WORKDIR /app
# ์์กด์ฑ ์ค์น
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# ์ ํ๋ฆฌ์ผ์ด์
์ฝ๋ ๋ณต์ฌ
COPY . .
# ํฌํธ ๋
ธ์ถ
EXPOSE 8501
# Streamlit ์คํ
CMD ["streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
# docker-compose.yml
version: '3.8'
services:
chatbot:
build: .
ports:
- "8501:8501"
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
volumes:
- ./documents:/app/documents
- ./chroma_db:/app/chroma_db
restart: unless-stopped
๐ Docker ์ฌ์ฉ๋ฒ:
1. docker-compose up -d๋ก ์คํ
2. http://localhost:8501์์ ์ ์
3. docker-compose down์ผ๋ก ์ข
๋ฃ
์ด๋ ๊ฒ ํ๋ฉด ๊ฐ๋ฐ ํ๊ฒฝ๊ณผ ํ๋ก๋์
ํ๊ฒฝ์ ๋์ผํ๊ฒ ์ ์งํ ์ ์์ด์!
๐ ์ค์ ํ๋ก์ ํธ: ์์ ํ FAQ ์์คํ ๊ตฌ์ถ
์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ชจ๋ ๋ด์ฉ์ ์ข ํฉํด์ ์ค์ ๋ก ์ฌ์ฉ ๊ฐ๋ฅํ ์์ ํ ์์คํ ์ ๋ง๋ค์ด๋ณผ๊ฒ์! ๐ฏ
๐ ํ๋ก์ ํธ ๊ตฌ์กฐ
faq-chatbot/
โโโ app/
โ โโโ __init__.py
โ โโโ chatbot.py # ์ฑ๋ด ํต์ฌ ๋ก์ง
โ โโโ config.py # ์ค์ ๊ด๋ฆฌ
โ โโโ utils.py # ์ ํธ๋ฆฌํฐ ํจ์
โโโ documents/ # ๋ฌธ์ ์ ์ฅ ํด๋
โ โโโ faq.pdf
โ โโโ manual.pdf
โ โโโ policy.txt
โโโ chroma_db/ # ๋ฒกํฐ DB (์๋ ์์ฑ)
โโโ logs/ # ๋ก๊ทธ ํ์ผ
โโโ tests/ # ํ
์คํธ ์ฝ๋
โ โโโ test_chatbot.py
โโโ streamlit_app.py # ์น UI
โโโ requirements.txt # ์์กด์ฑ ๋ชฉ๋ก
โโโ .env # ํ๊ฒฝ ๋ณ์
โโโ .gitignore
โโโ Dockerfile
โโโ docker-compose.yml
โโโ README.md
โ๏ธ config.py - ์ค์ ๊ด๋ฆฌ
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
"""์ ํ๋ฆฌ์ผ์ด์
์ค์ """
# OpenAI ์ค์
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-3.5-turbo")
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "text-embedding-ada-002")
# ๋ฌธ์ ์ฒ๋ฆฌ ์ค์
DOCUMENTS_PATH = os.getenv("DOCUMENTS_PATH", "./documents")
CHUNK_SIZE = int(os.getenv("CHUNK_SIZE", "1000"))
CHUNK_OVERLAP = int(os.getenv("CHUNK_OVERLAP", "200"))
# ๋ฒกํฐ DB ์ค์
VECTOR_DB_PATH = os.getenv("VECTOR_DB_PATH", "./chroma_db")
SEARCH_K = int(os.getenv("SEARCH_K", "3"))
# ์ฑ๋ด ์ค์
TEMPERATURE = float(os.getenv("TEMPERATURE", "0.3"))
MAX_TOKENS = int(os.getenv("MAX_TOKENS", "500"))
# ๋ณด์ ์ค์
MAX_QUERY_LENGTH = int(os.getenv("MAX_QUERY_LENGTH", "500"))
MAX_QUERIES_PER_SESSION = int(os.getenv("MAX_QUERIES_PER_SESSION", "100"))
# ๋ก๊น
์ค์
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
LOG_FILE = os.getenv("LOG_FILE", "./logs/chatbot.log")
๐งช ํ ์คํธ ์ฝ๋ ์์ฑ
์์ ์ ์ธ ์๋น์ค๋ฅผ ์ํด์๋ ํ ์คํธ๊ฐ ํ์์์! ๊ฐ๋จํ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด๋ณผ๊ฒ์:
import unittest
from app.chatbot import DocumentFAQChatbot
class TestChatbot(unittest.TestCase):
"""์ฑ๋ด ํ
์คํธ"""
@classmethod
def setUpClass(cls):
"""ํ
์คํธ ์์ ์ ์ฑ๋ด ์ด๊ธฐํ"""
cls.chatbot = DocumentFAQChatbot("./test_documents")
cls.chatbot.setup()
def test_basic_query(self):
"""๊ธฐ๋ณธ ์ง๋ฌธ ํ
์คํธ"""
answer, sources = self.chatbot.ask("ํ๋ถ ์ ์ฑ
์ด ๋ญ๊ฐ์?")
self.assertIsNotNone(answer)
self.assertGreater(len(sources), 0)
def test_empty_query(self):
"""๋น ์ง๋ฌธ ํ
์คํธ"""
with self.assertRaises(ValueError):
self.chatbot.ask("")
def test_long_query(self):
"""๊ธด ์ง๋ฌธ ํ
์คํธ"""
long_query = "ํ
์คํธ " * 200
with self.assertRaises(ValueError):
self.chatbot.ask(long_query)
def test_cache(self):
"""์บ์ฑ ํ
์คํธ"""
question = "๋ฐฐ์ก์ ์ผ๋ง๋ ๊ฑธ๋ฆฌ๋์?"
answer1, _ = self.chatbot.ask(question)
answer2, _ = self.chatbot.ask(question)
self.assertEqual(answer1, answer2)
if __name__ == '__main__':
unittest.main()
๐จ ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์ ํ๊ธฐ
๊ธฐ์ ์ ์ผ๋ก ์๋ฒฝํด๋ ์ฌ์ฉ์ ๊ฒฝํ์ด ๋์๋ฉด ์๋ฌด๋ ์ฌ์ฉํ์ง ์์์. UX๋ฅผ ๊ฐ์ ํ๋ ๋ช ๊ฐ์ง ํ์ ๊ณต์ ํ ๊ฒ์! โจ
๐ก UX ๊ฐ์ ์ฒดํฌ๋ฆฌ์คํธ
โ
๋น ๋ฅธ ์๋ต: 3์ด ์ด๋ด ๋ต๋ณ ์ ๊ณต (๋๋ ๋ก๋ฉ ํ์)
โ
๋ช
ํํ ํผ๋๋ฐฑ: ์ฒ๋ฆฌ ์ค์์ ์๊ฐ์ ์ผ๋ก ํ์
โ
์ค๋ฅ ๋ฉ์์ง: ์น์ ํ๊ณ ๊ตฌ์ฒด์ ์ธ ์๋ด
โ
๋ํ ๋งฅ๋ฝ: ์ด์ ๋ํ ๋ด์ฉ ์ ์ง
โ
์ ์ ์ง๋ฌธ: ์์ฃผ ๋ฌป๋ ์ง๋ฌธ ๋ฒํผ ์ ๊ณต
โ
์ถ์ฒ ํ์: ๋ต๋ณ์ ๊ทผ๊ฑฐ ๋ฌธ์ ๋ณด์ฌ์ฃผ๊ธฐ
โ
ํผ๋๋ฐฑ ์์ง: ๋ต๋ณ์ด ๋์๋์๋์ง ๋ฌผ์ด๋ณด๊ธฐ
โ
๋ค๊ตญ์ด ์ง์: ํ์์ ์ฌ๋ฌ ์ธ์ด๋ก ์ ๊ณต
โ
๋ชจ๋ฐ์ผ ์ต์ ํ: ์์ ํ๋ฉด์์๋ ์ ๋ณด์ด๊ฒ
โ
์ ๊ทผ์ฑ: ์คํฌ๋ฆฐ ๋ฆฌ๋ ๋ฑ ๋ณด์กฐ ๊ธฐ์ ์ง์
๐ฏ ์ ์ ์ง๋ฌธ ๊ธฐ๋ฅ ์ถ๊ฐ
# streamlit_app.py์ ์ถ๊ฐ
SUGGESTED_QUESTIONS = [
"ํ๋ถ ์ ์ฑ
์ด ์ด๋ป๊ฒ ๋๋์?",
"๋ฐฐ์ก์ ์ผ๋ง๋ ๊ฑธ๋ฆฌ๋์?",
"ํ์ ๊ฐ์
์ ์ด๋ป๊ฒ ํ๋์?",
"๋น๋ฐ๋ฒํธ๋ฅผ ์์ด๋ฒ๋ ธ์ด์",
"๊ฒฐ์ ๋ฐฉ๋ฒ์ ๋ฌด์์ด ์๋์?"
]
st.markdown("### ๐ฌ ์์ฃผ ๋ฌป๋ ์ง๋ฌธ")
cols = st.columns(3)
for i, question in enumerate(SUGGESTED_QUESTIONS):
col_idx = i % 3
with cols[col_idx]:
if st.button(question, key=f"suggested_{i}"):
# ์ง๋ฌธ ์๋ ์
๋ ฅ
st.session_state.current_question = question
๐ ํผ๋๋ฐฑ ์์งํ๊ธฐ
# ๋ต๋ณ ํ ํผ๋๋ฐฑ ๋ฒํผ ์ถ๊ฐ
col1, col2 = st.columns(2)
with col1:
if st.button("๐ ๋์์ด ๋์์ด์"):
save_feedback(prompt, answer, positive=True)
st.success("ํผ๋๋ฐฑ ๊ฐ์ฌํฉ๋๋ค!")
with col2:
if st.button("๐ ๋์์ด ์ ๋์์ด์"):
save_feedback(prompt, answer, positive=False)
st.info("๋ ๋์ ๋ต๋ณ์ ์ ๊ณตํ๋๋ก ๊ฐ์ ํ๊ฒ ์ต๋๋ค.")
def save_feedback(question, answer, positive):
"""ํผ๋๋ฐฑ์ ํ์ผ์ ์ ์ฅ"""
import json
from datetime import datetime
feedback = {
"timestamp": datetime.now().isoformat(),
"question": question,
"answer": answer,
"positive": positive
}
with open("feedback.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps(feedback, ensure_ascii=False) + "\n")
๐ ๊ณ ๊ธ ๊ธฐ๋ฅ: RAG ์ต์ ํ
RAG(Retrieval-Augmented Generation)๋ ๋ฌธ์ ๊ธฐ๋ฐ ์ฑ๋ด์ ํต์ฌ์ด์์. ๋ ์ ๊ตํ๊ฒ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์์๋ณผ๊ฒ์! ๐
๐ ํ์ด๋ธ๋ฆฌ๋ ๊ฒ์
๋ฒกํฐ ๊ฒ์๋ง ์ฌ์ฉํ๋ฉด ํค์๋๊ฐ ์ ํํ ์ผ์นํ๋ ๋ฌธ์๋ฅผ ๋์น ์ ์์ด์. ๋ฒกํฐ ๊ฒ์๊ณผ ํค์๋ ๊ฒ์์ ๊ฒฐํฉํ๋ฉด ๋ ์ข์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์ฃ :
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
def create_hybrid_retriever(vectorstore, documents):
"""ํ์ด๋ธ๋ฆฌ๋ ๊ฒ์๊ธฐ ์์ฑ"""
# ๋ฒกํฐ ๊ฒ์๊ธฐ
vector_retriever = vectorstore.as_retriever(
search_kwargs={"k": 5}
)
# ํค์๋ ๊ฒ์๊ธฐ (BM25)
bm25_retriever = BM25Retriever.from_documents(documents)
bm25_retriever.k = 5
# ์์๋ธ ๊ฒ์๊ธฐ (๊ฐ์ค์น ์กฐํฉ)
ensemble_retriever = EnsembleRetriever(
retrievers=[vector_retriever, bm25_retriever],
weights=[0.7, 0.3] # ๋ฒกํฐ 70%, ํค์๋ 30%
)
return ensemble_retriever
๐ฏ ์ฌ์์ํ (Re-ranking)
๊ฒ์๋ ๋ฌธ์๋ค์ ๋ค์ ํ๋ฒ ์ ๋ ฌํด์ ๊ฐ์ฅ ๊ด๋ จ์ฑ ๋์ ๊ฒ๋ง ์ฌ์ฉํ๋ฉด ๋ต๋ณ ํ์ง์ด ์ฌ๋ผ๊ฐ์:
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
def create_compressed_retriever(base_retriever, llm):
"""์์ถ ๊ฒ์๊ธฐ ์์ฑ (๊ด๋ จ ๋ถ๋ถ๋ง ์ถ์ถ)"""
compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=base_retriever
)
return compression_retriever
๐ฐ ๋น์ฉ ์ต์ ํ ์ ๋ต
OpenAI API๋ ์ฌ์ฉ๋์ ๋ฐ๋ผ ๋น์ฉ์ด ๋ฐ์ํด์. ๋๋ํ๊ฒ ๋น์ฉ์ ์ ๊ฐํ๋ ๋ฐฉ๋ฒ๋ค์ ์์๋ณผ๊ฒ์! ๐ธ
๐ต ๋น์ฉ ๋ฐ์ ์์
1. ์๋ฒ ๋ฉ ์์ฑ: ๋ฌธ์๋ฅผ ๋ฒกํฐ๋ก ๋ณํํ ๋
2. LLM ํธ์ถ: ๋ต๋ณ์ ์์ฑํ ๋
3. ํ ํฐ ์ฌ์ฉ: ์
๋ ฅ๊ณผ ์ถ๋ ฅ ํ ํฐ ๋ชจ๋ ๊ณผ๊ธ
์์ ๋น์ฉ (gpt-3.5-turbo ๊ธฐ์ค):
โข ์๋ฒ ๋ฉ: $0.0001 / 1K ํ ํฐ
โข ์
๋ ฅ: $0.0015 / 1K ํ ํฐ
โข ์ถ๋ ฅ: $0.002 / 1K ํ ํฐ
1000๋ช
์ด ํ๋ฃจ 10๋ฒ์ฉ ์ง๋ฌธํ๋ฉด ์ ์ฝ $300~500 ์ ๋ ์์๋ผ์.
๐ก ๋น์ฉ ์ ๊ฐ ํ
1. ์บ์ฑ ์ ๊ทน ํ์ฉ: ๋์ผ ์ง๋ฌธ์ ์ฌ๊ณ์ฐํ์ง ์๊ธฐ
2. ์ฒญํฌ ํฌ๊ธฐ ์ต์ ํ: ๋ถํ์ํ๊ฒ ํฌ์ง ์๊ฒ
3. ๊ฒ์ ๊ฐ์ ์ ํ: k=3 ์ ๋๋ฉด ์ถฉ๋ถ
4. ์งง์ ํ๋กฌํํธ: ๋ถํ์ํ ์ค๋ช
์ ๊ฑฐ
5. ๋ก์ปฌ ์๋ฒ ๋ฉ: ๊ฐ๋ฅํ๋ฉด ์คํ์์ค ๋ชจ๋ธ ์ฌ์ฉ
6. ๋ฐฐ์น ์ฒ๋ฆฌ: ์ฌ๋ฌ ์์ฒญ์ ๋ชจ์์ ์ฒ๋ฆฌ
7. ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง: ์ด์ ํจํด ๊ฐ์ง ๋ฐ ์ ํ
8. ์ ๋ ดํ ๋ชจ๋ธ: gpt-4 ๋์ gpt-3.5-turbo ์ฌ์ฉ
๐ ํ๋ก๋์ ์ฒดํฌ๋ฆฌ์คํธ
์ค์ ์๋น์ค์ ๋ฐฐํฌํ๊ธฐ ์ ์ ๋ฐ๋์ ํ์ธํด์ผ ํ ์ฌํญ๋ค์ด์์! โ
๐ ๋ฐฐํฌ ์ ์ฒดํฌ๋ฆฌ์คํธ
๋ณด์
โก API ํค๊ฐ ์ฝ๋์ ํ๋์ฝ๋ฉ๋์ง ์์๋๊ฐ?
โก ํ๊ฒฝ ๋ณ์๊ฐ ์์ ํ๊ฒ ๊ด๋ฆฌ๋๋๊ฐ?
โก ์
๋ ฅ ๊ฒ์ฆ์ด ์ ๋๋ก ๋๋๊ฐ?
โก HTTPS๋ฅผ ์ฌ์ฉํ๋๊ฐ?
โก ์ธ์ฆ/์ธ๊ฐ๊ฐ ๊ตฌํ๋์๋๊ฐ?
์ฑ๋ฅ
โก ์๋ต ์๊ฐ์ด 3์ด ์ด๋ด์ธ๊ฐ?
โก ์บ์ฑ์ด ์ ์ฉ๋์๋๊ฐ?
โก ๋์ ์ฌ์ฉ์๋ฅผ ์ฒ๋ฆฌํ ์ ์๋๊ฐ?
โก ์๋ฌ ์ฒ๋ฆฌ๊ฐ ์ ์ ํ๊ฐ?
๋ชจ๋ํฐ๋ง
โก ๋ก๊น
์ด ์ค์ ๋์๋๊ฐ?
โก ์ฑ๋ฅ ์งํ๋ฅผ ์ถ์ ํ๋๊ฐ?
โก ์๋ฆผ ์์คํ
์ด ์๋๊ฐ?
โก ๋ฐฑ์
๊ณํ์ด ์๋๊ฐ?
์ฌ์ฉ์ ๊ฒฝํ
โก ๋ชจ๋ฐ์ผ์์ ์ ์๋ํ๋๊ฐ?
โก ๋ก๋ฉ ์ํ๊ฐ ํ์๋๋๊ฐ?
โก ์๋ฌ ๋ฉ์์ง๊ฐ ์น์ ํ๊ฐ?
โก ํผ๋๋ฐฑ์ ์์งํ๋๊ฐ?
๋ฌธ์
โก README๊ฐ ์์ฑ๋์๋๊ฐ?
โก API ๋ฌธ์๊ฐ ์๋๊ฐ?
โก ์ค์น ๊ฐ์ด๋๊ฐ ๋ช
ํํ๊ฐ?
โก ๋ฌธ์ ํด๊ฒฐ ๊ฐ์ด๋๊ฐ ์๋๊ฐ?
๐ ํ์ต ๋ฆฌ์์ค ๋ฐ ๋ค์ ๋จ๊ณ
์ฌ๊ธฐ๊น์ง ๋ฐ๋ผ์ค์๋๋ผ ์๊ณ ๋ง์ผ์ จ์ด์! ๐ ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด ์ด๋ฐ ๋ฆฌ์์ค๋ค์ ์ถ์ฒํด์:
๐ ์ถ์ฒ ํ์ต ์๋ฃ
๊ณต์ ๋ฌธ์:
โข LangChain ๊ณต์ ๋ฌธ์: python.langchain.com
โข OpenAI API ๋ฌธ์: platform.openai.com/docs
โข Streamlit ๋ฌธ์: docs.streamlit.io
์จ๋ผ์ธ ๊ฐ์:
โข DeepLearning.AI - LangChain for LLM Application Development
โข Udemy - Building AI Applications with LangChain
โข YouTube - LangChain ํํ ๋ฆฌ์ผ ์๋ฆฌ์ฆ
์ปค๋ฎค๋ํฐ:
โข LangChain Discord ์๋ฒ
โข Reddit r/LangChain
โข Stack Overflow LangChain ํ๊ทธ
๋ธ๋ก๊ทธ & ์ํฐํด:
โข Towards Data Science
โข Medium AI ์น์
โข LangChain ๊ณต์ ๋ธ๋ก๊ทธ
๐ ๋ค์ ๋จ๊ณ๋ก ๋์๊ฐ๊ธฐ
๊ธฐ๋ณธ์ ๋ง์คํฐํ๋ค๋ฉด ์ด๋ฐ ๊ณ ๊ธ ๊ธฐ๋ฅ๋ค์ ๋์ ํด๋ณด์ธ์:
1. ๋ฉํฐ๋ชจ๋ฌ ์ง์: ์ด๋ฏธ์ง, ์ค๋์ค๋ ์ฒ๋ฆฌํ๋ ์ฑ๋ด
2. ์์ด์ ํธ ๊ตฌํ: ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ ์์จ AI
3. ํ์ธํ๋: ํน์ ๋๋ฉ์ธ์ ์ต์ ํ๋ ๋ชจ๋ธ
4. ์์ฑ ์ธํฐํ์ด์ค: ๋ง๋ก ์ง๋ฌธํ๊ณ ๋ต๋ณ ๋ฃ๊ธฐ
5. ๋ค๊ตญ์ด ์ง์: ์ฌ๋ฌ ์ธ์ด๋ก ์๋น์ค ์ ๊ณต
6. ์ค์๊ฐ ํ์ต: ์ฌ์ฉ์ ํผ๋๋ฐฑ์ผ๋ก ์ง์ ๊ฐ์
7. ํตํฉ ์์คํ
: CRM, ํฐ์ผํ
์์คํ
๊ณผ ์ฐ๋
8. ๋ถ์ ๋์๋ณด๋: ์ฌ์ฉ ํจํด ์๊ฐํ
๐ฌ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ ๋ง ๊ธด ์ฌ์ ์ด์์ฃ ? ๐
ํ์ง๋ง ์ด์ ์ฌ๋ฌ๋ถ์ LangChain์ ์ฌ์ฉํด์ ์ค์ฉ์ ์ธ ๋ฌธ์ ๊ธฐ๋ฐ FAQ ์ฑ๋ด์ ๋ง๋ค ์ ์๋ ๋ฅ๋ ฅ์ ๊ฐ์ถ์
จ์ด์! ๐
์ฒ์์๋ ๋ณต์กํด ๋ณด์๋ ๊ฐ๋
๋ค์ด ์ด์ ๋ ์ข ๋ ๋ช
ํํด์ก์ ๊ฑฐ์์. ๋ฌธ์ ๋ก๋ฉ๋ถํฐ ๋ฒกํฐ ์ ์ฅ, ๊ฒ์, ๋ต๋ณ ์์ฑ๊น์ง์ ์ ์ฒด ํ์ดํ๋ผ์ธ์ ์ดํดํ๊ณ ๊ตฌํํ ์ ์๊ฒ ๋์์ผ๋๊น์.
๊ธฐ์ตํ์ธ์: ์๋ฒฝํ ์ฑ๋ด์ ํ ๋ฒ์ ๋ง๋ค์ด์ง์ง ์์์. ์ฌ์ฉ์ ํผ๋๋ฐฑ์ ๋ฐ์๊ฐ๋ฉฐ ์ง์์ ์ผ๋ก ๊ฐ์ ํ๋ ๊ฒ ์ค์ํด์. ์๊ฒ ์์ํด์ ์ ์ง์ ์ผ๋ก ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ ๋ฐฉ์์ ์ถ์ฒ๋๋ ค์! ๐ฑ
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ฌ๋ฌ๋ถ์ ์ ๋ฌธ ์ง์์ ๊ณต์ ํ ๋๋ ์ด๋ฐ ์ฑ๋ด์ ํ์ฉํ๋ฉด ๋ ๋ง์ ์ฌ๋๋ค์๊ฒ ํจ์จ์ ์ผ๋ก ๋์์ ์ค ์ ์์ ๊ฑฐ์์. ์ฌ๋ฌ๋ถ์ ์ง์์ด AI๋ฅผ ํตํด 24์๊ฐ ์ผํ๋ ๋๋ํ ๋น์๊ฐ ๋๋ ๊ฑฐ์ฃ ! ๐คโจ
๊ถ๊ธํ ์ ์ด ์๊ฑฐ๋ ๋งํ๋ ๋ถ๋ถ์ด ์๋ค๋ฉด ์ฃผ์ ํ์ง ๋ง๊ณ ์ปค๋ฎค๋ํฐ์ ์ง๋ฌธํ์ธ์. ์ฐ๋ฆฌ ๋ชจ๋ ํจ๊ป ๋ฐฐ์๊ฐ๋ ๊ฑฐ๋๊น์! ๐ช
๊ทธ๋ผ ์ฌ๋ฌ๋ถ์ ๋ฉ์ง ์ฑ๋ด ํ๋ก์ ํธ๋ฅผ ์์ํฉ๋๋ค! ํ์ดํ
! ๐๐
๐ ์ถํํฉ๋๋ค!
LangChain ๋ฌธ์ ๊ธฐ๋ฐ FAQ ์ฑ๋ด ๋ง์คํฐ ๊ณผ์ ์ ์๋ฃํ์
จ์ต๋๋ค!
์ด์ ์ฌ๋ฌ๋ถ๋ง์ AI ์ฑ๋ด์ ๋ง๋ค์ด๋ณด์ธ์! ๐ชโจ
๊ด๋ จ ํค์๋
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.

๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.