Последние несколько месяцев я занимаюсь продуктом, который работает почти полностью за счет ИИ — около 80% основного функционала. Еще пару лет назад для создания похожего по возможностям продукта понадобилась бы целая команда бэкенд-разработчиков. Сейчас же мой бэкенд — это просто набор промптов к LLM.
Не спешите закидывать помидорами. Это новый тип AI-based продуктов, которых будет становиться все больше. И для некоторых задач такой подход действительно отлично подходит.
Но все оказалось не так просто, как казалось. ИИ под капотом открывает целый спектр проблем — от сложности выбора среди множества инструментов до нестабильных ответов.
Интро — «а давайте внедрим ИИ в наш продукт»
С большой вероятностью в какой-то момент ваш руководитель придёт с идеей «нужно прикрутить AI к нашему продукту». Потому что модно, потому что у конкурентов есть, или просто потому что.
Причина простая: рынок перегрет, хайпа много, и всем кажется, что если у продукта нет AI-функций, значит он отстаёт.
Поэтому в 2024–2025 мы наблюдаем странную тенденцию:
В продуктах появляются поп-апы «Try our new AI feature!», которые ничего не объясняют, не решают никакой боли, и которые 90% пользователей закрывают сразу же.
Иногда такие фичи выглядят как:
• авто-резюме, которое никто не просил,
• генерация чего-то, что проще сделать вручную.
В итоге получается набор случайных элементов в интерфейсе, которые существуют не потому, что нужны пользователю, а потому что компания «должна что-то показать рынку».
Такое действительно встречается — мы не осуждаем. Возможно, и к вам придут с такой же задачей и попросят куда-то впилить AI.
Но иногда AI идеально вписывается в архитектуру продукта — будто он буквально был создан для такого типа приложения.
AI-based продукты: когда LLM — это и есть ваш бэкенд
Когда я начал разбираться в продуктах вокруг моделей, меня удивило, что во многих кейсах модель заменяет целый бэкенд — и делает это лучше, чем любые базы знаний, правила и ручная бизнес-логика, которую мы раньше писали месяцами.
Есть категории продуктов, где LLM буквально «сломали рынок», потому что старый подход просто не выдерживает конкуренции.
Вот несколько примеров:
Изучение языков
Раньше нужны были:
• огромные словари,
• грамматические базы,
• готовые примеры,
• постоянное обновление контента,
• редакторы, лингвисты и т.д.
Сегодня даже самая простая доступная модель выдаёт:
• естественные примеры,
• точный перевод,
• корректировки ошибок,
• адаптацию под уровень,
и всё это динамически — в контексте конкретного запроса пользователя.
Конкурировать со старым подходом просто невозможно.
Кастомный «словарь» или база знаний сегодня — как музейный экспонат.
Я как раз делаю такое приложение (ссылка на AppStore) и ещё буду возвращаться к нему в следующих темах, где разберу промпты на конкретных примерах.
Обучающие продукты и репетиторы
Личные объяснения, советы, разборы ошибок — раньше это невозможно было автоматизировать без огромного количества сценариев.
Теперь модель делает это «как преподаватель» — без тонны хардкода.
Умный поиск и аналитика
Всю жизнь компании пытались сделать поиск по документации «как в Google».
Сейчас модель выдаёт объяснения или summary лучше, чем любые правила, которые мы писали годами.
Поддержка и help-центры
Раньше — огромные базы FAQ, скучные боты с кнопками, сценарии, ветвления.
Теперь — модель, которая читает документацию и отвечает как живой специалист.
И даже простая модель часто оказывается полезнее хардкода из 5000 строк.
Какие AI-инструменты выбрать
При подключении AI к продукту разработчики чаще всего выбирают один из двух подходов: использовать API конкретного провайдера (OpenAI, Anthropic, Google, Mistral) или применять агрегаторы моделей. Первый вариант даёт доступ к «фирменным» моделям напрямую, но требует отдельной интеграции под каждого поставщика, разных ключей, политик, лимитов и цен. В итоге даже простая задача — сравнить модели или переключиться с одной на другую превращается в дополнительную работу и усложняет архитектуру.
Агрегаторы — например, OpenRouter — решают эту проблему. Это единый API-слой, который предоставляет доступ к десяткам моделей от разных компаний через одну и ту же спецификацию. Достаточно изменить название модели, а не переписывать весь код. Платформа также даёт прозрачную аналитику и трекинг токенов. В общем, это то что нужно.
В дальнейших примерах мы будем использовать именно OpenRouter, потому что он позволяет быстро переключаться между моделями, тестировать разных провайдеров, экономить на стоимости запросов и при этом писать код только один раз. Такой подход гораздо практичнее для стартапов, пет-проектов и приложений, которые активно развиваются и ищут оптимальное соотношение цены, скорости и качества.
Достаточно создать аккаунт на https://openrouter.ai/, сгенерировать токен и сразу можно экспериментировать. Есть даже тестовый лимит для новых аккаунтов.
Архитектура AI-продукта на минималках
Даже самый простой AI-продукт должен иметь минимальную архитектуру, похожую на нормальный сервис.
Вот схема, которая спасает в большинстве случаев:
Front → Backend → LLM provider (OpenRouter) → Retry/Fallback → Response Normalizer
Разберём по пунктам.
Почему нельзя отправлять запросы в модель прямо с фронта
Теоретически это работает: в OpenRouter мы получаем уникальный токен и можем слать запросы прямо с фронта. На этапе тестирования так действительно можно делать, я именно так и начинал. Но выкатывать это в прод — плохая идея.
Чуть подробнее:
1. Токены и безопасность
Вы не можете хранить ключи к провайдеру на фронте. Они утекут — неизбежно. После этого придётся заводить новый токен и менять его в запросах. А заменить ключ без релиза фронта уже не получится, особенно если речь про мобильное приложение.
2. Нужен контроль над запросами
Иногда пользователи случайно (или специально) отправляют огромные тексты, странные команды или сотни запросов подряд. Фронт не должен напрямую бить в провайдера. На фронте легко сделать дыру, через которую будут улетать лишние запросы. Это либо сожжёт токены впустую, либо провайдер автоматически сделает этот токен неактивным из-за подозрительной активности.
3. Нужен нормальный трейсинг и логирование
Чтобы понять, почему модель ответила странно, нужно логировать весь запрос и ответ. На фронте это сделать сложнее.
4. Всё равно понадобится нормализация
Модель почти никогда не возвращает идеально то, что вы ожидаете. Обработка должна происходить на сервере, чтобы на фронтенде было минимум бизнес-логики.
5. Ретраи и фолбэки невозможны на фронте
Если модель упала, вернула ошибку или нужно автоматически переключиться на другую — это задача бэкенда.
В общем, в идеальном мире фронт должен отправлять на бэкенд только данные, которые меняются — например, кусок текста, который нужно перевести. А сервер уже делает остальное: формирует промпт, выбирает модель, подставляет токен и отправляет полный запрос в OpenRouter.
Что такое «нормализация ответа» и зачем она нужна
Модель — это не чистая функция, а собеседник. У неё бывают «настроения». Сегодня она возвращает аккуратный JSON. Завтра забудет кавычки. Послезавтра добавит лишнюю строку «Конечно, вот ваш ответ!». В общем, штука не самая стабильная.
Нормализация — это этап, где вы приводите всё к единому виду. Или хотя бы максимально пытаетесь это сделать.
Типичные шаги:
• обрезать лишний текст до/после JSON
• пытаться «спасать» поломанный формат
• вычищать служебные фразы вроде «Sure!»
• проверять, что структура соответствует схеме
• убирать лишние пробелы, переносы строк и невидимые символы
Короче: модель творит — вы приводите к порядку.
Например, в моём приложении я уже вынес довольно много логики в программную нормализацию. Вот несколько случаев:
1-й пример

Мне приходится переводить сербскую кириллицу в сербскую латиницу с помощью кода. Здраво → Zdravo. Потому что модель иногда игнорирует просьбу всегда отдавать ответ в латинице.
Если не вдаваясь в лор, то сербы пишут и так и так. Но для AWS озвучки текста нужна именно латиница.
2-й пример

Когда я запрашиваю варианты неправильных ответов, приходится вычищать дубли правильного варианта.
Также в этом примере нужно приводить первые буквы вариантов к единому регистру — в таком же регистре, как у перевода. Это тоже удобно решать кодом.
Валидация ответа по схеме
После нормализации — валидация.
Чаще всего используют:
• Zod
• Yup
• JSON Schema
• Ajv
Задача простая: модель должна вернуть объект, который точно подходит под схему.
Если нет — либо ретрай, либо fallback-модель.
Это критично для продуктов, где результат не может быть «примерным». Часто ответ дальше участвует в бизнес-логике приложения, где ожидается объект с чёткой структурой. Если структура окажется неверной — можно легко получить белый экран.
Минимальный Node.js-бэкенд (рабочая заготовка)
Вот пример самого маленького сервиса с прокси, нормализацией и схемой.
// server.js
import express from "express";
import fetch from "node-fetch";
import { z } from "zod";
const app = express();
app.use(express.json());
// схема ожидаемого ответа от модели
const ResponseSchema = z.object({
result: z.string(),
});
app.post("/api/llm", async (req, res) => {
const userInput = req.body.text;
const payload = {
model: "openai/gpt-4o-mini",
messages: [
{ role: "system", content: "Return JSON: {\\"result\\": \\"...\\"}" },
{ role: "user", content: userInput },
],
};
try {
const response = await fetch("<https://api.openrouter.ai/v1/chat/completions>", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const data = await response.json();
// нормализация ответа
const raw = data.choices?.[0]?.message?.content || "";
const cleanJson = extractJson(raw); // твоя функция парсинга
// валидация
const parsed = ResponseSchema.parse(cleanJson);
res.json(parsed);
} catch (err) {
console.error("Error:", err);
res.status(500).json({ error: "LLM error" });
}
});
function extractJson(text) {
try {
const start = text.indexOf("{");
const end = text.lastIndexOf("}");
return JSON.parse(text.substring(start, end));
} catch {
return { result: "" }; // fallback, лучше сделать ретрай
}
}
app.listen(3000, () => console.log("Server started on 3000"));
Этот каркас:
• получает текст,
• отправляет его в модель,
• нормализует ответ,
• валидирует по схеме,
• возвращает чистый JSON.
Дальше можно добавить:
• кэширование,
• ретраи,
• fallback-модели,
• логирование,
• метрики,
• ограничения на размер запроса.
Промпт-инженерия без магии: как писать промпты, которые работают
Когда мы общаемся с ChatGPT, мы пишем промпты в свободной форме — и нас понимают. Если модель что-то не уловит, она уточнит детали и продолжит думать.
Но при использовании LLM внутри продуктов всё гораздо сложнее. От ответа напрямую зависит бизнес-логика. И уточнять у пользователя ничего не получится — у нас буквально одна попытка максимально подробно, но при этом ёмко объяснить, что именно нам нужно.
Поэтому хорошая промпт-инженерия — это просто хорошая спецификация.
System vs User: что куда писать и почему это важно
Разделение system/user — это база стабильного поведения модели.
Что помещать в system:
• строгие правила поведения модели;
• формат ответа;
• ограничения (без лишнего текста, без пояснений, только JSON и т.п.);
• роль и контекст («Ты — сервис, который исправляет текст…»).
System — это фундамент стабильного поведения. Он задаётся один раз и не меняется.
Что помещать в user:
• конкретный запрос пользователя;
• данные, которые нужно обработать;
• уточнения, относящиеся только к этому запросу;
• примеры, если задача может быть неоднозначной.
Если говорить по-человечески:
• system = правила игры
• user = что нужно сделать прямо сейчас
Это разделение защищает от ситуаций, когда модель решает: «в этот раз можно не возвращать JSON, давайте просто поговорим».
Простой пример:
{
"model": "google/gemini-2.0-flash",
"messages": [
{
"role": "system",
"content": `Ты — сервис поиска фильмов по актёрам.
Пользователь будет присылать только имя актёра.
Ты должен вернуть список известных фильмов с его участием.
Отвечай строго в JSON-формате:
{"actor": "имя актёра", "movies": ["фильм1", "фильм2", ...]}
Никаких пояснений или текста вне JSON.`
},
{
"role": "user",
"content": "Tom Hanks"
}
]
}
Ожидаемый ответ модели:
{
"actor": "Tom Hanks",
"movies": [
"Forrest Gump",
"Saving Private Ryan",
"Cast Away",
"The Green Mile",
"Catch Me If You Can"
]
}
Ну и дальше в пользовательском интерфейсе вместо Tom Hanks можно подставить, например, результат ввода пользователя. А после ответа от модели уже работать со списком фильмов — например, красиво отобразить их на странице.
Структура хорошего промпта
Вот проверенная структура:
• Кто ты (роль модели)
• Что нужно делать
• Что НЕ нужно делать
• Формат вывода с примером
• Правила для неоднозначных случаев
• Указание, что пользовательский текст может быть с ошибками
• Инструкция не добавлять пояснений и лишних слов
Модель чувствует себя гораздо увереннее, когда вы задаёте ей строгий каркас.
Пример промпта для получения соджестов в моём приложении:
[
{
role: "system",
content: `You are an autocomplete generator for the English language.
Return ONLY a valid JSON object with one key: "suggestions".
Value is an array of strings.
Definitions:
- "start" means index 0 of the suggestion.
- "exact match" means the Prefix (trimmed, case-insensitive) is a valid standalone word/lemma in English.
HARD CONSTRAINTS:
- All suggestions MUST start exactly with the given Prefix (case-insensitive).
- If an exact match exists, it MUST be suggestion[0] (echo the word itself).
This rule OVERRIDES all other rules (length, popularity, etc.).
- CRITICAL: All suggestions MUST be real, existing words in English.
DO NOT invent, create, or make up words. Only return words that actually exist in the English vocabulary.
If you are unsure if a word exists, do not include it.
- Suggestions should be natural and common in real English usage.
- Max 30 characters per suggestion. No trailing punctuation. No duplicates.
- If nothing reasonable exists, return {"suggestions": []}.
SORTING PRIORITY:
1) EXACT MATCH FIRST (if it exists) — the exact word equal to the Prefix (preserve user casing if possible).
2) Then, by highest user-typing likelihood (most common first).
SELF-CHECK BEFORE OUTPUT:
- Verify every suggestion is a real, existing word in English. Remove any invented or non-existent words.
- If an exact match exists and suggestions[0] is not exactly that word, fix it so it is.`,
},
{
role: "user",
content: `Prefix: "${query}"
Return a JSON like: {"suggestions": ["..."]}`,
},
];

Он используется на этом экране и возвращает самые вероятные продолжения введённой части слова или фразы, выдавая примерно такой результат:
// Пользователь вводит "Краси"
// Модель возвращает ответ
{
suggestions: ["Красивый", "Красить", "Краситель", ...]
}
В начале промпт был простым, а потом постепенно оброс дополнительными правилами.
Почему промпт стал таким:
• “If an exact match exists, it MUST be suggestion[0] (echo the word itself)” — это правило появилось, когда нужно было ставить существующее слово на первое место. До этого модель, например, ставила “go back” первым, когда я вводил “go”. А нужно было, чтобы было именно “go”, потому что это слово уже существует.
• “All suggestions MUST be real, existing words in English” — появилось после того, как модель начала выдумывать слова. Например, я вводил “краси…”, а модель придумывала что-то вроде “красифицировать” или “красивенный”.
• “Suggestions should be natural and common in real English usage.” — это правило возникло, когда модель стала подсовывать редкие и малоупотребимые слова, а мне нужны были самые распространённые варианты.
И примерно так появились почти все остальные пункты. Почти каждый промпт в приложении прошёл свою эволюцию — от простого запроса до полноценной спецификации.
После этого стабильность выросла почти до идеала.
Как бороться с галлюцинациями
Несколько реальных методов, которые действительно помогают:
1. Явное запрещение творчества
«Do not add anything not present in the input.»
«Do not invent facts.»
«If unsure — say you are unsure.»
2. Чёткие правила поведения при сомнении
Например: «If the text is unclear, choose the most likely meaning used by native speakers.»
Если модель сомневается, она может предпочесть вернуть пустой результат, потому что не любит ошибаться. Поэтому нужно явно указать ей, что делать в таких ситуациях.
3. Дублирование ключевых инструкций
Модели хорошо реагируют на повторение. Если что-то критично — скажите это дважды.
4. Примеры “плохого” и “хорошего” ответа
Если дать явные примеры, то это сильно повышает точность.
5. Сужение задачи
Чем уже задача, тем меньше шансов, что модель начнёт фантазировать.
В том же примере с саджестами из скрина выше переводы этих саджестов я вынес в отдельный запрос. Потому что модель сильно тупила, когда ей нужно было и сгенерировать саджесты с вероятным продолжением слова, и одновременно перевести их в одном промпте.
Как снизить затраты
Мне пока сложно судить о затратах, потому что приложением пользуется меньше 1000 человек, и за месяц уходит всего 1-2 доллара. Но у меня есть пару советов, как максимально сэкономить на токенах.
1. Умное кэширование
Пользователи часто отправляют похожие запросы. И если проанализировать промпты, может оказаться, что большая часть запросов — это повторяющиеся действия.
В таких случаях простой Redis творит чудеса.
2. Выбор моделей под сценарии, а не «лучшую модель всегда»
Это распространённая ошибка: отправлять всё в одну самую дорогую модель.
Правильнее делить:
• лёгкие запросы → дешёвые модели
• сложные, требующие логики → более дорогие
• структурные задачи → наиболее стабильные
• длинные тексты → модели с большим контекстом
Комбинация = оптимальная цена.
В том же OpenRouter все цены открыты — их можно посмотреть на этой странице:

Кстати, там есть и бесплатные модели, которые отлично подходят для тестирования.
Философия и выводы
AI — это очень мощный инструмент для продукта. Он может быть капризным и не всегда стабильным. Поэтому к нему нужно относиться не как к магическому API, а как к инструменту с характером.
И да, больше половины успеха — это не сама модель, а всё, что вы построили вокруг неё: спецификация, архитектура, тесты, метрики и хороший UX.
Надеюсь это пригодится вам, когда будете прикручивать AI к своим продуктам.
Здесь ссылка на моё мобильное приложение VibeLing в AppStore, из которого я брал примеры. Пока только для iOS, Android появится примерно через неделю.
Ещё я веду тг-канал про IT и разработку. В последнее время больше пишу про свой путь соло-разработчика — как я совмещаю разработку собственных продуктов с наймом.
Комментарии (4)

chuzhiegrably
11.12.2025 16:21• лёгкие запросы → дешёвые модели
• сложные, требующие логики → более дорогие
• структурные задачи → наиболее стабильные
• длинные тексты → модели с большим контекстомА что из себя представляет оценщик сложности запроса?

vital_pavlenko Автор
11.12.2025 16:21Обычно в продукте набор промптов под разные цели. Где-то промпт генерирует квиз, и там нужно более дорогую модель. А где-то исправляет синтаксические ошибки, там справится и дешевая, кажется. Можно просто потестировать разные модели и подобрать.
Если вопрос как налету это делать, то здесь уже сложная архитектура. Возможно для продукта такое и не нужно
Bardakan
Че? Создается файл на бэкенде/сайте/firebase с нужными настройками и мобильное приложение выкачивает его где-нибудь на старте. Приватные ключи правда не пытался запихнуть туда и зашифровать, но думаю, это тоже реализуемо
vital_pavlenko Автор
Как вариант. Я об этом и говорю, что главное в бандл токен не пихать