์๋ ํ์ธ์! ๋ญ ์ฆค์ ๋๋ค.
์ด๋ฒ ํฌ์คํ ์์๋ OpenAI์ Chat API์ ChromaDB๋ฅผ ํ์ฉํ RAG(Retrieval-Augmented Generation)์ ๋ํด ์ค๋ช ๋๋ฆด๊ฒ์. ์ค์ต ์ฝ๋๋ ์๋ต๋๋ค ๐
RAG๋ ์ธ๋ถ ๋ฐ์ดํฐ์ ์ธ์ด ๋ชจ๋ธ์ ๊ฒฐํฉํด ์ข ๋ ์ ํํ๊ณ ๋งฅ๋ฝ์ ๋ง๋ ๋ต๋ณ์ ์์ฑํ๋ ๋ฐ ๋์์ด ๋ผ์ ๐ค
๐ RAG (Retrieval-Augmented Generation)
RAG๋ Retrieval-Augmented Generation์ ์ฝ์๋ก, ์ ๋ณด ๊ฒ์๊ณผ ์์ฑํ AI๋ฅผ ๊ฒฐํฉํ ๋ฐฉ๋ฒ์ด์์. GPT์ ๊ฐ์ ์ธ์ด ๋ชจ๋ธ์ ์์ฒด์ ์ผ๋ก ๋ค์ํ ์ง์์ ๊ฐ์ง๊ณ ์์ง๋ง, ์ต์ ์ ๋ณด๋ ํน์ ๋๋ฉ์ธ์ ๋ํ ์์ธํ ๋ด์ฉ์ ์์ง ๋ชปํ ๋๋ ์์ด์. ์ด๋ฐ ๊ฒฝ์ฐ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด RAG๋ฅผ ์ฌ์ฉํด์.
- Retrieval: ์ฃผ์ด์ง ์ง๋ฌธ์ ๋ํด ์ธ๋ถ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ด๋ จ ์ ๋ณด๋ฅผ ๊ฒ์ํ๋ ๊ณผ์ ์ด์์. ์ด ๋จ๊ณ์์๋ ๊ฒ์๋ ๋ฌธ์๋ ํ ์คํธ ์กฐ๊ฐ์ด ๋ต๋ณ์ ์์ฑํ๋ ๋ฐ ์ฌ์ฉ๋ผ์.
- Augmented Generation: ๊ฒ์์ ํตํด ์ป์ ์ ๋ณด๋ฅผ ์ธ์ด ๋ชจ๋ธ์ ์ถ๊ฐํ์ฌ ๋ ์ ํํ๊ณ ๋งฅ๋ฝ์ ๋ง๋ ๋ต๋ณ์ ์์ฑํ๋ ๊ณผ์ ์ด์์.
โ ์ RAG๋ฅผ ์ฌ์ฉํ๋์?
- ์ต์ ์ ๋ณด ์ ๊ณต: ์ธ์ด ๋ชจ๋ธ ์์ฒด๊ฐ ๊ฐ๊ณ ์๋ ์ง์์ ํ๋ จ ์์ ์ดํ์ ์ ๋ณด๊ฐ ์ ๋ฐ์ดํธ๋์ง ์๊ธฐ ๋๋ฌธ์, ์ต์ ์ ๋ณด๋ฅผ ๊ฒ์ํ์ฌ ํ์ฉํ ์ ์์ด์.
- ์ ํํ ๋๋ฉ์ธ ์ง์: ํน์ ๋ถ์ผ๋ ์ ๋ฌธ ์ง์์ ํฌํจํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฐธ์กฐํด ๋ณด๋ค ์ ํํ ๋ต๋ณ์ ์ป์ ์ ์์ด์.
- ์ง์ ํ์ฅ: ์ธ์ด ๋ชจ๋ธ์ด ์ง์ ํ๋ จ๋์ง ์์ ๋ด์ฉ๋ ์ธ๋ถ ๋ฌธ์๋ฅผ ๊ฒ์ํ๊ณ ์ฐธ์กฐํ์ฌ ๋ต๋ณํ ์ ์์ด์.
โ RAG์ ์ ์ฒด์ ์ธ ํ๋ฆ
- ๋ฌธ์ ์ค๋น: ์ฌ์ฉ์๊ฐ ์ํ๋ ์ ๋ณด๊ฐ ๋ด๊ธด ๋ฌธ์๋ฅผ ์ค๋นํด์. ์ด ๋ฌธ์๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ ์๋ฃ๊ฐ ๋ผ์.
- ๋ฌธ์ ์๋ฒ ๋ฉ: ChromaDB๋ฅผ ์ฌ์ฉํด ์ค๋น๋ ๋ฌธ์๋ฅผ ๋ฒกํฐ ์๋ฒ ๋ฉํ์ฌ ์ ์ฅํด์.
- ์ง๋ฌธ ์๋ฒ ๋ฉ ๋ฐ ๊ฒ์: ์ฌ์ฉ์๊ฐ ์ง๋ฌธ์ ์ ๋ ฅํ๋ฉด, ํด๋น ์ง๋ฌธ๋ ๋ฒกํฐ๋ก ์๋ฒ ๋ฉ๋์ด ChromaDB์ ์ ์ฅ๋ ๋ฌธ์๋ค๊ณผ ์ ์ฌ๋๋ฅผ ๋น๊ตํด์.
- ๋ฌธ์ ๊ฒฐํฉ ๋ฐ ๋ต๋ณ ์์ฑ: ๊ฒ์๋ ๋ฌธ์์ ํจ๊ป ์ฌ์ฉ์์ ์ง๋ฌธ์ ์ธ์ด ๋ชจ๋ธ์ ์ ๋ ฅํด ๋ต๋ณ์ ์์ฑํด์. ์ด๋ ๊ฒ ํ๋ฉด ๋ณด๋ค ์ ํํ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ต๋ณ์ ์์ฑํ ์ ์์ด์.
๐ ChromaDB์ ์ญํ
ChromaDB๋ RAG๋ฅผ ๊ตฌํํ ๋ ์ค์ํ ์์์ธ ๊ฒ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ญํ ์ ํด์. ์ฝ๊ฒ ๋งํด, ์ฌ์ฉ์๊ฐ ์ง๋ฌธํ ๋ ์ฐธ๊ณ ํ ์ ์๋ ์ง์ ์ ์ฅ์์ธ ๊ฑฐ์ฃ ! ๐๏ธ
- ๋ฌธ์ ์๋ฒ ๋ฉ: ChromaDB๋ ๋ฌธ์๋ฅผ ๋ฒกํฐ๋ก ๋ณํํ๊ณ ์ ์ฅํด์. ์ด ๋ฒกํฐ๋ ๋ฌธ์์ ์๋ฏธ๋ฅผ ๋ด๊ณ ์์ด ๊ฒ์ ๋จ๊ณ์์ ํ์ฉ๋ผ์. ๋ฌธ์ ์๋ฒ ๋ฉ์ OpenAI์ ์๋ฒ ๋ฉ ๋ชจ๋ธ์ด๋ ๋ค๋ฅธ ์๋ฒ ๋ฉ ๋ชจ๋ธ์ ์ฌ์ฉํด ๋ง๋ค ์ ์์ด์.
- ์ ์ฌ๋ ๊ฒ์: ์ฌ์ฉ์๊ฐ ์ง๋ฌธ์ ํ ๋, ์ง๋ฌธ์ ๋ํ ์๋ฒ ๋ฉ์ ์์ฑํ๊ณ ์ ์ฅ๋ ๋ฌธ์ ์๋ฒ ๋ฉ๊ณผ ๋น๊ตํ์ฌ ๊ฐ์ฅ ๊ด๋ จ์ฑ ๋์ ๋ฌธ์๋ฅผ ์ฐพ์์.
- ์์ ๋ฐ ์๊ตฌ ์ ์ฅ: ChromaDB๋ ๋ฉ๋ชจ๋ฆฌ ๊ธฐ๋ฐ์ผ๋ก ๋์ํ ์๋ ์๊ณ , ์๊ตฌ์ ์ผ๋ก ๋ฌธ์๋ฅผ ์ ์ฅํด๋ ์๋ ์์ด์.
์๋ฒ ๋ฉ์ ํ ์คํธ๋ฅผ ์์น ๋ฒกํฐ๋ก ๋ณํํ๋ ์์ ์ ์๋ฏธํด์.
ChromaDB๋ ๊ฐ ๋ฌธ์๋ฅผ ์๋ฒ ๋ฉํ ํ ์ ์ฅํ๊ณ , ์ฌ์ฉ์์ ์ง๋ฌธ์ ์๋ฒ ๋ฉํ์ฌ ๊ทธ ๋ฒกํฐ์ ๊ฐ์ฅ ์ ์ฌํ ๋ฌธ์๋ฅผ ์ฐพ์๋ด์. ์ด ๊ณผ์ ์ด ๋ฌธ์ ๊ฒ์ ๋จ๊ณ์์ ํต์ฌ ์ญํ ์ ํด์. ๐ก
๐ RAG ์ฝ๋ ์ค์ต
์! ์ด์ ์ค์ต์ธ๋ฐ์! ์ด๋ฒ ์ค์ต์ OpenAI์ ์ธ์ด ๋ชจ๋ธ๊ณผ ChromaDB๋ฅผ ์ฌ์ฉํด์ ๊ฐ๋จํ RAG ๊ธฐ๋ฅ์ ๊ตฌํํฉ๋๋ค. ์ฐธ์กฐ ๋ฐ์ดํฐ๋ ์น ํ์ด์ง๋ฅผ html๋ก ์ ์ฅํด์ ์ฌ์ฉํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ์ผ๋, ์ฌ๋ฌ๋ถ์ด ์ฐธ์กฐ ๋ฐ์ดํฐ๋ก ์ฌ์ฉํ๊ณ ์ถ์ ์นํ์ด์ง๋ฅผ html ํ์ผ๋ก ์ ์ฅ๋ฉด ํด๋๋ฉด ๋ฐ๋ก ์ค์ตํด ๋ณผ ์ ์์ด์!
์๋์์ ์ค๋ช ํ๋ ์ฝ๋๋ ์ค์ํ ๋ถ๋ถ๋ง ์ค๋ช ํ๋, ์ค์ต ์ ์ฒด ์ฝ๋๋ ์ Colab ์ ์ฐธ๊ณ ๋ถํ๋๋ ค์ !
โ ์ฐธ์กฐ ๋ฐ์ดํฐ HTML ํ์ผ๋ก ์ ์ฅํ๊ธฐ
- RAG ์ฐธ์กฐ ๋ฐ์ดํฐ๋ก ์ฌ์ฉํ๊ณ ์ถ์ ์นํ์ด์ง์ ๋ค์ด๊ฐ ํ์ด์ง๋ฅผ HTML ํ์ผ๋ก ์ ์ฅํด ๋๊ณ ,
- ๊ตฌ๊ธ ๋๋ผ์ด๋ธ์ ์ ๋ก๋ํด๋๋ฉด ์ค์ต์ ํ์ฉํ ์ ์์ต๋๋ค.
โ ํ์ ํจ์ ์ ์ธ
# HTML ํ์ผ์์ ํ
์คํธ ์ถ์ถ ํจ์
def extract_text_from_html(html_path):
with open(html_path, 'r', encoding='utf-8') as file:
soup = BeautifulSoup(file, 'html.parser')
# ๋ชจ๋ ํ
์คํธ๋ฅผ ์ถ์ถํ๋ฉฐ, ํ์์ ๋ฐ๋ผ ํน์ ํ๊ทธ๋ฅผ ๋์์ผ๋ก ์์ ํ ์ ์์
text = ' '.join(soup.stripped_strings)
return text
# ํ
์คํธ๋ฅผ ๊ธธ์ด์ ๋ฐ๋ผ ์์ฐ์ค๋ฝ๊ฒ ๋ถํ ํ๋ ํจ์
def smart_chunk_splitter(text, max_chunk_size=500):
sentences = text.split('. ')
chunks = []
current_chunk = ""
for sentence in sentences:
if len(current_chunk) + len(sentence) + 1 <= max_chunk_size:
current_chunk += sentence + '. '
else:
chunks.append(current_chunk.strip())
current_chunk = sentence + '. '
if current_chunk: # ๋จ์ ์๋ ํ
์คํธ ์ถ๊ฐ
chunks.append(current_chunk.strip())
return chunks
# ํ๋กฌํํธ ์์ฑ ํจ์
def create_chat_prompt(system_prompt, user_query, context_documents):
# context_documents๊ฐ ์ค์ฒฉ ๋ฆฌ์คํธ์ผ ๊ฒฝ์ฐ ํํํ
if isinstance(context_documents[0], list):
context_documents = [item for sublist in context_documents for item in sublist]
return [
{'role': 'system', 'content': system_prompt},
{'role': 'user', 'content': f"""
Context:
{" ".join(context_documents)}
Context๋ฅผ ์ฐธ์กฐํด์ ์๋ ์ง๋ฌธ์ ๋ตํด์ฃผ์ธ์.
Question:
{user_query}
Answer:
"""}
]
# OpenAI API๋ฅผ ํตํ ์๋ต ์์ฑ ํจ์
def generate_response(system_prompt, user_query, context_documents):
response = client.chat.completions.create(
model=GPT_MODEL,
messages=create_chat_prompt(system_prompt=system_prompt, user_query=user_query, context_documents=context_documents),
max_tokens=1500 # ํ์ํ ๋งํผ ํ ํฐ ์ ์กฐ์
)
return response.choices[0].message.content
# RAG ๊ธฐ๋ฐ ์๋ต ์์ฑ ํจ์
def chat_with_rag(system_prompt, user_query):
query_result = corpus_collection.query(query_texts=[user_query], n_results=8)
print(query_result['documents'])
response = generate_response(system_prompt, user_query, query_result['documents'])
return response
- extract_text_from_html : HTML ํ์ผ์์ ํ
์คํธ๋ฅผ ์ถ์ถ
- ์ค์ต์์๋ RAG ์ฐธ์กฐ ๋ฐ์ดํฐ๋ฅผ HTML ํ์ผ์์ ํ ์คํธ๋ฅผ ์ถ์ถํด์ ์ฌ์ฉํฉ๋๋ค.
- PDF ํ์ผ์ ์ฝ๊ฑฐ๋ ์ง์ ํ ์คํธ๋ฅผ ์ ๋ ฅํ๋ ๋ฑ์ ๋ค์ํ ๋ฐฉ๋ฒ์ ๊ณ ๋ คํด ๋ณผ ์ ์์ต๋๋ค.
- smart_chunk_splitter : ํ
์คํธ ๊ธธ์ด์ ๋ฐ๋ผ ๋ถํ ํ๋ ํจ์
- ์ค์ต์์๋ ๊ฐ์ฅ ๊ฐ๋จํ๊ฒ ํ ์คํธ์ ๊ธธ์ด์ ๋ฐ๋ผ chunk๋ฅผ ๋ถํ ํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ด์.
- ์ค์ ๋ก๋ ์กฐ๊ธ ๋ ์ค๋งํธํ ๋ฐฉ๋ฒ๋ค์ด ์กด์ฌํฉ๋๋ค.
- create_chat_prompt : ํ๋กฌํํธ ์์ฑ ํจ์
- generate_response : OpenAI API ์๋ต ์์ฑ
- chat_with_rag : RAG ๊ธฐ๋ฐ ์๋ต ์์ฑ ํจ์
โ ๋ชจ๋ธ ์ ์ธ
# models
EMBEDDING_MODEL = "text-embedding-3-small"
GPT_MODEL = "gpt-4o-mini"
# ChromaDB ์๋ฒ ๋ฉ ํจ์ ์ด๊ธฐํ
embedding_function = OpenAIEmbeddingFunction(
api_key=os.getenv("OPENAI_API_KEY"),
model_name=EMBEDDING_MODEL
)
client = OpenAI()
- RAG ๊ฒ์์ ํ์ฉํ ํ ์คํธ ์๋ฒ ๋ฉ ๋ชจ๋ธ๊ณผ ์ฌ์ฉํ LLM ์ ์ ์ธ
- ChromaDB์ ์๋ฒ ๋ฉ ํจ์ ์ด๊ธฐํ ์งํ
โ RAG ์ฐธ์กฐ ๋ฐ์ดํฐ ์ธํ
# HTML ํ์ผ ๊ฒฝ๋ก
html_path = "/content/drive/MyDrive/แแ
ฉแแ
ณ แแ
จแแ
ฆ/LLM/ref_docs/KBO_แ
แ
ตแแ
ณ.html"
# ํ
์คํธ ์ถ์ถ ๋ฐ ๋ถํ
text = extract_text_from_html(html_path)
chunks = smart_chunk_splitter(text, max_chunk_size=2000)
# ๋ถํ ๋ ํ
์คํธ ์ถ๋ ฅ
for i, chunk in enumerate(chunks):
print(f"Chunk {i+1}:\n{chunk}\n")
# ChromaDB ํด๋ผ์ด์ธํธ ๋ฐ ์ปฌ๋ ์
์ด๊ธฐํ
chroma_client = chromadb.Client() # ๋ฉ๋ชจ๋ฆฌ ๊ธฐ๋ฐ ์ํ๋ก ์คํ
corpus_collection = chroma_client.create_collection(
name='KBO',
embedding_function=embedding_function
)
# ๊ฐ ํ
์คํธ ์ฒญํฌ๋ฅผ ์ปฌ๋ ์
์ ์ถ๊ฐ
for i, chunk in enumerate(chunks):
corpus_collection.add(ids=[str(i)], documents=[chunk])
- HTML ํ์ผ์์ ํ ์คํธ ์ถ์ถํ๊ธฐ
- ์น ํ์ด์ง์์ ํ์ํ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด HTML ํ์ผ์์ ํ ์คํธ๋ฅผ ์ถ์ถ
- ์ด๋ BeautifulSoup ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด HTML ๊ตฌ์กฐ๋ฅผ ํ์ฑํ๊ณ , ํ ์คํธ๋ฅผ ์ถ์ถํด์.
- ํ ์คํธ ๋ถํ (Chunking)
- OpenAI ๋ชจ๋ธ์ ์ ๋ ฅ ๊ธธ์ด์ ์ ํ์ด ์๊ธฐ ๋๋ฌธ์, ๊ธด ํ ์คํธ๋ฅผ ์ ์ ํ๊ฒ ๋ถํ ํ๋ ๊ณผ์ ์ด ํ์ํด์.
- smart_chunk_splitter ํจ์๋ ๋ฌธ์ฅ์ ๊ธฐ์ค์ผ๋ก ํ ์คํธ๋ฅผ ์์ฐ์ค๋ฝ๊ฒ ๋๋๋ ์ญํ ์ ํด์.
- ChromaDB ์ค์ ๋ฐ ๋ฐ์ดํฐ ์ถ๊ฐ
- ChromaDB๋ RAG ๊ตฌํ์์ ์ค์ํ ์ญํ ์ ํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก, ๋ฌธ์ ์๋ฒ ๋ฉ์ ์ ์ฅํ๊ณ ๊ฒ์ํด์.
- ์ฌ๊ธฐ์๋ OpenAI์ ์๋ฒ ๋ฉ ๋ชจ๋ธ์ ์ฌ์ฉํด์.
- ChromaDB ํด๋ผ์ด์ธํธ๋ฅผ ๋ฉ๋ชจ๋ฆฌ ๊ธฐ๋ฐ ์ํ๋ก ์คํํ๊ณ , ์๋ฒ ๋ฉ ํจ์๋ฅผ ์ฌ์ฉํด ์ปฌ๋ ์ ์ ์์ฑํด์.
- ์์ ์ถ์ถํ๊ณ ๋ถํ ํ ํ ์คํธ๋ฅผ ChromaDB์ ์ถ๊ฐํด์.
โ RAG ๊ธฐ๋ฐ ์๋ต ์์ฑ
# system prompt ์ค์
system_prompt = "KBO ๊ด๋ จ ์ง๋ฌธ์ ์ฌ์ค ๊ธฐ๋ฐ์ผ๋ก ๋ต๋ณํ๋ ์ด์์คํดํธ์
๋๋ค."
# ์์ ์ง๋ฌธ์ ๋ํ RAG ์๋ต ์์ฑ
user_query = "KBO ๊ตฌ๋จ๋ณ ๋งค์ถ์ ๋ํด ๊ฐ๋ตํ๊ฒ ์์ฝํด์ค"
response = chat_with_rag(system_prompt, user_query)
print(response)
- ์์ ์ ์ธํ ํจ์๋ฅผ ์ฌ์ฉํ์ฌ, ์ฌ์ฉ์ ์ง๋ฌธ์ ๋ง๋ ๋ฌธ์๋ฅผ ChromaDB์์ ๊ฒ์ํ๊ณ , ํด๋น ๋ฌธ์์ ํจ๊ป OpenAI ๋ชจ๋ธ์ ์ฌ์ฉํด ๋ต๋ณ์ ์์ฑํ๋ ๊ณผ์ ์ด์์.
- ์ด ํจ์๋ฅผ ํธ์ถํ๋ฉด ChromaDB์์ ๊ด๋ จ ๋ฌธ์๋ฅผ ์ฐพ๊ณ , ๊ทธ ๋ฌธ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก OpenAI ๋ชจ๋ธ์ด ๋ต๋ณ์ ์์ฑํด์.
์ค์ ๋ก ์ค์ตํด ๋ณด๋ฉด ์์ ๊ฐ์ด ์ฐธ์กฐ ๋ฐ์ดํฐ์ ๊ธฐ๋ฐํ ์๋ต์ ์์ฑํ๋ ๊ฒ์ ํ์ธํ ์ ์์ด์!๐
์ด๋ ๊ฒ RAG ๊ธฐ๋ฅ๊ณผ ChromaDB๋ฅผ ์ด์ฉํด OpenAI ๋ชจ๋ธ์ ์ฑ๋ฅ์ ํ์ธต ๋ ํฅ์์ํฌ ์ ์์ด์. ์ฌ๋ฌ๋ถ๋ง์ ์ฐธ์กฐ ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค์ด์ ํน์ ๋ฐ์ดํฐ์ ํนํ๋ ์ฑ๋ด์ ๋ง๋ค์ด๋ณด๋ฉด ์ด๋จ๊น์!?
๋ ์์ธํ ๋ด์ฉ์ด๋ ์ถ๊ฐ์ ์ธ ์ฝ๋ ์ค๋ช ์ด ํ์ํ๋ฉด ์ธ์ ๋ ์ง ์๋ ค์ฃผ์ธ์! ๐