开源 Python 3.9+ 零依赖 单文件状态 Apache-2.0 本地 CLI MCP 可接入

会自我证伪的
建模助手

它不承诺"算得对"。它承诺:你分得清哪一句是真的—— 每条结论都有状态、有证据、有一条能重算出它的命令。

TroubleSolver 是一个开源的、会自我证伪的建模助手。 它先引导你把问题问全(12 维度《问题说明书》+ 卡点校验),再科学建模, 再主动搜自己的反例(七条可执行验证协议 P1–P7);交付数字零手写、逐条可复算。

所有"AI 建模"工具都在解决怎么写出来(调 API 谁都会,这片是红海, 还跟学术诚信打架)。TroubleSolver 解决的是另一个问题: 怎么知道它是对的。 差异化不在生成能力,而在两样东西——验证协议库(主动去找自己的反例) 和可追溯台账(把"可信度"变成可查询的字段)。

7 条验证协议,已是可执行检查器
4 个异构案例,引擎零改动(库存随机 / 设施覆盖 / 信贷风控 / 渔业资源)
60 项测试,含一组"故意做错看会不会放过去"的负例
100%交付数字来自脚本,零手写

需求来源

每一个功能,都对应一次真实的翻车

这不是从方法论推演出来的工具,是踩完坑之后把补丁固化下来的产物。 下面六条都真实发生过,每一条都能直接变成一条产品需求。

真实踩坑本质变成哪个功能
3° 步长扫描,整段跳过宽仅 2° 的违例带,输出"恒成立"的假结论 离散采样会系统性骗人协议 P2 细步重扫 + 对抗构造
粗网格把宽度不足 0.5% 的盲区窄峰误报为 0 极值落在窄峰上,固定网格取不到协议 P3 连续复核(报区间)
自建仿真的虚拟时间差点被填进正式测试的同一张表 数据口径没人管协议 P6 口径声明 + 禁止混用
结论里的数字无法复算,只能靠嘴说 数字与脚本脱钩协议 P7 台账对账:数字必须由脚本产出
图里落纸字号只有 3~4 pt、中文字体缺字形变方框 交付物质量没人守交付自检(页数 / 字号 / 字形 / 脱敏)
交付物里分不清哪些结论可信、哪些是猜的 结论没有状态台账状态机(已证 / 已证伪 / 待重验)

思维导图

一张图看懂 TroubleSolver

中间是它要解决的问题,左右六条主干分别是:为什么做、怎么记账、什么不做, 以及协议、流程、交付物。点任意主干可跳到对应章节,鼠标悬停会高亮该分支。

读法:主干 = 产品的一个侧面;叶子 = 该侧面的具体条目 颜色:蓝=协议 · 青=台账 · 橙=边界 · 紫=流程 · 棕=交付 · 灰=取舍

快速开始

怎么用:一条命令跑通一个闭环

核心闭环不需要 API key、不需要数据库、不需要服务端 —— 一个案例 = 一个文件夹, 状态全在文件里,看得见、改得动、能回滚。 只有可选的 LLM 编排(tsolve agent)才需要一个 key,且只从环境变量读,代码里搜不到。
它还能被雇进任何项目:bash install/hire.sh <你的项目> 会投递岗位说明书并接好 MCP 工具(6 个), 宿主直接判断它这次干活过没过(exit code → isError)。

拿到代码,跑起来

不装也能跑(把 src 加进模块路径即可);想用 tsolve 这个短命令就本地安装。

git clone <repo> && cd trouble-solver # 方式一:免安装 PYTHONPATH=src python -m troublesolver.cli --help # 方式二:装成命令(开发模式) pip install -e . tsolve --help

先写《问题说明书》——这是卡点①

生成模板 → 填 12 个小节 → 机器校验。不通过就不许进入建模: 因为 90% 的返工都源于"问题没问全",而不是"算错了"。

tsolve charter new mycase/charter.md # 生成 12 节模板 # …… 填:现象/目标/决策/约束/数据/成功标准/时间尺度/相关方/ # 已有尝试/边界/风险与底线/口径表;【未知】项必须给出处理计划 tsolve charter check mycase/charter.md # 卡点①:不通过则退出码 2

备好四件套,然后 tsolve solve

求解脚本只负责算,把数字全部写进 out/metrics.json; 报告模板只负责说,数字一律写成占位符。二者由工具焊在一起。

tsolve solve mycase # 形式化 → 基线 → 建模 → 证伪 → 台账 → 交付 tsolve audit mycase # 临时目录独立重跑 + 逐数字对账 + 状态漂移检查 tsolve ledger check mycase/ledger.json # 只查台账(快)

把结论钉在台账上

每条结论一个条目:状态、方法、跑过哪些协议、口径、证据文件、重算命令、依赖。 报告只能引用 verified 的条目。

tsolve ledger show mycase/ledger.json # 看全部结论与状态分布

案例目录约定(一个文件夹说清一切)

文件角色谁看
charter.md问题说明书(阶段 0 产物,卡点① 凭据)业务方 / 决策者
solve.py求解脚本:唯一的数字出口任何复现的人
ledger.spec.json结论清单(声明式:id / claim / 协议 / 口径 / 依赖)复核者
report.template.md报告模板,数字只能写占位符决策者
falsify.template.md证伪记录模板(七条协议逐条留痕)复核者
expected.json冻结的关键数字(golden 文件)机器
out/metrics.json脚本产出,不入库机器

跑起来长这样(真实输出)

$ tsolve solve examples/newsvendor_inventory 【阶段 0 形式化】问题说明书:通过 【阶段 1-2 基线与建模】求解脚本已运行,数字出口 out/metrics.json 基线 Q0=150 件,周期望利润=1007.35 元(键 baseline) 【阶段 3-5 证伪与交付】台账 9 条,状态分布 {"verified": 8, "refuted": 1} 已生成 report.md 已生成 falsify.md 闭环通过:报告/证伪记录里的每个数字都来自 out/metrics.json(P7) $ tsolve audit examples/newsvendor_inventory 【阶段 6 复核】已在临时目录独立重跑 solve.py 逐数字对账 41 项 台账 9 条,状态分布 {"verified": 8, "refuted": 1} 复核通过:数字可复算、结论状态未漂移

产品本体

七条验证协议

不是提示词技巧,而是可执行、带判据、能自动跑的检查。 其中 P2 / P3 / P5 是"主动去找自己的错",也是真正的门槛—— 它们要求的不是算力,是对抗自己的意愿。

#协议判据来历(实战翻车)
P1双路径交叉 同一结论用两种独立方法实现(解析式 / 数值 / 仿真),相对误差 < 1e-9;不通过就查哪一路写错,两路都进台账 闭式解与半平面交互相印证
P2细步重扫 凡出现"恒成立 / 一定 / 总是 / 从无例外":步长细化 ≥10× 重扫,并主动构造最坏构型 3° 步长跳过宽 2° 的违例带,得出"恒成立"的假结论;0.1° 细扫后结论被推翻
P3连续复核 离散网格算出的极值必须用连续 / 自适应搜索复核,报区间,禁止报单点 网格峰值 vs 连续上确界只差 0.0001,但固定网格会把"很小的正数"误报成 0
P4极限与量纲 参数 → 0 / ∞ / 临界值时须退化为已知结果;量纲自洽 临界值处判据恰好取等,可用于反查公式
P5反例搜索 主动搜使结论翻转的参数区域,报"翻转边界";找不到也要写明搜索范围与未覆盖区域 外环相位偏移半步,就能让"零盲区"变成相反结论
P6口径声明 每个数字标:来源 / 定义 / 适用边界 / 能否外推;有分歧的两套都算,禁止相减 虚拟时间与官方测试数字量纲不同,差点进同一张表比较
P7台账对账 对外每个数字都能由一条命令从原始数据重算,禁止手抄 所有统计值由脚本产 JSON,报告读 JSON

「未触发」要写出来

没出现"恒成立"类断言,不等于不用检查——是检查了才发现不需要 P2。 留空 ≠ 通过,留空只是没人知道你做没做。

「未覆盖」要写出来

搜不到反例时,写清搜索范围与没覆盖的区域, 而不是写"未发现反例"。

「已证伪」要留痕

删掉错误结论等于让同一个错误再来一次。 台账里的 refuted 是资产,不是污点。

第二个护城河

可追溯台账:把"可信度"变成字段

AI 建模最大的信任问题不是"算错",而是分不清哪句是真的。 台账把这件事变成一个可查询、可校验、可传播的字段。

new→ verifying→ verified ✓ 可被引用  │ 上游改动 →  stale 作废待重验  /  refuted 已证伪(留痕,不删)

一条台账长什么样

{ "id": "C-017", "claim": "环覆盖证书在 (ρ=980, n=8) 下成立,最大未覆盖距离 970.03 m", "status": "verified", "method": "解析不等式 + 网格枚举(0.1° 细扫)", "protocols": ["P1", "P3"], "caliber": { "定义": "d_max = 环上相邻站角平分线处的最坏距离", "适用边界": "R=1800, r_min=1000;ρ>r_min 时不成立", "可外推": false }, "evidence": ["out/metrics.json", "report.md"], "recompute": "python solve.py", "depends_on": ["C-005", "C-007"] }

对账规则(缺一即报错)

  • 证据文件存在——不是"我记得有"
  • 有重算命令——没有重算命令的结论等于口说无凭
  • 有口径定义——同一个数字换个口径可能是另一个意思
  • 依赖干净——不得引用已证伪 / 已作废的条目

stale 沿依赖链传播

改一个上游参数,invalidate() 会把这条 及其全部下游一起置为 stale。机器告诉你哪些结论已经过期, 而不是靠人回忆"这条是不是也受那个参数影响"。

为什么 audit 必须在临时目录重跑:同目录重跑留了一个隐蔽的作弊面—— 上一步的产物还在原地,"重跑"可能只是读了上次的结果。复制到临时目录、 让 out/ 不存在再跑,才是真正的从原始数据重算一遍。

P7 的执行体

数字零手写:不是靠自觉,是靠跑不过去

"报告里的数字必须来自脚本"这句话,如果只写在文档里,等于没写。 TroubleSolver 把它做成构建期硬失败:模板里只能写占位符,取不到值就报错退出, 连留空的机会都没有。

模板里这样写(不允许出现任何手写数字)

建议每周备货 {{m.result.recommend_Q}} 件, 相对基线多 {{m.result.gain_vs_baseline_per_week|.2f}} 元/周 (+{{m.result.gain_vs_baseline_pct|.1f}}%), 失效边界:促销概率 ≥ {{m.P5_counterexample.flip_point|.0%}}

渲染出来的成品

建议每周备货 133 件, 相对基线多 50.85 元/周 (+5.0%), 失效边界:促销概率 ≥ 25%

格式交给 Python(.2f / .1% / ,.0f), 数字交给 metrics.json,人只负责写字。

三个容易漏的后门(都已堵上)

  • 口径字段里的数字:caliber.适用边界 常写"概率 ≥ 某值即失效", 若只渲染 claim,口径就成了手抄的后门 → 递归渲染整棵树
  • 错别字会静默漏过:占位符路径原本只认 ASCII, {{m.需求}} 这类写错会既不报错也不替换 → 改为匹配任意路径字符
  • 注释里的示例:模板开头的说明注释写了占位符示例,会被当成本尊去找值 → 渲染前先剥离注释,注释也不进交付物

还有个隐蔽的坑

JSON 键名不要带点号。 写成 profit_band_0.1pct,占位符路径 m.P3_continuous.profit_band_0.1pct.0 就会被点号切断,渲染期直接报"取不到值"。改成 profit_band_0p1pct 即可。

这不只是洁癖:它会在跑的时候炸出来,而不是在交付之后被人发现。

泛化

引擎不认识你的领域,只认识"结论类型"

这是它敢说"能解决各种各样的难题"的全部底气:协议不是写在提示词里给人看的文字, 而是能跑的检查器。引擎只认结论类型——等式、极值、全称断言、最优性—— 而"这类结论该怎么验"这件事与领域无关。把它结构化,泛化就成立了。

结论类型必跑它防的是什么
等式P1单条推导写错 → 双路径交叉
极值P1 + P3极值在窄峰上,网格取不到 → 连续复核报区间
全称断言P2 + P5粗采样整段跳过窄违例带 → 对抗搜索 + 翻转边界
覆盖P2 + P3 + P5同上,且最坏点常落在边界上
最优性P1 + P3 + P5局部最优冒充全局最优
稳健性P5扰动下结论翻转而没人知道
统计量P6口径混用(数字可复算由 P7 兜底)
极限P4收敛了,但不退化到已知结果
口径相关P6不同口径的数字被直接相减

加一个全新领域的难题:三步

tsolve charter new mycase/charter.md # 1 填 12 节(卡点①) # 2 写两个文件: # solve.py 数字只出口到 out/metrics.json # verify.py 把"可检查的对象"暴露成钩子 # def cover_margin(x): # return nearest_gap(x, stations) - R # 3 声明每条结论的类型与要跑的检查: # ledger.spec.json # { "kind": "全称断言", # "checks": [{"protocol":"P2", # "fn":"cover_margin", # "lo":0,"hi":2000,"base_n":51}] } tsolve agent mycase # 可选:让 LLM 读 charter.md 生成上面第 2~3 步的文件 tsolve solve mycase # 引擎自动跑检查、填台账、渲染报告 tsolve audit mycase # 临时目录重跑 + 逐数字/逐检查对账

证据:四个不同领域的案例,同一个引擎

案例 A案例 B案例 C案例 D
领域零售库存
报童模型
园区设施
步道照明覆盖
消费金融
信贷审批阈值
生物 / 资源
渔业最大可持续产量
数学族随机优化
期望值 · 离散决策
几何 / 全称断言
连续 · ∀ 量词
统计量 / 全称断言
局部窄带 · 口径对照
连续优化 / 全称断言
Schaefer 平衡 · 口径对照
结论类型统计量·等式·极值
稳健性·口径相关
全称断言·极值·覆盖
稳健性·极限
全称断言·极值·统计量
口径相关
全称断言·最优性
口径相关·极限
P2 触发未触发(无全称断言)触发,且自动认出窄带陷阱触发,且自动认出局部窄带违约率超上限触发,且自动认出局部额外死亡带陷阱
台账9 条(8 证 + 1 证伪留痕)7 条(6 证 + 1 证伪留痕)6 条(5 证 + 1 证伪留痕)5 条(4 证 + 1 证伪留痕)
引擎改动零
诚实边界(不能泛化的部分必须说清楚): ① 怎么建模穷举不了——那是方法论与创造力的活,工具只做它的对偶问题"怎么知道它是对的"; ② 钩子要有人(或 LLM)写、有人过目——tsolve agent 能让 LLM 读 charter.md 生成 solve.py / verify.py / 台账,但它们仍走完全相同的引擎闭环,且"钩子暴露得对不对" 最终仍需理解问题的人过目一道;引擎只保证"结论不可作弊",不替人担保"建模一定对"; ③ 搜索无法保证找到任意窄的特征——所以 P2 每次都把搜索分辨率一起报出来, 把"未找到"读作"在该分辨率下未找到",绝不写成"已证明成立"。

防护实测

不是"设计了防护",是"防护真的会拦"

测试里专门留了一组负例——故意把事情做错,看工具会不会放过去。 下面四个是真实输出(覆盖:参数漂移、卡点①、模板占位符、跨领域反例自动抓取)。

① 悄悄改一个参数(价格 25 → 26)

同一个案例重跑,数字必然会变。审计会逐项报出来,逼你显式确认这次漂移是"有意的"。

$ tsolve audit ./case 数字漂移:result.recommend_Q 期望 133,实测 134(容差 1e-09) 数字漂移:result.Q_vs_baseline 期望 -17,实测 -16(容差 1e-09) 数字漂移:result.gain_vs_baseline_per_week 期望 50.846…,实测 47.769… 复核未通过(5 项)

② 用户还没确认口径(卡点①)

问题说明书里 用户确认:否,后面直接不给往下走——因为这里错了,后面全白做。

$ tsolve solve ./case 【阶段 0 形式化】问题说明书:未通过 缺失小节 : 无 已知/未知/假设 : 3 / 4 / 1 口径表 : 4 行(未填完 0 行,判定不一致/需双算 2 行) 用户已确认 : 否 未处理的未知项: 无 结论 : 不通过 卡点① 未通过:问题说明书不完整,禁止进入建模

③ 模板里写了一个不存在的键

不是静默留空,是直接失败——不给"猜一个数字印上去"留任何空间。

$ tsolve solve ./case 已生成 falsify.md 对账未通过(1 项): - 模板 report.template.md 有 1 个占位符无法从 metrics.json 取值:m.nonexistent.key

④ 换一个领域,它自己抓到了真反例(案例 B)

园区步道照明覆盖案例里有一条断言:「实测(光衰后)半径下现状布点仍覆盖全长」。 51 点均匀网格(步长 40 m)判它成立;引擎的对抗搜索在 x=833.333 m 处找到了约 7 cm 宽的照度缺口, 并当场把"网格在骗人"这件事写进结论。

$ tsolve solve examples/facility_coverage 【阶段 3 证伪】自动执行检查 12 条:通过 11,不通过 1 ⚠️ [P2] 实测半径下的覆盖断言:不通过 网格(51 点/步长 40) 最坏裕度 -6.600e+00 → 对抗搜索最坏裕度 6.667e-02 @ x=833.333 ⇒ 断言被证伪;⚠️ 网格给出的是假结论(窄带陷阱:真反例落在网格点之间)
再加上一组常规测试覆盖:空白《问题说明书》必须判不通过、【未知】项没有处理计划必须拦、 标 verified 却没证据 / 没重算命令 / 没口径定义必须拦、引用已证伪条目必须拦、 refuted 条目本身不报错(它是留痕不是错误)、依赖链 stale 传播不影响无关条目。

边界

它不做什么(明确砍掉)

产品文案避免"保证最优",改成"给出可验证的次优解 + 失效边界"。 同样地,功能清单里"不做什么"和"做什么"一样重要。

✓ 做

  • CLI 一条命令跑通单个问题的完整闭环
  • 七条验证协议 + 台账(状态机 / 对账 / stale 传播)
  • 输出报告 + 证伪记录 + 台账 + 一键复现脚本
  • 报告强制包含"局限与失效边界"章节
  • 本地文件为唯一状态,没有隐藏状态

✗ 不做

  • 生成竞赛论文(红海,且与学术诚信冲突)
  • Web UI / 前端 / 账号体系 / 支付
  • 多智能体角色扮演(Persona 花活)
  • 数据库 / 向量库 / 服务端
  • 对接外部业务系统(留给社区)
定位提醒:TroubleSolver 这个名字里的 solver,指的是「求解过程可被验证」, 不是「自动解出答案」——所以它不是"自动解题神器",不替你想模型,也不替你写数字; 它做两件事——卡住不该往下走的(两个人工卡点), 以及把结论钉在脚本上(台账 + 占位符)。

进度

里程碑

阶段交付状态
W1骨架 + 三阶段 + 台账 + 卡点校验 + 一个业务案例跑通全闭环 + 独立复核 已完成(可运行、19 项测试通过)
W2协议引擎化(checks.py 七条可执行检查器)+ 结论类型→必跑协议矩阵 + 三个异构案例(库存 / 设施覆盖 / 信贷风控)+ 检查结论纳入 golden 回归 已完成(44 项测试通过)
W3LLM 编排层(tsolve agent:prompts + charter.md → 案例文件 → 引擎闭环)+ 第 4 个领域案例(生物 / 资源)+ 英文 README 已完成(53 项测试通过)
W4能被"雇"进任何项目:MCP server(6 个工具,跨宿主)+ 岗位说明书 agents/ + 一键 install/hire.sh;WorkBuddy 专家包改为从本仓库构建 已完成(60 项测试通过)
W5报告 / 图表 / 合规产出模块(600 dpi、禁彩色文字、缺字形检测、身份脱敏自检) 规划中
W6技术文章 + 首个公开发布 规划中

仓库结构

trouble-solver/ ├── prompts/ 阶段提示词:00 引导提问 · 20 建模 · 30 证伪 · 50 交付 ├── src/troublesolver/ │ ├── cli.py 命令入口:charter / ledger / solve / audit / agent │ ├── mcp_server.py ★ MCP server(stdio):6 个工具,任何宿主都能把它"雇"进去 │ ├── charter.py 问题说明书 12 节机器校验(卡点①) │ ├── checks.py ★ 可执行检查器 + 结论类型→必跑协议矩阵(泛化核心) │ ├── ledger.py 台账状态机 + stale 依赖传播 + 交付前对账 │ ├── orchestrate.py LLM 编排:prompts + charter.md → 案例文件 → 引擎闭环 │ ├── case.py 案例编排:solve 与 audit │ └── report.py 模板渲染:数字只能从 metrics.json 注入 ├── agents/troublesolver.md 岗位说明书:宿主可读的角色卡(用来"雇"进任何项目) ├── install/ ★ 一键雇佣:hire.sh + 免安装 MCP 启动器 + 各宿主接法 ├── packaging/ WorkBuddy 专家包的定义源 + 构建脚本(仓库是唯一真源) ├── examples/newsvendor_inventory/ 案例 A:零售库存(单 SKU 备货,报童模型) ├── examples/facility_coverage/ 案例 B:园区设施(步道照明覆盖,几何 / 全称断言) ├── examples/loan_approval_threshold/ 案例 C:消费金融(信贷审批阈值,统计量 / 口径相关) ├── examples/fishery_msy/ 案例 D:生物资源(渔业最大可持续产量 MSY,连续优化 / 全称断言 / 口径) ├── tests/ 60 项,含一组负例("故意做错看会不会放过去") ├── docs/ 协议详解 · 台账详解 · 本发布页 └── LICENSE Apache-2.0