티스토리 뷰
메타데이터는 본문을 설명하는 부가 정보라기보다 검색 후보군을 줄이는 첫 번째 인덱스입니다.

RAG 검색이 생각보다 엉뚱한 결과를 내는 가장 흔한 이유는 “비슷한 내용”과 “찾고 싶은 범위”를 분리하지 않았기 때문입니다. 분기, 문서 종류, 출처 같은 조건은 임베딩만으로 깔끔하게 처리되지 않습니다.
이번 예제는 작은 문서 세 개를 FAISS에 넣고, filter 파라미터로 category와 quarter를 바꾸면서 검색 결과가 어떻게 달라지는지 확인합니다.
메타데이터 스키마 설계

검색 스키마는 필드 수를 늘리는 일이 아니라 실제로 후보군을 줄일 키를 좁혀 가는 일에 가깝습니다.
필터가 후보군을 줄이는 흐름

의미상 비슷한 청크가 많아도 필터가 먼저 범위를 좁혀 주면 검색 결과가 훨씬 덜 흔들립니다.
실행 예제
from __future__ import annotations
import hashlib
from dataclasses import dataclass
from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document
from langchain_core.embeddings import Embeddings
class SimpleHashEmbeddings(Embeddings):
def __init__(self, size: int = 32):
self.size = size
def _embed(self, text: str) -> list[float]:
vector = [0.0] * self.size
for token in text.lower().split():
digest = hashlib.sha256(token.encode('utf-8')).digest()
for index in range(self.size):
vector[index] += digest[index] / 255.0
return vector
def embed_documents(self, texts: list[str]) -> list[list[float]]:
return [self._embed(text) for text in texts]
def embed_query(self, text: str) -> list[float]:
return self._embed(text)
@dataclass
class ChunkSpec:
title: str
text: str
category: str
quarter: str
source: str
def to_document(self) -> Document:
metadata = {
'title': self.title,
'category': self.category,
'quarter': self.quarter,
'source': self.source,
}
return Document(page_content=self.text, metadata=metadata)
def build_vectorstore() -> FAISS:
docs = [
ChunkSpec(
title='Q4 marketing budget',
text='The 2024 Q4 marketing budget focuses on campaign spend and partner events.',
category='marketing',
quarter='2024Q4',
source='q4-report.pdf',
).to_document(),
ChunkSpec(
title='Q4 infrastructure cost',
text='The 2024 Q4 infrastructure budget focuses on storage migration and backup cost.',
category='engineering',
quarter='2024Q4',
source='q4-report.pdf',
).to_document(),
ChunkSpec(
title='Q3 marketing review',
text='The 2024 Q3 marketing review summarizes webinar leads and conversion rate.',
category='marketing',
quarter='2024Q3',
source='q3-review.md',
).to_document(),
]
return FAISS.from_documents(docs, SimpleHashEmbeddings())
def main() -> None:
vectorstore = build_vectorstore()
query = 'marketing budget'
print('[filter=category:marketing]')
for doc in vectorstore.similarity_search(query, k=3, filter={'category': 'marketing'}):
print(doc.metadata['title'], doc.metadata['quarter'], '-', doc.page_content)
print('
[filter=quarter:2024Q4]')
for doc in vectorstore.similarity_search(query, k=3, filter={'quarter': '2024Q4'}):
print(doc.metadata['title'], doc.metadata['category'], '-', doc.page_content)
if __name__ == '__main__':
main()
실행 방법
python main.py
검증된 실행 결과
[filter=category:marketing]
Q3 marketing review 2024Q3 - ...
Q4 marketing budget 2024Q4 - ...
[filter=quarter:2024Q4]
Q4 marketing budget marketing - ...
Q4 infrastructure cost engineering - ...
이 코드에서 봐야 할 것
하이브리드 검색 결합 순서

유사도와 필터는 경쟁 관계가 아니라 순서를 가진 협력 관계로 봐야 결과 해석이 쉬워집니다.
ChunkSpec이 본문과 메타데이터를 함께 정의하므로 검색 스키마를 코드에서 한눈에 볼 수 있습니다.SimpleHashEmbeddings를 써서 네트워크 없이도filter동작 자체를 재현할 수 있습니다.- 같은 질의라도 필터 조건을 바꾸면 결과 집합이 달라진다는 점이 핵심입니다.
실무에서 헷갈리는 지점
출처 추적과 감사 경로

운영에서 잘못된 답을 추적할 때는 본문보다 source와 분기 정보가 먼저 단서를 주는 경우가 많습니다.
- 메타데이터는 많이 붙일수록 좋은 것이 아닙니다. 실제 필터에 쓰는 필드만 남겨야 유지비가 낮습니다.
- 벡터 검색 결과가 부정확해 보여도 필터 문제일 수 있습니다. 먼저 후보군이 올바르게 제한됐는지 봐야 합니다.
- FAISS 자체는 관계형 DB가 아니므로 복잡한 다중 조건은 애플리케이션 레이어 설계가 함께 필요합니다.
체크리스트
- chunk 메타데이터에 최소한 category, quarter, source를 넣었다.
- 같은 질의에 대해 서로 다른 filter 결과를 비교했다.
- 필터 필드 이름이 문서 생성 코드와 검색 코드에서 일관된다.
- 운영에서 필요한 필드만 남기도록 스키마를 정리했다.
시리즈 목차
- PDF 파싱과 텍스트 추출
- 청킹 전략 — 문서 유형별 최적화
- 메타데이터 설계와 필터링 (현재 글)
- 증분 인덱싱 — 변경된 문서만 업데이트 (예정)
- 다중 포맷 문서 파이프라인 (예정)
- 문서 수집 파이프라인 완성 (예정)
참고 자료
'AI·LLM' 카테고리의 다른 글
| 다중 포맷 문서 파이프라인 (0) | 2026.05.05 |
|---|---|
| 증분 인덱싱 — 변경된 문서만 업데이트 (0) | 2026.05.05 |
| 청킹 전략 — 문서 유형별 최적화 (0) | 2026.05.05 |
| PDF 파싱과 텍스트 추출 (0) | 2026.05.05 |
| 벡터 검색 파이프라인 — 문서 수집부터 쿼리까지 (0) | 2026.05.04 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- DevOps
- ALTAIR
- Python
- openAI
- app service
- Ai
- CloudArchitecture
- appserviceplan
- vector search
- Prompt engineering
- cloudcomputing
- pandas
- embeddings
- Kubernetes
- Document Processing
- aks
- rag
- AZURE
- Tutorial
- scaling
- streaming
- langchain
- 데이터시각화
- faiss
- Cloud
- serverless
- 공공데이터
- Azure Functions
- LLM
- AppService
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
글 보관함

