본문으로 건너뛰기

Ontology Hybrid RAG — M3 Step 4 (M3 종결)

본 가이드는 Epic #1232 M3 Step 4 (M3 종결) 에서 도입된 Hybrid RAG 채널을 다룹니다. 청크 벡터 검색 + 그래프 traversal + 온톨로지 Class 컨텍스트를 단일 프롬프트 블록으로 융합.

아키텍처

질의어

├─ Step 1 (Embedding) ───────── EmbeddingProvider
├─ Step 2 (Cache check) ─────── DocumentProvider semantic cache
├─ Step 3 (Schema) ──────────── SchemaProvider (FK 그래프)
├─ Step 4 (Doc chunks) ──────── DocumentProvider vector search
├─ Step 5 (Security gate) ──── ContextGateway

├─ Step 5.4 (Ontology, M3 #1236) — NEW
│ ├─ identify_relevant_classes (DB substring matching)
│ ├─ ABAC filter (evaluate_class_access, M3 Step 1)
│ ├─ fetch_class_neighbors (M3 Step 3 GRAPH traversal)
│ └─ format_ontology_block → ontology_text

└─ Step 5.5 (Glossary) ──────── SchemaProvider terms

최종 RAGContext: {schema_text, doc_text, glossary_text, ontology_text, ...}

API contract

RAGContext (services/rag_context.py) 에 신규 필드 2종:

@dataclass
class RAGContext:
# 기존
schema_text: str = ""
doc_text: str = ""
glossary_text: str = ""
# M3 Step 4 신규
ontology_text: str = ""
ontology_classes_found: int = 0

assemble_rag_context 시그니처에 keyword-only 인자 2개 추가:

async def assemble_rag_context(
question, user,
schema_provider=None, doc_provider=None,
embedding_provider=None, gateway=None,
*,
ontology_db: AsyncSession | None = None,
instance_repo: OntologyInstanceRepo | None = None,
) -> RAGContext:

미주입 (None) 시 ontology 채널은 silent skip — backwards compat 보장.

services/ontology/hybrid_rag.py

3 헬퍼:

함수책무
identify_relevant_classes(db, question, limit=5)name / label_ko / label_en / description substring 매칭. 빈 question → 빈 리스트.
fetch_class_neighbors(repo, class_row, sample_keys_per_class=3, depth=1, max_neighbors=10)Class 의 sample instance 의 1-hop neighbors 합치기 (M3 Step 3 GRAPH traversal).
format_ontology_block(classes, neighbors_by_class)LLM-friendly prompt block 직렬화.

ABAC 통합 (M3 Step 1 연동)

rag_context.py::assemble_rag_context 의 ontology 채널은 후보 Class 마다 evaluate_class_access(class, caller, action="read") 호출 — deny 된 Class 는 ontology_text 에 노출되지 않음. caller 별 결과 차이 보장 (fail-closed).

회귀 가드: 같은 질의 + admin vs viewer caller → ontology 채널 결과 다름 검증.

LLM-free identification (M3 Step 4 의도적 단순화)

M3 Step 4 의 Class 식별은 단순 substring 매칭. LLM 기반 식별 (#1099 mapper 패턴 재사용 + prompt caching) 은 후속 PR. "hybrid" 의 핵심은 채널 결합이며, identifier 정교화는 별도 사이클.

향후 확장:

  • LLM 기반 Class 식별 (semantic similarity)
  • GRAPH traversal depth 동적 조정 (질의어 복잡도 기반)
  • 인스턴스 row-level filter (condition_expr 평가 → instance 단위 mask)

회귀 가드

파일범위
apps/api/tests/test_ontology_hybrid_rag.pyidentify (empty/name/label/description/limit) + fetch_class_neighbors (combine/list-fail/traverse-fail/max-cap) + format (empty/full/label fallback) + rag_context 통합 (no-embedding skip, with-embedding 동작) — 13
apps/api/tests/test_ontology_abac.pyM3 Step 1 회귀 유지 (ABAC engine 이 hybrid_rag 채널에서도 동작)

M3 종결

본 PR 머지 + M3 Step 1~4 모두 머지 → Epic #1232 종결. 가스공사 납품 기준선:

  1. Class 단위 ABAC (Step 1) ✅
  2. 메트릭 + Grafana (Step 2) ✅
  3. 인덱스 + GRAPH + k6 (Step 3) ✅
  4. Hybrid RAG channel (본 PR) ✅

후속 PR (별도 Epic):

  • LLM 기반 Class 식별 (#1099 mapper 패턴)
  • 인스턴스 row-level mask (condition_expr instance.<field> 평가)
  • 가스공사 시나리오 e2e (생산기지 "재기화 공정 출구 압력" 질의 → 통합 응답)

관련