111 lines
4.5 KiB
Python
111 lines
4.5 KiB
Python
"""
|
|
유연한 지식(Context) 제공자 패턴 모듈.
|
|
LLM Worker가 추출한 키워드를 받아 관련 지식 DB(또는 Mock)에서
|
|
추가 컨텍스트 딕셔너리를 반환합니다. 향후 RAG 서버로 코드 수정 없이 확장할 수 있습니다.
|
|
"""
|
|
from abc import ABC, abstractmethod
|
|
import logging
|
|
from typing import List, Dict, Any
|
|
|
|
from app.core.config import settings
|
|
|
|
logger = logging.getLogger("uvicorn.error")
|
|
|
|
class ContextProvider(ABC):
|
|
"""지식 제공자 추상 클래스"""
|
|
@abstractmethod
|
|
def get_extended_context(self, keywords: List[str]) -> List[Dict[str, Any]]:
|
|
pass
|
|
|
|
|
|
class MockContextProvider(ContextProvider):
|
|
"""개발 및 데모를 위한 하드코딩된 Mock 지식 제공자"""
|
|
def __init__(self):
|
|
# 데모용 지식 딕셔너리
|
|
self._mock_db = {
|
|
"검측차": {
|
|
"title": "검측차 (Inspection Train)",
|
|
"desc": "선로, 전차선, 신호 등의 상태를 주행하며 실시간으로 점검하는 특수 목적 차량. 주로 야간 작업에 투입됨."
|
|
},
|
|
"다대포": {
|
|
"title": "다대포 해수욕장역 (Dadaepo Beach)",
|
|
"desc": "부산 도시철도 1호선의 종착역. 회차선 및 차량 유치선 존재."
|
|
},
|
|
"tcms": {
|
|
"title": "TCMS (열차종합제어장치)",
|
|
"desc": "Train Control and Monitoring System. 열차의 각종 주요 기기를 제어하고 실시간 상태를 감시, 진단하는 시스템."
|
|
},
|
|
"관제": {
|
|
"title": "관제 센터 (Control Center)",
|
|
"desc": "전체 철도 시스템의 운행 상황을 실시간 모니터링하고 통제, 지시를 내리는 종합 사령실."
|
|
}
|
|
}
|
|
|
|
def get_extended_context(self, keywords: List[str]) -> List[Dict[str, Any]]:
|
|
results = []
|
|
found_keys = set()
|
|
|
|
for kw in keywords:
|
|
kw_lower = kw.lower()
|
|
for key, val in self._mock_db.items():
|
|
if key in found_keys:
|
|
continue
|
|
# 키워드가 DB 키에 포함되거나, DB 키가 키워드에 포함된 경우 매칭
|
|
if key.lower() in kw_lower or kw_lower in key.lower():
|
|
results.append({
|
|
"keyword": key,
|
|
"title": val["title"],
|
|
"desc": val["desc"]
|
|
})
|
|
found_keys.add(key)
|
|
|
|
return results
|
|
|
|
|
|
class CSVContextProvider(ContextProvider):
|
|
"""
|
|
[Chapter 6.1]
|
|
대규모 도메인 사전(CSV) 기반의 지식 제공자. O(1) 검색 최적화 및
|
|
동음이의어(리스트 Value) 중복 처리를 허용합니다.
|
|
"""
|
|
def get_extended_context(self, keywords: List[str]) -> List[Dict[str, Any]]:
|
|
from app.core.dictionary import RAILWAY_TERMS_DICT
|
|
results = []
|
|
found_keys = set()
|
|
|
|
for kw in keywords:
|
|
kw_clean = kw.strip()
|
|
if not kw_clean: continue
|
|
|
|
entries = RAILWAY_TERMS_DICT.get(kw_clean)
|
|
if entries:
|
|
for entry in entries:
|
|
# 동음이의어 방지용 해시 (keyword+desc)
|
|
unique_hash = f"{kw_clean}_{entry['desc'][:20]}"
|
|
if unique_hash not in found_keys:
|
|
results.append({
|
|
"keyword": entry.get("category", "일반 용어"), # Frontend tag
|
|
"title": kw_clean, # Frontend subtitle
|
|
"desc": entry.get("desc", "") # Frontend text
|
|
})
|
|
found_keys.add(unique_hash)
|
|
|
|
return results
|
|
|
|
def get_context_service() -> ContextProvider:
|
|
"""설정된 CONTEXT_PROVIDER 환경 변수에 따라 적절한 구현체를 반환하는 팩토리 함수"""
|
|
provider_type = getattr(settings, "CONTEXT_PROVIDER", "mock").strip().lower()
|
|
|
|
if provider_type == "mock":
|
|
return MockContextProvider()
|
|
elif provider_type == "csv":
|
|
return CSVContextProvider()
|
|
# elif provider_type == "rag":
|
|
# return RAGContextProvider() # 향후 구축 시 연결
|
|
else:
|
|
logger.warning(f"[Context Service] 알 수 없는 프로바이더 '{provider_type}', Mock Provider로 폴백합니다.")
|
|
return MockContextProvider()
|
|
|
|
# 전역 싱글톤 인스턴스 (워커에서 Import 용)
|
|
context_service = get_context_service()
|