- BrainTools - https://www.braintools.ru -
Изначально идея казалась кристально чистой: пользователь отправляет текстовый или голосовой запрос (например: «Выведи топ должников по Тверской области на текущую дату и суммы задолженности»).
Шлюз транскрибирует голос в текст (использована Java + библиотека Vosk), передает его ИИ, а тот «понимает», какие запросы нужно сделать к OData 1С, получает данные и возвращает пользователю красивый, структурированный отчет.
Для голосового ввода использовался отдельный модуль на базе Vosk [1], преобразующий речь в текст. В данной статье мы сосредоточимся на этапе обработки полученного текста и его конвертации в OData-запросы
Поскольку приложение работает по протоколу OData, оно теоретически универсально для любой системы, поддерживающей этот стандарт.
Ссылки на документацию, которыми я руководствовался:
Перед тем как перейти к архитектуре, важно обозначить границы «песочницы», в которой проводилось исследование.
Локальность и безопасность: Под ИИ в статье подразумеваются исключительно локальные языковые модели (LLM), запущенные через Ollama. Весь процесс — от векторизации до генерации ответа — проходит внутри закрытого контура. Это может быть важно для систем на базе 1С, где передача данных во внешние облачные API (OpenAI и др.) недопустима.
«Мозги» системы (LLM):
Llama 3.1 (8B) — основная рабочая модель для классификации намерений.
Mistral Small (24B) — использовалась на этапе проверки гипотез.
Поиск и память [4] (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-запросов занимался сам ИИ.
Я выгрузил структуру метаданных 1С, векторизовал их и сохранил в векторную БД (PostgreSQL + pgvector).

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

Примечание: На схеме процесс упрощен для наглядности. В реальности классификация проходит в два шага: сначала модель определяет целевую сущность, затем Шлюз обогащает контекст списком полей именно этой таблицы и отправляет второй запрос для формирования GET-параметров. Это позволило нам не перегружать контекст модели лишними данными других таблиц
Важный нюанс: На этом этапе я столкнулся с капризностью моделей вроде qwen2.5-coder. Несмотря на мощь в коде, в режиме локального запуска через Ollama они часто игнорировали Tools и пытались «философствовать» вместо вызова функций.
Когда я попросил «выведи 2 контрагента», RAG начал выдавать случайный мусор: «Приходно-кассовые ордера» или «Договоры». Так как слово «Контрагент» часто встречается в документах и справочниках. Тогда пришла идея: разбить ресурсы в базе на Сущности (имена таблиц) и Поля. Качество поиска сразу возросло.

Когда связка заработала, я увидел в логах: ИИ формирует правильный GET, получает правильный JSON от 1С… и выдает пользователю «ООО Ромашка, ООО Ромашка». Дубли.
Я предположил, что Llama 3.1 8B просто не хватает мощности. Переходим на модель поумнее: арендуем сервер с NVIDIA RTX 4090 и запускаем Mistral Small 24B.
С новой моделью дубли исчезли. Пользователю вернулись три разных контрагента. Но радость была недолгой. Взглянув на реквизиты, я увидел странный ИНН. Сверил с базой — такого нет. Модель получила верный ИНН в JSON-ответе, но при «пересказе» результата выдумала контрагента и ИНН. Более того, в другом тесте общеизвестная «РобоКасса» легким движением нейронных связей превратилась в «Робассу».
Философский перелом: ИИ — не бухгалтер
В этот момент у меня кристаллизовалась мысль:
Нельзя требовать точных данных от инструмента, который работает статистически приближенно к верному.
Ошибка [5] в один знак в ИНН — это потрясающая точность для нейросети, но катастрофа для бухгалтерии. Я не готов краснеть перед заказчиком за «галлюцинированные» цифры. Если ИИ ошибается на именах, то на расчетах «Выведи топ должников по Тверской области на текущую дату и суммы задолженности»« ошибок будет на порядок больше.
Как инженер с бэкграундом в системном администрировании, я привык доверять логам и фактам, а не вероятностям. Мои тесты наглядно показали фундаментальный конфликт [6] технологий: архитектура LLM на текущем этапе развития не предназначена для трансляции строгих данных. Там, где требуется 100% достоверность, «почти правильный» ответ ИИ эквивалентен ошибке .
Я решил не «домучивать» модель промптами, а изменить архитектуру.

Примечание к схеме:
На представленной схеме отображен текущий рабочий процесс. При изучении исходного кода в репозитории вы заметите, что механизм получения контекста полей реализован, но на данный момент закомментирован. Это осознанное решение: сейчас данный функционал является технологическим рудиментом от более ранних этапов разработки. Я решил не удалять этот код, а оставить его в качестве готового задела на будущее.
Архитектура проекта позволяет в любой момент активировать этот слой для реализации сложной фильтрации (поиск по конкретным реквизитам, ИНН или датам), когда в этом возникнет реальная бизнес-необходимость.
Итак:
ИИ — как Навигатор: Модель только находит нужную сущность и параметры фильтрации.
Java — как Гарант: Мы используем параметр returnDirect = true. Инструмент получает данные от 1С и напрямую возвращает сырой JSON пользователю в обход «испорченного телефона» нейросети.
@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 [7]
P.S. При подготовке структуры статьи и редактировании некоторых формулировок использовались инструменты ИИ. Однако весь цикл разработки, тестирование гипотез, архитектурные решения и финальный код в репозитории — полностью моя работа и личный опыт [8].
Автор: peta0982
Источник [9]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/24162
URLs in this post:
[1] Vosk: https://alphacephei.com/vosk/index.ru.html
[2] OData в 1С:Предприятие (ИТС): https://its.1c.ru/db/intgr83/content/47/hdoc
[3] Настройка и примеры OData (1CFresh): https://1cfresh.com/articles/data_odata
[4] память: http://www.braintools.ru/article/4140
[5] Ошибка: http://www.braintools.ru/article/4192
[6] конфликт: http://www.braintools.ru/article/7708
[7] GitHub: https://github.com/AlekseiPetrovJ/1c-odata-ai-bridge
[8] опыт: http://www.braintools.ru/article/6952
[9] Источник: https://habr.com/ru/articles/984008/?utm_source=habrahabr&utm_medium=rss&utm_campaign=984008
Нажмите здесь для печати.