Вводная часть: Наивная мечта

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

Шлюз транскрибирует голос в текст (использована Java + библиотека Vosk), передает его ИИ, а тот «понимает», какие запросы нужно сделать к OData 1С, получает данные и возвращает пользователю красивый, структурированный отчет.

Для голосового ввода использовался отдельный модуль на базе Vosk, преобразующий речь в текст. В данной статье мы сосредоточимся на этапе обработки полученного текста и его конвертации в OData-запросы

Поскольку приложение работает по протоколу OData, оно теоретически универсально для любой системы, поддерживающей этот стандарт.
Ссылки на документацию, которыми я руководствовался:

Технический стек и условия эксперимента

Перед тем как перейти к архитектуре, важно обозначить границы «песочницы», в которой проводилось исследование.

  • Локальность и безопасность: Под ИИ в статье подразумеваются исключительно локальные языковые модели (LLM), запущенные через Ollama. Весь процесс — от векторизации до генерации ответа — проходит внутри закрытого контура. Это может быть важно для систем на базе 1С, где передача данных во внешние облачные API (OpenAI и др.) недопустима.

  • «Мозги» системы (LLM):

    • Llama 3.1 (8B) — основная рабочая модель для классификации намерений.

    • Mistral Small (24B) — использовалась на этапе проверки гипотез.

  • Поиск и память (RAG):

    • Модель эмбеддингов: mxbai-embed-large (локальная векторизация метаданных).

    • Векторная БД: PostgreSQL + расширение pgvector.

  • Инфраструктура:

    • Backend: Java 21, Spring Boot 4 (Spring AI).

    • Протокол: OData (стандартный интерфейс 1С).

    • Железо: Арендованный сервер 4 ядра 16 Гб с RTX 2080 TI (11 ГБ RAM) для 8B-моделей. Для тестов 24B-модели привлекался облачный сервер с RTX 4090.

Первый подход: Хардкод и первые разочарования

Я начал с простого: написал метод fetchTopKontragents(Integer limit), пометил его аннотацией @Tool и захардкодил в нем вызов получения контрагентов.

«Получение контрагента
@Tool(description = "Получить топ контрагентов из базы 1С")
public List fetchTopKontragents(
       @ToolParam(description = "Количество записей для получения (по умолчанию 5)") Integer limit) {
   int topValue = (limit != null) ? limit : 5;
   log.info("Инструмент fetchTopKontragents вызван с лимитом: {}", topValue);

   return webClient.get()
           .uri("Catalog_Контрагенты?$top=5&$format=json")
           .retrieve()
           .bodyToMono(new ParameterizedTypeReference>() {
           })
           .map(ODataResponse::getValue)
           .block();
}

Spring AI работает корректно и ИИ успешно вызывал инструмент. Но писать отдельный метод под каждый справочник или документ 1С — это тупик. Я хотел, чтобы генерацией правильных GET-запросов занимался сам ИИ.

Попытка №2: Векторная база (RAG) и капризные модели.

Я выгрузил структуру метаданных 1С, векторизовал их и сохранил в векторную БД (PostgreSQL + pgvector).

так выглядит xml со структурой метаданных

Теперь при запросе пользователя я подмешивал в контекст подходящие ресурсы.

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

Важный нюанс: На этом этапе я столкнулся с капризностью моделей вроде qwen2.5-coder. Несмотря на мощь в коде, в режиме локального запуска через Ollama они часто игнорировали Tools и пытались «философствовать» вместо вызова функций.

Когда я попросил «выведи 2 контрагента», RAG начал выдавать случайный мусор: «Приходно-кассовые ордера» или «Договоры». Так как слово «Контрагент» часто встречается в документах и справочниках. Тогда пришла идея: разбить ресурсы в базе на Сущности (имена таблиц) и Поля. Качество поиска сразу возросло.

Пример json со списком сущностей

Битва за точность: Эффект «Робассы»

Когда связка заработала, я увидел в логах: ИИ формирует правильный GET, получает правильный JSON от 1С... и выдает пользователю «ООО Ромашка, ООО Ромашка». Дубли.

Я предположил, что Llama 3.1 8B просто не хватает мощности. Переходим на модель поумнее: арендуем сервер с NVIDIA RTX 4090 и запускаем Mistral Small 24B.

С новой моделью дубли исчезли. Пользователю вернулись три разных контрагента. Но радость была недолгой. Взглянув на реквизиты, я увидел странный ИНН. Сверил с базой — такого нет. Модель получила верный ИНН в JSON-ответе, но при «пересказе» результата выдумала контрагента и ИНН. Более того, в другом тесте общеизвестная «РобоКасса» легким движением нейронных связей превратилась в «Робассу».

Философский перелом: ИИ — не бухгалтер

В этот момент у меня кристаллизовалась мысль:

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

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

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

Итоговое решение: Интеллектуальный шлюз

Я решил не «домучивать» модель промптами, а изменить архитектуру.

Примечание к схеме:

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

Архитектура проекта позволяет в любой момент активировать этот слой для реализации сложной фильтрации (поиск по конкретным реквизитам, ИНН или датам), когда в этом возникнет реальная бизнес-необходимость.

Итак:

ИИ —   как Навигатор: Модель только  находит нужную сущность и параметры фильтрации.

Java — как Гарант: Мы используем параметр  returnDirect = true. Инструмент получает данные от 1С и напрямую возвращает сырой JSON пользователю в обход «испорченного телефона» нейросети.

результат executeSmartQuery сразу возвращаем пользователю
@Tool(
       name = "executeSmartQuery",
       returnDirect = true, // результат метода сразу возвращаем пользователю
       description = "Универсальный запрос к 1С. Параметры (entity, filter) нужно брать из базы знаний метаданных.")
public Object executeSmartQuery(
       @ToolParam(description = "Имя сущности из метаданных (напр. Catalog_Контрагенты)") String entity,
       @ToolParam(description = "Фильтр OData (напр. ИНН eq '12345' или Number eq '001')") String filter,
       @ToolParam(description = "Лимит записей (по умолчанию 5)") Integer top,
       @ToolParam(description = "Только если нужен подсчет количества (Boolean)") Boolean countOnly
) {

Результат: 100% достоверность данных. Если в 1С написано «РобоКасса», пользователь увидит «РобоКасса». ИИ больше не имеет права голоса в части фактов.

Оптимизация ресурсов: Возврат к истокам

Внедрение детерминированного вывода привело к приятному побочному эффекту. Когда я снял с ИИ задачу «быть бухгалтером» и оставил только роль интеллектуального навигатора, надобность в арендованной RTX 4090 и тяжелой модели Mistral 24B отпала.

Я вернулся на локальную Llama 3.1 8B, и она показала отличные результаты. Оказалось, что для распознавания намерения пользователя и поиска нужной таблицы в RAG-базе мощностей обычной «бытовой» видеокарты более чем достаточно. Система стала работать мгновенно, а главное — полностью бесплатно и приватно. В итоге стало очевидно: попытка добиться точности данных путем простого усложнения модели — это тупик. Намного эффективнее оказалась смена архитектурного подхода, где ИИ выполняет роль диспетчера, а гарантированная достоверность цифр обеспечивается прямым программным вызовом OData.

Выводы

Проект не стал «всезнающим ассистентом», но превратился в надежный AI-шлюз. Мы получили:

  • Интеллектуальный поиск по метаданным (RAG).

  • Детерминированный вывод (никаких галлюцинаций в цифрах).

  • Работающую связку на Spring AI, готовую к Enterprise-задачам.

ИИ — отличный диспетчер, но ужасный секретарь-референт. Пускать его в святая святых (учетные данные) можно только в «наморднике» строгого программного кода.

GitHub

P.S. При подготовке структуры статьи и редактировании некоторых формулировок использовались инструменты ИИ. Однако весь цикл разработки, тестирование гипотез, архитектурные решения и финальный код в репозитории — полностью моя работа и личный опыт.

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


  1. mixsture
    10.01.2026 16:41

    получает данные и возвращает пользователю красивый, структурированный отчет

    а с чего бы модель вообще могла такое сделать?

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

    --
    Да и к реализации тут будут большие вопросы. Примерно такие же, как к худшим представителям low-code платформ: в конечном итоге, когда вы реализуете таки пункт выше (про трансляцию человеческого языка в запросы к сущностям) - окажется, что вы для построения отчета делаете 300 запросов к базе данных (естественно, я беру аналог реального сложного отчета, который соединяет кучу сущностей, группирует и рассчитывает по ним дополнительные данные), вынуждены прокачать в 10-100 раз больше данных к месту, где работает ваша модель, и обработать эти все переходы кодом. Это прям на порядки менее эффективно, чем работает сейчас внутри 1с:
    где запрос будет 1,
    где порядок обхода сущностей определит оптимизатор субд,
    где не вернется лишнего объема данных,
    где максимально все будет обработано еще в субд.

    Куда потом пристроить ваше решение, если оно настолько неэффективно? Купить сервер в 10-100 раз мощнее, чтобы покрывал эту неэффективность?


    1. peta0982 Автор
      10.01.2026 16:41

      В начале статьи я специально обозначил ожидания как "наивные". Целью исследования было не создать замену СКД (системе компоновки данных), а проверить: может ли ИИ вообще ориентироваться в структуре OData "с чистого листа".


      1. peta0982 Автор
        10.01.2026 16:41

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


      1. mixsture
        10.01.2026 16:41

        Так в "выводах" вы заявляете, что все очень даже получилось:

        Работающую связку на Spring AI, готовую к Enterprise-задачам.

        Вот я хочу понять, где и что получилось то?
        То, что оно может "найди контрагента с инн 123" перевести в url (точнее параметры тула)? Это совсем не enterprise задача. А вот отчет, который я описал в комментах выше - как раз да.


        1. peta0982 Автор
          10.01.2026 16:41

          На самом деле получилось собрать рабочую техническую связку, которая стабильно (без галлюцинаций) вытаскивает атомарные данные по запросу на естественном языке.
          Под "готовностью к Enterprise" я, пожалуй, поспешно подразумевал лишь инфраструктурную часть: стек Java/Spring AI, работу в закрытом контуре и безопасность данных. С точки зрения бизнеса — это пока лишь умная поисковая строка.
          Статья — это в первую очередь история моего пути от наивного "сейчас заменю всех" до понимания, что ИИ — это просто новый, довольно капризный тип интерфейса к данным, у которого очень узкая ниша применения. Спасибо за конструктивную критику, она помогает правильно расставить акценты в итогах исследования.