Code-RAG 검색을 위한 인지적 벤치마크: 파트 2 — 모델 순위가 파이프라인에 따라 달라지는 이유
요약
Code-RAG 시스템에서 임베딩 모델의 성능이 청킹 전략, 검색 모드, 쿼리 문구 등 파이프라인 설정에 따라 어떻게 변하는지 분석한 연구입니다. Apache Kafka 프로젝트를 활용한 벤치마크를 통해 모델 순위가 설정에 따라 가변적임을 입증했습니다.
핵심 포인트
- 임베딩 모델의 성능은 단독 결정 요소가 아닌 파이프라인 설정에 의존함
- 청킹 전략과 검색 모드 변경 시 모델 간 성능 순위가 역전될 수 있음
- BM25, 벡터 검색, 하이브리드 검색 등 다양한 검색 모드 비교 수행
- 실제 다국어 프로젝트(Apache Kafka) 기반의 정교한 벤치마크 구축
개발자들이 익숙하지 않은 프로젝트에 들어갔을 때, 파일 이름을 통해 특정 파일을 검색하는 경우는 드뭅니다. 그들은 보통 시스템 동작에 대해 질문합니다. 예를 들어, 들어오는 연결이 어디서 수락되는지, 어떤 컴포넌트가 로그를 정리하는지, 또는 요청이 아키텍처 계층 간에 어떻게 이동하는지 등을 묻습니다.
Code-RAG (Code Retrieval-Augmented Generation)는 시맨틱 검색 (semantic search)을 통해 이러한 질문에 답하려고 시도합니다. 소스 코드를 분할하고 인덱싱한 다음, 개발자의 쿼리 (query)와 가장 밀접하게 관련된 컨텍스트 (context)를 검색합니다.
이 검색의 품질은 종종 임베딩 모델 (embedding model)의 선택으로 축소되곤 합니다. 즉, 여러 후보를 비교하고 가장 높은 지표를 가진 모델을 선택하는 방식입니다. 하지만 실제로 결과는 코드가 어떻게 분할되었는지와 어떤 검색 모드 (retrieval mode)가 사용되었는지에 따라서도 달라집니다.
이러한 의존성을 연구하기 위해, 저는 Java와 Scala로 작성된 실제 다국어 (polyglot) 프로젝트인 Apache Kafka 4.0.0 브로커 코어 (broker core)를 기반으로 Code-RAG 벤치마크를 구축했습니다. 시스템 동작에 관한 30개의 질문에 대해 사전에 올바른 파일들을 식별해 두었으며, 이를 통해 검색 파이프라인 (retrieval pipeline)이 관련 코드를 얼마나 정확하게 찾아내는지 측정할 수 있었습니다.
결과에 따르면, 모델 순위는 오직 특정 설정 내에서만 존재합니다. 청킹 전략 (chunking strategy), 검색 모드 (retrieval mode), 또는 쿼리 문구 (query phrasing)를 변경하면 지표 값과 순위 내 모델의 순서가 모두 바뀔 수 있습니다.
1. 실험 설정
본 연구의 이번 파트에서는 16개의 임베딩 모델 (embedding models), 5개의 청킹 (chunking) 설정, 그리고 3가지 검색 모드인 BM25, 벡터 검색 (vector search), 하이브리드 검색 (hybrid search)을 비교합니다. 30개의 각 질문은 자연스러운 개발자 질문부터 부정확한 용어를 사용한 쿼리 또는 인접 모듈에 대한 참조에 이르기까지 5가지 형태로 표현되었습니다. 이러한 변형들의 구조와 평가 방법론은 연구의 파트 1에 설명되어 있습니다.
저는 네 가지 변수 그룹을 비교했습니다:
| 변수 (Variable) | 변경 사항 | 테스트 내용 |
|---|---|---|
| 임베딩 모델 (Embedding model) | Ollama를 통한 7개의 로컬 모델 및 9개의 상용 API | 쿼리(query)와 코드의 벡터 표현 (vector representation)에 따라 품질이 얼마나 강력하게 의존하는지 |
| ... |
세 가지 검색 모드 (retrieval modes)는 다음과 같이 작동합니다:
| 모드 (Mode) | 순위 생성 방식 |
|---|---|
BM25_ONLY | Lucene 어휘 검색 (lexical search). 쿼리 용어가 코드 내 용어와 일치할 때 파일의 순위가 높게 매겨집니다. 임베딩 모델은 사용되지 않습니다. |
| ... |
이 글의 주요 지표는 recall@10입니다. 단일 질문에 대해, 정답 파일이 상위 10개 결과 안에 포함되면 1, 그렇지 않으면 0이 됩니다. 최종 값은 30개 질문에 대한 평균값입니다. 예를 들어, recall@10 = 0.900은 30개 질문 중 27개에 대해 정답 파일이 상위 10위 안에 나타났음을 의미합니다.
모델 순위는 또한 95% 부트스트랩 신뢰 구간 (95% bootstrap confidence interval)인 95% CI를 보고합니다. 이를 계산하기 위해, 질문 세트를 복원 추출 (resampling with replacement) 방식으로 반복하여 샘플링하고 각 샘플에 대해 재현율 (recall)을 다시 계산했습니다. 구간이 넓다는 것은 30개의 질문이 미세한 차이를 정밀하게 추정하기에는 불충분함을 의미합니다. 구간이 겹치는 것 자체가 공식적인 쌍체 검정 (pairwise test)은 아니지만, 인접한 행의 순위를 안정적인 것으로 간주하는 것에 대해 경고하는 역할을 합니다.
청킹 (chunking) 레이블 c500-o100은 500자의 조각 (fragments)과 100자의 중첩 (overlap)을 의미합니다. whole-file은 파일 전체가 하나의 조각으로 인덱싱됨을 의미합니다.
모든 파라미터의 완전한 데카르트 곱 (Cartesian product)을 테스트하지는 않았습니다. 모델들은 고정된 베이스라인 설정 하에 비교되었습니다. 로컬 및 상용 모델은 5가지 청킹 설정에 걸쳐 테스트되었으며, VECTOR_ONLY는 고정된 c1500-o200 청킹에서 HYBRID_RRF와 비교되었습니다. 검색 모드와 청킹 사이의 전체적인 상호작용은 연구 범위에서 제외되었습니다. BM25는 임베딩 모델에 의존하지 않기 때문에 단일 베이스라인으로 실행되었습니다.
2. 모델 비교를 위한 조건
임베딩 모델을 비교하기 위해서는 나머지 검색 파이프라인 (retrieval-pipeline) 매개변수들을 고정해야 합니다. 그렇지 않으면 차이가 모델 때문인지, 조각 크기 (fragment size) 때문인지, 아니면 검색 방법 (retrieval method) 때문인지 구분하는 것이 불가능합니다.
베이스라인 비교에는 자연스러운 human 질문, HYBRID_RRF, 그리고 c1500-o200 청킹 (chunking)을 사용했습니다. 각 모델에 대해, 30개의 질문 중 정답 파일이 상위 10개 결과 내에 나타난 비율을 측정했습니다.
이 순위는 동일한 조건 하에서 모델들을 비교하지만, 선택된 설정 이외의 품질을 설명하지는 않습니다. 예를 들어, OpenAI의 text-embedding-3-large는 c1500-o200 설정에서 recall@10 = 0.833을 기록했고, 더 작은 c500-o100 조각에서는 0.900을, 파일 전체를 인덱싱했을 때는 0.433을 기록했습니다.
따라서 0.833이라는 값은 모델의 독립적인 속성으로 취급될 수 없습니다. 이는 모델, 청킹 (chunking), 검색 모드 (retrieval mode), 코퍼스 (corpus), 그리고 질문 세트의 한 가지 조합을 설명할 뿐입니다. 베이스라인 순위는 유용한 시작점이지만, 다른 매개변수들을 테스트하지 않고서는 최적의 설정을 식별할 수 없습니다.
3. 청킹 (Chunking)의 효과
이상적으로는 코드가 메서드 (methods), 클래스 (classes) 또는 기타 구조적 단위와 같은 논리적 경계를 따라 분할되어야 합니다. 그러나 구조적 청킹 (structural chunking)은 모든 언어에 대해 전용 파서 (parser)를 필요로 합니다.
본 연구에서는 의도적으로 다국어 Java 및 Scala 프로젝트를 사용합니다. 따라서 코드를 고정된 크기의 조각 (fragments)으로 분할했습니다. 이것이 코드를 인덱싱하는 최적의 방법이라고 제시하는 것은 아닙니다. 다만 언어 간의 공통 분모를 제공하며 조각 크기의 효과를 분리하여 확인할 수 있게 해줍니다.
표의 모든 값은 하이브리드 검색 (hybrid retrieval)을 사용한 자연스러운 human 질문에 대한 recall@10입니다. 각 모델에서 관찰된 최상의 결과는 굵게 표시되었습니다.
| 모델 (Model) | c500-o100 | c1500-o200 | c3000-o300 | c5000-o500 | whole-file | 유형 (Type) |
|---|---|---|---|---|---|---|
| all-minilm | 0.733 | 0.700 | 0.667 | 0.700 | 0.567 | local |
| ... |
더 작은 조각이 대부분의 로컬 모델에 도움이 되었습니다
7개의 로컬 모델 중 5개에서 c500-o100이 가장 높은 결과를 기록했습니다. 한 가지 가능한 설명은 작은 조각(fragment)이 관련 없는 코드를 더 적게 포함한다는 것입니다. 작은 조각의 임베딩 (embedding)은 로컬 구현을 더 정밀하게 설명할 수 있는 반면, BM25는 특정 용어의 매칭을 통해 이점을 얻습니다.
이 실험이 이러한 메커니즘을 직접적으로 입증하는 것은 아닙니다. 이를 입증하려면 검색된 조각들을 조사하고, 모든 청크 크기(chunk size)에서 하이브리드 (hybrid) 검색과 벡터 전용 (vector-only) 검색을 비교해야 합니다.
일부 모델은 더 큰 조각에서 이점을 얻습니다
qwen3-embedding:0.6b는 c3000-o300에서 가장 높은 결과를 달성했으며, 파일 전체를 인덱싱 (indexing)했을 때도 0.900에 도달했습니다. 대부분의 로컬 모델과 달리, 이 모델은 더 큰 조각에서도 품질을 유지했습니다.
가능한 설명은 더 긴 컨텍스트 (context)를 처리하는 모델의 능력입니다. 더 큰 조각은 작은 조각이 놓칠 수 있는 메서드 (method)와 이를 둘러싼 클래스 (class) 사이의 관계를 보존합니다. mistral-embed-2312, EmbeddingsGigaR, 그리고 부분적으로 voyage-code-3에서도 유사한 패턴이 나타났습니다.
이는 여전히 가설 단계입니다. 이 실험은 검색 결과 (retrieval outcomes)를 측정한 것이지, 각 모델의 동작에 대한 내부적인 원인을 측정한 것이 아니기 때문입니다.
파일 전체 인덱싱은 가장 위험한 선택입니다
whole-file 방식의 결과는 0.133에서 0.900 사이였습니다. 이 접근 방식은 qwen3-embedding, voyage-4-large, voyage-code-3에는 유효했지만, nomic-embed-text와 EmbeddingsGigaR에서는 품질이 급격히 떨어졌습니다.
가장 유력한 설명은 컨텍스트 윈도우 (context-window) 제한과 긴 파일의 절단 (truncation) 문제입니다. 제공업체의 토크나이저 (tokenizer)에 의한 절단을 직접 측정하지 않았으므로, 이 또한 가설로 남겨두어야 합니다.
이 매트릭스 (matrix)는 보편적으로 가장 좋은 조각 크기를 밝혀내지는 않습니다. 대신, 세 가지 유형의 동작을 보여줍니다:
- 작은 조각으로부터 상당한 이점을 얻는 모델
- 더 많은 주변 컨텍스트가 필요한 모델
- 청킹 (chunking) 설정에 관계없이 안정적으로 유지되는 모델
따라서 청킹 (chunking)은 임베딩 모델 (embedding model)과 함께 선택되어야 합니다. 튜닝 (tuning) 시간이 제한적인 경우 c500-o100이 합리적인 시작점이 될 수 있지만, 최소한 하나 이상의 더 큰 대안을 테스트해야 하며, 별도의 검증 없이 whole-file을 사용해서는 안 됩니다.
4. 검색 모드 (Retrieval Mode)의 효과
코드를 분할하는 방법을 결정한 후, 다음 질문은 관련 조각들을 어떻게 검색할 것인가입니다. 실험에서는 세 가지 모드를 비교했습니다:
BM25_ONLY: 쿼리 (query)의 단어를 코드 내의 단어와 매칭합니다.VECTOR_ONLY: 임베딩 (embeddings) 간의 의미적 유사성 (semantic similarity)을 비교합니다.HYBRID_RRF: BM25와 벡터 검색 (vector search)의 순위 위치 (rank positions)를 결합합니다.
검색 모드 비교에는 c1500-o200이 사용되었습니다. 이전 실험에서 c1500-o200 + HYBRID_RRF 조합은 당시 사용 가능한 가장 강력한 결과를 생성했으며, 이후 실행을 위한 대조 구성 (control configuration)이 되었습니다.
이후의 청킹 매트릭스 (chunking matrix) 결과는 보편적으로 최적인 조각 크기 (fragment size)는 존재하지 않음을 보여주었습니다. 그러나 c1500-o200을 유지함으로써, 청킹 변경에 따른 효과가 섞이지 않도록 동일한 조건 하에서 검색 모드들을 비교할 수 있었습니다.
검색 모드와 청킹 설정의 전체 매트릭스는 테스트되지 않았습니다. 따라서 아래 결과는 c1500-o200 환경에서의 검색 모드 동작만을 설명합니다.
모든 값은 자연스러운 human 질문에 대한 recall@10입니다. 각 모델에 대한 최적의 모드는 굵게 표시되었습니다.
| 모델 (Model) | BM25_ONLY | VECTOR_ONLY | HYBRID_RRF | 유형 (Type) |
|---|---|---|---|---|
| 임베딩 모델 없음 (No embedding model) | 0.600 | — | — | 어휘적 베이스라인 (lexical baseline) |
| ... |
상용 모델 (Commercial-model) 값은 세 번의 반복 실행을 통해 평균을 냈으므로, 일부 값은 30개 질문 중 하나에 대한 배수가 아닙니다.
하이브리드 검색 (Hybrid Search)이 항상 개선을 가져오는 것은 아니었다
벡터 검색에 BM25를 추가하는 것은 두 개의 로컬 모델과 세 개의 상용 모델에 도움이 되었습니다. 세 개의 로컬 모델에는 차이가 없었습니다. 나머지 사례의 경우, 하이브리드 검색은 recall@10을 감소시켰습니다.
로컬 모델 중에서는 bge-m3와 snowflake-arctic-embed2에서 가장 뚜렷한 차이가 나타났습니다. 벡터 전용 검색 (vector-only search)이 이들의 결과를 0.100만큼 향상시켰습니다. 상용 모델 중에서는 mistral-embed-2312가 동일한 향상을 보였습니다.
한 가지 가능한 설명은, 정답 파일이 벡터 검색에서 놓친 쿼리 용어 (query terms)를 포함하고 있을 때 BM25가 도움이 된다는 것입니다. 또한 BM25는 어휘적으로는 유사하지만 의미적으로는 틀린 파일들을 우선시하거나, 이미 강력한 벡터 순위 (vector ranking)를 약화시킬 수도 있습니다. 실험에서 이 메커니즘을 직접 테스트하지는 않았습니다.
BM25는 여전히 유용한 베이스라인 (Baseline) 으로 남는다
자연어 질문 (natural questions)의 경우, BM25는 recall@10 = 0.600을 기록하며 테스트된 모든 임베딩 기반 조합보다 낮은 수치를 보였습니다. 그러나 그 결과는 쿼리 언어에 크게 의존했습니다.
기술 용어와 키워드로 구성된 쿼리의 경우, BM25는 0.833–0.867에 도달했습니다. 부정확한 용어를 사용할 경우 0.400으로 떨어졌습니다. 어휘 검색 (lexical search)은 개발자가 관련 엔티티 (entities)의 이름을 이미 알고 있을 때는 잘 작동하지만, 시스템 동작이 개발자 자신의 언어로 설명될 때는 효과가 떨어집니다.
청킹 (chunking)의 선택과 마찬가지로, 검색 모드 (retrieval mode)의 선택도 임베딩 모델에 따라 달라집니다. 하이브리드 검색 (hybrid retrieval)이 벡터 검색을 개선할 것이라고 가정할 수 없습니다. 하이브리드 검색은 일부 모델에는 도움이 되었고, 일부 모델에는 변화가 없었으며, 다른 모델들의 결과는 감소시켰습니다.
실질적인 평가를 위해서는 선택된 모델과 대표적인 쿼리에 대해 최소한 VECTOR_ONLY와 HYBRID_RRF를 비교해야 합니다. BM25는 유용한 대조점 (control point)이자 정밀한 기술 검색을 위한 독립적인 옵션으로 여전히 유효합니다.
5. 쿼리 문구 (Query Phrasing)의 효과
코드에 대한 동일한 질문이라도 다양한 방식으로 표현될 수 있습니다. 개발자는 시스템 동작을 자연어로 설명하거나, 알려진 기술 용어를 나열하거나, 혹은 컴포넌트에 대해 그럴듯하지만 틀린 이름을 사용할 수 있습니다.
이러한 변화 속에서 검색의 견고성 (robustness)을 테스트하기 위해, 각 질문은 다음과 같이 여러 형태로 표현되었습니다:
human— 자연스러운 개발자 질문;ai_optimized— 정확한 기술 용어를 사용한 상세 쿼리 (query);keyword— 짧은 키워드 목록;wrong_terminology— 통제된 하나의 용어 오류가 포함된 원래의 의도;cross_module— 여러 시스템 구성 요소를 연결하는 질문.
이러한 변형들의 생성 규칙은 연구의 파트 1 (Part 1 of the study)에 설명되어 있습니다.
이 비교에서는 청킹 (chunking)을 c1500-o200으로 고정하고 HYBRID_RRF를 사용했습니다. 모든 값은 recall@10입니다. cross_module 변형은 적용 가능한 질문 10개에 대해서만 존재했으며, 나머지 결과는 30개 전체에 대해 계산되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기