Агент поверх 1С с правом записи: узкий HTTP-сервис вместо доступа к базе и замер до/после
6 октября на Инфостарте вышла статья «Агент пометил на удаление 5700 документов в продуктиве. Базу восстановили, а мои нервные клетки нет». Скрипт, написанный ИИ-ассистентом (помощником на основе искусственного интеллекта), создавал документы перемещения и помечал на удаление дубли, найденные по документу-основанию; раз в час его запускал планировщик под учётной записью автора. У части перемещений основание пустое, и за ночь пометку удаления получили 5 700 документов, начиная с 2021 года. Выводы автора: агентам только чтение, оповещение о сотнях записей от одной учётной записи за полчаса, ревизия всех планировщиков.
Для разработки «только чтение» — разумное правило. Но в заявках из почты запись и есть работа агента: без неё он не снимет с оператора перенос таблицы из письма в 1С. Ниже — конструкция, которая сводит ущерб от ошибки агента к лишним заказам без резерва в пределах часового лимита: существующие документы он не трогает вовсе. Агент получает не доступ к базе, а одну узкую операцию «создать заказ клиента по письму в статусе „Не согласован“». Вторая тема — как считать пользу: по пути заявки от письма до отгрузки, а не по скорости ввода. Код — схема: документ ЗаказКлиента — как в УТ 11 (1С:Управление торговлей 11) и 1С:ERP 2 (1С:ERP Управление предприятием 2), объекты агента условные, реквизиты сверьте со своей редакцией.
Путь заявки: где модель, где код, где человек
LLM (большая языковая модель) только читает письмо и возвращает поля заявки в JSON (текстовом формате обмена данными). Дальше работают HTTP-сервис 1С (HTTP — протокол веб-запросов), код и человек:
Письмо: Message-ID, время, In-Reply-To <- КОД
v
Поля заявки из письма и вложений <- МОДЕЛЬ
v
Схема полей; HTTP-сервис 1С: дубль? лимит? <- КОД
контрагент однозначен? поля заполнены?
| 422 --> ручная очередь <- ЧЕЛОВЕК
v 201
Заказ клиента «Не согласован», без резерва
v
Согласование, ответ клиенту <- ЧЕЛОВЕК (ответ необратим)
v
Сборка, отгрузка <- необратимо; входит в замер
- Модель только предлагает поля; адрес, метод и запрос формирует код.
- Необратимое — за человеком. Заказ в статусе «Не согласован» товар не резервирует, а проведение отменяется; не отозвать ответ клиенту и отгрузку. Согласует и отвечает сотрудник, пока выборочный контроль не покажет, что узкий класс заявок можно доверить агенту.
- Повтор письма не создаёт документ: ключ —
Message-ID, а ответ на обработанное письмо (заголовокIn-Reply-To) агент передаёт оператору со ссылкой на заказ.
Предусловие: включена функциональная опция «Использовать расширенные возможности заказа клиента» (НСИ и администрирование → Продажи → Оптовые продажи) — без неё поле «Статус» в заказе скрыто. В интерфейсе значение НеСогласован обычно называется «На согласовании».
Через что писать в 1С
| Способ | Что даёт агенту | Выбор |
|---|---|---|
| REST-интерфейс (у каждого объекта базы свой веб-адрес) по протоколу OData (открытый протокол доступа к данным) | Всё сразу: чтение, создание, изменение, удаление, проведение. С правом «Изменение» агент поставит DeletionMark любому доступному объекту |
Только чтение справочников |
| Собственный HTTP-сервис | Ровно то, что написал разработчик, с проверками конфигурации | Создание заказа |
| Запросы к СУБД (системе управления базами данных); RPA (робот, нажимающий кнопки в формах) | СУБД — обход прав и нарушение лицензии 1С (справка Инфостарта); RPA ломается при изменении формы | Нет |
Из документации 1С: при записи через REST-интерфейс права и обработчики событий работают, а проверка заполнения пропускается (v8.1c.ru, «REST интерфейс»). В состав OData (УстановитьСоставСтандартногоИнтерфейсаOData()) включайте только справочники для чтения. MCP-сервер (Model Context Protocol — протокол подключения инструментов к модели) годится для чтения; с универсальной записью безопасность зависит от каждого запроса модели.
Права агента: отдельный пользователь, почти пустая роль, своя публикация
Агенту — отдельный пользователь, например agent_orders (латиница: при базовой HTTP-аутентификации многие библиотеки не передают кириллицу в логине). Роль АгентЗаявки даёт самое большее чтение справочников через OData в другой публикации. На документ «Заказ клиента», регистр и константы агента у неё нет ни «Добавления», ни «Изменения», ни «Проведения», ни «Удаления»; запуска клиентов и внешнего соединения тоже нет.
Почему нет прав на документ, который агент создаёт? Программная запись, включая пометку удаления кодом, упирается в базовые права «Добавление» и «Изменение» — без них ошибочный скрипт под учёткой агента ничего не изменит и не пометит. Пишет только HTTP-сервис, в привилегированном режиме, после проверок. Это верно, пока остальные HTTP- и веб-сервисы конфигурации тоже проверяют права вызывающего.
Публикация — отдельная. В её default.vrd только этот сервис: <httpServices publishByDefault="false" publishExtensionsByDefault="false"> и внутри один <service name="АгентЗаявки" rootUrl="agent" enable="true"/>; у элемента point — enable="false" (клиенты выключены) и enableStandardOData="false". Сервис расширения вписывают в vrd руками, а переопубликация из Конфигуратора может этот элемент затереть, поэтому надёжнее публиковать через webinst -descriptor со своим vrd под контролем версий. Проверка на стенде: /hs/agent/v1/orders отвечает, сервис чужого расширения — 404. Веб-сервер принимает только HTTPS и пускает к публикации только адрес хоста агента, в Apache — Require ip.
HTTP-сервис «создать заказ»
Всё агентское — HTTP-сервис, роль, регистр и константы — держите в расширении конфигурации: типовая остаётся на поддержке. У расширения снимите флажок «Безопасный режим»: в безопасном режиме УстановитьПривилегированныйРежим вызывает исключение, и сервис упадёт на первом же вызове. Без флажка ограничения сняты со всего кода расширения, поэтому в нём — только сервис агента. На БСП (Библиотеке стандартных подсистем) роль расширения включайте в профиль группы доступа: назначенную напрямую снимет обновление ролей.
Для примера: HTTP-сервис АгентЗаявки (корневой URL agent, шаблон /v1/orders, метод POST), константы АгентЗаявкиВключен (выключатель) и АгентЗаявкиЛимитВЧас (порог), независимый регистр сведений ОбработанныеПисьма: измерение КлючПисьма (Строка(64)), ресурсы Заказ (ДокументСсылка.ЗаказКлиента), ДатаОбработки (индексировать), ПисьмоПолучено.
Первая часть модуля — вход и проверки:
Функция ЗаказыPOST(Запрос)
// В привилегированном режиме РольДоступна() всегда Истина — проверяем до него
Если Не РольДоступна("АгентЗаявки") Тогда
Возврат ОтветJSON(403, "forbidden");
КонецЕсли;
УстановитьПривилегированныйРежим(Истина); // расширение — без безопасного режима
Если Не Константы.АгентЗаявкиВключен.Получить() Тогда
Возврат ОтветJSON(503, "agent_disabled");
КонецЕсли;
Данные = ПрочитатьТелоJSON(Запрос); // Соответствие или Неопределено
Если Данные = Неопределено Тогда
Возврат ОтветJSON(400, "bad_json");
КонецЕсли;
ИдентификаторПисьма = Данные.Получить("message_id");
ПисьмоПолучено = ДатаИзСтроки(Данные.Получить("received_at"));
Если ТипЗнч(ИдентификаторПисьма) <> Тип("Строка") Или ПустаяСтрока(ИдентификаторПисьма)
Или ПисьмоПолучено = Неопределено Тогда
Возврат ОтветJSON(400, "message_id_and_received_at_required");
КонецЕсли;
Результат = СоздатьЗаказОдинРаз(КлючПисьма(ИдентификаторПисьма), ПисьмоПолучено, Данные);
Возврат ОтветJSON(Результат.Код, Результат.Ошибка, Результат.Заказ);
КонецФункции
Выключатель — константа: ответственный снимает галочку без обновления конфигурации, сервис отвечает 503, письма ждут в ящике. Служебные функции опущены: КлючПисьма — SHA-256 от Message-ID (длинный идентификатор в измерение не влезет), ОтветJSON — HTTPСервисОтвет в UTF-8 без BOM (метки порядка байтов), на которой спотыкается часть HTTP-клиентов.
Одно письмо — один заказ, и сигнальный лимит
Вторая часть — запись в базу, в одной транзакции с управляемой блокировкой по ключу письма:
Функция СоздатьЗаказОдинРаз(КлючПисьма, ПисьмоПолучено, Данные)
Результат = Новый Структура("Код, Ошибка, Заказ", 500, "", Неопределено);
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ОбработанныеПисьма");
ЭлементБлокировки.УстановитьЗначение("КлючПисьма", КлючПисьма);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
Записанное = РегистрыСведений.ОбработанныеПисьма.Получить(Новый Структура("КлючПисьма", КлючПисьма));
Если ЗначениеЗаполнено(Записанное.Заказ) Тогда
// Повтор по тому же письму: отдаём созданный заказ
Результат.Код = 200;
Результат.Заказ = Записанное.Заказ;
ИначеЕсли ЛимитЧасаИсчерпан() Тогда
Результат.Код = 429;
Результат.Ошибка = "hourly_limit";
ЗаписьЖурналаРегистрации("АгентЗаявки.Лимит", УровеньЖурналаРегистрации.Предупреждение, , ,
"Часовой лимит заказов исчерпан");
Иначе
Ошибки = Новый Массив;
Партнер = ПартнерПоИНН(Данные.Получить("inn"), Ошибки); // ПЕРВЫЕ 2, только однозначный
ЗаказОбъект = Документы.ЗаказКлиента.СоздатьДокумент();
Если Ошибки.Количество() = 0 Тогда
// Организацию, операцию, соглашение и цены заполнит типовой код
ЗаказОбъект.Заполнить(Новый Структура("Партнер", Партнер));
// Иначе менеджером встанет пользователь агента; имена реквизитов — по редакции
ЗаказОбъект.Менеджер = Партнер.ОсновнойМенеджер;
ЗаказОбъект.Статус = Перечисления.СтатусыЗаказовКлиентов.НеСогласован;
ЗаполнитьТовары(ЗаказОбъект, Данные, Ошибки); // по артикулу, только однозначные
КонецЕсли;
Если Ошибки.Количество() = 0 И Не ЗаказОбъект.ПроверитьЗаполнение() Тогда
Ошибки.Добавить("fill_check_failed");
Для Каждого Сообщение Из ПолучитьСообщенияПользователю(Истина) Цикл
Ошибки.Добавить(Сообщение.Текст);
КонецЦикла;
КонецЕсли;
Если Ошибки.Количество() > 0 Тогда
ЗаписьЖурналаРегистрации("АгентЗаявки.Отказ", УровеньЖурналаРегистрации.Предупреждение, , ,
СтрСоединить(Ошибки, Символы.ПС));
Результат.Код = 422;
Результат.Ошибка = СтрСоединить(Ошибки, "; ");
Иначе
ЗаказОбъект.Записать(РежимЗаписиДокумента.Запись); // согласует и проводит менеджер
// Статус переписан — исключение, откат транзакции, ответ 500
Если ЗаказОбъект.Статус <> Перечисления.СтатусыЗаказовКлиентов.НеСогласован Тогда
ВызватьИсключение "status_overwritten";
КонецЕсли;
Запись = РегистрыСведений.ОбработанныеПисьма.СоздатьМенеджерЗаписи();
Запись.КлючПисьма = КлючПисьма;
Запись.Заказ = ЗаказОбъект.Ссылка;
Запись.ДатаОбработки = ТекущаяДатаСеанса();
Запись.ПисьмоПолучено = ПисьмоПолучено;
Запись.Записать();
Результат.Код = 201;
Результат.Заказ = ЗаказОбъект.Ссылка;
КонецЕсли;
КонецЕсли;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("АгентЗаявки.Ошибка", УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Результат.Код = 500;
Результат.Ошибка = "internal_error";
КонецПопытки;
Возврат Результат;
КонецФункции
Функция ЛимитЧасаИсчерпан()
Запрос = Новый Запрос(
"ВЫБРАТЬ
| КОЛИЧЕСТВО(*) КАК Создано
|ИЗ
| РегистрСведений.ОбработанныеПисьма КАК Т
|ГДЕ
| Т.ДатаОбработки >= &Граница");
Запрос.УстановитьПараметр("Граница", ТекущаяДатаСеанса() - 3600);
Выборка = Запрос.Выполнить().Выбрать();
Выборка.Следующий();
Возврат Выборка.Создано >= Константы.АгентЗаявкиЛимитВЧас.Получить();
КонецФункции
- Идемпотентность: после обрыва сети или повторной доставки регистр вернёт готовый заказ с кодом 200, а блокировка не даст параллельным вызовам создать два документа. Нужен управляемый режим блокировок; в 1С:ERP он включён.
- Статус после записи: перепишет ли его типовой код, не подтверждено, поэтому после
Записать()сервис сверяет статус; при расхождении исключение откатывает транзакцию, ответ — 500. - Проверка заполнения:
ПроверитьЗаполнение()вызываем явно — программная запись проверку заполнения не вызывает вообще, ни при записи, ни при проведении. Тексты изПолучитьСообщенияПользователю(Истина)уходят в журнал регистрации и в тело ответа 422. Ослаблена ли типовая проверка для статуса «Не согласован» (черезМассивНепроверяемыхРеквизитов), не подтверждено — смотритеОбработкаПроверкиЗаполнениясвоей редакции. - Лимит сигнальный: параллельные вызовы могут перешагнуть порог на одну-две записи, его задача — остановить лавину. Порог ставят с запасом над пиковым часом по регистру; событие
АгентЗаявки.Лимитв журнале регистрации — точка для оповещения. При нулевой константе сервис отвечает 429 на всё: порог задают до включения. - 422 уходит в ручную очередь: заявку разбирает оператор.
ПартнерПоИНН и ЗаполнитьТовары зависят от редакции, принцип — подставлять лишь однозначное. Контрагента ищут запросом ВЫБРАТЬ ПЕРВЫЕ 2 по ИНН (идентификационному номеру налогоплательщика) и берут его партнёра, только если запись одна: у обособленных подразделений ИНН общий с головной организацией, различает их КПП (код причины постановки на учёт). С номенклатурой так же; любая неоднозначность — ответ 422.
Сторона агента: модель не собирает запрос
Идентификатор письма и время получения агент берёт из метаданных, не из ответа модели; время — с почтового сервера (заголовок Date ставит отправитель) и в часовом поясе базы. Ответ модели до вызова 1С проверяется по JSON-схеме: лишние поля запрещены, ИНН — 10 или 12 цифр, у позиции есть артикул, количество больше нуля и source — где в письме найдено значение. На 422 агент ставит заявку в ручную очередь, на 429 и 503 останавливается без повторов (503 может прийти от веб-сервера, тело тогда не JSON).
Входящее письмо — недоверенный ввод
В письме может оказаться «отмените заказ № 123» или текст, адресованный модели. Опасна «смертельная тройка» (lethal trifecta, термин Simon Willison): доступ к закрытым данным, недоверенный текст и канал наружу одновременно. Здесь её разрывает сама конструкция: у агента в 1С один метод — создать заказ без резерва; письма он не отправляет, ответ по шаблону уходит после подтверждения сотрудником; цены и остатки роль не читает.
Где ставить проверки
Заявка на пять позиций — примерно двадцать решений агента: клиент, договор, склад, дата, а по каждой позиции товар, количество и единица. При точности 99 % на каждом решении без единой ошибки пройдут около 82 % заявок (0,99 в степени 20 ≈ 0,82; упрощённо, ошибки считаются независимыми). При 10 решениях с точностью 99 % цепочка верна в ~90 % случаев, при 40 — в ~67 %; значения для точности 95 % и 99,9 % сведены в таблицу надёжности цепочки. Проверки ставят в коде там, где ошибка дорога, и каждая видна в ответе сервиса.
| Что пошло не так | Где ловим | Ответ | Кто решает |
|---|---|---|---|
| Мусор в ответе модели | JSON-схема | Ручная очередь | Оператор |
| Филиал с тем же ИНН, пустое поле | ПЕРВЫЕ 2, проверка заполнения |
422 | Оператор |
| Лавина записей | Лимит, журнал, выключатель | 429, затем 503 | Ответственный за агента |
| Правдоподобная ошибка | Согласование человеком, поле source |
— | Оператор |
| Незамеченные ошибки | Выборочный контроль, журнал правок | — | Владелец процесса |
По журналу правок через месяц видно, какие поля операторы исправляют чаще всего, какую проверку можно снять и где нужна новая.
Замер до и после: что записать до первой строки кода
Мерить нужно весь путь заявки, и начинать до запуска — иначе потом не с чем сравнить:
- медианное сквозное время «письмо → первая отгрузка» — главная цифра;
- отдельно — сколько письмо лежит в ящике до начала ввода (по журналу регистрации или дате заказа);
- заявок за неделю, обработанных до отгрузки;
- очередь несогласованных заказов на конец дня и возраст самого старого;
- доля заявок без правок после ввода (история изменений, журнал регистрации);
- сдерживающие: доля ручной очереди, возвраты со склада или логистики.
До запуска хватит выборки из 30–50 недавних заявок. После запуска самая показательная цифра — очередь несогласованных заказов агента. Считайте её по статусу заказа и регистру агента, а не по признаку «Проведен»:
ВЫБРАТЬ
КОЛИЧЕСТВО(*) КАК ЗаказовВОчереди,
МИНИМУМ(О.ПисьмоПолучено) КАК СамоеСтароеПисьмо
ИЗ
РегистрСведений.ОбработанныеПисьма КАК О
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента КАК З
ПО О.Заказ = З.Ссылка
ГДЕ
З.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказовКлиентов.НеСогласован)
И НЕ З.ПометкаУдаления
Сквозное время считают из регистра агента (там есть время письма), соединяя его с первой проведённой отгрузкой по ссылке на заказ. Медиану — в коде или в Excel, в языке запросов её нет; заявки без отгрузки не выбрасывайте, их доля тоже метрика.
Почему быстрый ввод может почти ничего не дать. Условный расчёт: от письма до отгрузки 40 рабочих часов, ручной ввод из них — 1 час. Агент ускорил ввод в 10 раз, до 6 минут: минус 54 минуты из 40 часов, примерно 2 %. Заметный выигрыш даёт ожидание: письмо ждёт в ящике 6 рабочих часов, агент берёт его сразу — минус 6 часов из 40, около 15 % (тоже условный пример). Типичная картина без замера: заказы создаются за минуты и копятся у менеджеров — очередь переехала на согласование, запрос выше покажет это в первую неделю. Заранее договоритесь, по какой метрике процесса судить о результате и кто согласует заказы агента.
Сроки и проверка до запуска
Сколько это по времени. Оценка порядка: расширение с сервисом, ролью, регистром и публикацией — дни работы разработчика; прогон на прошлых письмах и замер «до» — недели, дольше всего идёт сбор выборки.
Как проверить до запуска. Сначала стенд: четыре сочетания настроек — расширенные возможности заказа клиента вкл/выкл × согласование заказов клиентов вкл/выкл — и в каждом смотрите, какой статус у записанного заказа. Затем на копии базы прогоните через агента прошлые письма и сравните его заказы с теми, что ввели люди: клиент, договор, склад, позиции, количества, единицы. По каждому расхождению решите, кто ошибся — агент или человек, и верно ли письмо ушло в ручную очередь.
Модели и данные
Модель — у российского облачного провайдера или в периметре заказчика; где именно, решают служба безопасности и юрист до пилота. ПДн (персональные данные) — имена, телефоны, адреса — маскируйте до отправки в модель, журнал агента храните у заказчика. Соответствие 152-ФЗ оценивает юрист или ответственный за ПДн.
Что осталось за рамками
Статья закрывает один канал записи. Где ещё агент может навредить:
- внешние обработки: сервис ограничивает только пользователя агента; обработку, которую написал ИИ-ассистент, человек запускает со своими правами — сначала на копии базы;
- регламентные задания и планировщики: ни одно не работает от имени агента, все входят в регулярную ревизию;
- журнал регистрации: до запуска решите, кто смотрит записи с отбором по пользователю агента, как часто и кто получает оповещение о событии лимита.
Когда агент не нужен
- Поток маленький — пара часов ввода в месяц.
- Заявки приходят в одном Excel-шаблоне или через портал — хватит обмена или OData.
- Крупный клиент готов слать заявки через API (программный интерфейс) или ЭДО (электронный документооборот).
- Входящие — типовые первичные документы: для них есть «1С:Распознавание первичных документов».
- Справочники в дублях — сначала чистка.
- Замер показал, что ввод не узкое место.
Чек-лист перед продуктивом
- Отдельный пользователь агента без интерактивного входа и внешнего соединения.
- У роли нет базовых прав на документы.
- Запись — один метод HTTP-сервиса в расширении без безопасного режима; опция расширенных возможностей заказа клиента включена.
- Отдельная публикация через
webinst -descriptor: вdefault.vrdтолько этот сервис, OData и клиенты выключены; только HTTPS и только с адреса хоста агента. - Заказ «Не согласован», статус сверяется после записи; согласование, ответ клиенту и отгрузка — за человеком.
- Идемпотентность по
Message-IDс блокировкой. - Сигнальный лимит, выключатель, оповещение по журналу регистрации и человек, который его читает.
- JSON-схема до вызова 1С, ручная очередь с ответственным, никаких методов «отменить» и «отправить».
- Стенд в четырёх сочетаниях настроек и прогон прошлых писем; метрики «до» записаны, метрика результата согласована.
Автор — практик: ставит ИИ-агентов в рабочие процессы компаний на 1С.
Вступайте в нашу телеграмм-группу Инфостарт
Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.