diff --git a/app/routers/qdrant_router.py b/app/routers/qdrant_router.py index 5796281..cb1234b 100644 --- a/app/routers/qdrant_router.py +++ b/app/routers/qdrant_router.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Any, Optional, List import uuid -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter from pydantic import BaseModel, Field from qdrant_client import QdrantClient from qdrant_client.http.models import ( @@ -20,26 +20,20 @@ from ..embeddings import embed_texts router = APIRouter(prefix="/qdrant", tags=["qdrant"]) - def _client() -> QdrantClient: s = get_settings() return QdrantClient(url=s.QDRANT_URL, api_key=s.QDRANT_API_KEY) - def _col(name: str) -> str: return f"{get_settings().COLLECTION_PREFIX}_{name}" - def _uuid5(s: str) -> str: """Deterministic UUIDv5 from arbitrary string (server-side point id).""" return str(uuid.uuid5(uuid.NAMESPACE_URL, s)) - # --- Models --- class BaseMeta(BaseModel): - note_id: str = Field( - ..., description="Stable ID of the note (e.g., hash of vault-relative path)" - ) + note_id: str = Field(..., description="Stable ID of the note (e.g., hash of vault-relative path)") title: Optional[str] = Field(None, description="Note or chunk title") path: Optional[str] = Field(None, description="Vault-relative path to the .md file") Typ: Optional[str] = None @@ -47,19 +41,14 @@ class BaseMeta(BaseModel): tags: Optional[List[str]] = None Rolle: Optional[List[str]] = None # allow list - class UpsertChunkRequest(BaseMeta): chunk_id: str = Field(..., description="Stable ID of the chunk within the note") text: str = Field(..., description="Chunk text content") - links: Optional[List[str]] = Field( - default=None, description="Outbound links detected in the chunk" - ) - + links: Optional[List[str]] = Field(default=None, description="Outbound links detected in the chunk") class UpsertNoteRequest(BaseMeta): text: Optional[str] = Field(None, description="Full note text (optional)") - class UpsertEdgeRequest(BaseModel): src_note_id: str dst_note_id: Optional[str] = None @@ -68,7 +57,6 @@ class UpsertEdgeRequest(BaseModel): relation: str = Field(default="links_to") link_text: Optional[str] = None - class QueryRequest(BaseModel): query: str limit: int = 5 @@ -76,7 +64,6 @@ class QueryRequest(BaseModel): path: Optional[str] = None tags: Optional[List[str]] = None - # --- Helpers --- def _ensure_collections(): s = get_settings() @@ -85,27 +72,17 @@ def _ensure_collections(): try: cli.get_collection(_col("chunks")) except Exception: - cli.recreate_collection( - _col("chunks"), - vectors_config=VectorParams(size=s.VECTOR_SIZE, distance=Distance.COSINE), - ) + cli.recreate_collection(_col("chunks"), vectors_config=VectorParams(size=s.VECTOR_SIZE, distance=Distance.COSINE)) # notes try: cli.get_collection(_col("notes")) except Exception: - cli.recreate_collection( - _col("notes"), - vectors_config=VectorParams(size=s.VECTOR_SIZE, distance=Distance.COSINE), - ) + cli.recreate_collection(_col("notes"), vectors_config=VectorParams(size=s.VECTOR_SIZE, distance=Distance.COSINE)) # edges (dummy vector of size 1) try: cli.get_collection(_col("edges")) except Exception: - cli.recreate_collection( - _col("edges"), - vectors_config=VectorParams(size=1, distance=Distance.COSINE), - ) - + cli.recreate_collection(_col("edges"), vectors_config=VectorParams(size=1, distance=Distance.COSINE)) @router.post("/upsert_chunk", summary="Upsert a chunk into mindnet_chunks") def upsert_chunk(req: UpsertChunkRequest) -> dict: @@ -114,16 +91,12 @@ def upsert_chunk(req: UpsertChunkRequest) -> dict: vec = embed_texts([req.text])[0] payload: dict[str, Any] = req.model_dump() payload.pop("text", None) - # Also store short preview - payload["preview"] = ( - (req.text[:240] + "…") if len(req.text) > 240 else req.text - ) + payload["preview"] = (req.text[:240] + "…") if len(req.text) > 240 else req.text qdrant_id = _uuid5(f"chunk:{req.chunk_id}") pt = PointStruct(id=qdrant_id, vector=vec, payload=payload) cli.upsert(collection_name=_col("chunks"), points=[pt]) return {"status": "ok", "id": qdrant_id} - @router.post("/upsert_note", summary="Upsert a note into mindnet_notes") def upsert_note(req: UpsertNoteRequest) -> dict: _ensure_collections() @@ -137,24 +110,18 @@ def upsert_note(req: UpsertNoteRequest) -> dict: cli.upsert(collection_name=_col("notes"), points=[pt]) return {"status": "ok", "id": qdrant_id} - @router.post("/upsert_edge", summary="Upsert a graph edge into mindnet_edges") def upsert_edge(req: UpsertEdgeRequest) -> dict: _ensure_collections() cli = _client() payload = req.model_dump() - # dummy vector vec = [0.0] - raw_edge_id = ( - f"{req.src_note_id}|{req.src_chunk_id or ''}->" - f"{req.dst_note_id or ''}|{req.dst_chunk_id or ''}|{req.relation}" - ) + raw_edge_id = f"{req.src_note_id}|{req.src_chunk_id or ''}->{req.dst_note_id or ''}|{req.dst_chunk_id or ''}|{req.relation}" qdrant_id = _uuid5(f"edge:{raw_edge_id}") pt = PointStruct(id=qdrant_id, vector=vec, payload=payload) cli.upsert(collection_name=_col("edges"), points=[pt]) return {"status": "ok", "id": qdrant_id} - @router.post("/query", summary="Vector query over mindnet_chunks with optional filters") def query(req: QueryRequest) -> dict: _ensure_collections() @@ -168,32 +135,22 @@ def query(req: QueryRequest) -> dict: if req.path: conds.append(FieldCondition(key="path", match=MatchValue(value=req.path))) if req.tags: - # tags as keyword list -> match any of the tags (OR) for t in req.tags: conds.append(FieldCondition(key="tags", match=MatchValue(value=t))) if conds: flt = Filter(must=conds) - res = cli.search( - collection_name=_col("chunks"), - query_vector=vec, - limit=req.limit, - with_payload=True, - with_vectors=False, - query_filter=flt, - ) + res = cli.search(collection_name=_col("chunks"), query_vector=vec, limit=req.limit, with_payload=True, with_vectors=False, query_filter=flt) hits = [] for p in res: pl = p.payload or {} - hits.append( - { - "chunk_id": p.id, - "score": p.score, - "note_id": pl.get("note_id"), - "title": pl.get("title"), - "path": pl.get("path"), - "preview": pl.get("preview"), - "tags": pl.get("tags"), - } - ) + hits.append({ + "chunk_id": p.id, + "score": p.score, + "note_id": pl.get("note_id"), + "title": pl.get("title"), + "path": pl.get("path"), + "preview": pl.get("preview"), + "tags": pl.get("tags"), + }) return {"results": hits}