LEM.RU · 2026

Практический эксперимент · 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: Qwen3.8 27B — 78 баллов, Flash-Next — 68, остальные — от 10 до 24.
Баллы показывают качество конкретных сохранённых ответов, а не готовность к запуску. Ни один из шести листингов не принят как рабочий.

* Время — промежуток от запроса до первого законченного ответа в соответствующей сессии 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С добавляют строку вызовом Результат.Добавить(). Следовательно, обещанное копирование и поиск по ключу не работают в приведённой форме.
  2. Новый Множество и проверка результата Добавить() не подтверждены для выбранной платформы. Это ломает объявленную проверку дублей. Для исправления нужен подтверждённый тип и отдельная проверка семантики ключей, например через Соответствие; простая замена слова без тестов недостаточна.
  3. Форма ВызватьИсключение(...) с категорией требует отдельной сверки с синтаксисом 1С. Экспертная проверка не признала листинг готовым. Нельзя полагаться на красивый текст ошибки как на доказательство компиляции.
  4. Политика дублей и строк, существующих только во второй таблице, придумана самим ответом. Исключение на дубль может быть хорошим договором, но его не было в исходном задании. Для вторичных ключей ответ оставляет общие неключевые колонки пустыми; это соответствует выбранной трактовке приоритета, но нуждается в явном согласовании.
  5. Заявление об индексированном поиске не проверено на рабочем коде. Пока .Строки.Найти() неверен, нельзя приписывать реализации обещанную линейную или близкую к ней сложность. При исправлении надо измерить поведение на больших таблицах и проверить значения разных типов в ключе.

Этот список не означает, что нужно «подправить две строки и запускать». Правильный следующий шаг — проверить весь модуль синтаксическим анализатором и платформой 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 и её поздний ответ сохранены отдельно.