RAG 찍먹해보기

콜 파머가 될 남자·2026년 5월 21일

진화하기

목록 보기
3/3
post-thumbnail

왜 기존 검색기능으로는 부족한가

기존에 리뷰 검색은 Elasicsearch의 Nori 형태소 분석기를 사용하고 있었다.

Nori는 텍스트를 형태소로 쪼개서 매칭한다.

위 리뷰가 저장되어있다고 가정해보자.

"복지 좋아요?" 라고 질문하면 DB에 일치하는 형태소가 존재하지 않으므로 매칭이 안 될 것이다.

의미는 비슷한데 단어가 다르면 못 찾는다. 이걸 해결하는 게 임베딩이다. 텍스트를 768개의 숫자로 이루어진 벡터로 변환하고, 의미가 비슷한 문장일수록 벡터의 방향이 비슷하고 가까울수록 코사인 유사도가 1에 가깝다.

질문과 저장된 리뷰 벡터들을 각각 비교하면 이런 결과가 나온다.

  • 첫번째 리뷰는 복지 언급이 없는데 유사도가 가장 높은것을 확인할 수 있다.
  • 마지막 리뷰는 복지 언급이 있어 유사도의 방향은 맞지만, 감정에서 유사도의 차이가 발생한것을 확인할 수 있다.

임베딩 과정이 없었다면 '복지'라는 형태소가 없는 리뷰들은 결과에서 제외됐을 것이다.


RAG - 검색으로 보강한 생성

RAG는 Retrieval-Augmented Generation의 약자이고 직역하면 '검색으로 보강한 생성'이다. LLM에게 그냥 "이 회사 복지 어때요?"라고 물으면 학습 데이터 기반의 일반론만 답한다. 실제 리뷰 내용을 알 리 없다.

그래서 질문과 관련된 리뷰를 먼저 검색하고, 그 내용을 LLM에게 함께 전달한다.

세 단계로 정리하면: 검색(Retrieval) → 주입(Prompt) → 생성(Generation)


Elasticsearch에 벡터 인덱스 구성

별도의 VectorDB를 도입하지 않았다. Elasticsearch 8.x 부터 dense_vector 타입을 지원해서 기존 ES를 VectorDB로 그대로 재활용할 수 있다.

인덱스 구조는 일반 ES 인덱스와 거의 같고 embedding 필드 하나만 다르다고한다.

dims(768)은 임베딩 모델의 출력 크기이다(배열의 길이). 모델을 바꾸면 이 값도 바꿔야 한다. 나머지 필드는 일반 ES 매핑과 동일하다.

인덱싱 코드도 단순하다. 임베딩 모델에 텍스트를 넣으면 float[] 배열이 나온다. 이걸 ES 문서에 저장하는 것뿐이다.

titlecontent를 합치는 이유는 제목 맥락까지 벡터에 반영하기 위함이다.


kNN 검색 - 유사 리뷰 찾기

전체 벡터를 모두 비교하면 정확하지만 느리다. 리뷰가 100만 건이면 100만 번 코사인 유사도를 계산해야 한다.

ES는 HNSW 알고리즘으로 이를 해결한다. 벡터들을 미리 그래프 구조로 연결해두고, 검색 시 그래프를 탐색하며 가까운 노드로 이동하는 방식이다. 전체를 보지 않고도 유사한 벡터 근방에 빠르게 도달한다.

numCandidates=50은 이 그래프 탐색에서 살펴볼 후보 개수다. 후보 50개를 추린 뒤 그 안에서 코사인 유사도가 가장 높은 5개(k=5)를 최종 반환한다. 후보를 넉넉하게 잡는 이유는 HNSW가 근사 알고리즘이라 전수 비교 대비 정확히 상위 5개를 보장하지 않기 때문이다. 후보를 충분히 확보해야 실제 상위 5개가 그 안에 포함될 가능성이 높아진다.

numCandidates 가 클수록 정확도는 높아지고 속도는 느려지는 트레이드오프다.

filtercompanyId 기준으로 해당 회사 리뷰만 검색 대상으로 좁힌다. 다른 회사 리뷰가 유사도가 높아도 결과에서 제외된다.


전체 흐름 - 질문부터 답변까지

앞서 설명한 임베딩, 인덱싱, kNN 검색을 모두 포함한 ask() 메서드이다.


학습한 점

이번 포스팅의 중점은 RAG의 개념을 학습하고 VectorDB를 간단히 사용해보는것에 있다. 리뷰 검색 흐름은 올바르게 동작하지만, 검색 정확도가 충분한지 판단하기는 어렵다.

numCandidates=50, k=5로 설정한 것도 레퍼런스를 따른 것이지 직접 측정해서 튜닝한 값이 아니다. 한국어 리뷰에 영어 중심 임베딩 모델인 nomic-embed-text 을 쓰는 것도 마찬가지다. 모델 선택과 정확도 튜닝은 AI 개발자의 영역이라고 생각한다.

백엔드 입장에서 보완이 필요하다고 느끼는 부분은 따로 있다.

  1. 현재 Ollama가 응답하지 않으면 SSE 연결이 그냥 끊긴다.
  2. 리뷰 작성 후 ES에 인덱싱되기까지 Kafka 지연이 있어서, 방금 쓴 리뷰는 바로 검색되지 않는다.
  3. 현재 kNN 검색은 전체 벡터를 탐색한 뒤 companyId로 필터링한다. 회사당 리뷰가 적으면 후보 50개를 탐색하는 동안 해당 회사 리뷰를 5개도 못 찾아 결과가 부족하게 반환될 수 있다.

첫 번째 보완이 필요한 점은 다음 포스팅에서 바로 다뤄 볼 예정이다.

profile
꾸준함 빼면 시체

0개의 댓글