콘텐츠로 이동

2026 04 01

2026-04-01

Embedding

  • 개요
    • 의미의 좌표화. 컴퓨터는 텍스트의 의미를 알지 못함
    • 임베딩을 통해 텍스트를 계산할 수 있는 숫자 배열로 변환하는 기술
    • 의미가 비슷한 문장일수록 벡터 공간에서 서로 가까운 거리에 위치하게 만듦
  • 선정 기준
    • 평가 프레임워크를 직접 만들기
      1. 골든 테스트셋 구축
        • 실제 문의 50~100건 수집
        • 각 질문에 대한 정답 문서를 직접 매핑
        • ex) 배송 언제 와요 -> 정답: 배송정책_FAQ_chunk_3
      2. 후보 모델 선정
        • text-embedding-3-small / large (OpenAI) / voyage-3 (Voyage) 등...
      3. 자동 평가 스크립트

Embedding 모델 테스트 방법

  • 1. 사전에 VectorDB에 CS 문서 청크들을 임베딩하여 저장
    VectorDB 안에 들어있는 상태:
        chunk_001: "환불은 결제일로부터 7일 이내 가능..." -> [0.12, -0.34, ...]
        chunk_002: "배송은 보통 2~3영업일 소요됩니다..." -> [0.45, 0.21, ...]
        chunk_003: "포인트는 결제 확정 후 3일 뒤 적립..." -> [0.98, -0.65, ...]
    
  • 2. 골든셋 구축: 사람이 직접 이 질문의 정답은 이 청크에 있다 지정
    골든셋:
        ("환불 어떻게 해요?",        [chunk_001])
        ("배송 며칠 걸려요?",        [chunk_002])
        ("포인트 언제 들어와요?",     [chunk_003])
        ("결제 취소하고 싶어요",      [chunk_001])   ← 같은 청크가 정답일 수 있음
    
  • 3. 평가 과정
    질문: "환불 어떻게 해요?"
    정답: [chunk_001]
    
    1) query_vec = model.embed("환불 어떻게 해요?")
       → [0.13, -0.31, ...]  (숫자 벡터)
    
    2) retrieved = vector_search(query_vec, top_k=5)
       → [chunk_001, chunk_004, chunk_012, chunk_007, chunk_003]
         (VectorDB가 코사인 유사도 순으로 반환한 청크 ID 목록)
    
    3) 판정: chunk_001이 retrieved 안에 있는가?
       → 있다! (1번째 위치) → Hit ✓
    
  • 4. 지표 계산

    • Recall@5: Top-5 안에 답변이 있으면 1, 아니면 0 => 전체 평균
      질문1: chunk_001이 Top-5에 있음 → 1
      질문2: chunk_002가 Top-5에 없음 → 0
      질문3: chunk_003이 Top-5에 있음 → 1
      질문4: chunk_001이 Top-5에 있음 → 1
      
      Recall@5 = (1+0+1+1) / 4 = 0.75
      
    • MRR: 정답이 몇번째 있었는지를 봐서 평균
      질문1: chunk_001이 1번째 → 1/1 = 1.0
      질문2: chunk_002가 없음    → 0
      질문3: chunk_003이 3번째 → 1/3 = 0.33
      질문4: chunk_001이 2번째 → 1/2 = 0.5
      
      MRR = (1.0 + 0 + 0.33 + 0.5) / 4 = 0.458
      

Chunking 전략

  • 1. Q&A 한 쌍
    • CS가 짧고 명확할 때 좋음. 200~500자 이내 방식이라면 권장
      [chunk_001]
      Q: 환불 처리는 얼마나 걸리나요?
      A: 환불 요청 후 영업일 기준 3~5일 소요됩니다. 카드사에 따라...
      
  • 2. Q만 임베딩, A는 메타데이터로 저장
    • 질문 <-> 질문 유사도만 비교하여, 긴 답변 테스트가 임베딩 벡터를 오염시키지 않게되는 장점
    • Q끼리 비교하는게 유사도가 더 좋을 수 있음. 임베딩할 때, 같은 종류의 Q를 여러개로 변형된것을 chunk_id로 저장하면 성능 좋음
      {
        "vector": embed("환불 처리 얼마나 걸리나요?"),
        "metadata": {
          "answer": "환불 요청 후 영업일 기준 3~5일...",
          "category": "결제",
          "chunk_id": "chunk_001
        }
      }
      
  • 3. 긴 문서형 답변일 때 분할
    • 답변이 길다면 분할이 필요하다.
      • 권장 기준은 보통 200~500 토큰 (한국어 기준 300~800자)
      • 오버랩은 앞뒤 50~100 토큰 정도 겹쳐 문맥 끊기는 것을 방지
  • 임베딩 모델이 VectorDB 보다 답변 품질에 훨씬 큰 영향을 미친다!! - VectorDB는 규모 커지기 전까진 대부분 비슷한 성능 보여줌
  • VectorDB에 들어가는 건 "벡터 + 메타데이터"

    • 최종적으로 VectorDB에 저장되는 것은... 1) 벡터값 / 2) 텍스트 원문 / 3) 메타데이터
    • 여러가지 소스 (FAQ, 서비스 가이드, 공지사항) 을 모두 벡터 디비에 저장하기 위해서는 청킹 전략과 메타데이터 설계를 소스별로 다르게!

      1. 기존 Q&A → Q만 임베딩 (앞서 논의한 접근법 2)

        {
            "vector": embed("환불 얼마나 걸려요?"),
            "text": "환불 얼마나 걸려요?",
            "metadata": {
                "source": "faq",
                "answer": "영업일 기준 3~5일 소요됩니다...",
                "category": "결제",
                "chunk_id": "faq_001",
                "updated_at": "2026-03-15"
            }
        }
        

      2. 서비스 가이드 문서 → 섹션 단위로 분할

        # 예: "프리미엄 플랜 이용 가이드" 10페이지짜리 문서
        {
            "vector": embed("프리미엄 플랜에서는 최대 5명까지 팀원을 초대할 수 있습니다..."),
            "text": "프리미엄 플랜에서는 최대 5명까지...",
            "metadata": {
                "source": "guide",
                "doc_title": "프리미엄 플랜 이용 가이드",
                "section": "팀원 초대",
                "chunk_id": "guide_premium_003",
                "updated_at": "2026-02-01"
            }
        }
        

      3. 공지사항 → 공지 하나 = 한 청크 (짧으면), 길면 분할

        {
            "vector": embed("2026년 4월 1일부터 무료 플랜의 저장 용량이 5GB에서 3GB로 변경됩니다..."),
            "text": "2026년 4월 1일부터 무료 플랜의...",
            "metadata": {
                "source": "notice",
                "title": "무료 플랜 저장 용량 변경 안내",
                "chunk_id": "notice_20260401",
                "published_at": "2026-03-25",
                "expires_at": "2026-06-01"  # 시한성 정보
            }
        }
        

    • Query는 이런식으로!
      results = vector_db.search(
          vector=embed(user_question),
          top_k=5,
          filter={
              "source": {"$in": ["faq", "notice"]},  # FAQ와 공지에서만
              "updated_at": {"$gte": "2025-01-01"}     # 최근 1년 이내 문서만
          }
      )
      

VectorDB

  • 개요
    • 사용자의 질문이 들어왔을 때 질문 벡터와 가장 가까운 문서 벡터들을 빠르게 찾아내는 DB
    • 이 벡터와 가장 가까운 벡터 Top-K를 찾아라!
  • 선정 기준
    • 소규모 (1만건 이하): Chroma/Qdrant(로컬)
    • 중규모 (1만~100만건): Qdrant(서버), Weaviate, Milvus
    • 관리형 서비스: Pinecone

RAG 흐름

  • 학습)
    • CS Q&A 문서를 청크로 분할
    • 각 청크를 임베딩하여 VectorDB에 저장
  • 답변)
    • 질의가 들어오면 고객 질문을 Embedding
    • VectorDB에서 유사한 청크 Top-K 검색
    • 검색된 청크 + 원래 질문 LLM에 전달하여 답변 생성

측정 방법

  • 한국어 CS 데이터에 맞는 것을 찾아야 함
  • 임베딩 모델의 성능이 너무 중요함!