This site is also available as markdown for AI agents. For a complete index of all pages, see the site index at /llms.txt

200만 단어 위키에 가벼운 로컬 검색을 붙인 과정

LLM-PKM 공개 구축기 11/12편

Read in English →

이 글에서 얻을 수 있는 것

  • API 키 없이 로컬에서 돌아가는 마크다운 위키와 문서 저장소용 하이브리드 검색 구성입니다. SQLite FTS5 키워드 검색과, 미리 계산한 벡터 표를 조회하는 정적 임베딩(의미 검색)을 합쳐 인덱스 파일 하나(약 16MB)로 만들었습니다.
  • 빌드 시간, 인덱스 크기, 메모리, 질의 시간의 측정값이므로 본인 환경과 비교해 볼 수 있습니다.
  • qmd(모델 2GB 이상)와 bm25s를 제외한 이유입니다. 같은 기준을 본인 문서 규모에 대어 볼 수 있습니다.
  • 명령어를 알려주지 않아도 에이전트가 검색 도구를 쓰게 만드는 세 가지 장치입니다. AGENTS.md 규칙, 프로젝트 스킬, 스크립트 보완을 다룹니다.

이런 분께 맞습니다: 에이전트가 읽는 마크다운 지식 저장소가 커지고 있고, 파일 통째 읽기나 grep이 한계에 다가오고 있는 경우입니다.

맞지 않는 경우: 노트가 아직 컨텍스트 하나에 들어가거나, 대규모 서비스용 검색이 필요한 경우입니다.

유의할 점: 품질 검증은 질의 20개뿐이므로 점수는 초기 수치로 보시기 바랍니다.

위키의 raw/ 폴더가 200만 단어를 넘었습니다. 클리핑한 원문이 760여 개이고, 이를 정리한 요약 파일이 761개입니다. 에이전트가 이 위키에서 무언가를 찾으려면 지금까지는 두 가지 방법이 있었습니다. index.md와 raw-index.md를 통째로 읽거나, grep으로 키워드를 훑는 것입니다.

문제는 raw-index.md가 이미 1만 5천 단어에 가깝고, 인제스트(새 소스를 위키에 추가하는 작업)를 할 때마다 소스가 15~20개씩 늘어난다는 점입니다. 머지않아 컨텍스트에 통째로 넣기 어려워집니다. grep은 “llms.txt가 실제로 도움이 된다는 근거가 있는가” 같은 질문에는 답하지 못합니다. 키워드가 정확히 일치하는 페이지만 찾아주기 때문입니다.

조건은 가볍고 어디서나 돌아가는 것

저는 이 위키를 Windows, MacBook, 클라우드 Linux VM에서 번갈아 작업합니다. 세 환경 모두 저장 공간과 메모리가 넉넉하지 않습니다. 그래서 Claude Code에게 검색 도구를 조사해 달라고 하면서 몇 가지 조건을 걸었습니다.

  • 로컬에서 돌아가고 API 키가 필요 없어야 합니다.
  • 키워드 질문과 자연어 질문을 모두 처리해야 합니다.
  • 한국어 소스가 있으므로 한국어 질의도 되어야 합니다.
  • 인덱스는 파생물이므로 git에 올리지 않고, 마크다운에서 언제든 다시 만들 수 있어야 합니다.

후보를 줄인 과정

Claude Code가 처음 추천한 것은 qmd입니다. BM25 키워드 검색, 벡터 검색, LLM 재순위를 결합하고 MCP 서버까지 제공하는 마크다운 검색 도구로, 기능만 보면 이 위키에 잘 맞았습니다. 다만 GGUF 모델 3개(임베딩 약 300MB, 재순위 약 640MB, 질의 확장 약 1.1GB)를 내려받아야 해서 합치면 2GB가 넘습니다. 저는 제 기기에서 위키 질의 몇 건을 위해 감당하기에는 너무 크다고 판단하고, 더 가벼운 대안을 찾아 달라고 요청했습니다.

그 결과 Claude Code가 제안한 구성은 이렇습니다. 키워드 검색은 SQLite에 내장된 FTS5로 BM25 순위를 받고, 의미 검색은 Model2Vec의 정적 임베딩으로 처리합니다. Model2Vec는 트랜스포머를 실행하지 않고 미리 계산된 벡터 표를 조회하는 방식이라, 프로젝트 설명에 따르면 모델이 수 MB에서 수십 MB 수준이고 CPU만으로 충분합니다. 두 결과 목록은 ‘상호 순위 결합’(Reciprocal Rank Fusion, 이하 RRF)으로 합칩니다. 점수를 보정할 필요 없이 순위만 사용하는 방식입니다.

키워드 검색을 bm25s로 바꾸면 어떨지는 제가 물어보았습니다. Claude Code가 문서를 확인한 결과, 속도와 메모리 이점은 문서가 수백만 건일 때 의미가 있는데 제 위키는 문서를 잘게 나눈 조각(청크) 5천여 개 규모였습니다. 게다가 문서에서 증분 갱신과 메타데이터 필터를 확인하지 못해서 이번에는 FTS5를 쓰기로 했습니다. 검색이 느려지는 때가 오면 키워드 단계만 바꾸면 됩니다.

구성

검색 대상은 wiki/와 raw/summaries/입니다. 원문 전체는 색인하지 않고, 요약을 먼저 찾은 뒤 원문의 정확한 문장이 필요할 때만 열도록 했습니다. 마크다운은 제목 단위로 나누어 최대 400단어의 청크로 만들고, 페이지 유형과 출처 등급 같은 frontmatter 값은 필터로 쓸 수 있게 함께 저장합니다. 벡터는 SQLite 파일 안에 넣었으므로 인덱스는 파일 하나입니다.

측정 결과

Claude Code가 Windows에서 측정했고, 대상은 956개 파일과 청크 5,333개입니다.

항목키워드만벡터 포함
빌드 시간약 3초약 6초
인덱스 크기12.5MB15.7MB
질의 시 메모리약 36MB약 92MB (최대 약 147MB)
질의 시간약 10ms약 0.3초 (모델을 처음 불러올 때 약 1.3초)

한 가지 짚어둘 점이 있습니다. model2vec와 numpy를 설치한 Python 가상환경이 약 110MB로, 처음 예상보다 컸습니다. 모델 자체보다 의존성 쪽이 용량을 차지합니다.

질의 20개로 확인한 품질

제가 평가용 질의 세트를 요청했고, Claude Code가 질의 20개를 작성했습니다. 키워드 8개, 영어 개념형(자연어로 풀어 쓴 질문) 8개, 한국어 4개입니다. 정답 페이지는 Claude Code가 검색 결과를 보기 전에 질의의 주제에 맞춰 먼저 정했고, 실제로 존재하는 파일인지만 확인했습니다. 상위 5개 안에 정답이 들어온 비율(hit@5)은 다음과 같습니다.

질의 유형키워드벡터하이브리드
키워드 (8개)1.000.881.00
개념형 (8개)0.750.750.88
한국어 (4개)1.001.001.00
전체 (20개)0.900.850.95

하이브리드가 가장 높지만 차이를 과장하고 싶지는 않습니다. 개념형 질의 8개 중 하나를 더 찾은 정도이고, 순위까지 반영하는 지표(MRR)는 키워드 0.78, 하이브리드 0.79로 거의 같습니다. 정답 목록이 완전하지 않아서 실제로는 맞는 유사 문서가 오답으로 처리된 경우도 있습니다. 한국어 4개를 모두 찾은 것도 질의에 고유한 단어가 들어 있었기 때문이라, 한국어 형태소 문제가 해결되었다고 말할 수는 없습니다.

에이전트가 알아서 쓰게 만들기

검색 도구를 만들어도 에이전트가 존재를 모르면 쓰이지 않습니다. 명령어를 매번 알려주지 않아도 되도록 Claude Code가 세 가지를 갖췄고, 저는 그중 AGENTS.md에 들어갈 문구를 먼저 확인하고 승인했습니다.

  • AGENTS.md 규칙: 페이지나 요약을 찾을 때는 인덱스를 읽거나 grep을 쓰기 전에 검색 명령을 먼저 실행하도록 적었습니다. 세션마다 읽히는 파일이라 가장 먼저 넣은 장치입니다.
  • 프로젝트 스킬: “키워드나 질문으로 위키 페이지를 찾을 때”라는 설명을 붙인 스킬(Claude Code가 설명을 보고 필요할 때 불러오는 지침 파일)을 만들어, Claude Code가 상황에 맞춰 불러오게 했습니다. 새 기기에서 설정하는 방법도 스킬에 넣었습니다.
  • 스크립트 보완: 일반 python으로 실행해도 가상환경이 있으면 스스로 그쪽으로 다시 실행합니다. 가상환경이 없으면 키워드 검색만 하고 그 사실을 알려주며, 인덱스가 없으면 첫 질의에서 만듭니다. 인제스트가 끝날 때 도는 reindex.py가 인덱스도 함께 갱신합니다.

확인하려고 제가 요청해 Claude Code가 새 세션을 비대화형으로 열어, 검색 도구를 언급하지 않은 채 질문하게 했습니다. “llms.txt가 에이전트에게 실제로 도움이 된다는 근거와 반대 근거가 무엇인가”라는 영어 질문이었습니다. 새 세션의 에이전트는 검색 명령을 가장 먼저 실행하고 첫 결과 페이지를 열어서 답했으며, grep이나 인덱스 파일 읽기는 하지 않았습니다. 다만 위키가 잘 다루는 주제로 한 번 해본 것이라, 규칙이 확실히 통한다는 증거로는 약합니다. 짧은 약어 질문이나 한국어 질문으로 더 시험해 볼 필요가 있습니다.

남은 일

  • 질의 20개와 세션 한 번은 표본이 작습니다. 실제로 제가 던지는 질문을 모아 40개 정도로 늘릴 필요가 있습니다.
  • 정적 임베딩은 미묘한 개념 질의에 약할 수 있으므로, 개념형에서 놓친 질의에 더 큰 임베딩 모델을 시험해 볼 필요가 있습니다.
  • 새 기기마다 가상환경을 한 번 만들어야 합니다. 건너뛰어도 키워드 검색은 동작합니다.

마치며

200만 단어 위키에서 에이전트가 길을 찾게 하는 데 꼭 큰 모델이 필요하지는 않았습니다. 작은 정적 임베딩과 SQLite만으로 인덱스가 16MB 안에 들어갔고, 하이브리드의 이득은 개념형 질의에서만 보였지만 한 건 차이입니다. 반면 검증은 아직 시작 단계입니다. 에이전트가 검색을 쓰도록 만드는 일도 도구를 만드는 일만큼 중요했고, 이 부분은 더 오래 지켜보아야 할 것 같습니다.

이상으로 위키에 로컬 검색을 붙인 과정에 대해서 알아보았습니다. 비슷한 규모의 위키나 문서 저장소에서 다른 구성을 쓰고 계시다면 어떤 방식이었는지 들려주시기 바랍니다.

참고 자료

  • qmd: 로컬 마크다운 검색 도구 (BM25, 벡터, LLM 재순위, MCP)
  • SQLite FTS5: SQLite 내장 전문 검색 확장
  • Model2Vec: 정적 임베딩 라이브러리
  • bm25s: NumPy 기반 BM25 구현

관련 글

  • 5분 분량

    AI 메모리 도구를 도입했다가 제거한 이유

    코딩 에이전트의 세션 간 컨텍스트 문제를 해결하기 위해 Mem0를 도입했다가 제거한 경험을 공유합니다. 전용 메모리 도구 없이 두 개의 마크다운 파일만으로도 크로스 세션, 크로스 에이전트 연속성을 유지하는 방법을 소개합니다.

  • 2분 분량

    이 디지털 가든을 가꾸는 이유

    AI 기반 지식 작업을 중심으로 한 공개 노트, 그리고 복잡한 시스템을 오래 만들고 문서로 풀어 온 사람이 이곳을 가꾸는 이유.

← 전체 글 목록으로