当 Claude Opus 5 在 ARC-AGI-3 上通关的消息传出后,AI 社区又掀起一轮关于“模型能力是否已经接近通用推理”的讨论。但真正跑过评估、做过模型评测的人会清楚:Benchmark 分数从来不是模型独力得出的,它是由“模型”和“测试脚手架”共同产出的。这里的“测试脚手架”就是 Harness。Harness 的原意是线束或测试夹具,在 AI 工程里,它指包裹在模型外层,负责输入输出、工具调用、状态管理、评分记录的整套代码和配置。
Harness 本应像安全带一样保护模型测试和部署过程,但在越来越多将模型封装进复杂环境的项目中,它正悄然变成一根“捆住模型的绳子”。尤其当模型本身变得更强以后,固定的 prompt 模板、先验的工具白名单、刚性的停止条件和脆弱的判分器,反而成为模型能力释放的天花板。这里不争论 Opus 5 的分数是否真实,也不去对比各家模型,而是围绕 ARC-AGI-3 场景,拆解 Harness 的工作原理、典型设计决策,以及它为什么会反过来限制模型。
1. ARC-AGI-3 和 Harness:先厘清这两个容易混淆的概念
1.1 ARC-AGI-3 到底在测什么
ARC-AGI(Abstraction and Reasoning Corpus for AGI)系列基准的目标是评估模型的抽象推理能力。它不像传统的问答或代码生成任务那样依赖大量知识记忆,而是通过一组组可拼图式的小任务,考察模型能否从少量示例中提取规则并推广到新形状、新空间关系和新变换上。
ARC-AGI-3 是该系列的进阶版本。从公开的信息来看,它增加了更多未见过的任务类型,减少示例数量,并要求模型在更复杂的环境中完成组合式推理。这类任务恰好是“查表式模型”最难应对的类型,因此也成为衡量模型是否具有真正泛化能力的重要试金石。
在实际评估中,模型不会直接看到一张“任务图”,而是通过 JSON 字符串接收任务描述、输入输出示例和当前测试输入。模型需要输出一个答案网格,然后由判分程序比较模型输出与标准答案是否一致。整个过程中,模型如何理解任务描述、如何组织推理、是否能够多步尝试,都由 Harness 的程序逻辑决定。
1.2 Harness 在 AI 工程里扮演什么角色
Harness 这个词在 AI 领域有几种常见用法:
- 评估型 Harness:负责把一个 benchmark 数据集分批发送给模型,收集输出,计算准确率或匹配度。
- 代理型 Harness:负责管理模型与外部工具之间的循环,例如调用代码解释器、搜索接口、文件系统等。
- 训练型 Harness:在强化学习或评估过程中,通过环境交互驱动策略更新。
在 ARC-AGI-3 这类评测场景中,Harness 通常指“评估型 Harness”。它可以很简单,也可以非常复杂。简单版本是:循环遍历任务,拼接 prompt,调用模型,得到输出,对比答案。复杂版本是:模型可以在多个步骤中调用工具,访问一个模拟世界,修改一个状态网格,再继续推理。
正因为 Harness 在“模型”和“结果”之间担任了中间层,它本身就携带了大量设计选择。比如同样是 Opus 5,不同评估团队可能用不同的工具集、不同的停止条件、不同的错误处理方式,最终分数可能相差很大。这不是模型能力变了,而是 Harness 变了。
1.3 为什么说“分数是模型和 Harness 共同决定的”
如果把模型比作一位棋手,Harness 就是棋手面前的棋盘、计时器和规则手册。棋手水平再高,如果规则手册不允许他使用某种合法的战术,或者计时器在关键时刻强制停止,他的比赛成绩就会低于真实水平。
更麻烦的是,Harness 中的很多选择不会写进论文或报告。比如同样一个任务,有的 Harness 允许模型输出失败后自动重试五次,有的 Harness 只允许一次;有的 Harness 会保留模型完整的多轮推理历史,有的则在每轮之间强制截断。这些差异对结果影响很大,但读者在排行榜上看到的只是一个最终分数。
因此,当讨论“Opus 5 通关 ARC-AGI-3”时,真正值得讨论的不仅是模型本身会不会推理,还包括它是在哪种 Harness 中推理的。如果 Harness 设计得好,模型的能力能够得到充分发挥;如果设计得不好,Harness 就会从测试工具变成限制模型发挥的“绳子”。
2. 一个典型评估 Harness 的解剖:从请求到评分经历了什么
2.1 Harness 的核心模块
一个可工作的评估 Harness 至少包含以下模块。这里的“模块”是指代码层面的组件,而非固定目录结构。
- 任务加载器:读取数据集的 JSON 文件,将任务拆分为单个 episode。
- Prompt 构建器:把任务描述、示例输入输出、当前输入、系统提示说明组合成模型输入。
- 模型客户端:负责调用模型接口,处理重试、超时、token 统计和异常。
- 输出解析器:将模型生成的文本或结构化输出转换为可比较的答案对象。
- 状态管理器:如果是多步推理,需要保存当前世界状态、工具调用结果、消息历史。
- 判分器:比较模型答案和期望答案,输出正确或错误,同时记录错误信息。
- 日志记录器:保存每个 episode 的 prompt、输出、中间步骤、耗时、token 消耗和评分结果。
下面是一份简化到只剩核心逻辑的 Python 示例。它展示的是单轮推理,不包含工具调用,但足够说明 Harness 的基本骨架。
import json from dataclasses import dataclass @dataclass class EpisodeResult: task_id: str correct: bool model_output: str expected: str reason: str class SimpleHarness: def __init__(self, model_client, prompt_builder, parser, judge): self.model_client = model_client self.prompt_builder = prompt_builder self.parser = parser self.judge = judge def run(self, dataset_path): with open(dataset_path, "r", encoding="utf-8") as f: tasks = json.load(f) results = [] for task in tasks: prompt = self.prompt_builder.build(task) raw_output = self.model_client.generate(prompt) parsed_answer = self.parser.parse(raw_output) correct, reason = self.judge(parsed_answer, task["answer"]) results.append(EpisodeResult( task_id=task["id"], correct=correct, model_output=raw_output, expected=task["answer"], reason=reason )) return results这段代码展示了评估 Harness 的最小闭环。prompt_builder负责把任务信息映射成模型输入;model_client负责外部通信;parser负责把自然语言转为结构化答案;judge负责判定正误。任何一步都可能引入偏差,后面会重点展开。
如果加入多步推理,Harness 还需要一个循环,通常长这样:
while step < max_steps: output = model_client.generate(current_prompt) action = parser.parse_action(output) if action.type == "final_answer": break observation = self.env.step(action) current_prompt = self.prompt_builder.append_observation( current_prompt, action, observation ) step += 1这里的max_steps、env.step的封装逻辑,以及append_observation的上下文策略,都直接决定模型能否完成复杂推理。
2.2 参数说明表
在配置一个评估 Harness 时,常见的参数及其影响如下表所示。
| 参数 | 含义 | 典型值 | 调大的影响 | 调小的影响 |
|---|---|---|---|---|
| max_steps | 允许模型执行的最大推理步数 | 5-20 | 模型有更多机会修正错误,但可能导致循环和成本上升 | 推理不够充分,容易提前失败 |
| max_tokens | 单次生成的最大 token 数 | 2048-8192 | 能容纳更长推理,但可能被无意义内容填满 | 长推理会被截断,答案不完整 |
| temperature | 采样温度 | 0-0.2 | 增加探索,结果不稳定 | 输出稳定,但可能缺乏多样性 |
| context_window 截断策略 | 保留历史消息的方式 | 最近 N 条 | 保留更多上下文,但容易超过窗口 | 上下文被裁剪,模型丢失关键信息 |
| retry_times | 模型调用失败后重试次数 | 2-3 | 减少网络抖动影响,但会拉长运行时间 | 偶发失败直接浪费正确样本 |
| timeout | 单次调用超时时间 | 60-300 秒 | 更宽容,但整体耗时增加 | 长思考过程被强制中断 |
| parse_strict | 是否严格要求结构化输出 | true/false | 解析成功率高,但可能要求模型严格遵循格式 | 允许自由格式,但解析失败率上升 |
实际项目里可以建一个harness_config.yaml来管理这些参数。不要把参数硬编码在代码中,否则换一个数据集或换一个模型时,无法快速对比。
harness: max_steps: 10 max_tokens: 4096 temperature: 0.0 retry_times: 3 timeout: 120 context_policy: "keep_all" judge_mode: "semantic" tools: - python_repl - file_search - calculator2.3 运行验证和预期输出
一个 Harness 完成后,不能直接拿大模型跑整个 benchmark。应该先用一个假模型验证全链路。假模型可以是一个固定返回特定 JSON 的服务,也可以是一个本地小模型。目的是确认:任务加载正确,prompt 构建符合预期,解析器能处理格式,判分器能给出正确对比结果。
预期输出类似下面这样:
Task abc123: correct Task abc124: wrong model_output: `[[1,2],[3,4]]` expected: `[[1,2],[3,5]]` reason: answer grid value mismatch at row 1, col 1 Accuracy: 0.50如果跑到这里发现parse抛异常,或者日志里缺少关键字段,说明 Harness 本身还没有达到可评估状态。此时不要急着上正式模型,先修 Harness。
3. 当 Harness 从支撑变成绳子:七种常见的“捆绑模式”
3.1 固定 Prompt 模板剥夺了模型的表达自由度
很多评估 Harness 会使用同一个 prompt 模板处理所有任务。模板内部只变动任务数据,不变化提示策略。这在早期模型能力较弱时是合理的,因为固定模板能减少方差,让比较更有意义。
但 ARC-AGI-3 这类任务本身就是高度多样化的。有的任务需要模型先识别网格对称性,有的需要理解颜色映射,有的需要按行列变换规则生成输出。如果 Harness 强行要求模型“必须逐步思考”,对某些任务而言反而会把模型引导到错误的思维路径。
更好的做法是支持分层 prompt:Harness 提供一个默认模板,同时允许为不同任务类别加载不同模板,或者允许模型在第一次尝试后,基于反馈重新形成自己的提示。把 prompt 设计的主导权从“代码”慢慢移交给“模型”。
3.2 白名单工具集限制了模型调用新能力
在多步推理 Harness 中,工具列表往往被写死。模型只能调用python_repl、calculator、web_search这些预设工具。如果模型在学习过程中产生了调用“表格可视化工具”或“形状变换推演器”的意图,Harness 会直接报错,模型只能被迫放弃。
这个问题的本质是:工具集是 Harness 定义的能力边界,不是模型定义的能力边界。当模型已经具备在更广工具集中选择策略的潜力,但 harness 不给它暴露工具入口,分数自然上不去。
一种缓解方案是工具注册表。Harness 通过注册表暴露工具,并支持在特定策略文件里显式启用或禁用。这样不同实验可以快速对比“有无某个工具”对结果的影响。同时,最好的 Harness 应该允许模型在受限环境中“申请”新工具,由人工审核后动态加入。
3.3 停止条件设置不当,模型没机会自我修正
多数 Harness 用max_steps或“输出中出现 final_answer 标记”作为停止条件。问题在于,这两者都可能过早切断推理。
例如,模型在第五步发现前面的推理有误,知道需要重新推导,但 Harness 的最大步数是 5。它只能要么给出一个错误答案,要么因为时间耗尽而返回空结果。另一个常见现象是模型在final_answerJSON 中写错了字段名,Harness 解析不到答案,于是直接判为失败,而不是给模型一次重新格式化的机会。
建议在设计 Harness 时,区分“正常停止”和“异常终止”。正常停止由模型主动发出 final 信号;异常终止则应该触发修复循环,比如要求模型重新输出结构化内容,而不是直接判负。对 ARC-AGI-3 这类推理任务,给模型 10 步以上的推理预算比 2-3 步更有价值,但要注意防止死循环。
3.4 判分器只认标准答案,不接受等价结果
ARC-AGI 的标准答案是一张输出网格。大多数情况下,网格必须精确匹配。但是,部分任务可能允许多个等价答案,例如旋转后的对称网格、颜色映射的不同解释、多个合理补全。如果判分器把所有“不一致”一律归为错误,模型的推理能力就被低估了。
好的判分器应当支持多层判断:先做严格匹配;若不匹配,再用规则判断是否等价;仍然不确定时,可以引入一个独立的 LLM judge 或人工抽样复核。同时也要记录 fail 原因,便于后续统计哪些任务存在多解歧义。
3.5 上下文裁剪把关键推理过程提前丢掉了
长任务中,模型可能已经在前面的步骤里推导出一个重要结论,并写在了对话历史中。如果 Harness 为了控制 token 成本,把旧消息截断,模型后续就无法访问先前结论。
很多框架默认只保留最近 N 轮消息。这在普通对话里没问题,但在需要多步推理、环境状态反馈的任务里,历史消息本身就是模型的“工作记忆”。建议不要简单截断,而是做摘要:每轮结束后,将已经完成的关键操作和结论压缩成一段摘要,代替删除历史。这样既节省 token,又不丢失信息。
3.6 固定采样参数让模型在“稳”和“活”之间被迫选择
为了评估的可复现性,多数 Harness 会把 temperature 设为 0。这对需要确定性输出的 benchmark 是合理的。但 ARC-AGI-3 中的抽象推理任务往往需要一定的探索和假设验证,temperature=0 可能让模型陷入高概率但不正确的解释中。
更合理的方法是:对简单任务使用低温度,对复杂任务或重试轮次使用稍高的温度,并对比多次采样的稳定性。Harness 可以记录每次采样的 seed 和 temperature,让下游分析知道哪些分数来自低温度、哪些来自高温度。
3.7 环境与并发机制导致评估结果不可复现
团队协作评估时,不同成员的开发机可能使用不同版本的依赖、不同模型接口环境,甚至不同初始化种子。同一个 Harness 在同一模型上可能得到不同的准确率,这不是模型变了,而是 Harness 的环境没有锁定。
这里的“绳子”更多来自工程层面:未冻结依赖、未记录模型版本、未统一随机种子、未持久化日志、并发运行时抢占资源导致超时。一个不稳定的 Harness 会让优秀模型背上“分数波动大”的标签。因此,评估结果必须附带完整的运行时指纹。
4. 从 DeepSeek Harness、Codex Harness 看 Harness 工程化趋势
4.1 为什么深度推理模型更需要 Harness
Opus 5 这类模型开始展现出更强的深度推理能力后,单次问答的评估方式已经不能反映真实水平。模型需要经历“观察-思考-工具调用-再观察”的长循环。
DeepSeek Harness、Codex Harness 这类社区工具的出现,说明了同一个趋势:把模型从“单次输出器”改造成“多步决策器”,需要一个独立的、可编程的执行环境。这个执行环境不仅要能调用模型,还要能处理代码执行结果、文件读写、环境状态变化和批量任务调度。
换句话说,Harness 已经从“测试工具”升级为“模型的外围操作系统”。模型负责思考,Harness 负责落地。如果落地的接口设计得不好,思考再强也无处发挥。
4.2 评估型 Harness 与生产型 Harness 的差异
理解 Harness 的用途边界非常重要。同一个 Harness 不能同时适配评估和生产,因为目标不同。
| 维度 | 评估型 Harness | 生产型 Harness |
|---|---|---|
| 目标 | 获得可复现的模型能力度量 | 稳定服务真实业务请求 |
| 容错策略 | 失败重试、记录错误、继续下一个样本 | 失败熔断、降级、人工转接 |
| 判分 | 离线判分,可接受歧义分析 | 在线反馈,必须定义明确成功指标 |
| 日志 | 完整记录每个样本的所有信息 | 只保留必要业务日志和追踪链路 |
| 上下文策略 | 可支持长历史和多轮摘要 | 严格控制延迟和 token 成本 |
| 工具权限 | 允许沙箱内任意工具调用 | 严格白名单和审计 |
| 可复现性 | 依赖版本和 seed 必须锁定 | 更关注高可用和滚动发布 |
如果在生产系统里照搬评估型 Harness 的随机重试逻辑,可能会导致延迟和成本不可控;反之,如果评估时使用生产型的超时和熔断策略,又可能低估模型能力。落地时应先确定 Harness 的用途,再选择对应的参数。
4.3 现有 Harness 的共同短板
从社区中常见的 Harness 实现来看,主要短板集中在四个方面:
- 对模型输出格式假设过强。很多 Harness 只解析
{"action": "...", "args": {...}}这类严格 JSON。如果模型输出带有自然语言解释,解析器就会失败。 - 对工具的封装不透明。工具失败的原因被 Harness 吞掉,模型只看到通用报错,找不到修复线索。
- 对并发评估的资源隔离不足。多个进程同时调用模型接口,容易触发限流或超时。
- 缺少对任务难度的动态识别。所有任务使用相同步数和相同工具,导致简单任务浪费计算,复杂任务不够算。
这些短板叠加起来,就是“Harness 正在变成捆住模型的绳子”的工程注脚。不是模型不想发挥,而是外围框架没有给模型足够的空间。
5. 设计一套“不捆住模型”的 Harness:关键原则与示例配置
5.1 原则一:Harness 只负责“提供可能性”,不要替模型做决定
Harness 的职责是让模型的每一个合理动作都能被执行,而不是预设“模型应该怎么思考”。评估 Harness 时,可以用一个简单的判断标准:如果某条推理路径是模型自己想出来的,但它因为 harness 不支持而无法执行,这就是 Harness 在捆绑模型。
具体实现上,Harness 应该提供:
- 开放的动作空间,而不是固定枚举的动作。
- 可配置的上下文保留和摘要策略。
- 对模型输出解析失败时的自动修复机制。
- 在每个失败点输出足够的信息,让开发者能判断问题在模型还是 Harness。
例如,当模型输出一个 JSON 但字段名写错时,不要直接返回失败,而是把错误信息回传给模型,请它修正后再重试。此类循环称为“格式修复循环”。
5.2 原则二:每一步都要可观测、可回放、可比较
不可观测的 Harness 无法排查问题。每个 episode 必须保存完整轨迹,包括原始 prompt、模型原始输出、解析后的动作、环境返回的 observation、每一步耗时、token 数、最终判分依据。
建议日志格式采用 JSONL,每行一个事件,方便使用 jq、Python 脚本或日志平台分析。示例:
{"event": "prompt_built", "task_id": "abc123", "prompt_length": 2048, "template_version": "v3"} {"event": "model_output", "task_id": "abc123", "step": 1, "raw_output": "...", "tokens": 1500} {"event": "tool_call", "task_id": "abc123", "step": 1, "tool": "python_repl", "args": "..."} {"event": "judge_finish", "task_id": "abc123", "correct": true, "reason": "grid_match"}有了这样的事件流,才能回答最核心的问题:某个任务失败,是模型想错了,还是 Harness 没接住。
5.3 一个更灵活的 Harness 配置示例
下面给出一个更符合“不要捆住模型”思路的配置片段。它支持分层 prompt、动态工具注册、格式修复循环和语义判分:
harness: name: flexible-arc-eval model: client: "anthropic" model_name: "claude-opus-5" temperature: initial: 0.0 retry: 0.7 max_tokens: 8192 prompt: default_template: "prompts/default.txt" per_task_prompt_script: "prompts/adapt.py" include_examples: true max_message_history: 100 steps: max_steps: 12 stop_on_final_answer: true invalid_json_retries: 2 on_timeout: "regenerate" tools: registry_file: "tools/registry.yaml" allow_dynamic_tools: true judge: mode: "hybrid" exact_match: true semantic_judge: enabled: true model: "claude-opus-5" threshold: 0.8 human_sample_ratio: 0.1 logging: path: "logs/run_{timestamp}.jsonl" record_raw_prompt: true record_tool_outputs: true这里的关键在于:
per_task_prompt_script允许每个任务在默认模板基础上做个性化调整。invalid_json_retries给模型修复格式问题的机会。on_timeout: "regenerate"让超时后的重试成为一种可选策略,而不是立即判负。semantic_judge.enabled表示在严格匹配失败后,还会通过语义判断确认是否存在等价答案。human_sample_ratio引入人工抽检,防止自动判分器系统性误判。
5.4 如何验证 Harness 本身没有引入偏差
一个 Harness 在正式用于评估前,需要做三组验证。
第一组是“空跑验证”:用 mock 模型跑 20-50 个样本,确认加载、解析、判分、日志链路没有异常。
第二组是“回归对比”:用同一模型、同一数据集跑两次 Harness,结果应完全一致。如果两次结果不同,说明 Harness 在并发、随机种子或依赖版本上有不稳定因素。
第三组是“能力下限对照”:用一个人畜无害的 baseline 模型跑同一 Harness。如果 baseline 模型分数低,说明 Harness 没有过度奖励某种输出格式;如果 baseline 分数异常高,说明 Harness 可能存在评分漏洞。
这三组验证的价值在于,把“模型能力”和“Harness 质量”分开看,避免把 Harness 的 bug 当成模型的缺陷。
6. 实际使用中的常见故障检查清单
6.1 现象:模型输出被截断,分数偏低
很多模型在生成长篇推理时会触发max_tokens截断,尤其当 Harness 设置了过小的单次生成上限。检查方式是在日志中查finish_reason。如果大量length而不是stop,说明 token 预算不足。
解决办法:提高max_tokens;引导模型先输出结论、后给出解释,或者使用“结论前置”的 prompt;对长任务启用“分段输出”协议,让模型分多次提交结果,而不是一次写完全部内容。