본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 18. 19:34

파트 3 — 도메인 특화 용어 시나리오에서의 벡터 검색: 모델 선택부터 이중 검증까지

요약

도메인 특화 용어 사용 시 범용 임베딩 모델에서 발생하는 의미론적 드리프트 문제를 다룹니다. ESG와 같은 전문 분야에서 벡터 검색의 재현율과 오탐률 문제를 분석하고, 단순 임계값 조절이 아닌 모델 선택의 중요성을 강조합니다.

핵심 포인트

  • 범용 임베딩 모델은 전문 용어 처리 시 의미론적 드리프트 발생
  • 단순 유사도 임계값 조정은 재현율과 오탐률을 동시에 높이는 한계 존재
  • ESG, 법률, 의료 등 밀집된 전문 용어 도메인에서는 특화된 모델 필요
  • 벡터 유사도 점수와 비즈니스 의미론적 관련성은 일치하지 않을 수 있음

이 기사는 풀스택 아키텍처(full-stack architecture)의 세 번째 계층인 하이브리드 검색 계층(Hybrid Retrieval Layer)을 다룹니다. 핵심 엔지니어링 과제는 다음과 같습니다: 범용 임베딩 모델(general-purpose embedding models)은 도메인 특화 용어에서 드리프트(drift) 현상이 발생하며, 단일 경로 벡터 검색(single-path vector retrieval)은 미세한 의미론적 차이를 구분할 수 없다는 점입니다.

📦 소스 코드: production-rag-engineeringesg/services/embedding_service.py, esg/services/search_service.py

0. 페인 포인트 (The Pain Point)

파트 1에서는 지식 베이스(knowledge base)를 구축했습니다. 파트 2에서는 청킹(chunking)을 처리했습니다. 시스템의 첫 번째 버전은 검색을 위해 text-embedding-ada-002를 사용했습니다. 이는 당시 OpenAI의 가장 주류인 임베딩 모델이었습니다.

결과는 다음과 같았습니다:

  • 재현율 (Recall rate): 82% — 관련 콘텐츠의 18%를 단순히 찾지 못함
  • 오탐률 (False positive rate): 12% — "Scope 1 배출 집약도"를 쿼리했을 때 "Scope 3 배출"이 반환됨
  • "저탄소(Low-carbon)"와 "무탄소(zero-carbon)"가 벡터 공간(vector space)에서 서로 가깝게 위치함 — 시스템이 이 둘을 구분하지 못함

첫 번째 본능적인 해결책은 유사도 임계값(similarity threshold)을 조정하는 것이었습니다. 0.85에서 0.75로 낮출까요? 아니면 0.65로 낮출까요?

전체 테스트를 거친 결과, 재현율은 올라갔지만 오탐률도 똑같이 상승했습니다. 임계값이 낮아지면 = 그물을 더 넓게 던지는 것이고 = 더 많은 무관한 콘텐츠를 끌어들이게 됩니다.

이것은 임계값의 문제가 아니었습니다. 모델의 문제였습니다.

더 정확하게 말하면: 특화된 도메인 텍스트를 처리하는 범용 모델로 인해 발생하는 의미론적 드리프트(semantic drift) 문제였습니다. ada-002의 학습 코퍼스(training corpus)는 주로 일반적인 텍스트로 구성되어 있습니다. ESG 도메인 용어는 해당 모델의 벡터 공간에서 제대로 인코딩되지 않습니다. 즉, 관련 용어들은 서로 멀리 떨어지게 되고, 관련 없는 용어들은 서로 가깝게 위치하게 됩니다.

이 문제는 ESG에만 국한되지 않습니다. 법률 조항, 의료 진단, 금융 컴플라이언스 등 밀집된 전문 용어를 사용하는 모든 도메인은 범용 임베딩 모델을 사용할 때 동일한 의미론적 드리프트 문제에 직면하게 됩니다.

1. 검색이 해결해야 할 과제

도메인 특화 시나리오에서의 벡터 검색에는 세 가지 핵심적인 긴장 관계가 있습니다:

긴장 관계 1: 범용 모델은 전문 용어에서 의미가 왜곡됨 (drift)

"탄소 발자국 (Carbon footprint)"과 "탄소 회계 (carbon accounting)"는 일반적인 텍스트에서는 유사한 의미를 갖지만, ESG 준수 (ESG compliance) 측면에서는 서로 다른 것을 의미합니다. 전자는 제품 수명 주기 배출량을 의미하며, 후자는 데이터 측정 방법론을 의미합니다. 이 둘은 서로 대체될 수 없습니다. 범용 모델은 이러한 미세한 차이를 구분하지 못합니다.

긴장 관계 2: 높은 유사도 점수 ≠ 의미론적 관련성 (semantic relevance)

벡터 유사도 (Vector similarity)는 "벡터 공간에서의 거리"를 측정하는 것이지, "비즈니스 의미론적 관련성"을 측정하는 것이 아닙니다. "에너지 소비 (Energy consumption)"와 "유출 사고 (spill incidents)"는 일반적인 벡터 공간에서는 서로 가까울 수 있지만 (둘 다 환경 관련 주제이므로), 실제로는 완전히 다른 규정 조항에 해당합니다.

긴장 관계 3: 단일 경로 벡터 검색은 동일 개념의 미세한 변형을 구분하지 못함

GRI에는 세 가지 배출 범위(emission scopes)가 있습니다: Scope 1, Scope 2, 그리고 Scope 3입니다. 벡터 공간에서 이 세 가지는 모두 서로 가깝게 위치합니다. 단일 경로 검색 (Single-path retrieval)을 사용하면 Scope 1을 쿼리할 때 Scope 3 콘텐츠가 쉽게 반환될 수 있습니다.

해결책은 단일 처방이 아닙니다. 세 가지 단계적 계층을 거쳐야 합니다: 모델 선택 (model selection) → 의미론적 왜곡 완화 (semantic drift mitigation) → 이중 검증 (dual validation).

2. 임베딩 모델 선택: "가장 비싼 것을 고르는 것"이 아니다

테스트 방법론:

우리는 환경(Environmental), 사회(Social), 지배구조(Governance) 카테고리를 아우르는 200개의 ESG 도메인 용어를 쿼리로 샘플링했습니다. 여기에는 "Scope 1 배출 집약도 계산 (Scope 1 emission intensity calculation)"과 같은 긴 형태의 용어와 "탄소 집약도 (carbon intensity)"와 같은 짧은 용어가 포함되었습니다. 각 쿼리를 GRI 지식 베이스와 수동으로 주석을 단 정답 (ground truth)에 대해 실행하였고, 네 가지 모델에 걸쳐 Top-3 재현율 (recall) 정확도를 비교했습니다.

4개 모델 비교:

모델재현율 (Recall Rate)항목당 비용배포 방식제외 사유
text-embedding-3-large91%$0.0001API✅ 최종 선택
...

왜 BGE-M3 (자체 호스팅)를 선택하지 않았는가?

직관적으로는 자체 호스팅 (self-hosting)이 더 저렴할 것이라고 생각할 수 있습니다. 하지만 전체 비용 계산을 수행해 보면:

차원 (Dimension)text-embedding-3-largeBGE-M3 self-hosted
월간 API / 서버 비용 (Monthly API / server cost)~$8/mo (100K 항목, 배치 할인 적용)~$50/mo (GPU 인스턴스)
...

자체 호스팅 (Self-hosting)은 매달 6배 더 많은 비용이 들고, 2주간의 적응 작업이 필요하며, 재현율 (recall)은 9% 더 낮습니다.

이것은 "비싸다 = 더 좋다"가 아닙니다. 명확한 ROI (투자 대비 수익) 계산에 기반한 모델 선택입니다.

데이터 보안은 어떻게 처리되나요?

텍스트는 업로드 전에 비식별화 (desensitized)됩니다. 정규 표현식 (regex)을 사용하여 민감한 정보(회사명, 매출 수치, 고객 데이터)를 식별하고 교체합니다. 기업 식별 정보 없이 ESG 용어와 보고서 파편들만 업로드됩니다. 또한 OpenAI의 데이터 처리 합의서 (Data Processing Agreement)에 서명하여 컴플라이언스 (compliance) 요구 사항을 충족했습니다.

3. 의미론적 드리프트 (Semantic Drift) 완화: 검색 전 모호성 해소

더 나은 모델로 전환하면서 재현율 (recall)은 82%에서 91%로 향상되었지만, 오탐률 (false positive rate)은 12%로 유지되었습니다.

근본 원인 분석 (Root cause analysis): text-embedding-3-large를 사용하더라도 미세한 ESG 용어 구분이 여전히 불충분합니다. "저탄소 (Low-carbon)"와 "제로 탄소 (zero-carbon)"의 유사도는 0.85입니다. "Scope 1 배출 집약도 (Scope 1 emission intensity)"와 "Scope 3 배출량 (Scope 3 emissions)"의 유사도는 0.78입니다. 모델은 이들을 의미론적으로 가깝게 취급하지만, 비즈니스 관점에서는 완전히 다릅니다.

해결책은 모델 위에 도메인 지식을 계층화하는 3단계 증강 (augmentation) 전략입니다:

Layer 1: 도메인 용어 사전 (500개 이상의 항목)

사전은 전문 용어, 약어 및 동의어를 매핑합니다:

ESG_TERM_DICT = {
    "Scope 1": {
        "definition": "조직이 소유하거나 통제하는 소스로부터 발생하는 직접적인 온실가스 (GHG) 배출",
...

사전 데이터는 다음 세 가지 계층에서 확보되었습니다:

  1. GRI 공식 표준 문서 → 200개 이상의 핵심 용어 추출
  2. 10개의 산업별 ESG 보고서 → 300개 이상의 빈번하게 사용되는 용어 추출
  3. ESG 도메인 전문가 → 동의어 및 미세한 모호성 해소 (disambiguation) 관계 주석 처리

Layer 2: 프롬프트 (prompt)에 임베딩된 도메인 힌트 (Domain hints)

인코딩 (encoding) 시점에, 모델에 정밀한 의미론적 문맥 (semantic context)을 제공하기 위해 사전 (dictionary) 정보가 프롬프트 (prompt)에 임베딩됩니다:

def build_embedding_prompt(text: str, term: str = None) -> str:
    base_prompt = f"Encode text: {text}"

...

Layer 3: 검색 후 재순위화 (Post-retrieval reranking)

상위 5개의 후보 (Top 5 candidates)를 검색한 후, 용어 사전 (term dictionary)을 사용하여 결과의 순위를 재조정 (rerank)합니다. 표준 유의어 (standard synonyms)를 포함하는 청크 (chunks)는 점수가 상향 조정되며, "distinct_from" 관계에 있는 용어를 포함하는 청크는 가중치가 낮아집니다:

def rerank_results(query_term: str, results: list) -> list:
    for result in results:
        # 표준 유의어 포함 → 점수 상향 (boost score)
...

두 가지 실제 사례 (incident cases):

사례 1: 저탄소 (low-carbon) vs. 탄소 중립 (zero-carbon)

  • 문제: "low-carbon"을 쿼리했을 때 유사도 0.85로 zero-carbon 콘텐츠가 반환됨
  • 근본 원인: 모델이 두 용어를 모두 "탄소 배출 감소"로 취급하여 의미론적으로 가깝게 인식함
  • 해결책: 사전 (dictionary)에서 distinct_from 관계를 명시적으로 표시하고, 프롬프트에서 "low-carbon ≠ zero-carbon"을 강조함
  • 결과: 유사도가 0.85에서 0.65로 하락하였으며, 이제 검색 시 두 용어를 정확히 구분함

사례 2: Scope 1 배출 집약도 (emission intensity) vs. Scope 3 배출량 (emissions)

  • 문제: "Scope 1 emission intensity"를 쿼리했을 때 유사도 0.78로 Scope 3 콘텐츠가 반환됨
  • 근본 원인: 모델이 Scope 1과 Scope 3를 모두 "배출 관련" 항목으로 취급하여 벡터 공간 (vector space)에서 가깝게 위치함
  • 해결책: 사전에 각 Scope에 대한 고유하고 정밀한 정의와 상호 distinct_from 관계를 부여함
  • 결과: 유사도가 0.78에서 0.55로 하락하였으며, Scope 혼동으로 인한 오탐률 (false positive rate)이 1% 미만으로 감소함

3단계 증강 (Three-layer augmentation) 결과: 오탐률 (false positive rate) 12% → 3%, 용어 매칭 정확도 (term matching accuracy) 82% → 90%.

4. 이중 검증 (Dual Validation): 한 가지 경로의 높은 점수만으로는 부족하다

의미론적 드리프트 (semantic drift) 완화 이후에도 한 가지 문제가 남아 있었습니다: 벡터 유사도는 높지만, 비즈니스 의미론 (business semantics)은 관련이 없는 경우입니다.

전형적인 사례: GRI 306 폐기물 관리 (waste management) 조항을 쿼리했을 때, 유사도 0.82로 "유출 사고 처리 (spill incident handling)"에 관한 보고서 청크가 반환되었습니다. 벡터 공간 (vector space) 상에서 두 항목은 실제로 가깝지만 (둘 다 환경 사고 관련), "폐기물 관리"와 "유출 사고"는 완전히 다른 준수 조항 (compliance clauses)입니다.

단일 경로 벡터 검색 (single-path vector retrieval)의 근본적인 한계: 벡터 유사도는 "벡터 공간 내의 텍스트 거리"에 대한 통계적 측정치일 뿐, "의미론적 관련성 (semantic relevance)"에 대한 비즈니스적 측정치가 아닙니다.

해결책은 이중 검증 (dual validation)입니다: 키워드 하드 매치 (keyword hard match) + 벡터 유사도 (vector similarity) — 두 조건이 모두 충족되어야 검색 결과(hit)로 간주합니다.

def dual_verify(query: dict, candidate_chunk: dict) -> bool:
    # 조건 1: 벡터 유사도 임계값 충족
    vector_match = candidate_chunk["similarity_score"] >= 0.7
...

3단계 오탐 (false positive) 필터 (전체 흐름):

Layer 1 — 키워드 하드 매치 (Keyword hard match) (밀리초 단위)
  GRI 305 (온실가스 배출)를 쿼리할 때,
  검색된 청크는 최소 2개 이상의 다음 항목을 포함해야 함:
...

이중 검증 결과: 정확도(accuracy) 70% → 94%, 오탐률(false positive rate) 15% → 3%.

5. 벡터 스토어 선택 및 파라미터 튜닝 (Parameter Tuning)

왜 Milvus인가?

세 가지 옵션을 비교했습니다:

옵션성능다중 조건 필터링 (Multi-condition filtering)생태계 (Ecosystem)제외 사유
Milvus50ms 내에 백만 단위 벡터 처리✅ 단일 쿼리로 처리 가능성숙한 Python SDK✅ 최종 선택
...

Milvus의 핵심 장점: 단일 쿼리 내에서의 다중 조건 필터링 (multi-condition filtering):

search_params = {
    "metric_type": "COSINE",
    "params": {"nprobe": 20}
...

세 가지 검색 파라미터 (retrieval parameters):

파라미터설계 근거
top_k3LLM 판단을 위해 3개의 후보를 검색 — 더 많으면 노이즈가 발생하고, 더 적으면 콘텐츠를 놓칠 위험이 있음
...

실제 사례: 동시성 (concurrency)이 10을 초과하자 지연 시간 (latency)이 50ms에서 200ms로 급증함

출시 초기, 동시 쿼리(concurrent queries)가 10개를 초과하자 지연 시간 (latency)이 50ms에서 200ms로 급증하며 간헐적인 타임아웃 (timeout)이 발생했습니다.

진단:

  1. Milvus 서버 리소스 확인 — CPU 및 메모리가 포화 상태가 아니었습니다. 리소스 병목 (bottleneck) 문제는 아니었습니다.
  2. 인덱스 파라미터 (index parameters) 확인 — nprobe=10 설정이 검색 범위를 너무 좁게 제한하여, 동시성 상황에서 큐 백로그 (queue backlog)가 쌓였습니다.
  3. 캐싱 (caching) 확인 — 고빈도 쿼리(예: "GRI 305-1 carbon emissions")가 매번 전체 검색을 다시 실행하고 있었습니다.

2단계 해결책:

# 해결책 1: 동시성 상황에서 더 나은 안정성을 위해 nprobe 증가
search_params = {"params": {"nprobe": 20}}  # 10에서 20으로 증가

...

결과: 지연 시간이 200ms에서 80ms로 감소하였고, 캐시 히트율 (cache hit rate)은 70%를 기록하며 10개 이상의 동시 쿼리를 안정적으로 지원하게 되었습니다.

6. 비용 제어 (Cost Control)

모델 선택이 완료된 후, 비용 제어는 두 가지 메커니즘 (mechanisms)에 의존했습니다.

메커니즘 1: 대량 할인을 위한 배치 처리 (Batch processing)

OpenAI Embedding API는 배치 제출 (batch submission)을 지원합니다. 배치당 100개의 항목을 처리하면 항목당 비용을 20% 절감할 수 있습니다:

def batch_embed(texts: list[str], batch_size: int = 100) -> list:
    all_embeddings = []
    for i in range(0, len(texts), batch_size):
...

메커니즘 2: 고빈도 용어를 위한 임베딩 (embeddings) 캐싱

GRI 조항 라이브러리는 비교적 정적입니다. 300개 이상의 조항에 대한 벡터 (vectors)를 매 요청마다 다시 생성할 필요가 없습니다. 시작 시점에 이를 미리 계산하여 캐싱하면 API 호출의 30%를 절약할 수 있습니다:

# 시작 시 GRI 조항 벡터를 사전 로드 (Preload)
def preload_gri_embeddings():
    clauses = get_all_gri_clauses()  # 약 300개 조항
...

최종 비용 비교:

옵션월간 비용재현율 (Recall rate)미검출률 (Miss rate)
ada-002 (기존)~$6/mo85%12%
...
3-large 최적화 시 ada-002보다 월 $2만 더 비싸지만, 재현율은 6% 더 높고 미검출률은 7% 더 낮습니다.

7. 마무리: 검색 결정 트리 (The Retrieval Decision Tree)

새로운 검색 시나리오에 직면했을 때, 두 가지 질문이 접근 방식을 결정합니다:

Q1: 데이터에 도메인 특화 용어 (domain-specific terminology)가 포함되어 있는가?
├─ 예 (법률 / 의료 / 금융 / ESG 또는 기타 전문 분야)
│ → 범용 모델 (General-purpose models)은 성능이 저하될 것입니다.
...

이 검색 접근 방식의 전이 가능성 (Transferability):

  • 도메인 용어 사전 (Domain term dictionary) → 법률 / 의료 / 금융 용어로 교체 가능; 로직은 동일함
  • 프롬프트 도메인 힌트 (Prompt domain hints) → 모든 전문 분야에 적용 가능; 사전 내용만 교체하면 됨
  • 이중 검증 (Dual validation) → 높은 정밀도 재현율 (high-precision recall)이 필요한 모든 시나리오에 적용 가능; 비즈니스 도메인에 맞는 키워드 라이브러리로 교체

소스 코드 (Source Code)

이 문서에서 참조된 모든 구현체는 여기서 확인할 수 있습니다:

👉 github.com/muzinan123/production-rag-engineering

이 파트와 관련된 파일:

  • esg/services/embedding_service.py — 멀티 프로바이더 임베딩 (multi-provider embedding) + 배치 쓰기 (batch write) + 4계층 메타데이터 (4-layer metadata)
  • esg/services/search_service.py — Milvus 벡터 검색 (vector retrieval), top_k + 임계값 (threshold) 이중 파라미터 필터링

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0