关于 andon
andon 是面向分析人员和 AI Agent 的确定性验证命令行工具与 MCP 服务,可检查已完成的报告和工作簿。它通过算术将报告结果与源数据及内部一致性进行核对,不依赖另一个大模型。
主要功能
- 01将报告声明与源数据核对
- 02检查内部总计和百分比
- 03验证架构与 Excel 完整性
- 04标记需人工复核的合理性问题
- 05归类不同版本工作簿的变更
适用场景
- 01发送前验证 AI 起草的报告
- 02无需规格文件即可检查工作簿
- 03在 CI 或提交前设置报告检查关卡
- 04审查不同版本工作簿的变更类别
MCP 相关
可选的 MCP 服务向 MCP 客户端提供 `run`、`inspect` 和 `diff` 工具,并返回结构化验证结果。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
andon
数字对不上时,立即拉停生产线。
andon 会重新核对已完成分析中的数字——无论是 AI Agent 刚起草的报告, 还是同事“快速更新”过的工作簿——并将其与原始数据及彼此进行核对。它使用算术运算,而非另一个大模型:对账、 内部一致性、架构契约和 Excel 工作簿完整性,都以精简的 YAML 规范记录下来,并通过退出码强制执行。
截图展示的是真实输出。运行示例位于
examples/quarterly-report/,由脚本生成,其中植入了我在实际报告工作中反复遇到的问题:从过期快照中获取的计数、手动覆盖的收入总额、合计为 101.2 的占比、数据刷新后无人更新的总计行、一个 #REF!,以及以文本形式存储的运费数字。
为什么要做这个
我是一名工业工程师,负责构建和运行运营报告——交付 KPI、 预测准确率、库存分析。过去两年里,我身边越来越多的报告初稿由 AI Agent 撰写。它们速度快、不知疲倦,却会自信地犯错,而且错误的方式连疲惫的人类都不会犯:筛选条件悄悄漏掉已取消的订单、合计几乎达到 100% 的百分比列、组成部分被编辑过三次却依然原封不动的总计行。
常见的做法是让第二个模型审查第一个模型。我认为这不是合适的工具。539 行数据是否确实合计为 257,060.48,不是主观问题,再强大的模型也无法让主观判断成为合适的验证手段。
制造业几十年前就解决了这个问题。在丰田生产线上,任何发现缺陷的工人都可以拉动一根绳子——andon——让生产线停下来,直到问题查明。对应的机器机制叫作 jidoka:机器检测到异常状况时会自动停止。这个工具就是电子表格和报告的这根绳子:在“分析已写好”和“分析已发送”之间设置一道小巧、确定性的关卡。
铁律
andon 的行为受到约束,因此更值得信任。以下规则由代码强制执行,而不只是写在这里:
- 只有算术检查能让构建失败。 启发式检查(分布偏移、 合理性边界)可以发出 REVIEW 标记;即使有缺陷的检查试图让引擎报 FAIL,引擎也不会允许。
- 绝不默默背书。 每份报告都会列出读取了什么、跳过了什么,以及哪些工作表从未检查(报告将此称为诚实区块)。PASS 仅涵盖列出的断言,不代表其他内容。
- 无法运行的检查会记录为发现项,而非通过。 文件缺失、范围无法读取、数字列中出现文本——运行会继续,检查会记为 ERROR,退出码也会非零。“未验证”绝不能被理解为“没问题”。
- 从设计上只读。 代码中不存在写入数据的路径。
安装
pip install andon-verify
PyPI 发行包名为 andon-verify(andon 这个名称已被占用);命令和导入名仍为 andon——andon run ...、import andon。
从源码安装:pip install git+https://github.com/gulmezeren2-byte/andon。
快速开始
指定数据和声明,让 andon 进行核对:
# andon.yaml
version: 1
sources:
orders: data/orders.csv
report: out/weekly.xlsx#Summary
checks:
- name: no dropped orders
reconcile.row_count:
left: { source: orders, where: "status != 'cancelled'" }
right: { source: report, cell: B4 }
- name: revenue adds up
reconcile.sum:
column: revenue
left: { source: orders, where: "status != 'cancelled'" }
right: { source: report, cell: B6 }
tolerance: 0.5%
- name: totals row is honest
internal.total_row:
source: report
parts: B10:B21
total: B22
tolerance: 0.01
- name: workbook is mechanically sound
excel.integrity:
source: report
andon run andon.yaml # human-readable verdict
andon run andon.yaml --json # full machine-readable report
andon inspect out/weekly.xlsx # integrity-scan a workbook, no spec needed
andon init # write a commented starter spec
也可以试试此仓库中的篡改示例:
git clone https://github.com/gulmezeren2-byte/andon
cd andon/examples/quarterly-report
andon run andon.yaml
检查内容
| 类别 | 检查项 | 回答的问题 | 会失败吗? |
|---|---|---|---|
reconcile | row_count、sum、aggregate、group_sum、keys | 报告与其来源数据一致吗? | 会 |
internal | total_row、percent_sum、recompute | 报告与自身一致吗? | 会 |
schema | columns、unique、not_null、allowed_values、date_continuity | 数据的结构是否符合大家的预期? | 会 |
excel | integrity | 工作簿在技术层面是否完好?(#REF!、值覆盖公式、数字存储为文本——包括 1.234,56 这种格式——隐藏行、外部链接) | 出现错误单元格时 |
plausibility | bounds、new_categories、mean_shift | 在任何人信任这些数据之前,是否应由人工查看? | 不会——最多标记为 REVIEW |
完整参数参考及示例:docs/checks.md。
退出代码和 CI
退出代码是一项约定:
| 代码 | 含义 |
|---|---|
| 0 | 所有检查均已通过 |
| 1 | 至少有一项 FAIL(使用 --strict 时,出现 REVIEW/ERROR/未运行任何检查也会返回此代码) |
| 2 | 没有失败项,但出现了 REVIEW 标记 |
| 3 | 未验证任何内容——某项检查无法运行,或所有检查均已跳过 |
| 4 | 规范本身有问题 |
作为 GitHub Action 使用——只需一行,判定结果就会显示在作业摘要中:
- uses: gulmezeren2-byte/andon@v1
with:
spec: reports/andon.yaml
args: "--strict"
或者,在任何运行器中直接运行:
- run: pip install andon-verify
- run: andon run reports/andon.yaml --strict --md verdict.md # --md → a PR-comment-ready verdict
作为 pre-commit 钩子使用
在有问题的报告离开你的机器之前,阻止提交:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gulmezeren2-byte/andon
rev: v0.6.2
hooks:
- id: andon
args: ["reports/andon.yaml", "--strict"]
在 AI Agent 中使用 andon
andon 专为 Agent 驱动而构建,而不是用来包含 Agent:
--json会输出包含稳定字段名称的完整报告;仅凭退出代码就足以做出是否放行的判断。- 错误消息会指出涉及的数据源、列和工作表,因此 Agent 可以自行修复规范,而不必猜测(“未找到列 'Revenue'。现有列为:region、share_pct、revenue”)。
skills/verify-with-andon/提供了适用于 Claude Code 和兼容运行环境的技能,用于教会 Agent 遵循规范:完成任何分析草稿后,编写规范、运行 andon 并报告判定结果——还包括一条规则:若要通过放宽容差使检查通过,必须明确声明,绝不能悄然更改。- MCP 服务。 运行
pip install 'andon-verify[mcp]'并启动andon-mcp,即可向任何支持 MCP 的运行时提供三个工具:run(执行规范)、inspect(不使用规范扫描工作簿完整性)和diff(判定两个版本之间的更改)。Agent 获得的判定结果与人工看到的结构相同——而不是还需要再解析回来的文字说明。
// e.g. Claude Desktop / Claude Code mcp config
{ "mcpServers": { "andon": { "command": "andon-mcp" } } }
没有本地 Python?Dockerfile 可以构建相同的服务:
docker build -t andon . && docker run --rm -i -v "$PWD:/work:ro" -w /work andon。
我的工作原则是:编写分析的 Agent 也要编写规范;只有当 andon run 返回 0,或人工确认了它标记的每一项问题后,分析和规范才算完成。
andon 不是什么
- 不是数据质量平台。 Great Expectations 和 pandera 会在代码中、通常针对数据仓库,在流水线内部验证数据。andon 验证的是已完成产物中的声明——报告是否与其来源一致;并将 Excel 视为一等公民,因为在大多数公司,分析实际都在 Excel 中进行。
- 不是大模型评估器。 它不会为模型输出打分,而是重新推导数字。
- 不能替代阅读报告。 它能排除一类机械性错误,让人工审核能够专注于判断。
局限性,如实说明
- 公式单元格需要缓存值。 andon 读取 Excel 上次计算的值。由某个库生成、且从未在 Excel/LibreOffice 中打开过的工作簿,其公式没有缓存值;andon 不会猜测,并会明确报告这一点。
where筛选条件是 pandasquery()表达式。 它们功能强大,这意味着规范也可能包含与任何查询相同的错误。规范就是代码——应像审查代码一样审查它们。- 启发式检查有意允许误报。 因此它们不能导致构建失败。
- 尚未测试中等规模文件以外的数据规模。 日常运营工作簿和 CSV(数十万行)都没问题;还没人针对 10 GB 的 parquet 文件做过基准测试。如果你测试了,请告诉我出了什么问题。
- Parquet 数据源需要执行
pip install 'andon-verify[parquet]'。
CSV 编码、分隔符和数字标点格式
默认情况下,CSV 数据源按 utf-8 读取,并会去除 Excel 的 BOM,避免其污染第一列名称。如果文件不是 utf-8 编码——从 Excel 导出的土耳其语文件通常使用 ; 分隔符、cp1254 编码,并将金额写成 1.234,56——请将数据源指定为映射,而不是单独的路径:
sources:
sales:
path: data/satis.csv
encoding: cp1254 # default: utf-8-sig
delimiter: ";" # default: ","
decimal: "," # default: "."
thousands: "." # default: none
如果编码有误,andon 会指出这一点,并说明可能的原因——它不会悄悄把列名乱码,然后再“验证”它们。
后两项比看上去更重要。不设置它们,1.234,56 就会被读取为文本,而文本列是数值检查无法检查的列。andon 会告诉你这一点,而不是通过检查——见下文——但声明标点格式,才能让该列可以接受检查。
无法读取数据时,检查会怎么处理
每份报告的结尾都有相同的一句话:PASS 并非对分析的主观判断,而是对这些声明进行的算术验证。 因此,检查必须区分粗心实现会混为一谈的两种情况:
- “我比较了它们,结果没问题”——PASS。
- “我没有可供比较的内容”——绝不会是 PASS。
因此,未找到数值的合理性检查会明确说明这一点,而不是报告有零个值超出范围:
REVIEW bounds no numeric values in tutar; nothing was compared against the bounds
如果一列中只有部分数据可读,判定结果会附带分母,因为关于成功解析的一半数据的陈述,并不等同于关于你提交的数据的陈述:
REVIEW bounds tutar stays within bounds, but only read 2 of 4 value(s) of tutar
每项此类检查都会在证据中报告 values_checked、values_total 和 values_skipped,因此覆盖情况也可由机器读取。pandas 在读取时转换为 NaN 的值——n/a、-、#N/A——与其他值一样计为跳过;它们在简单的强制转换计数中不可见,这正是它们过去会被忽略的原因。
根据数据仓库查询进行验证(DuckDB)
数据源可以是 SQL 查询,而不是文件——这样你就能根据 BI 工具读取的同一份数据核对报告,而不仅仅是根据 CSV。执行 pip install 'andon-verify[duckdb]' 后,在数据源前加上 duckdb: 前缀,DuckDB 就会运行该查询(可在查询中读取 CSV、parquet、JSON 和 .duckdb 文件;相对路径相对于规范解析):
sources:
# the report's headline number
report: out/q2.xlsx#Summary
# the same number, straight from the raw data via SQL
truth: "duckdb:SELECT SUM(revenue) AS rev FROM 'data/orders.parquet' WHERE status='shipped'"
checks:
- name: revenue matches the warehouse
reconcile.sum:
column: rev
left: { source: truth }
right: { source: report, cell: B6 }
tolerance: 0.5%
对比两个工作簿版本
“有人编辑了工作簿——究竟改了什么?” 对 .xlsx 执行 git diff 只会得到一堆无用信息,而比较电子表格的工具会平铺显示每个变更的单元格。andon diff 会对每项变更进行分类,因此新的 #REF! 不会被重新设置格式的日期掩盖:
andon diff last-week.xlsx this-week.xlsx
andon diff v1.xlsx v2.xlsx --tolerance 0.5% # hide numeric moves below 0.5%
andon diff v1.xlsx v2.xlsx --json # machine-readable
cell change before → after
Summary!B9 new_error 43.8 → #REF!
Summary!B4 numeric 261,687.57 → 266,687.57 (+5000, +1.91%)
新的错误单元格会单独标出;数值变化会附带差值和百分比。
退出代码:0 = 没有实质性变化,1 = 出现新错误(或使用 --strict 时有任何变化),2 = 有变化但没有新错误。
路线图
自 0.1 版以来已发布:GitHub Action 和 pre-commit 钩子、DuckDB 数据源、andon diff、MCP 服务、CSV 编码和分隔符控制,以及 JSON/JSONL 数据源。
近期计划,按顺序排列:
- 为数据表提供行级差异比较,而不只是逐单元格比较
--watch模式:当数据源在磁盘上发生变化时,重新运行规范
暂无计划:仪表板、定时运行器,以及在验证器内部加入任何由大模型驱动的功能。 验证器将保持确定性;这正是重点所在。
本项目的构建方式
我负责设计检查项、确定语义并审查每一行代码;我大量使用 AI Agent(Claude Code)来提高实现速度,提交说明也如实注明了这一点。如果你对此介意,请先看看 tests/:测试套件会构建真实的 CSV 和 XLSX 测试数据,不使用模拟对象,而这就是约定。测试并不在意代码是谁输入的。
相关项目
- opsaudit —— 同样的理念,但更进一层:运营分析会审计自身的数据,而不是盲目信任它们。
andon会根据数据源检查已完成的报告;opsaudit则会计算指标,并内置诚实性检查。
更多由 Eren Gülmez 制作的工具。
许可证
MIT — Mehmet Eren Gülmez
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。