AI
22 gennaio 20253 min read

Pattern di integrazione AI che abbiamo imparato

Dopo mesi di sviluppo con LLM, ecco i pattern che funzionano davvero in produzione e quelli che sembrano buone idee ma non lo sono.

lintedhub

redazione tecnica
AI
LLM
OpenAI
Architecture
Patterns

Pattern di integrazione AI che abbiamo imparato

Integrare LLM in produzione è diverso dal fare demo. Ecco cosa abbiamo imparato dopo mesi di trial and error.

Pattern 1: Structured Output First

Il primo errore che tutti fanno: chiedere all'LLM di generare testo libero e poi provare a parsarlo.

Non fare questo:

const response = await openai.chat.completions.create({
  messages: [{ role: "user", content: "Analizza questo testo e dimmi il sentiment" }],
});

// Poi provare a parsare response.choices[0].message.content
// Buona fortuna.

Fai questo invece:

const response = await openai.chat.completions.create({
  messages: [{ role: "user", content: "Analizza questo testo" }],
  response_format: { type: "json_object" },
  functions: [{
    name: "analyze_sentiment",
    parameters: {
      type: "object",
      properties: {
        sentiment: { enum: ["positive", "negative", "neutral"] },
        confidence: { type: "number", minimum: 0, maximum: 1 },
        reasoning: { type: "string" }
      },
      required: ["sentiment", "confidence"]
    }
  }]
});

Structured output elimina un'intera categoria di bug.

Pattern 2: Retry con Backoff Esponenziale

Gli LLM falliscono. Rate limits, timeout, errori transienti. Non puoi ignorarlo.

async function callWithRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;

      const delay = Math.pow(2, i) * 1000 + Math.random() * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  throw new Error("Unreachable");
}

Pattern 3: Fallback Chain

Non dipendere da un solo provider o modello.

const providers = [
  { name: "openai-gpt4", fn: callOpenAI },
  { name: "anthropic-claude", fn: callAnthropic },
  { name: "openai-gpt35", fn: callOpenAI35 }, // cheaper fallback
];

async function callLLM(prompt: string) {
  for (const provider of providers) {
    try {
      return await provider.fn(prompt);
    } catch (error) {
      console.error(`${provider.name} failed, trying next...`);
      continue;
    }
  }
  throw new Error("All providers failed");
}

Pattern 4: Cache Semantico

Le stesse domande tornano. Non pagare due volte.

import { createHash } from "crypto";

function hashPrompt(prompt: string): string {
  return createHash("sha256").update(prompt).digest("hex");
}

async function cachedLLMCall(prompt: string) {
  const hash = hashPrompt(prompt);
  const cached = await cache.get(hash);

  if (cached) {
    return JSON.parse(cached);
  }

  const result = await callLLM(prompt);
  await cache.set(hash, JSON.stringify(result), { ex: 3600 });

  return result;
}

Per cache semantico più avanzato, considera embedding + vector search.

Pattern 5: Prompt Template con Validazione

I prompt sono codice. Trattali come tale.

import { z } from "zod";

const ReviewPromptSchema = z.object({
  code: z.string().min(1),
  language: z.enum(["typescript", "python", "go"]),
  context: z.string().optional(),
});

function buildReviewPrompt(input: z.infer<typeof ReviewPromptSchema>) {
  const validated = ReviewPromptSchema.parse(input);

  return `
Review this ${validated.language} code:

\`\`\`${validated.language}
${validated.code}
\`\`\`

${validated.context ? `Context: ${validated.context}` : ""}

Focus on:
1. Security vulnerabilities
2. Performance issues
3. Best practice violations
`;
}

Anti-Pattern: Fine-tuning Prematuro

"Il modello non fa esattamente quello che voglio, facciamo fine-tuning!"

No. Prima:

  1. Migliora il prompt
  2. Aggiungi esempi few-shot
  3. Usa structured output
  4. Considera RAG

Il fine-tuning ha senso solo quando hai provato tutto il resto e hai abbastanza dati di qualità.

Conclusione

L'integrazione AI in produzione richiede lo stesso rigore di qualsiasi altro sistema distribuito. Fallimenti, latenza variabile, costi imprevedibili: tutto va gestito esplicitamente.

I pattern qui sopra non sono esaustivi, ma coprono l'80% dei problemi che abbiamo incontrato.

Hai un progetto in mente?

Trasformiamo scelte tecniche complesse in sistemi in produzione.

Se stai valutando stack, architetture o integrazioni AI, parliamone. Niente pitch: una conversazione tecnica.

Parliamone