
-
Profundización en la estructura Foundation Models
Sube de nivel con la estructura Foundation Models. Aprende cómo funciona la generación guiada y usa guías, expresiones regulares y esquemas de generación para obtener respuestas estructuradas personalizadas. Te mostraremos cómo usar la llamada de herramientas para permitir que el modelo acceda de forma autónoma a información externa y realice acciones, para una experiencia personalizada. Para aprovechar al máximo este video, recomendamos ver primero “Conoce la estructura Foundation Models”.
Capítulos
- 0:00 - Introducción
- 0:49 - Sesiones
- 7:57 - Generable
- 14:29 - Esquemas dinámicos
- 18:10 - Llamada de herramientas
Recursos
- Generate dynamic game content with guided generation and tools
- Human Interface Guidelines: Generative AI
Videos relacionados
WWDC25
-
Buscar este video…
Hola, soy Louis. Hoy veremos cómo aprovechar al máximo la estructura Foundation Models.
Como ya sabrás, Foundation Models te brinda acceso directo a un gran modelo de lenguaje en el dispositivo, con una conveniente API de Swift. Está disponible en macOS, iPadOS, iOS y visionOS. Y como se ejecuta en el dispositivo, para usarlo en tu proyecto solo debes importarlo. En este video, aprenderemos cómo funcionan las sesiones con Foundation Models, cómo usar Generable para obtener una salida estructurada, cómo obtener una salida estructurada con esquemas dinámicos definidos en tiempo de ejecución y a usar la llamada de herramientas para permitir que el modelo llame a tus funcionalidades personalizadas. Empecemos por algo sencillo: generar texto con una sesión.
Creé este juego de pixel art sobre una cafetería y creo que sería divertido usar Foundation Models para generar diálogos de juego y otros contenidos para que se sienta más vivo.
Podemos hacer que el modelo responda a la pregunta de un jugador, y así nuestro barista ofrece un diálogo único. Para ello, crearemos una LanguageModelSession con instrucciones personalizadas. Esto permite indicar al modelo cuál es su propósito en esta sesión y, para el prompt, usaremos lo que dijo el usuario. Eso todo lo que necesitas para crear un nuevo elemento de juego bastante divertido. Preguntémosle al barista: "¿Hace cuánto trabajas aquí?" y dejemos que él responda.
Se creó por completo en el dispositivo. Increíble. Pero ¿cómo funciona realmente? Conozcamos más sobre cómo Foundation Models genera texto y qué tener en cuenta. Cuando ejecutas respond(to:) en una sesión, primero toma las instrucciones de tu sesión y el prompt, en este caso lo que escribió el usuario, y convierte ese texto en tokens. Los tokens son subcadenas, a veces una palabra, pero suelen ser solo unos pocos caracteres. Un gran modelo de lenguaje toma una secuencia de tokens como entrada y genera una nueva secuencia de tokens como salida. Olvídate de los tokens exactos con los que opera Foundation Models, la API se encarga de abstraerlos por ti. Pero es importante entender que los tokens no son gratis. Cada token en tus instrucciones y prompt añade latencia adicional. Antes de que el modelo pueda empezar a producir tokens de respuesta, primero debe procesar todos los de entrada, y generar tokens también tiene un costo computacional, por lo que las salidas más largas tardan más en generarse.
Una LanguageModelSession tiene control de estado. Cada ejecución de respond(to:) se registra en la transcripción.
La transcripción incluye todos los prompts y respuestas de una sesión determinada.
Esto puede ser útil para depurar o incluso mostrarlo en la interfaz de usuario.
Pero una sesión tiene un límite de tamaño. Si haces muchas solicitudes, el prompt es muy largo u obtienes grandes salidas, puedes alcanzar el límite de contexto.
Si tu sesión supera el tamaño de contexto, generará un error que debes estar preparado para detectar. En el juego, si hablas con un personaje y hay un error, la conversación termina. Es una pena, ¡recién lo estaba conociendo! Por suerte, hay formas de solucionarlo.
Puedes ejecutar “catch” y “exceededContextWindowSize”.
Cuando lo hagas, podrás iniciar una nueva sesión sin historial. Pero en mi juego eso significaría que el personaje de repente olvida toda la conversación.
También puedes elegir parte de la transcripción de tu sesión actual para trasladarla a la nueva sesión.
Puedes tomar el contenido de la transcripción de una sesión y condensarlo en una nueva serie de entradas.
Para el diálogo de nuestro juego, podemos tomar la primera entrada de la transcripción, que son las instrucciones, y la última entrada, que es la última respuesta correcta. Y cuando lo pasamos a una nueva sesión, nuestro personaje está listo para charlar un rato más. Pero ten en cuenta que la transcripción incluye las instrucciones iniciales como primera entrada. Al transferir una transcripción para nuestro personaje del juego, queremos incluir esas instrucciones.
Incluir solo algunos fragmentos relevantes puede ser una solución sencilla y eficaz, pero a veces no es tan simple. Imaginemos una transcripción con más entradas. Siempre debes comenzar transfiriendo las instrucciones, pero muchas partes de la transcripción podrían ser relevantes, así que, en este caso, podríamos resumir la transcripción.
Puedes hacerlo con alguna biblioteca externa, o incluso resumir partes de la transcripción con Foundation Models.
Eso es lo que puedes hacer con la transcripción de una sesión. Ahora veamos cómo se generan las respuestas. En el juego, cuando se acerca al barista, el jugador puede hacerle cualquier pregunta. Pero si empiezas dos partidas nuevas y haces la misma pregunta en cada una, puedes tener respuestas diferentes. ¿Cómo es posible? Bueno, ahí es donde entra en juego el muestreo.
Cuando el modelo genera su salida, lo hace un token a la vez, creando una distribución para la probabilidad de cualquier token dado. Por defecto, Foundation Models elige tokens dentro de un rango de probabilidad. A veces puede comenzar diciendo “Ah” y otras veces puede elegir “Bueno” como primer token. Esto sucede con cada token que se genera. Elegir un token es un “muestreo”, y el comportamiento por defecto es el muestreo aleatorio. Obtener resultados variados es ideal para casos de uso como un juego. Pero a veces puede que desees una misma salida, como cuando escribes un demo que se debe repetir. GenerationOptions te permite controlar el método de muestreo. Puedes configurarlo como “greedy” para tener la misma salida. Cuando esté listo, tendrás la misma salida para el mismo prompt. Eso si tu sesión tiene la misma configuración. Eso sí, ten en cuenta que esto solo es válido para una versión determinada del modelo del dispositivo. Si el modelo se actualiza con el SO, tu prompt tendrá una salida diferente, incluso si usas un muestreo greedy. También puedes jugar con “temperature” para el muestreo aleatorio. Por ejemplo, ajusta “temperature” a 0.5 para obtener una salida que solo varíe un poco o a un valor más alto para obtener salidas muy diferentes para el mismo prompt. Además, ten en cuenta que, al introducir el texto en el prompt, puede que el idioma no sea compatible.
En este caso, puedes usar “unsupportedLanguageOrLocale” para detectar el error. Puede ser la manera ideal de mostrar un mensaje personalizado en tu IU. También hay una API para verificar si el modelo admite un determinado idioma. Sirve para comprobar si ese idioma es compatible y para mostrar una exención de responsabilidad cuando no lo es. Bien, esas son las sesiones. Puedes hacer un prompt y se guardará en la transcripción. También es opcional configurar el parámetro de muestreo para controlar la aleatoriedad de la salida de la sesión. Pero, aloquémonos. Cuando el jugador camina, podemos generar PNJ, personajes no jugables, con Foundation Models. Sin embargo, esta vez queremos una salida más compleja. En lugar de solo texto, nos gustaría que el PNJ tenga un nombre y pida un café. Generable nos puede ayudar con esto. Puede ser difícil obtener salidas estructuradas a partir de un gran modelo de lenguaje. Puedes indicarle los campos específicos que esperas y usar un código de análisis para extraerlos, pero es difícil de controlar y muy delicado, y puede que no siempre dé las claves válidas, lo que haría que todo el método fallara. Por suerte, Foundation Models tiene una API mucho mejor: Generable. En tu estructura, puedes aplicar la macro @Generable. ¿Qué es Generable? ¿Es siquiera una palabra? Bueno, sí lo es.
Generable permite al modelo generar datos estructurados con tipos Swift. Crea un esquema en la compilación, usado por el modelo para producir la estructura esperada. La macro también genera un inicializador, que se ejecuta automáticamente al hacer una solicitud a una sesión.
Así podemos generar instancias de nuestra estructura. Como antes, ejecutaremos “respond” en nuestra sesión, pero usaremos “generating” para indicar al modelo qué tipo generar. Foundation Models incluso incluirá automáticamente detalles sobre tu tipo Generable en el prompt, en un formato específico con el que se entrenó el modelo. No necesitas especificar los campos de tu tipo Generable. ¡En nuestro juego, ahora tendremos fantásticos encuentros con PNJ generados!
En realidad, Generable es más potente de lo que parece. A nivel básico, usa decodificación restringida, una técnica para que el modelo genere texto siguiendo un esquema específico. Recuerda, el esquema que genera la macro. Como vimos antes, un LLM genera tokens, que luego se transforman en texto. Y, con Generable, ese texto se analiza automáticamente de forma segura. Los tokens se generan en un bucle, denominado bucle de decodificación. Sin decodificación restringida, el modelo podría alucinar nombres de campos no válidos. Como firstName en vez de name, Lo que impediría su análisis en el tipo PNJ.
Pero con la decodificación restringida, evitas que el modelo cometa estos errores estructurales. Para cada token generado, hay una distribución de todos los tokens en el vocabulario del modelo. Y la decodificación restringida oculta los tokens que no son válidos. Así, en lugar de elegir cualquier token, el modelo solo puede elegir tokens válidos según el esquema.
Y todo esto sin necesidad de preocuparte por analizar manualmente la salida del modelo. Lo que significa que puedes dedicarte a lo importante, ¡como hablar con clientes virtuales en tu cafetería! Generable es la mejor manera de obtener salidas del LLM en el dispositivo. Y puede hacer mucho más. No solo puedes usarlo en estructuras, sino también en enumeraciones. ¡Usémoslo para que nuestros encuentros sean más dinámicos! Aquí añadí una enumeración “Encounter”, con dos casos. La enumeración incluso puede contener valores asociados en sus casos, así que usémosla para generar un pedido de café o para que alguien quiera hablar con el gerente.
¡Veamos qué encontramos en nuestro juego ahora!
Vaya, alguien realmente necesita un café.
Sabemos que no todos los clientes son tan fáciles de tratar, así que mejoremos esto agregando niveles a los PNJ. Generable es compatible con los tipos de Swift más comunes por defecto, incluido Int. Así que vamos a agregar una propiedad de nivel, pero no queremos generar ningún entero. Para mantener el nivel dentro de un rango específico, podemos usar @Guide. Podemos usar la macro Guide en nuestra propiedad y pasar un rango. Recuerda, el modelo usará decodificación restringida para garantizar un valor en este rango.
Ya que estamos, agreguemos también una matriz de atributos al PNJ.
Podemos volver a usar @Guide, pero para especificar que queremos tres atributos para esta matriz en el PNJ. Importante, las propiedades de tu tipo Generable se generan en el orden en que se declaran en el código fuente. Aquí se generará en el siguiente orden: name, level, atribute y encounter.
Este orden es importante si esperas que el valor de una propiedad se vea influenciado por otra. Y si no quieres esperar a que se genere la salida completa, puedes transmitir propiedad por propiedad. El juego es muy divertido. Está casi listo para compartirlo con mis amigos. Pero noto que los nombres de los PNJ no son los que tenía en mente. Preferiría que tengan nombre y apellido.
Podemos usar @Guide para esto, pero esta vez solo daremos una descripción en lenguaje natural. Podemos indicar que debe ser un “nombre completo”. Y esta es otra manera de hacer un prompt. En lugar de tener que describir diferentes propiedades en tu prompt, puedes hacerlo en tu tipo Generable y le da al modelo una relación más sólida con eso a lo que se refieren estas descripciones. Si damos un vistazo al juego ahora, veremos estos nuevos nombres en acción. Aquí tienes una lista de todas las guías que puedes aplicar a diferentes tipos.
Con tipos numéricos comunes, como int, puedes especificar el mínimo, el máximo o un rango. Y con Array, puedes controlar el recuento o especificar guías en el tipo de elemento de la matriz.
Para String, puedes dejar que el modelo elija de una matriz con anyOf o restringirlo a un patrón de expresión regular.
Una guía de patrones de expresiones regulares es muy poderosa. Quizás estés familiarizado con el uso de una expresión regular para hacer coincidir texto. Con Foundation Models, puedes usar un patrón de expresiones regulares para definir la estructura de una cadena. Por ejemplo, puedes restringir el nombre a un conjunto de prefijos. e incluso puedes usar la sintaxis del generador de expresiones regulares
Si esto te devuelve el interés por las expresiones regulares, te recomiendo que veas “Conoce Swift Regex” de hace unos años. En resumen, Generable es una macro que puedes aplicar a estructuras y enumeraciones, y te brinda una forma segura de obtener salidas estructuradas del modelo. No necesitas preocuparte por el análisis y, para obtener salidas aún más específicas, puedes aplicar guías a tus propiedades. Generable es fantástico cuando conoces la estructura en tiempo de compilación. La macro genera el esquema por ti y obtienes una instancia de tu tipo como salida. Pero a veces solo conocemos una estructura en tiempo de ejecución. Ahí es donde los esquemas dinámicos pueden ayudar. Añadí un creador de niveles a mi juego, donde los jugadores pueden definir entes de manera dinámica para encontrar mientras recorren el juego. Por ejemplo, un jugador puede crear una estructura de acertijo, donde un acertijo tiene una pregunta y respuestas de opción múltiple. Si conociéramos esta estructura en tiempo de compilación, podríamos definir una estructura Generable para ello, pero el creador de niveles permite crear cualquier estructura que el jugador pueda imaginar.
Podemos usar DynamicGenerationSchema para crear un esquema en tiempo de ejecución. Un esquema dinámico tiene una lista de propiedades, como una estructura en tiempo de compilación. Podemos agregar un creador de niveles que pueda recibir la entrada de un jugador.
Cada propiedad tiene un nombre y su propio esquema, que define su tipo. Puedes usar el esquema para cualquier tipo Generable, incluidos los integrados, como String.
Un esquema dinámico puede tener una matriz, en la que se especifica un esquema para su elemento. Y lo que es más importante, puede tener referencias a otros esquemas dinámicos. Aquí nuestra matriz puede hacer referencia a un esquema personalizado que también se define en tiempo de ejecución.
A partir de la entrada del usuario, podemos crear un esquema de acertijo con dos propiedades. “Question”, que es una propiedad de cadena. Segundo, una propiedad de matriz, llamada Answer. Y luego creamos la respuesta. Esta tiene una propiedad de cadena y booleana. No olvides que la propiedad de respuestas del acertijo se refiere al esquema de respuesta por su nombre. Luego podemos crear las instancias de DynamicGenerationSchema. Cada esquema dinámico es independiente. Lo que significa que el esquema dinámico del enigma no contiene el de la respuesta. Antes de poder realizar inferencias, debemos convertir nuestros esquemas dinámicos en uno validado. Esto puede generar errores si hay inconsistencias en los esquemas dinámicos, como referencias de tipo que no existen.
Y una vez que tengamos un esquema validado, podremos crear una sesión como siempre. Pero esta vez, el tipo de salida es una instancia GeneratedContent que contiene los valores dinámicos. Puedes consultar esto con los nombres de propiedad de tus esquemas dinámicos. Recuerda, Foundation Models usará generación guiada para asegurarse de que la salida coincida con tu esquema. Nunca formará un campo inesperado. Así que, aunque sea dinámico, no debes preocuparte por analizar manualmente la salida.
Ahora, cuando el jugador encuentra un PNJ, el modelo genera este contenido dinámico que mostraremos en una IU dinámica. Veamos qué descubrimos. Soy oscuro o claro, amargo o dulce, te despierto y doy calor, ¿qué soy? ¿Café o chocolate caliente? Creo que la respuesta es café. ¡Correcto! Creo que mis jugadores se divertirán mucho creando todo tipo de niveles divertidos. En resumen, con la macro Generable, es fácil generar una salida estructurada a partir de un tipo Swift definido en tiempo de compilación. Y, en segundo plano, Foundation Models se encarga del esquema y de convertir GeneratedContent en una instancia de tu propio tipo. Los esquemas dinámicos son muy parecidos, pero te ofrecen mucho más control. Controlas todo el esquema en tiempo de ejecución y tienes acceso directo a GeneratedContent. Ahora, hablemos de la llamada de herramientas, que puede permitir que el modelo use tus funciones. Tengo pensado crear un DLC, contenido descargable, para personalizar más el juego. Con la llamada de herramientas, el modelo puede recuperar información de forma autónoma. Sería divertido integrar los contactos y el calendario del jugador. No suelo hacer eso con un modelo basado en servidor, a mis jugadores no les gustaría que el juego subiera esos datos personales. Pero como todo está en el dispositivo con Foundation Models, puedes hacerlo sin perder la privacidad.
Definir una herramienta es muy fácil con el protocolo Tool. Comienza dándole un nombre y una descripción. Esto es la API colocará en el prompt para permitir que el modelo decida cuándo y con qué frecuencia usar tu herramienta.
Es mejor que el nombre sea corto, pero legible como texto en inglés. Evita abreviar y que la descripción sea demasiado larga, ni expliques ninguna de las implementaciones. Porque recuerda, estas cadenas se colocan tal cual en tu prompt. Cadenas más largas significan más tokens, lo que puede aumentar la latencia. En su lugar, considera usar un verbo en el nombre, como findContact. Y tu descripción debe tener aproximadamente una frase. Como siempre, hay que probar diversas variantes para ver cuál funciona mejor con tu herramienta.
Luego, podemos definir la entrada para la herramienta. Quiero que la herramienta obtenga contactos de una determinada generación, como los millennials. El modelo elegirá un caso divertido en función del estado del juego, y puedo agregar la estructura Arguments y hacerla Generable. Cuando el modelo decida usar esta herramienta, generará los argumentos de entrada. Usar Generable garantiza que tu herramienta siempre obtenga argumentos de entrada válidos. Así que no tomará una generación diferente, como la alfa, que no es compatible con el juego.
Luego puedo implementar la función Call. El modelo usará esta función cuando decida usar la herramienta. En este ejemplo, usaremos la API Contacts, y nos mostrará el nombre de un contacto para esa consulta.
Para usar la herramienta, la pasaremos en el inicializador de sesión. Luego, el modelo usará la herramienta cuando quiera esa información adicional. Esto es mejor que conseguir el contacto nosotros mismos, porque el modelo solo usará la herramienta cuando necesite un PNJ específico y puede elegir argumentos de entrada divertidos según el estado del juego, como la generación para el PNJ.
Ten en cuenta que esto usa la API Contacts normal que ya conoces. Cuando se inicia la herramienta por primera vez, le pedirá al jugador los permisos habituales. Incluso si el jugador no quiere dar acceso a sus contactos, Foundation Models aún puede generar contenido como antes, pero si dan acceso, lo hacemos más personalizado.
Caminemos un poco por el juego hasta que nos encontremos con otro PNJ. Y esta vez, nos dará un nombre de mis contactos. ¡Hola, Naomy! Veamos lo que tiene que decir. No sabía que te gustaba el café. Ten en cuenta que LanguageModelSession toma una instancia de una herramienta, lo que significa que tú controlas el ciclo de vida de la herramienta. La instancia de esta herramienta permanece igual durante toda la sesión. En este ejemplo, como solo obtenemos un carácter aleatorio con FindContactsTool, es posible que obtengamos el mismo contacto algunas veces. Ahora hay varias Naomy en el juego. Eso no está bien, solo puede haber una. Para arreglarlo, podemos realizar un seguimiento de los contactos que el juego ya usó. Podemos añadir estado a FindContactTool. Pero primero debemos convertir FindContactTool en una clase, de modo que pueda modificar su estado desde el método Call. Luego podemos dar seguimiento de los contactos seleccionados y en el método Call no volvemos a elegir el mismo.
Ahora los PNJ se llaman como mis contactos, pero hablarles aún se siente bien. Terminemos con otra herramienta, esta vez para acceder a mi calendario.
Para esta herramienta, tomaremos el nombre del contacto del diálogo que vimos en el juego. Y cuando el modelo use la herramienta, le permitiremos generar un día, mes y año para obtener eventos con este contacto, y usaremos esta herramienta en la sesión para el diálogo de PNJ.
Así que ahora, si le pregunto al PNJ de Naomy "¿Qué tal?", puede responder con eventos reales que planeamos.
Vaya, es como si hablara con la verdadera Naomy.
Veamos más de cerca cómo funciona la llamada de herramientas. Empezamos pasando la herramienta al inicio de la sesión, junto con las instrucciones. Y para este ejemplo, incluimos información como la fecha de hoy. Luego, cuando el usuario inicia la sesión, el modelo puede analizar el texto. En este ejemplo, el modelo entiende que el prompt solicita eventos, por lo que usar la herramienta de calendario tiene sentido.
Para usar la herramienta, el modelo primero genera los argumentos de entrada. En este caso, el modelo necesita generar la fecha para obtener los eventos. El modelo puede relacionar la información de las instrucciones y el prompt, y entender cómo completar los argumentos en función de eso. Así que, en este ejemplo, puede inferir lo que significa “mañana” con la fecha de hoy en las instrucciones. Una vez que se genera la entrada para tu herramienta, se usa el método Call. Es tu momento de brillar, tu herramienta puede hacer todo lo que quiera, pero recuerda que la sesión espera la respuesta de tu herramienta antes de generar más salidas.
Luego, la salida de tu herramienta se coloca en la transcripción, al igual que la salida del modelo, y en función de la salida de la herramienta, el modelo puede generar una respuesta al prompt. Ten en cuenta que una herramienta se puede usar varias veces para una sola solicitud. Y cuando eso sucede, tu herramienta se usa en paralelo. Ten esto en cuenta al acceder a los datos desde el método Call de tu herramienta. Muy bien, eso fue divertido. Nuestro juego ahora genera contenido aleatorio, con mis contactos personales y mi calendario. Todo sin que mis datos salgan de mi dispositivo. En resumen, la llamada de herramientas permite que el modelo use tu código para acceder a datos externos en una solicitud. Puede ser información privada, como contactos, o incluso datos externos de fuentes en la Web. Recuerda que una herramienta se puede usar varias veces dentro de una solicitud determinada. El modelo determina esto en función de su contexto. Las herramientas también se pueden usar en paralelo y pueden almacenar estado. Eso fue bastante. Quizás deberías tomar un café antes de hacer cualquier otra cosa. Para más información, puedes consultar el video dedicado a la ingeniería de prompts, que incluye consejos de diseño y seguridad. Y si quieres conocer a la verdadera Naomy, mira el video “Code-along”. Espero que te diviertas tanto como yo con Foundation Models. Gracias por ver este video.
-
-
1:05 - Prompting a session
import FoundationModels func respond(userInput: String) async throws -> String { let session = LanguageModelSession(instructions: """ You are a friendly barista in a world full of pixels. Respond to the player’s question. """ ) let response = try await session.respond(to: userInput) return response.content }
-
3:37 - Handle context size errors
var session = LanguageModelSession() do { let answer = try await session.respond(to: prompt) print(answer.content) } catch LanguageModelSession.GenerationError.exceededContextWindowSize { // New session, without any history from the previous session. session = LanguageModelSession() }
-
3:55 - Handling context size errors with a new session
var session = LanguageModelSession() do { let answer = try await session.respond(to: prompt) print(answer.content) } catch LanguageModelSession.GenerationError.exceededContextWindowSize { // New session, with some history from the previous session. session = newSession(previousSession: session) } private func newSession(previousSession: LanguageModelSession) -> LanguageModelSession { let allEntries = previousSession.transcript.entries var condensedEntries = [Transcript.Entry]() if let firstEntry = allEntries.first { condensedEntries.append(firstEntry) if allEntries.count > 1, let lastEntry = allEntries.last { condensedEntries.append(lastEntry) } } let condensedTranscript = Transcript(entries: condensedEntries) // Note: transcript includes instructions. return LanguageModelSession(transcript: condensedTranscript) }
-
6:14 - Sampling
// Deterministic output let response = try await session.respond( to: prompt, options: GenerationOptions(sampling: .greedy) ) // Low-variance output let response = try await session.respond( to: prompt, options: GenerationOptions(temperature: 0.5) ) // High-variance output let response = try await session.respond( to: prompt, options: GenerationOptions(temperature: 2.0) )
-
7:06 - Handling languages
var session = LanguageModelSession() do { let answer = try await session.respond(to: userInput) print(answer.content) } catch LanguageModelSession.GenerationError.unsupportedLanguageOrLocale { // Unsupported language in prompt. } let supportedLanguages = SystemLanguageModel.default.supportedLanguages guard supportedLanguages.contains(Locale.current.language) else { // Show message return }
-
8:14 - Generable
@Generable struct NPC { let name: String let coffeeOrder: String } func makeNPC() async throws -> NPC { let session = LanguageModelSession(instructions: ...) let response = try await session.respond(generating: NPC.self) { "Generate a character that orders a coffee." } return response.content }
-
9:22 - NPC
@Generable struct NPC { let name: String let coffeeOrder: String }
-
10:49 - Generable with enum
@Generable struct NPC { let name: String let encounter: Encounter @Generable enum Encounter { case orderCoffee(String) case wantToTalkToManager(complaint: String) } }
-
11:20 - Generable with guides
@Generable struct NPC { @Guide(description: "A full name") let name: String @Guide(.range(1...10)) let level: Int @Guide(.count(3)) let attributes: [Attribute] let encounter: Encounter @Generable enum Attribute { case sassy case tired case hungry } @Generable enum Encounter { case orderCoffee(String) case wantToTalkToManager(complaint: String) } }
-
13:40 - Regex guide
@Generable struct NPC { @Guide(Regex { Capture { ChoiceOf { "Mr" "Mrs" } } ". " OneOrMore(.word) }) let name: String } session.respond(to: "Generate a fun NPC", generating: NPC.self) // > {name: "Mrs. Brewster"}
-
14:50 - Generable riddle
@Generable struct Riddle { let question: String let answers: [Answer] @Generable struct Answer { let text: String let isCorrect: Bool } }
-
15:10 - Dynamic schema
struct LevelObjectCreator { var properties: [DynamicGenerationSchema.Property] = [] mutating func addStringProperty(name: String) { let property = DynamicGenerationSchema.Property( name: name, schema: DynamicGenerationSchema(type: String.self) ) properties.append(property) } mutating func addArrayProperty(name: String, customType: String) { let property = DynamicGenerationSchema.Property( name: name, schema: DynamicGenerationSchema( arrayOf: DynamicGenerationSchema(referenceTo: customType) ) ) properties.append(property) } var root: DynamicGenerationSchema { DynamicGenerationSchema( name: name, properties: properties ) } } var riddleBuilder = LevelObjectCreator(name: "Riddle") riddleBuilder.addStringProperty(name: "question") riddleBuilder.addArrayProperty(name: "answers", customType: "Answer") var answerBuilder = LevelObjectCreator(name: "Answer") answerBuilder.addStringProperty(name: "text") answerBuilder.addBoolProperty(name: "isCorrect") let riddleDynamicSchema = riddleBuilder.root let answerDynamicSchema = answerBuilder.root let schema = try GenerationSchema( root: riddleDynamicSchema, dependencies: [answerDynamicSchema] ) let session = LanguageModelSession() let response = try await session.respond( to: "Generate a fun riddle about coffee", schema: schema ) let generatedContent = response.content let question = try generatedContent.value(String.self, forProperty: "question") let answers = try generatedContent.value([GeneratedContent].self, forProperty: "answers")
-
18:47 - FindContactTool
import FoundationModels import Contacts struct FindContactTool: Tool { let name = "findContact" let description = "Finds a contact from a specified age generation." @Generable struct Arguments { let generation: Generation @Generable enum Generation { case babyBoomers case genX case millennial case genZ } } func call(arguments: Arguments) async throws -> ToolOutput { let store = CNContactStore() let keysToFetch = [CNContactGivenNameKey, CNContactBirthdayKey] as [CNKeyDescriptor] let request = CNContactFetchRequest(keysToFetch: keysToFetch) var contacts: [CNContact] = [] try store.enumerateContacts(with: request) { contact, stop in if let year = contact.birthday?.year { if arguments.generation.yearRange.contains(year) { contacts.append(contact) } } } guard let pickedContact = contacts.randomElement() else { return ToolOutput("Could not find a contact.") } return ToolOutput(pickedContact.givenName) } }
-
20:26 - Call FindContactTool
import FoundationModels let session = LanguageModelSession( tools: [FindContactTool()], instructions: "Generate fun NPCs" )
-
21:55 - FindContactTool with state
import FoundationModels import Contacts class FindContactTool: Tool { let name = "findContact" let description = "Finds a contact from a specified age generation." var pickedContacts = Set<String>() ... func call(arguments: Arguments) async throws -> ToolOutput { contacts.removeAll(where: { pickedContacts.contains($0.givenName) }) guard let pickedContact = contacts.randomElement() else { return ToolOutput("Could not find a contact.") } return ToolOutput(pickedContact.givenName) } }
-
22:27 - GetContactEventTool
import FoundationModels import EventKit struct GetContactEventTool: Tool { let name = "getContactEvent" let description = "Get an event with a contact." let contactName: String @Generable struct Arguments { let day: Int let month: Int let year: Int } func call(arguments: Arguments) async throws -> ToolOutput { ... } }
-
-
- 0:00 - Introducción
Obtén información sobre la estructura Foundation Models para dispositivos Apple, que proporciona un modelo de lenguaje grande en el dispositivo accesible a través de la API de Swift. Se explica cómo usar Generable para obtener resultados estructurados, esquemas dinámicos y llamadas de herramientas para funcionalidades personalizadas.
- 0:49 - Sesiones
En este ejemplo, Foundation Models mejora un juego de cafetería de pixel art generando diálogos y contenido de juego dinámicos. Mediante la creación de una LanguageModelSession, se proporcionan instrucciones personalizadas al modelo, lo que le permite responder a las preguntas de los jugadores. El modelo procesa la entrada del usuario y las instrucciones de la sesión en tokens, pequeñas subcadenas, que luego utiliza para generar nuevas secuencias de tokens como salida. LanguageModelSession tiene estado y registra todas las indicaciones y respuestas en una transcripción. Puedes usar esta transcripción para depurar y mostrar el historial de conversaciones en la interfaz de usuario del juego. Sin embargo, existe un límite para el tamaño de la sesión, conocido como límite de contexto. La generación de respuestas no es determinista por defecto. El modelo utiliza muestreo, creando una distribución de probabilidades para cada token, lo que introduce aleatoriedad. Esta aleatoriedad se puede controlar mediante la API GenerationOptions, que permite ajustar el método de muestreo o la temperatura, o incluso configurarlo como codicioso para obtener una salida determinista. Más allá del diálogo simple, Foundation Models se puede utilizar para generar resultados más complejos, como nombres y pedidos de café para personajes no jugables (NPC). Esto agrega profundidad y variedad al mundo del juego, de modo que es más real e interactivo. También debes considerar posibles problemas como idiomas no compatibles y manejarlos con cuidado para brindar una experiencia de usuario fluida.
- 7:57 - Generable
La API Generable de Foundation Models es una herramienta poderosa que simplifica la obtención de datos estructurados de modelos de lenguaje de gran tamaño. Al aplicar la macro @Generable a las estructuras o enumeraciones de Swift, se genera un esquema en tiempo de compilación que guía el resultado del modelo. Generable genera automáticamente un inicializador y maneja el análisis del texto generado del modelo en objetos Swift de tipo seguro usando decodificación restringida. Esta técnica garantiza que el resultado del modelo se adhiera al esquema especificado, evitando alucinaciones y errores estructurales. Puedes personalizar aún más el proceso de generación utilizando “Guías”, que proporcionan restricciones, rangos o descripciones en lenguaje natural para propiedades específicas. Esto permite un mayor control sobre los datos generados, como especificar formatos de nombres, recuentos de matrices o rangos numéricos. Generable permite una generación de datos eficiente y confiable, de modo que los desarrolladores se pueden concentrar en aspectos más complejos de sus aplicaciones.
- 14:29 - Esquemas dinámicos
En el creador de niveles del juego, los esquemas dinámicos permiten a los jugadores definir entidades personalizadas en tiempo de ejecución. Estos esquemas, similares a las estructuras de tiempo de compilación, tienen propiedades con nombres y tipos, lo que permite matrices y referencias a otros esquemas dinámicos. A partir de la entrada del jugador, se crea un esquema de acertijo con una pregunta (cadena) y una matriz de respuestas (tipo personalizado con cadena y propiedades booleanas). Estos esquemas dinámicos se validan y, luego, se utilizan para generar contenido mediante Foundation Models, lo que garantiza que el resultado coincida con la estructura definida. Este enfoque dinámico permite que el juego muestre acertijos creados por los jugadores y otras entidades en una IU dinámica, lo que proporciona un alto grado de flexibilidad y creatividad para los jugadores y, al mismo tiempo, mantiene un manejo de datos estructurado.
- 18:10 - Llamada de herramientas
Con Foundation Models, los desarrolladores de juegos pueden crear DLC personalizado mediante llamadas a herramientas. Esto permite que el modelo obtenga de forma autónoma información del dispositivo del jugador, como contactos y calendario, preservando al mismo tiempo la privacidad porque los datos nunca salen del dispositivo. Definir una herramienta implica especificar un nombre, una descripción y argumentos de entrada. El modelo utiliza esta información para decidir cuándo y cómo llamar a la herramienta. Luego, la implementación de la herramienta interactúa con API externas, como la API de Contacts, para recuperar datos.