컬리가 배송 도메인 지식을 LLM 에 주입하려다 마크다운 문서가 쌓이며 토큰 비용과 "lost in the middle"에 부딪혀, 검색 구조를 두 번 갈아엎은 과정을 다룬다. inverted index → 본문 임베딩 → 요약 임베딩+본문 FTS 분업으로 이어지며, 결국 "무엇을 모델에 맡기고 무엇을 도구·사람에 분담할지 선을 옮긴 작업"이라는 결론에 닿는다.
핵심 포인트- 1단계 inverted index 는 동의어 누락("새벽배송" vs "샛별")과 키워드 한 개로 무관 문서 7개 반환 등 토큰 낭비로 실패했다.
- 2단계 본문 임베딩(sqlite-vec + multilingual-e5-small, 384차원/512토큰)은 동의어는 풀었지만 truncation·의미 평균화·약어 검색 불가가 남았다.
- 3단계는 요약만 임베딩(의미 매칭)하고 본문은 FTS5 BM25(정확 매칭)로 분업해 양쪽 약점을 메웠다.
- 다주제 문서를 "한 문서=한 주제"로 분리해 임베딩 의미 평균화 문제를 줄였다.
- 검색은 의미 매칭 top3 + 키워드 매칭 top2 를 합쳐 중복 제거하고 의미 순위를 우선한다.
상세 정리- 배경: 도메인 지식 마크다운이 늘수록 LLM 컨텍스트에 다 넣기엔 토큰 비용이 커지고, 긴 컨텍스트 중간 정보가 누락되는 lost in the middle 이 생겼다.
- 1단계 구조: 자모 prefix bucket 파일(예: ㅅ-ㅊ.md)에 키워드→문서 매핑을 둔 inverted index 를 만들었다.
- 1단계 실패: LLM 이 도메인 동의어를 몰라 "새벽배송"과 "샛별"을 못 잇고, "용차" 한 단어로 문서 7개를 다 반환해 6배 토큰을 낭비했으며, 색인 동기화 비용도 컸다.
- 2단계 도구: sqlite-vec 에 multilingual-e5-small(384차원, 512토큰 제한) 임베딩으로 본문을 색인했다.
- 2단계 성과: 동의어가 자동 해결되고 한 번의 호출로 검색이 끝났다.
- 2단계 truncation: 문서의 85%가 512토큰을 넘겨 뒷부분(최대 93%)이 임베딩에 안 들어갔고, wiki 요약 구조 덕에 사실상 요약만 임베딩되고 있었다.
- 2단계 의미 평균화: 다주제 문서에서 "정책" 같은 일반어가 모든 검색에 약하게 매칭돼 정밀도가 떨어졌다.
- 2단계 약어 한계: DOS-bot, TICKET-1234, KLS 같은 식별자·약어 검색이 임베딩으로는 거의 불가능했다.
- 3단계 분리: 한 문서=한 주제 원칙으로 다주제 문서를 쪼개 평균화를 줄였다.
- 3단계 이중 채널: 의미 매칭은 요약+별칭+키워드(100~200토큰)만 e5-small 로 임베딩하고, 정확 매칭은 본문 전체를 SQLite FTS5 BM25 로 검색한다.
- 3단계 메타데이터: summary(LLM 작성 후 사람 검토), keywords, aliases 를 두고 본문은 FTS5 와 미리보기에만 쓴다.
- 남은 한계: "컨티 정책" 검색에 "예약 설정 정책"이 약하게 끼는 일반어 노이즈가 일부 남고, summary 품질이 LLM 세션에 따라 편차가 있다.
- 다음 후보: 헤딩 단위 청킹, max_seq_length 가 더 큰 모델, 가중치 동적 조정, 자동 품질 평가를 개선 후보로 든다.
왜 읽나사내 문서 기반 RAG 를 구축하며 임베딩만으로는 안 풀리는 동의어·약어·다주제 문제에 부딪힌 엔지니어에게, 임베딩과 FTS 분업 설계의 시행착오 레퍼런스.