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

102 lines
4.5 KiB
Markdown

# 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):
```sql
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:
```sql
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:
```sql
WHERE (kinds IS NULL OR t.kind = ANY(kinds))
```
Se il corpo usa SQL dinamico, passa `kinds` come parametro (`USING`), non interpolarlo:
```sql
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:
```sql
SELECT proacl FROM pg_proc WHERE proname = 'search_similar';
```
4. **Ricarica lo schema cache di PostgREST** — senza questo la nuova firma risponde 404:
```sql
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`:
```sql
CREATE INDEX IF NOT EXISTS vectors_memory_kind_idx ON vectors.memory (kind);
```
6. **Test end-to-end** (con la API key reader):
```bash
# 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.