
最近在 AI 社区的讨论里“OpenAI 数学推理突破”和“24 小时复现”是两个绕不开的关键词。一边是大模型在数学难题上的表现越来越强另一边是社区里出现了不少轻量级评测框架让普通开发者也能快速复现类似的实验。这篇文章不打算追热点而是把这类“模型评测 实验复现”背后的技术栈拆开讲清楚。你会看到数学推理评测为什么难复现一个实验需要哪些模块怎样用 OpenAI 官方 SDK 搭一条最小可运行的“Fable 式复现流水线”以及项目落地时的常见坑和最佳实践。如果你是搞 NLP、AI 应用开发或者正在做大模型评估相关的工程这篇文章可以给你一套可以照着改的模板。文章里的代码都是独立可运行的片段版本细节我会特别标注你拿到之后按自己的环境微调即可。1. 背景从 OpenAI 数学突破到复现热潮1.1 为什么数学难题成为大模型试金石数学推理是大模型能力评估里非常特殊的一类任务。它不像开放聊天那样可以靠“流畅表达”蒙混过去也不像选择题那样容易靠概率分布碰运气。数学题的答案是确定的推理过程是可以被验证的因此它能直接反映模型的逻辑推理、符号运算和长期规划能力。最近关于 OpenAI 的讨论点主要集中在模型在多个数学基准如 AMC、AIME、MATH 这类比赛级题目上的表现提升。这类题目难度高光靠背训练数据很难答对模型必须真正理解题目条件拆解步骤并完成数值计算。所以当模型能连续解决多道高难度数学题时背后的技术价值就非常高它说明训练阶段引入的推理增强手段是有效的。对我们开发者来说更值得关注的不是“又刷了几个点”而是这些能力能否被复现、被评估、被集成到自己的业务里。这引出了本文的核心评测和复现。1.2 什么是“复现”以及为什么重要在机器学习领域“复现”指的是根据论文、开源代码或官方报告在本地环境重新得到接近的效果。复现的意义不只是验证一个结果更重要的是搞清楚达到这个结果需要哪些条件模型规模、数据格式、提示词、采样参数、评测脚本。过去复现一个实验门槛很高因为依赖环境复杂数据获取麻烦。现在随着各家模型统一提供 API配合像“Codex Harness”这类开源评测工具复现成本已经大幅降低。社区里甚至出现了“24 小时复现某某成果”的工程化玩法本质上就是套用一套流水线加载题目 - 调用模型 - 收集答案 - 自动判分 - 出报告。Fable 就可以看成这类“快速复现流水线”的代称。不同团队实现的框架细节不同但核心模块高度类似。本文用 Python 手写一套最小可运行的版本方便你理解内部原理。如果你在 GitHub 上找到了某个 Fable 复现仓库也可以用本文的思路去阅读和改造它。2. 复现前需要理解的核心概念2.1 数学推理评测的基本流程一个标准的数学推理评测流程包含四个部分题目集带标准答案的数学题通常以 JSONL 或 JSON 格式保存。模型推理把题目组装成 Prompt 发送给模型收集模型输出。答案抽取从模型的自然语言输出中抽取出最终结果。自动判分把抽取结果与标准答案做比对计算准确率。听起来很简单但实际落地时每一步都有细节。比如答案格式有的模型会在回复里写大量推导过程最终结果藏在一段文字里这时必须设计稳定的抽取规则。再比如判分数学答案可能是分数、根式、小数直接字符串比对容易误判需要统一成 “规范化数值” 再做比较。评测流程的稳定性直接影响复现结果。同一个模型、同一个题目如果 Prompt 写法变了准确率可能差好几个百分点。因此工程化的复现项目一定会把 Prompt 写进配置文件保证每次运行使用同一套模板。2.2 测试时计算与思维链大模型解决数学题的常用手段是“思维链”Chain-of-Thought也就是让模型在输出最终答案之前先输出一步步的推理过程。这种做法能显著提高复杂题目的正确率因为模型在逐步推理时每一小步的难度都低于整体题目出错概率被分散了。进一步地很多前沿模型还会在推理阶段做“测试时计算”Test-Time Compute。简单理解就是模型在解码时会对多个推理路径进行搜索或采样然后通过验证器评估哪条路径最可靠。这种方式让模型可以用更多计算量换取准确率但代价是响应延迟变高、Token 消耗变大。在复现评测实验中我们要注意这一点如果只请求一次答案可能无法体现模型的最佳水平。更严谨的评估方式是多次采样用投票或验证模型选出最终结果。本文后面的实战会给出一个最简单的多次投票实现。2.3 自动判分与代码执行验证数学题判分有两种常见路线。第一种是纯文本比对适合答案规范、形式固定的题目。第二种是代码执行验证让模型生成一段代码程序在 Docker 容器中执行并检查输出。第二种方式多用于竞赛编程题或需要复杂计算的题目也是 OpenAI Codex Harness 这类工具的核心思路。在“复现”场景里代码执行验证比文本比对更可靠因为数学题的中间结果往往不容易用正则抽取。但代码执行也带来安全和工程复杂度问题稍后我会在最佳实践里具体说明。3. 环境准备与版本说明3.1 基础环境本文示例在以下环境中验证操作系统Windows 10/11、Ubuntu 20.04/22.04、macOS 都可以。Python 版本建议 3.10 及以上。包管理工具pip 或 conda。OpenAI Python SDKopenai1.0。辅助库python-dotenv、pandas、tqdm。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果你使用的不是 OpenAI 官方服务而是兼容协议的其他平台下面代码里的base_url参数可以让你轻松切换。3.2 安装依赖打开终端创建一个虚拟环境并安装依赖python -m venv .venv source .venv/bin/activate # Windows 使用 .venv\Scripts\activate然后安装依赖pip install openai python-dotenv pandas tqdm如果你想把评测结果导出成 Excel 或做更多数据处理可以额外安装openpyxlpip install openpyxl3.3 获取 OpenAI API Key前往 OpenAI 官方网站创建账号并生成 API Key然后把 Key 保存到项目根目录下的.env文件里OPENAI_API_KEYsk-你的密钥注意.env文件不要提交到 Git 仓库建议在.gitignore中加入.env。运行代码时python-dotenv会自动读取这个文件。3.4 项目目录结构建议按下面的结构组织工程fable_repro/ ├── .env ├── config.json ├── fable_reproduce.py ├── data/ │ └── math_examples.jsonl └── results/ └── (报告输出目录)config.json用来存放模型参数和 Prompt 模板data目录放题results目录放评估报告。把配置和代码分离是保证实验可复现的第一步。4. 写一个“最小 Fable 复现流水线”4.1 定义数据集格式为了演示我构造一个非常小的数学题集包含三道题。实际使用中你可以换成 MATH 数据集、AIME 历年真题或自己的业务题库。data/math_examples.jsonl内容如下{question: 解方程2x 5 15, answer: 10} {question: 计算12 * 8, answer: 96} {question: 计算3 的 4 次方, answer: 81}每一行是一条题目字段包括question和标准答案answer。这种格式易于追加数据也方便流式读取。4.2 实现模型调用模块创建一个fable_reproduce.py先写模型调用函数。这里使用 OpenAI 新版 SDK 的写法自动从环境变量读取OPENAI_API_KEY。# 文件路径fable_reproduce.py import os import re import json import time import random from dotenv import load_dotenv from openai import OpenAI load_dotenv() # 创建客户端如果你的模型使用兼容接口可传入 base_url client OpenAI()调用模型时我建议把 Prompt 模板放到配置里而不是硬编码在代码中。这样调整措辞不需要改代码也能记录每次实验使用的 Promp 版本。def solve_question( question: str, model: str gpt-4o, system_prompt: str 你是数学解题助手。请逐步推理最后用答案输出最终结果。, temperature: float 0.0, max_tokens: int 1024, timeout: int 60, ) - str: 调用大模型求解数学题返回原始输出。 try: resp client.chat.completions.create( modelmodel, messages[ {role: system, content: system_prompt}, {role: user, content: question}, ], temperaturetemperature, max_tokensmax_tokens, timeouttimeout, ) return resp.choices[0].message.content.strip() except Exception as e: return fERROR: {str(e)}这里有几个参数值得说明system_prompt系统提示词决定了模型答题的风格。如果复现某个实验务必和原实验保持一致。temperature采样温度。评测场景建议设为 0减少随机性如果做多次投票可以设 0.7。max_tokens限制输出长度避免推理过程太长导致成本不可控。timeout请求超时时间大模型推理较慢时超时阈值需要适当提高。4.3 实现答案解析与评分模型输出可能是这样的思路... 2x 15 - 5 2x 10 x 5 5我们需要从输出中提取...中的内容。如果模型没有按格式输出可以取最后一行或整体去噪后作为兜底。def extract_answer(text: str) - str: 从模型输出中提取最终答案优先使用包裹内容。 match re.search(r(.*?), text, re.S) if match: return match.group(1).strip() # 兜底取最后一个非空行 lines [line.strip() for line in text.strip().splitlines() if line.strip()] if lines: return lines[-1] return text.strip()评分函数需要处理数值形式差异。比如96.0和96应该算对。简单的方式是先用float()转换再比较def normalize_answer(answer: str): 将答案字符串归一化为可比较的数值。 answer answer.replace(,, ).strip() try: return float(answer) except ValueError: return answer def grade_answer(predicted: str, expected: str) - bool: 判断模型答案与标准答案是否一致。 pred normalize_answer(predicted) exp normalize_answer(expected) return pred exp注意这种归一化只适合实数答案。如果题目答案是分数、根式或字符串需要额外的规则比如转换为Fraction或sympy表达式比较。4.4 主流程与报告输出现在把加载数据、逐题推理、评分、统计准确率的完整流程写出来并输出一个 JSON 报告。def load_dataset(path: str) - list[dict]: 从 JSONL 文件加载题目。 items [] with open(path, r, encodingutf-8) as f: for line in f: line line.strip() if line: items.append(json.loads(line)) return items def run_evaluation( dataset_path: str, output_path: str, model: str, system_prompt: str, temperature: float, max_tokens: int, ) - dict: 运行完整评测流程并保存报告。 dataset load_dataset(dataset_path) results [] for idx, item in enumerate(dataset, start1): question item[question] expected item[answer] # 简单重试机制遇到 ERROR 最多重试 2 次 raw_output for attempt in range(3): raw_output solve_question( questionquestion, modelmodel, system_promptsystem_prompt, temperaturetemperature, max_tokensmax_tokens, ) if not raw_output.startswith(ERROR:): break time.sleep(2) predicted extract_answer(raw_output) correct grade_answer(predicted, expected) results.append({ index: idx, question: question, expected: expected, predicted: predicted, raw_output: raw_output, correct: correct, }) total len(results) correct_count sum(1 for r in results if r[correct]) accuracy correct_count / total if total else 0.0 report { model: model, system_prompt: system_prompt, temperature: temperature, max_tokens: max_tokens, total: total, correct: correct_count, accuracy: accuracy, results: results, } os.makedirs(os.path.dirname(output_path) or ., exist_okTrue) with open(output_path, w, encodingutf-8) as f: json.dump(report, f, ensure_asciiFalse, indent2) print(f评测完成共 {total} 题答对 {correct_count} 题准确率 {accuracy:.2%}) return report这里实现了三层关键能力重试、抽取、评分统计。重试机制能有效避免网络抖动导致的偶发失败保证评测数据完整性。报告 JSON 里包含了每道题的原始输出这在复现排错时非常有用。5. 本地运行与结果说明5.1 配置文件把模型参数和 Prompt 模板写进config.json{ model: gpt-4o, system_prompt: 你是数学解题助手。请逐步推理最后用答案输出最终结果。, temperature: 0, max_tokens: 1024, timeout: 60, dataset_path: data/math_examples.jsonl, output_path: results/report.json }这样就能做到“一次配置多次复现”。下一次模型升级或 Prompt 调整时只需修改 JSON 文件。5.2 在脚本里读取配置在主模块中加入读取配置和调用运行函数的入口if __name__ __main__: with open(config.json, r, encodingutf-8) as f: config json.load(f) run_evaluation( dataset_pathconfig[dataset_path], output_pathconfig[output_path], modelconfig[model], system_promptconfig[system_prompt], temperatureconfig[temperature], max_tokensconfig[max_tokens], )然后运行python fable_reproduce.py5.3 预期输出示例因为我用的示例题目非常简单模型基本都能答对运行后终端输出类似评测完成共 3 题答对 3 题准确率 100.00%生成的results/report.json中包含详细结果{ model: gpt-4o, accuracy: 1.0, total: 3, result: [ { index: 1, question: 解方程2x 5 15, predicted: 5, correct: true } ] }到这里你就拥有了一套能跑的数学评测复现流水线。虽然功能精简但数据流是完整的。你可以把题目集换成任意数学题库就可以评估不同模型在某一组题目上的表现。6. 扩展如何复现更难的数学题6.1 允许多次采样并投票对于高难度数学题单次采样往往不够稳定。更科学的做法是对每题采样 N 次然后用“多数投票”或“独立验证器”决定最终答案。下面是多数投票的简单实现def solve_with_voting(question: str, n_samples: int 5, temperature: float 0.7) - str: answers [] for _ in range(n_samples): output solve_question(question, temperaturetemperature) answers.append(extract_answer(output)) # 简单多数投票 vote_counter {} for ans in answers: key normalize_answer(ans) vote_counter[key] vote_counter.get(key, 0) 1 best_key max(vote_counter, keyvote_counter.get) return best_key投票机制能让评测更接近模型“真实能力”也能减少 prompt 随机性带来的波动。代价是 API 调用次数增加成本上升。6.2 接入代码执行环境当题目涉及复杂运算时文本输出容易出错。更稳的做法是让模型生成 Python 代码然后在本地或容器内执行把 stdout 的结果作为答案。伪代码如下code generate_code(question) result execute_code_in_sandbox(code) # 需要容器隔离 correct grade_answer(result, expected)实际工程中你可以用 Docker 容器限制代码执行权限避免模型生成的恶意代码破坏宿主机。OpenAI Codex Harness 正是这类思路的代表。6.3 使用 Codex Harness 思路做容器化评测如果你是复现代码生成或竞赛题目建议参考 OpenAI 开源的 Codex Harness 工程设计。它会把每个题目跑在独立的 Docker 容器里传入测试用例然后检查输出是否匹配。这种设计有几个好处环境隔离避免污染宿主。可重复执行测试用例可版本化。并行度高能显著缩短大规模评测时间。你不需要完整使用 Harness只要在自己的流水线里加入 Docker 执行模块即可。6.4 手工复现实验的注意事项复现不等同于“调 API 跑完就行”越难的题目越要注意变量控制。比如模型切换了版本、Temperature 从 0 改成 0.2、System Prompt 加了一句“请用中文回答”这些都会影响最终准确率。建议每次复现都记录以下元数据模型名称与版本。Prompt 模板原文。采样参数。题目集版本。SDK 版本。评测日期。是否有重试策略。有了这套元数据任何一次实验结果都能被追测和比对这才是“可复现”的核心。7. 常见问题与排查思路在实际运行中最容易出现的问题集中在 API 调用、格式解析和结果不稳定三类。下表汇总了高频场景问题现象常见原因解决思路AuthenticationErrorAPI Key 未设置或错误检查.env文件和是否执行load_dotenv()RateLimitError请求频率超过账号限制降低并发加入退避重试或提高账号配额TimeoutError模型推理时间过长增大timeout减少max_tokens输出没有提示词约束不够在 System Prompt 中增加格式示例或使用结构化输出多次运行结果不一致temperature不为 0 或未固定随机种子评测场景设置temperature0需要采样时用投票分数类答案判错字符串直接比较使用分数规范化转换后再比较本地时区/环境差异依赖版本不一致用requirements.txt锁定版本或使用 DockerJSON 报告打不开输出目录不存在在写入前用os.makedirs(..., exist_okTrue)如果遇到ERROR: ...被写进结果首先要检查是不是网络或 API 配额问题而不是模型不会做题。建议在评测流程里区分“推理错误”和“网络错误”否则统计准确率会被污染。8. 最佳实践与工程建议8.1 密钥与安全不要在前端代码、公开仓库或日志中输出 API Key。使用.env或密钥管理服务保存密钥。为不同项目创建独立 API Key泄露时可以单独吊销。不要共享服务账号遵循最小权限原则。8.2 成本控制大模型评测是典型的“烧 Token”场景。建议先用 10 道题跑通流程再扩展到全量数据集。对输出长度做上限限制避免模型“写论文式解题”。对相同的题目结果做本地缓存避免重复调用 API。大批量评测时可以把题目分片并行但要注意速率限制。8.3 结果可复现性可复现性要落实到工程文件里用 Git 管理题目集、Prompt、配置和代码。每次实验生成独立报告文件命名包含模型和日期。固定openai包版本API 升级可能导致行为变化。保留原始模型输出不只为最终准确率。8.4 合规与授权使用外部模型和数据集前确认自己有权访问和使用这些数据。公司项目中尤其注意不要将保密数据发送到外部 API。如果数据敏感可以改用私有化部署的模型或本地模型完成评测。9. 总结与下一步本文从“OpenAI 数学突破”和“Fable 24 小时复现”这类话题切入讲清楚了数学推理评测的基本流程也给出了一套可直接运行的 Python 复现流水线。你学会了搭建 OpenAI 数学评测环境用 JSONL 定义题目集调用大模型并抽取答案自动判分并生成报告通过投票、代码执行等方式扩展复现能力排查 API 调用和结果不稳定的常见问题。下一步你可以把题目集换成公开数学基准例如 MATH 或 AIME 真题再对比不同模型在自己题库上的准确率。也可以参考 Codex Harness 的思路把评测流程容器化接入并行执行模块让复现效率提升一个量级。想在业务中真正用好大模型推理能力光有 API 调用经验还不够需要从评测设计、数据版本管理、成本控制和安全合规多个角度持续打磨。希望这篇文章能成为你动手实践的起点。如果你照着跑的过程中遇到问题欢迎在评论区留下具体的报错信息和你的配置大家一起研究。