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.py | identify (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.py | M3 Step 1 회귀 유지 (ABAC engine 이 hybrid_rag 채널에서도 동작) |
M3 종결
본 PR 머지 + M3 Step 1~4 모두 머지 → Epic #1232 종결. 가스공사 납품 기준선:
- Class 단위 ABAC (Step 1) ✅
- 메트릭 + Grafana (Step 2) ✅
- 인덱스 + GRAPH + k6 (Step 3) ✅
- Hybrid RAG channel (본 PR) ✅
후속 PR (별도 Epic):
- LLM 기반 Class 식별 (#1099 mapper 패턴)
- 인스턴스 row-level mask (
condition_exprinstance.<field>평가) - 가스공사 시나리오 e2e (생산기지 "재기화 공정 출구 압력" 질의 → 통합 응답)
관련
- Epic: #1232 Ontology M3 GA
- 자매 Epic: #994 Hybrid RAG
- 이전: #1233 (ABAC), #1234 (메트릭), #1235 (성능)