AgileVolto API v1 round 2

Inizia in 5 minuti

Dalla chiave al primo video parlante in sei comandi. Copiali, incollali, funzionano — non sono esempi decorativi.

Ti servono due cose. Un indirizzo del servizio e una chiave. La chiave la rilascia il portale sviluppatori, che la mostra una volta sola: qui in archivio ne resta soltanto l'impronta, quindi se la perdi non te la possiamo ridire — si revoca e se ne fa un'altra.
# le due variabili di tutti gli esempi che seguono
export BASE=https://api.agiletav.agile.software  # in locale: http://127.0.0.1:8000
export CHIAVE=atv_LA_TUA_CHIAVE

I sei comandi

  1. Il servizio è vivo? — GET /salute, senza chiave

    È l'unica rotta aperta. Serve a sorvegliare il servizio, non a usarlo: dice quali motori sono pronti davvero e perché quelli che mancano mancano.

    curl -s $BASE/salute
    {"stato":"ok","round":1,
     "codice":{"dichiarato":true,"sha":"2a04a8c06f23","ramo":"master", ...},
     "motori":{"voce":{"pronto":true,"motore":"agiletts", ...},
               "volto":{"pronto":true,"motore":"agilevolto-rapido2","round_attivo":2, ...},
               "ascolto":{"pronto":true,"motore":"agileascolto", ...},
               "cervello":{"pronto":true, ...},
               "vista":{"pronto":true,"motore":"agilevista/leggero-opencv", ...},
               "clona":{"pronto":false,"nota":"i sorgenti di OpenVoice non ci sono: esegui
                        `AGILETAV_CLONA=1 make pesi` (a runtime non si scarica niente)"}, ...},
     "api":{"contratto":"/v1 (POSTA §8.1)","documentazione":"/docs","guida":"/inizia",
            "chiavi":{"archivio_presente":true,"chiavi_attive":229, ...}}}

    Questo è il corpo vero, accorciato, di una risposta di questo servizio: un motore pronto:false porta sempre la sua nota, e la nota dice che cosa fare. codice dice quale sha sta rispondendo, così una misura si può attribuire a una versione.

  2. La chiave funziona? — GET /v1/chiave

    La prova più economica che c'è: nessun file, nessuna GPU. Ti dice anche quanta quota ti resta per oggi.

    curl -s -H "X-API-Key: $CHIAVE" $BASE/v1/chiave
    {"chiave":"k_1a2b3c4d5e6f","sviluppatore":"ADP","quota_giorno":200,
     "usate_oggi":1,"residue_oggi":199,"azzeramento":"mezzanotte UTC",
     "voce_predefinita":"it_IT-serena-medium"}

    Senza intestazione, o con una chiave revocata, arriva 401 con scritto quale dei due casi è.

  3. Carica la foto — POST /v1/riferimenti

    Una foto frontale, meglio se il volto occupa buona parte dell'inquadratura. Formati: png jpg jpeg webp bmp (e i video mp4 mov webm mkv, conservati ma non ancora usabili: l'identità da video è del round 2).

    RIF=$(curl -s -H "X-API-Key: $CHIAVE" \
          -F file=@volto.png \
          $BASE/v1/riferimenti | python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])')
    echo "riferimento: $RIF"
    Dove finisce quella foto. Su questo disco, in una cartella della tua chiave, con permessi 0600. Non viene inviata a nessun servizio esterno, non entra in nessun addestramento, non è visibile alle altre chiavi. La cancelli tu con il passo 6; se te ne dimentichi, sparisce da sola dopo 24 ore.
  4. Apri la sessione — POST /v1/sessioni

    La sessione lega la foto a una voce e a una lingua. Non consuma GPU: è un registro.

    SES=$(curl -s -H "X-API-Key: $CHIAVE" -H 'Content-Type: application/json' \
          -d "{\"persona\":\"Carla\",\"riferimento\":\"$RIF\",\"lingua\":\"it\"}" \
          $BASE/v1/sessioni | python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])')
    echo "sessione: $SES"

    voce è facoltativa: senza, si usa quella caricata dal servizio (la vedi in GET /v1/chiave). Al round 1 la lingua è it — e lo diciamo, invece di leggere l'italiano con una voce inglese.

  5. Falla parlare — POST /v1/sessioni/{id}/parla

    Testo dentro, video/mp4 fuori. Le misure viaggiano nelle intestazioni X-…, così il banco può cronometrare senza aprire il video.

    curl -s -D intestazioni.txt -o risposta.mp4 \
         -H "X-API-Key: $CHIAVE" \
         -F testo="Buongiorno, sono una persona digitale di AgileVolto." \
         -F px=512 -F max_secondi=10 \
         $BASE/v1/sessioni/$SES/parla
    
    grep -i '^x-' intestazioni.txt
    x-motore-voce: piper/it_IT-serena-medium
    x-motore-volto: agilevolto-r1
    x-tempo-voce-ms: 612
    x-tempo-volto-ms: 1804
    x-tempo-totale-ms: 2510
    x-secondi: 4.123
    x-quota-residue: 197

    In alternativa al testo puoi mandare un audio già pronto con -F audio=@domanda.wav: al round 1 quell'audio si usa così com'è — non c'è ascolto e non c'è cervello, quelli sono il round 3.

  6. Chiudi, e i file spariscono — DELETE /v1/sessioni/{id}

    curl -s -X DELETE -H "X-API-Key: $CHIAVE" $BASE/v1/sessioni/$SES
    {"id":"s_1a2b3c4d5e6f","chiusa":true,"turni":1,
     "riferimenti_rimossi":1,"byte_liberati":184320,
     "spiegazione":"sessione chiusa e riferimenti cancellati dal disco"}

    Il numero di file rimossi è dichiarato, non promesso: se un riferimento resta in piedi perché un'altra sessione aperta lo sta usando, la risposta lo scrive in riferimenti_trattenuti con il motivo.

Il flusso eventi (SSE)

Mentre la sessione lavora puoi restare in ascolto su GET /v1/sessioni/{id}/eventi. Quattro tipi di evento, nessuno di più:

eventoquandodati utili
prontola sessione è aperta e accetta /parlapersona, voce, lingua
parlaun turno è cominciatoturno, caratteri, lingua, px
fineil turno è finito, o la sessione è stata chiusams, byte, secondi — oppure motivo
errorequel turno è fallitoerrore, spiegazione
# -N disattiva il buffering di curl: senza, gli eventi si vedono tutti alla fine
curl -N -H "X-API-Key: $CHIAVE" "$BASE/v1/sessioni/$SES/eventi?da=0"
retry: 3000
: flusso eventi della sessione s_1a2b3c4d5e6f

id: 1
event: pronto
data: {"sessione":"s_1a2b3c4d5e6f","voce":"it_IT-serena-medium","lingua":"it", ...}

id: 2
event: parla
data: {"turno":1,"sorgente":"testo","caratteri":52,"lingua":"it","px":512, ...}

id: 3
event: fine
data: {"turno":1,"ms":2510,"byte":184320,"secondi":4.123, ...}

Chi si collega a metà riceve prima lo storico (fino a 200 eventi) e poi il seguito. Per riprendere da dove si era rimasti: ?da=<ultimo id>, oppure l'intestazione standard Last-Event-ID. Ogni 15 secondi passa un commento : battito che tiene viva la connessione attraverso i proxy.

Quota, e cosa succede quando finisce

Ogni chiave ha un tetto di chiamate al giorno, che si azzera a mezzanotte UTC. Ogni risposta te lo ricorda:

X-Quota-Giorno: 200
X-Quota-Usate: 43
X-Quota-Residue: 157

Superato il tetto arriva 429 con Retry-After in secondi. I rifiuti per quota non consumano quota: altrimenti il contatore direbbe «1000 su 200» e non vorrebbe dire più niente.

Gli errori, e come si leggono

Ogni rifiuto ha la stessa forma: una errore stabile che puoi confrontare nel codice, e una spiegazione in italiano che puoi leggere a occhio.

codiceerroreche cosa è successo
401chiave_mancantenon hai messo l'intestazione X-API-Key
401chiave_sconosciutaquella chiave non risulta in archivio
401chiave_revocatail portale l'ha revocata; non si riattiva
401archivio_assenteil servizio non ha ancora un archivio di chiavi: /v1 è chiuso — è la posizione sicura, non un guasto
404riferimento_sconosciutoquell'id non è di questa chiave
404sessione_sconosciutaidem, per le sessioni
409sessione_chiusahai già fatto DELETE: apri una sessione nuova
410riferimento_scomparsoil file non è più su disco (scaduto o cancellato)
422manca_la_domandané testo né audio
422domanda_ambiguatutti e due insieme
422lingua_non_disponibileal round 1 il motore parla solo italiano
429quota_esauritatetto giornaliero raggiunto; vedi Retry-After
503motore_voce_non_in_servizioi pesi non ci sono: make pesi
503catena_non_completaun motore che serve a quella chiamata non è in servizio su questa macchina: la risposta porta motori_mancanti e perche, motore per motore
Quello che oggi non facciamo. /conversa e /vede rispondono: gli organi ci sono tutti (GET /salute li elenca uno per uno, ed è lui la fonte, non questa pagina). Quello che manca è altro, e sono tre cose precise. La voce clonata: su questa macchina il motore clona non è in servizio, quindi una sessione con clonazione: "obbligatoria" prende 503 catena_non_completa con "motori_mancanti": ["clona"]; con "facoltativa" la sessione si apre e parla con una voce di catalogo. La vista col modello grande: dove il VLM non è in servizio risponde il motore «leggero», che conta e dichiara invece di descrivere una scena — nel corpo c'è motore, e dice quale dei due ha risposto. Il ferro: i motori del volto stanno su un pod che si accende e si spegne, e a pod spento le rotte che lo vogliono rispondono 503 dicendo quale motore manca. Non è un guasto ed è voluto: una risposta finta costerebbe più di un rifiuto onesto.

Dove continuare