Spaces:
Running
Running
File size: 4,352 Bytes
bc7e936 f7b909f bc7e936 e3e0bee bc7e936 16ff626 a08364d bc7e936 16ff626 bc7e936 f7b909f bc7e936 16ff626 bc7e936 16ff626 bc7e936 a08364d bc7e936 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | """Registry of retrieval models the UI can offer.
One place to declare every embedding model, what it can search, and whether this
deployment actually serves it. Adding or swapping a model is an edit to `MODELS`
plus its vectors and weights on disk — no change to the API surface or the frontend.
The registry includes known models that are not deployed. The catalog returns
their availability and reason, while the picker lists models that can serve the
selected search. Unsupported requests return an error.
"""
from __future__ import annotations
import json
from dataclasses import dataclass
from pathlib import Path
from . import paths
CODE_CATEGORIES = ("diagnosis", "medication", "lab", "procedure")
PHENO_CATEGORY = ("phenotype",)
@dataclass(frozen=True)
class ModelSpec:
id: str
label: str
categories: tuple[str, ...]
# Prebuilt phenotype vectors for this model (None -> no phenotype support).
pheno_emb_dir: Path | None = None
# True when the packaged FAISS code indexes were built with this model.
code_indexes: bool = False
# False when the vectors and weights are known but this deployment cannot run it.
served: bool = True
unavailable_reason: str = ""
def supports(self, category: str) -> bool:
return category in self.categories
def available(self) -> tuple[bool, str]:
"""(is_available, reason_if_not) — declaration plus what is on disk."""
if not self.served:
return False, self.unavailable_reason or "Not deployed on this backend."
if self.pheno_emb_dir is not None:
config_path = self.pheno_emb_dir / "config.json"
if not config_path.exists():
return False, f"Phenotype vectors missing ({self.pheno_emb_dir.name})."
try:
config = json.loads(config_path.read_text())
except (OSError, json.JSONDecodeError) as err:
return False, f"Invalid phenotype vector config: {err}"
manifest_id = config.get("model_id")
if manifest_id is not None and manifest_id != self.id:
return False, (
f"Phenotype vectors identify model '{manifest_id}', not '{self.id}'.")
return True, ""
# Overridable so a deployment can point at a different vector build without a
# code change (the ENCODE_PARTA_EMB_DIR escape hatch, applied in paths.py).
_FTVA_EMB = paths.PARTA_EMB_DIR
MODELS: tuple[ModelSpec, ...] = (
ModelSpec(
id="bge_ft_va",
label="BGE-FT-VA",
categories=PHENO_CATEGORY + CODE_CATEGORIES,
pheno_emb_dir=_FTVA_EMB,
code_indexes=True,
),
)
DEFAULT_MODEL_ID = MODELS[0].id
_BY_ID = {m.id: m for m in MODELS}
class ModelError(Exception):
"""Raised with an HTTP status when a requested model cannot serve a request."""
def __init__(self, status: int, detail: str):
super().__init__(detail)
self.status = status
self.detail = detail
def resolve(model_id: str | None, category: str) -> ModelSpec:
"""The model to serve `category` with, or ModelError explaining why not."""
if not model_id:
model_id = DEFAULT_MODEL_ID
spec = _BY_ID.get(model_id)
if spec is None:
known = ", ".join(_BY_ID)
raise ModelError(400, f"Unknown model '{model_id}'. Available: {known}")
if not spec.supports(category):
raise ModelError(400, f"Model '{spec.label}' does not support {category} search.")
if category in CODE_CATEGORIES and not spec.code_indexes:
raise ModelError(503, f"No code indexes were built with '{spec.label}'.")
if category in PHENO_CATEGORY and spec.pheno_emb_dir is None:
raise ModelError(503, f"No phenotype vectors were built with '{spec.label}'.")
ok, reason = spec.available()
if not ok:
raise ModelError(503, f"Model '{spec.label}' is not available: {reason}")
return spec
def catalog() -> dict:
"""What the model picker renders."""
entries = []
for spec in MODELS:
ok, reason = spec.available()
entries.append({
"id": spec.id,
"label": spec.label,
"categories": list(spec.categories),
"available": ok,
"unavailable_reason": reason,
})
return {"default": DEFAULT_MODEL_ID, "models": entries}
|