从OpenAI数学推理突破到24小时复现:手写Python评测流水线
2026/8/28 3:37:45 网站建设 项目流程

最近在 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 SDK:openai>=1.0
  • 辅助库:python-dotenvpandastqdm

版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。如果你使用的不是 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 或做更多数据处理,可以额外安装openpyxl

pip install openpyxl

3.3 获取 OpenAI API Key

前往 OpenAI 官方网站创建账号并生成 API Key,然后把 Key 保存到项目根目录下的.env文件里:

OPENAI_API_KEY=sk-你的密钥

注意:.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( model=model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": question}, ], temperature=temperature, max_tokens=max_tokens, timeout=timeout, ) return resp.choices[0].message.content.strip() except Exception as e: return f"ERROR: {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.096应该算对。简单的方式是先用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

注意:这种归一化只适合实数答案。如果题目答案是分数、根式或字符串,需要额外的规则,比如转换为Fractionsympy表达式比较。

4.4 主流程与报告输出

现在把加载数据、逐题推理、评分、统计准确率的完整流程写出来,并输出一个 JSON 报告。

def load_dataset(path: str) -> list[dict]: """从 JSONL 文件加载题目。""" items = [] with open(path, "r", encoding="utf-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, start=1): question = item["question"] expected = item["answer"] # 简单重试机制:遇到 ERROR 最多重试 2 次 raw_output = "" for attempt in range(3): raw_output = solve_question( question=question, model=model, system_prompt=system_prompt, temperature=temperature, max_tokens=max_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_ok=True) with open(output_path, "w", encoding="utf-8") as f: json.dump(report, f, ensure_ascii=False, indent=2) 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", encoding="utf-8") as f: config = json.load(f) run_evaluation( dataset_path=config["dataset_path"], output_path=config["output_path"], model=config["model"], system_prompt=config["system_prompt"], temperature=config["temperature"], max_tokens=config["max_tokens"], )

然后运行:

python fable_reproduce.py

5.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, temperature=temperature) 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, key=vote_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 或未固定随机种子评测场景设置temperature=0,需要采样时用投票
分数类答案判错字符串直接比较使用分数规范化转换后再比较
本地时区/环境差异依赖版本不一致requirements.txt锁定版本,或使用 Docker
JSON 报告打不开输出目录不存在在写入前用os.makedirs(..., exist_ok=True)

如果遇到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 调用经验还不够,需要从评测设计、数据版本管理、成本控制和安全合规多个角度持续打磨。希望这篇文章能成为你动手实践的起点。如果你照着跑的过程中遇到问题,欢迎在评论区留下具体的报错信息和你的配置,大家一起研究。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询