AI Sparkup

최신 AI 쉽게 깊게 따라잡기⚡

RAG 튜토리얼 – Python 임베딩과 메타데이터 필터로 문맥 검색 만들기

rag 검색에서 의미적으로 비슷한 문서를 찾는 것만으로는 충분하지 않다. “로그인 실패”와 “OAuth 토큰 갱신 오류”를 연결하는 것은 임베딩(embedding)의 역할이지만, 해당 티켓이 현재 열려 있는지, 어느 팀 소유인지, 최신 기간에 해당하는지는 메타데이터가 판단해야 한다. 이 글은 로컬 Python만으로 두 신호를 결합하는 최소 구현을 구성한다.

준비

pip install sentence-transformers numpy

all-MiniLM-L6-v2는 문장을 384차원 벡터로 변환하며 CPU에서도 실행할 수 있다. 문서 임베딩을 정규화하면 코사인 유사도(cosine similarity)는 내적 한 번으로 계산된다.

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")
embeddings = model.encode(
    [ticket["text"] for ticket in tickets],
    normalize_embeddings=True,
)

검색은 필터 뒤에 점수를 계산한다

메타데이터 조건에 맞지 않는 문서를 먼저 제외하면 결과 정확도와 계산 비용을 동시에 개선할 수 있다.

import numpy as np

def search(query, team=None, status=None, top_k=5):
    q_vec = model.encode([query], normalize_embeddings=True)[0]
    mask = np.ones(len(tickets), dtype=bool)
    for i, doc in enumerate(tickets):
        if team and doc["team"] != team:
            mask[i] = False
        if status and doc["status"] != status:
            mask[i] = False

    candidates = np.where(mask)[0]
    scores = embeddings[candidates] @ q_vec
    order = np.argsort(scores)[::-1][:top_k]
    return [(tickets[candidates[i]], float(scores[i])) for i in order]

필터로 저장해야 할 값

메타데이터유용한 질의
team, product특정 담당 영역의 장애만 찾기
status, priority열린 고우선순위 이슈만 검색
created, updated최근 정책이나 장애로 범위 제한
tenant, acl사용자가 볼 수 있는 문서만 검색

권한 필터는 검색 결과를 만든 뒤 숨기는 것이 아니라, 후보 집합을 만들 때부터 적용해야 정보 유출을 막을 수 있다.

임베딩을 재계산하지 않는다

문서 임베딩은 색인 시 한 번 계산해 저장하고, 요청 시에는 질의 벡터만 생성한다.

import json
import numpy as np

np.save("ticket_embeddings.npy", embeddings)
with open("ticket_metadata.json", "w") as f:
    json.dump(tickets, f)

프로덕션에서는 이 최소 구현을 벡터 데이터베이스, 하이브리드 검색, 리랭커, 인덱스 버전 관리로 확장한다. 문서 수가 커지는 시점의 설계는 rag-tips-scale-million-documentsrag-tips-production을 함께 참고한다.

참고 자료



AI Sparkup 구독하기

최신 게시물 요약과 더 심층적인 정보를 이메일로 받아 보세요! (무료)