1. Главная
  2. Блог
  3. Как мы разгребли зоопарк Excel-прайсов от поставщиков с помощью ИИ-агента

Как мы разгребли зоопарк Excel-прайсов от поставщиков с помощью ИИ-агента

Как мы разгребли зоопарк Excel-прайсов от поставщиков с помощью ИИ-агента

Байер дистрибьютора электротехники открывает почту в понедельник — и там несколько десятков непрочитанных писем с прайс-листами. Один поставщик прислал xlsx на дюжину листов с объединёнными ячейками. Другой — xls двухтысячных годов, где артикул лежит в колонке F, а у соседнего поставщика тот же артикул — в колонке B. Третий вообще присылает PDF с экспортом из 1С, где цена набита в одну строку с НДС, скидкой и комментарием «оптом от 50 шт.».

К обеду из этой пачки нужно получить один обновлённый прайс в собственной номенклатуре — чтобы менеджеры формировали клиентам коммерческие предложения. По факту байер успевает разобрать десяток файлов и идёт пить очередной кофе. Остальное — «на завтра», а к завтра приходит новая порция.

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

Что приходит на вход на самом деле

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

Зоопарк выглядел так:

  • xlsx с шапкой на пять строк, где первые две — логотип и контакты, третья — пустая, четвёртая — категория товара жирным, и только пятая — настоящие заголовки колонок;
  • xls с объединёнными ячейками, где «Кабель ВВГнг-LS» написан один раз сверху, а ниже идут десятки строк с сечениями — и openpyxl видит их как пустые;
  • листы, где артикул и наименование склеены в одну ячейку через перенос строки: «КВВГ 4х1.5\nкабель контрольный»;
  • цены в форматах «1 245,50 руб.», «1245.5», «1.245,50 €», «1245,50 (от 100м — 1180)»;
  • отдельная категория ада — листы, где данные начинаются глубоко в файле, потому что сверху лежит «прайс прошлого месяца зачёркнут».

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

Почему «просто скормить файл LLM» не работает

Соблазн скормить xlsx целиком российской LLM и попросить «верни мне JSON со списком товаров» велик. На демо это выглядит убедительно. В проде разваливается в три приёма.

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

Второе — галлюцинации на артикулах. Модель видит «ВВГнг 3х2.5» много раз подряд и где-то в середине начинает «дорисовывать» несуществующие сечения — они же логично продолжают ряд. Мы это ловили руками. И потом уже никто не хотел проверять каждый прайс глазами.

Третье — цены. LLM любого производителя стабильно ошибаются в переносе чисел с пробелами и запятыми в разделителях. «1 245,50» превращается то в 124550, то в 1245.5, то в 12455 — редко, но регулярно. На длинной таблице это десятки ошибок в одном файле. Каждая — потенциальный убыток на сделке.

Настройка ИИ-агента: LLM как нормализатор схемы, а не парсер данных

В какой-то момент мы развернули задачу. LLM не должна видеть весь прайс. Её работа — посмотреть на структуру файла и сказать, где что лежит. Дальше — обычный код.

Настройка ИИ-агента получилась примерно такой:

# 1. Открываем файл, читаем верхние строки всех листов
sample = extract_sample(file_path, rows=30, sheets="all")

# 2. Скармливаем LLM только этот образец + промпт-инструкцию
schema = llm.extract_schema(sample, response_format=PriceSchema)
# Возвращает: header_row=4, sku_col="B", name_col="C", 
#             price_col="F", unit_col="G", category_source="merged_A"

# 3. Парсим весь файл по схеме обычным openpyxl
rows = parse_by_schema(file_path, schema)

# 4. Валидация: цены — regex + Decimal, артикулы — по словарю поставщика
clean = validate_and_clean(rows, supplier_id=42)

# 5. Матчинг с собственной номенклатурой через pgvector
matched = match_to_catalog(clean)

LLM здесь делает ровно одну вещь — отвечает на вопрос «где у этого поставщика лежат данные». Тридцать строк образца — по грубой оценке пара тысяч токенов. Промпт стабильный. Ответ — строгая JSON-схема через function calling. По API это укладывается в секунды и в копейки за вызов.

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

Отдельно про интеграцию по API. Мы обернули GigaChat Pro в тонкий сервис, который умеет ретраить упавшие вызовы, кэшировать ответы по хешу образца (один поставщик — одна и та же схема из месяца в месяц, повторный вызов делать смысла нет) и уважать таймауты вышестоящего прокси. Прокси у нас режет любые HTTP-соединения на сотой секунде, так что синхронно ждать медленную модель нельзя в принципе — ИИ-агент через API отправляет запрос и подхватывает результат уже из очереди.

Объединённые ячейки, НДС и единицы измерения: грязная работа

Самая неприятная часть оказалась не там, где ждали. Объединённые ячейки openpyxl честно отдаёт как «значение в первой, None во всех остальных». Мы написали отдельный шаг — unmerge_forward_fill, который перед парсингом протягивает значение вниз по объединённому диапазону. Для прайсов, где категории оформлены «шапками», это закрыло сразу большой пласт случаев — на треть точно.

Цены оказались проще, чем казалось. Регулярка плюс приоритет правил: сначала убираем валютные символы, потом пробелы как разделители тысяч, потом нормализуем запятую в точку, потом конвертируем в Decimal. Если в ячейке скобка с альтернативной ценой («1245 (от 100 — 1180») — это отдельный признак, который кладём в поле price_tiers, а основной ценой считаем первое число.

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

ИИ-агент для поиска товара в собственной номенклатуре

Получив чистые строки от поставщика, нужно понять — это новый товар или у нас такой уже есть в каталоге под своим артикулом. Здесь работает отдельный ИИ-агент для поиска: эмбеддинг полного описания товара поставщика ищется в индексе нашей номенклатуры. Выше порога косинусной близости — автоматический match. Ниже — карточка попадает в очередь на ручное подтверждение байеру.

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

Порог косинусной близости мы подбирали итеративно, глядя на выборку размеченных вручную пар. Сначала держали строго — байеру прилетало слишком много «на подтверждение». Ослабили — начали проскакивать похожие, но разные товары («провод ПВС 3х1.5» и «кабель ВВГ 3х1.5» модели кажутся близкими, но это разные позиции). В итоге пришли к двум порогам: жёсткий — автоматический match, средний — «предложить как вариант», ниже — «новый товар». Байер видит только среднюю зону.

Что изменилось для людей

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

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

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

Что не сработало и где мы оставили человека

Сканы в PDF мы из основного пайплайна выкинули. Для них отдельный маршрут через OCR, и качество там пока не дотягивает до автомата — байер всё равно перепроверяет. Прайсы с фотографиями товара вместо артикула — тоже руками, благо их единицы.

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

Байер, кстати, первую неделю не верил, что «оно правда правильно всё разобрало» — открывал соседним окном исходную экселку и сравнивал руками. К концу месяца перестал.

А вы пробовали считать, сколько часов в неделю ваш отдел закупок тратит на копирование цифр из чужих Excel в свой? Если такая задача есть — расскажем, как разворачивали похожий пайплайн у себя. Личный ИИ-агент для закупщика, который живёт внутри вашего контура и разбирает почту за него, — это уже не футурология, а вполне рабочая инженерная задача.

Похожие статьи

Хотите так же?

Обсудим, как это внедрить у вас

Опишите задачу — подберём ИИ-решение под ваш процесс и бюджет: что сделать агентом, какие нужны данные и интеграции, с чего начать пилот.