๊ฒ์ธก์์ฒญ์ PDF ์๋ ๊ฒ์ ยท ์ด์ (2026-06-30) ์กฐ์ฌ ๊ฒฐ๊ณผ + ๊ตฌ์ถ ํจํด ๋ฉ๋ชจ
ํธ๋ฆฌ๊ฑฐ ์กฐ๊ฑด: ๊ฒ์ธก์์ฒญ์ PDF๊ฐ ์์ญ~์๋ฐฑ ์ฅ ๋ชจ์ด๋ฉด ์งํ. ํ์ฌ ๋ฐ์ดํฐ ๋ถ์กฑ โ ์ฝ๋ ์์ฑ 0, ํจํด๋ง ํ๋ฆฝ.
๋ค์ ์ก์
: ๊ฒ์ธก์์ฒญ์ ๋์ โ 50์ฅ ์ด์ ์ PoC (1์ฅ) โ 5์ฅ โ ์ ๋ ์์๋ก ์ธ๋ฑ์ฑ.
AI ์ฑ ์ ์์ ๋น์
๐ค AI(LLM)๋ ๊ณต๋ถ๋ง ๋ง์ด ํด์ "์ฐ๋ฆฌ ํ์ฌ ๋งค๋ด์ผ", "์์ฅ๋ ๊ฒ์ธก์์ฒญ์" ๊ฐ์ ๊ฑด ๋ชฐ๋ผ์.
๐ก RAG = "์ง๋ฌธํ๊ธฐ ์ ์ ์ฑ ์ ์์ ๊ด๋ จ ๋ฌธ์ ํผ์ณ๋๊ธฐ"
๐ ํผ์ ๊ฐ๊ฒ ๋น์ :
โข AI๋ง ์ธ ๋: "ํผ์ ์ฃผ๋ฌธ ๋ฐ์๊ฒ์!" (๋ฉ๋ดํ ์ ๋ด)
โข RAG ๋ถ์ด๋ฉด: "ํผ์ ์ฃผ๋ฌธ ๋ฐ์๊ฒ์!" + ๋ฉ๋ดํ ํผ์ณ๋๊ณ โ "ํํผ๋ก๋ 12,000์, ๋ถ๊ณ ๊ธฐ 14,000์์ ๋๋ค" ์ ํํ ๋๋ต
sentence-transformers BAAI/bge-m3 (ํ๊ธ ๋ก์ปฌ ๋ฌด๋ฃ)chromadb ํ์ผ ๊ธฐ๋ฐ (DB ์๋ฒ ๋ถํ์)/mnt/ssd500/var/inspec-reports/ ๋ฑ ์
๋ฌด ํด๋๋ง ์ธ๋ฑ์ฑ.env ์๋ ์ ์ธ (์ํฌ๋ฆฟ ์ ์ถ ๋ฐฉ์ง)glob.glob(".../**/*.pdf") ์ฌ๊ท ์๋ฒ ๋ฉ| ํ์ | ์ฒ๋ฆฌ ๋ฐฉ๋ฒ | ๋น๊ณ |
|---|---|---|
| pypdf / vision_analyze (SINDOH) | ๊ฒ์ธก์์ฒญ์ 9์ชฝ SINDOH ํ๋ฐฑ โ vision ์ฐ์ | |
| DOCX/XLSX/PPTX | python-docx, openpyxl, python-pptx | |
| TXT / HTML | ์ง์ read | |
| ์ด๋ฏธ์ง | OCR (tesseract) or vision_analyze | |
| ์์ฑ | whisper | TBM ๋ น์ ๋ฑ |
| ์ด๋ฉ์ผ | mailbox ํ์ฑ (.eml/.mbox) |
| ๊ณ์ธต | ์ ํ | ์ด์ |
|---|---|---|
| ์๋ฒ ๋ฉ | BAAI/bge-m3 | ํ๊ธ ์ฐ์, ๋ก์ปฌ ๋ฌด๋ฃ, 567M params, 8K ํ ํฐ ์ปจํ ์คํธ |
| Vector DB | chromadb (PersistentClient) | ํ์ผ ํ๋๋ฉด ๋, PostgreSQL ๋ถํ์ |
| ๊ฒ์ | chromadb query() | ์ฝ์ฌ์ธ ์ ์ฌ๋ ๊ธฐ๋ณธ |
| ๋ต๋ณ LLM | miniMax (๊ธฐ๋ณธ) โ ํ์์ fallback | FAL/OpenRouter๋ณด๋ค ์ฐ์ |
| PDF ํ ์คํธ | pypdf ์ฐ์ , ๊นจ์ง๋ฉด vision_analyze | SINDOH ํ๋ฐฑ ์ธ์๋ vision ํ์ |
๊ฒ์ธก์์ฒญ์ 1์ฅ๋ง ๊ฐ์ง๊ณ 1์๊ฐ ์์ ์๋ ํ์ธํ๋ ํจํด:
uv pip install --python /home/jaekyung/.hermes/hermes-agent/venv/bin/python \
chromadb sentence-transformers pypdf
import chromadb
from pypdf import PdfReader
from sentence_transformers import SentenceTransformer
import glob
# 1) DB
client = chromadb.PersistentClient(path="/home/jaekyung/rag-db")
collection = client.get_or_create_collection("inspec-reports")
# 2) ์๋ฒ ๋ฉ ๋ชจ๋ธ (ํ๊ธ bge-m3)
model = SentenceTransformer("BAAI/bge-m3")
# 3) PDF ์ฝ๊ธฐ โ ๋จ๋ฝ ์กฐ๊ฐ
def pdf_to_chunks(path):
try:
reader = PdfReader(path)
text = "\n\n".join(p.extract_text() or "" for p in reader.pages)
except Exception:
# SINDOH ํ๋ฐฑ โ vision ํ์ (skill: pdf-split-by-marker)
text = vision_analyze(path) # placeholder
return [c for c in text.split("\n\n") if len(c.strip()) > 30]
# 4) ์ธ๋ฑ์ฑ (์ฌ๊ท)
for pdf in glob.glob("/mnt/ssd500/var/inspec-reports/**/*.pdf", recursive=True):
chunks = pdf_to_chunks(pdf)
embs = model.encode(chunks).tolist()
ids = [f"{pdf}-{i}" for i in range(len(chunks))]
metadatas = [{"source": pdf, "chunk_id": i} for i in range(len(chunks))]
collection.add(documents=chunks, embeddings=embs, ids=ids, metadatas=metadatas)
print(f"โ {pdf}: {len(chunks)} chunks")
def rag_search(query: str, n=5):
q_emb = model.encode([query]).tolist()
results = collection.query(query_embeddings=q_emb, n_results=n)
chunks = results["documents"][0]
sources = [m["source"] for m in results["metadatas"][0]]
return list(zip(chunks, sources))
# ์ฌ์ฉ
for chunk, src in rag_search("์๋
3์ ๊ฑด์ถ ๊ฒ์ธก"):
print(f"[{src}]\n{chunk}\n---")
| ์ต์ | ์์ ๋ | ์ฅ์ | ๋จ์ |
|---|---|---|---|
| A. Python ์คํฌ๋ฆฝํธ | 1์๊ฐ | ์ฆ์ ํ ์คํธ, ํ์ฅ ์์ ๋ ์ต๊ณ , MCP ํจ์ ์์ | UI ์์ (CLI/์ฑํ ์ผ๋ก๋ง ์ฌ์ฉ) |
| B. RAG MCP ์๋ฒ | 1์ผ | ํค๋ฅด๋ฉ์ค ๋๊ตฌ๋ก ์๋ ํตํฉ, "๊ฒ์ธก์์ฒญ์ ์ฐพ์์ค" ํ๋ง๋ | MCP ๋ฑ๋ก ํจ์ ๊ฐ๋ฅ (chrome-devtools ์ฃฝ์ ์ฌ๋ก ์์) |
๐ฏ ์์ฅ๋ ์ถ์ฒ: ์ต์ A โ B ์์
๋จผ์ A๋ก ํจํด ๊ฒ์ฆ, ์์ ๋๋ฉด B๋ก ์น๊ฒฉ.
ls /mnt/ssd500/var/inspec-reports/**/*.pdf | wc -l)pypdf ์ถ์ถ ์ ํ๊ธ ๊นจ์ง โ vision_analyze ํ์/mnt/ssd500/var/ ์
๋ฌด ํด๋๋ง, .env ์๋ ์ ์ธ| ๋ ์ง | ๋จ๊ณ | ์ํ |
|---|---|---|
| 2026-06-30 | ์กฐ์ฌ + ํจํด ํ๋ฆฝ | ์๋ฃ |
| 2026-07-01 | ํํ์ด์ง ์นด๋ + ๊ณํ ํ์ด์ง | ์๋ฃ |
| 2026-07-?? | ๊ฒ์ธก์์ฒญ์ 50์ฅโ ํ์ธ โ PoC ์์ | ๋๊ธฐ |
| - | ์ต์ A (Python ์คํฌ๋ฆฝํธ) PoC | ๋ฏธ์ |
| - | ์ต์ B (MCP ์๋ฒ) ์น๊ฒฉ | ๋ฏธ์ |
์์ฑ: 2026-07-01 ยท ์ด์ (6/30) ์ธ์ ๋ฉ๋ชจ ๊ธฐ๋ฐ ยท ๋ฉ์ธ์ผ๋ก