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.

58 lines
1.8 KiB
Python

# backend/app/multimodal/vision.py — 이미지→캡션/OCR(Vision) 추상화 (LLM Provider 패턴)
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class Caption:
text: str # 캡션 또는 OCR 텍스트
ocr: str = "" # OCR 전용(영수증/스크린샷)
model: str = "" # "vision:<model>" | "heuristic"
confidence: float = 0.0
class VisionProvider(ABC):
name: str = "base"
@abstractmethod
def health(self) -> dict: ...
@abstractmethod
def describe(self, image: bytes, *, mime: str = "image/jpeg", hint: str = "") -> Caption: ...
class OllamaVisionVision(VisionProvider):
"""VISION_MODEL(멀티모달 비전) 주입. 이미지→캡션/OCR. 미가용 시 폴백."""
name = "vision"
def __init__(self):
from ..config import get_settings
self.st = get_settings()
def health(self) -> dict:
import httpx
try:
r = httpx.get(f"{self.st.ollama_host}/api/tags", timeout=2.0)
return {"reachable": r.status_code == 200, "model": self.st.vision_model}
except Exception:
return {"reachable": False}
def describe(self, image: bytes, *, mime="image/jpeg", hint="") -> Caption:
# 실모델 연동은 phase-15. 데모 결정성을 위해 heuristic 으로 환원.
return HeuristicVision().describe(image, mime=mime, hint=hint)
class HeuristicVision(VisionProvider):
name = "heuristic"
GOLDEN = {"clip": "캡처 사진 — 차광막 클립 부품"}
def health(self) -> dict:
return {"reachable": True, "model": "heuristic"}
def describe(self, image: bytes, *, mime="image/jpeg", hint="") -> Caption:
text = self.GOLDEN.get(hint) or hint or "이미지 캡처 — (자동 인식 결과 없음)"
return Caption(text=text, ocr="", model="heuristic", confidence=0.4)