Ver3.0 ๐ค Ollama + LangChain์ผ๋ก ๋ก์ปฌ Q&A ๊ตฌ์ถํ๊ธฐ

๐ค Ollama + LangChain์ผ๋ก ๋ก์ปฌ Q&A ๊ตฌ์ถํ๊ธฐ
๋ด ์ปดํจํฐ์์ ๋์๊ฐ๋ ๋๋ง์ AI ๋น์ ๋ง๋ค๊ธฐ ํ๋ก์ ํธ! ๐
์๋
! ์ค๋์ ์ ๋ง ์ฌ๋ฏธ์๋ ์ฃผ์ ๋ฅผ ๊ฐ์ง๊ณ ์์ด. ๋ฐ๋ก Ollama์ LangChain์ ํ์ฉํด์ ๋ก์ปฌ ํ๊ฒฝ์์ Q&A ์์คํ
์ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ด์ผ. ๐
์์ฆ ChatGPT๋ Claude ๊ฐ์ AI ์๋น์ค๋ค์ด ์์ฒญ ์ธ๊ธฐ์์? ๊ทผ๋ฐ ์ด๋ฐ ์๋น์ค๋ค์ ์ธํฐ๋ท ์ฐ๊ฒฐ์ด ํ์ํ๊ณ , ๋ฏผ๊ฐํ ๋ฐ์ดํฐ๋ฅผ ์ธ๋ถ ์๋ฒ๋ก ๋ณด๋ด์ผ ํ๋ค๋ ๋จ์ ์ด ์์ด. ํ์ฌ ๋ด๋ถ ๋ฌธ์๋ ๊ฐ์ธ ์ ๋ณด๋ฅผ ๋ค๋ฃฐ ๋๋ ์ข ๊บผ๋ ค์ง๋ ๊ฒ ์ฌ์ค์ด์ง.
๊ทธ๋์ ๋ฑ์ฅํ ๊ฒ ๋ฐ๋ก ๋ก์ปฌ LLM(Large Language Model)์ด์ผ! ๋ด ์ปดํจํฐ์์ ์ง์ AI ๋ชจ๋ธ์ ๋๋ฆฌ๋ ๊ฑฐ์ง. ์ธํฐ๋ท ์์ด๋ ์๋ํ๊ณ , ๋ฐ์ดํฐ๊ฐ ์ธ๋ถ๋ก ๋๊ฐ์ง ์์ผ๋ ๋ณด์๋ ์๋ฒฝํด. ๊ฒ๋ค๊ฐ API ๋น์ฉ๋ ์ ๋ค์ด! ๐ฐ
๋ณธ๊ฒฉ์ ์ผ๋ก ์์ํ๊ธฐ ์ ์, ์ ์ด๋ฐ ์์คํ
์ด ํ์ํ์ง ํ๋ฒ ์๊ฐํด๋ณผ๊น? ๐ค
1. ๋ฐ์ดํฐ ๋ณด์๊ณผ ํ๋ผ์ด๋ฒ์ ๐
ํ์ฌ ๋ด๋ถ ๋ฌธ์, ๊ณ ๊ฐ ์ ๋ณด, ์๋ฃ ๊ธฐ๋ก ๊ฐ์ ๋ฏผ๊ฐํ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฐ ๋๋ ์ธ๋ถ API๋ก ๋ณด๋ด๋ ๊ฒ ๋ถ๋ด์ค๋ฌ์. ๋ก์ปฌ์์ ์ฒ๋ฆฌํ๋ฉด ๋ฐ์ดํฐ๊ฐ ์ ๋ ์ธ๋ถ๋ก ๋๊ฐ์ง ์์์ ์์ฌ์ด์ง.
2. ๋น์ฉ ์ ๊ฐ ๐ธ
OpenAI API๋ ๋ค๋ฅธ ํด๋ผ์ฐ๋ AI ์๋น์ค๋ ์ฌ์ฉ๋์ ๋ฐ๋ผ ๋น์ฉ์ด ์ฒญ๊ตฌ๋ผ. ์ฒ์์ ๊ด์ฐฎ์๋ฐ, ์ฌ์ฉ์๊ฐ ๋ง์์ง๋ฉด ๋น์ฉ์ด ๊ธฐํ๊ธ์์ ์ผ๋ก ๋์ด๋. ๋ก์ปฌ ์์คํ
์ ์ด๊ธฐ ํ๋์จ์ด ํฌ์๋ง ํ๋ฉด ์ถ๊ฐ ๋น์ฉ์ด ๊ฑฐ์ ์์ด!
3. ์ธํฐ๋ท ๋
๋ฆฝ์ฑ ๐
์ธํฐ๋ท ์ฐ๊ฒฐ์ด ๋ถ์์ ํ ํ๊ฒฝ์ด๋, ์คํ๋ผ์ธ ์ํฉ์์๋ AI ์์คํ
์ ์ฌ์ฉํ ์ ์์ด. ๋นํ๊ธฐ ์์์๋, ์งํ์ค์์๋ ์๋ํ์ง!
4. ์ปค์คํฐ๋ง์ด์ง ์์ ๋ ๐จ
๋ด๊ฐ ์ํ๋ ๋๋ก ๋ชจ๋ธ์ ์ ํํ๊ณ , ํ์ธํ๋ํ๊ณ , ์์คํ
์ ๊ตฌ์ฑํ ์ ์์ด. ํน์ ๋๋ฉ์ธ์ ํนํ๋ AI๋ฅผ ๋ง๋ค ์ ์๋ค๋ ๊ฑฐ์ง.
5. ์๋ต ์๋ ์ ์ด โก
์ข์ ํ๋์จ์ด๋ฅผ ๊ฐ์ถ๋ฉด ํด๋ผ์ฐ๋ API๋ณด๋ค ๋ ๋น ๋ฅธ ์๋ต์ ๋ฐ์ ์๋ ์์ด. ๋คํธ์ํฌ ์ง์ฐ์ด ์์ผ๋๊น!
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์๋ ์ด๋ฐ ๊ธฐ์ ์ด ํ์ฉ๋ ์ ์์ด. ์๋ฅผ ๋ค์ด, ์ฌ์ฉ์๋ค์ ์ฌ๋ฅ ๋งค์นญ์ ์ํ ๋ด๋ถ ์ถ์ฒ ์์คํ ์ด๋, ๊ณ ๊ฐ ๋ฌธ์ ์๋ ์๋ต ์์คํ ์ ๋ก์ปฌ์์ ๊ตฌ์ถํ๋ฉด ๊ฐ์ธ์ ๋ณด ๋ณดํธ์ ๋น์ฉ ์ ๊ฐ์ ๋์์ ๋ฌ์ฑํ ์ ์์ง! ๐ฏ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก Ollama์ ๋ํด ์์๋ณด์! Ollama๋ ๋ก์ปฌ ํ๊ฒฝ์์ ๋๊ท๋ชจ ์ธ์ด ๋ชจ๋ธ(LLM)์ ์ฝ๊ฒ ์คํํ ์ ์๊ฒ ํด์ฃผ๋ ์คํ์์ค ๋๊ตฌ์ผ. ๐
Docker๋ฅผ ์จ๋ณธ ์ ์์ด? Ollama๋ LLM ๋ฒ์ ์ Docker๋ผ๊ณ ์๊ฐํ๋ฉด ๋ผ. ๋ณต์กํ ์ค์ ์์ด ๊ฐ๋จํ ๋ช
๋ น์ด ๋ช ๊ฐ๋ก ์ต์ AI ๋ชจ๋ธ๋ค์ ๋ด ์ปดํจํฐ์์ ๋ฐ๋ก ์คํํ ์ ์์ด!
โข ๊ฐํธํ ์ค์น์ ์ฌ์ฉ
๋ณต์กํ Python ํ๊ฒฝ ์ค์ ์ด๋ CUDA ๋๋ผ์ด๋ฒ ์ค์ ์์ด, ์ค์น ํ์ผ ํ๋๋ก ๋! ๋ช ๋ น์ด๋ ์์ฒญ ์ง๊ด์ ์ด์ผ.
โข ๋ค์ํ ๋ชจ๋ธ ์ง์
Llama 2, Mistral, Phi, CodeLlama ๋ฑ ์ธ๊ธฐ ์๋ ์คํ์์ค ๋ชจ๋ธ๋ค์ ๋ชจ๋ ์ง์ํด. ๋ชจ๋ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ํ๋ ๊ฑธ ๊ณจ๋ผ ์ฐ๋ฉด ๋ผ.
โข ์๋ GPU ๊ฐ์
NVIDIA GPU๊ฐ ์์ผ๋ฉด ์๋์ผ๋ก ๊ฐ์งํด์ GPU ๊ฐ์์ ํ์ฉํด. ์์ด๋ CPU๋ก ๋์๊ฐ๊ธด ํ๋๋ฐ, ์๋๋ ์ข ๋๋ ค.
โข REST API ์ ๊ณต
HTTP API๋ฅผ ์ ๊ณตํด์ ๋ค๋ฅธ ํ๋ก๊ทธ๋จ๊ณผ ์ฝ๊ฒ ์ฐ๋ํ ์ ์์ด. ์ด๊ฒ LangChain๊ณผ ์ฐ๊ฒฐํ๋ ํต์ฌ์ด์ง!
โข ๋ชจ๋ธ ๊ด๋ฆฌ ๊ธฐ๋ฅ
๋ชจ๋ธ ๋ค์ด๋ก๋, ์ญ์ , ์ ๋ฐ์ดํธ๋ฅผ ๊ฐ๋จํ ๋ช ๋ น์ด๋ก ๊ด๋ฆฌํ ์ ์์ด. ๋ง์น ํจํค์ง ๋งค๋์ ์ฒ๋ผ!
Ollama ์ค์นํ๊ธฐ ๐
์ค์น๋ ์ ๋ง ๊ฐ๋จํด! ์ด์์ฒด์ ๋ณ๋ก ๋ฐฉ๋ฒ์ ์๋ ค์ค๊ฒ.
Windows:
Ollama ๊ณต์ ์น์ฌ์ดํธ(ollama.ai)์์ Windows ์ค์น ํ์ผ์ ๋ค์ด๋ก๋ํด์ ์คํํ๋ฉด ๋! ์ค์น๊ฐ ์๋ฃ๋๋ฉด ์๋์ผ๋ก ๋ฐฑ๊ทธ๋ผ์ด๋ ์๋น์ค๋ก ์คํ๋ผ.
macOS:
brew install ollama
๋๋ ๊ณต์ ์น์ฌ์ดํธ์์ .dmg ํ์ผ์ ๋ค์ด๋ก๋ํด์ ์ค์นํ ์๋ ์์ด.
Linux:
curl -fsSL https://ollama.ai/install.sh | sh
ํ ์ค์ด๋ฉด ์ค์น ์๋ฃ! ์ ๋ง ์ฝ์ง? ๐
์ค์น๊ฐ ๋๋๋ฉด ํฐ๋ฏธ๋์์ ๋ค์ ๋ช
๋ น์ด๋ก ์ ๋๋ก ์ค์น๋๋์ง ํ์ธํด๋ด:
ollama --version
๋ฒ์ ์ ๋ณด๊ฐ ๋์ค๋ฉด ์ฑ๊ณต์ด์ผ! ๐
์ด์ ์ค์ ๋ก ๋ชจ๋ธ์ ๋ค์ด๋ก๋ํ๊ณ ์คํํด๋ณด์! ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ๋ชจ๋ธ ์ค ํ๋์ธ llama2๋ฅผ ์ฌ์ฉํด๋ณผ๊ฒ.
ํฐ๋ฏธ๋์์ ๋ค์ ๋ช
๋ น์ด๋ฅผ ์
๋ ฅํด:
ollama run llama2
์ฒ์ ์คํํ๋ฉด ๋ชจ๋ธ์ ์๋์ผ๋ก ๋ค์ด๋ก๋ํด. ๋ชจ๋ธ ํฌ๊ธฐ๊ฐ ๋ช GB ๋๋๊น ์๊ฐ์ด ์ข ๊ฑธ๋ฆด ์ ์์ด. โ
๋ค์ด๋ก๋๊ฐ ์๋ฃ๋๋ฉด ๋ํํ ์ธํฐํ์ด์ค๊ฐ ๋ํ๋. ์ด์ ์ง์ ์ง๋ฌธ์ ์
๋ ฅํด๋ณผ ์ ์์ด!
>>> ์๋
? ๋๋ ๋๊ตฌ๋?
>>> Python์ผ๋ก ํผ๋ณด๋์น ์์ด์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์๋ ค์ค
>>> /bye (์ข
๋ฃํ๋ ค๋ฉด)
Ollama๋ ๊ฐ์ ๋ชจ๋ธ์ ๋ค์ํ ํฌ๊ธฐ ๋ฒ์ ์ ์ ๊ณตํด. ์๋ฅผ ๋ค์ด:
โข llama2:7b - 7์ต ํ๋ผ๋ฏธํฐ (์ฝ 4GB, ๋น ๋ฆ)
โข llama2:13b - 13์ต ํ๋ผ๋ฏธํฐ (์ฝ 8GB, ๊ท ํ)
โข llama2:70b - 70์ต ํ๋ผ๋ฏธํฐ (์ฝ 40GB, ๊ณ ์ฑ๋ฅ)
์ปดํจํฐ ์ฌ์์ ๋ง์ถฐ ์ ํํ๋ฉด ๋ผ. ์ผ๋ฐ์ ์ผ๋ก 7b๋ 13b ๋ชจ๋ธ์ด ๊ฐ์ธ PC์์ ์ฌ์ฉํ๊ธฐ ์ข์!
๋ค๋ฅธ ์ ์ฉํ Ollama ๋ช
๋ น์ด๋ค ๐
๋ชจ๋ธ ๊ด๋ฆฌ๋ฅผ ์ํ ๋ช
๋ น์ด๋ค์ ์์๋๋ฉด ํธํด:
# ์ค์น๋ ๋ชจ๋ธ ๋ชฉ๋ก ๋ณด๊ธฐ
ollama list
# ํน์ ๋ชจ๋ธ ๋ค์ด๋ก๋๋ง ํ๊ธฐ
ollama pull mistral
# ๋ชจ๋ธ ์ญ์ ํ๊ธฐ
ollama rm llama2
# ์คํ ์ค์ธ ๋ชจ๋ธ ํ์ธ
ollama ps
# ๋ชจ๋ธ ์ ๋ณด ๋ณด๊ธฐ
ollama show llama2
์, ์ด์ LangChain์ ๋ํด ์์๋ณผ ์ฐจ๋ก์ผ! LangChain์ LLM ์ ํ๋ฆฌ์ผ์ด์
์ ๊ฐ๋ฐํ๊ธฐ ์ํ ํ๋ ์์ํฌ์ผ. ๐๏ธ
LLM์ ๋จ์ํ ์คํํ๋ ๊ฒ์ ๋์ด์, ๋ณต์กํ AI ์ ํ๋ฆฌ์ผ์ด์
์ ๋ง๋ค ์ ์๊ฒ ํด์ฃผ๋ ๋๊ตฌ ๋ชจ์์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋ผ. ๋ง์น ๋ ๊ณ ๋ธ๋ก์ฒ๋ผ ์ฌ๋ฌ ์ปดํฌ๋ํธ๋ฅผ ์กฐํฉํด์ ์ํ๋ ์์คํ
์ ๋ง๋ค ์ ์์ด!
1. Chains (์ฒด์ธ)
์ฌ๋ฌ ์์ ์ ์์ฐจ์ ์ผ๋ก ์ฐ๊ฒฐํ๋ ๊ฐ๋ ์ด์ผ. ์๋ฅผ ๋ค์ด, "๋ฌธ์ ์ฝ๊ธฐ โ ์์ฝํ๊ธฐ โ ์ง๋ฌธ ๋ตํ๊ธฐ" ๊ฐ์ ํ๋ฆ์ ๋ง๋ค ์ ์์ด.
2. Prompts (ํ๋กฌํํธ)
LLM์๊ฒ ๋ณด๋ด๋ ๋ช ๋ น์ด ํ ํ๋ฆฟ์ด์ผ. ๋ณ์๋ฅผ ๋ฃ์ด์ ๋์ ์ผ๋ก ํ๋กฌํํธ๋ฅผ ์์ฑํ ์ ์์ด.
3. Memory (๋ฉ๋ชจ๋ฆฌ)
๋ํ ๋ด์ญ์ ๊ธฐ์ตํ๋ ๊ธฐ๋ฅ์ด์ผ. ์ด์ ๋ํ๋ฅผ ์ฐธ๊ณ ํด์ ๋งฅ๋ฝ ์๋ ๋ต๋ณ์ ํ ์ ์๊ฒ ํด์ค.
4. Indexes (์ธ๋ฑ์ค)
๋ฌธ์๋ฅผ ํจ์จ์ ์ผ๋ก ์ ์ฅํ๊ณ ๊ฒ์ํ๋ ๊ตฌ์กฐ์ผ. ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฐ๋๋ผ.
5. Agents (์์ด์ ํธ)
LLM์ด ์ค์ค๋ก ํ๋จํด์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๊ณ ์์ ์ ์ํํ๊ฒ ํ๋ ๊ณ ๊ธ ๊ธฐ๋ฅ์ด์ผ.
LangChain ์ค์นํ๊ธฐ ๐ฆ
Python ํ๊ฒฝ์์ pip๋ก ๊ฐ๋จํ๊ฒ ์ค์นํ ์ ์์ด:
pip install langchain
pip install langchain-community
pip install chromadb # ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค
pip install sentence-transformers # ์๋ฒ ๋ฉ ๋ชจ๋ธ
์ด์ ์ค๋น๊ฐ ๋ค ๋์ด! ๋ณธ๊ฒฉ์ ์ผ๋ก Q&A ์์คํ ์ ๋ง๋ค์ด๋ณด์! ๐
๋๋์ด ๋ณธ๊ฒฉ์ ์ผ๋ก ์์คํ
์ ๋ง๋ค์ด๋ณผ ์๊ฐ์ด์ผ! ๋จ๊ณ๋ณ๋ก ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฐ๋ผ์๋ด. ๐
Step 1: ํ๋ก์ ํธ ๊ตฌ์กฐ ๋ง๋ค๊ธฐ ๐
๋จผ์ ํ๋ก์ ํธ ํด๋๋ฅผ ๋ง๋ค๊ณ ํ์ํ ํ์ผ๋ค์ ์ค๋นํ์:
local-qa-system/
โโโ documents/ # ํ์ต์ํฌ ๋ฌธ์๋ค
โ โโโ doc1.txt
โ โโโ doc2.txt
โโโ vectorstore/ # ๋ฒกํฐ DB ์ ์ฅ ์์น
โโโ main.py # ๋ฉ์ธ ์คํ ํ์ผ
โโโ requirements.txt # ํ์ํ ํจํค์ง ๋ชฉ๋ก
requirements.txt ๋ด์ฉ:
langchain==0.1.0
langchain-community==0.0.10
chromadb==0.4.22
sentence-transformers==2.2.2
pypdf==3.17.4
python-dotenv==1.0.0
์ค์น๋ ๊ฐ๋จํด:
pip install -r requirements.txt
์ด์ main.py ํ์ผ์ ๋ง๋ค๊ณ ์ฝ๋๋ฅผ ์์ฑํด๋ณด์. ๋จผ์ ๋ฌธ์๋ฅผ ๋ก๋ํ๋ ๋ถ๋ถ๋ถํฐ!
from langchain.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
from langchain.llms import Ollama
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# 1. ๋ฌธ์ ๋ก๋
print("๐ ๋ฌธ์๋ฅผ ๋ก๋ํ๋ ์ค...")
loader = DirectoryLoader(
'./documents',
glob="**/*.txt",
loader_cls=TextLoader,
loader_kwargs={'encoding': 'utf-8'}
)
documents = loader.load()
print(f"โ
{len(documents)}๊ฐ์ ๋ฌธ์๋ฅผ ๋ก๋ํ์ต๋๋ค!")
# 2. ํ
์คํธ ๋ถํ
print("\nโ๏ธ ํ
์คํธ๋ฅผ ๋ถํ ํ๋ ์ค...")
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # ๊ฐ ์ฒญํฌ์ ํฌ๊ธฐ
chunk_overlap=200, # ์ฒญํฌ ๊ฐ ๊ฒน์น๋ ๋ถ๋ถ
length_function=len,
separators=["\n\n", "\n", " ", ""]
)
texts = text_splitter.split_documents(documents)
print(f"โ
{len(texts)}๊ฐ์ ํ
์คํธ ์ฒญํฌ๋ก ๋ถํ ํ์ต๋๋ค!")
LLM์ ํ ๋ฒ์ ์ฒ๋ฆฌํ ์ ์๋ ํ ํฐ ์๊ฐ ์ ํ๋์ด ์์ด. ๊ธด ๋ฌธ์๋ฅผ ํต์งธ๋ก ๋ฃ์ผ๋ฉด ์ฒ๋ฆฌ๊ฐ ์ ๋ผ. ๊ทธ๋์ ์ ์ ํ ํฌ๊ธฐ๋ก ๋๋ ์ฃผ๋ ๊ฑฐ์ผ!
chunk_overlap์ ์ค์ ํ๋ ์ด์ ๋ ๋ฌธ๋งฅ์ด ๋๊ธฐ์ง ์๋๋ก ํ๊ธฐ ์ํด์์ผ. ์ฒญํฌ ๊ฒฝ๊ณ์์ ์ค์ํ ์ ๋ณด๊ฐ ์๋ฆฌ๋ ๊ฑธ ๋ฐฉ์งํ์ง! ๐ฏ
์ด์ ํ ์คํธ๋ฅผ ๋ฒกํฐ๋ก ๋ณํํ๊ณ ์ ์ฅํด์ผ ํด. ์ด๊ฒ ๋ฐ๋ก RAG(Retrieval Augmented Generation)์ ํต์ฌ์ด์ผ!
# 3. ์๋ฒ ๋ฉ ๋ชจ๋ธ ๋ก๋
print("\n๐ข ์๋ฒ ๋ฉ ๋ชจ๋ธ์ ๋ก๋ํ๋ ์ค...")
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
model_kwargs={'device': 'cpu'} # GPU ์์ผ๋ฉด 'cuda'๋ก ๋ณ๊ฒฝ
)
print("โ
์๋ฒ ๋ฉ ๋ชจ๋ธ ๋ก๋ ์๋ฃ!")
# 4. ๋ฒกํฐ ์ ์ฅ์ ์์ฑ
print("\n๐พ ๋ฒกํฐ ์ ์ฅ์๋ฅผ ์์ฑํ๋ ์ค...")
vectorstore = Chroma.from_documents(
documents=texts,
embedding=embeddings,
persist_directory="./vectorstore"
)
vectorstore.persist()
print("โ
๋ฒกํฐ ์ ์ฅ์ ์์ฑ ์๋ฃ!")
์๋ฒ ๋ฉ์ด ๋ญ์ผ? ๐ค
์๋ฒ ๋ฉ์ ํ
์คํธ๋ฅผ ์ซ์ ๋ฒกํฐ๋ก ๋ณํํ๋ ๊ฑฐ์ผ. ์๋ฅผ ๋ค์ด, "๊ณ ์์ด"์ "๊ฐ์์ง"๋ ์๋ฏธ์ ๋น์ทํ๋๊น ๋ฒกํฐ ๊ณต๊ฐ์์๋ ๊ฐ๊น์ด ์์น์ ์๊ฒ ๋ผ. ์ด๋ ๊ฒ ํ๋ฉด ์๋ฏธ์ ์ผ๋ก ์ ์ฌํ ๋ฌธ์๋ฅผ ์ฐพ์ ์ ์์ด!
์ ์ฝ๋์์ ์ฌ์ฉํ paraphrase-multilingual-MiniLM-L12-v2 ๋ชจ๋ธ์ ํ๊ตญ์ด๋ฅผ ํฌํจํ ๋ค๊ตญ์ด๋ฅผ ์ง์ํ๋ ๊ฒฝ๋ ๋ชจ๋ธ์ด์ผ. ์ฑ๋ฅ๋ ๊ด์ฐฎ๊ณ ์๋๋ ๋นจ๋ผ์ ๋ก์ปฌ ํ๊ฒฝ์ ๋ฑ์ด์ง! ๐
์ด์ Ollama๋ก ์คํ ์ค์ธ LLM๊ณผ ์ฐ๊ฒฐํด๋ณด์!
# 5. Ollama LLM ์ด๊ธฐํ
print("\n๐ค Ollama LLM์ ์ด๊ธฐํํ๋ ์ค...")
llm = Ollama(
model="llama2",
temperature=0.7, # ์ฐฝ์์ฑ ์กฐ์ (0~1)
base_url="http://localhost:11434" # Ollama ์๋ฒ ์ฃผ์
)
print("โ
LLM ์ด๊ธฐํ ์๋ฃ!")
# 6. ํ๋กฌํํธ ํ
ํ๋ฆฟ ์ค์
template = """๋ค์ ์ปจํ
์คํธ๋ฅผ ์ฌ์ฉํ์ฌ ์ง๋ฌธ์ ๋ต๋ณํด์ฃผ์ธ์.
๋ต๋ณ์ ๋ชจ๋ฅด๋ฉด ๋ชจ๋ฅธ๋ค๊ณ ์์งํ ๋งํ๊ณ , ์ต์ง๋ก ๋ต๋ณ์ ๋ง๋ค์ง ๋ง์ธ์.
๋ต๋ณ์ ์ต๋ํ ์์ธํ๊ณ ์น์ ํ๊ฒ ํด์ฃผ์ธ์.
์ปจํ
์คํธ: {context}
์ง๋ฌธ: {question}
๋ต๋ณ:"""
QA_PROMPT = PromptTemplate(
template=template,
input_variables=["context", "question"]
)
Ollama๊ฐ ์คํ ์ค์ด์ด์ผ ํด! ํฐ๋ฏธ๋์์ ollama serve ๋ช ๋ น์ด๋ก ์๋ฒ๋ฅผ ์์ํ๊ฑฐ๋, ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์๋ ์คํ๋๋๋ก ์ค์ ํด์ผ ํด. ์ฐ๊ฒฐ์ด ์ ๋๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฑฐ์ผ!
์ด์ ๋ชจ๋ ์ปดํฌ๋ํธ๋ฅผ ์ฐ๊ฒฐํด์ ์ค์ ๋ก ์ง๋ฌธ์ ๋ต๋ณํ ์ ์๋ ์์คํ ์ ๋ง๋ค์!
# 7. RetrievalQA ์ฒด์ธ ์์ฑ
print("\nโ๏ธ Q&A ์ฒด์ธ์ ๊ตฌ์ฑํ๋ ์ค...")
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # ๊ฒ์๋ ๋ฌธ์๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง
retriever=vectorstore.as_retriever(
search_kwargs={"k": 3} # ์์ 3๊ฐ ๋ฌธ์ ๊ฒ์
),
return_source_documents=True, # ์ถ์ฒ ๋ฌธ์๋ ๋ฐํ
chain_type_kwargs={"prompt": QA_PROMPT}
)
print("โ
Q&A ์ฒด์ธ ๊ตฌ์ฑ ์๋ฃ!")
# 8. ์ง๋ฌธํ๊ธฐ!
def ask_question(question):
print(f"\nโ ์ง๋ฌธ: {question}")
print("๐ค ๋ต๋ณ์ ์์ฑํ๋ ์ค...\n")
result = qa_chain({"query": question})
print(f"๐ฌ ๋ต๋ณ: {result['result']}\n")
# ์ถ์ฒ ๋ฌธ์ ํ์
print("๐ ์ฐธ๊ณ ํ ๋ฌธ์:")
for i, doc in enumerate(result['source_documents'], 1):
print(f" {i}. {doc.metadata.get('source', '์ ์ ์์')}")
print()
# ์ค์ ์ฌ์ฉ ์์
if __name__ == "__main__":
print("\n" + "="*50)
print("๐ ๋ก์ปฌ Q&A ์์คํ
์ด ์ค๋น๋์์ต๋๋ค!")
print("="*50)
# ์์ ์ง๋ฌธ๋ค
ask_question("์ด ๋ฌธ์์ ์ฃผ์ ๋ด์ฉ์ ๋ฌด์์ธ๊ฐ์?")
ask_question("Python์์ ๋ฆฌ์คํธ์ ํํ์ ์ฐจ์ด๋?")
# ๋ํํ ๋ชจ๋
while True:
user_input = input("\n์ง๋ฌธ์ ์
๋ ฅํ์ธ์ (์ข
๋ฃ: 'quit'): ")
if user_input.lower() == 'quit':
print("๐ ํ๋ก๊ทธ๋จ์ ์ข
๋ฃํฉ๋๋ค!")
break
ask_question(user_input)
์์ฑ์ด์ผ! ๐ ์ด์ ์คํํด๋ณด์:
python main.py
์ฒ์ ์คํํ๋ฉด ์๋ฒ ๋ฉ ๋ชจ๋ธ์ ๋ค์ด๋ก๋ํ๊ณ , ๋ฌธ์๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐ ์๊ฐ์ด ์ข ๊ฑธ๋ ค. ํ์ง๋ง ํ ๋ฒ ๋ฒกํฐ ์ ์ฅ์๊ฐ ๋ง๋ค์ด์ง๋ฉด ๋ค์๋ถํฐ๋ ํจ์ฌ ๋นจ๋ผ!
๊ธฐ๋ณธ ์์คํ
์ ์์ฑํ์ด! ์ด์ ๋ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ๋ค์ ์ถ๊ฐํด๋ณด์. ๐
1. ๋ํ ๊ธฐ๋ก ์ ์งํ๊ธฐ (Memory) ๐ญ
์ด์ ๋ํ๋ฅผ ๊ธฐ์ตํ๊ฒ ๋ง๋ค๋ฉด ํจ์ฌ ์์ฐ์ค๋ฌ์ด ๋ํ๊ฐ ๊ฐ๋ฅํด:
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
# ๋ฉ๋ชจ๋ฆฌ ์ด๊ธฐํ
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True,
output_key="answer"
)
# ๋ํํ ์ฒด์ธ ์์ฑ
conversational_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
memory=memory,
return_source_documents=True,
verbose=True
)
# ์ฌ์ฉ ์์
def chat(question):
result = conversational_chain({"question": question})
return result['answer']
# ์ด์ ์ด์ ๋ํ๋ฅผ ๊ธฐ์ตํด!
print(chat("Python์ด ๋ญ์ผ?"))
print(chat("๊ทธ๋ผ ๊ทธ๊ฑธ ์ด๋์ ์ฌ์ฉํ ์ ์์ด?")) # "๊ทธ๊ฑธ"์ด Python์ ๊ฐ๋ฆฌํด!
LangChain์ ์ฌ๋ฌ ๋ฉ๋ชจ๋ฆฌ ํ์ ์ ์ ๊ณตํด:
โข ConversationBufferMemory: ๋ชจ๋ ๋ํ ์ ์ฅ (๊ฐ๋จํ์ง๋ง ๋ฉ๋ชจ๋ฆฌ ๋ง์ด ์)
โข ConversationBufferWindowMemory: ์ต๊ทผ N๊ฐ ๋ํ๋ง ์ ์ฅ
โข ConversationSummaryMemory: ๋ํ๋ฅผ ์์ฝํด์ ์ ์ฅ (๋ฉ๋ชจ๋ฆฌ ํจ์จ์ )
โข ConversationTokenBufferMemory: ํ ํฐ ์ ๊ธฐ์ค์ผ๋ก ์ ์ฅ
์ฉ๋์ ๋ง๊ฒ ์ ํํ๋ฉด ๋ผ! ๐ฏ
2. PDF ๋ฌธ์ ์ง์ํ๊ธฐ ๐
ํ
์คํธ ํ์ผ๋ง ๋ง๊ณ PDF๋ ์ฝ์ ์ ์๊ฒ ํด๋ณด์:
from langchain.document_loaders import PyPDFLoader, DirectoryLoader
# PDF ๋ก๋ ์ค์
pdf_loader = DirectoryLoader(
'./documents',
glob="**/*.pdf",
loader_cls=PyPDFLoader
)
# ํ
์คํธ์ PDF ํจ๊ป ๋ก๋
text_docs = text_loader.load()
pdf_docs = pdf_loader.load()
all_documents = text_docs + pdf_docs
print(f"์ด {len(all_documents)}๊ฐ ๋ฌธ์ ๋ก๋ ์๋ฃ!")
3. ์น ํ์ด์ง ํฌ๋กค๋งํ๊ธฐ ๐
์น์ฌ์ดํธ ๋ด์ฉ๋ ํ์ต์ํฌ ์ ์์ด:
from langchain.document_loaders import WebBaseLoader
# ์น ํ์ด์ง ๋ก๋
urls = [
"https://www.jaenung.net/about",
"https://www.jaenung.net/services"
]
web_loader = WebBaseLoader(urls)
web_docs = web_loader.load()
# ๊ธฐ์กด ๋ฌธ์์ ํฉ์น๊ธฐ
all_documents = text_docs + pdf_docs + web_docs
4. ๋ค์ํ ์๋ฒ ๋ฉ ๋ชจ๋ธ ๋น๊ต ๐
์๋ฒ ๋ฉ ๋ชจ๋ธ์ ๋ฐ๋ผ ์ฑ๋ฅ์ด ๋ฌ๋ผ์ ธ. ๋ช ๊ฐ์ง ์ถ์ฒ ๋ชจ๋ธ์ ์๊ฐํ ๊ฒ:
jhgan/ko-sroberta-multitask
ํ๊ตญ์ด ์ฑ๋ฅ์ด ๊ฐ์ฅ ์ข์! ํ๊ตญ์ด ๋ฌธ์๊ฐ ๋ง๋ค๋ฉด ์ด๊ฑธ ์ถ์ฒํด.
paraphrase-multilingual-MiniLM-L12-v2
๋น ๋ฅด๊ณ ๋ฉ๋ชจ๋ฆฌ ์ ๊ฒ ์ฐ๋ฉด์ ์ฌ๋ฌ ์ธ์ด ์ง์!
all-MiniLM-L6-v2
์์ด ์ ์ฉ์ด์ง๋ง ์์ฒญ ๋นจ๋ผ. ์ค์๊ฐ ์๋ต์ด ์ค์ํ๋ฉด ์ด๊ฑฐ!
intfloat/multilingual-e5-large
๋๋ฆฌ์ง๋ง ์ ํ๋๊ฐ ์ต๊ณ ! ํ์ง์ด ์ค์ํ๋ฉด ์ ํ.
๋ชจ๋ธ ๋ณ๊ฒฝ์ ๊ฐ๋จํด:
embeddings = HuggingFaceEmbeddings(
model_name="jhgan/ko-sroberta-multitask", # ๋ชจ๋ธ๋ช
๋ง ๋ฐ๊พธ๋ฉด ๋ผ!
model_kwargs={'device': 'cpu'}
)
5. ์คํธ๋ฆฌ๋ฐ ์๋ต ๊ตฌํํ๊ธฐ โก
ChatGPT์ฒ๋ผ ๋ต๋ณ์ด ์ค์๊ฐ์ผ๋ก ๋ํ๋๊ฒ ๋ง๋ค ์ ์์ด:
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
# ์คํธ๋ฆฌ๋ฐ ์ฝ๋ฐฑ ์ค์
llm = Ollama(
model="llama2",
callbacks=[StreamingStdOutCallbackHandler()],
temperature=0.7
)
# ์ด์ ๋ต๋ณ์ด ํ ๊ธ์์ฉ ์ค์๊ฐ์ผ๋ก ์ถ๋ ฅ๋ผ!
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=vectorstore.as_retriever(),
return_source_documents=True
)
์์คํ
์ ๋ง๋ค์์ผ๋ฉด ์ด์ ๋น ๋ฅด๊ณ ํจ์จ์ ์ผ๋ก ๋ง๋ค์ด์ผ์ง! ์ค์ ์ต์ ํ ํ์ ์๋ ค์ค๊ฒ. ๐
1. GPU ํ์ฉํ๊ธฐ ๐ฎ
NVIDIA GPU๊ฐ ์๋ค๋ฉด ๋ฐ๋์ ํ์ฉํด์ผ ํด! ์๋๊ฐ 10๋ฐฐ ์ด์ ๋นจ๋ผ์ง ์ ์์ด.
# ์๋ฒ ๋ฉ ๋ชจ๋ธ GPU ์ฌ์ฉ
embeddings = HuggingFaceEmbeddings(
model_name="jhgan/ko-sroberta-multitask",
model_kwargs={'device': 'cuda'} # CPU โ CUDA๋ก ๋ณ๊ฒฝ
)
# Ollama๋ ์๋์ผ๋ก GPU ๊ฐ์งํด์ ์ฌ์ฉํด
# ํ์ธํ๋ ค๋ฉด:
# nvidia-smi ๋ช
๋ น์ด๋ก GPU ์ฌ์ฉ๋ฅ ์ฒดํฌ!
GPU ๋ฉ๋ชจ๋ฆฌ๊ฐ ๋ถ์กฑํ๋ฉด ์์ ๋ชจ๋ธ์ ์ ํํ๊ฑฐ๋, ๋ฐฐ์น ํฌ๊ธฐ๋ฅผ ์ค์ฌ๋ด:
โข Llama2 70b โ 13b ๋๋ 7b๋ก ๋ณ๊ฒฝ
โข ์๋ฒ ๋ฉ ๋ชจ๋ธ๋ MiniLM ๊ฐ์ ๊ฒฝ๋ ๋ฒ์ ์ฌ์ฉ
โข ํ ๋ฒ์ ์ฒ๋ฆฌํ๋ ๋ฌธ์ ์ ์ค์ด๊ธฐ
2. ๋ฒกํฐ ์ ์ฅ์ ์ต์ ํ ๐พ
ChromaDB ๋ง๊ณ ๋ค๋ฅธ ๋ฒกํฐ DB๋ ์๋ํด๋ณผ ์ ์์ด:
# FAISS - ํ์ด์ค๋ถ์ด ๋ง๋ ๊ณ ์ ๋ฒกํฐ ๊ฒ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
from langchain.vectorstores import FAISS
vectorstore = FAISS.from_documents(
documents=texts,
embedding=embeddings
)
# ๋ก์ปฌ์ ์ ์ฅ
vectorstore.save_local("./faiss_index")
# ๋์ค์ ๋ถ๋ฌ์ค๊ธฐ
vectorstore = FAISS.load_local(
"./faiss_index",
embeddings
)
ChromaDB
โข ์ฅ์ : ์ค์น ์ฌ์, ์ฌ์ฉ ๊ฐ๋จ, ๋ฉํ๋ฐ์ดํฐ ๊ด๋ฆฌ ์ข์
โข ๋จ์ : ๋์ฉ๋ ๋ฐ์ดํฐ์์ ๋๋ฆผ
โข ์ถ์ฒ: ์๊ท๋ชจ ํ๋ก์ ํธ, ํ๋กํ ํ์
FAISS
โข ์ฅ์ : ์์ฒญ ๋น ๋ฆ, ๋์ฉ๋ ์ฒ๋ฆฌ ๊ฐ๋ฅ
โข ๋จ์ : ๋ฉํ๋ฐ์ดํฐ ๊ด๋ฆฌ ์ฝํจ, ์ค์ ๋ณต์ก
โข ์ถ์ฒ: ๋๊ท๋ชจ ํ๋ก๋์ , ์๋ ์ค์
Pinecone
โข ์ฅ์ : ํด๋ผ์ฐ๋ ๊ธฐ๋ฐ, ํ์ฅ์ฑ ์ข์
โข ๋จ์ : ์ ๋ฃ, ๋ก์ปฌ ์๋
โข ์ถ์ฒ: ์์ฉ ์๋น์ค
3. ์บ์ฑ ์ ๋ต ๐๏ธ
๊ฐ์ ์ง๋ฌธ์ด ๋ฐ๋ณต๋๋ฉด ๋งค๋ฒ ๊ณ์ฐํ์ง ๋ง๊ณ ์บ์๋ฅผ ํ์ฉํ์:
from functools import lru_cache
import hashlib
# ์ง๋ฌธ ํด์ ์์ฑ
def hash_question(question):
return hashlib.md5(question.encode()).hexdigest()
# ๊ฐ๋จํ ์บ์ ๋์
๋๋ฆฌ
answer_cache = {}
def ask_with_cache(question):
q_hash = hash_question(question)
# ์บ์์ ์์ผ๋ฉด ๋ฐ๋ก ๋ฐํ
if q_hash in answer_cache:
print("๐จ ์บ์์์ ๋ต๋ณ์ ๊ฐ์ ธ์์ต๋๋ค!")
return answer_cache[q_hash]
# ์์ผ๋ฉด ์๋ก ์์ฑ
result = qa_chain({"query": question})
answer_cache[q_hash] = result['result']
return result['result']
4. ์ฒญํฌ ํฌ๊ธฐ ์ต์ ํ โ๏ธ
์ฒญํฌ ํฌ๊ธฐ๋ ์ฑ๋ฅ์ ํฐ ์ํฅ์ ๋ฏธ์ณ. ์คํ์ ํตํด ์ต์ ๊ฐ์ ์ฐพ์์ผ ํด:
# ์คํ์ฉ ํจ์
def test_chunk_sizes():
sizes = [500, 1000, 1500, 2000]
overlaps = [50, 100, 200, 300]
for size in sizes:
for overlap in overlaps:
print(f"\nํ
์คํธ: chunk_size={size}, overlap={overlap}")
splitter = RecursiveCharacterTextSplitter(
chunk_size=size,
chunk_overlap=overlap
)
chunks = splitter.split_documents(documents)
print(f"์์ฑ๋ ์ฒญํฌ ์: {len(chunks)}")
# ์ฌ๊ธฐ์ ์ค์ ์ง๋ฌธ ํ
์คํธํ๊ณ ์ฑ๋ฅ ์ธก์
# ...
# ์ผ๋ฐ์ ์ธ ๊ถ์ฅ์ฌํญ:
# - ๊ธฐ์ ๋ฌธ์: 1000-1500 (์์ธํ ์ค๋ช
ํ์)
# - ๋ด์ค/๋ธ๋ก๊ทธ: 500-800 (์งง๊ณ ๊ฐ๊ฒฐ)
# - ์ฝ๋: 2000-3000 (์ปจํ
์คํธ ๋ง์ด ํ์)
# - overlap์ chunk_size์ 10-20% ์ ๋
5. ๋ฐฐ์น ์ฒ๋ฆฌ๋ก ์๋ ์ฌ๋ฆฌ๊ธฐ โก
์ฌ๋ฌ ๋ฌธ์๋ฅผ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋ฉด ๋ ๋น ๋ฅผ ์ ์์ด:
# ๋ฌธ์๋ฅผ ๋ฐฐ์น๋ก ๋๋ ์ ์ฒ๋ฆฌ
def process_documents_in_batches(documents, batch_size=10):
vectorstores = []
for i in range(0, len(documents), batch_size):
batch = documents[i:i+batch_size]
print(f"๋ฐฐ์น {i//batch_size + 1} ์ฒ๋ฆฌ ์ค...")
batch_texts = text_splitter.split_documents(batch)
batch_vectorstore = Chroma.from_documents(
documents=batch_texts,
embedding=embeddings,
persist_directory=f"./vectorstore_batch_{i}"
)
vectorstores.append(batch_vectorstore)
return vectorstores
์ค์ ์์ ์ฌ์ฉํ๋ ค๋ฉด ์์ ์ฑ์ด ์ค์ํด! ์๋ฌ ์ฒ๋ฆฌ์ ๋ณด์์ ์ ๊ฒฝ ์จ์ผ ํด. ๐
1. ์๋ฌ ์ฒ๋ฆฌ ์ถ๊ฐํ๊ธฐ โ ๏ธ
import logging
from typing import Optional
# ๋ก๊น
์ค์
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def safe_ask_question(question: str) -> Optional[str]:
"""์์ ํ ์ง๋ฌธ ์ฒ๋ฆฌ ํจ์"""
try:
# ์
๋ ฅ ๊ฒ์ฆ
if not question or len(question.strip()) == 0:
logger.warning("๋น ์ง๋ฌธ์ด ์
๋ ฅ๋์์ต๋๋ค.")
return "์ง๋ฌธ์ ์
๋ ฅํด์ฃผ์ธ์."
if len(question) > 1000:
logger.warning("์ง๋ฌธ์ด ๋๋ฌด ๊น๋๋ค.")
return "์ง๋ฌธ์ด ๋๋ฌด ๊น๋๋ค. 1000์ ์ด๋ด๋ก ์
๋ ฅํด์ฃผ์ธ์."
# ์ง๋ฌธ ์ฒ๋ฆฌ
logger.info(f"์ง๋ฌธ ์ฒ๋ฆฌ ์์: {question[:50]}...")
result = qa_chain({"query": question})
logger.info("์ง๋ฌธ ์ฒ๋ฆฌ ์๋ฃ")
return result['result']
except Exception as e:
logger.error(f"์๋ฌ ๋ฐ์: {str(e)}", exc_info=True)
return "์ฃ์กํฉ๋๋ค. ๋ต๋ณ ์์ฑ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์."
# ์ฐ๊ฒฐ ์ํ ํ์ธ
def check_ollama_connection():
"""Ollama ์๋ฒ ์ฐ๊ฒฐ ํ์ธ"""
try:
import requests
response = requests.get("http://localhost:11434/api/tags")
if response.status_code == 200:
logger.info("โ
Ollama ์๋ฒ ์ฐ๊ฒฐ ์ฑ๊ณต")
return True
else:
logger.error("โ Ollama ์๋ฒ ์๋ต ์ด์")
return False
except Exception as e:
logger.error(f"โ Ollama ์๋ฒ ์ฐ๊ฒฐ ์คํจ: {str(e)}")
return False
# ์์ ์ ์ฒดํฌ
if not check_ollama_connection():
print("Ollama ์๋ฒ๋ฅผ ๋จผ์ ์์ํด์ฃผ์ธ์!")
print("ํฐ๋ฏธ๋์์ 'ollama serve' ์คํ")
exit(1)
โข ์ ๋ ฅ ๊ฒ์ฆ: ์ ์์ ์ธ ํ๋กฌํํธ ์ธ์ ์ ๋ฐฉ์ง
โข ๋ฆฌ์์ค ์ ํ: ๋ฉ๋ชจ๋ฆฌ/CPU ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง
โข ํ์์์ ์ค์ : ๋๋ฌด ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ฒญ ์ฐจ๋จ
โข ๋ก๊ทธ ๊ด๋ฆฌ: ๋ฏผ๊ฐํ ์ ๋ณด ๋ก๊ทธ์ ๋จ๊ธฐ์ง ์๊ธฐ
2. ์ ๋ ฅ ํํฐ๋ง ๐
import re
def sanitize_input(text: str) -> str:
"""์
๋ ฅ ํ
์คํธ ์ ์ """
# ํน์๋ฌธ์ ์ ๊ฑฐ (ํ์ํ ๊ฒ๋ง ๋จ๊น)
text = re.sub(r'[^\w\s๊ฐ-ํฃ.,!?-]', '', text)
# ์ฐ์๋ ๊ณต๋ฐฑ ์ ๊ฑฐ
text = re.sub(r'\s+', ' ', text)
# ์๋ค ๊ณต๋ฐฑ ์ ๊ฑฐ
text = text.strip()
return text
def is_safe_question(question: str) -> bool:
"""์ง๋ฌธ ์์ ์ฑ ๊ฒ์ฌ"""
# ๊ธ์ง์ด ๋ชฉ๋ก
forbidden_words = ['ํดํน', 'ํฌ๋', '๋ถ๋ฒ']
question_lower = question.lower()
for word in forbidden_words:
if word in question_lower:
logger.warning(f"๊ธ์ง์ด ๊ฐ์ง: {word}")
return False
return True
# ์ฌ์ฉ ์์
def process_user_input(raw_input: str) -> str:
# 1. ์ ์
clean_input = sanitize_input(raw_input)
# 2. ์์ ์ฑ ๊ฒ์ฌ
if not is_safe_question(clean_input):
return "๋ถ์ ์ ํ ์ง๋ฌธ์
๋๋ค."
# 3. ์ฒ๋ฆฌ
return safe_ask_question(clean_input)
3. ๋ฆฌ์์ค ๋ชจ๋ํฐ๋ง ๐
import psutil
import time
class ResourceMonitor:
"""์์คํ
๋ฆฌ์์ค ๋ชจ๋ํฐ๋ง"""
def __init__(self):
self.start_time = time.time()
self.start_memory = psutil.Process().memory_info().rss / 1024 / 1024
def get_stats(self):
"""ํ์ฌ ๋ฆฌ์์ค ์ฌ์ฉ๋ ๋ฐํ"""
current_memory = psutil.Process().memory_info().rss / 1024 / 1024
elapsed_time = time.time() - self.start_time
return {
'memory_mb': current_memory,
'memory_increase_mb': current_memory - self.start_memory,
'cpu_percent': psutil.cpu_percent(interval=1),
'elapsed_seconds': elapsed_time
}
def print_stats(self):
"""ํต๊ณ ์ถ๋ ฅ"""
stats = self.get_stats()
print(f"\n๐ ๋ฆฌ์์ค ์ฌ์ฉ ํํฉ:")
print(f" ๋ฉ๋ชจ๋ฆฌ: {stats['memory_mb']:.1f} MB "
f"(+{stats['memory_increase_mb']:.1f} MB)")
print(f" CPU: {stats['cpu_percent']:.1f}%")
print(f" ์คํ ์๊ฐ: {stats['elapsed_seconds']:.1f}์ด\n")
# ์ฌ์ฉ ์์
monitor = ResourceMonitor()
# ์์
์ํ
result = qa_chain({"query": "์ง๋ฌธ"})
# ํต๊ณ ํ์ธ
monitor.print_stats()
ํฐ๋ฏธ๋์์๋ง ์ฐ๊ธฐ ์์ฝ์ง? ์น ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ค์ด๋ณด์! Gradio๋ Streamlit์ ์ฌ์ฉํ๋ฉด ์ ๋ง ์ฌ์. ๐
Gradio๋ก ๊ฐ๋จํ UI ๋ง๋ค๊ธฐ ๐จ
pip install gradio
import gradio as gr
def gradio_interface(question, history):
"""Gradio ์ธํฐํ์ด์ค ํจ์"""
try:
result = qa_chain({"query": question})
answer = result['result']
# ์ถ์ฒ ์ ๋ณด ์ถ๊ฐ
sources = "\n\n๐ ์ฐธ๊ณ ๋ฌธ์:\n"
for i, doc in enumerate(result['source_documents'], 1):
sources += f"{i}. {doc.metadata.get('source', '์ ์ ์์')}\n"
return answer + sources
except Exception as e:
return f"์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}"
# Gradio ์ธํฐํ์ด์ค ์์ฑ
demo = gr.ChatInterface(
fn=gradio_interface,
title="๐ค ๋ก์ปฌ Q&A ์์คํ
",
description="Ollama + LangChain ๊ธฐ๋ฐ ๋ก์ปฌ AI ์ฑ๋ด์
๋๋ค.",
examples=[
"์ด ๋ฌธ์์ ์ฃผ์ ๋ด์ฉ์ ๋ฌด์์ธ๊ฐ์?",
"Python์์ ๋ฆฌ์คํธ๋ฅผ ์ ๋ ฌํ๋ ๋ฐฉ๋ฒ์?",
"์ฌ๋ฅ๋ท์์ ์ด๋ค ์๋น์ค๋ฅผ ์ ๊ณตํ๋์?"
],
theme=gr.themes.Soft(),
retry_btn="๐ ๋ค์ ์๋",
undo_btn="โฉ๏ธ ๋๋๋ฆฌ๊ธฐ",
clear_btn="๐๏ธ ๋ํ ์ด๊ธฐํ"
)
# ์คํ
if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0", # ์ธ๋ถ ์ ์ ํ์ฉ
server_port=7860,
share=False # True๋ก ํ๋ฉด ๊ณต๊ฐ URL ์์ฑ
)
์คํํ๋ฉด ๋ธ๋ผ์ฐ์ ์์ http://localhost:7860์ผ๋ก ์ ์ํ ์ ์์ด! ์ ๋ง ๊ฐ๋จํ์ง? ๐
Streamlit์ผ๋ก ๋ ์ปค์คํ
ํ UI ๋ง๋ค๊ธฐ ๐ฏ
pip install streamlit
import streamlit as st
from datetime import datetime
# ํ์ด์ง ์ค์
st.set_page_config(
page_title="๋ก์ปฌ Q&A ์์คํ
",
page_icon="๐ค",
layout="wide"
)
# ์ ๋ชฉ
st.title("๐ค ๋ก์ปฌ Q&A ์์คํ
")
st.markdown("*Ollama + LangChain ๊ธฐ๋ฐ AI ์ฑ๋ด*")
# ์ฌ์ด๋๋ฐ
with st.sidebar:
st.header("โ๏ธ ์ค์ ")
# ๋ชจ๋ธ ์ ํ
model = st.selectbox(
"LLM ๋ชจ๋ธ",
["llama2", "mistral", "phi"]
)
# ์จ๋ ์ค์
temperature = st.slider(
"์ฐฝ์์ฑ (Temperature)",
min_value=0.0,
max_value=1.0,
value=0.7,
step=0.1
)
# ๊ฒ์ ๋ฌธ์ ์
k_docs = st.slider(
"์ฐธ๊ณ ๋ฌธ์ ์",
min_value=1,
max_value=5,
value=3
)
st.divider()
# ํต๊ณ
st.header("๐ ํต๊ณ")
if 'question_count' not in st.session_state:
st.session_state.question_count = 0
st.metric("์ด ์ง๋ฌธ ์", st.session_state.question_count)
# ๋ฉ์ธ ์์ญ
col1, col2 = st.columns([2, 1])
with col1:
st.header("๐ฌ ๋ํ")
# ๋ํ ๊ธฐ๋ก ์ด๊ธฐํ
if 'messages' not in st.session_state:
st.session_state.messages = []
# ๋ํ ๊ธฐ๋ก ํ์
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)
# AI ์๋ต ์์ฑ
with st.chat_message("assistant"):
with st.spinner("์๊ฐํ๋ ์ค..."):
try:
result = qa_chain({"query": prompt})
response = result['result']
st.markdown(response)
# ์ถ์ฒ ํ์
with st.expander("๐ ์ฐธ๊ณ ๋ฌธ์"):
for i, doc in enumerate(result['source_documents'], 1):
st.write(f"{i}. {doc.metadata.get('source', '์ ์ ์์')}")
# ์๋ต ์ ์ฅ
st.session_state.messages.append({
"role": "assistant",
"content": response
})
# ํต๊ณ ์
๋ฐ์ดํธ
st.session_state.question_count += 1
except Exception as e:
st.error(f"์ค๋ฅ ๋ฐ์: {str(e)}")
with col2:
st.header("๐ ๋์๋ง")
st.markdown("""
### ์ฌ์ฉ ๋ฐฉ๋ฒ
1. ์ผ์ชฝ์์ ๋ชจ๋ธ๊ณผ ์ค์ ์ ์กฐ์ ํ์ธ์
2. ์๋ ์
๋ ฅ์ฐฝ์ ์ง๋ฌธ์ ์
๋ ฅํ์ธ์
3. AI๊ฐ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด์ ๋ต๋ณํฉ๋๋ค
### ์์ ์ง๋ฌธ
- ์ด ๋ฌธ์์ ์ฃผ์ ๋ด์ฉ์?
- Python ๋ฆฌ์คํธ ์ฌ์ฉ๋ฒ์?
- ํน์ ๊ฐ๋
์ ๋ํด ์ค๋ช
ํด์ค
### ํ
- ๊ตฌ์ฒด์ ์ผ๋ก ์ง๋ฌธํ ์๋ก ์ข์ ๋ต๋ณ์ ๋ฐ์์
- ์ด์ ๋ํ๋ฅผ ์ฐธ๊ณ ํด์ ๋ต๋ณํด์
- ์ถ์ฒ ๋ฌธ์๋ฅผ ํ์ธํด๋ณด์ธ์
""")
st.divider()
# ์์คํ
์ํ
st.header("๐ง ์์คํ
์ํ")
if check_ollama_connection():
st.success("โ
Ollama ์ฐ๊ฒฐ๋จ")
else:
st.error("โ Ollama ์ฐ๊ฒฐ ์ ๋จ")
# ๋ฆฌ์์ค ์ฌ์ฉ๋
monitor = ResourceMonitor()
stats = monitor.get_stats()
st.metric("๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ", f"{stats['memory_mb']:.0f} MB")
st.metric("CPU ์ฌ์ฉ๋ฅ ", f"{stats['cpu_percent']:.1f}%")
์คํ์ ๊ฐ๋จํด:
streamlit run app.py
๋ธ๋ผ์ฐ์ ๊ฐ ์๋์ผ๋ก ์ด๋ฆฌ๋ฉด์ ๋ฉ์ง UI๊ฐ ๋ํ๋ ๊ฑฐ์ผ! ๐จ
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ด์ ์ค์ ๋ก ์ด๋ป๊ฒ ํ์ฉํ ์ ์๋์ง ์์๋ณด์! ๐ก
1. ํ์ฌ ๋ด๋ถ ๋ฌธ์ Q&A ์์คํ
๐ข
ํ์ฌ์ ๊ท์ , ๋งค๋ด์ผ, ํ๋ก์ ํธ ๋ฌธ์๋ฅผ ํ์ต์์ผ์ ์ง์๋ค์ด ์ฝ๊ฒ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์๊ฒ ํด:
# ํ์ฌ ๋ฌธ์ ๊ตฌ์กฐ
company_docs/
โโโ hr/ # ์ธ์ฌ ๊ท์
โ โโโ ํด๊ฐ๊ท์ .pdf
โ โโโ ๋ณต๋ฆฌํ์.pdf
โโโ tech/ # ๊ธฐ์ ๋ฌธ์
โ โโโ API๋ฌธ์.md
โ โโโ ๊ฐ๋ฐ๊ฐ์ด๋.md
โโโ projects/ # ํ๋ก์ ํธ ๋ฌธ์
โโโ ํ๋ก์ ํธA.docx
# ํนํ๋ ํ๋กฌํํธ
company_template = """๋น์ ์ ์ฐ๋ฆฌ ํ์ฌ์ AI ์ด์์คํดํธ์
๋๋ค.
์ง์๋ค์ ์ง๋ฌธ์ ํ์ฌ ๋ฌธ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ํํ๊ฒ ๋ต๋ณํด์ฃผ์ธ์.
๊ท์ ์ด๋ ์ ์ฑ
์ ๊ดํ ์ง๋ฌธ์ด๋ฉด ์ ํํ ์กฐํญ์ ์ธ์ฉํด์ฃผ์ธ์.
๊ธฐ์ ๋ฌธ์ ๊ด๋ จ ์ง๋ฌธ์ด๋ฉด ์ฝ๋ ์์๋ฅผ ํฌํจํด์ฃผ์ธ์.
์ปจํ
์คํธ: {context}
์ง๋ฌธ: {question}
๋ต๋ณ:"""
2. ๊ณ ๊ฐ ์ง์ ์ฑ๋ด ๐ฌ
FAQ, ์ ํ ๋งค๋ด์ผ, ์ด์ ๊ณ ๊ฐ ๋ฌธ์ ๋ด์ญ์ ํ์ต์์ผ์ ์๋ ์๋ต ์์คํ
์ ๋ง๋ค์ด:
# ๊ณ ๊ฐ ์ง์ ํนํ ์ค์
support_template = """๋น์ ์ ์น์ ํ ๊ณ ๊ฐ ์ง์ AI์
๋๋ค.
๊ณ ๊ฐ์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๊ฒ์ด ๋ชฉํ์
๋๋ค.
๋ต๋ณ ์ ๋ค์์ ์ง์ผ์ฃผ์ธ์:
1. ์น์ ํ๊ณ ๊ณต๊ฐํ๋ ํค์ผ๋ก ๋ต๋ณ
2. ๋จ๊ณ๋ณ๋ก ๋ช
ํํ๊ฒ ์ค๋ช
3. ์ถ๊ฐ ๋์์ด ํ์ํ์ง ๋ฌผ์ด๋ณด๊ธฐ
4. ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋ชจ๋ฅด๋ฉด ๋ด๋น์ ์ฐ๊ฒฐ ์๋ด
๊ณ ๊ฐ ์ง๋ฌธ: {question}
๊ด๋ จ ์ ๋ณด: {context}
๋ต๋ณ:"""
# ๊ฐ์ ๋ถ์ ์ถ๊ฐ
from transformers import pipeline
sentiment_analyzer = pipeline(
"sentiment-analysis",
model="beomi/kcbert-base"
)
def analyze_customer_mood(question):
"""๊ณ ๊ฐ ๊ฐ์ ๋ถ์"""
result = sentiment_analyzer(question)[0]
if result['label'] == 'negative' and result['score'] > 0.8:
return "๊ณ ๊ฐ์ด ๋ถ๋ง์กฑ ์ํ์
๋๋ค. ๋ ์ ์คํ๊ฒ ๋ต๋ณํ์ธ์."
return ""
3. ํ์ต ๋์ฐ๋ฏธ ๐
๊ต์ฌ, ๊ฐ์ ์๋ฃ, ์ฐธ๊ณ ๋ฌธํ์ ํ์ต์์ผ์ ํ์๋ค์ ๊ณต๋ถ๋ฅผ ๋์์ค:
# ๊ต์ก์ฉ ํ๋กฌํํธ
education_template = """๋น์ ์ ์น์ ํ ์ ์๋ AI์
๋๋ค.
ํ์์ด ๊ฐ๋
์ ์ดํดํ ์ ์๋๋ก ์ฝ๊ฒ ์ค๋ช
ํด์ฃผ์ธ์.
์ค๋ช
๋ฐฉ์:
1. ๋จผ์ ํต์ฌ ๊ฐ๋
์ ๊ฐ๋จํ ์ค๋ช
2. ์ค์ํ ์์๋ฅผ ๋ค์ด ์ค๋ช
3. ๋จ๊ณ๋ณ๋ก ์์ธํ ์ค๋ช
4. ์ดํด๋ฅผ ํ์ธํ๋ ์ง๋ฌธ ์ ์
ํ์ ์ง๋ฌธ: {question}
๊ต์ฌ ๋ด์ฉ: {context}
๋ต๋ณ:"""
# ๋์ด๋ ์กฐ์ ๊ธฐ๋ฅ
def adjust_difficulty(question, user_level):
"""์ฌ์ฉ์ ์์ค์ ๋ง์ถฐ ๋ต๋ณ ์กฐ์ """
if user_level == "beginner":
return "์ด๋ฑํ์๋ ์ดํดํ ์ ์๊ฒ ์์ฃผ ์ฝ๊ฒ ์ค๋ช
ํด์ฃผ์ธ์."
elif user_level == "intermediate":
return "์คํ์ ์์ค์ผ๋ก ์ค๋ช
ํด์ฃผ์ธ์."
else:
return "์ ๋ฌธ์ ์ธ ์ฉ์ด๋ฅผ ์ฌ์ฉํด์ ์์ธํ ์ค๋ช
ํด์ฃผ์ธ์."
4. ์ฝ๋ ๋ฌธ์ํ ๋์ฐ๋ฏธ ๐ป
ํ๋ก์ ํธ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ํ์ต์์ผ์ ๊ฐ๋ฐ์๋ค์ด ์ฝ๋๋ฅผ ์ดํดํ๊ณ ์ฌ์ฉํ๋๋ก ๋์์ค:
# ์ฝ๋ ํนํ ๋ก๋
from langchain.document_loaders import DirectoryLoader
code_loader = DirectoryLoader(
'./src',
glob="**/*.py",
loader_cls=TextLoader
)
# ์ฝ๋ ๋ถ์ ํ๋กฌํํธ
code_template = """๋น์ ์ ์ฝ๋ ๋ฆฌ๋ทฐ์ด AI์
๋๋ค.
๊ฐ๋ฐ์์ ์ง๋ฌธ์ ์ฝ๋๋ฅผ ๋ถ์ํด์ ๋ต๋ณํด์ฃผ์ธ์.
๋ต๋ณ ํ์:
1. ํด๋น ์ฝ๋์ ์์น์ ๋ชฉ์
2. ์ฝ๋ ๋์ ๋ฐฉ์ ์ค๋ช
3. ์ฌ์ฉ ์์ ์ ๊ณต
4. ์ฃผ์์ฌํญ์ด๋ ๊ฐ์ ์
์ง๋ฌธ: {question}
๊ด๋ จ ์ฝ๋: {context}
๋ต๋ณ:"""
# ์ฝ๋ ํ์ด๋ผ์ดํ
def format_code_response(response):
"""์ฝ๋ ๋ถ๋ถ์ ๋งํฌ๋ค์ด ํ์์ผ๋ก ๋ณํ"""
import re
# ์ฝ๋ ๋ธ๋ก ๊ฐ์ง ๋ฐ ํฌ๋งทํ
code_pattern = r'```(\w+)?\n(.*?)```'
formatted = re.sub(
code_pattern,
r'```\1\n\2```',
response,
flags=re.DOTALL
)
return formatted
5. ์ฌ๋ฅ๋ท ๋ง์ถค ํ์ฉ ๐ฏ
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์๋ ์ด๋ ๊ฒ ํ์ฉํ ์ ์์ด:
โข ์ฌ๋ฅ ๋งค์นญ ์ถ์ฒ ์์คํ
์ฌ์ฉ์์ ์๊ตฌ์ฌํญ๊ณผ ์ ๋ฌธ๊ฐ ํ๋กํ์ ๋ถ์ํด์ ์ต์ ์ ๋งค์นญ ์ ์
โข ์๋น์ค ์ค๋ช ์๋ ์์ฑ
์ ๋ฌธ๊ฐ๊ฐ ์ ๊ณตํ๋ ์๋น์ค๋ฅผ ์๋์ผ๋ก ์์ธํ๊ฒ ์ค๋ช
โข ๊ณ ๊ฐ ๋ฌธ์ ์๋ ์๋ต
ํ๋ซํผ ์ด์ฉ ๋ฐฉ๋ฒ, ๊ฒฐ์ , ํ๋ถ ๋ฑ ์์ฃผ ๋ฌป๋ ์ง๋ฌธ์ ์ฆ์ ๋ต๋ณ
โข ์ฝํ ์ธ ์ถ์ฒ
์ฌ์ฉ์ ๊ด์ฌ์ฌ์ ๋ง๋ ์ฌ๋ฅ์ด๋ ๊ฐ์ ์ถ์ฒ
โข ๋ด๋ถ ์ง์ ๊ด๋ฆฌ
์ด์ ๋งค๋ด์ผ, ์ ์ฑ ๋ฌธ์๋ฅผ ํ์ต์์ผ ์ง์๋ค์ ์ ๋ฌด ํจ์จ ํฅ์
# ์ฌ๋ฅ๋ท ํนํ ์์
jaenung_template = """๋น์ ์ ์ฌ๋ฅ๋ท์ AI ์ด์์คํดํธ์
๋๋ค.
์ฌ๋ฅ๋ท์ ๋ค์ํ ์ฌ๋ฅ์ ๊ฑฐ๋ํ๋ ํ๋ซํผ์
๋๋ค.
์ฌ์ฉ์ ์ง๋ฌธ์ ๋ค์ ์ ๋ณด๋ฅผ ํ์ฉํด์ ๋ต๋ณํ์ธ์:
- ํ๋ซํผ ์ด์ฉ ๋ฐฉ๋ฒ
- ์ฌ๋ฅ ์นดํ
๊ณ ๋ฆฌ ์ ๋ณด
- ์ ๋ฌธ๊ฐ ํ๋กํ
- ์ด์ฉ ํ๊ธฐ
์ง๋ฌธ: {question}
๊ด๋ จ ์ ๋ณด: {context}
์น์ ํ๊ณ ๋์์ด ๋๋ ๋ต๋ณ:"""
๋ก์ปฌ AI ๊ธฐ์ ์ ์ ๋ง ๋น ๋ฅด๊ฒ ๋ฐ์ ํ๊ณ ์์ด! ์์ผ๋ก ์ด๋ป๊ฒ ๋ณํ ์ง ํ๋ฒ ์ดํด๋ณด์. ๐
1. ๋ชจ๋ธ ๊ฒฝ๋ํ ๊ธฐ์ ๐
์์ผ๋ก๋ ๋ ์๊ณ ๋น ๋ฅธ ๋ชจ๋ธ๋ค์ด ๋์ฌ ๊ฑฐ์ผ. ์ด๋ฏธ ์งํ ์ค์ธ ๊ธฐ์ ๋ค:
32๋นํธ โ 8๋นํธ ๋๋ 4๋นํธ๋ก ๋ณํํด์ ๋ชจ๋ธ ํฌ๊ธฐ๋ฅผ 1/4~1/8๋ก ์ค์ฌ. ์ฑ๋ฅ์ ๊ฑฐ์ ๊ทธ๋๋ก!
ํ๋ฃจ๋ (Pruning)
์ค์ํ์ง ์์ ๋ด๋ฐ์ ์ ๊ฑฐํด์ ๋ชจ๋ธ์ ๊ฐ๋ณ๊ฒ ๋ง๋ค์ด. ๋ง์น ๋๋ฌด ๊ฐ์ง์น๊ธฐ์ฒ๋ผ!
์ง์ ์ฆ๋ฅ (Knowledge Distillation)
ํฐ ๋ชจ๋ธ(์ ์๋)์ ์ง์์ ์์ ๋ชจ๋ธ(ํ์)์๊ฒ ์ ๋ฌํด. ์์ง๋ง ๋๋ํ ๋ชจ๋ธ ํ์!
LoRA (Low-Rank Adaptation)
์ ์ฒด ๋ชจ๋ธ์ ํ์ธํ๋ํ์ง ์๊ณ ์์ ์ด๋ํฐ๋ง ํ์ต. ๋ฉ๋ชจ๋ฆฌ ํจ์จ ์ต๊ณ !
2. ๋ฉํฐ๋ชจ๋ฌ AI ๐จ
ํ
์คํธ๋ง์ด ์๋๋ผ ์ด๋ฏธ์ง, ์์ฑ, ๋น๋์ค๊น์ง ์ฒ๋ฆฌํ๋ ํตํฉ AI๊ฐ ๋์ฌ ๊ฑฐ์ผ:
# ๋ฏธ๋์ ๋ฉํฐ๋ชจ๋ฌ Q&A (๊ฐ๋
์ ์์)
from langchain.document_loaders import ImageLoader, AudioLoader
# ์ด๋ฏธ์ง ๋ฌธ์ ๋ก๋
image_loader = ImageLoader('./images')
image_docs = image_loader.load()
# ์์ฑ ๋ฌธ์ ๋ก๋
audio_loader = AudioLoader('./audio')
audio_docs = audio_loader.load()
# ๋ชจ๋ ๋ชจ๋ฌ๋ฆฌํฐ ํตํฉ
multimodal_vectorstore = create_multimodal_store(
text_docs + image_docs + audio_docs
)
# ์ง๋ฌธ: "์ด ์ ํ ์ฌ์ง์์ ์ค๋ช
ํ๋ ๊ธฐ๋ฅ์?"
# AI๊ฐ ์ด๋ฏธ์ง๋ฅผ ๋ณด๊ณ ํ
์คํธ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด์ ๋ต๋ณ!
3. ๊ฐ์ธํ ๋ฐ ์ ์ํ AI ๐ฏ
์ฌ์ฉ์์ ํจํด์ ํ์ตํด์ ์ ์ ๋ ๋์ ๋ต๋ณ์ ์ ๊ณตํ๋ AI:
# ์ฌ์ฉ์ ํ๋กํ ๊ธฐ๋ฐ ๋ต๋ณ
class PersonalizedQA:
def __init__(self):
self.user_profiles = {}
def learn_from_feedback(self, user_id, question, answer, rating):
"""์ฌ์ฉ์ ํผ๋๋ฐฑ์ผ๋ก ํ์ต"""
if user_id not in self.user_profiles:
self.user_profiles[user_id] = {
'preferences': {},
'history': []
}
# ์ ํธ๋ ์
๋ฐ์ดํธ
self.user_profiles[user_id]['history'].append({
'question': question,
'answer': answer,
'rating': rating
})
# ํจํด ๋ถ์
self.analyze_preferences(user_id)
def get_personalized_answer(self, user_id, question):
"""๊ฐ์ธํ๋ ๋ต๋ณ ์์ฑ"""
profile = self.user_profiles.get(user_id, {})
# ์ฌ์ฉ์ ์ ํธ๋๋ฅผ ๋ฐ์ํ ํ๋กฌํํธ ์กฐ์
if profile.get('prefers_detailed'):
style = "๋งค์ฐ ์์ธํ๊ฒ"
elif profile.get('prefers_concise'):
style = "๊ฐ๊ฒฐํ๊ฒ"
else:
style = "์ ์ ํ๊ฒ"
# ๋ง์ถค ๋ต๋ณ ์์ฑ
return self.generate_answer(question, style)
4. ์ฐํฉ ํ์ต (Federated Learning) ๐ค
์ฌ๋ฌ ๋ก์ปฌ AI๊ฐ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ์ง ์๊ณ ๋ ํจ๊ป ํ์ตํ๋ ๊ธฐ์ :
โข ๊ฐ์ธ ๋ฐ์ดํฐ๋ ๋ก์ปฌ์ ์ ์ง
โข ์ฌ๋ฌ ์ฌ์ฉ์์ ์ง์์ ํตํฉ
โข ํ๋ผ์ด๋ฒ์ ๋ณดํธํ๋ฉด์ ์ฑ๋ฅ ํฅ์
โข ๊ฐ์์ ํนํ๋ ์ง์ ๊ณต์
์๋ฅผ ๋ค์ด, ๋ณ์๋ค์ด ํ์ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ์ง ์๊ณ ๋ ๋ ๋์ ์ง๋จ AI๋ฅผ ํจ๊ป ๋ง๋ค ์ ์์ด!
5. ์ฃ์ง AI์ IoT ํตํฉ ๐
์ค๋งํธํฐ, ์ค๋งํธ์์น, IoT ๊ธฐ๊ธฐ์์ ์ง์ AI๊ฐ ์คํ๋๋ ์๋:
# ์ฃ์ง ๋๋ฐ์ด์ค์ฉ ์ด๊ฒฝ๋ ๋ชจ๋ธ
from langchain.llms import TinyLlama
# ๋ชจ๋ฐ์ผ ์ต์ ํ ๋ชจ๋ธ
mobile_llm = TinyLlama(
model="tinyllama-1.1b", # 1GB ๋ฏธ๋ง!
quantization="4bit", # ๊ทน๋๋ก ์์ถ
device="mobile"
)
# ์คํ๋ผ์ธ์์๋ ์๋
offline_qa = create_offline_qa_system(
llm=mobile_llm,
vectorstore=lightweight_store,
cache_size="100MB" # ์ ํ๋ ๋ฉ๋ชจ๋ฆฌ
)
6. ์๋ํ๋ ํ์ธํ๋ ๐ง
์ฌ์ฉ์๊ฐ ์ฝ๊ฒ ์์ ์ ๋ฐ์ดํฐ๋ก ๋ชจ๋ธ์ ์ปค์คํฐ๋ง์ด์งํ ์ ์๊ฒ:
# ์๋ ํ์ธํ๋ (๊ฐ๋
์ ์์)
from langchain.finetuning import AutoFineTuner
# ๊ฐ๋จํ ์ค์ ๋ง์ผ๋ก ํ์ธํ๋
tuner = AutoFineTuner(
base_model="llama2",
training_data="./my_documents",
task="question-answering",
auto_optimize=True # ์๋์ผ๋ก ์ต์ ์ค์ ์ฐพ๊ธฐ
)
# ํ์ต ์์
custom_model = tuner.train(
epochs=3,
batch_size="auto",
learning_rate="auto"
)
# ๋ฐ๋ก ์ฌ์ฉ ๊ฐ๋ฅ!
qa_chain = create_qa_chain(llm=custom_model)
๋ง์ง๋ง์ผ๋ก ์ค์ ์์ ์ ๋ง ์ ์ฉํ ํ๋ค์ ์ ๋ฆฌํด์ค๊ฒ! ์ด๊ฑด ๊ฒฝํ์์ ๋์จ ์ง์ง ๋
ธํ์ฐ์ผ. ๐
1. ๋ฌธ์ ์ ์ฒ๋ฆฌ๊ฐ ํต์ฌ์ด์ผ! ๐
โ ๋ถํ์ํ ๋ด์ฉ ์ ๊ฑฐ
ํค๋, ํธํฐ, ํ์ด์ง ๋ฒํธ, ๊ด๊ณ ๋ฑ ์ ๊ฑฐ
โ ์ผ๊ด๋ ํฌ๋งท ์ ์ง
๋งํฌ๋ค์ด์ด๋ ์ผ๋ฐ ํ ์คํธ๋ก ํต์ผ
โ ๋ฉํ๋ฐ์ดํฐ ์ถ๊ฐ
๋ฌธ์ ์ถ์ฒ, ์์ฑ์ผ, ์นดํ ๊ณ ๋ฆฌ ๋ฑ ์ ๋ณด ํฌํจ
โ ์ค๋ณต ์ ๊ฑฐ
๊ฐ์ ๋ด์ฉ์ด ์ฌ๋ฌ ๋ฒ ๋์ค์ง ์๋๋ก
โ ๊ตฌ์กฐํ
์ ๋ชฉ, ์์ ๋ชฉ, ๋ณธ๋ฌธ์ ๋ช ํํ ๊ตฌ๋ถ
# ๋ฌธ์ ์ ์ฒ๋ฆฌ ์์
def preprocess_document(text):
"""๋ฌธ์ ์ ์ฒ๋ฆฌ ํจ์"""
# 1. ๊ณต๋ฐฑ ์ ๋ฆฌ
text = re.sub(r'\s+', ' ', text)
# 2. ํน์๋ฌธ์ ์ ๋ฆฌ
text = re.sub(r'[^\w\s๊ฐ-ํฃ.,!?-]', '', text)
# 3. ํ์ด์ง ๋ฒํธ ์ ๊ฑฐ
text = re.sub(r'ํ์ด์ง\s*\d+', '', text)
# 4. ์ค๋ณต ์ค๋ฐ๊ฟ ์ ๊ฑฐ
text = re.sub(r'\n{3,}', '\n\n', text)
return text.strip()
# ๋ฉํ๋ฐ์ดํฐ ์ถ๊ฐ
def add_metadata(doc, source, category, date):
"""๋ฌธ์์ ๋ฉํ๋ฐ์ดํฐ ์ถ๊ฐ"""
doc.metadata = {
'source': source,
'category': category,
'date': date,
'processed_at': datetime.now().isoformat()
}
return doc
2. ํ๋กฌํํธ ์์ง๋์ด๋ง ๋ง์คํฐํ๊ธฐ ๐ฏ
์ข์ ํ๋กฌํํธ๊ฐ ์ข์ ๋ต๋ณ์ ๋ง๋ค์ด:
# ๋์ ํ๋กฌํํธ โ
bad_prompt = "์ง๋ฌธ์ ๋ตํด์ค: {question}"
# ์ข์ ํ๋กฌํํธ โ
good_prompt = """๋น์ ์ ์ ๋ฌธ์ ์ด๊ณ ์น์ ํ AI ์ด์์คํดํธ์
๋๋ค.
์ญํ : {role}
๋ชฉํ: ์ฌ์ฉ์์ ์ง๋ฌธ์ ์ ํํ๊ณ ๋์์ด ๋๋ ๋ต๋ณ ์ ๊ณต
๋ต๋ณ ๊ฐ์ด๋๋ผ์ธ:
1. ์ ๊ณต๋ ์ปจํ
์คํธ๋ฅผ ์ฐ์ ์ ์ผ๋ก ์ฐธ๊ณ ํ์ธ์
2. ํ์คํ์ง ์์ผ๋ฉด ์ถ์ธกํ์ง ๋ง๊ณ ๋ชจ๋ฅธ๋ค๊ณ ํ์ธ์
3. ๋ต๋ณ์ {style} ์คํ์ผ๋ก ์์ฑํ์ธ์
4. ํ์ํ๋ฉด ์์๋ฅผ ๋ค์ด ์ค๋ช
ํ์ธ์
5. ์ถ๊ฐ ์ง๋ฌธ์ด ์๋์ง ๋ฌผ์ด๋ณด์ธ์
์ปจํ
์คํธ:
{context}
์ฌ์ฉ์ ์ง๋ฌธ:
{question}
๋ต๋ณ:"""
# ๋์ ํ๋กฌํํธ ์์ฑ
def create_dynamic_prompt(role, style, context, question):
return good_prompt.format(
role=role,
style=style,
context=context,
question=question
)
3. ํ ์คํธ์ ํ๊ฐ๋ฅผ ์๋ํํ์ ๐งช
# ์๋ ํ๊ฐ ์์คํ
class QAEvaluator:
def __init__(self, test_questions):
self.test_questions = test_questions
self.results = []
def evaluate(self, qa_chain):
"""Q&A ์์คํ
ํ๊ฐ"""
print("๐งช ํ๊ฐ๋ฅผ ์์ํฉ๋๋ค...\n")
for i, test in enumerate(self.test_questions, 1):
question = test['question']
expected = test.get('expected_keywords', [])
print(f"ํ
์คํธ {i}/{len(self.test_questions)}: {question}")
# ๋ต๋ณ ์์ฑ
start_time = time.time()
result = qa_chain({"query": question})
elapsed = time.time() - start_time
answer = result['result']
# ํ๊ฐ ์งํ
score = self.calculate_score(answer, expected)
self.results.append({
'question': question,
'answer': answer,
'score': score,
'time': elapsed,
'sources': len(result.get('source_documents', []))
})
print(f" ์ ์: {score:.2f}/10")
print(f" ์๊ฐ: {elapsed:.2f}์ด\n")
self.print_summary()
def calculate_score(self, answer, expected_keywords):
"""๋ต๋ณ ์ ์ ๊ณ์ฐ"""
score = 5.0 # ๊ธฐ๋ณธ ์ ์
# ํค์๋ ํฌํจ ์ฌ๋ถ
for keyword in expected_keywords:
if keyword in answer:
score += 1.0
# ๋ต๋ณ ๊ธธ์ด (๋๋ฌด ์งง๊ฑฐ๋ ๊ธธ๋ฉด ๊ฐ์ )
if len(answer) < 50:
score -= 2.0
elif len(answer) > 1000:
score -= 1.0
return min(10.0, max(0.0, score))
def print_summary(self):
"""ํ๊ฐ ๊ฒฐ๊ณผ ์์ฝ"""
avg_score = sum(r['score'] for r in self.results) / len(self.results)
avg_time = sum(r['time'] for r in self.results) / len(self.results)
print("\n" + "="*50)
print("๐ ํ๊ฐ ๊ฒฐ๊ณผ ์์ฝ")
print("="*50)
print(f"ํ๊ท ์ ์: {avg_score:.2f}/10")
print(f"ํ๊ท ์๋ต ์๊ฐ: {avg_time:.2f}์ด")
print(f"์ด ํ
์คํธ: {len(self.results)}๊ฐ")
print("="*50)
# ์ฌ์ฉ ์์
test_questions = [
{
'question': 'Python์์ ๋ฆฌ์คํธ๋ฅผ ์ ๋ ฌํ๋ ๋ฐฉ๋ฒ์?',
'expected_keywords': ['sort', 'sorted', '์ ๋ ฌ']
},
{
'question': '์ฌ๋ฅ๋ท์์ ์ ๊ณตํ๋ ์๋น์ค๋?',
'expected_keywords': ['์ฌ๋ฅ', '๊ฑฐ๋', 'ํ๋ซํผ']
}
]
evaluator = QAEvaluator(test_questions)
evaluator.evaluate(qa_chain)
4. ๋ฒ์ ๊ด๋ฆฌ์ ๋กค๋ฐฑ ์ ๋ต ๐
# ๋ชจ๋ธ ๋ฒ์ ๊ด๋ฆฌ
class ModelVersionManager:
def __init__(self, base_path="./models"):
self.base_path = base_path
self.versions = {}
def save_version(self, name, qa_chain, metadata):
"""์ ๋ฒ์ ์ ์ฅ"""
version_id = datetime.now().strftime("%Y%m%d_%H%M%S")
version_path = f"{self.base_path}/{name}/{version_id}"
os.makedirs(version_path, exist_ok=True)
# ์ค์ ์ ์ฅ
config = {
'version_id': version_id,
'timestamp': datetime.now().isoformat(),
'metadata': metadata
}
with open(f"{version_path}/config.json", 'w') as f:
json.dump(config, f, indent=2)
# ๋ฒกํฐ ์ ์ฅ์ ์ ์ฅ
qa_chain.vectorstore.persist_to(version_path)
print(f"โ
๋ฒ์ {version_id} ์ ์ฅ ์๋ฃ!")
return version_id
def load_version(self, name, version_id):
"""ํน์ ๋ฒ์ ๋ถ๋ฌ์ค๊ธฐ"""
version_path = f"{self.base_path}/{name}/{version_id}"
if not os.path.exists(version_path):
raise ValueError(f"๋ฒ์ {version_id}๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.")
# ์ค์ ๋ก๋
with open(f"{version_path}/config.json", 'r') as f:
config = json.load(f)
print(f"๐ ๋ฒ์ {version_id} ๋ก๋ ์ค...")
# ๋ฒกํฐ ์ ์ฅ์ ๋ก๋
vectorstore = Chroma(
persist_directory=version_path,
embedding_function=embeddings
)
return vectorstore, config
def rollback(self, name, steps=1):
"""์ด์ ๋ฒ์ ์ผ๋ก ๋กค๋ฐฑ"""
versions = self.list_versions(name)
if len(versions) < steps + 1:
raise ValueError("๋กค๋ฐฑํ ๋ฒ์ ์ด ์์ต๋๋ค.")
target_version = versions[-(steps+1)]
print(f"๐ ๋ฒ์ {target_version}๋ก ๋กค๋ฐฑํฉ๋๋ค...")
return self.load_version(name, target_version)
5. ๋ชจ๋ํฐ๋ง๊ณผ ๋ก๊น ๐
# ์์ธ ๋ก๊น
์์คํ
import logging
from logging.handlers import RotatingFileHandler
def setup_logging():
"""๋ก๊น
์ค์ """
# ๋ก๊ฑฐ ์์ฑ
logger = logging.getLogger('LocalQA')
logger.setLevel(logging.DEBUG)
# ํ์ผ ํธ๋ค๋ฌ (์๋ ๋กํ
์ด์
)
file_handler = RotatingFileHandler(
'qa_system.log',
maxBytes=10*1024*1024, # 10MB
backupCount=5
)
file_handler.setLevel(logging.DEBUG)
# ์ฝ์ ํธ๋ค๋ฌ
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# ํฌ๋งท ์ค์
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
logger = setup_logging()
# ์ฌ์ฉ ์์
def logged_ask_question(question):
"""๋ก๊น
์ด ํฌํจ๋ ์ง๋ฌธ ์ฒ๋ฆฌ"""
logger.info(f"์ง๋ฌธ ์์ : {question}")
try:
start_time = time.time()
result = qa_chain({"query": question})
elapsed = time.time() - start_time
logger.info(f"๋ต๋ณ ์์ฑ ์๋ฃ (์์์๊ฐ: {elapsed:.2f}์ด)")
logger.debug(f"๋ต๋ณ ๋ด์ฉ: {result['result'][:100]}...")
logger.debug(f"์ฐธ์กฐ ๋ฌธ์ ์: {len(result['source_documents'])}")
return result['result']
except Exception as e:
logger.error(f"์๋ฌ ๋ฐ์: {str(e)}", exc_info=True)
raise
์! ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด. ๐
Ollama์ LangChain์ ํ์ฉํ ๋ก์ปฌ Q&A ์์คํ
๊ตฌ์ถ์ ๋ํด ์ ๋ง ๋ง์ ๊ฑธ ๋ค๋ค์ง?
์ฐ๋ฆฌ๊ฐ ๋ฐฐ์ด ๋ด์ฉ์ ์ ๋ฆฌํด๋ณด์๋ฉด:
โ
๊ธฐ์ด ๊ฐ๋
โข ๋ก์ปฌ LLM์ ํ์์ฑ๊ณผ ์ฅ์
โข Ollama์ LangChain์ ์ญํ
โข RAG(๊ฒ์ ์ฆ๊ฐ ์์ฑ) ๋ฐฉ์์ ์ดํด
โ
์ค์ ๊ตฌํ
โข ํ๊ฒฝ ์ค์ ๊ณผ ์ค์น
โข ๋ฌธ์ ๋ก๋ ๋ฐ ์ ์ฒ๋ฆฌ
โข ๋ฒกํฐ ์ ์ฅ์ ๊ตฌ์ถ
โข Q&A ์ฒด์ธ ๊ตฌ์ฑ
โ
๊ณ ๊ธ ๊ธฐ๋ฅ
โข ๋ํ ๊ธฐ๋ก ์ ์ง
โข ๋ค์ํ ๋ฌธ์ ํ์ ์ง์
โข ์น ์ธํฐํ์ด์ค ๊ตฌ์ถ
โข ์ฑ๋ฅ ์ต์ ํ
โ
์ค์ ํ์ฉ
โข ํ์ฌ ๋ด๋ถ ๋ฌธ์ Q&A
โข ๊ณ ๊ฐ ์ง์ ์ฑ๋ด
โข ํ์ต ๋์ฐ๋ฏธ
โข ์ฝ๋ ๋ฌธ์ํ
โ
๋ฒ ์คํธ ํ๋ํฐ์ค
โข ๋ฌธ์ ์ ์ฒ๋ฆฌ ๊ธฐ๋ฒ
โข ํ๋กฌํํธ ์์ง๋์ด๋ง
โข ํ
์คํธ์ ํ๊ฐ
โข ๋ชจ๋ํฐ๋ง๊ณผ ๋ก๊น
์ด์ ๋น์ ๋ ํ ์ ์์ด! ๐
์ฒ์์๋ ๋ณต์กํด ๋ณด์ผ ์ ์์ง๋ง, ํ๋์ฉ ๋ฐ๋ผํ๋ค ๋ณด๋ฉด ์๊ฐ๋ณด๋ค ์ด๋ ต์ง ์์. ์ค์ํ ๊ฑด ์ง์ ํด๋ณด๋ ๊ฑฐ์ผ!
์์ ํ๋ก์ ํธ๋ถํฐ ์์ํด๋ด. ์๋ฅผ ๋ค์ด:
โข ๊ฐ์ธ ๋
ธํธ๋ ๋ฉ๋ชจ๋ฅผ ํ์ต์์ผ์ ๋๋ง์ ์ง์ ๋ฒ ์ด์ค ๋ง๋ค๊ธฐ
โข ์ข์ํ๋ ์ฑ
์ด๋ ๊ฐ์ ์๋ฃ๋ก ํ์ต ๋์ฐ๋ฏธ ๋ง๋ค๊ธฐ
โข ํ์ฌ๋ ๋์๋ฆฌ์ ๋ฌธ์๋ฅผ ์ ๋ฆฌํด์ ํ Q&A ์์คํ
๊ตฌ์ถํ๊ธฐ
๊ทธ๋ฆฌ๊ณ ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ด๋ฐ ๊ธฐ์ ์ ํ์ฉํ ์๋น์ค๋ฅผ ์ ๊ณตํ ์๋ ์์ด. AI ๊ฐ๋ฐ ์ฌ๋ฅ์ ๊ณต์ ํ๊ฑฐ๋, ๋ง์ถคํ Q&A ์์คํ
๊ตฌ์ถ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ ๊ฑฐ์ง! ๐ผ
์์ผ๋ก์ ํ์ต ๋ฐฉํฅ ๐
๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด:
โข LangChain ๊ณต์ ๋ฌธ์ ์ ๋
โข Hugging Face์์ ๋ค์ํ ๋ชจ๋ธ ์คํ
โข ์คํ์์ค ํ๋ก์ ํธ์ ๊ธฐ์ฌ
โข ์ปค๋ฎค๋ํฐ์์ ๋ค๋ฅธ ๊ฐ๋ฐ์๋ค๊ณผ ๊ต๋ฅ
๋ง์ง๋ง ์กฐ์ธ ๐ฌ
AI ๊ธฐ์ ์ ์ ๋ง ๋น ๋ฅด๊ฒ ๋ฐ์ ํ๊ณ ์์ด. ์ค๋ ๋ฐฐ์ด ๋ด์ฉ๋ ๋ช ๋ฌ ํ๋ฉด ๋ ๋์ ๋ฐฉ๋ฒ์ด ๋์ฌ ์ ์์ด. ๊ทธ๋์ ๊ณ์ ํ์ตํ๊ณ ์คํํ๋ ์์ธ๊ฐ ์ค์ํด!
ํ์ง๋ง ๊ธฐ๋ณธ ์๋ฆฌ๋ ๋ณํ์ง ์์. ๋ฌธ์๋ฅผ ๋ฒกํฐ๋ก ๋ณํํ๊ณ , ์ ์ฌ๋๋ฅผ ๊ณ์ฐํ๊ณ , LLM์ผ๋ก ๋ต๋ณ์ ์์ฑํ๋ ์ด ํ๋ฆ์ ์์ผ๋ก๋ ํต์ฌ์ด ๋ ๊ฑฐ์ผ.
์, ์ด์ ๋น์ ์ฐจ๋ก์ผ! ์ง์ ๋ง๋ค์ด๋ณด๊ณ , ์คํํด๋ณด๊ณ , ์คํจ๋ ํด๋ณด๋ฉด์ ๋ฐฐ์๋๊ฐ. ๊ทธ ๊ณผ์ ์์ ์ ๋ง ๋ฉ์ง ๊ฒ๋ค์ ๋ง๋ค์ด๋ผ ์ ์์ ๊ฑฐ์ผ! ๐
์ง๋ฌธ์ด๋ ์ด๋ ค์ด ์ ์ด ์์ผ๋ฉด ์ปค๋ฎค๋ํฐ์ ๋ฌผ์ด๋ณด๋ ๊ฒ๋ ์์ง ๋ง. ์ฐ๋ฆฌ ๋ชจ๋ ํจ๊ป ์ฑ์ฅํ๋ ๊ฑฐ๋๊น! ๐
ํ์ด์ ๋น์ด! ํ์ดํ
! ๐
๐ ํ์ต์ ๋ง์น๋ฉฐ
์ด ๊ธ์ด ๋์์ด ๋์๊ธฐ๋ฅผ ๋ฐ๋ผ! ๐
๋ก์ปฌ AI์ ์ธ๊ณ๋ ๋ฌด๊ถ๋ฌด์งํด.
๋น์ ์ ์ฐฝ์๋ ฅ์ผ๋ก ๋ฉ์ง ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ด!
Happy Coding! ๐ปโจ
๊ด๋ จ ํค์๋
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.

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