Представьте приложение для записи на услуги. Пользователь открывает его и пишет:
«
«Перенеси мою запись на пятницу после обеда».
За этой короткой фразой скрывается целая последовательность действий. Нужно понять, о какой записи идёт речь, определить дату, проверить расписание, найти подходящее время и показать стоимость. Затем получить подтверждение и выполнить перенос.
В обычном приложении пользователь проходит эту последовательность самостоятельно: открывает запись, выбирает календарь, просматривает слоты и нажимает кнопку. AI-ассистент может взять на себя поиск и объяснение вариантов, оставив человеку выбор и подтверждение.
Именно такие сценарии становятся интереснее с выходом Genkit Dart 1.0 . Google объявила о стабильном релизе 8 октября 2026 года. Фреймворк позволяет описывать AI-логику, инструменты и типизированные операции на Dart — языке, который Flutter-команда уже использует каждый день. Официальный анонс .
Попробуем разобраться, как из этого собрать небольшого ассистента для записи на услуги и где проходит граница между возможностями модели и ответственностью приложения.
Текстовый интерфейс удобен, когда пользователь ещё не знает, какую кнопку искать.
«Хочу время попозже», «найди что-нибудь рядом с обедом», «можно к тому же специалисту на следующей неделе?» — такие запросы естественно выражаются словами. Превращать каждый из них в отдельный экран и набор фильтров довольно трудоёмко.
Модель может интерпретировать запрос и выбрать следующий шаг. Но для полезного результата ей нужны реальные данные и разрешённые способы их получить.
В Genkit эту роль выполняют tools — Dart-функции, доступные модели. Например, инструмент findSlots обращается к системе записи и возвращает свободное время. Модель решает, когда запросить его, а серверный код определяет, какие данные можно прочитать.
Есть и flows : операции с описанными входными и выходными данными. Они объединяют шаги обработки и позволяют запускать, тестировать и наблюдать за ними отдельно от интерфейса. Документация flows .
Получается вполне знакомая разработчику конструкция: функции, контракты данных и бизнес-правила. LLM занимает в ней конкретное место — помогает работать с запросами на естественном языке.
Главное значение релиза — стабилизация основного API Genkit Dart.
Фреймворк предоставляет общий интерфейс к Gemini, Claude, OpenAI и OpenAI-совместимым моделям. Плагины позволяют подключать провайдеров, сохраняя общий способ обращения к ним. При этом смена модели всё равно требует проверки качества: разные модели могут по-разному выбирать инструменты и соблюдать формат ответа. Анонс Genkit Dart 1.0 .
Для Flutter-команд особенно полезна возможность разделять типы между клиентом и сервером. Пакет schemantic генерирует классы, сериализацию и схемы из Dart-деклараций. Вместо двух независимо поддерживаемых описаний API появляется общий контракт. Документация schemantic .
Есть важная оговорка: стабильный статус ядра не распространяется автоматически на весь стек.
Высокоуровневые API агентов, включая defineAgent и remoteAgent , остаются экспериментальными. genkit_a2ui тоже имеет экспериментальный статус, а GenUI для Flutter находится в alpha. Анонс Flutter , статус GenUI .
Поэтому экспериментальный интерфейс стоит подключать поверх самостоятельной бизнес-логики. Перенос записи должен оставаться понятной серверной операцией, даже если способ отображения предложенных вариантов изменится.
Для нашего ассистента выберем простую архитектуру.
Flutter отображает разговор и интерактивные элементы. Dart-сервер запускает агента и предоставляет ему инструменты. Система записи хранит расписание, цены и бронирования.
Модель получает только необходимые данные через backend. API-ключ провайдера остаётся на сервере.
textTEXT
Flutter
│
│ сообщение пользователя
▼
Dart-сервер с Genkit
├── обращение к LLM
├── поиск свободных слотов
└── подготовка предложения
│
▼
Flutter: выбор и подтверждение
│
▼
Серверная операция переноса
На каждом запросе backend проверяет пользовательский токен. Затем проверяет доступ к конкретной записи.
Это две разные проверки. Пользователь может быть авторизован в приложении, но не иметь права изменить чужое бронирование. Идентификатор владельца берём из проверенного контекста авторизации, а принадлежность записи проверяем в системе хранения.
Самому агенту дадим инструмент чтения расписания. Перенос будет выполняться отдельной операцией после подтверждения.
Допустим, сервер нашёл два времени: 14:30 за 2 500 рублей и 16:00 за 2 800 рублей. Это условные данные для примера.
Можно вернуть их текстом. Но карточки с кнопками позволят выбрать время быстрее: пользователю не придётся писать «второй вариант» и ждать ещё одного ответа модели.
Здесь появляются A2UI и GenUI .
A2UI описывает интерфейс структурированными сообщениями. GenUI отображает его во Flutter, используя заранее подготовленный каталог виджетов. Модель выбирает компоненты и их параметры, а реализацию компонентов контролирует разработчик. Обзор GenUI .
Каталог задаёт словарь интерфейса. Для сервиса записи он может включать карточку слота, информацию об услуге, форму уточнения и сводку стоимости.
«
Генерируемый интерфейс особенно полезен там, где запрос уже понятен, а подходящее представление ответа зависит от ситуации.
Для списка свободного времени подойдут карточки. Для уточнения условий — форма. Для сравнения услуг — несколько вариантов рядом.
При этом дизайн остаётся частью приложения: размеры, цвета, доступность и поведение виджетов определяет Flutter-код.
Время передаём с явным часовым поясом. Деньги храним целым числом в минимальных единицах валюты: для рублей значение 250000 соответствует 2 500 рублям.
Такая схема помогает согласовать формат данных. Проверка доступности времени и корректности цены остаётся задачей сервера.
Следующий шаг — инструмент поиска. Ниже показан основной фрагмент: repo — адаптер системы записи, а requireUser получает пользователя из проверенного контекста запроса.
dartDART
final findSlots = ai.defineTool( name:'findSlots', description:'Returns available slots and prices for a booking ''on a YYYY-MM-DD date.', inputSchema:FindSlotsInput.$schema, outputSchema:SlotsOutput.$schema, fn: (input, ctx) async {final result =await repo.findSlots( userId:requireUser(ctx.context), bookingId: input.bookingId, date: input.date, );return.response(result); },);
Внутри repo.findSlots проверяются права на запись, дата и правила подбора времени. Модели возвращается результат, пригодный для отображения. Вызов инструментов в Genkit .
Затем подключаем агентный слой и A2UI:
dartDART
import'package:genkit/genkit.dart';import'package:genkit/experimental.dart';import'package:genkit_a2ui/a2ui.dart';import'package:genkit_google_genai/genkit_google_genai.dart';final ai =Genkit( plugins: [googleAI(),A2uiPlugin()],);// Здесь регистрируется findSlots.final bookingAgent = ai.defineAgent( name:'bookingAgent', model: googleAI.gemini('gemini-flash-latest'), tools: [findSlots], maxTurns:4, system:'''Помоги выбрать время переноса записи.Получай свободные слоты и цены через findSlots.Уточняй неоднозначные даты.Показывай варианты через A2UI.Кнопки выбора отправляют choose_slotс bookingId и slotId.''', use: [a2ui(validate:'strict')],);
Это фрагменты интеграции, а не готовое приложение: для запуска также нужны серверные маршруты, авторизация, адаптер данных и Flutter-экран.
Обратите внимание на набор инструментов: здесь есть поиск, а операции изменения записи нет. Агент может подобрать варианты, но выполнять перенос будет отдельный серверный endpoint.
Для прототипа можно использовать базовый каталог A2UI. В продукте собственные компоненты помогут точнее контролировать отображение и разрешённые действия. Пакет genkit_a2ui .
На клиенте поток ответа агента передаётся в SurfaceController . Он обрабатывает A2UI-сообщения и обновляет поверхности интерфейса.
Для API GenUI 0.10.4 подключение выглядит примерно так:
dartDART
final catalog = ui.BasicCatalogItems.asCatalog().copyWith( catalogId: a2ui.basicCatalogId,);final controller = ui.SurfaceController( catalogs: [catalog],);final turn = chat.sendStream( text:'Перенеси мою запись на пятницу после обеда',);awaitfor (final chunk in turn.stream) {final envelopes = a2ui.a2uiEnvelopesFromParts( chunk.raw.modelChunk?.content, );for (final envelope in envelopes) { controller.handleMessage( core.A2uiMessage.fromJson(envelope), ); }}await turn.response;
В этом фрагменте ui , a2ui и core — префиксы импортов GenUI, клиентских функций genkit_a2ui и a2ui_core . chat создаётся через remoteAgent , подключённый к нашему backend.
Экран отображает полученные поверхности через Surface и обрабатывает действия пользователя через controller.onSubmit . Идентификаторы каталогов клиента и сервера должны совпадать. Руководство по интеграции A2UI .
Но валидный интерфейс ещё не гарантирует достоверность содержимого. Режим strict проверяет структуру сообщений и имена компонентов; цена, URL и другие значения требуют собственных проверок. Границы валидации .
Это особенно важно на следующем шаге — перед изменением записи.
Пользователь нажимает на 14:30. Приложение отправляет идентификатор слота серверу, а сервер готовит предложение переноса.
В нём фиксируются исходная запись, новое время, стоимость и срок действия. Предложение получает собственный proposalId и связывается с пользователем.
После этого Flutter показывает привычный диалог:
Данные для этого экрана приходят из проверенного серверного предложения. Согласие относится к конкретным условиям, которые человек видит перед собой.
После нажатия «Перенести» backend снова проверяет:
— предложение принадлежит текущему пользователю;
— срок его действия не истёк;
— запись ещё находится в ожидаемом состоянии;
— выбранное время доступно;
— стоимость соответствует подтверждённой.
Проверка и изменение должны выполняться атомарно. Иначе между ними другой пользователь может занять слот.
Если условия изменились, приложение показывает новое предложение. Повторный запрос с уже выполненным proposalId возвращает сохранённый результат, чтобы сетевой сбой не привёл к повторному выполнению.
Для более длинных агентных процессов Genkit предоставляет interrupts : инструмент возвращает .interrupt(...) , выполнение приостанавливается и затем возобновляется после ответа человека. Документация interrupts .
Но возобновление тоже требует серверной проверки разрешения. Флаг approved: true сам по себе не доказывает согласие на конкретную операцию.
В нашем небольшом сценарии отдельный endpoint подтверждения делает процесс проще: агент подбирает время, пользователь выбирает, сервер выполняет согласованный перенос.
Каждое обращение к модели добавляет задержку и расход токенов. Поэтому полезно заранее определить, какие задачи действительно требуют LLM.
Понимание фразы «после обеда» и уточнение пожеланий — подходящие задачи для модели. Расчёт стоимости, проверка расписания и перенос записи хорошо выполняются обычными Dart-функциями.
После нажатия на карточку приложение уже располагает структурированными данными. Повторно интерпретировать их моделью обычно не требуется.
Streaming позволяет раньше показать текст или часть интерфейса. Но полное время операции зависит и от модели, и от базы данных, и от количества инструментальных вызовов.
Во время разработки Genkit Developer UI помогает исследовать выполнение по шагам:
bashShell
genkitstart--dartrunbin/server.dart
Для серверного мониторинга genkit_otel экспортирует телеметрию OpenTelemetry, включая использование токенов и длительность модельных операций. Документация genkit_otel .
Практически полезно следить за временем до первой карточки, полным временем подбора, количеством вызовов и ошибками генерации интерфейса. Отдельно — за случаями, когда предложение успело устареть до подтверждения.
Эти показатели помогают оценить качество пользовательского сценария целиком.
Если запись всегда проходит по одной схеме — услуга, дата, время, подтверждение — статические Flutter-виджеты могут оказаться самым удобным решением.
Разговорный слой становится полезнее, когда пользователь приходит с неполным запросом, сочетает несколько условий или ожидает помощи в выборе. GenUI добавляет ценность, когда результат удобнее представить разными способами.
Поэтому разумно двигаться постепенно: сохранить основной сценарий записи, добавить понимание свободного текста и инструменты чтения. Затем подключать динамический интерфейс там, где он действительно сокращает путь пользователя.
Genkit Dart 1.0 делает Flutter частью общего стека AI-приложений. Команда может описывать интерфейс, серверные операции и контракты данных на Dart, используя привычные подходы к разработке.
Для пользователя ценность этого стека выражается гораздо проще: он формулирует просьбу своими словами, получает подходящие варианты и подтверждает понятное действие. Приложение превращает разговор в результат — с реальными данными и предсказуемым выполнением.