Il problema: quanto costa davvero far leggere un PDF a un'IA?

L'idea di partenza era semplice: un tool dove l'utente carica un PDF — un contratto, una relazione, un documento lungo — e riceve indietro un riassunto strutturato in pochi secondi. Il resto del cluster PDF del sito (unione, divisione, compressione, OCR) elabora già tutto lato client, a costo zero. Un riassunto con l'IA è diverso per natura: serve per forza una chiamata a un modello linguistico, e quella ha un costo reale per token. Prima di scrivere una riga di codice, la domanda vera era: quanto mi costa, a scala, se il tool viene usato davvero?

Con un modello economico come Gemini Flash-Lite i numeri sono più rassicuranti di quanto pensassi. Per un contratto di una ventina di pagine (circa 13.000 token di input più il prompt di istruzioni, e un riassunto di 500 token in output), il costo di una singola chiamata resta nell'ordine di pochi millesimi di dollaro — il grosso del costo arriva dal volume di input, tariffato molto meno del token di output. Anche documenti molto più lunghi restano sotto uno o due centesimi. Su base mensile, anche con un traffico sostenuto per un sito personale, la spesa resta contenuta a poche decine di euro nello scenario peggiore.

💡

L'unico vero rischio di costo non è l'uso normale, ma l'abuso: qualcuno che manda in loop la chiamata o carica ripetutamente documenti enormi. Da qui la scelta di mettere un rate limit fin dal primo giorno, non come ottimizzazione successiva.

Architettura: estrazione nel browser, elaborazione IA sul server

La decisione che ha guidato tutto il resto è stata tenere l'estrazione del testo lato client, riusando la stessa libreria di parsing PDF già presente nel cluster Estrai Testo, e mandare alla funzione serverless solo il testo pulito, mai il file binario.

LivelloScelta
Estrazione testoPDF.js, interamente nel browser
BackendNetlify Function, riceve solo testo
Modello IAGemini Flash-Lite via REST API
Rate limitingContatore per IP/giorno su Firestore, via Admin SDK
Storage documentiNessuno — elaborazione al volo, nulla viene salvato

Il motivo principale non è solo di costo: le funzioni serverless hanno limiti stretti su dimensione del payload e tempo di esecuzione. Un PDF scansionato pesante rischierebbe di superarli facilmente. Estraendo il testo nel browser, alla funzione arriva un payload piccolo e prevedibile, il costo in token è più basso perché non c'è markup binario da processare, e il rischio di timeout scende quasi a zero. Per i PDF scansionati (immagine, non testo vero) resta disponibile come step preliminare opzionale l'OCR già presente nel cluster PDF.

Rate limiting senza obbligare al login

Non volevo mettere un muro di autenticazione davanti a un tool pensato per un uso rapido e occasionale. La soluzione è un contatore giornaliero per indirizzo IP, salvato lato server con le stesse credenziali di servizio già usate dalle altre funzioni del sito, verificato prima di ogni chiamata al modello e incrementato subito dopo. Superata una soglia giornaliera, la funzione risponde con un errore esplicito invece di chiamare l'IA.

Un dettaglio che mi ha fatto riflettere: le regole di sicurezza del database restano a allow read, write: if false per tutto il traffico diretto dal client, senza bisogno di aggiungere un'eccezione per la nuova collection del contatore. Le funzioni serverless usano l'Admin SDK con credenziali di servizio, che bypassa sempre le regole — quindi il "deny all" globale copre automaticamente anche una collection creata dopo, senza nessuna modifica.

Il problema imprevisto: una chiave API con un formato mai visto prima

Arrivato al momento di collegare davvero la chiave API, ho ricevuto una chiave che iniziava con un prefisso diverso da quello con cui avevo sempre lavorato in progetti precedenti (compreso l'assistente IA integrato in un altro gestionale che gestisco). Il primo istinto è stato pensare a un errore — magari la copia sbagliata di un token OAuth invece di una vera API key.

Non era un errore: è il nuovo formato di "Auth Key" che le interfacce di creazione più recenti generano di default. Il problema reale, però, era tecnico: le chiavi nel nuovo formato non funzionano se passate come parametro ?key= nell'URL della richiesta — il metodo più comune nei tutorial e in codice già scritto — e restituiscono un errore di autenticazione. Vogliono invece un header HTTP dedicato.

⚠️

Ho aggiornato la funzione per passare la chiave con l'header x-goog-api-key invece che nell'URL — compatibile sia con il nuovo formato che con quello precedente. Se un progetto smette improvvisamente di autenticarsi con Google dopo aver rigenerato una chiave, vale la pena controllare per prima cosa proprio questo.

Nota a margine di sicurezza operativa: durante il debug la chiave è stata incollata per errore in chiaro in una chat di lavoro. Anche in un canale privato, una chiave vista da qualcun altro (o da un altro sistema) va considerata potenzialmente compromessa: la prassi corretta è rigenerarla subito, non continuare a usarla "tanto nessuno l'ha vista".

Verificare la chiave prima del deploy, non dopo

Invece di collegare subito la chiave rigenerata alla funzione in produzione, ho preparato uno script Node.js isolato che fa una singola chiamata di test al modello e stampa solo esito e nome del modello — mai la chiave stessa. Sul mio computer Windows, l'unico intoppo è stato ricordarsi che la sintassi per impostare una variabile d'ambiente in PowerShell è diversa da quella di bash: due comandi separati invece di una singola riga.

Una volta ottenuta conferma che la chiave funzionasse e che il nome del modello fosse quello effettivamente disponibile per l'account, il collegamento a Netlify come variabile d'ambiente riservata alle funzioni è stato l'ultimo passo, senza sorprese.

Dalla pagina "solo form" a un tool indicizzabile

La prima versione della pagina del tool era, di fatto, quasi solo un modulo di caricamento file: funzionale per chi ci arriva già sapendo cosa fare, ma un problema per la ricerca organica. Una pagina con poco testo indicizzabile intorno al form viene classificata come "thin content" — Google non ha abbastanza segnali per capire di cosa tratta e per quali ricerche mostrarla.

Ho aggiunto: hreflang coerente con l'architettura bilingue a URL singolo del sito, dati strutturati BreadcrumbList e FAQPage (oltre al WebApplication già presente), una sezione "Come funziona" in tre passaggi, una FAQ visibile identica per contenuto ai dati strutturati, e link interni verso gli altri strumenti del cluster PDF. Tutto in versione italiana e inglese, validato prima della consegna: bilancio dei tag div, parsing dei tre blocchi JSON-LD, nessuna credenziale nel codice.

Un ultimo dettaglio quasi comico: dopo aver collegato il tool, mi sono accorto che non aveva lo sfondo animato canvas presente in tutte le altre pagine del sito. La causa era banale — nella pagina avevo usato un semplice <div id="canvas-container"> invece dell'elemento <canvas id="canvas"> che lo script di animazione condiviso cerca esplicitamente per id. Un promemoria di quanto un dettaglio piccolo, copiato male da un template, possa passare inosservato finché qualcuno non lo nota a occhio.

Cosa mi porto a casa da questo progetto

La lezione più utile non è tecnica in senso stretto: è che un tool "gratis per l'utente" non è mai davvero a costo zero per chi lo mantiene, e vale la pena fare i conti prima di scrivere codice, non dopo. Nel mio caso i numeri hanno confermato che il progetto era sostenibile — ma il rate limiting resta comunque il primo pezzo che ho scritto, non l'ultimo. Lo stesso vale per la sicurezza operativa: una chiave vista per sbaglio da un occhio in più si rigenera, punto, senza calcolare quanto sia "probabile" che sia stata usata.

Se ti interessa il resto del cluster PDF costruito con lo stesso principio di elaborazione client-side, ne ho parlato anche a proposito della redazione reale dei PDF con PDF.js e pdf-lib. E se il tema è integrare l'IA di Google in un progetto più grande, ho raccontato il percorso completo nel assistente IA con Gemini dentro un gestionale.

Domande frequenti

Quanto costa riassumere un PDF con l'IA usando Gemini?

Con un modello economico come Gemini Flash-Lite, un contratto di una ventina di pagine costa nell'ordine di pochi millesimi di dollaro a riassunto. Anche documenti da un centinaio di pagine restano sotto uno o due centesimi. A traffico sostenuto, la spesa mensile resta comunque contenuta a poche decine di euro.

Perché estrarre il testo del PDF nel browser invece di caricare il file alla funzione serverless?

Le funzioni serverless hanno limiti stretti su payload e tempo di esecuzione. Estraendo il testo lato client, alla funzione arriva solo testo pulito: meno byte, meno token da pagare, meno rischio di timeout, e il file binario non lascia mai il browser dell'utente.

Come si evita l'abuso di un tool IA gratuito esposto pubblicamente?

Con un contatore per indirizzo IP con reset giornaliero, salvato lato server con credenziali di servizio e verificato prima di ogni chiamata al modello. Le regole di sicurezza del database possono restare a "nega tutto" per il traffico diretto dal client.

Perché una chiave API Google può iniziare con un prefisso diverso dal solito?

Google sta migrando verso un nuovo formato di credenziale, generato di default dalle interfacce più recenti. Le chiavi nel nuovo formato vanno passate come header HTTP dedicato, non come parametro nell'URL — altrimenti l'autenticazione fallisce.

Perché una pagina-tool con solo un form si posiziona male su Google?

Senza testo indicizzabile intorno al form, la pagina viene classificata come "thin content". Una sezione di spiegazione, una FAQ visibile coerente con i dati strutturati e link interni verso strumenti correlati danno al motore di ricerca il contesto che il solo form non fornisce.