벡터 모델을 바꾸기 전에 먼저 봐야 할 것
의미론적 코드 검색 프로젝트를 시작한 팀이 가장 먼저 하는 질문은 대개 "어떤 임베딩 모델을 쓸 것인가"다. 그런데 검색 품질을 실제로 떨어뜨리는 원인의 상당 부분은 모델이 아니라 그 앞 단계, 즉 소스 코드를 어떤 단위로 잘라 모델에 넘기느냐에 있다.
RAG(검색 증강 생성, Retrieval-Augmented Generation) 파이프라인은 크게 세 단계로 구성된다. 소스 코드를 의미 있는 단위로 분리하는 파싱·청킹, 그 단위를 수치 벡터로 변환하는 임베딩, 그리고 쿼리와 가장 가까운 벡터를 찾아 LLM에 넘기는 검색. 이 중 파싱·청킹은 투자 대비 효과가 가장 크지만, 프로젝트 초반에 가장 가볍게 취급되는 단계다. 구조화되지 않은 코드 조각을 넣으면 아무리 좋은 모델도 의미를 올바르게 포착하지 못한다.
코드를 어떻게 나누느냐가 LLM 맥락을 결정한다
자연어 문서와 소스 코드는 다르다. 문서는 단락이나 문장이 어느 정도 독립적 의미를 가지지만, 코드는 특정 함수의 파라미터 타입이 import 선언과 멀리 떨어져 있어도 둘이 의미적으로 연결된다.
고정 크기 청킹(fixed-size chunking), 즉 파일을 단순히 N줄 단위로 자르는 방식의 문제가 여기서 드러난다. 한 덩어리 안에 import 구문과 무관한 함수 본문이 섞이거나, 클래스 선언 헤더와 메서드 본문이 분리된다. 이런 조각을 임베딩하면 벡터가 "이 코드가 무엇을 하는가"를 반영하는 게 아니라, "이 줄들이 한 파일의 같은 위치에 있었다"는 사실만 담게 된다.
반대 극단도 문제다. 함수 시그니처 한 줄이나 짧은 주석 하나를 독립 단위로 임베딩하면, 검색 결과로 맥락 없는 미시적 조각들이 쏟아진다. LLM이 세션 토큰 갱신 로직을 찾으려 해도, 관련 없는 한 줄짜리 조각이 높은 유사도로 올라올 수 있다.
적절한 청킹 단위는 언어의 구조를 반영해야 한다. Java라면 클래스, 메서드, 필드가 각각 의미 있는 단위가 되고, 독스 주석(doc-comment)은 해당 선언부와 묶여야 검색 시 함께 올라온다. 이 판단은 모델이 아니라 청킹 알고리즘이 한다.
AST 기반 파싱이 필요한 조건과 그렇지 않은 조건
AST(추상 구문 트리, Abstract Syntax Tree)는 파서가 소스 코드를 분석해 트리 구조로 나타낸 것이다. AST 기반 청킹은 노드 타입(클래스 선언, 메서드, 필드 등)을 직접 식별해 의미 단위로 자른다. 고정 크기 청킹보다 훨씬 정확한 이유다.
JetBrains가 Air Context를 구축하면서 공개한 사례에 따르면, 현재 구조 인식 청킹을 지원하는 언어는 Kotlin, Java, Python, JavaScript, TypeScript, C#, PHP, Go, Rust 9개다. 이 외의 언어는 라인 기반 분할로 처리된다. 이 구분은 어떤 코드베이스에 AST 파싱이 실질적 이득을 주는지 판단하는 기준이 된다.
AST 기반 파싱에 투자할 가치가 높은 경우:
- 검색 대상 언어가 위 9개 언어 중심으로 구성되어 있을 때
- 코드베이스 규모가 수천 파일 이상이고, LLM 에이전트가 특정 함수나 심벌을 목표로 검색해야 할 때
- 검색 결과를 LLM 프롬프트 컨텍스트에 직접 삽입해 정확한 근거로 써야 할 때
라인 기반 분할로 시작해도 되는 경우:
- 파서가 지원하지 않는 언어(내부 DSL, 레거시 COBOL 등)가 코드베이스의 상당 비중을 차지할 때
- PoC 단계에서 파이프라인 전체 흐름 검증이 우선일 때
중요한 점은 라인 기반 분할로 인덱스를 구성하더라도, 지원 언어부터 AST 청킹으로 교체하는 단계적 전환 계획을 처음부터 가지고 있어야 한다는 것이다. 두 방식이 섞인 인덱스를 장기 운영하면 검색 결과의 품질이 언어별로 들쭉날쭉해지고, 그 원인을 추적하기 어려워진다.
파이프라인 구축 순서: 무엇을 먼저 검증하는가
아래 순서는 레거시 코드베이스에 처음 의미론적 검색을 도입하는 팀을 위한 실행 권고다. 각 단계의 산출물이 다음 단계의 입력을 결정하는 구조로 설계했다. 단, 팀의 코드베이스 구성과 언어 분포에 따라 단계별 우선순위는 달라질 수 있다.
1단계: 코드베이스 인벤토리와 언어 분포 파악
시작 전에 실제 파일 수, 언어 분포, 평균 파일 크기를 수집한다. 이 데이터 없이 청킹 전략을 정하면 나중에 재작업이 생긴다.
산출물: 언어별 파일 수, 평균 파일 크기, AST 파서 지원 여부 목록
2단계: 청킹 전략 결정과 샘플 검증
전체 파이프라인을 구축하기 전에 대표 파일 20~30개를 골라 청킹 결과를 사람이 직접 읽어본다. 함수 선언과 독스 주석이 같은 청크에 있는지, import와 무관한 코드가 섞이지 않는지 눈으로 확인하는 것이다. 임베딩 전에 이 검증을 통과하지 못하면 이후 단계에서 문제 원인을 추적하기 어렵다.
산출물: 언어별 청킹 규칙 정의 문서, 샘플 청크 리뷰 결과
3단계: 임베딩 모델 선택과 인덱스 구축
청킹 품질이 확인된 뒤에 임베딩 모델을 선택한다. 청킹이 정리되어 있어야 모델 간 비교 실험의 결과도 신뢰할 수 있다. 청킹이 잘못된 상태에서 모델을 바꾸면 어느 쪽이 문제인지 구분하기 어렵기 때문이다. 다만 이 판단 순서 자체가 프로젝트 맥락에 따라 달라질 수 있으며, 청킹과 모델 선택을 병렬로 실험하는 팀도 있다.
산출물: 선택 모델, 벡터 인덱스 초기 구성
4단계: 키워드 검색과 병행 운영 기간 설정
의미론적 검색이 키워드 검색보다 모든 쿼리 유형에서 낫지는 않다. 정확한 심벌 이름이나 API 명칭을 찾을 때는 키워드 검색이 여전히 빠르고 정확하다. 병행 운영은 "새로운 방법이 충분히 검증될 때까지"라는 막연한 기준보다, 쿼리 유형별로 어느 방법이 더 나은지 실사용 데이터를 수집하는 기간으로 명확히 정의하는 편이 낫다.
병행 종료 시점은 팀마다 다르게 정의해야 한다. 기준이 될 수 있는 질문은 이렇다. 의미론적 검색이 커버하지 못하는 쿼리 유형이 식별되어 있는가, 그리고 그 유형에 대해 키워드 검색을 라우팅 규칙으로 처리할 수 있는가. 이 두 질문에 모두 답할 수 있을 때 병행 운영을 체계적으로 줄일 수 있다.
산출물: 쿼리 유형별 검색 방식 라우팅 기준, 병행 종료 조건 정의
5단계: LLM 맥락 품질 검증
검색 정확도(retrieve)와 LLM이 받은 맥락의 품질(context quality)은 다른 지표다. 검색 결과 상위 5개가 올바른 파일을 포함하더라도, 함수 본문이 잘리거나 의존 타입 정보가 누락되면 LLM이 틀린 답을 생성할 수 있다. 이 단계에서는 실제 LLM 에이전트가 검색 결과를 받아 작업을 수행하게 하고, 출력 코드의 컴파일 가능 여부와 논리적 정확성을 검토한다.
산출물: LLM 맥락 품질 평가 기준, 오류 패턴 분류
레거시 코드베이스에서 자주 나타나는 실패 신호
다음 징후가 보이면 임베딩 모델을 바꾸기 전에 청킹 단계를 먼저 점검해야 한다.
- 검색 쿼리에 함수 이름을 그대로 넣었을 때 해당 함수가 상위 결과에 나오지 않는다
- 기능이 다른 함수들이 항상 같이 묶여 올라온다. 청크 단위가 너무 크다는 신호다
- LLM이 검색 결과를 받아도 "관련 코드를 더 찾아봐야 한다"며 추가 검색을 반복한다
- 언어에 따라 검색 품질 편차가 크다. 파서 지원 여부 차이가 원인일 가능성이 높다
이 패턴들은 모델 성능 문제가 아니라 입력 품질 문제다. 청킹 규칙을 바꾸면 재인덱싱 비용이 발생하지만, 모델을 교체하는 것보다 빠르게 효과가 나타나는 경우가 많다.
AI 에이전트 도입 전에 이 질문을 먼저 해결해야 한다
코딩 에이전트는 작업을 수행하기 위해 대형 코드베이스에서 관련 코드를 찾아내는 과정을 반복한다. 에이전트가 이 검색을 키워드 grep에 의존하면, "세션 토큰이 갱신되는 곳"처럼 정확한 텍스트를 알 수 없는 쿼리에서 막힌다. 의미론적 검색이 에이전트의 이 약점을 보완하는 역할을 하는데, 청킹이 잘못되면 에이전트가 받는 맥락이 불완전해져 오히려 잘못된 코드를 생성하거나 불필요한 검색을 반복하게 된다.
에이전트 도입을 계획하고 있다면, 의미론적 검색 파이프라인이 갖추어져 있는지보다 그 파이프라인이 에이전트가 실제로 던지는 쿼리 유형을 처리할 수 있는 품질의 청크를 제공하는지를 먼저 확인해야 한다. 파이프라인이 있어도 청킹 품질 검증(2단계)과 LLM 맥락 품질 검증(5단계)을 거치지 않은 상태라면, 에이전트를 붙이는 것은 문제를 증폭시키는 방향으로 작동할 수 있다.