Genkit Dart 0.16: когда результат инструмента становится частью протокола
Воспроизводимое сравнение Tool-контрактов Genkit Dart 0.15.1 и 0.16.1: multipart, interrupt, границы Session и ответственности, которые по-прежнему принадлежат приложению.
- Опубликовано
- 7 сентября 2026 г.
- Проверено
- 3 сентября 2026 г.
Genkit Dart 0.16: когда результат инструмента становится частью протокола
Агент вызывает инструмент, получает строку и продолжает рассуждение. Такой
пример хорошо помещается в README: модель запросила погоду, Dart-функция
вернула "sunny", следующий ответ учёл результат. Пока инструмент только
читает данные, а весь результат укладывается в одно значение, этого объяснения
почти достаточно.
Затем инструменту требуется вернуть текст вместе с изображением. Другая
операция должна остановиться и дождаться подтверждения пользователя. Третья
может изменить внешний ресурс, поэтому её нельзя бездумно повторить после
ошибки. Обычного Future<Output> уже мало: вызывающей стороне нужно различать
несколько исходов и понимать, кто отвечает за продолжение.
Именно в этой точке Genkit Dart 0.16 меняет Tool API. В changelog изменение
занимает одну строку — redesign вокруг ToolResult и multipart. Для
архитектуры приложения эта строка заметно длиннее.
Что вышло и насколько оно стабильно
На 3 сентября 2026 года актуальная версия
genkit 0.16.1. Вместе
с ней опубликованы совместимые версии основных provider- и integration-пакетов:
genkit_google_genai 0.3.1, genkit_anthropic 0.3.1,
genkit_openai 0.4.1, genkit_middleware 0.6.1 и genkit_shelf 0.1.13.
Все перечисленные пакеты требуют genkit ^0.16.1 и Dart не ниже линии 3.10.
Наличие согласованной линейки зависимостей полезно, но не означает стабильность API уровня 1.0. Первый официальный анонс Genkit Dart от 10 марта 2026 года называл его early preview. С тех пор пакет получил обычные stable-релизы 0.10–0.16 на pub.dev, однако major-версия остаётся нулевой, а breaking changes продолжают выходить.
Практический вывод простой: Genkit уже можно исследовать на реальных задачах, но обновление нельзя прятать внутри автоматического dependency bump. Контракт инструментов требует отдельной проверки.
До 0.16: Tool возвращал значение
В Genkit 0.15.1 минимальный инструмент можно определить так:
final uppercase = ai.defineTool<String, String>(
name: 'uppercase',
description: 'Returns an uppercase string.',
fn: (input, _) async => input.toUpperCase(),
);
final String output = await uppercase('dart');
Handler возвращает объявленный Output, а прямой вызов Tool даёт тот же тип.
В локальном эксперименте результатом стал обычный String со значением
DART.
Этот контракт не плох и не ошибочен. Он точно описывает функцию, у которой есть один нормальный результат. Проблема возникает, когда Tool участвует не только в вычислении значения, но и в обмене сообщениями внутри агентского цикла.
После 0.16: Tool возвращает исход выполнения
На 0.16.1 handler того же инструмента выглядит иначе:
final uppercase = ai.defineTool<String, String>(
name: 'uppercase',
description: 'Returns an uppercase string and multipart metadata.',
fn: (input, _) => .response(
input.toUpperCase(),
parts: [TextPart(text: 'Transformed locally: $input')],
metadata: const {'source': 'local-fixture'},
),
);
final ToolResult<String> result = await uppercase('dart');
Теперь String остаётся полезной нагрузкой, но перестаёт быть всем результатом
Tool. Нормальное завершение представлено ToolResponseResult<String>. Рядом с
output могут находиться дополнительные части и metadata. Другой допустимый
исход — ToolInterruptResult<String>.
Старый и новый варианты были проверены не только чтением исходников. В compile-only эксперименте каждое определение запускалось на своей версии, а затем намеренно переносилось на другую:
- 0.15.1 вернула обычный
String; - 0.16.1 вернула
ToolResponseResult<String>с однимTextPartи metadata; .response(...)не скомпилировался на 0.15.1;- возврат обычной строки не скомпилировался на 0.16.1, потому что handler уже
обязан вернуть
ToolResult<String>.
Это настоящий разрыв контракта, а не вопрос предпочтительного синтаксиса.
Multipart — не украшение ответа
Название multipart легко прочитать как возможность приложить картинку к тексту. Технически это верно, но архитектурная граница шире. У Tool появляются как минимум три разных вида данных:
- типизированный
output, с которым работает прикладной код; - список
Part, предназначенный для модельного или коммуникационного контекста; - metadata, сопровождающая выполнение.
Если свести их обратно к одному Map<String, dynamic>, новая форма не даст
пользы. Прикладной результат, содержимое сообщения и технические сведения
снова окажутся смешаны, только уже внутри более крупного объекта.
Разделение важно и для проверки. Схема Output может подтвердить форму
структурированных данных. Она не доказывает, что media безопасно загружать, что
metadata не содержит приватную информацию, а текстовая часть заслуживает
доверия. У каждого канала остаётся собственная граница.
Interrupt не является системой согласования
Инструмент 0.16.1 может вернуть interrupt:
final approval = ai.defineTool<String, String>(
name: 'approval',
description: 'Interrupts before a synthetic side effect.',
fn: (input, _) => .interrupt({'question': input}),
);
В полном generate loop Genkit возвращает управление вызывающему коду с
FinishReason.interrupted. Затем приложение выбирает один из двух способов
продолжения. interruptRespond передаёт инструменту или модели полученный
ответ. interruptRestart просит запустить инструмент заново и может добавить
данные о выполненном подтверждении.
Механизм позволяет построить human-in-the-loop сценарий. Но сам по себе он не является авторизацией.
До возобновления приложение всё ещё должно определить:
- кто имеет право подтвердить действие;
- к какой операции относится подтверждение;
- сколько времени оно действительно;
- можно ли выполнить Tool повторно;
- что произойдёт, если первый запуск уже успел изменить внешний ресурс;
- где хранится состояние между interrupt и resume.
Особенно опасен interruptRestart для side-effecting Tool. Повторный запуск
является частью его семантики, а не технической деталью SDK. Если операция
создаёт платёж, отправляет сообщение или изменяет документ, ей нужен
идемпотентный ключ либо иной явно выбранный механизм защиты от повтора.
Слово approval в названии middleware этого не добавит.
Retry и Tool отвечают на разные вопросы
В текущем Genkit есть retry middleware с экспоненциальной задержкой и jitter.
По умолчанию он повторяет допустимые ошибки модели, но не ошибки выполнения
Tool: retryModel включён, retryTools выключен.
Такое значение по умолчанию выглядит осторожным. Оно всё равно не заменяет решение приложения. Ошибка чтения погоды и ошибка отправки банковского перевода имеют разную цену повтора, хотя обе могут прийти с одинаковым техническим статусом.
Поэтому retry policy должна появляться после определения семантики Tool:
- read-only вызов обычно допускает повтор при временном отказе;
- идемпотентная запись требует стабильного ключа и проверки ответа;
- необратимое действие может требовать запрета автоматического повтора;
- неизвестный исход после сетевого разрыва требует reconciliation, а не ещё одного вызова.
Genkit предоставляет точку расширения. Решение остаётся у приложения.
Session — ещё один срок жизни, а не общее состояние приложения
Серия 0.15 добавила типизированный Agent State, server-side Agent runtime,
transport-agnostic client, хранение Session и snapshot, а также file-backed
SessionStore. В API 0.16.1 Session<State> хранит сообщения, custom state и
artifacts; sessionId связывает трассировки нескольких agent turns.
Здесь полезно разделить четыре срока жизни:
| Объект | Что заканчивает его работу |
|---|---|
| Model call | один ответ или ошибка провайдера |
| Agent turn | один цикл рассуждения и вызовов Tool |
| Genkit Session | выбранная история сообщений, state и artifacts |
| Продуктовая операция | бизнес-результат: сохранение, поиск, согласование |
В прототипе все четыре срока могут совпасть. Например, один экран отправляет один prompt, получает один ответ и забывает историю. В приложении с продолжением диалога или внешними действиями совпадение быстро исчезает.
Session state не обязан быть источником истины для заказа, профиля или документа. Он хранит контекст агентского взаимодействия. Доменное состояние может измениться независимо: другим пользователем, backend-процессом или операцией, которая пережила текущий turn. Если эти два вида состояния назвать одним словом и хранить вместе, модель получит старую картину, а приложение — неявную систему синхронизации.
Строка changelog, которую пришлось перепроверить
Для 0.16.1 changelog сообщает: добавлен context parameter в операции session
store. Из этой формулировки естественно предположить, что пользовательская
реализация SessionStore должна получить новые сигнатуры после обновления с
0.15.1.
Сравнение опубликованных пакетов этого не подтвердило. В загруженных архивах
0.15.1 и 0.16.1 методы getSnapshot и saveSnapshot уже принимают
Map<String, dynamic>? context. Обе реализации с одинаковой сигнатурой
скомпилировались и получили synthetic tenant context.
Это не доказывает ошибку changelog. Изменение могло затронуть передачу context внутри runtime, дополнительные операции или тестовое покрытие. Но публичный diff между двумя выбранными версиями нельзя описывать как появление параметра: наблюдение этого не показывает.
Так выглядит нормальная проверка на «вшивость». Официальный источник остаётся основным, но короткая формулировка не получает более широкий смысл, чем подтверждает код.
Где проходит граница ArkTelos
Подход ArkTelos рассматривает Tool как адаптер ограниченной capability, а не как место, куда следует перенести весь use case. Продуктовая команда должна иметь собственный жизненный цикл и результат. Tool переводит вход агентского протокола в эту команду и возвращает только разрешённую часть результата.
Из этого следуют разные владельцы:
- use case отвечает за бизнес-инварианты и терминальный исход операции;
- Tool — за схему доступной агенту возможности и преобразование результата;
- Genkit Session — за контекст агентских turns;
- provider adapter — за вызов внешней модели;
- приложение — за авторизацию, подтверждение, повтор и компенсацию side effect.
Это не готовая интеграция ArkTelos с Genkit. Эксперимент проверял API самого Genkit и не подключал ArkTelos-пакеты. Рамка нужна лишь затем, чтобы обновление SDK не превратило Tool в случайного владельца всех соседних обязанностей.
Когда обновляться
Для нового прототипа 0.16.1 выглядит разумной точкой исследования: текущие provider-пакеты согласованы по зависимости, multipart и interrupt выражены явными типами, а compile-only пример не требует внешней модели.
Для существующего кода перед обновлением нужны как минимум четыре проверки:
- найти все Tool handlers, которые возвращают обычный Output;
- определить, где действительно нужны parts, metadata или interrupt;
- проверить side-effecting Tools на повтор, подтверждение и неизвестный исход;
- прогнать собственные SessionStore и middleware, не полагаясь только на список изменений.
Если приложение уже выполняет внешние действия через агента, одного успешного compile недостаточно. Потребуются тесты полного generate loop, сохранения Session, interrupt/resume и ошибок провайдера. Этот эксперимент их не проводил.
Вывод поэтому ограничен. Genkit 0.16.1 делает Tool-контракт выразительнее и лучше показывает, что результат инструмента является частью протокола. Он не берёт на себя смысл бизнес-операции, право на действие и цену повтора. Эти границы всё ещё приходится проектировать приложению.
Источники и воспроизведение
- Genkit 0.16.1 на pub.dev
- Genkit changelog
- Genkit Dart README и interrupt/retry examples
- Session API 0.16.1
- Официальный анонс Genkit Dart
- исходный код эксперимента
genkit-016-contract-boundariesи проверенная ревизия552911e
ArkTelos Lab: сайт | Telegram RU | Telegram EN
ArkTelos: сайт | Telegram RU | Telegram EN
Границы результата
- Результат ограничен зафиксированными контрактами пакетов Genkit Dart 0.15.1 и 0.16.1 и требует повторной проверки после обновления зависимостей.
- Успешная compile-only миграция не доказывает production readiness, совместимость провайдеров, безопасность повторов или восстановление interrupt после сбоя процесса.
CODE / DATA / AGENTS
Связанные эксперименты
genkit-016-contract-boundariesВоспроизвести значимое для миграции изменение результата Tool между Genkit Dart 0.15.1 и 0.16.1 без внешней модели.
Эксперимент вызывает Tool напрямую и не воспроизводит полный цикл модели и инструментов. Внешняя модель, поведение provider-плагинов, сбой persistence, задержка, стоимость и качество ответа не проверялись.
Открыть паспорт эксперимента →Версия в Telegram