diff --git a/AI_REFACTOR_PLAN.md b/AI_REFACTOR_PLAN.md index 5c9d2e3..8ad70b4 100644 --- a/AI_REFACTOR_PLAN.md +++ b/AI_REFACTOR_PLAN.md @@ -205,7 +205,7 @@ Tools must use **camelCase** field names (Drizzle maps them to snake_case intern - **Outcome:** Clean final frame. No more action descriptors in the protocol. ### Step B.6 — Add `/vectors/embed` endpoint -- [ ] Add to `app/api/routes/vectors.py`: +- [x] Add to `app/api/routes/vectors.py`: - `POST /api/v1/storage/vectors/embed`: - Request: `{ text: str }` - Response: `{ vector: list[float] }` (1536-dim from `text-embedding-3-small`) diff --git a/app/api/routes/vectors.py b/app/api/routes/vectors.py index 588d5c0..a03e602 100644 --- a/app/api/routes/vectors.py +++ b/app/api/routes/vectors.py @@ -1,4 +1,4 @@ -"""Vectors routes: upsert, search, and delete cloud vector store entries.""" +"""Vectors routes: upsert, search, delete cloud vector store entries, and embed text.""" from __future__ import annotations @@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends from pydantic import BaseModel from app.api.deps import get_current_user +from app.core.llm import embed from app.schemas import ( UserProfile, VectorSearchRequest, @@ -24,6 +25,14 @@ class _VectorDeleteRequest(BaseModel): ids: list[str] +class _EmbedRequest(BaseModel): + text: str + + +class _EmbedResponse(BaseModel): + vector: list[float] + + @router.post("/vectors/upsert", response_model=dict) async def upsert_vectors( body: VectorUpsertRequest, @@ -54,3 +63,17 @@ async def delete_vectors( """Delete vectors by ID, scoped to the authenticated user.""" await _vector_store.delete(current_user.id, body.ids) return {"ok": True} + + +@router.post("/vectors/embed", response_model=_EmbedResponse) +async def embed_text( + body: _EmbedRequest, + current_user: UserProfile = Depends(get_current_user), +) -> _EmbedResponse: + """Generate a 1536-dim embedding vector for the given text. + + Uses ``text-embedding-3-small`` via OpenAI. Auth required (JWT). + Used by backend tools (note_agent) and Electron (vectordb.ts) alike. + """ + vector = await embed(body.text) + return _EmbedResponse(vector=vector)