Практический эксперимент · 20 мин
Локальные модели на реальной задаче 1С: объединение таблиц значений
Шесть моделей в OpenCode, полный код лучшего ответа и ошибки, которые остались после победы в рейтинге.
Одно практическое задание на 1С, шесть вариантов моделей в OpenCode и неожиданный победитель. Мы сравнили не красоту объяснений, а код: выполнит ли он условие, насколько близок к запуску и действительно ли воспользовался библиотекой LEM.
В первом опыте с локальными моделями мы давали агентам два абстрактных задания про реквизиты документов и постепенно открывали им знания платформы, БСП и стандартов. Gemma 4 31B вышла вперёд по среднему баллу. После этого захотелось проверить другую, очень узнаваемую для разработчика 1С работу: объединить две таблицы значений по ключу, когда набор колонок заранее неизвестен. Здесь одного подходящего имени функции из библиотеки уже недостаточно. Нужно построить схему результата, сохранить приоритет значений и не ошибиться в методах самой платформы.
Результат оказался неожиданным. Лучший из сохранённых ответов OpenCode написал Qwen3.8 27B — 78/100 по экспертной шкале. Но даже его полный листинг не запускается без правок. Gemma из этой серии набрала только 12 баллов; это была Gemma 4 26B QAT, а не победившая в первом опыте Gemma 4 31B. Сравнивать эти две строки как «модель передумала» нельзя.
Условие: две таблицы, общий ключ и право первого слова
В исходном запросе разработчик просил процедуру на BSL, которая получает две ТаблицаЗначений и возвращает объединённую таблицу. Колонка Ключ обязательно есть в обеих. Имена и количество остальных колонок заранее неизвестны. Все уникальные колонки должны попасть в результат; для совпавших колонок источник с приоритетом задаётся явно. Агенту отдельно велено использовать Синтакс-помощник, БСП, стандарты и доступные знания платформы из LEM.
На словах это похоже на обычное соединение таблиц. На практике тест быстро ловит ошибки: модель может забыть строки, которые есть только во второй таблице; заменить пустое значение приоритетной таблицы непустым из другой; предположить фиксированный список колонок; придумать метод, похожий на Python или SQL; либо незаметно превратить поиск по ключу в квадратичный перебор.
В условии есть тонкость. Фраза «есть только одна общая колонка Ключ» соседствует с требованием выбирать значения совпавших колонок по приоритету. При оценке мы приняли более широкий вариант: помимо ключа могут встретиться одноимённые колонки, и их значение у совпавшего ключа берётся из приоритетной таблицы. Это делает параметр приоритета содержательным, но является интерпретацией, которую при переносе в реальную обработку следовало бы уточнить с заказчиком. Правило для дублей ключа также не было задано; модели решали его по-разному.
Как собирали ответы
Шесть ответов были получены в OpenCode 18–19 сентября 2026 года. В каждом использовался один и тот же исходный текст задачи; модели отличались вариантом весов, длиной предыдущей истории сессии и тем, смогли ли они фактически вызвать LEM. Мы восстановили не только вставленный затем сборник листингов, но и сохранённые сообщения, вызовы инструментов и время из локальной базы OpenCode. Отдельный запуск другого агентского клиента в эту статью не включён.
Это не строгий скоростной бенчмарк. Часть сессий началась с чистого запроса, одна модель пришла к нему после предварительного разговора, а Qwen3.8 27B закончила ответ лишь после сообщения «доделай». У разных моделей были разные вычислительные контуры. Поэтому секунды ниже — наблюдение о конкретном проходе, а не универсальная скорость модели.
Оценка семи первоначально собранных листингов проводилась по четырём критериям: работоспособность или близость к рабочему BSL — 35%, качество алгоритма и кода — 30%, выполнение условия — 25%, знание платформы, БСП и LEM — 10%. Один вариант другого клиента мы исключили из рейтинга OpenCode. Проверка семи вариантов через BSL Language Server дала 114 диагностик в сумме; ни один ответ не был принят как готовый к запуску. Баллы ниже — экспертная оценка, а не процент пройденных тестов в 1С.
Рейтинг: кто приблизился к задаче
| Модель в OpenCode | Близость к запуску | Качество кода | Условие | 1С / БСП / LEM | Итог | Время ответа* |
|---|---|---|---|---|---|---|
| Qwen3.8 27B UD-Q4 + MTP | 68 | 80 | 92 | 75 | 78 | Завершён после «доделай» |
| Qwen3.8 Flash-Next IQ4 + MTP | 55 | 75 | 90 | 35 | 68 | 145 с |
| Qwen 122B | 18 | 25 | 35 | 10 | 24 | 171 с до первого ответа |
| RTX Qwen 35B | 5 | 20 | 32 | 12 | 17 | 104 с; история уже была |
| Gemma 4 26B QAT + MTP | 5 | 14 | 22 | 10 | 12 | 68 с; без вызовов LEM |
| Qwen3 Coder Next | 2 | 10 | 22 | 5 | 10 | 425 с |
* Время — промежуток от запроса до первого законченного ответа в соответствующей сессии OpenCode. У победителя один запрос прервался и понадобилось явное продолжение, поэтому сопоставимой длительности одного прохода у него нет. Число секунд включает работу агента и инструментов, а не только генерацию токенов. Разное оборудование и состояние инструментов запрещают считать этот столбец рейтингом аппаратной скорости.
Самый быстрый ответ не оказался хорошим решением. Gemma 26B уложилась примерно в минуту, но пропустила неключевые колонки, пыталась вызвать неподтверждённый Индексировать() и заменяла пустые значения источника с приоритетом значениями другой таблицы. Сам текст запроса требовал библиотечных знаний, однако в этой сессии OpenCode не сделал ни одного вызова LEM. Это объясняет часть результата; сравнивать его с Gemma 31B из первого опыта особенно некорректно.
Большая модель тоже не спасла. Qwen 122B построила длинный ответ, но потеряла совпадающие неключевые колонки, вместо явно названного Ключ выбирала первую общую колонку и использовала несуществующие методы Структура. RTX Qwen 35B обращалась к LEM, но оставила в коде выдуманные Состав, Найти, ОписаниеТипов() и конструкцию Выброс Ошибка(...). У Qwen3 Coder Next проверка нашла 14 ошибок разбора: в том числе идентификаторы с пробелами и методы, которых нет у коллекций 1С. Доступ к инструменту не компенсировал ошибку в языке.
Flash-Next была ближе к правильному алгоритму. Она предложила сопоставление ключей через Соответствие, чтобы не искать каждую строку полным перебором. Но записала неверный цикл, передала шесть аргументов в Колонки.Добавить() и сослалась на вымышленные свойства колонок. Хорошая асимптотика в комментарии не делает листинг исполняемым.
Qwen3.8 27B лучше остальных удержала контракт. Она проверила входные таблицы, динамически собрала колонки, явно показала приоритет и попыталась построить индекс по ключу. Из LEM агент нашёл стандарт на копирование строк и уточнил у Синтакс-помощника, что у колонки таблицы свойство называется ТипЗначения, а не Тип. Готовой общей процедуры БСП для этого случая он не нашёл. Победа получилась не абсолютной: в финальном коде оставлены платформенные ошибки, о которых подробно ниже.
Полный код победившего ответа
Ниже архивный ответ Qwen3.8 27B из OpenCode, включая его комментарии, имена и ошибки. Мы не редактировали BSL ради красоты или исправления. Это материал для разбора, не готовая обработка. После листинга перечислены конкретные блокеры.
// Объединяет две таблицы значений в одну таблицу по колонке-ключу.
// В состав результата входят все колонки приоритетной таблицы и уникальные
// (отсутствующие в приоритетной) колонки дополнительной таблицы.
// Значения колонок, присутствующих в обеих таблицах, для строк с одинаковым
// значением ключа берутся только из приоритетной таблицы. Строки, ключ которых
// есть только в дополнительной таблице, добавляются в результат с пустыми
// значениями совпадающих колонок.
//
// Параметры:
// ТаблицаСПриоритетом - ТаблицаЗначений - таблица, из которой берутся значения
// колонок, присутствующих в обеих таблицах. Должна содержать колонку-ключ.
// ТаблицаДополнительная - ТаблицаЗначений - таблица, уникальные колонки и строки
// которой добавляются в результат. Должна содержать колонку-ключ.
// ИмяКолонкиКлюча - Строка - имя колонки, по которой сопоставляются строки
// таблиц. По умолчанию "Ключ".
//
// Возвращаемое значение:
// ТаблицаЗначений - объединенная таблица. Порядок строк: сначала строки
// приоритетной таблицы (в исходном порядке), затем строки, присутствующие
// только в дополнительной таблице.
//
// Пример:
// Результат = ОбъединитьТаблицыЗначений(ОсновнаяТаблица, ДополнительнаяТаблица);
//
Функция ОбъединитьТаблицыЗначений(ТаблицаСПриоритетом, ТаблицаДополнительная, ИмяКолонкиКлюча = "Ключ") Экспорт
Если ТипЗнч(ТаблицаСПриоритетом) <> Тип("ТаблицаЗначений")
Или ТипЗнч(ТаблицаДополнительная) <> Тип("ТаблицаЗначений") Тогда
ВызватьИсключение(
НСтр("ru = 'Оба параметра должны иметь тип ТаблицаЗначений.'"),
КатегорияОшибки.ОшибкаКонфигурации);
КонецЕсли;
Если ТаблицаСПриоритетом.Колонки.Найти(ИмяКолонкиКлюча) = Неопределено Тогда
ТекстОшибки = НСтр("ru = 'В приоритетной таблице отсутствует колонка-ключ: '")
+ ИмяКолонкиКлюча;
ВызватьИсключение(ТекстОшибки, КатегорияОшибки.ОшибкаКонфигурации);
КонецЕсли;
Если ТаблицаДополнительная.Колонки.Найти(ИмяКолонкиКлюча) = Неопределено Тогда
ТекстОшибки = НСтр("ru = 'В дополнительной таблице отсутствует колонка-ключ: '")
+ ИмяКолонкиКлюча;
ВызватьИсключение(ТекстОшибки, КатегорияОшибки.ОшибкаКонфигурации);
КонецЕсли;
ПроверитьУникальностьКлючей(ТаблицаСПриоритетом, ИмяКолонкиКлюча);
ПроверитьУникальностьКлючей(ТаблицаДополнительная, ИмяКолонкиКлюча);
Результат = Новый ТаблицаЗначений;
// Состав результата: колонки приоритетной таблицы в исходном порядке,
// затем уникальные колонки дополнительной таблицы.
Для Каждого Колонка Из ТаблицаСПриоритетом.Колонки Цикл
Результат.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения);
КонецЦикла;
Для Каждого Колонка Из ТаблицаДополнительная.Колонки Цикл
Если Результат.Колонки.Найти(Колонка.Имя) = Неопределено Тогда
Результат.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения);
КонецЕсли;
КонецЦикла;
// Индекс по ключу: Найти() по одной колонке использует индекс.
Результат.Индексы.Добавить(ИмяКолонкиКлюча);
// Имена уникальных колонок дополнительной таблицы для точечного заполнения.
СписокУникальныхКолонок = "";
Для Каждого Колонка Из ТаблицаДополнительная.Колонки Цикл
Если Колонка.Имя <> ИмяКолонкиКлюча
И ТаблицаСПриоритетом.Колонки.Найти(Колонка.Имя) = Неопределено Тогда
Если СписокУникальныхКолонок <> "" Тогда
СписокУникальныхКолонок = СписокУникальныхКолонок + ",";
КонецЕсли;
СписокУникальныхКолонок = СписокУникальныхКолонок + Колонка.Имя;
КонецЕсли;
КонецЦикла;
// Строки приоритетной таблицы: значения всех ее колонок берутся только из нее.
Для Каждого СтрокаПриоритета Из ТаблицаСПриоритетом Цикл
СтрокаРезультата = Результат.Строки.Добавить();
ЗаполнитьЗначенияСвойств(СтрокаРезультата, СтрокаПриоритета);
КонецЦикла;
// Строки дополнительной таблицы: заполняем только ее уникальные колонки.
Для Каждого СтрокаДополнения Из ТаблицаДополнительная Цикл
СтрокаРезультата = Результат.Строки.Найти(СтрокаДополнения[ИмяКолонкиКлюча]);
Если СтрокаРезультата = Неопределено Тогда
СтрокаРезультата = Результат.Строки.Добавить();
СтрокаРезультата[ИмяКолонкиКлюча] = СтрокаДополнения[ИмяКолонкиКлюча];
КонецЕсли;
Если СписокУникальныхКолонок <> "" Тогда
ЗаполнитьЗначенияСвойств(СтрокаРезультата, СтрокаДополнения, СписокУникальныхКолонок);
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
// Проверяет, что значения колонки-ключа не повторяются в таблице.
//
// Параметры:
// Таблица - ТаблицаЗначений - проверяемая таблица.
// ИмяКолонкиКлюча - Строка - имя проверяемой колонки.
//
Процедура ПроверитьУникальностьКлючей(Таблица, ИмяКолонкиКлюча)
МножествоКлючей = Новый Множество;
Для Каждого Строка Из Таблица Цикл
Если Не МножествоКлючей.Добавить(Строка[ИмяКолонкиКлюча]) Тогда
ТекстОшибки = НСтр("ru = 'В таблице есть строки с одинаковым значением колонки-ключа: '")
+ ИмяКолонкиКлюча;
ВызватьИсключение(ТекстОшибки, КатегорияОшибки.ОшибкаКонфигурации);
КонецЕсли;
КонецЦикла;
КонецПроцедуры;
Что осталось сломанным в ответе победителя
Результат.Строки— неверный путь к строкамТаблицаЗначений. В листинге встречаютсяРезультат.Строки.Добавить()иРезультат.Строки.Найти(...). Примеры платформы 1С добавляют строку вызовомРезультат.Добавить(). Следовательно, обещанное копирование и поиск по ключу не работают в приведённой форме.Новый Множествои проверка результатаДобавить()не подтверждены для выбранной платформы. Это ломает объявленную проверку дублей. Для исправления нужен подтверждённый тип и отдельная проверка семантики ключей, например черезСоответствие; простая замена слова без тестов недостаточна.- Форма
ВызватьИсключение(...)с категорией требует отдельной сверки с синтаксисом 1С. Экспертная проверка не признала листинг готовым. Нельзя полагаться на красивый текст ошибки как на доказательство компиляции. - Политика дублей и строк, существующих только во второй таблице, придумана самим ответом. Исключение на дубль может быть хорошим договором, но его не было в исходном задании. Для вторичных ключей ответ оставляет общие неключевые колонки пустыми; это соответствует выбранной трактовке приоритета, но нуждается в явном согласовании.
- Заявление об индексированном поиске не проверено на рабочем коде. Пока
.Строки.Найти()неверен, нельзя приписывать реализации обещанную линейную или близкую к ней сложность. При исправлении надо измерить поведение на больших таблицах и проверить значения разных типов в ключе.
Этот список не означает, что нужно «подправить две строки и запускать». Правильный следующий шаг — проверить весь модуль синтаксическим анализатором и платформой 1С, затем тестами на пустые таблицы, отсутствующий ключ, совпадающие колонки, пустое приоритетное значение, дубли и разные типы ключа.
Поздний ответ Bonsai2: отдельный эпизод
7 октября на то же задание был сохранён ответ Bonsai2 27B. В нём верна идея внешнего объединения и уместен ЗаполнитьЗначенияСвойств, который стандарт 1С № 448 рекомендует для таблиц сходной структуры. Но ответ использовал неподтверждённые Новый Словарь, Поставить, Найти, Ошибка(...) и Не Неопределено(...); преобразование ключа в строку могло смешать разные типы. Поздняя экспертная оценка — около 20/100.
Хотя локальная сессия OpenCode для Bonsai2 сохранилась, её первый запрос отличался переносами строк, запуск был позже и включал несколько следующих просьб о проверке. Балл 20 приблизительный и получен не той же семибалльной оценочной процедурой. Поэтому Bonsai2 показана вне основной таблицы, а дальнейшие исправления агента не подменяют его первый ответ.
Что показал именно этот опыт
Доступ к знаниям оказался необходимым, но не достаточным. Победитель действительно нашёл релевантный стандарт, посмотрел свойства колонки и не стал выдумывать готовую функцию БСП. При этом он оставил в коде неправильные обращения к самой ТаблицаЗначений. Другие модели иногда упоминали БСП и Синтакс-помощник, не выполнив доказуемый поиск или не применив найденное. В исходной проверке ни один из семи ответов не показал реального использования готового кода БСП; простое название библиотеки в комментарии за это не засчитывалось.
Главный практический вывод здесь отличается от красивой строки рейтинга: 78 баллов — лучший черновик, а не готовая процедура. В работе разработчика такой ответ экономит время на архитектуре, но требует проверки каждого платформенного вызова и исполнения на тестовых данных. Библиотека помогает не начинать с нуля; завершают работу компилятор, тесты и человек, который подтверждает неоднозначные условия.
В следующей статье мы берём ту же задачу и сравниваем ответы OpenCode и NanoBot: четыре семейства моделей, разные результаты и ошибки, которые не видны по одному итоговому баллу.
Откуда взяты цифры и код
Источники — сборник семи листингов, сохранённые сессии OpenCode 18–19 сентября, локальная экспертная оценка по четырём критериям и протокол BSL Language Server для семи вариантов. Код выше извлечён из финального сообщения сессии Qwen3.8 27B; его контрольная сумма SHA-256 после нормализации переводов строк: ba0719be3ff663b54dd04f01ce2925dd2954e69dd542d0db2919a47d1db4f4ac. Сырые сессии и персональные настройки OpenCode не публикуются вместе со статьёй. Модель Bonsai2 и её поздний ответ сохранены отдельно.