벡터 DB와 캐시 스토리지 SaaS로 RAG 기반 하이브리드 서치 챗봇 만들기 3

황상원
SaaS Evangelist

들어가며

지난 글에서는 Redis를 이용해 기존에 문의했던 질문들을 재차 AI에 요청하여 답변받는 프로세스 소요 시간을 줄이기 위해 캐시 기법을 적용해 보았습니다. 유사한 질문에 대한 답변 속도는 굉장히 빨라진 반면 질문의 유사성을 제대로 파악하지 못해 잘못된 답변을 주는 문제가 발생했습니다.

캐싱 기술 사용 후 받은 부정확한 답변
캐싱 기술 사용 후 받은 부정확한 답변

이를 해결하기 위해 이번 글에서는 유사도 계산 자체를 수정하여 질문들의 유사성을 좀 더 사람과 가깝게 개선해 보려고 합니다.

Dense 임베딩을 사용했을 때

Part 1에서 데이터셋을 저장할 때 자연어를 임베딩하였는데, OpenAI의 text-embedding-ada-002 모델로 임베딩할때는 dense 임베딩이라는 텍스트 데이터를 벡터로 표현하는 방식을 사용하게 됩니다.

앞에 사용했던 예제 문장인 “title hamilton men's h77705145 khaki navy 42mm automatic watch product type wristwatch. seller watchgooroo price $ 519.99. sold 10.0 many times.”라는 문장을 dense 임베딩 했을때 벡터값은 다음과 같습니다.

dense 임베딩 후 벡터값
dense 임베딩 후 벡터값

Sparse 임베딩을 사용했을 때

Dense 임베딩 방식 외에도 단어나 긴 문장을 임베딩하는 방식으로 Sparse 임베딩을 사용할 수도 있습니다. 일반적으로 단어나 문장의 출현 빈도를 기반으로 생성되며, 벡터의 각 차원이 해당 단어의 출현 여부와 빈도를 나타냅니다. 따라서 Dense 임베딩 방식과 달리 대부분의 차원값이 0이며, 같은 단어가 출현할 때마다 그 출현 횟수를 카운트해서 문장의 의미 해석에 중점을 두게 됩니다.

Sparse 임베딩 후 벡터값
Sparse 임베딩 후 벡터값

Sparse 임베딩 시 특정 단어들의 출현 횟수에 집중하는 만큼 질문이나 검색했을 때 키워드를 잡고 관련 답변을 찾기가 수월해질 수 있습니다.

Dense 임베딩과 Sparse 임베딩을 결합한 하이브리드 서치 모델 구현하기

앞선 Part 2에서는 질문의 문맥은 잘 이해하지만, 특정 브랜드와 같은 문장 내 중요한 단어가 다름에도 기존과 유사한 문장으로 인식했습니다. 자연어 처리에서 단어의 의미와 맥락을 고려해 좀 더 정확한 파악을 위해서 하이브리드 서치 방법을 사용하는데, 이는 Sparse 임베딩과 Dense 임베딩을 결합하여 단어의 의미와 맥락을 모두 고려하는 방법입니다.

Dense 임베딩과 Sparse 임베딩을 결합한 하이브리드 서치 모델을 구현하기 위해선 먼저 기존의 문장을 Dense 임베딩뿐만 아니라 동일한 문장을 추가로 Sparse 임베딩하여 Pinecone DB에 저장해야 합니다.

Dense 임베딩과 Sparse 임베딩을 결합한 하이브리드 서치 모델 구현
Dense 임베딩과 Sparse 임베딩을 결합한 하이브리드 서치 모델 구현

코드에서는 sparse_embedding 함수를 만들어 Sparese 임베딩 처리를 한 후 문장 안에 해당 단어가 포함된 횟수 혹은 빈번도를 저장해 두었습니다.

from transformers import BertTokenizerFast
from collections import Counter

tokenizer = BertTokenizerFast.from_pretrained('bert-base-uncased')

def sparse_embedding(text):
    inputs = tokenizer(
    text,
    padding=True,
    truncation=True,
    max_length=512,
    return_tensors='pt'  # Return PyTorch tensors
    )

    input_ids = inputs['input_ids']
    # Convert PyTorch tensor to a list
    input_ids_list = input_ids.squeeze().tolist()

    # Create a dictionary using Counter
    sparse_vec_dict = dict(Counter(input_ids_list))
    
    return sparse_vec_dict

그리고 임베딩 된 벡터를 pinecone에 upsert 할 때 dense 임베딩과 더불어 sparse embedding을 metadata로 같이 적재시켜 줍니다:

 meta_data = [
  {
      "index": str(row['Index']),
      "itemNumber": str(row['itemNumber']),
      "price": str(row['priceWithCurrency']),
      "seller": str(row['seller']),
      "title": str(row["title"]),
      "type": str(row["type"]),
      "sold": str(row["sold"]),
      "sparse_vector": str(sparse_embedding(str(row["filtered_text"])))
  }
  for _, row in batch.iterrows()

Upsert가 완료되어 Pinecone에서 데이터를 확인해 보면, Dense 임베딩된 값과 Sparse 임베딩된 값이 같이 적재된 것을 볼 수 있습니다.

Pinecone에서 확인한 데이터
Pinecone에서 확인한 데이터

유사도 판단하기

그렇다면, 이제 두 개의 임베딩 모델을 조합하여 새로운 방식으로 유사도를 판단해야 하겠죠. 하이브리드로 유사도를 계산하는 함수는 코드로 다음과 같이 작성하였는데요. alpha 값은 0과 1사이의 소수로 1에 근사할수록 Dense 임베딩에, 0에 근사할수록 Sparse 임베딩에 좀 더 무게를 둘 수 있는 기준치라고 볼 수 있습니다.

def hybrid_scale(dense, sparse, alpha: float):
    indices = list(map(int, sparse.keys()))
    values = [v * (1 - alpha) for v in sparse.values()]
    hsparse = {'indices': indices, 'values': values}
    hdense = [v * alpha for v in dense]
    return hdense, hsparse

Dense 임베딩과 Sparse 임베딩을 조합해 하이브리드로 유사도 계산
Dense 임베딩과 Sparse 임베딩을 조합해 하이브리드로 유사도 계산

기존의 dense 임베딩만을 기준으로 유사한 데이터를 찾은 것과 달리, 질문이 들어와 Pinecone DB에서 근사 데이터를 찾을 때 dense와 sparse를 적절히 비율을 섞어서 근사한 데이터를 찾을 수 있는 프로세스를 함수로 만들어 보겠습니다.

def hybrid_query(question):
  sparse_vec = sparse_embedding(question)

  dense_vec = get_embedding(question)

  dense_vec, sparse_vec = hybrid_scale(dense_vec, sparse_vec, 0.7)

  result = index.query(
      vector=dense_vec,
      sparse_vector=sparse_vec,
      top_k=3,
      include_metadata=True,
  )

  metadata_values = [match['metadata'] for match in result['matches']]
  return metadata_values

챗봇과 대화하기

이제 다른 시계를 추천해달라고 했을 때 이전과 유사한 문장으로 인식하여 캐시 답변을 주는지, AI가 새로 DB 정보를 기반으로 답변을 주는지 확인해 볼까요? 

챗봇과 대화하는 화면
IWC 시계 추천 요청했을 때, 약 9분의 리스폰스 시간이 소요되나 원하는 답변을 알려줌
챗봇과 대화하는 화면
세이코 시계 추천 요청했을 때 마찬가지로, 캐시 답변을 주지 않고 새로운 답변을 만들어서 알려줌

이렇게 하이브리드 서치 방식을 사용해 본 결과, 정확한 답변을 줘야 하는 상황에서는 유사한 답변을 캐싱해 오는 것이 아닌 시간이 소요되더라도 새로운 답변을 생성해 주는 것을 확인할 수 있었습니다.

마치며

비즈니스 용도로 LLM만 단독으로 사용했을 때는 실제 서비스를 제공하기까지 어려움이 있습니다. 특히 벡터 데이터베이스와 캐시 스토리지를 구축하고 또 본 블로그 글에서는 아직 다루지 않았지만, 대화 내용들을 비정형 데이터로 실시간으로 전송하고 저장하기까지 셀프로 구축하기에 시간과 비용이 많이 소모되는 부분들을 영역마다 제공되는 SaaS를 활용해 쉽게 구현해 보았습니다.

확실히 개발 및 운영 부담을 크게 줄일 수 있다는 것을 체감하면서 이를 통해 보다 효율적이고 사용자 친화적인 챗봇 서비스를 쉽게 제공할 수 있게 될 것 같습니다. 이 글을 읽으신 분들께서도 제 체험기가 실무에 조금이나마 도움이 되셨길 바라면서 다음번에는 챗봇 대화 내용을 SaaS 제품을 활용해 손쉽게 저장하고 활용할 수 있는 방법을 다뤄보려고 합니다.

관련하여 궁금한 사항이나 커피챗을 원하시면 링크드인 DM 보내주세요 : )

저희가 하는 일을 지켜보세요.

고객에게 어떤 가치를 제공하려고 노력하는지 소식을 받을 수 있습니다.

구독해주셔서 감사합니다.
올바른 이메일을 입력해주세요.
"구독하기" 버튼을 눌러 이메일을 제출하시면 마케팅 활용을 위한 광고성 정보 수신 동의한 것으로 간주하며 이는 선택사항으로 미 동의시에도 TeamDCX 서비스 이용에는 지장이 없습니다.
[필수] 개인정보 수집·이용 동의 안내
메가존클라우드("회사")는 해당 내용에 따라 귀하의 정보를 수집 및 활용합니다. 다음의 내용을 숙지하시고 동의하는 경우 체크 박스에 표시해 주세요.
1. 개인정보 수집자 : 메가존클라우드㈜
2. 수집 받는 개인 정보
[필수]  이름, 이메일, 전화번호, 회사명, 회사직원 수, 부서, 직함, 업종, 문의 제품/서비스, 문의내용
3. 수집/이용 목적
- 고객 상담업무 처리
4. 보유 및 이용 기간 : 개인정보 수집일로부터 3년(단, 고객 동의 철회 시 지체없이 파기)
※ 개인정보 이용 철회 방법
- 안내 문자 등의 동의 철회를 이용하는 방법 : 이메일 수신 거부 링크 클릭 또는 안내 문자 내 수신거부 연락처를 통한 수신 거부 의사 통보
- 개인정보 처리 상담 부서
- 부서명: Offering GTM Team
- 연락처:offering_gtm@mz.co.kr
※ 동의거부권 및 불이익
귀하는 동의를 거절할 수 있는 권리를 보유하며, 동의를 거절하는 경우 상기 이용 목적에 명시된 서비스가 제공되지 아니합니다.

[선택] 개인정보 수집·이용 동의 안내 (마케팅 활용 및 광고성 수신 정보 동의)
메가존클라우드("회사")는 해당 내용에 따라 귀하의 정보를 수집 및 활용합니다. 다음의 내용을 숙지하시고 동의하는 경우 체크 박스에 표시해 주세요.
1. 개인정보 수집자 : 메가존클라우드㈜
2. 수집 받는 개인 정보
[필수]  이름, 이메일, 전화번호, 회사명, 회사직원 수, 부서, 직함, 업종, 문의 제품/서비스, 문의 사항
3. 수집/이용 목적
- 마케팅 활용을 위한 뉴스레터 발송
※ 본 동의문에는 이메일을 활용한 광고성 수신 동의 내용이 포함되어 있습니다.
4. 보유 및 이용 기간 : 동의 철회 시 까지
※ 개인정보 이용 철회 방법
- 안내 문자 등의 동의 철회를 이용하는 방법 : 이메일 수신 거부 링크 클릭 통한 수신 거부 의사 통보
- 개인정보 처리 상담 부서
- 부서명: Offering GTM
- 연락처: offering_gtm@mz.co.kr
※ 동의거부권 및 불이익
귀하는 동의를 거절할 수 있는 권리를 보유하며, 동의를 거절하는 경우 상기 이용 목적에 명시된 서비스가 제공되지 아니합니다.