Leggere attraverso scarsa illuminazione e ostruzioni
Questa immagine è un menu di un bar brasiliano fotografato in poca luce. Tre pannelli di vetro elencano le bevande in testo dorato, ma delle lampade a sospensione bloccano parti del pannello sinistro, il pannello destro è appena visibile al bordo dell’inquadratura e riflessi sul vetro si disperdono sulla superficie.

Il prompt non menziona luminosità, ritagli o tecniche specifiche. Chiede solo il contenuto:
image_path = Path("images/bar_menu.jpg")
image_part = types.Part.from_bytes(
data=image_path.read_bytes(), mime_type="image/jpeg"
)
prompt = (
"Read every drink listed on this menu board, including the ones "
"partially hidden behind the lamps and on the side panels. For "
"each drink, give me the name and beer style."
)
La visione standard legge senza problemi il pannello centrale ben illuminato, estraendo otto birre dalla sezione "FRUTADAS" e quattro da "CHOPP". Ma il pannello sinistro torna così:
[ANSWER]
### Left Panel (Partially visible)
* **[Unreadable] LAGER** | [Style unreadable]
* **RED LAGER** | [Style unreadable]
* **[Unreadable] LAGER** | [Style unreadable]
* **[Unreadable]GER** | [Style unreadable]
* **HONKERS ALE** | [Style unreadable]
* **[Unreadable]SELLE** | [Style unreadable]
Sei voci, molte con nomi illeggibili e tutte con stili mancanti. Il modello vede che c’è del testo ma non riesce a decifrarlo.
La chiamata agentica esegue due round di codice. Prima, ritaglia tutti e tre i pannelli:
[CODE]
middle_panel = [200, 320, 950, 780]
left_panel = [200, 0, 950, 260]
right_panel = [200, 840, 700, 1000]
# ... crops and saves each region

Dopo aver osservato quei ritagli, il modello decide che i pannelli sinistro e destro necessitano di più zoom. Esegue un secondo round:
[CODE]
left_panel_top = [100, 0, 500, 250]
left_panel_bottom = [500, 0, 980, 250]
right_panel_top = [100, 800, 600, 1000]

Con quello zoom extra, il modello recupera nomi che la visione standard segnava come illeggibili: Amber Lager, Honkers Ale (Goose Island) e Demoiselle (Colorado) dal pannello sinistro, più Wals Dubbel, Tripel, Quadrupel e Petroleum dal destro. Identifica anche l’intestazione del pannello sinistro come "MISTURAS" e quella del destro come "ALCOÓLICAS".
Il prompt non ha mai detto "ritaglia i pannelli" o "zooma nelle aree scure". Ha chiesto le bevande e i loro stili, e il modello ha lavorato a ritroso da quell’obiettivo.
Ciò che rende questo esempio diverso dallo scaffale del supermercato è il secondo passaggio. Dopo il primo round di ritagli, il modello ha guardato cosa aveva, ha deciso che i pannelli sinistro e destro erano ancora troppo difficili da leggere e ha eseguito un altro round di zoom più stretti senza che gli venisse detto.
Il loop si è autocorretto.
Questo è il valore di lasciare aperto il metodo: il modello non fa solo un pre-processing una volta, ma itera finché i risultati raggiungono l’asticella fissata dal prompt.
Conteggio e annotazione con la visione agentica in Gemini 3
Il menu del bar ha funzionato perché il prompt lasciava aperto il metodo e il modello ha capito cosa fare. Non è sempre così. A volte il modello ha bisogno di istruzioni esplicite su quale strumento usare, e il prossimo esempio mostra dove sta il confine.
L’immagine sotto mostra monete sparse su una scrivania e una cartella nera. Alcune si sovrappongono, alcune sono parzialmente nascoste e il mix di rame e argento rende facile perdere il conto.

Un primo approccio sensato è chiedere al modello di annotare ogni moneta così da poter contare dalle sue stesse marcature. La documentazione di Google conferma che la visione agentica può disegnare riquadri di delimitazione ed etichette direttamente sulle immagini usando PIL e OpenCV nel sandbox. Quindi:
image_path = Path("images/coins.jpg")
image_part = types.Part.from_bytes(
data=image_path.read_bytes(), mime_type="image/jpeg"
)
prompt_v1 = (
"Count every coin in this image. Draw a bounding box or circle "
"around each one and number them. Check for overlapping or "
"partially hidden coins. Give me the final count."
)
response_v1 = client.models.generate_content(
model=MODEL,
contents=[image_part, prompt_v1],
config=agentic_config,
)
print_response(response_v1, show_thoughts=True)
Il modello ritaglia cinque regioni, le analizza e restituisce un conteggio di 34. Ma guarda il formato della risposta:
[ANSWER] Final Count: 34 coins in total.
[
{"box_2d": [387, 175, 451, 236], "label": "1"},
{"box_2d": [356, 196, 407, 248], "label": "2"},
{"box_2d": [319, 231, 375, 274], "label": "3"},
...
]
Ha restituito le coordinate dei riquadri come JSON invece di disegnare qualcosa sull’immagine. Il prompt diceva "draw", ma il modello lo ha interpretato come un compito descrittivo e ha fornito output testuale strutturato.
La documentazione sull’esecuzione di codice suggerisce il perché: "Mentre il modello gestisce automaticamente lo zoom per i dettagli minuti, dovresti esplicitamente invitarlo a usare il codice per altri compiti." La parola "draw" è abbastanza ambigua da far scegliere al modello la via solo-testo.
Descrivere il compito in termini del codice che vuoi che esegua rimuove quell’ambiguità:
prompt_v2 = (
"Count every coin in this image. Use Python to draw a numbered "
"bounding box on the image around each coin you find. Check for "
"overlapping or partially hidden coins. After annotating, give "
"me the final count."
)
response_v2 = client.models.generate_content(
model=MODEL,
contents=[image_part, prompt_v2],
config=agentic_config,
)
print_response(response_v2, show_thoughts=True)
Questa volta, il modello scrive del codice di disegno effettivo:
[CODE]
import PIL.Image
import PIL.ImageDraw
img = PIL.Image.open('input_file_0.jpeg')
width, height = img.size
coins = [
[383, 175, 452, 227], [352, 196, 411, 255], ... # 34 total
]
draw = PIL.ImageDraw.Draw(img)
for i, coin in enumerate(coins):
ymin, xmin, ymax, xmax = coin
left, top, right, bottom = (xmin*width/1000, ymin*height/1000,
xmax*width/1000, ymax*height/1000)
draw.rectangle([left, top, right, bottom], outline='red', width=3)
draw.text((left, top), str(i + 1), fill='red')
[RESULT] (Outcome.OUTCOME_OK)
Total coins detected: 34
[IMAGE] image/png, 3,685,043 bytes
[ANSWER] I have identified and counted a total of 34 coins.

Ogni moneta riceve un riquadro rosso e un numero. Il conteggio reale è 35 o 36, a seconda di come giudichi due monete sovrapposte vicino al bordo inferiore dell’inquadratura.
Il modello si è fermato a 34, mancandone una o due in quel gruppo. Ma l’annotazione funge da taccuino visivo: invece di un numero da prendere sulla fiducia, ottieni un artefatto verificabile che puoi controllare riquadro per riquadro.
L’output completo non troncato per entrambe le chiamate è disponibile in questa gist.
Estrazione multi-step e plotting
Gli esempi precedenti coinvolgevano un compito principale ciascuno (leggere testo, contare oggetti). Questo ne concatena diversi: ritagliare una tabella da una foto, estrarre i numeri in un DataFrame e generare un grafico. L’immagine è una tabella fiscale in stile IRS fotografata su una scrivania con una calcolatrice.

Il prompt descrive l’intera pipeline in un colpo solo:
image_path = Path("images/data_table.jpg")
image_part = types.Part.from_bytes(
data=image_path.read_bytes(), mime_type="image/jpeg"
)
prompt = (
"Crop and zoom into the table in this image. Extract the first "
"10 rows of data into a structured format. Identify the column "
"headers. Then create a bar chart comparing the values in the "
"numeric columns for those 10 rows. Summarize which row has the "
"highest and lowest values."
)
response = client.models.generate_content(
model=MODEL,
contents=[image_part, prompt],
config=agentic_config,
)
print_response(response, show_thoughts=True)
Il modello impiega quattro round di codice per passare dalla foto completa a un grafico finito. I primi tre sono zoom progressivi: ritaglia la tabella "Single or Married Filing Separately", poi le intestazioni di colonna, poi le prime 10 righe di dati, generando cinque immagini intermedie lungo il percorso. Ecco il primo ritaglio in cui isola la tabella:

Al quarto round, ha letto abbastanza dettagli per estrarre i numeri. Costruisce un DataFrame pandas, stampa la tabella e genera un grafico a barre raggruppate in un unico blocco di codice:
[CODE]
import matplotlib.pyplot as plt
import pandas as pd
data = [
{"Higher Paying Job Wage": "$0 - 9,999",
"$0 - 9,999": 0, "$10,000 - 19,999": 940,
"$20,000 - 29,999": 1020, "$30,000 - 39,999": 1020},
... # 10 rows total
]
df = pd.DataFrame(data)
df.set_index("Higher Paying Job Wage").plot(kind='bar', figsize=(12, 6))
plt.title('Tax Table: Single or Married Filing Separately')
plt.ylabel('Tax Amount')
plt.savefig('tax_bar_chart.png')
La tabella estratta:
|
Higher Paying Job Wage |
$0 - 9,999 |
$10,000 - 19,999 |
$20,000 - 29,999 |
$30,000 - 39,999 |
|
$0 - 9,999 |
$0 |
$940 |
$1,020 |
$1,020 |
|
$10,000 - 19,999 |
$940 |
$1,540 |
$1,620 |
$2,020 |
|
$20,000 - 29,999 |
$1,020 |
$1,620 |
$2,100 |
$3,100 |
|
$30,000 - 39,999 |
$1,020 |
$2,020 |
$3,100 |
$4,100 |
|
$40,000 - 59,999 |
$1,870 |
$3,470 |
$4,550 |
$5,550 |
|
$60,000 - 79,999 |
$1,870 |
$3,470 |
$4,690 |
$5,890 |
|
$80,000 - 99,999 |
$2,000 |
$3,810 |
$5,090 |
$6,290 |
|
$100,000 - 124,999 |
$2,040 |
$3,840 |
$5,120 |
$6,320 |
|
$125,000 - 149,999 |
$2,040 |
$3,840 |
$5,120 |
$6,910 |
|
$150,000 - 174,999 |
$2,220 |
$4,830 |
$6,910 |
$8,910 |
E il grafico a barre generato:

L’output completo non troncato è disponibile in questa gist.
Una sola chiamata API ha prodotto un DataFrame strutturato, un riepilogo con righe max/min identificate e un grafico a barre pronto per la pubblicazione. Il prompt elencava quattro cose (ritaglia, estrai, grafico, riassumi) e il modello ha trattato ciascuna come una fase di una pipeline in cui l’output di uno step alimentava il successivo.
Gli esempi precedenti usavano il loop per rifinire una singola operazione (ritagli migliori, annotazioni migliori). Questo mostra il loop che concatena operazioni diverse, ognuna costruita su quanto prodotto dal round precedente.
Conclusione
La visione agentica trasforma una singola chiamata API in un ciclo di feedback in cui il modello scrive codice, guarda cosa ha prodotto e decide cosa fare dopo.
L’esempio dello scaffale del supermercato ha mostrato il pattern di base: ritagliare regioni troppo piccole da leggere, poi rispondere dalle versioni ingrandite. Il menu del bar ha aggiunto una seconda dimensione, in cui il modello ha eseguito un round di ritagli, ha deciso che non bastavano ed è andato più a fondo nelle aree problematiche di sua iniziativa.
Per le monete, l’annotazione ha trasformato una stima in un artefatto verificabile. La tabella fiscale ha concatenato quattro operazioni distinte (ritaglia, estrai, calcola, traccia) nella stessa richiesta.
Ogni esempio ha anche rivelato qualcosa sul prompting. Fissare un livello di completezza ("elenca tutti i prodotti") ha spinto il modello verso la meticolosità. Lasciare aperto il metodo gli ha permesso di scegliere il proprio percorso di pre-processing.
Ma verbi ambigui come "draw" a volte lo hanno mandato fuori strada, e passare a "Use Python to draw" ha risolto.
Il pattern: descrivi cosa vuoi ottenere, sii specifico sullo strumento quando il compito è ambiguo e lascia che il loop gestisca il resto.
Tutto il codice di questo tutorial gira con gemini-3-flash-preview e l’SDK google-genai. Se vuoi approfondire, il corso di DataCamp Introduction to AI Agents copre il pattern di loop alla base di tutto questo.
Visione agentica in Gemini 3: FAQ
Cos’è la visione agentica in Gemini 3 Flash?
La visione agentica è una funzionalità in cui Gemini 3 Flash può scrivere ed eseguire codice Python a metà risposta per manipolare le immagini prima di rispondere. Usa un ciclo Think, Act, Observe per ritagliare, zoomare, annotare e migliorare le immagini in un ambiente sandbox, tutto all’interno di una singola chiamata API.
Come attivo la visione agentica nell’API di Gemini?
Aggiungi uno strumento di esecuzione di codice alla tua GenerateContentConfig: tools=[types.Tool(code_execution=types.ToolCodeExecution())]. Questo fornisce al modello un ambiente Python in sandbox con 43 librerie preinstallate tra cui PIL, OpenCV e matplotlib.
Perché il modello salta l’esecuzione di codice anche quando è attiva?
Il modello scrive codice solo quando decide che uno sguardo singolo all’immagine non basta. Se può rispondere con sicurezza dalla risoluzione originale, salta il percorso di esecuzione del codice. È voluto e documentato da Google.
Qual è la differenza tra "draw" e "Use Python to draw" nei prompt?
Dire "draw" è ambiguo e il modello potrebbe restituire descrizioni testuali o coordinate JSON invece di un’immagine effettiva. Aggiungere "Use Python to draw" dice al modello di scrivere codice eseguibile che produce un output visivo, innescando in modo affidabile il percorso di esecuzione del codice.
Posso combinare la visione agentica con la chiamata di funzioni personalizzate?
No. Esecuzione di codice e chiamata di funzioni personalizzate non possono essere usate nella stessa richiesta API. Se il tuo flusso richiede entrambe, devi fare chiamate separate.

Sono un creator di contenuti sulla data science con oltre 2 anni di esperienza e uno dei più seguiti su Medium. Mi piace scrivere articoli dettagliati su AI e ML con un pizzico di sarcasmo, perché bisogna pur fare qualcosa per renderli un po’ meno noiosi. Ho pubblicato oltre 130 articoli e un corso su DataCamp, con un altro in arrivo. I miei contenuti sono stati visti da oltre 5 milioni di persone, 20.000 delle quali sono diventate follower sia su Medium che su LinkedIn.


Invii un’immagine a una API di visione chiedendole di leggere una piccola etichetta prezzo. La risposta torna sicura e sbagliata. Stessa storia con il conteggio di oggetti in una foto disordinata: il modello guarda una volta l’immagine intera e indovina un numero del tutto fuori scala.
Gemini 3 Flash si comporta in modo diverso. Con l’esecuzione di codice attivata, può scrivere ed eseguire Python a metà risposta per ritagliare, zoomare e annotare immagini prima di impegnarsi in una risposta. Google lo chiama visione agentica e segnala un incremento qualitativo del 5-10% nei benchmark di visione.
Questo tutorial illustra quattro esempi via via più difficili: leggere le scritte minute da uno scaffale del supermercato, estrarre i nomi delle bevande da un menu di bar poco illuminato, contare monete sparse ed estrarre i dati di una tabella per creare un grafico.
Cos’è la visione agentica? Il ciclo "Think, Act, Observe"
L’idea è semplice. Con l’esecuzione di codice attiva, Gemini 3 può fermarsi a metà risposta, scrivere ed eseguire Python in un sandbox, guardare cosa ha prodotto il codice e decidere cosa fare dopo.
Per i compiti di visione, di solito significa che il modello ritaglia regioni di un’immagine, le ingrandisce, disegna riquadri di delimitazione o regola il contrasto per far emergere dettagli non visibili alla risoluzione originale.
Questo si svolge in un ciclo in tre fasi.
Durante Think, il modello guarda cosa ha (l’immagine originale o una versione già ritagliata o annotata) e pianifica la mossa successiva. Potrebbe decidere che un’etichetta prezzo è troppo piccola per essere letta e che serve ritagliarla e ingrandirla.
Act trasforma quel piano in Python. Il sandbox ha 43 librerie preinstallate (PIL, OpenCV, matplotlib e altre), quindi il modello può scrivere il codice di elaborazione immagini di cui ha bisogno.
Dopo l’esecuzione del codice, Observe chiude il ciclo. L’output (nuove immagini, testo stampato, valori calcolati) rientra nel contesto. Da lì, il modello risponde oppure riparte per un altro giro.
Una singola chiamata API può ripetere più volte questo ciclo. Negli esempi che seguono, lo vedrai ritagliare sette regioni dello scaffale in una richiesta. In un altro, estrae i dati di una tabella in un DataFrame pandas e genera un grafico a barre, tutto nella stessa chiamata.
Se il concetto di ciclo agentico è nuovo per te, il percorso AI Agent Fundamentals di DataCamp lo tratta in profondità.
Limitazioni e confini
Configurare l’ambiente per la visione agentica
Ottieni una chiave API da ai.google.dev e salvala come
GOOGLE_API_KEYnel tuo ambiente o in un file.env. Poi installa l’SDK e le librerie per le immagini:Inizia creando il client:
Quel
timeout=600_000è importante. Le chiamate di visione agentica prevedono più cicli di generazione ed esecuzione di codice nel sandbox, e una singola richiesta può durare circa quattro minuti.Il timeout predefinito di httpx interromperà la connessione ben prima, lasciandoti con un silenzioso
ReadTimeoute senza indizi su cosa sia successo.Poi configura l’agentic config:
toolsè l’interruttore per l’esecuzione di codice. Fornisce al modello un ambiente Python in sandbox con 43 librerie (numpy, pandas, PIL, matplotlib, OpenCV e altro). Ogni blocco di codice ha 30 secondi per l’esecuzione e il sandbox riprova fino a 5 volte in caso di errori.thinking_level="HIGH"dà al modello più spazio di pianificazione prima di scrivere il codice. Per lavori di visione a più step, fa una differenza sensibile. Impostareinclude_thoughts=Trueespone quel ragionamento nelle parti della risposta, utile per il debugging.media_resolutiondefinisce quanti token ottiene l’immagine in input prima ancora che il ciclo inizi.MEDIA_RESOLUTION_HIGHè un buon default. Puoi spingere suMEDIA_RESOLUTION_ULTRA_HIGHper immagini molto dense o scendere aMEDIA_RESOLUTION_LOWquando la velocità conta più del dettaglio.Questa stessa configurazione funziona su Vertex AI con modifiche minime. Per una panoramica più ampia della Gemini 3 API oltre la visione, vedi il tutorial complementare.
Visione agentica in Gemini 3: leggere i dettagli fini con ritaglio e zoom
L’immagine qui sotto mostra uno scaffale di un supermercato tedesco con tre file di prodotti. Etichette prezzo corrono lungo il bordo di ogni ripiano, e un avviso "Liebe Kunden" (Cari clienti) si trova vicino al fondo. Le etichette si leggono se zoomi manualmente, ma sono abbastanza piccole che un modello che osserva l’immagine intera deve decidere cosa riesce e cosa non riesce a distinguere.
Analizzare la risposta multi-parte
Le risposte agentiche non sono blocchi di testo unici. Arrivano come un elenco di parti: thinking, codice scritto dal modello, risultati dell’esecuzione, immagini generate e risposta finale. Questo helper le percorre:
Ogni esempio in questo tutorial usa la stessa funzione, quindi definiscila una volta e riutilizzala.
Ora carica l’immagine dello scaffale e scrivi un prompt che chieda al modello di leggere l’avviso e di elencare ogni prodotto con il relativo prezzo.
Eseguilo prima senza esecuzione di codice:
Ecco una versione ridotta dell’output:
La traduzione dell’avviso è corretta e il modello legge le etichette Uncle Ben’s che riconosce. Ma circa un terzo delle voci risultano "(Label obscured)" e marchi come Oryza, Reis-fit e Reiskugeln non compaiono affatto.
Ora lo stesso prompt con la configurazione agentica:
La struttura dell’output è diversa. Invece di un unico
[ANSWER], attraversa l’intero ciclo Think/Act/Observe:I sette ritagli generati dal modello:
Dopo aver osservato quei ritagli, il modello produce una risposta completa:
L’output completo non troncato per entrambe le chiamate è disponibile in questa gist.
La risposta agentica legge oltre 30 prodotti su tutti e tre i ripiani. Gli articoli Oryza, Reis-fit, Madras Curry, Reiskugeln e Steam Basmati che la visione standard etichettava come "(obscured)" compaiono con nomi e prezzi.
La differenza non riguarda allucinazioni vs accuratezza; la visione standard è onesta su ciò che non riesce a leggere. La visione agentica semplicemente non accetta quel limite e ingrandisce finché può.
Qui il prompt ha un ruolo. Chiedere al modello di "elencare tutti i nomi dei prodotti visibili e i relativi prezzi" fissa un livello di completezza che lo spinge verso il ritaglio.
Un prompt più vago come "descrivi questa immagine" probabilmente produrrebbe una risposta a passaggio singolo anche con l’esecuzione di codice attiva, perché il modello non vede motivo per andare oltre.