Как стать автором
Поиск
Написать публикацию
Обновить

Способ стабильного создания больших приложений с помощью ИИ

Уровень сложностиПростой
Время на прочтение7 мин
Количество просмотров1.7K

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

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

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

Вот список преимуществ в разработки приложений мелкими компонентами на нейронках:

  1. Не нужны платные или глупые AI-агенты, можно общаться с AI через чаты

  2. Если требуются правки в код, то нейросеть с высокой долей вероятности ничего в нем не забудет и внесет их корректно.

  3. Поправленный код нейронкам легко выдать целиком (а не инструкцию о том, что где поправить).

  4. С написание кода справляется большое количество нейронных сетей.

Для реализации бекенда из маленьких компонентов были выбраны Yandex Cloud Функции. Главным аргументом в пользу их выбора было, то что у меня была короткая возможность попользоваться ими бесплатно.

Из преимущества отмечу, что запрограммированные функции почти сразу доступны для REST-запросов или асинхронных вызовов, без лишних приседаний.

Из минусов:

  1. По умолчанию их доступно всего 10, а для моей архитектуры потребуются сотни. Однако, после письма в техническую поддержку мне увеличили количество функций до 100.

  2. Их нужно "разогревать". То есть если вы давно не вызывали ваши Яндекс Функции, то первые их ответы будут либо ошибками, либо слишком долгими. Как временное решение, мой Фронтенд автоматически вызывает их по три раза, если получает ошибку.

Для реализации фронтенда я использовал React-шаблон на https://codesandbox.io/, изменения в коде я комичу в git, откуда они автоматически применяются к развернутому на Versel приложению. Сразу скажу, что во фронтовой части моего приложения у меня не получилось маленьких компонентов и я ощущаю все "прелести" работы с большими кусками кода на ИИ.

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

В качестве языка программирования функций я выбрал "Node.JS 22", потому что нашел на нем понятный пример как из функций подключаться к PostgreSQL базе данных.

Архитектура приложения через картинку, нарисованную в Paint'е

Я покажу архитектуру на основании одного из экранов моего приложения. Остальные экраны работают примерно также.

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

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

Промты для написания Яндекс Функций

Путем некоторого количества попыток было выявлено, что Промт для написания Яндекс Функции должен состоять из двух частей:

  1. Текста который вы напишете в сообщение нейронному чату. Этот как правило простое "Напиши функцию по ТЗ, дай пошаговую инструкцию по ее развертыванию."

    1. Если ваша нейросеть - Grok, то нужно еще добавить "Ответ и комментарии в коде пиши на русском языке"

  2. Файлу с ТЗ (или спецификацией)

Подробное описание функции критически важно писать в редакторе, поддерживающим структурированные списки - через них кайфово делать табуляцию при описании входных / выходных данных и алгоритмов. Передавать ТЗ в нейронную сеть тоже следует файлом, а не копирование / вставкой, так как таким образом потеряется табуляция.

Структура ТЗ (в данном случае слово "ТЗ" используется для простоты, наши документы не будут похожи на обычные ТЗ):

  1. Назначение функции

  2. Наименование функции, указание языка на котором ее нужно писать. В нашем случае "Node.JS 22"

  3. Если функция будет вызваться асинхронно об этом нужно написать в явном виде в самом начале ее описания.

  4. Структурированное описание входных и выходных данных в функцию, включая типы данных чуть шире, чем они есть в JSON, в частности советую уточнять типы чисел (с запятой или нет). Пример будет ниже.

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

  6. (Опционально) Интеграционные контракты других функций, если вы собираетесь их вызывать из своей функции. Я ушел от этой практики, теперь когда я описываю процесс заполнения входных данных в другую функцию я уточняю их тип, то есть у меня интеграционный контракт получается прямо в алгоритме.

  7. Подробный структурированный алгоритм с описанием логирования. Описание обработки очевидных исключений можно не делать - нейронки все равно сделают эту обработку в коде.

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

Пример Промта для Яндекс Функции

Вырезки из документации в Промте

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

Функция должна уметь обрабатывать “предварительные запросы”. Пример соответствующего кода вот:

module.exports.handler = async (event, context) => {
  // Обработка предварительного запроса OPTIONS
  if (event.httpMethod === 'OPTIONS') {
    return {
      statusCode: 204,
      headers: {
        'Access-Control-Allow-Origin': '*', // Или укажите конкретный домен, например, 'https://your-app-domain.com'
        'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type',
        'Access-Control-Max-Age': '3600',
      },
      body: '',
    };
  }
 // Основная логика для POST/GET запросов
  const responseBody = {
    // Ваша текущая логика обработки запроса
    message: 'Request processed successfully',
  };
 return {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(responseBody),
  };
};

Если ваша функция должна обращаться к PostgreSQL базе данных, то вставьте еще и такой текст:

Подключение к базе необходимо производить с помощью следующего кода:
const connectionString = `postgres://${process.env.DB_USER}:${context.token.access_token}@${process.env.DB_PROXY_ENDPOINT}/${process.env.DB_CONNECTION_ID}?ssl=true`;


    const client = new Client({ connectionString });


    try {
        await client.connect();
    } catch (error) {
        console.error("Ошибка подключения к базе данных:", error);
        console.error("Connection String (for debug):", `postgres://${process.env.DB_USER}:***@${process.env.DB_PROXY_ENDPOINT}/${process.env.DB_CONNECTION_ID}?ssl=true`);
        return buildResponse(500, { errorText: "Не удалось подключиться к базе данных" });
    }

Если ваша функция будет асинхронно вызывать другую функцию, то

Асинхронный вызов функции
Чтобы вызвать функцию асинхронно, необходимо указать параметр строки запроса ?integration=async. При использовании такой формы вызова функция не может анализировать и задавать HTTP-заголовки:
Содержимое тела HTTPS-запроса передается первым аргументом (без преобразования в JSON-структуру).
Содержимое тела HTTPS-ответа совпадает с ответом функции (без преобразования и проверки структуры), HTTP-статус ответа: 202.

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

Абстрактный пример обработчика входных данных для асинхронной функции:
// index.js
module.exports.handler = async (event, context) => {
  // 1) Нормализация входа (sync через API GW vs async ?integration=async)
  const isApiGw = event && typeof event === 'object' && 'httpMethod' in event;
  const isAsync = !isApiGw;

  let input;
  try {
    if (isApiGw) {
      if (event.httpMethod === 'OPTIONS') return preflight();
      if (event.httpMethod !== 'POST') return http405();
      input = typeof event.body === 'string' ? JSON.parse(event.body) : event.body;
    } else {
      input = typeof event === 'string' ? JSON.parse(event) : event;
    }
    if (!input) throw new Error('EmptyBody');
  } catch (e) {
    return isAsync ? ack202('Bad input') : http400('Bad input');
  }

  // 2) Валидация входа (минимум)
  const { userId, monsterId } = input;
  if (!userId || !monsterId) {
    return isAsync ? ack202('Missing fields') : http400('Missing fields');
  }

  // 3) Бизнес-логика с идемпотентностью
  try {
    // ... do work ...
    const data = { ok: true };
    return isAsync ? ack202(data) : http200(data);
  } catch (e) {
    // 4) Контролируемые ошибки
    console.error('error:', e);
    return isAsync ? ack202('Internal error') : http500('Internal error');
  }
};

// Вспомогательные ответы
const headers = { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' };
const preflight = () => ({ statusCode: 204, headers, body: '' });
const http200 = (x) => ({ statusCode: 200, headers, body: JSON.stringify(x) });
const http400 = (m) => ({ statusCode: 400, headers, body: JSON.stringify({ errorText: m }) });
const http405 = () => ({ statusCode: 405, headers, body: JSON.stringify({ errorText: 'Method not allowed' }) });
const http500 = (m) => ({ statusCode: 500, headers, body: JSON.stringify({ errorText: m }) });
const ack202 = (x) => ({ statusCode: 202, body: typeof x === 'string' ? x : JSON.stringify(x) });

Особенности написания алгоритмов

Старайтесь, чтобы в каждой вашей строке была ссылка на какой-то объект, который участвует в алгоритме. Например:

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

Это очень сильно уменьшит путаницу нейронной сети что и откуда брать. Но не до нуля конечно.

Опыт написания Яндекс Функций на различных нейронных сетях

Самый лучший опыт по написанию Яндекс Функций у меня был с моделью Grok 3. К сожалению, на момент написания статьи Grok теперь сам выбирает версию модели, которая подойдет пользователю, и для написания функций он выбирает Grok 4, который на мой взгляд медленнее и тупее.

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

С учетом недоступности Grok 3 важная рекомендация для использования всех нейронных сетей: они забывают написать помимо файла с кодом еще и файл "package.json", который необходим для развертывания функции. Просто напомните им об этом, если его не будет.

На сегодняшний день я пользуюсь:

  1. Grok (В тексте сообщения к которому приложено ТЗ попросите его отвечать на русском языке, в противном случае он будет общаться на английском)

  2. Chat GPT 5

  3. Gemini 2.5 Pro

  4. DeepSeek

Все перечисленные нейронки стабильно справляются с задачей. Chat GPT 5 будет даже пытаться чуть-чуть с оптимизировать ваш алгоритм.

Рекомендация по тестированию

Встроенный в Яндекс Функции инструмент для тестирования требует написания стремных запросов в функцию, чтобы ее протестировать, поэтому я просто копирую ее URL и вызываю через Postman

Пока мое приложение в разработке, все методы (= функции) доступны без авторизации. Ближе к релизу буду исправлять.

Промты на Фронт

Особенности:

  1. Просите каждый значимый кусок UI писать в отдельном tsx файле, но такой супер атомарности, как в Яндекс Функциях достигнуть не получится

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

UI должен N раз повторно вызывать Яндекс Функции, в том случае, если они ответили сетевой ошибкой.

Теги:
Хабы:
-2
Комментарии13

Публикации

Ближайшие события