Я поставил локальную языковую модель на рабочий ноутбук, чтобы она смотрела чужой код 1С. Первый месяц был потрачен зря: на восемнадцати фрагментах с заранее известными дефектами она нашла ноль и семнадцать раз ответила, что код корректен.
Я решил, что её надо дообучать. Это была ошибка, и хорошо, что я до неё не дошёл.
Сработало другое: я перестал её учить и дал ей другую работу. После этого та же модель на том же ноутбуке объяснила восемь дефектов из девяти, а на ста чужих обработках не выдумала ни одного имени из семидесяти одного названного.
Теперь это бесплатный инструмент, которым я пользуюсь каждый день: Ollama бесплатна, модель бесплатна, видеокарта уже стояла в ноутбуке. Ниже вся дорога, включая тупики.
Зачем вообще локальная, а не облако
Хотелось простого: поставить модель на своей машине, дать ей чужую обработку и получить список проблем. Без облака, без токенов, без вопроса "а куда уехал наш код".
Последнее не паранойя, а обычный рабочий разговор: код заказчика в облако не отправишь, даже если очень хочется. Плюс подписка на облачную модель это ежемесячные деньги, а тут разовая установка.
Собрал стенд: Ollama, две модели Qwen2.5 (3B и 7B-coder), ноутбучная GTX 1650 на 4 гигабайта. Взял двенадцать фрагментов на встроенном языке: девять с одним известным типовым дефектом каждый (запрос в цикле, транзакция без отката, НайтиПоНаименованию в цикле, серверный объект на клиенте, запись в цикле, СрезПоследних в цикле, конкатенация в запросе, ТекущаяДата на клиенте, пустая Попытка) и три заведомо чистых как контроль на выдумку.
Шестьдесят генераций, temperature 0.2. Дальше три режима, и разница между ними и есть весь материал.
Режим первый: пусть ищет сама
Промпт простой: "есть ли в этом коде проблемы?". Плюс просьба не выдумывать.
Результат: ноль из девяти у каждой модели. 3B восемь раз ответила "код корректен" и один раз выдумала проблему с неопределённой переменной, которой там не было. 7B ответила "код корректен" все девять раз.
Контрольные чистые фрагменты обе прошли: три из трёх "корректен". Только радоваться нечему. Она отвечает так всему, и чистые прошли не потому, что модель отличает чистое от грязного.
Отдельная находка, которая мне дороже самих чисел. Инструкция "если проблем нет, так и скажи" превращает маленькую модель в соглашателя. Ты просишь её не выдумывать, и она честно не выдумывает: молчит про всё.
Вывод, который я забрал: продавать или собирать себе кнопку "вставь код, модель найдёт проблемы" на локальной модели этого класса нельзя. Она будет говорить, что всё хорошо, и ошибаться в вашу пользу, что хуже всего.
Режим второй: сказать ей, что искать
Тот же код, но в промпте уже лежит находка: "статический анализатор нашёл вот это, объясни на этом коде".
Восемь полезных объяснений из девяти. Текст по делу, на конкретном коде, с именами из этого кода. 3B при этом оказалась не хуже 7B и в шесть-десять раз быстрее: 8-15 секунд против 29-69.
Вот это и есть приём целиком. Он умещается в одну фразу: ищет не модель, а детерминированный код; модель объясняет уже найденное.
Почему это важнее, чем звучит. Модель, которая не ищет, не может выдумать дефект - ей нечего выдумывать, находка пришла снаружи. Меняется сам класс ошибок: вместо "нашла то, чего нет" остаётся "объяснила чуть хуже, чем могла бы". Первое подрывает доверие ко всему отчёту, второе нет.
Искать при этом можно чем угодно: своим парсером, набором проверок, готовым линтером. Мне было чем - у меня лежал анализатор на встроенном языке, лексер с парсером и таблицей типов. Но приём от инструмента поиска не зависит.
Что нужно дать модели, чтобы она отвечала правильно
Семь пунктов. Каждый я добавлял по одному, глядя, что меняется в ответах.
1. Находку, а не задачу её искать. В промпт идёт готовый результат детерминированной проверки: правило, уровень, номер строки, короткое пояснение. Это тот самый пункт, который делает ноль из восемнадцати восемью из девяти.
2. Фрагмент кода вокруг находки, а не модуль целиком. У меня четыре строки до и четыре после, с номерами и пометкой на строке находки. Модуль на тысячу строк класть бессмысленно: маленькая модель в нём тонет, а ответ становится общим. Плюс на длинном входе 7B у меня жевала промпт минутами.
3. Строки с номерами, а не голый текст. Формат "номер: текст" в промпте - и модель начинает ссылаться на номера сама. Без нумерации она пишет "в этом цикле", и привязать это к месту нельзя.
4. Справку по правилу: почему это плохо и чем кончится. У меня она лежит в реестре правил рядом с самим правилом. Без неё модель объясняет своими словами и уходит в общие места вида "это может привести к проблемам с производительностью".
5. Один пример хорошего ответа. Не три и не пять, один. Он задаёт формат: сколько пунктов, какой длины, в каком порядке.
b88;A039; И тут же ловушка, на которую я налетел. Совсем маленькая модель начинает дословно копировать пример, подставляя другой номер строки. Qwen2.5-Coder-1.5B выдала мне три ответа подряд, полностью совпадающие с примером, включая табличную часть и запрос к номенклатуре, которых в разбираемом коде не было вовсе. Проверять это надо на своём коде, а не на примере из промпта.
6. Отфильтрованный вход вместо запретов. Не пишите "процедуру X не описывай" - напишете, и она построит ответ вокруг X. Вырезайте X из текста до отправки. Разница между этими двумя подходами у меня была разительная, подробнее ниже в правках.
7. Низкую температуру и потолок ответа. У меня 0.2 и 350 токенов на карточку находки. Выше температура - модель начинает сочинять, больше потолок - расползается в рассуждения. Ответ на 400-500 знаков читают, на 2 000 нет.
И один пункт про то, чего давать не надо: задачу починить, если у вас есть точный рецепт. Про это отдельный раздел ниже, там самая дорогая ошибка из всех.
Почему починку я у модели забрал
Третий режим показал, где проходит граница, и это оказалось самым полезным решением во всей конструкции.
Просил: "анализатор нашёл вот это, предложи исправленный код". Получил ноль годных из девяти у 3B: четыре раза вернула код без изменений, три раза сломала, два раза выдала мусор. У 7B почти одна годная из девяти.
Что именно она выдумывала: ЗавершитьТранзакцию, КонецИсключения, КонецТранзакции, Запрос.Параметры.Добавить, БлокировкаДанных(Товар). Ни одного из этих имён в платформе нет. Выглядит всё это абсолютно уверенно.
Самое дорогое я узнал позже, уже на чужом коде. Модель предложила такую починку транзакции:
Попытка НачатьТранзакцию(); ОтменитьТранзакцию(); Исключение ОтменитьТранзакцию(); ВызватьИсключение; КонецПопытки;
Откат в теле Попытки вместо фиксации. Синтаксически верно, читается грамотно, откатывает каждую транзакцию. Это хуже исходного дефекта.
И вот что делает этот случай поучительным: правильный шаблон лежал у модели в том же промпте. Я передавал ей справку по правилу, где чёрным по белому стоит "Попытка ... ЗафиксироватьТранзакцию() Исключение ОтменитьТранзакцию()". Она получила верный ответ на входе и испортила его на выходе.
Отсюда правило, которое я теперь считаю главным: если у вас есть точный рецепт, печатайте его сами, а не просите модель его пересказать. Пересказ ничего не добавляет и иногда всё портит.
Так у меня и сделано: рецепт починки берётся из реестра правил и печатается дословно, отдельным блоком с подписью "способ из реестра, не ответ модели". Модель пишет только диагноз. После этой правки класс опасных советов исчез целиком, а не уменьшился: их больше неоткуда взяться.
Это, кстати, общий приём, и он шире моей задачи. Всё, для чего у вас есть точный ответ, модели показывать не надо вообще - ни как задачу, ни как материал для пересказа. Она нужна ровно там, где точного ответа нет: связать находку с конкретным кодом человеческими словами.
Что можно звать: техническая часть
Ollama после установки поднимает HTTP-сервис на localhost:11434. Никакого SDK не нужно, это обычный REST, и из 1С он вызывается штатным HTTPСоединение.
Проверить, что сервис жив, и получить список моделей:
GET /api/tags
Отдаёт JSON с массивом models, у каждой name. Это же удобный способ понять, скачана ли нужная модель, не дёргая генерацию.
Спросить модель:
POST /api/generate
{"model": "qwen2.5:3b", "prompt": "...", "stream": false}
Ответ приходит в поле response. Параметр stream в значении Ложь важен: иначе Ollama отдаёт поток из множества JSON-объектов по строке на токен, и разбирать его из 1С заметно неприятнее.
Из встроенного языка это выглядит так:
Соединение = Новый HTTPСоединение("localhost", 11434, , , , 120); Запрос = Новый HTTPЗапрос("/api/generate"); Запрос.Заголовки.Вставить("Content-Type", "application/json"); Запрос.УстановитьТелоИзСтроки(ТелоJSON, КодировкаТекста.UTF8); Ответ = Соединение.ВызватьHTTPМетод("POST", Запрос);
Три вещи, на которых легко споткнуться.
Таймаут ставьте руками и с запасом. Первый ответ после запуска дольше остальных: модель поднимается в видеопамять. На слабой карте она вообще не поместится и уедет считаться на процессор, и тогда минута на ответ это норма, а не сбой.
Вызов идёт с сервера 1С, а не с клиента. Если сервер приложений стоит на отдельной машине, localhost для него означает эту машину, а не вашу. Лечится полем "адрес сервиса" в настройках: одна машина с видеокартой спокойно обслуживает отдел.
Модель выбирайте явно. Автоподбор "возьмём любую, что стоит" однажды взял у меня Qwen2.5-Coder-1.5B, и та начала дословно повторять пример из промпта, подставляя другой номер строки. Выглядит как ответ, содержания ноль.
Какое железо для этого нужно
Вопрос, который задают первым, и ответ на него приятнее, чем ожидают.
Модель 3B весит 1,9 гигабайта и на видеокарте с 4 гигабайтами памяти помещается целиком. Это GTX 1650, карта 2019 года из обычного рабочего ноутбука. На ней ответ занимает 8-15 секунд.
7B весит 4,7 гигабайта и в те же 4 гигабайта уже не влезает. Ollama честно раскладывает её пополам между видеокартой и процессором, и ответ растягивается до 29-69 секунд. Работает, но ждать неприятно.
Без видеокарты вообще тоже работает, просто медленнее. Это тот случай, когда "минута на ответ" не поломка, а физика: считает процессор.
Практический вывод: гнаться за большой моделью на слабом железе не нужно. Маленькая на видеокарте обгоняет большую на процессоре и на объяснениях не уступает. А если в отделе есть одна машина с приличной картой, она спокойно обслужит всех: сервис слушает по сети, адрес задаётся настройкой.
Четыре правки, после которых заработало
Каждая началась с дефекта, и у каждой лечение оказалось не там, где я его искал.
1. Запрет работает наоборот. Я написал в промпте "процедуры СведенияОВнешнейОбработке и ДобавитьКоманду не описывай вовсе". Модель построила весь ответ вокруг них и приписала им выдуманное назначение. Классический прайминг: упоминание втягивает предмет в фокус, а отрицание маленькая модель держит плохо.
Лечение оказалось не в формулировке, а во входе: служебные процедуры я стал вырезать из текста до отправки. Повторный прогон той же обработки дал три абзаца по делу и названные реальные механизмы.
Общее правило: негативные инструкции про конкретные сущности заменяйте фильтрацией входа. Общие запреты стиля ("без общих советов") работают нормально, адресные нет.
2. Модель повторяет ваш же промпт в ответе. 7B в двух ответах из трёх вываливала обратно блок справки, который я ей подавал как материал. 3B иногда возвращала кусок кода, который я же ей и дал. Просить "не повторяй" бесполезно (см. пункт 1), поэтому эхо я режу на выходе по маркерам.
3. Модель склоняет идентификаторы. На шестидесяти находках чужого кода поймал три случая: Справочникам.Склады вместо Справочники.Склады, ПриОткрытие вместо ПриОткрытии. Смысл верный, форма испорчена, поиском в конфигураторе такое не найдёшь. Чинится постобработкой: имя из ответа, отличающееся на две буквы от имени, которое в коде есть, заменяется настоящим.
4. Аббревиатуры она расшифровывает наугад. На казахстанском коде модель написала "СНТ (Справочная Номенклатура Товаров)". СНТ это сопроводительная накладная на товары. В промпте при этом стояло "не расшифровывай аббревиатуры" - см. снова пункт 1.
Сколько в этом правды: проверка на чужом коде
Двенадцать фрагментов это мой набор, а на своих примерах у всех всё работает. Поэтому потом я прогнал связку по ста внешним обработкам боевой базы, которые писали разные люди годами. Сто тысяч строк, мой код там ноль.
Детерминированная часть: 159 модулей, 1135 находок, ноль падений и ноль номеров строк за пределами файла. Весь корпус за 141 секунду.
Модель проверял механически. Идея простая: она объясняет конкретный код, значит каждое имя, которое она называет, обязано быть в этом коде. Считал.
| Имён названо в диагнозе | 71 |
| Есть в исходнике | 67 (94 процента) |
| Искажено склонением | 4 |
| Выдумано с нуля | 0 |
Ноль выдумок на семьдесят одно имя. Это и есть плата за то, что модель не ищет: выдумывать ей нечего.
Что в итоге получилось
Связка работает и вот в каком виде.
Человек вставляет модуль объекта и модуль формы. Движок разбирает их по отдельности, чтобы номера строк совпадали с тем, что человек видит у себя в конфигураторе: склейка сдвинула бы нумерацию второго модуля на длину первого, и указание на строку потеряло бы смысл. Дальше по каждой находке модель пишет объяснение на этом конкретном коде.
На выходе три вещи: что эта обработка вообще делает человеческими словами, список находок с объяснениями и планом починки, и оценка качества с техдолгом в минутах.
Обработка на 1 300 строк модуля объекта плюс 500 строк модуля формы разбирается за 1 минуту 16 секунд. Это чужой код, который писали несколько лет и который до того не читал никто.
Про честность разделения: находка, её правило, номер строки и способ починки приходят от движка и проверяемы. Объяснение приходит от модели и помечено бейджем "может ошибаться". Смешивать эти две вещи в одном абзаце нельзя - тогда весь отчёт читается как догадка.
Про починку от модели я не жалею, что отказался. Четыре неверных совета из шестидесяти, дважды опасных, - это цена, которую платить незачем, когда точный рецепт уже лежит в реестре правил.
Сколько это стоит
Ноль. И это не фигура речи, а список.
Ollama бесплатна и ставится в два клика. Модель Qwen2.5 качается один раз, 1,9 гигабайта, тоже бесплатно. Видеокарта у меня уже стояла в рабочем ноутбуке, покупать ничего не пришлось. Подписки нет, токенов нет, лимитов нет: гоняю сколько хочу, хоть весь день.
Сравните с облаком: там каждая обработка это деньги и вопрос, можно ли вообще отправлять туда чужой код. Здесь ни того, ни другого.
Я не буду утверждать, что локальная модель делает работу так же, как облачная: я их не сравнивал, замера нет, а без замера такие слова стоят ноль. Скажу иначе и по факту: на моей задаче её хватает, и она обходится бесплатно. Для инструмента, которым пользуешься каждый день, это оказалось важнее, чем последние проценты качества.
Что забрать себе
Если будете делать похожее, вот то, что я бы хотел знать заранее.
Не давайте модели искать. Ищет детерминированный код, модель объясняет. Ноль из восемнадцати против восьми из девяти это не настройка промпта, это разные задачи.
Маленькая модель не хуже большой на объяснениях. 3B против 7B: качество то же, скорость в шесть-десять раз выше. Дорогая модель на этом классе задач не окупается, окупается подсказка.
Детерминированное печатайте сами. Всё, для чего у вас есть точный ответ, модель должна не пересказывать, а обходить стороной.
Проверяйте механически, а не на глаз. Имена из ответа ищутся в исходнике одной функцией. Это дёшево, считается само и ловит то, что на глаз читается как уверенный текст.
Меряйте на чужом коде. Свой набор льстит всегда: вы знаете, что туда положили.
Как это сделано у меня
Приём выше не зависит от инструмента, но раз спросят - у меня он собран во внешнюю обработку: движок находит, локальная модель объясняет, всё на машине пользователя, наружу не уходит ничего. Ollama ставится своим скриптом, модель качается один раз.
Обработка со всей связкой лежит здесь: ИИ-анализ кода 1С: логика, находки и рекомендации на локальной модели. Отдельно движок, который ищет, без модели - Анализ кода внешних обработок 1С.
Один вопрос оставляю открытым, потому что сам его не закрыл: удалось ли кому-нибудь получить годную починку от локальной модели этого размера. RAG со справкой синтаксиса я не пробовал, дообучение на парах дефект-правка тоже. Если у вас взлетело, напишите чем - это единственное место, где я пока обхожусь детерминированным шаблоном не от хорошей жизни.
Вступайте в нашу телеграмм-группу Инфостарт