Каталог, читаемый в реальном времени, без ключей и без плагина
Для WooCommerce мы используем Store API, публичный интерфейс магазина: `/wp-json/wc/store/v1/products`, 100 товаров на странице, максимальное время ожидания 20 секунд, собственный идентификатор клиента в заголовке и принятие исключительно кода 200. Количество страниц мы читаем из заголовка `x-wp-totalpages`, а общее количество товаров — из `x-wp-total` — мы не пагинируем, пока не выдадим себя. Первая страница берётся отдельно, остальные — параллельно, с потолком в 30 страниц. Ничего не устанавливается в магазине и нам не дают ключи торговца.
Правильно переведённая цена, а ноль обработан как не опубликовано
Store API возвращает цену как строку в младших единицах, плюс число знаков после запятой для валюты. Мы делим на десять в степени этого числа, а не на фиксированные 100 — иначе любая валюта с другим числом знаков после запятой будет вычислена неверно, и никто этого не заметит. Ноль становится `null`, а в ответе появляется «цена по запросу — подтверждается коллегами», вместе с явной инструкцией, данной агенту, не выдумывать и запросить подтверждение у коллеги. Цена уходит в двух формах сразу: готовый к отображению текст для интерфейса и число для логики на бэкенде.
Три инструмента, которые агент может вызывать
В платформе ассистентов каталог доступен как три встроенных инструмента — поиск по словам, сопоставление по устройству или сценарию использования и получение товара по идентификатору. Агент видит их под названиями `catalog_cauta`, `catalog_potrivire` и `catalog_produs`, каждый со своими объявленными параметрами. Когда платформа магазина не та, что ожидается, или адрес отсутствует, инструмент явно отказывает вместо того, чтобы вернуть пустой список, который был бы истолкован как «у нас нет товара».
Связующие слова не портят результат
«Фильтр для воды» при буквальном поиске совпадает со всем, что содержит «для». В поставленном пункте каталога и в поиске из голосового виджета мы отсекаем список стоп-слов — `si`, `sau`, `de`, `din`, `la`, `cu`, `pe`, `in`, `pentru` — и, если после отсечения не остаётся ничего полезного, возвращаемся к словам длиннее двух букв. Для частичного совпадения есть порог: продукт попадает в результаты только если набирает не менее половины оставшихся слов.
Когда у магазина нет интерфейса, мы читаем страницу
Не у каждого магазина есть Store API. Для остальных у нас есть экстрактор, который открывает страницы списка и читает карточки товаров, с бюджетом, записанным в коде: максимум 20 страниц списка, 200 товаров, 24 страницы деталей, 50.000 символов HTML на страницу, 5 секунд на запрос и общий лимит 45 секунд на всю операцию. Сначала он читает структурированные данные типа товара со страницы, затем карточки, затем метаданные для публикации — именно в таком порядке, потому что первый источник — тот, который магазин написал намеренно.
Свежесть с ограничениями, записанными, а не с надеждой
Каталог хранится в памяти 10 минут на магазин, а одновременные запросы к одному и тому же магазину объединяются в один, чтобы десять клиентов, спрашивающих в один момент, не вызывали десять загрузок. На публичной точке каталога ответ также несёт инструкцию кэша для сети доставки: 5 минут свежий, ещё 10 минут выдаётся старый, пока обновляется в фоне. Число возвращаемых результатов ограничено 20, значение по умолчанию — 6.
Заказ, корзина и связь с системой управления
Корзина, отправка заказа и передача его в систему управления клиента — это отдельные функции, а не форма. Есть также восстановление брошенных корзин, заказ через разговор и отслеживание состояния заказа, с двумя запланированными задачами, которые запускаются каждые 15 минут — одна синхронизирует результат заказов, другая дополняет статусы из системы управления на окне в два дня назад и один день вперёд.
Платежи: что работает и что нет
Работает и есть в коде: интерфейс MAIB для торговцев — токен, затем запрос на платёж, с реквизитами каждого магазина, читаемыми из базы данных, и с явно включённой функцией — и Stripe, с суммой, конвертированной в младшие единицы, и с поступлением, сохранённым в учёте. Что НЕ работает, чтобы не было сюрпризов: Paynet, Netopia и mobilPay не реализованы, и код явно отказывает, когда их запрашивают. Путь Moldindconbank — это каркас, который возвращает ошибку без реквизитов. Мы берём их в проект как работу, а не как существующую интеграцию.
Агент не выдумывает, когда сеть падает
Если запрос к магазину не удаётся, инструмент не возвращает пустой список — он возвращает сообщение, которое говорит агенту не формулировать ответ о каталоге. Разница важна: пустой список переводится как «у нас нет», а ошибочно сказанное «у нас нет» теряет заказ так же надёжно, как неверная цена.