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.

67 lines
2.2 KiB
Python

# backend/app/multimodal/stt.py — 음성→텍스트(STT) 추상화 (LLM Provider 패턴)
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class Transcript:
text: str
seconds: float = 0.0
model: str = "" # "stt:<model>" | "heuristic"
confidence: float = 0.0
class STTProvider(ABC):
name: str = "base"
@abstractmethod
def health(self) -> dict: ...
@abstractmethod
def transcribe(
self, audio: bytes, *, mime: str = "audio/webm", hint: str = ""
) -> Transcript: ...
class OllamaWhisperSTT(STTProvider):
"""STT_MODEL(whisper류) 주입. 로컬 whisper/멀티모달 서버에 오디오 전송.
미가용 시 factory.auto 가 HeuristicSTT 로 폴백."""
name = "stt"
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.stt_model}
except Exception:
return {"reachable": False}
def transcribe(self, audio: bytes, *, mime="audio/webm", hint="") -> Transcript:
# 실모델 연동은 phase-15. 현재는 health 불가 시 폴백되며, 가능 시에도
# 안전하게 heuristic 텍스트로 환원(데모 결정성 보장).
return HeuristicSTT().transcribe(audio, mime=mime, hint=hint)
class HeuristicSTT(STTProvider):
"""오프라인/CI 폴백. 골든 입력(data.js magicInbox / sinbox 음성메모)을 결정적으로 재현."""
name = "heuristic"
GOLDEN = {
"pool": "음성 메모 0:14 — 수영장 차광막 부품 알아보기",
"gift": "음성 메모 0:09 — 엄마 생신 선물 미리 알아보기",
}
def health(self) -> dict:
return {"reachable": True, "model": "heuristic"}
def transcribe(self, audio: bytes, *, mime="audio/webm", hint="") -> Transcript:
text = self.GOLDEN.get(hint) or hint or "음성 메모 — (텍스트로 적어주세요)"
return Transcript(text=text, seconds=0.0, model="heuristic", confidence=0.4)