n8n 内容流水线实测:格式过关仍会失真
作者用 8 篇博客重复测试两套社交内容生成流程:单提示版本的 120 条结果中有 118 条不合格,其中 93 条缺少链接;加入代码校验后,77 条通过独立检查,其余转人工。人工复核仍发现已通过内容存在事实错误,说明格式校验无法代替语义审核。
AI 深度解读
把一篇博客改成五个平台的推广文案,最容易想到的做法是:把正文、链接和平台规则塞进提示词,让模型一次返回 JSON。作者用 8 篇文章重复测试,结果 120 条文案里有 118 条不合格,其中 93 条连原文链接都没带。
把规则交给代码后,格式问题确实收住了,但新的问题更隐蔽:文案里的数字全部来自原文,结论仍然可能写反。这次实验值得看,因为它把 AI 内容流水线里代码能管什么、人还得看什么,分得很具体。
JSON 能解析,为什么文案几乎全军覆没
实验环境是自托管的 n8n 1.123.84,运行在 Node 24 上,使用独立的新用户目录。模型是 Ollama 本地运行的 Qwen 1.8B,temperature 为 0.7,开启 JSON mode。
输入是 8 篇虚构的短博客,每篇约 200 词,覆盖面包店减废、Kubernetes 迁回单台 VPS、冷邮件实验、SaaS 流失率等主题。每篇包含标题、URL 和正文,大部分带有具体数字,方便观察模型会不会乱用数据。
两个版本各执行 24 次:每篇重复 3 次,每次输出 X、LinkedIn、Threads、Bluesky、Mastodon 五条文案,因此各有 120 条结果。
版本 A 只有一条生成链路:
Webhook → Config → Build Prompt → LLM (HTTP Request) → Parse → Respond
提示词已经写明链接、字数、标签、不同开头和禁止编造数字。JSON.parse 也全部成功,但内容照样出错:X 文案长达 462 字符,五个平台重复同一句开头,甚至凭空出现「流失率下降 48%」。
JSON mode 保证了结果能被解析,没有保证结果符合业务要求。
作者用独立 Python 脚本评分,检查原文 URL 是否完整出现、有无其他链接或占位符、平台长度和标签限制、前 5 个词是否重复,以及数字是否来自原文。生成工作流看不到这个检查器,避免自己给自己判及格。
把模型的任务缩小,代码负责组装和验收
版本 B 的变化不是继续加长提示词,而是拆分职责:模型写文案,代码维护规则、拼接链接、限制标签、检查结果。
下面这段代码先计算正文真正可用的字符预算,避免模型把整个平台上限都用于正文:
const RULES = {
x: { max: 280, tags: 2, shortLinks: true },
linkedin: { max: 1300, tags: 3, shortLinks: false },
threads: { max: 500, tags: 1, shortLinks: false },
bluesky: { max: 300, tags: 2, shortLinks: false },
mastodon: { max: 500, tags: 3, shortLinks: true },
};
const url = String(b.url || '').trim();
if (!/^https?:\/\/\S+$/.test(url)) throw new Error('Blog post needs a valid url');
// 各平台正文预算 = 上限 - 链接 - 为标签预留的空间。
const budget = {};
for (const [p, r] of Object.entries(RULES))
budget[p] = r.max - (r.shortLinks ? 23 : url.length) - 2 - r.tags * 16;
重点看最后一行:链接和标签先占位,剩余空间才交给模型。这里的 23 字符链接计数和每个标签预留 16 字符,是作者采用的预算规则,不能直接当成所有平台的通用实现。
生成也从一次请求五条,改为每个平台单独调用。X 从最意外的结果开头,LinkedIn 从问题切入,再用 3 到 5 个短句讲教训;Threads 用提问,Bluesky 用简短观点,Mastodon 描述作者实际做过的事。
每次只要求返回 {"text": "...", "hashtags": [...]},正文不带链接和标签。Build Prompt 输出 5 个 item,让同一个 LLM HTTP Request 节点执行 5 次。
链接由代码统一接管:
function assemble(pl, item) {
const r = P.RULES[pl];
let t = String(item.text)
.replace(/https?:\/\/\S+/g, '')
.replace(/\[(link|url)[^\]]*\]|\{(link|url)\}|<(link|url)>|\(link\)|link in bio|insert link/gi, '');
t = t.replace(/(\s*#[^\s#]+)+\s*$/u, ''); // 删除末尾的标签块
// ...将多余的行内标签转为普通词语,整理空白...
const tags = [...new Set(item.hashtags.map(h => String(h).replace(/[^\p{L}\p{N}_]/gu, '')))]
.filter(h => h.length > 1 && h.length < 25).slice(0, free);
return t + '\n\n' + P.url + (tags.length ? '\n' + tags.map(h => '#' + h).join(' ') : '');
}
关键是最后一行:真实 URL 来自输入,由程序添加。这是原文节选,依赖外部的 P、free,中间也省略了处理逻辑,不能直接当成完整函数运行。
组装后统一检查长度、过短、句子截断、重复开头、照搬标题和陌生数字,英文拼写的数字也参与检查。失败的平台只重试一次,提示词带上具体失败原因和其他平台已经使用的开头。仍然超长的结果,尝试在最后一个放得下的完整句子处截断,再次验收。
最终每条文案标记为 ready 或 needs_review,并附带 problems。
格式全部过关,为什么还不能自动发布
版本 B 最终有 77 条标记为 ready,43 条送人工。独立检查器识别出的 31 条问题文案,全部落在这 43 条里;77 条 ready 没有触犯它检查的规则。
这不是模型一次就写好了。24 次运行全部触发重试,重试和截句前,120 条里有 76 条存在问题,包括超长 39 次、重复开头 39 次、标题开头 34 次、句中截断 21 次、出现原文没有的数字 5 次。这些类别会重叠,不能直接相加。
平台差异也明显:LinkedIn、Bluesky、Threads 各有 17 至 18 条 ready,X 只有 8 条。X 扣掉链接和两个标签的预算后,正文约剩 220 字符,小模型很难同时压缩信息和改写开头。
真正让作者改变发布方案的是人工阅读。第一轮的 21 条 ready 文案里,10 条误读了原文,另有 1 条语法混乱。
例如,模型写出「月流失率从 6.1% 上升到 4.3%」。两个数字都是真的,方向却反了。另一条把选择年度订阅的新客户占比 41%,写成了流失率。还有文案把有效的冷邮件实验概括为「冷邮件浪费时间」,把增加表格列写成删除表格列。
检查数字是否出现过,无法确认数字描述的是不是同一件事。
改写质量同样没有跟着格式一起改善。版本 B 有 38 条连续复制了原文至少 12 个词,其中 21 条已经 ready;版本 A 则有 23 条。要求「用自己的话写」依然只是要求。
我的判断
我赞成把链接、长度、标签这类确定规则交给代码。漏链接从 93 次降到 0 次,就是直接的收益。相比继续往提示词里补强调句,程序组装更容易验证,也更容易定位错误。
但这组实验只能支持有限结论。样本是 8 篇虚构短文,模型只有本地 Qwen 1.8B,作者没有测试更大的模型,也没有给出真实长文、多语言内容的结果。国内平台和中文计数规则能否沿用,同样没有验证。
版本 B 还同时改变了调用拆分、开头策略、校验、重试和截句,无法单独证明哪一项贡献最大。作者认为「每个平台一个小请求」更好,有组合实验支撑,但没有单变量对照,也没报告耗时和成本。
此外,Bluesky 被要求用鲜明观点开头,而部分错误恰好是把有条件的结论写成绝对判断。这值得单独测试,目前只能算推测。
作者最终建议仍然保留人工审批:用 Slack 消息或 Google Sheets 的 approve 列收集确认,再由第二条工作流只调度已批准内容。这个取舍合理。这里的 ready 应当理解为「通过已定义规则」,不能理解为「内容属实,可以发布」。
能拿来做什么
- 搭建 n8n 或 Node 内容流水线时,把 URL、平台上限和标签配额做成配置,由程序计算预算并组装最终文本,模型只返回需要创作的部分。
- 为失败结果返回具体原因,只允许一次定向修复;再次失败就进入
needs_review,避免不断重试后把偶然通过误当成可靠。 - 在发布前展示原文和生成文案,让人工重点核对指标名称、增减方向、操作动作和结论条件。格式检查已经省下的时间,应该用于这些阅读。
- 固定一组有数字和条件限制的文章,用独立脚本验收,再抽样人工核对语义。评估模型或提示词调整时,同时记录格式错误、事实误读和直接复制,别只盯着 JSON 是否有效。