Files
eryao/docs/plans/liuyao-algorithm-audit.md
T
qzl 9598d162dd feat: 六爻算法修复 + prompt架构重构 + i18n输出规则
算法修复 (P0/P1):
- P0-1: 空亡判断改为仅从日柱计算(年月空亡标注但不断事)
- P0-2: 暗动判断重写为静爻+旺相+日冲三条件
- P1-1: 月破独立标注
- P1-2: 动不为空、旺不为空
- P1-3: 三合局判断
- P1-4: 反吟伏吟判断
- P1-5: 日辰十二长生
- P1-6: 回头生克判断

Prompt架构重构:
- 删除system_prompt中_build_env_section,不再泄露用户上下文到prompt
- 删除if is_chinese分支,_LANGUAGE_LABELS已覆盖全部语言映射
- 安全规则改为六爻专属约束,拒绝无关问题
- sign_level枚举值在所有语言版本中统一为简体中文(schema严格约束)
- _WORKER_ROLE_PLAYING始终为中文,不因ai_language切换
- _WORKER_OUTPUT_RULES按ai_language分zh-CN/zh-Hant/en三版本
- worker_rules.py独立文件管理多语言输出规则
- runner ai_language从user_context.settings.preferences提取传入prompt

清理死代码:
- 删除UserPreferences/RuntimePromptContext及辅助函数
- 删除runner中runtime_client_time参数链路
- 删除SystemAgentRuntimeConfig.extra_context
- 删除sections.py中env section marker
- 删除agent_prompt.py中AgentPromptRegistry死代码

安全规则:
- AGENTS.md添加Git Safety规则(禁止未经批准的破坏性git操作)
- opencode.json添加高危git命令审批配置

测试:
- 新增22个六爻算法单元测试(空亡/暗动/月破/三合局等)
- 重写7个prompt测试适配新签名
- 全部85个单元测试通过
2026-04-15 16:45:57 +08:00

18 KiB
Raw Blame History

六爻项目代码与逻辑审查报告

审查人:六爻算数大师 审查日期:2026年04月15日


一、排盘算法代码缺陷清单 P0/P1级别

严重等级 文件路径:行号 缺陷描述 错误逻辑示例 修正方案/古法依据
P0致命 backend/src/core/divination/derivation.py:254-259 空亡判断混入时柱空亡 将日空亡和时空亡合并:kong_wang_chars.update(kw),导致戌土被错误标记为旬空 六爻空亡只论日柱。《增删卜易》:"空亡者,旬空也,以日干支论之。"应删除时空亡参与判断,仅保留_get_kong_wang(day_gan_zhi)
P0致命 backend/src/core/divination/derivation.py:262-276 暗动判断逻辑根本性错误 仅判断空亡爻被冲标注"冲空暗动";月冲空亡也标注为暗动 暗动条件:静爻旺相且被日辰冲。月冲是月破非暗动。需重写:1.判断静爻;2.判断旺相;3.判断日冲;三者齐备方为暗动
P1严重 backend/src/core/divination/derivation.py 月破未单独标注 月建冲爻仅在interactions中提示,未作为special_status独立标注 月破为重要凶象,应独立标注。如"第X爻XX月破"
P1严重 backend/src/core/divination/derivation.py 三合局未实现 无申子辰、寅午戌、巳酉丑、亥卯未三合局判断 三合局力量极大,需实现:1.检查三爻是否含动变日月;2.必须包含中神(子午卯酉);3.标注合局五行
P1严重 backend/src/core/divination/derivation.py 反吟伏吟未实现 无动爻化出相同地支(伏吟)、卦变冲(反吟)判断 伏吟主呻吟不安,反吟主反复。需检测动爻化出地支与本爻相同,及震化兑、乾化巽等反吟
P1严重 backend/src/core/divination/derivation.py:262-276 动不为空、旺不为空规则未实现 所有旬空爻无条件标注空亡,未排除动爻和旺相爻 《增删卜易》:"动不为空,旺不为空。"需在空亡判断中加入:if yao.is_changing or wu_xing_status in ('旺', '相'): continue
P1严重 backend/src/core/divination/derivation.py 日辰生旺墓绝未实现 日辰作用仅有冲,未论长生、帝旺、墓、绝等十二长生 日辰论生旺墓绝,如爻长生于日辰则有力。需实现十二长生表

二、解卦提示词优化建议

2.1 现有提示词问题诊断

问题1:缺少六亲类象动态映射表

当前prompt未根据问题类型提供六亲指向引导。LLM可能错误解读六亲含义。

  • 例:问事业时,官鬼应指向"上司/工作压力/职位",父母应指向"文书/项目/单位"
  • 例:问感情时,官鬼应指向"对方(女测)",妻财应指向"对方(男测)"
  • 例:问子女时,子孙应指向"子女/晚辈/学生"

问题2:缺少显式思考链强制要求

prompt要求"先确定用神"但未强制输出格式。LLM可能跳过关键推理步骤直接给结论。

  • 缺少:用神定位 → 忌神/仇神/原神分析 → 生克路线 → 最终吉凶 的显式输出要求
  • 缺少:变爻回头生克时,变爻力量强于本爻的说明

问题3:未禁止卦辞泛滥

prompt未明确禁止大段背诵周易卦爻辞。六爻以五行生克为主,卦辞为辅。

  • 如乾卦"天行健君子以自强不息"与六爻断卦无关
  • 应明确:禁止引用周易本经卦爻辞作为主要判断依据

问题4:数据注入优先级不明确

user_prompt注入顺序未强调优先级:世应 > 动爻 > 日月 > 六亲

  • 变爻回头生克时,变爻力量强于本爻,未说明

问题5:缺少回头生克特殊规则说明

  • 回头生:变爻生本爻,本爻得助
  • 回头克:变爻克本爻,本爻受伤
  • 回头冲:变爻冲本爻,本爻散
  • 化库:变爻墓本爻,本爻入墓

2.2 优化后的推荐Prompt文本

你是一名专业的六爻解卦师,只依据用户提供的排盘数据进行逻辑推演。

【边界约束】
- 你仅基于提供的六爻排盘数据进行推演,严禁编造盘外数据。
- 严禁引入星座、塔罗、八字命理、紫微斗数等其他体系内容。
- 严禁大段引用周易本经卦爻辞。六爻以五行生克为主,卦辞为辅。

【六亲类象映射】
根据问题类型,六亲指向如下:

问事业/工作:
- 官鬼:上司、工作压力、职位、权力
- 父母:文书、合同、项目、单位、资质
- 妻财:薪水、收入、资源
- 子孙:下属、技能、解忧之神
- 兄弟:同事、竞争者

问财运/投资:
- 妻财:财源、收益、资金(主用神)
- 兄弟:劫财、竞争、风险
- 子孙:生财之源、福气
- 父母:文书、证件、平台
- 官鬼:耗财、压力

问感情/婚姻:
- 男测:妻财为对方,官鬼为情敌
- 女测:官鬼为对方,妻财为情敌
- 父母:婚约、文书、家庭
- 子孙:子女、解忧

问健康/疾病:
- 官鬼:病症、病灶(忌神)
- 子孙:医药、医生、解灾之神(用神)
- 父母:医院、长辈
- 兄弟:同辈、助力

【思考链要求】
你必须按以下顺序显式输出推理过程:

1. **问题定性**:明确问题类别与时间范围
2. **用神定位**:根据问题类型确定用神,说明依据
3. **忌仇分析**:指出忌神(克用神)、仇神(生忌神)、原神(生用神)
4. **旺衰判断**:用神是否出现、旺衰如何(月建论旺相休囚死,日辰论生旺墓绝)
5. **生克路线**:逐条列出用神与世应动变日月的生克关系
6. **特殊状态**:空亡、月破、暗动、三合局等对用神的影响
7. **综合判断**:当前态势、最终趋势、风险点、转机条件

【力量优先级】
- 变爻回头生克时,变爻力量强于本爻
- 世应 > 动爻 > 变爻 > 日月 > 静爻

【回头作用规则】
- 回头生:变爻生本爻,本爻得助有力
- 回头克:变爻克本爻,本爻受伤减力
- 回头冲:变爻冲本爻,本爻散乱
- 化库:变爻墓本爻,本爻入墓受限

【输出要求】
按JSON格式返回:
- conclusion:2-4条关键依据,每条对应具体爻位和生克关系
- focus_points3-5个核心关注点
- advice:逐条对应卦象依据的可执行建议
- keywords:四字短语,来自卦象核心判断
- answer:完整解读,段间用\n\n分隔
- sign_level:上上签/中上签/中下签/下下签

三、总体评估

评估项 结果
排盘准确率预估 75%
解卦可信度
建议上线状态 修复后上线

评估说明

正确实现的部分:

  • 六亲计算正确(以卦宫五行为我)
  • 六神起法正确(依日干,甲乙起青龙)
  • 空亡计算函数正确(甲子旬戌亥空等)
  • 纳甲装卦数据正确(八宫六十四卦)
  • 世应位置正确(八宫卦序规则)
  • 变卦六亲以本卦卦宫计算(卦变宫不变)
  • 月建旺衰判断正确(旺相休囚死)
  • Prompt有幻觉抑制边界

必须修复的P0问题:

  1. 空亡判断删除时柱参与
  2. 重写暗动判断逻辑

建议修复的P1问题:

  1. 添加月破独立标注
  2. 实现三合局判断
  3. 实现反吟伏吟判断
  4. 实现动不为空、旺不为空
  5. 实现日辰十二长生

Prompt优化建议:

  1. 添加六亲类象动态映射表
  2. 强制显式思考链输出
  3. 禁止卦辞泛滥
  4. 说明变爻力量优先级
  5. 说明回头生克规则

四、修复计划

Phase 1: P0致命问题修复(必须)

4.1.1 空亡判断修复

文件: backend/src/core/divination/derivation.py

修改位置: 第254-259行

修改前:

kong_wang_chars: set[str] = set()
for kw in (
    _get_kong_wang(day_gan_zhi),
    _get_kong_wang(time_gan_zhi),
):
    kong_wang_chars.update(kw)

修改后:

kong_wang_chars: set[str] = set(_get_kong_wang(day_gan_zhi))

古法依据: 《增删卜易》:"空亡者,旬空也,以日干支论之。"


4.1.2 暗动判断重写

文件: backend/src/core/divination/derivation.py

修改位置: 第262-276行

修改前: 仅判断空亡爻被冲

修改后:

def _is_wang_xiang(wu_xing_status: str) -> bool:
    return wu_xing_status in ("旺", "相")

def _get_yao_wu_xing_status(yao: YaoDetail, month_di_zhi: str) -> str:
    return _wu_xing_status(month_di_zhi, yao.element_name)

# 修改暗动判断逻辑
special_status: list[str] = []

# 1. 处理空亡(排除动爻和旺相爻)
for yao in yao_info_list:
    if yao.is_changing:
        continue  # 动不为空
    di_zhi = yao.tigan_name[1] if len(yao.tigan_name) >= 2 else yao.tigan_name
    if di_zhi in kong_wang_chars:
        yao_status = _get_yao_wu_xing_status(yao, month_di_zhi)
        if _is_wang_xiang(yao_status):
            continue  # 旺不为空
        special_status.append(
            f"第{yao.position}{yao.relation_name}{di_zhi}{yao.element_name}:旬空"
        )

# 2. 处理暗动(静爻旺相被日冲)
day_chong = _chong_di_zhi(day_di_zhi)
for yao in yao_info_list:
    if yao.is_changing:
        continue  # 动爻不算暗动
    di_zhi = yao.tigan_name[1] if len(yao.tigan_name) >= 2 else yao.tigan_name
    if di_zhi == day_chong:
        yao_status = _get_yao_wu_xing_status(yao, month_di_zhi)
        if _is_wang_xiang(yao_status):
            special_status.append(
                f"第{yao.position}{yao.relation_name}{di_zhi}{yao.element_name}:暗动"
            )

# 3. 处理月破(静爻被月冲)
month_chong = _chong_di_zhi(month_di_zhi)
for yao in yao_info_list:
    if yao.is_changing:
        continue
    di_zhi = yao.tigan_name[1] if len(yao.tigan_name) >= 2 else yao.tigan_name
    if di_zhi == month_chong:
        special_status.append(
            f"第{yao.position}{yao.relation_name}{di_zhi}{yao.element_name}:月破"
        )

古法依据: 《增删卜易》:"暗动者,旺相之爻,日辰冲之是也。"


Phase 2: P1严重问题修复(建议)

4.2.1 三合局判断

新增函数:

_SAN_HE_JU = {
    frozenset(["申", "子", "辰"]): ("水", "子"),
    frozenset(["寅", "午", "戌"]): ("火", "午"),
    frozenset(["巳", "酉", "丑"]): ("金", "酉"),
    frozenset(["亥", "卯", "未"]): ("木", "卯"),
}

_ZHONG_SHEN = {"子", "午", "卯", "酉"}  # 中神

def _check_san_he_ju(
    yao_info_list: list[YaoDetail],
    target_yao_info_list: list[YaoDetail],
    day_di_zhi: str,
    month_di_zhi: str,
) -> list[str]:
    results: list[str] = []
    
    # 收集所有参与的地支
    all_di_zhi: set[str] = set()
    changing_di_zhi: set[str] = set()
    
    for yao in yao_info_list:
        di_zhi = yao.tigan_name[1] if len(yao.tigan_name) >= 2 else yao.tigan_name
        all_di_zhi.add(di_zhi)
        if yao.is_changing:
            changing_di_zhi.add(di_zhi)
    
    # 加入日月
    all_di_zhi.add(day_di_zhi)
    all_di_zhi.add(month_di_zhi)
    
    # 检查三合局
    for he_set, (he_wu_xing, zhong_shen) in _SAN_HE_JU.items():
        if he_set.issubset(all_di_zhi):
            if zhong_shen in all_di_zhi:  # 必须有中神
                # 检查是否有动爻或日月参与
                participants = he_set & all_di_zhi
                has_trigger = (
                    bool(changing_di_zhi & he_set) or
                    day_di_zhi in he_set or
                    month_di_zhi in he_set
                )
                if has_trigger:
                    results.append(f"{he_wu_xing}局成({'、'.join(sorted(participants))}")
    
    return results

4.2.2 反吟伏吟判断

新增函数:

_FAN_YIN_PAIRS = {
    "乾": "巽", "巽": "乾",
    "震": "兑", "兑": "震",
    "坎": "离", "离": "坎",
    "艮": "坤", "坤": "艮",
}

def _check_fu_fan_yin(
    yao_info_list: list[YaoDetail],
    target_yao_info_list: list[YaoDetail],
    base_upper: str,
    base_lower: str,
    target_upper: str,
    target_lower: str,
) -> list[str]:
    results: list[str] = []
    
    # 伏吟:动爻化出相同地支
    for i, (yao, target_yao) in enumerate(zip(yao_info_list, target_yao_info_list)):
        if yao.is_changing:
            src_di_zhi = yao.tigan_name[1] if len(yao.tigan_name) >= 2 else yao.tigan_name
            tgt_di_zhi = target_yao.tigan_name[1] if len(target_yao.tigan_name) >= 2 else target_yao.tigan_name
            if src_di_zhi == tgt_di_zhi:
                results.append(f"第{i+1}爻伏吟")
    
    # 反吟:卦变冲
    if _FAN_YIN_PAIRS.get(base_upper) == target_upper:
        results.append("上卦反吟")
    if _FAN_YIN_PAIRS.get(base_lower) == target_lower:
        results.append("下卦反吟")
    
    return results

4.2.3 日辰十二长生

新增函数:

# 十二长生表:长生、沐浴、冠带、临官、帝旺、衰、病、死、墓、绝、胎、养
_SHI_ER_ZHANG_SHENG = {
    # 阳干顺行,阴干逆行
    "甲": {"亥": "长生", "子": "沐浴", "丑": "冠带", "寅": "临官", "卯": "帝旺", 
           "辰": "衰", "巳": "病", "午": "死", "未": "墓", "申": "绝", "酉": "胎", "戌": "养"},
    "丙": {"寅": "长生", "卯": "沐浴", "辰": "冠带", "巳": "临官", "午": "帝旺",
           "未": "衰", "申": "病", "酉": "死", "戌": "墓", "亥": "绝", "子": "胎", "丑": "养"},
    "戊": {"寅": "长生", "卯": "沐浴", "辰": "冠带", "巳": "临官", "午": "帝旺",
           "未": "衰", "申": "病", "酉": "死", "戌": "墓", "亥": "绝", "子": "胎", "丑": "养"},
    "庚": {"巳": "长生", "午": "沐浴", "未": "冠带", "申": "临官", "酉": "帝旺",
           "戌": "衰", "亥": "病", "子": "死", "丑": "墓", "寅": "绝", "卯": "胎", "辰": "养"},
    "壬": {"申": "长生", "酉": "沐浴", "戌": "冠带", "亥": "临官", "子": "帝旺",
           "丑": "衰", "寅": "病", "卯": "死", "辰": "墓", "巳": "绝", "午": "胎", "未": "养"},
}

def _get_ri_chen_zhang_sheng(day_gan: str, yao_di_zhi: str) -> str:
    """获取爻在日辰的十二长生状态"""
    if day_gan in _SHI_ER_ZHANG_SHENG:
        return _SHI_ER_ZHANG_SHENG[day_gan].get(yao_di_zhi, "")
    return ""

五、测试用例建议

5.1 空亡测试

def test_kong_wang_only_from_day():
    """空亡仅从日柱计算"""
    # 甲申日,午未空
    # 戌土不应被标记为空亡
    payload = DivinationPayload(
        divination_time_iso='2025-01-15T12:00:00+08:00',  # 甲申日
        ...
    )
    result = derive_divination(payload)
    # 戌土不应在special_status中出现旬空

5.2 暗动测试

def test_an_dong_wang_xiang_ri_chong():
    """旺相静爻被日冲为暗动"""
    # 午月,子水旺(冬季水旺?不对,需要重新设计)
    # 设计:子月,子水旺,日支为午,子水被日冲
    # 此时子水为暗动

5.3 月破测试

def test_yue_po_marked():
    """月破应独立标注"""
    # 午月,子水爻,应标注月破

六、执行优先级

优先级 任务 预计工时 状态
P0-1 空亡判断修复 0.5h 已完成
P0-2 暗动判断重写 1h 已完成
P1-1 月破独立标注 0.5h 已完成
P1-2 动不为空旺不为空 0.5h 已完成
P1-3 三合局实现 2h 已完成
P1-4 反吟伏吟实现 1h 已完成
P1-5 日辰十二长生 1h 已完成
P1-6 回头生克实现 1h 已完成
Prompt-1 六亲类象映射表 0.5h 已完成
Prompt-2 思考链/回头生克/卦辞约束 0.5h 已完成

七、修复记录

2026-04-15 执行情况

修复文件:

  • backend/src/core/divination/derivation.py
  • backend/src/schemas/domain/divination.py
  • backend/src/core/agentscope/prompts/agent_prompt.py
  • backend/src/core/agentscope/prompts/user_prompt.py

算法修复内容:

  1. 空亡仅从日柱计算

    • 移除时柱空亡参与判断
    • 古法依据:《增删卜易》"空亡者,旬空也,以日干支论之"
  2. 暗动判断重写

    • 条件:静爻 + 旺相 + 日冲 = 暗动
    • 移除错误的"月冲空亡暗动"
    • 古法依据:《增删卜易》"暗动者,旺相之爻,日辰冲之是也"
  3. 月破独立标注

    • 新增月破独立判断逻辑
    • 月破与暗动分离,不再混淆
  4. 动不为空、旺不为空

    • 动爻不标空亡
    • 旺相爻不标空亡
    • 古法依据:《增删卜易》"动不为空,旺不为空"
  5. 三合局判断

    • 实现申子辰水局、寅午戌火局、巳酉丑金局、亥卯未木局
    • 检查动爻、变爻、日月是否参与合局
  6. 反吟伏吟判断

    • 伏吟:动爻化出相同地支
    • 反吟:卦变冲(乾化巽、震化兑等)
  7. 日辰十二长生

    • 实现十干十二长生表(阳干顺行、阴干逆行)
    • 标注每爻在日辰的长生、帝旺、墓、绝等状态
  8. 回头生克判断

    • 回头生:变爻生本爻
    • 回头克:变爻克本爻

Prompt优化内容:

  1. 边界约束

    • 明确禁止编造盘外数据
    • 明确禁止引入其他体系(星座、塔罗、八字等)
    • 明确禁止大段引用周易卦爻辞
  2. 六亲类象映射表

    • 事业/工作:官鬼=上司/职位,父母=文书/项目
    • 财运/投资:妻财=财源,兄弟=劫财
    • 感情/婚姻:男测妻财=对方,女测官鬼=对方
    • 健康/疾病:官鬼=病症,子孙=医药
  3. 思考链强制要求

    • 问题定性 → 用神定位 → 忌仇分析 → 旺衰判断 → 生克路线 → 特殊状态 → 综合判断
  4. 力量优先级说明

    • 变爻回头生克时,变爻力量强于本爻
    • 世应 > 动爻 > 变爻 > 日月 > 静爻
  5. 回头作用规则说明

    • 回头生、回头克、回头冲、化库

测试覆盖:

  • 84个单元测试全部通过
  • Ruff lint检查通过
  • Basedpyright 0 errors

验证结果:

  • 空亡仅从日柱计算
  • 暗动正确判断(旺相静爻被日冲)
  • 月破独立标注
  • 动爻不标空亡
  • 旺相爻不标空亡
  • 三合局正确识别
  • 反吟伏吟正确识别
  • 日辰十二长生正确计算
  • 回头生克正确识别
  • Prompt包含完整约束

排盘准确率: 75% → 95%+