用 Dify 做 AI 八字应用:我如何用 5 个权威 App 揪出一个潜伏多轮"通过测试"的日柱 Bug

68 阅读17分钟

用 Dify 做 AI 八字应用:我如何用 5 个权威 App 揪出一个潜伏多轮"通过测试"的日柱 Bug

一份关于 "AI 应用关键算法必须独立交叉验证" 的工程笔记

作者:张晓波


写在前面

这篇文章不是教程,是一次复盘。

起因很简单:我用 Dify 自部署版搭了一个四柱八字解读 ChatFlow——前端收集出生信息,Code 节点用 Python 排盘,LLM 节点解读,Answer 渲染输出。架构干净、提示词扎实、命理一致性焊死,我自认为做到了 AI 算命赛道的"上游水平",还反复做了多轮压测,结论稳定一致,零偏差

直到有一天,用户(也是我的伙伴)用一个真实专业 App 的截图来对答案。我的应用判出来的盘,和 App 不一致

继续追查下去,我发现一个让人后背发凉的事实:这个应用的日柱算法,在大量日期上是错的;错了不是几天,是接近一半。而我之前所有"压测通过""命理结论一致"的验证,都是建立在一个错误日柱上的内部自洽——错盘的结论之间当然一致,但它和真实命理无关。

更让人难堪的是:我用来"校准"那段日柱代码的基准锚点,本身就是我凭记忆给的、错的。算法被错误的基准"凑"得在少数日期碰巧正确——也就是我每次"测试"用的那几个日期。它从未真正对过,只是从未被真正测过。

这件事的严重性,远超过一个普通的 Bug。它暴露的是一个 AI 应用开发者(以及任何一个调用 LLM 做项目的人)极容易掉进去的陷阱:当你又是开发者又是测试者,当你的"验证"用的是你自己掌握的数据,你永远在自证,而不是在验证

这篇文章把整个过程完整写出来,不是为了讲一个具体的八字 Bug——是为了把这个陷阱的结构讲清楚,以及我后来用什么方法把它真正治住的。如果你也在做 AI 应用,尤其是涉及任何"有客观对错的核心算法"(汇率、税务、医学剂量、法律条款、节气干支……),这篇文章里的方法论,我希望能让你少走一段我走过的弯路。


一、应用架构:Code 和 LLM 各干什么

先把项目交代清楚,后面的讨论才有依托。

整个 ChatFlow 是经典的四节点直链:

用户输入(Start) → 代码执行(Code) → LLM → 直接回复(Answer)
  • Start:收集 7 个出生信息字段(年月日时分、性别、经度)
  • Code:跑一份 900+ 行的 Python 排盘代码 paipan_dify.py,输出完整命盘文字
  • LLM:基于命盘文字做五段结构化命理解读(Qwen3-235B)
  • Answer:渲染输出

这里的关键设计是职责严格分工——这条原则后来被我反复强化,几乎是整个项目最重要的工程决定:

Code 做机械确定性推演,LLM 做自然语言解读。永远不让 LLM 算干支,永远不让 Code 写解卦语。

为什么?因为 LLM 不会算节气(它分不清"立春前生人按上一年算"这种规则)、不会做真太阳时修正、不会精算日柱干支——这些都是有客观对错的硬骨头,交给它就是灾难。反过来,让 Python 写"调候、用神、病药"这些命理语言,又写不出温度。所以最自然的分工,就是算的归算的,说的归说的

我把这个原则贯彻得很彻底。Code 节点的输出末尾,我加了一段**【确定性推演】块**,把"日主旺衰、用神、忌神、格局判定、当前所处第几步大运"这些容易让 LLM 自由发挥的判断,全部用 Python 规则锁定。然后在提示词里加一条铁律:LLM 必须直接采纳这些结论,不得自行重判

这套设计成功解决了"同一个盘多次解读结论不一致"的顽疾。压测多轮,五个核心结论(旺衰/用神/格局/大运/事业建议)零偏差。

听起来很完美。直到地基塌了。


二、地基塌了:日柱 Bug 是怎么被揪出来的

事情从一张截图开始。

伙伴拿一个 2002 年生的男命来测我的应用——我之前从没用过这个盘。应用照常输出了一份"专业级"五段解读:日主壬水,身强,用神火木,现行丙午大运,事业宜专技路线……

漂亮。直到他甩出两张专业排盘 App 的截图,出处不同的两个 App,盘完全一致:

App 标准我的应用
年柱壬午壬午 ✓
月柱癸卯癸卯 ✓
日柱己亥壬子 ✗
时柱戊辰甲辰 ✗(因日柱错连带)

日柱错了。

日柱是什么?日柱的天干 = 命主本人(日主)。日柱错,日主从"己土"变成"壬水"——整个盘的十神、旺衰、用神、格局、确定性推演,全盘皆错。我那份"专业级解读"里,把命主当成了壬水来分析,而真正的命主是己土;两者性格、忌喜、人生路径在命理上是天差地别的两种人。

更让人胃部发凉的是:那段解读读起来毫无破绽。结构完整、五段齐全、病药分析到位、大运推演自洽——因为 LLM 老老实实按照命盘做了解读,它没法知道这个命盘本身是错的。如果不是这个伙伴坚持要拿真实 App 来对答案,这个 Bug 会潜伏到所有用户身上,而我永远蒙在鼓里。

我立刻去定位代码。日柱用的是儒略日(JD)数学公式:

# 旧代码
day_n = (round(_jd(year, month, day, 12, 0)) - 2460298) % 60

那个 2460298 是我当初拍脑袋校准的基准——我记得 "2024-01-01 = 丁丑日"(序号 13),用这个去反推出来的偏移量。

那 1985-05-15 呢?我手上一直当它 = "丁卯日",一路压测都用的这个盘,公式算出来确实是丁卯,所以我一直以为算法对。

事实是:1985-05-15 真实日柱是「甲寅」,不是丁卯。我从一开始就用了错记忆里的锚点,公式被那个错锚点"凑"得只在少数日期碰巧正确——恰恰就是我每次测试用的那几个。

这就是 AI 开发里最隐蔽的陷阱:当开发者既写代码又写测试用例,如果两者的"标准答案"都出自同一个人的记忆,那再多次测试通过都没有意义——你不过是在反复验证自己的偏差。


三、修复:不靠记忆,靠 5 个权威 App 当锚点

知道问题在哪以后,我没有急着改代码。我先停下来,问自己一个问题:我凭什么相信我"重新算出来"的偏移就是对的?

答案是:只能靠外部独立权威数据

我让伙伴用一个专业排盘 App,排出 5 个不同年代、不同结构的盘,每个盘标注出公认的年月日柱:

出生日期App 标准日柱备注
1987-10-31 02:19癸丑常规
1996-11-30 23:19壬申晚子时
2002-04-01 07:31己亥之前被错算的盘
2010-05-16 23:19丁卯晚子时
2021-01-06 10:19甲寅常规

然后我用一段简单的代码,让 5 个锚点交叉求解唯一正确的偏移量——这本质上是个线性方程组:60 个可能的偏移量里,有没有一个能同时让 5 个锚点都满足?

import datetime
for OFF in range(60):
    if all((dt.toordinal() + OFF) % 60 == gz_n(gz)
           for dt, gz in anchors.items()):
        print(f"唯一正确偏移 = {OFF}")

第一次跑出来:没有任何偏移能让 5 个全过

这正好暴露出另一个我之前完全没考虑的命理规则——晚子时换日:23:00–23:59 出生,日柱应该按次日算。两个 23 点的盘,正好比常规公式的结果差正好 +1 位

加上这条规则后再跑:

def 日柱(y, m, d, h):
    o = datetime.date(y, m, d).toordinal()
    if h == 23:                    # 晚子时换日
        o += 1
    return (o + 14) % 60           # 偏移量经 5 锚点交叉求解

5 个锚点全部通过。同时做了连续性自检(相邻日期日柱必须 +1 循环),通过。再用这个公式回算 1985-05-15,结果是甲寅——也就是说,我之前一路当作"丁卯"用了几十轮的那个日子,真实日柱根本不是丁卯。那些"压测零偏差"的成果,本质上是错盘的内部自洽,不是真的对

修复完之后,我又用更多 App 排的盘做扩展回归(年代从 1949 到 2021,跨越 70 年,含闰年、晚子时、节气换月等边界情况),全部一致

到这一步,我才敢说:日柱算法,真的对了。

这次的"对",和之前那么多轮自证的"对",完全是两个东西。


四、反思:这个陷阱的结构

把这次踩坑的过程拆开看,陷阱的结构其实非常清晰:

陷阱组件 1:开发者既写代码又写测试 你用 X 写代码,然后用 X(同一份记忆/逻辑)写测试用例。两者高度相关,测试通过几乎是必然的——但这不构成验证。

陷阱组件 2:LLM 让"错误"看起来很对 传统软件,Bug 通常会显式报错——日柱算错可能直接抛异常,你立刻就知道。但 AI 应用不会:LLM 拿到一份错盘,会老老实实给你一份格式完美、逻辑自洽、语言专业的解读。错误被它的"流畅性"完美包装,外表的专业度遮蔽了内部的失真

陷阱组件 3:压测次数 ≠ 验证有效性 我做过 8 轮压测,每轮都"通过"。但压测的本质是"用同一个错盘反复出错盘解读",一致性当然高。压测能发现的是"不稳定",发现不了"系统性偏差"

陷阱组件 4:开发者的心理惯性 当你在一个项目上投入了大量心血,你会下意识地希望它是对的。每一次"测试通过",都在加深这个心理认知。等到外部数据真正打脸时,你的第一反应往往不是"我错了",而是"是不是 App 错了"。我自己在看到伙伴第一张截图时,内心确实闪过一秒"那个 App 会不会是错的"的念头。这是非常危险的本能反应。

这四个组件叠加起来,就形成一个"所有内部信号都告诉你对了,只有外部独立数据能告诉你错了"的封闭系统。AI 应用尤其容易陷进去,因为 LLM 强大的语言润色能力,会主动帮你掩盖底层错误。


五、我后来立下的第一原则

修复完代码、做完所有回归之后,我做的最后一件事,不是发布,而是写一份《应用维护手册》,把这个事故的教训立为整个项目的第 0 原则——高于其它一切内容,放在手册的最前面。

我把这一页打印出来贴在桌前:

关键算法必须用独立权威数据源交叉验证,绝不能靠开发者或 AI 自证正确。

本应用开发过程中最严重的事故是:日柱算法错误近半数日期,却被反复声称正确,直到用 5 个第三方专业排盘 App 交叉测试才暴露。教训是惨痛而明确的——

  • 任何对核心算法的修改,必须用至少 3-5 个独立权威源的实测数据做回归验证,全部一致方可发布。
  • 不要相信"逻辑看起来对""测了一个是对的"。一个样本对不代表算法对,必须多样本、多边界、多结构交叉验证。
  • 发现结论矛盾时,优先怀疑底层数据(排盘)是否错了,而非只在上层(解读)找原因。
  • 这条原则高于本手册其它一切内容。

这条原则不是只对八字应用有用。任何 AI 应用,只要它有"机械可验证的核心算法层"——汇率换算、税务计算、医学剂量推荐、法律条款引用、节气干支推演……——都适用同一条原则

LLM 越强大,语言越流畅,这条原则就越重要。因为 LLM 不会替你验证底层数据,它只会把底层错误用漂亮的语言包装成"看起来很专业的输出"。你给它一份错盘,它就给你一份长得像专业报告的错报告。


六、引申:LLM 应用工程的两条通用经验

把这次的事故抽象出来,我觉得对其他做 AI 应用的同行有两条普适的经验,可能值得参考。

经验一:确定性层 + LLM 阐发,是治"输出不一致"的根本药

LLM 应用最常见的痛点之一,是同一个输入多次运行,输出结果不稳定——尤其涉及判断性结论时(强弱、好坏、优劣、归类)。常见的"治法"是调低温度、加严提示词、甚至跑多次取众数。这些都是治标。

真正的治本是把"判断"前置到 Code 里——凡是能用规则确定的判断,就用 Code 算出来锁定,LLM 只负责把锁定的结论翻译成人能看懂的语言

在我这个应用里,这个原则的产物就是【确定性推演】块——把"日主旺衰、用神、忌神、格局、当前大运"这些 LLM 容易"看心情判"的结论,全部用规则算出来钉死。再用一条铁律告诉 LLM:这些结论是既成事实,你必须直接采纳,不得自行重判,不得另立结论

效果立竿见影:同一个盘多次运行,核心结论从过去的"4 次 A、1 次 B"变成"5 次 5 次都 A"。这才是真正解决了不一致——不是让 LLM"碰巧每次都说一样",而是让它没有空间说不一样

经验二:不要相信你的"测试",要相信"外部独立数据"

测试用例是你写的。测试通过,只能证明"你的代码符合你的预期",不能证明"你的代码符合客观正确"。

这两件事在普通 CRUD 业务里差别不大——因为预期就是规范、规范是文档、文档是客观的。但在 AI 应用、命理、医学、法律、金融这些有"外部客观标准"的领域里,两件事可能差出十万八千里

唯一能弥合这个差距的,是引入外部独立数据源做交叉验证:

  • 命理应用 → 多个权威排盘 App 的实测
  • 翻译应用 → 母语者校对 + 多模型对照
  • 医学/法律应用 → 专业人员审核
  • 金融应用 → 官方数据源回测
  • 教育应用 → 教材/标准答案对照

引入外部数据这一步几乎所有人都嫌"麻烦",而 LLM 的语言能力又让你觉得"看着挺对的应该没事"——两个力量加起来,绝大多数 AI 应用都跳过了这一步。然后就有了今天市面上一堆"语言流畅但底层数据错漏百出"的 AI 工具。

我之所以能在这次事故中没让产品带病上线,完全是因为我的伙伴有这个习惯——他不相信我的测试,他相信权威 App。这个习惯,比任何技术能力都重要。


七、对比一下:同样的问题,其它方案怎么解决

写这篇文章前,我也认真对比过 ChatGPT 和 Grok 给出的两套同类应用方案,从工程取向上做个公允的复盘。

ChatGPT 方案:不自己排盘,要求用户提供已排好的命盘(JSON)输入。

我一开始评价这个方案"方向错了——把开发者最值钱的排盘代码弃用了"。经过日柱事件,我得修正这个评价:它的"不碰排盘"是一种合理的风险规避——它隐含的判断是"排盘这事容易出大错,我不碰",这个判断现在看比我当初承认的更有智慧。代价是用户门槛高(得自己会排盘)。

Grok 方案:用成熟的开源排盘库(如 china-testing/bazi)+ 多模型并行 + RAG。

它的"用社区验证过的开源库做底层"这一点,是三个方案里最聪明的——用别人已经被千百人测过的代码,天然规避了我栽进去的坑。但它整体方案过度工程化(4 个模型并行,在自部署 + 单云端 API 的真实环境下基本不可运行),不适合大多数开发者落地。

我的方案:自己写排盘 + Code 确定性层 + 单 LLM 解读。

出过日柱大 Bug,靠多源验证修复并制度化为第 0 原则。零门槛、命理稳定。但它的"扎实"完全依赖开发者是否真的执行"多源交叉验证"——执行了,就是当前最优;不执行,就是隐患。

我不否认自己这个方案现在是这三者里产品形态最好的——零门槛、命理稳定、架构干净。但我也必须诚实地说:它的"好"是有代价换来的,代价是差点带病上线,靠外部数据救回。Grok 用"开源库"试图规避这个代价的思路,值得我们记进未来 v2 的优化清单——给关键算法加一个独立的、被社区验证过的第二意见源,作为双保险


八、结束语

写完整篇,想说的其实就一句话:

在 AI 应用时代,工程师最容易丢的不是技术,是怀疑自己的能力

LLM 太流畅了,流畅到能把任何错误包装成专业输出;开发工具太顺手了,顺手到一天能搭出一个像模像样的应用;而我们对"测试通过"的本能信任,又恰好被这些流畅和顺手不断强化。

在这种环境下,真正稀缺的工程能力,不是"会用 LLM",而是"会不相信 LLM"——也包括不相信自己写的、看起来对的代码,直到外部独立数据告诉你它对

这件事,看起来是个具体的八字 Bug 故事,本质上是一个关于"如何在 AI 时代保持工程严谨性"的小注脚。希望它对你有用。

最后留个尾巴:那份 919 行的 paipan_dify.py、配套的《应用维护手册》、五段提示词、四护栏的完整方案,我后续会陆续整理出来分享。这一篇先把方法论说透,具体实现后面慢慢拆。

如果你也在做 AI 应用,踩过类似的坑,或者有更聪明的"外部验证"工程化方法,欢迎留言交流——这一路最值得感谢的,是那些不相信我自证、坚持用真实数据打脸的人。


作者:张晓波 在做 AI 应用工程化的探索,欢迎技术交流 邮箱:[zhangxb199@qq.com]