
-
Aprofunde-se no framework Foundation Models
Aprimore seus projetos com o framework Foundation Models. Saiba como a geração guiada funciona na parte interna e use guias, expressões regulares e esquemas de geração para obter respostas estruturadas personalizadas. Mostraremos como usar a chamada de ferramentas para permitir que o modelo acesse informações externas de maneira autônoma e como executar ações para criar uma experiência personalizada. Para aproveitar ao máximo este vídeo, recomendamos assistir primeiro ao à sessão "Conheça o framework Foundation Models".
Capítulos
- 0:00 - Introdução
- 0:49 - Sessões
- 7:57 - API Generable
- 14:29 - Esquemas dinâmicos
- 18:10 - Chamada de ferramentas
Recursos
- Generate dynamic game content with guided generation and tools
- Human Interface Guidelines: Generative AI
Vídeos relacionados
WWDC25
-
Buscar neste vídeo...
Olá! Meu nome é Louis. Hoje vamos ver como aproveitar ao máximo o framework Foundation Models.
Como você deve saber, o framework Foundation Models oferece acesso direto a um grande modelo de linguagem no dispositivo, com uma API Swift prática. Ele está disponível para macOS, iPadOS, iOS e visionOS. E, como é executado no dispositivo, usá-lo no seu projeto é tão simples quanto importar. Neste vídeo, veremos como funcionam as sessões com o Foundation Models. Como usar Generable para obter saídas estruturadas. Como obter saídas estruturadas com esquemas dinâmicos definidos em tempo de execução e usar chamadas de ferramentas para chamar funções personalizadas. Vamos começar com algo simples: gerar texto com uma sessão.
Estou trabalhando em um jogo de pixel art sobre uma cafeteria, e seria muito legal usar o Foundation Models para gerar diálogos e outro conteúdo, deixando tudo mais vivo.
Podemos usar o modelo para responder a perguntas do jogador, criando diálogos únicos para o barista. Para isso, vamos criar uma LanguageModelSession com instruções personalizadas. Isso permite informar ao modelo qual é seu propósito para a sessão e usaremos a entrada do usuário para o prompt. E isso é tudo de que precisamos para criar um elemento divertido no jogo. Vamos perguntar ao barista "Há quanto tempo você trabalha aqui?" e deixá-lo responder.
Isso foi gerado inteiramente no dispositivo. Incrível. Mas como funciona na prática? Vamos entender melhor como o Foundation Models gera texto e o que devemos observar. Quando você chama respond(to:) em uma sessão, ela pega as instruções da sessão e o prompt, neste caso a entrada do usuário, e transforma esse texto em tokens. Tokens são pequenas substrings, às vezes uma palavra, mas geralmente poucos caracteres. Um grande modelo de linguagem recebe uma sequência de tokens como entrada e gera uma nova sequência de tokens como saída. Você não precisa se preocupar com os tokens exatos que o Foundation Models usa, a API cuida disso para você. Mas é importante entender que tokens não são livres. Cada token nas suas instruções e no prompt adiciona latência extra. Antes que o modelo comece a gerar os tokens da resposta, ele precisa processar todos os tokens de entrada. E gerar tokens também tem um custo computacional, por isso respostas mais longas demoram mais para serem geradas.
Uma LanguageModelSession é stateful. Cada chamada a respond(to:) é registrada na transcrição.
A transcrição inclui todos os prompts e respostas de uma certa sessão.
Isso pode ser útil para depuração ou até para exibição na interface.
Mas uma sessão tem um limite de tamanho. Se você fizer muitas solicitações, usar prompts grandes ou receber respostas longas, poderá atingir o limite de contexto.
Se sua sessão ultrapassar o tamanho máximo do contexto, ocorrerá um erro que você deve estar preparado para tratar. No jogo, se ocorre um erro durante a conversa com um personagem, a conversa acaba, o que é uma pena porque eu estava começando a conhecê-lo. Felizmente, há formas de se recuperar desse erro.
Você pode capturar o erro exceededContextWindowSize.
E, ao fazer isso, você pode iniciar uma nova sessão, sem nenhum histórico. Mas, no meu jogo, isso faria o personagem se esquecer de toda a conversa.
Você também pode escolher parte da transcrição da sessão atual para levar para a nova sessão.
Você pode pegar as entradas da transcrição de uma sessão e condensá-las em uma nova matriz de entradas.
Para o diálogo do nosso jogo, poderíamos pegar a primeira entrada da transcrição da sessão, que são as instruções, bem como a última entrada, que é a última resposta bem-sucedida. E, ao passar isso para uma nova sessão, nosso personagem consegue continuar a conversa. A transcrição da sessão inclui as instruções iniciais como a primeira entrada. Ao transferir a transcrição para o personagem do jogo, queremos incluir essas instruções.
Incluir poucas partes relevantes da transcrição pode ser uma solução simples e eficaz. Às vezes não é tão simples assim. Vamos imaginar uma transcrição com mais entradas. Você sempre deve começar transferindo as instruções. Mas muitas entradas na transcrição podem ser relevantes. Nesse caso, considere resumir a transcrição.
Você pode fazer isso com uma biblioteca externa ou resumir partes da transcrição usando o Foundation Models.
É isso que você pode fazer com a transcrição de uma sessão. Agora vamos dar uma olhada em como as respostas são geradas. No jogo, ao se aproximar do barista, o jogador pode fazer qualquer pergunta. Se você iniciar dois jogos e fizer a mesma pergunta em cada um, provavelmente receberá respostas diferentes. Como isso funciona? É aí que entra a amostragem.
Quando o modelo gera a saída, ele faz isso um token por vez. Ele faz isso criando uma distribuição de probabilidade para cada token. Por padrão, o Foundation Models escolhe tokens dentro de um intervalo de probabilidade. Às vezes, ele pode começar dizendo "Ah", já em outras, pode escolher "Bem" como primeiro token. Isso acontece para cada token gerado. Escolher um token é o que chamamos de amostragem. E o comportamento padrão é a amostragem aleatória. Ter respostas variadas é ótimo para casos como um jogo. Mas você pode querer uma saída determinística, como quando está criando uma demonstração que deve ser repetível. A API GenerationOptions permite controlar o método de amostragem. Configure-a como greedy e obtenha uma saída determinística. Com essa configuração, a saída será sempre a mesma para o mesmo prompt, se a sessão estiver no mesmo estado. Mas note que isso vale apenas para uma versão específica do modelo no dispositivo. Quando o modelo é atualizado junto com o sistema operacional, seu prompt pode gerar respostas diferentes, mesmo usando amostragem greedy. Você também pode ajustar a temperatura na amostragem aleatória. Por exemplo, definindo a temperatura para 0,5, você obtém uma saída que varia pouco. Ou defina um valor mais alto para obter saídas diferentes para o mesmo prompt. Além disso, ao usar a entrada do usuário no prompt, o idioma pode não ser compatível.
Existe o erro específico unsupportedLanguageOrLocale que você pode receber para esse caso. Isso permite mostrar uma mensagem personalizada na interface. Também existe uma API para verificar se o modelo é compatível com certo idioma. Por exemplo, verificar se o idioma atual do usuário é compatível e mostrar um aviso quando não for. Esta foi uma visão geral sobre sessões. Você pode solicitá-lo, o que armazenará o histórico na transcrição. Ou pode definir o parâmetro de amostragem para controlar a aleatoriedade da saída da sessão. Vamos caprichar! Quando o jogador anda pelo cenário, podemos gerar NPCs, personagens não jogáveis, usando o Foundation Models. Mas, desta vez, queremos uma saída mais complexa. Em vez de texto simples, queremos um nome e um pedido de café do NPC. Generable pode nos ajudar aqui. Pode ser um desafio obter uma saída estruturada de um grande modelo de linguagem. Você pode instruí-lo com os campos específicos e usar um código de análise para extrair essas informações. Mas isso é difícil de manter e muito frágil, pois pode não retornar as chaves corretas, o que faria o método inteiro falhar. Felizmente, o Foundation Models tem uma API melhor, chamada Generable. Na struct, você pode aplicar a macro @Generable. O que é Generable? Essa palavra existe? Pois é, sim.
Generable é uma forma de permitir que o modelo gere dados estruturados, usando tipos Swift. A macro gera um esquema em tempo de compilação, que o modelo pode usar para produzir a estrutura esperada. A macro também gera um inicializador, que é sempre chamado para você ao fazer uma solicitação em uma sessão.
Assim, podemos gerar instâncias de nossa struct. Como antes, vamos chamar o método de resposta na nossa sessão. Desta vez, passe o argumento generating, indicando ao modelo qual tipo gerar. O Foundation Models até inclui detalhes sobre seu tipo Generable no prompt, em um formato específico no qual o modelo foi treinado. Você não precisa informar os campos do tipo Generable. No jogo, teremos ótimos encontros com NPCs gerados automaticamente.
Generable é, na verdade, mais poderoso do que parece. Em um nível inferior, ele usa decodificação restrita, uma técnica que permite ao modelo gerar texto que segue um esquema específico. Lembre-se daquele esquema que a macro gera. Como vimos antes, um LLM gera tokens, que depois são transformados em texto. E, com Generable, esse texto é analisado para você de maneira segura em relação aos tipos. Os tokens são gerados em um loop, chamado de loop de decodificação. Sem a decodificação restrita, o modelo pode alucinar algum nome de campo inválido. Como firstName em vez de um nome. O que deixaria de ser analisado no tipo NPC.
Mas, com a decodificação restrita, o modelo é impedido de cometer erros estruturais como esse. Para cada token gerado, existe uma distribuição de todos os tokens no vocabulário do modelo. E a decodificação restrita funciona ao mascarar os tokens que não são válidos. Em vez de escolher qualquer token, o modelo só pode selecionar tokens válidos de acordo com o esquema.
E tudo isso sem precisar se preocupar em analisar manualmente a saída do modelo. Ou seja, você pode dedicar seu tempo ao que realmente importa, como conversar com os clientes virtuais da sua cafeteria. Generable é a melhor forma de obter resultados do LLM diretamente no dispositivo. E ele pode fazer muito mais. Você pode usá-lo não só em structs, mas também em enums. Então vamos usar isso para deixar nossos encontros mais dinâmicos. Aqui, adicionei um enum Encounter, com dois casos: O enum pode até conter valores associados em seus casos, então vamos usá-lo para gerar um pedido de café ou para representar alguém que quer falar com o gerente.
Vamos ver o que encontramos no nosso jogo agora.
Uau, alguém está precisando de um café.
Nem todo cliente é fácil de lidar, então vamos elevar o desafio adicionando níveis aos NPCs. Generable é compatível com a maioria dos tipos comuns do Swift, incluindo Int. Vamos adicionar uma propriedade level. Mas não queremos gerar um número inteiro. Para que o nível esteja dentro de uma faixa específica, especifique isso usando Guide. Podemos usar a macro Guide na propriedade e passar um intervalo. O modelo vai usar a decodificação restrita para garantir que o valor esteja dentro do intervalo.
Aproveitando, vamos adicionar uma série de atributos ao nosso NPC.
Podemos usar Guide, desta vez para especificar que queremos três atributos na matriz do nosso NPC. As propriedades do tipo Generable são geradas na ordem em que são declaradas no código-fonte. Aqui, name será gerado primeiro, seguido por level, depois attributes e, por último, encounter.
Essa ordem pode ser importante caso você espere que o valor de uma propriedade seja influenciado por outra. E você pode até transmitir propriedade por propriedade, se não quiser esperar até que a saída completa seja gerada. O jogo está divertido agora. Quase pronto para compartilhar com meus amigos. Mas percebi que os nomes dos NPCs não são como eu imaginava. Prefiro que os NPCs tenham nome e sobrenome.
Podemos usar Guide para isso, mas desta vez fornecendo uma descrição em linguagem natural.
Nosso nome deve ser um "nome completo". E isso é outra forma de criar prompt. Em vez de descrever as diferentes propriedades no prompt, você pode fazer isso diretamente no tipo Generable. E isso dá ao modelo uma relação mais forte sobre a que essas descrições estão vinculadas. Se explorarmos nosso jogo agora, vamos ver esses novos nomes em ação. Aqui está uma visão geral dos Guides que você pode aplicar aos diferentes tipos.
Com tipos numéricos comuns, como Int, você pode especificar o valor mínimo, máximo ou um intervalo. E, com matrizes, você pode controlar a quantidade ou especificar Guides para o tipo dos elementos da matriz.
Para String, você pode deixar que o modelo escolha uma matriz usando anyOf ou restrinja a um padrão regex.
Guide com padrão regex é especialmente poderoso. Você já deve conhecer o uso de regex para combinar padrões em textos. Mas, com o Foundation Models, você pode usar um padrão regex para definir a estrutura da string a ser gerada. Você pode restringir o nome a um conjunto específico de prefixos. E você ainda pode usar a sintaxe do construtor regex.
Se isso renovou sua empolgação por regex, confira o clássico atemporal "Conhecer o Swift Regex", de alguns anos atrás. Recapitulando, Generable é uma macro que você pode aplicar em structs e enums, oferecendo uma forma confiável de obter uma saída estruturada do modelo. Você não precisa se preocupar com nenhuma análise. Para obter uma saída ainda mais específica, pode aplicar Guides às propriedades. Generable é ótimo quando você conhece a estrutura em tempo de compilação. A macro gera o esquema para você, e você recebe uma instância do seu tipo como resultado. Às vezes, você só conhece a estrutura em tempo de execução. É aí que os esquemas dinâmicos podem ajudar. Estou adicionando um criador de níveis, onde jogadores definem entidades dinamicamente para encontrar enquanto caminham pelo jogo. Por exemplo, um jogador pode criar uma estrutura de enigma. Onde um enigma tem uma pergunta e respostas de múltipla escolha. Se soubéssemos essa estrutura em tempo de compilação, poderíamos definir uma struct Generable para ela. Mas nosso criador de níveis permite criar qualquer estrutura que o jogador imaginar.
Podemos usar DynamicGenerationSchema para criar um esquema em tempo de execução. Um esquema dinâmico, como uma struct em tempo de compilação, tem uma lista de propriedades. Podemos adicionar um criador de níveis que recebe a entrada do jogador.
Cada propriedade tem um nome e seu próprio esquema, que define seu tipo. Você pode usar o schema para qualquer tipo Generable, incluindo tipos integrados como String.
Um esquema dinâmico pode conter uma matriz, onde você especifica o esquema para os elementos da matriz. E, o mais importante, um esquema dinâmico pode ter referências a outros esquemas dinâmicos. Nossa matriz pode referenciar um esquema personalizado definido em tempo de execução.
Com a entrada do usuário, criamos um esquema de enigma com duas propriedades. A primeira é a pergunta, que é uma propriedade string. E a segunda, uma propriedade array, de um tipo personalizado chamado Answer. E então criaremos a resposta. Esse tipo tem uma propriedade string e uma booleana. Note que a propriedade answers do enigma referencia o esquema answer pelo seu nome. Então, podemos criar as instâncias DynamicGenerationSchema. Cada esquema dinâmico é independente. Ou seja, o esquema dinâmico do enigma não contém o schema dinâmico das respostas. Antes de fazermos a inferência, precisamos primeiro converter os esquemas dinâmicos em um esquema validado. Isso pode gerar erros se houver inconsistências nos esquemas dinâmicos, como referências a tipos que não existem.
E, uma vez que temos um esquema validado, podemos iniciar uma sessão. Mas desta vez, o tipo de saída é uma instância GeneratedContent. Que armazena os valores dinâmicos. Você pode consultar esses valores com os nomes das propriedades dos esquemas dinâmicos. Novamente, o Foundation Models usará geração guiada para garantir que a saída esteja de acordo com o esquema. Ele nunca vai inventar um campo inesperado. Mesmo sendo dinâmico, você não precisa se preocupar em analisar a saída manualmente.
Agora, quando o jogador encontrar um NPC, o modelo pode gerar esse conteúdo dinâmico. Isso será exibido em uma interface dinâmica. Vamos ver o que encontramos. Sou escuro ou claro, amargo ou doce, acordo você e trago calor, o que sou? Café ou chocolate quente. Acho que a resposta é café. Isso mesmo! Os jogadores vão se divertir criando vários níveis divertidos. Para recapitular, com a macro Generable, podemos gerar uma saída estruturada a partir de um tipo Swift definido em tempo de compilação. Nos bastidores, o Foundation Models cuida do esquema e da conversão de GeneratedContent em uma instância do seu próprio tipo. Esquemas dinâmicos funcionam de modo semelhante, mas oferecem mais controle. Você controla o esquema em tempo de execução e tem acesso direto a GeneratedContent. Agora, vamos ver a chamada de ferramentas, que permite ao modelo chamar suas funções. Estou pensando em criar um DLC (conteúdo para download) para deixar meu jogo mais pessoal. Com a chamada de ferramentas, o modelo pode buscar informações de maneira autônoma. Integrar os contatos e o calendário do jogador pode ser muito divertido. Eu não faria isso com um modelo baseado em servidor, pois meus jogadores não gostariam que o jogo enviasse dados pessoais assim. Mas como tudo está no dispositivo com o Foundation Models, podemos preservar a privacidade.
É fácil definir uma ferramenta usando o protocolo Tool. Comece dando um nome e uma descrição. Isso é o que será inserido no prompt pela API, para que o modelo decida quando e com que frequência chamar a ferramenta.
É melhor que o nome da ferramenta seja curto, mas legível como texto em inglês. Evite abreviações e não faça uma descrição muito longa, nem explique os detalhes da implementação, Essas strings são literalmente inseridas no seu prompt. Strings mais longas significam mais tokens, o que pode aumentar a latência. Em vez disso, considere usar um verbo no nome, como findContact. E sua descrição deve ter cerca de uma frase. Como sempre, é importante testar variações para ver o que funciona melhor para sua ferramenta específica.
Agora, vamos definir a entrada da ferramenta. Quero que a ferramenta busque contatos de uma certa geração, como os millennials. O modelo poderá escolher um caso divertido baseado no estado do jogo, e eu posso adicionar a struct Arguments e torná-la Generable. Quando o modelo decidir chamar a ferramenta, ele vai gerar os argumentos de entrada. Ao usar Generable, isso garante que a ferramenta sempre receba argumentos de entrada válidos. Assim, ele não vai inventar outra geração, como a geração alfa, que não tem suporte no jogo.
Então, posso implementar a função de chamada. O modelo chamará essa função quando decidir invocar a ferramenta. Neste exemplo, vamos chamar Contacts API e retornar o nome de um contato para essa consulta.
Para usar a ferramenta, vamos passá-la no inicializador da sessão. O modelo então chamará nossa ferramenta quando precisar de informação extra. É mais potente do que buscar o contato direto, pois o modelo chama a ferramenta só quando precisa de um determinado NPC, escolhendo argumentos de entrada baseados no estado do jogo. Como a geração da idade do NPC.
Isso usa a Contacts API regular, com a qual você provavelmente já tem familiaridade. Quando a ferramenta for invocada pela primeira vez, pedirá ao jogador a permissão habitual. Mesmo que o jogador não dê acesso aos contatos, o Foundation Models pode gerar conteúdo como antes, mas se o acesso for concedido, a experiência é mais personalizada.
Vamos explorar um pouco o nosso jogo até encontrar outro NPC. Desta vez, vou pegar um nome dos meus contatos. Olá, Naomy! Vamos ver o que ela tem a dizer. Eu não sabia que você gostava de café. LanguageModelSession recebe uma instância de uma ferramenta. Isso significa que você controla o ciclo de vida da ferramenta. A instância da ferramenta permanece a mesma durante toda a sessão. Neste exemplo, como estamos apenas recebendo um personagem aleatório com FindContactsTool, é possível pegar o mesmo contato. No nosso jogo, agora existem várias Naomys. E isso não está certo, só pode haver uma Naomy. Para corrigir isso, podemos manter o controle dos contatos que o jogo já usou. Podemos adicionar estado a FindContactTool. Para isso, primeiro vamos converter FindContactTool em uma classe. Ela pode alterar seu estado a partir do método de chamada. Depois, podemos acompanhar os contatos já selecionados e, no método de chamada, evitar escolher o mesmo contato.
Os nomes dos NPCs agora são baseados nos meus contatos. Mas conversar com eles ainda não parece natural. Vamos finalizar com outra ferramenta, desta vez para acessar meu calendário. Essa ferramenta recebe o nome do contato de um diálogo acontecendo no jogo. Quando o modelo chamar essa ferramenta, ele vai gerar dia, mês e ano para buscar eventos com esse contato. E vamos passar essa ferramenta na sessão para o diálogo do NPC.
Agora, se perguntarmos para o NPC da Naomy "O que está acontecendo?", ela pode responder com eventos reais que planejamos juntos.
É como falar com a verdadeira Naomy.
Vamos entender como funciona a chamada de ferramentas. Começamos passando a ferramenta no início da sessão, junto com as instruções. Neste exemplo, incluímos informações como a data de hoje. Quando o usuário envia um comando na sessão, o modelo pode analisar o texto. Neste exemplo, o modelo entende que o comando está solicitando eventos, então chamar a ferramenta do calendário faz sentido.
Para chamar a ferramenta, o modelo gera os argumentos de entrada. Neste caso, o modelo precisa gerar a data para buscar os eventos. O modelo pode relacionar as informações das instruções e do prompt, entendendo como preencher os argumentos da ferramenta com base nisso. Neste exemplo, ele pode deduzir que amanhã significa com base na data de hoje nas instruções. Assim que a entrada para a ferramenta é gerada, o método de chamada é invocado. Este é o seu momento de brilhar: sua ferramenta pode fazer o que quiser. Mas a sessão espera sua ferramenta retornar antes de gerar qualquer saída adicional.
A saída da ferramenta é então adicionada à transcrição, assim como a saída do modelo. E, com base na saída da ferramenta, o modelo pode gerar uma resposta ao prompt. Uma ferramenta pode ser chamada várias vezes para uma única solicitação. Quando isso acontece, sua ferramenta é chamada em paralelo. Lembre-se disso ao acessar dados dentro do método de chamada da sua ferramenta. Isso foi bem divertido! O jogo agora gera conteúdo aleatório, baseado nos meus contatos pessoais e calendário. Tudo isso sem que meus dados saiam do meu dispositivo. A chamada de ferramenta permite que o modelo chame seu código para acessar dados externos durante uma solicitação. Pode ser informação privada, como Contatos, ou dados externos de fontes online. Uma ferramenta pode ser invocada várias vezes durante uma mesma solicitação. O modelo determina isso com base no seu contexto. As ferramentas também podem ser chamadas em paralelo e armazenar estado. Foi muita informação. Tome um café antes de fazer qualquer outra coisa. Para saber mais, você pode conferir o vídeo dedicado sobre engenharia de prompt, incluindo dicas de design e segurança. E, se você quiser conhecer a verdadeira Naomy, não perca o vídeo prático. Espero que você se divirta tanto com Foundation Models quanto me diverti. Agradeço sua participação.
-
-
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 - Introdução
Saiba mais sobre o framework Foundation Models para dispositivos Apple, que fornece um grande modelo de linguagem no dispositivo, acessível pela API Swift. A sessão aborda como usar Generable para obter um resultado estruturado, esquemas dinâmicos e chamadas de ferramentas para funções personalizadas.
- 0:49 - Sessões
Neste exemplo, o Foundation Models aprimora um jogo de pixel art sobre uma cafeteria gerando conteúdo e diálogos dinâmicos para o jogo. Ao criar uma LanguageModelSession, instruções personalizadas são fornecidas ao modelo, permitindo que ele responda às perguntas do jogador. O modelo transforma instruções da sessão e de entrada do usuário em tokens, pequenas substrings, que ele usa para gerar novas sequências de tokens como saída. A LanguageModelSession é stateful, gravando todos os prompts e respostas em uma transcrição. Você pode usar essa transcrição para depurar e exibir o histórico de conversas na interface do jogo. No entanto, há um limite para o tamanho da sessão, conhecido como limite de contexto. A geração de respostas não é determinística por padrão. O modelo usa amostragem, criando uma distribuição de probabilidades para cada token, o que insere aleatoriedade. A API GenerationOptions permite controlar a aleatoriedade, ajustando o método de amostragem e a temperatura ou configurando-a como greedy para saída determinística. Além da caixa de diálogo simples, o Foundation Models pode ser empregado para gerar resultados mais complexos, como nomes e pedidos de café para NPCs (personagens não jogáveis). Isso adiciona profundidade e variedade ao mundo do jogo, tornando-o mais vivo e interativo. Você também deve considerar possíveis problemas, como idiomas em que o app não está disponível, e resolvê-los para fornecer uma experiência melhor ao usuário.
- 7:57 - API Generable
A API Generable do Foundation Models é uma ferramenta que simplifica a obtenção de dados estruturados dos grandes modelos de linguagem. Ao aplicar a macro @Generable a structs ou enums do Swift, um esquema é gerado em tempo de compilação, orientando o resultado do modelo. O Generable gera automaticamente um inicializador e cuida da conversão do texto gerado pelo modelo em objetos Swift que independem de tipo usando decodificação restrita. Essa técnica garante que o resultado do modelo siga o esquema especificado, evitando alucinações e erros estruturais. Você pode personalizar ainda mais o processo usando Guias, que fornecem restrições, intervalos ou descrições de linguagem natural para propriedades específicas. Isso permite mais controle sobre os dados gerados, como especificar formatos de nome, contagens de matrizes ou intervalos numéricos. O Generable permite a geração de dados eficiente e confiável, liberando desenvolvedores para focar aspectos mais complexos dos apps.
- 14:29 - Esquemas dinâmicos
No criador de níveis do jogo, os esquemas dinâmicos permitem que os jogadores definam entidades personalizadas no tempo de execução. Esses esquemas, semelhantes a structs em tempo de compilação, têm propriedades com nomes e tipos, permitindo matrizes e referências a outros esquemas dinâmicos. Na entrada do jogador, um esquema de enigma é criado com uma pergunta (string) e uma matriz de respostas (tipo personalizado com string e propriedades booleanas). Esses esquemas são validados e depois usados para gerar conteúdo pelo Foundation Models, garantindo que o resultado corresponda à estrutura definida. Essa abordagem dinâmica permite que o jogo exiba enigmas criados pelo jogador e outras entidades em uma interface dinâmica, oferecendo alto grau de flexibilidade e criatividade para os jogadores enquanto mantém o processamento de dados estruturados.
- 18:10 - Chamada de ferramentas
Com o Foundation Models, os desenvolvedores podem criar conteúdo para DLC personalizado usando chamadas de ferramentas. Assim, o modelo pode buscar informações de forma autônoma do dispositivo do jogador, como contatos e calendário, preservando a privacidade, pois os dados nunca saem do dispositivo. Definir uma ferramenta envolve especificar nome, descrição e argumentos de entrada. O modelo usa essas informações para decidir quando e como chamar a ferramenta. A implementação da ferramenta interage com APIs externas, como a Contacts API, para recuperar dados.