Хорика · пояснительная записка

Зачем Хорике LLM

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

Источники: код backend, настройки и база на стенде, транскрипты сессий Дата: 2026-09-03, после починки доступа к моделям

Если одной фразой

LLM в Хорике — это наёмный читатель меню. Ей показывают фотографию, страницу сайта или PDF, а она возвращает аккуратную таблицу: раздел, блюдо, цена, вес. Всё остальное — сопоставление с каталогом, цены, документы 1С, расчёт НДС — делает обычный детерминированный код. Модель ничего не «решает» за менеджера и не придумывает: если не уверена, пропускает.

Почему без неё не обойтись

задача

Цель продукта — по меню заведения понять, какие продукты оно закупает, и предложить себя как поставщика. Значит, меню нужно превратить из «как получилось» в структуру. А меню в реальности выглядят по-разному: фотографии страниц в Яндекс.Картах, сайт на конструкторе, PDF-файл, иногда скан. Ни один набор правил не покрывает это разнообразие — каждый сайт свёрстан по-своему, на фото мелкий шрифт и разная разметка.

Языковая модель умеет то, что нужно ровно здесь: читать текст и картинки и отвечать в заданном формате. Ей не нужно объяснять, как выглядит цена на конкретном сайте. Поэтому её поставили на «входе» конвейера — там, где сырой хаос превращается в данные. Дальше по цепочке модель почти не участвует.

Где на конвейере стоит модель
Найти заведение
Яндекс.Карты, 2ГИС, Афиша, VK. Карточка, телефон, сайт, фото.
Правила и парсеры
Найти и прочитать меню
Обойти сайт, отобрать фото меню, вытащить позиции из фото, текста и PDF.
LLM · 3 задания
Разложить блюда
«Паста карбонара» → бекон, сливки, сыр, паста. Сначала кэш и рецепты, модель — в конце.
Кэш → LLM
Сопоставить с каталогом
Ингредиенты → номенклатура 1С, покрытие меню, кухни, цены по колонке прайса.
Детерминированно
Собрать КП
Шаблон, PDF/DOCX. Модель может предложить черновик набора позиций под бриф.
Код + LLM-черновик
участвует языковая модельобычный код без модели

Пять заданий, которые ей поручены

что делает

У каждого задания своя инструкция (промпт), своя модель и свой формат ответа. Все пять требуют ответ строго в виде JSON — так его читает программа, а не человек.

ЗАДАНИЕ 1

Провести по сайту

Модель получает текст страницы и список ссылок с неё и отвечает на два вопроса: «это страница с меню?» и «если нет — куда идти дальше?». Это заменило хрупкий поиск по словам «меню / прайс / каталог» в ссылках.

Входадрес, очищенный текст (до 12 000 знаков), до 60 ссылокВыходда/нет, найденные позиции, до 5 ссылок для перехода, одна фраза «почему»
Ограничения: только ссылки на том же домене; позиции только из текста, ничего не выдумывать.
llm_explore.py · задача fetch_site · дешёвая модель
ЗАДАНИЕ 2

Отсортировать фотографии

Из карточки заведения приходят десятки фото. Модель смотрит на них пачками по шесть и каждому ставит метку: страница меню, блюдо, интерьер или мусор — с оценкой уверенности. Дальше в работу идут только «меню».

Вход6 фото за раз, уменьшенных до 1280 pxВыходметка и уверенность для каждого
До модели: дубли отсеиваются по «отпечатку» картинки и в модель не отправляются — экономия.
classify.py · метки menu/dish/interior/garbage · менеджер может поправить вручную
ЗАДАНИЕ 3

Вытащить позиции меню

Главное и самое дорогое задание. Из фото страницы меню, текстового блока сайта или PDF модель собирает разделы и позиции: название, описание, цена, вес. Большой текст режется на куски и читается параллельно; PDF сначала пробуют прочитать как текст, и только сканы отправляют как картинки.

Входдо 12 фото (1900 px), до 3 текстов, до 3 PDF по 10 страницВыходразделы → позиции с ценой и весом
Правило: нет цены — ставь пусто; не уверен — пропусти. Точные позиции, снятые с разметки сайта без модели, никогда не затираются угаданными.
extract.py · сильная модель · результат → menu_items
ЗАДАНИЕ 4

Разложить блюдо на продукты

Чтобы сопоставить «Борщ с говядиной» с каталогом поставщика, нужно знать, что в нём свёкла, говядина, капуста. Модель просят об этом только для блюд, которых нет в кэше: сначала смотрят готовый словарь из 126 тысяч рецептов, затем простые правила, и лишь остаток — «длинный хвост авторских блюд» — уходит в модель. Ответ кладётся в кэш навсегда.

Входсписок названий пачками по 16Выходосновные ингредиенты в единственном числе, напиток → пусто
Экономия: одно блюдо раскладывается один раз на весь проект.
decompose_dishes_llm · кэш dish_decomposition · дешёвая модель
ЗАДАНИЕ 5

Предложить черновик КП

Необязательный шаг. Когда меню извлечено, модель получает список позиций (до 150) и бриф — число гостей, бюджет, формат мероприятия — и подбирает набор с количествами плюс два предложения обоснования. Менеджер видит это как кнопку «AI-черновик» в редакторе КП или как автоматически созданный черновик после разбора сайта. Дальше всё правится руками.

Входкаталог позиций заведения + брифВыходсекции с позициями и количествами, короткое обоснование
Правило: выбирать только из переданного списка, новых позиций не изобретать. Если модель недоступна — черновик создаётся пустым, без ошибки.
llm_proposal.py · auto_draft.py · сильная модель
Чего модель сознательно не делает

Логика простая: там, где ответ можно вычислить, модель не нужна — она дороже, медленнее и менее предсказуема.

Как это устроено внутри

архитектура

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

Напрямую в Anthropic

Официальный API Claude. Инструкция кэшируется на стороне провайдера — повторные вызовы с тем же промптом стоят в разы дешевле. Нужен собственный ключ.

транспорт api · не используется

Через локальный Claude Code

Запускает claude -p как программу на машине разработчика. Работает по подписке, без ключа и без затрат по счётчику. Медленно: запросы идут по очереди.

транспорт cli · для разработки

Через OpenRouter

Посредник: один ключ на много моделей разных вендоров. Быстрый, параллельный, можно дешёвую модель на сортировку и сильную на извлечение. Так настроен стенд, запросы идут через прокси.

транспорт openrouter · включён · работает
Что можно менять без релиза
  • Текст инструкции для каждого задания — лежит в таблице настроек. Удалили ключ — вернулся вариант из кода.
  • Модель на каждое задание отдельно: сортировку фото на дешёвую, извлечение на сильную.
  • Транспорт — один переключатель.
  • Потолок расходов на одно заведение в долларах.
Как система защищается от модели
  • Только JSON. Ответ разбирается программой; если модель дописала пояснение, берётся первый цельный блок.
  • Сбой — не авария. Ошибка сети или кривой ответ дают безопасное «неизвестно» или «пусто», в лог пишется предупреждение, конвейер идёт дальше.
  • Ничего не выдумывать. Каждая инструкция заканчивается этим правилом: пропусти, а не угадай.
  • Картинки готовятся заранее: уменьшаются, сжимаются, дубли отсеиваются. В модель уходит минимум.

Сколько это стоит и как ограничено

бюджет

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

Учёт. После каждого вызова система считает его стоимость по таблице цен и накапливает на заведении. Итог виден в карточке задачи и в поле заведения. Потолок. Перед каждым дорогим шагом проверяется, не исчерпан ли лимит на это заведение; если да — шаг пропускается с пометкой «бюджет исчерпан», а обход сайта останавливается посередине. Целевая цифра из реестра требований — около доллара на сайт, и такой лимит теперь выставлен на стенде.

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

Какая модель на какое задание (по умолчанию)
ЗаданиеЧерез OpenRouter (стенд)Напрямую в AnthropicПочему так
Провести по сайтуgemini-2.5-flashclaude-haiku-4-5Много вызовов, ответ простой — нужна дешёвая и быстрая.
Отсортировать фотоgemini-2.5-flashclaude-haiku-4-5Четыре метки, картинок много — дешёвая с поддержкой изображений.
Вытащить позицииclaude-sonnet-4claude-sonnet-4-6Мелкий шрифт, цены, веса — нужна сильная, ошибки дорого обходятся.
Разложить блюдоgemini-2.5-flashclaude-haiku-4-5Короткие списки продуктов, кэшируется навсегда.
Черновик КПclaude-sonnet-4claude-sonnet-4-6Осмысленный подбор набора — дороже, но вызов редкий.
Ориентиры цен, заложенные в калькулятор (за миллион единиц текста)
МодельОтправлено, $Получено, $Роль
gemini-2.5-flash0.302.50Дешёвая рабочая лошадка на стенде
claude-haiku15Дешёвая при прямом подключении
claude-sonnet315Сильная, для извлечения и КП
claude-opus525Не используется

Повторное чтение закэшированной инструкции считается по 0,1 от цены, запись в кэш — по 1,25. Неизвестная модель для осторожности считается по цене Sonnet.

Что происходит на стенде сейчас

2026-09-03

Модель заработала: 403 снят прокси плюс живой ключ

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

18
позиций снято с одного меню
49
позиций с меню на 12 фото
10
фото рассортировано за 2 секунды
$0.33
самое дорогое заведение
$0.36
потрачено за всю проверку
146
тестов проходит на стенде

Что пришлось починить по дороге. Прокси оказался капризным на установке соединения: первая попытка зависала на две минуты, а немедленный повтор проходил за секунду. Из-за этого картинки не доходили, хотя текст доходил. Добавлены короткое ожидание подключения и повторы, после чего сбои прекратились. Ответы сервиса вроде «деньги кончились» при этом не повторяются: это не сбой связи, повторять бессмысленно.

Следующее препятствие уже не в модели: мёртвые прокси для обхода сайтов

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

Две мелочи, которые стоит поправить

Как это менялось

по транскриптам сессий
июнь 2026
Старт. Claude напрямую: Sonnet на меню и картинки, Haiku на дешёвое, кэширование инструкций. Добавлен CLI-транспорт, чтобы разработчик мог работать по подписке без ключа.
25.06
Кто платит. При разборе вопросов заказчику решено: ключи, прокси и бюджет модели — операционная зона команды разработки, заказчика об этом не спрашивать.
28.06
Аудит конвейера. Выявлено: лимит «доллар на сайт» не реализован, один текст оплачивается дважды (навигация и извлечение), кэша вызовов нет. Запланирована «Волна 3 · Стоимость».
июль
Волна 3 сделана. Учёт стоимости каждого вызова, накопление на заведении, жёсткий потолок и остановка обхода при превышении. Добавлен OpenRouter как третий транспорт.
27.08
Ключ OpenRouter умер. Обнаружен 403 при проверке стенда. С тех пор извлечение меню на стенде не работает.
03.09 · утро
Подтверждено повторно. Ключ по-прежнему отклоняется. Остальная система жива.
03.09 · день
Починено. Найдены обе причины: региональная блокировка и исчерпанный ключ. Запросы пущены через прокси, ключ заменён, добавлены повторы на нестабильное соединение. Извлечение меню проверено на реальных заведениях.
Что осталось в планах по модели