์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿค– Ollama + LangChain์œผ๋กœ ๋กœ์ปฌ Q&A ๊ตฌ์ถ•ํ•˜๊ธฐ

๐Ÿค– Ollama + LangChain์œผ๋กœ ๋กœ์ปฌ Q&A ๊ตฌ์ถ•ํ•˜๊ธฐ

๋‚ด ์ปดํ“จํ„ฐ์—์„œ ๋Œ์•„๊ฐ€๋Š” ๋‚˜๋งŒ์˜ AI ๋น„์„œ ๋งŒ๋“ค๊ธฐ ํ”„๋กœ์ ํŠธ! ๐Ÿš€

์•ˆ๋…•! ์˜ค๋Š˜์€ ์ •๋ง ์žฌ๋ฏธ์žˆ๋Š” ์ฃผ์ œ๋ฅผ ๊ฐ€์ง€๊ณ  ์™”์–ด. ๋ฐ”๋กœ Ollama์™€ LangChain์„ ํ™œ์šฉํ•ด์„œ ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ Q&A ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด์•ผ. ๐Ÿ˜Š

์š”์ฆ˜ ChatGPT๋‚˜ Claude ๊ฐ™์€ AI ์„œ๋น„์Šค๋“ค์ด ์—„์ฒญ ์ธ๊ธฐ์ž–์•„? ๊ทผ๋ฐ ์ด๋Ÿฐ ์„œ๋น„์Šค๋“ค์€ ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์ด ํ•„์š”ํ•˜๊ณ , ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์™ธ๋ถ€ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์•ผ ํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์–ด. ํšŒ์‚ฌ ๋‚ด๋ถ€ ๋ฌธ์„œ๋‚˜ ๊ฐœ์ธ ์ •๋ณด๋ฅผ ๋‹ค๋ฃฐ ๋•Œ๋Š” ์ข€ ๊บผ๋ ค์ง€๋Š” ๊ฒŒ ์‚ฌ์‹ค์ด์ง€.

๊ทธ๋ž˜์„œ ๋“ฑ์žฅํ•œ ๊ฒŒ ๋ฐ”๋กœ ๋กœ์ปฌ LLM(Large Language Model)์ด์•ผ! ๋‚ด ์ปดํ“จํ„ฐ์—์„œ ์ง์ ‘ AI ๋ชจ๋ธ์„ ๋Œ๋ฆฌ๋Š” ๊ฑฐ์ง€. ์ธํ„ฐ๋„ท ์—†์ด๋„ ์ž‘๋™ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๊ฐ€ ์™ธ๋ถ€๋กœ ๋‚˜๊ฐ€์ง€ ์•Š์œผ๋‹ˆ ๋ณด์•ˆ๋„ ์™„๋ฒฝํ•ด. ๊ฒŒ๋‹ค๊ฐ€ API ๋น„์šฉ๋„ ์•ˆ ๋“ค์–ด! ๐Ÿ’ฐ

๋กœ์ปฌ AI ์‹œ์Šคํ…œ ๊ตฌ์กฐ ์‚ฌ์šฉ์ž ์งˆ๋ฌธ ์ž…๋ ฅ ๐Ÿ’ฌ LangChain ์ฒ˜๋ฆฌ ๋ฐ ์กฐ์œจ โš™๏ธ Ollama ๋กœ์ปฌ LLM ์‹คํ–‰ ๐Ÿง  ๋กœ์ปฌ ๋ฌธ์„œ ๋ฒกํ„ฐ DB ๐Ÿ“š ์ฐธ์กฐ 100% ๋กœ์ปฌ ์ธํ„ฐ๋„ท ๋ถˆํ•„์š” ๐Ÿ”’
๐ŸŽฏ ์™œ ๋กœ์ปฌ Q&A ์‹œ์Šคํ…œ์ด ํ•„์š”ํ• ๊นŒ?

๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์—, ์™œ ์ด๋Ÿฐ ์‹œ์Šคํ…œ์ด ํ•„์š”ํ•œ์ง€ ํ•œ๋ฒˆ ์ƒ๊ฐํ•ด๋ณผ๊นŒ? ๐Ÿค”

1. ๋ฐ์ดํ„ฐ ๋ณด์•ˆ๊ณผ ํ”„๋ผ์ด๋ฒ„์‹œ ๐Ÿ”
ํšŒ์‚ฌ ๋‚ด๋ถ€ ๋ฌธ์„œ, ๊ณ ๊ฐ ์ •๋ณด, ์˜๋ฃŒ ๊ธฐ๋ก ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃฐ ๋•Œ๋Š” ์™ธ๋ถ€ API๋กœ ๋ณด๋‚ด๋Š” ๊ฒŒ ๋ถ€๋‹ด์Šค๋Ÿฌ์›Œ. ๋กœ์ปฌ์—์„œ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์ ˆ๋Œ€ ์™ธ๋ถ€๋กœ ๋‚˜๊ฐ€์ง€ ์•Š์•„์„œ ์•ˆ์‹ฌ์ด์ง€.

2. ๋น„์šฉ ์ ˆ๊ฐ ๐Ÿ’ธ
OpenAI API๋‚˜ ๋‹ค๋ฅธ ํด๋ผ์šฐ๋“œ AI ์„œ๋น„์Šค๋Š” ์‚ฌ์šฉ๋Ÿ‰์— ๋”ฐ๋ผ ๋น„์šฉ์ด ์ฒญ๊ตฌ๋ผ. ์ฒ˜์Œ์—” ๊ดœ์ฐฎ์€๋ฐ, ์‚ฌ์šฉ์ž๊ฐ€ ๋งŽ์•„์ง€๋ฉด ๋น„์šฉ์ด ๊ธฐํ•˜๊ธ‰์ˆ˜์ ์œผ๋กœ ๋Š˜์–ด๋‚˜. ๋กœ์ปฌ ์‹œ์Šคํ…œ์€ ์ดˆ๊ธฐ ํ•˜๋“œ์›จ์–ด ํˆฌ์ž๋งŒ ํ•˜๋ฉด ์ถ”๊ฐ€ ๋น„์šฉ์ด ๊ฑฐ์˜ ์—†์–ด!

3. ์ธํ„ฐ๋„ท ๋…๋ฆฝ์„ฑ ๐ŸŒ
์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์ด ๋ถˆ์•ˆ์ •ํ•œ ํ™˜๊ฒฝ์ด๋‚˜, ์˜คํ”„๋ผ์ธ ์ƒํ™ฉ์—์„œ๋„ AI ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ๋น„ํ–‰๊ธฐ ์•ˆ์—์„œ๋„, ์ง€ํ•˜์‹ค์—์„œ๋„ ์ž‘๋™ํ•˜์ง€!

4. ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ์ž์œ ๋„ ๐ŸŽจ
๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋Œ€๋กœ ๋ชจ๋ธ์„ ์„ ํƒํ•˜๊ณ , ํŒŒ์ธํŠœ๋‹ํ•˜๊ณ , ์‹œ์Šคํ…œ์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์–ด. ํŠน์ • ๋„๋ฉ”์ธ์— ํŠนํ™”๋œ AI๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑฐ์ง€.

5. ์‘๋‹ต ์†๋„ ์ œ์–ด โšก
์ข‹์€ ํ•˜๋“œ์›จ์–ด๋ฅผ ๊ฐ–์ถ”๋ฉด ํด๋ผ์šฐ๋“œ API๋ณด๋‹ค ๋” ๋น ๋ฅธ ์‘๋‹ต์„ ๋ฐ›์„ ์ˆ˜๋„ ์žˆ์–ด. ๋„คํŠธ์›Œํฌ ์ง€์—ฐ์ด ์—†์œผ๋‹ˆ๊นŒ!

๐Ÿ’ก ์‹ค์ œ ํ™œ์šฉ ์‚ฌ๋ก€

์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ์ด๋Ÿฐ ๊ธฐ์ˆ ์ด ํ™œ์šฉ๋  ์ˆ˜ ์žˆ์–ด. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๋“ค์˜ ์žฌ๋Šฅ ๋งค์นญ์„ ์œ„ํ•œ ๋‚ด๋ถ€ ์ถ”์ฒœ ์‹œ์Šคํ…œ์ด๋‚˜, ๊ณ ๊ฐ ๋ฌธ์˜ ์ž๋™ ์‘๋‹ต ์‹œ์Šคํ…œ์„ ๋กœ์ปฌ์—์„œ ๊ตฌ์ถ•ํ•˜๋ฉด ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ์™€ ๋น„์šฉ ์ ˆ๊ฐ์„ ๋™์‹œ์— ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ์ง€! ๐ŸŽฏ
๐Ÿ› ๏ธ Ollama๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ Ollama์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž! Ollama๋Š” ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ๋Œ€๊ทœ๋ชจ ์–ธ์–ด ๋ชจ๋ธ(LLM)์„ ์‰ฝ๊ฒŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์˜คํ”ˆ์†Œ์Šค ๋„๊ตฌ์•ผ. ๐Ÿ˜Ž

Docker๋ฅผ ์จ๋ณธ ์  ์žˆ์–ด? Ollama๋Š” LLM ๋ฒ„์ „์˜ Docker๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ผ. ๋ณต์žกํ•œ ์„ค์ • ์—†์ด ๊ฐ„๋‹จํ•œ ๋ช…๋ น์–ด ๋ช‡ ๊ฐœ๋กœ ์ตœ์‹  AI ๋ชจ๋ธ๋“ค์„ ๋‚ด ์ปดํ“จํ„ฐ์—์„œ ๋ฐ”๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด!

๐Ÿ“ฆ Ollama์˜ ์ฃผ์š” ํŠน์ง•

โ€ข ๊ฐ„ํŽธํ•œ ์„ค์น˜์™€ ์‚ฌ์šฉ
๋ณต์žกํ•œ 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์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ ์ฐจ๋ก€์•ผ! LangChain์€ LLM ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์•ผ. ๐Ÿ—๏ธ

LLM์„ ๋‹จ์ˆœํžˆ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ ๋„˜์–ด์„œ, ๋ณต์žกํ•œ AI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋„๊ตฌ ๋ชจ์Œ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ผ. ๋งˆ์น˜ ๋ ˆ๊ณ  ๋ธ”๋ก์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์กฐํ•ฉํ•ด์„œ ์›ํ•˜๋Š” ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด!

๐ŸŽฏ LangChain์˜ ํ•ต์‹ฌ ๊ฐœ๋…

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 ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž! ๐Ÿš€

LangChain ์ž‘๋™ ํ๋ฆ„ 1. ๋ฌธ์„œ ๋กœ๋“œ ๐Ÿ“„ 2. ํ…์ŠคํŠธ ๋ถ„ํ•  โœ‚๏ธ 3. ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ ๐Ÿ”ข 4. ๋ฒกํ„ฐ ์ €์žฅ ๐Ÿ’พ 5. ์งˆ๋ฌธ ์ž…๋ ฅ โ“ 6. ์œ ์‚ฌ๋„ ๊ฒ€์ƒ‰ ๐Ÿ” 7. ์ปจํ…์ŠคํŠธ ๊ตฌ์„ฑ ๐Ÿ“‹ 8. LLM ์‘๋‹ต ๐Ÿ’ฌ ์ฐธ์กฐ RAG (Retrieval Augmented Generation) ๊ฒ€์ƒ‰ ์ฆ๊ฐ• ์ƒ์„ฑ ๋ฐฉ์‹์œผ๋กœ ์ •ํ™•ํ•œ ๋‹ต๋ณ€ ์ œ๊ณต
๐Ÿ—๏ธ ๋กœ์ปฌ 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
Step 2: ๋ฌธ์„œ ๋กœ๋“œ ๋ฐ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๐Ÿ“š

์ด์ œ 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์„ ์„ค์ •ํ•˜๋Š” ์ด์œ ๋Š” ๋ฌธ๋งฅ์ด ๋Š๊ธฐ์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ์•ผ. ์ฒญํฌ ๊ฒฝ๊ณ„์—์„œ ์ค‘์š”ํ•œ ์ •๋ณด๊ฐ€ ์ž˜๋ฆฌ๋Š” ๊ฑธ ๋ฐฉ์ง€ํ•˜์ง€! ๐ŸŽฏ
Step 3: ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ ๋ฐ ๋ฒกํ„ฐ ์ €์žฅ์†Œ ๊ตฌ์ถ• ๐Ÿ”ข

์ด์ œ ํ…์ŠคํŠธ๋ฅผ ๋ฒกํ„ฐ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  ์ €์žฅํ•ด์•ผ ํ•ด. ์ด๊ฒŒ ๋ฐ”๋กœ 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 ๋ชจ๋ธ์€ ํ•œ๊ตญ์–ด๋ฅผ ํฌํ•จํ•œ ๋‹ค๊ตญ์–ด๋ฅผ ์ง€์›ํ•˜๋Š” ๊ฒฝ๋Ÿ‰ ๋ชจ๋ธ์ด์•ผ. ์„ฑ๋Šฅ๋„ ๊ดœ์ฐฎ๊ณ  ์†๋„๋„ ๋นจ๋ผ์„œ ๋กœ์ปฌ ํ™˜๊ฒฝ์— ๋”ฑ์ด์ง€! ๐Ÿ‘

Step 4: Ollama LLM ์—ฐ๊ฒฐํ•˜๊ธฐ ๐Ÿค–

์ด์ œ 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 ๋ช…๋ น์–ด๋กœ ์„œ๋ฒ„๋ฅผ ์‹œ์ž‘ํ•˜๊ฑฐ๋‚˜, ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ž๋™ ์‹คํ–‰๋˜๋„๋ก ์„ค์ •ํ•ด์•ผ ํ•ด. ์—ฐ๊ฒฐ์ด ์•ˆ ๋˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฑฐ์•ผ!
Step 5: Q&A ์ฒด์ธ ๊ตฌ์„ฑํ•˜๊ธฐ โ›“๏ธ

์ด์ œ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋ฅผ ์—ฐ๊ฒฐํ•ด์„œ ์‹ค์ œ๋กœ ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•  ์ˆ˜ ์žˆ๋Š” ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์ž!
# 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
)
์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต ํ•˜๋“œ์›จ์–ด ์ตœ์ ํ™” ๐Ÿ’ป โ€ข GPU ํ™œ์šฉ โ€ข ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ โ€ข ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ๋ชจ๋ธ ์ตœ์ ํ™” ๐ŸŽฏ โ€ข ์–‘์žํ™” โ€ข ํ”„๋ฃจ๋‹ โ€ข ๊ฒฝ๋Ÿ‰ ๋ชจ๋ธ ์„ ํƒ ๋ฐ์ดํ„ฐ ์ตœ์ ํ™” ๐Ÿ“Š โ€ข ์ฒญํฌ ํฌ๊ธฐ ์กฐ์ • โ€ข ์บ์‹ฑ ์ „๋žต โ€ข ์ธ๋ฑ์Šค ์ตœ์ ํ™” ์ข…ํ•ฉ ์„ฑ๋Šฅ ํ–ฅ์ƒ ํšจ๊ณผ ์‘๋‹ต ์†๋„ 3-5๋ฐฐ ๊ฐœ์„  ๊ฐ€๋Šฅ! โšก
โšก ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์—ˆ์œผ๋ฉด ์ด์ œ ๋น ๋ฅด๊ณ  ํšจ์œจ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์•ผ์ง€! ์‹ค์ „ ์ตœ์ ํ™” ํŒ์„ ์•Œ๋ ค์ค„๊ฒŒ. ๐Ÿš€

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 ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑํ•  ๋•Œ

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
)
๋ฒกํ„ฐ DB ๋น„๊ต ๐Ÿ“Š

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์˜ ๋ฏธ๋ž˜ ๋” ์ž‘๊ณ  ๋น ๋ฅธ ๋ชจ๋ธ ์–‘์žํ™” ๊ธฐ์ˆ  ๋ฐœ์ „ ๋ชจ๋ฐ”์ผ ๊ธฐ๊ธฐ์—์„œ๋„ ์‹คํ–‰ ๐Ÿ“ฑ ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ง€์› ํ…์ŠคํŠธ + ์ด๋ฏธ์ง€ + ์Œ์„ฑ ํ†ตํ•ฉ AI ์‹œ์Šคํ…œ ๐ŸŽจ ๊ฐœ์ธํ™” AI ์‚ฌ์šฉ์ž ๋งž์ถค ํ•™์Šต ํ”„๋ผ์ด๋ฒ„์‹œ ๋ณด์žฅ ๐Ÿ” ์˜คํ”ˆ์†Œ์Šค ์ƒํƒœ๊ณ„ ์ปค๋ฎค๋‹ˆํ‹ฐ ์ฃผ๋„ ๋ฐœ์ „ ๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผ ๊ฐ€๋Šฅ ๐ŸŒ ์—ฃ์ง€ ์ปดํ“จํŒ… IoT ๊ธฐ๊ธฐ์—์„œ AI ์‹คํ–‰ ์‹ค์‹œ๊ฐ„ ์ฒ˜๋ฆฌ โšก
๐Ÿ”ฎ ๋ฏธ๋ž˜ ์ „๋ง๊ณผ ๋ฐœ์ „ ๋ฐฉํ–ฅ

๋กœ์ปฌ AI ๊ธฐ์ˆ ์€ ์ •๋ง ๋น ๋ฅด๊ฒŒ ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด! ์•ž์œผ๋กœ ์–ด๋–ป๊ฒŒ ๋ณ€ํ• ์ง€ ํ•œ๋ฒˆ ์‚ดํŽด๋ณด์ž. ๐Ÿš€

1. ๋ชจ๋ธ ๊ฒฝ๋Ÿ‰ํ™” ๊ธฐ์ˆ  ๐Ÿ“‰

์•ž์œผ๋กœ๋Š” ๋” ์ž‘๊ณ  ๋น ๋ฅธ ๋ชจ๋ธ๋“ค์ด ๋‚˜์˜ฌ ๊ฑฐ์•ผ. ์ด๋ฏธ ์ง„ํ–‰ ์ค‘์ธ ๊ธฐ์ˆ ๋“ค:

์–‘์žํ™” (Quantization)
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