
最近“OpenAI 连续解出 10 道高难度数学题”的消息在技术社区里讨论度很高。更让人感兴趣的是社区里有人基于 Fable 这类智能体项目做了一次“24 小时复现实验”最终成功复现了其中 5 道题。和单纯转发新闻不同我整理了一份完整的复现过程笔记从环境配置、API 调用、结构化输出到本地验证、失败重试、报告生成把整条链路串起来。文章会用可运行的 Python 示例演示“AI 解数学题”的工程化流程适合对 LLM 应用开发、Agent 工作流、推理评测感兴趣的开发者。1. 背景为什么“数学难题”是衡量 AI 推理能力的试金石1.1 所谓“10 道数学难题”是什么这里所说的“数学难题”通常来自公开数学推理基准或竞赛级题目涵盖代数、几何、数论、组合数学等方向。这类题目有几个共同特点题干短推理长一道题可能只有两三行描述但完整推理过程需要多步演算。不能靠“记忆”回答即使模型在训练数据中见过相似题型也很难通过直接复述得到正确答案。答案可以程序化验证很多题目存在枚举、构造或反推的验证方式这就为“自动复现”提供了基础。过去几年大语言模型在自然语言对话、文本生成上表现很强但在数学推理上经常出现“看起来很有道理、结果却是错的”的情况。原因在于模型每一步都在做概率预测多步推理会累积误差。因此能否稳定解出竞赛级数学题成为检验模型推理能力的重要指标。1.2 OpenAI 的“连续破题”为什么值得关注当模型能连续解出多道高难度题时说明它不只是“背题”而是在多个推理步骤之间建立了自洽的逻辑链路。这一进步背后通常有三类技术支撑更强的推理范式让模型“先想清楚再回答”而不是直接输出答案。代码辅助验证模型先写代码再跑程序验证结果而不是纯靠脑内演算。评测链路自动化把题目下发、模型输出、本地验证、失败重试串成流水线。第三点尤其重要因为“能被自动化验证”是工程技术上的前提。没有这一步AI 解题只是“演示”无法形成可规模化的评测体系。1.3 Fable 在“复现”中扮演什么角色Fable 在这里可以理解为一个智能体式复现项目把题目输入给大模型让模型生成推理过程和候选答案再用一个本地验证器判断答案是否正确。整个过程可以自动跑多轮直到通过验证或超过重试次数。Fable 类项目的核心价值在于自动拆题从题目文件里读取描述统一发送给模型。结构化输出要求模型返回 JSON包含“答案”和“推理摘要”。本地验证对每道题使用对应的验证函数避免模型“自说自话”。失败重试答案错误时带着验证结果反馈给模型让它重新推理。生成报告最后输出哪些题通过、哪些题失败、失败原因是什么。因此即使不关心 Fable 本身这套“模型推理 程序验证 自动复盘”的思路也值得每一个做大模型应用开发的工程师掌握。2. 环境准备与版本说明2.1 运行环境本文示例基于以下环境但思路不限于这些版本操作系统Linux / macOS / Windows建议使用 WSL2 或 macOS 终端语言Python 3.9 及以上依赖openai 库、requests、python-dotenv模型接口OpenAI API兼容 OpenAI 协议的接口也可需要说明的是API 的具体模型名称和参数在不同时期会有调整。本文示例使用gpt-4o系列作为默认模型如果你的账号可用模型不同请把modelgpt-4o改成你实际可用的模型名。2.2 安装依赖建议先创建一个虚拟环境python -m venv .venv source .venv/bin/activate # Windows 使用 .venv\Scripts\activate然后安装必要的 Python 包pip install openai python-dotenv # 如果你计划使用 requests 方式调用也可以安装 pip install requests2.3 配置 API Key在项目根目录创建.env文件OPENAI_API_KEY你的key OPENAI_BASE_URLhttps://api.openai.com/v1注意.env文件不要提交到 Git 仓库建议在.gitignore中加入.env。然后写一个环境检查脚本scripts/check_env.py# scripts/check_env.py import os import sys from dotenv import load_dotenv load_dotenv() def main(): key os.getenv(OPENAI_API_KEY, ) if not key: print([WARN] 未设置 OPENAI_API_KEY 环境变量) else: print([OK] OPENAI_API_KEY 已设置长度:, len(key)) try: import openai print([OK] openai 库版本:, openai.__version__) except ImportError: print([FAIL] 请先安装 openai: pip install openai) sys.exit(1) base_url os.getenv(OPENAI_BASE_URL, ) if base_url: print([OK] OPENAI_BASE_URL:, base_url) else: print([INFO] 使用 openai 默认 Base URL) if __name__ __main__: main()运行方式python scripts/check_env.py预期输出类似[OK] OPENAI_API_KEY 已设置长度: 51 [OK] openai 库版本: 1.40.0 [OK] OPENAI_BASE_URL: https://api.openai.com/v1这一节解决了“环境可用性”问题。接下来进入核心原理拆解。3. 核心链路拆解AI 数学题求解的工程化步骤3.1 分步推理让模型“想清楚再回答”直接让大模型输出答案容易出现“一步错、步步错”。工程上常用办法是拆解推理步骤。在提示词里显式要求模型先复述题目关键条件判断题目类型分步推导最后给出结论。这样做的好处是让模型把“推理过程”作为上下文的一部分而不是直接跳到答案。先来看一个最简单的提示词模板# prompts/solver.py SOLVER_SYSTEM_PROMPT 你是一名严谨的数学解题助手。你的任务是解决用户输入的数学题。 请遵守以下要求 1. 先分析题目类型与已知条件 2. 给出完整的推理步骤 3. 最后输出严格的 JSON格式如下 { answer: 最终数值, reasoning: 推理摘要, confidence: 0.0-1.0 } 注意 - answer 必须是纯数值不要带单位或中文 - reasoning 不超过 200 字 - 如果题目信息不足请在 answer 中返回 INSUFFICIENT_INFO。 这里的关键点是答案格式约束。如果模型输出的是自然语言后续自动化验证会非常困难要求输出 JSON才能被程序解析。3.2 统一调用客户端写一个统一的调用客户端方便后续批量跑题。以 openai 库 1.x 版本为例# core/client.py import json import os from dotenv import load_dotenv from openai import OpenAI load_dotenv() from prompts.solver import SOLVER_SYSTEM_PROMPT class MathSolverClient: def __init__(self, model: str gpt-4o): self.model model self.client OpenAI( api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL) or None, ) def solve(self, question: str, temperature: float 0.0) - dict: 返回 dict包含 answer、reasoning、confidence 等字段。 response self.client.chat.completions.create( modelself.model, temperaturetemperature, messages[ {role: system, content: SOLVER_SYSTEM_PROMPT}, {role: user, content: question}, ], ) content response.choices[0].message.content # 尝试提取 JSON 部分 try: # 有些模型会在 JSON 前后加 markdown 代码块这里做简单清理 cleaned content.strip() if cleaned.startswith(): cleaned cleaned.strip() if cleaned.lower().startswith(json): cleaned cleaned[4:].strip() result json.loads(cleaned) except json.JSONDecodeError: result { answer: PARSE_ERROR, reasoning: content, confidence: 0.0, } return result代码里做了两件事读取.env中的 API Key 和 Base URL对模型返回内容做简单的 JSON 解析兼容避免因 Markdown 代码块而导致解析失败。3.3 本地验证不信任模型的“自信”模型即使给了一个看起来很合理的答案也不代表正确。我们需要针对每道题写一个本地验证函数。下面用一个可运行的基础示例说明。题目在 1 到 100 中有多少个正整数 n 满足 n^2 的末两位是 44这个问题可以通过穷举验证# verifiers/demo_verifier.py def verify_answer(candidate: int) - bool: 验证候选答案是否正确。 题目统计 1..100 中满足 n^2 末两位为 44 的 n 的个数。 if not isinstance(candidate, (int, float)): return False count 0 for n in range(1, 101): if (n * n) % 100 44: count 1 return count int(candidate) if __name__ __main__: # 穷举真实结果 real [n for n in range(1, 101) if (n * n) % 100 44] print(满足条件的 n:, real) print(个数:, len(real)) print(verify_answer(2):, verify_answer(2)) print(verify_answer(0):, verify_answer(0))运行这段脚本会得到满足条件的 n: [12, 38, 62, 88] 个数: 4 verify_answer(2): False verify_answer(0): False真实答案是 4。这里能直观看到本地验证器可以“一票否决”错误答案。3.4 自动复盘把错误反馈给模型如果模型第一次做错了千万不要直接放弃。可以把“本地验证失败”这个结果反馈给它让它重新推理。这种方式类似于“智能体调用外部工具得到反馈”。以下是带重试的求解器# core/solver.py import time from core.client import MathSolverClient from verifiers.demo_verifier import verify_answer RETRY_PROMPT 你的上一次答案经过本地程序验证后被判定为错误。 题目{question} 你的答案{answer} 推理摘要{reasoning} 请您重新推理。注意 - 请重新读题确认题目要求的是“个数”还是“具体值” - 请重新验证边界条件 - 最后仍然按照原定 JSON 格式输出。 class RetrySolver: def __init__(self, client: MathSolverClient, max_retry: int 3): self.client client self.max_retry max_retry def solve_with_verify(self, question: str) - dict: for attempt in range(self.max_retry 1): if attempt 0: result self.client.solve(question) else: retry_prompt RETRY_PROMPT.format( questionquestion, answerresult.get(answer), reasoningresult.get(reasoning), ) result self.client.solve(retry_prompt) answer result.get(answer) if verify_answer(answer): result[status] PASS result[attempt] attempt return result # 等待一下避免触发限流 time.sleep(1) result[status] FAIL result[attempt] self.max_retry return result这段代码的逻辑是第 0 次直接让模型解题如果验证不通过带上错误信息重新问一次最多重试max_retry次最终返回PASS或FAIL状态。4. 完整实战从单题到“30 分钟复现 5 道题”下面把上面的模块串成一个完整流水线。目标是给定一个题目列表自动调用模型、验证答案、输出报告。4.1 创建项目结构math-reproduce/ ├── .env ├── requirements.txt ├── prompts/ │ └── solver.py ├── core/ │ ├── client.py │ └── solver.py ├── verifiers/ │ ├── demo_verifier.py │ └── registry.py ├── data/ │ └── questions.json ├── scripts/ │ ├── check_env.py │ └── run_pipeline.py └── output/ └── report.json4.2 准备题目数据把题目放到data/questions.json{ questions: [ { id: demo_001, type: number_theory, question: 在 1 到 100 中有多少个正整数 n 满足 n^2 的末两位是 44, verifier: demo_verifier }, { id: demo_002, type: number_theory, question: 在 1 到 100 中有多少个正整数 n 满足 n^3 的末两位是 44, verifier: demo_verifier } ] }注意这里demo_002的验证器也是demo_verifier但原验证函数只判断n^2 % 100 44这是不对的。实际项目中不同题目要对应不同验证函数。下面我们改进验证器注册机制。4.3 验证器注册表工程上不建议把验证逻辑散落各处可以用一个注册表管理# verifiers/registry.py from verifiers.demo_verifier import verify_answer def verify_answer_by_question(question: dict, candidate: str) - bool: verifier_name question.get(verifier, ) if verifier_name demo_verifier: return verify_answer(candidate) # 以后可以继续 else if 扩展 raise ValueError(f未知的 verifier: {verifier_name})为了更准确把verify_answer微调成支持“平方”和“立方”两种模式# verifiers/demo_verifier.py def verify_answer(candidate: int, power: int 2) - bool: if not isinstance(candidate, (int, float)): return False count 0 for n in range(1, 101): if pow(n, power) % 100 44: count 1 return count int(candidate)然后在questions.json里增加一个power字段注册表里把它传给验证函数。4.4 主流水线脚本# scripts/run_pipeline.py import json import os from datetime import datetime from dotenv import load_dotenv from core.client import MathSolverClient from core.solver import RetrySolver from verifiers.registry import verify_answer_by_question load_dotenv() DATA_PATH os.path.join(data, questions.json) OUTPUT_PATH os.path.join(output, report.json) DEFAULT_MODEL os.getenv(OPENAI_MODEL, gpt-4o) MAX_RETRY 3 def load_questions(path: str) - list: with open(path, r, encodingutf-8) as f: data json.load(f) return data[questions] def run(): questions load_questions(DATA_PATH) client MathSolverClient(modelDEFAULT_MODEL) solver RetrySolver(clientclient, max_retryMAX_RETRY) report { timestamp: datetime.now().isoformat(), model: DEFAULT_MODEL, total: len(questions), passed: 0, failed: 0, items: [], } for q in questions: qid q[id] question_text q[question] print(f[{qid}] 开始推理...) result solver.solve_with_verify(question_text) # 真实项目中验证器应按题目类型区分 # 这里由 registry 统一处理 answer result.get(answer) try: candidate float(answer) except (TypeError, ValueError): candidate answer # 这里演示直接调用注册表 # 注意demo_verifier 需要 power 参数这里先简化处理 passed verify_answer_by_question(q, candidate) if passed: report[passed] 1 result[status] PASS else: report[failed] 1 result[status] FAIL report[items].append({ id: qid, question: question_text, model_answer: answer, reasoning: result.get(reasoning, ), attempt: result.get(attempt, 0), status: result[status], }) os.makedirs(output, exist_okTrue) with open(OUTPUT_PATH, w, encodingutf-8) as f: json.dump(report, f, ensure_asciiFalse, indent2) print(f\n复现完成共 {report[total]} 题通过 {report[passed]} 题失败 {report[failed]} 题) print(f报告已保存到 {OUTPUT_PATH}) if __name__ __main__: run()这里只是一个示例流水线。实际跑题时需要把verify_answer_by_question扩展成“按题目类型选择不同验证函数”的完整实现。4.5 运行与预期结果运行命令python scripts/run_pipeline.py在你的 API 可用、网络正常情况下会看到类似输出[demo_001] 开始推理... [demo_002] 开始推理... 复现完成共 2 题通过 2 题失败 0 题 报告已保存到 output/report.json注意这里演示的是同一验证器处理两道相似题目。如果模型第二次做错会进入失败分支。4.6 为什么只“复现 5 道”而不是 10 道文章开头提到 Fable 24 小时复现了 5 道这个结果其实很典型。完整复现 10 道题面临的困难通常包括验证器缺失部分题目无法用简单程序验证需要编写复杂判定逻辑。模型答案格式不稳定即使要求输出 JSON某些题目下模型仍会输出多余文字。上下文长度限制多步推理过长时模型可能丢失前文条件。重试次数限制如果一次推理失败重试成本较高24 小时内只能覆盖有限题目。代码执行沙箱问题部分验证代码依赖特定环境部署不一致会影响结果。因此“复现 5 道”背后不是“模型能力下降了”而是“工程链路对每题的可验证性要求不同”。这正是做评测、复现项目时需要关注的地方。5. 常见问题与排查思路5.1 常见报错速查表问题现象常见原因解决思路APIConnectionError网络不通、Base URL 配错检查.env中OPENAI_BASE_URL确认网络环境可访问 API 域名AuthenticationErrorAPI Key 无效或未设置检查.env中OPENAI_API_KEY确认 key 未过期、未泄露RateLimitError请求频率超限增加time.sleep降低并发或提高账号额度返回内容无法解析为 JSON模型输出被 Markdown 包裹或包含多余文字增加清理逻辑或让模型只输出 JSON验证结果为PARSE_ERROR模型输出格式不符合预期在重试时明确提示“必须输出 JSON 格式”本地验证代码报错数据类型不匹配用float(answer)转换后比较并处理异常5.2 模型“看似正确但实际错误”怎么办这是最典型的问题。模型会生成一段看起来很完整的推理但答案却是错的。建议按以下顺序排查确认验证器正确先用穷举或手算验证验证器本身没问题。检查答案精度有些题目答案是浮点数需要设置误差范围。重试时带反馈把验证错误信息写进提示词引导模型重新思考。降低 temperature数学推理场景使用temperature0减少随机性。拆分题目条件对于复杂题目可以先把题目拆成子问题分别推理后再汇总。5.3 重试仍然不通过怎么办如果重试 3 次仍然失败不要盲目增加重试次数。更有效的办法是换一个表达方式重新描述题目在提示词中加入“请用枚举法验证”等指令增加一个“代码生成 结果分析”步骤让模型先生成验证代码再根据代码输出判断答案记录失败的题目手工分析模型是在哪一步开始出错。6. 最佳实践与工程建议6.1 提示词层面固定系统提示词不同的求解任务应该使用不同的系统提示词不要混用。强调 JSON 输出在提示词里直接给出 JSON 示例比只写“返回 JSON”更有效。要求展示推理摘要答案是否有用是一回事推理过程是否合理是另一回事。保留摘要便于事后追溯。重试时提供上下文重试不是简单重复提问而是把上一轮的答案和验证结果一起反馈。6.2 验证器层面优先写程序化验证函数能用枚举、穷举、模拟验证的就不要靠人工判断。验证器要覆盖边界条件比如题目范围是 1 到 100就必须检查 n1 和 n100。验证器失败信息要具体不要只返回False最好返回expected_count和actual_count方便反馈给模型。统一注册管理多道题时用注册表管理验证器避免流水线代码无限膨胀。6.3 工程与安全层面API Key 不要硬编码一律通过环境变量或密钥管理服务读取。不要公开分享 API Key任何公网仓库出现 key都可能导致额度被盗用。准备预算控制批量跑题前先估算 token 消耗设置调用次数上限。保存完整报告每道题至少要记录题目、答案、推理摘要、重试次数、最终状态。失败任务要可重跑报告里保存足够信息后续优化提示词后可以只重跑失败题目。6.4 从“复现”到“评测平台”的扩展方向如果你不想只跑这一个脚本可以把它扩展成一个小的评测平台支持题目文件批量导入支持多种模型对比支持并发控制与限流支持一键生成 Markdown 评测报告支持把失败案例沉淀为回归测试集。这套架构可以从“复现数学题”延伸到代码生成、逻辑推理、数据标注质检等场景。7. 总结与下一步建议这篇文章从“OpenAI 连续解出 10 道数学难题Fable 24 小时复现其中 5 道”这个热点出发完整拆解了 AI 数学解题与自动复现的工程链路环境准备、结构化提示词、统一 API 客户端、本地验证器、失败重试、批量报告。核心思路可以概括为一句话把模型当作推理引擎把程序当作裁判用工程手段保证输出可靠。如果你接下来想继续深入建议从这几个方向入手给现有流水线增加更多真实数学题和对应验证器尝试“代码生成 沙箱执行”的路线让模型自己写验证程序对比不同模型、不同 temperature、不同提示词对复现率的影响把失败题目整理成小数据集做回归测试。环境问题、API 版本、模型名称都可能变化但“生成候选解、程序化验证、失败反馈”这套方法论是稳定的。希望这份笔记能帮你少踩一些坑。