AI è il team, non un pulsante chatbot
Prompt caching che abbatte i costi di un ordine di grandezza, tool use che trasforma Claude in un operatore, server MCP che collegano il modello al tuo stack e l'audit trail che ti fa passare la compliance. Il sistema operativo, non la slide di marketing.
Perché questo stack
Il prompt caching è la leva che la maggior parte dei team si perde
Un system prompt stabile e un documento di riferimento lungo vengono messi in cache dopo la prima richiesta. Le richieste successive pagano circa il dieci percento del costo dei token di input. Progettiamo la struttura del prompt in modo che il caching si applichi a ogni chiamata significativa, e questo sposta un'integrazione Claude da "funzionalità costosa" a "sempre attiva".
Il tool use trasforma Claude in un membro del team
Un tool è una funzione tipizzata che il modello può chiamare. Definiamo i tool come schemi TypeScript, il modello ne sceglie uno, il nostro handler lo esegue e il risultato torna nella conversazione. Il pattern funziona per leggere il database, fare una ricerca, chiamare un'operazione Stripe o invocare un altro agente.
I server MCP collegano il modello al tuo stack
Model Context Protocol è il modo in cui un agente parla al tuo codice applicativo senza scaffolding REST. Abbiamo rilasciato server MCP per Design System Ops, Dev Ops e Social Ops; lo stesso pattern vale per i tuoi tool interni. Il modello usa il tuo codice, il tuo codice non parsa il modello.
Streaming, ma fatto bene
Streaming basato su SSE con gestione corretta della backpressure, rendering parziale dei messaggi, error boundary che non fanno crollare la UI quando il modello sbaglia a metà stream e parsing strutturato degli eventi così una chiamata a tool dentro uno stream non è un hack di parsing.
Cost accounting e governance dal primo giorno
Ogni richiesta Claude è loggata con modello, token di input, token di output, cache hit ratio e costo. Rate limit per tenant, budget per utente e una dashboard finanza che non richiede uno step di export su Excel.
Cosa sviluppiamo con questa tecnologia
Integrazione tipizzata Anthropic SDK
Client TypeScript con retry, backoff consapevole del rate-limit, gestione strutturata degli errori, selezione del modello per task (Opus / Sonnet / Haiku).
Architettura di prompt caching
Cache breakpoint piazzati sulle parti stabili (system prompt, doc di riferimento, esempi lunghi), cache hit ratio misurabile nella dashboard.
Framework di tool use
Tool definiti come schemi TypeScript con validazione Zod. La catena di handler è transazionale con il database, così una scrittura innescata dal modello fa rollback su validazione fallita.
Server MCP per il tuo stack
Un server Model Context Protocol che espone le funzionalità della tua applicazione a Claude (leggi DB, scrivi, chiama API, fa partire workflow). Versionato, governato, con audit log.
UI streaming
Trasporto Server-Sent Events, rendering parziale dei messaggi, rendering inline delle tool call, error boundary recuperabili.
Orchestrazione di agenti
Agenti multi-step che pianificano, eseguono tool, osservano i risultati e decidono cosa fare dopo. Loop con limite massimo di step e condizioni di uscita esplicite.
Persistenza delle conversazioni
Archivio delle conversazioni su database con sommarizzazione per thread lunghi, gestione della context window consapevole del budget di token.
Token accounting e report di costo
Logging per richiesta di modello, token, costo, cache hit ratio. Aggregati per tenant, budget per utente, reportistica mensile collegata alla dashboard di amministrazione.
Mitigazione prompt injection
Isolamento del system prompt, fencing dell'input non fidato, validazione dell'output contro schema atteso, gestione del refusal per istruzioni avversariali.
Pipeline di validazione output
Output strutturati validati contro schemi Zod, re-prompt in caso di parse fallito, dead-letter logging per i casi irrecuperabili.
Policy di selezione del modello
Haiku per task ad alto volume e basso costo, Sonnet per il livello standard, Opus per il reasoning difficile. La policy vive nel codice, non nella testa di qualcuno.
Strategia di fallback
Degrado graduale quando Anthropic non è disponibile, retry con backoff, fallback opzionale cross-provider dove il caso d'uso lo permette.
Una chiamata Claude tipizzata con prompt caching, tool use e streaming
Una funzione fa tutto il flusso. System prompt e documento di riferimento vengono messi in cache alla prima richiesta. Il modello può chiamare un tool `lookupCustomer` il cui risultato torna nella conversazione. La risposta va in stream al client via SSE.
La maggior parte delle "integrazioni Claude" collega un input di chatbot all'API e chiude lì. L'AI in produzione ha un'altra faccia. La funzione qui sotto è quella che rilasciamo: una chiamata tipizzata a messages.create, system prompt e documento di riferimento in cache, un tool lookupCustomer che il modello può chiamare, la risposta che torna al client via Server-Sent Events.
1. Il client tipizzato
L'Anthropic SDK è incapsulato in un client sottile che aggiunge retry, mappatura degli errori e un'union tipizzata per il modello. La selezione del modello avviene al punto di chiamata; il client non sceglie.
// src/lib/claude/client.ts
import Anthropic from '@anthropic-ai/sdk'
export const claude = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY!,
maxRetries: 3,
})
export type ClaudeModel =
| 'claude-opus-4-7'
| 'claude-sonnet-4-6'
| 'claude-haiku-4-5'
export const MODEL_BY_TASK: Record<'reasoning' | 'standard' | 'cheap', ClaudeModel> = {
reasoning: 'claude-opus-4-7',
standard: 'claude-sonnet-4-6',
cheap: 'claude-haiku-4-5',
}
2. La definizione del tool
Un tool è una funzione tipizzata con uno schema JSON. Zod produce lo schema; l'handler gira nel normale flusso transazionale dell'applicazione. Il modello non tocca mai il database direttamente.
// src/lib/claude/tools/lookup-customer.ts
import { z } from 'zod'
import { zodToJsonSchema } from 'zod-to-json-schema'
import { adminClient } from '@/lib/supabase/admin'
export const lookupCustomerInput = z.object({
email: z.string().email(),
})
export const lookupCustomerTool = {
name: 'lookupCustomer',
description:
'Find a customer by email. Returns the canonical record or null.',
input_schema: zodToJsonSchema(lookupCustomerInput, {
target: 'jsonSchema7',
}) as Record<string, unknown>,
} as const
export async function runLookupCustomer(
input: z.infer<typeof lookupCustomerInput>,
): Promise<unknown> {
const { data, error } = await adminClient
.from('customers')
.select('id, name, plan, status, created_at')
.eq('email', input.email)
.maybeSingle()
if (error) throw error
return data ?? null
}
3. La chiamata in streaming con prompt caching
System prompt e documento di riferimento portano entrambi cache_control. Dopo la prima richiesta, le chiamate successive pagano la tariffa cache-read (circa il dieci percento del prezzo standard di input) su quei blocchi. I messaggi della conversazione invece non vengono messi in cache perché cambiano a ogni turno.
// src/lib/claude/run.ts
import { claude, MODEL_BY_TASK } from './client'
import { lookupCustomerTool, runLookupCustomer, lookupCustomerInput } from './tools/lookup-customer'
import { systemPrompt } from './prompts/system'
import { referenceDoc } from './prompts/reference'
interface RunInput {
conversation: { role: 'user' | 'assistant'; content: string }[]
tenantId: string
}
export async function* runClaude(input: RunInput) {
const stream = await claude.messages.create({
model: MODEL_BY_TASK.standard,
max_tokens: 4096,
system: [
{
type: 'text',
text: systemPrompt,
cache_control: { type: 'ephemeral' },
},
{
type: 'text',
text: referenceDoc,
cache_control: { type: 'ephemeral' },
},
],
messages: input.conversation,
tools: [lookupCustomerTool],
stream: true,
})
for await (const chunk of stream) {
yield chunk
// Quando il modello decide di chiamare un tool, lo stream emette un
// content_block_stop con il payload tool_use. Eseguiamo il tool e
// continuiamo la conversazione con il risultato.
if (
chunk.type === 'content_block_stop' &&
'content_block' in chunk &&
chunk.content_block?.type === 'tool_use' &&
chunk.content_block.name === 'lookupCustomer'
) {
const parsed = lookupCustomerInput.parse(chunk.content_block.input)
const result = await runLookupCustomer(parsed)
yield {
type: 'tool_result' as const,
tool_use_id: chunk.content_block.id,
content: JSON.stringify(result),
}
}
}
}
4. La route Server-Sent Events
Il generator dello streaming va in pipe in un trasporto Server-Sent Events così il browser riceve i token parziali man mano che arrivano. La route handler scrive anche una riga di log per chiamata, così cost accounting e audit non sono opzionali.
// app/api/chat/route.ts
import { runClaude } from '@/lib/claude/run'
import { logClaudeRequest } from '@/lib/claude/logging'
import { getServerSession } from '@/lib/auth/server'
export const runtime = 'nodejs'
export async function POST(request: Request) {
const session = await getServerSession()
if (!session) return new Response('unauthorised', { status: 401 })
const { conversation } = await request.json()
const start = Date.now()
const stream = new ReadableStream({
async start(controller) {
let inputTokens = 0
let outputTokens = 0
let cacheHits = 0
try {
for await (const chunk of runClaude({
conversation,
tenantId: session.tenantId,
})) {
if (chunk.type === 'message_start') {
inputTokens = chunk.message.usage.input_tokens
cacheHits = chunk.message.usage.cache_read_input_tokens ?? 0
}
if (chunk.type === 'message_delta') {
outputTokens = chunk.usage.output_tokens
}
controller.enqueue(
new TextEncoder().encode(`data: ${JSON.stringify(chunk)}\n\n`),
)
}
} finally {
controller.close()
await logClaudeRequest({
tenantId: session.tenantId,
userId: session.userId,
model: 'claude-sonnet-4-6',
inputTokens,
outputTokens,
cacheHits,
durationMs: Date.now() - start,
})
}
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
})
}
5. Cosa ti compra questo
Il prompt caching trasforma una chiamata da cinque centesimi in una da mezzo centesimo. Il tool use trasforma il modello da un'interfaccia di chat a un operatore che legge il tuo database, chiama le tue API, fa partire i tuoi workflow. L'audit log dà al team finanza un numero su cui pianificare e alla security un record da firmare. Tutto sta in quattro file perché l'SDK è progettato bene e l'applicazione smette di combatterlo.
Questo è cosa significa "AI è il team" nel codice, non nella slide di marketing.
Domande frequenti
Perché Claude rispetto a OpenAI o Google?
Per coding, reasoning strutturato e agenti pesanti di tool use, Claude vince costantemente i nostri benchmark interni al prezzo di riferimento. L'Anthropic SDK ha oggi il supporto più maturo per prompt caching e tool use. Per casi specifici (image gen, audio) usiamo lo strumento giusto per il lavoro; Claude è il default.
Quanto può davvero risparmiare il prompt caching?
Su un workflow di agente tipico con system prompt da 5.000 token e un doc di riferimento da 20.000 token, vediamo il costo di input scendere di circa l'80-90 percento una volta che il caching va a regime. Il numero esatto dipende dal cache hit ratio e dalla struttura dei turni di conversazione; lo misuriamo per progetto e lo riportiamo mensilmente.
Qual è la differenza tra tool use e function calling?
Stessa idea, parole diverse. Anthropic la chiama tool use, OpenAI la chiama function calling. I pattern sono concettualmente intercambiabili; noi usiamo il formato structured tool di Anthropic che ci dà parametri tipizzati via JSON Schema e gestione pulita dello stop reason.
Costruite server MCP?
Sì, ne abbiamo rilasciati per i nostri team Ops interni (Design System Ops, Dev Ops, Social Ops). Per i progetti cliente il pattern è lo stesso: scegli le operazioni che Claude deve poter chiamare, le esponi via MCP, versioni lo schema, applichi governance e audit.
Come gestite l'ottimizzazione dei costi?
Tre leve, in ordine. Primo, prompt caching: il singolo guadagno più grande, su ogni progetto. Secondo, selezione del modello: Haiku per i task economici, Sonnet per il livello standard, Opus per i casi difficili. Terzo, trimming del contesto: sommarizziamo i turni vecchi, scartiamo i risultati di tool stantii, teniamo il working set nel budget.
Sicurezza e prompt injection?
Isolamento del system prompt grazie al design dell'API Anthropic, fencing dell'input non fidato nel codice (non concateniamo mai input utente nel system prompt), validazione dell'output contro uno schema Zod e gestione del refusal per istruzioni avversariali. Loggato e verificabile per ogni richiesta.
Anthropic API diretta, Bedrock o Vertex?
Anthropic API diretta per la maggior parte dei progetti (cadenza di rilascio dei modelli più veloce, feature parity piena). Bedrock quando il buyer richiede data residency AWS, Vertex quando il buyer è su Google Cloud. Il codice applicativo resta portabile; cambia solo la costruzione del client.
Raccontaci cosa stai costruendo con Claude
Una call di scoping, un numero concreto nella prima risposta, niente recite da agenzia. Un'integrazione Claude in produzione in due-quattro settimane.