- Регистрация
- 26.02.2024
- Сообщения
- 69
- Реакции
- 77
- Баллы
- 18
Спинтакс, который знает про данные: отчёт об одном эксперименте с ИИ Агентом
Спинтакс — старая технология дешёвого текста. В конце нулевых
Потом пришли модели и за пару лет закрыли копирайтинг как профессию: текст на любую тему за секунды. Но одну вещь модель победить не смогла — цену тысячной копии. Каждая карточка через модель стоит запрос, каждый запрос — деньги, время и очередь на проверку глазами; пять тысяч карточек — пять тысяч раз. И здесь старый спинтакс ожил, потому что задача разложилась на две половины: придумать текст — работа модели, размножить его без брака — работа шаблона. Модель пишет один шаблон, движок проверяет его и рендерит остальное без сети.
Условие одно — шаблонизация должна быть правильной. Плоский
1. Вот что получается
Шаблон карточки товара (см. архив в атаче), обычный спин, каким его пишут все:
Четвёртый и пятый прогоны из шести (
Сказуемое не согласовано с подлежащим, число — с существительным. Это не опечатка в шаблоне: каждая из шести строк допустима по синтаксису, и на витрине окажутся все шесть.
2. Почему так
Родной спинтакс ZennoPoster — это
Различие между карточками должно нести данные — цену, остаток, скидку, — а не одни синонимы: уберите подставленные значения, и если тексты схлопнулись в один, различались только данные; если остались разными — различие держится на обёртке. Карточке нужно и то и другое, и ниже — конструкции для обоих.
Откуда взялся язык, который это умеет
Честно скажу сразу: ничего особенного здесь не придумано. Синтаксис, о котором пойдёт речь, задала настольная программа Generating The Web — GTW, Windows-приложение, первый выпуск около 2009 года, писалось для вебмастеров, которым нужно было много текста из структурированных шаблонов. Уже у GTW были перечисления в фигурных скобках, перестановки в квадратных с настраиваемыми разделителями, переменные через
Семья движков spintax взяла этот синтаксис как стандарт и расширила: добавила то, чего в GTW не было, — условие по данным и согласование с числом, — и убрала то, что жило только внутри настольного приложения. Сегодня один и тот же язык читают пять независимых реализаций — JavaScript, PHP, Python, Object Pascal и .NET, — и держит их вместе не общий код, а общий золотой корпус из 258 проверочных случаев: шаблон, который прошёл в одном движке, обязан дать то же в остальных. Что это за язык и зачем он нужен, коротко: https://ru.spintax.net/what-is-spintax/; все пять движков и как их ставить: https://ru.spintax.net/spintax-engines/.
Пятый движок, под .NET, появился ради ZennoPoster. Ещё до этой статьи я открыл на этом форуме тему «Предложение: поддержать открытый расширенный синтаксис spintax» и предложил команде ZennoLab поддержать его нативно: плоский
Пока это предложение, синтаксис доступен как плагин — о нём и статья.
3. Семь конструкций, которых нет
Справочник по всему языку — https://ru.spintax.net/docs/syntax/. Ниже — по одному примеру на конструкцию и ровно то, что ломается без неё; каждый пример — реальный рендер.
Как переменные живут в шаблоне, откуда берутся значения и что перебивает что — https://ru.spintax.net/docs/variables/.
Второй прогон:
Четыре прогона, ни одного рассогласования:
Прогоны по значениям 1, 2, 5, 11, 21:
Прогоны:
Экранирование значений (
Если то же значение подклеить в шаблон до рендера, спин его прочитает:
Всё это можно потрогать без установки: https://ru.spintax.net/play/ рендерит шаблон в браузере на эталонном движке семьи, и демо-шаблон из архива (
4. Арифметика
Теперь о выгоде. Пять тысяч карточек через кубик «ИИ Агент» — пять тысяч запросов к модели, по одному на карточку, каждый со своим ответом и своей очередью на проверку глазами. Через шаблон — один запрос: модель пишет шаблон, движок его проверяет и рендерит пять тысяч раз без сети и без случайностей — один seed, один текст.
Сколько весит этот один запрос — измерено на демо-брифе («карточка товара для витрины: название, цена, остаток, скидка»): кубик 10 из шаблона 1 (
Сколько разных карточек даёт демо-шаблон — посчитано движком точным обходом дерева, не выборкой: 124 416 разных текстов для одного товара со скидкой, 62 208 — без скидки, 1 866 240 — в сумме по двадцати строкам демо-таблицы (у шаблона все альтернативы разные, так что пути выбора и разные тексты — одно число). Откуда каждое из них берётся, по множителям, — в
Ёмкость не отвечает на вопрос, который задаёт себе владелец витрины: сколько из пяти тысяч карточек одного товара совпадут. Это измерено прогоном, не выведено: тот же шаблон, та же строка со скидкой, seed
И один замер про совместимость, чтобы не было иллюзий: шаблон в расширенном синтаксисе нельзя отдать родному макросу.
На ZennoPoster Pro 7.9.1.0 кубик из архива (
Это не недостаток родного спина — он и не обещал их понимать. Это правило: шаблон пишется либо под родной спин, либо под движок, и во втором случае рендерит его движок. В обратную сторону граница проходима: плоский
5. Петля
Так выглядит эксперимент целиком. Шаблон 1 из архива (
Так это прошло на ZennoPoster Pro 7.9.1.0 с
Где эта петля кончается. Всё выше измерено на карточке: короткий бриф, две-три фразы, четыре переменные. На таком объёме модель пишет шаблон с нуля и попадает с первой попытки — это и видно по пяти прогонам. Длинные структурные тексты я так не гонял и цифр по ним не привожу; порядок, с которого стоит начинать там, обратный: сначала один чистый текст — написанный или готовый, — а модели поручить только разметку: заменить слова на
Круг починки пришлось проверять, сломав шаблон руками — модель сама не ошибалась. Лишняя
Единственный сбой за день был наш, не модели: значения, набранные в панели переменных, ProjectMaker сбросил при запуске «С начала», и модель получила промпт без брифа — написала общий лендинг про «рутину». Отсюда форма «Входные настройки»: её значения читаются при каждом старте.
Два родных кубика — «ИИ Агент» и «C# код» — и одна dll. Шаблон 2 (
6. Грабли
Четыре грабли, на которые я наступил, — и по ним видно, что это запускалось, а не описано по документации.
И одна грабля не про спин, а про сборку: dll под
7. Перед боевым запуском
Шаблон 2 не привязан к шаблону 1:
Движок: https://github.com/investblog/spintax-dotnet (NuGet
Ключи ИИ-сервисов лежат в настройках программы (
Спинтакс — старая технология дешёвого текста. В конце нулевых
{a|b} крутили многие, кому нужно было много страниц почти даром: одна заготовка, тысяча вариантов, ноль рублей за штуку. Потом его забыли — не потому, что он перестал работать, а потому, что тексты из него читались как тексты из него: слова перемешаны, смысл на месте, а согласование и цифры — как повезёт. Ремесло ушло к копирайтерам.Потом пришли модели и за пару лет закрыли копирайтинг как профессию: текст на любую тему за секунды. Но одну вещь модель победить не смогла — цену тысячной копии. Каждая карточка через модель стоит запрос, каждый запрос — деньги, время и очередь на проверку глазами; пять тысяч карточек — пять тысяч раз. И здесь старый спинтакс ожил, потому что задача разложилась на две половины: придумать текст — работа модели, размножить его без брака — работа шаблона. Модель пишет один шаблон, движок проверяет его и рендерит остальное без сети.
Условие одно — шаблонизация должна быть правильной. Плоский
{a|b} делает сегодня ту же ошибку, что и в 2009-м: он не знает про данные. Ниже — что я получил на ZennoPoster Pro 7.9.1.0, когда провёл через кубик «ИИ Агент» самый обычный сценарий, пять тысяч карточек товара для витрины, и рендерил их языком шаблонов, который семья открытых движков spintax довела до пяти сред. Всё нужное — dll, оба проекта, промпты, таблица — в архиве у этого поста, начинать с README.txt; движок открыт: https://github.com/investblog/spintax-dotnet, NuGet Spintax.Core, лицензия MIT. Все числа сняты с прогонов и тестов, не с потолка.1. Вот что получается
Шаблон карточки товара (см. архив в атаче), обычный спин, каким его пишут все:
{Наша платформа|Наши инструменты} {создана|созданы} для {магазинов|витрин}. На складе %in_stock% единиц.Четвёртый и пятый прогоны из шести (
in_stock = 1):Наша платформа созданы для витрин. На складе 1 единиц.Наши инструменты созданы для витрин. На складе 1 единиц.Сказуемое не согласовано с подлежащим, число — с существительным. Это не опечатка в шаблоне: каждая из шести строк допустима по синтаксису, и на витрине окажутся все шесть.
2. Почему так
{a|b} выбирает вариант случайно и ничего не знает ни о соседней скобке, ни о значении in_stock. Согласование — это связь между двумя местами в тексте, а число единиц — связь между текстом и данными. У {a|b} нет ни того, ни другого. Отсюда два типа брака: текст, который спорит сам с собой, и текст, который спорит с таблицей.Родной спинтакс ZennoPoster — это
{a|b}, вложенность и перестановка [+sep+a|b|c]. Он делает ровно то, для чего сделан; претензия не к нему, а к самому {a|b}: в этой записи нет способа сказать «эти два слова согласованы» или «эта форма зависит от числа». Для случайной подписи к посту такой способ и не нужен. Для пяти тысяч карточек с ценой и остатком — нужен, и дальше речь о конструкциях, которые его дают.Различие между карточками должно нести данные — цену, остаток, скидку, — а не одни синонимы: уберите подставленные значения, и если тексты схлопнулись в один, различались только данные; если остались разными — различие держится на обёртке. Карточке нужно и то и другое, и ниже — конструкции для обоих.
Откуда взялся язык, который это умеет
Честно скажу сразу: ничего особенного здесь не придумано. Синтаксис, о котором пойдёт речь, задала настольная программа Generating The Web — GTW, Windows-приложение, первый выпуск около 2009 года, писалось для вебмастеров, которым нужно было много текста из структурированных шаблонов. Уже у GTW были перечисления в фигурных скобках, перестановки в квадратных с настраиваемыми разделителями, переменные через
#set, сборка шаблона из кусков через #include и многоступенчатая постобработка текста после рендера. Программа дожила до версии 2.7 в русской редакции, сайт generating-the-web.com больше не работает, а синтаксис остался: его история и оригинальные установщики сохранены на https://ru.spintax.net/spintax-editor/.Семья движков spintax взяла этот синтаксис как стандарт и расширила: добавила то, чего в GTW не было, — условие по данным и согласование с числом, — и убрала то, что жило только внутри настольного приложения. Сегодня один и тот же язык читают пять независимых реализаций — JavaScript, PHP, Python, Object Pascal и .NET, — и держит их вместе не общий код, а общий золотой корпус из 258 проверочных случаев: шаблон, который прошёл в одном движке, обязан дать то же в остальных. Что это за язык и зачем он нужен, коротко: https://ru.spintax.net/what-is-spintax/; все пять движков и как их ставить: https://ru.spintax.net/spintax-engines/.
Пятый движок, под .NET, появился ради ZennoPoster. Ещё до этой статьи я открыл на этом форуме тему «Предложение: поддержать открытый расширенный синтаксис spintax» и предложил команде ZennoLab поддержать его нативно: плоский
{a|b} с вложенностью движок читает как есть, а всё остальное добавляет сверху; у перестановки, правда, другая запись границ и разделителя — об этом в п. 3.Пока это предложение, синтаксис доступен как плагин — о нём и статья.
3. Семь конструкций, которых нет
Справочник по всему языку — https://ru.spintax.net/docs/syntax/. Ниже — по одному примеру на конструкцию и ровно то, что ломается без неё; каждый пример — реальный рендер.
%VAR% — переменная внутри шаблона. Без неё значение из таблицы приходится склеивать со спином в коде, до рендера — и тогда значение само становится частью спина (см. п. 7).{Купите|Закажите} %product% {сегодня|сейчас}.Как переменные живут в шаблоне, откуда берутся значения и что перебивает что — https://ru.spintax.net/docs/variables/.
#def — один бросок на документ. #set бросает кубик при каждом упоминании, #def — один раз. Шаблон:#set %adj% = {надёжный|лёгкий|тихий}%adj% %product%. Почему %adj%? Потому что %adj%.Второй прогон:
Надёжный Ferra Nova. Почему тихий? Потому что лёгкий. С #def вместо #set все три прогона дают одно прилагательное во всех трёх местах. Тем же способом связывается согласование из первого экрана — фраза целиком в одной альтернативе:#def %who% = {Наша платформа создана|Наши инструменты созданы}%who% для {магазинов|витрин}.Четыре прогона, ни одного рассогласования:
Наши инструменты созданы для магазинов., Наши инструменты созданы для витрин.{plural} — форма по числу.На складе {plural %in_stock%: %in_stock% единица|%in_stock% единицы|%in_stock% единиц}.Прогоны по значениям 1, 2, 5, 11, 21:
1 единица, 2 единицы, 5 единиц, 11 единиц, 21 единица. Без конструкции — На складе 1 единиц., На складе 21 единиц., как в первом экране. Правило трёх форм — для ru, uk, be, sr, hr, bs; остальные локали — две формы. Справочник конструкции — https://ru.spintax.net/docs/plural-spintax/; на какие обходные пути идут авторы, когда у движка плюрала нет, — https://ru.spintax.net/russian-spintax-plurals/.{?VAR?then|else} — условие по данным. В демо-шаблоне:{На складе|В наличии|Сейчас есть} %in_stock% {plural %in_stock%: штука|штуки|штук}{?has_discount?, {сегодня|на этой неделе} со скидкой|}.has_discount = yes → На складе 21 штука, на этой неделе со скидкой.; пусто → На складе 3 штуки. Без условия фраза про скидку либо есть у всех, либо ни у кого — или шаблонов становится два. Три формы условия и таблица, что считается «пусто»: https://ru.spintax.net/docs/conditional-spintax/.[<minsize=…;maxsize=…>a|b|c|d] — перестановка с границами.Доставим [<minsize=2;maxsize=3;sep=", ";lastsep=" и ">за два дня|с проверкой|в удобное время|до двери].Прогоны:
Доставим в удобное время, до двери и за два дня., Доставим за два дня и с проверкой. Из четырёх пунктов это 36 разных хвостов (12 пар и 24 тройки — движок считает точно, не выборкой). Родная перестановка [+sep+a|b|c|d] перечисляет все четыре пункта всегда и в одном из 24 порядков; выбрать два из четырёх она не умеет. Приёмы с перестановками — заголовки и перечни: https://ru.spintax.net/docs/permutations/.#include — общий фрагмент в нескольких шаблонах. Подвал, юридическая строка, блок доставки — один файл, подключённый в десять шаблонов. Без него фрагмент копируется в десять мест и правится в девяти. В сборке для ZennoPoster резолвер пока выключен: строка #include остаётся в выводе как есть, а шаблон живёт одним файлом — это записано в ограничениях.Экранирование значений (
neutralize). Всё, что приходит из таблицы или со страницы, движок по умолчанию считает текстом, не спином. Значение product = Aurix {Mini|Maxi} 50% off:Закажите Aurix {Mini|Maxi} 50% off сегодня.Если то же значение подклеить в шаблон до рендера, спин его прочитает:
Закажите Aurix Mini 50% off сегодня. — и часть карточек будет про другой товар.Всё это можно потрогать без установки: https://ru.spintax.net/play/ рендерит шаблон в браузере на эталонном движке семьи, и демо-шаблон из архива (
demo/card-ru.spintax) туда вставляется как есть.4. Арифметика
Теперь о выгоде. Пять тысяч карточек через кубик «ИИ Агент» — пять тысяч запросов к модели, по одному на карточку, каждый со своим ответом и своей очередью на проверку глазами. Через шаблон — один запрос: модель пишет шаблон, движок его проверяет и рендерит пять тысяч раз без сети и без случайностей — один seed, один текст.
Сколько весит этот один запрос — измерено на демо-брифе («карточка товара для витрины: название, цена, остаток, скидка»): кубик 10 из шаблона 1 (
template-1-author/10-build-authoring-prompt.cs) собирает промпт на 7 320 символов, из них 6 939 — системная часть с правилами синтаксиса (она одна на все брифы, лежит в template-1-author/prompts/authoring.system.ru.txt) и 379 — сам бриф с перечнем разрешённых переменных. Ответ модели — шаблон размером с demo/card-ru.spintax: 422 символа. Тарифы у пяти встроенных провайдеров ZennoPoster (OpenAI, Anthropic, DeepSeek, Gemini, Perplexity) разные и меняются, поэтому в рублях считайте сами: один запрос на ~7 тысяч символов против пяти тысяч запросов на бриф каждый. Порядок величин не зависит от провайдера.Сколько разных карточек даёт демо-шаблон — посчитано движком точным обходом дерева, не выборкой: 124 416 разных текстов для одного товара со скидкой, 62 208 — без скидки, 1 866 240 — в сумме по двадцати строкам демо-таблицы (у шаблона все альтернативы разные, так что пути выбора и разные тексты — одно число). Откуда каждое из них берётся, по множителям, — в
demo/README.md.Ёмкость не отвечает на вопрос, который задаёт себе владелец витрины: сколько из пяти тысяч карточек одного товара совпадут. Это измерено прогоном, не выведено: тот же шаблон, та же строка со скидкой, seed
row-0…row-(N−1), совпадение — побайтовое. На 500 карточках повторов нет. На 5 000 — 4 882 различных, и у 236 есть хотя бы один двойник. На 20 000 — 18 401 различных, с двойником 3 103. Для 124 416 равновероятных текстов прикидка N·(1−e^(−N/C)) дала бы 197 из 5 000; на деле больше — в том числе потому, что пути выбора не равновероятны — перестановка minsize=2;maxsize=3 сначала бросает размер, и 12 пар делят вероятность с 24 тройками. Поэтому близнецов считают рендером, а не по ёмкости; три числа выше прибиты тестом в репозитории.И один замер про совместимость, чтобы не было иллюзий: шаблон в расширенном синтаксисе нельзя отдать родному макросу.
На ZennoPoster Pro 7.9.1.0 кубик из архива (
demo/native-spin-count.cs) прогнал демо-шаблон через Macros.TextProcessing.Spintax 10 000 раз в каждом из двух его режимов: родной спин честно перемешал {a|b} — 4 287 различных строк в обычном режиме, 9 568 в расширенном (extendedSyntax), — а #def, %переменная%, plural и {? напечатал как есть, во всех 10 000 строках.Это не недостаток родного спина — он и не обещал их понимать. Это правило: шаблон пишется либо под родной спин, либо под движок, и во втором случае рендерит его движок. В обратную сторону граница проходима: плоский
{a|b} с вложенностью движок читает как есть, так что старые шаблоны переезжают без правок.5. Петля
Так выглядит эксперимент целиком. Шаблон 1 из архива (
template-1-author/template-1-author.zp) — семь кубиков, схема 10 → 20 → 30 → 50 → 60 → 70, с ответвлением 30 → 40 → 20 на один круг. Каждый файл NN-*.cs в той же папке — тело одного кубика «C# код» целиком, вставляется как есть. Бриф, язык, разрешённые переменные и канал клиент задаёт в форме «Входные настройки» проекта — той, что открывается двойным кликом по проекту в ZennoPoster.- C# код (
10-build-authoring-prompt.cs) собирает промпт: системная часть из файла + бриф + список разрешённых%переменных%+ канал. - ИИ Агент пишет шаблон («Текст запроса» =
{-Variable.prompt-}, температура 0.2–0.3, лимит токенов поднять с 400 — по умолчанию он режет карточку). - C# код (
30-clean-and-validate.cs) чистит ответ от ограждений кода, кавычек и префиксов вроде «Шаблон:» и зовётValidate. Валиден — зелёная ветка. Нет —return null, красная ветка. - На красной ветке C# код (
40-build-repair-prompt.cs) собирает промпт починки: шаблон + диагностики со строкой и колонкой (error 1:1 [bracket.unclosed] Unclosed '{'.,error 1:56 [plural.arity] expected 3 forms, got 2) — и возвращает управление ИИ Агенту. Один круг, не цикл: не починил со второго раза — стоп и лог. RenderMany(50-render-many.cs) — до 20 разных вариантов, у каждого свой seed видаимя:i(тот, что вернулAttemptSeed: повторы отбрасываются, номера идут с пропусками); по seed любой из них воспроизводится побайтово.Lint(60-lint.cs) — 50 документов, доля чистых, худшие находки первыми. На демо-шаблоне линт поймал{руб.|рублей}.→руб..в 24 документах из 50 (строка A-001, seedA-001; прибито тестом) до того, как это увидел человек. Почему дефекты живут в сочетании веток и как их чинить по рецепту, а не переписыванием: https://ru.spintax.net/docs/repairing-spintax/.- Файл
.spintax(70-save-template.cs) — в папку проекта.
Так это прошло на ZennoPoster Pro 7.9.1.0 с
claude-opus-4-5-20251101 22–23 августа — пять чистых прогонов, и в каждом шаблон валиден с первой попытки: #def, {plural} по %in_stock%, условие по %has_discount%, все четыре переменные на месте и ни одной выдуманной; 20 из 20 вариантов; линт на пятидесяти документах — 1.0, 1.0, 0.98, 1.0 и 1.0. Последний шаблон, его 20 вариантов с seed каждого и мета-файл лежат в архиве — template-1-author/out/card-ru.spintax, card-ru.variants.txt, card-ru.meta.txt; тест в репозитории перерендеривает каждый вариант по его seed и сверяет побайтово.Где эта петля кончается. Всё выше измерено на карточке: короткий бриф, две-три фразы, четыре переменные. На таком объёме модель пишет шаблон с нуля и попадает с первой попытки — это и видно по пяти прогонам. Длинные структурные тексты я так не гонял и цифр по ним не привожу; порядок, с которого стоит начинать там, обратный: сначала один чистый текст — написанный или готовый, — а модели поручить только разметку: заменить слова на
{а|б}, повторяющееся вынести в #def, счётное — в {plural}. Придумывать при этом нечего, а проверка та же — Validate, RenderMany, Lint. Этот подход в семье называют обратным авторингом, и с него начинается вся серия руководств: https://ru.spintax.net/docs/authoring-mindset/. Шаблон 1 сделан под карточку. Для длинного текста есть отдельный проект и вторая статья — руководство без кода, только практика, по-русски и по-английски: https://zenno.club/discussion/threa...adok-obratnyi-avtoring-na-zennoposter.134262/.
Круг починки пришлось проверять, сломав шаблон руками — модель сама не ошибалась. Лишняя
{ перед Validate: кубик 30 ушёл по красной ветке с bracket.unclosed, кубик 40 собрал промпт починки (его системная часть — template-1-author/prompts/repair.system.ru.txt), модель вернула исправленный шаблон за четыре секунды, дальше всё зелёное.Единственный сбой за день был наш, не модели: значения, набранные в панели переменных, ProjectMaker сбросил при запуске «С начала», и модель получила промпт без брифа — написала общий лендинг про «рутину». Отсюда форма «Входные настройки»: её значения читаются при каждом старте.
Два родных кубика — «ИИ Агент» и «C# код» — и одна dll. Шаблон 2 (
template-2-production/) — прод: таблица товаров (products.csv, 20 строк вымышленных брендов), на каждую строку Protect → Render(seed = артикул:витрина) → Restore и файл; весь проход — один кубик 10-render-table.cs, цикл из кубиков не нужен. Без модели вообще. Двадцать файлов, которые этот кубик записал на ZennoPoster Pro 7.9.1.0, лежат в архиве — template-2-production/out/site-a/; тест рендерит те же строки движком и сравнивает побайтово; повторный прогон на ZennoPoster записал те же байты — git не увидел изменений. Это и значит «у вас получится то же самое»: прогоните шаблон 2 у себя с той же таблицей, тем же шаблоном, той же dll и тем же именем витрины (site-a) — файлы совпадут с моими до байта.6. Грабли
Четыре грабли, на которые я наступил, — и по ним видно, что это запускалось, а не описано по документации.
{-Variable.x-}съедается парсером. Для движка{-Variable.sku-}— перечисление из одного варианта-Variable.sku-. ШаблонАртикул {-Variable.sku-}: {наличие|остаток} на складе — {-Variable.in_stock-} единиц.валиден и рендерится вАртикул -Variable.sku-: наличие на складе — -Variable. In_stock- единиц.— типограф ещё и заглавную поставил после точки. Поэтому в кубиках стоитProtect/Restore: макросы заменяются маркерами на время рендера и возвращаются побайтово; если рендер маркер повредил — исключение, а не испорченный файл. Шаблон с макросом внутри, чтобы повторить, —demo/card-macro.spintax.- Шаблон из файла макросы получает — и это хуже, чем если бы не получал. Я ждал обратного и проверил: в файл
.spintaxвписана строка{-Variable.sku-}, кубик «Чтение файла» → переменная → кубик «C# код» возвращает её содержимое. Последняя строка вывода —A-001: макрос раскрылся по дороге, в тексте шаблона его уже нет. Значит, значение переменной попало в шаблон как текст шаблона, без щита — и если в нём есть|или{, парсер прочитает его как спин (п. 3). Поэтому значения в шаблон идут только через%переменные%и словарь вRender(движок их экранирует), а{-Variable.x-}в файле шаблона — не способ подставить данные. Читает файл сам кубик «C# код» (File.ReadAllText) — макросы остаются текстом, и тогда их прячетProtect(п. 1). - Спарсенное значение с
|— это выбор для любого спина, в который его подклеили.Macros.TextProcessing.Spintax("Закажите " + product + " сегодня.", false)приproduct=Aurix {Mini|Maxi} 50% offдаётЗакажите Aurix Maxi 50% off сегодня.; сextendedSyntax: true—Закажите Aurix Mini 50% off сегодня.Название товара стало выбором в обоих режимах — и так поступил бы любой спин, потому что склейка строк не отличает данные от разметки. У движка то же значение печатается как есть не потому, что он умнее, а потому, что значения идут отдельным словарём, а не в тексте шаблона (п. 3, экранирование). СтрокаA-007вproducts.csvнесёт{,|и%в названии ровно для этой проверки. - Дефект живёт на стыке, и ни в одном блоке его не видно.
{руб.|рублей}.— оба варианта верны, точка после скобки верна, а рендер даётруб..в 24 документах из 50: брак возникает только в сочетании. То же с двумя соседними группами, которые выпадают на один корень. ПоэтомуLintгоняется по рендерам, а не по шаблону: повтор корня в окне шести слов, «который» не в том роде, мусор пунктуации — всё это существует только в готовом тексте.
И одна грабля не про спин, а про сборку: dll под
netstandard2.0 в ProjectMaker загружается, Spintax.Net.Version отвечает, а кубик с Dictionary<,> из её API не компилируется — CS0012: add a reference to assembly 'netstandard'. У компилятора кубика нет ссылки на netstandard.dll. В ZennoPoster уходит сборка под net472 — она и лежит в lib/ архива; netstandard2.0 остаётся для других хостов.7. Перед боевым запуском
- Проверено на ZennoPoster Pro 7.9.1.0 — это и есть поддерживаемая база. Шаблону 1 нужен кубик «ИИ Агент»: в релизах 7.8.x он не упомянут, есть ли он там — не проверял; рассчитывайте на 7.9 и новее. Шаблону 2 хватит «C# код», таблицы и входных настроек.
- Две dll из
lib/лежат вProgs\ExternalAssemblies, в проекте одна ссылка из GAC — наSpintax.Zenno.dll; движок подтянулся изExternalAssemblies(без копии там — не проверено); в каждом проекте своя. - Бриф, язык, переменные и папки заданы в форме «Входные настройки», не в панели переменных — панель сбрасывается при запуске.
Validateзелёный, и список колонок таблицы передан как известные переменные — иначе каждая%колонка%даст предупреждениеvariable.undefined.Lintна 50 документах посмотрен глазами, худшие находки — сверху. Глаза ищут то, чего линт не видит по построению: смысловую несогласованность между слотами, когда каждый вариант верен сам по себе, а сочетание — нет; для этого читают несколько рендеров, не первый.- Число вариантов от
RenderManyсверено с запрошенным: бедный шаблон честно возвращает меньше, а не крутится до упора. - Шаблон содержит макросы ZennoPoster → рендер обёрнут в
Protect/Restore. - Seed = ключ строки + имя витрины: один и тот же текст на любой машине и в любом потоке; другая витрина — другой независимый розыгрыш (совпасть может, как любые два броска).
- Лимит токенов в «ИИ Агент» поднят; температура 0.2–0.3.
- Перед заменой dll закрыты ProjectMaker и ZennoPoster — сборка кешируется;
Spintax.Net.Buildдо и после замены различается.
Шаблон 2 не привязан к шаблону 1:
.spintax можно написать и без ИИ Агента — руками или в Spintax Studio (редактор для Windows из Microsoft Store, офлайн, тот же язык и движок семьи, с валидацией и предпросмотром) — положить файл рядом с проектом и указать его в форме. И это, пожалуй, главная выгода, которую я вынес из эксперимента: шаблон, написанный для ZennoPoster, — тот же файл, который читают узел n8n, плагин для WordPress и пакет для JavaScript. Переезд процесса на другую платформу — файл едет с вами без изменений.Движок: https://github.com/investblog/spintax-dotnet (NuGet
Spintax.Core; корпус семьи, тесты, CI). Приложение к статье — архив у этого поста: lib/ с двумя dll, оба проекта .zp с формами, тела кубиков, промпты по локалям, демо-таблица на 20 товаров и README.txt, с которого стоит начать. Предложение команде ZennoLab поддержать синтаксис нативно остаётся в силе — тема открыта, движок MIT, корпус общий.Ключи ИИ-сервисов лежат в настройках программы (
Редактирование → Настройки → ИИ, %AppData%\ZennoLab\Configs\), а не в проекте: скачанный шаблон ключей не содержит и запускается на ваших. Поле «Дополнительные параметры» там же — это адрес приёма запросов, а значит, свой прокси или локальная модель без выхода в интернет подключаются тем же окном.Вложения
Последнее редактирование:


