검색을 고치면 RAG 출력도 고쳐집니다
요약
RAG 시스템의 품질 저하는 LLM의 문제가 아니라 검색 레이어의 컨텍스트 제공 문제인 경우가 많습니다. 단순한 토큰 단위의 기계적 청킹 대신, 의미론적 단위로 문서를 나누는 전략이 중요합니다.
핵심 포인트
- RAG의 환각 현상은 대부분 잘못된 컨텍스트를 제공하는 검색 문제에서 발생함
- 단순 토큰 기반 청킹은 의미의 최소 단위를 파괴하여 검색 품질을 저하시킴
- 임베딩 모델 최적화보다 의미론적(Semantic) 청킹 전략이 더 효과적일 수 있음
- 효과적인 RAG를 위해서는 시스템의 의미론적 최소 단위를 정의해야 함
지난달 한 고객이 자신들의 새로운 RAG 애플리케이션을 시연하는 것을 보았습니다. 그들은 이를 구축하는 데 3개월을 보냈습니다. 인터페이스는 아름다웠습니다. LLM (Large Language Model)의 응답은 유창하고 형식이 잘 갖춰져 있었습니다. 그리고 거의 모든 답변이 미묘하고도 자신만만하게 틀렸습니다.
“모델이 계속 환각 (Hallucination)을 일으켜요,” 그들은 좌절하며 저에게 말했습니다. 그들은 세 가지 다른 LLM을 시도해 보았습니다. Temperature (온도) 설정을 조정해 보았습니다. 프롬프트 (Prompt)를 수십 번 다시 작성했습니다. 하지만 아무도 아직 살펴보지 않은 것이 있었습니다. 바로 해당 프롬프트에 컨텍스트 (Context)를 제공하는 검색 레이어 (Search layer)였습니다.
이것은 RAG에서 받는 관심보다 더 많은 관심을 받아야 할 부분입니다. 대부분의 품질 문제는 검색 (Retrieval) 문제입니다. 생성형 AI (Generative AI)가 이야기를 지어낸다면, 그것은 대개 전달받은 컨텍스트가 잘못되었기 때문입니다. 너무 많거나, 너무 적거나, 혹은 그냥 완전히 관련이 없는 경우입니다. LLM은 당신이 요청한 대로 정확히 수행하고 있습니다. 즉, 당신이 제공한 정보에 기반하여 유창한 텍스트를 생성하는 것입니다. 만약 그 정보가 목표를 벗어난다면, 결국 '쓰레기가 들어가면 유창한 쓰레기가 나온다 (Garbage in, eloquent garbage out)'는 상황이 됩니다.
문제는 검색이 이미 해결된 문제처럼 취급되는 경향이 있다는 점입니다. 문서를 청킹 (Chunking)하고, 모든 것을 벡터 데이터베이스 (Vector database)에 던져 넣고, 상위 10개의 결과를 검색하면 끝이라고 생각합니다. 데모용으로는 그것으로 충분합니다. 하지만 프로덕션 (Production) 환경에서는 시스템이 부품 번호 XJ-447에 대한 정밀한 질문과 배관 원리에 대한 추상적인 질문을 구분할 수 있어야 합니다. 그 구분이 바로 진정으로 흥미로워지는 지점입니다.
사람들을 헷갈리게 만드는 청킹의 부분
문서 청킹에 대해 제가 흥미롭다고 느끼는 점이 하나 있습니다. 그것은 기술적인 문제처럼 보이지만, 실제로는 의미론적 (Semantic)인 문제입니다.
통념은 대략 이렇습니다. 문서를 가져와서 50 토큰의 오버랩 (Overlap)을 가진 512 토큰 단위의 청크 (Chunk)로 나누고, 임베딩 (Embedding)을 생성하면 끝입니다. 깔끔하고 체계적이죠. 하지만 이는 진단하기 정말 어려운 방식으로 검색 품질을 파괴하곤 합니다.
문서를 청킹 (Chunking)할 때 실제로 무엇을 하고 있는지 생각해 보십시오. 여러분은 시스템을 위한 의미의 최소 단위 (Atomic unit of meaning)가 무엇인지 결정하고 있는 것입니다. 512-토큰 (Token) 창은 중요한 설명의 한복판을 잘라버릴 수도 있습니다. 혹은 우연히 같은 페이지에 있었던 서로 관련 없는 두 개념을 하나로 뭉쳐버릴 수도 있습니다. 여러분의 청크 (Chunk)는 단순한 저장 단위가 아닙니다. 그것은 시스템이 검색하여 가져올 답변 그 자체입니다.
저는 어떤 팀들이 "이 문제에 대한 해결책은..."이라는 문장과 다음 단락에 나오는 실제 해결책을 분리해버리는 단락 기반 청킹 (Paragraph-based chunking)을 사용하면서, 정작 임베딩 모델 (Embedding model)을 최적화하는 데에만 몇 주를 허비하는 것을 보았습니다. 일단 이 문제를 인지하고 나면 해결책은 명확합니다. 하지만 청킹은 파이프라인 (Pipeline)의 초기 단계에서 발생하고, 그 영향은 LLM (Large Language Model)의 모호하거나 틀린 답변이라는 형태로 후반부에 나타나기 때문에 놓치기 쉽습니다.
효과적인 접근 방식은 기계적 (Mechanically)이 아니라 의미론적 (Semantically)으로 청킹하는 것입니다. 만약 구조화된 문서 (Structured documents)를 다루고 있다면 — 그리고 대부분의 기업용 콘텐츠는 구조화되어 있습니다 — 그 구조를 존중하십시오. 가격에 관한 섹션은 하나로 묶여 있어야 합니다. 문제 해결 절차는 온전하게 유지되어야 합니다. 개념을 설명하는 단락은 임의의 토큰 임계값 (Token threshold)을 넘었다는 이유로 분리되어서는 안 됩니다.
제 작업에서는 계층적 전략 (Tiered strategy)을 사용합니다. 먼저 구조적 마커 (Structural markers, 제목, 섹션 구분 등)를 기준으로 분할한 다음, 슬라이딩 문장 창 (Sliding sentence windows)을 임베딩하고 인접한 창 사이의 코사인 유사도 (Cosine similarity)가 낮아지는 골짜기 (Valleys) 지점에서 분할합니다. 이 골짜기들이 바로 주제가 자연스럽게 전환되는 지점입니다. 이 기술(여기 시각적으로 잘 설명되어 있습니다)은 현재 LangChain과 LlamaIndex의 기본 의미론적 청커 (Semantic chunkers)로 내장되어 있습니다. 임베딩 기반의 골짜기 탐지 방식은 모든 청크를 LLM에 보내는 비용을 들이지 않고도, 구조적 마커가 놓치는 주제의 경계선을 포착해냅니다.
이와 관련된 미묘한 차이가 있습니다. 모든 청크가 독립적으로 검색될 필요는 없습니다. 때로는 1957년 못 가격에 대한 PDF처럼 전체 문서를 검색하고 싶을 때가 있습니다. 이런 경우, 청크를 부문서(subdocuments)로 사용하고 부모-자식 관계를 설정하세요. 청크는 올바른 문서를 찾는 데 도움을 주지만, 최종적으로는 전체 문서를 반환합니다. 다른 경우에는, 특히 RAG 시나리오에서는 청크 자체를 원할 때가 있습니다. 배관에 관한 문서 전체를 찾고 있는 것이 아닙니다. P-trap이 어떻게 작동하는지 설명하는 특정 단락을 원하는 것입니다.
대부분의 팀은 하나의 접근 방식을 선택하여 모든 곳에 적용하는데, 이것이 그들의 시스템이 어떤 쿼리에서는 뛰어나지만 다른 쿼리에서는 당황스럽게 만드는 이유를 설명해줍니다.
흥미롭게 만드는 정밀도의 문제점
벡터 검색(vector search)에 대해 정말 직관적이지 않은 점이 하나 있습니다. 바로 벡터는 절대 0개의 결과를 반환하지 않는다는 것입니다.
이것은 버그가 아니라 수학입니다. 벡터 공간에서 모든 것에는 가장 가까운 이웃(nearest neighbor)이 존재합니다. 요리 레시피 데이터베이스에 양자 물리학에 대한 질문을 던지면, 기꺼이
올바른 접근 방식은 어휘 검색 (lexical search)과 의미 검색 (semantic search) 중 하나를 선택하는 것이 아닙니다. 각각을 언제 사용할지 아는 것이며, 점점 더 두 방식을 함께 사용하는 것입니다. 키워드 매칭 (keyword matching)과 의미적 상관관계 (semantic correlation)를 결합한 하이브리드 검색 (hybrid search) 전략은 단일 방식보다 일관되게 더 나은 성능을 보여줍니다.
하지만 그 결정을 조율할 무언가가 필요합니다. 이것이 정밀한 쿼리 (query)인가요, 아니면 추상적인 쿼리인가요? 정확한 매칭을 요구하는 특정 식별자 (identifiers)가 포함되어 있나요? 의미 검색이 놓칠 수 있는 도메인 특화 용어 (domain-specific terms)가 있나요? 대부분의 팀은 점점 더 복잡한 쿼리 로직으로 이 문제를 해결하려고 시도합니다. 더 흥미로운 움직임은 에이전트 (agent)가 이를 스스로 파악하도록 하는 것입니다.
RAG를 위해 실제로 작동하는 검색 구축하기
그렇다면 제대로 설계된 RAG 검색 레이어 (search layer)는 어떤 모습일까요?
검색 대상 (retrieval target)부터 시작하세요. 무엇인가를 청킹 (chunking)하기 전에 스스로에게 물어보세요: 내가 검색하려는 것이 무엇인가? 만약 문서 발견 시스템을 구축하고 있다면, 당신의 대상은 문서입니다. 찾기 쉽도록 청킹하되, 전체 문서를 반환하세요. 만약 질의응답 시스템을 구축하고 있다면, 당신의 대상은 질문에 답하는 특정 정보 조각입니다. 의미적 일관성 (semantic coherence)을 위해 청킹하고, 해당 청크만 반환하세요.
특히 RAG의 경우, 더 크고 포괄적인 것보다 더 작고 의미적으로 일관된 것이 더 낫습니다. 당신은 LLM에게 관련이 있을지도 모르는 모든 것을 주려는 것이 아닙니다. 정확히 관련이 있는 것만을 주려는 것입니다. 질문에 직접적으로 답하는 긴밀하게 집중된 문단이, 간접적으로 관련된 내용 3페이지를 포함하는 '주방 싱크대(kitchen-sink, 이것저것 다 집어넣는)' 방식보다 더 나은 성능을 발휘할 것입니다.
여기서 리랭킹 (Reranking)이 등장하며, 리랭킹이 실제로 무엇을 하는지에 대해 정확히 이해하는 것이 도움이 됩니다. 리랭킹은 단순히 켜고 끌 수 있는 마법 같은 품질 향상 도구가 아닙니다. 그것은 정밀 도구입니다. 만약 초기 검색 (Retrieval) 단계에서 대부분 관련 없는 결과들을 가져온다면, 리랭킹은 그저 그 결과들의 순서만 바꿀 뿐입니다. 통념적으로는 과잉 샘플링 (Over-sample)을 권장합니다. 즉, 50개의 결과를 검색하고, 이를 리랭킹한 뒤, 상위 10개를 취하는 방식입니다. 이것이 무엇을 의미하는지 생각해 보십시오. 당신은 의도적으로 관련성이 낮은 결과들(보통 11번째부터 50번째 결과가 그러합니다)을 검색하면서, 리랭커가 숨겨진 보석을 찾아내기를 바라고 있는 것입니다.
때로는 이것이 작동하기도 합니다. 하지만 더 빈번하게는, 이미 형편없는 결과들을 아주 미세하게 개선하는 과정에서 지연 시간 (Latency)과 비용만 추가하게 됩니다. 더 나은 접근 방식은, 애초에 높은 정밀도 (Precision)를 가진 결과들을 검색하도록 쿼리 (Query)를 작성한 다음, 리랭킹을 사용하여 순서를 미세 조정하는 것입니다. 만약 10개의 좋은 결과를 얻기 위해 50개의 결과를 검색해야 한다면, 레버리지 (Leverage)를 발휘해야 할 지점은 바로 쿼리 전략입니다.
이 지점에서 에이전트 프레임워크 (Agentic frameworks)가 의미를 갖기 시작합니다. 처음부터 완벽한 쿼리를 만드는 대신, 에이전트가 여러 쿼리 전략을 실행하고, 결과를 평가하며, 무엇을 전달할지 결정하도록 하는 것입니다. Amazon OpenSearch Service는 이를 오케스트레이션 (Orchestrate)할 수 있는 내부적인 에이전트 기능을 갖추고 있습니다. 즉, 어휘 검색 (Lexical search)과 의미론적 검색 (Semantic search)을 병렬로 실행하고, 서로 다른 랭킹 전략을 적용하며, LLM을 사용하여 어떤 결과가 실제로 질문에 답하는지 판단할 수 있습니다.
마지막 포인트는 검색 품질 평가의 새로운 표준이 되고 있습니다. 전통적으로 정밀도 (Precision)와 재현율 (Recall)을 측정하려면 사람이 수동으로 결과를 검토해야 했습니다. 이는 확장성 (Scale)이 없습니다. 하지만 LLM은 검색된 청크 (Chunks)가 쿼리와 관련된 정보를 포함하고 있는지 평가할 수 있으며, 이를 실시간으로 수행할 수 있습니다. 이를 통해 검색 결과가 좋지 않을 때 이를 인지하고, 잘못된 답을 자신 있게 생성하는 대신 다른 접근 방식을 시도하는 자기 비판적 (Self-critical)인 시스템을 구축할 수 있습니다.
이것이 향후 의미하는 바
실제 운영 환경에서 작동하는 RAG 시스템은 가장 화려한 LLM을 사용하거나 가장 정교한 프롬프트 (Prompt)를 사용하는 시스템이 아닙니다. 이들은 검색 (Retrieval)을 생성 계층 (Generative layer)과 동일한 수준의 엔지니어링적 주의를 기울일 가치가 있는 일급 문제 (First-class problem)로 취급하는 시스템입니다.
우리는 "모든 것을 임베딩 (Embed)하고, 상위 k개를 검색하여, 답변을 생성하는" 파이프라인에서 벗어나, 쿼리 (Query)에 대해 추론하고, 여러 검색 전략을 오케스트레이션 (Orchestrate)하며, 응답을 생성하기 전에 자신의 결과를 비판적으로 평가하는 시스템으로 나아가고 있습니다. 검색 계층은 단순히 기능적인 수준을 넘어 지능적으로 변하고 있습니다.
만약 이번 주에 RAG 시스템을 구축하거나 수정하고 있다면, LLM이 아니라 다음과 같은 간단한 질문에서부터 시작해 보십시오: "이 쿼리에 대한 컨텍스트 (Context)를 검색할 때, 실제로 그 질문에 답할 수 있는 구체적인 정보를 가져오고 있는가?" 로그를 확인하고, 무엇이 검색되고 있는지 살펴본 뒤, 사람이 그 컨텍스트만으로 질문에 답할 수 있는지 확인해 보십시오.
왜냐하면 사람이 답할 수 없다면, LLM 또한 답할 수 없기 때문입니다. LLM은 그저 틀린 것에 대해 더 유창하게 말할 뿐일 것입니다. 그리고 그것은 프롬프트 엔지니어링 (Prompt engineering)이 아닌 실제 엔지니어링 (Actual engineering)이 필요한 문제입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기