作者在八字算命app中让规则引擎决定所有推导事实,LLM只负责用自然语言表达,彻底解决幻觉问题。
我在一个 LLM 真实性最差的领域做软件:算命。一个八字排盘应用,模型的工作是听起来像一位智者——而竞品 AI 产品的用户评价集中在一个抱怨上:"纯属胡扯。" LLM 被要求"解读出生盘"时,会 hallucinate 出盘中不存在的元素,发明传统中不存在的规则,然后以完全自信的语气呈现这一切。在这个没有外部客观事实可供验证的领域,用户无从判断——直到同一张盘的两份解读相互矛盾。
无论你怎么看待这个领域(我之前写过它真正困难的时区计算),这个工程答案可以迁移到任何不能胡编乱造的 LLM 产品。只有一条规则:
确定性引擎决定说什么。LLM 只决定怎么说。
盘、五行强弱、吉凶分析、每一个推导事实——全部由 TypeScript 规则引擎计算,有单元测试,公开的常量。模型收到这些事实作为一个紧凑的块,加上一条指令:只引用给定的内容。它是一个带着人设的翻译,不是一个预言家。
这是简单的 80%。有趣的工程在三个差点打破这条规则的地方。
许多用户不知道自己的出生时辰——而时辰是四柱之一。朴素的做法都很糟糕:拒绝用户,或者让模型在空白处即兴发挥。
第二种是危险的。你猜如果直接省略时辰模型会做什么:它会填充那个空洞。静悄悄地。用一个具体的、合理的、虚构的柱。
修复方法是让引擎确定性处理这种不确定性。
未知时辰意味着恰好 12 种可能的盘。我计算全部 12 种,然后取交集:只有对所有候选盘都成立的事实才会进入 prompt。五行强弱在全部 12 种中都一致?那就陈述。7/5 分化?那 prompt 就原样说:
强弱未定(12 个候选盘中 7 个倾向强)——不可据此立论。
关键的是,空洞本身要显式标出而不是省略:
Chart: 己未 丙寅 庚午 ▢
(hour pillar unknown — 12 candidate charts computed; only facts true
in all of them are listed)
这个 ▢ 物有所值。显式标出的空洞胜过静默的空洞:把位置留空,模型会填上它;标记它并指示("不要提及时辰柱;不要讨论它主管的人生领域;虚构一个就是说谎"),模型就会绕开它。
Prompt 甚至告诉模型如何优雅收尾:一句话说明如果用户得知出生时间还能看出什么。不确定性成了产品特性,而不是 hallucination 的切面。
Prompt 是策略,不是执行。所以每一份生成的解读在存储前都要经过验证器。
虚构柱检测
对于未知时辰的盘,扫描输出中是否出现 12 个候选时辰柱中的任何一个。
关键细节是匹配完整的单字+地支组合,绝不是单字——单字"子"出现在普通词汇"孩子"里,"金"出现在"资金"里。配对匹配基本没有误报;单字匹配会给几乎每个句子都打上标记。
禁止模式扫描
一张宿命论和恐吓式表达的 regex 列表:"一定会离婚"、"命短"、"无药可救"。这是这个领域的黑暗模式,编码为规则。
这不只是品味问题。每个管理这个垂直领域的内容平台政策——搜索质量指南、广告政策、支付处理商——都在具体末日论断和解读性反思之间划定允许/禁止的界限。regex 列表把这条合规边界变成了代码。
封闭词汇检查
一份星象和神祇术语的清单,引擎永远不会计算这些。如果输出中出现了其中一个,说明模型从训练数据里导入了民间传说,该解读被标记待审。
验证器原本有第三个更强力的检查:一个白名单断言,输出中的每一个天干、地支和"十神"术语都来自盘 JSON。
几个月后 code review 时,我发现它的两个循环都在问一个集合是否包含从同一个集合中取出的元素——这个条件永远为真,因此链接到一个永远不会触发的检查。两个支撑数组从未被读取过。
它从未抓到过任何违规。不是因为输出是干净的,而是因为护栏无法触发。
我删掉了它,写了一条注释解释原因,包括真正的版本需要解决什么:与上面相同的单字碰撞问题。我现在处处应用两条教训:
无法触发的护栏比没有护栏更糟——它出现在每张架构图和 code review 里作为"我们验证了",然后每个人就不再想它了。
像测试代码一样测试你的验证器:用必然会失败的输入来测。一个没有失败测试用例的验证函数是一个假设,不是一道门。
人设和所有指令用中文写;应用也提供英文解读。一句"用英文回复"在一句 2000 字的中文 prompt 面前活不下来。
模型把混合句子送进了生产环境。一个真实例子:
Your盘的里,其实事业和财这两条线比性格更有讲头
这个措辞是从 persona 内的中文示例句子里直接搬过来的。
站得住的修复更直接:一段专门的段落说明 prompt 中的每个引用句只是语气示范,不得复制或翻译,输出中除标注的拼音外不得包含任何中文字符。当你的 prompt 是双语的时候,语言指示必须压过整个 prompt 的其余部分。
成本。 模型每节写 300 词风格化散文,而不是"推理"盘。不需要 chain-of-thought——thinking mode 关闭,调用约 1.5 秒,成本几分钱。
一致性。 同一张盘的两个用户得到相同事实上的风格化变体,而不是两个不同的命运。重读不会矛盾。
这条线是可审计的。 当用户问"为什么这么说?"时,有一个引擎事实可以指向——与网站方法页面上公开的是同一个。
这个模式很老了,说实话——就是一个编译器发出事实,一个美化器渲染它们。唯一的新部分是美化器去读了艺术学校,只要你允许它就会虚构事实。不要让它:计算真相,标记空洞,验证输出,测试你的验证器确实能够失败。
应用:auspiceoracle.com —— 引擎的评分常量公开在方法页上,这是"展示你的推演过程"规则在营销中的应用。