Ваня, этот гайд — про то, как встроить LLM в твою веб-систему управления производством лестниц так, чтобы модель помогала, а не создавала головную боль. Акценты: structured outputs, арифметика через tools, генерация UI, и жёсткое разделение «что отдаём LLM, что оставляем на сервере».
LLM — это парсер и переводчик. Правда живёт в Postgres. Арифметику делает Kotlin. Авторизацию решает сервер. Валидацию ввода — обязательно.
У OpenAI включается флагом response_format с JSON-схемой.
Модель гарантированно вернёт JSON, соответствующий схеме. Для
всех парсеров используй это — избавит от «почти JSON» с
пояснениями вокруг.
val schema = """
{
"type": "object",
"required": ["floors", "wood_type"],
"properties": {
"floors": {"type": ["integer", "null"], "enum": [2, 3, null]},
"wood_type": {"type": "string", "enum": ["oak", "pine", "ash", "unknown"]},
"approx_run_length_mm": {"type": ["integer", "null"]},
"notes": {"type": "string"}
},
"additionalProperties": false
}
""".trimIndent()
val response = openAi.chatCompletion {
model = "gpt-5.4-mini"
messages { system(PARSE_PROMPT); user(rawText) }
responseFormat = JsonSchema(schema = schema, strict = true)
}
Что здесь происходит: модель не может вернуть лишние поля или неправильные типы — API это гарантирует. Парсинг в Kotlin становится надёжным однострочником.
Ты парсер заявок на деревянные лестницы. Извлеки данные из свободного
текста клиента.
<rules>
- Все размеры в миллиметрах. 2.8 м = 2800 мм.
- wood_type: oak, pine, ash, или unknown.
- floors: 2 или 3. Иначе null.
- Поле не упомянуто → null (или "unknown" для wood_type).
- Не выдумывай.
- Если текст бессмысленный — {"parse_error": "причина"}.
</rules>
<examples>
... 3-4 примера ...
</examples>
Текст клиента:
{{USER_TEXT}}
Ты — генератор спецификации материалов для деревянной лестницы.
<order>{{PARSED_ORDER_JSON}}</order>
<rules>
- Считай количество ступеней: run_length / 200 мм, округление вверх.
- Всегда включай: ступени, подступёнки, тетива, поручень, стойки,
крепёж (саморезы), клей, лак.
- Размеры — в метрах (для досок) или штуках (для крепежа и т.п.).
- Учитывай технологический перерасход 15% для досок.
- Цены НЕ указывай — их посчитает код.
</rules>
Верни JSON-массив позиций в теге <bom>:
[{"material_code": string, "qty": number, "unit": "m|pcs|l"}, ...]
Что здесь происходит: модель решает «что и сколько», а
сумму и остаточный расчёт прибыльности Kotlin берёт на себя
(дёргает get_material_price(material_code) из прайса и
умножает).
Ты — опытный прораб по производству лестниц. Ревизуй готовую смету.
<estimate>{{ESTIMATE_JSON}}</estimate>
Думай внутри <thinking>:
1. Все ли ключевые материалы учтены?
2. Адекватны ли количества по заданным размерам?
3. Нет ли явных противоречий (например, дуб + цена сосны)?
Итог — в <review>:
{
"issues": [
{"severity": "low|med|high", "field": "items[3].qty", "text": "..."}
],
"summary": "1-2 предложения"
}
Не переписывай смету — только находи проблемы.
Что здесь происходит: генератор создаёт, ревьюер находит дыры, человек (мастер) решает. Хороший пример evaluator-optimizer-паттерна.
Это делает сервер, не LLM. Перед каждым LLM-вызовом твой код должен:
Если мастер видит только свои заказы — в
<user_orders> промпта кладёшь только его заказы.
Модель по определению не может «случайно показать» чужой заказ,
если его не передали. Так безопаснее, чем надеяться на промпт
«не показывай чужое».
Claude Code хорошо делает черновики React-компонентов по описанию формы. Подход: описываешь поля, валидацию, поведение — получаешь TypeScript/JSX. Потом сам правишь под свой дизайн-систему. Пример промпта в Claude Code:
В файле web/src/components/OrderForm.tsx собери форму ввода нового
заказа. Поля:
- customer_name (обязательное, string)
- address (string или пусто)
- floors (выбор: 2, 3)
- wood_type (выбор: oak, pine, ash)
- run_length_mm (число, 1500-5000)
- notes (textarea, до 500 символов)
Валидацию сделай через zod. При submit — POST на /api/orders.
Стиль — как в существующем CustomerForm.tsx, чтобы визуально
совпадало. Тестов не пиши, я добавлю отдельно.
LlmService с тонкой обёрткой над
OpenAI / Anthropic API. Все промпты читает из файлов
resources/prompts/.
EstimateCalculator на
Kotlin (BigDecimal). LLM только даёт список позиций.