Home RAG 넌 대체 뭐니?
Post
Cancel

RAG 넌 대체 뭐니?

RAG 란 무엇일까

  • Retrieval Augmented Generation 의 약자.
  • 말 그대로 찾아온 내용 을 기반으로 LLM 이 답변하게 만드는 방식이다.
  • LLM 이 세상의 모든 최신 문서, 사내 문서, 우리 서비스 DB 내용을 다 알고 있을 수는 없다.
  • 그래서 질문이 들어오면 먼저 관련 문서를 검색하고, 그 검색 결과를 프롬프트에 같이 넣어서 답변하게 한다.

결국 내 식대로 이해하면 RAG 는 검색 DB + LLM 이다.

  • 검색 DB 에 문서를 잘 넣어둔다.
  • 질문이 들어오면 관련 문서를 잘 찾는다. 이건 내가 하는거다.
  • 찾은 문서를 LLM 에게 같이 준다.
  • LLM 은 그 문서를 근거로 답변한다.

LLM 이 똑똑해서 다 해결하는 구조라기보다는, 좋은 검색 결과를 잘 물려줘야 답이 좋아지는 구조다.

그냥 DB 조회랑 뭐가 다른가

  • 일반 DB 조회는 보통 정확한 조건이 필요하다.
    • user_id = 10
    • created_at between ...
    • title like '%환불%'
  • RAG 에서 필요한 검색은 질문과 문서가 정확히 같은 단어를 쓰지 않아도 찾아야 한다.
    • “퇴사자는 계정 어떻게 막아?”
    • “임직원 비활성화 절차”
    • “계정 잠금 정책”

사람이 보기엔 다 비슷한 말인데, 단순 문자열 검색으로는 놓치기 쉽다.

그래서 RAG 에서는 보통 문서를 벡터로 바꿔서 저장하고, 질문도 벡터로 바꿔서 가까운 문서를 찾는다.

옛날 검색 감각부터 보면

검색은 원래도 대충 이런 생각을 했다.

TF IDF

  • TF 는 Term Frequency, 문서 안에서 어떤 단어가 얼마나 자주 나오는지.
  • IDF 는 Inverse Document Frequency, 너무 흔한 단어는 점수를 낮추는 방식.

예를 들어 모든 문서에 “서비스”, “사용자” 라는 단어가 나오면 그 단어는 별로 중요하지 않다. 반대로 특정 문서에만 “퇴사자 계정 회수” 같은 표현이 자주 나오면 그 문서는 그 주제와 관련이 높다고 볼 수 있다.

그래서 TF IDF 는 대충 이런 감각이다.

1
2
문서 안에서 자주 나오고,
전체 문서에서는 흔하지 않은 단어일수록 중요하다.

이 방식도 꽤 좋다. 실제로 검색 엔진의 기본 감각을 이해하는 데는 아직도 좋다.

다만 “계정 잠금” 과 “사용자 비활성화” 처럼 단어는 다른데 의미가 비슷한 경우는 약하다.

벡터 내적

문서를 숫자 배열로 바꿨다고 해보자.

1
2
3
질문: [0.2, 0.8, 0.1]
문서A: [0.1, 0.9, 0.2]
문서B: [0.9, 0.1, 0.1]

질문 벡터와 문서 벡터가 얼마나 비슷한 방향을 보고 있는지 계산하면, 어떤 문서가 질문과 가까운지 볼 수 있다.

대표적으로 내적(dot product) 이나 cosine similarity 같은 걸 쓴다.

내적은 단순하게 보면 같은 위치의 숫자를 곱해서 더한다.

1
2
질문 · 문서A = 0.2*0.1 + 0.8*0.9 + 0.1*0.2 = 0.76
질문 · 문서B = 0.2*0.9 + 0.8*0.1 + 0.1*0.1 = 0.27

그러면 문서A 가 질문과 더 가깝다고 판단할 수 있다.

물론 실제 embedding 은 3차원이 아니라 수백, 수천 차원의 벡터고, 모델이 문장의 의미를 숫자 공간에 잘 배치하도록 학습되어 있다.

Embedding

Embedding 은 텍스트를 벡터로 바꾸는 일이다.

1
2
"퇴사자 계정 회수 절차는 어떻게 되나요?"
-> [0.013, -0.221, 0.447, ...]

중요한 건 단어를 세는 게 아니라, 의미가 비슷한 문장들이 비슷한 위치에 오도록 만드는 것이다.

  • “환불은 며칠 걸려?”
  • “결제 취소 처리 기간은?”

이 두 문장은 단어가 많이 다르지만 의미는 가깝다. embedding 모델이 괜찮으면 두 문장의 벡터도 가까워진다.

그래서 RAG 는 검색 DB 라고 해도, 일반적인 keyword index 만 쓰는 게 아니라 vector index 를 같이 쓰는 경우가 많다.

Chunking

문서를 통째로 embedding 하면 문제가 생긴다.

  • 문서 하나가 너무 길면 embedding 품질이 떨어질 수 있다.
  • 질문에 필요한 부분은 문서의 한 단락뿐인데, 전체 문서를 가져오면 노이즈가 많다.
  • LLM 프롬프트에 넣을 수 있는 토큰도 제한이 있다.

그래서 문서를 적당한 크기로 자른다. 이게 chunking 이다.

1
2
3
4
긴 문서
-> chunk 1
-> chunk 2
-> chunk 3

그리고 각 chunk 를 embedding 해서 index 에 넣는다.

RAG 품질은 여기서 많이 갈린다.

  • 너무 작게 자르면 문맥이 끊긴다.
  • 너무 크게 자르면 검색 결과가 흐려진다.
  • 표, 코드, 정책 문서처럼 구조가 있는 문서는 그냥 글자 수로 자르면 망할 수 있다.
  • overlap 을 조금 주면 앞뒤 문맥이 이어져서 도움이 될 때가 있다.

개인적으로 RAG 는 모델보다 chunking 이 먼저 삐끗하는 경우가 꽤 많다고 느낀다.

Index

index 는 검색하기 좋은 형태로 데이터를 저장해둔 것이다.

RAG 에서는 보통 이렇게 들어간다.

1
2
3
4
원본 문서
-> chunking
-> embedding
-> vector index 저장

질문이 들어오면 반대로 간다.

1
2
3
4
5
사용자 질문
-> embedding
-> vector index 에서 가까운 chunk 검색
-> 검색 결과를 LLM 프롬프트에 추가
-> 답변 생성

즉 RAG 의 핵심 파이프라인은 ingestion 과 retrieval 로 나눠볼 수 있다.

Ingestion

  • 문서를 가져온다.
  • 문서를 파싱한다.
  • chunk 로 자른다.
  • embedding 한다.
  • vector index 에 저장한다.

Retrieval

  • 사용자 질문을 embedding 한다.
  • vector index 에서 비슷한 chunk 를 찾는다.
  • 필요하면 reranking 이나 필터링을 한다.
  • 찾은 내용을 LLM 에게 넘긴다.

여기서 검색 결과가 안 좋으면 LLM 은 아무리 좋아도 헛소리를 하게 된다. RAG 를 붙였다고 hallucination 이 자동으로 사라지는 건 아니다.

Bedrock Knowledge Bases 로 보면

AWS Bedrock Knowledge Bases 를 쓰면 위 과정을 많이 대신 해준다.

대략 이런 역할을 한다.

  • S3 같은 data source 에 있는 문서를 읽는다.
  • 문서를 chunk 로 나눈다.
  • embedding model 로 vector embedding 을 만든다.
  • vector store 에 index 로 저장한다.
  • 질문이 들어오면 관련 chunk 를 retrieve 한다.
  • 필요하면 retrieve 한 내용을 기반으로 답변까지 생성한다.

AWS 문서에서도 Knowledge Bases 는 data source 를 embedding 으로 바꿔 vector index 에 저장하는 흐름으로 설명한다.

직접 만들면 LangChain 같은 걸 붙이고, chunking 전략 만들고, embedding batch 돌리고, OpenSearch 나 pgvector 같은 vector store 에 넣고, 검색 API 만들고, prompt 에 끼워넣는 일을 다 해야 한다.

Bedrock KB 를 쓰면 이 부분을 AWS managed 기능으로 가져갈 수 있다.

특히 PoC 나 사내 문서 검색 정도는 아래 조합이 꽤 편하다.

1
2
3
4
5
S3 문서
-> Bedrock Knowledge Bases
-> chunking / embedding
-> vector index
-> retrieve / retrieveAndGenerate

물론 managed 라고 고민이 없어지는 건 아니다.

  • 어떤 embedding model 을 쓸지
  • chunk size 를 어떻게 잡을지
  • metadata filter 를 어떻게 넣을지
  • 문서 sync 주기를 어떻게 할지
  • 권한별 문서 접근 제어를 어떻게 할지
  • 검색 결과를 몇 개 가져올지
  • 답변에 출처를 어떻게 보여줄지

이런 건 여전히 애플리케이션 설계의 영역이다.

RAG 를 너무 대단하게 보지 않아도 된다

RAG 라고 하면 뭔가 거창해 보이는데, 단순하게 보면 이거다.

1
2
질문과 비슷한 문서를 잘 찾고,
그 문서를 보고 답변하게 한다.

검색이 반이다.

LLM 에 넣기 전에 이미 승부가 많이 난다. 관련 없는 chunk 를 넣으면 답변도 이상해지고, 필요한 chunk 를 못 찾으면 “문서 기준 답변” 이 불가능하다.

그래서 RAG 를 만들 때는 모델 호출 코드보다 아래를 더 많이 봐야 할 것 같다.

  • 문서가 잘 파싱됐는지
  • chunk 가 읽을만하게 잘렸는지
  • embedding model 이 도메인 문장을 잘 표현하는지
  • index 검색 결과가 사람이 봐도 납득되는지
  • 답변에 근거 chunk 가 제대로 들어가는지

결국 RAG 는 LLM 기능이라기보다는 검색 시스템에 가깝다. LLM 은 마지막에 말 예쁘게 정리해주는 애고, 진짜 중요한 건 좋은 근거를 찾아주는 쪽이다.

참고

This post is licensed under CC BY 4.0 by the author.

앞으로 가야하는 방향

-