Skip to main content

Level 2: MVP (파이프라인 + 평가체계)

목표: 반복 평가 가능한 시스템(데이터셋/지표/리포트) 만들기
LangSmith는 “Dataset을 만들어 반복 평가”를 강조하고, Langfuse도 Dataset/Experiment 기반 평가를 제공합니다.

아키텍처

구현 체크리스트

  • 문서 추출기 구현 (PDF, Docx, HTML, Markdown)
  • 텍스트 정규화 (불필요한 공백, 특수문자 정리)
  • 중복 제거 로직 (해시 기반 또는 유사도 기반)
  • 메타데이터 스키마 설계 (출처, 날짜, 카테고리, ACL)
  • 도메인에 맞는 Chunking 전략 비교 테스트
  • Chunk 크기/overlap 표준 확정
  • Chunk에 메타데이터 부착 (출처, 섹션, 페이지 등)
  • Embedding 모델 선택 (단일언어 vs 멀티링구얼, API vs OSS)
  • Embedding 모델 버전 고정 (재현성 보장)
  • 증분 인덱싱 구현 (문서 추가/삭제/변경 반영)
  • Vector DB 인덱스 최적화 (HNSW 파라미터 등)
  • 평가셋 구축: 최소 50~100개 Q/A/근거(출처) 쌍
  • 오프라인 평가 파이프라인: Recall@k, MRR, NDCG
  • 평가 결과 기록 및 버전별 비교 체계
  • Trace에 Retrieval 결과 기록 (doc_id, top-k, 필터, 임베딩 버전)
  • Dataset v1 생성 및 버전 관리
  • 평가/실험 리포트 저장 (회귀 테스트 가능)
  • Prompt Playground로 프롬프트 개선 및 버전 비교

코드 예제

평가셋 구축 및 오프라인 평가

from ragas import evaluate, EvaluationDataset
from ragas.metrics import ContextRecall, ContextPrecision
from ragas.llms import LangchainLLMWrapper
from langchain_openai import ChatOpenAI

# 평가용 LLM 초기화
evaluator_llm = LangchainLLMWrapper(ChatOpenAI(model="gpt-4o-mini"))

# 평가 데이터 (50~100문항 권장)
eval_data = [
    {
        "user_input": "증분 인덱싱이란 무엇인가요?",
        "response": "증분 인덱싱은 전체 재인덱싱 없이 변경된 문서만 반영하는 방식입니다.",
        "retrieved_contexts": ["증분 인덱싱은 문서의 추가, 삭제, 변경을 실시간으로 반영합니다."],
        "reference": "증분 인덱싱은 변경된 문서만 추가/삭제하여 인덱스를 최신 상태로 유지합니다.",
    },
    {
        "user_input": "메타데이터 필터링의 장점은?",
        "response": "메타데이터 필터링은 검색 범위를 줄여 정확도와 속도를 높입니다.",
        "retrieved_contexts": ["메타데이터 필터링으로 부서별, 날짜별 검색이 가능합니다."],
        "reference": "메타데이터 필터링은 검색 범위를 좁혀 관련성 높은 결과를 반환합니다.",
    },
    # ... 50~100개
]

dataset = EvaluationDataset.from_list(eval_data)
results = evaluate(
    dataset=dataset,
    metrics=[ContextRecall(llm=evaluator_llm), ContextPrecision(llm=evaluator_llm)],
)
print(results.to_pandas())

증분 인덱싱 패턴

from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
import hashlib

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma(
    collection_name="documents",
    embedding_function=embeddings,
    persist_directory="./chroma_db",
)

def compute_hash(text: str) -> str:
    return hashlib.sha256(text.encode()).hexdigest()

def incremental_index(new_docs, existing_hashes):
    """변경된 문서만 인덱싱"""
    to_add = []
    for doc in new_docs:
        doc_hash = compute_hash(doc.page_content)
        if doc_hash not in existing_hashes:
            doc.metadata["content_hash"] = doc_hash
            to_add.append(doc)

    if to_add:
        vectorstore.add_documents(to_add)
        print(f"Added {len(to_add)} new documents")
    return to_add

Gate 2 통과 기준

Gate 2는 검색 품질(Retrieval) 중심입니다. 생성 품질은 Level 3에서 본격적으로 다룹니다.

Retrieval

기준목표
Recall@100.80
권한 필터 위반 (접근 불가 문서 노출)0건

LLMOps

기준목표
Dataset v150~200문항 버전 고정 + 평가 리포트 저장
Trace 디버깅doc_id/top-k/필터/임베딩 버전/청킹 버전이 기록되어 디버깅 가능
Gate 2의 핵심은 “검색이 잘 되는가”입니다. 좋은 검색 없이는 아무리 좋은 LLM도 좋은 답변을 생성할 수 없습니다.