Il protocollo della prova dal posto

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.

1. Il controllo che conta: il tuo riferimento, congelato da te

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ì.

  1. Adesso, mentre in linea c'è la versione con cui hai indicizzato: scegli due o tre frasi fisse tue (scritte nel tuo file, non prese dai dati dei tuoi utenti), chiedine i vettori e congelali nel tuo repo con la data e la versione di contract letta da /health.
  2. Dopo ogni deploy che annunciamo (il prossimo è il contratto 1.2) e prima di ogni reindicizzazione grossa: richiedi gli stessi vettori e calcola il coseno con quelli congelati.
  3. Soglia: 0,9999. Sopra, il tuo indice è confrontabile con le query nuove. Sotto, non reindicizzare e scrivici subito: il problema è nostro, e reindicizzare su vettori sbagliati costa a te.

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.

Attraverso un deploy «bit per bit» non vale sempre, e il numero è questo

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 richiestaCosa 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 documentil'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».

2. I controlli, con il risultato atteso

Cosa chiediAttesoPerché
GET /health (senza chiave)200, ok:true, loaded:true, dim:1024, contractil servizio c'è e i modelli sono caricati; contract ti dice quale versione stai misurando
GET /v1/models (senza chiave)200, agilev-m3 e agilev-rerankelenca solo ciò che esiste davvero su questa istanza
una chiamata senza intestazione401 con error.code = KEY_MISSINGgli errori sono quelli documentati: il tuo client può ramificare su error.code
una chiamata con una chiave inventata401 con error.code = KEY_INVALID
GET /v1/usage200: owner, scopes, rate_per_min, i tuoi consumi del mesechi 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,0000il formato compatibile OpenAI: è quello da usare per i client nuovi
la stessa richiesta, due voltevettori identici (coseno 1,000000), la seconda quasi istantaneadeterminismo e cache
il coseno col tuo riferimento congelato> 0,9999il 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 documents200, results ordinati, index riferito al tuo arrayil riordino. Attenzione ai campi: §3
intestazioni X-RateLimit-Limit/Remaining/Resetpresenti su ogni risposta autenticata dal contratto 1.2con la 1.1 in linea non ci sono: segnalo saltato, non KO
tempi su 10 chiamate ugualimediana e massimo dal tuo boxandata e ritorno compresi: è il numero che vive il tuo utente, non il nostro
/v1/usage prima e dopo+N richieste, esattamente quelle che hai speditola fattura conta quello che hai davvero mandato

3. I due corpi che fanno sbagliare diagnosi

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.

RichiestaRisposta
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.

4. Le regole del banco (sono le nostre, applicate a casa tua)

5. La riga da rimandarci

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.

this page in English