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.
-
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 DEFINEReSET search_pathse presenti, shape del risultato (le righe devono restare{id, similarity, metadata}), e come viene usatotable_name(quasi certamente SQL dinamico conEXECUTE format(...)). -
Sostituisci la funzione in una sola transazione. In Postgres non si può aggiungere un parametro con ALTER:
CREATE OR REPLACEcon firma diversa creerebbe un overload, e due overload con lo stesso nome mandano PostgREST in ambiguità (PGRST203) per le chiamate senzakinds. 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
kindscome 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.)
-
Ripristina i GRANT. Il DROP cancella i grant della funzione: ri-esegui gli
GRANT EXECUTEche 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'; -
Ricarica lo schema cache di PostgREST — senza questo la nuova firma risponde 404:
NOTIFY pgrst, 'reload schema';(oppure riavvia il servizio PostgREST).
-
Verifica indice. La colonna
kinddovrebbe già avere un indice (<table>_kind_idx); se manca sulla tabellamemory:CREATE INDEX IF NOT EXISTS vectors_memory_kind_idx ON vectors.memory (kind); -
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>" --jsonetht memory search "<domanda>" --jsondevono 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.