前阵子技术群里和朋友圈频繁刷到“Opus 5 通关 ARC-AGI-3”的讨论贴。和以往单纯的模型刷榜不同,这次评论区的高赞话题几乎都落在了另一个词上:Harness。有人把它理解成“评估框架”,有人觉得它是“模型外挂”,也有人直接调侃:评测做得越精致,就越看不出模型本身到底有没有变强。考虑到这类消息在社区里流传得比较快,榜单口径在不同渠道也存在差异,本文不打算围绕某个具体模型做能力背书,而是把镜头拉远,从 ARC-AGI 评测模型、Harness 工程化设计、评估可信度三个层面,拆一拆背后的技术逻辑。无论你是做大模型应用开发,还是在做模型选型与测评,这篇文章都能提供一套可落地的分析思路和轻量级评测脚手架代码。
1. 背景:ARC-AGI 与 Opus 5 通关事件
1.1 ARC-AGI 是什么:不是为了考知识点
ARC-AGI 的全称是 Abstraction and Reasoning Corpus,最早由 François Chollet 提出,目的是评估系统在抽象推理方面的泛化能力。普通 benchmark 考的大多是“记住多少知识”或“见过多少题”——训练数据里一旦出现过类似题目,再复杂的题也能靠检索和模式匹配做出来。ARC-AGI 的设计思路恰恰相反:每一道题都是彩色小方格组成的图形推理题,给定若干输入输出示例,让答题者猜出背后的变换规则,再推到新的测试输入上。它不依赖常识库,也不依赖预训练数据里的题目原样,重点考察的是“面对从未见过的模式,能不能推理出规律并泛化”。
所以 ARC-AGI 上的分数,往往被当作“通用智能”的参考指标之一。过去几年人类基线能做到 85% 左右,而 AI 系统的分数长期在较低水平徘徊。这也是为什么网络上出现“Opus 5 通关 ARC-AGI-3”的消息时,会引发剧烈讨论——如果这个消息背后没有特殊工程修饰,意味着推理系统在抽象模式发现上迈了一大步;但这里恰恰也是 Harness 最容易“掺水”的地方。
1.2 ARC-AGI-3 与“通关”消息的两种解读
“ARC-AGI-3”在社区里通常指向难度更高、示例限制更严格或测试集经过加扰的 ARC 评测形态。和公开的 ARC-AGI 基准不同,私有子集无法通过训练语料直接“背题”,相对更能反映推理泛化能力。不过关于 ARC-AGI-3 的具体题目数量、难度分段、评分规则,目前渠道之间信息并不完全一致,部分描述甚至是相互矛盾的。
对“通关”现象,一般有两条技术解读路径:
- 模型推理能力确实变强了。多步思维链、自适应搜索、显式的规则枚举与验证,让模型不再单纯靠“猜”,而是真的在示例中拟合规则并泛化。这种情况下,Achievement 归因于模型架构或训练算法的进步。
- Harness 在背后承担了关键工作。比如用专门的提示词工程把图形转换成中间表示,用外部工具去枚举候选规则,再用一个验证器自动筛选正确变换。模型只负责一小步,其余都是 harness 代码完成的。严格来说,这不是“模型通关”,而是“系统通关”。
判断这两种情况哪种更接近事实,不能只看最终分数。至少要拆开评测日志,看清楚模型到底独立完成了多少步,工具和脚本又替它完成了多少步。
1.3 为什么这次讨论聚焦在 Harness
讨论从“模型分数”转移到“Harness”,本质是大家开始关心评测有效性了。一个评测结果,至少要保证两点:第一,评测环境是可复现的;第二,分数能真实反映被测对象的能力,而不是反映评估脚本本身的能力。
之前的模型榜单也存在 prompt 泄漏、测试集泄漏、人工筛选有利样本等问题,但那时候大家默认问题是偶发的。当 ARC-AGI 这种以“泛化”为核心目标的基准上也出现 harness 高度介入的情况,就会倒逼整个行业重新思考:我们究竟应该在多大程度上信任模型自带的零样本推理,又应该如何区分“模型能力”和“评估系统能力”。这个区分工作在学术上叫能力归因,在工程上叫 build a trustworthy evaluation harness。
2. Harness 到底是个什么东西
2.1 从词源理解 Harness
Harness 的本意是“马具、牵索”,在软件工程里引申为“测试夹具、操纵装置”。开发者熟悉的 Test Harness 一般用来加载测试用例、执行被测对象、收集结果。到了大模型场景,Harness 的含义扩展成“围绕模型推理过程搭起来的一整套外部控制工具”,包含提示词构造、模型调用、工具调用、结果校验、失败重试、资源限制、日志记录等环节。
一个典型的模型评测 harness 大致包含六个模块:
| 模块 | 职责 | 例子 |
|---|---|---|
| 任务加载器 | 读取测试集,统一格式 | JSONL、Excel、数据库查询 |
| 提示词构造器 | 把原始任务转成模型输入 | few-shot prompt、系统提示、多模态输入 |
| 模型调用层 | 并发调用模型接口,处理重试 | OpenAI 兼容接口、本地 vLLM、外部 SDK |
| 工具执行层 | 允许模型调用代码解释器或外部工具 | Python 沙箱、shell 命令、API 请求 |
| 答案校验器 | 判断模型输出是否正确 | 字符串匹配、规则校验、自动评测脚本 |
| 统计与日志层 | 汇总分数、记录中间过程 | 输出 CSV、JSON、数据库、监控看板 |
同一套 harness 设计,既能用来做严肃的学术评测,也能变成不严谨的灌水工具。区分点在于每个模块的“干预程度”。
2.2 Harness Engineering 是一个新工种
Harness Engineering 并不是一个官方技术名,更多是社区对这类岗位和工作的统称。它和 Prompt Engineering 最大的区别是:后者通常只是改文本提示词,而前者要处理的是包括代码沙箱、任务编排、评测数据管理、异步重试、安全校验、结果统计在内的一整套工程系统。
实际工作里,harness 工程师需要关心这些问题:
- 如何控制模型输入的历史上下文长度,避免长任务把上下文撑爆。
- 如何并发请求上千条评测用例而不会被服务商限流。
- 如何在模型生成的代码可能被执行的情况下做好安全隔离。
- 如何设计评分函数,让“格式正确但语义不对”的答案也能被准确识别。
- 如何把每次评测的完整输入、输出、中间日志留档,便于回溯。
这些工作的复杂度不亚于模型训练数据 pipeline。只是它通常藏在模型榜单背后,很少被前端观众看到。也正因为如此,当大家开始研究 ARC-AGI 成绩时,harness 内部的水位和设计边界才这么重要。
2.3 为什么 deepseek harness、codex harness 这些词会同时涌现
搜索热度里同时出现了 deepseek harness、codex harness、harness 插件、harness 桌面版等词,说明这类需求已经不只是大厂研究员的专属:大量个人开发者和中小企业也在尝试给开源模型搭建一套自己的评估与执行工具链。开源模型本地部署以后,用户非常需要一个“能喂数据、能调用工具、能回收结果统计分数”的外壳,而不是直接裸调 API。于是 harness 就成了这些工程实践的统称。
另一个背景是智能体(Agent)类应用的兴起。Agent 场景里模型天然需要读文件、执行代码、调接口,这些动作都需要一个执行框架来编排。如果没有完善的 harness,模型输出会直接裸露在系统里,不仅容易出错,还有安全风险。所以 harness 的需求量上升不是炒作,而是工程化的自然演进。
3. 为什么说 Harness 正在变成捆住模型的绳子
3.1 评测集一旦公开,记忆就会伪装成能力
一个非常现实的困境是:只要 benchmark 的题目在互联网上公开传播,预训练模型就可能在训练语料里“见过”这些题。ARC-AGI 的原始训练集早期是公开的,很多博客、论文和代码仓库都有完整题目,这给模型“背答案”创造了空间。Chollet 和团队在后来推出的间隔时间更久、未公开的 ARC 私有评测集上测试,就是因为普通的公开 data 已经无法评估真正的泛化能力了。
一旦评测集不再干净,harness 里写的提示词越贴近原始题目格式,模型就越容易从记忆里调出答案。这个时候分数涨了,但能力没有涨。类似情况在 MMLU、HumanEval 等老牌榜单上也出现过。这不是模型厂商恶意作弊,而是“公开数据被训练集吸收”导致的结构性问题。专业的 harness 应该把防泄漏当成第一原则,而不是把评测速度当成第一原则。
3.2 Prompt 特调:把推理题变成填空题
ARC 类的推理题,原始输入是一组带颜色的网格。模型如果要直接处理像素级输入,难度很高。很多 harness 会先把网格解析成一个文本描述,比如“3x3 矩阵,左上角是红色,右下角是蓝色”,然后用 few-shot 示例引导模型找出变换规律。这一步本身无可厚非,因为视觉到文本的转换往往能让模型表现更稳定。
问题在于,当提示词里的 few-shot 示例经过多轮手工挑选,已经覆盖了测试集中绝大部分规则类型时,模型要做的就不是“发现新规则”,而是从几个候选规则里“选一个匹配”。这种评测更像分类,而不像泛化。Harness 越好用,提示词约束越强,模型暴露的自由度就越低。久而久之,系统看起来是“完成了推理”,实际上是把推理过程提前固化到了 harness 工程里。
3.3 工具被写进能力项:模型没变,分数却提高了
另一个常见的“绳子效应”是把工具能力计入了模型能力。假设 ARC 评测 harness 允许模型调用一段 Python 代码,先去枚举颜色连通域、旋转翻转矩阵、统计形状数量,再基于这些统计特征去匹配规则。这时,模型只需要读代码输出,挑一个看起来合理的规则,就能大幅提升正确率。这在很多自动化评测里是允许的,因为任务目标只是“拿到正确答案”,不限制解题辅助工具。
但对评估模型通用推理能力这件事来说,这类工具已经替模型完成了相当一部分计算。更合理的做法是分开报告:一条曲线是模型裸推理成绩,另一条曲线是系统总成绩(含工具增强)。很多评测报告没有做这个拆分,读者误以为分数提升全部来自模型,这是评测设计和信息呈现共同造成的误导。
3.4 统计口径:一次通过不等于真的会了
LLM 推理本身有随机性,温度、采样种子、并发时的资源竞争都会影响结果。有些评测只跑一次,遇到一个高分随机种子就把结果发出来;有些评测在测试集上做多次采样,然后取最大值作为最终分数。“取最大值”在模型评估里是一个很危险的操作,因为它放大了好运气的权重,掩盖了普通水平。
更合理的统计口径包含了多次运行的平均分、方差、最高分、最低分,以及置信区间。如果没有这些统计信息,一个孤立的“通关分数”只能说明在某个随机状态下模型达到了某个水平,无法说明稳定表现。评测 harness 里如果少了统计模块,就像考试只公布一次成绩,却不说班级平均分和分数分布,参考价值非常低。
4. 自己动手搭一个轻量级模型评测 Harness
为了避免空谈概念,下面用一个完整的 Python 示例,演示如何搭建一个可复现、可统计、包含基本安全校验的模型评测 harness。示例以 ARC-AGI 格式的图形推理任务为背景,假设模型服务是 OpenAI 兼容协议,你需要将其中的模型名、base_url 替换成实际环境。
4.1 明确评测边界
这个 demo 的目标不是替代官方 ARC-AGI 评测,而是演示一套工程上怎么组织评测任务。设计上做几个约束:
- 每条评测任务包含若干示例对和测试输入。
- 模型输出必须是 JSON 格式的结果,便于校验。
- 如果模型输出无法解析,计入“解析失败”,而不是直接判错。
- 支持并发调用、超时与重试。
- 每次评测的所有中间结果都写入日志文件,方便回溯。
- 对模型生成的 Python 代码执行场景,默认不启用;如果启用,必须走白名单沙箱。
4.2 项目结构
基本的项目文件如下:
arc-harness-demo/ ├── config.yaml ├── run_evaluation.py ├── harness/ │ ├── __init__.py │ ├── loader.py │ ├── prompts.py │ ├── runner.py │ ├── validator.py │ └── logger.py ├── tasks/ │ └── arc_sample.json └── results/4.3 评测配置
配置文件使用 YAML 格式,集中管理模型、并发、超时和输出目录。这样做的好处是评测参数和代码逻辑分离,以后复跑只需要改配置,不用改代码。
# 文件路径:arc-harness-demo/config.yaml model: name: "your-model-name" # 替换成实际模型名 base_url: "https://api.example.com/v1" # 替换成模型服务地址 api_key_env: "MODEL_API_KEY" # 从环境变量读取密钥 generation: temperature: 0.2 # 推理任务温度建议调低 max_tokens: 2048 timeout_seconds: 120 execution: concurrency: 4 # 并发请求数 max_retries: 2 # 失败重试次数 retry_interval_seconds: 5 evaluation: allow_code_execution: false # 安全开关,默认不允许执行模型生成的代码 max_samples: 50 # 单次评测最大样本数,可限制开销 task_file: "tasks/arc_sample.json" output: result_dir: "results" save_each_result: true # 是否保存每一条输出4.4 任务数据与提示词模板
ARC 任务的本体是网格数据。出于演示简单,我把任务抽象成一个 JSON 文件:每条任务包含题目的输入输出示例,以及需要预测的测试输入。真实 ARC 评测中需要把网格转成文本描述,下面给一个简化版。
// 文件路径:arc-harness-demo/tasks/arc_sample.json [ { "id": "task_001", "examples": [ { "input": [[1, 0], [0, 1]], "output": [[1, 0], [0, 1]] }, { "input": [[0, 1], [1, 0]], "output": [[0, 1], [1, 0]] } ], "test_input": [[1, 1], [0, 0]] }, { "id": "task_002", "examples": [ { "input": [[2, 2, 0], [2, 2, 0], [0, 0, 0]], "output": [[2, 2], [2, 2]] }, { "input": [[3, 3, 3], [0, 0, 0], [0, 0, 0]], "output": [[3, 3, 3]] } ], "test_input": [[4, 0, 0], [4, 0, 0], [4, 0, 0]] } ]提示词模块负责把任务转成文本描述,并强化输出格式约束。这里会明确要求模型只输出 JSON,不要输出额外解释,以提高解析成功率。
# 文件路径:arc-harness-demo/harness/prompts.py import json SYSTEM_PROMPT = ( "You are an ARC reasoning assistant. " "You will be given a few input-output grid examples. " "Infer the underlying transformation rule and apply it to the test input. " "Return ONLY a JSON object in the format {\"prediction\": [[...]]}. " "Do not include any explanatory text." ) def build_user_prompt(examples, test_input): blocks = [] for idx, item in enumerate(examples, start=1): blocks.append(f"Example {idx} input:\n{json.dumps(item['input'])}") blocks.append(f"Example {idx} output:\n{json.dumps(item['output'])}") blocks.append(f"Test input:\n{json.dumps(test_input)}") return "\n\n".join(blocks) def build_messages(examples, test_input): return [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": build_user_prompt(examples, test_input)} ]从 prompt 设计能看出,few-shot 示例并没有手工挑选到足以覆盖所有规则的程度。在真实评测中,提示词越少越能说明模型本身的泛化能力;提示词越多,越接近“规则检索”而不是“规则发现”。
4.5 模型调用与重试
调用层选用通用的 ChatCompletion 结构。它支持 OpenAI 兼容协议的服务,也容易改成本地 vLLM 或 HuggingFace 接口。为了控制风险,API Key 从环境变量读取,不硬编码到代码里。
# 文件路径:arc-harness-demo/harness/runner.py import json import os import time from openai import OpenAI class ModelRunner: def __init__(self, config): api_key = os.getenv(config["model"]["api_key_env"]) self.client = OpenAI( api_key=api_key, base_url=config["model"]["base_url"] ) self.model_name = config["model"]["name"] self.temperature = config["generation"]["temperature"] self.max_tokens = config["generation"]["max_tokens"] self.timeout = config["generation"]["timeout_seconds"] self.max_retries = config["execution"]["max_retries"] self.retry_interval = config["execution"]["retry_interval_seconds"] def complete(self, messages): current_retry = 0 while True: try: response = self.client.chat.completions.create( model=self.model_name, messages=messages, temperature=self.temperature, max_tokens=self.max_tokens, timeout=self.timeout ) content = response.choices[0].message.content.strip() return {"ok": True, "content": content} except Exception as exc: current_retry += 1 if current_retry > self.max_retries: return {"ok": False, "error": str(exc)} time.sleep(self.retry_interval)需要强调的是,这里把异常吞掉并返回错误结果,是为了评测流程不因单个任务失败而中断。但在实际生产评测里,应该完整记录异常堆栈,便于后续定位模型服务或网络问题。
4.6 结果校验与安全边界
结果校验模块负责解析模型输出 JSON,并和标准答案比较。对于 ARC 这种任务,网格完全相等才算正确。如果模型生成了代码,我们默认不执行;只有在显式允许代码执行时才进入沙箱,并且会加一层白名单检查。
# 文件路径:arc-harness-demo/harness/validator.py import json def parse_json_response(content): try: data = json.loads(content) return data.get("prediction") except json.JSONDecodeError: return None def exact_grid_equal(prediction, expected): if not isinstance(prediction, list) or not isinstance(expected, list): return False return prediction == expected def validate(content, expected): prediction = parse_json_response(content) if prediction is None: return {"valid": False, "reason": "parse_error", "prediction": None} matched = exact_grid_equal(prediction, expected) return { "valid": matched, "reason": "correct" if matched else "wrong", "prediction": prediction }代码执行的安全模式不在本 demo 里展开,但原则必须说明:模型生成的代码默认不可信。如果评测任务需要模型写代码并执行,应当放到 Docker 容器或专用沙箱进程中,并严格控制内存、CPU、网络与文件系统权限。
4.7 主流程:并发执行与结果汇总
主脚本读取配置和任务,通过线程池控制并发度,逐个任务执行模型调用与结果校验,最后统计正确率、解析失败率、平均耗时,并把明细写入 results 目录。
# 文件路径:arc-harness-demo/run_evaluation.py import concurrent.futures import csv import datetime import json import os import time import yaml from openai import OpenAI from harness.prompts import build_messages from harness.runner import ModelRunner from harness.validator import validate def load_tasks(path, max_samples=50): with open(path, "r", encoding="utf-8") as f: tasks = json.load(f) return tasks[:max_samples] def evaluate_one(runner, task, save_path): messages = build_messages(task["examples"], task["test_input"]) expected = task.get("expected") or [] start = time.time() result = runner.complete(messages) latency = round(time.time() - start, 3) if not result["ok"]: record = { "task_id": task["id"], "status": "call_error", "reason": result["error"], "prediction": None, "expected": expected, "latency_seconds": latency } else: check = validate(result["content"], expected) record = { "task_id": task["id"], "status": "correct" if check["valid"] else check["reason"], "reason": check["reason"], "prediction": check["prediction"], "expected": expected, "latency_seconds": latency } with open(save_path, "w", encoding="utf-8") as f: json.dump(record, f, ensure_ascii=False, indent=2) return record def main(): with open("config.yaml", "r", encoding="utf-8") as f: config = yaml.safe_load(f) tasks = load_tasks( config["evaluation"]["task_file"], max_samples=config["evaluation"]["max_samples"] ) os.makedirs(config["output"]["result_dir"], exist_ok=True) stamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") summary = { "total": 0, "correct": 0, "wrong": 0, "parse_error": 0, "call_error": 0, "latency_summary": {} } latency_list = [] runner = ModelRunner(config) records = [] save_dir = os.path.join(config["output"]["result_dir"], stamp) os.makedirs(save_dir, exist_ok=True) with concurrent.futures.ThreadPoolExecutor( max_workers=config["execution"]["concurrency"] ) as pool: future_map = {} for task in tasks: save_path = os.path.join(save_dir, f"{task['id']}.json") future = pool.submit(evaluate_one, runner, task, save_path) future_map[future] = task for future in concurrent.futures.as_completed(future_map): record = future.result() records.append(record) latency_list.append(record["latency_seconds"]) for record in records: summary["total"] += 1 if record["status"] == "correct": summary["correct"] += 1 elif record["status"] == "wrong": summary["wrong"] += 1 elif record["status"] == "parse_error": summary["parse_error"] += 1 else: summary["call_error"] += 1 if latency_list: summary["latency_summary"] = { "avg_seconds": round(sum(latency_list) / len(latency_list), 3), "max_seconds": max(latency_list), "min_seconds": min(latency_list) } summary_path = os.path.join(save_dir, "summary.json") with open(summary_path, "w", encoding="utf-8") as f: json.dump(summary, f, ensure_ascii=False, indent=2) for key, value in summary.items(): print(f"{key}: {value}") if __name__ == "__main__": main()这个主流程相较企业级评测系统已经做了很多简化,但它包含了评测工程的核心要素:配置分离、并发控制、超时重试、结构化日志、结果汇总。把日志目录和时间戳用起来以后,任何一次评测结果都能回溯到当时的模型参数、任务文件和代码版本。
5. 常见问题与排查思路
自己搭评测 harness 或者复现别人评测结果时,经常会遇到一些“看起来分数不对”的情况。下面按问题现象整理了一份排查指引。
| 问题现象 | 常见原因 | 排查与解决思路 |
|---|---|---|
| 同样的任务,两次运行分数差异很大 | 采样温度过高、并发导致超时、任务顺序随机 | 把 temperature 调到 0 或 0.2,固定随机种子,多次运行取平均值 |
| 模型输出合法但解析失败 | 模型在 JSON 外追加了说明文字 | 在 prompt 中强化格式约束;解析时使用正则截取首个 JSON 块 |
| 部分任务一直超时 | 输入网格过大、prompt 太长、模型服务排队 | 缩小 max_tokens,检查服务端负载,适当提高超时阈值 |
| 正确率高于预期,但看不出模型理解规则 | 测试集被公开、提示词里包含答案线索、工具帮助完成推理 | 换私有测试集;把 few-shot 示例和工具调用结果从报告里单独记录 |
| 评分结果和官方结果对不上 | 评分规则不一致、答案比较方式不同 | 先在小样本上做人工核对,再扩展到完整测试集 |
| 并发过高导致 API 429 限流 | 请求频率超过服务限制 | 调整 concurrency,增加指数退避重试策略 |
在实际排查时,不要只盯着正确率。查看每一条记录里的 prediction 和 reason,能帮助你判断模型是“规则推错了”还是“格式没对上”。格式问题可以通过解析层修复,而规则推错则可能要回到 prompt 或模型选择层面解决。
6. 最佳实践:让 Harness 回到安全绳的位置
6.1 评测集隔离:不要让公开数据污染结果
可靠的评测首先要求测试集和训练数据严格分离。对于 ARC 这类推理基准,最好的情况是使用未公开的私有子集,其次是对公开测试集做形状变换、颜色替换、任务组合等扰动,降低背题的可能性。无论采用哪种方式,评测报告里都要说明数据集版本和处理方法,方便别人复核。
实际项目中,我建议至少保留两个评测集:一个面向快速迭代的冒烟集,大概几十条,跑得快;另一个面向正式结论的留空集,规模大、不外传、只在发版或月报时使用。这样既能保证开发效率,又不至于让公开的冒烟集污染最终结论。
6.2 消融实验:拆开模型能力和 harness 能力
一份有说服力的评测报告,应当包含能力归因分析。建议在同一批任务上运行四种配置:
- 模型裸跑,无工具、无 few-shot 特调。
- 模型 + 基础 few-shot。
- 模型 + few-shot + 工具辅助。
- 模型 + few-shot + 工具辅助 + 多次采样取最优。
把四种配置的分数放在一起对比,就能知道每个模块贡献了多少分。如果分数提升主要来自工具和采样策略,那结论不应该是“模型变强了”,而是“评测系统变强了”。在实际汇报模型能力时,应优先引用第一类或第二类配置的分数。
6.3 多次运行与置信区间
单次运行不能作为任何重要决策的依据。建议在预算允许的情况下至少跑 3 次到 5 次,报告平均分和标准差。如果两次评测之间的差距小于标准差,那基本可以判定为统计噪声,不能得出“新模型更好”的结论。对于模型对比类评测,甚至可以在同一批任务上做配对检验,看差异是否显著。
6.4 记录一切:可复现是评测的生命线
评测 harness 的输出不只是分数,还要包括以下内容:
- 评测代码版本(Git commit id)。
- 模型名称与版本号。
- prompt 模板全文。
- 模型参数(temperature、max_tokens、top_p)。
- 每条任务的输入、输出、中间日志。
- 工具调用的完整参数和返回结果。
- 数据集的 SHA256 校验码。
只有当这些信息完整保留时,后续才能复现分数、复现错误、定位问题。很多团队评测结果不可信,不是数据不对,而是记录不全,出了争议无法复盘。
6.5 安全边界:模型输出默认不可信
在评测 harness 里,只要允许模型生成的代码被执行,就必须默认模型输出不可信。建议做到以下几点:
- 代码执行放到独立容器或虚拟机中,禁止直接在主宿主机执行。
- 镜像尽量精简,不包含业务密钥和敏感数据。
- 设置 CPU、内存、磁盘配额,并限制网络访问。
- 超时强制执行,默认 30 到 60 秒就杀掉任务。
- 所有文件读写限制在临时目录,结束后自动清理。
即便不执行代码,凡是模型输出要写库、发请求、改文件的场景,都要经过一个明确的“人工确认或规则校验”步骤。这些边界控制是 harness 工程里最容易被忽略,但影响最大的部分。
6.6 警惕 Harness 过度工程化
最后想说一点看似反常识的建议:harness 不是越强大越好。评测 harness 的核心目标是“稳定、可复现、公平地反映被测对象”,而不是“尽力帮助模型得分”。当你发现评测框架里充满了规则枚举、答案校验、候选筛选、工具辅助时,要停下来确认:这些模块是该评测任务本身需要的,还是为了让模型分数好看而加的。
真正稳妥的工程实践是把两类 harness 分开:一类是“能力评测 harness”,尽量保持模型独立推理,减少外部干预;另一类是“产品应用 harness”,追求业务效果最大化,允许工具和工程充分辅助。两者可以共存,但在报告和讨论中不能混为一谈。否则,harness 就从帮助模型发挥能力的安全绳,变成了捆住模型、也捆住我们判断力的绳子。
7. 总结:关注能力,而不是分数
“Opus 5 通关 ARC-AGI-3”这类消息真正值得关注的地方,不在于一个孤立的分数,而在于它暴露了模型评测方法论仍然不成熟:我们很难快速判断一个高分来自模型推理能力、prompt 工程、工具执行,还是评估数据泄露。Harness 作为连接模型与评测目标的关键工程层,既可以是强大的安全绳,也可以是制造幻觉的定身绳。
结合 ARC-AGI 这个特殊场景,建议每一位对模型评估感兴趣的开发者,都去亲手搭一个哪怕很小的评测 harness,跑一遍自己的模型、自己的任务。你会很快发现:原来影响分数的变量比想象中多得多,原来同一个模型在不同 harness 下可以表现出完全不同的水平。这种体感比看任何榜单都更有价值。
请记住一个基本原则:分数是用来帮助决策的,不是用来制造结论的。评测报告里若缺少数据版本、代码版本、统计口径和消融实验,那么任何“通关”都只能算一次工程演示,而谈不上能力验证。把 harness 控制在正确的边界内,它才能继续扮演连接模型与真实世界的那根可靠绳索。