~/khan

RAG 이 뭔가요, 그리고 왜 Spring AI 를 끼우게 되나요 — 검색과 생성을 묶는 한 줄짜리 약속

· 13 min read · by Khan
#RAG#Spring AI#Vector DB#Elasticsearch#LLM

RAG 라는 단어를 처음 봤을 때 머릿속에 떠오른 그림은 "검색해서 LLM 한테 그대로 넘기는 거" 정도였습니다. 그런데 실제 시스템을 그려 보니 그 한 줄 사이에 임베딩, Vector 검색, BM25 검색, 재랭킹, 프롬프트 합성 이 줄줄이 끼어 있었습니다. 이번 글은 그 사이에 무엇이 들어가는지를 주니어 입장에서 한 번 정리한 기록입니다.

1. RAG 의 정의 — 세 단어로 묶어보기

RAG 는 Retrieval-Augmented Generation 의 줄임말입니다. 단어를 그대로 풀면 이렇게 됩니다.

  • Retrieval (검색) — 질문과 관련 있는 문서를 어딘가에서 찾아옵니다.
  • Augmentation (보강) — 찾아온 문서를 LLM 입력에 함께 끼워 넣습니다.
  • Generation (생성) — LLM 이 그 문서를 근거 삼아 답변을 만들어 냅니다.

한 줄로 정리해 두면 이렇게 적어둘 수 있을 것 같습니다.

"LLM 이 답을 만들기 전에, 사실 기반 문서를 먼저 찾아서 같이 보여주는 아키텍처."

2. 왜 굳이 검색을 끼워 넣는가

LLM 만 가지고도 답은 나옵니다. 그런데 LLM 단독으로 두면 이런 한계가 따라옵니다.

  • Hallucination — 사실이 아닌 내용을 그럴듯하게 지어내는 문제가 있습니다.
  • 사내 문서를 모름 — 회사 내부 위키, 정책 문서, DB 스키마 같은 정보는 학습되어 있지 않습니다.
  • 최신 정보를 모름 — 학습 시점 이후의 일은 알 수가 없습니다.

RAG 는 이 셋을 "외부 문서를 같이 보여줘서 답변의 근거를 만들어 주는" 방식으로 푸는 시도였습니다. LLM 한테 답을 외워 두라고 시키는 대신, 답할 때 참고할 자료를 옆에 같이 깔아 주는 쪽 입니다.

3. 처리 흐름 — 질문 한 줄이 답이 되기까지

사용자 질문이 들어왔을 때 시스템 내부에서 일어나는 일을 풀어 적으면 이런 단계가 됩니다.

1) 질문 텍스트
      │  Embedding 모델로 벡터화

2) 질문 벡터
      │  Vector DB (의미 기반)        ┐
      ▼                             ├─▶ 두 결과를 모음
   유사 문서 후보군                    │

1') 질문 텍스트                       │
      │  Elasticsearch (BM25, 키워드) │
      ▼                             ┘
   키워드 매칭 문서 후보군


3) 점수 정규화 + 가중치 + 재랭킹


4) 상위 1~5 개 문서를 프롬프트에 끼워 넣기


5) LLM 이 그 문서를 근거로 답변 생성


6) 사용자에게 응답

여기서 자주 헷갈렸던 부분이 있습니다. "왜 Vector 검색 하나로 안 끝나고 BM25 까지 같이 도는지" 였습니다. 이걸 다음 섹션에서 풀어 보겠습니다.

4. Vector 검색과 BM25 검색은 잘하는 영역이 다릅니다

4.1 Vector Search (pgvector 같은 것)

Embedding 모델로 문장을 벡터로 바꾸고, 벡터 간 유사도(HNSW, L2, cosine 등) 로 가까운 문서를 찾습니다. 이쪽은 "개념적으로 비슷한 내용" 을 잘 찾습니다.

예를 들어 "환불 정책" 으로 검색하면 본문에 "환불" 이라는 단어가 없어도, "결제 취소 시 처리 방법" 같은 문서를 잘 끌고 옵니다. 의미가 가까우면 단어가 달라도 찾아 주는 게 강점입니다.

4.2 BM25 Search (Elasticsearch 같은 것)

전통적인 텍스트 검색 알고리즘입니다. 단어가 얼마나 자주 등장하는지(TF), 그 단어가 전체 문서에서 얼마나 희소한지(IDF) 를 보고 점수를 매깁니다. 이쪽은 "단어가 정확히 일치하는 경우" 를 잘 찾습니다.

고유명사, 모델명, 에러 코드, SKU, 사람 이름 같은 것들은 의미 기반 검색이 오히려 헷갈려 하는 영역입니다. ERR_NETWORK_2041 같은 키워드는 의미가 아니라 문자열 자체로 매칭이 되어야 하니까요.

4.3 그래서 하이브리드

실무 RAG 이 두 검색을 같이 도는 이유가 여기 있었습니다. 각자 잘하는 영역이 다르니, 둘을 같이 굴리고 결과를 합쳐서 좋은 후보를 뽑자는 발상입니다.

최종 스코어 =
  (Vector 유사도 점수 × w1) + (BM25 점수 × w2)

가중치(w1, w2)와 정규화 방법은 도메인에 따라 다르게 잡습니다. 사내 코드 검색이라면 BM25 가중치를 올리고, 자연어 질의에 가까운 챗봇이라면 Vector 가중치를 올리는 식입니다.

5. 저장소는 두 군데로 갈라집니다

RAG 에서는 Vector 인덱스와 BM25 인덱스가 보통 물리적으로 다른 저장소 에 들어 있습니다.

  • Vector DB (PostgreSQL + pgvector 같은 조합)
    • 임베딩 벡터를 저장합니다.
    • HNSW, IVF 같은 인덱스로 유사도 검색을 수행합니다.
  • Elasticsearch (BM25 기반 검색엔진)
    • 텍스트 역색인(inverted index)을 저장합니다.
    • 키워드 기반 BM25 검색을 수행합니다.

두 저장소가 갈라져 있어도, 위에서 본 것처럼 검색 단계에서 두 결과를 가져와 점수를 합치는 것 이라 동일한 시스템처럼 동작합니다.

6. 검색 품질을 끌어올리는 확장 기능들

RAG 를 단순히 "검색 → 프롬프트에 붙이기" 로 끝내지 않고, 품질을 끌어올리기 위해 보통 다음과 같은 확장이 같이 들어갑니다.

6.1 문서 Chunking

긴 문서를 작은 단위로 잘라서 임베딩합니다. 페이지 통째로 임베딩하면 의미가 평균화돼서 검색 정확도가 떨어지기 때문입니다. "한 청크에 한 주제" 정도가 좋다고들 합니다.

6.2 Metadata 저장

문서 출처, 제목, 작성일자, 카테고리 같은 정보를 함께 저장해 두면 검색 결과를 필터링하거나 답변 출처를 표시할 때 유용합니다. "이 답변은 어느 문서의 어느 부분에서 가져왔나" 를 사용자에게 보여줄 수 있게 됩니다.

위에서 본 Vector + BM25 조합입니다. 의미 검색과 키워드 검색의 장점을 함께 가져갑니다.

6.4 Reranking

1차 검색에서 뽑은 후보군을 다시 한번 정밀하게 정렬합니다. 보통 cross-encoder 같은 별도 모델을 두거나, LLM 자체를 reranker 로 쓰기도 합니다.

6.5 Hallucination 방지

프롬프트에 "검색된 문서 범위를 벗어난 내용은 만들지 마라" 같은 지시를 넣거나, 출처를 강제로 인용하게 만드는 식의 후처리를 둡니다. RAG 를 끼웠다고 자동으로 환각이 사라지는 건 아니어서, 이 부분은 따로 챙겨야 합니다.

7. 그래서 Spring AI 는 어디에 끼는가

여기까지의 흐름에서 Spring AI 가 필수는 아닙니다. JDBC + HTTP 클라이언트만 가지고도 RAG 를 직접 짤 수 있습니다. 그럼에도 Spring 기반 환경에서 Spring AI 를 쓰는 이유는 대략 이렇습니다.

  • LLM Provider 통합 API — OpenAI / Gemini / Azure / Anthropic 등 각 제공자별 SDK 차이를 한 단계 추상화해 줍니다.
  • Embedding / VectorStore 추상화 — pgvector, Pinecone, Qdrant, Milvus 등 Vector DB 를 거의 같은 코드로 갈아 끼울 수 있게 해 줍니다.
  • Prompt 템플릿 / 합성 — 검색 결과를 프롬프트에 끼우는 자리를 깔끔하게 잡아 줍니다.
  • Tool Calling / Function Calling — LLM 이 함수를 호출하는 패턴을 Spring Bean 으로 자연스럽게 노출합니다.

요약하면, Spring AI 는 RAG 자체를 발명하지 않습니다. 이미 있는 부품들을 Spring 답게 묶어 주는 어댑터 모음 에 가깝습니다. 직접 짜는 것보다 손이 덜 가고, 부품을 교체할 때 코드 변경이 덜 들어갑니다.

              ┌────────────────────────────────┐
              │            애플리케이션          │
              └──────────────┬─────────────────┘

            ┌────────────────┴────────────────┐
            │           Spring AI             │
            │  (LLM / Embedding / Vector 추상화) │
            └─┬───────────┬──────────────┬────┘
              ▼           ▼              ▼
           OpenAI      pgvector      Elasticsearch
           Gemini      Pinecone      OpenSearch
            ...         ...            ...

8. 한 줄로 다시 적어두기

마지막으로 RAG 의 정의를 제 말로 다시 적어두면 이렇게 될 것 같습니다.

"질문에 가장 적합한 문서를 찾아 와서, 그 문서를 근거로 LLM 이 답변을 만들도록 하는 전체 과정."

Vector DB 도, BM25 도, Reranker 도, Spring AI 도 RAG 를 구성하는 부품 일 뿐이고, RAG 자체는 그 부품들을 어떤 순서로 엮느냐에 관한 아키텍처 설계 방식 이었습니다.

마무리

이번 글에서 가장 크게 정리된 부분은 두 가지였습니다.

  1. 검색은 한 종류로 끝나지 않는다. 의미 기반과 키워드 기반은 각자 잘하는 영역이 따로 있어서, 실무 RAG 는 보통 둘을 같이 굴립니다.
  2. Spring AI 는 RAG 그 자체가 아니다. RAG 라는 아키텍처를 Spring 환경에서 짧은 코드로 묶기 위한 어댑터에 가깝습니다.

다음에 RAG 시스템을 들여다보거나 직접 만들 일이 생기면, 코드부터 짜기 전에 종이 한 장에 이 세 줄을 먼저 적어 두려고 합니다.

  1. 이 질문은 의미 검색이 어울리는가, 키워드 검색이 어울리는가
  2. 청크 크기와 메타데이터는 어떻게 설계할 것인가
  3. LLM 에 몇 개의 문서를, 어떤 순서로 넘길 것인가

이 세 줄만 먼저 적어 두어도 한 번에 만들 수 있는 RAG 의 품질이 꽤 달라질 것 같습니다.

참고