코퍼스가 한 번 구축하고 끝나는 정적인 자산이라면 이 섹션이 짧아도 된다. 하지만 변경되는 데이터를 다루는 환경 — IT 회사의 서비스 기획서, Knowledge Base, API 문서 등 — 에서는 코퍼스 품질 관리가 RAG 파이프라인에서 가장 중요한 부분이 된다.
| 유형 | 설명 | 예시 |
|---|---|---|
| 정보 부재 (Gap) | 코퍼스에 해당 주제가 아예 없음 | 신규 출시 기능의 기획서가 아직 미반영 |
| 정보 부패 (Staleness) | 예전에는 맞았지만 지금은 틀린 정보 | 작년 API v1 문서가 남아있는데 현재는 v2 |
| 정보 중복 (Redundancy) | 같은 내용이 여러 소스에서 반복 | 동일 기능의 기획서 v1, v2, v3가 모두 코퍼스에 존재 |
이 중 정보 부패가 가장 위험하다. Gap은 "검색 결과 없음"으로 드러나지만, 부패한 정보는 "검색 결과가 있지만 틀린 것"이므로 LLM이 자신있게 틀린 답을 내놓는다.
새 문서를 추가할 때, 기존 코퍼스와 코사인 유사도로 비교:
# 신규 청크 vs 기존 청크 유사도 행렬
sim_matrix = new_embeddings @ existing_embeddings.T # (new × existing)
# 각 신규 청크의 최대 유사도가 threshold(0.92) 이상이면 중복
for i, row in enumerate(sim_matrix):
if row.max() >= 0.92:
dup_count += 1
판정 기준:
duplicate (추가하지 않음)partial (검토 후 추가)new (바로 추가)코퍼스가 커지면 서로 다른 소스에서 동일한 내용이 겹칠 수 있다:
python scripts/dedup.py --self-dedup data/chunks_meta.jsonl --threshold 0.95
인접 청크(overlap으로 인한 자연스러운 유사)는 제외하고, 다른 소스 파일 간 0.95 이상 유사도만 보고.
IT 회사 환경에서는 문서가 끊임없이 업데이트된다. 전체 리빌드를 매번 하는 것은 비효율적이므로, 증분 업데이트(Incremental Update) 전략이 필요하다:
전략 1: 타임스탬프 기반 증분 동기화
[변경 감지]
최종 동기화 시각 이후 수정된 문서만 추출
(Confluence: lastModified, Git: git log --since, 파일시스템: mtime)
│
▼
[기존 청크 제거]
해당 소스의 기존 청크를 chunks_meta.jsonl에서 삭제
│
▼
[신규 청킹]
변경된 문서만 chunk.py로 재처리
│
▼
[증분 인덱싱]
신규 청크만 임베딩 생성 → 기존 embeddings.npy에 append
BM25 인덱스는 전체 재구축 (in-memory 방식이므로 빠름)
전략 2: 버전 관리 기반 교체
문서에 버전이 있는 경우 (기획서 v1 → v2 → v3):
[신규 버전 감지]
v3 문서가 들어옴
│
▼
[이전 버전 청크 삭제]
source가 "기획서_v1", "기획서_v2"인 청크를 모두 제거
│
▼
[신규 버전 청킹 + 인덱싱]
v3만 코퍼스에 남김
이렇게 하면 "정보 부패" 문제를 구조적으로 방지할 수 있다. 이전 버전을 아카이브에 보관하되, 검색 대상에서는 제외한다.
전략 3: TTL(Time-To-Live) 기반 만료
빈번히 변경되는 문서(주간 보고, 스프린트 기획 등)에는 TTL을 설정:
# 청크 메타데이터에 만료 시각 추가
{
"chunk_id": "sprint_plan_0001",
"source": "2026_Q1_sprint_plan.md",
"expires_at": "2026-04-01T00:00:00", # 분기 종료 시 만료
...
}
# 검색 시 만료된 청크 필터링
results = [r for r in results if r.get("expires_at", "9999") > now()]
코퍼스의 정보가 현실과 불일치하는 것을 자동으로 감지하는 방법:
방법 1: 정기적 갭 검색 (이 프로젝트에서 사용)
핵심 쿼리 세트를 정의하고, 주기적으로 검색하여 점수 변화를 추적:
쿼리: "결제 API v3 엔드포인트"
- 1주 전 score: 0.85 (정상)
- 현재 score: 0.45 (급락) → 문서가 삭제되었거나 변경됨 → 알림
방법 2: 사용자 피드백 루프
RAG 기반 봇이 답변할 때 "도움이 되었나요?" 버튼을 추가:
방법 3: 교차 검증
같은 주제에 대해 여러 소스가 있을 때, 답변이 서로 모순되면 플래그:
소스 A: "결제 타임아웃은 30초"
소스 B: "결제 타임아웃은 60초"
→ 모순 감지 → 어느 것이 최신인지 확인 필요
코퍼스는 시간이 지나면 반드시 열화된다. 새로운 질문이 나오고, 기존 정보가 낡고, 빈 영역이 드러난다. 이것을 사람이 수동으로 관리하면 지속 불가능하다. "갭을 찾고 → 채우고 → 검증하는" 사이클을 자동화해야 RAG가 시간이 지날수록 좋아진다.
┌───────────────────────────────────────────┐
│ │
▼ │
Step 1: 갭 분석 │
(핵심 쿼리 배치 검색, score < 0.75 = 갭) │
│ │
▼ │
Step 2: 자료 수집 │
(수집 에이전트 병렬, 온라인 검증 소스만) │
│ │
▼ │
Step 3: 출처 검증 + 중복 검사 │
(출처 없는 파일 삭제, dedup.py) │
│ │
▼ │
Step 4: 인덱스 리빌드 │
(chunk.py → embed.py) │
│ │
▼ │
Step 5: 검증 (동일 쿼리 재검색) │
│ │
▼ │
Step 6: 수렴 판단 │
모든 쿼리 score ≥ 0.75? ──No──────────────────────┘
│
Yes
▼
수렴 완료
수렴 조건:
사주 프로젝트에서는 "온라인에서 자료를 검색하여 수집"하는 방식이지만, IT 회사에서는 자료 소스가 다르다:
| 단계 | 사주 프로젝트 | IT 회사 적용 |
|---|---|---|
| 갭 감지 | 20개 핵심 쿼리 배치 검색 | 사용자 쿼리 로그에서 score < 0.5인 쿼리 자동 수집 |
| 자료 수집 | 온라인 검색 에이전트 | Confluence/Notion/Git 동기화 스케줄러 |
| 출처 검증 | URL 유무 확인 | 문서 소유자·최종수정일 확인 |
| 중복 검사 | 코사인 유사도 0.92 | 문서 ID 기반 중복 방지 + 코사인 유사도 |
| 리빌드 | 전체 재청킹+재임베딩 | 증분 업데이트 (변경분만) |
| 검증 | 동일 쿼리 재검색 | A/B 테스트 (이전 인덱스 vs 새 인덱스) |
구체적 시나리오: 서비스 기획서 RAG
[매일 02:00 크론잡]
↓
Confluence API로 최근 24h 내 수정된 페이지 목록 조회
↓
수정된 페이지: 기존 청크 삭제 → 신규 청킹 → 증분 임베딩
삭제된 페이지: 기존 청크 삭제
신규 페이지: 청킹 → 임베딩 → 추가
↓
BM25 인덱스 재구축 (in-memory라 30초 이내)
↓
검증 쿼리 셋 실행 → 이전 점수 대비 급락 항목 알림
갭 분석의 핵심은 "이 쿼리에 대해 좋은 결과가 나와야 한다"는 기대 쿼리 세트를 잘 설계하는 것이다:
// 이 프로젝트의 갭 쿼리 (20개)
[
"오행 상생상극 생극제화",
"지장간 투출 통근 격국",
"십성 식신 상관 정관 편관",
"용신 억부 조후 통관",
"궁합 천간합 오행 배합",
...
]
IT 환경에서는:
// 서비스 Knowledge Base 갭 쿼리 예시
[
"결제 API 타임아웃 설정값",
"iOS 앱 최소 지원 버전",
"신규 가입 프로모션 할인율",
"장바구니 상품 최대 개수",
"개인정보 보유 기간 정책",
...
]
각 쿼리에 대해 기대 score 임계값을 설정하고, 주기적으로 검증한다. 임계값 미달 쿼리가 발생하면 해당 영역의 문서가 부족하거나 변경되었다는 신호이다.
사용자: "1990-05-15 14:30 남자 사주 봐줘"
│
▼
┌─────────────────────────────────┐
│ saju-analysis (오케스트레이터) │
│ Phase 1: saju.py 실행 → JSON │
│ Phase 2: 5개 서브에이전트 병렬 │
│ Phase 3: 결과 통합 → .md 출력 │
└─────────┬───────────────────────┘
│ Agent() × 5 (parallel)
┌─────┼─────┬──────┬──────┐
▼ ▼ ▼ ▼ ▼
saju- saju- saju- saju- saju-
profile structure dynamics fortune timeline
│ │ │ │ │
│ 각 에이전트가 search.py로 RAG 검색
│ (주제당 2~3개 쿼리, 총 ~30회 이내)
│ │ │ │ │
└─────┴─────┴──────┴──────┘
│
각자 원문 인용 기반 분석 작성
│
▼
오케스트레이터가 통합·중복 제거
│
▼
output/YYYY-MM-DD_이름_일주_사주.md
궁합 분석(gunghap-analysis)도 동일한 구조로 5개 서브에이전트(gunghap-mind-energy, gunghap-ilji, gunghap-destiny, gunghap-yongsin, gunghap-timeline)를 병렬 실행한다. 사주 분석과 궁합 분석 모두 동일한 RAG 검색 프로토콜을 따른다.
각 서브에이전트는 동일한 규칙을 따른다:
검색 명령:
.venv/bin/python scripts/search.py \
"갑목(甲木) 인월(寅月) 조후용신" \
"궁통보감(窮通寶鑑) 갑목 봄" \
"甲木 寅月 用神" \
--top-k 3 --mode hybrid --json
결과 활용 기준:
score ≥ 0.6 → 고신뢰. 원문을 반드시 인용score 0.3~0.59 → 조건부 포함 (해당 주제의 유일한 결과이면 포함)score < 0.3 → 제외원문 보존 원칙:
서브에이전트는 검색 결과를 요약하지 않고 원문을 그대로 전달한다. 해석은 오케스트레이터가 담당. 이렇게 분리해야 원전 인용의 정확성이 보장된다.