Apple Foundation Models: LLM on-device en tu app iOS#
Por qué Apple metió un LLM dentro de iOS#
Hace un año, en la WWDC 2025, Apple abrió el acceso a su propio modelo de lenguaje on-device — el mismo que alimenta Apple Intelligence — mediante un nuevo framework llamado Foundation Models. Sin API key, sin factura por token, sin que los datos del usuario salgan del dispositivo. El modelo ya está en el teléfono, y con iOS 26 / macOS 26 / iPadOS 26 / visionOS 26 puedes llamarlo desde tu propio código con pocas líneas de Swift.
Esto no es marketing: cambió qué funciones tiene sentido construir en una app. Soy desarrollador iOS en activo, y este artículo no repite el keynote: son las APIs que he probado, los límites reales, y una opinión honesta sobre cuándo el LLM on-device es la decisión correcta y cuándo no.
A mediados de 2026 el framework ya pasó por otro ciclo: en la WWDC 2026 Apple presentó la tercera generación de Apple Foundation Models (AFM 3) y, por separado, la posibilidad de conectar proveedores de LLM externos a la misma API de Swift — tema de la sesión "Bring an LLM provider to the Foundation Models framework". Es una señal clara: Foundation Models deja de ser un envoltorio sobre el modelo propio de Apple para convertirse en una interfaz unificada de inferencia on-device e híbrida.
El modelo base on-device es denso, de unos 3.000 millones de parámetros (Apple lo llama AFM 3 Core en la generación actual). Está afinado para un conjunto concreto de tareas: resumen, extracción de entidades, comprensión y refinamiento de texto, diálogos cortos, generación creativa breve. No es un chatbot de propósito general ni un sustituto de buscador — la propia documentación de Apple es explícita: no está pensado como fuente de conocimiento general sobre el mundo.
En algunos dispositivos — los que tienen 12 GB de RAM, es decir los iPhone tope de gama — hay disponible un modelo ampliado: AFM 3 Core Advanced, unos 20.000 millones de parámetros con arquitectura dispersa que activa solo entre 1.000 y 4.000 millones por petición. Para el desarrollador esto significa que la misma línea de código puede ejecutarse en un "cerebro" distinto según el dispositivo: no esperes calidad idéntica entre un iPhone 15 Pro y un iPhone 17 Pro.
Trabajar con el modelo gira en torno a dos tipos:
SystemLanguageModel— el punto de entrada al modelo, su disponibilidad y modos especializados (useCase, por ejemplo.contentTaggingpara etiquetado y extracción de entidades listos para usar).LanguageModelSession— la sesión de conversación: es a través de ella que se envían las peticiones, con historial (transcript), instrucciones y herramientas.
Gating de disponibilidad: tres razones de "no funciona" — y no son el mismo problema#
El error más común es tratar la no disponibilidad como un único estado. En la práctica, SystemLanguageModel.default.availability devuelve una de tres razones distintas, y confundirlas es la forma más segura de lanzar una función que se siente rota:
import FoundationModels
func checkFoundationModelsAvailability() -> String {
switch SystemLanguageModel.default.availability {
case .available:
return "✅ Modelo listo para usar"
case .unavailable(.deviceNotEligible):
// A13 o más antiguo — esta función nunca aparecerá en este dispositivo
return "❌ El dispositivo no soporta Apple Intelligence"
case .unavailable(.appleIntelligenceNotEnabled):
// Dispositivo compatible, pero Apple Intelligence está apagado
return "⚠️ Activa Apple Intelligence en Ajustes"
case .unavailable(.modelNotReady):
// El modelo aún se está descargando — estado temporal
return "⏳ El modelo se está descargando, inténtalo más tarde"
case .unavailable(let reason):
return "❓ No disponible: \(reason)"
}
}deviceNotEligible es permanente: hardware antiguo, hay que ocultar la función sin insistir. appleIntelligenceNotEnabled lo controla el usuario — un aviso educado para activarlo está bien. modelNotReady es temporal — hay que reintentar, no mostrar un error. Mezclar estos tres escenarios en una pantalla genérica de "función no disponible" es la fuente más común de quejas de "la IA está rota".
Plataformas soportadas a fecha de esta publicación: iOS, iPadOS, macOS, visionOS. watchOS y tvOS no forman parte del framework.
Salida estructurada: @Generable en vez de parsear JSON a mano#
Antes de Foundation Models, obtener una salida estructurada de un LLM significaba pedirle que devolviera JSON, parsearlo a mano y rezar para que no añadiera texto suelto antes o después de las llaves. El macro @Generable elimina toda esa clase de bugs: genera un esquema en tiempo de compilación, y el modelo garantiza devolver un valor del tipo Swift real.
import FoundationModels
@Generable
struct TripSummary {
@Guide(description: "Título corto del viaje, hasta 6 palabras")
var title: String
@Guide(description: "Momentos clave del viaje", .count(3))
var highlights: [String]
@Guide(description: "Puntuación general del estado de ánimo del 1 al 5")
var moodScore: Int
}
func summarizeTrip(notes: String) async throws -> TripSummary {
let session = LanguageModelSession(
instructions: "Eres un asistente que resume brevemente notas de viaje."
)
let response = try await session.respond(
to: "Notas del usuario: \(notes)",
generating: TripSummary.self
)
return response.content
}@Guide añade a un campo no solo una descripción para el modelo, sino restricciones programáticas — .count(), .maximumCount() y otras que acotan el espacio de generación de verdad.
Streaming y llamadas a herramientas#
Para una UI que responda al instante, la salida estructurada puede transmitirse en partes — el tipo generado por @Generable obtiene automáticamente una versión "parcial" con campos opcionales:
func streamTripSummary(notes: String) async throws {
let session = LanguageModelSession()
let stream = session.streamResponse(
to: "Resume este viaje a partir de las notas: \(notes)",
generating: TripSummary.self
)
for try await partial in stream {
// partial: TripSummary.PartiallyGenerated — campos opcionales,
// se rellenan de forma progresiva, útil para actualizar la UI en vivo
print(partial)
}
}Y si el modelo necesita datos que no están en el prompt — por ejemplo, la ciudad actual del usuario — puede llamar a una herramienta que describas mediante el protocolo Tool:
struct CurrentLocationTool: Tool {
let name = "getCurrentLocation"
let description = "Devuelve la ciudad actual del usuario"
@Generable
struct Arguments {
@Guide(description: "Si se necesita precisión a nivel de barrio")
var preciseArea: Bool
}
func call(arguments: Arguments) async throws -> ToolOutput {
let city = arguments.preciseArea ? "Lisboa, Alfama" : "Lisboa"
return ToolOutput(city)
}
}
let session = LanguageModelSession(
tools: [CurrentLocationTool()],
instructions: "Usa getCurrentLocation cuando necesites la ciudad del usuario."
)El propio modelo decide si llamar a la herramienta según su descripción — un modelo mental distinto al enrutamiento manual de intents de los pipelines de NLP clásicos.
Dónde está el límite: contexto, tareas y hardware#
La restricción práctica principal es el tamaño de la ventana de contexto. Según la nota técnica TN3193 de Apple, el límite de una sesión ronda los 4.096 tokens para toda la conversación, incluyendo instrucciones, historial y espacio para la respuesta. Es órdenes de magnitud menor que en modelos en la nube como GPT o Claude: documentos largos, un historial extenso o un prompt few-shot voluminoso no caben en una sesión — hay que resumir, recortar o crear una sesión nueva.
Si te acercas al límite, el modelo puede fallar antes de superarlo — quizá no tenga espacio para generar la respuesta. Regla práctica: reserva margen para la respuesta, no gastes toda la ventana en la entrada.
La segunda restricción es la calidad: un modelo de 3.000 millones (o 20.000 dispersos) de parámetros acota y transforma texto que ya tienes, no responde preguntas abiertas tipo "cuéntame sobre...". La tercera es la fragmentación del hardware: AFM 3 Core Advanced no está disponible en todos los dispositivos con Apple Intelligence, solo donde hay RAM suficiente. Diseña tu UX de forma que degradar al modelo base no rompa el flujo.
LLM on-device o la nube: una tabla para elegir con honestidad#
| Criterio | On-device (Foundation Models) | LLM en la nube (API) |
|---|---|---|
| Privacidad de datos | Los datos nunca salen del dispositivo | Los datos van al servidor del proveedor |
| Coste | Gratis, sin factura por token | Pago por token/petición |
| Funcionamiento offline | Funciona sin red | Requiere conexión |
| Latencia | Baja, sin ida y vuelta por red | Depende de la red y la cola del servidor |
| Calidad en tareas difíciles | Limitada (resumen, extracción, texto corto) | Sustancialmente mayor en razonamiento y conocimiento |
| Ventana de contexto | ~4.096 tokens por sesión | Decenas o cientos de miles de tokens |
| Disponibilidad | Solo dispositivos compatibles con Apple Intelligence | Cualquier dispositivo con internet |
| Personalización del modelo | Sin fine-tuning de pesos | Fine-tuning, system prompts, elección de modelo |
Conclusión práctica de la tabla: el LLM on-device es una herramienta para tareas concretas y acotadas, con altos requisitos de privacidad y coste cero — no un reemplazo universal de una API en la nube.
No toda tarea necesita un LLM — y un checklist para empezar#
Trabajando en MeteoHealth (/projects/meteohealth/) — una app que relaciona el clima con cómo te sientes — elegí un motor estadístico clásico en el dispositivo en lugar de ML o LLM. Las correlaciones entre presión, humedad y síntomas se calculan con métodos estadísticos deterministas: el resultado es reproducible, explicable y no cuesta ni un token de texto generativo.
No es un compromiso por falta de recursos, sino una elección deliberada de herramienta según la tarea. Un LLM, incluso on-device, añade imprevisibilidad justo donde se necesita lógica transparente: "si la presión bajó X hPa, el riesgo de migraña subió". Foundation Models encaja en tareas donde la entrada es texto no estructurado y la salida es estructurada o también texto: resumir notas, extraer entidades, generar títulos, asistentes conversacionales cortos. Para cálculos deterministas sobre series numéricas no hace falta, y meter un LLM donde basta la aritmética es complejidad sin beneficio.
Checklist antes de añadir Foundation Models#
- Comprueba las tres razones de
unavailablepor separado — piden UX distinta, no una única pantalla de error. - Usa
@Generable/@Guideen vez de parsear JSON a mano — elimina toda una clase de bugs. - Presupuesta la ventana de contexto (~4.096 tokens) desde el principio, no cuando las sesiones empiecen a fallar.
- No esperes conocimiento enciclopédico de un modelo de 3B — es una herramienta de transformación de texto, no un chatbot general.
- Antes de recurrir a un LLM, pregúntate si código normal sin modelo generativo ya resuelve el problema. A veces la estadística gana a la generación.
Foundation Models no es hype por hype: un LLM gratuito, privado y offline en pocas líneas de Swift es algo que se esperaba desde hace años. Por eso importa usarlo con precisión, donde realmente supera a las alternativas, no porque "nosotros también tenemos IA".
Enlaces útiles:



