Приветствую, Хабр!

Макросы Swift предоставляют мощный механизм для генерации кода, позволяя разработчикам уменьшать количество шаблонного кода и повышать читаемость. Фреймворк FoundationModels представляет новые макросы, призванные упростить генерацию данных для определённых типов моделей с использованием языковых моделей.

Обзор @Generable

Макрос @Generable можно применить к структуре Swift, чтобы указать, что экземпляры этого типа могут быть сгенерированы языковой моделью. При использовании этого макроса компилятор автоматически синтезирует необходимые соответствия протоколам и методы для поддержки потоковых ответов от сессии языковой модели.

@Generable
struct ShoppingItem: Identifiable {
    let id: String
    let value: String
}

В этом примере структура ShoppingItem становится совместимой с логикой автоматической генерации, предоставляемой фреймворком. Ожидается, что каждый экземпляр ShoppingItem будет создан на основе структурированного вывода языковой модели.

При желании можно предоставить описание, определяющее контекст или цель генерации:

@Generable(description: "Генерировать элементы для списка покупок")
struct ShoppingItem: Identifiable {
    let id: String
    let value: String
}

Это описание помогает направлять языковую модель к более точным результатам в процессе генерации.

Использование @Guide для контекста на уровне свойств

Для более детального контроля над тем, как языковая модель интерпретирует отдельные свойства, макрос @Guide можно применить к конкретным свойствам. Это позволяет разработчикам предоставлять дополнительные инструкции, непосредственно связанные с ожидаемым содержимым каждого свойства.

@Generable(description: "Генерировать элементы для списка покупок")
struct ShoppingItem: Identifiable {
    let id: String

    @Guide(description: "Название продукта для покупки")
    let value: String
}

Каждая аннотация @Guide служит директивой для языковой модели при генерации значений для соответствующих полей.

Интеграция в приложение

После того как модель помечена макросом @Generable, её можно использовать внутри LanguageModelSession для запроса данных. Типичный сценарий использования включает запуск задачи генерации в ответ на ввод пользователя.

private func generateShoppingList() {
    let prompt = "Создать 15 элементов списка покупок"
    Task {
        do {
            let session = LanguageModelSession()

            let response = session.streamResponse(
                generating: [ShoppingItem].self
            ) {
                prompt
            }

            isGenerating = true

            for try await chunk in response {
                self.shoppingList = chunk.compactMap {
                    guard let id = $0.id, let task = $0.value else {
                        return nil
                    }

                    return ShoppingItem(id: id, value: task)
                }
            }

            isGenerating = false
        } catch {
            print(error.localizedDescription)
            isGenerating = false
        }
    }
}

В этом фрагменте метод streamResponse(generating:prompt:) инициирует потоковый запрос на получение массива экземпляров ShoppingItem. По мере поступления порций результатов они обрабатываются и добавляются в состояние представления.

Теперь давайте интегрируем это в наше представление SwiftUI:

struct ContentView: View {
    @State private var shoppingList: [ShoppingItem] = []
    @State private var isGenerating: Bool = false

    var body: some View {
        List(shoppingList) { item in
            Text(item.value)
        }
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                Button(
                    "Сгенерировать список покупок",
                    systemImage: "cart.fill.badge.plus"
                ) {
                    generateShoppingList()
                }
                .disabled(isGenerating)
            }
        }
    }

    private func generateShoppingList() {
        ...
    }
}

Заключение

Макрос @Generable предоставляет чистый и декларативный способ интеграции генерации на основе языковых моделей в приложения на Swift. В сочетании с @Guide он предлагает структурированный подход к определению ожиданий как на уровне модели, так и на уровне полей для генерируемых данных.