Foundation Models in iOS 26: Guida Pratica all'IA On-Device con Swift

Scopri come sfruttare il Foundation Models framework per portare l'IA direttamente nelle tue app iOS 26. Esempi pratici con @Generable, @Guide, tool calling e streaming — tutto on-device e senza costi.

Introduzione

Alla WWDC 2025, Apple ha fatto qualcosa che in molti aspettavano da tempo: ha aperto il Foundation Models framework, esponendo per la prima volta il modello linguistico on-device di Apple Intelligence agli sviluppatori di terze parti. Parliamo di un LLM da circa 3 miliardi di parametri, ottimizzato per girare direttamente sui dispositivi Apple senza inviare un singolo byte a server esterni.

Onestamente, quando l'ho provato per la prima volta, sono rimasto sorpreso da quanto fosse semplice da integrare.

Disponibile a partire da iOS 26, iPadOS 26, macOS 26 e visionOS 26, questo framework cambia le regole del gioco per tre motivi:

  • Privacy assoluta: ogni inferenza avviene localmente. I dati dell'utente non lasciano mai il dispositivo — punto.
  • Inferenza gratuita: niente costi per chiamata API, nessun abbonamento cloud, nessun rate limit. Il modello è già sul dispositivo come parte di Apple Intelligence.
  • Funzionamento offline: l'IA funziona anche senza connessione. Dall'aereo alla montagna, non importa dove sei.

Ma la cosa più interessante è che non si tratta di un semplice wrapper attorno a un modello generico. Apple ha progettato un'integrazione profonda con Swift, sfruttando i macro del compilatore per offrire una generazione strutturata type-safe che, a mio parere, non ha eguali nel panorama attuale. Con poche righe di codice puoi ottenere risposte testuali, generare oggetti Swift strutturati e persino estendere le capacità del modello con strumenti personalizzati.

In questa guida esploreremo ogni aspetto del framework, dai concetti fondamentali alle tecniche avanzate, con esempi pratici pronti per i vostri progetti. Allora, cominciamo.

Requisiti e Configurazione

Prima di mettere le mani sul codice, assicuriamoci di avere tutto il necessario:

  • Xcode 26 o successivo
  • macOS Tahoe (macOS 26) come sistema di sviluppo
  • Un dispositivo compatibile con Apple Intelligence (iPhone 16+, iPad con chip M1+, Mac con chip M1+)
  • Apple Intelligence attivata nelle impostazioni

Il modello on-device viene scaricato automaticamente quando l'utente attiva Apple Intelligence. Però — e questo è un punto importante — non è garantito che sia sempre disponibile: potrebbe essere ancora in download, oppure il dispositivo potrebbe non supportarlo. Per questo è fondamentale verificare la disponibilità prima di creare una sessione.

import FoundationModels

// Verificare la disponibilità del modello prima dell'uso
func verificaDisponibilita() async {
    let disponibilita = SystemLanguageModel.default.availability

    switch disponibilita {
    case .available:
        print("Modello disponibile e pronto all'uso")
    case .unavailable(.deviceNotEligible):
        print("Dispositivo non compatibile con Apple Intelligence")
    case .unavailable(.appleIntelligenceNotEnabled):
        print("Apple Intelligence non è attiva nelle impostazioni")
    case .unavailable(.modelNotReady):
        print("Il modello è in fase di download o preparazione")
    default:
        print("Modello non disponibile")
    }
}

Questa verifica è essenziale. Se il modello non è disponibile, la vostra app dovrebbe offrire un percorso alternativo o informare l'utente in modo chiaro e gentile — nessuno vuole un crash silenzioso.

La Prima Sessione: Generazione di Testo

Creare la prima interazione con il modello è sorprendentemente semplice. Il cuore del framework è la classe LanguageModelSession, che gestisce la comunicazione col modello e mantiene lo stato della conversazione.

import FoundationModels

// Creazione di una sessione base e generazione di testo
func generaTesto() async throws {
    // Creare una nuova sessione con il modello di default
    let sessione = LanguageModelSession()

    // Inviare un prompt e attendere la risposta completa
    let risposta = try await sessione.respond(to: "Spiegami il concetto di closure in Swift in modo semplice")

    // Accedere al testo generato
    print(risposta.content)
}

Il metodo respond(to:) è asincrono e restituisce un oggetto con il testo generato nel campo content. La chiamata usa await perché l'inferenza richiede tempo, anche se avviene in locale.

Un aspetto che vale la pena capire subito: la sessione è stateful. Mantiene un Transcript dell'intera conversazione. Ogni chiamata a respond(to:) aggiunge sia il messaggio dell'utente sia la risposta del modello alla trascrizione, quindi il modello ha sempre il contesto completo:

import FoundationModels

func conversazioneStateful() async throws {
    let sessione = LanguageModelSession()

    // Prima domanda
    let risposta1 = try await sessione.respond(to: "Qual è la capitale della Francia?")
    print(risposta1.content) // "La capitale della Francia è Parigi."

    // Seconda domanda - il modello ricorda il contesto precedente
    let risposta2 = try await sessione.respond(to: "Quanti abitanti ha?")
    print(risposta2.content) // Risponderà sugli abitanti di Parigi

    // Accedere alla trascrizione completa
    let trascrizione = sessione.transcript
    for entrata in trascrizione.entries {
        print("Ruolo: \(entrata.role), Contenuto: \(entrata.content)")
    }
}

Niente concatenazione manuale dei messaggi precedenti come tocca fare con tante API cloud. Il framework se ne occupa per noi, ed è una comodità non da poco.

Risposte in Streaming

Per app interattive, far aspettare l'utente fino alla risposta completa non è il massimo. Il Foundation Models framework supporta lo streaming, così puoi mostrare il testo man mano che viene generato — esattamente come nelle interfacce di ChatGPT o Claude.

import FoundationModels

// Generazione di testo in streaming
func generaTestoInStreaming() async throws {
    let sessione = LanguageModelSession()

    // Usare streamResponse invece di respond
    let stream = sessione.streamResponse(to: "Scrivi una breve storia su un robot che impara a cucinare")

    // Iterare sui frammenti parziali con un ciclo async for
    for try await risposta_parziale in stream {
        // Ogni frammento contiene il testo generato fino a quel punto
        print(risposta_parziale.content, terminator: "")
    }
}

Il metodo streamResponse(to:) restituisce un AsyncSequence di tipo LanguageModelSession.ResponseStream. Ogni elemento è un oggetto PartiallyGenerated col testo accumulato fino a quel momento. Si integra perfettamente con async/await di Swift.

L'integrazione con SwiftUI è particolarmente elegante. Ecco un esempio completo:

import SwiftUI
import FoundationModels

struct VistaStreaming: View {
    @State private var testoGenerato: String = ""
    @State private var staGenerando: Bool = false

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Assistente IA")
                .font(.title2)
                .bold()

            ScrollView {
                Text(testoGenerato)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }

            Button(staGenerando ? "Generazione in corso..." : "Genera Storia") {
                Task {
                    await generaStoria()
                }
            }
            .disabled(staGenerando)
        }
        .padding()
    }

    func generaStoria() async {
        staGenerando = true
        testoGenerato = ""

        let sessione = LanguageModelSession()
        let stream = sessione.streamResponse(to: "Racconta una fiaba breve ambientata in Italia")

        do {
            for try await parziale in stream {
                // Aggiornare l'interfaccia sul MainActor
                testoGenerato = parziale.content
            }
        } catch {
            testoGenerato = "Errore nella generazione: \(error.localizedDescription)"
        }

        staGenerando = false
    }
}

Le proprietà @State di SwiftUI aggiornano automaticamente la vista, quindi il testo apparirà progressivamente sullo schermo. L'effetto è fluido e naturale — gli utenti lo adorano.

Generazione Guidata con @Generable

Questa è, a mio avviso, la caratteristica più innovativa del framework: la generazione strutturata tramite decodifica vincolata. Invece di ottenere testo libero e poi impazzire col parsing, puoi far generare direttamente oggetti Swift tipizzati.

Come funziona? Il macro @Generable trasforma una struct in uno schema che il modello deve rispettare. A tempo di compilazione genera automaticamente uno schema JSON che viene passato al modello, il quale è costretto a produrre output conformi a quella struttura.

import FoundationModels

// Definire una struttura generabile
@Generable
struct RaccomandazioneFilm {
    var titolo: String
    var regista: String
    var anno: Int
    var genere: String
    var motivazione: String
}

// Usare la generazione strutturata
func ottieniRaccomandazione() async throws {
    let sessione = LanguageModelSession()

    // Il modello genererà direttamente un oggetto RaccomandazioneFilm
    let film: RaccomandazioneFilm = try await sessione.respond(
        to: "Consigliami un film italiano classico per una serata romantica",
        generating: RaccomandazioneFilm.self
    )

    print("Titolo: \(film.titolo)")
    print("Regista: \(film.regista)")
    print("Anno: \(film.anno)")
    print("Genere: \(film.genere)")
    print("Motivazione: \(film.motivazione)")
}

Il risultato è un oggetto Swift completamente tipizzato. Niente parsing JSON, niente errori di formattazione, niente validazione manuale. Il modello è vincolato a livello di token a produrre output conforme allo schema.

Un dettaglio importante: l'ordine delle proprietà conta. Il modello genera i valori in sequenza, dall'alto verso il basso. Questo significa che il valore di una proprietà successiva può essere influenzato da quelle già generate. Conviene ordinare le proprietà in modo logico — prima il contesto, poi le informazioni che ne dipendono.

Ecco un esempio più articolato con una domanda di quiz:

import FoundationModels

@Generable
struct DomandaQuiz {
    var argomento: String
    var domanda: String
    var opzioneA: String
    var opzioneB: String
    var opzioneC: String
    var opzioneD: String
    var rispostaCorretta: String
    var spiegazione: String
}

func generaDomandaQuiz() async throws {
    let sessione = LanguageModelSession()

    let quiz: DomandaQuiz = try await sessione.respond(
        to: "Genera una domanda di quiz sulla storia dell'Impero Romano",
        generating: DomandaQuiz.self
    )

    print("Argomento: \(quiz.argomento)")
    print("Domanda: \(quiz.domanda)")
    print("A) \(quiz.opzioneA)")
    print("B) \(quiz.opzioneB)")
    print("C) \(quiz.opzioneC)")
    print("D) \(quiz.opzioneD)")
    print("Risposta corretta: \(quiz.rispostaCorretta)")
    print("Spiegazione: \(quiz.spiegazione)")
}

@Generable supporta tipi primitivi come String, Int, Double, Bool, array di tipi Generable, tipi opzionali e anche enum conformi a String e Generable. Le strutture possono essere annidate per schemi più complessi.

Vincoli Precisi con @Guide

Se @Generable definisce la struttura dell'output, @Guide permette di aggiungere vincoli precisi ai singoli campi. E fidatevi, questo livello di controllo fa una differenza enorme quando si lavora su app reali.

I vincoli disponibili sono:

  • .count(): limita il numero di elementi in un array
  • .range(): definisce un intervallo numerico per Int o Double
  • .anyOf(): limita una stringa a un insieme predefinito di valori (tipo un enum)
  • description: fornisce una guida semantica al modello per indirizzare la generazione
import FoundationModels

@Generable
struct RicettaCucina {
    @Guide(description: "Nome della ricetta italiana tradizionale")
    var nome: String

    @Guide(.anyOf(["Antipasto", "Primo", "Secondo", "Contorno", "Dolce"]))
    var categoria: String

    @Guide(description: "Livello di difficoltà da 1 (facile) a 5 (esperto)", .range(1...5))
    var difficolta: Int

    @Guide(description: "Tempo di preparazione in minuti", .range(5...180))
    var tempoPreparazione: Int

    @Guide(description: "Lista degli ingredienti principali", .count(3...12))
    var ingredienti: [String]

    @Guide(description: "Passaggi dettagliati per la preparazione", .count(3...10))
    var passaggi: [String]

    @Guide(description: "Suggerimento per la presentazione del piatto")
    var suggerimentoPresentazione: String
}

func generaRicetta() async throws {
    let sessione = LanguageModelSession()

    let ricetta: RicettaCucina = try await sessione.respond(
        to: "Genera una ricetta per un primo piatto a base di pasta fresca fatto in casa",
        generating: RicettaCucina.self
    )

    print("=== \(ricetta.nome) ===")
    print("Categoria: \(ricetta.categoria)")
    print("Difficoltà: \(ricetta.difficolta)/5")
    print("Tempo: \(ricetta.tempoPreparazione) minuti")
    print("\nIngredienti:")
    for ingrediente in ricetta.ingredienti {
        print("  - \(ingrediente)")
    }
    print("\nPreparazione:")
    for (indice, passaggio) in ricetta.passaggi.enumerated() {
        print("  \(indice + 1). \(passaggio)")
    }
    print("\nPresentazione: \(ricetta.suggerimentoPresentazione)")
}

La combinazione di @Generable e @Guide è davvero potente. Vediamo un esempio ancora più interessante per un'app di pianificazione viaggi:

import FoundationModels

@Generable
struct AttivitaViaggio {
    @Guide(description: "Nome dell'attività o attrazione")
    var nome: String

    @Guide(description: "Descrizione breve dell'esperienza")
    var descrizione: String

    @Guide(description: "Costo stimato in euro", .range(0...500))
    var costoStimato: Int

    @Guide(.anyOf(["Mattina", "Pomeriggio", "Sera"]))
    var fasciaDiOrario: String
}

@Generable
struct GiornoViaggio {
    @Guide(description: "Numero del giorno nell'itinerario", .range(1...14))
    var giorno: Int

    @Guide(description: "Attività pianificate per la giornata", .count(2...4))
    var attivita: [AttivitaViaggio]

    @Guide(description: "Consiglio del giorno per il viaggiatore")
    var consiglio: String
}

@Generable
struct ItinerarioViaggio {
    @Guide(description: "Nome della città o regione da visitare")
    var destinazione: String

    @Guide(description: "Programma giornaliero dettagliato", .count(1...7))
    var giorni: [GiornoViaggio]

    @Guide(description: "Budget totale stimato in euro", .range(100...5000))
    var budgetTotale: Int
}

Come vedete, le strutture @Generable possono essere annidate: ItinerarioViaggio contiene un array di GiornoViaggio, che a sua volta contiene un array di AttivitaViaggio. Il modello genererà l'intera gerarchia rispettando tutti i vincoli. Piuttosto elegante, no?

Chiamata di Strumenti (Tool Calling)

Il modello on-device, per quanto potente, ha i suoi limiti: non conosce dati in tempo reale, non può accedere a database locali e non ha informazioni specifiche della vostra app. Il Tool Calling risolve questo problema in modo elegante, permettendo al modello di invocare funzioni Swift per ottenere le informazioni che gli servono.

Per creare uno strumento basta conformarsi al protocollo Tool:

import FoundationModels

// Definire la struttura dei parametri dello strumento
@Generable
struct ParametriMeteo {
    @Guide(description: "Nome della città per cui cercare il meteo")
    var citta: String
}

// Creare lo strumento conformandosi al protocollo Tool
struct StrumentoMeteo: Tool {
    // Nome identificativo dello strumento
    let name = "cerca_meteo"

    // Descrizione che aiuta il modello a capire quando usare lo strumento
    let description = "Cerca le condizioni meteo attuali per una città specificata"

    // Tipo dei parametri
    typealias Input = ParametriMeteo

    // Esecuzione dello strumento
    func call(input: ParametriMeteo) async throws -> String {
        // In un'app reale, qui si farebbe una chiamata API
        let datiMeteo = ottieniDatiMeteo(citta: input.citta)
        return "Meteo a \(input.citta): \(datiMeteo.temperatura)°C, \(datiMeteo.condizione). Umidità: \(datiMeteo.umidita)%"
    }

    // Funzione ausiliaria simulata
    private func ottieniDatiMeteo(citta: String) -> (temperatura: Int, condizione: String, umidita: Int) {
        // Simulazione - in produzione usereste un servizio meteo reale
        return (22, "Soleggiato con nuvole sparse", 65)
    }
}

Una volta definito, lo strumento si passa alla sessione:

import FoundationModels

func usaStrumentoMeteo() async throws {
    // Creare la sessione con gli strumenti disponibili
    let sessione = LanguageModelSession(
        tools: [StrumentoMeteo()]
    )

    // Il modello deciderà autonomamente se e quando usare lo strumento
    let risposta = try await sessione.respond(
        to: "Che tempo fa oggi a Roma? Consigliami come vestirmi."
    )

    print(risposta.content)
}

Ma le cose si fanno ancora più interessanti quando combini più strumenti. Ecco un esempio con un cercatore di ristoranti:

import FoundationModels

@Generable
struct ParametriCercaRistorante {
    @Guide(description: "Tipo di cucina desiderata")
    var tipoCucina: String

    @Guide(description: "Città in cui cercare")
    var citta: String

    @Guide(description: "Budget massimo per persona in euro", .range(10...200))
    var budgetMassimo: Int
}

struct CercaRistorante: Tool {
    let name = "cerca_ristorante"
    let description = "Cerca ristoranti in base a tipo di cucina, città e budget"

    typealias Input = ParametriCercaRistorante

    func call(input: ParametriCercaRistorante) async throws -> String {
        // In produzione, interroghereste un database o un'API
        return "Trattoria da Mario - Via Roma 42 - €35/persona - 4.5/5\nOsteria del Borgo - Piazza Dante 8 - €28/persona - 4.7/5"
    }
}

// Combinare più strumenti in una sessione
func assistenteViaggio() async throws {
    let sessione = LanguageModelSession(
        instructions: "Sei un assistente di viaggio esperto. Usa gli strumenti a disposizione per fornire informazioni precise.",
        tools: [StrumentoMeteo(), CercaRistorante()]
    )

    let risposta = try await sessione.respond(
        to: "Sono a Firenze, piove e cerco un ristorante di cucina toscana con budget di 40 euro a persona. Cosa mi consigli?"
    )

    print(risposta.content)
}

Il modello è sorprendentemente intelligente nella scelta degli strumenti: analizza la richiesta, decide quali invocare e con quali parametri, e combina i risultati in una risposta coerente. Tutto in automatico.

Istruzioni e Conversazioni Multi-Turno

Le istruzioni sono un concetto fondamentale del framework, e non vanno confuse con i semplici prompt utente. Definiscono il comportamento, la personalità e lo scopo del modello. Il modello è addestrato a dare priorità alle istruzioni rispetto ai prompt dell'utente — il che le rende perfette per i guardrail comportamentali.

import FoundationModels

func creaAssistenteSpecializzato() async throws {
    // Le istruzioni definiscono il ruolo e il comportamento del modello
    let sessione = LanguageModelSession(
        instructions: """
        Sei un sommelier virtuale esperto di vini italiani. Il tuo compito è:
        - Consigliare vini basandoti sulle preferenze dell'utente
        - Suggerire abbinamenti cibo-vino
        - Spiegare le caratteristiche dei vini in modo accessibile
        - Rispondere SOLO su argomenti legati al vino e alla gastronomia
        - Usare un tono professionale ma amichevole
        Se l'utente chiede qualcosa non correlato al vino, rispondi gentilmente
        che puoi aiutarlo solo con argomenti enogastronomici.
        """
    )

    // Prima domanda
    let r1 = try await sessione.respond(to: "Cosa abbino a un piatto di tagliatelle al ragù?")
    print("Sommelier: \(r1.content)")

    // Seconda domanda - il contesto è mantenuto
    let r2 = try await sessione.respond(to: "E se preferisco un bianco?")
    print("Sommelier: \(r2.content)")

    // Terza domanda - il modello ricorda tutta la conversazione
    let r3 = try await sessione.respond(to: "Qual è la temperatura ideale per servirlo?")
    print("Sommelier: \(r3.content)")
}

La Transcript API offre un controllo completo sulla cronologia. E la parte più utile? Puoi riprendere una conversazione da una trascrizione salvata in precedenza:

import FoundationModels

class GestoreConversazione {
    private var trascrizioneSalvata: Transcript?

    // Salvare lo stato della conversazione
    func salvaConversazione(sessione: LanguageModelSession) {
        trascrizioneSalvata = sessione.transcript
    }

    // Riprendere una conversazione precedente
    func riprendiConversazione() async throws {
        guard let trascrizione = trascrizioneSalvata else {
            print("Nessuna conversazione salvata")
            return
        }

        // Creare una nuova sessione ripristinando la trascrizione precedente
        let sessione = LanguageModelSession(
            instructions: "Sei un assistente utile e cordiale.",
            transcript: trascrizione
        )

        // Continuare la conversazione dal punto in cui era stata interrotta
        let risposta = try await sessione.respond(
            to: "Riprendiamo da dove eravamo rimasti"
        )
        print(risposta.content)
    }
}

Questa funzionalità è particolarmente utile per app che devono mantenere continuità tra sessioni diverse — pensate a un assistente che ricorda le preferenze dell'utente tra un utilizzo e l'altro.

Sicurezza e Guardrail

Apple ha integrato nel framework un sistema di sicurezza robusto e, dettaglio fondamentale, non disattivabile. Questo riflette la filosofia di Apple: indipendentemente dall'applicazione, il modello non genererà mai contenuti dannosi.

I guardrail operano su due livelli:

  • Input: il sistema analizza i prompt in ingresso per identificare tentativi di prompt injection, richieste inappropriate o manipolazioni.
  • Output: le risposte generate vengono verificate prima di essere restituite, filtrando contenuti potenzialmente dannosi.

Quando un guardrail si attiva, il framework lancia un errore specifico che va gestito:

import FoundationModels

func generaConGestioneErrori() async {
    let sessione = LanguageModelSession()

    do {
        let risposta = try await sessione.respond(to: "Scrivi un racconto breve sulla natura")
        print(risposta.content)

    } catch let errore as LanguageModelSession.GenerationError {
        switch errore {
        case .guardrailViolation(let dettagli):
            // Il contenuto ha violato i guardrail di sicurezza
            print("Contenuto non consentito: \(dettagli)")

        case .exceededContextWindowSize:
            // La conversazione ha superato la dimensione massima del contesto
            print("Conversazione troppo lunga. Iniziare una nuova sessione.")

        case .unsupportedLanguage:
            // La lingua richiesta non è supportata dal modello
            print("Lingua non supportata.")

        default:
            print("Errore di generazione: \(errore.localizedDescription)")
        }

    } catch {
        print("Errore imprevisto: \(error.localizedDescription)")
    }
}

Ricordatevi che questi guardrail non si possono disabilitare. È una scelta di design intenzionale di Apple. Gli sviluppatori devono progettare le proprie app tenendone conto e gestire con grazia i casi in cui vengono attivati.

La protezione contro il prompt injection è particolarmente importante. Se la vostra app passa input utente non filtrato al modello, il sistema rileverà tentativi di manipolazione come "Ignora le istruzioni precedenti" o istruzioni nascoste in testi apparentemente innocui. Le istruzioni della sessione hanno sempre priorità — un ulteriore livello di protezione che vi semplifica la vita.

Casi d'Uso Pratici

Bene, passiamo alla parte divertente. Il Foundation Models framework apre un ventaglio enorme di possibilità. Ecco alcuni dei casi d'uso più interessanti che ho visto (e provato).

Suggerimenti di Ricerca Intelligenti

Migliorare la ricerca nella propria app generando suggerimenti contestuali:

import FoundationModels

@Generable
struct SuggerimentiRicerca {
    @Guide(description: "Lista di suggerimenti di ricerca pertinenti", .count(3...6))
    var suggerimenti: [String]
}

func generaSuggerimenti(query: String, contesto: String) async throws -> [String] {
    let sessione = LanguageModelSession(
        instructions: "Genera suggerimenti di ricerca pertinenti per un'app di e-commerce di moda italiana."
    )

    let risultato: SuggerimentiRicerca = try await sessione.respond(
        to: "L'utente sta cercando: '\(query)'. Contesto: \(contesto). Suggerisci ricerche correlate.",
        generating: SuggerimentiRicerca.self
    )

    return risultato.suggerimenti
}

Dialoghi per Personaggi di Gioco

Creare NPC con dialoghi dinamici è un caso d'uso che mi entusiasma particolarmente:

import FoundationModels

@Generable
struct DialogoNPC {
    @Guide(description: "La battuta del personaggio, in carattere con la sua personalità")
    var battuta: String

    @Guide(.anyOf(["amichevole", "sospettoso", "entusiasta", "triste", "arrabbiato"]))
    var tono: String

    @Guide(description: "Suggerimento per l'animazione facciale del personaggio")
    var espressione: String
}

func generaDialogoPersonaggio(nomePersonaggio: String, situazione: String) async throws -> DialogoNPC {
    let sessione = LanguageModelSession(
        instructions: """
        Sei \(nomePersonaggio), un fabbro nano in un gioco fantasy. Parli con accento toscano,
        sei burbero ma dal cuore d'oro. Ami parlare delle tue creazioni e del tuo mestiere.
        Le tue battute devono essere brevi (massimo 2 frasi) e in carattere.
        """
    )

    return try await sessione.respond(
        to: situazione,
        generating: DialogoNPC.self
    )
}

Riassunto di Contenuti

Generare riassunti strutturati di articoli o documenti:

import FoundationModels

@Generable
struct RiassuntoArticolo {
    @Guide(description: "Riassunto in una frase del contenuto principale")
    var riassuntoBrieve: String

    @Guide(description: "Punti chiave dell'articolo", .count(3...5))
    var puntiChiave: [String]

    @Guide(.anyOf(["Tecnologia", "Scienza", "Politica", "Economia", "Sport", "Cultura", "Altro"]))
    var categoria: String

    @Guide(description: "Tempo stimato di lettura in minuti", .range(1...30))
    var tempoLettura: Int
}

func riassumi(testo: String) async throws -> RiassuntoArticolo {
    let sessione = LanguageModelSession()

    return try await sessione.respond(
        to: "Riassumi il seguente articolo:\n\n\(testo)",
        generating: RiassuntoArticolo.self
    )
}

Guida Contestuale in App

Offrire assistenza intelligente basata sul contesto attuale dell'utente:

import FoundationModels

func guidaContestuale(schermataAttuale: String, azioneUtente: String) async throws -> String {
    let sessione = LanguageModelSession(
        instructions: """
        Sei l'assistente integrato dell'app FotoCreativa, un'app di editing fotografico.
        Fornisci suggerimenti brevi e pratici basati su cosa sta facendo l'utente.
        Rispondi sempre in modo conciso, massimo 2-3 frasi.
        """
    )

    let risposta = try await sessione.respond(
        to: "L'utente è nella schermata '\(schermataAttuale)' e sta provando a \(azioneUtente). Aiutalo."
    )

    return risposta.content
}

Vale la pena menzionare che app reali stanno già sfruttando queste capacità. VLLO, la popolare app di editing video, usa il Foundation Models framework insieme al framework Vision per analizzare anteprime video e suggerire automaticamente musica di sottofondo e sticker adatti a ogni scena — tutto on-device, senza mai toccare il cloud.

Best Practice per lo Sviluppo

Dopo aver sperimentato parecchio con il framework, ecco le best practice che considero essenziali per costruire app robuste.

Gestione Asincrona e Thread

Tutte le operazioni di inferenza vanno eseguite in modo asincrono. Per gli aggiornamenti UI, assicuratevi di usare @MainActor:

import FoundationModels
import SwiftUI

@MainActor
class ViewModelAssistente: ObservableObject {
    @Published var risposta: String = ""
    @Published var staCaricando: Bool = false
    @Published var errore: String?

    private var sessione: LanguageModelSession?

    func inizializza() {
        guard SystemLanguageModel.default.availability == .available else {
            errore = "Il modello IA non è disponibile su questo dispositivo"
            return
        }

        sessione = LanguageModelSession(
            instructions: "Sei un assistente utile e conciso."
        )
    }

    func inviaMessaggio(_ testo: String) async {
        guard let sessione = sessione else { return }

        staCaricando = true
        errore = nil

        do {
            let risultato = try await sessione.respond(to: testo)
            risposta = risultato.content
        } catch {
            self.errore = "Errore: \(error.localizedDescription)"
        }

        staCaricando = false
    }
}

Riutilizzo delle Sessioni

Creare una nuova LanguageModelSession per ogni richiesta è uno spreco. Riutilizzate le sessioni quando possibile, specialmente nelle conversazioni multi-turno:

// CORRETTO: riutilizzare la sessione
class ServizioIA {
    private let sessione = LanguageModelSession()

    func rispondi(a domanda: String) async throws -> String {
        let risposta = try await sessione.respond(to: domanda)
        return risposta.content
    }
}

// SBAGLIATO: creare una nuova sessione per ogni richiesta
func rispondiMale(a domanda: String) async throws -> String {
    let sessione = LanguageModelSession() // Nuova sessione ogni volta!
    let risposta = try await sessione.respond(to: domanda)
    return risposta.content
}

Limitare la Lunghezza dell'Input

Il modello on-device ha una finestra di contesto limitata. Limitate la lunghezza del testo inviato per evitare errori:

import FoundationModels

func elaboraTestoSicuro(_ testo: String) async throws -> String {
    let limiteCaratteri = 4000
    let testoTroncato = String(testo.prefix(limiteCaratteri))

    let sessione = LanguageModelSession()

    do {
        let risposta = try await sessione.respond(to: testoTroncato)
        return risposta.content
    } catch let errore as LanguageModelSession.GenerationError {
        if case .exceededContextWindowSize = errore {
            let nuovaSessione = LanguageModelSession()
            let testoRidotto = String(testo.prefix(2000))
            let risposta = try await nuovaSessione.respond(to: testoRidotto)
            return risposta.content
        }
        throw errore
    }
}

Testing e Debug con Mock

Il modello on-device potrebbe non essere disponibile negli ambienti CI/CD (e quasi sicuramente non lo sarà). Create dei mock per i test:

// Protocollo per l'astrazione del servizio IA
protocol ServizioLinguaggio {
    func genera(prompt: String) async throws -> String
}

// Implementazione reale
class ServizioLinguaggioReale: ServizioLinguaggio {
    private let sessione = LanguageModelSession()

    func genera(prompt: String) async throws -> String {
        let risposta = try await sessione.respond(to: prompt)
        return risposta.content
    }
}

// Mock per i test
class ServizioLinguaggioMock: ServizioLinguaggio {
    var rispostaSimulata: String = "Risposta simulata per i test"

    func genera(prompt: String) async throws -> String {
        return rispostaSimulata
    }
}

Usare un protocollo per astrarre il servizio permette di iniettare facilmente un mock durante i test. La vostra suite di test potrà girare in qualsiasi ambiente, senza dipendere dalla disponibilità del modello.

Conclusione

Il Foundation Models framework è, senza girarci troppo intorno, un punto di svolta per lo sviluppo di app intelligenti nell'ecosistema Apple. Per la prima volta abbiamo accesso a un LLM potente che gira interamente on-device, senza compromessi sulla privacy e senza costi di infrastruttura.

Ricapitoliamo i punti chiave:

  • Privacy by design: nessun dato lascia il dispositivo. L'inferenza è completamente locale.
  • Gratuità totale: niente costi per token, niente abbonamenti API, niente limiti.
  • Funzionamento offline: le funzionalità IA sono disponibili ovunque, anche senza rete.
  • Type-safety con i macro Swift: @Generable e @Guide offrono un'integrazione con Swift che nessun altro framework IA può eguagliare.
  • Tool Calling: estendi le capacità del modello collegandolo ai dati e servizi della tua app.
  • Sicurezza integrata: guardrail non disattivabili che proteggono utenti e sviluppatori.

Siamo solo all'inizio. Man mano che Apple migliorerà il modello e amplierà il framework, le possibilità cresceranno enormemente. Il mio consiglio? Iniziate a sperimentare adesso: importate il framework, create la vostra prima LanguageModelSession, definite le vostre struct @Generable e scoprite come l'IA on-device può trasformare l'esperienza utente delle vostre app.

Il futuro dello sviluppo iOS è intelligente, privato e locale. E con il Foundation Models framework, quel futuro è già qui.

Sull'Autore Editorial Team

Our team of expert writers and editors.