AI 回复总是套话?用代码节点把关
作者测试 n8n 评论回复流程,发现 48 条回复全部使用同类开头,单改提示词效果有限。其改进思路是让 Code 节点负责发布前判定,实验采用本地 Ollama 运行的 1.8B Qwen 模型。
AI 深度解读
48 条 AI 评论回复,48 条都用「Thank you for」开头。作者在 n8n 里搭了一套商家回评流程,发现问题不只是语气机械:模型还会违反退款政策、编造电话号码。把提示词写得更严,错误依然存在。
真正有效的改动,是让模型只写草稿,再由 JavaScript 判断是否转人工。这次实验值得看,不是因为它证明了自动回评已经可靠,而是因为它展示了:怎样把 AI 的不确定输出,接到一条有明确放行条件的业务流程里。
提示词写得很细,为什么还是拦不住错误
作者使用 self-hosted n8n 1.123.84,通过 production webhooks 触发流程。模型是 Ollama 本地运行的 1.8B Qwen,经 OpenAI-compatible /v1/chat/completions 接口调用。
这是刻意选择的弱模型:小模型更容易暴露失败模式,也符合部分自托管用户希望使用免费本地模型的需求。作者认为 gpt-4o-mini 这样的更强模型会表现更好,但没有测试,不能据此估算提升幅度。
实验对象是虚构的 Bangalore 南印度早餐店 Udupi Corner,店主署名 Ramesh。作者准备了 8 条评论,每条在每个工作流中执行两次,共跑 3 轮,因此每个版本得到 48 条草稿,并使用同一套脚本评分。原文没有完整展示这 8 条评论。
初版流程很常见:Webhook → Config → Build Prompt → HTTP Request → parse → Respond to Webhook。模型以 JSON mode 返回 sentiment、topics、reply,以及自己判断的 needs_owner_attention。提示词要求回复温暖、具体、符合品牌要求,不超过 80 个英文词,未经政策允许不得承认法律责任或提供补偿。
结果却很空泛。面对一条 2 星、抱怨 vada 湿软的评论,回复没有提 vada、Swiggy 或 chutney,放到任何商家页面都能用。
作者随后要求模型扮演店主、引用评论细节,把长度收紧到 60 词以内,并把 temperature 提高到 0.7,希望措辞更多样。下面这段提示词负责约束内容、承诺和署名:
'1. Mention one specific thing the reviewer wrote (a dish, a person, the wait, the parking).',
'2. Two to four sentences, under 60 words. Do not start with "Thank you for".',
`3. Never use these phrases: ${banned.join('; ')}.`,
`4. Never offer refunds, discounts, vouchers or free items, and never admit fault. For complaints, say you want to look into it and ask them to ${contact}.`,
`5. End with the sign-off: ${signoff}`,
重点看第 4 条。即使明确禁止退款,其中一条草稿仍让投诉 idli 里有头发的顾客联系 WhatsApp 退款,违背了「No refunds via review replies」政策。
改版后的 48 条草稿中,仍有 26 条包含提示词逐字禁止的套话。模型还输出过 [insert number]、[our profile number],甚至编造出 9876543210,并在一条 5 星回复里主动提供「special offer」。
提示词可以提高遵守规则的概率,但不能充当发布权限。
把发布条件交给代码,模型才有明确边界
作者在 LLM 调用后加入 Check Reply Code 节点。模型负责生成,代码负责检查,并输出 auto_post_ok 和 reasons。
检查分为两类。一类看草稿本身:是否包含套话、退款或责任承诺,是否出现方括号占位符、连续 7 位以上数字,长度是否少于 12 词或多于 70 词,是否仍以「Thank you for」开头。
另一类看原始评论。1–2 星评论,以及涉及中毒、生病、头发、昆虫、FSSAI、律师、警察或过敏的内容,无论草稿看起来多好,都必须转人工。下面是节点中的部分原始判断,依赖前面已经构造好的变量:
if (/^thank you for/i.test(reply)) reasons.push('generic opening');
if (prev.stars !== null && prev.stars <= 2) reasons.push(`${prev.stars}-star review`);
if (alarm.test(prev.review)) reasons.push('health, safety or legal claim in review');
关键是最后两行:它们直接读取评论和星级,模型无法靠一段得体的回复改变分流结果。
「是否具体」也被写成了规则:从评论提取至少 4 个字母的英文词,过滤停用词;剩余词达到 3 个时,草稿必须包含其中至少一个。作者将它概括为复用评论中的真实词语,但实现比这个表述更有限,短评论可能直接跳过检查。
署名则由代码补齐。因为模型曾签成「Ramesh, Jayanagar」,还感谢过「Mr. Swiggy」,固定信息没有必要继续交给它自由生成。
规则也会出错。作者最初把「feel free」误判成赠送,把「offerings」误判成优惠,后来改成更具体的短语和单词边界匹配。这说明规则需要拿真实输出校准,不能列完关键词就算完成。
另外,原文完整代码的去除 Markdown 围栏部分混入了 {% raw %}、{% endraw %} 模板标记。准备复用时,需要先修复这一段,不能直接把网页中的整段代码粘进生产节点。
96 条草稿只放行两条,收益到底在哪里
作者把两个版本的 96 条历史草稿,全部送进最终的 Check Reply 节点,只有两条通过,而且都来自 5 星评论。
这不是生成质量大幅提升的证据。代码没有把差草稿改好,而是把多数草稿挡了下来。连通过的样例也只是表达「很高兴你喜欢这次体验,期待再次光临」,作者自己都认为具体性规则还应该收紧。
它真正改善的是审核信息。
「placeholder or made-up contact detail」提示审核人修正联系方式;「health, safety or legal claim in review」则提示店主亲自处理。相比模型只返回一个 needs_owner_attention 布尔值,明确原因更容易决定下一步动作。
作者建议的接法是:定时从 Google Business Profile API 或评论工具导出数据,把 business、review、stars、signoff、contact POST 到 webhook,再用 IF 节点检查 auto_post_ok。通过的进入发布步骤,其余进入 Slack、email 或 Google Sheet,附上草稿和原因。
上线前先让全部结果走人工分支一周,调整词表后,再考虑只开放 4–5 星评论自动发布。基础工作流位于 arkintof/n8n-ai-agent-workflows 的 workflows/07-review-reply-agent.json,文章方案需要替换 Build Prompt 和 parse 节点。
我的判断
我赞同把明确的业务约束从模型判断中拿出来。星级、长度、占位符、固定署名,都适合由程序处理。尤其原流程让同一个模型既写回复,又判断是否需要人工,本来就缺少独立的检查依据。
但 auto_post_ok 这个字段名容易让人高估它的保证。它实际表示「没有命中当前规则」,并不证明内容真实、得体或符合所有政策。英文子串匹配可能漏掉改写后的补偿承诺;连续数字检查也没有核实号码来源,只是把可疑数字统一拦住。换成中文评论,现有英文分词规则更无法直接照搬。
实验规模也有限:虚构商家、8 条评论、单个 1.8B 模型,没有强模型对照,也没有真实发布后的客户反馈。用同一套规则评分,再用这套规则筛选,可以展示规则如何工作,却不能证明它覆盖了全部风险。
和只改提示词相比,这套办法的执行边界更清楚;和全部人工审核相比,它增加了分流依据,但这次实验仍拦住了绝大多数草稿。作者关于节省审核时间的判断,目前属于经验之谈,没有耗时对照数据支撑。
能拿来做什么
- 在 n8n 或 Node 的 AI 回评流程中,把生成与校验拆成两个步骤,返回明确的拒绝原因,再根据校验结果决定后续动作。
- 把真实联系方式、店名和署名作为输入或由代码追加;缺少 contact 时先补数据,避免要求模型自行补齐联系方式。
- 准备历史评论和失败草稿,专门检查退款承诺、虚构号码、套话及关键词误报,先验证规则能否正确分流。
- 按作者建议先运行一周人工审核,观察误拦和漏拦,再决定是否开放 4–5 星自动发布;更换模型后继续保留校验节点。