You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

77 lines
2.8 KiB
Python

# backend/app/runtime_config.py
# 런타임 LLM 설정 오버레이(인메모리). app_setting 테이블 → 프로세스 메모리.
#
# 설계 의도:
# - .env(Settings)는 ops 가 관리하는 읽기전용 기본값.
# - 사용자가 설정 페이지에서 바꾸는 LLM 연결만 이 오버레이가 흡수해 즉시 반영한다.
# - 오버레이가 비어 있으면(기본) effective_* 는 Settings 값과 100% 동일 → 데모/테스트 무손상.
# - 테스트는 lifespan 을 띄우지 않으므로 load_from_db 가 호출되지 않아 항상 비어 있다.
# _test/reset 은 clear() 를 호출해 같은 프로세스 내 잔여 오버레이를 비운다.
from dataclasses import dataclass
from .config import get_settings
# 허용된 LLM 오버레이 키(이 외의 키는 무시).
LLM_KEYS = ("llm.provider", "llm.model", "llm.host", "llm.timeout")
_PROVIDERS = ("ollama",) # 실 LLM 전용(auto/heuristic 제거, phase-16+)
# 인메모리 오버레이. key → str value.
_overrides: dict[str, str] = {}
@dataclass(frozen=True)
class EffectiveLLM:
provider: str # auto | ollama | heuristic
model: str
host: str
timeout: float
def clear() -> None:
"""오버레이 비우기 → effective_* 가 .env 기본값으로 복귀."""
_overrides.clear()
def snapshot() -> dict[str, str]:
"""현재 오버레이의 사본(디버그/응답용)."""
return dict(_overrides)
def set_llm(values: dict[str, object]) -> None:
"""LLM 오버레이 갱신. None/빈 값은 '기본으로 되돌리기'로 간주해 키를 제거한다."""
for k, v in values.items():
if k not in LLM_KEYS:
continue
if v is None or (isinstance(v, str) and v.strip() == ""):
_overrides.pop(k, None)
else:
_overrides[k] = str(v)
def load_from_db(session) -> None:
"""앱 시작 시(lifespan) app_setting → 오버레이 적재."""
from sqlmodel import select
from .models import AppSetting
_overrides.clear()
for row in session.exec(select(AppSetting)).all():
if row.key in LLM_KEYS:
_overrides[row.key] = row.value
def effective_llm() -> EffectiveLLM:
"""오버레이 > .env 우선순위로 합성한 유효 LLM 설정."""
st = get_settings()
provider = _overrides.get("llm.provider") or st.llm_provider
if provider not in _PROVIDERS:
provider = "ollama" # 미지원/구 값(auto·heuristic) → 실 LLM 으로 강제
model = _overrides.get("llm.model") or st.ollama_model
host = _overrides.get("llm.host") or st.ollama_host
raw_timeout = _overrides.get("llm.timeout")
try:
timeout = float(raw_timeout) if raw_timeout else st.llm_timeout
except ValueError:
timeout = st.llm_timeout
return EffectiveLLM(provider=provider, model=model, host=host, timeout=timeout)