홍승아블로그

시맨틱(의미 기반) + 키워드 하이브리드 검색 RAG 구축

0. 배경

특정 비즈니스 로직(결제, 인증, 캠페인 등)이 프론트엔드·백엔드 서비스 등 여러 레포에 걸쳐 구현되어 있어, 관련 코드를 찾기 위해 각 레포를 개별적으로 검색해야 하는 비효율이 발생하고 있다.

기존 GitHub 검색은 키워드 기반으로만 동작하여 “결제 처리 로직”, “캠페인 생성 흐름”과 같은 의미 기반 검색이 불가능하다. 또한 Claude AI를 활용한 코드 분석 시, AI가 사내 코드베이스에 직접 접근할 수 없어 컨텍스트 제공에 수작업이 필요하다.

이러한 문제를 해결하기 위해 다중 레포 코드를 통합 인덱싱하고, 시맨틱 검색 + Claude AI 연동을 제공하는 RAG 시스템을 구축하게 되었다.


1. 개요

code-retrieval은 사내 다중 GitHub 레포지토리의 코드를 대상으로 시맨틱(의미 기반) + 키워드 하이브리드 검색을 제공하는 RAG(Retrieval-Augmented Generation) 시스템이다.

개발자가 자연어 질의로 여러 레포에 흩어진 관련 코드를 한 번에 찾을 수 있으며, Claude AI와 직접 연동하여 코드 이해·분석 작업을 지원한다.

2가지 접근 경로를 제공한다.

  • REST API (FastAPI): 웹 UI, 외부 서비스 연동용
  • MCP 서버 (FastMCP): Claude Desktop / Claude Code에서 직접 코드 검색용

2. 목표

핵심 목표

  • 다중 레포 코드를 하나의 검색 엔진으로 통합하여 개발자의 코드 탐색 시간을 단축한다
  • “결제 처리 함수”, “캠페인 생성 API” 같은 자연어 질의로 의미 기반 코드 검색을 가능하게 한다
  • Claude AI가 사내 코드베이스에 직접 접근하여 코드 분석·리뷰·질의응답을 수행할 수 있게 한다

기술 목표

  • AST 기반 구조적 코드 청킹으로 함수·클래스 단위의 의미 있는 검색 결과를 제공한다
  • Dense(시맨틱) + Sparse(키워드) 하이브리드 검색 + 리랭킹으로 검색 품질을 극대화한다
  • git diff 기반 증분 동기화로 코드 변경 시 전체 재인덱싱 없이 효율적으로 업데이트한다

3. 설치 및 실행 (Installation & Execution)

3.1 사전 요구사항

  • Python 3.12
  • Docker 또는 Colima (Qdrant 실행용, Docker Desktop 불필요)
  • uv (Python 패키지 매니저)

3.2 의존성 설치

uv sync --extra dev

3.3 Qdrant 벡터 DB 실행

colima start
docker compose up -d qdrant

3.4 레포 등록 및 인덱싱

# GitHub 레포 등록 + 인덱싱
uv run python -m src.indexer --add-repo https://github.com/org/repo.git

# 로컬 레포 등록
uv run python -m src.indexer --add-repo /local/path --local

# Private 레포 (토큰 지정)
uv run python -m src.indexer --add-repo <url> --branch develop --token ghp_xxx

# 서비스 별칭 지정
uv run python -m src.indexer --add-repo /local/path --local --description "서비스"

3.5 서버 실행

# FastAPI 서버 (REST API)
uv run python -m src.api.app
# → http://localhost:8000

# MCP 서버 (streamable-http)
uv run python -m src.mcp
# → http://localhost:8765/mcp

# MCP 서버 (stdio 모드)
MCP_TRANSPORT=stdio uv run python -m src.mcp

3.6 동기화 및 운영

# 특정 레포 동기화 (증분)
uv run python -m src.indexer --sync org/repo

# 전체 레포 동기화
uv run python -m src.indexer --sync-all

# 특정 레포 재인덱싱 (전체)
uv run python -m src.indexer --reindex org/repo

# 전체 레포 재인덱싱
uv run python -m src.indexer --reindex

# 등록된 레포 목록 확인
uv run python -m src.indexer --list

3.7 테스트

uv run pytest                             # 전체 테스트
uv run pytest tests/test_reranker.py -v   # 특정 모듈
uv run ruff check src/                    # 린트

4. 프로젝트 구조

code-retrieval/
├── src/
│   ├── config.py                    # Pydantic Settings — 환경변수 정의
│   ├── indexer/                     # 인덱싱 파이프라인
│   │   ├── cli.py                   # CLI 엔트리포인트
│   │   ├── chunker.py              # Python AST 기반 코드 청킹
│   │   ├── treesitter_chunker.py   # Tree-sitter 다국어 구조적 청킹
│   │   ├── embedder.py             # FastEmbed dense + sparse 임베딩
│   │   ├── qdrant_sync.py          # Qdrant upsert/delete (insert-then-delete)
│   │   ├── qdrant_init.py          # Qdrant 컬렉션 자동 생성
│   │   ├── repo_manager.py         # 레포 등록/클론/동기화 + registry.json
│   │   ├── git_diff.py             # Git diff 변경 감지 + 필터링
│   │   ├── context_enricher.py     # Ollama 기반 컨텍스트 요약 (선택)
│   │   └── poller.py               # 폴링 기반 자동 동기화
│   ├── search/                      # 검색 엔진
│   │   ├── hybrid.py               # 하이브리드 검색 (RRF 융합)
│   │   ├── reranker.py             # BGE-Reranker-v2-M3 리랭킹
│   │   ├── query.py                # 쿼리 전처리 (NFC, 키워드 추출)
│   │   └── metrics.py              # 검색 품질 평가 메트릭 (MRR, NDCG 등)
│   ├── mcp/                         # Claude MCP 서버
│   │   ├── server.py               # FastMCP 인스턴스 + 4개 도구
│   │   ├── formatter.py            # 응답 포맷터
│   │   └── __main__.py             # MCP 엔트리포인트
│   └── api/                         # REST API
│       ├── app.py                   # FastAPI 앱 팩토리
│       ├── dependencies.py          # 의존성 주입
│       └── routes/                  # 라우트 (search, repos, health)
├── tests/
│   ├── eval/                        # 검색 품질 평가
│   │   ├── golden_queries.json     # 평가용 골든 쿼리셋
│   │   └── evaluate.py             # 평가 스크립트
│   ├── test_reranker.py
│   ├── test_treesitter_chunker.py
│   ├── test_contextual_chunking.py
│   └── test_metrics.py
├── data/
│   └── registry.json                # 레포 메타데이터 레지스트리
├── docker-compose.yml               # Qdrant + App 컨테이너
├── pyproject.toml
└── .env                             # 환경변수 설정

5. 기술 스택

구분 기술 용도
언어 Python 3.12 전체 시스템
벡터 DB Qdrant (Docker / Colima) 코드 벡터 저장 및 하이브리드 검색
Dense 임베딩 multilingual-e5-large (1024d) 시맨틱 검색용 벡터 생성
Sparse 임베딩 BM25 (Qdrant/bm25) 키워드 검색용 희소 벡터 생성
임베딩 런타임 FastEmbed (ONNX) 경량 임베딩 추론
리랭커 BGE-Reranker-v2-M3 검색 결과 재순위 (FlagEmbedding)
코드 파서 (Python) ast 모듈 Python AST 기반 구조적 청킹
코드 파서 (다국어) tree-sitter-language-pack JS/TS/Java/Kotlin/Go/Rust 청킹
API 프레임워크 FastAPI + uvicorn REST API 서버
MCP 프레임워크 FastMCP Claude Desktop / Code 연동
패키지 관리 uv 의존성 관리 및 실행
컨테이너 Docker Compose 인프라 구성
LLM (선택) Ollama (gemma4:26b) 청크 컨텍스트 요약 보강

6. 시스템 구조 및 흐름

6.1 전체 아키텍처

┌─────────────────────────────────────────────────────────┐
│                     접근 계층                              │
│  ┌──────────────┐    ┌──────────────────────────────┐    │
│  │  REST API    │    │  MCP 서버                     │    │
│  │  (FastAPI)   │    │  (FastMCP)                    │    │
│  │  :8000       │    │  :8765/mcp 또는 stdio         │    │
│  └──────┬───────┘    └──────────────┬────────────────┘    │
│         │                           │                     │
│         └───────────┬───────────────┘                     │
│                     ▼                                     │
│  ┌─────────────────────────────────────────┐              │
│  │           검색 엔진 (HybridSearcher)      │              │
│  │  쿼리전처리 → Dense+Sparse → RRF → Rerank │              │
│  └─────────────────────┬───────────────────┘              │
│                        ▼                                  │
│  ┌─────────────────────────────────────────┐              │
│  │          Qdrant 벡터 DB (:6333)          │              │
│  │  Dense 벡터 (1024d) + Sparse 벡터 (BM25)  │              │
│  └─────────────────────────────────────────┘              │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                   인덱싱 파이프라인                         │
│                                                          │
│  GitHub URL ──→ git clone ──→ 파일 탐색                   │
│       │                          │                       │
│       │                          ▼                       │
│       │              AST 청킹 (Python ast / Tree-sitter)  │
│       │                          │                       │
│       │                          ▼                       │
│       │              임베딩 생성 (Dense + Sparse)           │
│       │                          │                       │
│       │                          ▼                       │
│       │              Qdrant upsert (insert-then-delete)  │
│       │                                                  │
│  동기화: git pull → git diff → 변경 파일만 재청킹 → upsert   │
└─────────────────────────────────────────────────────────┘

6.2 인덱싱 흐름

  1. 레포 등록: GitHub URL 또는 로컬 경로를 CLI로 등록, data/registry.json에 메타데이터 저장
  2. 코드 수집: git clone (또는 기존 로컬 레포 참조)
  3. 파일 필터링: 확장자 기반 인덱싱 대상 판별 (.py, .ts, .kt 등), node_modules/, .git/ 등 제외
  4. AST 청킹: Python은 ast 모듈, JS/TS/Java/Kotlin/Go/Rust는 Tree-sitter로 함수·클래스·모듈 단위 분할
  5. 임베딩 생성: FastEmbed으로 Dense(1024d) + Sparse(BM25) 벡터 동시 생성
  6. Qdrant 저장: Insert-then-Delete 패턴으로 안전하게 upsert

6.3 검색 흐름

  1. 쿼리 전처리: 유니코드 NFC 정규화 → camelCase/snake_case 코드 키워드 자동 추출
  2. 임베딩 생성: 쿼리를 Dense + Sparse 벡터로 변환
  3. 하이브리드 검색: Qdrant에서 Dense 검색과 Sparse 검색을 각각 수행 (prefetch_limit=100)
  4. RRF 융합: Reciprocal Rank Fusion으로 두 검색 결과를 하나로 병합
  5. 리랭킹: BGE-Reranker-v2-M3로 상위 후보를 크로스인코더 재순위 매김
  6. 결과 반환: top_k개의 코드 청크를 관련도 순으로 반환 (GitHub 링크 포함)

6.4 증분 동기화 흐름

  1. git fetch → git reset –hard origin/branch
  2. git diff –name-only old_sha..new_sha로 변경/삭제 파일 감지
  3. 삭제된 파일: Qdrant에서 해당 파일의 청크 삭제
  4. 변경된 파일만 재청킹 → 재임베딩 → Qdrant sync_file (insert-then-delete)

7. 주요 기능

7.1 AST 기반 구조적 코드 청킹

단순 텍스트 분할이 아닌 코드의 구문 구조(AST)를 분석하여 의미 있는 단위로 분할한다.

언어 파서 청킹 단위
Python ast 모듈 함수, 클래스, 모듈 레벨 코드
JS/TS Tree-sitter 함수, 클래스, 인터페이스, arrow function
Java Tree-sitter 메서드, 클래스, 인터페이스
Kotlin Tree-sitter 함수, 클래스, object 선언
Go Tree-sitter 함수, 메서드, 타입 선언
Rust Tree-sitter 함수, impl, struct, trait, enum
기타 (YAML, JSON 등) 고정 크기 ~512 토큰 단위, 50 토큰 오버랩

큰 클래스 처리: 100줄 초과 클래스는 메서드별 개별 청크 + 클래스 헤더 청크로 분할

7.2 하이브리드 검색 (Dense + Sparse + RRF)

  • Dense 검색: multilingual-e5-large로 의미 기반 유사도 검색 (한국어/영어 모두 지원)
  • Sparse 검색: BM25로 정확한 키워드 매칭 (함수명, 변수명 등 코드 식별자에 강점)
  • RRF 융합: 두 검색 결과를 Reciprocal Rank Fusion으로 통합하여 상호 보완

7.3 BGE-Reranker 리랭킹

1차 검색 결과(top_k x 3개 후보)를 크로스인코더 모델로 정밀 재순위 매김한다.

  • FP16 추론: 메모리 절감 및 속도 향상
  • 배치 처리: 32건씩 배치 스코어링
  • 메타데이터 부스트 (선택): 함수명·파일경로·언어 매칭 시 가산점 부여

7.4 MCP 도구 (Claude 연동)

Claude Desktop / Claude Code에서 직접 호출 가능한 4개 도구를 제공한다.

도구 기능 예시
code_search 자연어 쿼리로 다중 레포 코드 검색 “결제 처리 함수” → 관련 코드 반환
find_function 함수명으로 정확한 정의 위치 조회 “payment” → 함수 코드 + 위치
get_file_context 특정 파일의 전체 청크 조회 “src/auth/login.ts” → 파일 내 모든 함수
list_repos 인덱싱된 레포 목록 및 통계 레포별 상태, 청크 수, 동기화 시점

7.5 선택적 기능 (Feature Flag) — 상세

아래 3가지 기능은 환경변수로 개별 활성화할 수 있으며, 모두 비활성화 상태에서도 시스템은 정상 동작한다.

기능 환경변수 기본값 적용 시점
Contextual Chunking ENABLE_CONTEXTUAL_CHUNKING false 인덱싱 시
Ollama 컨텍스트 보강 ENABLE_OLLAMA_CONTEXT false 인덱싱 시
메타데이터 부스트 ENABLE_RERANKER_METADATA_BOOST false 검색 시

7.5.1 Contextual Chunking (휴리스틱 문맥 프리픽스)

목적: 코드 청크만으로는 부족한 문맥 정보(이 코드가 어떤 파일에 있고, 무엇을 import하며, 누가 호출하는지)를 자동으로 보강하여 검색 정확도를 높인다.

활성화: ENABLE_CONTEXTUAL_CHUNKING=true

전체 흐름

┌─────────────────────────────────────────────────────────────┐
│                 Contextual Chunking 흐름                      │
│                                                              │
│  소스 파일 읽기                                                │
│       │                                                      │
│       ▼                                                      │
│  AST 청킹 (함수/클래스/모듈 분할)                               │
│       │                                                      │
│       ▼                                                      │
│  [ENABLE_CONTEXTUAL_CHUNKING=true?] ──── false ──→ 임베딩     │
│       │ true                                                 │
│       ▼                                                      │
│  각 청크에 대해 build_context_prefix() 실행                     │
│       │                                                      │
│       ├── (1) 파일 경로 추출: "File: src/auth/login.py"        │
│       ├── (2) 언어 식별: "Language: python"                    │
│       ├── (3) 클래스 스코프: "Class: AuthService" (메서드인 경우)│
│       ├── (4) import 추출 (상위 5줄): "Imports: from x import y"│
│       ├── (5) docstring 추출 (첫 줄, 최대 120자)               │
│       └── (6) 호출자 탐지 (최대 3개): "Callers: main, handler" │
│       │                                                      │
│       ▼                                                      │
│  적응형 토큰 예산 계산                                          │
│  formula: min(80, (512 - chunk_tokens) * 0.2)                │
│  → 예산 초과 시 prefix 절삭                                    │
│       │                                                      │
│       ▼                                                      │
│  chunk.contextual_prefix에 저장                               │
│       │                                                      │
│       ▼                                                      │
│  임베딩 시 prefix + raw_content 결합하여 벡터 생성               │
│  (embedding_content = contextual_prefix + "\n" + raw_content) │
└─────────────────────────────────────────────────────────────┘

symbol_type별 전략

symbol_type 생성되는 프리픽스 정보
function / class File + Language + Class scope + Imports(5줄) + Docstring + Callers(3개)
module / config File + Language만 (최소 정보)

호출자 탐지 로직 (_find_callers)

  • 같은 파일 내에서 def/function/const/let/var/fun/func 선언을 추적하여 현재 함수 스코프를 판별
  • 청크 자체 범위(start_line~end_line) 밖에서 해당 심볼을 참조하는 함수를 최대 3개까지 추출
  • 길이 3 미만이거나 <module>, <anonymous> 같은 제네릭 이름은 건너뜀

docstring 추출 로직 (extract_docstring)

  • Python: ast.get_docstring()으로 첫 번째 함수/클래스의 docstring 첫 줄 추출
  • JS/TS/Java/Kotlin: 선행 // 주석 또는 JSDoc /** … */ 블록의 텍스트 추출 (최대 120자)

7.5.2 Ollama 컨텍스트 보강 (LLM 요약)

목적: 휴리스틱 프리픽스에 더해, 로컬 LLM이 코드 청크의 의미를 한 문장으로 요약하여 시맨틱 검색 품질을 추가로 향상시킨다.

활성화: ENABLE_CONTEXTUAL_CHUNKING=true AND ENABLE_OLLAMA_CONTEXT=true (Contextual Chunking이 선행 활성화되어야 함)

사전 요구사항: Ollama 서버 실행 + gemma4:26b 모델 다운로드

# Ollama 설치 후
ollama pull gemma4:26b
ollama serve   # http://localhost:11434

전체 흐름

┌───────────────────────────────────────────────────────────────┐
│               Ollama 컨텍스트 보강 흐름                          │
│                                                                │
│  [Contextual Chunking 완료된 청크 목록]                         │
│       │                                                        │
│       ▼                                                        │
│  [ENABLE_OLLAMA_CONTEXT=true?] ──── false ──→ 임베딩 단계로     │
│       │ true                                                   │
│       ▼                                                        │
│  OllamaContextEnricher 초기화                                   │
│       │                                                        │
│       ▼                                                        │
│  Ollama 서버 가용성 확인 (GET /api/tags)                         │
│  → 모델(gemma4:26b) 존재 여부 확인                                │
│       │                                                        │
│       ├── 사용 불가 → WARNING 로그, 휴리스틱 프리픽스만 사용       │
│       │                                                        │
│       ▼ 사용 가능                                               │
│  각 청크에 대해 (function/class 타입 + prefix 보유한 것만):       │
│       │                                                        │
│       ▼                                                        │
│  LLM 프롬프트 생성:                                             │
│  "Summarize this code chunk in one concise sentence (max 80    │
│   chars). Focus on WHAT it does and WHY, not implementation    │
│   details."                                                    │
│  + Context: {기존 휴리스틱 prefix}                               │
│  + Code: {raw_content 앞 1500자}                                │
│       │                                                        │
│       ▼                                                        │
│  POST /api/generate                                            │
│  model: gemma4:26b                                              │
│  options: num_predict=60, temperature=0.1                      │
│  timeout: 30초                                                  │
│       │                                                        │
│       ▼                                                        │
│  응답 후처리: 첫 줄만 추출, 최대 120자 절삭                       │
│       │                                                        │
│       ▼                                                        │
│  기존 prefix에 "Summary: {요약}" 줄 추가                         │
│  chunk.contextual_prefix += "\nSummary: {summary}"             │
│       │                                                        │
│       ▼                                                        │
│  임베딩 단계로 (prefix + summary + raw_content 결합)             │
└───────────────────────────────────────────────────────────────┘

Graceful Fallback 설계

  • Ollama 서버가 꺼져 있거나 모델이 없는 경우: WARNING 로그 출력 후 휴리스틱 프리픽스만으로 정상 동작
  • 개별 청크 요약 실패: 해당 청크만 건너뛰고 나머지 계속 처리
  • 타임아웃(30초) 초과: 해당 청크 스킵

관련 환경변수

변수 기본값 설명
ENABLE_OLLAMA_CONTEXT false Ollama 보강 활성화
OLLAMA_URL http://localhost:11434 Ollama 서버 주소
OLLAMA_MODEL gemma4:26b 사용할 모델
OLLAMA_CONTEXT_TIMEOUT 30 청크당 타임아웃 (초)

7.5.3 메타데이터 부스트 (Reranker Metadata Boost)

목적: 리랭킹 단계에서 코드의 의미적 유사도뿐만 아니라, 쿼리와 메타데이터(함수명, 파일명, 언어)의 직접적 매칭에 가산점을 부여하여 정확한 코드를 상위로 올린다.

활성화: ENABLE_RERANKER_METADATA_BOOST=true

전체 흐름

┌───────────────────────────────────────────────────────────────┐
│              메타데이터 부스트 흐름                                │
│                                                                │
│  검색 쿼리: "payment 결제 함수 typescript"                │
│       │                                                        │
│       ▼                                                        │
│  1차 하이브리드 검색 (Dense + Sparse + RRF)                      │
│  → 후보 30개 (top_k x 3)                                       │
│       │                                                        │
│       ▼                                                        │
│  [ENABLE_RERANKER_METADATA_BOOST=true?]                        │
│       │                                                        │
│       ├── false → 일반 rerank (코드 텍스트만으로 스코어링)         │
│       │                                                        │
│       ▼ true                                                   │
│  BGE-Reranker로 base_score 산출 (코드 텍스트 기반)               │
│       │                                                        │
│       ▼                                                        │
│  각 후보에 대해 메타데이터 부스트 계산:                             │
│       │                                                        │
│       ├── symbol_name 완전 매칭                                  │
│       │   "payment" in query → +boost x 2.0              │
│       │                                                        │
│       ├── symbol_name 부분 매칭 (클래스.메서드 중 메서드 부분)      │
│       │   "payment.api" → 마지막 부분 매칭      │
│       │   → +boost x 1.0                                       │
│       │                                                        │
│       ├── file_path stem 매칭 (파일명 >= 3자)                    │
│       │   "payment.api.ts" stem = "payment" in query  │
│       │   → +boost x 0.5                                       │
│       │                                                        │
│       └── language 매칭                                         │
│           "typescript" in query → +boost x 0.3                  │
│       │                                                        │
│       ▼                                                        │
│  final_score = base_score + total_boost                        │
│       │                                                        │
│       ▼                                                        │
│  final_score 기준 내림차순 정렬                                   │
│       │                                                        │
│       ▼                                                        │
│  score_threshold 필터링 → top_k 결과 반환                        │
└───────────────────────────────────────────────────────────────┘

부스트 가중치 상세

매칭 조건 가중치 예시
symbol_name 완전 매칭 boost x 2.0 쿼리에 “payment” → symbol_name이 “processPayment”인 청크에 +0.2
symbol_name 부분 매칭 boost x 1.0 쿼리에 “payment” → “payment.api”의 마지막 부분 매칭 시 +0.1
file_path stem 매칭 boost x 0.5 쿼리에 “login” → file_path가 “src/auth/login.ts”이면 stem “login” 매칭 시 +0.05
language 매칭 boost x 0.3 쿼리에 “typescript” → language가 “typescript”인 청크에 +0.03

(기본 boost factor = 0.1, RERANKER_METADATA_BOOST 환경변수로 조정 가능)

관련 환경변수

변수 기본값 설명
ENABLE_RERANKER_METADATA_BOOST false 메타데이터 부스트 활성화
RERANKER_METADATA_BOOST 0.1 기본 부스트 계수 (높을수록 메타데이터 영향력 증가)
RERANKER_SCORE_THRESHOLD 0.0 최소 스코어 임계값 (이하 결과 제외)

7.5.4 선택적 기능 조합 매트릭스

3가지 기능은 독립적으로 또는 조합하여 사용할 수 있다. 아래는 가능한 조합과 효과를 정리한 것이다.

Contextual Chunking Ollama 보강 메타데이터 부스트 효과
OFF OFF OFF 기본 모드. 코드 텍스트만으로 검색. 가장 빠르고 가벼움
ON OFF OFF 휴리스틱 문맥(파일경로, import, docstring, 호출자)이 임베딩에 반영되어 검색 정확도 향상
ON ON OFF 휴리스틱 + LLM 요약으로 시맨틱 임베딩 품질 극대화. 인덱싱 시간 증가 (청크당 ~2-5초)
OFF OFF ON 검색 시 함수명/파일명 매칭에 가산점. 정확한 이름을 알고 있을 때 효과적
ON OFF ON 인덱싱 시 문맥 보강 + 검색 시 메타데이터 부스트. 균형 잡힌 권장 조합
ON ON ON 모든 기능 활성화. 최고 품질이지만 인덱싱 시간 가장 길음

권장 구성

  • 빠른 시작: 모두 OFF (기본값) — 환경 구성 없이 즉시 사용
  • 품질 개선: ENABLE_CONTEXTUAL_CHUNKING=true + ENABLE_RERANKER_METADATA_BOOST=true — Ollama 없이도 유의미한 품질 향상
  • 최대 품질: 3가지 모두 ON — Ollama 서버 필요, 인덱싱 시간 증가 감수

7.6 검색 품질 평가 체계

Golden Query 기반 자동 평가 시스템을 내장하고 있다.

  • 평가 메트릭: MRR@K, NDCG@K, Recall@K, Precision@K
  • 카테고리별 평가: function_search, class_search, api_search, config_search, concept_search
  • 베이스라인 비교: 이전 결과와 델타 비교로 검색 품질 변화 추적
uv run python -m tests.eval.evaluate --mode current --output results.json
uv run python -m tests.eval.evaluate --mode current --compare baseline.json

8. 결과

8.1 구현 완료 항목

  • 다중 레포 통합 인덱싱 파이프라인 (7개 프로그래밍 언어 AST 파싱 지원)
  • Dense + Sparse 하이브리드 검색 + RRF 융합 + BGE-Reranker 리랭킹
  • REST API (FastAPI) + MCP 서버 (FastMCP) 이중 접근 인터페이스
  • Claude Desktop / Claude Code 직접 연동 (4개 MCP 도구)
  • git diff 기반 증분 동기화 (전체 재인덱싱 불필요)
  • Insert-then-Delete 패턴으로 데이터 유실 방지
  • Golden Query 기반 검색 품질 자동 평가 체계
  • Docker Compose 기반 원클릭 인프라 구성

8.2 지원 언어

  • AST 구조적 파싱 (7개): Python, JavaScript, TypeScript, Java, Kotlin, Go, Rust
  • 고정 크기 청킹: Markdown, YAML, JSON, TOML, HTML, CSS, SQL 등

8.3 활용 시나리오

  • 개발자가 “로그인 처리 로직”으로 검색하면 프론트엔드·백엔드 전체에서 관련 코드를 찾아줌
  • Claude Code에서 MCP 도구로 사내 코드를 직접 참조하며 코드 리뷰·분석 수행
  • 신규 입사자가 특정 기능의 구현 위치를 빠르게 파악
  • 코드 중복 탐지 및 크로스 레포 의존성 파악

9. 사용자 사용 가이드 (Getting Started)

GitHub: https://github.com/seungahhong/code-retrieval 본 가이드는 현재 프로젝트 환경 기준이다 — Python 3.12 (uv가 자동 프로비저닝), Qdrant는 Docker Desktop 없이 Colima, 컨텍스트 보강 LLM은 gemma4:26b(선택).

사용자는 두 가지 방식으로 이 시스템을 사용한다.

  • (A) Claude에서 바로 검색 — Claude Desktop / Claude Code에 MCP 서버로 연동 (가장 권장)
  • (B) REST API 호출 — 웹 UI·스크립트·외부 서비스 연동

9.1 사전 요구사항

항목 버전 / 비고
uv Python 패키지 매니저 (Python 3.12 자동 설치)
Colima + Docker CLI brew install colima docker docker-compose (Docker Desktop 불필요)
Ollama (선택) 컨텍스트 보강(gemma4:26b) 사용 시에만

9.2 5분 빠른 시작

# 1) 레포 클론
git clone https://github.com/seungahhong/code-retrieval.git
cd code-retrieval

# 2) 의존성 설치 (uv가 Python 3.12 자동 프로비저닝)
uv sync --extra dev

# 3) Qdrant 벡터 DB 기동 (Colima)
colima start
docker compose up -d qdrant
curl -s http://localhost:6333/healthz        # 응답: ok
# 웹 대시보드: http://localhost:6333/dashboard

# 4) 환경변수
cp .env.example .env
# Private 레포를 색인하려면 .env의 GITHUB_TOKEN 설정

# 5) 검색 대상 레포 등록 + 인덱싱
uv run python -m src.indexer --add-repo https://github.com/org/repo.git
uv run python -m src.indexer --add-repo /local/path --local --description "결제 서비스"
uv run python -m src.indexer --list          # 레포별 청크 수·상태 확인

9.3 사용 방법 A — Claude에서 바로 검색 (MCP)

인덱싱이 끝나면 Claude Desktop 또는 Claude Code에 MCP 서버로 연동하여 대화 중 사내 코드를 직접 검색·참조할 수 있다. Claude가 프로세스를 직접 spawn하므로 MCP_TRANSPORT=stdio가 필수다.

Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.json (macOS)

{
  "mcpServers": {
    "code-retrieval": {
      "command": "uv",
      "args": ["--directory", "/path/to/code-retrieval", "run", "python", "-m", "src.mcp"],
      "env": { "QDRANT_URL": "http://localhost:6333", "MCP_TRANSPORT": "stdio" }
    }
  }
}

Claude Code — 프로젝트 루트에서 한 줄로 등록

claude mcp add code-retrieval \
  --command "uv" \
  --args "--directory" "/path/to/code-retrieval" "run" "python" "-m" "src.mcp"

/path/to/code-retrieval를 실제 클론 경로로 변경한다. Claude Desktop은 재시작하면 MCP 도구가 자동 활성화된다.

연동 후 Claude에게 이렇게 질문한다 (내부적으로 4개 MCP 도구 호출):

질문 예시 호출되는 도구
“결제 처리 로직 검색해줘” code_search
“processPayment 함수 정의 어디 있어?” find_function
“src/auth/login.ts 파일 전체 보여줘” get_file_context
“인덱싱된 레포 목록 보여줘” list_repos

9.4 사용 방법 B — REST API

# 서버 실행 → http://localhost:8000 (대화형 문서: /docs)
uv run python -m src.api.app

주요 엔드포인트:

메서드 / 경로 설명 요청 본문(JSON) 주요 필드
POST /api/search 자연어 코드 검색 query(필수), top_k, repo, language
GET /api/repos 등록 레포 목록
GET /api/repos/{repo}/stats 레포별 청크·벡터 통계
POST /api/repos 레포 등록(백그라운드 인덱싱) url 또는 local_path, branch, description
POST /api/repos/{repo}/sync 레포 증분 동기화

검색 응답에는 results[](코드·파일경로·라인·github_url 포함), total_found, query_time_ms, repos_searched가 담긴다. 요청/응답 스키마와 직접 실행은 /docs(Swagger UI)에서 확인할 수 있다.

9.5 운영 — 코드 변경 반영 (증분 동기화)

레포에 새 커밋이 쌓이면 전체 재인덱싱 없이 git diff 기반으로 변경 파일만 갱신한다.

uv run python -m src.indexer --sync org/repo     # 특정 레포 증분 동기화
uv run python -m src.indexer --sync-all          # 전체 레포 동기화
uv run python -m src.indexer --reindex org/repo  # 전체 재인덱싱(필요 시)

REST API로도 동기화할 수 있다(POST /api/repos/{repo}/sync).

9.6 (선택) 검색 품질 강화

.env에서 플래그를 켠다. 모두 꺼도 정상 동작한다.

# 권장 조합 (Ollama 없이 품질 향상) — .env.example 기본값으로 이미 켜져 있음
ENABLE_RERANKER_METADATA_BOOST=true   # 검색 시 함수명/파일명 매칭 가산점
RERANKER_SCORE_THRESHOLD=0.3          # 저품질 결과 컷오프

# 최대 품질 (인덱싱 시 LLM 요약 보강) — Ollama 필요
ollama pull gemma4:26b && ollama serve
ENABLE_CONTEXTUAL_CHUNKING=true
ENABLE_OLLAMA_CONTEXT=true
OLLAMA_MODEL=gemma4:26b

플래그를 바꾼 뒤에는 효과 반영을 위해 대상 레포를 --reindex 해야 한다. 조합별 효과는 7.5.4 선택적 기능 조합 매트릭스 참고.

9.7 트러블슈팅

증상 확인 / 해결
MCP 도구가 Claude에 안 보임 Claude Desktop 완전 종료 후 재시작, config JSON 문법 확인, command를 which uv 절대경로로 지정
Qdrant 연결 실패 colima start → docker compose up -d qdrant → curl localhost:6333/healthz 순서 확인
검색 결과가 비어 있음 --list로 청크 수 확인, 인덱싱 완료(status=indexed) 여부 확인
Private 레포 클론 실패 .env의 GITHUB_TOKEN 또는 --token ghp_xxx 지정
GET /mcp 접속 시 406 정상 응답(핸드셰이크 헤더 없는 요청). 서버 생존 신호로 활용 가능
이전글
LLM Wiki: RAG를 넘어 지식을 미리 정리해 두는 위키 패턴
다음글
Vite 7.0