요즘 RAG는 종종 좋지 않은 평가를 받습니다. 이제는 완전히 단순한 기술이라고 여기거나(시작은 쉽지만 확장은 그렇지 않습니다), ‘에이전트형 시스템’에 밀렸다고 생각하기 때문입니다. 하지만 많은 경우 그 속을 들여다보면 금세 RAG와 매우 비슷해 보이기 시작합니다…
이 글에서는 한두 가지 실제 사례를 통해 다음과 같은 일반적인 과제를 해결하는 방법을 설명합니다.
텍스트와 수치가 혼합된 데이터 처리 및 단순한 RAG가 실패하는 이유: 키워드가 겹치고 숫자 자체에는 의미론적 의미가 없습니다.
요약 우선 임베딩 설계가 유용한 이유: 청크별로 짧은 설명형 요약을 생성한 다음, 그 요약을 임베딩하고 쿼리합니다.
맥락을 반영한 요약을 생성하는 방법: 상위 문서의 맥락을 포함해 형태가 비슷한 통계도 명확히 구분합니다.
코드와 Pydantic 모델을 활용해야 할 때: 원문 그대로의 내용이 중요하다면 신뢰성을 위해 사용자 지정 코드 및 Pydantic 모델을 LLM 호출과 함께 사용합니다.
기본 원리
RAG 시스템은 고객 지원 봇부터 사내 지식 도우미까지 다양한 서비스의 기반이 됩니다.
일반적으로 내부에서는 다음 단계를 거칩니다.
소스 문서를 청크로 분할
각 청크를 벡터 공간에 임베딩
쿼리 시 상위 K개 청크를 검색
해당 청크를 조건으로 답변을 생성
LangChain, LlamaIndex, OpenAI의 Filestore 같은 인기 도구를 사용하면 이러한 단계가 거의 간단한 작업이 됩니다. 하지만 실제 파이프라인에서는 내용이 풍부한 텍스트만 있는 것이 아니며, 기본적인 RAG로는 처리하기 어려운 데이터도 만나게 됩니다. 이어지는 섹션에서는 구체적인 데이터 문제를 살펴보고, 복잡성이 늘어남에 따라 솔루션을 단계적으로 구축해 보겠습니다.
데이터가 텍스트로만 구성되지 않은 경우(사실 흔한 일입니다)
게임이라는 맥락에서 다음 데이터 청크를 살펴보겠습니다.
JSON
임베딩은 의미와 문법을 통해 학습된 단어 간 관계를 바탕으로 작동합니다. 위 데이터에는 텍스트와 숫자가 섞여 있으며, 이 특정 맥락을 벗어나면 숫자와 단어 사이에는 아무런 관계가 없습니다. 따라서 이 데이터 청크는 어느 정도 설명적인 단어 뒤에 무작위 숫자가 붙은 조합에 가깝다고 할 수 있습니다.
이런 유형의 데이터만 있다면 사실 문제가 되지 않습니다. 몇 안 되는 설명형 단어의 임베딩으로도 검색할 수 있기 때문입니다(또는 text-to-SQL을 사용하면 됩니다). 하지만 이 청크가 같은 단어가 등장하는 수많은 텍스트 중심 청크 사이에 묻혀 있다면 어떨까요? 예를 들면 다음과 같습니다.
JSON
이제 “What is the attack range with Draconic Ascension?”을 검색한다고 가정해 보겠습니다.같은 키워드가 포함된 다른 청크의 잡음 속에 깊이 묻혀 있으므로, 원하는 관련 청크를 검색하지 못할 가능성이 매우 높습니다.
근본적인 문제는 같은 주제에 관해 서로 다른 유형의 정보를 담고 있는데도 이러한 데이터 청크를 제대로 구분할 수 없다는 점입니다. 어떻게든 데이터를 더 풍부하게 만들거나 개선할 수 있을까요? 물론 가능합니다:smile:
요약을 통해 데이터를 강화하세요. 잘못 읽은 것이 아닙니다
청크 자체를 곧바로 임베딩하는 대신, 먼저 데이터의 내용을 설명하는 요약을 생성한 다음 그 요약을 임베딩하고 검색할 수 있습니다. 생성 단계에서는 요약에 연결된 원본 데이터를 그대로 사용합니다.
그러면 앞서 살펴본 두 청크에 대해 다음과 같은 요약을 생성할 수 있습니다.
공격 사거리, 속도, 피해량의 통계(기본 상태 및 Draconic Ascension 적용 상태).
활성화 조건, 시각 효과, 배경 이야기를 포함한 Draconic Ascension의 설명과 세부 정보.
그런 다음 쿼리도 요약에 ‘맞도록’ 보강합니다. 예를 들어 “What is the attack range with Draconic Ascension?”을“What is the statistics of attack range with Draconic Ascension?”으로 바꿉니다.검색 쿼리가 기술 분야 밖의 사용자가 ~~‘자유로운’~~ 평범한 사람의 언어로 질문한 것이라면 특히 중요합니다. 사용자는 RAG가 정밀도와 재현율을 극대화하는 방식까지 알 필요도, 신경 쓸 이유도 없기 때문입니다.


맥락에서 떼어 놓고 판단하지 마세요(삶에도 대체로 적용되는 원칙입니다)
다음으로는 아래와 같이 형태가 똑같아 보이는 수많은 데이터 청크를 처리해야 하는 상황을 살펴보겠습니다.
Plain Text
같은 접근 방식을 유지한 채 “what is character X’s attack range?”라고 질문한다고 가정해 보겠습니다.방금 생성한 요약들도 매우 비슷해 보이기 때문에, 운에 맡긴 추측 게임을 하게 될 것입니다. 그렇다면 어떻게 구분할 수 있을까요?
간단한 답은 맥락을 제공하는 것입니다. 데이터 청크에 상위 문서를 가리키는 참조를 포함하면 됩니다. 이 경우에는 예를 들어 {”character”: “X”}를 추가할 수 있습니다. 그러면 캐릭터 Y와 Z의 동일한 데이터가 함께 있어도 캐릭터 X의 올바른 데이터를 정확하게 검색할 수 있습니다.
하지만 더 효과적이고 일반화하기 쉬운 방법은 청크의 맥락 요약을 생성하는 것입니다. 즉, 데이터 청크만 요약하는 대신 상위 문서와 청크를 모두 입력해 전반적인 맥락 요약을 생성할 수 있습니다. 이때 다음과 같이 해당 청크가 상위 문서에서 어떤 역할을 하는지도 요약에 포함합니다.
이 청크는 캐릭터 X의 …에 관한 상세 통계를 제공합니다. 이 청크는 X의 공격 속도 측면 강점을 보여 줌으로써 전체 문서의 맥락에 연결됩니다…
이 청크는 캐릭터 Y의 …에 관한 상세 통계를 제공합니다. 이 청크는 Y가 특수 능력을 사용했을 때 향상되는 능력치를 보여 줌으로써 전체 문서의 맥락에 연결됩니다…
이 청크는 캐릭터 Z의 …에 관한 상세 통계를 제공합니다. 이 청크는 Z가 팀전에서 탱커 역할에 적합하다는 점을 보여 주는 능력치를 통해 전체 문서의 맥락에 연결됩니다…
Anthropic에서 일부 영감을 받은 이 방법은 앞의 사례에는 지나치게 복잡해 보일 수 있습니다. 하지만 ‘맥락을 벗어나면’ 잘못 해석될 수 있는 청크에 매우 효과적이며, 모든 청크에 적용되는 통합 접근 방식을 제공해 엔지니어링 파이프라인도 깔끔하게 유지할 수 있습니다.


~~통제광처럼~~ 엄밀하게 처리해야 할 때
일반적으로는 완전한 형태로 데이터를 받은 뒤 RAG 시스템에 사용할 청크로 분할합니다. 이번에는 조금 다른 사례를 살펴봅니다. 데이터가 이미 청크로 나뉘어 있지만 잘못 분할된 경우입니다. 실제로는 하나의 논리적 청크로 다시 묶어야 할 내용이 무작위 구간으로 잘려 있습니다. 논리적 청크란 문서의 하위 섹션이나 일관된 문단처럼 자연스럽게 한데 묶여야 하는 콘텐츠 단위를 뜻합니다.


첫 번째 시도에서는 모든 데이터를 LLM 호출에 입력하고, 적절하다고 판단하는 방식으로 묶은 뒤 그룹화된 내용을 반환하도록 요청했습니다. LLM이라면 꽤 잘할 수 있겠죠? 글쎄요, 그렇기도 하고 아니기도 합니다.
다른 여러 사례에서도 확인했듯이 LLM은 전체 내용을 정확히 요구할 때 작업을 대충 처리하는 경향이 있고 신뢰하기 어렵습니다. 특히 맥락이 길수록 그렇습니다. 충분히 이해되는 현상입니다. 하지만 이 사용 사례에서는 치명적인 문제였습니다. 요약해서도 안 되고 원본 내용을 조금이라도 빠뜨려서도 안 되며, 단어 하나하나를 정확히 유지해야 했기 때문입니다. 어떤 세부 정보도 놓쳐서는 안 됩니다.
물론 ‘그렇다’고 할 수 있는 부분도 있었습니다. LLM은 분할된 청크의 의미와 구조를 이해하는 데는 탁월했습니다. 정확한 내용을 그대로 되풀이하기만 거부하지 않는다면 말이죠. 이런:/
그렇다면 LLM이 잘하는 일은 활용하면서 신뢰하기 어려운 부분은 어떻게 피할 수 있을까요? 저희는 오랜 친구인 코드, 다시 말해 맞춤형 Python 함수에 의지했습니다. 그리고 ‘이보다 더 간단할 수 없는’ Pydantic 모델도 사용했습니다. 해결 방법은 다음과 같습니다.
현재 논리적 청크를 유지하면서 각 섹션을 순회합니다.
각 섹션에서 LLM에 질문합니다. “이 섹션이 현재 논리적 청크에 속하는가?” Pydantic 모델에 따라 예 또는 아니요로 답하게 합니다.
예라면 섹션을 청크에 추가합니다. 아니라면 현재 논리적 청크가 완성된 것으로 보고 그대로 내보낸 뒤, 해당 섹션으로 새 청크를 시작합니다.


물론 전체 내용을 한 번에 처리할 때보다 토큰을 조금 더 사용합니다. 하지만 정확한 내용을 온전히 유지하는 것이 최우선인 이 사용 사례에서는 약간의 추가 비용을 충분히 감수할 만했습니다.
매우 간단한 해결책이지만 중요한 원칙을 따릅니다. 엄밀성이 필요할 때는 확률적으로 작동하는 LLM에만 의존해서는 안 된다는 원칙입니다.
맞춤형 코드와 함수, Pydantic 모델을 활용하면 LLM의 역량을 충분히 끌어내면서도 예측 가능하고 신뢰할 수 있는 결과를 얻을 수 있습니다.
생성형 AI 솔루션 구축은 AI 과제인 동시에 그만큼 중요한 엔지니어링 과제이기도 합니다. 이 사례들이 여러분만의 고유한 과제를 해결하는 데 영감을 주었기를 바랍니다. 엔지니어링 우선 방식의 생성형 AI 솔루션에 관해 더 알아보려면 라우터 기반 에이전트형 시스템 설계에 관한 블로그 글을 확인해 보세요.