LLM + 1C: Почему чат-бот для учета — это плохая идея, и как реализовать AI-шлюз через OData. 1С.. 1С. Java.. 1С. Java. llm.. 1С. Java. llm. OData.. 1С. Java. llm. OData. ollama.. 1С. Java. llm. OData. ollama. rag.. 1С. Java. llm. OData. ollama. rag. spring ai.

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

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

Шлюз транскрибирует голос в текст (использована 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 со структурой метаданных
LLM + 1C: Почему чат-бот для учета — это плохая идея, и как реализовать AI-шлюз через OData - 1

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

LLM + 1C: Почему чат-бот для учета — это плохая идея, и как реализовать AI-шлюз через OData - 2

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

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

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

Пример json со списком сущностей
LLM + 1C: Почему чат-бот для учета — это плохая идея, и как реализовать AI-шлюз через OData - 3

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

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

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

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

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

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

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

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

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

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

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

LLM + 1C: Почему чат-бот для учета — это плохая идея, и как реализовать AI-шлюз через OData - 4

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

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

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

Итак:

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

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. При подготовке структуры статьи и редактировании некоторых формулировок использовались инструменты ИИ. Однако весь цикл разработки, тестирование гипотез, архитектурные решения и финальный код в репозитории — полностью моя работа и личный опыт.

Автор: peta0982

Источник

Rambler's Top100