kcsc-mcp / src /vector_db.py
nicefree19's picture
Upload 105 files
0ee3c92 verified
Raw
History Blame Contribute Delete
7.73 kB
import os
import json
import numpy as np
from sentence_transformers import SentenceTransformer
import chromadb
from chromadb.config import Settings
import logging
os.makedirs("logs", exist_ok=True)
logging.basicConfig(filename='logs/vector_db.log', level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')
class KCSCVectorDB:
def __init__(self, data_dir="data", db_dir="vector_db"):
self.data_dir = data_dir
self.db_dir = db_dir
os.makedirs(self.db_dir, exist_ok=True)
# ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋“œ (๋‹ค๊ตญ์–ด ๋ชจ๋ธ ์‚ฌ์šฉ)
self.model = SentenceTransformer('jhgan/ko-sroberta-multitask')
# Chroma DB ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
self.client = chromadb.PersistentClient(path=self.db_dir, settings=Settings(allow_reset=True))
# ๊ฐ ๋ฌธ์„œ ํƒ€์ž…๋ณ„ ์ปฌ๋ ‰์…˜ ์ƒ์„ฑ
self.collections = {}
for doc_type in ["KDS", "KCS"]:
try:
self.collections[doc_type] = self.client.get_or_create_collection(
name=doc_type,
metadata={"description": f"{doc_type} ์„ค๊ณ„๊ธฐ์ค€ ๋ฌธ์„œ"}
)
except Exception as e:
logging.error(f"{doc_type} ์ปฌ๋ ‰์…˜ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
def load_processed_docs(self):
"""์ฒ˜๋ฆฌ๋œ ๋ฌธ์„œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ"""
try:
file_path = os.path.join(self.data_dir, "processed_docs.json")
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logging.error(f"์ฒ˜๋ฆฌ๋œ ๋ฌธ์„œ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
return []
def create_document_chunks(self, docs, chunk_size=1000, overlap=200):
"""๋ฌธ์„œ๋ฅผ ์ฒญํฌ๋กœ ๋ถ„ํ• """
chunks = []
for doc in docs:
content = doc.get('content', '')
if not content or len(content) <= chunk_size:
# ๋‚ด์šฉ์ด ์—†๊ฑฐ๋‚˜ ์ฒญํฌ ํฌ๊ธฐ๋ณด๋‹ค ์ž‘์€ ๊ฒฝ์šฐ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
chunk_doc = doc.copy()
chunk_doc['chunk_id'] = f"{doc['id']}-0"
chunks.append(chunk_doc)
continue
# ๊ธด ๋ฌธ์„œ๋ฅผ ์ฒญํฌ๋กœ ๋ถ„ํ• 
for i in range(0, len(content), chunk_size - overlap):
chunk_content = content[i:i + chunk_size]
if len(chunk_content) < 100: # ๋„ˆ๋ฌด ์ž‘์€ ์ฒญํฌ๋Š” ๊ฑด๋„ˆ๋›ฐ๊ธฐ
continue
chunk_doc = doc.copy()
chunk_doc['content'] = chunk_content
chunk_doc['chunk_id'] = f"{doc['id']}-{i//(chunk_size-overlap)}"
chunks.append(chunk_doc)
logging.info(f"์ด {len(docs)}๊ฐœ ๋ฌธ์„œ์—์„œ {len(chunks)}๊ฐœ์˜ ์ฒญํฌ ์ƒ์„ฑ๋จ")
return chunks
def build_index(self):
"""๋ฒกํ„ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ตฌ์ถ•"""
docs = self.load_processed_docs()
if not docs:
logging.error("๋ฌธ์„œ๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
return False
# ๋ฌธ์„œ๋ฅผ ์ฒญํฌ๋กœ ๋ถ„ํ• 
chunks = self.create_document_chunks(docs)
# ๋ฌธ์„œ ํƒ€์ž…๋ณ„๋กœ ๊ทธ๋ฃนํ™”
doc_type_groups = {}
for chunk in chunks:
doc_type = chunk.get('doc_type', 'unknown')
if doc_type not in doc_type_groups:
doc_type_groups[doc_type] = []
doc_type_groups[doc_type].append(chunk)
# ๊ฐ ๋ฌธ์„œ ํƒ€์ž…๋ณ„๋กœ ์ธ๋ฑ์‹ฑ
for doc_type, type_chunks in doc_type_groups.items():
if doc_type not in self.collections:
logging.warning(f"{doc_type} ์ปฌ๋ ‰์…˜์ด ์—†์Šต๋‹ˆ๋‹ค. ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.")
continue
collection = self.collections[doc_type]
# ๊ธฐ์กด ๋ฐ์ดํ„ฐ ํ™•์ธ ๋ฐ ํ•„์š”์‹œ ์ดˆ๊ธฐํ™”
if collection.count() > 0:
logging.info(f"{doc_type} ์ปฌ๋ ‰์…˜ ์ดˆ๊ธฐํ™” (๊ธฐ์กด {collection.count()}๊ฐœ ํ•ญ๋ชฉ)")
collection.delete(where={})
# ์ฒญํฌ ๋ฐ์ดํ„ฐ ์ค€๋น„
ids = []
documents = []
metadatas = []
embeddings = []
for chunk in type_chunks:
# ํ…์ŠคํŠธ ์ค€๋น„ (์ œ๋ชฉ + ๋‚ด์šฉ)
text_for_embedding = f"{chunk.get('name', '')} {chunk.get('title', '')}: {chunk.get('content', '')}"
# ํ…์ŠคํŠธ๊ฐ€ ๋น„์–ด์žˆ์œผ๋ฉด ๊ฑด๋„ˆ๋œ€
if not text_for_embedding.strip():
continue
# ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ
embedding = self.model.encode(text_for_embedding)
ids.append(chunk['chunk_id'])
documents.append(text_for_embedding)
# ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ค€๋น„ (๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์—์„œ ํ•„์š”ํ•œ ์ •๋ณด)
metadata = {
"id": chunk['id'],
"code": chunk.get('code', ''),
"full_code": chunk.get('full_code', ''),
"name": chunk.get('name', ''),
"title": chunk.get('title', ''),
"doc_type": doc_type,
"version": chunk.get('version', '')
}
metadatas.append(metadata)
embeddings.append(embedding.tolist())
# ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
if ids:
collection.add(
ids=ids,
documents=documents,
metadatas=metadatas,
embeddings=embeddings
)
logging.info(f"{doc_type} ์ปฌ๋ ‰์…˜์— {len(ids)}๊ฐœ ํ•ญ๋ชฉ ์ถ”๊ฐ€๋จ")
return True
def search(self, query, doc_types=None, limit=5):
"""์ž์—ฐ์–ด ์ฟผ๋ฆฌ๋กœ ๊ด€๋ จ ๋ฌธ์„œ ๊ฒ€์ƒ‰"""
if not doc_types:
doc_types = list(self.collections.keys())
# ์ฟผ๋ฆฌ ์ž„๋ฒ ๋”ฉ
query_embedding = self.model.encode(query)
all_results = []
# ๊ฐ ์ปฌ๋ ‰์…˜์—์„œ ๊ฒ€์ƒ‰
for doc_type in doc_types:
if doc_type not in self.collections:
continue
collection = self.collections[doc_type]
results = collection.query(
query_embeddings=[query_embedding.tolist()],
n_results=limit
)
# ๊ฒฐ๊ณผ ํ˜•์‹ ๋ณ€ํ™˜
for i in range(len(results['ids'][0])):
result = {
"id": results['ids'][0][i],
"text": results['documents'][0][i],
"metadata": results['metadatas'][0][i],
"distance": float(results['distances'][0][i])
}
all_results.append(result)
# ์ „์ฒด ๊ฒฐ๊ณผ๋ฅผ ๊ฑฐ๋ฆฌ(์œ ์‚ฌ๋„)๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌ
all_results = sorted(all_results, key=lambda x: x['distance'])
return all_results[:limit]
# ์‚ฌ์šฉ ์˜ˆ์‹œ
if __name__ == "__main__":
vector_db = KCSCVectorDB()
# ๋ฒกํ„ฐ DB ๊ตฌ์ถ•
vector_db.build_index()
# ๊ฒ€์ƒ‰ ํ…Œ์ŠคํŠธ
results = vector_db.search("์ฒ ๊ทผ์ฝ˜ํฌ๋ฆฌํŠธ ๊ธฐ๋‘ฅ์˜ ์„ค๊ณ„ ๋ฐฉ๋ฒ•")
for i, result in enumerate(results):
print(f"\n--- ๊ฒฐ๊ณผ {i+1} ---")
print(f"๋ฌธ์„œ: {result['metadata']['name']} ({result['metadata']['code']})")
print(f"์œ ์‚ฌ๋„: {1 - result['distance']:.4f}")
print(f"๋‚ด์šฉ ๋ฏธ๋ฆฌ๋ณด๊ธฐ: {result['text'][:200]}...")