[CDocs] - 2. 데이터 수집

이우철·2026년 3월 27일

cdocs

목록 보기
2/4

[데이터 수집] 노션 문서를 지능형 데이터로 변환하기

"AI가 문서를 잘 찾으려면 '잘게 쪼개는 기술'이 필요합니다. 너무 길면 AI가 집중력을 잃거든요."

토이프로젝트는 가능한 무료를 활용한다.
그래야 쉽게 접근하고 누구든 따라해 보기 편하기 때문이다.
노션은 어느정도 까진(?) 우선 무료다.
거기에 데이터베이스를 구성한다.
데이터베이스 이지만 약간은 엑셀 같은 느낌이다.
노션 api key 가져오기와 데이터베이스 활용에 대한 내용은 웹에 많이 정보가 있으므로 우선 여기선 건너 뛰기로 한다.
혹시 하다 모르겠으면 언제든 물어보세요!

캡쳐한 이미지는 그냥 임의로 내가 아무 내용이나 적었다. 어자피 토이프로젝트 아닌가...

우선 노션의 데이터베이스에 회사 관련 문서를 저장하고 시작한다.
그 데이터를 백터디비에 넣는 부분이 우리가 하고자 함이다.

  1. 핵심 코드: src/ingestion.py
    노션 페이지를 읽어와서 벡터 DB에 저장합니다.
import time
import schedule

# [핵심 라이브러리 설명]
# NotionDBLoader: 노션의 API를 호출하여 데이터베이스 내의 페이지들을 가져오는 역할을 합니다.
# RecursiveCharacterTextSplitter: 긴 문서를 AI가 처리하기 좋은 크기로 쪼개는 '청킹(Chunking)' 도구입니다.
# OllamaEmbeddings: 텍스트를 고차원 수치 벡터로 변환하는 '임베딩(Embedding)' 과정을 수행합니다.
# Chroma: 변환된 벡터 데이터를 저장하고 고속으로 검색할 수 있게 해주는 '벡터 데이터베이스'입니다.
from langchain_community.document_loaders import NotionDBLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma

# 프로젝트 설정값과 로거를 불러옵니다.
from src.utils.config import NOTION_API_KEY, NOTION_DATABASE_ID, CHROMA_DB_DIR
from src.utils.logger import get_logger

logger = get_logger("IngestionBatch")

def run_ingestion():
    """
    Notion API에서 데이터를 가져와 임베딩 후 로컬 ChromaDB에 저장하는 메인 함수입니다.
    이 과정이 잘 되어야 AI가 사내 지식을 정확히 검색할 수 있습니다.
    """
    logger.info("데이터 동기화 배치 잡 (Ingestion) 시작...")
    
    # 방어적 프로그래밍: 설정값이 없으면 에러를 내지 않고 친절하게 안내 후 종료합니다.
    if not NOTION_API_KEY or not NOTION_DATABASE_ID:
        logger.error("NOTION_API_KEY 또는 NOTION_DATABASE_ID가 설정되지 않았습니다. .env 파일을 확인하세요.")
        return

    try:
        # 1. Notion 문서 로드
        # integration_token: 노션 API 키 / database_id: 읽어올 데이터베이스 ID입니다.
        logger.info(f"Notion 데이터베이스({NOTION_DATABASE_ID})에서 문서를 읽어옵니다.")
        loader = NotionDBLoader(
            integration_token=NOTION_API_KEY,
            database_id=NOTION_DATABASE_ID,
            request_timeout_sec=30,
        )
        docs = loader.load()
        logger.info(f"총 {len(docs)} 개의 노션 페이지를 성공적으로 가져왔습니다.")

        if not docs:
            logger.warning("가져올 문서가 없습니다. 데이터베이스 내용을 확인해 보세요.")
            return

        # 2. 문서 청킹 (Text Splitting)
        # chunk_size: 1000자 단위로 자릅니다. LLM이 한 번에 이해하기 가장 적절한 크기입니다.
        # chunk_overlap: 150자씩 겹치게 하여 문맥이 잘리는 것을 방지합니다. (검색 품질의 핵심!)
        logger.info("문서를 의미 단위(Chunk)로 분할합니다.")
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,
            chunk_overlap=150,
        )
        splits = text_splitter.split_documents(docs)
        logger.info(f"분할 완료: {len(splits)} 개의 데이터 조각이 생성되었습니다.")

        # 3. 임베딩 및 ChromaDB 저장
        # mxbai-embed-large: 로컬 환경(Ollama)에서 가장 성능이 좋은 임베딩 모델 중 하나입니다.
        logger.info("로컬 Ollama 임베딩을 이용해 벡터 DB 저장을 시작합니다.")
        embeddings = OllamaEmbeddings(
            model="mxbai-embed-large",
            base_url="http://localhost:11434"
        )
        
        # Chroma.from_documents: 텍스트를 숫자로 바꾸고 디스크에 저장까지 한 번에 처리합니다.
        Chroma.from_documents(
            documents=splits,
            embedding=embeddings,
            persist_directory=CHROMA_DB_DIR
        )
        
        logger.info(f"벡터 데이터베이스 동기화 완료! 저장 경로: {CHROMA_DB_DIR}")
        
    except Exception as e:
        # 실무 팁: 로그에 exc_info=True를 주면 에러 발생 위치(Traceback)까지 기록되어 디버깅이 쉽습니다.
        logger.error(f"동기화 중 오류 발생: {e}", exc_info=True)


def start_scheduler(interval_minutes: int = 60):
    """
    주기적으로 동기화를 수행하는 스케줄러입니다. 
    보통 사내 문서 업데이트 주기에 맞춰 30분~60분 단위로 설정합니다.
    """
    logger.info(f"🚀 노션 동기화 스케줄러 가동 (주기: {interval_minutes}분)")
    
    # 시작 시 즉시 실행
    run_ingestion()
    
    # schedule 라이브러리를 이용한 주기적 잡 등록
    schedule.every(interval_minutes).minutes.do(run_ingestion)
    
    while True:
        schedule.run_pending()
        time.sleep(1)

if __name__ == "__main__":
    # 스크립트 단독 실행 시 1시간 간격으로 동작하도록 설정합니다.
    start_scheduler(interval_minutes=60)

나름(?) 자세히 주석을 남겼으니 잘 따라가며 살펴보리라 생각됩니다.

mxbai-embed-large는 한국어 성능도 훌륭하고 로컬에서 가볍게 돌아가는 괜찮은 임베딩 모델 중 하나입니다.

profile
개발 정리 공간 - 업무일때도 있고, 공부일때도 있고...

0개의 댓글