Skip to content

01-通用框架

本章讲不分领域、所有问数技能都该遵守的「五个铁原则」和「三层架构」。这部分直接抄,不必重新发明。


┌─────────────────────────────────────────┐
│ 业务表达层(SKILL.md + references/) │ 角色 / 纪律 / 意图分层 / 归因框架
│ who: LLM 主体 │
└─────────────────┬───────────────────────┘
│ 业务语言 → 模板填槽 / DSL JSON
┌─────────────────────────────────────────┐
│ 编译层(DSL + 二级指令 + Schema) │ 把意图编译成 SQL;自动行为
│ who: Python CLI │
└─────────────────┬───────────────────────┘
│ SQL
┌─────────────────────────────────────────┐
│ 数据层(星型宽表) │ 维度 + 事实分层
│ who: MySQL / ClickHouse / Doris │
└─────────────────────────────────────────┘

三层职责严格分离:

  • 业务表达层:不直接生成 SQL,不直接调编译器接口;只用业务语言决策”该查什么、怎么校验口径、结论说到多强”;
  • 编译层:不做业务判断,不做归因;只把 DSL / 二级指令翻译成正确的 SQL;
  • 数据层:被动响应;不依赖业务上下文。

为什么必须分离:cost-query V1-V2 阶段三层混在一起,导致改业务话术会破坏 DSL 编译;V3 引入双层 schema 之后才解耦。任何项目复刻这个架构都应该从一开始就分离,不要等到混乱了再重构。


跨领域通用,不要尝试改

数值与判断只能来自实查数据或用户提供的可核验材料;缺数据时说明无结果并给可放宽方向,不补数、不猜数、不用通识冒充数据库结论。

落地形态:

  • SKILL.md 顶部明文「数据底线」段落,含 3 条高危红线;
  • 引入 receipt 机制:所有数字必须可回指到本会话查询结果;
  • 用户挑战结论时先复盘查询过程,不立刻认错;过程没问题时用查询链路向用户说明结论依据。

cost-query 实例:

所有数据库类数字必须可回指到本会话 cost-query 结果:数字必须出现在某条
`[receipt: cost-query-v2 ...]` 之前的结果表格中。用户给出的数字只能作为待
复核输入,不能冒充数据库结论。外部常识不能写成项目数据库结果。

为什么是铁原则:违反这一条的问数技能会在 2-3 周内被业务用户抛弃——业务方一旦发现你”编数据”,整个工具的可信度就崩了。

编译器需要 SQL 物理列名;LLM 决策不需要。两者分离到两份文件,互不污染。

  • 编译源 scripts/schema.yaml:含 SQL 物理列名、JOIN 实现、隐藏字段、自动行为声明;
  • AI 速查版 references/schema/*.yaml:业务字段视图,删 SQL 列名与隐藏字段,按 cube 拆文件;
  • 派生关系:编译源是唯一源,速查版自动生成,严禁直接编辑速查版。

为什么是铁原则:单文件 schema 会让 LLM 每次查询都加载 700+ 行物理列细节,挤占上下文且干扰决策;分离后 LLM 首读 _index.yaml(一句话清单),按需加载单 cube 详情。

详见 Part 3 · Schema 双层架构

让 LLM 拿到问题后对照模板填槽,而不是”自由推理走 N 步决策树”。

落地形态:

  • 二级指令封装常见查询场景(agg-project-indicatorfind-unit-price 等),LLM 只填参数;
  • 模式手册 M1-Mn 把”什么业务问题对应哪个二级指令、关键参数怎么填”列成表,LLM 命中即抄;
  • 留 DSL 直查作为兜底,覆盖二级指令表达不了的场景。

为什么是铁原则:自由发挥的 LLM 每多一步推理就会多一处错——cost-query V2 阶段 LLM 平均要做 8-12 步内部推理才发出 SQL,错误率 35%;V3 引入模式手册后 LLM 推理减到 3-4 步,错误率降到 15%。这不是 prompt 调优能弥补的差距。

详见 Part 5 · 二级指令系统Part 7 · 模式手册

原则 4:0 行机械撤宽,撤宽即终点

Section titled “原则 4:0 行机械撤宽,撤宽即终点”

主查询 0 行时机械地撤宽(保留 1 个核心 filter,其余移到 groupBy 让分布显形);撤宽仍 0 行 = 终点,告知用户无数据,逐参数试错、继续换关键词死磕。

cost-query 实例(query-guide §0 必读铁律):

D1 | 0 行 = 机械撤宽(filter→groupBy,保留 1 个核心 filter),禁逐参数试错
D2 | indicator 全 NULL 不兜底切 cube;告知用户选择
D3 | 撤宽仍 0 行 = 终点(库中无记录),不换替代物料

为什么是铁原则:LLM 拿到 0 行结果时的本能是”调整参数再试一次”——这会触发”5 次 6 次反复试探”的死循环,既消耗时间又干扰用户判断。机械撤宽 + 撤宽即终点能让 90% 的 0 行场景在 2 步内给出诚实结论。

详见 Part 6 · SKILL 业务表达层 §收口门部分。

每次 SKILL.md / 速查表 / 编译器修改都跑一次测评集;Bad case 必须进入下一轮治理;没有测评集的迭代是闭门造车

落地形态:

  • 至少 30 道覆盖 M1-Mn 全模式的测评题(cost-query 现在 71 题);
  • headless 跑批工具(cost-query 用 cctest 子模块):每题独立 Claude Code 会话,stream-json 解析记录工具调用与耗时;
  • 评分维度解耦:CLI 调用质量(路径分)与业务结论质量(结论分)分开打分;
  • Bad case 流转:测评报告 → 速查表治理候选 → 下一轮跑批验证 → 双轮通过即转 status=fix。

为什么是铁原则:cost-query V3 → V4 阶段的正确率提升(75% → 88%)90% 来自测评驱动的反复治理;没有测评集就没有这条增长曲线。

详见 Part 8 · 测评驱动迭代


什么可以直接抄、什么必须重做——一张表说明白。

资产cost-query 实例复刻方式
三层架构(业务表达 / 编译 / 数据)见 §1.1目录结构原样复制
双层 schema(编译源 + 速查版)scripts/schema.yaml + references/schema/格式约定原样复制,内容重写
DSL 三 verb(find / aggregate / rank)scripts/query.py 的 verb 分发代码 fork,主体逻辑不动
自动行为机制(inferredFilters / defaultFilters / friendlyAlias)_builder.py代码 fork,规则配置改写
二级指令模板格式(commands/*.yaml)见 commands/ 下 15 个 yaml格式约定原样复制,模板重写
batch 批量执行机制commands/_ext/batch.py代码 fork
find-dimension 通用维度探测commands/find-dimension.yaml + _ext/代码 fork
错误自救机制(带候选清单)query.py 的 3 级错误分类代码 fork
0 行机械撤宽 / 撤宽即终点query-guide.md §0 D1-D3文档原样复制,措辞调整
数据底线红线SKILL.md 顶部 3 条文档原样复制,例子换成你的领域
L1-L4 意图分层SKILL.md 中部文档原样复制,例子换成你的领域
收口门机制SKILL.md 中部文档原样复制
模板填槽 + 槽位风险分级query-guide.md §2格式原样复制,对照表重写
测评驱动闭环cctest 子模块代码 fork + 测评集重写
资产cost-query 实例你的领域需要做什么
业务专家角色成本经理销售经理 / 工程经理 / HR BP / 财务分析师
维度 cube 清单12 个(Project/City/BzItem/…)列出你领域的 5-15 个核心维度
事实 cube 清单7 个(ProjectIndicator/BqUnitPrice/…)列出你领域的 3-8 个核心事实
数据语义陷阱父子科目混算 / isJianAn 切分你的领域有哪些容易踩坑的字段语义(如售楼”未网签 vs 已网签” / 工程”分部分项 vs 单位工程”)
维度风险分级建安 / 精装 / 公区 等高风险词你的领域哪些词高风险(必须前置标准化)
模式手册 M1-Mn39 个模式从你的测评集反推 10-50 个模式
反模式黑名单7 条(自由发挥安全网)你的领域踩过的坑沉淀成反模式条目
归因框架五差(量/价/配置/条件/口径)你的领域的归因维度(售楼可能是”量价位时”,工程可能是”进度成本质量安全”)
业务术语对照表建安→建筑安装工程费 / 公装→公共部位装修工程费你的领域的口语词 → 标准词候选
测评集71 题从你的领域真实业务问题里抽 30-100 题

底层基础设施(编译器、DSL、二级指令框架、batch、自动行为、错误自救)——这些跨领域几乎不需要改动,直接 fork 即可。本指南采用「参考实现」策略而非「共享 Python 包」策略,意味着你需要把 cost-query 的 scripts/ 目录原样复制到你的项目下,然后只改 schema.yamlcommands/*.yaml

为什么选择 fork 而不是共享包

  • 领域差异大:售楼/工程/HR 的字段命名、JOIN 关系、特殊语义都不同;硬塞进一个通用包会让接口越变越复杂;
  • 演进节奏不同:你的领域可能要加一种 cost-query 没有的能力(如售楼的房源状态机查询),抽共享包要等 cost-query 团队评审接口扩展;fork 你自己改自己的;
  • 复用成本可控:cost-query 的 scripts/ 总共 ~5500 行 Python,fork 一次的成本 < 1 周;之后跟主仓同步重要 fix 用 cherry-pick 即可。

fork 的具体边界

直接抄(fork 后基本不改):
scripts/query.py # DSL 编译 + CLI 入口
scripts/_builder.py # 二级指令 → DSL 渲染
scripts/_registry.py # commands/ 自动注册
scripts/cli.py # MySQL 直连执行
scripts/fetch_schema.py # 速查版生成器
scripts/commands/_ext/batch.py # 批量执行
scripts/commands/_ext/find_dimension.py # 维度探测
抄结构、重写内容:
scripts/schema.yaml # 改成你领域的 cube
scripts/commands/*.yaml # 改成你领域的二级指令
references/ # 全部文档重写(保留 dsl-spec.md 大部分)
完全重写:
SKILL.md # 业务表达层
tests/ 或 eval/ 下的测评集

fork 后维护策略

  • 跟踪 cost-query 主仓的 commits,每月看一次有没有可借鉴的 fix(编译器 bug、错误自救改进、自动行为增强);
  • 你领域的通用新发现(如某种新的反模式)反馈给 cost-query 主仓,长期演化为本指南;
  • DSL 协议(一级指令的 JSON 结构)尽量保持兼容,方便跨项目共享测评工具。

cost-query 团队从外部团队接触中收集到的最常见误区,提前知道能省 1-3 个月

误区 1:以为问数技能就是 NL2SQL

Section titled “误区 1:以为问数技能就是 NL2SQL”

“我们做一个能把自然语言翻译成 SQL 的工具就行了。”

实质问题:忽略了「业务结论质量」这层。LLM 写出正确 SQL ≠ 给出正确业务结论。一个 SQL 拿到 5 行结果,能不能下”住宅项目钢筋偏高”的强结论?要看样本是不是同口径、样本量够不够、有没有异常值——这些都不是 SQL 翻译能解决的。

正确做法:从一开始就建业务表达层(SKILL.md),把口径校验、样本评估、归因框架、对外叙事都做进去。

误区 2:以为 LLM 会自己想清楚口径

Section titled “误区 2:以为 LLM 会自己想清楚口径”

“Claude 这么聪明,给它 schema 它自己会知道用 isEndCost=1 防止父子科目混算。”

实质问题:LLM 在你给的 schema 描述里看到「父子科目混算」字样可能会避开,但绝对不会每次都避开——尤其是问题改个措辞、上下文长了、用户挑战结论时。

正确做法:把「数据语义陷阱」做进编译器的 defaultFilters(自动加 isEndCost=1),把「同名父子混算」写进反模式黑名单,双重保险

“我们先把功能做出来,测评等上线前再加上。”

实质问题:没有测评集你不知道自己改了什么有没有破坏其它能力。cost-query V2 阶段每次改 SKILL.md 都是”凭感觉”,V3 加测评集后发现 V2 末期的”凭感觉”修复有 30% 在破坏别的能力。

正确做法:第 1 周就建至少 5 题的测评集,每次改动跑一遍。第 2 个月扩到 30 题。第 4 个月扩到 50-100 题。


Part 1 完。读完应能回答:“为什么三层架构、为什么 5 个铁原则、什么直接抄什么必须重做、3 个共性误区是什么”。