Files
ThothII/harness/docs/vector-rest-kinds-migration.md
T

4.5 KiB

Migrazione server: filtro kinds nella RPC search_similar

Contesto. Il vectorstore ThothII espone la similarity search via PostgREST/Supabase: POST <base_url>/rpc/search_similar con payload {query_embedding, limit_count, table_name}. Da quando memory e solved_question condividono la tabella vectors.memory (feature "active memory", 2026-07-07), il top-k della tabella mista diluisce i risultati: il filtro per kind avviene client-side DOPO il taglio a limit_count. Serve il filtro nel WHERE della funzione SQL.

Lato client: già pronto. Il harness manda kinds nel payload quando filtra (tht memory search, tht memory solved-search); se il server risponde 404 (funzione a 3 argomenti, pre-migrazione) ritenta senza kinds e filtra client-side. Quindi l'ordine di deploy è libero — ma finché la migrazione non è fatta il filtro resta client-side e la diluizione persiste.

Cosa fare (istruzioni per il Claude del server)

Obiettivo: aggiungere a search_similar un quarto parametro kinds text[] DEFAULT NULL che filtra kind = ANY(kinds) quando valorizzato, preservando il comportamento attuale quando assente.

  1. Recupera la definizione corrente (non riscriverla a memoria):

    SELECT pg_get_functiondef(oid)
    FROM pg_proc
    WHERE proname = 'search_similar';
    

    Prendi nota di: schema della funzione, LANGUAGE, SECURITY DEFINER e SET search_path se presenti, shape del risultato (le righe devono restare {id, similarity, metadata}), e come viene usato table_name (quasi certamente SQL dinamico con EXECUTE format(...)).

  2. Sostituisci la funzione in una sola transazione. In Postgres non si può aggiungere un parametro con ALTER: CREATE OR REPLACE con firma diversa creerebbe un overload, e due overload con lo stesso nome mandano PostgREST in ambiguità (PGRST203) per le chiamate senza kinds. Quindi:

    BEGIN;
    DROP FUNCTION search_similar(vector, integer, text);  -- adatta la firma esatta trovata al punto 1
    CREATE FUNCTION search_similar(
        query_embedding vector,
        limit_count integer,
        table_name text,
        kinds text[] DEFAULT NULL
    ) RETURNS ... -- stessa shape di prima
    ...
    COMMIT;
    

    Nel corpo, la condizione deve essere null-safe così i chiamanti legacy (senza kinds) mantengono il comportamento attuale:

    WHERE (kinds IS NULL OR t.kind = ANY(kinds))
    

    Se il corpo usa SQL dinamico, passa kinds come parametro (USING), non interpolarlo:

    EXECUTE format(
      'SELECT metadata, 1 - (embedding <=> $1) AS similarity
         FROM vectors.%I
        WHERE ($3::text[] IS NULL OR kind = ANY($3))
        ORDER BY embedding <=> $1
        LIMIT $2', table_name)
    USING query_embedding, limit_count, kinds;
    

    (Adatta al corpo reale: l'esempio mostra solo dove inserire la condizione.)

  3. Ripristina i GRANT. Il DROP cancella i grant della funzione: ri-esegui gli GRANT EXECUTE che la definizione originale aveva (ruolo reader della REST, es. vector_reader, e il ruolo con cui PostgREST esegue le RPC). Verifica con:

    SELECT proacl FROM pg_proc WHERE proname = 'search_similar';
    
  4. Ricarica lo schema cache di PostgREST — senza questo la nuova firma risponde 404:

    NOTIFY pgrst, 'reload schema';
    

    (oppure riavvia il servizio PostgREST).

  5. Verifica indice. La colonna kind dovrebbe già avere un indice (<table>_kind_idx); se manca sulla tabella memory:

    CREATE INDEX IF NOT EXISTS vectors_memory_kind_idx ON vectors.memory (kind);
    
  6. Test end-to-end (con la API key reader):

    # senza kinds (comportamento legacy) — deve rispondere come prima
    curl -s -X POST "<base_url>/rpc/search_similar" -H "X-API-Key: $KEY" \
      -H "Content-Type: application/json" \
      -d '{"query_embedding": [...], "limit_count": 3, "table_name": "memory"}'
    # con kinds — deve restituire SOLO righe con metadata.kind = "solved_question"
    curl -s -X POST "<base_url>/rpc/search_similar" -H "X-API-Key: $KEY" \
      -H "Content-Type: application/json" \
      -d '{"query_embedding": [...], "limit_count": 3, "table_name": "memory",
           "kinds": ["solved_question"]}'
    

    Poi dal workstation: tht memory solved-search "<domanda>" --json e tht memory search "<domanda>" --json devono continuare a funzionare (il secondo ora senza righe solved nei top-k).

Non toccare existing_vector_hashes e upsert_vector_records (path di scrittura): già ricevono kinds/kind e non sono coinvolte.