AI解题系统工程闭环:Codex Harness与本地沙箱搭建实践

📅 2026/8/27 7:17:02
AI解题系统工程闭环:Codex Harness与本地沙箱搭建实践
“OpenAI Astra 用 2000 美元拿下 10 道世纪难题”这类标题很容易把讨论引向“数学家的定义是否被改写”。但如果退回到工程视角真正值得拆解的其实是另一组问题一个 AI 解题系统如何把自然语言题目变成可执行任务它在什么环境里运行代码、调用工具并完成验证2000 美元这个成本数字又是由哪些部分构成的。这篇文章不讨论“AI 会不会取代数学家”而是把标题里的几个关键词拆开来看Astra 可以理解为一个 AI 智能体项目的代号Codex Harness 是让智能体安全执行代码的基础设施而“世纪难题”的成败最终取决于评测集、验证脚本和运行日志而不是一两句渲染性的结论。读完这篇文章你会理解这类系统的最小工程闭环并且能在本地用 Docker 和 OpenAI API 搭建一个可运行的 AI 解题沙箱。1. 先拆开“世纪难题”和“2000 美元”这两层信息1.1 新闻结论与工程结论不是一回事媒体报道“数学家的定义被改写了”本质上是把一次实验结论放大成了行业判断。工程上的问题永远是另一套这次解题是否可复现模型生成的每一步是否可追踪答案有没有经过独立验证换一道同类型题目系统还能不能保持同等表现如果模型先给出了错误答案它是否能在执行环境反馈后自动改正。如果只看到“2000 美元”这个数字却没有看到背后的评测集、验证脚本和运行日志很多结论都无法迁移到自己的项目里。反过来面对这类消息时先把“结论性标题”翻译成“工程问题”会更接近真实的技术事实。1.2 “2000 美元”在 AI 评测里到底代表什么2000 美元并不是一个固定的“解题单价”。在一次 AI 解题评测中成本至少可能包括以下部分成本项说明是否容易被忽略模型 API 调用费用每次生成代码、多轮重试、模型读取错误信息都会产生 token 费用否沙箱资源开销Docker 容器启动、代码执行、依赖安装、内存和 CPU 占用是人工复核成本专家阅读题目、检查证明或验证脚本、修正 prompt 的时间是失败重试成本模型生成错误代码、超时、验证失败后重新运行的费用是评测集建设成本把题目形式化为可执行验证脚本的工作量是所以读到“2000 美元拿下 10 道题”这类说法时第一反应不应该是“AI 真便宜”而应该是这个成本里包含了哪些环节用的是哪一档模型实验重复了几次哪些题目是真正首次被 AI 系统解决哪些只是用 AI 重新验证了已有结论。1.3 核心判断被改变的是“执行链路”而不是某个定义从工程角度看AI 解决数学难题这件事真正发生变化的地方不是模型突然懂了数学而是它获得了一条更完整的执行链路模型可以生成代码代码可以运行运行结果可以返回给模型模型可以根据报错或验证结果继续修改直到满足验证条件。也就是说AI 从“生成一段看起来正确的文字”变成了“在一个受控环境里完成任务并看到真实反馈”。这才是值得关注的质变。至于数学家的定义是否被改写更适合留给学术共同体去讨论。工程师能从这次事件中带走的是一套如何设计任务、搭建沙箱、控制成本、验证结果的实践方法。2. Codex Harness 到底解决了什么问题2.1 复杂任务为什么不能只靠一次 API 调用如果只是让大模型直接输出数学题的答案通常会出现以下问题模型容易出现计算错误尤其是多位数运算、代数展开和逻辑推导较长时模型没有可执行环境无法验证自己的中间结果无法自动纠错一次生成不对只能换 prompt 再试复杂任务需要调用外部工具比如符号计算、数论库、穷举程序而模型本身不具备这些能力。所以解决这类任务需要把“模型生成”和“环境执行”拆成两个部分。模型负责规划、生成代码和读取反馈环境负责真正运行代码并返回结果。这也是 Codex Harness 这类执行层存在的原因。2.2 Harness 的职责是“让模型动手”Harness 可以理解为智能体系统的“手脚”。在一个 AI 解题系统中它通常负责以下事情启动一个隔离的容器或虚拟机把模型生成的代码写入工作目录在受限网络和受限权限下执行代码收集标准输出、标准错误、返回码和文件变化把执行结果返回给模型供下一轮修改使用限制资源使用比如 CPU、内存、最大执行时间和磁盘空间。通过这种设计模型不再只是“说答案”而是“做任务”。它生成的每一步都有真实结果作为反馈因此具备自纠错能力。2.3 开源仓库中 harness 的常见组成从公开渠道可以看到OpenAI 在 GitHub 上维护了github.com/openai/codex仓库其中包含与 Codex 智能体及其 harness 相关的实现。这类 harness 的典型组成包括容器镜像定义确定执行环境里的操作系统、依赖和工具执行器负责在容器中运行命令并捕获输出提示词模板告诉模型有哪些工具可用、输出格式是什么、什么算完成沙箱配置控制网络、文件系统权限、资源配额测试与评测脚本用于验证模型输出是否满足任务要求。在一个实际项目中不一定需要完整复刻 OpenAI 的实现但可以参考它的分层思路模型层负责决策工具层提供能力沙箱层负责隔离验证层负责把关。2.4 与裸 API 调用的区别维度裸 API 调用带 Harness 的智能体输入一段用户问题任务描述 可用工具列表输出一段文本代码、命令、文件操作中间反馈无有 stdout、stderr、返回码纠错能力需要人工重试可以自动读取报错并修改安全边界不涉及需要依赖容器和权限隔离适用场景翻译、摘要、问答编程、数学计算、数据分析这个表格可以解释为什么同一个模型在接上 harness 之后能完成更复杂的任务。模型本身没有变变的是它周围的工作环境。3. 把“AI 解数学题”拆成一条工程流水线3.1 任务形式化从自然语言题目到可验证断言AI 解题的第一步不是让模型直接写答案而是先判断“什么算正确”。对于数学题来说通常需要把自然语言题目转成可执行验证脚本。以一道经典数论题为例判断对于所有 1 到 100 之间的整数 n表达式 n^3 - n 是否都能被 6 整除。这个题目本身很容易验证。因为 n^3 - n n(n - 1)(n 1)是三个连续整数的乘积其中必然有一个偶数也必然有一个 3 的倍数所以一定能被 6 整除。但在工程系统中我们不能只依赖模型口述这个证明还要让它通过代码验证并把结果写成机器可判断的格式。形式化之后任务变成让模型写一个 Python 脚本脚本中必须调用check_answer()函数该函数在断言成立时打印PASS否则打印FAIL。3.2 工具选型Python、SymPy、Lean 等各承担什么角色数学题类型不同适合的工具也不一样工具用途适合场景Python通用计算、穷举、数值验证动态规划、枚举、简单数论SymPy符号计算、多项式化简、微积分代数推导、公式化简SageMath高级数论、代数、组合数学研究级数学验证Lean形式化证明人工可读的证明检查定理证明、逻辑推导Wolfram Alpha API数学查询、解析结果需要现成数学引擎的场景在工程实现中最简单的做法是先从 Python 加 SymPy 开始。因为 Python 生态成熟、镜像好构建、模型训练数据充足模型生成代码的成功率也更高。3.3 多轮迭代模型不是一次就能写对即使使用很强的模型也不能保证一次生成的代码完全正确。更可靠的流程是让模型进入“生成—执行—反馈—修改”循环模型读取题目生成第一版代码harness 在容器中执行代码如果代码崩溃或输出不符合格式harness 把 stderr 返回给模型模型读取报错信息修改代码重新执行直到通过验证或达到最大轮次。这种设计的关键在于“反馈闭环”。没有执行环境的模型只能盲猜有了执行环境之后模型的行为就变成了调试者看错误、改代码、再试。3.4 验证层不能只盯着模型的输出文本AI 解题系统最危险的问题是“答案看起来对实际根本没有被验证”。因此验证层至少要做三件事对已知用例跑测试比如遍历题目范围内的所有输入检查代码是否调用了允许的工具集合防止模型用硬编码结果绕过验证对证明型任务由规则脚本或人工检查中间步骤。验证层的设计原则是机器能判断的结论不要交给人工去猜人工只需要处理机器无法判断的部分。4. 本地搭建一个最小 AI 解题沙箱4.1 环境准备在开始之前需要准备以下环境组件版本建议用途Docker20.10 以上提供隔离执行环境Python3.10 或 3.11运行调度脚本OpenAI Python SDK1.x调用模型接口OPENAI_API_KEY在 OpenAI 平台创建模型调用鉴权注意API Key 属于敏感凭证不要写进代码仓库不要分享给他人启动脚本时通过环境变量注入。4.2 目录结构一个最小项目可以这样组织math_sandbox/ ├── Dockerfile ├── runner.py ├── verifier.py └── prompts/ └── divisibility.mdDockerfile定义沙箱镜像runner.py负责调用模型、生成代码、在容器中执行verifier.py负责检查执行结果prompts/divisibility.md存放任务描述便于调整 prompt。4.3 沙箱容器配置Dockerfile的核心目标有两个最小依赖和最小权限。下面这个示例安装了 SymPy并创建了非 root 用户FROM python:3.11-slim RUN useradd --create-home --uid 1000 sandbox \ pip install --no-cache-dir sympy USER sandbox WORKDIR /workspace ENTRYPOINT [python]关键点USER sandbox让容器内代码不以 root 权限运行WORKDIR /workspace给代码执行提供默认工作目录ENTRYPOINT [python]保证容器启动时直接执行传入的 Python 文件。构建镜像docker build -t math-sandbox:latest .如果构建失败常见原因是网络下载依赖超时可以换用国内镜像源或在pip install时指定-i https://pypi.tuna.tsinghua.edu.cn/simple。4.4 调度脚本 runner.pyrunner.py是整个沙箱的调度入口。它做四件事调用模型生成代码、把代码写入工作目录、用 Docker 容器执行、返回执行结果。import json import os import subprocess import tempfile from openai import OpenAI CONTAINER_IMAGE math-sandbox:latest MAX_TURNS 3 client OpenAI(api_keyos.environ[OPENAI_API_KEY]) def generate_code(prompt: str) - str: response client.chat.completions.create( modelgpt-4o-mini, messages[ { role: system, content: ( 你是一个数学计算助手。你的任务是生成 Python 代码求解题目。 只输出代码不输出多余解释。代码必须包含 check_answer() 函数。 ), }, {role: user, content: prompt}, ], temperature0, ) return response.choices[0].message.content.strip() def run_in_sandbox(code: str) - dict: workdir tempfile.mkdtemp(prefixmath-worker-) code_path os.path.join(workdir, solution.py) with open(code_path, w, encodingutf-8) as f: f.write(code) cmd [ docker, run, --rm, --network, none, --memory, 1g, --cpus, 1, -v, f{workdir}:/workspace:rw, -w, /workspace, CONTAINER_IMAGE, python, /workspace/solution.py, ] try: result subprocess.run( cmd, capture_outputTrue, textTrue, timeout60 ) except subprocess.TimeoutExpired: return { stdout: , stderr: TIMEOUT, returncode: -1, } return { stdout: result.stdout[-2000:], stderr: result.stderr[-2000:], returncode: result.returncode, } def solve(prompt: str) - dict: for turn in range(MAX_TURNS): code generate_code(prompt) result run_in_sandbox(code) if result[returncode] 0 and result[stdout].strip(): return { code: code, result: result, turns: turn 1, } return {error: max_turns_exceeded, result: result} if __name__ __main__: with open(prompts/divisibility.md, r, encodingutf-8) as f: task_prompt f.read() payload solve(task_prompt) with open(result.json, w, encodingutf-8) as f: json.dump(payload, f, ensure_asciiFalse, indent2) print(json.dumps(payload, ensure_asciiFalse, indent2))几个关键设计--network none关闭容器网络避免模型生成的恶意代码外联--memory 1g --cpus 1限制资源用量防止死循环拖垮宿主机timeout60控制单次执行最大时间stdout 和 stderr 都截断为最后 2000 字符避免返回给模型的内容过长最大迭代轮次为 3防止模型反复出错产生巨额 API 费用。4.5 验证器 verifier.pyrunner.py只负责执行代码verifier.py负责判断结果是否满足要求。这里只验证两点进程退出码是否为 0以及 stdout 中是否出现PASS。import json import sys def main(payload_path: str) - int: with open(payload_path, r, encodingutf-8) as f: payload json.load(f) if error in payload: print(error:, payload[error]) return 1 result payload.get(result, {}) if result.get(returncode) ! 0: print(execution failed) print(result.get(stderr, )) return 1 if PASS not in result.get(stdout, ): print(answer not verified) return 1 print(verified) return 0 if __name__ __main__: sys.exit(main(sys.argv[1]))使用方式python verifier.py result.json如果最终输出verified说明模型生成的代码在沙箱中真实运行并通过了验证。这个流程虽然简单但它已经具备了 AI 解题系统最基本的闭环生成、执行、反馈、验证。4.6 模型输出示例与预期结果当题目是“验证 1 到 100 的所有整数 n 是否满足 n^3 - n 能被 6 整除”时模型可能生成类似下面的代码def check_answer(): for n in range(1, 101): if (n**3 - n) % 6 ! 0: print(FAIL) return False print(PASS) return True check_answer()在容器中执行后预期 stdout 为PASSreturncode为 0。验证器会输出verified。需要强调这段代码是“模型输出示例”不是固定的标准答案。实际运行中模型可能生成多轮才能得到这样的结果也可能因为代码格式问题导致 stderr 被返回并触发重试。5. 评测与成本如何理解 2000 美元这类数字5.1 成本构成明细在真实的 AI 解题评测里成本远不止“模型调用”一项。以本地这套最小沙箱为例成本仍然包括成本项本项目中包含哪些具体开销控制手段模型 API每次generate_code()的输入输出 token使用便宜模型、限制生成长度、限制重试轮次沙箱资源Docker 镜像拉取、容器启动、Python 解释器运行小镜像、缩短超时、控制并发调试时间写 prompt、调格式、验证器开发先跑最小用例再扩展到全量题目失败重试代码崩溃、格式错误、验证失败后的再生成设置最大轮次、在 prompt 中写清输出格式在生产级评测中还要计入存储、日志、监控和人工复核的成本。所以“2000 美元”如果按 10 道题平均每道 200 美元听起来不多但如果没有说明评测集规模、重试次数和人工投入这个数字就没有工程参考价值。5.2 评测指标不只是正确率一个合格的 AI 解题评测应该同时记录以下指标指标含义为什么重要正确率通过验证的题目数 / 总题目数最直观但容易过拟合任务完成率在预算内完成的任务比例更贴近真实成本平均重试轮次每道题模型修改了几次反映提示词质量和模型稳定性单题平均成本总成本 / 题目数用于预算评估人工介入次数需要人工改 prompt 或验证脚本的次数反映系统自动化程度验证强度是穷举、测试用例还是形式化证明决定结论可信度5.3 为什么一次实验结果不能等同于“能力改写”即使一个系统用 2000 美元解决了 10 道题也需要问几个问题这 10 道题是从全部题目里随机选出来的还是提前挑选过模型是否在训练数据中见过这些题目或相近题目验证脚本是否覆盖了所有关键分支换一个同类题目是否还能同样成功跑 10 次成功率是 100% 还是只有 30%。这些因素决定了实验结果能否推广。学术研究中单个成功案例只说明“存在一种可能路径”而不说明“系统普遍具备这种能力”。工程上做评测时应当把一次成功当作“值得复现的中间结果”而不是“结论”。5.4 工程上控制成本的几种手段要在可控成本内运行 AI 解题系统可以从这些方向入手分层使用模型简单任务用便宜小模型难题才用强模型缓存中间结果同一道题多次执行时缓存模型第一次生成的可用代码限制最大轮次避免模型在错误路径上反复试错提前验证输入合法性在进入模型之前就过滤掉无法任务形式化的题目任务拆解把大题目拆成多个可单独验证的小步骤缩小单次生成范围。成本控制不是省掉验证而是把每一轮运行都变成有效反馈让模型能在有限次数内收敛。6. AI 解题沙箱常见问题排查6.1 问题总览问题现象常见原因检查方式处理建议容器启动失败Docker 未启动或镜像不存在docker images查看镜像先构建镜像并确认服务正常API Key 报错环境变量未设置或 Key 失效检查os.environ和平台控制台重新配置环境变量模型输出包含 Markdownprompt 未限制格式查看stderr或代码文件在 system prompt 中强调只输出代码沙箱内缺依赖镜像中未安装对应库docker run ... pip list修改 Dockerfile 并重新构建执行超时代码进入死循环查看 timeout 日志调整timeout或提示模型避免穷举验证不一致验证逻辑只判断部分条件人工检查result.json补全验证器并重跑6.2 Docker 容器无法启动现象运行docker run时报错比如Cannot connect to the Docker daemon或Unable to find image。原因Docker 服务未启动或本地从未构建过math-sandbox:latest镜像。检查docker version docker images解决先启动 Docker Desktop 或对应的系统服务再执行docker build -t math-sandbox:latest .如果镜像构建成功但容器启动仍然失败可以把docker run命令单独拿出来执行去掉--rm观察完整报错。6.3 API Key 与配额错误现象runner.py报AuthenticationError、RateLimitError或InsufficientQuota。原因环境变量未传入Key 失效账户余额不足或请求频率超过限制。检查echo $OPENAI_API_KEY解决确认环境变量存在在 OpenAI 平台查看账户配额和使用情况不要把 Key 硬编码在脚本中。注意API Key 一旦泄露到公开仓库应立即在平台上撤销并重新创建。6.4 模型输出格式不稳定现象模型生成的内容包含 Markdown 代码块标记比如python导致保存为.py文件后无法直接执行。原因system prompt 对输出格式约束不够强。解决在 prompt 中增加明确说明并在保存代码前做一次清理去掉首尾的代码块标记raw response.choices[0].message.content.strip() if raw.startswith(): raw raw.strip() lines raw.split(\n, 1) if len(lines) 2: raw lines[1]这样即使模型偶尔输出代码块调度器也能自动恢复为纯 Python 代码。6.5 沙箱内缺少依赖现象代码在本地能运行但在容器内报ModuleNotFoundError: No module named sympy。原因Dockerfile 中只安装了部分依赖。检查docker run --rm math-sandbox:latest -c import sympy; print(sympy.__version__)解决在 Dockerfile 中补充依赖并重新构建镜像。如果团队内部有私有镜像源也可以统一通过requirements.txt管理。6.6 超时与无限重试现象容器执行超过 60 秒脚本被timeout中断但模型仍然不修改策略继续生成低效代码。原因题目搜索空间过大模型在穷举路径上陷入死循环。解决在 prompt 中提示模型优先使用数学推导而不是大规模穷举适当降低超时时间让模型更快拿到“执行失败”反馈增加最大轮次限制避免 API 费用无限增长。6.7 验证结果不一致现象verifier.py提示失败但人工检查代码逻辑是正确的。原因验证器只检查了 stdout 是否包含PASS但模型可能输出了多余前缀比如[INFO] PASS或者代码根本没有进入check_answer()函数。解决在 prompt 中约定输出必须精确为PASS或FAIL验证器可以改为“最后一行为 PASS”并忽略空行再配合退出码一起判断。7. 最佳实践与扩展方向7.1 运行前检查清单在把 AI 解题系统从“能跑”推进到“稳定运行”之前建议逐项确认[ ] 容器是否使用非 root 用户[ ] 容器是否关闭网络或严格限制白名单[ ] 是否限制 CPU、内存、磁盘和执行时间[ ] API Key 是否通过环境变量注入[ ] prompt 是否明确输出格式和完成条件[ ] 是否存在最大重试轮次和成本上限[ ] 验证器是否覆盖题目全部必要分支[ ] 是否保留每个步骤的日志方便回溯[ ] 是否对模型输出做了代码块、引号等清理[ ] 是否对代码文件做了敏感信息检查。7.2 安全边界沙箱不是绝对保险Docker 容器提供了隔离但并不意味着绝对安全。对于运行不可信代码的场景还应该考虑使用--network none或防火墙白名单使用只读文件系统只有/workspace可写限制容器允许的系统调用为不同任务分配独立工作目录避免数据串用定期更新基础镜像修复已知漏洞。安全不是某一个配置项而是一组最小权限规则的叠加。7.3 从数学题扩展到真实工程任务数学题最适合作为 AI 智能体系统的第一个基准测试因为它的验证成本低一道题写一个检查函数就够了。但生产环境里的大部分任务比如修 Bug、写报表、配置服务、数据分析验证成本要高得多。扩展路径通常分几步第一步把数学题换成“带单元测试的编程题”第二步让模型可以读取项目中的多个文件第三步加入工具调用比如执行grep、运行pytest、查看 git diff第四步引入人工评审环节让模型输出修改说明而不是直接改代码。每一步都在增加工作环境的复杂度也对应着更长的反馈链和更高的成本。建议从数学题这类闭环任务开始打磨调度、提示词和验证逻辑成熟后再逐步迁移。7.4 给新手的实践建议如果刚接触这个方向不建议一上来就复刻大型 harness。可以先做这样一个小练习用 Docker 构建一个隔离的 Python 沙箱写一个 prompt要求模型生成代码解决一道中学数学题用脚本调用模型、保存代码、在容器内执行让模型读取stderr并自动重试一次最后用验证器判断结果并记录成本。这样跑通一遍之后再去看github.com/openai/codex这类开源仓库的代码结构理解起来会快很多。AI 解题系统的核心不是模型参数而是“模型能够在一个受控环境里获得真实反馈并持续改进”这个工程闭环。理解和掌握这条链路比争论“数学家是否会被替代”更有实际价值。