대부분의 RAG 튜토리얼은 실제 운영 환경이 시작되는 바로 그 지점에서 끝납니다. 문서를 512토큰 단위로 나누고, 단일 임베딩 모델을 통과시킨 뒤, 단순한 top-k 검색으로 벡터 데이터베이스를 호출합니다. 데모에서는 이것이 그럴싸해 보입니다. 봇에게 회사의 휴가 정책에 대해 물으면 일관된 문단을 답변으로 내놓습니다. 모두가 고개를 끄덕이죠. 하지만 안타깝게도, 데모는 거짓말을 합니다.
실제 운영 환경은 모든 지름길의 허점을 드러냅니다. 고정된 크기의 청크는 법률 계약서의 면책 조항 중간을 잘라버립니다. API 문서는 실제로 필요한 신호를 가로막는 중복된 노이즈로 변합니다. 지연 시간(latency)은 점점 늘어나 사용자는 답변이 도착하기도 전에 검색을 포기합니다. 저희도 이 벽에 부딪혔고, 시스템을 재구축해야 했습니다. 저희의 검색 레이어는 '의미론적 검색과 요행'에서 정밀하게 측정되고 모니터링되는 파이프라인으로 진화했습니다. 그 결과 재현율(recall)은 95%로 높아졌고 지연 시간은 40% 단축되었습니다. 실제로 효과가 있었던 방법들을 소개합니다.
문서에 맞는 청킹 전략 선택하기
512토큰 기본 설정이 계속 사용되는 이유는 그것이 옳아서가 아니라 쉽기 때문입니다. 문서마다 의미를 담는 방식이 다르며, 청킹 전략 또한 이를 반영해야 합니다.
법률 계약서의 경우, 구조적 경계를 존중하는 재귀적 청킹(recursive chunking)을 사용하십시오. 법률 용어는 계층적입니다. 하나의 조항은 상위 섹션에 의존하며, 문장 중간을 고정된 크기로 잘라버리면 의무 사항의 논리가 파괴됩니다. 재귀적 청킹은 토큰 제한을 적용하기 전에 문단, 문장과 같은 자연스러운 구분자를 우선적으로 사용하여 분할을 시도합니다. 이를 통해 면책 또는 책임 조항을 온전하게 유지할 수 있습니다.
API 문서의 경우, 함수 인식 청킹(function-aware chunking)을 사용하십시오. 개발자는 무작위 문단을 검색하지 않습니다. 엔드포인트, 파라미터, 에러 시그니처를 검색합니다. 하나의 청크는 전체 함수 시그니처, 설명, 그리고 반환 스키마를 하나의 논리적 단위로 포함해야 합니다. 만약 이 블록을 반으로 나누면, 검색 시스템은 절반의 컨텍스트만 반환하게 되고 생성 모델은 나머지 부분을 환각(hallucination)하게 됩니다.
지원 티켓의 경우, 대화 흐름을 따르는 의미론적 청킹(semantic chunking)에 의존하십시오. 지원 스레드는 선형적이고 반복적입니다. 고객이 문제를 반복하고, 상담원이 로그를 요청하며, 고객이 이를 첨부합니다. 각 대화 차례(turn)는 그 자체로 하나의 의미 단위입니다. 대화 단위로 청킹하면 누가, 언제, 무엇을 말했는지가 보존되며, 이는 사용자가 "상담원이 화요일에 뭐라고 제안했지?"라고 물을 때 매우 중요합니다.
사내 위키의 경우, 에이전트 기반 청킹(agentic chunking)을 시도해 보십시오. LLM에 섹션을 전달하고 한 주제가 어디서 끝나고 다른 주제가 어디서 시작되는지 결정하도록 요청합니다. 이는 데이터 수집(ingest) 시점에 비용이 더 많이 들지만, 위키는 매우 무질서합니다. 페이지에는 서로 다른 팀의 관련 없는 업데이트가 섞여 있으며, 사람이 정의한 경계는 도움이 되지 않는 경우가 많습니다. 모델이 주제 변화에 따라 경계를 그리도록 하면 노이즈를 획기적으로 줄일 수 있습니다.
하나의 파이프라인에서 여러 전략을 실행하려면 데이터 수집 시점에 문서 유형별로 태그를 지정해야 합니다. 이러한 작은 스키마 규칙이 즉각적인 보상으로 돌아옵니다.
검색 방법을 하나만 선택하지 말고 결합하세요
벡터 검색은 의도를 이해하지만, 정확한 일치(exact match)에는 번번이 실패합니다. 에러 코드 ERR_CONNECTION_REFUSED나 특정 SKU를 요청하면, 밀집 임베딩(dense embeddings)은 개념적으로는 유사하지만 사실관계가 틀린 결과를 반환하는 경우가 많습니다. 고전적인 키워드 희소 검색(sparse retrieval) 방식인 BM25는 정확한 문자열 처리는 훌륭하지만 의미론적 뉘앙스를 놓칩니다. 두 가지 모두가 필요합니다.
하이브리드 검색(hybrid retrieval)을 사용하십시오. 벡터 검색과 BM25를 병렬로 실행한 뒤, 상호 순위 융합(Reciprocal Rank Fusion, RRF)으로 결합하십시오. RRF는 두 방법 모두 관련이 있다고 동의하는 문서에 높은 점수를 주면서도, 어느 한 방식에서 나온 강력한 후보들도 놓치지 않습니다. 수식은 간단하며 결과는 안정적입니다. 특정 검색 방법이 최종 순위를 독점하지 않습니다.
융합 후에는 교차 인코더 재순위화 모델(cross-encoder reranker)을 추가하십시오. 첫 번째 단계인 벡터 및 희소 검색은 빠르고 광범위합니다. 그다음 교차 인코더가 각 쿼리-문서 쌍에 대해 전체 어텐션(full attention)을 사용하여 점수를 매깁니다. 즉, 원래 질문과 후보 문서를 실제로 대조하며 읽는 것입니다. 네, 이 과정에서 지연 시간이 추가됩니다. 저희의 경우 약 50~100밀리초 정도였습니다. 하지만 정밀도(precision)의 향상이 매우 뚜렷하기 때문에 충분히 가치 있는 트레이드오프입니다. 재현율을 중요하게 생각한다면 이 단계를 건너뛰어서는 안 됩니다.
인덱스를 수정하기 전에 쿼리를 먼저 수정하세요
사용자는 검색 엔진을 위해 쿼리를 작성하지 않습니다. 사람을 위해 작성합니다. "작동 안 함"은 흔한 지원 요청 쿼리입니다. 모호한 기능 설명은 흔한 사내 위키 검색어입니다. 이러한 가공되지 않은 입력을 그대로 인덱스에서 검색하면 쓰레기 같은 결과가 돌아옵니다.
쿼리가 검색기(retriever)에 도달하기 전에 변환하십시오.
**쿼리 확장(query expansion)**을 사용하여 사용자의 질문을 여러 버전으로 생성하세요. 누군가 “server down”이라고 입력하면, 시스템은 “service unavailable”, “502 error”, “connection timeout”도 함께 검색해야 합니다. 이러한 의도 변형(intent variants)을 포괄함으로써 우리의 재현율(recall)은 78%에서 96%로 향상되었습니다. 이는 단 한 단계의 과정일 뿐이며, 얻는 이득에 비하면 비용은 거의 들지 않습니다.
복잡한 질문에는 **쿼리 분해(query decomposition)**를 사용하세요. 사용자가 “레거시 결제 API에서 새로운 API로 어떻게 마이그레이션하며, 엔터프라이즈 계정에 영향을 미치는 breaking changes는 무엇인가요?”와 같은 질문을 하면, 이를 하위 질문으로 나누십시오. 한 하위 질문은 마이그레이션 단계를 대상으로 하고, 다른 질문은 엔터프라이즈 전용 breaking changes를 대상으로 합니다. 각 질문은 인덱스의 서로 다른 부분을 타격합니다. 다운스트림 언어 모델은 노이즈가 많은 컨텍스트 창에서 추측하는 대신, 잘 검색된 청크(chunks)로부터 최종 답변을 합성합니다.
하이퍼파라미터를 추측하는 일을 멈추세요
여러 청킹 전략, 하이브리드 검색, 쿼리 변환 기술을 갖추고 나면 조합 최적화 문제(combinatorial problem)에 직면하게 됩니다. 청크 크기, 오버랩, 퓨전 가중치, 리랭커 깊이, 확장 횟수는 모두 서로 상호작용합니다. 하나를 독립적으로 조정하면 다른 하나가 망가집니다. 이 공간 전체를 그리드 탐색(Grid search)하는 것은 낭비적이고 느립니다.
대신 베이지안 최적화(Bayesian optimization)를 사용하세요. 이를 머신러닝 튜닝 작업처럼 다루십시오. 목표를 명확히 정의하세요. 즉, 지연 시간(latency)을 일정 수준 이하로 유지하면서 재현율(recall)을 최대화하는 것입니다. 어떤 청크가 검색되어야 하는지 정확히 알고 있는 수백 개의 대표 질문으로 구성된 골든 데이터셋(golden dataset)을 구축하세요. 그런 다음 베이지안 탐색이 효율적으로 구성 공간을 탐색하도록 합니다. 이는 무엇이 효과적인지에 대한 확률 모델을 구축하고, 다음에 가장 유망한 영역을 테스트합니다.
모든 후보 구성은 스테이징 환경에 도달하기 전에 반드시 골든 데이터셋을 통과해야 합니다. 새로운 청크 크기가 재현율을 떨어뜨리거나, 더 무거운 리랭커가 지연 시간 예산(latency budget)을 초과하면 최적화 과정에서 자동으로 감지됩니다. 이를 통해 주관적인 의견을 배제할 수 있습니다. 256 토큰이 나은지 512 토큰이 나은지 논쟁하는 대신 결과를 읽기 시작하면 됩니다.
결과
파이프라인의 변화는 우리가 기대했던 대로 복합적인 시너지 효과를 일으켰습니다.
- Recall@10이 78%에서 95%로 상승했습니다.
- **P95 지연 시간(latency)**이 850ms에서 320ms로 감소했습니다.
- **환각률(Hallucination rate)**이 12%에서 3%로 떨어졌습니다.
- 쿼리당 비용이 38% 감소했습니다. 이는 검색 품질이 향상됨에 따라 더 작은 생성 모델과 더 적은 프롬프트 토큰을 사용할 수 있었기 때문입니다.
지연 시간 감소는 팀 내 일부 사람들을 놀라게 했습니다. 리랭커와 쿼리 확장을 추가하면 속도가 느려질 것 같기 때문입니다. 하지만 검색 품질이 향상되었기 때문에 생성 모델에 필요한 프롬프트가 줄어들었고, 추측과 재시도 횟수도 감소했습니다. 좋은 검색은 다운스트림의 모든 과정을 더 저렴하게 만듭니다.
검색을 인프라처럼 다루세요
검색은 한 번 실행하고 잊어버리는 노트북 코드가 아닙니다. 검색은 인프라이며, 코드처럼 관리되어야 합니다. 청킹 전략을 버전 관리하세요. 법무 팀에서 새로운 계약 템플릿을 출시하면, 운영 환경(production)에 적용하기 전에 재귀적 스플리터(recursive splitter)를 테스트하세요. 골든 데이터셋을 지난 분기의 정적인 CSV 파일이 아닌, 살아있는 문서로 유지하세요. CI에서 평가를 자동화하여, 임베딩 모델이나 퓨전 가중치를 수정하는 풀 리퀘스트(pull request)가 사람이 검토하기 전에 재현율과 지연 시간 수치가 포함된 코멘트를 받을 수 있도록 하세요.
사용자들은 여러분이 어떤 임베딩 모델을 사용하는지 절대 묻지 않을 것입니다. 그들은 여러분의 청킹 휴리스틱이나 리랭커 아키텍처에는 관심이 없습니다. 그들은 답변이 정확한지, 빠르게 도착하는지, 그리고 그 답변을 신뢰할 수 있는지에만 관심이 있습니다. 그 신뢰를 얻을 수 있는 파이프라인을 구축하고, 정직하게 측정하며, 검색을 나중에 생각할 문제(afterthought)처럼 취급하는 일을 멈추십시오.
Source: Optimizing RAG At Scale
Join the discussion: GyaanSetu AI Community
