AI 에이전트 전용 검색 엔진 xerj를 자체 리포지토리에 적용해 본 경험
요약
Rust 기반 로컬 검색 엔진 xerj를 활용하여 AI 에이전트 전용 검색 시스템을 구축하는 경험을 공유합니다. 이 도구는 코드, 문서 등 전체 폴더를 인덱싱하며 BM25, 벡터, 하이브리드 검색 기능을 제공합니다. 특히 `autoindex`와 `def` 기능으로 개발 리포지토리 내부의 정보를 효과적으로 추출할 수 있음을 검증했습니다.
핵심 포인트
- xerj는 코드/문서 전체 폴더를 인덱싱하는 로컬 검색 엔진입니다.
- BM25 모드에서는 원본 텍스트 기반의 정확한 구절 검색이 가능합니다.
- `def` 기능은 특정 함수 정의(Python, C, Rust)를 찾아내는 데 유용했습니다.
- JavaScript 코드 블록 등 일부 언어는 심볼 추출 대상에서 제외되는 한계가 있습니다.
이것은 무엇인가
xerj (xerj-org/xerj)는 코드, 문서, 로그, PDF 등의 폴더 전체를 인덱싱하여 AI 에이전트가 파일 전체를 읽어오지 않고도 해당 부분만 추출할 수 있게 해주는 Rust 기반의 로컬 검색 엔진입니다(Apache-2.0). Elasticsearch 8.x와 호환되는 REST API를 갖추고 있으며, 전문 용어로는 전역 검색(BM25)·벡터 검색·하이브리드 검색에 대응하고, MCP 서버로서 AI 에이전트에게 16개의 도구를 제공합니다.
오늘 날짜(기사 작성 시점) 기준으로 GitHub Trending에서 급상승하고 있어 Zenn, Qiita, note 등지에서 검색해 본 범위 내에서는 일본어 원본 검증 기사를 찾을 수 없었습니다.
이번에는 본 리포지토리(ai-100days) 자체를 xerj의 인덱싱 대상으로 삼아 실제로 직접 만져보며 검증했습니다.
출처: https://github.com/xerj-org/xerj (Apache-2.0)
도입
GitHub Releases에서 공식 프리빌드 바이너리를 다운로드하는 것만으로 추가적인 유료 API 키가 필요하지 않았습니다.
$ curl -sSL -o xerj.tar.gz \
$ tar xzf xerj.tar.gz
...
autoindex
--data-dir이 없다
첫 번째 실패: 서버 실행(xerj [OPTIONS])과 클라이언트 조작(xerj autoindex <folder>)이 같은 바이너리의 다른 서브 커맨드 형태로 되어 있어, 일부 플래그 이름이 중복되지 않아 혼동했습니다.
$ xerj autoindex /home/user/ai-100days --data-dir ./data
error: unknown argument: --data-dir
autoindex는
데이터 디렉토리를 재구성하여 깨끗한 상태에서 다시 시작하고, 이후의 검증을 진행했다. 최종적으로 이 리포지토리 전체(280개 파일)가 9.0초 만에 3,694개의 문서로 인덱싱되었다.
lexical 모드 검색: 짧은 구절은 히트하지만, 의역된 문장은 히트하지 않음
기본값인 --embed-mode lexical로 검색했을 때, 짧은 구절은 일본어에서도 문제없이 히트했다.
$ xerj search "NUL 바이트"
─── articles/day-018-reverse-engineer-minified-jquery.md
... `\0` 이스케이프가 NUL 바이트에 깨짐 ...
이는 day-018에서 실제로 발견한 버그에 대한 기사가 그대로 히트하는 경우다. 반면, 본문 내에 존재하지 않는 '부자연스러운 한 문장의 의역' 쿼리는 히트하지 않았다. 이는 BM25로서 기대되는 동작이며, 도구의 결함이 아니다.
xerj def
(go-to-definition)은 Python, C, Rust의 함수 정의를 정확하게 찾아낸다. day-002에서 검증했던 '같은 rle_decode 함수의 C 버전 및 Rust 버전'을 한 번에 찾을 수 있었던 점이 인상적이었다.
$ xerj def "rle_decode"
─── experiments/day-002/rust/src/lib.rs:17 [function]
pub fn rle_decode(input: &[u8], out_cap: usize) -> Result<Vec<u8>, CapacityExceeded>
...
반면, Markdown 내 코드 블록에 작성된 JavaScript 함수(day-018의 toType())는 def로는 전혀 찾을 수 없었다. autoindex map에서 보이는 language 필드의 예시는 python/c/rust만이며, JavaScript는 심볼 추출 대상에서 제외되는 것으로 보인다.
semantic_text가 되지 않는 경우가 있다
neural 모드의 함정: --embed-mode neural로 서버를 시작하고, 작은 폴더(articles/의 18개 파일, day-002의 9개 파일)를 대상으로 autoindex 했을 때, body 필드가 semantic_text가 되지 않고 원본 text 그대로인 현상을 두 번 재현했다. 둘 다 처리가 1~1.2초로 완료되는 소규모 케이스였다.
$ curl -s http://localhost:9200/<small>-docs/_mapping | ...
"body": {"type": "text"}
반면, experiments/ 전체(약 200개 파일・4.85MB)라는 비교적 큰 폴더를 같은 서버에 대해 autoindex 했을 때는, body가 올바르게 semantic_text가 되었다. 근본 원인은 특정할 수 없으나, 작고 빠르게 끝나는 코퍼스에서는 임베딩 모델 초기화 전에 첫 번째 쓰기가 완료되어 기본값인 원본 text 매핑으로 확정되는 타이밍 의존적 동작을 의심하고 있다.
중요한 점은, 이 상태에서 xerj_semantic_search를 호출해도 검게 속이지 않고 HTTP 400으로 명확하게 실패한다는 것이다.
{"error":{"reason":"semantic query on field [body]: it is not a `semantic_text` field, so there is no comparable stored vector to match. ..."},"status":400}
--embed-mode neural를 지정했다고 무조건 신뢰해서는 안 되며, autoindex map이나 _mapping으로 실제 필드 타입을 확인해야 한다는 실질적인 교훈을 얻었다.
MCP 서버: 자체 진단 힌트가 가장 큰 수확
xerj mcp는 이미 구동 중인 노드에 HTTP를 통해 프록시만 하는 명령어이므로, Claude Code 자체의 글로벌 설정을 수정할 필요가 없다. day-018에서 검증한 REA의 rea setup
(글로벌 설정 변경 및 재시작이 필요)와는 대조적인 설계로, 무인 실행 검증에서도 안전하게 시도할 수 있었다(xerj init
라는 프로젝트 스코프 설정 생성 명령어도 있지만, 이 역시 '재시작 필요'라고 명기되어 있어 --dry-run으로 확인하는 데 그쳤다).
MCP의 JSON-RPC(stdio)를 직접 호출하여 initialize → tools/list를 실행하자, 백로그에서 예상했던 대로 16개의 도구(xerj_search, xerj_semantic_search, xerj_vector_search, xerj_hybrid_search, xerj_memory_*, xerj_brain_*, xerj_map, xerj_code_search, xerj_plan, xerj_hub_*)를 확인할 수 있었다.
가장 인상적이었던 점은, xerj가 쿼리 방법의 오류를 응답 자체로 알려준다는 것이다. semantic_text 필드에 대해 일반적인 xerj_search (BM25만)로 일본어 의역(言い換え) 쿼리를 던지자, 결과와 별도로 _xerj.hints라는 필드를 통해 다음과 같이 응답이 왔다.
{
"_xerj": {"hints": [{
"code": "lexical_on_semantic_text",
...
결과와는 별개로 '이 쿼리에서는 무엇을 놓치고 있는지'와 '다음으로 무엇을 시도해야 하는지'를 구체적인 JSON 예시까지 첨부하여 돌려주는 설계가, AI 에이전트가 스스로 수정하기 쉽도록 일관성 있게 만들어졌다고 느꼈다.
실제로 semantic_text가 올바르게 활성화된 인덱스(experiments/ 부분 인덱스, 383건 처리 시점)에 대해, BM25로는 0건이었던 일본어 패러프레이즈 쿼리(
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기