Workers AI: Inferenza di LLM e Modelli Vision Direttamente sull'Edge
Workers AI ha registrato una crescita del 4000% YoY nelle richieste di inferenza a Q1 2026. Scopri come eseguire modelli di testo, visione e speech direttamente nei tuoi Workers senza GPU dedicata, con latenza sotto i 200ms.
L'Inferenza AI Spostata all'Edge
Fino al 2023, eseguire un Large Language Model significava quasi obbligatoriamente comunicare con un API esterna (OpenAI, Anthropic, Google) o deployare GPU costose su infrastruttura dedicata. La latenza della rete verso questi endpoint centralizzati aggiungeva 200-800ms a ogni richiesta, e i costi di GPU erano proibitivi per applicazioni a basso volume.
Workers AI ha cambiato questo scenario. Cloudflare ha distribuito hardware di inferenza AI (GPU specializzate) in decine di datacenter in tutto il mondo. I modelli vengono eseguiti sullo stesso hardware che gestisce il tuo Worker, eliminando il round-trip di rete verso provider esterni. Il risultato è inferenza a latenza ridotta, fatturata al consumo senza gestione di infrastruttura.
Cosa Imparerai
- Modelli disponibili in Workers AI: LLM, vision, speech, embedding
- Text generation con Llama 3.1 e streaming della risposta
- Vision models: analisi di immagini con LLaVA
- Speech-to-text con Whisper
- Text embeddings per search semantica
- AI Gateway: caching, rate limiting e osservabilità delle richieste AI
- Limiti, costi e strategie di ottimizzazione
Panoramica dei Modelli Disponibili
Workers AI offre una selezione di modelli open-source ottimizzati per l'inferenza
su hardware Cloudflare. I modelli vengono indicati con il prefisso
@cf/ o @hf/ (Hugging Face hosted):
| Categoria | Modelli principali | Caso d'uso |
|---|---|---|
| Text Generation | @cf/meta/llama-3.1-8b-instruct, @cf/mistral/mistral-7b-instruct-v0.2 | Chatbot, summary, Q&A, code generation |
| Text Generation (Large) | @cf/meta/llama-3.3-70b-instruct-fp8-fast | Reasoning complesso, analisi avanzata |
| Vision | @cf/llava-hf/llava-1.5-7b-hf, @cf/unum/uform-gen2-qwen-500m | Image captioning, visual Q&A |
| Speech-to-Text | @cf/openai/whisper, @cf/openai/whisper-large-v3-turbo | Trascrizione audio |
| Text Embeddings | @cf/baai/bge-base-en-v1.5, @cf/baai/bge-large-en-v1.5 | Semantic search, similarity, RAG |
| Image Classification | @cf/microsoft/resnet-50 | Classificazione immagini |
| Translation | @cf/meta/m2m100-1.2b | Traduzione 100+ lingue |
| Image Generation | @cf/stabilityai/stable-diffusion-xl-base-1.0 | Text-to-image |
Configurazione: Binding AI nel wrangler.toml
Per usare Workers AI basta aggiungere il binding [ai] nella configurazione:
# wrangler.toml
name = "ai-worker"
main = "src/worker.ts"
compatibility_date = "2024-09-23"
# Binding per Workers AI
[ai]
binding = "AI"
Il tipo TypeScript del binding va dichiarato nell'interfaccia Env:
// types.ts - dichiarazione del binding AI
interface Env {
AI: Ai; // Tipo fornito da @cloudflare/workers-types
}
Text Generation con Llama 3.1
Il caso d'uso più comune è la generazione di testo con un modello istruzione-risposta. Vediamo come implementare un endpoint di chat:
// src/worker.ts - endpoint di chat con Llama 3.1
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== 'POST' || new URL(request.url).pathname !== '/chat') {
return new Response('POST /chat required', { status: 400 });
}
const { messages, stream = false } = await request.json<ChatRequest>();
// Valida l'input
if (!Array.isArray(messages) || messages.length === 0) {
return Response.json({ error: 'messages array required' }, { status: 400 });
}
if (stream) {
// Streaming response: il modello restituisce token man mano
const aiStream = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
messages,
stream: true,
max_tokens: 1024,
temperature: 0.7,
});
// Trasforma lo stream AI in Server-Sent Events
return new Response(aiStream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
// Risposta sincrona: attende il completion completo
const result = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
messages,
max_tokens: 1024,
temperature: 0.7,
});
return Response.json({
response: (result as AiTextGenerationOutput).response,
usage: {
// Workers AI non espone ancora i token counts nella risposta base
model: '@cf/meta/llama-3.1-8b-instruct',
},
});
},
};
interface ChatRequest {
messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>;
stream?: boolean;
}
interface Env {
AI: Ai;
}
Un esempio più completo che implementa un assistente con system prompt e gestione degli errori robusta:
// src/assistant-worker.ts
const SYSTEM_PROMPT = `Sei un assistente tecnico esperto in cloud computing e edge computing.
Rispondi in modo conciso e tecnico. Se non conosci la risposta, dillo chiaramente.
Non inventare informazioni. Rispondi sempre in italiano a meno che l'utente non scriva in un'altra lingua.`;
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}
let body: AssistantRequest;
try {
body = await request.json<AssistantRequest>();
} catch {
return Response.json({ error: 'Invalid JSON body' }, { status: 400 });
}
if (!body.question?.trim()) {
return Response.json({ error: 'question field is required' }, { status: 400 });
}
try {
const result = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
messages: [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: body.question },
],
max_tokens: 2048,
temperature: 0.3, // Bassa temperatura per risposte piu deterministiche
}) as AiTextGenerationOutput;
return Response.json({
answer: result.response,
model: '@cf/meta/llama-3.1-8b-instruct',
timestamp: new Date().toISOString(),
});
} catch (err) {
console.error('AI inference error:', err);
return Response.json(
{ error: 'AI inference failed', details: (err as Error).message },
{ status: 500 }
);
}
},
};
interface AssistantRequest {
question: string;
}
interface Env {
AI: Ai;
}
Vision Models: Analisi di Immagini
I modelli vision permettono di analizzare immagini in input insieme a una domanda testuale. Questo è utile per moderazione di contenuti, estrazione di informazioni da documenti scansionati e accessibility features:
// src/vision-worker.ts - analisi immagini con LLaVA
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}
// Accetta immagine come Base64 o URL
const body = await request.json<VisionRequest>();
let imageData: number[];
if (body.imageUrl) {
// Scarica l'immagine e converti in array di byte
const imgResponse = await fetch(body.imageUrl);
if (!imgResponse.ok) {
return Response.json({ error: 'Failed to fetch image' }, { status: 400 });
}
const buffer = await imgResponse.arrayBuffer();
imageData = Array.from(new Uint8Array(buffer));
} else if (body.imageBase64) {
// Decodifica Base64
const binaryString = atob(body.imageBase64);
imageData = Array.from({ length: binaryString.length }, (_, i) =>
binaryString.charCodeAt(i)
);
} else {
return Response.json({ error: 'imageUrl or imageBase64 required' }, { status: 400 });
}
const prompt = body.prompt ?? 'Descrivi questa immagine in dettaglio in italiano.';
const result = await env.AI.run('@cf/llava-hf/llava-1.5-7b-hf', {
image: imageData,
prompt,
max_tokens: 512,
}) as AiTextGenerationOutput;
return Response.json({
description: result.response,
prompt,
model: '@cf/llava-hf/llava-1.5-7b-hf',
});
},
};
interface VisionRequest {
imageUrl?: string;
imageBase64?: string;
prompt?: string;
}
interface Env {
AI: Ai;
}
Speech-to-Text con Whisper
Workers AI include Whisper per la trascrizione audio. Il modello accetta audio
in formato ArrayBuffer e restituisce la trascrizione con timestamp
opzionali:
// src/transcribe-worker.ts - Speech-to-text con Whisper
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}
const contentType = request.headers.get('Content-Type') ?? '';
// Accetta audio come multipart/form-data o application/octet-stream
let audioBuffer: ArrayBuffer;
if (contentType.includes('multipart/form-data')) {
const formData = await request.formData();
const audioFile = formData.get('audio') as File | null;
if (!audioFile) {
return Response.json({ error: 'audio field required in form data' }, { status: 400 });
}
audioBuffer = await audioFile.arrayBuffer();
} else {
// Raw binary audio
audioBuffer = await request.arrayBuffer();
}
if (audioBuffer.byteLength === 0) {
return Response.json({ error: 'Empty audio data' }, { status: 400 });
}
// Limita a 25MB (limite Whisper)
if (audioBuffer.byteLength > 25 * 1024 * 1024) {
return Response.json({ error: 'Audio file too large (max 25MB)' }, { status: 413 });
}
const result = await env.AI.run('@cf/openai/whisper', {
audio: Array.from(new Uint8Array(audioBuffer)),
}) as AiSpeechRecognitionOutput;
return Response.json({
text: result.text,
wordCount: result.text.split(/\s+/).filter(Boolean).length,
model: '@cf/openai/whisper',
});
},
};
interface Env {
AI: Ai;
}
Text Embeddings per Semantic Search
Gli embeddings sono vettori numerici che rappresentano il significato semantico di un testo. Workers AI include modelli BGE ottimizzati per la ricerca semantica. Combinati con Vectorize (il vector database di Cloudflare), permettono di costruire pipeline RAG completamente all'edge:
// src/embedding-worker.ts - generazione embeddings + ricerca semantica
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/embed' && request.method === 'POST') {
const { texts } = await request.json<EmbedRequest>();
if (!Array.isArray(texts) || texts.length === 0) {
return Response.json({ error: 'texts array required' }, { status: 400 });
}
// BGE genera embedding di 768 dimensioni (base) o 1024 (large)
const result = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: texts,
}) as AiTextEmbeddingsOutput;
return Response.json({
embeddings: result.data,
dimensions: result.data[0]?.length ?? 0,
count: result.data.length,
model: '@cf/baai/bge-base-en-v1.5',
});
}
if (url.pathname === '/search' && request.method === 'POST') {
const { query, topK = 5 } = await request.json<SearchRequest>();
// 1. Genera l'embedding per la query
const queryEmbed = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: [query],
}) as AiTextEmbeddingsOutput;
// 2. Ricerca semantica su Vectorize
const results = await env.VECTORIZE.query(queryEmbed.data[0], {
topK,
returnMetadata: 'all',
});
return Response.json({
query,
results: results.matches.map((match) => ({
id: match.id,
score: match.score,
metadata: match.metadata,
})),
});
}
return new Response('Not Found', { status: 404 });
},
};
interface EmbedRequest {
texts: string[];
}
interface SearchRequest {
query: string;
topK?: number;
}
interface Env {
AI: Ai;
VECTORIZE: VectorizeIndex;
}
AI Gateway: Osservabilità e Caching
Cloudflare AI Gateway è un proxy trasparente che si posiziona davanti alle chiamate AI (sia Workers AI che provider esterni come OpenAI). Aggiunge caching semantico, rate limiting, logging e fallback automatico:
// src/worker-with-gateway.ts - Workers AI via AI Gateway
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { prompt } = await request.json<{ prompt: string }>();
// Usa il gateway invece del binding diretto
// Il gateway aggiunge: caching, retry, logging, rate limiting
const response = await fetch(
`https://gateway.ai.cloudflare.com/v1/${env.CF_ACCOUNT_ID}/${env.AI_GATEWAY_ID}/workers-ai/@cf/meta/llama-3.1-8b-instruct`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${env.CF_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages: [{ role: 'user', content: prompt }],
max_tokens: 512,
}),
}
);
if (!response.ok) {
const error = await response.text();
return Response.json({ error }, { status: response.status });
}
const result = await response.json();
return Response.json(result);
},
};
interface Env {
AI: Ai;
CF_ACCOUNT_ID: string;
CF_API_TOKEN: string;
AI_GATEWAY_ID: string;
}
In alternativa, puoi configurare il gateway direttamente nel binding AI del wrangler.toml:
# wrangler.toml con AI Gateway
[ai]
binding = "AI"
# Il gateway viene usato automaticamente per tutte le chiamate
# Configurato nella dashboard Cloudflare
Limiti e Considerazioni sui Costi
| Modello | Free (Neuron Units) | Paid ($ per 1K neurons) | Latenza tipica |
|---|---|---|---|
| Llama 3.1 8B | 10K NU/giorno gratis | $0.011 / 1K NU | ~500ms-2s (dipende da tokens) |
| Llama 3.3 70B FP8 | Incluso nel piano paid | $0.050 / 1K NU | ~1-5s |
| Whisper | 10K NU/giorno gratis | $0.011 / 1K NU | ~1-3s per minuto di audio |
| BGE embeddings | 10K NU/giorno gratis | $0.011 / 1K NU | ~50-200ms |
| Stable Diffusion XL | 10K NU/giorno gratis | $0.020 / immagine | ~3-10s |
Timeout e CPU Limits
Workers AI opera al di fuori del normale budget CPU del Worker (30s paid plan). Tuttavia, i modelli grandi come Llama 70B possono richiedere 5-15 secondi per rispondere. In questi casi, assicurati di usare lo streaming per restituire token man mano e non superare il timeout HTTP del client. Per inferenze lunghe considera di usare una coda (Workers Queues) e notificare il client al termine.
Pattern di Produzione: RAG all'Edge
Un pattern sempre più comune è il RAG (Retrieval-Augmented Generation) interamente all'edge: Vectorize per il retrieval, Workers AI per l'embedding e la generazione.
// src/rag-worker.ts - RAG completo all'edge
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== 'POST') return new Response('POST only', { status: 405 });
const { question } = await request.json<{ question: string }>();
// Step 1: Genera l'embedding della domanda
const queryEmbedding = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: [question],
}) as AiTextEmbeddingsOutput;
// Step 2: Recupera i chunk rilevanti dal vector store
const relevant = await env.DOCS.query(queryEmbedding.data[0], {
topK: 3,
returnMetadata: 'all',
});
// Step 3: Costruisce il contesto dai chunk recuperati
const context = relevant.matches
.map((m) => m.metadata?.['text'] as string ?? '')
.filter(Boolean)
.join('\n\n---\n\n');
// Step 4: Genera la risposta con il contesto
const answer = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
messages: [
{
role: 'system',
content: `Rispondi alla domanda basandoti SOLO sul contesto fornito.
Se il contesto non contiene informazioni sufficienti, dillo esplicitamente.
Contesto:
${context}`,
},
{ role: 'user', content: question },
],
max_tokens: 1024,
temperature: 0.1,
}) as AiTextGenerationOutput;
return Response.json({
question,
answer: answer.response,
sources: relevant.matches.map((m) => ({
id: m.id,
score: m.score,
title: m.metadata?.['title'],
})),
});
},
};
interface Env {
AI: Ai;
DOCS: VectorizeIndex;
}
Conclusioni e Prossimi Passi
Workers AI rappresenta un cambio di paradigma nell'accesso all'inferenza AI: nessuna GPU da gestire, nessun provider esterno da integrare, fatturazione al consumo con free tier generoso. La crescita del 4000% YoY a Q1 2026 riflette l'adozione rapida da parte di sviluppatori che cercano un percorso più semplice verso l'AI nei propri prodotti.
La combinazione Workers AI + Vectorize + Durable Objects (per la gestione dello storico delle conversazioni) permette di costruire assistenti AI completi interamente sulla piattaforma Cloudflare, senza dipendenze esterne.
Prossimi Articoli della Serie
- Articolo 6: Vercel Edge Runtime - Middleware Avanzato, Geolocalizzazione e A/B Testing: come Vercel usa l'edge runtime con Next.js per personalizzazione e feature flags.
- Articolo 7: Routing Geografico all'Edge - Personalizzazione Contenuti e Compliance GDPR: costruire logica geo-based senza toccare il server principale.
- Articolo 8: Cache API e Strategie di Invalidation in Cloudflare Workers: CDN programmabile con TTL, stale-while-revalidate e purge per chiave.

Commenti
Caricamento commenti...
Accedi per commentare