用 AI 写 spintax 模板
你只为模板向模型付一次钱。之后引擎在本地免费渲染任意数量的变体。本页讲的就是这两个事实之间的部分:给模型什么、要求什么、怎么检查返回的结果,以及每个模型都会在 spintax 里犯的错误。
最短版本
把文档给模型,把你写完的文章给模型,要求一个模板。就按这个顺序。现在通往这个模板有两条路:Spintax Studio 替你起草并做检查,或者你亲手把模型接到文档上。两条路都通向同一处——一个引擎已经校验过的模板。是要把它接进自己的系统,而不是做一次就完?提示词本身就是一个包——npm 上的 @spintax/authoring-prompt——所以流水线是安装这套规则,而不是复制它。
本页教的方法是把写好的文本交出去:这样模型是在给你已经批准的句子做标记,而不是替你写。两件事一起要——又写又标记——你得到的就是一个由“从没有人当作句子读过的句子”拼出来的模板。先把文章写好,写成一版你可以原样发布的干净文本,然后再交出去。(Studio 也接受一段光秃秃的说明、从零起草——更快,但那时你审的是模型的写作,而不只是它的标记。)
最简单的路:Spintax Studio
Spintax Studio 是这个家族的免费原生 Windows 编辑器;从 0.2.0.0 版起,它会替你起草模板——把下面那套设置和检查做掉,而不用你自己一件件拼起来。
把你手头已有的文本——商品页、一封信、一段描述——贴进去,或者写一段简短说明,然后 Generate 会把它变成一个 spintax 模板。草稿不是靠信任交付的:它到手时已经由渲染预览的同一个引擎校验过,并且落在答复框里,绝不落进你的文档。用 Insert 或 Replace 应用它,是你自己的动作。若引擎仍然发现问题,Fix 会把你的文档连同诊断——行号与列号——一起送回模型去修。
连接是你的:你的服务商、你的账号,端点需要时用你自己的密钥。我们这边没有密钥也没有服务器,应用本身不附带任何模型,而且这个功能在你打开之前一直是关闭的——它发送什么、发给谁,都写在应用的隐私政策里。完全不想连接?Copy prompt 会把同一份准备好的提示词交给你,粘到任何模型里手动运行——就是下面印着的那一份——你粘回来的东西,引擎会像对待其他一切一样校验并渲染,只是没有 Fix 的自动修复环节。
也就是说,Studio 把设置和检查收进两个按钮——文档、提示词、运行,以及“校验加修复”这一趟。它唯一不能替你做的,是读那些变体;这个判断仍归你。本页其余部分讲的是同一套方法的手动版:面向任何平台上的任何模型,以及自己起草 spintax 的 agent。
给模型什么
本站以机器可读的形式发布自己的文档,所以你不需要向任何东西解释 spintax。按你的工具选一个入口。
| 入口 | 是什么 | 什么时候用 |
|---|---|---|
/llms-full.txt |
文档核心合成一个文件:语法参考、嵌套指南和全部创作指南,一次抓取。(英文——这是有意的:一份索引只有一张地图。) | 默认选它。一次抓取,或往聊天里粘一次。 |
spintax-authoring |
同一套方法压缩成两页,为模型而不是读者而写。 | 你的 agent 支持技能,或上下文预算紧张。 |
/docs/variables.md 等 20 个 |
任何文档页的纯 Markdown 版。URL 后加 .md,或发送 Accept: text/markdown。 |
干活途中的一个具体问题。 |
https://spintax.net/mcp |
MCP 服务器:校验、渲染、分析模板的三个工具。 | 你想让模型在给你看之前先检查自己的活。 |
authoring 技能旁边还有两个。spintax-syntax 讲各个构件和每个构件里的坑;spintax-engines 讲怎样从 JavaScript、PHP 或 Python 安装并调用引擎。写模板要用的是第一个。
提示词
这是我们自己在用的提示词。它假定模型能抓取 URL;如果你的不能,把 llms-full.txt 粘在它上面,并删掉提到抓取的那两行。提示词保留英文原文——它指向的文档表面就是英文的,而且硬规则以规范措辞为准;模型输出的语言跟随你粘贴的文章,所以拿中文文章喂它,回来的就是中文模板。
You are converting a finished article into ONE spintax template.
Read the syntax and authoring rules first:
https://spintax.net/llms-full.txt
If you cannot fetch URLs, say so and I will paste the file instead.
Priorities, in this order:
1. Every rendered variant must be grammatical.
2. Every rendered variant must read like something a person wrote.
3. Variety comes last. A difference the reader would not notice
is not worth a branch.
Hard rules:
- Never put a branch boundary inside a phrase whose grammar binds:
subject and verb, preposition and object, article and noun.
- Do not repeat fixed text in both branches. Fork the smallest span
that differs.
- Use %VAR% for anything that changes per site or per product.
- Use #def, not #set, when two references to the same value must agree.
- Any number followed by a noun goes through {plural %n%: …}. Never
hand-roll the forms as {item|items}.
- Use only the syntax in the rules above. Do not borrow from ICU,
Handlebars or Jinja.
- Do not spin brand names, product names, prices, URLs or legal
wording. Vary the copy around them.
- Do not invent facts. If a slot needs a fact I did not give you,
make it a variable.
Give me back:
1. The template, in one code block.
2. The variables you introduced, with an example value for each.
3. Anything in the article you could not express in spintax, and why.
Here is the article:
[paste your article here]
它有两处是刻意为之的。
它指向规则,而不是复述规则。语法不在提示词里,也不该在。文档只有一次抓取的距离,而且始终是最新的;半年前粘进提示词的副本可不是。
它把优先级排在规则前面。放任不管,模型会优化可见的多样性,因为模板看起来就是为此而生。语法和可读性必须被明确排在它前面,否则每个槽位四个选项,输出成一锅粥。
同一个提示词,做成了包
上面那段是给聊天窗口和一双手用的。如果你要把它接进某个自动运行的东西——机器人、workflow 节点、你自己的脚本——别复制它。规范提示词已经发布:npm 上的 @spintax/authoring-prompt,MIT,与引擎同一个仓库。它就是 MCP 服务器通过 spintax_authoring_guide 交给智能体的那套规则,也是 n8n 节点里那些提示词构建器背后的同一套。
npm i @spintax/authoring-prompt @spintax/core
import { buildAuthoringPrompt, cleanModelTemplate } from '@spintax/authoring-prompt';
import { validate } from '@spintax/core';
const { systemPrompt, userPrompt, promptVersion } = buildAuthoringPrompt({
brief: 'A two-sentence welcome message for a new subscriber',
locale: 'zh',
channel: 'email',
allowedVariables: ['firstName', { name: 'product', note: 'brand name — do not inflect' }],
});
三件事是粘贴提示词给不了你的。
- 它有版本号。它构建的每个提示词都会报告
promptVersion。当你的流水线输出变了味道,这个数字能告诉你,是规则动了还是模型动了。 - 它去问引擎,而不是靠记忆。
@spintax/core作为 peer 依赖只有一个理由:提示词向引擎询问你的语言有几种复数形式,而不是自带一份会漂移的表。 - 它已经会修复那一轮。
buildRepairPrompt()把validate()的诊断变成那句「尽量少改,其余逐字节保持原样」的提示词——下面那个循环,已经替你写好了。
它刻意不属于引擎。@spintax/core 只负责渲染和校验,对模板该怎么写不持立场;立场住在这里。所以是两个包,而不是一个。
三种手动跑法
这些是 Studio 里 Generate 的手动等价物——同一份文档、同一个提示词,由你自己对着模型跑一遍。按你的工具选一个。
聊天窗口
任何大上下文窗口的模型都行。粘 llms-full.txt,粘提示词,粘你的文章。整个文档核心约 100 KB,对当前模型很从容,但不是免费的:长会话里,authoring 技能只花它的零头,覆盖同一套方法。
这里没有校验。模型没有办法运行模板,所以把它的输出当草稿,下一步拿去演练场EN。
能读文档的 agent
在 Claude Code、Cursor 或任何会浏览的 agent 里,把它指向技能,让它按需拉取单页:
Load https://spintax.net/.well-known/agent-skills/spintax-authoring/SKILL.md
and follow it. Fetch the guides it links when you need the detail.
技能里的每条链接都已指向 .md 镜像,agent 读我们的文档从来不用解析网页。
MCP 服务器
值得多花五分钟的就是这套。https://spintax.net/mcp 是一个在线 MCP 服务器,无需认证,三个工具:
validate_spintax——诊断,带严重级别、稳定的 code 和从 1 起数的行列。没有 error 就可以放心渲染。render_spintax——最多 20 个变体,seed 保证可复现,变量表当上下文。analyze_spintax——模板需要什么、包含什么:引用的变量、#set与#def定义、include、构件计数。
Claude Code 里一行搞定:
claude mcp add --transport http spintax https://spintax.net/mcp
吃配置文件的客户端:
{
"mcpServers": {
"spintax": { "type": "http", "url": "https://spintax.net/mcp" }
}
}
或者在你自己的机器上跑同一个服务器——不用账号:
{
"mcpServers": {
"spintax": { "command": "npx", "args": ["-y", "@spintax/mcp"] }
}
}
@spintax/mcp 是同样的三个工具,走 stdio,出自同一个引擎——当托管那扇门不合适时就找它。它没有大小上限(真实模板会超过 8 KB),运行时不发起网络调用(装好后即可离线,含气隙环境——而且在 Cloudflare 被封的地方,它是唯一能走的 MCP 通路,托管端点根本连不上),并且能通过 --include-root 从磁盘读取 #include 片段,这是托管服务器绝不能做的。两扇门跑同一个工具模块,所以在一扇上通过校验的,在另一扇上也通过。完整设置和“托管还是本地”的取舍:spintax MCP 服务器。
然后在提示词末尾加上:
You have the spintax MCP server. Before you show me anything:
call validate_spintax and fix every diagnostic with severity "error",
then call render_spintax with count 5 and read the variants yourself.
If a variant reads wrong, fix the template and repeat.
差别不是装饰性的。没有工具,你看到的第一版模板是模型的第一次猜测,你的审读时间花在找没闭合的花括号上。有了工具,没闭合的花括号模型自己已经找完了,你的审读时间花在文案好不好上。
托管那扇门的限制,方便你规划:每次调用模板上限 8 KB,每次渲染最多 20 个变体,#include 被禁用——它要为自己的 CPU 买单。大文档按小节组装、逐节校验,或者换用本地服务器,那边这些都没有。托管服务器的完整描述在它的 server card 里。
能抓住问题的循环
不管用哪种工具,审读都是同样三步,而被跳过的永远是第三步。
- 校验。结构性错误最便宜,而且引擎替你找:演练场EN边打字边划线,
validate_spintax把它们当数据返回。 - 渲染二十个,不是一个。六个分支的模板有几百种结果。一次渲染几乎什么都说明不了——而让你批准了坏模板的,通常恰恰是那一次渲染。
- 读它们。真的把二十个读完。检查每个条件的两种状态、每种复数形式(包括选中罕见形式的数:俄语里是 1、2、5、11、21),以及最短和最长的排列结果——分隔符和空格的毛病都在那里现形。
读出问题时,改模板而不是改变体,然后再渲染二十个。Studio 替你做机械的那一半——Generate 校验,预览渲染变体,Fix 把文档和诊断送回模型——但读还是你的事。手动时,模型接上 MCP 工具后可以自己驱动这些检查——这正是接它们的全部理由。
模型会做错什么
这些不是随机错误。它们跨模型重复出现,因为根源是模型在别处学到的东西。知道这份清单,就能把含糊的“这模板哪里不对劲”变成两分钟的核对。
注意它们几乎都不抛错。引擎有意宽容:不认识的构件丢掉花括号、以普通文字落进正文。模板校验全绿、渲染却是错的——这正是循环以“读变体”而不是以绿勾结束的原因,也是为什么 Studio 交给你、标为有效的草稿仍然需要你亲眼看一遍。“有效”意味着它通过了引擎的结构检查,而不是文案写得好。
1. 它把分支切进了绑定的语法里
最常见的失败,也是产出“错误”而不仅仅是“乏味”文本的那种。主语和它的动词、介词和它的宾语、冠词和它的名词,不能各自独立变化。
{Our platform|Our tools} {is|are} built for small teams.
→ four combinations, two of them ungrammatical
{Our platform is|Our tools are} built for small teams.
→ two combinations, both correct
详见:语法安全的同义替换。
2. 引用必须一致时,它却伸手拿 #set
#set 的值是宏:每次引用重新抽取。模型默认选它,因为它读起来像编程语言里的赋值。
#set %tool% = {Slack|Jira|Linear}
Connect %tool% in one click. %tool% syncs both ways.
→ "Connect Slack in one click. Linear syncs both ways."
#def %tool% = {Slack|Jira|Linear}
Connect %tool% in one click. %tool% syncs both ways.
→ "Connect Jira in one click. Jira syncs both ways."
详见:变量与多站点复用。
3. 它用别家库的语法写复数
模型见过的 ICU MessageFormat 远多于 spintax,这一点藏不住。它们还默认两种形式——对俄语、乌克兰语、白俄罗斯语和塞尔维亚语是错的。
We support {count, plural, one {# language} other {# languages}}.
→ "We support count, plural, one # language other # languages."
We support %n% {plural %n%: language|languages}.
→ "We support 3 languages." / "We support 1 language."
这个错误的两半表现完全不同,值得知道。借来的 ICU 是安静的错:完全没有诊断,因为不带竖线的花括号组是合法的,引擎丢掉括号、打印里面的内容。形式数量不对是响亮的错:俄语要三种形式 {plural %n%: язык|языка|языков},在俄语 locale 下只给两种会返回 plural.arity 错误,构件原样留在输出里。同一个习惯,只有一半会被替你抓住。
详见:复数一致。
4. 它发明条件语法
Handlebars 和 Jinja 的习惯会产出 {if …} 块,而引擎把它们当字面文本渲染。
We ship worldwide. {if %HasCrypto%}We also accept crypto.{/if}
→ "We ship worldwide. If 1We also accept crypto. /if"
We ship worldwide. {?HasCrypto?We also accept crypto.}
→ "We ship worldwide. We also accept crypto."
第一个渲染就是构件泄漏的样子:花括号没了,变量在没人想要的位置被代入,闭合标记成了一个词。
条件由值驱动。当选择真的是任意的时,{a|b} 才是对的,条件不是。
详见:条件 spintax。
5. 它把方括号当成了选择
[a|b|c] 是把三项全部打乱再拼接,默认空格分隔。它不是选一个。想表达“选一个”却写了方括号的模型,会在你只要一个形容词的地方产出三个。
Try our [fast|simple|affordable] tool.
→ "Try our fast simple affordable tool."
Try our {fast|simple|affordable} tool.
→ "Try our simple tool."
Try our [<minsize=2;maxsize=3;sep=", ";lastsep=" and ">fast|simple|affordable] tool.
→ "Try our simple, affordable and fast tool."
→ "Try our simple and fast tool."
详见:排列实战。
6. 它给每个从句都上变体
没有优先级时,模型把变化密度当目标,往每个短语里塞分支。输出不再像任何东西。
{Our|The} {platform|system} {helps|lets} {teams|groups} {ship|release}
{faster|quicker|sooner}.
有价值的变体是读者会注意到的那些。一段里两个放对位置的分支,胜过一句里的十二个。提示词里的优先级块就是为这个准备的。
7. 它把固定部分重复进两个分支
重复的文本就是漂移的邀请函:下次修改会改了一个分支、忘了另一个。
{Spintax (spin syntax) is|Spintax — "spin syntax" — is} a template language.
→ "Spintax" and "is" duplicated across both branches
Spintax {(spin syntax)|— "spin syntax" —} is a template language.
→ one copy of the fixed words, only the difference forked
详见:逆向创作思维。
8. 它把外部值直接丢进上下文
变量值默认是可携带标记的:包含 {、[ 或 % 的值会被引擎再次解析。数据库里的商品名、用户提交的字符串、带一个流浪花括号的标题——你的模板就渲染出了你从没写过的东西。不可信的值先过 neutralize(),再进上下文。
详见:变量与多站点复用。
9. 它写一个巨无霸模板
要一整个落地页,你就会得到一个无法审读的大块头。超过一屏就拆:条目模板渲染一个单元,小节模板组装条目,编排模板组装小节。每一块都能独立测试,MCP 服务器的 8 KB 上限也不再是上限。
详见:模板组合。
模板发布之前
- 渲染并真正读过二十个变体。
- 每个条件的两种状态都见过。
- 每种复数形式都见过,包括罕见的那一档。
- 两处引用必须一致的地方没有
#set。 - 模板引用的每个变量在渲染时都有值。
- 不可信的值已经过防护。
- 最短和最长的排列结果标点都正确。
这要花多少钱
一个模板就是与模型的一次对话,之后的渲染免费:引擎在本地运行,确定性,每个变体零 API 调用。这就是这套工作流的全部经济学论证——按模型逐一算清、带当前价格的版本在 AI 内容的真实成本里。
然后去渲染
你刚做好的模板在这个家族的每个引擎上原样运行:JavaScript 里的 @spintax/core,PHP 里的 spintax/core,Python 里的 spintax-core,.NET 里的 Spintax.Core,还有 WordPress 插件。同一套语法,同样的输出,一套共享测试语料。挑一个就去看引擎一览。