-
RAG 시스템에 코드 위키를 적용한 실전 사례 — 7개 문서로 11,000 벡터를 설명하다IT 2026. 4. 26. 23:00
코드 위키 시리즈, 실전으로 넘어가다
지금까지 코드 위키의 구성 요소를 하나씩 다뤘다 — architecture.md, data-pipeline.md, ADR, 모듈 문서. 이론은 충분하다. 이번에는 실제 프로젝트에 코드 위키를 만들어 본 결과를 공유한다.
대상 프로젝트는 개인 지식 금고(2,269개 마크다운 파일)를 벡터 DB로 인덱싱하고 검색하는 로컬 RAG 시스템이다. 12개 Python 모듈, 10개 운영 스크립트, 3가지 AI 모델이 협업하는 시스템에 7개 문서를 작성했다. 각 문서의 하이라이트를 보여주면서, 왜 그 내용이 중요한지를 설명한다.
1. index.md — 네비게이션 허브
코드 위키의 진입점이다. 실제 문서에서 발췌한 페이지 목록이다:
## 페이지 목록 - 시스템 아키텍처 (architecture.md) — 3-모델 구조, 인프라 의존성, 설정 체계 - 데이터 파이프라인 (data-pipeline.md) — 문서가 벡터로 변환되는 7단계 흐름 - 검색 파이프라인 (retrieval-pipeline.md) — 쿼리가 검색 결과가 되는 2-stage 흐름 - 운영 가이드 (operations.md) — 인덱싱 스크립트, 크론 설정, 트러블슈팅 - 품질 평가 (evaluation.md) — RAGAS 평가 파이프라인, 골든 셋 관리 - 모듈 레퍼런스 (module-reference.md) — 12개 모듈 + 10개 스크립트 API 빠른 참조이 한 페이지가 AI 에이전트에게 "이 프로젝트의 문서 체계가 어떻게 구성되어 있는가"를 알려주는 지도 역할을 한다. CLAUDE.md에서
docs/index.md를 참조하면, 에이전트가 필요한 문서를 즉시 찾아갈 수 있다.2. architecture.md — 3-모델 아키텍처
최신 architecture.md의 6-레이어 HLD를 SVG로 재현했다:
아래는 실제 문서에서 발췌한 3-모델 표다:
## 3-모델 아키텍처 | 모델 | 서비스 | 포트 | 역할 | GPU 메모리 | |----------------------|-------------|-------|-----------------------|-----------| | Qwen3-Embedding-4B | vLLM Docker | 8001 | 임베딩 (2560차원) | 16GB | | Gemma4:26B | Ollama | 11434 | 맥락 생성, RAGAS 채점 | 30GB | | BGE-reranker-v2-m3 | in-process | - | Cross-encoder reranking| CPU |이 RAG 시스템의 가장 독특한 점은 3가지 AI 모델이 각각 다른 역할을 맡는다는 것이다:
모델 역할 GPU 메모리 Qwen3-Embedding-4B 임베딩 (2560차원) 16GB Gemma4:26B 맥락 생성 + RAGAS 채점 30GB BGE-reranker-v2-m3 Cross-encoder 리랭킹 CPU 이 표 하나가 AI 에이전트에게 결정적인 정보를 준다. "임베딩 차원을 바꾸고 싶다"는 요청이 오면, 에이전트는 Qwen3-Embedding-4B가 2560차원을 출력한다는 것과 함께 Qdrant 컬렉션도 같은 차원으로 설정되어야 한다는 것을 즉시 파악한다. GPU 메모리 정보는 "새 모델을 추가해도 되는가?"라는 판단에도 필수다.
3. data-pipeline.md — 7단계 인덱싱 흐름
문서가 벡터가 되기까지 7단계를 거친다. 전체 흐름을 SVG로 표현하면 이렇다:
실제 문서에서 발췌한 청킹 전략 부분이다:
## 2단계: 청킹 (chunker.py) ### chunk_file(file_path, validate=True) → list[Chunk] 카테고리에 따라 4가지 전략으로 분기: if category == "life/calendar": → _chunk_whole_doc # 통문서 elif category == "notes": → _chunk_fixed_size # 고정 크기 elif _is_moc_file(file_path): → _chunk_moc # ## 단위 else: → _chunk_by_structure # 헤더 기반 ### 전략별 동작 통문서 (_chunk_whole_doc): 캘린더 노트용. CHUNK_MAX_TOKENS(1500) 이하이면 문서 전체를 1개 청크로. 프리픽스 [캘린더 노트 2026-04-18] 추가. 고정 크기 (_chunk_fixed_size): 비정형 노트용. 문장 단위로 분할하여 CHUNK_TARGET_TOKENS(512)까지 누적. 20% 오버랩으로 맥락 유지. 프리픽스 [노트 ...] 추가. 헤더 기반 구조 (_chunk_by_structure): 기본 전략. #, ##, ### 위치를 찾아 섹션별 분할. header_chain 스택을 유지하여 "문서 > 챕터 > 절" 계층 추적.이 중 가장 흥미로운 부분은 청킹 전략의 4-way 분기다.
캘린더 노트는 쪼개면 맥락이 사라지므로 통째로 1개 청크로 넣고, 비정형 노트은 구조가 없으므로 고정 크기로 자르되 20% 오버랩으로 맥락을 유지한다. 모든 문서를 같은 방식으로 청킹하지 않는 것이 이 시스템의 핵심 설계다.
이 분기 로직이 문서에 없으면 AI 에이전트가 새로운 문서 유형을 추가할 때 "기본 전략으로 하면 되겠지"라고 판단하고, 캘린더 노트처럼 특수 처리가 필요한 유형을 놓칠 수 있다.
4. retrieval-pipeline.md — 2-stage 검색
검색은 넓게 잡고 좁혀가는 2-stage 구조다. 전체 흐름을 SVG로 표현하면:
실제 문서의 수치 흐름 부분이다:
### 검색 파이프라인 수치 흐름 Qdrant 검색: 50건 후보 (SEARCH_TOP_K_INITIAL) | Reranking 입력: 10건 (SEARCH_TOP_K_RERANK) | 최종 반환: 5건 (SEARCH_TOP_K_FINAL) ### rerank(query, candidates, top_k=5) -> list[dict] 1. 후보 수가 top_k 이하이면 리랭킹 스킵. 2. 모델 로드: BAAI/bge-reranker-v2-m3 싱글턴 로드. 3. 입력 쌍 구성: [query, context_description + chunk_text[:300]] 4. 스코어링: model.predict(pairs) → 각 후보에 rerank_score 부여. 5. 정렬: rerank_score 기준 내림차순, 상위 top_k 반환. 6. Fallback: 예외 발생 시 bi-encoder 순서 유지.50 → 10 → 5로 줄어가는 숫자가 핵심이다. 왜 처음부터 5건만 검색하지 않는가? Bi-encoder(임베딩 유사도)는 빠르지만 정확도가 낮고, Cross-encoder(리랭커)는 정확하지만 느리기 때문이다. Bi-encoder로 넓게 후보를 잡고, Cross-encoder로 정밀하게 걸러내는 것이 비용 대비 최적의 전략이다.
이 수치(50, 10, 5)가
config.py의SEARCH_TOP_K_INITIAL,SEARCH_TOP_K_RERANK,SEARCH_TOP_K_FINAL에 있다는 것도 문서에 명시되어 있으므로, 에이전트가 검색 품질을 튜닝할 때 정확한 위치를 즉시 찾을 수 있다.5. operations.md — 3가지 인덱싱 전략
실제 문서의 인덱싱 전략 표다:
## 인덱싱 전략 3가지 | 방식 | 스크립트 | 용도 | 트리거 | |-------------|------------------------------|-------------------------|---------------------| | 전체 인덱싱 | scripts/full_index.py | 초기 구축, 재구축 | 수동 | | 증분 업데이트 | scripts/incremental_update.py| git commit 시 변경 파일 | git post-commit hook| | 야간 배치 | scripts/nightly_index.py | 큐 소화 + 정합성 검사 | Cron 매일 02:00 |운영 관점에서 가장 유용했던 부분은 인덱싱 전략의 구분이다:
방식 용도 트리거 소요 시간 전체 인덱싱 초기 구축, 재구축 수동 ~23분 증분 업데이트 git commit 시 변경분 post-commit hook 수 초 야간 배치 큐 소화 + 정합성 검사 Cron 매일 02:00 ~5분 트러블슈팅 섹션도 실용적이다. "임베딩 서버가 응답하지 않을 때", "Qdrant가 죽었을 때", "맥락 캐시가 손상됐을 때" 각각의 확인 명령과 복구 절차가 한 곳에 정리되어 있다. 새벽에 장애가 나도 이 문서만 보면 복구할 수 있다.
6. evaluation.md — RAGAS 평가
실제 문서에서 발췌한 2-pass 최적화 구조다:
### 2-pass 최적화 구조 GPU 모델 swap 최소화를 위해 2-pass로 분리: Pass 1 (Embedding): 쿼리 임베딩 + Qdrant 검색 + Decay └─ vLLM 임베딩 모델 사용 └─ 산출물: pass1_search.json ↓ 모델 전환 (임베딩 → Judge) Pass 2 (LLM): Reranking + 답변 생성 + 채점 └─ Gemma4:26B (Ollama) 사용 └─ 3단계: (A) LLM reranking → (B) 답변 생성 → (C) 메트릭 채점 └─ 산출물: pass2_judge_pipeline.jsonRAG 시스템에서 가장 간과되기 쉬운 것이 "이 검색 결과가 정말 좋은 건가?"를 측정하는 것이다. RAGAS 프레임워크의 4대 메트릭 중 이 시스템이 다루는 것은:
메트릭 의미 상태 Context Precision 검색된 컨텍스트가 답변에 유용했는가 구현 완료 (목표: 0.80) Context Recall 정답에 필요한 컨텍스트를 빠뜨리지 않았는가 추가 예정 Faithfulness 답변이 컨텍스트에 근거하는가 (환각 방지) 구현 완료 (목표: 0.85) Answer Relevancy 답변이 질문에 적절한가 추가 예정 현재는 Context Precision과 Faithfulness 2개로 운영하고 있다. Context Recall(검색 누락 감지)과 Answer Relevancy(답변 적절성)는 LLM 호출 비용과의 트레이드오프를 고려하여 추가 예정이다.
특히 인상적인 것은 2-pass 최적화다. GPU 모델을 바꾸는 비용이 크므로, Pass 1에서 임베딩 모델로 검색을 모두 끝낸 후, 모델을 전환하고 Pass 2에서 LLM으로 채점을 몰아서 한다. 이런 최적화 결정은 코드만 봐서는 이해하기 어렵고, 문서로 명시해야 "왜 2번에 나눠서 하지?"라는 의문이 풀린다.
골든 셋은 50개 Q&A 쌍으로 구성되고, 일상 검증용 compact 모드(20건)와 정밀 평가용 full 모드(50건)로 나뉜다.
trend.jsonl에 실행마다 결과를 누적하여 시계열 추이를 추적한다.7. module-reference.md — 빠른 참조 카드
마지막 문서는 12개 모듈과 10개 스크립트의 API 시그니처를 한 곳에 모은 참조 카드다. 실제 문서에서 발췌한 검색 관련 부분이다:
### retriever.py — 검색 API search(query: str, top_k: int = 5, category_filter: str | None = None) -> list[dict] safe_search(query: str, top_k: int = 5, category_filter: str | None = None) -> list[dict] ### reranker.py — Cross-encoder 리랭킹 rerank(query: str, candidates: list[dict], top_k: int = 5) -> list[dict] 모델: BAAI/bge-reranker-v2-m3 (sentence-transformers CrossEncoder, 싱글턴) ### indexer.py — 인덱싱 엔진 full_index(generate_context: bool = True) -> dict incremental_index(changed_files: list[str], generate_context: bool = True) -> dict ensure_qdrant(timeout: float = 10.0) # Docker 컨테이너 + 임베딩 서버 확인이런 참조 카드의 가치는 에이전트가 전체 코드를 읽지 않고도 사용 가능한 API를 파악할 수 있다는 데 있다. 특히
search와safe_search의 차이(후자는 서버 미가동 시 빈 리스트 반환)처럼, 이름만으로는 구분하기 어려운 함수들이 한 곳에 정리되어 있으면 에이전트의 함수 선택이 정확해진다.7개 문서의 역할 요약
문서 AI 에이전트에게 답해주는 질문 index.md "어떤 문서를 먼저 읽어야 하나?" architecture.md "어떤 모델이 어떤 역할이고 GPU를 얼마나 쓰나?" data-pipeline.md "문서가 벡터가 되기까지 무슨 일이 일어나나?" retrieval-pipeline.md "검색 결과는 어떤 과정을 거쳐 나오나?" operations.md "장애가 나면 어떻게 복구하나?" evaluation.md "검색 품질을 어떻게 측정하나?" module-reference.md "어떤 함수를 호출하면 되나?" 실전 후기: 작성하면서 발견한 것들
코드 위키를 작성하면서 예상치 못한 이점이 있었다:
- 코드의 일관성 부족 발견 — 문서로 정리하다 보니 "이 모듈은 에러를 raise하고, 저 모듈은 빈 리스트를 반환하네?" 같은 불일치가 눈에 띄었다
- 설정값의 산재 파악 —
config.py에 있어야 할 값이 개별 모듈에 하드코딩되어 있는 경우를 발견 - 운영 지식의 체화 — 트러블슈팅 절차를 문서로 쓰면서 "사실 이 절차는 스크립트로 자동화할 수 있겠네"라는 깨달음
코드 위키는 단순히 "문서를 남기는 것"이 아니라, 코드를 한 번 더 리뷰하는 과정이기도 했다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
하네스 엔지니어링 9단계 베스트 프랙티스 #4. 검증 (Hook) (0) 2026.04.27 하네스 엔지니어링 9단계 베스트 프랙티스 #3. Skill 정의 (0) 2026.04.27 하네스 엔지니어링 9단계 베스트 프랙티스 #2. Code Wiki 작성 (0) 2026.04.27 하네스 엔지니어링 9단계 베스트 프랙티스 #1. 프로젝트 규칙 설정 (0) 2026.04.27 하네스 엔지니어링 9단계 베스트 프랙티스 #0. 업무 프로세스 매핑 (0) 2026.04.27 코드 위키의 마지막 퍼즐, 모듈 문서 — AI 에이전트가 함수를 이해하는 방법 (1) 2026.04.26 ADR, 설계 결정을 기록하는 가장 가벼운 방법 — AI 에이전트가 '왜'를 알게 되는 문서 (1) 2026.04.26 데이터 파이프라인 문서에는 뭘 써야 할까 — architecture.md가 다루지 않는 것 (1) 2026.04.25 아키텍처 문서에는 뭘 써야 할까 — AI 에이전트가 읽는 시대의 architecture.md (0) 2026.04.25 Docs-as-Code로 사이드 프로젝트 문서화하기 — 코드 위키 실전 적용기 (1) 2026.04.25