imag vector generation with dimentionality reduction
This commit is contained in:
@@ -1,9 +1,16 @@
|
||||
"""Pydantic request/response models for the FastAPI layer."""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from app.infrastructure.settings import (
|
||||
IMAGE_SEARCH_DEFAULT_MIN_SCORE,
|
||||
IMAGE_SEARCH_DEFAULT_TOP_K,
|
||||
IMAGE_SEARCH_MAX_TOP_K,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -120,6 +127,17 @@ class AuthConfigOut(BaseModel):
|
||||
api_keys_source: str = "default"
|
||||
|
||||
|
||||
class ImageVectorsOut(BaseModel):
|
||||
"""Why img_vector is (or is not) being filled. Reported without loading
|
||||
the model. `model_present=false` after a deploy means the .tflite was not
|
||||
shipped in the image - the one failure this feature absorbs silently."""
|
||||
enabled: bool = True
|
||||
model_path: str = ""
|
||||
model_present: bool = False
|
||||
runtime_importable: bool = False
|
||||
state: str = "unknown"
|
||||
|
||||
|
||||
class HealthOut(BaseModel):
|
||||
status: str
|
||||
database: bool
|
||||
@@ -127,6 +145,9 @@ class HealthOut(BaseModel):
|
||||
ollama_model: str
|
||||
embeddings_model: str
|
||||
auth: AuthConfigOut
|
||||
# Defaulted so a client of this schema still validates against a
|
||||
# deployment predating the field.
|
||||
image_vectors: ImageVectorsOut = Field(default_factory=ImageVectorsOut)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -214,6 +235,52 @@ class SuggestOut(BaseModel):
|
||||
suggestions: List[SuggestionOut]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Image search (POST /api/search/image-vector, POST /api/search/image)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class ImageVectorSearchRequest(BaseModel):
|
||||
"""A phone photo's embedding, as the Nearle app computes it on-device."""
|
||||
vector: List[float] = Field(
|
||||
..., min_length=1024, max_length=1024,
|
||||
description="L2-normalised MobileNetV3-Small embedding, 1024 floats",
|
||||
)
|
||||
text: Optional[str] = Field(None, max_length=500, description="OCR text read off the label")
|
||||
brand: Optional[str] = Field(None, max_length=120, description="Restrict to one brand (no fallback)")
|
||||
category: Optional[str] = Field(None, max_length=120)
|
||||
top_k: int = Field(IMAGE_SEARCH_DEFAULT_TOP_K, ge=1, le=IMAGE_SEARCH_MAX_TOP_K)
|
||||
min_score: float = Field(IMAGE_SEARCH_DEFAULT_MIN_SCORE, ge=-1.0, le=1.0,
|
||||
description="Drop matches with cosine similarity below this")
|
||||
|
||||
@field_validator("vector")
|
||||
@classmethod
|
||||
def _finite_and_nonzero(cls, v: List[float]) -> List[float]:
|
||||
if not all(math.isfinite(x) for x in v):
|
||||
raise ValueError("vector contains NaN or infinite values")
|
||||
if math.sqrt(sum(x * x for x in v)) < 1e-6:
|
||||
raise ValueError("vector is all zeros")
|
||||
return v
|
||||
|
||||
|
||||
class ImageMatchOut(ProductOut):
|
||||
"""One catalog product that looks like the photo: the product card plus
|
||||
how close it is. `score` is cosine similarity (1 - pgvector distance);
|
||||
`text_overlap` is the label-text tie-break weight, 0 when no text was sent."""
|
||||
score: float
|
||||
text_overlap: float = 0.0
|
||||
|
||||
|
||||
class ImageSearchOut(BaseModel):
|
||||
results: List[ImageMatchOut]
|
||||
total: int
|
||||
detected_brand: Optional[str] = None
|
||||
scoped_to_brand: bool = False
|
||||
scope_fallback: bool = False
|
||||
min_score: float = 0.0
|
||||
top_k: int = 0
|
||||
query_text: Optional[str] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# RAG chat
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user