Per chi: i prodotti che consumano AgileV e non possono eseguire codice scaricato da fuori. Da dove nasce: il 28/9 l'agente di trpg-01 ha quella regola nel proprio harness, non ha eseguito il nostro script (non gli ha nemmeno fatto vedere la propria chiave) e ha riscritto il banco in casa, nel proprio linguaggio, committato nel proprio repo. Ha funzionato meglio: il riferimento era suo. Questa pagina è quel banco descritto come protocollo, così chiunque può rifarlo senza prendere un byte da noi.
Se invece puoi eseguire il nostro script, usalo: fa le stesse cose in un comando
(sha256 adb7eeafd59e6e2d608c6d2b5f12ed997afafa0b839c8cffabcf10fc8b707dad, prova-dal-posto.html).
Quello che conta non è il file: è la riga che ci rimandi indietro.
Dal nostro box vediamo che il servizio risponde e quanto ci mette. Non vediamo se i vettori che tu ricevi oggi sono ancora quelli con cui hai costruito il tuo indice. Stesso nome di modello, vettori diversi, ricerca che peggiora senza un solo errore nei log: è il guasto più silenzioso che AgileV possa avere, e si vede solo da lì.
contract letta da /health.Il coseno atteso è 1,00000000, non «vicino a 1»: gli stessi testi danno gli stessi vettori bit per bit (lo stesso giorno, lo stesso modello, la stessa macchina). trpg l'ha misurato su tre frasi il 28/9: 1,00000000 su tutte e tre.
Dentro la stessa versione i vettori sono identici bit per bit. Attraverso un deploy annunciato possono
muoversi, e meritate di sapere di quanto prima di misurarlo, se no il primo che vede 0,9999999999 invece di
1,00000000 ci scrive per un guasto che non c'è — come è già successo con il confronto dei nomi.
La ragione è nota e voluta: dalla 1.2 le richieste grosse vengono divise in più passaggi nel modello a budget di token, per non far finire il servizio in memoria esaurita. Raggruppare i testi diversamente vuol dire padding diverso, e su un modello in virgola mobile gli ultimi bit possono cambiare.
Misurato da noi il 28/9 sul modello vero, mettendo le due strade (1.1 e 1.2) a confronto sullo stesso oggetto modello, così che l'unica variabile fosse il raggruppamento:
| La tua richiesta | Cosa cambia col deploy |
|---|---|
| sta in un solo passaggio (fino a 16 testi, o testi corti) | niente: vettori identici bit per bit, coseno 1,00000000 |
| supera il budget e viene divisa (10 testi da 1.822 token → 2 passaggi) | 2 vettori su 10 si muovono; coseno peggiore 0,9999999999960902, scarto massimo su una componente 3,06·10-7 |
riordino (/v1/rerank), 10 e 40 documenti | l'ordine non cambia; punteggi entro 4·10-11 |
Cosa farne. Il patto resta 0,9999, e ora si sa quanto margine ha: il deploy consuma quattro cento milionesimi di quel margine. Quindi la regola diventa più forte, non più debole — se la tua misura del «dopo» scende sotto 0,9999, non è il cambio di raggruppamento: è un guasto vero, ed è nostro. Fermati e scrivici, come d'accordo.
Un consiglio che rende la tua misura più sensibile: congela il riferimento su frasi corte, poche per richiesta. Così la tua richiesta sta sempre in un passaggio solo, il confronto prima/dopo è esatto bit per bit, e qualunque movimento che vedi è vero al primo decimale utile invece di essere sepolto sotto il rumore del raggruppamento. È quello che ha fatto trpg, ed è il motivo per cui la loro riga dice 1,00000000 e non «circa 1».
| Cosa chiedi | Atteso | Perché |
|---|---|---|
GET /health (senza chiave) | 200, ok:true, loaded:true, dim:1024, contract | il servizio c'è e i modelli sono caricati; contract ti dice quale versione stai misurando |
GET /v1/models (senza chiave) | 200, agilev-m3 e agilev-rerank | elenca solo ciò che esiste davvero su questa istanza |
| una chiamata senza intestazione | 401 con error.code = KEY_MISSING | gli errori sono quelli documentati: il tuo client può ramificare su error.code |
| una chiamata con una chiave inventata | 401 con error.code = KEY_INVALID | |
GET /v1/usage | 200: owner, scopes, rate_per_min, i tuoi consumi del mese | chi sei e quanto hai speso. Gratis: non consuma quota e non consuma una richiesta al minuto |
POST /v1/embeddings con {"model":"agilev-m3","input":[…]} | 200, data[].embedding da 1024 valori, norma 1,0000 | il formato compatibile OpenAI: è quello da usare per i client nuovi |
| la stessa richiesta, due volte | vettori identici (coseno 1,000000), la seconda quasi istantanea | determinismo e cache |
| il coseno col tuo riferimento congelato | > 0,9999 | il controllo che conta (§1) |
POST /embed con {"texts":[…]} | 200, vectors[][] da 1024, model:"bge-m3" | il formato storico di AgileVoyage, congelato. Attenzione al corpo: §3 |
POST /v1/rerank con query e documents | 200, results ordinati, index riferito al tuo array | il riordino. Attenzione ai campi: §3 |
intestazioni X-RateLimit-Limit/Remaining/Reset | presenti su ogni risposta autenticata dal contratto 1.2 | con la 1.1 in linea non ci sono: segnalo saltato, non KO |
| tempi su 10 chiamate uguali | mediana e massimo dal tuo box | andata e ritorno compresi: è il numero che vive il tuo utente, non il nostro |
/v1/usage prima e dopo | +N richieste, esattamente quelle che hai spedito | la fattura conta quello che hai davvero mandato |
Il banco di trpg, al primo giro, aveva segnato /embed come «rotto» e il rerank come «vuole un altro modello».
Nessuna delle due: erano i corpi. Misurato il 28/9 sul codice 1.1 in produzione e sulla 1.2, esito identico.
| Richiesta | Risposta |
|---|---|
POST /embed con {"model":…,"input":[…]} (corpo OpenAI) | 422: /embed vuole texts, input lì non esiste |
POST /v1/embeddings con {"texts":[…]} | 422: lo scambio opposto, /v1/embeddings vuole input |
POST /v1/rerank senza documents (per esempio con input) | 422: i campi obbligatori sono query e documents |
POST /v1/rerank con "model":"agilev-m3" | 200, e risponde "model":"agilev-rerank": oggi il nome del modello viene solo contato, mai rifiutato. Se un rerank non passa, non è il modello |
Del 422 sappi tre cose: è l'unica risposta senza {"error":{"code":…}} (è il corpo di FastAPI,
detail[], nessun code: riconoscilo dallo stato HTTP); arriva prima dell'autenticazione, quindi con corpo
sbagliato e chiave mancante ricevi 422 e non 401; e ti rimanda indietro ciò che hai inviato
(un lotto da 64 testi, 89.600 caratteri, torna come 89.909 byte di errore). Nel tuo log degli errori registra stato e
error.code, non il corpo intero, o i tuoi testi finiscono lì dentro.
ps e nella storia della shell. Leggila da un file 0600 o da una variabile d'ambiente./health, /v1/models, i due 401 e /v1/usage non consumano nulla. Il resto costa: la prova piena sono 7 richieste di poche decine di caratteri.403 KEY_REVOKED) fermati subito, senza spendere richieste.Rimandaci solo questa riga — mai la chiave, mai i vettori, mai i tuoi testi. Una riga sola, questo formato:
RIGA: agilev-prova-dal-posto <cliente> <data UTC ISO> contratto=<1.1|1.2> controlli=<N> KO=<N> coseno=<x.xxxxxxxx> mediana=<t>
Esempio vero, trpg, 28/9 (il suo banco riporta la mediana in millisecondi: va bene, basta che l'unità ci sia):
RIGA: agilev-prova-dal-posto trpg 2026-09-28T17:53Z contratto=1.1 controlli=22 KO=0 coseno=1.00000000 mediana=33ms
Se qualche controllo non si applica (le X-RateLimit-* sulla 1.1, per esempio) contalo come saltato, non come KO,
e dillo a parole. Se un controllo è KO, mandaci cosa hai chiesto e cosa hai ricevuto: prima di dichiarare rotto
un endpoint, confrontalo con la §3 — la prima volta è successo, e non era rotto.
Quando va rifatta: dopo ogni deploy annunciato su Novità — il prossimo è il passaggio al contratto 1.2, e ti avvisiamo noi quando è in linea — e prima di ogni reindicizzazione grossa. Il contratto completo è nella documentazione dell'API.