Add stored-vector point search path
This commit is contained in:
+65
-1
@@ -169,6 +169,16 @@ class SearchVectorRequest(BaseModel):
|
||||
exact: bool = False
|
||||
indexed_only: bool = False
|
||||
|
||||
class SearchPointRequest(BaseModel):
|
||||
id: int = Field(ge=1)
|
||||
limit: int = Field(default=5, ge=1, le=100)
|
||||
score_threshold: Optional[float] = Field(default=None, ge=0.0, le=1.0)
|
||||
collection: Optional[str] = None
|
||||
filter_metadata: Dict[str, Any] = Field(default_factory=dict)
|
||||
hnsw_ef: Optional[int] = Field(default=None, ge=1, le=512)
|
||||
exact: bool = False
|
||||
indexed_only: bool = False
|
||||
|
||||
|
||||
class DeleteRequest(BaseModel):
|
||||
ids: List[str]
|
||||
@@ -271,6 +281,22 @@ def _id_filter(original_id: str) -> Filter:
|
||||
return Filter(must=[FieldCondition(key="_original_id", match=MatchValue(value=original_id))])
|
||||
|
||||
|
||||
def _lookup_point_id(raw: str):
|
||||
"""Parse an existing Qdrant point id without generating a new id."""
|
||||
try:
|
||||
value = int(raw)
|
||||
if value < 0:
|
||||
raise ValueError
|
||||
return value
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
try:
|
||||
return str(uuid.UUID(raw))
|
||||
except (TypeError, ValueError, AttributeError):
|
||||
raise HTTPException(400, "Invalid point ID. Expected an unsigned integer or UUID.")
|
||||
|
||||
|
||||
def _point_id(raw: Optional[str]) -> str:
|
||||
"""Return a Qdrant-compatible point id.
|
||||
|
||||
@@ -592,6 +618,43 @@ async def search_file(
|
||||
vector = await _embed_bytes(data)
|
||||
return _do_search(vector, int(limit), score_threshold, collection, filter_metadata, hnsw_ef, exact, indexed_only)
|
||||
|
||||
@app.post("/search/point")
|
||||
def search_point(req: SearchPointRequest):
|
||||
"""
|
||||
Search using an already-indexed Qdrant point.
|
||||
|
||||
This avoids re-downloading and re-embedding the source artwork through CLIP.
|
||||
"""
|
||||
col = _col(req.collection)
|
||||
|
||||
try:
|
||||
points = client.retrieve(
|
||||
collection_name=col,
|
||||
ids=[req.id],
|
||||
with_vectors=True,
|
||||
with_payload=False,
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(500, f"Qdrant point lookup failed: {e}")
|
||||
|
||||
if not points:
|
||||
raise HTTPException(404, f"Point '{req.id}' not found")
|
||||
|
||||
vector = points[0].vector
|
||||
|
||||
if not isinstance(vector, list) or not vector:
|
||||
raise HTTPException(500, f"Point '{req.id}' has no usable vector")
|
||||
|
||||
return _do_search(
|
||||
vector,
|
||||
req.limit,
|
||||
req.score_threshold,
|
||||
req.collection,
|
||||
req.filter_metadata,
|
||||
req.hnsw_ef,
|
||||
req.exact,
|
||||
req.indexed_only,
|
||||
)
|
||||
|
||||
@app.post("/search/vector")
|
||||
def search_vector(req: SearchVectorRequest):
|
||||
@@ -655,7 +718,8 @@ def delete_points(req: DeleteRequest):
|
||||
def get_point(point_id: str, collection: Optional[str] = None):
|
||||
col = _col(collection)
|
||||
try:
|
||||
points = client.retrieve(collection_name=col, ids=[point_id], with_vectors=True)
|
||||
pid = _lookup_point_id(point_id)
|
||||
points = client.retrieve(collection_name=col, ids=[pid], with_vectors=True)
|
||||
if not points:
|
||||
raise HTTPException(404, f"Point '{point_id}' not found")
|
||||
p = points[0]
|
||||
|
||||
Reference in New Issue
Block a user