Вообще-то мы делаем конструктор корпоративных баз данных с автоматизациями, но меня давно просили сделать какой-нибудь демонстрационно-учебный проект — чтобы, типа, best practice можно было глянуть. Так как ИИ бесжалостно прёт изо всех щелей и от него никуда не деться, решили сделать такой, чтобы использовал его, но только, блин, с реальной пользой, а не очередное «напиши статью про то — не знаю что». Получилось хорошо: по задаче работает лучше, чем если бы мы человека за разумные деньги посадили делать то же самое. Ну и, понятно, быстрее (хотя с последними замедлениями API OpenAI хотелось бы ещё побыстрее).

Итак, задача: есть у нас, например, небольшой завод — и покупает он там всякое разное: болты, винты, пузырьки, доски, пропановые баллоны и так далее. Есть поставщики, присылают счёта. Счета их даже кто-то из начальства, возможно, проверяет за кладовщиком. Если проверять хорошо, то это вообще куча времени уходит — и было бы здорово:

а) получать отчёт автоматически: как цена в счёте соотносится с тем, что продают в интернете (конечно, с учётом доставок и НДС);

б) если в счёте дороже, чем в интернетах — то где можно купить дешевле и насколько (также с учётом доставок).

Это для малого предприятия интересно, а для крупного — интереснее, конечно, посмотреть, как там отдел закупок работает. Фоново, так, не привлекая внимания «санитаров», понять, какой установился уровень неформальных расходов и соответствует ли это вашим представлениям о прекрасном.

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

На двух пользователей (этого достаточно, чтобы система работала) — бесплатно.

Ссылка: https://ru.revizor.online
(там же — инструкция по установке и live-demo, который можно потыкать без установки; правда, только как пользователь, без доступа к промптам. Чтобы посмотреть промпты — ставьте себе на сервер; в статье далее я их приводить не буду).

Конструктор, на котором это сделано: https://ru.totum.online

Для полноценной работы решения вам потребуются аккаунты: OpenAI, Perplexity и Яндекс Клауд (токены оплачиваете сами).

Про бизнес-составляющую — закончили, далее техническая часть.

Справочный вид отчета
Справочный вид отчета

Общая концепция (которая, возможно, сэкономит вам время при разработке аналогичной системы)

Предварительная обработка документа

Один: Нам нужно обработать файл счёта, УПД, накладной или КП и достать из него — что мы покупаем, у кого, в каком количестве и почем. Форматы бывают разные: PDF (текстовый или скан), DOCX, XLSX (возможно, ещё какие-то, но этих достаточно).

Содержимое извлекаем в виде текста с помощью Python-скриптов — под каждый случай свой скрипт. Для PDF сначала проверяем: если внутри только одна картинка, то, скорее всего, это скан, запакованный в PDF. Тогда рендерим его в PNG и кодируем в base64 — чтобы отправить в OpenAI, где их OCR распознает текст. По нашему опыту, работает хорошо, но вы спокойно можете подключить и свой OCR в пайплайн.

Далее всю эту «кашу» текста отправляем в OpenAI и просим разложить по json_schema. Работает с надёжностью чугунного утюга. Пока разрабатывали — прогнали около пятисот счётов, и ошибок в этой части не было.

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

Также важно: использовать большую модель (маленькие теряют части данных) и отключать режим «рассуждений» — он здесь только замедляет обработку и тратит лишние токены.

Два: отдельным действием определяем, где находится НДС. Я видел такие, блин, счёта, что даже человеку сложно было понять — где цена указана с НДС, а где без. Положение НДС нам нужно, чтобы корректно сравнивать стоимость со сведениями из интернета (в интернете в 99 % случаев цены указаны с НДС). Здесь тоже нужна крупная модель, но уже с включённой цепочкой рассуждений.

Мы закончили этот проект некоторое время назад, и использовали там GPT-5 (уровень не выше low). Medium оказался безумно медленным, а low достаточен для качественного выполнения всех описанных выше и ниже задач.

Православные модели мы не тестировали — и последние версии от OpenAI тоже. Это, пожалуйста, проверяйте сами.

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

Обработка позиций

Один: Просим Perplexity (он с этой задачей справляется лучше всего) дать описание товара по названию, описать возможные варианты поставки и ключевые характеристики.

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

Три: Также полезно проверить, не анализировали ли мы уже похожий товар в нашей базе. Для этого нужен смысловой поиск. Многие пытаются реализовать его только через векторный поиск по эмбеддингам — но этого недостаточно для качественного результата. Например, если использовать только эмбеддинги, то «Винт DIN 912 6×80 оц» и «Винт DIN 912 6×80 A2» будут иметь 98 % схожести, хотя это принципиально разные позиции.

Здесь нужно три действия, кардинально улучшающих результат:

  • Одного названия недостаточно. В эмбеддинг кладётся не только название, но и описание — полученное от Perplexity.

  • После того как векторный поиск выдал 3–5 кандидатов, мы передаём их большой модели в режиме рассуждений и сравниваем нашу позицию + её описание с найденными позициями и их описаниями — уже по смыслу. То есть: совпадают ли основные параметры, с допуском по второстепенным (степень «расслабленности» задаётся через промпты, если нужно). Второстепенное — например, вариант поставки (коробка, поштучно) или артикул изделия у конкретного продавца.

  • Сравнивать нужно в два этапа:

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

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

На этом этапе у нас уже получается такой набор:

  • поисковая строка,

  • определение соответствия с позициями в базе,

  • описание товара.

Если позиция новая — мы создаём для неё эмбеддинг (название + описание) и сохраняем в векторную БД.

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

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

Определив, например, что для крупных винтов мы будем сравнивать поштучно, а для мелких — в пересчёте на килограмм, мы рассчитываем коэффициент приведения для позиции в счёте. То есть если в счёте указана коробка из 100 штук, а цена стоит за всю коробку — мы умножаем её на 0,01, чтобы получить цену за единицу сравнения. То же самое с весами: крупные модели, обученные на обширных данных, знают веса винтов и болтов, плотности жидкостей и т.п. — и могут выполнить такие расчёты с достаточным качеством.

Здесь же считается вес и объём изделия — чтобы потом корректно оценивать стоимость доставки. Но статья превратится в монстра, если я начну это здесь расписывать — посмотрите сами в исходниках проекта.

Пять: ищем в Яндексе, кто это продаёт. Надо сказать, что российский интернет в этом плане существенно более прогрессивный. Если в Штатах приходится обрабатывать 50 сайтов, и только на трёх найдётся нормальная информация с ценой, то в РФ достаточно пройтись по 15–20 сайтам — и из них 10 будут содержать действительно релевантную позицию, а у 7 ещё и указана цена. (Исключение — позиции, поставляемые исключительно через тендеры: там всегда «молчок», и понятно почему.)

Получив выдачу, оцениваем её как человек:

  • Оцениваем сниппеты большой моделью. Задача — отбросить совсем нерелевантные: когда по сниппету сразу ясно, что искали «Болт купить», а получили видос на VK.

  • Если сниппет в порядке — загружаем DOM целевой страницы с отработавшим JS, очищаем его от разметки и анализируем, что именно там продаётся.
    В текущей версии получение DOM реализовано через Node + Lightpanda — чтобы всё работало на одном сервере и достаточно быстро, только на CPU (до 10 секунд на сайт).

    Можно, конечно, использовать Chrome с Puppeteer или Camoufox, но тогда понадобится сервер с GPU. Про Lightpanda и альтернативы расскажу в отдельной статье (но это неточно ?).

    Также на этом этапе считаем количество токенов после очистки, чтобы не превысить лимит модели. Средняя страница — 10–20 тыс. токенов, но я видел и монстров по 200 тыс.

    В РФ, слава богу, людей пока не настигло cloudflare-безумие — с Lightpanda открывается 19 из 20 сайтов. В США — наоборот: 19 из 20 не открываются, приходится морочиться.

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

    Такие системы сделают востребованными даже 2–4 страницы выдачи. На 3-й странице Яндекса часто лежат очень интересные предложения — люди туда не доходят, а боты — доходят. Им всё равно: они не устают и спокойно читают по 50 сайтов на каждую позицию из счёта.

  • Теперь нужно оценить, соответствует ли товар на странице тому, который мы ищем. И делать это ни в коем случае нельзя по принципу «да/нет» — бинарная логика не работает.

    Хороший результат появился, когда мы ввели шкалу соответствия:
    - «не совпадает»,
    - «низкое»,
    - «средне-низкое»,
    - «среднее»,
    - «высокое»...
    ...с чёткими правилами в промпте, как её применять. Обязательно учитываем разницу между важными и второстепенными характеристиками.

    Одновременно на этом шаге извлекаем цены при разных объёмах закупки: сколько стоит единица при покупке 1 шт, 10 шт, 100 шт и т.д. И обязательно фиксируем, за что именно указана цена на сайте (за штуку? за упаковку? за метр?).

    Ещё один лайфхак, работающий только в РФ: если видим, что попали не на карточку товара, а на страницу каталога — сразу ставим «несоответствие» и идём дальше.

    В Штатах это не сработает: Google чаще всего ведёт именно в каталог, где ещё нужно поискать, где же сам товар.

    Эту оценку мы делаем маленькой моделью. Подходит, например, GPT-5-mini (nano — нет, не подходит). Также пробовали OSS-120B и Qwen-235B — они работают.
    Маленькие модели сильно зависят от качества промпта и ошибаются, но обязательны из-за стоимости: 20 сайтов × 20 тыс. токенов = 400 тыс. входных токенов. На цене большой модели это выйдет конски дорого.

    Маленькие модели действительно ошибаются. Особенно путаются в кратности: если цена указана за штуку, но товар продаётся кратно 5 шт, ~30 % запросов модели решают, что это цена за упаковку из 5 шт.

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

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

    И, как ни странно, этот второй проход реально повышает точность и устраняет основные ошибки.

Шесть: обработав все сайты, полученные через поиск, просим большую модель рассчитать, экономим мы или переплачиваем, взяв 5–7 лучших предложений (отсортированных по приведённой цене). Делаем это именно крупной моделью, передавая ей также данные о весе товара, тарифах на перевозку и прося учитывать приоритет предложений в регионе поставки.

Также просим отбросить предложения, где на сайте указано: «цена предварительная», «звоните менеджеру для согласования» и подобные формулировки. И ещё раз повторно оцениваем соответствие по ключевым характеристикам.

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

Для финальной выборки берём только результаты с высоким и средним уровнем совпадения.

И здесь — лайфхак, который работает везде, где модель даёт оценку: просим её не просто выдать вердикт, а ещё и написать один абзац с объяснением — почему она пришла к такому выводу. Это очень сильно повышает качество результата!

Завершение оценки счета

В конце оцениваем все позиции счёта и классифицируем сам счёт: требует внимания или нет — и почему, исходя из пороговых значений, заданных в настройках. Здесь всё просто.

Чё по деньгам *

* Исходя из стоимости токенов API OpenAI для GPT-5 и курса 80 ₽ за доллар

Обработка 7 счётов, содержащих в сумме 30 позиций (с анализом 15 сайтов на каждую позицию), обошлась в 900 рублей.

Конкретные расходы по задачам можно посмотреть в live-demo: раздел Настройки → таблица Лог.

Качество в сравнении с усреднённым «кожаным» сотрудником

Сотрудник нервно курит в сторонке.

Модель:

  • внимательнее при поиске данных на сайте;

  • лучше считает всякую мелкую математику, которой здесь дофига;

  • сразу, без обучения, как про, разбирается в весьма специфических товарах;

  • не теряет внимательности после обработки любого объёма — у человека мозг просто спекается в блинчик после первых 10 счетов.

Прочие технические моменты

Так как это всё-таки блог про наш конструктор баз данных для различных корпоративных задач, сделано всё это было на https://ru.totum.online.

В отличие от n8n, ставшего очень популярным в эпоху AI-обработчиков, Totum из коробки даёт:

  • Готовую базу данных — и вся логика строится именно от данных, а не от потоков.

  • Возможность писать сложные выборки данных и гибко управлять тем, когда и что именно передаётся на вход модели.

  • Возможность вызывать из консоли сторонние скрипты на любом языке (в нашем случае — Python и Node.js).

  • Прямое взаимодействие со сторонними серверами и создание собственных входных эндпоинтов.

  • Готовый интерфейс для взаимодействия с пользователем — без необходимости собирать UI отдельно.

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

  • Ролевое разделение прав доступа — из коробки.

  • Ну и многое другое...

Totum — не вчерашнее решение: пару сотен проектов уже эксплуатируется, и можно сказать, что платформа устоялась.

Чтобы собрать это готовое решение, ушло 120 часов одного человека (не программиста). Плюс ещё около 100 часов ушло на эксперименты с o3.

Делали мы это так, чтобы оно было максимально тиражируемым — когда делаешь «для себя», можно уложиться в 2–3 раза быстрее.

Чего еще можно сделать именно по этой истории с закупками

На поверхности лежат две вещи.

  • Это добавление — помимо документа — произвольного запроса пользователя как стартовой точки. Например, он пишет: «найди мне таблетку белую», и тут надо с ним работать: уточнять характеристики, определять, какие важные, а какие — второстепенные.

  • По позициям, найденным в интернете, но без цены — автоматически слать email: «Хотим купить такой-то объём с доставкой туда-то» — и потом так же автоматически читать ответы, включая файлы.

Мы этого не делали (всё-таки бесплатный проект), но у платформы такие возможности есть.

Также можно не использовать нашу платформу вообще — это всё не rocket science. Посмотрите логику, принципы — и реализуйте на своих инструментах. Нам уже звонили из нескольких крупных компаний, которым одно только применение смыслового матчинга и поиска цен теоретически даёт очень приличный профит.

Если есть уточнения — на https://ru.revizor.online есть телега. Напишите туда. В течение пары дней ответим. Конкретно этот проект некоммерческий — ответим коротко.

Больших вам экономий…

Комментарии (0)