T2M Bridge, часть 1: как я написал Java‑библиотеку для MAX Bot API с помощью Codex

от автора

Это первая статья из цикла о разработке T2M Bridge — бота для кросспостинга между Telegram‑каналами и MAX‑каналами.

В цикле планируются четыре части:

  1. T2M Bridge, часть 1: как я написал Java‑библиотеку для MAX Bot API с помощью Codex

  2. T2M Bridge, часть 2: как я написал Java‑библиотеку для Telegram Bot API вместе с Codex — уже примерно 50/50

  3. T2M Bridge, часть 3: как я своими силами собрал бота для кросспостинга между Telegram и MAX

  4. T2M Bridge, часть 4: как я выкатывал бота в прод и почему всё оказалось сложнее Docker Compose

В первой части расскажу про maxlib — Java‑библиотеку для разработки ботов под MAX.

Я писал её по большей части с помощью Codex. Причина была простая: сама библиотека не была конечной целью. Мне нужен был рабочий инструмент для T2M Bridge, и я не хотел тратить слишком много времени на инфраструктурный слой.

Но получилось не так, что ИИ написал всё сам, а я только нажал кнопку «готово». На первичную разработку, проверку, исправление ошибок и доведение библиотеки до состояния, в котором её можно использовать в реальном проекте, всё равно ушло около пары недель.

Основная проблема оказалась не в синтаксисе Java и не в boilerplate‑коде. С этим Codex справлялся неплохо. Основная проблема была в другом: ИИ периодически начинал придумывать свои поля, объекты и структуры, вместо того чтобы строго следовать документации MAX Bot API.

В этой статье разберу, зачем мне понадобилась своя библиотека, как она устроена, как ей пользоваться и какие выводы я сделал после разработки с активным использованием ИИ.

Репозиторий:

https://github.com/tardyon‑soft/maxlib

Зачем понадобилась своя библиотека

T2M Bridge — это бот для кросспостинга между Telegram‑каналом и MAX‑каналом.

Чтобы такой бот работал, мне нужно было реализовать две стороны:

  • Telegram‑бота;

  • MAX‑бота.

Для Telegram в Java‑экосистеме уже есть разные библиотеки и подходы. Для MAX на момент старта мне не хватало готового инструмента, который закрывал бы не только HTTP‑вызовы к API, но и более высокий runtime‑слой.

Мне хотелось получить не просто набор методов вроде sendMessage, а нормальную основу для разработки бота:

  • typed client для MAX Bot API;

  • обработку входящих событий;

  • dispatcher;

  • router;

  • фильтры;

  • middleware;

  • FSM;

  • экраны;

  • polling и webhook;

  • интеграцию со Spring Boot;

  • возможность тестировать обработчики.

Можно было сделать минимальный HTTP‑клиент прямо внутри T2M Bridge.

Например:

  • вызвать API MAX;

  • отправить сообщение;

  • загрузить медиа;

  • обработать входящий update.

Для первого прототипа этого могло бы хватить. Но дальше код быстро начал бы смешиваться: бизнес‑логика кросспостинга, работа с пользователями, состояние диалогов, обработка каналов, MAX API, медиа, callback’и.

Поэтому я решил вынести MAX‑часть в отдельную библиотеку.

Так появилась maxlib.

Почему я подключил Codex

На тот момент я не хотел превращать разработку библиотеки в отдельный большой проект.

Цель была практической: быстро получить рабочий фундамент для бота и не тратить недели на ручное написание однотипной инфраструктуры.

Codex хорошо подходил для таких задач:

  • сгенерировать DTO;

  • набросать builder’ы;

  • сделать каркас клиента;

  • подготовить интерфейсы;

  • написать starter‑конфигурации;

  • собрать routing‑слой;

  • сгенерировать первичные тесты;

  • подготовить README и документацию по модулям.

То есть там, где нужно много механического кода, ИИ действительно экономил время.

Но довольно быстро проявилась граница такого подхода.

Codex хорошо генерировал Java‑код, но не всегда строго следовал документации MAX Bot API. Иногда он начинал достраивать API «по аналогии» с другими платформами или по собственной логике.

В результате появлялись:

  • поля, которых нет в документации;

  • объекты, которых нет в реальном API;

  • неверные названия свойств;

  • лишние enum‑значения;

  • методы, которые выглядели правдоподобно, но не соответствовали API;

  • структуры request/response, которые компилировались, но не работали с реальным MAX.

Это важный момент.

ИИ может уверенно написать код, который выглядит правильно. Особенно если код типичный: DTO, JSON mapping, client method, builder.

Но если источник истины — конкретная документация внешнего API, то красивого кода недостаточно. Нужно проверять каждое поле и каждую модель.

Как выглядел рабочий процесс

На практике процесс был таким:

  1. Я описывал Codex конкретную задачу.

  2. Он генерировал каркас.

  3. Я сверял результат с документацией MAX Bot API.

  4. Удалял выдуманные поля и объекты.

  5. Исправлял request/response‑модели.

  6. Прогонял код на demo‑боте.

  7. Исправлял ошибки, которые проявлялись уже при реальных вызовах.

  8. Только после этого считал часть библиотеки условно готовой.

Самый плохой результат получался, когда задача была слишком широкой.

Например:

Сделай библиотеку для MAX Bot API.

Такой промпт почти гарантированно приводит к тому, что ИИ начинает фантазировать. Он строит красивую архитектуру, но часть моделей может быть не из документации, а из его представления о том, как «обычно устроены bot API».

Лучше работал другой подход:

Вот конкретный раздел документации. Сделай модели только для этих request/response. Не добавляй поля, которых нет в документации. Если данных не хватает‑ не придумывай.

Даже в таком режиме всё равно приходилось проверять результат вручную.

Основной вывод: при работе с внешним API ИИ можно использовать как ускоритель, но нельзя использовать как источник истины.

Источник истины — документация и реальные запросы.

Что получилось в итоге

maxlib сейчас собирается под Java 17 и состоит из нескольких модулей:

  • max-model — модели MAX API;

  • max-client-core — клиентский слой и HTTP transport;

  • max-fsm — FSM storage, scenes и wizard primitives;

  • max-dispatcher — dispatcher, router, filters, middleware, screens, upload contracts;

  • max-spring-boot-starter — auto‑configuration для Spring Boot;

  • max-micronaut-starter — auto‑configuration для Micronaut;

  • max-quarkus-starter — auto‑configuration для Quarkus;

  • max-testkit — утилиты для тестирования обработчиков и update‑потока.

Для T2M Bridge мне в первую очередь был нужен Spring Boot starter, потому что основной бот пишется на Java и Spring.

Позже появились Micronaut и Quarkus starter’ы. Это уже развитие той же идеи: runtime библиотеки не должен быть жестко привязан только к Spring Boot.

Если нужен низкоуровневый клиент, можно подключить только max-client-core.

Если нужен runtime‑слой с обработкой update’ов, роутингом и middleware, нужен max-dispatcher.

Если приложение пишется на Spring Boot, Micronaut или Quarkus, проще взять соответствующий starter.

Установка

Для Spring Boot:

dependencies {    implementation("ru.tardyon.botframework:max-spring-boot-starter:<version>")}

Для Micronaut:

dependencies {    implementation("ru.tardyon.botframework:max-micronaut-starter:<version>")}

Для Quarkus:

dependencies {    implementation("ru.tardyon.botframework:max-quarkus-starter:<version>")}

Для vanilla Java:

dependencies {    implementation("ru.tardyon.botframework:max-client-core:<version>")    implementation("ru.tardyon.botframework:max-dispatcher:<version>")}

Для тестов:

dependencies {    testImplementation("ru.tardyon.botframework:max-testkit:<version>")}

В примерах ниже буду использовать Spring Boot, потому что это основной сценарий для моего проекта.

Минимальный Spring Boot бот через polling

Для polling‑режима нужно подключить starter и добавить конфигурацию:

max:  bot:    token: ${MAX_BOT_TOKEN}    mode: POLLING    polling:      enabled: true      limit: 100      timeout: 30s

После этого можно объявить Router как Spring bean:

import java.util.concurrent.CompletableFuture;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import ru.tardyon.botframework.dispatcher.BuiltInFilters;import ru.tardyon.botframework.dispatcher.Router;import ru.tardyon.botframework.message.Messages;@Configurationpublic class BotConfig {    @Bean    Router botRouter() {        Router router = new Router("main");        router.message(BuiltInFilters.command("start"), (message, ctx) -> {            ctx.reply(Messages.text("Привет из Spring Boot"));            return CompletableFuture.completedFuture(null);        });        return router;    }}

Starter создаёт основные runtime‑компоненты:

  • MaxBotClient;

  • Dispatcher;

  • facade для сообщений, callback’ов и chat actions;

  • FSM storage;

  • registry для screens и scenes;

  • polling lifecycle.

Для минимального бота не нужно руками собирать весь runtime.

Webhook‑режим

Для webhook нужно добавить web‑зависимость Spring Boot:

dependencies {    implementation("org.springframework.boot:spring-boot-starter-web")}

И настроить режим:

max:  bot:    token: ${MAX_BOT_TOKEN}    mode: WEBHOOK    webhook:      path: /webhook/max      secret: ${MAX_WEBHOOK_SECRET}      max-in-flight: 64

В этом режиме starter поднимает HTTP endpoint по указанному пути и передаёт входящие запросы в webhook receiver.

Для локальной разработки polling проще. Для production webhook обычно удобнее, если уже есть внешняя точка входа, TLS и reverse proxy.

Dispatcher и Router

Внутри runtime есть два основных понятия: Dispatcher и Router.

Dispatcher принимает update, определяет тип события, подготавливает RuntimeContext и передаёт управление в router’ы.

Router регистрирует обработчики:

  • update;

  • message;

  • callback;

  • error.

Пример с фильтром команды:

router.message(BuiltInFilters.command("start"), (message, ctx) -> {    ctx.reply(Messages.text("Старт"));    return CompletableFuture.completedFuture(null);});

В BuiltInFilters есть готовые фильтры:

  • command(...);

  • textEquals(...);

  • textStartsWith(...);

  • chatType(...);

  • fromUser(...);

  • hasAttachment();

  • callbackDataPresent();

  • callbackDataEquals(...);

  • callbackDataStartsWith(...);

  • state(...);

  • stateIn(...).

Для простого бота этого уже достаточно: можно маршрутизировать команды, текстовые сообщения, callback’и и состояния пользователя.

Но в реальном боте быстро хочется уйти от ручной регистрации всего через router.message(...).

Поэтому в библиотеке есть аннотационный API.

Аннотационный роутинг

Вместо ручной регистрации обработчиков можно описывать route‑классы через аннотации.

Минимальный пример:

import ru.tardyon.botframework.dispatcher.RuntimeContext;import ru.tardyon.botframework.dispatcher.annotation.Command;import ru.tardyon.botframework.dispatcher.annotation.Route;import ru.tardyon.botframework.message.Messages;@Route(value = "menu", autoRegister = true)public final class MenuRoute {    @Command("start")    public void start(RuntimeContext ctx) {        ctx.reply(Messages.text("Команды: /menu"));    }    @Command("menu")    public void menu(RuntimeContext ctx) {        ctx.reply(Messages.text("Меню открыто"));    }}

Поддерживаются аннотации:

  • @Route;

  • @Message;

  • @Text;

  • @Command;

  • @Callback;

  • @CallbackPrefix;

  • @State;

  • @UseFilters;

  • @UseMiddleware.

Для Spring Boot starter’а это удобно тем, что route‑классы можно автоматически регистрировать в контексте приложения.

В T2M Bridge такой подход полезен для разделения сценариев: профиль пользователя, связывание аккаунтов, список каналов, настройки кросспостинга и другие части бота можно держать отдельно.

RuntimeContext

Основной объект, с которым работает обработчик, — RuntimeContext.

Через него доступны:

  • messaging();

  • callbacks();

  • actions();

  • media();

  • fsm();

  • scenes();

  • wizard();

  • reply(...);

  • answerCallback(...);

  • chatAction(...).

Например:

@Command("ping")public void ping(RuntimeContext ctx) {    ctx.reply(Messages.text("pong"));}

Или callback:

@Callback("menu:ok")public void ok(RuntimeContext ctx) {    ctx.answerCallback("OK");}

Задача RuntimeContext — скрыть детали сборки клиента, storage, callback facade и screen runtime.

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

FSM: состояние пользователя

Для диалоговых сценариев нужен FSM.

Например, пользователь нажал кнопку «Связать профили», запросил код, потом должен ввести этот код во втором боте.

Это уже не обработка одной команды, а последовательность шагов.

В maxlib для этого есть FSMContext.

У него есть базовые операции:

  • currentState();

  • setState(...);

  • clearState();

  • data();

  • setData(...);

  • updateData(...);

  • clearData();

  • clear().

Упрощённый пример:

router.message(BuiltInFilters.command("form"), (message, ctx) ->        ctx.fsm().setState("demo.form.name")                .thenAccept(ignored -> ctx.reply(Messages.text("Введите имя"))));router.message(BuiltInFilters.state("demo.form.name"), (message, ctx) ->        ctx.fsm().updateData(Map.of("name", message.text()))                .thenCompose(ignored -> ctx.fsm().setState("demo.form.done")));

По умолчанию в starter’е используется scope USER_IN_CHAT.

Для хранения состояния можно использовать memory storage или Redis.

Spring Boot конфигурация Redis storage:

max:  bot:    storage:      type: REDIS      redis:        key-prefix: max:bot:fsm        ttl: 30m

Для локальной разработки memory storage может быть достаточно. Для сервиса, который должен переживать рестарты, Redis практичнее.

Screens: интерфейс внутри бота

Командный интерфейс быстро становится неудобным.

Можно сделать так:

/start/link/channels/settings/crosspost_on

Но обычному пользователю не хочется помнить список команд.

Поэтому в maxlib есть screen API. Идея простая: описывать экран как модель, состоящую из текста, кнопок и действий.

Минимальный пример через @Screen:

import ru.tardyon.botframework.screen.ScreenContext;import ru.tardyon.botframework.screen.ScreenModel;import ru.tardyon.botframework.screen.Widgets;import ru.tardyon.botframework.screen.annotation.Render;import ru.tardyon.botframework.screen.annotation.Screen;@Screen("profile")public final class ProfileScreen {    @Render    public ScreenModel render(ScreenContext ctx) {        String name = String.valueOf(ctx.params().getOrDefault("name", "Гость"));        return ScreenModel.builder()                .title("Профиль")                .widget(Widgets.text("Имя: " + name))                .showBackButton(true)                .build();    }}

Экран можно открыть из обработчика команды или callback’а, передав параметры. Дальше screen runtime занимается навигацией, callback’ами и перерисовкой.

Для T2M Bridge это важно, потому что пользовательский сценарий состоит из нескольких шагов:

  1. открыть профиль;

  2. связать профили Telegram и MAX;

  3. показать список каналов;

  4. выбрать канал;

  5. связать его с каналом другой платформы;

  6. выбрать режим кросспостинга.

Сделать это только командами можно, но интерфейс получится менее понятным.

@ScreenController: controller‑style API для экранов

В Spring Boot starter есть @ScreenController.

Он нужен, когда удобнее описывать несколько экранов и действий в одном Spring bean.

Пример:

import java.util.Map;import java.util.concurrent.CompletionStage;import ru.tardyon.botframework.screen.ScreenButton;import ru.tardyon.botframework.screen.ScreenContext;import ru.tardyon.botframework.screen.ScreenModel;import ru.tardyon.botframework.screen.Widgets;import ru.tardyon.botframework.spring.screen.annotation.OnScreenAction;import ru.tardyon.botframework.spring.screen.annotation.OnScreenText;import ru.tardyon.botframework.spring.screen.annotation.ScreenController;import ru.tardyon.botframework.spring.screen.annotation.ScreenView;@ScreenControllerpublic final class ProfileController {    @ScreenView(screen = "profile")    public ScreenModel profile(ScreenContext ctx) {        String name = String.valueOf(ctx.params().getOrDefault("name", "Гость"));        return ScreenModel.builder()                .title("Профиль")                .widget(Widgets.text("Имя: " + name))                .widget(Widgets.text("Отправьте новый текст, чтобы изменить имя"))                .widget(Widgets.buttonRow(ScreenButton.of("Сбросить", "reset_name")))                .showBackButton(true)                .build();    }    @OnScreenAction(screen = "profile", action = "reset_name")    public CompletionStage<Void> reset(ScreenContext ctx) {        return ctx.nav().replace("profile", Map.of("name", "Гость"));    }    @OnScreenText(screen = "profile")    public CompletionStage<Void> onText(ScreenContext ctx, String text) {        String next = text == null || text.isBlank() ? "Гость" : text.trim();        return ctx.nav().replace("profile", Map.of("name", next));    }}

Такой стиль удобен для пользовательских flow, где несколько экранов относятся к одной области.

Например:

  • профиль;

  • связывание профилей;

  • список каналов;

  • настройки конкретного канала.

Один controller может держать несколько связанных screen id, а navigation и handlers остаются рядом.

Widgets

Если часть интерфейса повторяется на разных экранах, её можно вынести в widget.

Для этого есть @WidgetController.

Идея такая:

  • widget сам умеет отрендерить свой фрагмент;

  • widget может обрабатывать свои action’ы;

  • screen может подключить widget через reference.

Упрощённо:

Widgets.ref("demo.counter")

А сам widget controller описывает render и action handlers.

Это полезно для переиспользуемых блоков: статус профиля, список быстрых действий, фрагмент настроек, общий header или footer экрана.

Middleware

В maxlib есть два уровня middleware:

  • OuterMiddleware — на уровне Dispatcher;

  • InnerMiddleware — на уровне Router или конкретного annotated handler.

Middleware может обогащать RuntimeContext.

Пример:

public final class AttemptMiddleware implements InnerMiddleware {    @Override    public CompletionStage<DispatchResult> invoke(RuntimeContext context, MiddlewareNext next) {        context.putEnrichment("attempt", 1);        return next.proceed();    }}

В реальном боте middleware удобно использовать для сквозных задач:

  • логирование;

  • метрики;

  • проверка доступа;

  • загрузка пользователя;

  • ограничение действий;

  • enrichment контекста.

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

Тестирование обработчиков

Когда бот состоит из множества сценариев, тестировать его только вручную неудобно.

Для этого в maxlib есть max-testkit.

Его задача — помочь проверить обработчики и поток update’ов без реального запроса к MAX API.

Тест должен отвечать на несколько вопросов:

  • какой update пришёл;

  • какой handler его обработал;

  • какой ответ бот попытался отправить;

  • как изменилось состояние.

Для библиотечного кода это особенно важно.

Codex может быстро сгенерировать много инфраструктуры, но ошибки в деталях всплывают именно на тестах, demo‑приложениях и реальных вызовах API.

Особенно это касается моделей MAX API. Если ИИ придумал поле, которого нет в документации, компилятор может ничего не заметить. Ошибка проявится только когда реальный API вернёт или не вернёт ожидаемые данные.

Vanilla Java

Хотя основной сценарий для меня ‑Spring Boot, библиотека не завязана только на него.

В vanilla Java можно руками собрать:

  • MaxBotClient;

  • Router;

  • Dispatcher;

  • FSM storage;

  • polling runner.

Упрощённо это выглядит так:

MaxApiClientConfig config = MaxApiClientConfig.builder()        .token(System.getenv("MAX_BOT_TOKEN"))        .build();MaxHttpClient transport = new OkHttpMaxHttpClient(config.baseUri(), okHttpClient);JsonCodec jsonCodec = new JacksonJsonCodec();MaxBotClient botClient = new DefaultMaxBotClient(config, transport, jsonCodec);Router router = new Router("main");router.message(BuiltInFilters.command("start"), (message, ctx) -> {    ctx.reply(Messages.text("Привет из vanilla Java"));    return CompletableFuture.completedFuture(null);});Dispatcher dispatcher = new Dispatcher()        .withBotClient(botClient)        .withFsmStorage(new MemoryStorage())        .includeRouter(router);

Такой вариант полезен, если не хочется тащить фреймворк или нужно встроить бота в существующее приложение.

Где Codex помог

Codex хорошо помог в местах, где нужно много однотипной работы.

Например:

  • создать структуру модулей;

  • набросать DTO;

  • подготовить builder’ы;

  • сделать первичный client layer;

  • собрать starter’ы;

  • написать часть тестов;

  • подготовить документацию;

  • быстро накидать demo‑примеры.

Если задача хорошо ограничена, результат получается полезным.

Например:

Сделай Spring Boot auto‑configuration для уже существующих компонентов.

Или:

Напиши тесты для этого router handler.

Или:

Подготовь README для модуля по уже существующему коду.

В таких местах ИИ действительно ускоряет работу.

Где Codex мешал

Самая неприятная категория ошибок — выдуманные элементы API.

Codex периодически писал код так, как будто он знает, каким должен быть MAX Bot API. Он достраивал модели по аналогии с другими bot API или просто по внутренней логике.

В результате появлялись поля и объекты, которых не было в документации.

На первый взгляд код выглядел нормально:

  • классы назывались правдоподобно;

  • JSON‑поля выглядели логично;

  • builder’ы собирались;

  • тесты могли проходить;

  • IDE не показывала проблем.

Но при сверке с документацией или реальном запросе выяснялось, что часть модели не соответствует API.

Это особенно опасно в библиотеке.

В прикладном коде ошибка может затронуть один сценарий. В библиотеке ошибка в модели или transport‑слое размножается по всем пользователям библиотеки.

Поэтому большую часть времени после генерации я тратил не на исправление синтаксиса, а на проверку соответствия документации:

  • есть ли такое поле в API;

  • правильно ли оно называется;

  • обязательное оно или нет;

  • какой у него тип;

  • может ли оно быть null;

  • есть ли такой объект в реальном response;

  • не добавил ли ИИ лишний enum;

  • не объединил ли он две разные сущности в одну.

После этого пришлось сделать для себя правило:

Если Codex пишет код для внешнего API, каждую модель нужно проверять по документации. Не выборочно, а полностью.

Что получилось удачно

Первое: разделение на модули.

Можно использовать только typed client, можно подключить dispatcher, а можно взять starter под нужный фреймворк.

Второе: аннотационный routing и screen API.

Для небольшого demo‑бота можно жить на командах. Но для реального пользовательского интерфейса внутри бота screens оказались намного удобнее.

Третье: Codex хорошо помог на boilerplate‑коде.

DTO, builders, starter boilerplate, первичные тесты, документация — это как раз те места, где ИИ экономит время.

Четвёртое: demo‑приложения.

Я специально держу demo‑проекты для разных фреймворков, чтобы проверять не только компиляцию библиотеки, но и реальное подключение starter’ов.

Что я бы сделал иначе

Если бы я начинал заново, я бы жёстче разделил работу с Codex на два режима.

Первый режим — генерация инфраструктуры.

Здесь можно давать ИИ больше свободы:

  • starter;

  • wiring;

  • тестовые утилиты;

  • документация;

  • примеры;

  • boilerplate.

Второй режим — модели внешнего API.

Здесь свободы быть не должно.

Для таких задач я бы сразу задавал ограничения:

  • не добавлять поля, которых нет в документации;

  • не придумывать enum‑значения;

  • не объединять похожие сущности;

  • не переносить поведение из других API;

  • если в документации нет информации — оставлять TODO, а не фантазировать.

И отдельно вести чек‑лист сверки с документацией.

Потому что именно несоответствие реальному API оказалось главным источником ошибок после генерации.

Где это используется в T2M Bridge

В T2M Bridge maxlib используется как MAX‑часть инфраструктуры.

С её помощью бот:

  • принимает события от MAX;

  • показывает пользователю экраны;

  • обрабатывает callback’и;

  • хранит состояние сценариев;

  • работает с каналами;

  • отправляет сообщения и медиа;

  • участвует в связке MAX‑канала с Telegram‑каналом.

В этой статье я не разбираю сам кросспостинг подробно. Это тема третьей части цикла.

Там уже будут вопросы другого уровня:

  • как связать аккаунты пользователя в двух мессенджерах;

  • как сопоставить Telegram‑канал и MAX‑канал;

  • как хранить соответствия между исходными и перенесёнными постами;

  • как переносить разные типы медиа;

  • что делать с replies, редактированием и удалением;

  • где проходит граница между библиотекой и бизнес‑логикой бота.

Итоги

maxlib начиналась как вспомогательная библиотека для одного бота.

Я не хотел тратить на неё слишком много времени, поэтому значительную часть инфраструктурного кода писал с помощью Codex. Это действительно ускорило старт, но не отменило ручное проектирование, проверку и исправление ошибок.

Главная проблема после ИИ была не в том, что код не компилировался. Наоборот, чаще всего он выглядел вполне убедительно.

Проблема была в том, что Codex периодически придумывал поля, объекты и структуры, которых не было в документации MAX Bot API.

Поэтому основной вывод получился такой:

ИИ хорошо ускоряет написание инфраструктурного кода, но при работе с внешним API источником истины должна оставаться документация, а не сгенерированная модель.

В текущем виде maxlib закрывает несколько уровней:

  • typed client к MAX API;

  • dispatcher/router runtime;

  • фильтры и middleware;

  • FSM;

  • screens и widgets;

  • polling и webhook;

  • starter’ы для Spring Boot, Micronaut и Quarkus;

  • testkit.

В следующей статье расскажу про telegalib — Java‑библиотеку для Telegram Bot API, которую я писал уже иначе: не настолько «сгенерировать каркас и поправить», а ближе к формату 50/50 между мной и Codex.

Репозиторий maxlib:

https://github.com/tardyon‑soft/maxlib

Живой MVP и документация T2M Bridge:

https://docs.t2m‑bridge.ru

Новости разработки и roadmap:

https://t.me/telega2max

ссылка на оригинал статьи https://habr.com/ru/articles/1049564/